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



โครงข่ายประสาทเทียมเบื้องต้น บทที่ ๑๗: การป้องกันการเรียนรู้เกินด้วยดรอปเอาต์
เขียนเมื่อ 2018/08/30 19:41
แก้ไขล่าสุด 2021/09/28 16:42
>> ต่อจาก บทที่ ๑๖



ในบทที่แล้วได้กล่าวถึงปัญหาการเรียนรู้เกิน (过学习, overlearning) ไปแล้ว

บทนี้จะพูดถึงวิธีการดรอปเอาต์ (dropout) ซึ่งนิยมใช้ในการลดปัญหาการเรียนรู้เกินของโครงข่ายประสาทเทียม

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

ตัวอย่างภาพแสดงการไหลของข้อมูล กรณีที่ดรอปเอาต์ของแต่ละชั้นเป็น 0.5 คือสุ่มตัดไปครึ่งนึง



แต่ละรอบจะสุ่มไม่เหมือนกัน รอบต่อไปอาจไหลแบบนี้



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

เพียงแต่ว่าการสุ่มเอาเซลล์ออกนี้จะทำเฉพาะตอนที่กำลังฝึกแบบจำลองอยู่เท่านั้น แต่ตอนที่ใช้งานเพื่อทำการทำนายจริงๆจะเปิดใช้หมดทุกเซลล์พร้อมกัน

ชั้นดรอปเอาต์จะวางไว้ที่ก่อนหรือหลังชั้นฟังก์ชันกระตุ้นในแต่ละชั้นก็ได้

ตัวอย่างผังโครงสร้างเพอร์เซปตรอน ๔ ชั้นเมื่อวางดรอปเอาต์ไว้ก่อนฟังก์ชันกระตุ้น





แล้วทำไมวิธีการแบบนี้ถึงป้องกันการเรียนรู้เกินได้?

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

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

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

ที่จริงแล้วแนวคิดของดรอปเอาต์นั้นเหมือนกับเทคนิคที่เรียกว่าการเรียนรู้แบบอ็องซ็องบล์ (集成学习, ensemble learning) ซึ่งเป็นวิธีการที่สร้างแบบจำลองสำหรับทำนายขึ้นมาหลายๆตัวแล้วให้เรียนรู้ข้อมูลเดียวกัน แล้วสุดท้ายเวลาทำนายก็เอาแบบจำลองเหล่านี้มาพิจารณาร่วมกัน ซึ่งจะได้ประสิทธิภาพมากกว่าการพิจารณาด้วยแบบจำลองตัวเดียว

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



เราจะทำการสร้างคลาสของชั้นดรอปเอาต์ขึ้นในลักษณะเดียวกันกับชั้นของฟังก์ชันกระตุ้นซึ่งสร้างมาในบทก่อนๆ

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

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

ก่อนอื่น ให้ทำการดึงคลาสที่จำเป็นต้องใช้ในบทนี้จาก unagi.py
from unagi import Chan,ha_1h,Affin,Relu,Softmax_entropy,Adam

ชั้นดรอปเอาต์อาจนิยามดังนี้
import numpy as np

class Dropout(Chan):
    def __init__(self,drop=0.5):
        self.drop = drop
        self.fuekyu = 1
        
    def pai(self,x):
        if(self.fuekyu):
            self.krong = np.random.rand(*x.shape)>self.drop
            return x*self.krong
        else:
            return x*(1.-self.drop)
    
    def yon(self,g):
        return g*self.krong

ค่า .fuekyu มีไว้แบ่งกรณีว่ากำลังอยู่ในโหมดฝึกอยู่หรือเปล่า ถ้าฝึกอยู่จะมีค่าเป็น 1 และจะมีการกรองเพื่อตัดค่าขาเข้าส่วนหนึ่งทิ้งไป ถ้าไม่ได้ฝึกอยู่ก็จะมีค่าเป็น 0 ก็จะไม่มีการกรองแต่ปล่อยให้ค่าทั้งหมดไหลผ่านไป

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

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

จะทำแบบไหนผลก็ไม่ต่างกันนัก

ที่สำคัญคือ อย่าลืมปรับโหมดให้ถูกต้อง คือตอนฝึกให้ตั้ง .fuekyu=1 แต่พอจะใช้ทดสอบทำนายจริงให้ตั้ง .fuekyu=0

ต่อไปเป็นตัวอย่างการนำมาใช้ เราอาจสร้างคลาสโครงข่ายประสาทเทียมโดยใส่ดรอปเอาต์ด้วยได้ในลักษณะแบบนี้
class Prasat:
    def __init__(self,m,eta=0.001,drop=0):
        self.m = m
        self.chan = []
        for i in range(len(m)-1):
            self.chan.append(Affin(m[i],m[i+1],np.sqrt(2./m[i])))
            if(i<len(m)-2):
                if(drop): # ใส่ดรอปเอาต์ทุกชั้นยกเว้นชั้นสุดท้าย
                    self.chan.append(Dropout(drop))
                self.chan.append(Relu())
        self.chan.append(Softmax_entropy())
        self.opt = Adam(self.param(),eta=eta)
    
    def rianru(self,X,z,X_truat,z_truat,n_thamsam=100):
        Z = ha_1h(z,self.m[-1])
        self.khanaen_fuek = []
        self.khanaen_truat = []
        for o in range(n_thamsam):
            entropy = self.ha_entropy(X,Z)
            entropy.phraeyon()
            self.opt()
            khanaen_fuek = self.ha_khanaen(X,z)
            khanaen_truat = self.ha_khanaen(X_truat,z_truat)
            self.khanaen_fuek.append(khanaen_fuek)
            self.khanaen_truat.append(khanaen_truat)
            if(o%100==99):
                print(u'ผ่านไป %d รอบ, ทำนายข้อมูลฝึกแม่น=%.2f, ทำนายข้อมูลตรวจสอบแม่น=%.2f'%(o+1,khanaen_fuek,khanaen_truat))
    
    def ha_entropy(self,X,Z,fuekyu=1):
        for c in self.chan[:-1]:
            c.fuekyu = fuekyu
            X = c(X)
        return self.chan[-1](X,Z)
    
    def param(self):
        p = []
        for c in self.chan:
            if(hasattr(c,'param')):
                p.extend(c.param)
        return p
    
    def thamnai(self,X,fuekyu=0):
        for c in self.chan[:-1]:
            c.fuekyu = fuekyu
            X = c(X)
        return X.kha.argmax(1)
    
    def ha_khanaen(self,X,z):
        return (self.thamnai(X)==z).mean()

เมธอด .ha_entropy() ใช้คำนวนไปข้างหน้าในตอนฝึก จึงมีการปรับโหมดให้เป็นโหมดฝึก fuekyu=1

พอทำแบบนี้ค่า .fuekyu นี้จะถูกป้อนให้กับทุกชั้น แต่สำหรับชั้นอื่นนอกจากชั้นดรอปเอาต์แล้วไม่ได้มีผลอะไร

ส่วนเมธอด .thamnai() สำหรับใช้ทำนายผลนั้นตั้งให้ปกติเป็น fuekyu=0 เพราะเป็นการใช้งานจริง

ลองนำมาใช้เพื่อวิเคราะห์ข้อมูลรูปร่างต่างๆเช่นเดียวกับบทที่แล้ว >> https://phyblas.hinaboshi.com/triamhai/ruprang-raisi-25x25x1000x5.rar

เพื่อให้เห็นปัญหาเรื่องการเรียนรู้เกินชัดเจนคราวนี้จะใช้ข้อมูลแค่ 1/10 ของทั้งหมด คือแค่ 500 รูปเท่านั้น โดยแบ่ง 400 รูปเป็นข้อมูลฝึก ส่วนอีก 100 รูปเป็นข้อมูลตรวจสอบ

เปรียบเทียบผลระหว่างมีดรอปเอาต์ใส่อยู่ในแต่ละชั้นโดยมีอัตราดรอปเป็น 0.2 กับไม่มีดรอปเอาต์
import matplotlib.pyplot as plt
from glob import glob

# โหลดเฉพาะรูปที่ชื่อลงท้ายด้วยเลข 1 ซึ่งมีอยู่ 1/10 ของรูปทั้งหมด
X = np.array([plt.imread(x) for x in sorted(glob('ruprang-raisi-25x25x1000x5/*/*1.png'))])
X = X.reshape(-1,25*25)
z = np.arange(5).repeat(100)
n = len(z) # จำนวนข้อมูลทั้งหมด
n_fuek = int(n*0.8) # จำนวนข้อมูลฝึกเป็น 4 ใน 5
np.random.seed(9)
sumlueak = np.random.permutation(n)
X_fuek,X_truat = X[sumlueak[:n_fuek]],X[sumlueak[n_fuek:]]
z_fuek,z_truat = z[sumlueak[:n_fuek]],z[sumlueak[n_fuek:]]

plt.figure(figsize=[6.5,5])
for drop in [0,1]:
    plt.subplot(1,2,1+drop,ylim=[0.5,1.005])
    drop *= 0.2
    prasat = Prasat(m=[625,100,100,100,6],drop=drop)
    prasat.rianru(X_fuek,z_fuek,X_truat,z_truat,n_thamsam=600)
    plt.plot(prasat.khanaen_fuek,'#cc99ee')
    plt.plot(prasat.khanaen_truat,'#55cc77')
    plt.title(u'dropout %.1f'%drop,family='Tahoma',size=12)
    if(drop==0):
        plt.legend([u'ข้อมูลฝึก',u'ข้อมูลตรวจสอบ'],prop={'family':'Tahoma','size':14})
    else:
        plt.yticks([])
plt.tight_layout()
plt.show()



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

อาจลองปรับแบบจำลองโดยเปลี่ยนอัตราดรอปหรือจำนวนชั้นแล้วลองใหม่ดูได้



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


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

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

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

หมวดหมู่

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

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

ไทย

日本語

中文