จาก ep ที่แล้วที่เราเล่าถึงคอนเซ็ปต์ของ SGD ไป ใน ep นี้เราจะมาดูตัวอย่างโค้ดแบบง่ายที่สุด ซับซ้อนน้อยที่สุด ซึ่งเป็นพื้นฐานสำคัญของ Machine Learning แบบ Neural Network คือ Linear Regression ด้วยอัลกอริทึม Stochastic Gradient Descent (SGD)

แต่ในการหา Slope นั้นเราไม่ต้อง Diff เอง แต่เราจะใช้ความสามารถ ของ Pytorch เรียกว่า Autograd หา Gredient ของ Parameter ทุกตัวให้โดยอัตโนมัติ

โมเดลที่เราใช้ครั้งนี้จะเป็น Linear Equation แบบง่าย ๆ ไม่มี Deep Learning หลายชั้น มี Parameter แค่ 2 ตัวเท่านั้น คือ Slop และ จุดตัดแกน y ไม่มี Activation Function ใด ๆ ทั้งสั้น

Loss Function ที่เราใช้ในเคสนี้คือ Mean Squared Error

Illustration of least squares fitting. The data (red dots) are at co-ordinates (1,6), (2,5), (3,7) and (4,10). A linear approximation is obtained using least-squares estimation (blue line).  Credit: https://commons.wikimedia.org/wiki/File:Linear_least_squares_example2.svg
Illustration of least squares fitting. The data (red dots) are at co-ordinates (1,6), (2,5), (3,7) and (4,10). A linear approximation is obtained using least-squares estimation (blue line). Credit: https://commons.wikimedia.org/wiki/File:Linear_least_squares_example2.svg

เรามาเริ่มกันเลย

Open In Colab

เราจะสร้าง Machine Learning เรียนรู้จากข้อมูล x, y ที่ให้มา เพื่อทำ Linear Regression สร้างฟังก์ชันขึ้นมาใหม่ ที่สามารถทำงานสร้าง Output ได้ใกล้เคียงข้อมูลตัวอย่าง โดยใช้วิธี Stochastic Gradient Descent (SGD) Optimization

0. Import Library

Import Library ที่จะจำเป็น

In [1]:
%matplotlib inline
from fastai.basics import *

1. Generate x

สร้างข้อมูล x เป็นเลข 0 ถึง 29 เอาไว้เป็นข้อมูล input ให้ฟังก์ชัน จะได้เอาไว้พล็อตกราฟดู

  • Column แรก เป็นเลข 0 - 29 เอาไว้คุณกับความชัน (x1)
  • Column ที่สองเป็น เลข 1, 1, 1, ... อย่างเดียว เอาไว้คุณกับจุดตัด จะได้คงค่าจุดตัดไว้เหมือนเดิม (x0 เป็น 1 เสมอ)

ลองดู x ใน 10 แถวแรก

In [2]:
n = 30
x = torch.ones(n, 2)
x[:,0] = torch.arange(n, out=torch.FloatTensor())
x
Out[2]:
tensor([[ 0.,  1.],
        [ 1.,  1.],
        [ 2.,  1.],
        [ 3.,  1.],
        [ 4.,  1.],
        [ 5.,  1.],
        [ 6.,  1.],
        [ 7.,  1.],
        [ 8.,  1.],
        [ 9.,  1.],
        [10.,  1.],
        [11.,  1.],
        [12.,  1.],
        [13.,  1.],
        [14.,  1.],
        [15.,  1.],
        [16.,  1.],
        [17.,  1.],
        [18.,  1.],
        [19.,  1.],
        [20.,  1.],
        [21.,  1.],
        [22.,  1.],
        [23.,  1.],
        [24.,  1.],
        [25.,  1.],
        [26.,  1.],
        [27.,  1.],
        [28.,  1.],
        [29.,  1.]])

2. สมมติโจทย์

สมมติ โจทย์ คือ สมการเส้นตรง y = mx + b โดยที่ m = 2 และ b = 5 เราใส่ค่า x ตั้งแต่ 0-29 จะได้ค่า y ดังนี้

In [3]:
y = (2. * x[:, 0]) + 5.
y
Out[3]:
tensor([ 5.,  7.,  9., 11., 13., 15., 17., 19., 21., 23., 25., 27., 29., 31.,
        33., 35., 37., 39., 41., 43., 45., 47., 49., 51., 53., 55., 57., 59.,
        61., 63.])

นำมาพล็อตกราฟ จะได้กราฟเส้นตรง ความชัน เท่ากับ 2 จุดตัดแกน y เท่ากับ 5

In [4]:
plt.scatter(x[:,0], y)
Out[4]:
<matplotlib.collections.PathCollection at 0x7f311c60aba8>

2.1 เพิ่ม Noise

เพื่อให้เหมือนข้อมูลในโลกความเป็นจริง เราจะใส่ noise เข้าไปในข้อมูล จะได้เพิ่มความยากขึ้นอีกนิดนึง โดย noise จะอยู่ในช่วง -5 ถึง 5

In [5]:
noise = (torch.rand_like(y) * 10.) - 5.
noise
Out[5]:
tensor([ 2.8612, -3.2135,  0.2997, -1.5954, -3.3458, -3.4270,  4.6956, -4.7680,
         4.8474, -4.6814,  2.1791, -3.2981,  0.4119, -4.6041, -4.5301,  1.2258,
        -0.6476,  3.7096, -3.0807, -3.0260,  2.9252, -1.9989,  1.0456,  3.5734,
         4.5677,  3.3817,  2.1774,  4.5590, -1.8750, -0.9189])

นำ noise มาใส่ใน y

In [6]:
y = y + noise
y
Out[6]:
tensor([ 7.8612,  3.7865,  9.2997,  9.4046,  9.6542, 11.5730, 21.6956, 14.2320,
        25.8474, 18.3186, 27.1791, 23.7019, 29.4119, 26.3959, 28.4699, 36.2258,
        36.3524, 42.7096, 37.9193, 39.9740, 47.9252, 45.0011, 50.0456, 54.5734,
        57.5677, 58.3817, 59.1774, 63.5590, 59.1250, 62.0811])

พล็อตกราฟดูจะเห็นว่า ข้อมูลมีความเหมือนจริงมากขึ้น แต่ก็ยังดูเป็นเส้นตรงอยู่

In [7]:
plt.scatter(x[:,0], y)
Out[7]:
<matplotlib.collections.PathCollection at 0x7f311c52ab38>

3. Loss Function

เรามากำหนด loss function ด้วยฟังก์ชัน Mean Squared Error คือการนำ yhat (y hat) ที่เราคำนวนได้ และ y ของจริง มาลบกัน แล้วยกกำลังสอง เพื่อให้เครื่องหมายลบหายไป และหาค่าเฉลี่ยของทั้งหมด

In [8]:
def mse(yhat, y): 
    return ((y-yhat)**2).mean()

4. Linear Regression

เราจะสร้างฟังก์ชันเส้นตรงขึ้นมา 1 ฟัง์ชัน เพื่อให้ทำงานนี้ y = mx + b โดยสุ่มค่าเริ่มต้น m, b ขึ้นแล้ว แล้วค่อย ๆ ปรับไปทีละนิด เรียกว่า Linear Regression

4.1 Initialization

สมมติว่าเราไม่รู้โจทย์ ไม่รู้มาก่อนว่า ค่า m และ b คืออะไร เราเห็นแต่ข้อมูล x และ y เราจะสมมติให้ค่าเริ่มต้นของโมเดลของเราเป็น m = -2 และ b = 6

In [9]:
a = tensor([-2., 6.])

5. yhat

นำมาคูณกับ x ออกมาเป็น yhat

In [10]:
yhat = x @ a
yhat
Out[10]:
tensor([  6.,   4.,   2.,   0.,  -2.,  -4.,  -6.,  -8., -10., -12., -14., -16.,
        -18., -20., -22., -24., -26., -28., -30., -32., -34., -36., -38., -40.,
        -42., -44., -46., -48., -50., -52.])

แล้วนำมาพล็อตกราฟเทียบกัน ระหว่าง y และ yhat

In [11]:
fig,ax = plt.subplots(figsize=(9, 9))
ax.scatter(x[:,0], y, label="y")
ax.plot(x[:,0], yhat, label="yhat", color='red')
ax.legend(loc='upper right')
Out[11]:
<matplotlib.legend.Legend at 0x7f311c4a4198>

นำ yhat และ y มาคำนวน loss ด้วย mse

In [12]:
loss = mse(yhat, y)
loss
Out[12]:
tensor(4510.4062)

6. Training Loop

ประกาศให้ a เป็น Parameter คือจะให้ระบบคำนวน หา Gradient ให้อัตโนมัติ requires_grad=True

In [13]:
a = torch.nn.Parameter(a)
a
Out[13]:
Parameter containing:
tensor([-2.,  6.], requires_grad=True)

กำหนดฟังก์ชัน สำหรับคำนวน yhat, loss แล้วอัพเดท a ด้วย การลบด้วย Gradient (a.grad) * Learning Rate (lr) เรียกว่า Stochastic Gradient Descent (SGD)

print loss, ค่า a, และ ค่า a.grad

In [14]:
def update():
    yhat = x @ a
    loss = mse(yhat, y)
    loss.backward()
    with torch.no_grad():
        if i % 10 == 0: print(f"loss={loss}, a={a.data}, a.grad={a.grad}")
        a.sub_(lr * a.grad)        
        a.grad.zero_()

7. เทรนไป 100 Epoch

กำหนดค่า lr แล้ววน Loop เทรน ให้อัพเดท a ไปเรื่อย ๆ โดยดูข้อมูล x, y ทั้งหมด 100 รอบ เรียกว่า 100 Epoch

In [15]:
lr = 1e-4
for i in range(100):
    update()
loss=4510.40625, a=tensor([-2.,  6.]), a.grad=tensor([-2265.3533,  -113.8300])
loss=1397.366943359375, a=tensor([-0.2370,  6.0883]), a.grad=tensor([-1257.2770,   -62.5255])
loss=438.5127868652344, a=tensor([0.7415, 6.1366]), a.grad=tensor([-697.8066,  -34.0525])
loss=143.17311096191406, a=tensor([1.2846, 6.1628]), a.grad=tensor([-387.3073,  -18.2506])
loss=52.20352554321289, a=tensor([1.5861, 6.1767]), a.grad=tensor([-214.9839,   -9.4811])
loss=24.182355880737305, a=tensor([1.7534, 6.1837]), a.grad=tensor([-119.3464,   -4.6145])
loss=15.550036430358887, a=tensor([1.8463, 6.1870]), a.grad=tensor([-66.2687,  -1.9139])
loss=12.889720916748047, a=tensor([1.8979, 6.1882]), a.grad=tensor([-36.8113,  -0.4155])
loss=12.068842887878418, a=tensor([1.9265, 6.1882]), a.grad=tensor([-20.4627,   0.4158])
loss=11.814542770385742, a=tensor([1.9425, 6.1875]), a.grad=tensor([-11.3895,   0.8768])

สังเกต loss ลดลงอย่างรวดเร็ว ค่า a ก็เข้าใกล้ โจทย์

8. Inference

นำโมเดลที่เราเทรนได้ มาใช้จริง ด้วยการคำนวน yhat จาก a ที่เราเทรนได้

In [16]:
yhat = x@a

พล็อตกราฟเปรียบเทียบ หลังเทรนเรียบร้อยแล้ว

In [17]:
fig,ax = plt.subplots(figsize=(9, 9))
ax.scatter(x[:,0], y, label="y")
ax.plot(x[:,0], yhat, label="yhat", color='red')
ax.legend(loc='upper right')
Out[17]:
<matplotlib.legend.Legend at 0x7f311c459e10>

เราจะได้ค่า a ดังนี้ ใกล้เคียงกับโจทย์ โดยที่เราเรียนรู้จากข้อมูล x, y เท่านั้น

In [18]:
a
Out[18]:
Parameter containing:
tensor([1.9514, 6.1865], requires_grad=True)

9. สรุป

  1. นี่เป็นตัวอย่างแบบง่ายที่สุด ซับซ้อนน้อยที่สุด เราสามารถ เพิ่ม Parameter จาก 2 ตัวเป็นร้อย พัน ไปจนถึง หลายล้านตัว สร้างฟังก์ชันที่ซับซ้อนมากขึ้นได้
  2. ในการอัพเดท Parameter มีอัลกอริทึมที่จะช่วยให้ Converge เร็วขึ้นได้อีกมากกว่า แค่ SGD ตรง ๆ
  3. เวลาเราเทรนข้อมูลจริง ๆ มักจะแบ่งข้อมูล ป้อนให้โมเดล ทีละ mini-batch ไม่ได้ป้อนทีเดียวหมดแบบนี้

10. Credit

In [ ]:
 

แชร์ให้เพื่อน:

Keng Surapong on FacebookKeng Surapong on GithubKeng Surapong on Linkedin
Keng Surapong
Project Manager at Bua Labs
The ultimate test of your knowledge is your capacity to convey it to another.

Published by Keng Surapong

The ultimate test of your knowledge is your capacity to convey it to another.

Enable Notifications    OK No thanks