φυβλαςのβλογ
บล็อกของ phyblas



โครงข่ายประสาทเทียมเบื้องต้น บทที่ ๑: เพอร์เซปตรอนชั้นเดียว
เขียนเมื่อ 2018/08/26 23:20
แก้ไขล่าสุด 2022/07/10 21:14
ในบทนำได้แนะนำแนวคิดและภาพรวมทั่วไปเกี่ยวกับโครงข่ายประสาทเทียมไปแล้ว

สำหรับในส่วนนี้จะเริ่มเข้าสู่เนื้อหาหลักที่มีการคำนวณ และวิธีการเขียนโปรแกรมภาษาไพธอนขึ้นเพื่อใช้งานจริง

โดยจะเริ่มแนะนำจากโครงสร้างที่เรียบง่ายที่สุดอย่างเพอร์เซปตรอนชั้นเดียว แล้วจึงต่อยอดไปสู่เพอร์เซปตรอนหลายชั้น แล้วปิดท้ายด้วยโครงข่ายประสาทแบบคอนโวลูชัน (CNN)



ความรู้พื้นฐานที่ควรจะมีคือ

- เวกเตอร์: เข้าใจวิธีบวกลบและด็อตเวกเตอร์

- เมทริกซ์: เข้าใจวิธีบวกลบคูณเมทริกซ์

- พื้นฐานแคลคูลัส: เข้าใจวิธีหาอนุพันธ์ โดยเฉพาะอนุพันธ์ย่อย ส่วนปริพันธ์ไม่ต้องใช้จึงไม่จำเป็น

- พื้นฐานไพธอน: อ่านบทเรียนไพธอนพื้นฐานอย่างน้อยถึงบทที่ ๒๒ ใน https://phyblas.hinaboshi.com/saraban/python

- พื้นฐาน numpy: อ่านบทที่ ๒,๓,๔,๑๕,๒๗,๒๑,๒๓ ใน https://phyblas.hinaboshi.com/saraban/numpy_matplotlib

เพียงแต่โค้ดตรงไหนที่ใช้คำสั่งที่เข้าใจยากจะมีการอธิบายละเอียดอีกที ดังนั้นรู้แค่คร่าวๆไว้ก็อาจเพียงพอ

นอกจากนี้ในนี้มีการวาดภาพแสดงผลด้วย matplotlib แต่เนื่องจากไม่ใช่จุดสำคัญที่ต้องเน้นจึงจะแค่ลงโค้ดไว้ ถ้าใครเข้าใจ matplotlib ก็จะดีกว่า แต่ไม่ได้ขาดไม่ได้

การเขียนจะใช้ไพธอน มอดูลที่ต้องการมีเพียง numpy และ matplotlib ไม่ได้ใช้อย่างอื่นเลย

โค้ดถูกเขียนเพื่อใช้สำหรับไพธอน 3 เป็นหลัก แต่ก็ใช้ในไพธอน 2 ได้เช่นกัน



เพอร์เซปตรอน

เพอร์เซปตรอน (感知器, perceptron) ถือเป็นจุดเริ่มต้นของโครงข่ายประสาทเทียมในยุคแรกสุด ถูกคิดค้นขึ้นในปี 1957

โครงสร้างมีลักษณะง่ายๆคือเป็นเซลล์ประสาทจำลองเซลล์หนึ่งที่รับสัญญาณจำนวนหนึ่งมาจากชั้นขาเข้าซึ่งอาจมีหลายช่องทาง เซลล์จะทำการพิจารณาว่าสัญญาณทั้งหมดรวมแล้วเกินค่าค่าหนึ่งหรือไม่ ถ้าเกินก็จะทำการส่งสัญญาณต่อไป ค่าที่เป็นตัวแบ่งนั้นเรียกว่าค่าขีดแบ่ง (阈值, threshold)

เพียงแต่ว่าข้อมูลต่างๆที่ป้อนเข้าไปจะมีน้ำหนักความสำคัญไม่เท่ากัน โดยจะมีการคูณด้วยค่าน้ำหนัก (权重, weight) ก่อนจึงค่อยนำมาบวกกัน เขียนเป็นสมการจะได้ว่า
..(1.1)

โดย x1, x2, ... คือข้อมูลป้อนเข้า w1, w2, ... เป็นค่าน้ำหนักที่มาคูณค่าของข้อมูลนั้น ส่วน θ คือค่าขีดแบ่ง

ถ้าผลคูณรวมแล้วได้ค่าถึง θ ก็จะได้ค่าเป็น 1 ซึ่งหมายถึงมีสัญญาณไหล แต่ถ้าไม่ถึงก็จะได้ค่า 0 หมายถึงไม่มีสัญญาณไหลผ่าน

เขียนเป็นภาพได้ดังนี้



h คือผลลัพธ์ที่ได้ ซึ่งจะมีค่า 1 หรือ 0

เพียงแต่ว่าโดยทั่วไปแล้วแทนที่จะเขียนในรูปของค่าขีดแบ่ง กลับมักจะถูกเขียนในรูปของค่าไบแอส (偏差, bias) มากกว่า คือ
..(1.2)

และ
..(1.3)

ในที่นี้ b เรียกว่าค่าไบแอส ซึ่งจะเห็นว่า b = -θ



การเขียนในรูปค่าไบแอสแบบนี้สะดวกกว่า เพราะแค่สร้างฟังก์ชันอันนึงขึ้นมา (ในที่นี้แทนด้วย a) แล้วเอามันมาเปรียบเทียบกับค่า 0

ถ้าผลคูณรวมบวกไบแอสแล้วค่าถึง 0 ขึ้นไปก็จะได้ค่าเป็น 1 คือมีการสร้างสัญญาณไหลผ่าน

แต่ถ้าน้อยกว่า 0 จะได้ค่าเป็น 0  คือไม่มีการสร้างสัญญาณไหลผ่าน

ค่า w และ b ในที่นี้เรียกว่าเป็นค่าพารามิเตอร์ (参数, parameter) ของเพอร์เซปตรอน เป็นค่าที่จะต้องปรับให้เหมาะสมกับปัญหาที่พิจารณา

เพื่อให้เห็นภาพชัดต่อไปจะเป็นการยกตัวอย่าง

สมมุติว่าเด็กคนหนึ่งต้องการซื้อของราคา 1000 บาท เขาลองเปิดกระเป๋าตังค์ตัวเองดูก็พบว่ามีอยู่แค่ธนบัตรใบละ 20 กับ 50 บาทเท่านั้น ถามว่าเขาต้องมีธนบัตรใบละเท่าไหร่อยู่กี่ใบจึงจะซื้อได้?

สำหรับปัญหาข้อนี้ถ้าให้ x1 เป็นจำนวนธนบัตร 20 บาทและ x2 เป็นจำนวน 50 บาท จะได้ว่า w1 คือ 20 และ w2 คือ 50 ส่วนไบแอส b ก็เท่ากับ -1000

ดังนั้นสมการ (1.2) จะได้
..(1.4)

อาจเขียนแผนภาพได้แบบนี้



สมมุติว่าเราต้องการเขียนโปรแกรมในรูปแบบของเพอร์เซปตรอนเพื่อคำนวณแล้วตอบคำถามข้อนี้จะได้แบบนี้
def h(x1,x2):
    w1 = 20
    w2 = 50
    b = -1000
    a = w1*x1 + w2*x2 + b
    if(a>=0):
        return 1
    else:
        return 0

print(h(x1=20,x2=10)) # ได้ 0
print(h(x1=14,x2=15)) # ได้ 1

วิธีการเขียนข้างต้นนี้เขียนขึ้นในแบบที่ให้ดูง่าย ตรงไปตรงมา แต่ต่อจากนี้ไปเพื่อความสะดวกในการคำนวณจริงๆจะเขียนในรูปอาเรย์ของ numpy เป็นแบบนี้
import numpy as np

def h(X):
    w = np.array([20,50])
    b = -1000
    a = (w*X).sum() + b
    return int(a>=0)

X = np.array([20,10])
print(h(X)) # ได้ 0
X = np.array([14,15])
print(h(X)) # ได้ 1

โดยทั้ง x และ w เป็นปริมาณที่มีหลายตัวดังนั้นจึงสะดวกที่จะเขียนในรูปของอาเรย์ โดยในที่นี้
X = [x1,x2]
w = [w1,w2]

อนึ่ง ในบทความชุดนี้จะใช้ X ตัวใหญ่แบบนี้ภายในโค้ดเพื่อแสดงข้อมูลตัวแปรต้นหลายมิติ แต่เวลาเขียนสมการจะใช้ x ตัวเล็กเสมอ

ส่วนไบแอส b มีตัวเดียวอยู่แล้วก็ไม่ต้องไปทำอะไร

ตอนที่คำนวณ a ก็แค่นำอาเรย์มาคูณกันแล้วก็รวมทั้งหมด แล้วค่อยไปบวก b อีกที

ในที่นี้มีจำนวนตัวแปรต้นและน้ำหนักอยู่ ๒ ตัว แต่หากกำหนดแบบนี้แล้วตัวแปรต้น x จะมีกี่ตัวก็ได้ แค่ w ต้องมีจำนวนเท่านั้นตาม



การคำนวณในแบบอาเรย์

โครงข่ายประสาทนั้นประกอบด้วยการคำนวณที่ซับซ้อน และเพื่อที่จะทำให้การคำนวณเป็นไปอย่างประสิทธิภาพ โดยทั่วไปทุกอย่างจะถูกทำอยู่ในรูปของอาเรย์ หรือก็คือเมทริกซ์

ดังนั้นแล้วเพื่อที่จะไปต่อได้จำเป็นต้องอาศัยความเข้าใจเรื่องเมทริกซ์ค่อนข้างดี หากใครยังพื้นฐานตรงนี้ไม่ดีจำเป็นจะต้องไปทวน

จากตัวอย่างที่แล้วเราเขียน x ในรูปของข้อมูลที่มีหลายตัวแปร ดังนั้น x จึงอยู่ในรูปของอาเรย์หนึ่งมิติ

อย่างไรก็ตามโดยทั่วไปเวลาคำนวณเราจะไม่ได้มีข้อมูลป้อนเข้าแค่ค่าเดียวแต่มาเป็นชุดๆหลายตัว

นั่นคือเราจะมีข้อมูลหลายตัว และแต่ละตัวก็เป็นข้อมูลหลายตัวแปร ดังนั้นเมื่อต้องการเขียนข้อมูลทั้งหมดทีเดียว จะได้ว่าอยู่ในรูปของอาเรย์สองมิติ
..(1.5)

โดย n คือจำนวนข้อมูล ส่วน m คือจำนวนตัวแปร

อนึ่ง เนื่องจากอาเรย์ในไพธอนจะเริ่มนับจาก 0 เพื่อความง่ายแล้วต่อจากนี้จะเริ่มนับเลขไล่ตั้งแต่ 0 แทนที่จะเริ่มจาก 1

และค่า a ก็จะมีหลายค่าเหมือนกัน สามารถเขียนสูตรการคำนวณในรูปการคูณเมทริกซ์ได้โดย
..(1.6)

ที่ใช้อักษรเป็นตัวหนาตรงนี้คือแสดงถึงว่าเป็นอาเรย์สองมิติ (เมทริกซ์) ไม่ใช่ตัวแปรที่เป็นเลขตัวเดียว ส่วนตัวแปรที่มีลูกศรอยู่ด้านบนจะหมายถึงอาเรย์หนึ่งมิติ (เวกเตอร์) ในขณะที่ถ้าเป็นตัวเอียงธรรมดาจะหมายถึงค่าเลขตัวเดียว ในบทความชุดนี้ทั้งหมดจะใช้วิธีการเขียนแบบนี้ในสมการเพื่อแยกแยะให้ชัดเจน

เครื่องหมายจุดตรงกลาง ⋅ ในที่นี้คือการคูณเมทริกซ์ โดยใน numpy จะใช้คำสั่ง np.dot() ไม่ใช่การคูณกันแบบคูณอาเรย์ธรรมดาซึ่งจะเป็นการนำสมาชิกทั้งหมดมาคูณกันเฉยๆ

โดย w ในที่นี้เป็นอาเรย์หนึ่งมิติ โดยอาเรย์หนึ่งมิติปกติจะเขียนเรียงในแนวตั้ง
..(1.7)

และจะได้ว่า
..(1.8)

a ของแต่ละตัวคือ
..(1.9)

ทีนี้จะสามารถเขียนฟังก์ชันของเพอร์เซปตรอนใหม่เพื่อให้สามารถคำนวณได้พร้อมกันทีละหลายตัว แบบนี้
def h(X):
    w = np.array([20,50])
    b = -1000
    a = np.dot(X,w) + b
    return (a>=0).astype(int)

จากนั้นลองนำมาใช้กับอาเรย์ค่า x ที่เป็นสองมิติได้แบบนี้
X = np.array([
    [10,15],
    [10,18],
    [22,15]
])
print(h(X)) # ได้ [0 1 1]

เท่ากับเป็นการคำนวณหาคำตอบใน ๓ กรณีในคราวเดียวเลย ซึ่งการทำแบบนี้นอกจากการเขียนจะดูกะทัดรัดกว่าแล้วยังคำนวณเร็วกว่ามากด้วย

อนึ่ง .astype(int) ในที่นี้ทำเพื่อเปลี่ยนค่าที่เดิมทีควรจะอยู่ในรูป True,False ให้เป็น 1,0 แต่ที่จริงในกรณีส่วนใหญ่ต่อให้ไม่เปลี่ยนก็สามารถใช้คำนวณได้เหมือนกัน

เราสามารถเขียนโปรแกรมให้ทำการวาดภาพแสดงการแบ่งอาณาเขตระหว่างส่วนที่เป็น 0 และ 1 ได้โดยเขียนแบบนี้
import matplotlib.pyplot as plt
mx,my = np.meshgrid(np.linspace(0,60,200),np.linspace(0,30,200))
mX = np.array([mx.ravel(),my.ravel()]).T
mz = h(mX).reshape(200,-1)
plt.axes(aspect=1)
plt.contourf(mx,my,mz,cmap='hot')
plt.xlabel(u'๒๐ บาท',family='Tahoma',size=14)
plt.ylabel(u'๕๐ บาท',family='Tahoma',size=14)
plt.show()


สีดำเป็นเขตที่ได้ 0 สีเหลืองคือได้ 1 จะเห็นว่าถ้ามีแค่ธนบัตรใบละ 20 อย่างเดียวสัก 50 ใบก็ซื้อของได้ หรือมีใบละ 50 สัก 20 ใบก็ซื้อได้เหมือนกัน สมเหตุสมผล

สำหรับวิธีการวาดภาพนั้นใช้ matplotlib ซึ่งไม่ใช่เนื้อหาที่ต้องเน้นตรงนี้จึงจะไม่อธิบายรายละเอียด จะลงแค่โค้ดไว้ หากใครต้องการเข้าใจว่าทำงานยังไงให้อ่านรายละเอียดในเนื้อหา "numpy & matplotlib เบื้องต้น" ในบล็อกนี้



ลอจิกเกต

เพื่อให้เข้าใจมากขึ้นจะขอยกตัวอย่างเพิ่มเติมอีก

ตัวอย่างหนึ่งที่มักถูกยกมาใช้กันมากคือเกต OR เกต AND ในวงจรตรรกะซึ่งถูกใช้บ่อยในเรื่องของวงจรอิเล็กทรอนิกส์

เกี่ยวกับเรื่องนี้หากใครยังไม่เคยรู้จักคุ้นเคยมาก่อนอาจรายละเอียดอ่านได้ในวิกิ https://th.wikipedia.org/wiki/ประตูสัญญาณตรรกะ

ลอจิกเกตในวงจรอิเล็กทรอนิกส์นั้นโดยปกติจะรับสัญญาณเข้ามาทีละ ๒ ตัวแล้ว

เกต OR เป็นเกตที่จะให้ค่า 1 ถ้ามีตัวใดตัวหนึ่งเป็น 1 แต่ถ้าเป็น 0 ทั้งคู่จึงจะเป็น 0

x0 x1 z
0 0 0
0 1 1
1 0 1
1 1 1

เราอาจเขียนฟังก์ชันสำหรับทำเกต OR ได้ดังนี้
def h(X):
    w = np.array([1,1])
    b = -0.9
    a = np.dot(X,w) + b
    return (a>=0).astype(int)

X = np.array([
    [0,0],
    [0,1],
    [1,0],
    [1,1]
])
print(h(X)) # ได้ [0 1 1 1]

ซึ่งในความเป็นจริงแล้วค่า b และ w ที่ทำเกต OR ได้ตามที่ต้องการนั้นไม่ได้มีเพียงหนึ่งเดียว เช่น b จะปรับเป็น -0.1 หรือ -1 ก็ยังคงได้คำตอบแบบเดิม เพราะยังไงข้อมูลป้อนเข้าก็มีอยู่ได้เพียง ๔ รูปแบบดังที่ยกมา ขอแค่แบ่งทั้ง ๔ แบบได้ตามเงื่อนไขที่ยกมาก็ถือว่าใช้ได้แล้ว

ถ้าวาดแสดงเป็นภาพก็จะได้แบบนี้
mx,my = np.meshgrid(np.linspace(-0.5,1.5,200),np.linspace(-0.5,1.5,200))
mX = np.array([mx.ravel(),my.ravel()]).T
mz = h(mX).reshape(200,-1)
plt.axes(aspect=1)
plt.contourf(mx,my,mz,cmap='summer')
plt.scatter(X[:,0],X[:,1],100,c=h(X),edgecolor='r',marker='D',cmap='hot')
plt.show()



ถ้าปรับค่าใน h เป็น w = np.array([0.5,2]) และ b = -0.4 ก็จะได้เขตการแบ่งที่เปลี่ยนไปแต่ก็ยังสามารถแบ่งค่าตามที่ต้องการได้อยู่



ตรงนี้ประเด็นสำคัญที่ต้องการจะให้เห็นก็คือ เราจะเห็นว่าเมื่อค่าพารามิเตอร์ต่างๆในเพอร์เซปตรอนเป็นค่าที่เหมาะสมแล้วผลลัพธ์ก็จะออกมาตามที่ควรจะเป็น

ว่าแต่ว่าจะทำยังไงให้ได้ค่าพารามิเตอร์เหล่านี้มา? นี่ล่ะคือประเด็นสำคัญ

ความจริงแล้วก็คือ เวลาใช้งานจริงๆพารามิเตอร์เหล่านี้ผู้เขียนโปรแกรมจะไม่ได้เป็นคนใส่ให้ แต่โปรแกรมจะทำการเรียนรู้จากข้อมูลที่ใส่เข้าไปเพื่อหาค่าเหล่านี้เอง

เพราะเป็นเช่นนั้นเพอร์เซปตรอนจึงถูกเรียกว่าเป็นเทคนิคการเรียนรู้ของเครื่อง ไม่เช่นนั้นมันก็จะเป็นแค่ฟังก์ชันธรรมดาที่ไม่มีการเรียนรู้

ในบทต่อไปจะกล่าวถึงวิธีการในการเรียนรู้เพื่อปรับพารามิเตอร์



>> อ่านต่อ บทที่ ๒


-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> ปัญญาประดิษฐ์ >> โครงข่ายประสาทเทียม
-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python >> numpy

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文