tensorflow的自動求導具體是在哪部分代碼里實現的?


tensorflow的自動求導是在每一個op裡面自己做的,然後通過graph的前後依賴根據鏈式規則去計算input和output的gradient的信息,怎麼解釋呢,你每個op(設計到數據計算)都應該有你對應的grad的計算邏輯,然後tf在改造graph的時候才會有所謂的前後grad傳播,很難用文字去描述,這裡有一個log1p的op實現,https://github.com/tensorflow/tensorflow/commit/7b7c02de56e013482b5fe5ab05e576dc98fe5742 你可以直接研究下,下圖中部分是log1p的gradient的實現


根據TensorFLow的白皮書 (https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/45166.pdf) (4.1節,Gradient Computation)可知,TensorFlow實現了自動求導(automatic gradient),方式是利用反向傳遞與鏈式法則建立一張對應原計算圖的梯度圖。(具體請看白皮書4.1,講的很詳細,也有圖示。)

由Python的API:

tf.gradients() (https://www.tensorflow.org/api_docs/python/tf/gradients)

可知,實現文件在tensorflow/python/ops/gradients_impl.py

C++實現在似乎在tensorflow/core/graph/gradients.cc,我看到源碼中有SymbolicGradientBuilder。

還可以根據這兩個文件的import與include橫向縱向去看。

TensorFlow的源碼也是最近才開始看,而且目前也看的不多,希望這些信息能幫到你,同時也希望拋磚引玉,期待源碼層面更有經驗的人來回答。


這一點也是我剛開始看 TensorFlow 時候的疑惑,後來自己輸出了一遍 TensorFlow 工作時的整個數據流圖就懂了。

還是從代碼裡面來看吧,這裡是一段線性回歸的片段:

X = tf.placeholder("float")
Y = tf.placeholder("float")
#
W = tf.Variable(initial_value=rng.randn(), name="weight")
b = tf.Variable(initial_value=rng.randn(), name="bias")
# A = X * W + b
activation = tf.add(tf.multiply(X, W), b)
#
cost = tf.reduce_sum(tf.pow(activation-Y, 2))/(2*n_samples) #L2 loss
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost) #Gradient descent
#
init = tf.global_variables_initializer()

整個構建數據流圖就這麼點代碼,後面就是調用 sess.run(optimizer) 來算了

從表面上看,這裡似乎是只構建了正向的數據流,而整個反向求導以及更新部分的構圖其實都是在tf.train.GradientDescentOptimizer() 這裡完成的。

我們把這部分輸出到 TensorBoard 里看一下整個數據流圖:

這張圖非常清晰明了,甚至每個 node 都能夠很容易地跟代碼中的每一句對應起來。

紅框部分是 A = X * W + b ,再往左上走是求了一個 cost 誤差,上面的 gradients 對應 tf.train.GradientDescentOptimizer() 這個 操作,那麼求導和反向部分在哪裡呢?

答案就是上面那個 gradients!!!

這張圖裡面 gradients 看上去只是小小的一個框,代碼裡面也只有 tf.train.GradientDescentOptimizer() 這麼一句,但是如果我們把這個框展開。

看到的就會是下面這樣的東西:

下面正向運算的每個 node 都有一條到上面 gradients 的 Tensor 流,相對的 gradients 中有 sum_grad、pow_grad、mul_grad 等等,完成了整個圖的反向求導的數據流。

然後,到了數據流圖的最上方:

結果回饋到 GradientDescent 這裡,再更新回 Weight 和 bias 這兩個 Variable:

---------- 我是不怎麼華麗的分割線 ----------

嗯,總結一下,所以在整個 Python 框架的構圖的過程中,我認為最為重要的一個部分就是 tf.train 下面的各種 Optimizer() 的 API 了,調用一下,人家就會沿著正向的數據流路給你自動推出需要的導數、更新操作等等。(哦,糾正一下,有其他答主給出了源碼的實現是在每個 op 自己裡面就包含了求導,那 Optimizer() 的作用就簡單多了,是把它們串成反向的數據流路即可)

這部分具體的實現源碼我也是剛開始看,所以了解的只有這麼多了。

TensorFlow 作為一個好用的框架,在這部分 API 中對寫代碼的人來說起的就是決定性的幫助。我數學不好,導數不會求,沒關係,調個 Optimizer() 就行了,剩下的框架自動幫你解決。


Tensorflow是由一個個獨立的operator組成的,事實上對於每個operator,並不存在什麼自動求導,而是要手工寫一個相應的gradient operator。 有了每個operator和其對應的導數實現後,就可以通過依賴關係求導了。

Tensorflow 目前C++端的實現並不完備。如果要新增一個operator O,就要通過註冊機制告訴tensorflow新增了這麼一個operator O,同樣的,如果要實現該operator的導數,就需要再註冊一個operator(記為GO)。那麼,怎麼知道GO是O的導數的實現呢?tensorflow還是通過註冊機制實現的,只是這個註冊必須在python中完成(GO也可以完全通過python實現)。

當前無法在python外完成導數註冊的原因也很簡單,對於tf.nn.embedding來說,導數一般是稀疏的,也即不能通過一個Tensor類來表示,但是tensorflow的C++端的輸入輸入都必須是tensor(list)類型。

官方新增operator的教程(含在python中實現導數的方法):https://www.tensorflow.org/extend/adding_an_op

用C++實現gradient的教程: http://davidstutz.de/implementing-tensorflow-operations-in-c-including-gradients/

自動求導方法如下:

比如operator

z=x+2*y, 我們實現了其導數 dz/dx=1, dz/dy=2

如果已經求出了 dL/dz=10, 則可以求出dL/dx=dL/dz*dz/dx=10*1=10

最後,吐槽一下tensorflow的導數註冊機制,除了麻煩之外,還有性能問題:

以下是官方教程中的代碼片段

@ops.RegisterGradient("ZeroOut")
def _zero_out_grad(op, grad):
to_zero = op.inputs[0]

當我們計算導數時,可能並不需要輸入x、y、輸出或輸出z, 但是我並沒有看到該信息如何才能註冊,從而降低內存佔用


自動求導就是鏈式求導。到目前為止,還沒見過不支持自動求導的深度學習框架。

類似1+1+1,只要你知道加法怎麼做,那麼多個數相加也是一樣做,只不過拆分成了2步:第1步解析並展開,第2步實際計算。

自動求導就是每一個op/layer自己依據自己的輸入和輸出做前向計算/反向求導,而框架則負責組裝調度這些op/layer,表現出來就是你通過框架去定義網路/計算圖,框架自動前向計算並自動求導。


推薦閱讀:

如何看待Theano宣布終止開發 ?
如何評價 Google 發布的 Tensor Processing Unit?
誰能詳細講解一下TensorFlow Playground所展示的神經網路的概念?
手頭沒有GPU,學習tensorflow有意義嗎?

TAG:深度學習DeepLearning | TensorFlow |