如何用飛槳復(fù)現(xiàn)CapsuleNetwork

如何用飛槳復(fù)現(xiàn)Capsule Network,相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)公司!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、小程序定制開發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了運(yùn)河免費(fèi)建站歡迎大家使用!

下面讓我們一起來探究Capsule Network網(wǎng)絡(luò)結(jié)構(gòu)和原理,并使用飛槳進(jìn)行復(fù)現(xiàn)。

下載安裝命令## CPU版本安裝命令pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle

## GPU版本安裝命令pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu

卷積神經(jīng)網(wǎng)絡(luò)的不足之處

卷積神經(jīng)網(wǎng)絡(luò)(CNN)雖然表現(xiàn)的很優(yōu)異,但是針對于旋轉(zhuǎn)或元素平移等變換后的圖片,卻無法做到準(zhǔn)確提取特征。

比如,對下圖中字母R進(jìn)行旋轉(zhuǎn)、加邊框,CNN會錯誤地認(rèn)為下圖的三個R是不同的字母。

如何用飛槳復(fù)現(xiàn)Capsule Network

這就引出了位姿的概念。位姿結(jié)合了對象之間的相對關(guān)系,在數(shù)值上表示為4維位姿矩陣。三維對象之間的關(guān)系可以用位姿表示,位姿的本質(zhì)是對象的平移和旋轉(zhuǎn)

對于人類而言,可以輕易辨識出下圖是自由女神像,盡管所有的圖像顯示的角度都不一樣,這是因為人類對圖像的識別并不依賴視角。雖然從沒有見過和下圖一模一樣的圖片,但仍然能立刻知道這些是自由女神像。

如何用飛槳復(fù)現(xiàn)Capsule Network

此外,人造神經(jīng)元輸出單個標(biāo)量表示結(jié)果,而膠囊可以輸出向量作為結(jié)果。CNN使用卷積層獲取特征矩陣,為了在神經(jīng)元的活動中實現(xiàn)視角不變性,通過最大池化方法來達(dá)成這一點。但是使用最大池化的致命缺點就是丟失了有價值的信息,也沒有處理特征之間的相對空間關(guān)系。但是在膠囊網(wǎng)絡(luò)中,特征狀態(tài)的重要信息將以向量的形式被膠囊封裝。

膠囊的工作原理

讓我們比較下膠囊與人造神經(jīng)元。下表中Vector表示向量,scalar表示標(biāo)量,Operation對比了它們工作原理的差異。

如何用飛槳復(fù)現(xiàn)Capsule Network

下面將詳剖析這4個步驟的實現(xiàn)原理:

如何用飛槳復(fù)現(xiàn)Capsule Network

低層膠囊通過加權(quán)把向量輸入高層膠囊,同時高層膠囊接收到來自低層膠囊的向量。所有輸入以紅點和藍(lán)點表示。這些點聚集的地方,意味著低層膠囊的預(yù)測互相接近。

比如,膠囊JK中都有一組聚集的紅點,因為這些膠囊的預(yù)測很接近。在膠囊J中,低層膠囊的輸出乘以相應(yīng)的矩陣W后,落在了遠(yuǎn)離膠囊J中的紅色聚集區(qū)的地方;而在膠囊K中,它落在紅色聚集區(qū)邊緣,紅色聚集區(qū)表示了這個高層膠囊的預(yù)測結(jié)果。低層膠囊具備測量哪個高層膠囊更能接受其輸出的機(jī)制,并據(jù)此自動調(diào)整權(quán)重,使對應(yīng)膠囊K的權(quán)重C變高,對應(yīng)膠囊J的權(quán)重C變低。

關(guān)于權(quán)重,我們需要關(guān)注:

1. 權(quán)重均為非負(fù)標(biāo)量。

2. 對每個低層膠囊i而言,所有權(quán)重的總和等于1(經(jīng)過softmax函數(shù)加權(quán))。

3. 對每個低層膠囊i而言,權(quán)重的數(shù)量等于高層膠囊的數(shù)量。

4. 這些權(quán)重的數(shù)值由迭代動態(tài)路由算法確定。

對于每個低層膠囊i而言,其權(quán)重定義了傳給每個高層膠囊j的輸出的概率分布。

3. 加權(quán)輸入向量之和

這一步表示輸入的組合,和通常的人工神經(jīng)網(wǎng)絡(luò)類似,只是它是向量的和而不是標(biāo)量的和。

4. 向量到向量的非線性變換

CapsNet的另一大創(chuàng)新是新穎的非線性激活函數(shù),這個函數(shù)接受一個向量,然后在不改變方向的前提下,壓縮它的長度到1以下。

實現(xiàn)代碼如下:

def squash(self,vector):
         '''
        壓縮向量的函數(shù),類似激活函數(shù),向量歸一化
        Args:
            vector:一個4維張量 [batch_size,vector_num,vector_units_num,1]
        Returns:
            一個和x形狀相同,長度經(jīng)過壓縮的向量
            輸入向量|v|(向量長度)越大,輸出|v|越接近1
        '''        vec_abs = fluid.layers.sqrt(fluid.layers.reduce_sum(fluid.layers.square(vector)))  
        scalar_factor = fluid.layers.square(vec_abs) / (1 + fluid.layers.square(vec_abs))  
        vec_squashed = scalar_factor * fluid.layers.elementwise_div(vector, vec_abs) 
        return(vec_squashed)

囊間動態(tài)路由(精髓所在)

低層膠囊將其輸出發(fā)送給對此表示“同意”的高層膠囊。這是動態(tài)路由算法的精髓。

如何用飛槳復(fù)現(xiàn)Capsule Network

囊間動態(tài)路由算法偽代碼

  • 偽代碼的第一行指明了算法的輸入:低層輸入向量經(jīng)過矩陣乘法得到的?,以及路由迭代次數(shù)r。最后一行指明了算法的輸出,高層膠囊的向量Vj。

  • 第2行的bij是一個臨時變量,存放了低層向量對高層膠囊的權(quán)重,它的值會在迭代過程中逐個更新,當(dāng)開始一輪迭代時,它的值經(jīng)過softmax轉(zhuǎn)換成cij。在囊間動態(tài)路由算法開始時,bij的值被初始化為零(但是經(jīng)過softmax后會轉(zhuǎn)換成非零且各個權(quán)重相等的cij)。

  • 第3行表明第4-7行的步驟會被重復(fù)r次(路由迭代次數(shù))。

  • 第4行計算低層膠囊向量的對應(yīng)所有高層膠囊的權(quán)重。bi的值經(jīng)過softmax后會轉(zhuǎn)換成非零權(quán)重ci且其元素總和等于1。

  • 如果是第一次迭代,所有系數(shù)cij的值會相等。例如,如果我們有8個低層膠囊和10個高層膠囊,那么所有cij的權(quán)重都將等于0.1。這樣初始化使不確定性達(dá)到最大值:低層膠囊不知道它們的輸出最適合哪個高層膠囊。當(dāng)然,隨著這一進(jìn)程的重復(fù),這些均勻分布將發(fā)生改變。

  • 第5行,那里將涉及高層膠囊。這一步計算經(jīng)前一步確定的路由系數(shù)加權(quán)后的輸入向量的總和,得到輸出向量sj。

  • 第7行進(jìn)行更新權(quán)重,這是路由算法的精髓所在。我們將每個高層膠囊的向量vj與低層原來的輸入向量?逐元素相乘求和獲得內(nèi)積(也叫點積,點積檢測膠囊的輸入和輸出之間的相似性(下圖為示意圖)),再用點積結(jié)果更新原來的權(quán)重bi。這就達(dá)到了低層膠囊將其輸出發(fā)送給具有類似輸出的高層膠囊的效果,刻畫了向量之間的相似性。這一步驟之后,算法跳轉(zhuǎn)到第3步重新開始這一流程,并重復(fù)r次。

如何用飛槳復(fù)現(xiàn)Capsule Network   

▲ 點積運(yùn)算即為向量的內(nèi)積(點積)運(yùn)算,

可以表現(xiàn)向量的相似性。

重復(fù)次后,我們計算出了所有高層膠囊的輸出,并確立正確路由權(quán)重。下面是根據(jù)上述原理實現(xiàn)的膠囊層:

class Capsule_Layer(fluid.dygraph.Layer):
    def __init__(self,pre_cap_num,pre_vector_units_num,cap_num,vector_units_num):
        '''
        膠囊層的實現(xiàn)類,可以直接同普通層一樣使用
        Args:
            pre_vector_units_num(int):輸入向量維度 
            vector_units_num(int):輸出向量維度 
            pre_cap_num(int):輸入膠囊數(shù) 
            cap_num(int):輸出膠囊數(shù) 
            routing_iters(int):路由迭代次數(shù),建議3次 
        Notes:
            膠囊數(shù)和向量維度影響著性能,可作為主調(diào)參數(shù)
        '''
        super(Capsule_Layer,self).__init__()
        self.routing_iters = 3
        self.pre_cap_num = pre_cap_num
        self.cap_num = cap_num
        self.pre_vector_units_num = pre_vector_units_num
        for j in range(self.cap_num):
            self.add_sublayer('u_hat_w'+str(j),fluid.dygraph.Linear(\
            input_dim=pre_vector_units_num,output_dim=vector_units_num))


    def squash(self,vector):
        '''
        壓縮向量的函數(shù),類似激活函數(shù),向量歸一化
        Args:
            vector:一個4維張量 [batch_size,vector_num,vector_units_num,1]
        Returns:
            一個和x形狀相同,長度經(jīng)過壓縮的向量
            輸入向量|v|(向量長度)越大,輸出|v|越接近1
        '''
        vec_abs = fluid.layers.sqrt(fluid.layers.reduce_sum(fluid.layers.square(vector)))
        scalar_factor = fluid.layers.square(vec_abs) / (1 + fluid.layers.square(vec_abs))
        vec_squashed = scalar_factor * fluid.layers.elementwise_div(vector, vec_abs)
        return(vec_squashed)

    def capsule(self,x,B_ij,j,pre_cap_num):
        '''
        這是動態(tài)路由算法的精髓。
        Args:
            x:輸入向量,一個四維張量 shape = (batch_size,pre_cap_num,pre_vector_units_num,1)
            B_ij: shape = (1,pre_cap_num,cap_num,1)路由分配權(quán)重,這里將會選取(split)其中的第j組權(quán)重進(jìn)行計算
            j:表示當(dāng)前計算第j個膠囊的路由
            pre_cap_num:輸入膠囊數(shù)
        Returns:
            v_j:經(jīng)過多次路由迭代之后輸出的4維張量(單個膠囊)
            B_ij:計算完路由之后又拼接(concat)回去的權(quán)重
        Notes:
            B_ij,b_ij,C_ij,c_ij注意區(qū)分大小寫哦
        '''
        x = fluid.layers.reshape(x,(x.shape[0],pre_cap_num,-1))
        u_hat = getattr(self,'u_hat_w'+str(j))(x)
        u_hat = fluid.layers.reshape(u_hat,(x.shape[0],pre_cap_num,-1,1))
        shape_list = B_ij.shape#(1,1152,10,1)
        split_size = [j,1,shape_list[2]-j-1]
        for i in range(self.routing_iters):
            C_ij = fluid.layers.softmax(B_ij,axis=2)
            b_il,b_ij,b_ir = fluid.layers.split(B_ij,split_size,dim=2)
            c_il,c_ij,b_ir = fluid.layers.split(C_ij,split_size,dim=2)
            v_j = fluid.layers.elementwise_mul(u_hat,c_ij) 
v_j = fluid.layers.reduce_sum(v_j,dim=1,keep_dim=True)
            v_j = self.squash(v_j)
            v_j_expand = fluid.layers.expand(v_j,(1,pre_cap_num,1,1))
            u_v_produce = fluid.layers.elementwise_mul(u_hat,v_j_expand)
            u_v_produce = fluid.layers.reduce_sum(u_v_produce,dim=2,keep_dim=True) 
            b_ij += fluid.layers.reduce_sum(u_v_produce,dim=0,keep_dim=True)
            B_ij = fluid.layers.concat([b_il,b_ij,b_ir],axis=2)
        return v_j,B_ij

    def forward(self,x):
        '''
        Args:
            x:shape = (batch_size,pre_caps_num,vector_units_num,1) or (batch_size,C,H,W)
                如果是輸入是shape=(batch_size,C,H,W)的張量,
                則將其向量化shape=(batch_size,pre_caps_num,vector_units_num,1)
                滿足:C * H * W = vector_units_num * caps_num
                其中 C >= caps_num
        Returns:
            capsules:一個包含了caps_num個膠囊的list
        '''
        if x.shape[3]!=1:
            x = fluid.layers.reshape(x,(x.shape[0],self.pre_cap_num,-1))
            temp_x = fluid.layers.split(x,self.pre_vector_units_num,dim=2)
            temp_x = fluid.layers.concat(temp_x,axis=1)
            x = fluid.layers.reshape(temp_x,(x.shape[0],self.pre_cap_num,-1,1))
            x = self.squash(x)
        B_ij = fluid.layers.ones((1,x.shape[1],self.cap_num,1),dtype='float32')/self.cap_num#
        capsules = []
        for j in range(self.cap_num):
            cap_j,B_ij = self.capsule(x,B_ij,j,self.pre_cap_num)
            capsules.append(cap_j)
        capsules = fluid.layers.concat(capsules,axis=1)
        return capsules

損失函數(shù)

將一個10維one-hot編碼向量作為標(biāo)簽,該向量由9個零和1個一(正確標(biāo)簽)組成。在損失函數(shù)公式中,與正確的標(biāo)簽對應(yīng)的輸出膠囊,系數(shù)Tc為1。

如果正確標(biāo)簽是9,這意味著第9個膠囊輸出的損失函數(shù)的Tc為1,其余9個為0。當(dāng)Tc為1時,公式中損失函數(shù)的右項系數(shù)為零,也就是說正確輸出項損失函數(shù)的值只包含了左項計算;相應(yīng)的左系數(shù)為0,則右項系數(shù)為1,錯誤輸出項損失函數(shù)的值只包含了右項計算。

|v|為膠囊輸出向量的模長,一定程度上表示了類概率的大小,我們再擬定一個量m,用這個變量來衡量概率是否合適。公式右項包括了一個lambda系數(shù)以確保訓(xùn)練中的數(shù)值穩(wěn)定性(lambda為固定值0.5),這兩項取平方是為了讓損失函數(shù)符合L2正則。

 def get_loss_v(self,label):
        '''
        計算邊緣損失
        Args:
            label:shape=(32,10) one-hot形式的標(biāo)簽
        Notes:
            這里我調(diào)用Relu把小于0的值篩除
            m_plus:正確輸出項的概率(|v|)大于這個值則loss為0,越接近則loss越小
            m_det:錯誤輸出項的概率(|v|)小于這個值則loss為0,越接近則loss越小
            (|v|即膠囊(向量)的模長)
        '''        #計算左項,雖然m+是單個值,但是可以通過廣播的形式與label(32,10)作差        max_l =  fluid.layers.relu(train_params['m_plus'] - self.output_caps_v_lenth)
        #平方運(yùn)算后reshape        max_l = fluid.layers.reshape(fluid.layers.square(max_l),(train_params['batch_size'],-1))#32,10        #同樣方法計算右項        max_r =  fluid.layers.relu(self.output_caps_v_lenth - train_params['m_det'])
        max_r = fluid.layers.reshape(fluid.layers.square(max_r),(train_params['batch_size'],-1))#32,10        #合并的時候直接用one-hot形式的標(biāo)簽逐元素乘算便可        margin_loss = fluid.layers.elementwise_mul(label,max_l)\
                        + fluid.layers.elementwise_mul(1-label,max_r)*train_params['lambda_val']
        self.margin_loss = fluid.layers.reduce_mean(margin_loss,dim=1)

編碼器

完整的網(wǎng)絡(luò)結(jié)構(gòu)分為編碼器解碼器,我們先來看看編碼器。

如何用飛槳復(fù)現(xiàn)Capsule Network

1. 輸入圖片28x28首先經(jīng)過1x256x9x9的卷積層 獲得256個20x20的特征圖;

2. 用8組256x32x9x9(stride=2)的卷積獲得8組32x6x6的特征圖;

3. 將獲取的特征圖向量化輸入10個膠囊,這10個膠囊輸出向量的長度就是各個類別的概率。

class Capconv_Net(fluid.dygraph.Layer):
    def __init__(self):
        super(Capconv_Net,self).__init__()
        self.add_sublayer('conv0',fluid.dygraph.Conv2D(\
        num_channels=1,num_filters=256,filter_size=(9,9),padding=0,stride = 1,act='relu'))
                for i in range(8):
            self.add_sublayer('conv_vector_'+str(i),fluid.dygraph.Conv2D(\
            num_channels=256,num_filters=32,filter_size=(9,9),stride=2,padding=0,act='relu'))

    def forward(self,x,v_units_num):
        x = getattr(self,'conv0')(x)
        capsules = []
        for i in range(v_units_num):
            temp_x = getattr(self,'conv_vector_'+str(i))(x)
            capsules.append(fluid.layers.reshape(temp_x,(train_params['batch_size'],-1,1,1)))
        x = fluid.layers.concat(capsules,axis=2)        x = self.squash(x)
        return x

從實現(xiàn)代碼中我們不難看出特征圖轉(zhuǎn)換成向量實際的過程,是將每組二維矩陣展開成一維矩陣(當(dāng)然有多個二維矩陣則展開后前后拼接);之后再將所有組的一維矩陣在新的維度拼接形成向量(下圖為示意圖)。根據(jù)下面這個思路我經(jīng)把8次卷積縮小到了一次卷積,本質(zhì)上脫離循環(huán)只用split和concat方法直接向量化,加快了訓(xùn)練效率。

如何用飛槳復(fù)現(xiàn)Capsule Network

解碼器從正確的膠囊中接受一個16維向量,輸入經(jīng)過三個全連接層得到784個像素輸出,學(xué)習(xí)重建一張28×28像素的圖像,損失函數(shù)為重建圖像與輸入圖像之間的歐氏距離。

下圖是我自己訓(xùn)練的網(wǎng)絡(luò)重構(gòu)獲得的圖像,上面是輸入網(wǎng)絡(luò)的原圖片,下面是網(wǎng)絡(luò)重建的圖片。

如何用飛槳復(fù)現(xiàn)Capsule Network

再來玩一下,當(dāng)訓(xùn)練到一半時將所有圖片轉(zhuǎn)置(可以理解為將圖片水平垂直翻轉(zhuǎn)+旋轉(zhuǎn)角度,改變位姿)的情況,實驗結(jié)論如下。

如何用飛槳復(fù)現(xiàn)Capsule Network

看完上述內(nèi)容,你們掌握如何用飛槳復(fù)現(xiàn)Capsule Network的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!

新聞標(biāo)題:如何用飛槳復(fù)現(xiàn)CapsuleNetwork
本文鏈接:http://muchs.cn/article16/jsoigg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站設(shè)計、虛擬主機(jī)、外貿(mào)網(wǎng)站建設(shè)響應(yīng)式網(wǎng)站、微信公眾號、定制網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站建設(shè)網(wǎng)站維護(hù)公司