跟繁瑣的模型說拜拜!深度學習腳手架 ModelZoo 來襲!

摘要:我個人開發的深度學習腳手架 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。

謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章