- Backpropagation เป็นหัวใจสำคัญของการฝึกโครงข่ายประสาทเทียม แต่หากไม่เข้าใจกลไกการทำงานภายใน ก็อาจเกิดข้อผิดพลาดที่ไม่คาดคิดได้ เพราะมันเป็นโครงสร้างแบบ "leaky abstraction"
- ฟังก์ชันกระตุ้น sigmoid และ tanh หากกำหนดค่าน้ำหนักตั้งต้นไม่เหมาะสม อาจทำให้เกิดปัญหา vanishing gradient จนการฝึกหยุดชะงัก
- ReLU อาจก่อให้เกิดปรากฏการณ์ dead ReLU ที่นิวรอนถูกปิดใช้งานถาวรเมื่ออินพุตมีค่าไม่เกิน 0
- ใน RNN การคูณเมทริกซ์ซ้ำ ๆ ทำให้เกิด exploding gradient ได้ และมักต้องป้องกันด้วย gradient clipping หรือใช้ LSTM
- หากไม่เข้าใจหลักการทำงานของ backpropagation ต่อให้เฟรมเวิร์กจัดการให้อัตโนมัติ ความสามารถในการ debug และปรับปรุงโมเดลก็จะลดลงอย่างมาก
ความจำเป็นของการเข้าใจ backpropagation
- ใน วิชา CS231n ของ Stanford มีการออกแบบการบ้านให้นักศึกษาต้อง ลงมือเขียน forward pass และ backpropagation เอง
- นักศึกษาบางส่วนบ่นว่าไม่จำเป็น เพราะเฟรมเวิร์กอย่าง TensorFlow สามารถคำนวณ backpropagation ให้อัตโนมัติได้
- แต่ backpropagation นั้น ไม่ใช่ abstraction ที่สมบูรณ์แบบ แต่เป็น leaky abstraction ดังนั้นหากไม่รู้การทำงานภายใน ก็ยากที่จะวิเคราะห์สาเหตุที่การฝึกล้มเหลว
- ท่าทีแบบ “เฟรมเวิร์กจัดการให้เอง” เพียงอย่างเดียว จะนำไปสู่ การลดลงของความสามารถในการออกแบบโมเดลและการ debug
vanishing gradient ใน sigmoid
- ฟังก์ชันไม่เชิงเส้นอย่าง sigmoid และ tanh เมื่อค่าอินพุตมีขนาดใหญ่ เอาต์พุตจะเข้าใกล้ 0 หรือ 1 จนเกิดภาวะ saturation
- ในจุดนี้ local gradient อย่าง z(1-z)* จะกลายเป็น 0 ทำให้ระหว่าง backpropagation การส่งต่อ gradient ถูกตัดขาด
- gradient สูงสุดของ sigmoid คือ 0.25 ดังนั้นทุกครั้งที่ผ่านชั้นหนึ่ง สัญญาณจะ ลดลงเหลือไม่เกิน 1/4
- ผลลัพธ์คือการเรียนรู้ของชั้นล่างจะ ช้ากว่าชั้นบนอย่างชัดเจน
- ดังนั้นเมื่อใช้ชั้น sigmoid จึงต้องระวังเป็นพิเศษเรื่อง การกำหนดค่าน้ำหนักตั้งต้นและการเตรียมข้อมูลล่วงหน้า
ปัญหา dead ReLU
- ReLU เป็นฟังก์ชันที่ทำให้เอาต์พุตเป็น 0 เมื่ออินพุตมีค่าไม่เกิน 0
- หากเอาต์พุตของนิวรอนเป็น 0 ใน forward pass ระหว่าง backpropagation gradient ก็จะเป็น 0 ด้วย ทำให้นิวรอนนั้น ถูกปิดใช้งานถาวร
- ระหว่างการฝึก การอัปเดตค่าน้ำหนักครั้งใหญ่หรือ learning rate ที่สูงเกินไป อาจทำให้นิวรอนติดอยู่ในสถานะ "ตาย" ได้
- หลังการฝึก บางกรณีอาจพบว่านิวรอนจำนวนมากในทั้งระบบให้ค่าเอาต์พุตเป็น 0
- ดังนั้นเมื่อใช้ ReLU เรื่อง การปรับ learning rate และ กลยุทธ์การกำหนดค่าเริ่มต้น จึงสำคัญมาก
exploding gradient ใน RNN
- ใน RNN แบบง่าย เมทริกซ์ hidden state (Whh) เดิมจะถูกคูณซ้ำในทุก time step
- ระหว่าง backpropagation gradient จะลู่เข้าเป็น 0 หรือพุ่งไม่จำกัด ขึ้นอยู่กับขนาดของ eigenvalue ของเมทริกซ์นี้
- หาก |b| < 1 จะเกิด vanishing gradient และหาก |b| > 1 จะเกิด exploding gradient
- วิธีป้องกันที่ใช้กันทั่วไปคือใช้ gradient clipping หรือเลือกสถาปัตยกรรม LSTM
กรณี clipping ที่ผิดพลาดในโค้ด DQN
- มีการพบโค้ดใน DQN implementation ที่ใช้ TensorFlow ซึ่งทำการ clip delta (ความคลาดเคลื่อนของ Q) โดยตรงด้วย
tf.clip_by_value
- วิธีนี้ทำให้เมื่อ delta หลุดออกนอกช่วง gradient จะกลายเป็น 0 และการฝึกหยุดลง
- สิ่งที่ตั้งใจจริง ๆ คือ gradient clipping ดังนั้นจึงควรใช้ Huber loss แทน
- ในโค้ดตัวอย่างมีการใช้
tf.square และ tf.abs แบบมีเงื่อนไขเพื่อสร้าง Huber loss
- ปัญหานี้ถูกรายงานเป็น GitHub issue และ ได้รับการแก้ไขทันที
บทสรุป
- Backpropagation ไม่ใช่แค่เครื่องมืออัตโนมัติ แต่เป็นระบบ credit assignment ที่ก่อให้เกิดผลลัพธ์ซับซ้อน
- หากไม่เข้าใจการทำงานภายใน ก็จะเผชิญกับ ความไม่เสถียรของโมเดล, การฝึกล้มเหลว, และข้อจำกัดในการ debug
- Backpropagation ไม่ได้ยากในเชิงคณิตศาสตร์มากนัก และสามารถเรียนรู้อย่างเป็นรูปธรรมได้ผ่าน วิชาและการบ้าน CS231n
- เมื่อเข้าใจ backpropagation แล้ว ความสามารถในการ ออกแบบโครงข่ายประสาทเทียมและแก้ปัญหา จะดีขึ้นอย่างมาก
- สิ่งสำคัญคือไม่ควรพึ่งพา automatic differentiation ของเฟรมเวิร์กเพียงอย่างเดียว แต่ควร เข้าใจการไหลจริงของ backpropagation
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ดูเหมือนว่า backpropagation กำลังถูกมองในแง่ลบแบบไม่ยุติธรรมอยู่ตรงนี้
จริง ๆ แล้วประเด็นนี้พูดถึงว่า gradient และตัวแปรดัดแปลงของ gradient descent เป็น abstraction ที่ไม่สมบูรณ์แค่ไหนในกระบวนการ optimization มากกว่าจะพูดถึง backpropagation เอง
backprop เป็นเพียงอัลกอริทึมสำหรับคำนวณอนุพันธ์ของฟังก์ชันประกอบเท่านั้น และปัญหาอย่าง gradient หายไปที่เกิดจากการซ้อน sigmoid หลายชั้นก็ไม่ใช่ปัญหาของ backprop แต่เป็นคุณสมบัติของตัวฟังก์ชันเอง
เหตุผลที่ให้คนลงมือเขียน backward pass เอง ก็เพื่อให้ได้สัมผัสโดยตรงว่าเลขยกกำลังทำงานอย่างไรผ่านการ คำนวณอนุพันธ์ ด้วยตัวเอง
ประเด็นสำคัญคือ ในบางสถานการณ์ เราไม่สามารถทำ abstraction รายละเอียดของ backprop ออกไปได้ทั้งหมด (รวมถึงการคำนวณ gradient)
โดยเฉพาะเมื่อใช้ gradient descent ส่วนอัลกอริทึม optimization แบบ global อื่น ๆ อาจมีปัญหาน้อยกว่า
เพราะในความเป็นจริงตอนนี้ วิธีเดียวในการคำนวณ gradient สำหรับ deep learning ก็คือ backprop ดังนั้นการรั่วของ abstraction นี้จึงเป็นเรื่องที่เกิดขึ้นจริง
ผมเคารพผลงานของ Karpathy แต่บทความและการบรรยายของเขามัก ทำให้เส้นแบ่งของแนวคิดไม่ชัดเจน จนก่อให้เกิดความเข้าใจผิด
ด้วยระดับของเขา คนย่อมคาดหวังความแม่นยำที่สูงกว่านี้
ผลงานด้านการสอน deep learning ของ Karpathy นั้นมหาศาลจริง ๆ
ตั้งแต่บทความสั้น ๆ ไปจนถึง บทความคลาสสิกเรื่อง RNN รวมถึงคอร์สบน YouTube และโปรเจกต์บน GitHub ล้วนยอดเยี่ยม
nanochat ที่เพิ่งเปิดตัวล่าสุดก็เป็นตัวอย่างที่ดีมาก เพราะตัวอย่างที่เล็ก ชัดเจน และครบถ้วนแบบนี้ช่วยผู้เรียนได้มาก
ทีหลังถึงได้รู้ว่าพวกเขาสนใจ การเชื่อและนำ LLM ไปใช้ มากกว่าการทำความเข้าใจมัน
จริง ๆ แล้วพวกเขาสนใจ การถกเถียงเชิงเพ้อฝัน อย่าง “สังคมที่เครื่องจักรอัจฉริยะทำทุกอย่าง” มากกว่ากลไกการทำงานของ LLM เสียอีก
แทนที่จะ просто “ไปถาม ChatGPT” เขากลับค้นหาด้วยตัวเอง อ่านโค้ด และเจอบั๊ก
แนวทางแบบสำรวจค้นคว้า นี้แหละคือการเรียนรู้อย่างแท้จริง
ตอนเรียนปริญญาโท ผมเคยทำ งานที่ต้อง implement backprop ด้วยตัวเอง โดยอิงจากเปเปอร์
เป็นการเขียนทั้ง forward และ backward pass ด้วยปฏิบัติการทางคณิตศาสตร์ล้วน ๆ และมันเป็นประสบการณ์การเรียนรู้ที่ดีที่สุดของปีนั้นเลย
ปกติเราไม่ค่อยได้ทำอะไรแบบนี้ด้วยตัวเอง แต่ถ้าถูกบังคับให้ทำ มันช่วยได้มหาศาล
ผมยังทำ UI เพื่อให้เห็นด้วยว่าน้ำหนักและ bias เปลี่ยนไปอย่างไรระหว่างการเรียนรู้
ผมเคยสงสัยเรื่อง ความสัมพันธ์ระหว่าง backprop กับ optimizer
SGD แค่ขยับไปตามทิศทางของ gradient ตรง ๆ แต่ optimizer ขั้นสูงอย่าง Adam ไม่ได้ใช้ gradient แบบตรง ๆ เพราะมีการทำ normalization, momentum, clipping ฯลฯ
ถ้าอย่างนั้น เราจำเป็นต้องคำนวณ gradient ให้แม่นยำจริง ๆ ไหม หรือแค่รู้ทิศทางคร่าว ๆ ก็พอ?
มีงานวิจัยที่เกี่ยวข้องอย่าง เปเปอร์เรื่อง SGD noise, งานวิจัยด้านการมองภาพ เป็นต้น
แต่การกะทิศทางแบบหยาบ ๆ นั้นอันตราย เพราะ curvature (Hessian) ของ loss function เปลี่ยนแปลงอย่างรวดเร็ว
จริง ๆ แล้ว stochastic gradient descent ก็เกิดจากแนวคิดนี้ และต่อยอดไปถึงแนวทางอย่าง Direct Feedback Alignment ด้วย
สรุปความสัมพันธ์ระหว่าง optimization กับ reinforcement learning ของ Ben Recht ก็ชวนสนใจเช่นกัน: ลิงก์
สิ่งสำคัญไม่ใช่ค่าของ loss function แต่คือ รูปร่างของ gradient และ curvature
optimizer อย่าง Adam จะปรับ gradient โดยประมาณ ความไวต่อสเกล ของแต่ละพารามิเตอร์ด้วยการประมาณอันดับหนึ่ง
การ optimization ลำดับสูงกว่านั้นทำไม่ได้กับฟังก์ชันไม่เชิงเส้นอย่าง ReLU
แต่มันคือมาตรการจำเป็นเพื่อแก้ปัญหา vanishing gradient
ในปริภูมิหลายมิติ ความคลาดเคลื่อนเพียงเล็กน้อยก็ส่งผลใหญ่ได้ ดังนั้นการคำนวณ gradient ให้แม่นยำจึงสำคัญมาก
ปัญหาคือจะคำนวณ “ทิศทางคร่าว ๆ” อย่างไรตั้งแต่แรก
แม้แต่ optimizer ขั้นสูงอย่าง AdamW ก็ยังใช้ gradient เป็นแกนหลักอยู่ดี
ช่วงราวปี 2016 ดูเหมือนว่าจะมีการใช้ทริกอย่าง gradient clipping บ่อยกว่ามาก
ตัวอย่างเช่น ในเปเปอร์ปี 2013 ของ Alex Graves เรื่อง Sequence Generation with RNNs ก็ระบุว่าใช้ clipping เพื่อป้องกัน gradient ระเบิดใน LSTM
ผมเองก็รู้สึกถึงความสำคัญของ backprop มากจนถึงขั้นไปเรียน คอร์สเฉพาะทางที่สอน PyTorch autograd เลย
ตอนนี้ framework ต่าง ๆ รองรับ clipping แล้ว และความเข้าใจเรื่อง ปัญหาระหว่างการเทรน ก็สูงขึ้น
แต่ไม่ได้แปลว่าปัญหานี้หายไปแล้ว — ReLU และ GELU ก็ยังเป็น activation function พื้นฐานอยู่ และการฝึก LLM ก็ยังใกล้เคียงกับ ‘ศาสตร์มืด’ อยู่ดี
Smol Training Playbook ของ Hugging Face คือหลักฐานของเรื่องนั้น
ในระยะยาว ผมคิดว่าการฝึก โมเดลที่ทนทานต่อความหลากหลายของ activation function อาจมีประโยชน์กว่า
ตัวอย่างเช่น ถ้าสลับใช้ ReLU, Swish, GELU แบบสุ่มระหว่างการฝึก ก็อาจได้ ผลแบบ regularization คล้าย dropout
แบบนี้ตอน inference ก็อาจสลับไปใช้ฟังก์ชันที่คำนวณถูกที่สุดแทนได้ด้วย
ชื่อเดิม “Yes you should understand backprop” ชัดเจนและดีกว่ามาก
ถ้าปล่อยให้โค้ดทำแทนมากเกินไป ก็หลงคิดได้ง่ายว่า ‘มันทำงานได้อย่างกับเวทมนตร์’
ตอนเรียนบัณฑิตศึกษาผมเคยคำนวณ convolution และ backprop ด้วยมือตรง ๆ ซึ่งช่วยได้มาก
คำถามที่ว่า “ในเมื่อ framework คำนวณ backward pass ให้อัตโนมัติ แล้วเราจะเขียนเองไปทำไม?”
ฟังดูน่ากังวลเพราะใช้ตรรกะเดียวกับ “มีเครื่องคิดเลขแล้วจะเรียนการบวกไปทำไม?”
เช่นเดียวกับที่การเข้าใจ compiler, อัลกอริทึมการ sort หรือการทำงานของทรานซิสเตอร์มีประโยชน์ การเรียน backprop ก็มีคุณค่า
แต่เพราะ เวลาในการเรียนมีจำกัด ประเด็นสำคัญคือการเรียนสิ่งนี้คุ้มกว่าหัวข้ออื่นหรือไม่
backprop คุ้มค่าแก่การเรียนเพราะถ้าไม่เข้าใจกลไกภายใน ก็ยากจะสังเกตเห็น โหมดความล้มเหลวที่ซ่อนอยู่
ผมเองก็เคย implement มาหลายครั้ง และมันเป็นระดับความซับซ้อนที่เหมาะมากสำหรับใช้ประเมินภาษาใหม่
ตอนเริ่มเรียน deep learning ใหม่ ๆ backprop ดูเหมือน เวทมนตร์ มาก
แต่พอลอง implement เองก็พบว่ามันเป็นเพียงลำดับของการคำนวณธรรมดา ๆ และนั่นทำให้ผมมั่นใจขึ้นมากเวลา debug หรือหาสาเหตุที่ loss ไม่ขยับ
ถ้าใครกำลังเรียน deep learning ผมแนะนำให้ ลองเขียนเองด้วยมือสักครั้ง
ก็มีมุมมองตรงข้ามอยู่เหมือนกัน
ผมไม่คิดว่านักศึกษาจำเป็นต้อง implement backprop ด้วย NumPy เสมอไป
ปัญหา abstraction leak ของ BackProp นั้นนักวิจัยจะเป็นคนแก้ด้วย optimizer แบบใหม่ ส่วนฝั่งนักพัฒนาก็แค่หา hyperparameter ที่ดีให้เจอ
ปัญหาส่วนหนึ่งเกิดจาก การออกแบบโมเดลหรือ training loop
เช่น gradient clipping ไม่ได้เป็นค่าเริ่มต้นใน framework ส่วนใหญ่
บทความนี้มุ่งไปที่ ผู้อ่านสายวิจัยหรือมุมมองเชิงวิชาการ
ถ้าไม่เข้าใจพฤติกรรม gradient ของฟังก์ชันอย่าง Sigmoid หรือ ReLU ก็แก้ปัญหา gradient ระเบิดหรือหายไป ไม่ได้
ถ้าจะสร้างสถาปัตยกรรมโมเดลแบบใหม่ ก็ต้องเข้าใจว่า backprop ทำงานอย่างไร ไม่อย่างนั้นการเทรนอาจล้มเหลวหรือประสิทธิภาพตกลง
เราต้องเจาะ abstraction ลงไปก่อน จึงจะเริ่มมองเห็น สิ่งที่เราไม่รู้ว่าเราไม่รู้ (unknown unknowns) ได้