自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何利用C++搭建個(gè)人專屬的TensorFlow

大數(shù)據(jù) 后端
在實(shí)際過(guò)程中,C++可能并不適合做這類事情。我們可以在像「Oaml」這樣的函數(shù)式語(yǔ)言中花費(fèi)更少的時(shí)間開(kāi)發(fā)。現(xiàn)在我明白為什么「Scala」被用于機(jī)器學(xué)習(xí)中,主要就是因?yàn)椤窼park」。然而,使用 C++有很多好處。

[[208061]]

在開(kāi)始之前,首先看一下最終成型的代碼:

1. 分支與特征后端(https://github.com/OneRaynyDay/autodiff/tree/eigen)

2. 僅支持標(biāo)量的分支(https://github.com/OneRaynyDay/autodiff/tree/master)

這個(gè)項(xiàng)目是我與 Minh Le 一起完成的。

為什么?

如果你修習(xí)的是計(jì)算機(jī)科學(xué)(CS)的人的話,你可能聽(tīng)說(shuō)過(guò)這個(gè)短語(yǔ)「不要自己動(dòng)手____」幾千次了。它包含了加密、標(biāo)準(zhǔn)庫(kù)、解析器等等。我想到現(xiàn)在為止,它也應(yīng)該包含了機(jī)器學(xué)習(xí)庫(kù)(ML library)。

不管現(xiàn)實(shí)是怎么樣的,這個(gè)震撼的課程都值得我們?nèi)W(xué)習(xí)。人們現(xiàn)在把 TensorFlow 和類似的庫(kù)當(dāng)作理所當(dāng)然了。他們把它看作黑盒子并讓它運(yùn)行起來(lái),但是并沒(méi)有多少人知道在這背后的運(yùn)行原理。這只是一個(gè)非凸(Non-convex)的優(yōu)化問(wèn)題!請(qǐng)停止對(duì)代碼無(wú)意義的胡搞——僅僅只是為了讓代碼看上去像是正確的。

 

TensorFlow

在 TensorFlow 的代碼里,有一個(gè)重要的組件,允許你將計(jì)算串在一起,形成一個(gè)稱為「計(jì)算圖」的東西。這個(gè)計(jì)算圖是一個(gè)有向圖 G=(V,E),其中在某些節(jié)點(diǎn)處 u1,u2,…,un,v∈V,和 e1,e2,…,en∈E,ei=(ui,v)。我們知道,存在某種計(jì)算圖將 u1,…,un 映射到 vv。

舉個(gè)例子,如果我們有 x + y = z,那么 (x,z),(y,z)∈E。

這對(duì)于評(píng)估算術(shù)表達(dá)式非常有用,我們能夠在計(jì)算圖的匯點(diǎn)下找到結(jié)果。匯點(diǎn)是類似 v∈V,∄e=(v,u) 這樣的頂點(diǎn)。從另一方面來(lái)說(shuō),這些頂點(diǎn)從自身到其他頂點(diǎn)并沒(méi)有定向邊界。同樣的,輸入源是 v∈V,∄e=(u,v)。

對(duì)于我們來(lái)說(shuō),我們總是把值放在輸入源上,而值也將傳播到匯點(diǎn)上。

反向模式求微分

如果你覺(jué)得我的解釋不正確,可以參考下這些幻燈片的說(shuō)明。

微分是 Tensorflow 中許多模型的核心需求,因?yàn)槲覀冃枰鼇?lái)運(yùn)行梯度下降。每一個(gè)從高中畢業(yè)的人都應(yīng)該知道微分的意思。如果是基于基礎(chǔ)函數(shù)組成的復(fù)雜函數(shù),則只需要求出函數(shù)的導(dǎo)數(shù),然后應(yīng)用鏈?zhǔn)椒▌t。

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

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

 

對(duì) x 求導(dǎo):

 

對(duì) y 求導(dǎo):

 

其它的例子: 

 

其導(dǎo)數(shù)是:

 

所以其梯度是:

 

鏈?zhǔn)椒▌t,例如應(yīng)用于 f(g(h(x))):

 

在 5 分鐘內(nèi)倒轉(zhuǎn)模式

所以現(xiàn)在請(qǐng)記住我們運(yùn)行計(jì)算圖時(shí)用的是有向無(wú)環(huán)結(jié)構(gòu)(DAG/Directed Acyclic Graph),還有上一個(gè)例子用到的鏈?zhǔn)椒▌t。正如下方所示的形式:

  1. x -> h -> g -> f 

作為一個(gè)圖,我們能夠在 f 獲得答案,然而,也可以反過(guò)來(lái):

  1. dx <- dh <- dg <- df 

這樣它看起來(lái)就像鏈?zhǔn)椒▌t了!我們需要沿著路徑把導(dǎo)數(shù)相乘以得到最終的結(jié)果。這是一個(gè)計(jì)算圖的例子:

 

這就將其簡(jiǎn)化為一個(gè)圖的遍歷問(wèn)題。有誰(shuí)察覺(jué)到了這就是拓?fù)渑判蚝蜕疃葍?yōu)先搜索/寬度優(yōu)先搜索?

沒(méi)錯(cuò),為了在兩種路徑都支持拓?fù)渑判?,我們需要包含一套父組一套子組,而匯點(diǎn)是另一個(gè)方向的來(lái)源。反之亦然。

執(zhí)行

在開(kāi)學(xué)前,Minh Le 和我開(kāi)始設(shè)計(jì)這個(gè)項(xiàng)目。我們決定使用特征庫(kù)后端(Eigen library backend)進(jìn)行線性代數(shù)運(yùn)算,這個(gè)庫(kù)有一個(gè)叫做 MatrixXd 的矩陣類,用在我們的項(xiàng)目中:

  1. class var {// Forward declarationstruct impl;public
  2.     // For initialization of new vars by ptr    var(std::shared_ptr<impl>); 
  3.  
  4.     var(double); 
  5.     var(const MatrixXd&); 
  6.     var(op_type, const std::vector<var>&);     
  7.     ... 
  8.      
  9.     // Access/Modify the current node value    MatrixXd getValue() const; 
  10.     void setValue(const MatrixXd&); 
  11.     op_type getOp() const; 
  12.     void setOp(op_type); 
  13.      
  14.     // Access internals (no modify)    std::vector<var>& getChildren() const; 
  15.     std::vector<var> getParents() const; 
  16.     ...private:  
  17.     // PImpl idiom requires forward declaration of the class:    std::shared_ptr<impl> pimpl;};struct var::impl{public
  18.     impl(const MatrixXd&); 
  19.     impl(op_type, const std::vector<var>&); 
  20.     MatrixXd val; 
  21.     op_type op;  
  22.     std::vector<var> children; 
  23.     std::vector<std::weak_ptr<impl>> parents;};  

在這里,我們使用了一個(gè)叫「pImpl」的語(yǔ)法,意思是「執(zhí)行的指針」。它有很多用途,比如接口的解耦實(shí)現(xiàn),以及當(dāng)在堆棧上有一個(gè)本地接口時(shí)實(shí)例化內(nèi)存堆上的東西?!竝Impl」的一些副作用是微弱的減慢運(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)該是持久的。

我們有一些枚舉來(lái)告訴我們目前正在進(jìn)行哪些操作:

  1. enum class op_type { 
  2.     plus, 
  3.     minus, 
  4.     multiply, 
  5.     divide, 
  6.     exponent, 
  7.     log, 
  8.     polynomial, 
  9.     dot, 
  10.     ... 
  11.     none // no operators. leaf.};  

執(zhí)行此樹的評(píng)估的實(shí)際類稱為 expression:

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

在反向傳播里,我們的代碼能做類似以下所示的事情:

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

這幾乎就是在做一個(gè)深度優(yōu)先搜索(DFS),你發(fā)現(xiàn)了嗎?

為什么是 C++?

在實(shí)際過(guò)程中,C++可能并不適合做這類事情。我們可以在像「Oaml」這樣的函數(shù)式語(yǔ)言中花費(fèi)更少的時(shí)間開(kāi)發(fā)。現(xiàn)在我明白為什么「Scala」被用于機(jī)器學(xué)習(xí)中,主要就是因?yàn)椤窼park」。然而,使用 C++有很多好處。

Eigen(庫(kù)名)

舉例來(lái)說(shuō),我們可以直接使用一個(gè)叫「Eigen」的 TensorFlow 的線性代數(shù)庫(kù)。這是一個(gè)不假思索就被人用爛了的線性代數(shù)庫(kù)。有一種類似于我們的表達(dá)式樹的味道,我們構(gòu)建表達(dá)式,它只會(huì)在我們真正需要的時(shí)候進(jìn)行評(píng)估。然而,使用「Eigen」在編譯的時(shí)間內(nèi)就能決定什么時(shí)候使用模版,這意味著運(yùn)行的時(shí)間減少了。我對(duì)寫出「Eigen」的人抱有很大的敬意,因?yàn)椴榭茨0娴腻e(cuò)誤幾乎讓我眼瞎!

他們的代碼看起來(lái)類似這樣的:

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

這個(gè)特征庫(kù)非常的強(qiáng)大,這就是它作為 TensortFlow 主要后端之一的原因,即除了這個(gè)慵懶的評(píng)估技術(shù)之外還有其它的優(yōu)化。

運(yùn)算符重載

在 Java 中開(kāi)發(fā)這個(gè)庫(kù)很不錯(cuò)——因?yàn)闆](méi)有 shared_ptrs、unique_ptrs、weak_ptrs;我們得到了一個(gè)真實(shí)的,有用的圖形計(jì)算器(GC=Graphing Calculator)。這大大節(jié)省了開(kāi)發(fā)時(shí)間,更不必說(shuō)更快的執(zhí)行速度。然而,Java 不允許操作符重載,因此它們不能這樣:

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

順便說(shuō)一下,上面是實(shí)際使用的代碼。是不是非常的漂亮?我想說(shuō)的是這甚至比 TensorFlow 里的 Python 封裝還更優(yōu)美!我只是想表明,它們也是矩陣。

在 Java 中,有一連串的 add(), divide() 等等是非常難看的。更重要的是,這將讓用戶更多的關(guān)注在「PEMDAS」上,而 C++的操作符則有非常好的表現(xiàn)。

特征,而不是一連串的故障

在這個(gè)庫(kù)中,可以確定的是,TensorFlow 沒(méi)有定義清晰的 API,或者有但我不知道。例如,如果我們只想訓(xùn)練一個(gè)特定子集的權(quán)重,我們可以只對(duì)我們感興趣的特定來(lái)源做反向傳播。這對(duì)于卷積神經(jīng)網(wǎng)絡(luò)的遷移學(xué)習(xí)非常有用,因?yàn)楹芏鄷r(shí)候,像 VGG19 這樣的大型網(wǎng)絡(luò)可以被截?cái)?,然后附加一些額外的層,這些層的權(quán)重使用新領(lǐng)域的樣本來(lái)訓(xùn)練。

基準(zhǔn)

在 Python 的 TensorFlow 庫(kù)中,對(duì)虹膜數(shù)據(jù)集進(jìn)行 10000 個(gè)「Epochs」的訓(xùn)練以進(jìn)行分類,并使用相同的超參數(shù),我們有:

  1. TensorFlow 的神經(jīng)網(wǎng)絡(luò): 23812.5 ms
  2. 「Scikit」的神經(jīng)網(wǎng)絡(luò):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

令人驚訝的是,Scikit 是所有這些中最快的。這可能是因?yàn)槲覀儧](méi)有做龐大的矩陣乘法。也可能是 TensorFlow 需要額外的編譯步驟,如變量初始化等等?;蛘?,也許我們不得不在 python 中運(yùn)行循環(huán),而不是在 C 中(Python 循環(huán)真的非常糟糕!)我自己也不是很確定。我完全明白這絕不是一種全面的基準(zhǔn)測(cè)試,因?yàn)樗辉谔囟ǖ那闆r下應(yīng)用了單個(gè)數(shù)據(jù)點(diǎn)。然而,這個(gè)庫(kù)的表現(xiàn)并不能代表當(dāng)前***,所以希望各位讀者和我們共同完善。 

責(zé)任編輯:龐桂玉 來(lái)源: 36大數(shù)據(jù)
相關(guān)推薦

2023-04-04 22:28:43

2010-01-08 17:13:46

Visual C++環(huán)

2010-01-26 15:51:06

C++變量

2009-06-05 14:54:09

EclipseC++環(huán)境搭建

2010-01-15 16:17:04

Carbide C++

2010-01-13 10:45:44

Visual C++

2009-07-16 10:20:21

赫夫曼編碼

2010-01-20 09:54:27

C++數(shù)據(jù)類型

2021-10-11 11:53:07

C++接口代碼

2010-01-15 19:28:59

C++

2010-01-15 16:25:48

學(xué)習(xí)C++

2010-01-14 16:54:02

C++開(kāi)發(fā)環(huán)境

2010-01-28 10:33:10

C++開(kāi)發(fā)程序

2019-08-28 14:21:39

C++C接口代碼

2020-07-31 18:33:56

C++編程語(yǔ)言

2010-01-13 11:02:50

C++環(huán)境

2010-01-13 18:44:03

C++編譯

2010-01-15 18:06:20

C++引用

2010-01-28 15:31:34

學(xué)習(xí)C++語(yǔ)言

2010-01-27 09:38:27

C++書籍
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)