中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

如何用C++實(shí)現(xiàn)自己的Tensorflow

2018-07-20    來(lái)源:編程學(xué)習(xí)網(wǎng)

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬(wàn)Linux鏡像隨意使用

TensorFlow是由谷歌基于DistBelief進(jìn)行研發(fā)的第二代人工智能學(xué)習(xí)系統(tǒng),其命名來(lái)源于本身的運(yùn)行原理,它完全開(kāi)源,作者通過(guò)自己的一個(gè)小項(xiàng)目,闡述了如何用C++實(shí)現(xiàn)自己的TensorFlow,這篇文章看起來(lái)可能會(huì)有點(diǎn)晦澀,你需要對(duì)相關(guān)知識(shí)有所了解。以下是譯文。

在我們開(kāi)始之前,以下是代碼:

  1. Branch with Eigen backend
  2. Branch that only supports scalars

我和 Minh Le 一起做了這個(gè)項(xiàng)目。

為什么?

如果你是CS專業(yè)的人員,可能聽(tīng)過(guò)這句“不要使自己陷入_”的話無(wú)數(shù)次。CS有加密、標(biāo)準(zhǔn)庫(kù)、解析器等等。我覺(jué)得現(xiàn)在還應(yīng)該包含ML庫(kù)。

不管事實(shí)如何,它仍然是一個(gè)值得學(xué)習(xí)的驚人的教訓(xùn)。人們現(xiàn)在認(rèn)為TensorFlow和類似的庫(kù)是理所當(dāng)然的;把它們當(dāng)成是一個(gè)黑盒子,讓其運(yùn)行。沒(méi)有多少人知道后臺(tái)發(fā)生了什么。這真是一個(gè)非凸的優(yōu)化問(wèn)題!不要停止攪拌那堆東西,直到它看起來(lái)合適為止(結(jié)合下圖及機(jī)器學(xué)習(xí)系統(tǒng)知識(shí)去理解這句話)。

Tensorflow

TensorFlow是由Google開(kāi)源的一個(gè)深度學(xué)習(xí)庫(kù)。在TensorFlow的內(nèi)核,有一個(gè)大的組件,將操作串在一起,行成一個(gè)叫做 運(yùn)算符圖 的東西。這個(gè)運(yùn)算符圖是一個(gè)有向圖 G = ( , )

,在某些節(jié)點(diǎn) u 1 , u 2 , … , u n , v ∈ 和 e 1 , e 2 , … , e n ∈ , e i = ( u i , v ) 存在某些運(yùn)算符將 u 1 , … , u n 映射到 v 。

例如,如果我們有x + y = z,那么 ( x , ) , ( , ) ∈

。

這對(duì)于評(píng)估算術(shù)表達(dá)式非常有用。我們可以通過(guò)尋找運(yùn)算符圖中的 sinks 來(lái)得到結(jié)果。 Sinks 是諸如 v ∈ , ?? e = ( v , u )

這樣的頂點(diǎn)。換句話說(shuō),這些頂點(diǎn)沒(méi)有到其它頂點(diǎn)的有向邊。同樣的, sources 是 v ∈ , ?? e = ( u , v ) 。

對(duì)我們來(lái)說(shuō), 總是 把值放在sources,值會(huì)傳播到Sinks。

 

反向模式求導(dǎo)

 

如果認(rèn)為我的解釋不夠好,這里有一些 幻燈片 。

 

求導(dǎo)是TensorFlow所需的許多模型的核心要求,因?yàn)樾枰鼇?lái)運(yùn)行 梯度下降算法 。每個(gè)高中畢業(yè)的人都知道什么是求導(dǎo); 它只是獲取函數(shù)的導(dǎo)數(shù),如果函數(shù)是由基本函數(shù)組成的復(fù)雜組合,那么就做 鏈?zhǔn)椒▌t

 

超級(jí)簡(jiǎn)單的概述

 

如果有一個(gè)這樣的函數(shù):

 

f(x,y) = x * y

 

那么關(guān)于X的求導(dǎo)將產(chǎn)生:

 

( x , ) x =

 

關(guān)于Y的求導(dǎo)將產(chǎn)生:

 

( x , ) = x

 

另外一個(gè)例子:

 

( x 1 , x 2 , . . . , x n ) = ( x ) = x x

 

這個(gè)導(dǎo)數(shù)是:

 

( x ) x i = 2 x i

 

所以梯度就是:

 

? x ( x ) = 2 x

 

鏈?zhǔn)椒▌t,譬如應(yīng)用于復(fù)雜的函數(shù) ( ( h ( x ) ) )

( ( h ( x ) ) ) x = ( ( h ( x ) ) ) ( h ( x ) ) ( h ( x ) ) h ( x ) h ( x ) x

 

5分鐘內(nèi)反向模式

 

現(xiàn)在記住運(yùn)算符圖的DAG結(jié)構(gòu),以及上一個(gè)例子中的鏈?zhǔn)椒▌t。如果要評(píng)估,我們可以看到:

 

x -> h -> g -> f

 

作為圖表。會(huì)給出答案f。但是,我們也可以采取反向求解:

 

dx <- dh <- dg <- df

 

這看起來(lái)像鏈?zhǔn)椒▌t!需要將導(dǎo)數(shù)相乘在一起,以獲得最終結(jié)果。

 

下圖是一個(gè)運(yùn)算符圖的例子:

 

 

所以這基本上退化成圖遍歷問(wèn)題。 有誰(shuí)發(fā)覺(jué)拓?fù)渑判蚝虳FS / BFS嗎?

 

所以要支持雙向拓?fù)渑判虻脑,需要包含一組父節(jié)點(diǎn)和一組子節(jié)點(diǎn),Sinks是另一個(gè)方向的Sources, 反之亦然 。

 

實(shí)施

 

在開(kāi)學(xué)之前,Minh Le和我開(kāi)始設(shè)計(jì)這個(gè)項(xiàng)目。我們決定使用Eigen 庫(kù)后臺(tái)進(jìn)行線性代數(shù)運(yùn)算。它們有一個(gè)稱為MatrixXd的矩陣類。我們?cè)谶@里使用它。

 

每個(gè)變量節(jié)點(diǎn)由var類表示:

 

class var {
// Forward declaration
struct impl;

public:
// For initialization of new vars by ptr
var(std::shared_ptr<impl>);

var(double);
var(const MatrixXd&);
var(op_type, const std::vector<var>&);    
...

// Access/Modify the current node value
MatrixXd getValue() const;
void setValue(const MatrixXd&);
op_type getOp() const;
void setOp(op_type);

// Access internals (no modify)
std::vector<var>& getChildren() const;
std::vector<var> getParents() const;
...
private: 
// PImpl idiom requires forward declaration of the     class:
std::shared_ptr<impl> pimpl;
};

struct var::impl{
public:
impl(const MatrixXd&);
impl(op_type, const std::vector<var>&);
MatrixXd val;
op_type op; 
std::vector<var> children;
std::vector<std::weak_ptr<impl>> parents;
};

 

在這里,我們采用 pImpl 慣用法,這意味著“通過(guò)指針來(lái)實(shí)現(xiàn)”。這在許多方面是非常好的,例如接口解耦實(shí)現(xiàn), 當(dāng)在堆棧上有一個(gè)本地shell接口時(shí),允許在堆棧上實(shí)例化 。pImpl的副作用是運(yùn)行時(shí)間稍慢,但是編譯時(shí)間縮短了很多。這讓我們通過(guò)多個(gè)函數(shù)調(diào)用/返回來(lái)保持?jǐn)?shù)據(jù)結(jié)構(gòu)的持久性。像這樣的樹狀數(shù)據(jù)結(jié)構(gòu)應(yīng)該是持久的。

 

有幾個(gè) 枚舉 ,告訴我們目前正在執(zhí)行哪些操作:

 

enum class op_type {
plus,
minus,
multiply,
divide,
exponent,
log,
polynomial,
dot,
...
none // no operators. leaf.
};

 

執(zhí)行該樹評(píng)價(jià)的實(shí)際類稱為expression:

 

class expression {
public:
expression(var);
...
// Recursively evaluates the tree.
double propagate();
...
// Computes the derivative for the entire graph.
// Performs a top-down evaluation of the tree.
void backpropagate(std::unordered_map<var, double>& leaves);
...    
private:
var root;
};

 

反向傳播 的內(nèi)部,有一些類似于此的代碼:

 

backpropagate(node, dprev):
derivative = differentiate(node)*dprev
for child in node.children:
    backpropagate(child, derivative)

 

這相當(dāng)于做一個(gè)DFS; 你看到了嗎?

 

為什么選擇C ++?

 

事實(shí)上,C ++語(yǔ)言用于此不是特別合適。我們可以花 更少的時(shí)間 用OCaml等功能性語(yǔ)言來(lái)開(kāi)發(fā)。現(xiàn)在我明白了為什么Scala被用于機(jī)器學(xué)習(xí),主要看你喜歡;)。

 

然而,C ++有明顯的好處:

 

Eigen

 

例如,可以直接使用tensorflow的線性代數(shù)庫(kù),稱之為Eigen。這是一個(gè)多模板惰性計(jì)算的線性代數(shù)庫(kù)。類似于表達(dá)式樹的樣子,構(gòu)建表達(dá)式,只有在需要時(shí)才會(huì)對(duì)表達(dá)式進(jìn)行評(píng)估。然而,對(duì)于Eigen來(lái)說(shuō), 在編譯的時(shí)候就確定何時(shí)使用模板,這意味著運(yùn)行時(shí)間的減少 。我特別贊賞寫Eigen的人,因?yàn)閷徱暷0宓腻e(cuò)誤,讓我的眼睛充血。

 

Eigen的代碼看起來(lái)像:

 

Matrix A(...), B(...);
auto lazy_multiply = A.dot(B);
typeid(lazy_multiply).name(); // the class name is something like Dot_Matrix_Matrix.
Matrix(lazy_multiply); // functional-style casting forces evaluation of this matrix.

 

Eigen庫(kù)是非常強(qiáng)大的,這就是為什么它是tensorflow自我使用的主要后臺(tái)。這意味著除了這種惰性計(jì)算技術(shù)之外,還有其他方面的優(yōu)化。

 

運(yùn)算符重載

 

用Java開(kāi)發(fā)這些庫(kù)會(huì)非常好—沒(méi)有 shared_ptrs, unique_ptrs, weak_ptrs 代碼;我們可以采取 實(shí)際的,能勝任的,GC算法 。使用Java開(kāi)發(fā)可以節(jié)省許多開(kāi)發(fā)時(shí)間,更不用說(shuō)執(zhí)行速度也會(huì)變得更快。可是,Java不允許運(yùn)算符重載,因而它們就不能這樣:

 

// These 3 lines code up an entire neural network!
var sigm1 = 1 / (1 + exp(-1 * dot(X, w1)));
var sigm2 = 1 / (1 + exp(-1 * dot(sigm1, w2)));
var loss = sum(-1 * (y * log(sigm2) + (1-y) * log(1-sigm2)));

 

順便說(shuō)一下,上面的是實(shí)際代碼。這不是很漂亮嗎?我認(rèn)為 這比用于TensorFlow的python包裝更漂亮 。只想讓你知道,這些也都是矩陣。

 

在Java語(yǔ)言中,這將是極其丑陋的,有著一堆 add(), divide() …等等代碼。更為重要的是, 用戶將被隱式強(qiáng)制使用PEMDAS(括號(hào) ,指數(shù)、乘、除、加、減),這一點(diǎn)上,C++的運(yùn)算符表現(xiàn)的很好。

 

性能,而不是Bug

 

有一些東西,你可以在這個(gè)庫(kù)中實(shí)際指定,TensorFlow沒(méi)有明確的API,或者我不知道。比如,如果想訓(xùn)練某個(gè)特定子集的權(quán)重,可以只反向傳播到感興趣的具體來(lái)源。這對(duì)于卷積神經(jīng)網(wǎng)絡(luò)的 轉(zhuǎn)移學(xué)習(xí)非常有用 ,一些大的網(wǎng)絡(luò),如VGG19網(wǎng)絡(luò),很容易用TensorFlow實(shí)現(xiàn),其附加的幾個(gè)額外的層的權(quán)重是根據(jù)新的域樣本進(jìn)行訓(xùn)練的。

 

基準(zhǔn)

 

用Python的Tensorflow庫(kù),在Iris數(shù)據(jù)集上對(duì)10000個(gè)歷史紀(jì)元進(jìn)行分類訓(xùn)練,這些歷史紀(jì)元具有相同的超參數(shù),結(jié)果是:

 

  1. Tensorflow的神經(jīng)網(wǎng)絡(luò) 23812.5 ms
  2. Scikit的神經(jīng)網(wǎng)絡(luò)庫(kù): 22412.2 ms
  3. Autodiff的神經(jīng)網(wǎng)絡(luò),迭代,優(yōu)化: 25397.2 ms
  4. Autodiff的神經(jīng)網(wǎng)絡(luò),具有迭代,無(wú)優(yōu)化: 29052.4 ms
  5. Autodiff的神經(jīng)網(wǎng)絡(luò),具有遞歸,無(wú)優(yōu)化: 28121.5 ms

 

如此看來(lái),令人驚訝的是,Scikit在所有這些中運(yùn)行最快。這可能是因?yàn)槲覀儧](méi)有做大量的矩陣乘法運(yùn)算。也可能是因?yàn)閠ensorflown不得不通過(guò)變量初始化采用額外的編譯步驟;蛘,也許可能不得不在python中運(yùn)行循環(huán),而不是在C語(yǔ)言中(python循環(huán) 真的很糟糕 !)。我自己也不確定這到底是因?yàn)槭裁础?/p>

 

我完全意識(shí)到這絕對(duì)不是一個(gè)全面的基準(zhǔn)測(cè)試,因?yàn)樗贿m用于在特定情況下的單個(gè)數(shù)據(jù)點(diǎn)。不過(guò),這個(gè)庫(kù)的性能并不是最先進(jìn)的技術(shù),因?yàn)槲覀儾幌M炎约壕磉M(jìn)TensorFlow。

 

 

來(lái)自:http://blog.csdn.net/dev_csdn/article/details/78500708

 

標(biāo)簽: Google 代碼 谷歌 網(wǎng)絡(luò)

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:為什么說(shuō)SQL正在擊敗NoSQL,這對(duì)數(shù)據(jù)的未來(lái)意味著什么?

下一篇:App開(kāi)發(fā)團(tuán)隊(duì)必須知道的 iOS 11 更新點(diǎn)