標籤:

如何看待tensorflow新出的eager模式?

官方介紹:https://research.googleblog.com/2017/10/eager-execution-imperative-define-by.html

與pytorch比較:https://medium.com/@yaroslavvb/tensorflow-meets-pytorch-with-eager-mode-714cce161e6c


做為一名試用完畢的TFBoy,來貢獻一篇心得體會。

安裝與開啟Eager模式

首先,要使用TensorFlow新出的Eager模式是非常方便的,不必從源碼編譯,官方已經為我們準備了預編譯的包,CPU版本的安裝方式:

pip install tf-nightly

GPU版本的安裝:

pip install tf-nightly-gpu

提供Linux、Windows、MacOS三平台支持,安裝即用。

安裝之後,在Python里運行下面的代碼:

import tensorflow as tf
import tensorflow.contrib.eager as tfe
tfe.enable_eager_execution()

就開啟了Eager模式,這時,TensorFlow會從原先的聲明式(declarative)編程形式變成命令式(imperative)編程形式。當寫下語句"c = tf.matmul(a, b)"後(以及其他任何tf開頭的函數),就會直接執行相應的操作並得到值,而不再像之前那樣,生成一個Tensor,通過sess.run()才能拿到值。注意:這種Eager模式一旦被開啟就不能被關閉。

好處

使用Eager模式的好處大家應當都很清楚,可以直接參考PyTorch的相關問題來看:PyTorch到底好用在哪裡?。這裡就簡單說一說,大概有以下幾點:

  1. 搭模型更方便了:之前搭模型通常要認真記下每一步Tensor的shape和意義,然後再操作。現在可以輕鬆點,邊搭邊寫,忘記形狀或者含義的時候可以直接打出來看。另外流程式控制制可以使用Python的內建語法,更加直觀。
  2. 調試時no more sess.run() ! 之前在調試時必須要加上sess.run(),很麻煩,現在可以直接把變數print出來,亦可使用IDE的監控工具單步調試。
  3. 最後,如果之前我們想在自己的程序中用tf開頭的函數,需要手動開啟Session將結果的Tensor轉換成Numpy數組,或者使用官方提供的函數修飾器。現在只需要用開啟這個Eager模式,就可以直接把tf開頭的函數當普通函數用了。

如何在Eager模式下反向傳播求梯度

好處簡單說完,再來講一些實現上的技術細節。在Eager模式中,正向傳播很直觀很好理解,但應該怎麼求梯度呢?

大致研究了一下,在tfe中共有四個函數直接服務於反向傳播,它們是:

  • tfe.gradients_function
  • tfe.value_and_gradients_function
  • tfe.implicit_gradients
  • tfe.implicit_value_and_gradients

這四個函數的功能非常類似於Python中的函數修飾器,直接來看代碼:

def f(x, y):
return x ** 2 + y ** 2
g = tfe.gradients_function(f)
g(2., 3.)

tfe.gradients_function的輸入是一個函數,輸出是輸入函數相對於它所有參數的梯度函數。例如在這裡的代碼中f(x, y) = x ** 2 + y ** 2,f關於x的偏導是2*x,關於y的偏導是2*y,因此g(x, y) = (2 * x, 2* y)。調用g(2., 3.)的返回值就是(4., 6.)。這就是Eager模式下的自動求導,f中可以使用絕大多數tf開頭的函數,都能自動得到計算導數的函數。

如果你了解Python的函數修飾器語法的話,上面的代碼就可以直接寫成:

@tfe.gradients_function
def g(x, y):
return x ** 2 + y ** 2
g(2., 3.)

效果是一樣的。

tfe.gradients_function的功能是對函數的輸入參數求導,但在實際使用中,我們往往更希望對TensorFlow中的變數(Variable)求導,因為變數中保存的是模型的參數,這才是我們真正要優化、做梯度下降的部分。tfe.implicit_gradients的功能就是,生成可以對「計算過程中所有用到的變數」求導的函數。為了說清楚,還是來看代碼:

vx = tfe.Variable(initial_value=1.0, name="vx")
vy = tfe.Variable(initial_value=1.0, name="vy")
def f(x):
return 2 * vx * x
g = tfe.implicit_gradients(f)
g(2.)

我們定義了兩個變數vx和vy,但在f在計算過程中,只用到了vx這個變數,所以只會對vx求導,相應的導數為2 * x ,這就是g所要做的計算。g的返回值是一個列表,列表中以(梯度,變數)的形式存儲了所有計算的梯度的值和變數的值。這裡就應當是[(4, 1)]。

如果需要同時獲取f的值和f的梯度,就可以分別用tfe.value_and_gradients_function和tfe.implicit_value_and_gradients取代tfe.gradients_function、tfe.implicit_gradient。原先tfe.gradients_function返回的是梯度,tfe.value_and_gradients_function返回的就是(函數值,梯度),tfe.implicit_value_and_gradients的效果也是類似的。

將求出的梯度應用到變數上的方法可以參照下面的代碼:

def loss_fn(...):
....
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.1)
value_and_gradients_fn = tfe.implicit_value_and_gradients(loss_fn)
empirical_loss, gradients_and_variables = value_and_gradients_fn(.....)
optimizer.apply_gradients(gradients_and_variables)

壞處

在我看來,引入TensorFlow Eager有一點不大好的是:又增加了一些框架學習成本

這表現在,開啟Eager的代碼並不能和之前的代碼完全兼容。

例如數據讀取,之前如果我們想把訓練數據讀入TensorFlow,主要有兩種方式:

  • 用placeholder
  • 用基於隊列的方法建立文件到Tensor的映射(如用tf.train.string_input_producer等函數)

很遺憾,在Eager模式下,這兩種讀取方式都是用不了的,開啟Eager模式後用tf.placeholder甚至會直接報錯!

與Eager模式適配的是新的TensorFlow Dataset API(記得好像是在1.2版本引入的),即要用Dataset的方式讀入數據。

此外還有一些小問題,比如不能用tf.Variable創建變數,而是要用tf.get_variable()或者tfe.Variable()來創建。官方推薦用tf.layers這種高層API來創建變數和操作。

一個Eager模式下的MNIST示例代碼

大家如果學過TensorFlow,都應該對官方的MNIST數據集Softmax分類示例有點印象,我用Dataset的API在Eager模式下重新寫了這個代碼,分類效果和老的代碼一模一樣,有興趣的可以參考我的Githubhzy46/TensorFlow-Eager-Examples之後也可能會傳一些別的示例在這個repo里,歡迎來star或者拍磚~

------

PS一個小廣告:由於用Eager必須要用到TensorFlow Dataset API,所以我打算這周抽時間寫一個教程帖,感興趣的可以提前關注一下我的專欄:AI Insight ~~


來來來,自問自答。(占坑提問,占坑回答)

整體感覺挺好的,感覺勸退不了tf boys girls了。

對用tf的人來說,提升了debug體驗,且沒有什麼要新學的東西,api都能兼容。

小問題(強行找問題):

1 拿參數還是得用get_variable的感覺,但是不確定,因為一眼沒看到scope。。會不會封裝在track_layer裡面了

2 速度迷之慢一些(看問題描述中第二個鏈接),但是應該不是什麼原則上的問題,應該之後可以優化的更好。

(毫無營養的回答。估計也沒有補充了,沒時間試驗。坐等tfboysgirls的使用體驗。)


Google 的 TensorFlow 速度不算快,語法也不算簡單,文檔也不算友好,API經常謎一般變動,

但是勝在格局大,版圖大,社區大。

eager模式的推出,標誌著 TF 不再是一個純靜態圖的框架,正式將動態圖也納入了自己的版圖當中,雖然勢必有許多bug,而且肯定和現有的許多 API 會出現融合上的問題,但是 TF 邁出了這一步。

事實上,1.0 前的 TF 和 1.0 後的 TF 完全是兩個不同的系統,1.0 後的 TF 陸續加入了 tf.layers, tf.estimator, tf.keras, tf.metrics, tf.data,開始逐漸閉合形成一個穩定的開發機制。

尤其是 tf.estimator 感覺正在成為 TF 的核心,只要寫一個model_fn,再寫一下 train_op, loss_op, acc_op, 其他工作基本都省了,x 和 y 直接放進 input_fn 就行,非常方便。更關鍵的是,Estimator 本身在設計的時候就考慮到了和 distributed TF 以及 TF serving 上的一些介面配合,在工業開發上會簡化掉更多問題。

現在再回過頭來看 TF:

  1. 有一套比較易用、流行的 Python API,雖然 PyTorch 更好,在 research 界的佔有率逐日上升,不過大多數公司的招聘還是要求 TensorFlow(用 MXNet 的企業也有一定數量) ,這也是 PyTorch 粉絲需要考慮的現實問題(除了在學界發展的 Researcher,或者在企業作指導的 Scientist);
  2. 工業考慮上有 Distributed TF, TF serving, XLA, 可以把訓練好的模型用 Java API 作 inference,有 mobile 支持,和一些其他常用的開發組件比如 Spark 也會有一些融合上的嘗試,現在的 TF 真正的前進方向,不是說要去優化計算速度(趕上其他框架),而是形成一個類似 Hadoop 的生態系統,變成一個機器學習開發的 TF 帝國;
  3. 在 PyTorch 出來以前,趕了個早,形成了強大的用戶社區,在中小型企業的佔有率也非常高,各種教程、文章、研究資料非常多,連學校都把 TF 放進了教學、作業里;

Google 真的是很強的一個公司,今年 Facebook 發出拍案叫絕的 Convolutional Seq2Seq 的時候,一度感覺 Google 的 NLP 是不是要守不住了,結果沒想到幾個月後用更加驚艷的 Attention Is All You Need 強勢回應。在動態圖大行其道的2017年,Google 交出了 tf.contrib.eager 予以對抗。這種對於競爭對手的回應,毫不退縮的精神面貌,充分體現了 Google 的強大。


10.24參加了Google在上海組織的TensorFlow技術研討會,會上就講到了eager execution,第一反應就是參考了pytorch,有了eager模式debug確實方便很多,然後eager和graph可以混編也不錯,但是最後主講人也說:1.在大模型上eager模式和原來的靜態圖模式速度上是「comparable performance」,2.在小模型上eager會有明顯的速度差異(就是比靜態圖慢很多)

個人猜測目前想用也很難用吧,畢竟demo example實在太簡單,很有可能會有各種bug。

各方面後續優化空間還是很大的

ps:我已經tf轉到pytorch上了,哈哈,pytorch的動態模式貌似比tf的靜態快(沒有嚴格驗證),pytorch真是各種友好,當然pytorch不適合直接用在移動端(但用ONNX可以轉到caffe2啊),我就是來宣傳pytorch的


如果注意觀察用tf寫代碼,比如def一個函數,給我們的幻覺是tf每次都會進入這個func執行。

其實tf非常聰明的利用tf.variable這種數據結構作為hook或者說錨定了程序員設計的演算法流程flow,並據此構建內部graph數據結構,這樣tf只要地層用c/c++寫一個很小的這個framework,上層中層都可以用 python來搞,地層framework可以自動用tf在python中的實體錨定flow,所以即使你def loss_func,但是tf不會每次都調用這個loss func

而是只會進入這個func一次,然後「記住」flow的流程

所以程序員以為設計了演算法,代碼在 python裡面跑。其實是幻覺,代碼再tf裡面跑,tf對於python或者程序員差不多是黑盒子,只能用tf提供的一些方法來窺視流程執行。

這樣在寫演算法不是非常方便,有時候希望自己可以一步一步的print一些東西出來,如果tf可以切換到eager模式這就可以實現了。

所以說eager其實是一種debug模式


pytorch 已經上 jit了 ,開始互相滲透咯。其實對整個生態發展是個好事,互相激勵,希望最後三個框架都兼顧了易用性 和 性能。


可以參考Github上TensorFlow Eager的示例:

https://github.com/ZhuanZhiCode/TensorFlow-Eager-Execution-Examples

從示例中的MLP和CNN就可以明顯看出來了,相對於老版本,優點實在是太多了:

  1. 由於省去了Session和Feed之類的機制,代碼量減少了一半
  2. 訓練過程即Python代碼執行過程,方便調試
  3. 可以用很Python和很程序的方法來實現很多操作,這樣也會導致代碼量的減少
  4. 自定義Operation和Gradient非常容易,而且可在GPU上運行,可以參考Github上的示例:用Eager Execution實現Softmax的示例並自定義梯度
  5. 對老版本的常用操作都兼容,因此學習成本非常低


優點是很多的,我一邊使用一邊來回答:

1、調試方便。以前調試,尤其是需要看中間變數,我們都需要sess.run來查看,現在不需要了。

2、中間變數的使用。比如以前的tensor如果要作為中間變數來進行range(X)的使用,這裡X假如是tensor的話就如法完成,現在eager模式下就不會有這個問題咯。


隱約覺得tf,pytorch,mxnet三分天下,終有一戰


eager模式的推出,標誌著深度學習框架大戰的結束。毋庸置疑,Tensorflow將以絕對領先的優勢排在第一位。其它框架能夠給TF提鞋的也只剩MXNet一家了,Gluon API對標eager,NNVM/TVM對標XLA,Mu Li /Tianqi Chen對標Jeff Dean。其它框架連提鞋的機會都沒有了。。


推薦閱讀:

TensorFlow中.crf_log_likelihood()怎麼用,越具體越好?
tensor flow dynamic_rnn 與rnn有啥區別?
怎樣在tensorflow中使用batch normalization?
tensorflow如何訓練自己的圖像數據?
關於Tensorflow的一些想法?

TAG:TensorFlow |