摘要:我個人開發的深度學習腳手架 ModelZoo 發佈了!
好多天沒有更新原創文章了,國慶前的一段時間確實比較忙,整個九月在參加各種面試,另外還有公司的項目,還有自己的畢設,另外還需要準備參加一些活動和講座,時間排的很緊,不過還在這些事情基本在國慶來臨之際都暫告一段落了,所以國慶我也沒打算再幹太多事情,就準備在家休養生息。
在家一段時間,我嘗試對之前做過的一些項目進行一些梳理,另外還對一些比較新的技術進行了一些探索,這其中就包括深度學習相關的一些框架,如 TensorFlow、Keras 等等。
想必大家都或多或少聽過 TensorFlow 的大名,這是 Google 開源的一個深度學習框架,裏面的模型和 API 可以說基本是一應俱全,但 TensorFlow 其實有很多讓人吐槽的地方,比如 TensorFlow 早期是隻支持靜態圖的,你要調試和查看變量的值的話就得一個個變量運行查看它的結果,這是極其不友好的,而 PyTorch、Chainer 等框架就天生支持動態圖,可以直接進行調試輸出,非常方便。另外 TensorFlow 的 API 各個版本之間經常會出現不兼容的情況,比如 1.4 升到 1.7,裏面有相當一部分 API 都被改了,裏面有的是 API 名,有的直接改參數名,有的還給你改參數的順序,如果要做版本兼容升級,非常痛苦。還有就是用 TensorFlow 寫個模型,其實相對還是比較繁瑣的,需要定義模型圖,配置 Loss Function,配置 Optimizer,配置模型保存位置,配置 Tensor Summary 等等,其實並沒有那麼簡潔。
然而爲啥這麼多人用 TensorFlow?因爲它是 Google 家的,社區龐大,還有一個原因就是 API 你別看比較雜,但是確實比較全,contrib 模塊裏面你幾乎能找到你想要的所有實現,而且更新確實快,一些前沿論文現在基本都已經在新版本里面實現了,所以,它的確是有它自己的優勢。
然後再說說 Keras,這應該是除了 TensorFlow 之外,用的第二廣泛的框架了,如果你用過 TensorFlow,再用上 Keras,你會發現用 Keras 搭模型實在是太方便了,而且如果你仔細研究下它的 API 設計,你會發現真的封裝的非常科學,我感覺如果要搭建一個簡易版的模型,Keras 起碼得節省一半時間吧。
一個好消息是 TensorFlow 現在已經把 Keras 包進來了,也就是說如果你裝了 TensorFlow,那就能同時擁有 TensorFlow 和 Keras 兩個框架,哈哈,所以你最後還是裝個 TensorFlow 就夠了。
還有另一個好消息,剛纔我不是吐槽了 TensorFlow 的靜態圖嘛?這的確是個麻煩的東西,不過現在的 TensorFlow 不一樣了,它支持了 Eager 模式,也就是支持了動態圖,有了它,我們可以就像寫 Numpy 操作一樣來搭建模型了,要看某個變量的值,很簡單,直接 print 就 OK 了,不需要再去調用各種 run 方法了,可以直接拋棄 Session 這些繁瑣的東西,所以基本上和 PyTorch 是一個套路的了,而且這個 Eager 模式在後續的 TensorFlow 2.0 版本將成爲主打模式。簡而言之,TensorFlow 比之前好用多了!
好,以上說了這麼多,我今天的要說的正題是什麼呢?嗯,就是我基於 TensorFlow Eager 模式和 Keras 寫了一個深度學習的框架。說框架也不能說框架,更準確地說應該叫腳手架,項目名字叫做 ModelZoo,中文名字可以理解成模型動物園。
有了這個腳手架,我們可以更加方便地實現一個深度學習模型,進一步提升模型開發的效率。
另外,既然是 ModelZoo,模型必不可少,我也打算以後把一些常用的模型來基於這個腳手架的架構實現出來,開源供大家使用。
動機
有人說,你這不是閒的蛋疼嗎?人家 Keras 已經封裝得很好了,你還寫個啥子哦?嗯,沒錯,它的確是封裝得很好了,但是我覺得某些地方是可以寫得更精煉的。比如說,Keras 裏面在模型訓練的時候可以自定義 Callback,比如可以實現 Tensor Summary 的記錄,可以保存 Checkpoint,可以配置 Early Stop 等等,但基本上,你寫一個模型就要配一次吧,即使沒幾行代碼,但這些很多情況都是需要配置的,所以何必每個項目都要再去寫一次呢?所以,這時候就可以把一些公共的部分抽離出來,做成默認的配置,省去不必要的麻煩。
另外,我在使用過程中發現 Keras 的某些類並沒有提供我想要的某些功能,所以很多情況下我需要重寫某個功能,然後自己做封裝,這其實也是一個可抽離出來的組件。
另外還有一個比較重要的一點就是,Keras 裏面默認也不支持 Eager 模式,而 TensorFlow 新的版本恰恰又有了這一點,所以二者的兼併必然是一個絕佳的組合。
所以我寫這個框架的目的是什麼呢?
- 第一,模型存在很多默認配置且可複用的地方,可以將默認的一些配置在框架中進行定義,這樣我們只需要關注模型本身就好了。
- 第二,TensorFlow 的 Eager 模式便於 TensorFlow 的調試,Keras 的高層封裝 API 便於快速搭建模型,取二者之精華。
- 第三,現在你可以看到要搜一個模型,會有各種花式的實現,有的用的這個框架,有的用的那個框架,而且參數、數據輸入輸出方式五花八門,實在是讓人頭大,定義這個腳手架可以稍微提供一些規範化的編寫模式。
- 第四,框架名稱叫做 ModelZoo,但我的理想也並不僅僅於實現一個簡單的腳手架,我的願景是把當前業界流行的模型都用這個框架實現出來,格式規範,API 統一,開源之後分享給所有人用,給他人提供便利。
所以,ModelZoo 誕生了!
開發過程
開發的時候,我自己首先先實現了一些基本的模型,使用的是 TensorFlow Eager 和 Keras,然後試着抽離出來一些公共部分,將其封裝成基礎類,同時把模型獨有的實現放開,供子類複寫。然後在使用過程中自己還封裝和改寫過一些工具類,這部分也集成進來。另外就是把一些配置都規範化,將一些常用參數配置成默認參數,同時放開重寫開關,在外部可以重定義。
秉承着上面的思想,我大約是在 10 月 6 日 那天完成了框架的搭建,然後在後續的幾天基於這個框架實現了幾個基礎模型,最終打磨成了現在的樣子。
框架介紹
GitHub 地址:https://github.com/ModelZoo/ModelZoo
框架我已經發布到 PyPi,直接使用 pip 安裝即可,目前支持 Python3,Python 2 尚未做測試,安裝方式:
pip3 install model-zoo
其實我是很震驚,這個名字居然沒有被註冊!GitHub 和 PyPi 都沒有!不過現在已經被我註冊了。
OK,接下來讓我們看看用了它能怎樣快速搭建一個模型吧!
我們就以基本的線性迴歸模型爲例來說明吧,這裏有一組數據,是波士頓房價預測數據,輸入是影響房價的各個因素,輸出是房價本身,具體的數據集可以搜 Boston housing price regression dataset 瞭解一下。
總之,我們只需要知道這是一個迴歸模型就好了,輸入 x 是一堆 Feature,輸出 y 是一個數值,房價。好,那麼我們就開始定義模型吧,模型的定義我們繼承 ModelZoo 裏面的 BaseModel 就好了,實現 model.py 如下:
from model_zoo.model import BaseModel import tensorflow as tf class BostonHousingModel(BaseModel): def __init__(self, config): super(BostonHousingModel, self).__init__(config) self.dense = tf.keras.layers.Dense(1) def call(self, inputs, training=None, mask=None): o = self.dense(inputs) return o
好了,這就定義完了!有人會說,你的 Loss Function 呢?你的 Optimizer 呢?你的 Checkpoint 保存呢?你的 Tensor Summary 呢?不需要!因爲我已經把這些配置封裝到 BaseModel 了,有默認的 Loss Function、Optimizer、Checkpoint、Early Stop、Tensor Summary,這裏只需要關注模型本身即可。
有人說,要是想自定義 Loss Function 咋辦呢?自定義 Optimizer 咋辦呢?很簡單,只需要複寫一些基本的配置或複寫某個方法就好了。
如改寫 Optimizer,只需要重寫 optimizer 方法即可:
def optimizer(self): return tf.train.AdamOptimizer(0.001)
好,定義了模型之後怎麼辦?那當然是拿數據訓練了,又要寫數據加載,數據標準化,數據切分等等操作了吧,寫到什麼方法裏?定義成什麼樣比較科學?現在,我們只需要實現一個 Trainer 就好了,然後複寫 prepare_data 方法就好了,實現 train.py 如下:
import tensorflow as tf from model_zoo.trainer import BaseTrainer from model_zoo.preprocess import standardize tf.flags.DEFINE_integer('epochs', 100, 'Max epochs') tf.flags.DEFINE_string('model_class', 'BostonHousingModel', 'Model class name') class Trainer(BaseTrainer): def prepare_data(self): from tensorflow.python.keras.datasets import boston_housing (x_train, y_train), (x_eval, y_eval) = boston_housing.load_data() x_train, x_eval = standardize(x_train, x_eval) train_data, eval_data = (x_train, y_train), (x_eval, y_eval) return train_data, eval_data if __name__ == '__main__': Trainer().run()
好了,完事了,模型現在已經全部搭建完成!在這裏只需要實現 prepare_data 方法,返回訓練集和驗證集即可,其他的什麼都不需要!
數據標準化在哪做的?這裏我也封裝好了方法。 運行在哪運行的?這裏我也做好了封裝。 模型保存在哪裏做的?同樣做好了封裝。 Batch 切分怎麼做的?這裏也做好了封裝。
我們只需要按照格式,返回這兩組數據就好了,其他的什麼都不用管!
那同樣的,模型保存位置,模型名稱,Batch Size 多大,怎麼設置?還是簡單改下配置就好了。
如要修改模型保存位置,只需要複寫一個 Flag 就好了:
tf.flags.DEFINE_string('checkpoint_dir', 'checkpoints', help='Data source dir')
好了,現在模型可以訓練了!直接運行上面的代碼就好了:
python3 train.py
結果是這樣子的:
Epoch 1/100 1/13 [=>............................] - ETA: 0s - loss: 816.1798 13/13 [==============================] - 0s 4ms/step - loss: 457.9925 - val_loss: 343.2489 Epoch 2/100 1/13 [=>............................] - ETA: 0s - loss: 361.5632 13/13 [==============================] - 0s 3ms/step - loss: 274.7090 - val_loss: 206.7015 Epoch 00002: saving model to checkpoints/model.ckpt Epoch 3/100 1/13 [=>............................] - ETA: 0s - loss: 163.5308 13/13 [==============================] - 0s 3ms/step - loss: 172.4033 - val_loss: 128.0830 Epoch 4/100 1/13 [=>............................] - ETA: 0s - loss: 115.4743 13/13 [==============================] - 0s 3ms/step - loss: 112.6434 - val_loss: 85.0848 Epoch 00004: saving model to checkpoints/model.ckpt Epoch 5/100 1/13 [=>............................] - ETA: 0s - loss: 149.8252 13/13 [==============================] - 0s 3ms/step - loss: 77.0281 - val_loss: 57.9716 .... Epoch 42/100 7/13 [===============>..............] - ETA: 0s - loss: 20.5911 13/13 [==============================] - 0s 8ms/step - loss: 22.4666 - val_loss: 23.7161 Epoch 00042: saving model to checkpoints/model.ckpt
可以看到模型每次運行都會實時輸出訓練集和驗證集的 Loss 的變化,另外還會自動保存模型,自動進行 Early Stop,自動保存 Tensor Summary。
可以看到這裏運行了 42 個 Epoch 就完了,爲什麼?因爲 Early Stop 的存在,當驗證集經過了一定的 Epoch 一直不見下降,就直接停了,繼續訓練下去也沒什麼意義了。Early Stop 哪裏配置的?框架也封裝好了。
然後我們還可以看到當前目錄下還生成了 events 和 checkpoints 文件夾,這一個是 TensorFlow Summary,供 TensorBoard 看的,另一個是保存的模型文件。
現在可以打開 TensorBoard 看看有什麼情況,運行命令:
cd events tensorboard --logdir=.
可以看到訓練和驗證的 Loss 都被記錄下來,並化成了圖表展示。而這些東西我們配置過嗎?沒有,因爲框架封裝好了。
好,現在模型有了,我們要拿來做預測咋做呢?又得構建一邊圖,又得重新加載模型,又得準備數據,又得切分數據等等,還是麻煩,並沒有,這裏只需要這麼定義就好了,定義 infer.py 如下:
from model_zoo.inferer import BaseInferer from model_zoo.preprocess import standardize import tensorflow as tf tf.flags.DEFINE_string('checkpoint_name', 'model.ckpt-20', help='Model name') class Inferer(BaseInferer): def prepare_data(self): from tensorflow.python.keras.datasets import boston_housing (x_train, y_train), (x_test, y_test) = boston_housing.load_data() _, x_test = standardize(x_train, x_test) return x_test if __name__ == '__main__': result = Inferer().run() print(result)
這裏只需要繼承 BaseInferer,實現 prepare_data 方法就好了,返回的就是 test 數據集的 x 部分,其他的還是什麼都不用幹!
另外這裏額外定義了一個 Flag,就是 checkpoint_name,這個是必不可少的,畢竟要用哪個 Checkpoint 需要指定一下。
這裏我們還是那數據集中的數據當測試數據,來看下它的輸出結果:
[[ 9.637125 ] [21.368305 ] [20.898445 ] [33.832504 ] [25.756516 ] [21.264557 ] [29.069794 ] [24.968184 ] ... [36.027283 ] [39.06852 ] [25.728745 ] [41.62165 ] [34.340042 ] [24.821484 ]]
就這樣,預測房價結果就計算出來了,這個和輸入的 x 內容都是一一對應的。
那有人又說了,我如果想拿到模型中的某個變量結果怎麼辦?還是很簡單,因爲有了 Eager 模式,直接輸出就好。我要自定義預測函數怎麼辦?也很簡單,複寫 infer 方法就好了。
好,到現在爲止,我們通過幾十行代碼就完成了這些內容:
- 數據加載和預處理
- 模型圖的搭建
- Optimizer 的配置
- 運行結果的保存
- Early Stop 的配置
- Checkpoint 的保存
- Summary 的生成
- 預測流程的實現
總而言之,用了這個框架可以省去很多不必要的麻煩,同時相對來說比較規範,另外靈活可擴展。
以上就是 ModelZoo 的一些簡單介紹。
願景
現在這個框架剛開發出來幾天,肯定存在很多不成熟的地方,另外文檔也還沒有來得及寫,不過我肯定是準備長期優化和維護下去的。另外既然取名叫做 ModelZoo,我後面也會把一些常用的深度學習模型基於該框架實現出來併發布,包括 NLP、CV 等各大領域,同時在實現過程中,也會發現框架本身的一些問題,並不斷迭代優化。
比如基於該框架實現的人臉情緒識別的項目:https://github.com/ModelZoo/EmotionRecognition
其識別準確率還是可以的,比如輸入這些圖片:
模型便可以輸出對應的情緒類型和情緒分佈:
Image Path: test1.png Predict Result: Happy Emotion Distribution: {'Angry': 0.0, 'Disgust': 0.0, 'Fear': 0.0, 'Happy': 1.0, 'Sad': 0.0, 'Surprise': 0.0, 'Neutral': 0.0} ==================== Image Path: test2.png Predict Result: Happy Emotion Distribution: {'Angry': 0.0, 'Disgust': 0.0, 'Fear': 0.0, 'Happy': 0.998, 'Sad': 0.0, 'Surprise': 0.0, 'Neutral': 0.002} ==================== Image Path: test3.png Predict Result: Surprise Emotion Distribution: {'Angry': 0.0, 'Disgust': 0.0, 'Fear': 0.0, 'Happy': 0.0, 'Sad': 0.0, 'Surprise': 1.0, 'Neutral': 0.0} ==================== Image Path: test4.png Predict Result: Angry Emotion Distribution: {'Angry': 1.0, 'Disgust': 0.0, 'Fear': 0.0, 'Happy': 0.0, 'Sad': 0.0, 'Surprise': 0.0, 'Neutral': 0.0} ==================== Image Path: test5.png Predict Result: Fear Emotion Distribution: {'Angry': 0.04, 'Disgust': 0.002, 'Fear': 0.544, 'Happy': 0.03, 'Sad': 0.036, 'Surprise': 0.31, 'Neutral': 0.039} ==================== Image Path: test6.png Predict Result: Sad Emotion Distribution: {'Angry': 0.005, 'Disgust': 0.0, 'Fear': 0.027, 'Happy': 0.002, 'Sad': 0.956, 'Surprise': 0.0, 'Neutral': 0.009}
如果大家對這個框架感興趣,或者也想加入實現一些有趣的模型的話,可以在框架主頁提 Issue 留言,我非常歡迎你的加入!另外如果大家感覺框架有不足的地方,也非常歡迎提 Issue 或發 PR,非常非常感謝!
最後,如果你喜歡的話,還望能贈予它一個 Star,這樣我也更有動力去維護下去。
項目的 GitHub 地址:https://github.com/ModelZoo/ModelZoo。
謝謝!