日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 人工智能 > Caffe >内容正文

Caffe

从零开始山寨Caffe·壹:仰望星空与脚踏实地

發(fā)布時(shí)間:2025/3/17 Caffe 53 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从零开始山寨Caffe·壹:仰望星空与脚踏实地 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

請以“仰望星空與腳踏實(shí)地”作為題目,寫一篇不少于800字的文章。除詩歌外,文體不限。

                                  ——2010·北京卷

仰望星空

規(guī)范性

Caffe誕生于12年末,如果偏要形容一下這個(gè)框架,可以用"須敬如師長"。

這是一份相當(dāng)規(guī)范的代碼,這個(gè)規(guī)范,不應(yīng)該是BAT規(guī)范,那得是Google規(guī)范。

很多自稱碼農(nóng)的人應(yīng)該好好學(xué)習(xí)這份代碼,改改自己丑陋的C++編程習(xí)慣。

下面列出幾條重要的規(guī)范準(zhǔn)則:

★const

先說說const問題,Google為了增加代碼的可讀性,明確要求:

不做修改的量(涵蓋函數(shù)體內(nèi)、函數(shù)參數(shù)列表),必須以const標(biāo)記。

相對的,對于那些改變的量,可選擇用mutable標(biāo)記。

因?yàn)閙utable關(guān)鍵詞不是很常用,所以一般在自設(shè)函數(shù)中使用。

嚴(yán)格的const不在于擔(dān)心變量是否被誤修改,而在于給代碼閱讀者一個(gè)清晰的思路:

這個(gè)值不會(huì)改變,這個(gè)值肯定要改變。

★引用

"引用"是C/C++設(shè)計(jì)的一個(gè)敗筆,因?yàn)镃/C++默認(rèn)是深拷貝,這在大內(nèi)存數(shù)據(jù)結(jié)構(gòu)操作的時(shí)候,

容易讓新手程序員寫出弱智低能的代碼。假設(shè)Datum結(jié)構(gòu)A使用了2G內(nèi)存,令:

Datum B=A;

那么,內(nèi)存會(huì)占用4G空間,而且,我們大概需要幾秒的時(shí)間去拷貝A的2G內(nèi)存。

這個(gè)幾秒看起來不是很成問題,但是在多線程編程中,兩個(gè)異步線程共享數(shù)據(jù):

如果你不用引用會(huì)怎么樣?

很有趣,這個(gè)復(fù)制再賦值的操作會(huì)被CPU中斷,變成無效指令。

這在Caffe的多線程I/O設(shè)計(jì)架構(gòu)中,是個(gè)關(guān)鍵點(diǎn)。

另外,對于基本數(shù)據(jù)類型(char/int/float/double),引用是沒用必要的。

但是,string、vector<int>等容器,引用就相當(dāng)有必要了。

★const引用

const引用最常見于函數(shù)參數(shù)列表,用于傳遞常、大數(shù)據(jù)結(jié)構(gòu)量。

與此相對的,如果你要修改一個(gè)大數(shù)據(jù)結(jié)構(gòu)量,應(yīng)當(dāng)在參數(shù)列表中傳入指針,而不是引用。

傳入引用來修改是C規(guī)范,傳入指針來修改是C++規(guī)范,Caffe嚴(yán)格遵照C++規(guī)范,這點(diǎn)要明確。

★常成員函數(shù)

常成員函數(shù),在OO里通常容易被新手忽略掉。(Java就沒那么復(fù)雜),通常寫作:

void xxx() const,目的是:

const標(biāo)記住傳入成員函數(shù)的this指針。

常成員函數(shù)其實(shí)不是必要的,但是在一定情況下,就會(huì)變成必要的。

這個(gè)情況相當(dāng)有趣,而且在Caffe中也經(jīng)常發(fā)生:

void xxx(const Blob& blob){blob.count(); }

如果我們遵照Google的編程規(guī)范,用const引用鎖定傳入的Blob。

那么,blob.count()這個(gè)成員函數(shù)的調(diào)用就會(huì)被編譯器的語義分析為:成員變量不可修改。

如果你的代碼寫成這樣,那就會(huì)被編譯器攔下,錯(cuò)誤信息為:this指針不一致。

class Blob{ public:int count() {} //錯(cuò)誤int count() const {} //正確 };

★public、private、protected

OO的封裝性是比較難定位的一個(gè)規(guī)范,成員變量及成員函數(shù)如何訪問權(quán)限是個(gè)問題。

Caffe嚴(yán)格遵照標(biāo)準(zhǔn)的OO封裝概念:方法是public,變量是private或者是protected。

區(qū)別private和protected就一句話:

private成員變量或是函數(shù),不可能被繼承。通常只用在本Class獨(dú)有,而派生類不直使用的函數(shù)/變量上。

比如im2col和col2im,這兩個(gè)為卷積做Patch預(yù)變換的函數(shù)。

protected和private的成員函數(shù)和成員變量都不可能從外部被訪問,應(yīng)當(dāng)在public里專門設(shè)置訪問接口。

并且接口根據(jù)需要,恰當(dāng)使用const標(biāo)記,避免越權(quán)訪問。

有趣的是,如果這么做,會(huì)增加相當(dāng)多的代碼量,而且都是一些復(fù)制粘貼的廢品代碼。

為了避免這種情況,Google開發(fā)了Protocol Buffer,將數(shù)據(jù)結(jié)構(gòu)大部分訪問接口自動(dòng)生成,且獨(dú)立安排。

這樣,在主體代碼里,我們不會(huì)因?yàn)閿?shù)據(jù)的訪問接口的規(guī)范,而導(dǎo)致閱讀代碼十分頭疼(想想那一掃下來的廢代碼)。

獨(dú)立性

如果你研究過Word2Vec的源碼,應(yīng)該就知道,為什么Word2Vec必須跑在Linux下。。

因?yàn)镸ikolov同學(xué)在寫代碼的時(shí)候,用了POSIX OS的API函數(shù)pThread,來實(shí)現(xiàn)內(nèi)核級線程。

這為跨平臺(tái)帶來麻煩,一份優(yōu)秀的跨平臺(tái)代碼,必須具有相當(dāng)出色的平臺(tái)獨(dú)立性。

在這點(diǎn)上,Caffe使用了C++最強(qiáng)大的Boost庫,來避免對OS API函數(shù)的使用。

?

Boost庫,又稱為C++三千佳麗的后宮,內(nèi)涵1W+頭文件,完整編譯完大小達(dá)3.3G,相當(dāng)龐大。

它的代碼來自世界上頂級的C++開發(fā)者,是C++最忠實(shí)的第三方庫,并且是ISO C++新規(guī)范的唯一來源。

Boost在Caffe中的主要作用是提供OS獨(dú)立的內(nèi)核級線程。

當(dāng)然,已經(jīng)于C++11中被列入規(guī)范的boost::shared_ptr其實(shí)也算。

還有一個(gè)十分精彩的boost::thread_specific_ptr,也在Caffe中起到了核心作用。

?

不足之處也有,而且其中一處還成了Bug,那就是API函數(shù)之一的open。

Linux的open默認(rèn)是以二進(jìn)制打開的,而Windows則是以文本形式打開的。

移植到Windows時(shí),需要補(bǔ)上 O_BINARY作為flag。

異構(gòu)性

大家都知道Caffe能跑GPU,一個(gè)關(guān)鍵點(diǎn)是:

它是在何處,又是怎么進(jìn)行CPU與GPU分離的?

這個(gè)模型實(shí)際上應(yīng)當(dāng)算是CUDA標(biāo)準(zhǔn)模型。

由于內(nèi)存顯存不能跨著訪問(一個(gè)在北橋,一個(gè)在南橋),又要考慮的CPU和GPU的平衡。

所以,數(shù)據(jù)的讀取、轉(zhuǎn)換不僅要被平攤到CPU上,而且應(yīng)當(dāng)設(shè)計(jì)成多線程,多線程的生產(chǎn)者消費(fèi)者模型。

并且具有一定的多重緩沖能力,這樣保證最大化CPU/GPU的計(jì)算力。

在一個(gè)機(jī)器學(xué)習(xí)系統(tǒng)當(dāng)中,我們要珍惜計(jì)算設(shè)備的每一個(gè)時(shí)鐘周期,切實(shí)做到計(jì)算力的最大化利用。

設(shè)計(jì)模式

實(shí)際使用的設(shè)計(jì)模式只有兩個(gè)。

第一個(gè)是MVC,這個(gè)其實(shí)是迫不得已。

異構(gòu)編程決定著,數(shù)據(jù)、視圖、控制三大塊必須獨(dú)立開來。

但視圖和控制并不是很明顯,在設(shè)計(jì)接口/可視化GUI的時(shí)候,將凸顯重要性。

?

第二個(gè)稱為工廠模式,這是一個(gè)存在于Java的概念,盡管C++也可以模仿。

具體來說,工廠模式是為了彌補(bǔ)面向?qū)ο笮途幾g語言的不足,會(huì)被OO的多態(tài)所需要。

以Caffe為例:

我們當(dāng)前有一個(gè)基類指針Layer* layer;

在程序運(yùn)行之前,計(jì)算機(jī)并不知道這個(gè)指針究竟要指向何種派生類。是卷積層?Pooling層?ReLU層?

鬼才知道。一個(gè)愚蠢的方法:

if(type==CONV) {....} else if(type==POOLING) {....} else if(type==RELU} {.....} else {ERROR}

看起來,還是可以接受的,但是在軟件工程專業(yè)看來,這種模式相當(dāng)?shù)么馈?/p>

工廠模式借鑒了工廠管理產(chǎn)品的經(jīng)驗(yàn),將各種類型存在數(shù)據(jù)庫中,需要時(shí),拿出來看看。

這種模式相當(dāng)?shù)渺`活,當(dāng)然,在Caffe中作用不是很大,僅僅是為了花式好看。

要實(shí)現(xiàn)這個(gè)模式,你只需要一個(gè)關(guān)聯(lián)容器(C++/JAVA),字典容器(Python)。

將string與創(chuàng)建指針綁定即可。

C/C++中有函數(shù)指針的說法,如:

typedef boost::shared_ptr< Layer<Dtype> > (*NEW_FUNC)(const LayerParameter& );

經(jīng)過typdef之后,NEW_FUNC就可以指向函數(shù):

boost::shared_ptr< Layer<Dtype> > xxx(const LayerParameter& x); NEW_FUNC yyy=boost::shared_ptr< Layer<Dtype> > xxx(const LayerParameter& x);yyy(); //相當(dāng)于xxx() xxx();

需要訪問工廠時(shí),我們只需要訪問這個(gè)代替工廠管理數(shù)據(jù)庫的容器,而不是幼稚地使用if(.....)

序列化與反序列化

如果Caffe不使用Protocol Buffer,那么代碼量將擴(kuò)大一倍。

這不是危言聳聽,在傳統(tǒng)系統(tǒng)級程序設(shè)計(jì)中,序列化與反序列化一直是一個(gè)碼農(nóng)問題。

尤其是在機(jī)器學(xué)習(xí)系統(tǒng)中,復(fù)雜多變的數(shù)據(jù)結(jié)構(gòu),給序列化和反序列化帶來巨大麻煩。

Protocol Buffer在序列化階段,是一個(gè)高效的編碼器,能將數(shù)據(jù)最小體積序列化。

而在反序列化階段,它是一個(gè)強(qiáng)大的解碼器,支持二進(jìn)制/文本兩類數(shù)據(jù)的解析與結(jié)構(gòu)反序列化。

其中,從文本反序列化意義頗大,這就形成了Caffe著名的文本配置文件prototxt,用于net和solver。

相對靈活的配置方式,尤其適合超大規(guī)模神經(jīng)網(wǎng)絡(luò),這點(diǎn)在早期機(jī)器學(xué)習(xí)系統(tǒng)中獨(dú)領(lǐng)風(fēng)騷(很多人認(rèn)為這比圖形界面還要方便)。

據(jù)說寫庫狂人都是用宏狂人。

C/C++提供了強(qiáng)大了自定義宏函數(shù)(#define),Caffe通過宏,大概減少了1000~2000行代碼。

宏函數(shù)大致有如下幾種:

?

① #define DISABLE_COPY_AND_ASSIGN(classname)

俗稱禁止拷貝和賦值宏,如果你熟悉Qt,就會(huì)發(fā)現(xiàn),Qt中大部分?jǐn)?shù)據(jù)結(jié)構(gòu)都用了這個(gè)宏來保護(hù)。

這個(gè)宏算是最沒用的宏,用在了所有Caffe大型數(shù)據(jù)結(jié)構(gòu)上(Blob、Layer、Net、Solver)

目的是禁止兩個(gè)大型數(shù)據(jù)結(jié)構(gòu)直接復(fù)制、構(gòu)造、然后賦值。

實(shí)際上,Caffe也沒有去編寫復(fù)制構(gòu)造函數(shù)代碼,所以最終還是會(huì)被編譯器攔下。

前面以及說過了,兩個(gè)大型數(shù)據(jù)結(jié)構(gòu)之間的復(fù)制會(huì)是什么樣的下場,這是絕對應(yīng)該被禁止的。

如果你要使用一個(gè)數(shù)據(jù)結(jié)構(gòu),請用指針或是引用指向它。

如果你有亂賦值的編程陋習(xí),請及時(shí)打上這個(gè)宏,避免自己手賤。反之,可以暫時(shí)無視它。

當(dāng)然,從庫的完整性角度,這個(gè)宏是明智的。

Java/Python不需要這個(gè)宏,因?yàn)镴ava對大型數(shù)據(jù)結(jié)構(gòu),默認(rèn)是淺拷貝,也就是直接引用。

而Python,這個(gè)沒有數(shù)據(jù)類型的奇怪語言,則默認(rèn)全部是淺拷貝。

?

②#define INSTANTIATE_CLASS(classname)

非常非常非常重要的宏,重要的事說三遍。

由于Caffe采用分離式模板編程方法(據(jù)說也是Google倡導(dǎo)的)

模板未類型實(shí)例化的定義空間和實(shí)例化的定義空間是不同的。

實(shí)際上,編譯器并不會(huì)理睬分離在cpp里的未實(shí)例化的定義代碼,而是將它放置在一個(gè)虛擬的空間。

一旦一段明確類型的代碼,訪問這段虛擬代碼空間,就會(huì)被編譯器攔截。

如果你想要讓模板的聲明和定義分離編寫,就需要在cpp定義文件里,將定義指定明確的類型,實(shí)例化。

這個(gè)宏的作用正是如此。(Google編程習(xí)慣的宏吧)。

更詳細(xì)的用法,將在后續(xù)文章中詳細(xì)介紹。

?

③#define INSTANTIATE_LAYER_GPU_FUNCS(classname)

通樣是實(shí)例化宏,專門寫這個(gè)宏的原因,是因?yàn)镹VCC編譯器相當(dāng)傲嬌。

打在cpp文件里的INSTANTIATE_CLASS宏,NVCC在編譯cu文件時(shí),可不會(huì)知道。

所以,你需要在cu文件里,為這些函數(shù)再次實(shí)例化。

其實(shí)也沒幾個(gè)函數(shù),也就是forward_gpu和backward_gpu

?

④#define NOT_IMPLEMENTED

俗稱偷懶宏,你要是這段代碼不想寫了,打個(gè)NOT_IMPLEMENTED就行了。

就是宣告:“老子就是不想寫這段代碼,留空,留空!”

但是注意,宏封裝了LOG(FATAL),這是個(gè)Assert(斷言),會(huì)引起CPU硬件中斷。

一旦代碼空間轉(zhuǎn)到你沒寫的這段,整個(gè)程序就會(huì)被終止。

所以,偷懶有度,還是認(rèn)真寫代碼吧。

?

⑤#define REGISTER_LAYER_CLASS(type)?

Layer工廠模式用的宏,也就是將這個(gè)Layer的信息寫到工廠的管理數(shù)據(jù)庫里。

此宏省了不少代碼,在使用工廠之前,記得要為每個(gè)成品(Layer)打上這個(gè)宏。

命名空間

Caffe為了與Boost等庫接軌,幾乎為所有結(jié)構(gòu)提供了以caffe為關(guān)鍵字的命名空間。

設(shè)置命名空間的主要目的是防止Caffe的函數(shù)、變量與其他庫產(chǎn)生沖突。

在我們的山寨過程中,為了代碼的簡潔,將忽略全部的命名空間。

命名法

Caffe中普遍采用下劃線命名法。

我們對其作出了部分修改,整體采用兩種命名法:

①針對變量而言: 采用下劃線命名法

②針對函數(shù)而言:采用駝峰命名法

腳踏實(shí)地

編程手冊

Caffe幾乎是C++ Primer 第五版的鮮活例子,如果你需要讀懂它,經(jīng)常翻一翻C++ Primer是一個(gè)不錯(cuò)的主意。

(另:不要閱讀C++ Primer Plus,它的作者僅僅是一個(gè)普通教師,

而C++ Primer作者則包含C++協(xié)發(fā)明者、ISO C++委員會(huì)的人,是權(quán)威圣經(jīng))

耐心閱讀和模仿代碼

注意你接觸的是一個(gè)系統(tǒng)級程序,Windows還是全球5000位微軟工程師開發(fā)的。

系統(tǒng)級程序相當(dāng)龐大和復(fù)雜,切記不要心浮氣躁,不要以套庫的心理去學(xué)習(xí)。

更不要認(rèn)為,看看高層代碼就可以了,這簡直是噩夢,最后你會(huì)發(fā)現(xiàn)你根本讀不懂。

來一個(gè)響亮的名字

為自己的工程取個(gè)名字是一件有趣的事,本項(xiàng)目默認(rèn)名為:Dragon。

因?yàn)樯疃壬窠?jīng)網(wǎng)絡(luò)活像一頭蠢龍。

總結(jié)

以上是生活随笔為你收集整理的从零开始山寨Caffe·壹:仰望星空与脚踏实地的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。