回覆列表
  • 1 # 你看我獨角獸嗎

    Tensorflow是一款功能強大且設計精良的神經網路工具。 Python API已有詳細記錄,開始非常簡單。 另一方面,C ++ API的文件減少到最低限度。 本教程將向您展示如何:

    使用Python構建和訓練一個簡單的圖形,

    儲存圖形並在C ++中執行它。

    在本教程中,我們將使用Google自己的構建工具bazel。 如果您更喜歡在沒有bazel的情況下工作,請檢視如何讓Tensorflow在沒有bazel的情況下執行。 作為一個例子,我們將使用世界上最小的網路。 它只包含一個輸入神經元和一個輸出神經元。 網路如下所示:

    結果目標是為輸出和輸入獲得相同的值。 雖然沒有任何意義,但它只是一個例子。 損失函式將是平方誤差,要求安裝Bazel和Tensorflow。

    建立一個資料夾且包含兩個檔案:

    實際的C ++檔案RunGraph.cpp;

    一個名為BUILD的檔案,帶有Bazel的說明。

    這兩個檔案都可以從我們的GitHub Repo下載, 我們來看看RunGraph.cpp檔案。

    首先,您需要使用計算圖的路徑更改PathGraph。 另一件事是我們不需要提供兩個佔位符。 我們指定了輸出Y.第二個佔位符(Y_)僅用於計算損失而不是Y。如果我們將損失指定為輸出,程式也將要求第二個佔位符,我們來看看BUILD檔案。

    然後執行以下命令:

  • 2 # 機器之心Pro

    在我們開始講解前,可以先看看最終成型的程式碼:

    1. 分支與特徵後端

    2. 僅支援標量的分支

    這個工程是我與 Minh Le 一起完成的。

    為什麼?

    如果你修習的是計算機科學(CS)的人的話,你可能聽說過這個短語「不要自己動手____」幾千次了。它包含了加密,標準庫,解析器等等。我想到現在為止,它也應該包含機器學習庫(ML library)了。

    不管現實是怎麼樣的,這個震撼的課程都值得我們去學習。人們現在把 TensorFlow 和類似的庫當作理所當然了。他們把它看作黑盒子並讓他執行起來,但是並沒有多少人知道在這背後的執行原理。這只是一個非凸(Non-convex)的最佳化問題!請停止對程式碼無意義的胡搞——僅僅只是為了讓程式碼看上去像是正確的。

    TensorFlow

    在 TensorFlow 的程式碼裡,有一個重要的元件,允許你將操作串在一起,形成一個稱為「圖形運算子」(此處英文錯誤?正確英文應為 Graph Operator)的東西。這個操作圖是一個有向圖 G=(V,E)G=(V,E), 在某些節點處 u1,u2,…,un,v∈Vu1,u2,…,un,v∈V,和 e1,e2,…,en∈E,ei=(ui,v)e1,e2,…,en∈E,ei=(ui,v)。我們知道,存在某種操作圖從 u1,…,unu1,…,un 對映到 vv.

    舉個例子,如果我們有 x + y = z,那麼 (x,z),(y,z)∈E(x,z),(y,z)∈E.

    這對於評估算術表示式非常有用,我們能夠在操作圖的匯點下找到結果。匯點是類似 v∈V,∄e=(v,u)v∈V,∄e=(v,u) 這樣的頂點。從另一方面來說,這些頂點從自身到其他頂點並沒有定向邊界。同樣的,輸入源是 v∈V,∄e=(u,v)v∈V,∄e=(u,v).

    對於我們來說,我們總是把值放在輸入源上,而值也將傳播到匯點上。

    反向模式分化

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

    差異化是 Tensorflow 中許多模型的核心需求,因為我們需要它梯度下降的執行。每一個從高中畢業的人都應該知道差異化的意思。如果是基於基礎函式組成的複雜函式,則只需要求出函式的導數,然後做鏈式法則。

    在 5 分鐘內倒轉模式

    所以現在請記住我們執行運算子時用的有向無環結構(DAG=Directed Acyclic Graph=有向無環圖),還有上一個例子用到的鏈式法則。做出評估,我們能看到像這樣的

    x -> h -> g -> f

    作為一個圖表,在 f 它能夠給予我們答案。然而,我們也可以反過來:

    dx <- dh <- dg <- df

    這樣它看起來就像鏈式法則了!我們需要把導數相乘到最終結果的路徑上。

    這裡是一個運算子的例子:

    所以這將衰減為一個圖的遍歷問題。有誰感覺到這是個拓撲排序和深度優先搜尋/寬度優先搜尋?

    執行

    在學校開學前,Minh Le 和我開始設計這個工程。我們決定使用後端的特徵庫進行線性代數的運算。他們有一個叫做 MatrixXd 的矩陣類。我們在這兒使用那個東西。

    class var {// Forward declarationstruct 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」的習語,意識是「執行的指標」。它對很多東西都很好,比如介面的解耦實現,以及當我們在堆疊上有一個本地介面時,允許我們例項化堆上的東西。一些「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)

    這幾乎是在做一個深度優先搜尋;你看到了吧?

    為什麼是 C++?

    在實際過程中,C++可能不是合適的語言來做這些事兒。我們可以在像「Oaml」這樣的函式式語言中花費更少的時間來開發。現在我明白為什麼「Scala」被用於機器學習中,主要就是因為「Spark」

    然而,這很明顯有利於 C++。

    Eigen(庫名)

    舉例來說,我們可以直接使用一個叫「Eigen」的 TensorFlow 的線性代數庫。這是一個不假思索就被人用爛了的線性代數庫。有一種類似於我們的表示式樹的味道,我們構建表示式,它只會在我們真正需要的時候進行評估。然而,對於「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.

    這個特徵庫非常的強大,這就是為什麼它是 TensortFlow 使用這些程式碼作為主要後端之一的原因。這意味著除了這個慢吞吞的評估技術之外還有其他的最佳化。

    運算子過載

    在 Java 中開發這個庫很不錯——因為沒有 shared_ptrs, unique_ptrs, weak_ptrs;我們得到了一個真實的,有用的圖形計算器(GC=Graphing Calculator)。這大大節省了開發時間,更不必說更快的執行速度。然而,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++的運算子則有非常好的表現。

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

    在這個庫中,有一些東西是可以指定的,它沒有明確的應用程式程式設計介面(API=Application Programming Interface),或者有但我知道。舉例子,實際上,如果我們只想訓練一個特定的權重子集,我們只可以回溯到我們感興趣的特定來源。這對於卷積神經網路的轉移學習非常有用,因為很多時候,像 VGG19 這樣的大網路被斬斷,會附加了一些額外的層,根據新的域名樣本來訓練權重。

    基準

    在 Python 的 TensorFlow 庫中,對虹膜資料集上的 10000 個「Epochs」進行訓練以進行分類,並使用相同的超引數,我們有:

    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 是所有這些中最快的。這可能是因為我們沒有做龐大的矩陣乘法。也可能是 TensorFlow 需要額外的編譯步驟,如變數初始化等等。或者,也許我們不得不在 python 中執行迴圈,而不是在 C 中(Python 迴圈真的非常糟糕!)我對自己也不是很自信。我完全意識到,這絕不是一種全面的基準測試,因為在特定的情況下,它只適用於單個數據點。然而,庫的表現並不能代表行為狀態,因為我們不需要回滾我們自己的 TensorFlow。

  • 中秋節和大豐收的關聯?
  • 青柚子幹是幹什麼用的?