見出し画像

【Part7】ディープラーニングのモデル軽量化専門ライブラリ "Distiller"

AWSでエッジコンピューティング環境を作る
Part7です。今回から推論の話になります。エッジで推論を行うには、普段よりも計算リソースが限られていることを意識しなければなりません。そのため、ソフトウェア・ハードウェアの双面から高速化をしていく必要があるわけです。今回は、ソフトウェア面の高速化の話。

以前、エッジコンピューティングとは何か?というのを記事に書きました。

エッジでディープラーニングを可能にする「モデル圧縮」技術

ここで、枝刈り・量子化・蒸留という手法を紹介したのですが、Distillerという良いライブラリを見つけました。この分野にしては破格のスターとフォーク数...!これはやるしか無い!

画像1

Distillerとは

・Intel製のモデル軽量化専門ライブラリ
・PyTorch1.1.0ベースに書かれている

下記が、なかなか良さそうな機能でした

☑ ドキュメント(手法に関する理論的説明)が充実している
☑ .yaml形式で軽量化手法のスケジューリングを書ける

他にも、Tensorboardを簡単に使えたりなど、良いことが結構あります。チュートリアルやExampleを参考にしながら進めてみましたので、簡単に説明します。

ドキュメントの充実

モデル軽量化というと、ディープラーニングのメインストリームでは(おそらく)無いと思うので、まとまった情報を得たり、その実装例を探したりするのは大変ではなかろうかと思います。

Distillerのドキュメントは結構良くて、枝刈りや量子化など、その手法をかなりガッツリ説明してくれています。

更に、最新の研究もキャッチアップしてくれていますので、こういう手法がでてきているのか~など動向を追うこともできていい感じです。

軽量化手法のスケジューリングを書ける

yamlファイルに書くことで、予めライブラリ化された軽量化手法を試すことができ、これがかなり便利です。例えば、「1エポックから100エポックにかけて、2エポックに1回枝刈りを30%になるまで徐々に実施していって」みたいなスケジュールを簡単に書けるわけです。

画像2

軽量化のライブラリ自体も、枝刈り・正規化・量子化・蒸留など、各種あるので、柔軟に実施できます。

使ってみた : インストール

実際にモデル軽量化をどのようにやるのかを試しながら進めてみます。

githubのページに書かれてある通り、cloneしてインストールすれば動くと思います。(Link:installation)

注意点として、GPU(CUDA9)を使えないときはPyTorchの公式サイトから前もってライブラリをインストールしておく必要があります。

```
python3 -m virtualenv env
source env/bin/activate
cd distiller
# cpuしか使えないとき
pip3 install torch==1.2.0+cpu torchvision==0.4.0+cpu -f https://download.pytorch.org/whl/torch_stable.html
# パッケージ
pip3 install -e .
```

ここで、virtualenvを導入していますが、このように仮想環境として切り出しておけば、その他の環境に影響を受けない/与えないでインストールを実行できるので便利です。必要なくなったらその環境ごと捨てればよいですし。

virtualenvの環境を消すときは↓

deactivate
rm -rf env/

モデルの学習 Training Only

まずは普通に学習するだけ。特にモデル軽量化をしません。

モデル : simplenet
データ : CIFAR10
※はじめにCIFAR10をDLするので、少し時間がかかります。
--lr 学習率(learning rate) : 0.01
$ cd distiller/examples/classifier_compression
$ python compress_classifier.py --arch simplenet_cifar ../../../data.cifar10 -p 30 -j=1 --lr=0.01 

こんな感じで学習が進んでいきます。

Dataset sizes:
       training=45000
       validation=5000
       test=10000


Training epoch: 45000 samples (256 per mini-batch)
Epoch: [0][   30/  176]    Overall Loss 2.302621    Objective Loss 2.302621    Top1 10.247396    Top5 50.729167    LR 0.010000    Time 0.176763
Epoch: [0][   60/  176]    Overall Loss 2.302543    Objective Loss 2.302543    Top1 10.299479    Top5 50.891927    LR 0.010000    Time 0.146631
Epoch: [0][   90/  176]    Overall Loss 2.302210    Objective Loss 2.302210    Top1 10.373264    Top5 51.601562    LR 0.010000    Time 0.135162
170500096it [01:10, 6986856.84it/s]
Epoch: [0][  120/  176]    Overall Loss 2.301693    Objective Loss 2.301693    Top1 11.598307    Top5 51.800130    LR 0.010000    Time 0.139054
Epoch: [0][  150/  176]    Overall Loss 2.300758    Objective Loss 2.300758    Top1 12.460938    Top5 53.031250    LR 0.010000    Time 0.139810

Parameters:
+----+-----------------+---------------+---------------+----------------+------------+------------+----------+----------+----------+------------+---------+----------+------------+
|    | Name            | Shape         |   NNZ (dense) |   NNZ (sparse) |   Cols (%) |   Rows (%) |   Ch (%) |   2D (%) |   3D (%) |   Fine (%) |     Std |     Mean |   Abs-Mean |
|----+-----------------+---------------+---------------+----------------+------------+------------+----------+----------+----------+------------+---------+----------+------------|
|  0 | conv1.weight    | (6, 3, 5, 5)  |           450 |            450 |    0.00000 |    0.00000 |  0.00000 |  0.00000 |  0.00000 |    0.00000 | 0.06774 |  0.00854 |    0.05909 |
|  1 | conv2.weight    | (16, 6, 5, 5) |          2400 |           2400 |    0.00000 |    0.00000 |  0.00000 |  0.00000 |  0.00000 |    0.00000 | 0.04768 |  0.00054 |    0.04133 |
|  2 | fc1.weight      | (120, 400)    |         48000 |          48000 |    0.00000 |    0.00000 |  0.00000 |  0.00000 |  0.00000 |    0.00000 | 0.02892 |  0.00006 |    0.02507 |
|  3 | fc2.weight      | (84, 120)     |         10080 |          10080 |    0.00000 |    0.00000 |  0.00000 |  0.00000 |  0.00000 |    0.00000 | 0.05261 |  0.00015 |    0.04549 |
|  4 | fc3.weight      | (10, 84)      |           840 |            840 |    0.00000 |    0.00000 |  0.00000 |  0.00000 |  0.00000 |    0.00000 | 0.06507 | -0.00537 |    0.05637 |
|  5 | Total sparsity: | -             |         61770 |          61770 |    0.00000 |    0.00000 |  0.00000 |  0.00000 |  0.00000 |    0.00000 | 0.00000 |  0.00000 |    0.00000 |
+----+-----------------+---------------+---------------+----------------+------------+------------+----------+----------+----------+------------+---------+----------+------------+
Total sparsity: 0.00

また、学習の様子がlogsに保存されている(学習終わりにフォルダを教えてくれます)ので、TensorBoardでその様子を見ることもできます

tensorboard -logdir='logs/2019.10.01/' &
localhost:6006

学習終わりのファイルは、latest_log_dirにbest.pth.tarにて保存されます。

モデルの学習(軽量化編)

yamlを用いて、軽量化のスケジューリングをし、実行してみましょう。

まず、yamlを書いていきます。各層において、徐々に枝刈りをしていくGradual Prunerというものを使います。一気に刈ると精度に影響が出る可能性が高いので、少しずつ刈るという手法です。各層ごとに最終的な枝刈り率を決定できます。

version: 1
pruners:
 conv1_pruner:
   class: 'AutomatedGradualPruner'
   initial_sparsity : 0.15
   final_sparsity: 0.3
   weights: [conv1.weight]

 conv2_pruner:
   class: 'AutomatedGradualPruner'
   initial_sparsity : 0.15
   final_sparsity: 0.5
   weights: [conv2.weight]

 fc_pruner:
   class: 'AutomatedGradualPruner'
   initial_sparsity : 0.15
   final_sparsity: 0.80
   weights: [fc1.weight, fc2.weight, fc3.weight]

lr_schedulers:
 pruning_lr:
   class: StepLR
   step_size: 30
   gamma: 0.10

policies:
 - pruner:
     instance_name : 'conv1_pruner'
   starting_epoch: 1
   ending_epoch: 50
   frequency: 2

 - pruner:
     instance_name : 'conv2_pruner'
   starting_epoch: 1
   ending_epoch: 50
   frequency: 2

 - pruner:
     instance_name : 'fc_pruner'
   starting_epoch: 1
   ending_epoch: 50
   frequency: 2

 - lr_scheduler:
     instance_name: pruning_lr
   starting_epoch: 1
   ending_epoch: 50
   frequency: 1

わかりやすさのために、上記のyamlファイルと、compress_classifier.py、先程学習したbest.pth.tarをbefore_compress.pth.tarに名称変更し、compress_testフォルダに配置しておきます。

配置後、compress_test上で下記コマンドで実行していきましょう。各オプションは以下のとおりです。

--compress : モデル圧縮に使うyamlを指定
--resume :  予め学習した、圧縮したいモデルを指定
time python3 compress_classifier.py --arch simplenet_cifar ../../../data.cifar10 -p 50 --lr=0.001 --epochs=150 --resume=before_compress.pth.tar --compress=simplenet_cifar.schedule_agp.yaml

こうしていくと枝刈りがグングン進んでいきます。

終了後のモデルもcompress_testに配置しておきましょう(今回はわかりやすさのために、枝刈り前のモデルをbefore_compress.pth.tar、枝刈り後のモデルをafter_compress.pth.tarとしています。)

$ ls compress_test/
after_compress.pth.tar  before_compress.pth.tar  simplenet_cifar.schedule_agp.yaml

スパース性と計算量の確認

スパース性(前)

$ python compress_classifier.py --resume=compress_test/before_compress.pth.tar --arch=simplenet_cifar ../../../data.cifar10 --summary=sparsity
Parameters:
+----+-----------------+---------------+---------------+----------------+------------+------------+----------+----------+----------+------------+---------+----------+------------+
|    | Name            | Shape         |   NNZ (dense) |   NNZ (sparse) |   Cols (%) |   Rows (%) |   Ch (%) |   2D (%) |   3D (%) |   Fine (%) |     Std |     Mean |   Abs-Mean |
|----+-----------------+---------------+---------------+----------------+------------+------------+----------+----------+----------+------------+---------+----------+------------|
|  0 | conv1.weight    | (6, 3, 5, 5)  |           450 |            450 |    0.00000 |    0.00000 |  0.00000 |  0.00000 |  0.00000 |    0.00000 | 0.15561 | -0.00109 |    0.12227 |
|  1 | conv2.weight    | (16, 6, 5, 5) |          2400 |           2400 |    0.00000 |    0.00000 |  0.00000 |  0.00000 |  0.00000 |    0.00000 | 0.08192 |  0.00936 |    0.06470 |
|  2 | fc1.weight      | (120, 400)    |         48000 |          48000 |    0.00000 |    0.00000 |  0.00000 |  0.00000 |  0.00000 |    0.00000 | 0.03335 |  0.00070 |    0.02762 |
|  3 | fc2.weight      | (84, 120)     |         10080 |          10080 |    0.00000 |    0.00000 |  0.00000 |  0.00000 |  0.00000 |    0.00000 | 0.06170 |  0.00200 |    0.05207 |
|  4 | fc3.weight      | (10, 84)      |           840 |            840 |    0.00000 |    0.00000 |  0.00000 |  0.00000 |  0.00000 |    0.00000 | 0.13179 |  0.00195 |    0.10702 |
|  5 | Total sparsity: | -             |         61770 |          61770 |    0.00000 |    0.00000 |  0.00000 |  0.00000 |  0.00000 |    0.00000 | 0.00000 |  0.00000 |    0.00000 |
+----+-----------------+---------------+---------------+----------------+------------+------------+----------+----------+----------+------------+---------+----------+------------+
Total sparsity: 0.00

スパース性(後)

$ python compress_classifier.py --resume=compress_test/after_compress.pth.tar --arch=simplenet_cifar ../../../data.cifar10 --summary=sparsity
Parameters:
+----+-----------------+---------------+---------------+----------------+------------+------------+----------+----------+----------+------------+---------+----------+------------+
|    | Name            | Shape         |   NNZ (dense) |   NNZ (sparse) |   Cols (%) |   Rows (%) |   Ch (%) |   2D (%) |   3D (%) |   Fine (%) |     Std |     Mean |   Abs-Mean |
|----+-----------------+---------------+---------------+----------------+------------+------------+----------+----------+----------+------------+---------+----------+------------|
|  0 | conv1.weight    | (6, 3, 5, 5)  |           450 |            315 |    0.00000 |    0.00000 |  0.00000 |  0.00000 |  0.00000 |   30.00000 | 0.39249 | -0.00189 |    0.27442 |
|  1 | conv2.weight    | (16, 6, 5, 5) |          2400 |           1200 |    0.00000 |    0.00000 |  0.00000 |  0.00000 |  0.00000 |   50.00000 | 0.17448 |  0.00151 |    0.10912 |
|  2 | fc1.weight      | (120, 400)    |         48000 |           9600 |    2.50000 |    0.00000 |  0.00000 |  2.50000 |  0.00000 |   80.00000 | 0.05315 | -0.00066 |    0.02291 |
|  3 | fc2.weight      | (84, 120)     |         10080 |           2016 |    0.00000 |    2.50000 |  0.00000 |  0.00000 |  0.00000 |   80.00000 | 0.07160 |  0.00142 |    0.03110 |
|  4 | fc3.weight      | (10, 84)      |           840 |            168 |    0.00000 |    7.14286 |  0.00000 |  0.00000 |  0.00000 |   80.00000 | 0.14027 |  0.01264 |    0.06163 |
|  5 | Total sparsity: | -             |         61770 |          13299 |    0.00000 |    0.00000 |  0.00000 |  0.00000 |  0.00000 |   78.47013 | 0.00000 |  0.00000 |    0.00000 |
+----+-----------------+---------------+---------------+----------------+------------+------------+----------+----------+----------+------------+---------+----------+------------+
Total sparsity: 78.47

計算量(前)

$ python compress_classifier.py --resume=compress_test/before_compress.pth.tar --arch=simplenet_cifar ../../../data.cifar10 --summary=compute
reset_optimizer flag set: Overriding resumed optimizer and resetting epoch count to 0
+----+--------+--------+----------+----------------+--------------+-----------------+--------------+------------------+--------+
|    | Name   | Type   | Attrs    | IFM            |   IFM volume | OFM             |   OFM volume |   Weights volume |   MACs |
|----+--------+--------+----------+----------------+--------------+-----------------+--------------+------------------+--------|
|  0 | conv1  | Conv2d | k=(5, 5) | (1, 3, 32, 32) |         3072 | (1, 6, 28, 28)  |         4704 |              450 | 352800 |
|  1 | conv2  | Conv2d | k=(5, 5) | (1, 6, 14, 14) |         1176 | (1, 16, 10, 10) |         1600 |             2400 | 240000 |
|  2 | fc1    | Linear |          | (1, 400)       |          400 | (1, 120)        |          120 |            48000 |  48000 |
|  3 | fc2    | Linear |          | (1, 120)       |          120 | (1, 84)         |           84 |            10080 |  10080 |
|  4 | fc3    | Linear |          | (1, 84)        |           84 | (1, 10)         |           10 |              840 |    840 |
+----+--------+--------+----------+----------------+--------------+-----------------+--------------+------------------+--------+
Total MACs: 651,720

計算量(後)

$ python compress_classifier.py --resume=compress_test/after_compress.pth.tar --arch=simplenet_cifar ../../../data.cifar10 --summary=compute
reset_optimizer flag set: Overriding resumed optimizer and resetting epoch count to 0
+----+--------+--------+----------+----------------+--------------+-----------------+--------------+------------------+--------+
|    | Name   | Type   | Attrs    | IFM            |   IFM volume | OFM             |   OFM volume |   Weights volume |   MACs |
|----+--------+--------+----------+----------------+--------------+-----------------+--------------+------------------+--------|
|  0 | conv1  | Conv2d | k=(5, 5) | (1, 3, 32, 32) |         3072 | (1, 6, 28, 28)  |         4704 |              450 | 352800 |
|  1 | conv2  | Conv2d | k=(5, 5) | (1, 6, 14, 14) |         1176 | (1, 16, 10, 10) |         1600 |             2400 | 240000 |
|  2 | fc1    | Linear |          | (1, 400)       |          400 | (1, 120)        |          120 |            48000 |  48000 |
|  3 | fc2    | Linear |          | (1, 120)       |          120 | (1, 84)         |           84 |            10080 |  10080 |
|  4 | fc3    | Linear |          | (1, 84)        |           84 | (1, 10)         |           10 |              840 |    840 |
+----+--------+--------+----------+----------------+--------------+-----------------+--------------+------------------+--------+
Total MACs: 651,720

今回の結果では、スパース性は高くなっている一方で、計算量は変わっていませんね。これはまだ重みを0にしているだけで、枝を刈っているわけではないからです。本来はその後thinningという処理を行い、計算量も軽くすることができるわけです。

画像3

まとめ

さて、チュートリアルを行いながらDistillerについて説明してきました。yamlを書くと、いとも簡単にモデル軽量化をしてくれます。Pytorchベースで使えますし、今までの資産を活かせてかなり便利です。IoTなど比較的貧弱なエッジデバイスでなにか賢いことをやりたいときに、一つの武器として持っておくと、良いのではないでしょうか。

さて、次回はハードウェア最適化部分についてです。私自体具体的な中身は詳しくないので触れませんが、Amazon SageMaker Neoというサービスを使うとハードウェアに最適化したコンパイルを行ってくれます。これを行うことによって、ラズパイに推論モデルを送るのがグッと楽になるわけです。ではではっ

Part6. 遠隔でのカメラ撮影&画像をS3に転送する
Part8.  Amazon SageMaker Neo

サポートいただけると励みになります! よろしくおねがいします!!