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



โครงข่ายประสาทเทียมเบื้องต้น บทที่ ๒๑: โครงข่ายประสาทแบบคอนโวลูชันสองมิติ
เขียนเมื่อ 2021/03/27 22:40
แก้ไขล่าสุด 2022/07/23 10:36

ต่อจาก บทที่ ๒๐

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




ว่าด้วยเรื่องข้อมูลรูปภาพ

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

ในบทนี้จะใช้ข้อมูลตัวอย่างเป็นรูปภาพ อย่างที่ใช้มาตั้งแต่บทที่ ๔

ข้อมูลสามารถโหลดได้จาก >> ruprang-raisi-25x25x1000x5.rar

เมื่อโหลดแล้วลองเปิดภาพขึ้นมาดู จะเห็นว่าแต่ละภาพเป็นภาพขาวดำขนาด 25×25 พิกเซล

ลองเอารูปหนึ่งในนั้นมาวาดแสดงโดย matplotlib
import numpy as np
import matplotlib.pyplot as plt

X = plt.imread('ruprang-raisi-25x25x1000x5/4/00112.png')
plt.figure(figsize=[6.5,5.7],dpi=100)
tick = np.arange(1,26)
plt.xticks(tick-0.5,tick)
plt.yticks(tick-0.5,tick)
plt.xlabel('ตำแหน่งในมิติ 1 ($t_1$)',family='Tahoma',size=14)
plt.ylabel('ตำแหน่งในมิติ 2 ($t_2$)',family='Tahoma',size=14)
plt.imshow(X,cmap='gray')
plt.colorbar(pad=0.01,aspect=50)
plt.grid(ls='--')
plt.tight_layout()
plt.show()



ในที่นี้ข้อมูลมีสองมิติ ให้ตำแหน่งในทั้งสองมิตินี้เป็น และ ในตัวอย่างนี้ข้อมูล 25×25 ก็คือมี และ

นอกจากนี้ในบทนี้จะใช้ข้อมูลภาพสีด้วย ซึ่งก็สามารถโหลดได้จาก >> ruprang-misi-25x25x1000x6.rar

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

ลองเอารูปหนึ่งในนั้นมาวาดแสดงแยกส่วนประกอบแต่ละสีโดย matplotlib ดู
X = plt.imread('ruprang-misi-25x25x1000x6/4/00005.png')
X = X[:,:,:3]
plt.figure(figsize=[6.5,6.5],dpi=100)
tick = np.arange(1,26)
plt.subplot(2,2,1,xticks=tick-0.5,yticks=tick-0.5,xticklabels=[],yticklabels=[])
plt.title('ภาพเดิม',family='Tahoma',size=13)
plt.imshow(X)
plt.grid(ls='--')

for i in range(3):
    plt.subplot(2,2,2+i,xticks=tick-0.5,yticks=tick-0.5,xticklabels=[],yticklabels=[])
    plt.title(['ส่วน R (ช่อง 1)','ส่วน G (ช่อง 2)','ส่วน B (ช่อง 3)'][i],family='Tahoma',size=13)
    X_ = np.zeros_like(X)
    X_[:,:,i] = X[:,:,i]
    plt.imshow(X_)
    plt.grid(ls='--')
    
plt.tight_layout()
plt.show()



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




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

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



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



สมมุติว่ามีตัวกรองอยู่ 2 ตัว ขนาดสูง 3 กว้าง 4 ก็จะกลายเป็น 2×3×4=24 เซลล์ ดังในภาพนี้






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

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

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



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




การคำนวณภายในชั้นคอนโวลูชันสองมิติ

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

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

..(21.1)

..(21.2)

ให้ลองเทียบกับสมการ (20.11) กับ (20.12) ในบทที่ ๒๐ จะเห็นว่าคล้ายกันแต่เพิ่มความซับซ้อนขึ้นมา

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

กรณีภาพขาวดำ ข้อมูลขาเข้าในชั้นแรกสุดจะมีแค่ค่าเดียว ดังนั้น แต่หากเป็นภาพสี จะเป็นข้อมูล RGB (แดง,เขียว,น้ำเงิน) ดังนั้น

แต่เมื่อเป็นสองมิติ ตัวกรองก็จะมีสองมิติคือ เป็นความสูงและความกว้างของตัวกรองชั้นที่ 1 และ เป็นความสูงและความกว้างของตัวกรองชั้นที่ 2

ส่วนดัชนีที่อยู่ในวงเล็บด้านบนมี

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

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

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

คือดัชนีบอกตำแหน่งข้อมูลตามแนวความสูงและความกว้าง

ถ้าหาก x เป็นค่าในข้อมูลรูปภาพสี จะได้ว่า คือข้อมูลของภาพที่ ในสีที่ (โดย แดง, เขียว, น้ำเงิน) ในตำแหน่ง

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

คือพารามิเตอร์น้ำหนักในตัวกรอง ซึ่งจำนวนพารามิเตอร์นี้จะมีเท่ากับ



เช่น มีจำนวนเท่ากับ และ มีจำนวนเท่ากับ

ส่วนพารามิเตอร์ค่าไบแอส นั้นจะมีแค่จำนวนเท่ากับจำนวนช่องขาออก

ความสัมพันธ์ระหว่างขนาดข้อมูลขาเข้าและขาออกเป็นดังนี้

..(21.3)

ซึ่งเหมือนกับสมการ (20.13) ในบทที่แล้ว แค่ต้องคิดพิจารณาทั้งสองมิติ ตามแนวความสูงและความกว้างแยกกัน

สำหรับภาพแสดงการคำนวณที่เกิดขึ้นภายในชั้นนั้นได้ทำเอาไว้เป็นคลิปลงใน facebook ไว้ ให้เข้าไปดูวีดีโอตัวอย่างในลิงก์นี้ >> https://www.facebook.com/watch/?v=904503580382726

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



ในภาพนี้เป็นกรณีที่ข้อมูลขาเข้ามี ความสูง×ความกว้าง เป็น 4×4 และ

ซึ่งจะได้ข้อมูลขาออกเป็นขนาด 2×2

ช่องสีเขียวในภาพคือข้อมูลขาเข้า

ช่องสีน้ำเงินทางซ้ายที่เอามาวิ่งไล่คูณกับข้อมูลขาเข้าก็คือพารามิเตอร์น้ำหนักบนตัวกรอง ซึ่งมีทั้งหมด 3×3×3×3=27 ตัว

ช่องสีม่วงที่มุมบนขวาคือค่าพารามิเตอร์ไบแอสมี 3 ตัว เท่ากับจำนวนช่องขาออก

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




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

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

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

โค้ดเขียนคลาสคอนโวลูชันสองมิติ
class Conv2d(Chan):
    def __init__(self,m0,m1,kk,st=1,pad=0,sigma=1):
        '''
        m0 : จำนวนช่องขาเข้า
        m1 : จำนวนช่องขาออก
        kk : ความสูงและความกว้างของตัวกรอง
        st : ความสูงและกว้างที่เลื่อน
        pad : ขอบที่เติมตามแนวความสูงและกว้าง
        sigma : ส่วนเบี่ยงเบนมาตรฐานของค่าสุ่มเริ่มต้นของพารามิเตอร์น้ำหนัก
        
        '''
        # kk,st,pad นั้นควรเป็นลิสต์ของสองค่า แต่หากใส่ค่าเดียวมาให้ทำเป็นเท่ากันทั้งสองมิติ
        if(type(kk)==int):
            kk = [kk,kk] 
        if(type(st)==int):
            st = [st,st]
        if(type(pad)==int):
            pad = [pad,pad]
        
        # พารามิเตอร์ตัวแรกคือน้ำหนักในแต่ละช่อง มีจำนวน [m1,m0,kk[1],k[0]]
        # อีกตัวคือไบแอส มีจำนวนเป็น m1 ให้เริ่มต้นจาก 0
        self.param = [Param(np.random.normal(0,sigma,[m1,m0,kk[1],kk[0]])),
                      Param(np.zeros(m1))]
        self.st = st
        self.pad = pad
    
    def pai(self,X):
        px,py = self.pad
        stx,sty = self.st
        # เติมขอบ ถ้า pad>0
        X = np.pad(X,[(0,0),(0,0),(py,py),(px,px)],'constant')
        # ขนาดในแต่ละมิติของพารามิเตอร์ w (จำนวนช่องขาออก, จำนวนช่องขาเข้า, ความสูงตัวกรอง, ความยาวตัวกรอง)
        m1,m0,kky,kkx = self.param[0].kha.shape
        # ขนาดของข้อมูลขาเข้า (จำนวนข้อมูล, จำนวนช่องขาเข้า, ความสูงตัวกรองหลังเติมขอบ, ความกว้างตัวกรองหลังเติมขอบ)
        n,m0_,ky0,kx0 = X.shape
        # จำนวนช่องของข้อมูลขาเข้าควรเท่ากับจำนวนช่องของพารามิเตอร์ w ขาเข้า
        assert m0_==m0
        # ความสูงข้อมูลขาออก
        kx1 = int((kx0-kkx)/stx)+1
        # ความกว้างข้อมูลขาออก
        ky1 = int((ky0-kky)/sty)+1
        # สร้างอาเรย์ของข้อมูลขาเข้าที่ปรับรูปใหม่เพื่อให้ทำการคำนวณสะดวก
        X_ = np.zeros([n,m0,kky,kkx,ky1,kx1])
        for _j in range(kky):
            j_ = _j+ky1*sty
            for _i in range(kkx):
                i_ = _i+kx1*stx
                X_[:,:,_j,_i,:,:] = X[:,:,_j:j_:sty,_i:i_:stx]
        X_ = X_.transpose(0,4,5,1,2,3).reshape(-1,m0*kkx*kky)
        w = self.param[0].kha.reshape(m1,-1).T
        b = self.param[1].kha
        a = np.dot(X_,w) + b
        a = a.reshape(n,ky1,kx1,-1).transpose(0,3,1,2)
        self.ruprang = n,m1,m0,kky,kkx,ky0,kx0,ky1,kx1
        self.X_ = X_
        return a
    
    def yon(self,g):
        px,py = self.pad
        stx,sty = self.st
        # จำนวนข้อมูล, จำนวนช่องขาเข้า, จำนวนช่องขาออก, ความสูงตัวกรอง, ความกว้างตัวกรอง, ความสูงข้อมูลขาเข้า, ความกว้างข้อมูลขาเข้า, ความสูงข้อมูลขาออก, ความกว้างข้อมูลขาออก
        n,m1,m0,kky,kkx,ky0,kx0,ky1,kx1 = self.ruprang
        g = g.transpose(0,2,3,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,kky,kkx)
        self.param[1].g = g.sum(0)
        gX_ = np.dot(g,w.T)
        gX_ = gX_.reshape(-1,ky1,kx1,m0,kky,kkx).transpose(0,3,4,5,1,2)
        gX = np.zeros([n,m0,ky0+2*py,kx0+2*px])
        for _j in range(kky):
            j_ = _j+ky1*sty
            for _i in range(kkx):
                i_ = _i+kx1*stx
                gX[:,:,_j:j_:sty,_i:i_:stx] += gX_[:,:,_j,_i,:,:]
        return gX[:,:,py:ky0-py,px:kx0-px]




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

เช่นเดียวกับกรณีหนึ่งมิติ สำหรับสองมิติเองก็ต้องมีการใช้ชั้นบ่อรวมสูงสุดเช่นกัน ซึ่งก็เขียนคล้ายกับกรณีหนึ่งมิติ แต่เพิ่มความซับซ้อนขึ้น
import numpy as np
import matplotlib.pyplot as plt
from unagi import Chan,Param

class MaxP2d(Chan):
    def __init__(self,kk,st=None):
        '''
        kk : ความยาวตัวกรอง
        st : ความยาวการเลื่อน (ถ้าไม่กำหนด ก็ให้เท่ากับความยาวตัวกรอง)
        '''
        if(type(kk)==int):
            self.kk = [kk,kk]
        else:
            self.kk = kk
        
        if(st==None):
            self.st = self.kk
        elif(type(st)==int):
            self.st = [st,st]
        else:
            self.st = st
    
    def pai(self,X):
        stx,sty = self.st
        kkx,kky = self.kk
        # ขนาดของข้อมูลขาเข้า (จำนวนข้อมูล, จำนวนช่องขาเข้า, ความสูงข้อมูลขาเข้า, ความกว้างข้อมูลขาเข้า)
        n,m,ky0,kx0 = X.shape
        kx1 = int((kx0-kkx)/stx)+1
        ky1 = int((ky0-kky)/sty)+1
        X_ = np.zeros([n,m,kky,kkx,ky1,kx1])
        for _j in range(kky):
            j_ = _j+ky1*sty
            for _i in range(kkx):
                i_ = _i+kx1*stx
                X_[:,:,_j,_i,:,:] = X[:,:,_j:j_:sty,_i:i_:stx]
        X_ = X_.transpose(0,4,5,1,2,3).reshape(-1,kkx*kky)
        self.argmax = X_.argmax(1)
        self.ruprang = n,m,ky0,kx0,ky1,kx1
        return X_.max(1).reshape(n,ky1,kx1,m).transpose(0,3,1,2)
    
    def yon(self,g):
        g = g.transpose(0,2,3,1)
        stx,sty = self.st
        kkx,kky = self.kk
        # ขนาดของข้อมูลขาเข้า (จำนวนข้อมูล, จำนวนช่องขาเข้า, ความสูงข้อมูลขาเข้า, ความกว้างข้อมูลขาเข้า, ความสูงข้อมูลขาออก, ความกว้างข้อมูลขาออก)
        n,m,ky0,kx0,ky1,kx1 = self.ruprang
        gX_ = np.zeros([g.size,kkx*kky])
        gX_[np.arange(self.argmax.size),self.argmax.flatten()] = g.flatten()
        gX_ = gX_.reshape(-1,ky1,kx1,m,kky,kkx).transpose(0,3,4,5,1,2)
        gX = np.zeros([n,m,ky0,kx0])
        for _j in range(kky):
            j_ = _j+ky1*sty
            for _i in range(kkx):
                i_ = _i+kx1*stx
                gX[:,:,_j:j_:sty,_i:i_:stx] += gX_[:,:,_j,_i,:,:]
        return gX




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

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

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

เป้าหมายคราวนี้คือข้อมูลรูปร่างต่างๆ ดังที่แสดงในตัวอย่างภาพด้านบน

รูปขาวดำ ขนาด 25×25 แบ่งเป็น 5 กลุ่ม กลุ่มละ 1000 มีรวมทั้งหมด 5000 ภาพ
>> ruprang-raisi-25x25x1000x5.rar

รูปสี ขนาด 25×25 แบ่งเป็น 6 กลุ่ม กลุ่มละ 1000 มีรวมทั้งหมด 6000 ภาพ
>> ruprang-misi-25x25x1000x6.rar

ในที่นี้จะสร้างเป็นคลาสของโครงข่ายประสาทแบบคอนโวลูชันสองมิติที่มีโครงสร้างภายในตายตัวเป็นชั้นคอนโวลูชัน 2 ชั้น และชั้นเชิงเส้น 2 ชั้น

ชั้นที่ 1 ข้อมูลขาออก 32 ช่อง ขนาดตัวกรอง 2×2

ตามด้วยชั้นที่ 2 ข้อมูลขาออก 32 ช่อง ขนาดตัวกรอง 3×3

จำนวนช่องข้อมูลขาเข้านั้นให้กำหนดได้อิสระ ถ้าเป็นภาพขาวดำก็มี 1 ช่อง ภาพเป็นภาพสีก็มี 3 ช่อง

ตัวอย่างที่จะใช้ในที่นี้มีขนาด 25×25 ดังนั้นเมื่อผ่านตัวกรองขนาด 2×2 แล้วตามด้วยบ่อรวมสูงสุด 2×2 อีกก็จะกลายเป็น (25-2+1)/2 × (25-2+1)/2 = 12×12

จากนั้นในชั้นที่ 2 เมื่อเมื่อผ่านตัวกรองขนาด 3×3 แล้วตามด้วยบ่อรวมสูงสุด 2×2 อีกก็จะกลายเป็น (12-3+1)/2 × (12-3+1)/2 = 5×5

ข้อมูลที่ออกจากชั้นคอนโวลูชันนี้มีขนาด 5×5 และมี 32 ช่อง ดังนั้นจำนวนเซลล์ขาเข้าของชั้นเชิงเส้นที่จะตามมาก็จะต้องเป็น 5×5×32=800

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

คลาสเขียนขึ้นมาได้ดังนี้
import numpy as np
import matplotlib.pyplot as plt
from unagi import Affin,Relu,Softmax_entropy,ha_1h,Adam,Conv2d,Plianrup,MaxP2d

class PrasatConvo2D:
    def __init__(self,c0,n,eta):
        '''
        c0: จำนวนช่องขาเข้า
        n: จำนวนกลุ่มของคำตอบ
        eta: อัตราการเรียนรู้
        '''
        self.chan = []
        # ชั้น 1 คอนโวลูชัน (ช่องขาเข้า c0, ช่องขาออก 32, ขนาดตัวกรอง 2×2)
        self.chan.append(Conv2d(c0,32,[2,2],[1,1],[0,0],sigma=1))
        self.chan.append(Relu())
        self.chan.append(MaxP2d(2)) # ชั้นบ่อรวมสูงสุด
        # ชั้น 2 คอนโวลูชัน (ช่องขาเข้า 32, ช่องขาออก 32, ขนาดตัวกรอง 3×3)
        self.chan.append(Conv2d(32,32,[3,3],[1,1],[0,0],sigma=1))
        self.chan.append(Relu())
        self.chan.append(MaxP2d(2)) # ชั้นบ่อรวมสูงสุด
        # ชั้นเปลี่ยนรูปก่อนเข้าสู่ชั้นเชิงเส้น
        self.chan.append(Plianrup(-1))
        # ชั้น 3 เชิงเส้น (เซลล์ขาเข้า 800, เซลล์ขาออก 64)
        self.chan.append(Affin(800,64,np.sqrt(2./800)))
        self.chan.append(Relu())
        # ชั้น 4 (เซลล์ขาเข้า 64, เซลล์ขาออก n)
        self.chan.append(Affin(64,n,np.sqrt(2./64)))
        self.chan.append(Softmax_entropy())
        
        self.opt = Adam(self.param(),eta=eta)
        
        self.n = n
    
    def rianru(self,X,z,X_truat,z_truat,n_thamsam=100,n_batch=50,ro=0):
        n = len(z)
        Z = ha_1h(z,self.n)
        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. เอนโทรปี=%.3e, ทำนายข้อมูลฝึกแม่น=%.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)

เริ่มจากลองนำมาใช้แบ่งกลุ่มข้อมูลภาพขาวดำ
from glob import glob

X = np.array([plt.imread(x) for x in sorted(glob('ruprang-raisi-25x25x1000x5/*/*.png'))]) # โหลดภาพขาวดำ
X = X.reshape(-1,1,25,25) # แปลงรูป ให้มีมิติของจำนวนช่องซึ่งเป็น 1
z = np.arange(5).repeat(1000) # คำตอบ เป็นเลข 0,1,2,3,4
np.random.seed(0)
sumlueak = np.random.permutation(5000) # สุ่มสลับลำดับ
# ข้อมูลมี 5000 ภาพ ในที่นี้แบ่งให้เป็นข้อมูลฝึก 4000 ภาพ ข้อมูลตรวจสอบ 1000 ภาพ
X_fuek,X_truat = X[sumlueak[:4000]],X[sumlueak[4000:]]
z_fuek,z_truat = z[sumlueak[:4000]],z[sumlueak[4000:]]
# สร้างโครงข่าย จำนวนช่องขาเข้าเป็น 1 เพราะเป็นภาพขาวดำ ส่วนคำตอบมี 5 กลุ่ม
prasat = PrasatConvo2D(1,5,eta=0.005)
prasat.rianru(X_fuek,z_fuek,X_truat,z_truat,n_thamsam=200,n_batch=64,ro=20)
# วาดกราฟแสดงเอนโทรปีและคะแนนสำหรับข้อมูลฝึกและข้อมูลตรวจสอบ
plt.figure(figsize=[5,7],dpi=100)
plt.subplot(211)
plt.semilogy()
plt.plot(prasat.entropy,'b')
plt.title('เอนโทรปี',family='Tahoma',size=14)
plt.grid(ls='--')

plt.subplot(212)
plt.plot(prasat.khanaen_fuek,'m',label='ข้อมูลฝึก')
plt.plot(prasat.khanaen_truat,'g',label='ข้อมูลตรวจสอบ')
plt.grid(ls='--')
plt.title('คะแนน',family='Tahoma',size=14)
plt.legend(prop={'family':'Tahoma','size':14})

plt.tight_layout()
plt.show()

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

สุดท้ายแล้วก็จะได้ผลออกมาดังนี้



จะเห็นว่าเมื่อฝึกไปแล้วก็จะสามารถทำนายข้อมูลได้แม่นยำเป็นอย่างดีทั้งชุดข้อมูลฝึกและข้อมูลทดสอบ แม้ว่าอาจจะยังทำนายข้อมูลทดสอบได้ไม่ถูกต้องบางส่วนอยู่ แต่ก็ถือว่าเกือบ 100%



จากนั้นมาดูอีกตัวอย่าง คราวนี้มาใช้กับภาพสี ข้อแตกต่างคือโครงสร้างของโครงข่าย ให้มีข้อมูลขาเข้าเป็น 3 ช่อง และขาออกเป็น 6 ตัว นอกนั้นโดยรวมแล้วก็เหมือนเดิม
X = np.array([plt.imread(x) for x in sorted(glob('ruprang-misi-25x25x1000x6/*/*.png'))]) # โหลดภาพสี
X = X.transpose(0,3,1,2)[:,:3] # ภาพที่โหลดมาจะมีมิติของสีสามสีเป็นมิติที่ 4 ให้ทำการสลับเอามิติของสีมาไว้เป็นมิติที่ 2
z = np.arange(6).repeat(1000) # เลขบอกกลุ่ม 0,1,2,3,4,5
np.random.seed(0)
sumlueak = np.random.permutation(6000) # สุ่มสลับลำดับ
# แบ่งข้อมูล 6000 ภาพเป็นข้อมูลฝึก 4800 ภาพ ข้อมูลทดสอบ 1200 ภาพ
X_fuek,X_truat = X[sumlueak[:4800]],X[sumlueak[4800:]]
z_fuek,z_truat = z[sumlueak[:4800]],z[sumlueak[4800:]]
# สร้างโครงข่าย จำนวนช่องขาเข้าเป็น 3 เพราะเป็นภาพสี ส่วนคำตอบมี 6 กลุ่ม
prasat = PrasatConvo2D(3,6,eta=0.005)
prasat.rianru(X_fuek,z_fuek,X_truat,z_truat,n_thamsam=200,n_batch=64,ro=20)
# วาดกราฟแสดงเอนโทรปีและคะแนนสำหรับข้อมูลฝึกและข้อมูลตรวจสอบ
plt.figure(figsize=[5,7],dpi=100)
plt.subplot(211)
plt.semilogy()
plt.plot(prasat.entropy,'b')
plt.title('เอนโทรปี',family='Tahoma',size=14)
plt.grid(ls='--')

plt.subplot(212)
plt.plot(prasat.khanaen_fuek,'m',label='ข้อมูลฝึก')
plt.plot(prasat.khanaen_truat,'g',label='ข้อมูลตรวจสอบ')
plt.grid(ls='--')
plt.title('คะแนน',family='Tahoma',size=14)
plt.legend(prop={'family':'Tahoma','size':14})

plt.tight_layout()
plt.show()

ผลที่ได้



จะเห็นว่าสำหรับภาพสีนั้นค่อนข้างจะยากกว่า ผลที่ออกมาได้จึงอาจไม่ดีเท่า ข้อมูลตรวจสอบทำนายได้ถูกต้องอยู่แค่ที่ประมาณ 85%

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




สรุปส่งท้ายบท

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

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

หากใช้ 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)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

ไทย

日本語

中文