您好,登錄后才能下訂單哦!
如何用飛槳復(fù)現(xiàn)Capsule Network,相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。
下面讓我們一起來(lá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)異,但是針對(duì)于旋轉(zhuǎn)或元素平移等變換后的圖片,卻無(wú)法做到準(zhǔn)確提取特征。
比如,對(duì)下圖中字母R進(jìn)行旋轉(zhuǎn)、加邊框,CNN會(huì)錯(cuò)誤地認(rèn)為下圖的三個(gè)R是不同的字母。
這就引出了位姿的概念。位姿結(jié)合了對(duì)象之間的相對(duì)關(guān)系,在數(shù)值上表示為4維位姿矩陣。三維對(duì)象之間的關(guān)系可以用位姿表示,位姿的本質(zhì)是對(duì)象的平移和旋轉(zhuǎn)。
對(duì)于人類而言,可以輕易辨識(shí)出下圖是自由女神像,盡管所有的圖像顯示的角度都不一樣,這是因?yàn)槿祟悓?duì)圖像的識(shí)別并不依賴視角。雖然從沒(méi)有見(jiàn)過(guò)和下圖一模一樣的圖片,但仍然能立刻知道這些是自由女神像。
此外,人造神經(jīng)元輸出單個(gè)標(biāo)量表示結(jié)果,而膠囊可以輸出向量作為結(jié)果。CNN使用卷積層獲取特征矩陣,為了在神經(jīng)元的活動(dòng)中實(shí)現(xiàn)視角不變性,通過(guò)最大池化方法來(lái)達(dá)成這一點(diǎn)。但是使用最大池化的致命缺點(diǎn)就是丟失了有價(jià)值的信息,也沒(méi)有處理特征之間的相對(duì)空間關(guān)系。但是在膠囊網(wǎng)絡(luò)中,特征狀態(tài)的重要信息將以向量的形式被膠囊封裝。
膠囊的工作原理
讓我們比較下膠囊與人造神經(jīng)元。下表中Vector表示向量,scalar表示標(biāo)量,Operation對(duì)比了它們工作原理的差異。
下面將詳剖析這4個(gè)步驟的實(shí)現(xiàn)原理:
低層膠囊通過(guò)加權(quán)把向量輸入高層膠囊,同時(shí)高層膠囊接收到來(lái)自低層膠囊的向量。所有輸入以紅點(diǎn)和藍(lán)點(diǎn)表示。這些點(diǎn)聚集的地方,意味著低層膠囊的預(yù)測(cè)互相接近。
比如,膠囊J和K中都有一組聚集的紅點(diǎn),因?yàn)檫@些膠囊的預(yù)測(cè)很接近。在膠囊J中,低層膠囊的輸出乘以相應(yīng)的矩陣W后,落在了遠(yuǎn)離膠囊J中的紅色聚集區(qū)的地方;而在膠囊K中,它落在紅色聚集區(qū)邊緣,紅色聚集區(qū)表示了這個(gè)高層膠囊的預(yù)測(cè)結(jié)果。低層膠囊具備測(cè)量哪個(gè)高層膠囊更能接受其輸出的機(jī)制,并據(jù)此自動(dòng)調(diào)整權(quán)重,使對(duì)應(yīng)膠囊K的權(quán)重C變高,對(duì)應(yīng)膠囊J的權(quán)重C變低。
關(guān)于權(quán)重,我們需要關(guān)注:
1. 權(quán)重均為非負(fù)標(biāo)量。
2. 對(duì)每個(gè)低層膠囊i而言,所有權(quán)重的總和等于1(經(jīng)過(guò)softmax函數(shù)加權(quán))。
3. 對(duì)每個(gè)低層膠囊i而言,權(quán)重的數(shù)量等于高層膠囊的數(shù)量。
4. 這些權(quán)重的數(shù)值由迭代動(dòng)態(tài)路由算法確定。
對(duì)于每個(gè)低層膠囊i而言,其權(quán)重定義了傳給每個(gè)高層膠囊j的輸出的概率分布。
3. 加權(quán)輸入向量之和
這一步表示輸入的組合,和通常的人工神經(jīng)網(wǎng)絡(luò)類似,只是它是向量的和而不是標(biāo)量的和。
4. 向量到向量的非線性變換
CapsNet的另一大創(chuàng)新是新穎的非線性激活函數(shù),這個(gè)函數(shù)接受一個(gè)向量,然后在不改變方向的前提下,壓縮它的長(zhǎng)度到1以下。
實(shí)現(xiàn)代碼如下:
def squash(self,vector): ''' 壓縮向量的函數(shù),類似激活函數(shù),向量歸一化 Args: vector:一個(gè)4維張量 [batch_size,vector_num,vector_units_num,1] Returns: 一個(gè)和x形狀相同,長(zhǎng)度經(jīng)過(guò)壓縮的向量 輸入向量|v|(向量長(zhǎng)度)越大,輸出|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)
囊間動(dòng)態(tài)路由(精髓所在)
低層膠囊將其輸出發(fā)送給對(duì)此表示“同意”的高層膠囊。這是動(dòng)態(tài)路由算法的精髓。
▲ 囊間動(dòng)態(tài)路由算法偽代碼
偽代碼的第一行指明了算法的輸入:低層輸入向量經(jīng)過(guò)矩陣乘法得到的?,以及路由迭代次數(shù)r。最后一行指明了算法的輸出,高層膠囊的向量Vj。
第2行的bij是一個(gè)臨時(shí)變量,存放了低層向量對(duì)高層膠囊的權(quán)重,它的值會(huì)在迭代過(guò)程中逐個(gè)更新,當(dāng)開(kāi)始一輪迭代時(shí),它的值經(jīng)過(guò)softmax轉(zhuǎn)換成cij。在囊間動(dòng)態(tài)路由算法開(kāi)始時(shí),bij的值被初始化為零(但是經(jīng)過(guò)softmax后會(huì)轉(zhuǎn)換成非零且各個(gè)權(quán)重相等的cij)。
第3行表明第4-7行的步驟會(huì)被重復(fù)r次(路由迭代次數(shù))。
第4行計(jì)算低層膠囊向量的對(duì)應(yīng)所有高層膠囊的權(quán)重。bi的值經(jīng)過(guò)softmax后會(huì)轉(zhuǎn)換成非零權(quán)重ci且其元素總和等于1。
如果是第一次迭代,所有系數(shù)cij的值會(huì)相等。例如,如果我們有8個(gè)低層膠囊和10個(gè)高層膠囊,那么所有cij的權(quán)重都將等于0.1。這樣初始化使不確定性達(dá)到最大值:低層膠囊不知道它們的輸出最適合哪個(gè)高層膠囊。當(dāng)然,隨著這一進(jìn)程的重復(fù),這些均勻分布將發(fā)生改變。
第5行,那里將涉及高層膠囊。這一步計(jì)算經(jīng)前一步確定的路由系數(shù)加權(quán)后的輸入向量的總和,得到輸出向量sj。
第7行進(jìn)行更新權(quán)重,這是路由算法的精髓所在。我們將每個(gè)高層膠囊的向量vj與低層原來(lái)的輸入向量?逐元素相乘求和獲得內(nèi)積(也叫點(diǎn)積,點(diǎn)積檢測(cè)膠囊的輸入和輸出之間的相似性(下圖為示意圖)),再用點(diǎn)積結(jié)果更新原來(lái)的權(quán)重bi。這就達(dá)到了低層膠囊將其輸出發(fā)送給具有類似輸出的高層膠囊的效果,刻畫了向量之間的相似性。這一步驟之后,算法跳轉(zhuǎn)到第3步重新開(kāi)始這一流程,并重復(fù)r次。
▲ 點(diǎn)積運(yùn)算即為向量的內(nèi)積(點(diǎn)積)運(yùn)算,
可以表現(xiàn)向量的相似性。
重復(fù)次后,我們計(jì)算出了所有高層膠囊的輸出,并確立正確路由權(quán)重。下面是根據(jù)上述原理實(shí)現(xiàn)的膠囊層:
class Capsule_Layer(fluid.dygraph.Layer): def __init__(self,pre_cap_num,pre_vector_units_num,cap_num,vector_units_num): ''' 膠囊層的實(shí)現(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:一個(gè)4維張量 [batch_size,vector_num,vector_units_num,1] Returns: 一個(gè)和x形狀相同,長(zhǎng)度經(jīng)過(guò)壓縮的向量 輸入向量|v|(向量長(zhǎng)度)越大,輸出|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): ''' 這是動(dòng)態(tài)路由算法的精髓。 Args: x:輸入向量,一個(gè)四維張量 shape = (batch_size,pre_cap_num,pre_vector_units_num,1) B_ij: shape = (1,pre_cap_num,cap_num,1)路由分配權(quán)重,這里將會(huì)選取(split)其中的第j組權(quán)重進(jìn)行計(jì)算 j:表示當(dāng)前計(jì)算第j個(gè)膠囊的路由 pre_cap_num:輸入膠囊數(shù) Returns: v_j:經(jīng)過(guò)多次路由迭代之后輸出的4維張量(單個(gè)膠囊) B_ij:計(jì)算完路由之后又拼接(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:一個(gè)包含了caps_num個(gè)膠囊的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ù)
將一個(gè)10維one-hot編碼向量作為標(biāo)簽,該向量由9個(gè)零和1個(gè)一(正確標(biāo)簽)組成。在損失函數(shù)公式中,與正確的標(biāo)簽對(duì)應(yīng)的輸出膠囊,系數(shù)Tc為1。
如果正確標(biāo)簽是9,這意味著第9個(gè)膠囊輸出的損失函數(shù)的Tc為1,其余9個(gè)為0。當(dāng)Tc為1時(shí),公式中損失函數(shù)的右項(xiàng)系數(shù)為零,也就是說(shuō)正確輸出項(xiàng)損失函數(shù)的值只包含了左項(xiàng)計(jì)算;相應(yīng)的左系數(shù)為0,則右項(xiàng)系數(shù)為1,錯(cuò)誤輸出項(xiàng)損失函數(shù)的值只包含了右項(xiàng)計(jì)算。
|v|為膠囊輸出向量的模長(zhǎng),一定程度上表示了類概率的大小,我們?cè)贁M定一個(gè)量m,用這個(gè)變量來(lái)衡量概率是否合適。公式右項(xiàng)包括了一個(gè)lambda系數(shù)以確保訓(xùn)練中的數(shù)值穩(wěn)定性(lambda為固定值0.5),這兩項(xiàng)取平方是為了讓損失函數(shù)符合L2正則。
def get_loss_v(self,label): ''' 計(jì)算邊緣損失 Args: label:shape=(32,10) one-hot形式的標(biāo)簽 Notes: 這里我調(diào)用Relu把小于0的值篩除 m_plus:正確輸出項(xiàng)的概率(|v|)大于這個(gè)值則loss為0,越接近則loss越小 m_det:錯(cuò)誤輸出項(xiàng)的概率(|v|)小于這個(gè)值則loss為0,越接近則loss越小 (|v|即膠囊(向量)的模長(zhǎng)) ''' #計(jì)算左項(xiàng),雖然m+是單個(gè)值,但是可以通過(guò)廣播的形式與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 #同樣方法計(jì)算右項(xiàng) 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 #合并的時(shí)候直接用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)分為編碼器和解碼器,我們先來(lái)看看編碼器。
1. 輸入圖片28x28首先經(jīng)過(guò)1x256x9x9的卷積層 獲得256個(gè)20x20的特征圖;
2. 用8組256x32x9x9(stride=2)的卷積獲得8組32x6x6的特征圖;
3. 將獲取的特征圖向量化輸入10個(gè)膠囊,這10個(gè)膠囊輸出向量的長(zhǎng)度就是各個(gè)類別的概率。
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
從實(shí)現(xiàn)代碼中我們不難看出特征圖轉(zhuǎn)換成向量實(shí)際的過(guò)程,是將每組二維矩陣展開(kāi)成一維矩陣(當(dāng)然有多個(gè)二維矩陣則展開(kāi)后前后拼接);之后再將所有組的一維矩陣在新的維度拼接形成向量(下圖為示意圖)。根據(jù)下面這個(gè)思路我經(jīng)把8次卷積縮小到了一次卷積,本質(zhì)上脫離循環(huán)只用split和concat方法直接向量化,加快了訓(xùn)練效率。
解碼器從正確的膠囊中接受一個(gè)16維向量,輸入經(jīng)過(guò)三個(gè)全連接層得到784個(gè)像素輸出,學(xué)習(xí)重建一張28×28像素的圖像,損失函數(shù)為重建圖像與輸入圖像之間的歐氏距離。
下圖是我自己訓(xùn)練的網(wǎng)絡(luò)重構(gòu)獲得的圖像,上面是輸入網(wǎng)絡(luò)的原圖片,下面是網(wǎng)絡(luò)重建的圖片。
再來(lái)玩一下,當(dāng)訓(xùn)練到一半時(shí)將所有圖片轉(zhuǎn)置(可以理解為將圖片水平垂直翻轉(zhuǎn)+旋轉(zhuǎn)角度,改變位姿)的情況,實(shí)驗(yàn)結(jié)論如下。
看完上述內(nèi)容,你們掌握如何用飛槳復(fù)現(xiàn)Capsule Network的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。