nn.Moduleปัญหาที่แยกได้ด้วยเส้นตรง (linearly separable)
เช่น AND, OR gates
- XOR: (0,0)->0, (0,1)->1, (1,0)->1, (1,1)->0
- ไม่มีเส้นตรงเดียวที่แยก class 0 กับ class 1 ได้
- Perceptron เรียนรู้ไม่ได้ ไม่ว่าจะฝึกกี่ครั้ง
[ความจริง] Minsky & Papert พิสูจน์ในปี 1969 ว่า perceptron มีข้อจำกัดนี้ ทำให้ AI winter นานหลายปี
x1 x2 | y 0 0 | 0 0 1 | 1 1 0 | 1 1 1 | 0
x2
1 | . O
|
0 | O .
--------
0 1 x1
O = class 0, . = class 1
- ไม่มีเส้นตรงเดียวที่แยก O กับ . ได้
- ต้องใช้เส้นโค้ง หรือหลายเส้น
- Perceptron มีแค่ 1 layer = แยกไม่ได้
- วิธีแก้: เพิ่ม hidden layer = Multi-Layer Perceptron (MLP)
Input(2) -> Linear(2,1) -> Output(1) w1*x1 + w2*x2 + b = y_hat
แยก XOR ไม่ได้
Input(2) -> Linear(2,4) -> ReLU
-> Linear(4,1) -> Output(1)
ซ่อน layer ระหว่าง input กับ output
- Hidden layer ทำหน้าที่ แปลงข้อมูล ให้แยกได้ด้วยเส้นตรง
- ReLU ทำให้ network ไม่ใช่แค่การแปลงเชิงเส้น
- ยิ่งมี hidden layers มาก = ยิ่งเรียนรู้รูปแบบซับซ้อนได้
- นี่คือจุดเริ่มต้นของ "deep" learning
import torch
import torch.nn as nn
class MyMLP(nn.Module):
def __init__(self):
super().__init__()
self.layer1 = nn.Linear(2, 4)
self.layer2 = nn.Linear(4, 1)
self.relu = nn.ReLU()
self.sigmoid = nn.Sigmoid()
def forward(self, x):
x = self.relu(self.layer1(x))
x = self.sigmoid(self.layer2(x))
return x
model = MyMLP()
print(model)
print(sum(p.numel() for p in model.parameters()))
- สืบทอดจาก nn.Module
- __init__: กำหนด layers ทั้งหมด
- forward(x): กำหนดว่าข้อมูลไหลอย่างไร
- ห้ามเรียก model.forward(x) โดยตรง! ใช้ model(x)
[เคล็ดลับ] nn.Module จัดการ autograd, device, state_dict ให้อัตโนมัติ
# วิธีสั้น: nn.Sequential
model = nn.Sequential(
nn.Linear(2, 4),
nn.ReLU(),
nn.Linear(4, 1),
nn.Sigmoid()
)
print(model)
# Sequential(
# (0): Linear(2, 4)
# (1): ReLU()
# (2): Linear(4, 1)
# (3): Sigmoid()
# )
# ใช้เหมือนกัน
output = model(x)
- nn.Sequential จัดเรียง layers ให้อัตโนมัติ
- ข้อมูลไหลตามลำดับ: layer 0 -> 1 -> 2 -> ...
- เหมาะกับ model ที่ไม่ซับซ้อน
- ถ้าต้องการ skip connections หรือ branching ใช้ nn.Module แทน
[เคล็ดลับ] เริ่มจาก Sequential แล้วย้ายไป nn.Module เมื่อ model ซับซ้อนขึ้น
import torch
import torch.nn as nn
# Data
X = torch.tensor([[0,0],[0,1],[1,0],[1,1]],
dtype=torch.float32)
y = torch.tensor([[0],[1],[1],[0]],
dtype=torch.float32)
# Model
model = nn.Sequential(
nn.Linear(2, 8), # hidden layer ขนาด 8
nn.ReLU(),
nn.Linear(8, 1),
nn.Sigmoid()
)
# Loss + Optimizer
criterion = nn.BCELoss()
optimizer = torch.optim.Adam(
model.parameters(), lr=0.01)
# Training loop
for epoch in range(1000):
y_hat = model(X)
loss = criterion(y_hat, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch+1) % 200 == 0:
print(f"Epoch {epoch+1}: loss={loss.item():.4f}")
# Test
with torch.no_grad():
pred = model(X)
print((pred > 0.5).float())
- ซ่อน layer ขนาด 8 neurons แก้ XOR ได้!
- BCELoss: binary cross-entropy สำหรับ binary output
- optim.Adam: optimizer ที่ปรับ learning rate อัตโนมัติ
- Output: [[0],[1],[1],[0]] = XOR ถูกต้อง!
# 1. Forward pass y_hat = model(X) # 2. Compute loss loss = criterion(y_hat, y) # 3. Zero gradients optimizer.zero_grad() # 4. Backward pass loss.backward() # 5. Update weights optimizer.step()
Step 1 - Forward: ใส่ข้อมูลเข้า model ได้ output
Step 2 - Loss: คำนวณว่าผิดมากแค่ไหน
Step 3 - Zero grad: ล้าง gradient เก่า (ไม่ล้างจะสะสม!)
Step 4 - Backward: autograd คำนวณ gradient อัตโนมัติ
Step 5 - Update: ปรับ weights ด้วย gradient
[สำคัญ] ลำดับนี้ต้องถูกต้องเสมอ: forward -> loss -> zero_grad -> backward -> step
# SGD: พื้นฐาน, ช้าแต่เสถียร
optimizer = torch.optim.SGD(
model.parameters(), lr=0.01)
# Adam: ปรับ lr อัตโนมัติ, นิยมใช้
optimizer = torch.optim.Adam(
model.parameters(), lr=0.001)
# AdamW: Adam + weight decay
optimizer = torch.optim.AdamW(
model.parameters(), lr=0.001,
weight_decay=0.01)
# ดู learning rate ปัจจุบัน
for param_group in optimizer.param_groups:
print(param_group['lr'])
CrossEntropyLoss = LogSoftmax + NLLLoss (ใส่ raw logits ไม่ต้อง softmax ก่อน!)
import torch
import torch.nn as nn
# Data
X = torch.tensor([[0,0],[0,1],[1,0],[1,1]], dtype=torch.float32)
y = torch.tensor([[0],[1],[1],[0]], dtype=torch.float32)
# Model
model = nn.Sequential(
nn.Linear(2, 8),
nn.ReLU(),
nn.Linear(8, 1),
nn.Sigmoid()
)
# Loss + Optimizer
criterion = nn.BCELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
# Training
for epoch in range(1001):
y_hat = model(X)
loss = criterion(y_hat, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if epoch % 200 == 0:
print(f"Epoch {epoch:4d} | loss: {loss.item():.4f}")
# Evaluate
with torch.no_grad():
pred = model(X)
print("\nPredictions:")
for i in range(4):
x1, x2 = X[i].tolist()
p = pred[i].item()
print(f" ({int(x1)},{int(x2)}) -> {p:.4f} -> {int(p > 0.5)}")
# (0,0) -> 0.0012 -> 0
# (0,1) -> 0.9987 -> 1
# (1,0) -> 0.9989 -> 1
# (1,1) -> 0.0014 -> 0
uv add django
uv run django-admin startproject wk10 .
uv run manage.py startapp dashboard
# wk10/urls.py
from django.urls import path, include
urlpatterns = [
path('', include('dashboard.urls')),
]
# dashboard/views.py
import json, torch, torch.nn as nn
from django.http import StreamingHttpResponse
def train(request):
def event_stream():
X = torch.tensor([[0,0],[0,1],
[1,0],[1,1]], dtype=torch.float32)
y = torch.tensor([[0],[1],[1],[0]],
dtype=torch.float32)
model = nn.Sequential(
nn.Linear(2, 8), nn.ReLU(),
nn.Linear(8, 1), nn.Sigmoid())
criterion = nn.BCELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.05)
for epoch in range(201):
loss = criterion(model(X), y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
with torch.no_grad():
pred = model(X).squeeze().tolist()
yield f"data: {json.dumps({'epoch': epoch, 'loss': loss.item(), 'pred': pred})}\n\n"
return StreamingHttpResponse(event_stream(), content_type='text/event-stream')
- Django: web framework สำหรับ backend
- SSE (Server-Sent Events): server ส่งข้อมูล real-time ให้ client ผ่าน EventSource
- StreamingHttpResponse: ส่ง training update ทีละ epoch ผ่าน SSE
- Frontend ใช้ Canvas แสดง XOR scatter plot
[โครงสร้าง] manage.py -> settings.py -> urls.py -> views.py -> templates/
# ฝึกสอน: ต้องการ gradient
for epoch in range(1000):
y_hat = model(X) # forward
loss = criterion(y_hat, y)
optimizer.zero_grad()
loss.backward() # คำนวณ gradient
optimizer.step() # อัปเดต
# ประเมิน: ไม่ต้องการ gradient
with torch.no_grad(): # ปิด gradient
y_hat = model(X)
accuracy = (y_hat > 0.5).float().eq(y).mean()
print(f"Accuracy: {accuracy:.4f}")
# model.eval() / model.train()
model.eval() # ประเมิน mode
y_hat = model(X)
model.train() # ฝึก mode
y_hat = model(X)
- torch.no_grad(): ปิดการคำนวณ gradient = เร็วขึ้น, ประหยัด memory
- ใช้ตอน evaluate, predict, ไม่ใช่ตอน train
- model.eval(): เปลี่ยนเป็น evaluation mode
- model.train(): เปลี่ยนกลับเป็น training mode
[สำคัญ] eval() สำคัญสำหรับ Dropout/BatchNorm ที่ทำงานต่างกันระหว่าง train/eval
# บันทึก weights (แนะนำ)
torch.save(model.state_dict(),
'model_weights.pth')
# โหลด weights
model = MyMLP() # สร้าง model ก่อน
model.load_state_dict(
torch.load('model_weights.pth'))
model.eval()
# บันทึก model ทั้งหมด
torch.save(model, 'model_full.pth')
# โหลด model ทั้งหมด
model = torch.load('model_full.pth')
- state_dict(): dictionary ของ weights ทั้งหมด
- แนะนำบันทึกแบบ state_dict (ยืดหยุ่นกว่า)
- บันทึก: .pth หรือ .pt
- โหลด: ต้องสร้าง model ก่อน แล้ว load_state_dict()
[สำคัญ] ต้องสร้าง model architecture ก่อนโหลด weights!
Hidden layers คือสิ่งที่ทำให้ "deep" learning เป็น "deep"
state_dict()สัปดาห์หน้า: Dataset/DataLoader และ classification จริงด้วย PyTorch