capsule系列之Dynamic Routing Between Capsules

capsule在出現之後就除了Hinton老爺子的兩個版本之外,還有例如Investigating Capsule Networks with Dynamic Routing for Text Classification這樣的新作,可見capsule的很多理念都是work的,因爲打算好好拜讀幾篇paper,做一個系列,深刻理解capsule。
本篇文章我覺得確實還是有難度的,作爲一篇筆記博客,爲了能夠比較透徹的理解,在原文基礎上參考了很多的文章,並添加了相關自己的理解,參考文獻會在最後章節詳細列出。
論文下載地址:zsweet github

1.背景

一個新的事物出現,必然以藏着以往事物的缺陷,那麼capsule因何而生呢?
在講膠囊網絡之前,首先我們回顧一下我們熟悉的CNN:
在這裏插入圖片描述
CNN做了什麼事情呢? 假設這裏有一個卷積核(左圖),除了曲線軌跡上的值很大,其他的值都是零,所以這個卷積覈對旁邊這種曲線(右圖)就有很高的輸出,也就是說對這種曲線有很高的識別,而對其他的曲線類型輸出就低。

所以比如圖像分類中,一旦卷積核檢測到了類似於眼睛啊、鼻子啊、嘴巴啊這種特徵;從數學角度上說就,相關卷積覈對鼻子、眼睛等卷積出來的值很大,那麼與人臉相關的神經元就相當興奮,最後將圖像分類到人臉這一類。

所以這就導致了一個問題。如圖,右邊那張眼睛、鼻子、嘴巴都有了,當然我們的CNN也相當興奮的將它歸於人臉。

這就就暴露了CNN的問題:

  • 組件的朝向和空間上的相對關係對它來說不重要,它只在乎有沒有特徵。
  • CNN還有一個問題,那就是池化層。Hinton自己就說過:最大池化層表現的如此優異是一個巨大的錯誤,是一場災難。誠然,從網絡設計上來說,池化層不僅減少了參數,還可以避免過擬合。但是,它的確拋棄了一些信息,比如位置信息。

再比如說,下面這張圖,儘管拍攝的角度不同,但你的大腦可以輕易的辨識這些都是同一對象,CNN卻沒有這樣的能力。它不能舉一反三,它只能通過擴大訓練的數據量才能得到相似的能力。
在這裏插入圖片描述

2.什麼是capsule

現在網上有關於capsule多數翻譯成膠囊,但是我覺太缺乏學術性,知乎上的
雲夢居客(文章裏有詳細的ppt和錄播視頻)大佬把它稱作“矢量神經元”或者“張量神經元”,我覺得比較合適,但是很多人可能又沒聽說過這種說法,所以還是直接稱之爲capsule吧!(懶癌犯了…)

論文中說道,capsule是一個實體,那實體又是什麼?一般是指能夠獨立存在的、作爲一切屬性的基礎和萬物本原的東西(百度百科)。capsule就是一個完整概念單元的封裝,能夠表徵不同屬性。

這次Capsule盛宴的特色是“vector in vector out”,取代了以往的“scaler in scaler out”,也就是神經元的輸入輸出都變成了向量,從而算是對神經網絡理論的一次革命。然而真的是這樣子嗎?難道我們以往就沒有做過“vector in vector out”的任務了嗎?有,而且多的是!NLP中,一個詞向量序列的輸入,不就可以看成“vector in”了嗎?這個詞向量序列經過RNN/CNN/Attention的編碼,輸出一個新序列,不就是“vector out”了嗎?在目前的深度學習中,從來不缺乏“vector in vector out”的案例,因此顯然這不能算是Capsule的革命。

Capsule的革命在於:它提出了一種新的“vector in vector out”的傳遞和權重更新方案,並且這種方案在很大程度上是可解釋的。

那最後,什麼是capsule?
其實,只要把一個向量當作一個整體來看,它就是一個capsule。 你可以這樣理解:神經元就是標量,膠囊就是向量,就這麼粗暴!Hinton的理解是:每一個膠囊表示一個屬性,而膠囊的向量則表示這個屬性的“標架”。也就是說,我們以前只是用一個標量表示有沒有這個特徵(比如有沒有羽毛),現在我們用一個向量來表示,不僅僅表示有沒有,還表示“有什麼樣的”(比如有什麼顏色、什麼紋理的羽毛),如果這樣理解,就是說在對單個特徵的表達上更豐富了。

說到這裏,我感覺有點像NLP中的詞向量,以前我們只是用one hot來表示一個詞,也就是表示有沒有這個詞而已。現在我們用詞向量來表示一個詞,顯然詞向量表達的特徵更豐富,不僅可以表示有沒有,還可以表示哪些詞有相近含義。詞向量就是NLP中的“膠囊”?這個類比可能有點牽強,但我覺得意思已經對了。

3.capsule原理和結構

在論文中,Geoffrey Hinton 介紹 Capsule 爲:「Capsule 是一組神經元,其輸入輸出向量表示特定實體類型的實例化參數(即特定物體、概念實體等出現的概率與某些屬性)。我們使用輸入輸出向量的長度表徵實體存在的概率,向量的方向表示實例化參數(即實體的某些圖形屬性)。同一層級的 capsule 通過變換矩陣對更高級別的 capsule 的實例化參數進行預測。當多個預測一致時(本論文使用動態路由使預測一致),更高級別的 capsule 將變得活躍。
Capsule 中的神經元的激活情況表示了圖像中存在的特定實體的各種性質。這些性質可以包含很多種不同的參數,例如姿勢(位置,大小,方向)、變形、速度、反射率,色彩、紋理等等。而輸入輸出向量的長度表示了某個實體出現的概率,所以它的值必須在 0 到 1 之間。

3.1.capsule結構

下面我們來看看capsule的結構。
Capsule針對着“層層遞進”的目標來設計的,我們先來看一張整體的capsule結構圖,更好理解:
在這裏插入圖片描述

圖3-1-1.capsule結構

上圖展示了Capsule整體結構:Capsule層級結構動態 Routing 的過程
其中最下面的層級uiu_i共有兩個 Capsule 單元,該層級傳遞到下一層級 vjv_j 共有四個 Capsule。

capsule的運行流程大致如下:

  • u1u_1u2u_2 是一個向量,即含有一組神經元的 Capsule 單元,它們分別與不同的權重 WijW_{ij}(同樣是向量,更準確來說是矩陣)相乘得出 u^ji\widehat {u}_{j|i}。例如 u1u_1W12W_{12} 相乘得出預測向量 u^21\widehat{u}_{2|1}
  • 預測向量和對應的「耦合係數」cijc_{ij} 相乘並傳入特定的後一層 Capsule 單元得到sjs_j。不同 Capsule 單元的輸入 sjs_j 是所有可能傳入該單元的加權和,即所有可能傳入的預測向量與耦合係數的乘積和。
  • 將向量sjs_j投入到「squashing」非線性函數就能得出後一層 Capsule 單元的輸出向量 vjv_j
  • 利用該輸出向量 vjv_j 和對應預測向量 u^ji\widehat{u}_{j|i} 的乘積更新耦合係數 cijc_{ij},這樣的迭代更新不需要應用反向傳播。

其實capsule就是一個向量版的全連接層,與傳統的全連接對比如下:
在這裏插入圖片描述

關於capsule的構建過程,我覺得蘇建林的這篇文章說的挺好的,不愧是數學出身的大佬,大家可以拜讀一下。

3.2.Dynamic Routing 算法

按照 Hinton 的思想,找到最好的處理路徑就等價於正確處理了圖像,所以在 Capsule 中加入 Routing 機制可以找到一組係數 cijc_{ij},它們能令預測向量 u^ji\widehat{u}_{j|i} 最符合輸出向量 vjv_j,即最符合輸出的輸入向量,這樣我們就找到了最好的路徑。

從上面的capsule結果和運行過程可以看出,爲了求vjv_j需要先求cijc_{ij},可是爲了求cijc_{ij}又需要知道vjv_j,這不是個雞生蛋、蛋生雞的問題了嗎?

之前瞭解過EM算法的小夥伴應該有印象,可以說是特別像了,所以分類聚類雖有本質的區別,可是還是相通的啊,關於EM算法可以參考我之前的文章從最大似然到EM算法淺解

那怎麼辦呢?
這就需要像EM一樣通過迭代的方式了,在這裏起個名字叫“動態路由”(Dynamic Routing),它能夠根據自身的特性來更新(部分)參數,從而初步達到了Hinton的放棄梯度下降的目標。具體的過程就是:爲了得到各個vj,一開始先讓它們全都等於ui的均值,然後反覆迭代就好。說白了,輸出是輸入的聚類結果,而聚類通常都需要迭代算法,這個迭代算法就稱爲“動態路由”

至於這個動態路由的細節,其實是不固定的,取決於聚類的算法,比如關於Capsule的新文章《MATRIX CAPSULES WITH EM ROUTING》就使用了Gaussian Mixture Model來聚類。

本文給出的動態路由過程如下:
在這裏插入圖片描述
這裏解釋下,怎麼又冒出一個bijb_{ij}來?其實cijc_{ij}就是通過對bijb_{ij}進行softmax得來的,你可以把它看成一個東西,只不過bijb_{ij}是規格化了的。

Dynamic Routing的過程可以總結爲
對於所有在 ll 層的 Capsule i 和在 l+1l+1 層的 Capsule j,先初始化 bijb_{ij} 等於零。然後迭代 r 次,每次先根據 bib_i 計算 cic_i,然後在利用 cijc_{ij}u^ji\widehat {u}_{j|i} 計算 sjs_jvjv_j。利用計算出來的 vjv_j 更新 bijb_{ij} 以進入下一個迭代循環更新 cijc_{ij}。該 Routing 算法十分容易收斂,基本上通過 3 次迭代就能有不錯的效果。

3.3.小部件

上面從上而下講完了capule結構和routing過程,但是有些小的結構(我成爲小部件)沒有詳述,這裏整理一下。

3.3.1.爲耦合係數(coupling coefficients)

cijc_{ij}係數由動態 Routing 過程迭代地更新與確定。Capsule i 和後一層級所有 Capsule j間的耦合係數和爲 1,即如圖3-1-1中, c11+c12+c13+c14=1c_{11}+c_{12}+c_{13}+c_{14}=1。此外,該耦合係數由「routing softmax」決定,且 softmax 函數中的 logits bijlogits\ b_{ij} 初始化爲 0,耦合係數 cijc_{ij} 的 softmax 計算方式爲:
cij=exp(bij)kexp(bik)c_{ij} = \frac{exp(b_{ij})}{\sum _k exp(b_{ik})}
我們可以通過測量後面層級中每一個 Capsule j 的當前輸出 vjv_j 和 前面層級 Capsule i 的預測向量間的一致性,然後藉助該測量的一致性迭代地更新耦合係數。本論文簡單地通過內積度量這種一致性,即:
aij=vju^jia_{ij} = v_j \cdot \widehat{u}_{j|i}
然後用aija_{ij}來間接更新cijc_{ij} (下面詳述)
這裏可以認爲vjv_j實際上就是各個u^i\widehat {u}_i的某個聚類中心(這裏蘇建林說的是和uiu_i,我覺得不妥),這裏的aija_{ij}可以看做是和聚類中心的距離,從而返過來更新cijc_{ij}

3.3.1.b_ij權值更新

上面說到了通過bijb_{ij}求softmax來得到cijc_{ij},那麼這個bijb_{ij}怎麼得到呢?前面也提到了,通過vjv_ju^ji\widehat u_{j|i}的內積結果來更新。具體是:
bijbij+u^jivjb_{ij} \leftarrow b_{ij} + \widehat u_{j|i} \cdot \mathbf v_j
但是這爲什麼呢?爲什麼能這麼更新呢?
看下面這張圖:
在這裏插入圖片描述
點積運算接收兩個向量,並輸出一個標量。對於給定長度但方向不同的的兩個向量而言,點積有下列幾種情況:正值、零、負值。故當u^ji\widehat u_{j|i}vj\mathbf v_j的相乘結果爲正時,代表兩個向量指向的方向相似,b更新結果變大,那麼耦合係數就高,說明該u^ji\widehat u_{j|i}vj\mathbf v_j十分匹配。相反,若是u^ji\widehat u_{j|i}vj\mathbf v_j相乘結果爲負,b更新結果變小,那麼耦合係數就小,說明不匹配。通過迭代確定C,也就等於確定了一條路線,這條路線上膠囊神經元的模都特別大,路線的盡頭就是那個正確預測的膠囊。

根據論文描述,b的迭代更新次數取值爲3比較好。

3.3.2.squashing

可以看到在從sjs_j得到vjv_j時進行了非線性激活和scale,那這個函數又是怎麼來的呢?爲什麼用這個函數呢?
文章開篇我們引用過Hinton老爺子的話,使用輸入輸出向量的長度表徵實體(特徵)的概率(也可以稱爲可以稱爲是特徵的“顯著程度”,這就好解釋了,模長越大,這個特徵越顯著。),所以需要對sjs_j向量進行歸一化,也就是壓縮,將他的模長壓縮到0-1之間,才能表徵概率。
Hinton選取的壓縮方案是:
(3)squash(x)=x21+x2xxsquash(\boldsymbol{x})=\frac{\Vert\boldsymbol{x}\Vert^2}{1+\Vert\boldsymbol{x}\Vert^2}\frac{\boldsymbol{x}}{\Vert\boldsymbol{x}\Vert}\tag{3}

其中x/x\boldsymbol{x}/\Vert\boldsymbol{x}\Vert是很好理解的,就是將模長變爲1,那麼前半部分怎麼理解呢?爲什麼這樣選擇?事實上,將模長壓縮到0~1的方案有很多,比如:
tanhx,1ex,x1+x\tanh \Vert\boldsymbol{x}\Vert, \quad 1-e^{-\Vert\boldsymbol{x}\Vert}, \quad \frac{\Vert\boldsymbol{x}\Vert}{1+\Vert\boldsymbol{x}\Vert}

等等,並不確定Hinton選擇目前這個方案的思路。也許可以每個方案都探索一下?事實上,實驗中發現,選擇
squash(x)=x20.5+x2xxsquash(\boldsymbol{x})=\frac{\Vert\boldsymbol{x}\Vert^2}{0.5+\Vert\boldsymbol{x}\Vert^2}\frac{\boldsymbol{x}}{\Vert\boldsymbol{x}\Vert}

效果要好一點。這個函數的特點是在模長很接近於0時起到放大作用,而不像原來的函數那樣全局都壓縮。

然而,一個值得思考的問題是:如果在中間層,那麼這個壓縮處理是不是必要的呢?因爲已經有了後面說的動態路由在裏邊,因此即使去掉squash函數,網絡也已經具有了非線性了,因此直覺上並沒有必要在中間層也引入特徵壓縮,正如普通神經網絡也不一定要用sigmoid函數壓縮到0~1。我覺得這個要在實踐中好好檢驗一下。(這裏再下面講的CapNet裏面中間層也用了該函數)

4.CapsNet模型介紹

4.1.層次結構

Hinton 等人實現了一個簡單的 CapsNet 架構,該架構由兩個卷積層和一個全連接層組成,其中第一個爲一般的卷積層,第二個卷積相當於爲 Capsule 層做準備,並且該層的輸出爲向量,所以它的維度要比一般的卷積層再高一個維度。第三層纔是真正意義上的capsule,通過向量的輸入與 Routing 過程等構建出 10 個vjv_j向量,每一個向量的長度都直接表示某個類別的概率。
在這裏插入圖片描述
第一層就是一個常規的conv layer,第一個卷積層使用了 256 個 9×9 卷積核,步幅爲 1,且使用了 ReLU 激活函數。該卷積操作應該沒有使用 Padding,輸出的張量才能是 20×20×256。此外,CapsNet 的卷積核感受野使用的是 9×9,相比於其它 3×3 或 5×5 的要大一些,這個能是因爲較大的感受野在 CNN 層次較少的情況下能感受的信息越多。這兩層間的權值數量應該爲 9×9×256+256=20992。
按照原文的意思,這層的主要作用就是先在圖像pixel上做一次局部特徵檢測,然後才送給capsule。至於爲何不從第一層就開始使用capsule提取特徵?當然,MNIST是灰度圖,每個pixel是一個標量,每個pixel看作低層capsule的輸出uiu_i的話,就不符合capsule輸入輸出都是vector的要求(不考慮標量是長度爲1的vector這個情況,那將退化成現在的CNN模式)。

第二個卷積層開始爲 Capsule 層的輸入而構建相應的張量結構。我們可以從上圖看出第二層卷積操作後生成的張量維度爲 6×6×8×32,那麼我們該如何理解這個張量呢?雲夢居客在知乎上給出了一個十分形象且有意思的解釋,如前面章節所述,如果我們先考慮 32 個(32 channel)9×9 的卷積核在步幅爲 2 的情況下做卷積,那麼實際上得到的是傳統的 6×6×32 的張量,即等價於 6×6×1×32。
因爲傳統卷積操作每次計算的輸出都是一個標量,而 PrimaryCaps 的輸出需要是一個長度爲 8 的向量,因此傳統卷積下的三維輸出張量 6×6×1×32 就需要變化爲四維輸出張量 6×6×8×32。如下所示,其實我們可以將第二個卷積層看作對維度爲 20×20×256 的輸入張量執行 8 次不同權重的 Conv2d 操作(其實可以設置*8的filter number,等效),每次 Conv2d 都執行帶 32 個 9×9 卷積核、步幅爲 2 的卷積操作。
在這裏插入圖片描述
相當於上圖中左面8個並行,從上可知 PrimaryCaps 就相當於一個深度爲 32 的普通卷積層,只不過每一層由以前的標量值變成了長度爲 8 的向量。

第三層 DigitCaps 在第二層輸出的向量基礎上進行傳播與 Routing 更新,可以說是真正的capsule。第二層共輸出 6×6×32=1152 個向量,每一個向量的維度爲 8,即第 i 層共有 1152 個 Capsule 單元。而第三層 j 有 10 個標準的 Capsule 單元,每個 Capsule 的輸出向量有 16 個元素。前一層的 Capsule 單元數是 1152 個,那麼 wijw_{ij} 將有 1152×10 個,且每一個wijw_{ij}的維度爲 8×16。當uiu_i 與對應的 wijw_{ij} 相乘得到預測向量後,我們會有 1152×10 個耦合係數 cijc_{ij},對應加權求和後會得到 10 個 16×1 的輸入向量。將該輸入向量輸入到「squashing」非線性函數中求得最終的輸出向量 vjv_j,其中 vjv_j 的長度就表示識別爲某個類別的概率。

4.2.損失函數

我們該如何構建損失函數,並根據該損失函數迭代地更新整個網絡?前面我們耦合係數 c_ij 是通過一致性 Routing 進行更新的,他並不需要根據損失函數更新,但整個網絡其它的卷積參數和 Capsule 內的 W_ij 都需要根據損失函數進行更新。一般我們就可以對損失函數直接使用標準的反向傳播更新這些參數,而在原論文中,作者採用了 SVM 中常用的 Margin loss,該損失函數的表達式爲:
Lc=Tcmax(0,m+vc)2+λ(1Tc)max(0,vcm)2L_c = T_c max(0,m^+ - ||\mathbf v_c||)^2 + \lambda(1-T_c) max(0,||\mathbf v_c||-m^-)^2

其中 c 是分類類別,TcT_c 爲分類的指示函數(也就是groundtruthc ,存在爲 1,c 不存在爲 0),m+m^+ 爲上邊界,mm^- 爲下邊界。此外,vc\mathbf v_c 的模即向量的 L2 距離。

這裏paper還通過重構即我們希望利用預測的類別重新構建出該類別代表的實際圖像,例如我們前面部分的模型預測出該圖片屬於一個類別,然後後面重構網絡會將該預測的類別信息重新構建成一張圖片。也就是在最後的vc\mathbf v_c後面接了幾個全連接層進行預測,並將這一過程的損失函數通過計算 FC Sigmoid 層的輸出像素點與原始圖像像素點間的歐幾里德距離而構建。Hinton 等人還按 0.0005 的比例縮小重構損失,以使它不會主導訓練過程中的 Margin loss。
在這裏插入圖片描述
因爲這一部分我覺得可遷移能力比較差,所以不詳細說了(其實也沒啥可說的),可詳參paper

4.3.關於Digit層權值共享

貿然多出這麼一節來有點突兀,但是我實在覺得capsule裏面的這個仿射參數有點多,也就是這裏的WijW_{ij},在原版的文章中,他的維度應該ll_layer_capsule_number*ll_plus1_layer_capsule_number*ll_layer_capsule_vector_len*ll_plus1_layer_capsule_vector_len,相當於全連接層,並且還需要把ll層的vector的維度(8)升到l+1l+1的維度(16),所以參數是很多的。

所以能不能共享參數呢,也就變成了卷積的形式,我也從網上看到了蘇建林的文章,並且還給出了代碼(贊!),詳細的代碼地址可參考蘇建林capsule第36-49行源碼。

if self.share_weights:
     self.W = self.add_weight(name='capsule_kernel',
                              shape=(
                              	1, 
                              	input_dim_capsule,
                              	self.num_capsule * self.dim_capsule
                               ),
                              initializer='glorot_uniform',
                              trainable=True)
 else:
     input_num_capsule = input_shape[-2]
     self.W = self.add_weight(name='capsule_kernel',
                              shape=(
                              	input_num_capsule,
                                input_dim_capsule,
                                self.num_capsule * self.dim_capsule
                               ),
                              initializer='glorot_uniform',
                              trainable=True)

5.源碼解析

這裏提供幾分我覺得比較靠譜的源碼,源碼比較簡單,更明瞭:

6.相關討論

capsule出來之後還是有不少觀點和爭論的,網上的很多說法都有,但是這些問題可以給自己帶來啓發,也希望能激發自己更多思考。
在這裏插入圖片描述
在這裏插入圖片描述
關於蘇劍林的一些一些討論

7.capsule相關論文

參考文獻

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章