Machine Learning › Deep learning เบื้องต้น
Neuron และ MLP
ประมาณ 30 นาที
เป้าหมายของบท
- คำนวณ neuron และ layer ด้วยมือ (weighted sum + activation) ด้วย numpy
- เทรน MLPClassifier, นับพารามิเตอร์ และอธิบายว่าทำไมต้อง scale
- เข้าใจว่าโมเดลใหญ่ไม่ได้ดีกว่าเสมอเมื่อข้อมูลน้อยและสัญญาณอ่อน
Neuron หนึ่งตัวทำอะไร
Neural network ประกอบด้วยหน่วยเล็กๆ ที่ทำสามอย่าง:
- รวมแบบถ่วงน้ำหนัก: z = w₁x₁ + w₂x₂ + … + b (เหมือน logistic/linear regression)
- ส่งผ่านฟังก์ชัน activation: a = f(z) เพื่อให้เกิดความไม่เป็นเส้นตรง
- ส่ง a ไปเป็น input ของหน่วยถัดไป
import numpy as np
x = np.array([1.0, 2.0, 1.0])
w = np.array([0.5, -0.25, 0.8])
b = 0.1
z = w @ x + b # 0.9
relu = max(0.0, z) # 0.9 (ReLU: ค่าลบกลายเป็น 0)
sigmoid = 1 / (1 + np.exp(-z)) # ≈ 0.7109 (บีบให้อยู่ระหว่าง 0–1)
ถ้าไม่มี activation ที่ไม่เป็นเส้นตรง การซ้อนหลายชั้นก็เท่ากับโมเดลเชิงเส้นชั้นเดียว (คูณเมทริกซ์ต่อกันได้เมทริกซ์เดียว)
หลาย neuron = layer; หลาย layer = MLP
หนึ่ง layer คือ neuron หลายตัวที่รับ input ชุดเดียวกัน ในรูปเมทริกซ์: Z = W @ x + b เมื่อ W มี 1 แถวต่อ neuron
MLP (multi-layer perceptron) ซ้อน layer: input → hidden layer (ReLU) → … → output (sigmoid สำหรับ 2 คลาส)
from sklearn.neural_network import MLPClassifier
mlp = MLPClassifier(hidden_layer_sizes=(16,), max_iter=200, random_state=42)
mlp.fit(Z_train, y_train)
[c.shape for c in mlp.coefs_] # [(21, 16), (16, 1)]
ข้อมูลของเรามี 21 feature, hidden 16 หน่วย, output 1 หน่วย ⇒ จำนวนพารามิเตอร์ = (21×16 + 16) + (16×1 + 1) = 369 ตัว (เทียบกับ logistic regression ที่มีเพียง 22) — พารามิเตอร์เยอะขึ้นมาก แต่ข้อมูลเทรนมีแค่ 2,129 แถวที่ signal อ่อน
ทำไมต้อง scale ก่อนเข้า neural network
Feature ที่ช่วงกว้างมาก (เช่น days_since_first_order 3–728) เทียบกับ dummy 0/1 ทำให้การอัปเดตน้ำหนักถูกครอบงำโดย feature ตัวใหญ่ การ scale ด้วย StandardScaler (fit บน train เท่านั้น) ทำให้ optimizer ทำงานได้ราบรื่น
ผลจริงในข้อมูลนี้ (hidden 16, max_iter=200):
| iteration ที่ใช้ | train acc | test acc | |
|---|---|---|---|
| ไม่ scale | 57 (หยุดเพราะ loss ไม่ลดพอ) | 0.6318 | 0.6141 |
| scale | 200 (ยังไม่หยุด) | 0.7027 | 0.5986 |
การ scale ทำให้เทรนได้ลึกขึ้นมาก (train 0.6318 → 0.7027) แต่ test ไม่ได้ดีขึ้น — ซ้ำยังต่ำกว่า logistic regression (AUC 0.5746 เทียบ 0.625) เพราะสัญญาณมีน้อย โมเดลใหญ่จึงจำ noise neural network ไม่ใช่ยาวิเศษ โดยเฉพาะกับข้อมูลตารางขนาดเล็ก
ตัวอย่างแก้โจทย์
กด "ขั้นต่อไป" ทีละขั้น ลองรันและแก้โค้ดในแต่ละขั้นได้
neuron ด้วยมือ (numpy)
คำนวณ weighted sum และ activation เอง
z = w·x + b แล้วส่งผ่าน ReLU และ sigmoid: z = 0.9, ReLU = 0.9, sigmoid ≈ 0.7109
MLPClassifier และการนับพารามิเตอร์
เทรน MLP เล็กๆ บนข้อมูล scale แล้ว
fit MLP hidden (16,) แล้วดูรูปร่างของน้ำหนัก: [(21, 16), (16, 1)] รวม 369 พารามิเตอร์
เติมช่องว่าง
แทน ___ ด้วยโค้ดที่ถูกต้อง แล้วกดตรวจคำตอบ
fit MLP ที่มี hidden layer เดียว 8 หน่วย (max_iter=200, random_state=42) บน Z_train แล้วเก็บจำนวนพารามิเตอร์ทั้งหมดไว้ในตัวแปร n_params เติม ___ ให้ครบ
แบบฝึกท้ายบท
บทนี้จะนับว่าเสร็จเมื่อผ่านข้อพื้นฐานและประยุกต์ครบ ข้อท้าทายได้ดาวเพิ่ม
🟢 พื้นฐาน
คำนวณ weighted sum ของ neuron ที่ให้ไว้ (z = w · x + b) ปัด 4 ตำแหน่ง เก็บไว้ในตัวแปร z
ส่ง z ผ่าน ReLU และ sigmoid (1 / (1 + e^−z)) ปัด 4 ตำแหน่ง เก็บไว้ในตัวแปร relu_out, sigmoid_out
layer ที่มี 2 neuron (W, b_vec ที่ให้ไว้): คำนวณ Z = W @ x + b_vec แล้วผ่าน ReLU เก็บผลรวมของเอาต์พุตทั้งสอง (ปัด 4 ตำแหน่ง) ไว้ในตัวแปร layer_sum
fit MLPClassifier(hidden_layer_sizes=(16,), max_iter=200, random_state=42) บน Z_train แล้วนับจำนวนพารามิเตอร์ทั้งหมด (น้ำหนัก + bias) เก็บไว้ในตัวแปร n_params
🟡 ประยุกต์
MLP เดิม (hidden (16,), max_iter=200, random_state=42): accuracy บน train และ test (ปัด 4 ตำแหน่ง) เก็บไว้ในตัวแปร train_acc, test_acc
เปรียบเทียบ hidden (4,) กับ (16, 8) (max_iter=200, random_state=42): จำนวนพารามิเตอร์ของแต่ละแบบ เก็บไว้ใน params_4, params_16_8 และ accuracy บนชุดทดสอบ (ปัด 4 ตำแหน่ง) ไว้ใน acc_4, acc_16_8
เทียบการ scale: fit MLP (16,) (max_iter=200, random_state=42) บน X_train ดิบ และบน Z_train — เก็บจำนวน iteration ที่ใช้ (n_iter_) ไว้ใน iters_raw, iters_scaled และ train accuracy (ปัด 4 ตำแหน่ง) ไว้ใน train_raw, train_scaled
🔴 ท้าทาย
fit MLP (4,) (max_iter=200, random_state=42) บน Z_train แล้วคำนวณ forward pass ด้วยมือสำหรับแถวแรกของชุดทดสอบ: hidden = ReLU(z @ coefs_[0] + intercepts_[0]), out = sigmoid(hidden @ coefs_[1] + intercepts_[1]) เก็บความน่าจะเป็นที่คำนวณเอง (ปัด 4 ตำแหน่ง) ไว้ใน manual_proba และค่าจาก predict_proba ของ sklearn ไว้ใน sklearn_proba
เทียบ ROC-AUC บนชุดทดสอบของ MLP (8,) (max_iter=200, random_state=42) กับ LogisticRegression(max_iter=2000) บนข้อมูลที่ scale แล้วเหมือนกัน ปัด 4 ตำแหน่ง เก็บไว้ในตัวแปร mlp_auc, logreg_auc
Quiz
สรุป
- neuron = weighted sum + bias แล้วผ่าน activation; layer = neuron หลายตัว; MLP = layer ซ้อนกัน
- พารามิเตอร์ของ MLP (21→16→1) = 369 ตัว; ต้อง scale input และ activation ไม่เป็นเส้นตรงจึงจะได้ประโยชน์จากความลึก
- ในข้อมูลของเรา MLP train 0.7027 / test 0.5986 (AUC 0.5746) ไม่ชนะ logistic regression — โมเดลใหญ่ต้องการข้อมูลและสัญญาณมากกว่า
คำถามสัมภาษณ์
อธิบาย MLP สั้นๆ ว่าคำนวณอย่างไร
input คูณน้ำหนักของ layer แรกบวก bias แล้วผ่าน activation (เช่น ReLU) ได้ค่า hidden จากนั้นทำซ้ำกับ layer ถัดไปจนถึง output ซึ่ง sigmoid/softmax แปลงเป็นความน่าจะเป็น ทั้งหมดเป็นการคูณเมทริกซ์และฟังก์ชันไม่เชิงเส้นซ้อนกัน พารามิเตอร์ (น้ำหนักและ bias) ถูกเรียนรู้ด้วย gradient descent
ทำไม neural network ถึงต้อง scale feature
การอัปเดตน้ำหนักด้วย gradient ขึ้นกับขนาดของ input feature ที่ช่วงกว้างจะทำให้ loss surface บิดเบี้ยว ทำให้ optimizer ต้องใช้ learning rate เล็กหรือติดขัด การ standardize (fit บน train เท่านั้น) ทำให้ทุก feature อยู่ในสเกลใกล้กันและการเทรนเสถียรขึ้น