【Part7】ディープラーニングのモデル軽量化専門ライブラリ "Distiller"
AWSでエッジコンピューティング環境を作る
Part7です。今回から推論の話になります。エッジで推論を行うには、普段よりも計算リソースが限られていることを意識しなければなりません。そのため、ソフトウェア・ハードウェアの双面から高速化をしていく必要があるわけです。今回は、ソフトウェア面の高速化の話。
以前、エッジコンピューティングとは何か?というのを記事に書きました。
エッジでディープラーニングを可能にする「モデル圧縮」技術
ここで、枝刈り・量子化・蒸留という手法を紹介したのですが、Distillerという良いライブラリを見つけました。この分野にしては破格のスターとフォーク数...!これはやるしか無い!
Distillerとは
・Intel製のモデル軽量化専門ライブラリ
・PyTorch1.1.0ベースに書かれている
下記が、なかなか良さそうな機能でした
☑ ドキュメント(手法に関する理論的説明)が充実している
☑ .yaml形式で軽量化手法のスケジューリングを書ける
他にも、Tensorboardを簡単に使えたりなど、良いことが結構あります。チュートリアルやExampleを参考にしながら進めてみましたので、簡単に説明します。
ドキュメントの充実
モデル軽量化というと、ディープラーニングのメインストリームでは(おそらく)無いと思うので、まとまった情報を得たり、その実装例を探したりするのは大変ではなかろうかと思います。
Distillerのドキュメントは結構良くて、枝刈りや量子化など、その手法をかなりガッツリ説明してくれています。
更に、最新の研究もキャッチアップしてくれていますので、こういう手法がでてきているのか~など動向を追うこともできていい感じです。
軽量化手法のスケジューリングを書ける
yamlファイルに書くことで、予めライブラリ化された軽量化手法を試すことができ、これがかなり便利です。例えば、「1エポックから100エポックにかけて、2エポックに1回枝刈りを30%になるまで徐々に実施していって」みたいなスケジュールを簡単に書けるわけです。
軽量化のライブラリ自体も、枝刈り・正規化・量子化・蒸留など、各種あるので、柔軟に実施できます。
使ってみた : インストール
実際にモデル軽量化をどのようにやるのかを試しながら進めてみます。
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という処理を行い、計算量も軽くすることができるわけです。
まとめ
さて、チュートリアルを行いながらDistillerについて説明してきました。yamlを書くと、いとも簡単にモデル軽量化をしてくれます。Pytorchベースで使えますし、今までの資産を活かせてかなり便利です。IoTなど比較的貧弱なエッジデバイスでなにか賢いことをやりたいときに、一つの武器として持っておくと、良いのではないでしょうか。
さて、次回はハードウェア最適化部分についてです。私自体具体的な中身は詳しくないので触れませんが、Amazon SageMaker Neoというサービスを使うとハードウェアに最適化したコンパイルを行ってくれます。これを行うことによって、ラズパイに推論モデルを送るのがグッと楽になるわけです。ではではっ
Part6. 遠隔でのカメラ撮影&画像をS3に転送する
Part8. Amazon SageMaker Neo
サポートいただけると励みになります! よろしくおねがいします!!