Dynamic Routing Between Capsules.

引言

斯蒂文認(rèn)為機(jī)器學(xué)習(xí)有時(shí)候像嬰兒學(xué)習(xí),特別是在物體識(shí)別上。比如嬰兒首先學(xué)會(huì)識(shí)別邊界和顏色,然后將這些信息用于識(shí)別形狀和圖形等更復(fù)雜的實(shí)體匙监。比如在人臉識(shí)別上,他們學(xué)會(huì)從眼睛和嘴巴開(kāi)始識(shí)別最終到整個(gè)面孔小作。當(dāng)他們看一個(gè)人的形象時(shí)亭姥,他們大腦認(rèn)出了兩只眼睛,一只鼻子和一只嘴巴顾稀,當(dāng)認(rèn)出所有這些存在于臉上的實(shí)體达罗,并且覺(jué)得“這看起來(lái)像一個(gè)人”。

斯蒂文首先給他的女兒悠悠看了以下圖片静秆,看她是否能自己學(xué)會(huì)認(rèn)識(shí)圖中的人(金·卡戴珊)粮揉。

image

斯蒂文接下來(lái)用幾張圖來(lái)考她:

image

悠悠

圖中有兩只眼睛一個(gè)鼻子一張嘴巴,圖中的物體是個(gè)人抚笔。

image
image

斯蒂文

正確扶认!

image

悠悠

圖中有兩只眼睛一個(gè)鼻子一張嘴巴,圖中的物體是個(gè)人殊橙。

image
image

斯蒂文

錯(cuò)誤辐宾!嘴巴長(zhǎng)到眼睛上還是個(gè)人嗎?

image

悠悠

圖中有一大塊都是黑色的膨蛮,圖中的物體好像是頭發(fā)叠纹。

image
image

斯蒂文

錯(cuò)誤!這只是把第一張圖顛倒一下敞葛,怎么就變成頭發(fā)了誉察?

斯蒂文很失望,覺(jué)得她第二惹谐、三張都應(yīng)該答對(duì)持偏,但是他對(duì)悠悠要求太高了驼卖,要知道現(xiàn)在深度學(xué)習(xí)里流行的卷積神經(jīng)網(wǎng)絡(luò) (convolutional neural network, CNN) 給出的答案也和悠悠一樣,如下:

image

第一張 CNN 給出的答案是人鸿秆,概率為 0.88款慨,正確;第二張 CNN 給出的答案也是人谬莹,概率為 0.90 檩奠,開(kāi)玩笑在?第三張 CNN 給出的答案是黑發(fā)附帽,概率為 0.79 埠戳,呵呵,和悠悠一樣天真蕉扮。

CNN 弄錯(cuò)的兩張圖也是因?yàn)樗膬蓚€(gè)缺陷:

  1. CNN 對(duì)物體之間的空間關(guān)系 (spatial relationship) 的識(shí)別能力不強(qiáng)整胃,比如卡戴珊的嘴巴和眼睛換位置了還被識(shí)別成人?
  2. CNN 對(duì)物體旋轉(zhuǎn)之后的識(shí)別能力不強(qiáng) (微微旋轉(zhuǎn)還可以)喳钟,比如卡戴珊倒過(guò)來(lái)就被識(shí)別成頭發(fā)了屁使?

Convolutional neural networks are doomed. -- Hinton

大神 Hinton 如此說(shuō)道“卷積神經(jīng)網(wǎng)絡(luò)要完蛋了”,因此他前不久也提出了一個(gè) Capsule 的東西奔则,直譯成膠囊蛮寂。但是這個(gè)翻譯丟失了很多重要的東西,個(gè)人認(rèn)為叫做向量神經(jīng)元 (vector neuron) 甚至張量神經(jīng)元 (tensor neuron) 更貼切易茬。正式介紹 Capsule 的這篇文章在 2017 年 11 月 7 日才出來(lái)酬蹋,論文名字叫《Dynamic Routing Between Capsules》,有興趣的同學(xué)跟我走一遭吧抽莱。

目錄

第一章 - 前戲王

1.1 物體姿態(tài)

1.2 不變性和同變性

1.3 全連接層

1.4 卷積神經(jīng)網(wǎng)絡(luò)

第二章 - 理論皇

2.1 膠囊定義

2.2 神經(jīng)元類(lèi)比

2.3 工作原理

2.4 動(dòng)態(tài)路由

2.5 網(wǎng)絡(luò)結(jié)構(gòu)

第三章 - 實(shí)踐狼

3.1 帆船房子

3.2 代碼解析

總結(jié)和下帖預(yù)告

image

1

前戲王

1.1

物體姿態(tài)

為了正確的分類(lèi)和識(shí)別物體范抓,保持物體部分之間的分層姿態(tài) (hierarchical pose) 關(guān)系是很重要的。姿態(tài)主要包括平移 (translation)食铐、旋轉(zhuǎn) (rotation) 和放縮 (scale) 三種形式匕垫。

在拍攝人物時(shí),我們調(diào)動(dòng)照相機(jī)的角度從 3D 的人生成 2D 的照片虐呻。照出來(lái)的人物照角度多種多樣象泵,但人是個(gè)整體 (臉和身體對(duì)于人的相對(duì)位置不會(huì)變)。因此我們不想定義相對(duì)于相機(jī)的所有對(duì)象 (臉和身體)铃慷,而將它們定義一個(gè)相對(duì)穩(wěn)定的坐標(biāo)系 (coordinate frame) 中单芜,然后僅僅通過(guò)轉(zhuǎn)動(dòng)相機(jī)來(lái)照出不同角度的照片蜕该。

在創(chuàng)建這些圖形時(shí)犁柜,我們首先會(huì)定義臉和身體相對(duì)于人的位置,更進(jìn)一層堂淡,我們會(huì)定義眼睛和嘴巴對(duì)于相對(duì)于臉的位置馋缅,但不是相對(duì)于人的位置扒腕。因?yàn)橹耙呀?jīng)有了臉相對(duì)于人的位置,現(xiàn)在又有了眼睛相對(duì)于臉的位置萤悴,那么也有了眼睛相對(duì)于人的位置瘾腰。本質(zhì)上,你將有層次的創(chuàng)建一個(gè)完整的人覆履,而所需要的數(shù)學(xué)工具就是姿態(tài)矩陣 (pose matrix)蹋盆,這個(gè)矩陣定義所有對(duì)象相對(duì)于照相機(jī)的視點(diǎn) (viewpoint),并且還表示了部件與整體之間的關(guān)系硝全。

In order to correctly do classification and object recognition, it is important to preserve hierarchical pose relationships between object parts. -- Hinton

Hinton 認(rèn)為栖雾,為了正確地進(jìn)行分類(lèi)和對(duì)象識(shí)別,重要的是保持對(duì)象部分之間的分層姿態(tài)關(guān)系伟众。后面講到的 Capsule 就符合這個(gè)重要直覺(jué)析藕,它結(jié)合了對(duì)象之間的相對(duì)關(guān)系,并以姿態(tài)矩陣來(lái)表示凳厢。

首先我們看看 2 維平面中姿態(tài)矩陣是如何平移账胧、旋轉(zhuǎn)和放縮物體:

image

用 R, T, S 定義旋轉(zhuǎn)、平移和縮放矩陣先紫,那么將 (x, y) 先逆時(shí)針轉(zhuǎn) 30 度治泥,再向右平移 2 個(gè)單位,最后縮放 50% 到 (x', y') 可以由下列矩陣連乘得到

image

在 2 維平面中遮精,我們加了 1 個(gè)維度 z车摄,是為了方便完成平移操作。寫(xiě)出 2 維平面姿態(tài)矩陣 M 的一般形式仑鸥,并延伸并類(lèi)比到 3 維空間的姿態(tài)矩陣吮播,表示如下:

image

下面看個(gè)具體例子:

image

整體是由它的各個(gè)部分組成的,如上圖:

  • 人 (整體) 是由臉和身體組成
  • 臉 (整體) 是由眼睛和嘴巴組成
  • 身體 (整體) 是由軀干和手組成

每個(gè)部分通過(guò)一個(gè)姿態(tài)矩陣與其主體相關(guān)聯(lián)眼俊。如果 M 是臉對(duì)人姿態(tài)矩陣意狠,N 是嘴巴對(duì)臉姿態(tài)矩陣荚虚,那么嘴巴對(duì)人的姿態(tài)矩陣為 N' = MN会钝。

現(xiàn)在我們有一個(gè)照相機(jī),并且我們知道人對(duì)相機(jī)的幀的姿態(tài)矩陣是 P逗柴,可以通過(guò)連乘姿態(tài)矩陣來(lái)提取人的每個(gè)部分的所有基本屬性澎灸,比如:

  • 臉對(duì)相機(jī)的幀的姿態(tài)矩陣由 M' = PM 給出
  • 嘴對(duì)相機(jī)的幀的姿態(tài)矩陣由 N' = M'N = PMN 給出
image

姿態(tài)矩陣 P 表示我們可以從相機(jī)看對(duì)象的不同視點(diǎn)院塞。一張臉上所有特征都是一樣的,所有不同的是你看臉的角度性昭。所有其他對(duì)象 (比如身體拦止、嘴巴和手) 的所有視點(diǎn)都可以由 P 得到。

現(xiàn)在告訴你左眼的位置,你可以想象臉的位置了吧汹族。同理萧求,你以為可以從嘴的位置估計(jì)臉的位置。如果由左眼和嘴的位置推出臉的位置相符顶瞒,數(shù)學(xué)上表示為 Ev·E = Mv·M夸政,其中

  • Ev 是眼睛的位置向量
  • E 是眼睛對(duì)臉的姿態(tài)矩陣
  • Mv 是嘴巴的位置向量
  • M 是嘴巴對(duì)臉的姿態(tài)矩陣
image

還記得引言中正常的卡戴珊的圖像 (左圖) 嗎?從嘴和左眼推出臉的位置是相似的榴徐,因此得出結(jié)論它們屬于同一個(gè)臉守问。

image

但是對(duì)于非正常的卡戴珊的圖像 (下左圖)

  • 從嘴位置推出臉在圖像上角 (下右圖)
  • 從左眼位置推出臉在圖像底部 (下右圖)
image

從嘴和左眼的位置出發(fā)得到的結(jié)論似乎不相符 (disagreement),因此它們不應(yīng)該被認(rèn)為出現(xiàn)在同一張臉上坑资。只有當(dāng)嘴和左眼處在正確的位置酪碘,從它們出發(fā)得到的結(jié)論才會(huì)相符 (agreement)。在這種情況下盐茎,我們就會(huì)發(fā)現(xiàn)嘴巴應(yīng)該在兩只眼睛的下面的中間兴垦,只有這樣放置的眼睛和嘴巴才是臉部的一部分,而不是僅僅靠一張嘴巴和眼睛來(lái)識(shí)別臉部字柠。

1.2

不變性和共變性

廣義上講探越,不變性 (invariance) 是表示 (representation) 不隨變換 (transformation) 變化;而同變性(equivariance) 是表示的變換等價(jià)于變換的表示窑业。

從計(jì)算機(jī)視覺(jué)角度上講钦幔,不變性指不隨一些變換來(lái)識(shí)別一個(gè)物體,具體變換包括平移 (translation)常柄,旋轉(zhuǎn) (rotation)鲤氢,視角 (viewpoint),放縮 (scale) 等西潘,如下圖所示:

image

不變性通常在物體識(shí)別上是好事卷玉,因?yàn)椴还艿裣裨趺雌揭啤?D旋轉(zhuǎn)、3D旋轉(zhuǎn)和放縮喷市,我們都可以識(shí)別出它是雕像相种。

如果我們的任務(wù)比物體識(shí)別稍微困難一點(diǎn),比如我想知道雕像平移了多少個(gè)單位品姓,旋轉(zhuǎn)了多少度寝并,放縮了百分之多少,那么不變性遠(yuǎn)遠(yuǎn)不夠腹备,這時(shí)需要的是同變性衬潦。

下圖給出不變性和同變性的具體例子

image

對(duì)平移和旋轉(zhuǎn)的不變性,其實(shí)是丟棄了“坐標(biāo)框架”植酥,而同變性不會(huì)丟失這些信息镀岛,它只是對(duì)內(nèi)容的一種變換。具體來(lái)講:

  • 左圖:平移前的 2 和平移后的 2 的表示是一樣的 (比如用 CNN 的池化),這樣我們只能識(shí)別出 2 哎媚,根本無(wú)法判斷出 2 在圖像中的位置。
  • 右圖:平移前的 2 和平移后的 2 的表示里含有位置這個(gè)信息 (比如用 Capsule)喊儡,這樣我們不但能識(shí)別出 2拨与,還能判斷出 2 在圖像中的位置。

1.3

全連接層

在人工神經(jīng)網(wǎng)絡(luò)一貼講的神經(jīng)網(wǎng)絡(luò)每層都是全連接的艾猜,也就是說(shuō)上一層每一個(gè)神經(jīng)元都連接到下一層每一個(gè)神經(jīng)元买喧,如下圖所示:

image

除了偏置項(xiàng),每層的每一個(gè)神經(jīng)元都連著近鄰層的所有神經(jīng)元匆赃,以這種連接關(guān)系的層就叫做全連接層 (fully connected layer, FC layer)淤毛,后文簡(jiǎn)稱(chēng) FC 層。

如果一個(gè)神經(jīng)網(wǎng)絡(luò)每一層都是全連接的算柳,那么它稱(chēng)作全連接神經(jīng)網(wǎng)絡(luò) (fully connected neural network, FCNN)低淡,這種 FCNN 不能太深,要不然參數(shù)太多瞬项,訓(xùn)練速度太慢蔗蹋。在圖像識(shí)別中,數(shù)據(jù)是高像素彩色照片囱淋,它的維度是 324×324×3猪杭,第一個(gè) 324 代表高,第二個(gè) 324 代表寬妥衣,最后的 3 代表 RGB 三個(gè)顏色維度皂吮,乘起來(lái)已經(jīng)有 314928 個(gè)元素了,如果隱藏層有 1024 個(gè)神經(jīng)元税手,那么總共有 314928×1024 = 3 億多個(gè)參數(shù) (假設(shè)忽略偏置項(xiàng))蜂筹。這還是一層,如果弄個(gè)十多層芦倒,那么訓(xùn)練這么多參數(shù)顯然不現(xiàn)實(shí)狂票,因此在圖像識(shí)別中用的是卷積神經(jīng)網(wǎng)絡(luò),它有稀疏連接 (sparse connection) 和參數(shù)共享 (parameter sharing) 等特性熙暴,會(huì)大大減少需要訓(xùn)練的參數(shù)闺属。

1.4

卷積神經(jīng)網(wǎng)絡(luò)

卷積神經(jīng)網(wǎng)絡(luò) (convolutional neural network,CNN) 的一個(gè)例子如下圖。

image

想象給了這張車(chē)的圖片周霉,在黑天里你看不到是輛車(chē)掂器,你只能用手電筒一點(diǎn)一點(diǎn)掃過(guò),把每次掃過(guò)看到的東西投影到下一層俱箱,以此類(lèi)推国瓮。比如第一層你看到一些橫線(xiàn)豎線(xiàn)斜線(xiàn),第二層組合成一些圓形方形,第三層組合成輪子車(chē)門(mén)車(chē)身乃摹,第四層組合成一輛車(chē)禁漓。這樣就能用個(gè)手電筒在黑天里辨別出照片里有輛車(chē)了。

上面的例子雖然不嚴(yán)謹(jǐn)孵睬,但是聽(tīng)起來(lái)很直觀播歼,接下來(lái)給出 CNN 里面的一些定義。

  • 濾波器 (filter):在輸入數(shù)據(jù)的寬度和高度上滑動(dòng)掰读,與輸入數(shù)據(jù)進(jìn)行卷積秘狞,就像上例中的手電筒
  • 卷積 (convolution):在這里的定義就是把所有“濾波器的像素”乘以“濾波器掃過(guò)圖片的像素”再加總
  • 步長(zhǎng) (stride):遍歷圖像時(shí)濾波器的步長(zhǎng),默認(rèn)值為 1蹈集,既濾波器每次移動(dòng)一個(gè)像素
  • 填充 (padding):有時(shí)候會(huì)將輸入數(shù)據(jù)用 0 在邊緣進(jìn)行填充烁试,可以控制輸出數(shù)據(jù)的尺寸 (最常用的是保持輸出數(shù)據(jù)的尺寸與輸入數(shù)據(jù)一致)

千言萬(wàn)語(yǔ)不如兩幅動(dòng)圖 (藍(lán)色是輸入圖片的像素,綠色是濾波器掃過(guò)圖片之后的卷積值):

image

第一幅動(dòng)圖將一個(gè) 5x5 的圖像饋送到 3x3 的濾波器拢肆。其步長(zhǎng)為 2 (濾波器每2格滑動(dòng))减响,沒(méi)用填充 (最外層沒(méi)有虛線(xiàn)格),結(jié)果產(chǎn)生一個(gè)2x2 的圖像郭怪。

image

第二幅動(dòng)圖也將一個(gè) 5x5 的圖像饋送到 3x3 的濾波器辩蛋。其步長(zhǎng)為 1 (濾波器每1格滑動(dòng)),用了 1 層填充(最外層只有一格虛線(xiàn)格)移盆,結(jié)果產(chǎn)生一個(gè) 5x5 的圖像 (加填充可使得輸出和輸入圖像大小不變)悼院。

如果用 nI代表輸入圖像的大小,f 代表濾波器的大小咒循,s 代表步長(zhǎng)据途,p 代表填充層數(shù),nO 代表輸入圖像的大小叙甸,那么有 (公式很簡(jiǎn)單就不推導(dǎo)了颖医,大家可以試試上面兩個(gè)例子)

image

把具體數(shù)字帶進(jìn)來(lái),大家再捋一遍上面的卷積裆蒸、濾波器熔萧、步長(zhǎng)和填充的概念:

image

輸出右下角的 1 是這樣卷積來(lái)的:

0x1 + 1x1 + 0x0

  • 1x0 + 0x0 + 0x1

  • 0x0 + 0x0 + 1x0 = 1

除了上面定義之外,CNN 還有個(gè)很重要的概念叫做池化 (pooling)僚祷。它的作用是逐漸降低數(shù)據(jù)體的空間尺寸佛致,這樣的話(huà)能減少網(wǎng)絡(luò)中參數(shù)的數(shù)量,使得計(jì)算資源消耗變少辙谜,也能有效的控制過(guò)擬合俺榆。通常池化使用 max 操作,比如使用尺寸 2x2 的濾波器装哆,以步長(zhǎng)為 2 對(duì)輸入數(shù)據(jù)進(jìn)行降采樣罐脊,從 2x2 個(gè)數(shù)字中取最大值定嗓。字不如圖,上圖大家慢慢理會(huì):

image

雖然池化這項(xiàng)技術(shù)在 CNN 上用的非常好萍桌,但是 Hinton 有話(huà)要說(shuō)

The pooling operation used in convolutional neuralnetworks is a big mistake and the fact that it works so well is a disaster. -- Hinton

Hinton 認(rèn)為池化在 CNN 的好效果是個(gè)大錯(cuò)誤甚至災(zāi)難宵溅。因?yàn)槌鼗瘯?huì)導(dǎo)致重要的信息丟失,如果它是兩層之間的信使上炎,它告訴第二層的是“我們看到左上角有一個(gè)最大值 2恃逻,右上角有一個(gè)最大值 4”,但不知道這個(gè) 2 和 4 是從第一層哪里來(lái)的反症。在引言的例子中辛块,我們知道“兩只眼睛一個(gè)鼻子一張嘴巴”并不代表“一張臉”畔派,要確認(rèn)是張臉铅碍,我們還需要知道這些器官之間的相互位置,比如眼睛要在鼻子上方线椰,鼻子要在嘴巴上方胞谈,那么才可能是張臉。

2

理論皇

2.1

膠囊定義

膠囊 (Capsule) 是一個(gè)包含多個(gè)神經(jīng)元的載體憨愉,每個(gè)神經(jīng)元表示了圖像中出現(xiàn)的特定實(shí)體的各種屬性烦绳。這些屬性可以包括許多不同類(lèi)型的實(shí)例化參數(shù) (instantiation parameter),例如姿態(tài) (位置配紫、大小径密、方向),變形躺孝,速度享扔,色相,紋理等植袍。膠囊里一個(gè)非常特殊的屬性是圖像中某個(gè)類(lèi)別的實(shí)例的存在惧眠。它的輸出數(shù)值大小就是實(shí)體存在的概率。

數(shù)學(xué)上常說(shuō)的向量是一個(gè)有方向和長(zhǎng)度的概念于个,把膠囊類(lèi)比于數(shù)學(xué)向量氛魁,它也有所謂的“長(zhǎng)度”和“方向”。假設(shè)一個(gè)膠囊代表卡戴珊的眼睛厅篓,戲稱(chēng)“卡戴珊眼睛膠囊”秀存,那么其

  • 長(zhǎng)度代表眼睛在圖像某個(gè)位置存在的概率
  • 方向代表眼睛的一些參數(shù),比如位置羽氮,轉(zhuǎn)角应又,清晰度等等

兩者類(lèi)比圖如下:

image

現(xiàn)在大家看膠囊的概念可能還是一頭霧水,我確保你越看到后面思路越清晰乏苦,尤其要看小節(jié) 3.1株扛。

2.2

神經(jīng)元類(lèi)比

為了用詞嚴(yán)謹(jǐn)和類(lèi)比方便尤筐,我們將 Capsule 稱(chēng)作向量神經(jīng)元 (vector neuron, VN),而普通的人工神經(jīng)元叫做標(biāo)量神經(jīng)元 (scalar neuron, SN)洞就,下表總結(jié)了 VN 和 SN 之間的差異:

image

上表中 VN 里的操作不懂不要緊盆繁,接下來(lái)會(huì)一一詳述,本節(jié)只是想從高層面上區(qū)分 VN 和 SN 的區(qū)別旬蟋,因此大家比較熟悉 SN油昂,從對(duì) SN 的性質(zhì)理解再慢慢過(guò)渡到對(duì) VN 的理解。

回想一下人工神經(jīng)網(wǎng)絡(luò)一貼倾贰,SN 從其他神經(jīng)元接收輸入標(biāo)量冕碟,然后乘以標(biāo)量權(quán)重再求和,然后將這個(gè)總和傳遞給某個(gè)非線(xiàn)性激活函數(shù) (比如 sigmoid, tanh, Relu)匆浙,生出一個(gè)輸出標(biāo)量安寺。該標(biāo)量將作為下一層的輸入變量。實(shí)質(zhì)上首尼,SN 可以用以下三個(gè)步驟來(lái)描述:

  1. 將輸入標(biāo)量 x 乘上權(quán)重 w
  2. 對(duì)加權(quán)的輸入標(biāo)量求和成標(biāo)量 a
  3. 用非線(xiàn)性函數(shù)將標(biāo)量 a 轉(zhuǎn)化成標(biāo)量 h

VN 的步驟在 SN 的三個(gè)步驟前加一步:

  1. 將輸入向量 u 用矩陣 W 加工成新的輸入向量 U
  2. 將輸入向量 U 乘上權(quán)重 c
  3. 對(duì)加權(quán)的輸入向量求和成向量 s
  4. 用非線(xiàn)性函數(shù)將向量 s 轉(zhuǎn)化成向量 v

VN 和 SN 的過(guò)程總結(jié)如下圖所示:

image

下一節(jié)來(lái)仔細(xì)研究 VN 的四步工作原理挑庶。

2.3

工作原理

為了使問(wèn)題具體化,假設(shè):

  1. 上一層的 VN 代表眼睛 (u1), 鼻子 (u2) 和嘴巴 (u3)软能,稱(chēng)為低層特征
  2. 下一層第 j 個(gè)的 VN 代表臉迎捺,稱(chēng)為高層特征。注意下一層可能還有很多別的高層特征查排,臉是最直觀的一個(gè)

第一步:矩陣轉(zhuǎn)化

公式

image

根據(jù)小節(jié) 1.1 介紹的姿態(tài)矩陣可知

  • Uj|1 是根據(jù)眼睛位置來(lái)檢測(cè)臉的位置
  • Uj|2 是根據(jù)鼻子位置來(lái)檢測(cè)臉的位置
  • Uj|3 是根據(jù)嘴巴位置來(lái)檢測(cè)臉的位置

現(xiàn)在凳枝,直覺(jué)應(yīng)該是這樣的:如果這三個(gè)低層特征 (眼睛,鼻子和嘴) 的預(yù)測(cè)指向相同的臉的位置和狀態(tài)跋核,那么出現(xiàn)在那個(gè)地方的必定是一張臉岖瑰。如下圖所示:

image

上左圖預(yù)測(cè)出臉,因?yàn)榧t藍(lán)黃綠圈非常吻合了罪;而上右圖沒(méi)有沒(méi)有預(yù)測(cè)出臉锭环,因?yàn)榧t藍(lán)黃綠圈相差甚遠(yuǎn)。

第二步:輸入加權(quán)

公式

image

乍一看泊藕,這個(gè)步驟和標(biāo)量神經(jīng)元 SN 的加權(quán)形式有點(diǎn)類(lèi)似辅辩。在 SN 的情況下,這些權(quán)重是通過(guò)反向傳播 (backward propagation) 確定的娃圆,但是在 VN 的情況下玫锋,這些權(quán)重是使用動(dòng)態(tài)路由 (dynamic routing) 確定的,具體算法見(jiàn)小節(jié) 2.4讼呢。本節(jié)只從高層面來(lái)解釋動(dòng)態(tài)路由撩鹿,如下圖:

image

在上圖中,我們有一個(gè)較低級(jí)別 VNi需要“決定”它將發(fā)送輸出給哪個(gè)更高級(jí)別 VN1和 VN2悦屏。它通過(guò)調(diào)整權(quán)重 ci1和 ci2來(lái)做出決定节沦。

現(xiàn)在键思,高級(jí)別 VN1和 VN2已經(jīng)接收到來(lái)自其他低級(jí)別 VN 的許多輸入向量,所有這些輸入都以紅點(diǎn)和藍(lán)點(diǎn)表示甫贯。

  • 紅點(diǎn)聚集在一起吼鳞,意味著低級(jí)別 VN 的預(yù)測(cè)彼此接近
  • 藍(lán)點(diǎn)聚集在一起,意味著低級(jí)別 VN 的預(yù)測(cè)相差很遠(yuǎn)

那么叫搁,低別級(jí) VNi應(yīng)該輸出到高級(jí)別 VN1還是 VN2赔桌?這個(gè)問(wèn)題的答案就是動(dòng)態(tài)路由的本質(zhì)。由上圖看出

  • VNi 的輸出遠(yuǎn)離高級(jí)別 VN1 中的“正確”預(yù)測(cè)的紅色簇
  • VNi 的輸出靠近高級(jí)別 VN2 中的“正確”預(yù)測(cè)的紅色簇

而動(dòng)態(tài)路由會(huì)根據(jù)以上結(jié)果產(chǎn)生一種機(jī)制渴逻,來(lái)自動(dòng)調(diào)整其權(quán)重疾党,即調(diào)高 VN2相對(duì)的權(quán)重 ci2,而調(diào)低 VN1相對(duì)的權(quán)重 ci1惨奕。

第三步:加權(quán)求和

公式

這一步類(lèi)似于普通的神經(jīng)元的加權(quán)求和步驟雪位,除了總和是向量而不是標(biāo)量。加權(quán)求和的真正含義就是計(jì)算出第二步里面講的紅色簇心 (cluster centroid)墓贿。

第四步:非線(xiàn)性激活

公式

image

這個(gè)公式的確是 VN 的一個(gè)創(chuàng)新茧泪,采用向量的新型非線(xiàn)性激活函數(shù)蜓氨,又叫 squash 函數(shù)聋袋,姑且翻譯成“壓縮”函數(shù)。這個(gè)函數(shù)主要功能是使得 vj 的長(zhǎng)度不超過(guò) 1穴吹,而且保持 vj和 sj同方向幽勒。

  • 公式第一項(xiàng)壓扁函數(shù)

  • 如果 sj 很長(zhǎng),第一項(xiàng)約等于 1

  • 如果 sj 很短港令,第一項(xiàng)約等于 0

  • 公式第二項(xiàng)單位化向量 sj啥容,因此第二項(xiàng)長(zhǎng)度為 1

這樣一來(lái),輸出向量 vj的長(zhǎng)度是在 0 和 1 之間的一個(gè)數(shù)顷霹,因此該長(zhǎng)度可以解釋為 VN 具有給定特征的概率咪惠。

2.4

動(dòng)態(tài)路由

在小節(jié) 2.3 的第二步已經(jīng)講過(guò),低級(jí)別 VNi 需要決定如何將其輸出向量發(fā)送到高級(jí)別 VNj淋淀,它是通過(guò)改變權(quán)重 cij而實(shí)現(xiàn)的遥昧。首先來(lái)看看 cij的性質(zhì):

  1. 每個(gè)權(quán)重是一個(gè)非負(fù)值
  2. 對(duì)于每個(gè)低級(jí)別 VNi,所有權(quán)重 cij 的總和等于 1
  3. 對(duì)于每個(gè)低級(jí)別 VNi朵纷,權(quán)重的個(gè)數(shù)等于高級(jí)別 VN 的數(shù)量
  4. 權(quán)重由迭代動(dòng)態(tài)路由 (iterative dynamic routing) 算法確定

前兩個(gè)性質(zhì)說(shuō)明 c 符合概率概念炭臭。回想一下小節(jié) 2.1袍辞,VN 的長(zhǎng)度被解釋為它的存在概率鞋仍。VN 的方向是其特征的參數(shù)化狀態(tài)。因此搅吁,對(duì)于每個(gè)低級(jí)別 VNi威创,其權(quán)重 cij定義了屬于每個(gè)高級(jí)別 VNj 的輸出的概率分布落午。

一言以蔽之,低級(jí)別 VN 會(huì)將其輸出發(fā)送到“同意”該輸出的某個(gè)高級(jí)別 VN肚豺。這是動(dòng)態(tài)路由算法的本質(zhì)板甘。很繞口是吧?分析完 Hinton 論文中的動(dòng)態(tài)路由算法就懂了详炬,見(jiàn)截圖:

image

算法字面解釋如下:

  • 第 1 行:這個(gè)過(guò)程用到的所有輸入 - l 層的輸出 Uj|i盐类,路由迭代次數(shù) r
  • 第 2 行:定義 bij 是 l 層 VNi 應(yīng)該連接 l+1 層 VNj 的可能性,初始值為 0
  • 第 3 行:執(zhí)行第 4-7 行 r 次
  • 第 4 行:對(duì) l 層的 VNi呛谜,將 bij 用 softmax 轉(zhuǎn)化成概率 cij
  • 第 5 行:對(duì) l+1 層的 VNj在跳,加權(quán)求和 sj
  • 第 6 行:對(duì) l+1 層的 VNj,壓縮 sj 得到 vj
  • 第 7 行:根據(jù) Uj|i 和 vj 的關(guān)系來(lái)更新 bij

算法邏輯解釋如下:

  • 第 1 行無(wú)需說(shuō)明隐岛,唯一要指出的是迭代次數(shù)為 3 次猫妙,Hinton 在他論文里這樣說(shuō)道

  • 第 2 行初始化所有 b 為零,這是合理的聚凹。因?yàn)閺牡?4 行可看出割坠,只有這樣 c 才是均勻分布的,暗指“l(fā) 層 VN 到底要傳送輸出到 l+1 層哪個(gè) VN 是最不確定的”

  • 第 4 行的 softmax 函數(shù)產(chǎn)出是非負(fù)數(shù)而且總和為 1妒牙,致使 c 是一組概率變量

  • 第 5 行的 sj 就是小節(jié) 2.3 第二步里面講的紅色簇心彼哼,可以認(rèn)為是低層所有 VN 的“共識(shí)”輸出

  • 第 6 行的 squash 確保向量 sj 的方向不變,但長(zhǎng)度不超過(guò) 1湘今,因?yàn)殚L(zhǎng)度代表 VN 具有給定特征的概率

  • 第 7 行是動(dòng)態(tài)路由的精華敢朱,用 Uj|i 和 vj 的點(diǎn)積 (dot product) 更新 bij,其中前者是 l 層 VNi對(duì) l+1 層 VNj 的“個(gè)人”預(yù)測(cè)摩瞎,而后者是所有 l 層 VN 對(duì) l+1 層 VNj 的“共識(shí)”預(yù)測(cè):

  • 當(dāng)兩者相似拴签,點(diǎn)積就大,bij 就變大旗们,低層 VNi 連接高層 VNj 的可能性就變大

  • 當(dāng)兩者相異蚓哩,點(diǎn)積就小,bij 就變小上渴,低層 VNi 連接高層 VNj 的可能性就變小

下面兩幅圖幫助進(jìn)一步理解第 7 行的含義岸梨,第一幅講的是點(diǎn)積,論文中用點(diǎn)積來(lái)度量?jī)蓚€(gè)向量的相似性驰贷,當(dāng)然還有很多別的度量方式盛嘿。

image

第二幅講的是更新權(quán)重,此消彼長(zhǎng)括袒。

image

2.5

網(wǎng)絡(luò)結(jié)構(gòu)

本章的前四節(jié)已經(jīng)講明 Capsule 的工作原理和動(dòng)態(tài)路由的邏輯次兆。本節(jié)以 MNIST 數(shù)據(jù)集為例,來(lái)闡明向量神經(jīng)網(wǎng)絡(luò) (capsule network, CapsNet) 的結(jié)構(gòu)和工作原理锹锰。

MNIST 全稱(chēng)為 Modified National Institute of Standards and Technology芥炭,其中訓(xùn)練集由來(lái)自 250 個(gè)不同人手寫(xiě)的數(shù)字構(gòu)成漓库,其中 50% 是高中學(xué)生,50% 來(lái)自人口普查局的工作人員园蝠,總共 60000 個(gè)數(shù)字款票;而測(cè)試集也是同樣比例的手寫(xiě)數(shù)字?jǐn)?shù)據(jù)瑟押,總共 10000 個(gè)數(shù)字今穿。每幅圖像為一個(gè) 28x28 像素的單元坯汤,下圖給出 MNIST 里面 0-9 的一些示例。

image

CapsNet 的輸入輸出和 CNN 是一樣的:

  • 輸入都是 28x28 的二維矩陣
  • 輸出都是 10x1 的概率向量

但 CapsNet 和業(yè)界最先進(jìn)的 CNN 相比善延,是一個(gè)非常淺的網(wǎng)絡(luò)少态,中間只有兩個(gè)卷積 (Conv) 層 (見(jiàn)小節(jié)1.4) 和一個(gè)全連接 (FC) 層 (見(jiàn)小節(jié)1.3),如下圖所示:

image

圖像輸入到低級(jí)特征 (Conv1)

這一步就是一個(gè)常規(guī)的卷積操作易遣,用了 256 個(gè) stride 為 1 的 9x9 的 filter彼妻,得到一個(gè) 20x20x256 的輸出。按照原文的意思豆茫,這一步主要作用就是對(duì)圖像像素做一次局部特征檢測(cè)侨歉。讓我們 Conv1 層的維度是如何得到的。

image

但為什么不一開(kāi)始就用 Capsule 呢揩魂?因?yàn)?Capsule 是用來(lái)表征某個(gè)物體的“實(shí)例”幽邓,因此它更適合于表征高級(jí)的實(shí)例。如果直接用 Capsule 吸取圖片的低級(jí)特征內(nèi)容肤京,不是很理想颊艳,而 CNN 卻擅長(zhǎng)抽取低級(jí)特征茅特,因此一開(kāi)始用 CNN 是合理的忘分。

低級(jí)特征到 Primary Capsule (Conv2)

Conv2 層才是開(kāi)始含有 Capsule。如果按照普通 CNN 里面的做法白修,用了 32 個(gè) stride 為 2 的 9x9x256 的 filter妒峦,也只能得到 6x6x32 的輸出,算法如下:

image

但是從上圖和 Hinton 的論文發(fā)現(xiàn)兵睛,Conv2 層的維度是 6x6x8x32肯骇。這個(gè) 8怎么來(lái)的?它又代表著什么含義祖很?個(gè)人理解是用 32 個(gè) stride 為 2 的 9x9x256 的filter做了 8次卷積操作笛丙,而且

  • 在 CNN 中,維度為 6x6x1x32 的層里有 6x6x32 元素假颇,每個(gè)元素是一個(gè)標(biāo)量
  • 在 Capsule 中胚鸯,維度為 6x6x8x32 的層里有 6x6x32 元素,每個(gè)元素是一個(gè) 1x8的向量笨鸡,既 capsule

Conv2 層的輸出在論文中稱(chēng)為 Primary Capsule姜钳,簡(jiǎn)稱(chēng) PrimaryCaps坦冠,主要儲(chǔ)存低級(jí)別特征的向量。

Primary Capsule 到 Digit Capsule (FC)

下一層就是存儲(chǔ)高級(jí)別特征的向量哥桥,在本例中就是數(shù)字辙浑,F(xiàn)C 層的輸出在論文中稱(chēng)為 Digit Capsule,簡(jiǎn)稱(chēng) DigitCaps拟糕。PrimaryCaps 和 DigitCaps 是全連接的判呕,但不是像傳統(tǒng)神經(jīng)網(wǎng)絡(luò)標(biāo)量和標(biāo)量相連,而是向量與向量相連送滞。

PrimaryCaps 里面有 6x6x32 元素佛玄,每個(gè)元素是一個(gè) 1x8的向量,而 DigitCaps 有 10 個(gè)元素 (因?yàn)橛?10 個(gè)數(shù)字)累澡,每個(gè)元素是一個(gè) 1x16的向量梦抢。為了讓 1x8向量與 1x16向量全連接,需要 6x6x32 個(gè) 8x16的矩陣 (姿態(tài)矩陣還記得嗎)愧哟。

現(xiàn)在 PrimaryCaps 有 6x6x32 = 1152 個(gè) VN奥吩,而 DigitCaps 有 10 個(gè) VN,那么 I= 1,2, …, 1152, j = 0,1, …, 9蕊梧。再用小節(jié) 2.4 講的動(dòng)態(tài)路由算法迭代 3 次計(jì)算 cij并輸出 10 個(gè) vj霞赫。

Digit Capsule 到最終輸出

根據(jù) Capsule 定義,它的長(zhǎng)度表示其表征的內(nèi)容出現(xiàn)的概率肥矢,所以做分類(lèi)時(shí)取輸出向量的 L2 范數(shù) (也就是長(zhǎng)度) 即可端衰。需要注意的是,最后 Capsule 輸出的概率總和并不等于 1甘改,也就是 Capsule 有同時(shí)識(shí)別多個(gè)物體的能力旅东。

損失函數(shù)

由于 Capsule 允許多個(gè)分類(lèi)同時(shí)存在,所以不能直接用傳統(tǒng)的交叉熵 (cross-entropy) 損失十艾,一種替代方案是用間隔損失 (margin loss)

其中

  • k 是分類(lèi)
  • Tk 是分類(lèi)的指示函數(shù) (k 類(lèi)存在為 1抵代,不存在為 0)
  • m+ 為上界,懲罰假陽(yáng)性 (false positive) 忘嫉,即預(yù)測(cè) k 類(lèi)存在但真實(shí)不存在荤牍,識(shí)別出來(lái)但錯(cuò)了
  • m- 為下界,懲罰假陰性 (false negative) 庆冕,即預(yù)測(cè) k 類(lèi)不存在但真實(shí)存在康吵,沒(méi)識(shí)別出來(lái)
  • λ 是比例系數(shù),調(diào)整兩者比重

總的損失是各個(gè)樣例損失之和访递。論文中 m+= 0.9, m-= 0.1, λ = 0.5晦嵌,用大白話(huà)說(shuō)就是

  • 如果 k 類(lèi)存在,||vk|| 不會(huì)小于 0.9
  • 如果 k 類(lèi)不存在,||vk|| 不會(huì)大于 0.1
  • 懲罰假陽(yáng)性的重要性大概是懲罰假陰性的重要性的 2 倍

重構(gòu)表示

魯棒性強(qiáng)的模型一定有重構(gòu)的能力耍铜。如果模型能夠重構(gòu)邑闺,證明它至少有了一個(gè)好的表示,并且從重構(gòu)結(jié)果中可以看出模型存在的問(wèn)題棕兼。

image

重構(gòu)的時(shí)候陡舅,我們單獨(dú)取出 (上圖橘色) 需要重構(gòu)的向量,扔到后面的 3 層全連接網(wǎng)絡(luò)中重構(gòu)伴挚。注意最終輸出的維度是 784 = 28×28靶衍,正好是最初圖像輸入的維度。

重構(gòu)損失 (reconstruction loss) 就是把最終輸出和最初輸入的 784 個(gè)單元上的像素值相減并平方求和茎芋÷簦總體損失 (total loss) 就是

總體損失 = 間隔損失 + α·重構(gòu)損失

其中 α = 0.005,間隔損失還是占主導(dǎo)地位田弥。

3

實(shí)踐狼

3.1

帆船房子

本節(jié)用一個(gè)具體的“三角形長(zhǎng)方形組成帆船房子”的例子來(lái)直觀解釋第 2 章的理論知識(shí)和重要概念涛酗,假設(shè)“低層 Capsule”里面有三角形和長(zhǎng)方形,而“高層 Capsule”里面有帆船和房子偷厦。為了解釋方便商叹,定義:

  • 三角形 VN:低層 Capsule 的三角形
  • 長(zhǎng)方形 VN:低層 Capsule 的長(zhǎng)方形
  • 帆船 VN:高層 Capsule 的帆船
  • 房子 VN:高層 Capsule 的房子

正向作圖和反向作圖

image

如上圖所示,計(jì)算機(jī)作圖 (computer graphics) 只泼,通常認(rèn)為是正向作圖剖笙,是根據(jù)各個(gè)物體的參數(shù),比如中心橫坐標(biāo) x请唱,中心縱坐標(biāo) y 和旋轉(zhuǎn)角度弥咪,在屏幕中打出 (rendering) 帆船的圖像。而反向作圖 (inverse graphics) 是根據(jù)屏幕中帆船的圖像十绑,反推出各個(gè)物體的參數(shù)聚至。

想知道上圖三角形的 -65 度和長(zhǎng)方形的 16 度怎么來(lái)的,見(jiàn)下圖解釋孽惰。

image

向量神經(jīng)元做的事就是反向作圖晚岭。

向量神經(jīng)元性質(zhì)

假設(shè)藍(lán)箭頭代表三角形黑箭頭代表長(zhǎng)方形

  • 藍(lán)箭頭的長(zhǎng)度表示三角形出現(xiàn)的概率大小
  • 黑箭頭的長(zhǎng)度表示長(zhǎng)方形出現(xiàn)的概率大小
  • 藍(lán)箭頭的方向表示三角形的姿態(tài)參數(shù) (這里指朝向)
  • 黑箭頭的方向表示長(zhǎng)方形的姿態(tài)參數(shù) (這里指朝向)
image

由上圖左邊明顯看出

  • 有一個(gè)藍(lán)箭頭和一個(gè)黑箭頭非常大勋功,說(shuō)明在上圖右邊各自相應(yīng)位置上存在的三角形和長(zhǎng)方形的可能性非常大
  • 其他地方的所有藍(lán)箭頭和黑箭頭非常小,說(shuō)明在上圖右邊那些位置上存在的三角形和長(zhǎng)方形的可能性非常小
  • 根據(jù)藍(lán)箭頭的方向库说,我們大概知道三角形逆時(shí)針轉(zhuǎn)了 65 度
  • 根據(jù)黑箭頭的方向狂鞋,我們大概知道長(zhǎng)方形順時(shí)針轉(zhuǎn)了 16 度

同變性

CNN 的池化只能帶來(lái)“不變性 (invariance)”,只能識(shí)別下面兩圖中都有帆船潜的,但我們不想只追求識(shí)別率骚揍,我們想要的更多,比如 VN 帶來(lái)的“同變性 (equivariance)”,不但能識(shí)別兩圖中有帆船信不,還能看出它們的傾斜度不同嘲叔。

image

從上圖看出,當(dāng)帆船旋轉(zhuǎn)了一些角度抽活,它包含的三角形長(zhǎng)方形也旋轉(zhuǎn)了一些角度硫戈,而對(duì)應(yīng)的藍(lán)箭頭黑箭頭也旋轉(zhuǎn)了一些角度。三角形長(zhǎng)方形是低層物體下硕,帆船是高層物體丁逝,物體與物體之間是有層次 (hierarchy) 的。當(dāng)高層物體轉(zhuǎn)動(dòng)時(shí)梭姓,它包含的所有低層物體也隨之轉(zhuǎn)動(dòng)霜幼。

物體層次

三角形和長(zhǎng)方形可以組成帆船,也可以組成房子誉尖。

image

如果把帆船和房子當(dāng)成一個(gè)整體的話(huà) (忽略其組成成分三角形和長(zhǎng)方形)罪既,那么它們也有自己的 x-y 坐標(biāo)和角度,如圖所示铡恕,帆船沿順時(shí)針?lè)较蛐D(zhuǎn)了 16 度萝衩,房子沿逆時(shí)針?lè)较蛐D(zhuǎn)了 5 度。

現(xiàn)在問(wèn)題是没咙,如果我們識(shí)別出圖片上有三角形和長(zhǎng)方形猩谊,那么它們組合的是房子還是帆船?

預(yù)測(cè)物體

下圖勾畫(huà)出由“低層 VN 代表的三角形長(zhǎng)方形”來(lái)預(yù)測(cè)“高層 VN 代表的房子帆船”的來(lái)龍去脈祭刚。

image
  • 如果根據(jù)長(zhǎng)方形的姿態(tài)開(kāi)始預(yù)測(cè)牌捷,則房子和帆船的姿態(tài)如左圖所示。注意房子和帆船里的長(zhǎng)方形朝向和位置完全相同涡驮。
  • 如果根據(jù)三角形的姿態(tài)開(kāi)始預(yù)測(cè)暗甥,則房子和帆船的姿態(tài)如右圖所示。注意房子和帆船里的三角形朝向和位置完全相同捉捅。

淺談路由

路由 (routing) 就是通過(guò)互聯(lián)網(wǎng)絡(luò)把信息從源地址傳輸?shù)侥康牡刂返幕顒?dòng)撤防,而這里路由指的是通過(guò)神經(jīng)網(wǎng)絡(luò)把信息從低層 VN 傳輸?shù)礁邔?VN 的活動(dòng)。

image

“三角形和長(zhǎng)方形的 VN”路由出來(lái)的“帆船 VN”看起來(lái)非常相似棒口,而它們路由出來(lái)的“房子 VN”看來(lái)一點(diǎn)也不像寄月。因此我們有信心的認(rèn)為圖像里存在就是一艘帆船而不是一棟房子。

動(dòng)態(tài)路由

動(dòng)態(tài)路由 (dynamic routing) 是找到每一個(gè)“低層 VN”的輸出最有可能貢獻(xiàn)給哪個(gè)“高層 VN”无牵。具體到我們的實(shí)例漾肮,就是找到“三角形或長(zhǎng)方形”最有可能組成“房子或帆船”。

用 i 代表低層 VN 中長(zhǎng)方形或三角形的索引 (本例中 i = 1, 2)茎毁,用 j 代表高層 VN 中房子或帆船的索引 (本例中 j = 1, 2)克懊,定義

  • bij = 低層 VNi 連接高層 VNj 的可能性,初始值為 0
  • cij = 低層 VNi 連接高層 VNj 的概率,總和為 1
  • bi = 低層 VNi 連接所有高層 VNj 的可能性谭溉,初始值為 0
  • ci = 低層 VNi 連接所有高層 VNj 的概率
  • Uj|i = 由低層 VNi 預(yù)測(cè)的高層 VNj

cij是 bij做 softmax 之后的結(jié)果墙懂,因此初始值 0.5 (j 層只有 2 個(gè) VN)。

為了達(dá)到以上目的扮念,動(dòng)態(tài)路由在每個(gè)回合都干了“歸一损搬、預(yù)測(cè)、加總扔亥、壓縮和更新”這五件事场躯,然后重復(fù)若干回合:

對(duì)長(zhǎng)方形 (i=1) 和三角形 (i=2)

  • 歸一:

  • 計(jì)算概率 (c11, c12) = 歸一(b11, b12)

  • 計(jì)算概率 (c21, c22) = 歸一(b21, b22)

  • 預(yù)測(cè):

  • 從長(zhǎng)方形到房子 U1|1 和小船 U2|1

  • 從三角形到房子 U1|2 和小船 U2|2

  • 加總:

  • 房子的綜合預(yù)測(cè) s1 = c11U1|1 + c21U1|2

  • 帆船的綜合預(yù)測(cè) s2 = c12U2|1 + c22U2|2

  • 壓縮:

  • 單位化房子的綜合預(yù)測(cè) v1 = 壓縮(s1)

  • 單位化帆船的綜合預(yù)測(cè) v2 = 壓縮(s2)

  • 更新:

  • b11 = b11 + 相似度(U1|1, v1)

  • b12 = b12 + 相似度(U2|1, v2)

  • b21 = b21 + 相似度(U1|2, v1)

  • b22 = b22 + 相似度(U2|2, v2)

其中歸一函數(shù)是 softmax 函數(shù),壓縮函數(shù)是 squash 函數(shù)旅挤,相似度函數(shù)是 dot product踢关。下面接著用實(shí)例來(lái)解釋上述步驟。

初始化概率和參數(shù):

image

初始化所有 b 為零粘茄,根據(jù) softmax 函數(shù)計(jì)算出所有 c 都是 0.5签舞。該初始化是符合直覺(jué)的,一開(kāi)始“三角形或長(zhǎng)方形到底是帆船還是房子的一部分”這樣一個(gè)判斷是最不確定的柒瓣,而 50% 的概率對(duì)應(yīng)著這種最不確定情景儒搭。

預(yù)測(cè)-加總-壓縮:

image

預(yù)測(cè)就是用姿態(tài)矩陣做了轉(zhuǎn)化 (見(jiàn)小節(jié) 1.1),分別由長(zhǎng)方形和三角形的位置預(yù)測(cè)了房子/帆船的位置:

image

加總就是分別將房子/帆船的預(yù)測(cè)位置求個(gè)加權(quán)總和芙贫,可以理解成房子/帆船的平均位置

image

壓縮就是單位化位置向量:

image

更新參數(shù):

參數(shù) b 就是從三角形/長(zhǎng)方形推出房子/帆船的可能性搂鲫,如圖:

image
image

上圖已解釋的很清楚,核心思想就是

  • 當(dāng)兩個(gè)物體相似時(shí)磺平,它們的點(diǎn)積比較大魂仍,從而增大可能性
  • 當(dāng)兩個(gè)物體相異時(shí),它們的點(diǎn)積比較小拣挪,從而減小可能性
image

最后用 softmax 更新概率 cij

image

重復(fù)以上預(yù)測(cè)-加總-壓縮的步驟擦酌,循環(huán) r 次結(jié)束。

image

最后用以下規(guī)則來(lái)判斷到底從低層 VN 路由到高層 VN:

  • 如果 b11 > b12 則 c11 > c12菠劝,那么三角形路由到房子概率大赊舶,反之路由到帆船概率大
  • 如果 b21 > b22 則 c21 > c22,那么長(zhǎng)方形路由到房子概率大赶诊,反之路由到帆船概率大

3.2

代碼解析

基本引入包和設(shè)置

Import useful packages

importnumpy asnp

importtensorflow astf

%matplotlib inline

importmatplotlib.pyplot asplt

Reset the default graph for rerun notebook

tf.reset_default_graph()

Reset the random seed for reproducibility

np.random.seed(42)

tf.set_random_seed(42)

讀取 MNIST 數(shù)據(jù)

from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets("/tmp/data/")

n_samples = 5

plt.figure(figsize=(n_samples * 2, 3))

forindexin range(n_samples):

plt.subplot(1, n_samples, index+ 1)

sample_image = mnist.train.images[index].reshape(28, 28)

plt.imshow(sample_image, cmap="binary")

plt.axis("off")

plt.show()

image

從 tensorflow 數(shù)據(jù)庫(kù)里引進(jìn) MNSIT 數(shù)據(jù)笼平,選出 5 個(gè)樣本打印出來(lái)。

特征 X 和標(biāo)簽 y

X= tf.placeholder(shape=[None, 28, 28, 1], dtype=tf.float32, name="X")

y= tf.placeholder(shape=[None], dtype=tf.int64, name="y")

定義特征 X和標(biāo)簽 y甫何,placeholder 是占位符的意思出吹,用于創(chuàng)建占位,當(dāng)需要時(shí)再將真正的數(shù)據(jù)傳入進(jìn)去辙喂,即利用 feed_dict 的字典結(jié)構(gòu)給 placeholder 變量“喂數(shù)據(jù)”。Placeholder 有三個(gè)參數(shù):

  1. 數(shù)據(jù)維數(shù)
  2. 數(shù)據(jù)類(lèi)型
  3. 數(shù)據(jù)命名

X有四維,分別是圖片個(gè)數(shù)巍耗,寬度像素秋麸,高度像素,色彩維度炬太。

  • 圖片個(gè)數(shù)在定義占位符時(shí)不知道灸蟆,只有在喂數(shù)據(jù)時(shí)才知道,因此用 None
  • 圖片都包含 28x28 像素亲族,每個(gè)像素用 float32 類(lèi)型表示
  • 圖片是黑白的炒考,沒(méi)有 RGB,因此維度是 1

y只有一維霎迫,就是圖片個(gè)數(shù)斋枢。其標(biāo)簽值就是用 0 到 9 的 int64 類(lèi)型表示。

卷積層

conv1_params = {

"filters": 256,

"kernel_size": 9,

"strides": 1,

"padding": "valid",

"activation": tf.nn.relu,

}

conv1 = tf.layers.conv2d(X, name="conv1", **conv1_params)

首先在字典 conv1_params 里定義卷積層的參數(shù)知给,濾波器個(gè)數(shù) 256瓤帚、濾波器大小 9、步長(zhǎng) 1涩赢,填充 valid 指的沒(méi)有填充戈次、激活函數(shù)用的 relu。然后用 tensorflow 里的函數(shù) conv2d 建立 conv1筒扒,其中 ** 代表傳遞一個(gè)字典類(lèi)型的變量怯邪。最終 conv1 的 shape 是[?, 20, 20, 256],其中 ?代表之后才確定的圖片個(gè)數(shù)花墩。

Primary Capsules

caps1_n_maps = 32

caps1_n_dims = 8

conv2_params = {

"filters": caps1_n_maps * caps1_n_dims,

"kernel_size": 9,

"strides": 2,

"padding": "valid",

"activation": tf.nn.relu

}

conv2 = tf.layers.conv2d(conv1, name="conv2", **conv2_params)

建立 conv2 和 conv1是一樣的悬秉,conv2 的 shape 是 [?, 6, 6, 256]。這里 256 其實(shí)是 32 和 8 的乘積观游,由小節(jié) 2.5 可知搂捧,該層實(shí)際用了 32 個(gè)濾波器濾了 8 遍。

更需要注意的是懂缕,該層 (PrimaryCaps) 每個(gè) Capsule (1x8 向量) 和下層 (DigitCaps) 每個(gè) Capsule (1x16 向量) 全連接允跑,那么最好生成一個(gè)變量含有 1152 個(gè) Capsule,因此將 conv2 的 shape 轉(zhuǎn)成 [?, 1152, 8](總元素和 6x6x256 一樣多)搪柑,該變量記做 caps1_raw, 見(jiàn)下圖代碼聋丝。

caps1_n_caps = caps1_n_maps * 6* 6

caps1_raw = tf.reshape(conv2, [-1, caps1_n_caps, caps1_n_dims],

name="caps1_raw")

Reshape 函數(shù)里面 -1指的是某個(gè)維度大小,使得變換維度后的變量和變換前的變量的總元素個(gè)數(shù)不變工碾。比如 A 原來(lái)的 shape 是 [3, 2, 3]弱睦,如果 B 用

  • reshape(A, [-1,9]),則 B.shape = [2,9]
  • reshape(A, [9,-1])渊额,則 B.shape = [9,2]
  • reshape(A, [2,-1,3])况木,則 B.shape = [2,3,3]

定義壓縮函數(shù) squash

def squash(s, axis=-1, epsilon=1e-7, name=None):

with tf.name_scope(name, default_name="squash"):

squared_norm = tf.reduce_sum(tf.square(s), axis=axis,

keep_dims=True)

safe_norm = tf.sqrt(squared_norm + epsilon)

squash_factor = squared_norm / (1. + squared_norm)

unit_vector = s / safe_norm

returnsquash_factor * unit_vector

image

這里有個(gè)技巧垒拢,在分母 ||s|| 里面加入小量10-7,防止分母為零火惊。最后用 squash 函數(shù)將 caps1_raw單位化得到 cap1_output求类。它的 shape 也是 [?, 1152, 8]。

caps1_output= squash(caps1_raw, name="caps1_output")

Digit Capsules

根據(jù)小節(jié) 2.3屹耐,1152 個(gè) PrimaryCaps 的變量 (1x8) 需要乘以姿態(tài)矩陣 (8x16) 得到 10 個(gè) DigitCaps 的變量 (1x16)尸疆。下面設(shè)計(jì)的高維矩陣相乘是一種最高效的做法。

image

其中

  • 第一個(gè)數(shù)組的 shape 是 [1152, 10, 16, 8]
  • 第二個(gè)數(shù)組的 shape 是 [1152, 10, 8, 1]
  • 第三個(gè)數(shù)組的 shape 是 [1152, 10, 16, 1]

上面數(shù)組已經(jīng)是四維了惶岭,但別忘了還有圖片個(gè)數(shù)這一維寿弱,需要用 tensorflow 里面的 tile 函數(shù)來(lái)增加一維。見(jiàn)下面三塊代碼:

數(shù)組 W

caps2_n_caps = 10

caps2_n_dims = 16

init_sigma = 0.01

W_init = tf.random_normal(

shape=(1, caps1_n_caps, caps2_n_caps, caps2_n_dims, caps1_n_dims),

stddev=init_sigma, dtype=tf.float32, name="W_init")

W = tf.Variable(W_init, name="W")

batch_size = tf.shape(X)[0]

W_tiled = tf.tile(W, [batch_size, 1, 1, 1, 1], name="W_tiled")

首先定義一個(gè)四維隨機(jī)變量 W_init按灶,當(dāng) W的初始值症革,它的 shape 是 [1152, 10, 16, 8],batch_size 是一批圖片的個(gè)數(shù)兆衅。tile 函數(shù)實(shí)際就是將 W復(fù)制了batch_size個(gè)地沮,儲(chǔ)存在 W_tiled,它的 shape 是 [?, 1152, 10,16, 8]羡亩,如下圖:

image

數(shù)組 u

caps1_output_expanded = tf.expand_dims(caps1_output, -1,

name="caps1_output_expanded")

caps1_output_tile = tf.expand_dims(caps1_output_expanded, 2,

name="caps1_output_tile")

caps1_output_tiled = tf.tile(caps1_output_tile, [1, 1, caps2_n_caps, 1, 1],

name="caps1_output_tiled")

這一步是最讓人困惑的摩疑。

  • 首先看最終想要的結(jié)果的 shape 是 [?, 1152, 10, 8, 1],而 caps1_output 的 shape 是 [?, 1152, 8]
  • 需要在最后的 axis 上擴(kuò)張一維畏铆,用 expand_dims 函數(shù)和參數(shù) -1雷袋,得到 caps1_output_expanded 的 shape 是 [?, 1152, 8, 1]
  • 需要在第二個(gè) axis 上擴(kuò)張一維,用 expand_dims 函數(shù)和參數(shù) 2辞居,得到 caps1_output_tile 的 shape 是 [?, 1152, 1, 8, 1]
  • 用 tile 函數(shù)將第三個(gè) axis 上復(fù)制 10 個(gè)楷怒,得到 caps1_output_tiled 的 shape 是 [?, 1152, 10, 8, 1]
image

數(shù)組 u_hat

caps2_predicted = tf.matmul(W_tiled, caps1_output_tiled,

name="caps2_predicted")

函數(shù) matmul 是將高維數(shù)組中每個(gè)矩陣元素相乘

  • 用 shape 為 [?, 1152, 10, 16, 8] 的 W_tiled
  • 乘以 shape 為 [?, 1152, 10, 8, 1] 的 caps1_output_tiled
  • 等于 shape 為 [?, 1152, 10, 16, 1] 的 caps2_predicted

如下圖所示:

image

動(dòng)態(tài)路由

第一輪初始化 b

b= tf.zeros([batch_size, caps1_n_caps, caps2_n_caps, 1, 1],

dtype=np.float32, name="raw_weights")

b的 shape 為 [?, 1152, 10, 1, 1]。

第一輪初始化 c

c= tf.nn.softmax(raw_weights, dim=2, name="routing_weights")

c 的 shape 為 [?, 1152, 10, 1, 1]瓦灶,而且在第二個(gè) axis 上做歸一化鸠删,原因就是每一個(gè) caps1 到所有 caps2 的概率總和為一。

第一輪計(jì)算 sv

weighted_predictions = tf.multiply(c, caps2_predicted,

name="weighted_predictions")

s = tf.reduce_sum(weighted_predictions, axis=1,

keep_dims=True, name="weighted_sum")

v = squash(s, axis=-2, name="caps2_output_round_1")

weighted_predictions 的 shape 為 [?, 1152, 10, 16, 1]贼陶,而 sv的 shape 為 [?, 1, 10, 16, 1]刃泡,因?yàn)樵诘谝粋€(gè) axis 上用 reduce_sum 函數(shù)求和再用 squash 函數(shù)壓縮。

第二輪迭代

v_tiled = tf.tile(v, [1, caps1_n_caps, 1, 1, 1],

name="caps2_output_round_1_tiled")

agreement = tf.matmul(caps2_predicted, v_tiled,

transpose_a=True, name="agreement")

b= tf.add(b, agreement, name="raw_weights_round_2")

c= tf.nn.softmax(b, dim=2, name="routing_weights_round_2")

weighted_predictions = tf.multiply(c, caps2_predicted,

name="weighted_predictions_round_2")

s = tf.reduce_sum(weighted_predictions, axis=1,

keep_dims=True, name="weighted_sum_round_2")

v = squash(s, axis=-2, name="caps2_output_round_2")

第三輪迭代

v_tiled = tf.tile(v, [1, caps1_n_caps, 1, 1, 1],

name="caps2_output_round_2_tiled")

agreement = tf.matmul(caps2_predicted, v_tiled,

transpose_a=True, name="agreement")

b= tf.add(b, agreement, name="raw_weights_round_3")

c= tf.nn.softmax(b, dim=2, name="routing_weights_round_3")

weighted_predictions = tf.multiply(c, caps2_predicted,

name="weighted_predictions_round_3")

s = tf.reduce_sum(weighted_predictions, axis=1,

keep_dims=True, name="weighted_sum_round_3")

v = squash(s, axis=-2, name="caps2_output_round_3")

上面這種寫(xiě)出每一輪迭代的方法有點(diǎn)低效碉怔,一種替代方法可以用 for 語(yǔ)句烘贴,但是它是靜態(tài)循環(huán) (static loop), 在 tensorflow 里面每定義一次操作都會(huì)增大內(nèi)部的流程圖。這里三次迭代沒(méi)問(wèn)題撮胧,如果很多的建議用 tf.while_loop() 函數(shù)桨踪,這個(gè)是動(dòng)態(tài)循環(huán) (dynamic loop)。除了減小流程圖大小以外芹啥,動(dòng)態(tài)循環(huán)還能減少 GPU RAM 的使用锻离。

間隔損失

m_plus= 0.9

m_minus= 0.1

lambda_= 0.5

T= tf.one_hot(y, depth=caps2_n_caps, name="T")

v_norm= tf.norm(v, axis=-2, keep_dims=True, name="caps2_output_norm")

FP_raw= tf.square(tf.maximum(0., m_plus - v_norm), name="FP_raw")

FP= tf.reshape(FP_raw, shape=(-1, 10), name="FP")

FN_raw= tf.square(tf.maximum(0., v_norm - m_minus), name="FN_raw")

FN= tf.reshape(FN_raw, shape=(-1, 10), name="FN")

L= tf.add(T * FP, lambda_ * (1.0- T) * FN, name="L")

margin_loss= tf.reduce_mean(tf.reduce_sum(L, axis=1), name="margin_loss")

實(shí)現(xiàn)小節(jié) 2.5 里面的公式铺峭,用 one_hot 函數(shù)將數(shù)字轉(zhuǎn)換成 0-1 的啞變量矩陣。

Mask 機(jī)制

mask_with_labels = tf.placeholder_with_default(False, shape=(),

name="mask_with_labels")

reconstruction_targets = tf.cond(mask_with_labels, # condition

lambda:y, # ifTrue

lambda:y_pred, # ifFalse

name="reconstruction_targets")

reconstruction_mask = tf.one_hot(reconstruction_targets,

depth=caps2_n_caps,

name="reconstruction_mask")

reconstruction_mask_reshaped = tf.reshape(

reconstruction_mask, [-1, 1, caps2_n_caps, 1, 1],

name="reconstruction_mask_reshaped")

caps2_output_masked = tf.multiply(

v, reconstruction_mask_reshaped,

name="caps2_output_masked")

在重構(gòu)中纳账,并不是每一個(gè)數(shù)字的輸出都傳送到解碼器的逛薇,只有目標(biāo)數(shù)字的輸出才需要傳送出去捺疼,因此需要做一個(gè) one_hot 轉(zhuǎn)換疏虫。此外

  • 在訓(xùn)練中,需要傳出的是 y
  • 在測(cè)試中啤呼,需要傳出的是 y_pred
image

解碼器

n_hidden1 = 512

n_hidden2 = 1024

n_output = 28* 28

decoder_input = tf.reshape(caps2_output_masked,

[-1, caps2_n_caps * caps2_n_dims],

name="decoder_input")

with tf.name_scope("decoder"):

hidden1 = tf.layers.dense(decoder_input, n_hidden1,

activation=tf.nn.relu,

name="hidden1")

hidden2 = tf.layers.dense(hidden1, n_hidden2,

activation=tf.nn.relu,

name="hidden2")

decoder_output = tf.layers.dense(hidden2, n_output,

activation=tf.nn.sigmoid,

name="decoder_output")

解碼器由個(gè) 3 全連接層組成卧秘,每層大小分別為 512,1024 和 784,用layers.dense 函數(shù)來(lái)構(gòu)建官扣。

重構(gòu)損失

X_flat = tf.reshape(X, [-1, n_output], name="X_flat")

squared_difference = tf.square(X_flat - decoder_output,

name="squared_difference")

reconstruction_loss = tf.reduce_sum(squared_difference,

name="reconstruction_loss")

最終損失

alpha= 0.0005

loss= tf.add(margin_loss, alpha * reconstruction_loss, name="loss")

額外設(shè)置

全局初始化

init= tf.global_variables_initializer()

saver= tf.train.Saver()

計(jì)算精度

correct= tf.equal(y, y_pred, name="correct")

accuracy= tf.reduce_mean(tf.cast(correct, tf.float32), name="accuracy")

用 Adam 優(yōu)化器

optimizer= tf.train.AdamOptimizer()

training_op= optimizer.minimize(loss, name="training_op")

小結(jié)

以上已經(jīng)完成構(gòu)建所有的網(wǎng)絡(luò)結(jié)構(gòu)翅敌,接下來(lái)訓(xùn)練和測(cè)試的步驟都非常標(biāo)準(zhǔn)化,就不再多言了惕蹄。需要提醒的是蚯涮,在訓(xùn)練時(shí),mask_with_labels 設(shè)置成 True卖陵,y 被傳出去用在重構(gòu)損失函數(shù)里遭顶,如下圖:

image

在測(cè)試時(shí),mask_with_labels 設(shè)置成 False泪蔫,y_pred 被傳出去用在重構(gòu)損失函數(shù)里棒旗,如下圖:

image

4

總結(jié)

深度學(xué)習(xí),本質(zhì)就是一系列的張量變換 (tensor transformation)撩荣。Capsule 現(xiàn)在將神經(jīng)元的輸入和輸出升級(jí)成二維向量铣揉,以后很容易會(huì)將其延伸為高維張量。

在識(shí)別數(shù)字上餐曹,人只需要看幾十個(gè)最多幾百個(gè)樣例就能分辨數(shù)字逛拱。Capsule 只需要 CNN 需要的一小部分樣例就能達(dá)到同等水平,而 CNN 通常需要上萬(wàn)的數(shù)據(jù)台猴,從這點(diǎn)看 Capsule 的運(yùn)作方式比 CNN 更接近人的大腦朽合。此外Capsule還可以識(shí)別重疊數(shù)字。

不過(guò) CapsNet 在 ImageNet 數(shù)據(jù)集上訓(xùn)練起來(lái)太耗時(shí)卿吐,而且目前這個(gè)路由算法過(guò)于簡(jiǎn)單 (Hinton 論文坑已挖好旁舰,等著大家來(lái)填)。最有趣的是從論文結(jié)果來(lái)看嗡官,引進(jìn)重構(gòu)比沒(méi)引進(jìn)重構(gòu)的識(shí)別誤差小很多箭窜,這到底是 Capsule 的功勞,還是單單重構(gòu)的功勞衍腥?

本帖把 Capsule 的原理徹底弄清楚了磺樱,也提供了部分 tensorflow 代碼纳猫,希望對(duì)大家了解這個(gè)前言課題有所幫助。Stay Tuned!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末竹捉,一起剝皮案震驚了整個(gè)濱河市芜辕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌块差,老刑警劉巖侵续,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異憨闰,居然都是意外死亡状蜗,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)鹉动,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)轧坎,“玉大人,你說(shuō)我怎么就攤上這事泽示「籽” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵械筛,是天一觀的道長(zhǎng)捎泻。 經(jīng)常有香客問(wèn)我,道長(zhǎng)变姨,這世上最難降的妖魔是什么族扰? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮定欧,結(jié)果婚禮上渔呵,老公的妹妹穿的比我還像新娘。我一直安慰自己砍鸠,他們只是感情好扩氢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著爷辱,像睡著了一般录豺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上饭弓,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天双饥,我揣著相機(jī)與錄音劈猪,去河邊找鬼宝穗。 笑死夹抗,一個(gè)胖子當(dāng)著我的面吹牛值桩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蜗元,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼傻咖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼它碎!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起棚菊,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤浸踩,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后统求,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體检碗,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年球订,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了后裸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡冒滩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出浪谴,到底是詐尸還是另有隱情开睡,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布苟耻,位于F島的核電站篇恒,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏凶杖。R本人自食惡果不足惜胁艰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望智蝠。 院中可真熱鬧腾么,春花似錦、人聲如沸杈湾。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)漆撞。三九已至殴泰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浮驳,已是汗流浹背悍汛。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留至会,地道東北人离咐。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像奋献,于是被迫代替她去往敵國(guó)和親健霹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子旺上,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容