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

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

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月

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

ไทย

日本語

中文