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



โครงข่ายประสาทเทียมเบื้องต้น บทที่ ๒๐: โครงข่ายประสาทแบบคอนโวลูชัน (CNN)
เขียนเมื่อ 2021/03/24 20:48
แก้ไขล่าสุด 2022/07/23 10:36

ต่อจาก บทที่ ๑๙

ในบทนี้จะเข้าสู่เรื่องของโครงข่ายประสาทแบบคอนโวลูชัน (卷积神经网路, convolutional neural network) หรือนิยมเรียกย่อว่า CNN

โครงข่ายปราสาทเทียมชนิดนี้นิยมใช้ในการวิเคราะห์ภาพหรืออนุกรมเวลาซึ่งเป็นข้อมูลที่มีความต่อเนื่อง มีการใช้งานที่กว้างขวางมากมาย




ว่าด้วยเรื่องของข้อมูลที่มีความเกี่ยวพันเชิงตำแหน่งหรือลำดับ

ข้อมูลบางประเภทเป็นตัวแปรที่มีความต่อเนื่องเกี่ยวพันกันเชิงตำแหน่งหรือลำดับหรือเวลา เช่น รายได้ในแต่ละเดือน ซึ่งอาจวาดเป็นกราฟได้แบบนี้



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

ข้อมูลที่แสดงลำดับการเปลี่ยนแปลงของค่าตามเวลาแบบนี้เรียกว่าอนุกรมเวลา (时间序列, time series)

  ข้อมูลลักษณะแบบนี้ ความสัมพันธ์ระหว่างค่าของจุดข้างเคียงกันมีความสำคัญ และลำดับข้อมูลก็มีความสำคัญ

แต่ในโครงข่ายประสาทแบบเพอร์เซปตรอนหลายชั้นแบบเดิมนั้นข้อมูลทั้งหมดจะถูกป้อนเข้ามาในฐานะข้อมูลตัวแปรหนึ่งเหมือนๆกันหมด

นั่นคือ แต่ละจุดบนเส้นกราฟ จะถูกนำมาแยกใส่ในแต่ละช่องข้อมูล ไม่ได้ถูกใส่ในฐานะเส้นต่อเนื่อง อาจมองภาพได้เป็นแบบนี้



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

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



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

โครงข่ายประสาทแบบคอนโวลูชันมีทั้งแบบหนึ่งมิติและสองมิติ หรือจำนวนมิติมากกว่านั้น ความซับซ้อนจะมากขึ้นตามลำดับ

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

แต่ในที่นี้จะขอไปทีละขั้น โดยบทนี้จะเริ่มจากอธิบายแบบหนึ่งมิติก่อน โดยตัวอย่างที่พบได้ทั่วไปคือข้อมูลอนุกรมเวลา

ตัวอย่างหนึ่งสำหรับงานวิจัยที่ใช้คอนโวลูชันหนึ่งมิติวิเคราะห์คือการวิเคราะห์กราฟแสงของดาวเพื่อค้นหาดาวเคราะห์ อ่านรายละเอียดได้ใน https://phyblas.hinaboshi.com/20171216




โครงสร้างของโครงข่ายประสาทแบบคอนโวลูชัน

โดยทั่วไปแล้วโครงข่ายประสาทแบบคอนโวลูชันจะประกอบไปด้วย ๒ ส่วนคือ

- ส่วนคอนโวลูชัน ประกอบไปด้วย
-- ชั้นคอนโวลูชัน (convolutional layer)
-- ฟังก์ชันกระตุ้น อย่างเช่น ReLU
-- ชันบ่อรวมสูงสุด (max pooling layer)

- ส่วนเชิงเส้น ประกอบไปด้วย
-- ชั้นเชิงเส้น (affine layer)
-- ฟังก์ชันกระตุ้น อย่างเช่น ReLU ยกเว้นชั้นสุดท้ายใช้ Softmax หรือ Sigmoid


ตัวอย่างเช่นโครงสร้างแบบในภาพนี้



ชั้นคอนโวลูชัน (convolutional layer) กับชันบ่อรวมสูงสุด (max pooling layer) นั้นกำลังจะกล่าวถึงต่อไปในบทนี้ ส่วนชั้นที่เหลือมีเขียนอธิบายในบทก่อนหน้าไปแล้ว โค้ดจะใช้ตามในบทที่ ๑๑

ในโครงสร้างนี้ข้อมูลจะเริ่มเข้ามาในโครงข่ายประสาทโดยเริ่มผ่านจากส่วนคอนโวลูชัน ซึ่งอาจมีกี่ชั้นก็ได้ ในแต่ละชั้นจะประกอบไปด้วยชั้นย่อยที่ประกอบไปด้วยชั้นคอนโวลูชัน ตามด้วยฟังก์ชันกระตุ้นและชั้นบ่อรวมสูงสุด ทั้งหมดนี้นับรวมเป็น ๑ ชั้น

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

ตัวอย่างในภาพนี้มีชั้นคอนโวลูชัน ๒ ชั้น ชั้นเชิงเส้น ๒ ชั้น รวมเป็น ๔ ชั้น

หากเขียนการคำนวณภายในแต่ละชั้นไปทีละขั้นก็อาจเขียนได้แบบนี้

ชั้น 1

..(20.1)

ชั้น 2

..(20.2)

ชั้น 3

..(20.3)

ชั้น 4

..(20.4)

โดยในที่นี้ คืออาเรย์ข้อมูลขาเข้าของชั้นที่ 1,2,3,4 ตามลำดับ

และ คืออาเรย์ข้อมูลขาออกชั้นที่ 1,2,3,4 ตามลำดับ

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




สหสัมพันธ์ไขว้และคอนโวลูชัน

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

รายละเอียดเกี่ยวกับเรื่องสหสัมพันธ์ไขว้และคอนโวลูชันนี้ค่อนข้างยาว และไม่ได้ใช้แค่ในเรื่องของโครงข่ายประสาทเทียม ดังนั้นจึงขอเขียนแยกไว้ โดยได้อธิบายไว้ในบทความนี้ ขอให้ข้ามไปอ่านแล้วค่อยกลับมาอ่านหน้านี้ต่อ https://phyblas.hinaboshi.com/20180609

สรุปคร่าวๆตรงนี้อีกครั้งก็คือ การคำนวณสหสัมพันธ์ไขว้คือการที่เรามีตัวกรองเป็นค่าตัวเลขชุดหนึ่ง แล้วเอามันมาวิ่งไล่จับคู่คูณกับแผงค่าตัวแปรตั้งต้น ผลที่ได้จะออกมาเป็นผลรวมของผลคูณทุกคู่

สำหรับกรณีหนึ่งมิติ มองภาพตัวอย่างเป็นภาพเคลื่อนไหวเพื่อให้เข้าใจได้ง่ายขึ้น



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

ด้วยเหตุนี้การคำนวณสหสัมพันธ์ไขว้จึงถูกนำมาใช้เพื่อวิเคราะห์ข้อมูลที่ต้องการพิจารณาความสัมพันธ์ระหว่างจุดที่อยู่ใกล้กัน

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




การคำนวณภายในชั้นคอนโวลูชัน

คราวนี้มาดูการคำนวณที่เกิดขึ้นโดยทั่วไปในชั้นคอนโวลูชัน

จากสมการ (20.1) และ (20.2) ซึ่งมีชั้นคอนโวลูชัน จะเกิดการคำนวณแบบนี้ขึ้นภายในชั้นนี้

..(20.5)

..(20.6)

ในที่นี้ คือจำนวนช่องของข้อมูลขาเข้าของชั้นที่ 1

คือจำนวนช่องของข้อมูลขาออกของชั้นที่ 1 และเท่ากับจำนวนช่องของข้อมูลขาเข้าในชั้นที่ 2

และ คือความยาวของตัวกรองในชั้นที่ 1 และ 2 ตามลำดับ

ตัว ที่ใส่อยู่ในวงเล็บลอยอยู่ด้านบนคือดัชนีชี้ว่าเป็นค่าตัวไหน

คือดัชนีบอกว่าเป็นข้อมูลตัวที่เท่าไหร่

คือดัชนีบอกลำดับช่องของข้อมูลขาเข้าของชั้นนั้น

คือดัชนีลอบลำดับช่องของข้อมูลขาออกของชั้นนั้น

คือดัชนีบอกลำดับข้อมูลในอนุกรมเวลา

และ เป็นดัชนีบอกตำแหน่งภายในตัวกรอง

พารามิเตอร์ของชั้นคอนโวลูชันคือ w ค่าน้ำหนักภายในแต่ละช่องในชั้นนั้นคือ กับ b ไบแอสของแต่ละชั้น

จำนวนพารามิเตอร์ w จะเป็นผลคูณของ: จำนวนช่องของข้อมูลขาเข้า × จำนวนช่องของข้อมูลขาออก × ความยาวตัวกรอง

เช่น สำหรับชั้นแรกเป็น ชั้นที่ 2 เป็น

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

สมมุติว่าเรามีข้อมูลของการวัดค่าอะไรบางอย่างที่เปลี่ยนแปลงไปตามเวลา โดยวัดค่าที่เวลา t=1 ไปจนถึง t=20 มีข้อมูลอยู่ 10 ตัว วาดเป็นกราฟได้ดังนี้



จะเห็นว่าใน 10 เส้นนี้มีความแตกต่างกันแบ่งเป็น ๒ กลุ่มชัดเจน คือเส้นที่ 1-5 ค่าเปลี่ยนแปลงรวดเร็ว และเส้นที่ 6-10 ค่าเปลี่ยนช้าๆ เราสามารถใช้โครงข่ายประสาทคอนโวลูชันหนึ่งมิติเพื่อวิเคราะห์แยกข้อมูลลักษณะนี้ได้

ในกรณีส่วนใหญ่แล้ว จำนวนช่องข้อมูลขาเข้าจะเป็น 1 คือเป็นข้อมูลอนุกรมเวลาเส้นเดียว

ข้อมูลเช่นในกราฟตัวอย่างนี้ก็เช่นกัน ดังนั้นในที่นี้จะมีช่องเดียวคือ i=1 โดย เป็นค่า ตัวที่ ที่เวลา

ความสัมพันธ์ระหว่างความยาว (สำหรับในอนุกรมเวลาคือ มี t กี่ตัว) ในของข้อมูลขาเข้าและขาออกคือ

..(20.7)

เช่นถ้าขาเข้ามี 20 ช่อง ความยาวตัวกรองเป็น 3 ความยาวที่ได้ออกมาก็จะเป็น 20-3+1 = 18

เพื่อให้เข้าใจได้มากขึ้น ต่อไปจะลองวาดภาพเคลื่อนไหวแสดงการคำนวณที่เกิดขึ้น

ให้ความยาวของข้อมูลขาเข้าเป็น 7 (ในกราฟด้านบนเป็น 20 แต่ในที่นี้ขอวาดแค่ 7 เพื่อความง่าย) สมมุติว่าในชั้นที่ 1 มีจำนวนช่องข้อมูลขาเข้า จำนวนช่องข้อมูลขาออก และความยาวตัวกรอง

ถ้าข้อมูลตัวที่ มีค่าเป็น [6,2,1,0,5,1,3] การคำนวณที่เกิดกับข้อมูลตัวที่ อาจวาดภาพแสดงได้ดังนี้



ในที่นี้ตัวกรอง (สีเขียวด้านบน) จะมี 3 ตัว เท่ากับจำนวนช่องขาออก โดยแต่ละตัวมีค่าน้ำหนัก w แยกกันอยู่ (ค่าในช่องเขียว) ตัวกรองหนึ่งมี 3 ค่า ดังนั้นรวมทั้งหมดจะเห็นว่ามีพารามิเตอร์ w ทั้งหมด 3×3=9 ตัว

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

ผลลัพธ์ที่ได้จากคนละตัวกรองก็จะแยกกันไม่ได้เกี่ยวข้องอะไรกัน

ดังนั้นผลที่ได้ก็จะออกมาเป็นข้อมูลความยาว 5 ทั้งหมด 3 ช่อง (สีแดงด้านล่าง) ซึ่งจะถูกนำไปใช้เป็นข้อมูลขาเข้าของชั้นที่ 2 ต่อไป

ต่อมาสำหรับในชั้นที่ 2 จะซับซ้อนขึ้นมาอีกเพราะค่าต้องคำนวณจากหลายช่อง ไม่ได้มีแค่ช่องเดียว

การคำนวณในชั้นที่ 2 อาจวาดเป็นภาพได้ดังนี้ โดยค่าข้อมูลขาออกจำนวน 5×3=15 ตัวที่ได้เป็นสีแดงในชั้นแรก ก็จะเป็นค่าป้อนเข้าของชั้นที่ 2 (สีม่วง)



ในที่นี้จำนวนค่าพารามิเตอร์ในตัวกรองทั้งหมดก็เท่ากับจำนวนสี่เหลี่ยมเขียวในภาพ ก็คือ 3×4×3=36

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

จะเห็นว่าการคำนวณภายในชั้นคอนโวลูชันค่อนข้างซับซ้อนขึ้นเมื่อเทียบกับการคำนวณเชิงเส้น (ดูบทที่ ๗)

เพื่อให้เห็นภาพว่าคล้ายกันหรือต่างกันอย่างไร ลองมาเทียบกับการคำนวณเชิงเส้นดังที่เกิดขึ้นในสมการ (20.3) และ (20.4) ดู ซึ่งเขียนได้เป็นดังนี้

..(20.8)

..(20.9)

ลองเปรียบเทียบกับสมการ (20.5) กับ (20.6) จะเห็นว่ามีส่วนไหนหายไป

จะเห็นว่าข้อแตกต่างคือการคำนวณเชิงเส้นไม่มีส่วนของตัวกรองเหมือนอย่างชั้นคอนโวลูชัน

แต่ค่าไบแอส b นั้นจะมีเป็นจำนวนเท่ากับจำนวนเซลล์ขาออก เช่นเดียวกับที่ในชั้นคอนโวลูชันมีค่าไบแอส b เท่ากับจำนวนช่องขาออก

ในที่นี้ คือจำนวนเซลล์ขาออกของชั้นที่ 3 และ 4





การเลื่อนข้ามในระหว่างคำนวณคอนโวลูชัน

ต่อมาจะแสดงถึงตัวเลือกปรับแต่งที่สำคัญภายในชั้นคอนโวลูชัน โดยเริ่มจากเรื่องของขนาดการเลื่อน (stride) ในแต่ละการคำนวณ

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

เมื่อพิจารณาจำนวนที่เลื่อนไปด้วย ความยาวที่ได้ก็จะเป็น

..(20.10)

ในที่นี้หากหารแล้วมีเศษก็จะปัดลงให้เป็นจำนวนเต็ม

เช่นในรูปนี้ (10-3)/2+1 = 4.5 ก็คือจะเหลือ 4 ช่อง



ซึ่งถ้าความยาวที่เลื่อนเป็น 1 ก็จะกลับไปสู่สมการ (20.7)

สมการ (20.5) และ (20.6) อาจเขียนใหม่เป็นกรณีทั่วไปมากขึ้นได้แบบนี้

..(20.11)

..(20.12)

ในที่นี้ คือจำนวนช่องที่เลื่อนในแต่ละครั้งของชั้นที่ 1 และ 2 หาก ก็จะกลับไปสู่สมการ (20.5) และ (20.6)




การเติมขอบก่อนเริ่มทำการคอนโวลูชัน

ต่อมาส่วนที่สำคัญอีกเรื่องคือการเติมขอบ (padding)

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

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



ส่วนความยาวของข้อมูลขาออกที่ได้ก็เขียนเพิ่มขึ้นมาจากสมการ (20.10) เป็น

..(20.13)

เช่นดังในรูปนี้ (8-3+2×2)/2 + 1= 5.5 ตัดเศษทิ้งก็จะเท่ากับได้ค่าออกมา 5 ตัว

บ่อยครั้งที่การเติมขอบจะทำเพื่อชดเชยให้ความยาวชั้นที่ออกมากเท่ากับที่ป้อนเข้า เช่นถ้าตัวกรองมีความยาวเป็น 3 เลื่อนทีละ 1 ก็เติมขอบลงไปข้าง 1 ก็จะได้เท่าเดิม






ชั้นบ่อรวมสูงสุด

ในโครงสร้างของโครงข่ายประสาทแบบคอนโวลูชันมักเติมชั้นบ่อรวมสูงสุด (maxpool) ไว้หลังจากชั้นคอนโวลูชัน

การทำงานของชั้นนี้ง่ายๆก็คือจับกลุ่มแล้วคัดเลือกเอาแค่ตัวที่มีค่าสูงกว่าออกมา

ตัวอย่างเช่นเมื่อจับกลุ่มเอาตัวที่ติดกันเป็นกลุ่มละ ๒​ ตัวก็เป็นแบบนี้



..(20.13)

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

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

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




การเขียนคลาสของชั้นคอนโวลูชันหนึ่งมิติ

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

โค้ดสร้างคลาสชั้นคอนโวลูชันหนึ่งมิติ

ก่อนอื่น ให้ทำการเรียกคลาสที่จำเป็นต้องใช้ในบทนี้จาก unagi.py พร้อมทั้ง numpy
import numpy as np
from unagi import Chan,Param

คลาสคอนโวลูชันเขียนได้เป็น
class Conv1d(Chan):
    def __init__(self,m0,m1,kk,st=1,pad=0,sigma=1):
        '''
        m0 : จำนวนช่องขาเข้า
        m1 : จำนวนช่องขาออก
        kk : ความยาวตัวกรอง
        st : ความยาวการเลื่อน
        pad : เติมของเพิ่มยาวข้างละ
        sigma : ส่วนเบี่ยงเบนมาตรฐานของค่าสุ่มเริ่มต้นของพารามิเตอร์น้ำหนัก
        '''
        # พารามิเตอร์ตัวแรกคือน้ำหนักในแต่ละช่อง มีจำนวน [m1,m0,kk] ให้เริ่มต้นด้วยค่าสุ่มโดยแจกแจงแบบปกติโดยส่วนเบียงเบนมาตรฐานเป็นตามค่า sigma
        # อีกตัวคือไบแอส มีจำนวนเป็น m1 ให้เริ่มต้นจาก 0
        self.param = [Param(np.random.normal(0,sigma,[m1,m0,kk])),
                      Param(np.zeros(m1))]
        self.st = st
        self.pad = pad
    
    def pai(self,X):
        # เติมขอบ ถ้า pad>0
        X = np.pad(X,[(0,0),(0,0),(self.pad,self.pad)],'constant')
        # ขนาดในแต่ละมิติของพารามิเตอร์ w (จำนวนช่องขาออก, จำนวนช่องขาเข้า, ความยาวตัวกรอง)
        m1,m0,kk = self.param[0].kha.shape
        # ขนาดของข้อมูลขาเข้า (จำนวนข้อมูล, จำนวนช่องขาเข้า, ความยาวข้อมูลขาเข้าหลังเติมขอบ)
        n,m0_,k0 = X.shape
        # จำนวนช่องของข้อมูลขาเข้าควรเท่ากับจำนวนช่องของพารามิเตอร์ w ขาเข้า
        assert m0_==m0
        # ความยาวข้อมูลขาออก
        k1 = int((k0-kk)/self.st)+1
        # สร้างอาเรย์ของข้อมูลขาเข้าที่ปรับรูปใหม่เพื่อให้ทำการคำนวณสะดวก
        X_ = np.zeros([n,m0,kk,k1])
        for i in range(kk):
            X_[:,:,i,:] = X[:,:,i:i+k1*self.st:self.st]
        X_ = X_.transpose(0,3,1,2).reshape(-1,m0*kk)
        w = self.param[0].kha.reshape(m1,-1).T
        b = self.param[1].kha
        # คำนวณค่าข้อมูลขาออก
        a = np.dot(X_,w) + b
        a = a.reshape(n,k1,-1).transpose(0,2,1)
        self.ruprang = n,m1,m0,kk,k0,k1
        self.X_ = X_
        return a ̰
    
    def yon(self,g):
        # จำนวนข้อมูล, จำนวนช่องขาเข้า, จำนวนช่องขาออก, ความยาวตัวกรอง, ความยาวข้อมูลขาเข้า, ความยาวข้อมูลขาออก
        n,m1,m0,kk,k0,k1 = self.ruprang
        g = g.transpose(0,2,1).reshape(-1,m1)
        w = self.param[0].kha.reshape(m1,-1).T
        self.param[0].g = np.dot(self.X_.T,g).transpose(1,0).reshape(m1,m0,kk)
        self.param[1].g = g.sum(0)
        gX_ = np.dot(g,w.T)
        gX_ = gX_.reshape(-1,k1,m0,kk).transpose(0,2,3,1)
        gX = np.zeros([n,m0,k0+self.pad*2])
        for i in range(kk):
            gX[:,:,i:i+k1*self.st:self.st] += gX_[:,:,i,:]
        return gX[:,:,self.pad:k0-self.pad]




การเขียนคลาสของชั้นบ่อรวมสูงสุดหนึ่งมิติ

ต่อมาเป็นโค้ดสร้างคลาสชั้นบ่อรวมสูงสุดหนึ่งมิติ

ลักษณะจะคล้ายๆกับชั้นคอนโวลูชัน แต่จะง่ายกว่าเพราะไม่มีพารามิเตอร์
class MaxP1d(Chan):
    def __init__(self,kk,st=None):
        '''
        kk : ความยาวตัวกรอง
        st : ความยาวการเลื่อน (ถ้าไม่กำหนด ก็ให้เท่ากับความยาวตัวกรอง)
        '''
        self.kk = kk
        if(st==None):
            self.st = self.kk
        else:
            self.st = st
    
    def pai(self,X):
        # ขนาดของข้อมูลขาเข้า (จำนวนข้อมูล, จำนวนช่องขาเข้า, ความยาวข้อมูลขาเข้า)
        n,m,k0 = X.shape
        k1 = int((k0-self.kk)/self.st)+1
        X_ = np.zeros([n,m,self.kk,k1])
        for i in range(self.kk):
            X_[:,:,i,:] = X[:,:,i:i+k1*self.st:self.st]
        X_ = X_.transpose(0,3,1,2).reshape(-1,self.kk)
        self.argmax = X_.argmax(1)
        self.ruprang = n,m,k0,k1
        return X_.max(1).reshape(n,k1,m).transpose(0,2,1)
    
    def yon(self,g):
        # จำนวนข้อมูลขาเข้า, จำนวนช่องข้อมูลขาเข้า, ความยาวข้อมูลขาเข้า, ความยาวข้อมูลขาออก
        n,m,k0,k1 = self.ruprang
        g = g.transpose(0,2,1)
        gX_ = np.zeros([g.size,self.kk])
        gX_[np.arange(len(self.argmax)),self.argmax] = g.flatten()
        gX_ = gX_.reshape(-1,k1,m,self.kk).transpose(0,2,3,1)
        gX = np.zeros([n,m,k0])
        for i in range(self.kk):
            gX[:,:,i:i+k1*self.st:self.st] += gX_[:,:,i,:]
        return gX




การเขียนคลาสของชั้นตัวเปลี่ยนรูป

อีกส่วนประกอบหนึ่งที่สำคัญของโครงข่ายประสาทคอนโวลูชันก็คือชั้นที่คั่นระหว่างส่วนคอนโวลูชันและส่วนเชิงเส้น ซึ่งเราจำเป็นต้องปรับเปลี่ยนรูปร่างมิติของข้อมูลไป

เพราะรูปร่างของข้อมูลที่ออกมาจากชั้นคอนโวลูชันจะเป็น 3 มิติ (จำนวนข้อมูล, จำนวนช่อง, ความยาวตัวกรอง)

ในขณะที่ชั้นเชิงเส้นข้อมูลป้อนเข้าต้องการข้อมูลแค่ 2 มิติ (จำนวนข้อมูล, จำนวนเซลล์ขาเข้า)

ดังนั้นต้องทำการเปลี่ยนรูป โดยเอาค่าจากทุกช่องในทุกตัวกรองมาเรียงต่อกันเป็นแถวเดียว

ก็จะได้ว่า



เขียนโค้ดสร้างคลาสของชั้นเปลี่ยนรูปได้ดังนี้
class Plianrup(Chan):
    def __init__(self,*rupmai):
        self.rupmai = rupmai
        
    def pai(self,x):
        self.rupdoem = x.shape
        return x.reshape(self.rupdoem[0],*self.rupmai)
    
    def yon(self,g):
        return g.reshape(*self.rupdoem)

ในที่นี้ rupmai คือรูปร่างของข้อมูลที่ต้องการจะได้ออกมา แต่ไม่ต้องใส่มิติแรกซึ่งคือมิติของข้อมูลแยกแต่ละตัว ให้มิติแรกเท่าเดิม

สำหรับการบีบให้เหลือแค่มิติเดียว (รวมมิติจำนวนข้อมูลเป็นสองมิติ) ก็ให้เติมเป็น -1 เป็น Plianrup(-1)




ตัวอย่างการสร้างและใช้งานโครงข่ายประสาทแบบคอนโวลูชันหนึ่งมิติ

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

ในที่นี้ขอยกตัวอย่าง โดยสมมุติว่าเรามีข้อมูลชุดหนึ่งเป็นค่าอะไรบางอย่างที่เปลี่ยนแปลงไปตามเวลา โดยวัดค่าที่เวลา t=1 ไปจนถึง t=200 ได้ผลออกมาคือ ก. ข. ค. ซึ่งมีความเร็วในการเปลี่ยนแปลงต่างกันไป นำผลของค่าทั้ง ๓ กลุ่มส่วนหนึ่งมาวาดกราฟได้เป็นดังนี้



(วิธีการสร้างข้อมูลแบบนี้จะไม่พูดถึงในรายละเอียด หากสนใจอาจอ่านได้ใน https://phyblas.hinaboshi.com/20180525)

เราสามารถใช้โครงข่ายประสาทแบบคอนโวลูชันเพื่อทำการจำแนกข้อมูลกราฟ ๓ กลุ่มนี้ได้

คลาสของชั้นต่างๆที่จำเป็นได้ใส่รวมไว้ใน unagi.py แล้ว รวมทั้งชั้นคอนโวลูชัน ชั้นบ่อรวมสูงสุด และชั้นเปลี่ยนรูปด้วย สามารถเรียกใช้ (โดย from unagi import) คลาสส่วนประกอบที่จำเป็นเพื่อสร้างโครงข่ายประสาทคอนโวลูชันในที่นี้ได้เลย

ลองสร้างโครงข่ายประสาทคอนโวลูชันที่ประกอบไปด้วยชั้นคอนโวลูชัน ๒ ชั้น และชั้นเชิงเส้น ๒ ชั้น และนำมาใช้วิเคราะห์จำแนกข้อมูลอนุกรมเวลา ๓ กลุ่ม ดังนี้
import numpy as np
import matplotlib.pyplot as plt
from unagi import Affin,Relu,Softmax_entropy,ha_1h,Adam,Conv1d,MaxP1d,Plianrup

# โครงข่ายประสาทคอนโวลูชัน
class PrasatConvo:
    def __init__(self,eta):
        self.chan = []
        # ชั้น 1 คอนโวลูชัน (ช่องขาเข้า 1, ช่องขาออก 16, ความยาวตัวกรอง 29)
        self.chan.append(Conv1d(1,16,29,sigma=0.1))
        self.chan.append(Relu())
        self.chan.append(MaxP1d(2))
        # ชั้น 2 คอนโวลูชัน (ช่องขาเข้า 16, ช่องขาออก 8, ความยาวตัวกรอง 21)
        self.chan.append(Conv1d(16,8,21,sigma=0.1))
        self.chan.append(Relu())
        self.chan.append(MaxP1d(2))
        # ใส่ชั้นสำหรับทำการเปลี่ยนรูปก่อนไปเข้าชั้นเชิงเส้น
        self.chan.append(Plianrup(-1))
        # ชั้น 3 เชิงเส้น (เซลล์ขาเข้า 264, เซลล์ขาออก 64)
        self.chan.append(Affin(264,64,np.sqrt(2./264)))
        self.chan.append(Relu())
        # ชั้น 4 (เซลล์ขาเข้า 64, เซลล์ขาออก 3)
        self.chan.append(Affin(64,3,np.sqrt(2./64)))
        self.chan.append(Softmax_entropy())
        
        self.opt = Adam(self.param(),eta=eta)
    
    def rianru(self,X,z,X_truat,z_truat,n_thamsam=100,n_batch=50,ro=0):
        n = len(z)
        Z = ha_1h(z,3)
        self.entropy = []
        self.khanaen_fuek = []
        self.khanaen_truat = []
        khanaen_sungsut = 0
        for o in range(n_thamsam):
            lueak = np.random.permutation(n)
            for i in range(0,n,n_batch):
                Xb = X[lueak[i:i+n_batch]]
                Zb = Z[lueak[i:i+n_batch]]
                entropy = self.ha_entropy(Xb,Zb)
                entropy.phraeyon()
                self.opt()
            entropy,khanaen_fuek = self.ha_entropy(X_fuek,Z,ao_khanaen=1)
            khanaen_truat = self.ha_khanaen(X_truat,z_truat)
            self.entropy.append(entropy.kha)
            self.khanaen_fuek.append(khanaen_fuek)
            self.khanaen_truat.append(khanaen_truat)
            print('รอบที่ %d. เอนโทรปี=%.2e, ทำนายข้อมูลฝึกแม่น=%.3f, ทำนายข้อมูลตรวจสอบแม่น=%.3f'%(o,entropy.kha,khanaen_fuek,khanaen_truat))
            
            if(khanaen_truat>khanaen_sungsut):
                khanaen_sungsut = khanaen_truat
                maiphoem = 0
            else:
                maiphoem += 1
            
            if(ro>0 and maiphoem>=ro):
                break
    
    def ha_entropy(self,X,Z,ao_khanaen=0):
        for c in self.chan[:-1]:
            X = c(X)
        if(ao_khanaen):
            return self.chan[-1](X,Z),(X.kha.argmax(1)==Z.argmax(1)).mean()
        return self.chan[-1](X,Z)
    
    def ha_khanaen(self,X,z):
        return (self.thamnai(X)==z).mean()
    
    def param(self):
        p = []
        for c in self.chan:
            if(hasattr(c,'param')):
                p.extend(c.param)
        return p
    
    def thamnai(self,X):
        for c in self.chan[:-1]:
            X = c(X)
        return X.kha.argmax(1)

# สร้างข้อมูลตัวอย่าง
def cov(x,s):
    x1,x2 = np.meshgrid(x,x)
    return np.exp(-0.5*((x1-x2)/s)**2)
# จำนวนข้อมูลแต่ละกลุ่ม
n0,n1,n2 = 240,50,110
n = n0+n1+n2 # จำนวนข้อมูลทั้งหมด
nt = 200 # ความยาวของข้อมูลอนุกรมเวลา
t = np.arange(nt)
# สร้างข้อมูลทั้ง 3 กลุ่ม แล้วเอามาผสมกัน
# กลุ่ม ก.
X0 = np.random.multivariate_normal(np.zeros(nt),cov(t,5),n0)
# กลุ่ม ข.
X1 = np.random.multivariate_normal(np.zeros(nt),cov(t,25),n1)
# กลุ่ม ค.
X2 = np.random.multivariate_normal(np.zeros(nt),cov(t,100),n2)
# ผสมรวม
X = np.vstack([X0,X1,X2])
# คำตอบจริง ว่าเป็นกลุ่มที่ 0,1,2
z = np.array([0]*n0+[1]*n1+[2]*n2)

X = X.reshape(-1,1,nt)
# ทำการสุ่มสลับลำดับข้อมูลแล้วแบ่งเป็นส่วนฝึกฝนกับส่วนตรวจสอบ
sumlueak = np.random.permutation(n)
n_fuek = int(n*0.8)
X_fuek,X_truat = X[sumlueak[:n_fuek]],X[sumlueak[n_fuek:]]
z_fuek,z_truat = z[sumlueak[:n_fuek]],z[sumlueak[n_fuek:]]
# สร้างโครงข่ายประสาท
prasat = PrasatConvo(eta=0.005)
# เริ่มทำการเรียนรู้
prasat.rianru(X_fuek,z_fuek,X_truat,z_truat,n_thamsam=200,n_batch=64,ro=10)

# วาดกราฟแสดงความเปลี่ยนแปลงค่าเอนโทรปีและคะแนนในแต่ละขั้น
plt.figure(figsize=[5,6],dpi=100)
plt.subplot(211)
plt.semilogy()
plt.plot(prasat.entropy,'b') # วาดกราฟค่าเอนโทรปีที่บันทึกไว้ระหว่างการฝึก
plt.title('เอนโทรปี',family='Tahoma',size=13)
plt.grid(ls='--')

plt.subplot(212)
# วาดกราฟค่าคะแนนฝึกของข้อมูลชุดฝึกและชุดตรวจสอบที่บันทึกไว้ระหว่างฝึก
plt.plot(prasat.khanaen_fuek,'m')
plt.plot(prasat.khanaen_truat,'g')
plt.title('คะแนน',family='Tahoma',size=13)
plt.legend(['ข้อมูลฝึก','ข้อมูลตรวจสอบ'],prop={'family':'Tahoma','size':14})
plt.grid(ls='--')
plt.tight_layout()
plt.show()

ผลที่ได้



ในที่นี้ความยาวข้อมูลขาเข้าเป็น 200 เมื่อผ่านชั้นคอนโวลูชันที่ 1 ซึ่งมีขนาดตัวกรองเป็น 29 ก็จะกลายเป็น 200-29+1=172 และผ่านชั้นบ่อรวมขนาด 2 ก็เป็น 172/2=86

จากผ่านชั้นคอนโวลูชันที่ 2 ซึ่งมีขนาดตัวกรองเป็น 21 ก็จะกลายเป็น 86-21+1=66 และผ่านชั้นบ่อรวม 2 ก็เป็น 66/2=33

และเนื่องจากชั้นที่ 2 นี้มีจำนวนช่องขาออกเป็น 8 ดังนั้นเมื่อแปลงเป็นมิติเดียวเพื่อเป็นค่าป้อนเข้าให้ชั้นที่ 3 ซึ่งเป็นชั้นเชิงเส้นก็จะได้ว่ามีค่าขาเข้าเป็น 8×33=264 ตัว

ส่วนจำนวนเซลล์ข้อมูลขาออกของชั้นที่ 4 จะต้องเป็น 3 เพราะในที่นี้เราต้องการวิเคราะห์แยกแยะข้อมูลออกเป็น ๓ กลุ่ม




สรุปท้ายบท

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

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

ตัวอย่างโค้ดในที่นี้เป็นแค่การฝึกเขียนเพื่อให้พอเห็นภาพ แต่ในการใช้งานจริงๆในทางปฏิบัติแนะนำให้ใช้มอดูลที่ถูกทำไว้ดีอยู่แล้วเช่น pytorch >> (pytorch เบื้องต้น บทที่ ๑๒ โครงข่ายประสาทเทียมแบบคอนโวลูชัน)

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




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


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

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

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

หมวดหมู่

-- คอมพิวเตอร์ >> ปัญญาประดิษฐ์ >> โครงข่ายประสาทเทียม
-- คอมพิวเตอร์ >> เขียนโปรแกรม >> 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月

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

ไทย

日本語

中文