TensorFlow 2.X,會是它走下神壇的開始嗎?

機器之心原創,機器之心編輯部

現在都 2021 年了,機器學習好填的坑都已經填了,大家都在想怎麼將模型用到各種實際任務上。我們再去討論深度學習框架,吐槽它們的體驗,會不會有點過時?並不會,新模型與新演算法,總是框架的第一生產力。

從 Theano 一代元老,到 TensorFlow 與 PyTorch 的兩元世界,到現在各個國產框架與工具元件的興起。深度學習框架,總是跟隨前沿 DL 技術的進步而改變。

不過今天並不是討論深度學習框架的演變,而只是單純分享一下在演算法工程中,使用 TensorFlow 遇到的各種問題與感想。

TF 1.X :令人又愛又恨

TensorFlow 2.X 已經正式發佈 1 年多了,一週多前 TF 2.4 剛剛發佈,看 Release Notes 最新版仍然關注多機並行訓練、Keras 性能等新模組,甚至發佈了「TensorFlow 版」的 NumPy 工具。然而,除去這些新特性,TF 2.X 很多不和諧的問題仍然存在。

以至於,一直維護 TF 1.15 的演算法工程師,似乎 TensorFlow 的更新,對自己沒有任何影響。TF 1.X,仍然活躍在眾多的 GPU 上。

如果我們開始一項新任務,最先要做的就是查找已有的研究,以及已有的開源程式碼。這樣的開源程式碼,即使到現在,很多最新的前沿模型,尤其是谷歌大腦的各項研究,仍然採用的 1.X 的寫法與 API。

比如說,預訓練語言模型 T5、Albert、Electra 或者圖像處理模型 EfficientNet 等等。他們實際上還是用 1.X 那一套方法寫的,只不過能兼容 TensorFlow 2.X。

你會驚奇地發現,它們的 TensorFlow 匯入都是這種風格:

import tensorflow.compat.v1 as tf
import tensorflow.compat.v2 as tf

其中,「compat」是 TF2.X 專門為兼容 TF 1.X 配置的模組。目前,還是有很多前沿研究,放不下 TF 1.X。那就更不用說之前的經典模型,絕大多都是 TF 1.X 寫的。

不過如果只是匯入「compat」模組,那麼使用 TensorFlow 2.0 是為了什麼?難道只是饞它的版本號麼。

維護 OR 更新?

假設我們要使用這些 TF 模型,從開源程式碼開始進行修改或重寫。那麼就遇到了第一個問題,我到底是維護一個 TF 1.X 的程式碼庫呢,還是忍痛更新的 2.X?

假定我們決定維護 1.X 的靜態計算圖,那麼你會發現,我們寫程式碼只是在寫計算圖,中間變數列印不出資訊,循環語句或條件語句,基本都要改成矩陣運算形式。

TF 1.X 還是挺費勁的,就說列印變數,只調用 tf.print() 還不行,你還有將這條語句綁定到主要計算流程上,控制 Dependency。這樣,才能列印出真實的矩陣資訊。

假設我們選擇更新到 TF 2.0,基本上就相當於重寫模型了。官方確實有一個升級腳本:

但是看上面日誌也就知道,它差不多等同於「import tensorflow.compat.v1 as tf」。真正要利用上 TF 2.0 的 Eager Exexution,還是得手動重寫。

API 接口,難以明瞭

Tensorflow 1.X 時代,靜態圖雖說上手稍微難了那麼一丟丟,但是這並不是什麼問題。既然入了機器學習的坑,這當然是能掌握的。

TensorFlow 1.X 不好用的主要原因,還在於 API 接口比較 混亂。

  • tf.nn

  • tf.layer

  • tf.keras.layers

  • tf.contrib.layer

  • tf.contrib.slim

說到底,它們都是基本的神經網路層級,很多時候都是有重疊的。這種 API 上的冗餘,極大地降低了 TF 的生態質量。尤其是,很多官方教程,很多谷歌開源的模型程式碼,都用的是 tf.contrib.slim 來寫模型。比如說 MobileNet 之類的經典模型,官方實現就是用 TF 第三方庫「contrib」中的一個模組「slim」來寫的。

然後到了 TensorFlow 2.X,整個「contrib」庫都被放棄了。

在 1.X 後期,各個教程使用的接口都不相同,我們又分不清楚哪個接口到底好,哪個到底差。由此引出來的,就不僅是很差的使用者體驗,同時還有性能上的差異。

如果我們用 1.X 中的 tf.nn.rnn_cell 來做 LSTM,這也是沒問題的,只不過會特別慢。如果我們將運運算元換成 LSTM,那麼無疑速度會提升很多。整個 TF 1.X,在 API 接口上,總是存在大量的坑,需要演算法工程師特別注意。

那麼 TensorFlow 2.X 呢?

雖然說 TF 2.X 方向很明確,默認採用動態計算圖,大力推進 tf.keras 這樣的高級 API。這些都非常好,甚至用 Keras 寫模型比 PyTorch 還要精簡一些。

但是別忘了 TF 傳統藝能是靜態計算圖,它天生就比 tf.keras 擁有更多的底層配置。這就會導致兩種割裂的程式碼風格,一種是非常底層,使用 tf.function 等更一般的 API 構建模型,能進行各方面的定製化。另一種則非常抽象,使用 tf.keras 像搭積木一樣搭建模型,我們不用了解底層的架構如何搭建,只需要關注整體的設計流程即可。

如果教程與 API 對兩種模式都分的清清楚楚還好,但問題在於,引入 Keras 卻讓 API 又變得更加混亂了。

TF 2.X 官方教程目前以 Keras API 為主。

這其實和 1.X 的情況還是挺像的,同一個功能能由不同的 API 實現,但是不同 API 進行組合的時候,就會出問題。也就是說,如果我們混淆了 tf.keras 和底層 API,那麼這又是一個大坑。

比如說使用 tf.keras,以 model = tf.keras.Sequential 的方式構建了模型。那麼訓練流程又該是什麼樣的?是直接用 model.fit() ,還是說用 with tf.GradientTape() as Tape 做更具體的定製?如果我們先自定義損失函數,那這樣用高級 API 定義的模型,又該怎麼修改?

TF 2.X 官方教程,有的以類繼承的新方式,以及更底層的 API 構建模型。

本來還沒進入 TF 2.X 時代時,keras 與 tf 兩部分 API 互不影響,該用哪個就用哪個。但是現在,tf.keras 中的高級 API,與 tf 中的底層 API 經常需要混用,這樣的整合會讓開發者不知所措。

與此同時,API 的割裂,也加大了開發者尋找教程的難度。因為除了「TF2.0」 這個關鍵字,同時還要弄清楚:這個文件是關於 TF2.0 本身的,還是關於 tf.keras 的。

教程文件

不管怎麼說,TensorFlow 都是第一大深度學習框架,GitHub 上高達 15.2 萬的 Star 量遠超其它框架。其實在 TF 早期使用靜態計算圖的時期,整體教程還是比較連貫的,靜態計算圖有一條完整的路線。而且我們還有 tensor2tensor 這樣的程式碼庫,裡面的程式碼質量還是非常不錯的。

後來隨著深度學習成為主流,也就有了各種非官方教程,tf.contrib 模組裡面的程式碼也就越來越多。

到了 TF 2.X,tf.keras 整合進去之後,相關的文件還是比較少的,以至於整個指引文件成了 Keras 和經典 TF 的混合。

還是拿之前的例子來說,在官方文件上,如果要做圖像識別,教程會告訴你用 tf.keras.Sequential() 組合不同的神經網路層級,然後依次定義 model.compile() 與 model.fit(),然後深度學習模型就能訓練起來了。

同樣,如果要做圖像生成模型,那麼教程還是告訴你用 tf.keras.Sequential() 組合神經網路層級,但接下來卻需要自己定義損失函數、最最佳化器、控制迭代梯度等等。@tf.function、tf.GradientTape() 等等新模組,都會用上。

採用 @tf.function、tf.GradientTape() 等 TF 2.X 新特性的一個示例。

除了這兩種,對於更復雜的模型,TF2.0 還有一套解決方案,即從 tf.keras.Model 繼承模型,重新實現 call 方法。

總之官方文件有多種解決方案,能處理相同的問題。這種高級 API 與底層 API 混合在一起的做法特別常見,因此很多時候會感覺 TF 2.X 的技術路線不是非常明確。

此外,tf.keras 是個「大雜燴」,神經網路層級、最最佳化器、損失函數、資料預處理 API 等等都包含在內。它要與 tf.nn、tf.train、tf.data 之類的 API 在相同層級,總感覺有點怪怪的。看上去底層 API 與高級 API 兩大類文件,應該是平級的,這樣找起來比較好理解。

還有一點:速度

還有一點:速度

按理來說,TensorFlow 的速度最佳化的應該還是可以的。而且本來各框架的速度差別就不是很明顯,所以速度上應該沒什麼問題。但是在做演算法工程的時候,總會聽到周圍的朋友抱怨 TensorFlow 的速度還不如 PyTorch,抱怨 TF 2.0 儘管用上了動態計算圖,但速度還不如沒升級之前的 TF 1.X 程式碼。

這樣抱怨最大的可能性是,在做演算法時選擇的 Kernel 不太對,或者計算流、資料流在某些地方存在瓶頸,甚至是某些訓練配置就根本錯了。所以說,速度方面,很可能是我們自己最佳化沒做到位。但是 TF 1.X 升級到 2.X 之後,速度真的會有差別嗎?

筆者還真的做過非標準測試,如果使用升級腳本完成升級,同樣的程式碼,兩者底層的計運算元還真不一樣。速度上甚至 TF 1.X 略有優勢。

在最初的 TF 1.X 程式碼中,很多矩陣運算用的都是 tf.einsum(),靜態計算圖應該把它都轉化為了 MatMul 等計運算元,整體推斷速度平均是 16ms。

相同程式碼,在 TF 1.X 下的推斷速度。

而如果根據 tf.compat.v1 升級程式碼,相同的模型確實底層計運算元都不太一樣。但沒想到的是,TF 2.X 採用了新的 Einsum 運算,速度好像並不佔優?

相同程式碼,在 TF 2.X 下的推斷速度。

小結

最後,我們想說的是,作為AI工程師,選擇適合自己的深度學習框架需要認真考慮。雖說大家很多都用 TensorFlow,但維護起來真的有挺多坑要踩。而且一升級TF 2.X,就相當於換了個框架,十分麻煩,甚至不如根據業務情況重新考慮其他框架。

放眼望去,國產的幾種框架也許會是不錯的選擇。它們至少都能經受得起業務的考驗,且與框架維護者交流起來也會比較方便。此外,一些國產新興框架,沒有歷史包袱,技術路線比較統一與確定。一般而言,只要訓練效率高、部署方便高效、程式碼比較好維護,且能滿足業務所在領域的需求,那麼這就是個好框架。

總而言之,適合自己的才是最好的。

相關文章

福布斯:2022 區塊鏈 50 強榜單

福布斯:2022 區塊鏈 50 強榜單

區塊鏈已經走了很長一段路了!自 2019 年首次發佈區塊鏈 50 強以來,福布斯年度榜單上的十億美元級公司 (按銷售額或市值計算至少是十億美...

蔚小理走到了命運的「岔路口」

蔚小理走到了命運的「岔路口」

當新能源汽車的資格賽進入衝刺階段,競爭的焦點也發生了變化。 作者 | 周永亮編輯| 鄭玄 近日,隨著小鵬財報發佈,蔚小理都交出了 2022 ...

各大品牌的下一個代言人,何必是真人

各大品牌的下一個代言人,何必是真人

機器之心原創,機器之心編輯部 如果追溯「數字人」概念的起源,最早可以到上世紀 90 年代。當然,那時的數字人大多是存在於影視作品之中的非真人...

深度學習,撞牆了

深度學習,撞牆了

早在 2016 年,Hinton 就說過,我們不用再培養放射科醫生了。如今幾年過去,AI 並沒有取代任何一位放射科醫生。問題出在哪兒? 近年...