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

如何用C++實現自己的Tensorflow

2018-07-20    來源:編程學習網

容器云強勢上線!快速搭建集群,上萬Linux鏡像隨意使用

TensorFlow是由谷歌基于DistBelief進行研發(fā)的第二代人工智能學習系統(tǒng),其命名來源于本身的運行原理,它完全開源,作者通過自己的一個小項目,闡述了如何用C++實現自己的TensorFlow,這篇文章看起來可能會有點晦澀,你需要對相關知識有所了解。以下是譯文。

在我們開始之前,以下是代碼:

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

我和 Minh Le 一起做了這個項目。

為什么?

如果你是CS專業(yè)的人員,可能聽過這句“不要使自己陷入_”的話無數次。CS有加密、標準庫、解析器等等。我覺得現在還應該包含ML庫。

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

Tensorflow

TensorFlow是由Google開源的一個深度學習庫。在TensorFlow的內核,有一個大的組件,將操作串在一起,行成一個叫做 運算符圖 的東西。這個運算符圖是一個有向圖 G = ( , )

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

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

。

這對于評估算術表達式非常有用。我們可以通過尋找運算符圖中的 sinks 來得到結果。 Sinks 是諸如 v ∈ , ?? e = ( v , u )

這樣的頂點。換句話說,這些頂點沒有到其它頂點的有向邊。同樣的, sources 是 v ∈ , ?? e = ( u , v ) 。

對我們來說, 總是 把值放在sources,值會傳播到Sinks。

 

反向模式求導

 

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

 

求導是TensorFlow所需的許多模型的核心要求,因為需要它來運行 梯度下降算法 。每個高中畢業(yè)的人都知道什么是求導; 它只是獲取函數的導數,如果函數是由基本函數組成的復雜組合,那么就做 鏈式法則 。

 

超級簡單的概述

 

如果有一個這樣的函數:

 

f(x,y) = x * y

 

那么關于X的求導將產生:

 

( x , ) x =

 

關于Y的求導將產生:

 

( x , ) = x

 

另外一個例子:

 

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

 

這個導數是:

 

( x ) x i = 2 x i

 

所以梯度就是:

 

? x ( x ) = 2 x

 

鏈式法則,譬如應用于復雜的函數 ( ( h ( x ) ) )

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

 

5分鐘內反向模式

 

現在記住運算符圖的DAG結構,以及上一個例子中的鏈式法則。如果要評估,我們可以看到:

 

x -> h -> g -> f

 

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

 

dx <- dh <- dg <- df

 

這看起來像鏈式法則!需要將導數相乘在一起,以獲得最終結果。

 

下圖是一個運算符圖的例子:

 

 

所以這基本上退化成圖遍歷問題。 有誰發(fā)覺拓撲排序和DFS / BFS嗎?

 

所以要支持雙向拓撲排序的話,需要包含一組父節(jié)點和一組子節(jié)點,Sinks是另一個方向的Sources, 反之亦然 。

 

實施

 

在開學之前,Minh Le和我開始設計這個項目。我們決定使用Eigen 庫后臺進行線性代數運算。它們有一個稱為MatrixXd的矩陣類。我們在這里使用它。

 

每個變量節(jié)點由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 慣用法,這意味著“通過指針來實現”。這在許多方面是非常好的,例如接口解耦實現, 當在堆棧上有一個本地shell接口時,允許在堆棧上實例化 。pImpl的副作用是運行時間稍慢,但是編譯時間縮短了很多。這讓我們通過多個函數調用/返回來保持數據結構的持久性。像這樣的樹狀數據結構應該是持久的。

 

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

 

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

 

執(zhí)行該樹評價的實際類稱為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;
};

 

反向傳播 的內部,有一些類似于此的代碼:

 

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

 

這相當于做一個DFS; 你看到了嗎?

 

為什么選擇C ++?

 

事實上,C ++語言用于此不是特別合適。我們可以花 更少的時間 用OCaml等功能性語言來開發(fā),F在我明白了為什么Scala被用于機器學習,主要看你喜歡;)。

 

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

 

Eigen

 

例如,可以直接使用tensorflow的線性代數庫,稱之為Eigen。這是一個多模板惰性計算的線性代數庫。類似于表達式樹的樣子,構建表達式,只有在需要時才會對表達式進行評估。然而,對于Eigen來說, 在編譯的時候就確定何時使用模板,這意味著運行時間的減少 。我特別贊賞寫Eigen的人,因為審視模板的錯誤,讓我的眼睛充血。

 

Eigen的代碼看起來像:

 

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庫是非常強大的,這就是為什么它是tensorflow自我使用的主要后臺。這意味著除了這種惰性計算技術之外,還有其他方面的優(yōu)化。

 

運算符重載

 

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

 

// 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)));

 

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

 

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

 

性能,而不是Bug

 

有一些東西,你可以在這個庫中實際指定,TensorFlow沒有明確的API,或者我不知道。比如,如果想訓練某個特定子集的權重,可以只反向傳播到感興趣的具體來源。這對于卷積神經網絡的 轉移學習非常有用 ,一些大的網絡,如VGG19網絡,很容易用TensorFlow實現,其附加的幾個額外的層的權重是根據新的域樣本進行訓練的。

 

基準

 

用Python的Tensorflow庫,在Iris數據集上對10000個歷史紀元進行分類訓練,這些歷史紀元具有相同的超參數,結果是:

 

  1. Tensorflow的神經網絡 23812.5 ms
  2. Scikit的神經網絡庫: 22412.2 ms
  3. Autodiff的神經網絡,迭代,優(yōu)化: 25397.2 ms
  4. Autodiff的神經網絡,具有迭代,無優(yōu)化: 29052.4 ms
  5. Autodiff的神經網絡,具有遞歸,無優(yōu)化: 28121.5 ms

 

如此看來,令人驚訝的是,Scikit在所有這些中運行最快。這可能是因為我們沒有做大量的矩陣乘法運算。也可能是因為tensorflown不得不通過變量初始化采用額外的編譯步驟。或者,也許可能不得不在python中運行循環(huán),而不是在C語言中(python循環(huán) 真的很糟糕 。。我自己也不確定這到底是因為什么。

 

我完全意識到這絕對不是一個全面的基準測試,因為它只適用于在特定情況下的單個數據點。不過,這個庫的性能并不是最先進的技術,因為我們不希望把自己卷進TensorFlow。

 

 

來自:http://blog.csdn.net/dev_csdn/article/details/78500708

 

標簽: Google 代碼 谷歌 網絡

版權申明:本站文章部分自網絡,如有侵權,請聯系:west999com@outlook.com
特別注意:本站所有轉載文章言論不代表本站觀點!
本站所提供的圖片等素材,版權歸原作者所有,如需使用,請與原作者聯系。

上一篇:為什么說SQL正在擊敗NoSQL,這對數據的未來意味著什么?

下一篇:App開發(fā)團隊必須知道的 iOS 11 更新點