在Caffe中加入PythonLayer

下面是一些參考鏈接

[1]Caffe Python Layer

[2]Using Python Layers in your Caffe models with DIGITS

[3]What is a “Python” layer in caffe?

[4]caffe python layer

[5]Building custom Caffe layer in python

[6]Aghdam, Hamed Habibi, and Elnaz Jahani Heravi. Guide to Convolutional Neural Networks: A Practical Application to Traffic-Sign Detection and Classification. Springer, 2017.

[7]Softmax with Loss Layer

1. 準(zhǔn)備工作

1.1 系統(tǒng)配置

Ubuntu 16.04 LTS

Caffe:https://github.com/BVLC/caffe

Python 2.7.14

1.2 編譯Caffe

按照一般的caffe編譯流程(可參考官網(wǎng),也可參考Install caffe in Ubuntu)就好,唯一的區(qū)別就是在Makefile.config中聪轿,把這一行修改一下:

# WITH_PYTHON_LAYER := 1

改成

WITH_PYTHON_LAYER := 1

說明我們是要使用python_layer這個(gè)功能的虚缎。然后編譯成功后,在Terminator中輸入:

$ caffe$ python>>> import caffe

像這樣,沒有給你報(bào)錯(cuò),說明caffe和python_layer都編譯成功啦.

1.3 添加Python路徑

1.3.1 在~/.bashrc文件中加入路徑

寫自己的python layer勢必需要.py文件,為了讓caffe運(yùn)行的時(shí)候可以找到你的py文件晨逝,接下來需要把py文件的路徑加到python的系統(tǒng)路徑中,步驟是:

打開Terminator

輸入vi ~/.bashrc

輸入i懦铺,進(jìn)入編輯模式

在打開的文件的末尾添加

export PYTHONPATH=/path/to/my_python_layer:$PYTHONPATH

鍵入esc捉貌,:wq,回車阀趴,即可保存退出

如果這部分沒有看明白昏翰,需要上網(wǎng)補(bǔ)一下如何在Linux環(huán)境中用vim語句修改文檔的知識. 實(shí)質(zhì)上就是修改一個(gè)在~/路徑下的叫.bashrc的文檔.

1.3.2 在要訓(xùn)練的.sh腳本文件中加入路徑

首先將PythonLayer放入任意位置(要知道其所在路徑),然后在訓(xùn)練網(wǎng)絡(luò)的.sh腳本文件中加入PythonLayer腳本路徑

export PYTHONPATH=/home/用戶名/pythonlayer文件夾名

2. 修改代碼

首先我們定義一個(gè)要實(shí)現(xiàn)的目標(biāo):訓(xùn)練過程中刘急,在Softmax層和Loss層之間棚菊,加入一個(gè)Python Layer,使得這個(gè)Layer的輸入等于輸出. 換句話說叔汁,這個(gè)Layer沒有起到一點(diǎn)作用统求,正向傳播的時(shí)候y=x,反向傳播的時(shí)候?qū)?shù)y'=1. 因此訓(xùn)練的結(jié)果應(yīng)該和沒加很相似.

2.1 train_val.prototxt

這個(gè)文檔是Caffe訓(xùn)練的時(shí)候据块,定義數(shù)據(jù)和網(wǎng)絡(luò)結(jié)構(gòu)用的码邻,所以如果用添加新的層,需要在這里定義. 第一步是在網(wǎng)絡(luò)結(jié)構(gòu)的定義中找到添加Python Layer的位置另假,根據(jù)問題的定義像屋,Python Layer應(yīng)該在softmax和loss層之間,不過網(wǎng)上的prototxt大多會把這兩個(gè)層合并在一起定義边篮,成為了

layer{name:"loss"type:"SoftmaxWithLoss"bottom:"fc8_2"bottom:"label"top:"loss"}

我們需要把這個(gè)層拆開己莺,變成softmax層和loss層奏甫,根據(jù)Caffe提供的官方文檔,我們知道SoftmaxWithLoss是softmax層和MultinomialLogisticLoss的合并.

The softmax loss layer computes the multinomial logistic loss of the softmax of its inputs. [7]

那拆開后的代碼就是

layer{name:"output_2"type:"Softmax"bottom:"fc8_2"top:"output_2"}layer{name:"loss"type:"MultinomialLogisticLoss"bottom:"output_2"bottom:"label"top:"loss"}

拆完了以后就只需要把你定義的Python Layer加到它們中間就好了凌受,注意這個(gè)層的輸出和輸出阵子,輸入是bottom,輸出是top胜蛉,這兩個(gè)值需要和上一層的softmax輸出和下一層的loss輸入對接好挠进,就像這樣(請仔細(xì)看注釋和代碼):

layer {# softmax層name:"output_2"type:"Softmax"bottom:"fc8_2"# 是上一層Fully Connected Layer的輸出top:"output_2"# 是Softmax的輸出,Python Layer的輸入}layer {type:"Python"name:"output"bottom:"output_2"# 要和Softmax輸出保持一致top:"output"# Python Layer的輸出python_param {? ? module:"my_layer"# 調(diào)用的Python代碼的文件名# 也就是1.3中添加的Python路徑中有一個(gè)python文件叫my_layer.py# Caffe通過這個(gè)文件名和Python系統(tǒng)路徑誊册,找到你寫的python代碼文件layer:"MyLayer"# my_layer.py中定義的一個(gè)類领突,在下文中會講到# MyLayer是類的名字param_str:'{ "x1": 1, "x2": 2 }'# 額外傳遞給my_layer.py的值# 如果沒有要傳遞的值,可以不定義. 相當(dāng)于給python的全局變量# 當(dāng)Python Layer比較復(fù)雜的時(shí)候會需要用到.}}layer {? name:"loss"type:"MultinomialLogisticLoss"bottom:"output"# 要和Python Layer輸出保持一致bottom:"label"# loss層的另一個(gè)輸入# 因?yàn)橐?jì)算output和label間的距離top:"loss"# loss層的輸出解虱,即loss值}

加完以后的參數(shù)傳遞如圖

2.2 my_layer.py

重頭戲其實(shí)就是這一部分攘须,以上說的都是相對固定的修改漆撞,不存在什么算法層面的改動殴泰,但是python里面不一樣,可以實(shí)現(xiàn)很多調(diào)整和試驗(yàn)性的試驗(yàn). 最最基本的就是加入一個(gè)上面定義的那個(gè)"可有可無"的Python Layer.

在Python的文件中浮驳,需要定義類悍汛,類的里面包括幾個(gè)部分:

setup( ): 用于檢查輸入的參數(shù)是否存在異常,初始化的功能.

reshape( ): 也是初始化至会,設(shè)定一下參數(shù)的size

forward( ): 前向傳播

backward( ): 反向傳播

結(jié)構(gòu)如下:

importcaffeclassMyLayer(caffe.Layer):defsetup(self, bottom, top):passdefreshape(self, bottom, top):passdefforward(self, bottom, top):passdefbackward(self, top, propagate_down, bottom):pass

根據(jù)需要慢慢地填充這幾個(gè)函數(shù)离咐,關(guān)于這方面的知識,我很推薦閱讀"Guide to Convolutional Neural Networks: A Practical Application to Traffic-Sign Detection and Classification." 中的這個(gè)章節(jié) [6].

setup()的定義:

def setup(self, bottom, top):# 功能1: 檢查輸入輸出是否有異常iflen(bottom) !=1:? ? ? ? raiseException("異常:輸入應(yīng)該就一個(gè)值奉件!")iflen(top) !=1:? ? ? ? raiseException("異常:輸出應(yīng)該就一個(gè)值宵蛀!")# 功能2: 初始化一些變量,后續(xù)可以使用self.delta = np.zeros_like(bottom[0].data, dtype=np.float32)# 功能3: 接受train_val.prototxt中設(shè)置的變量值params =eval(self.param_str)self.x1 = int(params["x1"])self.x2 = int(params["x2"])

reshape()的定義:

defreshape(self, bottom, top):# 功能1: 修改變量的sizetop[0].reshape(*bottom[0].data.shape)# 看了很多材料县貌,我感覺這個(gè)函數(shù)就是比較雞肋的那種.# 這個(gè)函數(shù)就像格式一樣术陶,反正寫上就好了...# 不知道還有其他什么功能了,歡迎補(bǔ)充煤痕!

forward()的定義:

這個(gè)函數(shù)可以變的花樣就多了梧宫,如果是要定義不同的loss function,可以參考[1]摆碉,稍微高級一點(diǎn)的可以參考[2]塘匣,這里就實(shí)現(xiàn)一個(gè)y=x的簡單功能.

defforward(self, bottom, top):# 目標(biāo):y = x# bottom相當(dāng)于輸入x# top相當(dāng)于輸出ytop[0].data[...] = bottom[0].data[:]# 哈哈哈哈,是不是感覺被騙了巷帝,一行代碼就完事兒了:-)

了解bottom中數(shù)據(jù)的存儲結(jié)構(gòu)是比較重要的忌卤,因?yàn)閰⒖嘉臋n不多,我只能通過print out everything來了解bottom里面究竟存著些什么. 回想在2.1的prototxt中楞泼,我們有定義輸入Python Layer的都有什么(bottom). bottom可以有多個(gè)定義驰徊,如果像例子中的只有一個(gè)bottom: "output_2"团甲,那么bottom[0].data中就存著output_2的值,當(dāng)二分類問題時(shí)也就是兩列叮阅,一列是Softmax后屬于label 0的概率璧函,一列是Softmax后屬于label 1的概率. 當(dāng)bottom定義了多個(gè)輸入的時(shí)候,即

layer{type:"Python"name:"output"bottom:"output_2"bottom:"label"top:"output"python_param {? ? ...? }}

那么按照順序勋桶,bottom[0].data中依舊存著output_2脱衙,bottom[1].data中存著label值,以此類推例驹,可以定義到bottom[n]捐韩,想用的時(shí)候調(diào)用bottom[n].data就可以了. top[n].data和bottom的情況類似,也是取決于prototxt中的定義.

想象一下鹃锈,既然你可以掌控了output_2和label和其他你需要的任何值(通過bottom或者param_str定義)荤胁,是不是可以在這個(gè)forward()函數(shù)里面大展身手了?

是的.

但是同時(shí)屎债,也要負(fù)責(zé)計(jì)算這個(gè)前饋所帶來的梯度仅政,可以自己定義變量存起來,網(wǎng)上修改loss函數(shù)的例子就是拿self.diff來存梯度的盆驹,不過在這個(gè)例子中圆丹,因?yàn)樘荻仁?,所以我沒有管它.

backward()的定義:

defbackward(self, top, propagate_down, bottom):# 由于是反向傳播躯喇,top和bottom的意義就和前向傳播反一反# top:從loss層傳回來的值# bottom:反向?qū)拥妮敵鰂oriinrange(len(propagate_down)):ifnotpropagate_down[i]:continuebottom[i].diff[...] = top[i].diff[:]# 其實(shí)還要乘以這個(gè)層的導(dǎo)數(shù)辫封,但是由于y=x的導(dǎo)數(shù)是1.# 所以無所謂了,直接把loss值一動不動的傳遞下來就好.

對于top和bottom在forward()和backward()函數(shù)中不同的意義廉丽,不要懵...

top[i].diff是從loss層返回來的梯度倦微,以二分類為例,它的結(jié)構(gòu)是兩列正压,一列是label 0的梯度欣福,一列是label 1的梯度. 因此在backward()正常情況是需要把top[i].diff乘以self.diff的,也就是在forward()中算好的Python Layer自身的梯度. 然后賦值給bottom[i].diff蔑匣,反向傳播到上一層.

關(guān)于propagate_down這個(gè)東西劣欢,我認(rèn)為是定義是否在這個(gè)層做反向傳播(權(quán)值更新)的,也就是在遷移學(xué)習(xí)中裁良,如果要固定不更新某一層的參數(shù)凿将,就是用propagate_down來控制的. 不用管它,反正用默認(rèn)的代碼就好了.

總的來說价脾,要實(shí)現(xiàn)y=x這么一個(gè)層牧抵,需要寫的python代碼就是:

import caffeclassMyLayer(caffe.Layer):defsetup(self, bottom, top):? ? ? ? passdefreshape(self, bottom, top):? ? ? ? top[0].reshape(*bottom[0].data.shape)defforward(self, bottom, top):? ? ? ? top[0].data[...] = bottom[0].data[:]defbackward(self,self, top, propagate_down, bottom):foriinrange(len(propagate_down)):ifnotpropagate_down[i]:? ? ? ? ? ? ? ? continue? ? ? ? ? ? bottom[i].diff[...] = top[i].diff[:]

3. 結(jié)語

我認(rèn)為要在Caffe中寫好一個(gè)Python Layer,最重要的是抓住兩點(diǎn)

1)處理好prototxt到python文件的參數(shù)傳遞

2)不能忘了在forward()中計(jì)算反向傳播梯度

接下來就是一些代碼理解和學(xué)術(shù)創(chuàng)新的事情了,懂得如何寫Python Layer犀变,在運(yùn)動Caffe的過程中就多開了一扇窗妹孙,從此不再只是調(diào)整solver.prototxt,還有在train_val.prototxt中組合卷積層/池化層/全連接/Residual Unit/Dense Unit這些低級的修改.

更多的細(xì)節(jié)可以參考最前面的幾個(gè)參考鏈接還有自己的理解實(shí)踐. 在實(shí)踐過程中获枝,超級建議print所有你不了解的數(shù)據(jù)結(jié)構(gòu)蠢正,例如forward()中的bottom,top; backward()中的bottom省店,top嚣崭,即便Caffe用GPU加速,它也會給你打印出來你想要看的數(shù)據(jù)懦傍,一步一步的摸索數(shù)據(jù)的傳遞和存儲雹舀,這也是我花最多時(shí)間去弄明白的地方.

該文章主要引用于 :引用文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市粗俱,隨后出現(xiàn)的幾起案子说榆,更是在濱河造成了極大的恐慌,老刑警劉巖寸认,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件签财,死亡現(xiàn)場離奇詭異,居然都是意外死亡废麻,警方通過查閱死者的電腦和手機(jī)荠卷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門模庐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烛愧,“玉大人,你說我怎么就攤上這事掂碱×耍” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵疼燥,是天一觀的道長沧卢。 經(jīng)常有香客問我,道長醉者,這世上最難降的妖魔是什么但狭? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮撬即,結(jié)果婚禮上立磁,老公的妹妹穿的比我還像新娘。我一直安慰自己剥槐,他們只是感情好唱歧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般颅崩。 火紅的嫁衣襯著肌膚如雪几于。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天沿后,我揣著相機(jī)與錄音沿彭,去河邊找鬼。 笑死尖滚,一個(gè)胖子當(dāng)著我的面吹牛膝蜈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播熔掺,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼饱搏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了置逻?” 一聲冷哼從身側(cè)響起推沸,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎券坞,沒想到半個(gè)月后鬓催,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡恨锚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年宇驾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猴伶。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡课舍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出他挎,到底是詐尸還是另有隱情筝尾,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布办桨,位于F島的核電站筹淫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏呢撞。R本人自食惡果不足惜损姜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望殊霞。 院中可真熱鬧摧阅,春花似錦、人聲如沸脓鹃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至娇跟,卻和暖如春岩齿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背苞俘。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工盹沈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吃谣。 一個(gè)月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓乞封,卻偏偏與公主長得像,于是被迫代替她去往敵國和親岗憋。 傳聞我的和親對象是個(gè)殘疾皇子肃晚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354