1. 程式人生 > >如何用C++實現自己的Tensorflow

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

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

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

我和Minh Le一起做了這個專案。

為什麼?

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

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

這裡寫圖片描述

Tensorflow

TensorFlow是由Google開源的一個深度學習庫。在TensorFlow的核心,有一個大的元件,將操作串在一起,行成一個叫做 運算子圖 的東西。這個運算子圖是一個有向圖G=(V,E),在某些節點u1,u2,,un,vVe1,e2,,enE,ei=(ui,v) 存在某些運算子將u1,,un 對映到 v

例如,如果我們有x + y = z,那麼 (x,z),(y,z)E

這對於評估算術表示式非常有用。我們可以通過尋找運算子圖中的 sinks來得到結果。 Sinks是諸如 vV,∄e=(v,u)這樣的頂點。換句話說,這些頂點沒有到其它頂點的有向邊。同樣的, sources

vV,∄e=(u,v)

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

反向模式求導

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

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

超級簡單的概述

如果有一個這樣的函式:

f(x,y) = x * y

那麼關於X的求導將產生:

df(x,y)dx=y

關於Y的求導將產生:

df(x,y)dy=x

另外一個例子:

f(x1,x2,...,x

n)=f(x)=xTx

這個導數是:

df(x)dxi=2xi

所以梯度就是:

xf(x)=2x

鏈式法則,譬如應用於複雜的函式f(g(h(x)))

df(g(h(x)))dx=df(g(h(x)))dg(h(x))dg(h(x))dh(x)dh(x)x

5分鐘內反向模式

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

x -> h -> g -> f

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

dx <- dh <- dg <- df

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

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

這裡寫圖片描述

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

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

實施

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

每個變數節點由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的副作用是執行時間稍慢,但是編譯時間縮短了很多。這讓我們通過多個函式呼叫/返回來保持資料結構的永續性。像這樣的樹狀資料結構應該是持久的。

有幾個 列舉,告訴我們目前正在執行哪些操作:

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

執行該樹評價的實際類稱為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等功能性語言來開發。現在我明白了為什麼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自我使用的主要後臺。這意味著除了這種惰性計算技術之外,還有其他方面的優化。

運算子過載

用Java開發這些庫會非常好—沒有shared_ptrs, unique_ptrs, weak_ptrs程式碼;我們可以採取 實際的,能勝任的,GC演算法。使用Java開發可以節省許多開發時間,更不用說執行速度也會變得更快。可是,Java不允許運算子過載,因而它們就不能這樣:

// 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的神經網路,迭代,優化: 25397.2 ms
  4. Autodiff的神經網路,具有迭代,無優化: 29052.4 ms
  5. Autodiff的神經網路,具有遞迴,無優化: 28121.5 ms

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

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

相關推薦

自己動手c++實現雜湊表

雜湊表 查詢效率約等於1 實現思想介紹 一般的hash思想 未採用模板,簡單的實現 key是int,value是string 把輸入的key值經過hash函式計算,算出它要放入的桶的編號 採用一個指標陣列記錄各個桶 每個桶裡都有50個key_value物件

如何用C++實現自己Tensorflow-----whatwhat

摘要:TensorFlow是由谷歌基於DistBelief進行研發的第二代人工智慧學習系統,其命名來源於本身的執行原理,它完全開源,作者通過自己的一個小專案,闡述瞭如何用C++實現自己的TensorFlow,這篇文章看起來可能會有點晦澀,你需要對相關知識有所瞭解。以下是譯文。 在我們開始之前,以

如何用C++實現自己Tensorflow

摘要:TensorFlow是由谷歌基於DistBelief進行研發的第二代人工智慧學習系統,其命名來源於本身的執行原理,它完全開源,作者通過自己的一個小專案,闡述瞭如何用C++實現自己的TensorFlow,這篇文章看起來可能會有點晦澀,你需要對相關知識有所

C++實現約瑟夫環的問題

content 人在 -h tel padding next family bsp sun 約瑟夫問題是個有名的問題:N個人圍成一圈。從第一個開始報數,第M個將被殺掉,最後剩下一個,其余人都將被殺掉。比如N=6,M=5。被殺掉的人的序號為5,4,6。2。3。最後剩下1

c++實現高精度加法

strlen 數位 cout col 代碼 code pre 操作數 eof c++實習高精度加法 最近遇到一個c++實現高精度加法的問題,高精度問題往往十復雜但發現其中的規律後發現並沒有那麽復雜,這裏我實現了一個整數的高精度加法,主要需要註意以下幾點: 1:將所需

c實現的各種排序的方法

else print switch %d [] code article 選擇 ++ #include <stdio.h> void swap(int *a, int *b); void bubble_sort(int a[], int n); void

C/C++】:C實現輸出日期的陰歷日子

print http this while lunar void 大小 oid pan 前言 輸出陰歷一直是個老大難的問題。由於陰歷日子沒有規律。所以這裏須要做的就是通過打表的算法做到輸出陰歷日子,可是非常多人都不太了解原理,我這裏就給大家送上了一個福

C# 實現一個簡單的 Rest Service 供外部調

message [] operation rem adk www span method title 用 C# 實現一個簡單的 Restful Service 供外部調用,大體總結為4點: The service contract (the methods it o

學習筆記-c實現三角函數的計算

math \n 數值 print spa 轉化 弧度 can ID 剛剛編好,記錄一下: #include <stdio.h> #include <math.h> int main(void) { while(1) {

RabbitMQ初探--C#實現通訊服務

download gin summary color 發送消息 sta 過程調用 change rod MQ全稱為Message Queue, 消息隊列(MQ)是一種應用程序對應用程序的通信方法。應用程序通過讀寫出入隊列的消息(針對應用程序的數據)來通信,而無需專用連接來鏈

c#實現文件的讀取和系列操作

static filename ros ima git int lin {0} ase Gitee代碼鏈接:https://gitee.com/hyr5201314/workcount 1.解題思路 首先要先讀取文件,然後調用函數實現返回文件的字符數,行數,單詞總數。用

c實現一個跳動的小球

#include<stdio.h> #include<stdlib.h> int main() {  int x=1,y=1,dirx=1,diry=1;  for(;;)  {   int line,col;   fo

c++實現環形陣列的最大子陣列之和(結對)

結對作業 1.分解問題,將環形陣列,剪開變成一個一維陣列。 2.用一維陣列的最大子陣列和解決。 對於一個環形陣列,對每一個一維陣列的表示共有n-1種 原始碼如下: 1 #include<iostream> 2 using namespace std; 3 int max_

c++實現環形陣列的最大子陣列之和

分析:   1.將環形陣列,剪開變成一個一維陣列。   2.用一維陣列的最大子陣列和解決。 對於一個環形陣列,表示成一個一維陣列總共有n種。如圖所示: 程式程式碼: 1 #include<iostream> 2 using namespace std; 3 int mai

c++實現環形數組的最大子數組之和

color 長度 http c++ 子數組和的最大值 ostream names alt 如圖所示 分析:   1.將環形數組,剪開變成一個一維數組。   2.用一維數組的最大子數組和解決。 對於一個環形數組,表示成一個一維數組總共有n種。如圖所示: 程序代碼: 1

c++實現環形陣列的最大子陣列的和

分析:對環形陣列確定首元素,從而變成一位陣列。因為有n個元素,所以有n種情況 如圖: 程式程式碼: #include<iostream> using namespace std; int max_sum1(int a[],int n) { int max_sum_h=a[0

c#實現json解析與序列化及格式化輸出

1. 簡介        json(javascript object notation)是一種使用可讀文字形式的檔案格式,用於傳輸由key-value對和array陣列形式的資料物件。這種資料格式在非同步的瀏覽器-服務端通訊模式中經常使用,作為替

C#實現 迴圈:空心菱形與實心菱形,三角形

三元表示式(三目運算): 語法:x>y?z:k 例如:      int int_num1 = 10;          &nb

c++實現顏色空間rgb,grey,luv和lab的互轉

1 rgb轉grey,rgb轉luv,rgb轉lab 1. 1 rgb轉grey     void RgbToGrey(unsigned char *rgb, double *grey) { double R = ((dou

Linux下C實現域名到IP的轉換(域名解析)

只需呼叫一個函式即可gethostbyname(),gethostbyname()返回對應於給定主機名的包含主機名字和地址資訊的hostent結構指標。結構的宣告與gethostaddr()中一致。下面是函式原型: Windows平臺下 #include <winsock2.h>