DistillerでDeepLearningのモデルを軽量化: Gradual Pruning編
DeepLearningのモデル軽量化の気になっていたライブラリを使ってみました。今回はざっくりと導入の仕方と簡単な使い方、ライブラリの仕組みなどを調べた内容を書きたいと思います。はじめて使う人のガイドになればと思います。
IntelのNeural Network Distiller。pruningや8-bit quantizationなど軽量化アルゴリズムのフレームワーク。PyTorchのモデルを軽量化してONNX出力。TensorBoardと連携したモニタリングもできて使い勝手良さそう。https://t.co/5qm2IoUuuA
— tkato (@_tkato_) 2018年5月1日
Distillerとは
PyTorch向けのモデル圧縮ライブラリです。以下のような特徴があります。
- 数種類の枝刈り(pruning), 量子化(quantization), 正則化(regularization)アルゴリズムを実装
- 既存の学習スクリプトのtraining loopに追加するだけで使える
- 設定はYAML。モデルのレイヤー単位でpruningのパラメータを変えるなど柔軟な設定。
- TensorBoardと連携した、モデルのweightや精度の可視化
実用的でよさそう。これをインストールして、サンプルを動かしていきます。
インストール
詳しくはREADMEに書いてあるけれど、GPUささったUbuntu使うのが一番簡単。環境はPyTorch公式のdockerイメージ(pytorch/pytorch/0.4_cuda9_cudnn7)をつかいました。
コンテナの中で以下を実行してインストール完了
$ git clone https://github.com/NervanaSystems/distiller.git $ cd distiller $ pip install -r requirements.txt
動作確認
コンソールで動かせるサンプル(distiller/examples)と、Jupyter Notebook(distiller/jupyter)が用意されてます。まずはREADMEのGetting Startedに記載のある、training onlyのサンプルを動かしてみます。これは、軽量化云々はせず、ただ学習するだけですが、動けば環境構築は成功。また、ここで学習したモデルを今回のpruningの実験に使っていきます。
$ cd distiller/examples/classifier_compression $ python3 compress_classifier.py --arch simplenet_cifar ../../../data.cifar10 -p 30 -j=1 --lr=0.01
デフォルトでTensorBoard用のログも出力するので、以下でTensorBoardを起動して閲覧できます
$ cd distiller/examples/classifier_compression $ tensorboard --logdir='./logs'
図の最下段のsparsityは”モデル全体における値が0のweightの比率”で、これが大きいほどweightを削れた(=pruningできた)ことになります。例えば、sparsity=80なら、モデルの80%のWeightが0ということです。今回はただ学習しただけなのでsparsityは0です。
デフォルトの設定での学習終了時の精度は、以下となります。とりあえずこれをベースラインにします。
Saving checkpoint --- test --------------------- 10000 samples (256 per mini-batch) Test: [ 30/ 39] Loss 0.824917 Top1 70.820312 Top5 97.916667 ==> Top1: 70.990 Top5: 97.930 Loss: 0.82
学習済みモデルは移動しておきましょう。
$ mkdir simplenet_cifar $ mv best.pth.tar checkpoint.pth.tar simplenet_cifar/
このcompress_classifier.pyはclassificationモデルの学習コードにDistillerの呼び出しを追加した汎用的なサンプルです。”こんな感じでdistillerをご自身のtrain loopに組み込んで使ってね”という感じで、これを読めばなんとなくdistillerが理解できます。
ヘルプを見ると、以下のようになっており、いろいろ設定を変えられることがわかります。
$ python compress_classifier.py -h usage: compress_classifier.py [-h] [--arch ARCH] [-j N] [--epochs N] [-b N] [--lr LR] [--momentum M] [--weight-decay W] [--print-freq N] [--resume PATH] [-e] [--pretrained] [--act-stats] [--param-hist] [--summary {sparsity,compute,optimizer,model,modules,png}] [--compress [COMPRESS]] [--sense {element,filter}] [--extras EXTRAS] [--deterministic] [--quantize] [--gpus DEV_ID] [--name NAME] DIR Distiller image classification model compression positional arguments: DIR path to dataset optional arguments: -h, --help show this help message and exit --arch ARCH, -a ARCH model architecture: alexnet | densenet121 | densenet161 | densenet169 | densenet201 | inception_v3 | mobilenet | mobilenet_025 | mobilenet_050 | mobilenet_075 | resnet101 | resnet152 | resnet18 | resnet20_cifar | resnet32_cifar | resnet34 | resnet44_cifar | resnet50 | resnet56_cifar | simplenet_cifar | squeezenet1_0 | squeezenet1_1 | vgg11 | vgg11_bn | vgg13 | vgg13_bn | vgg16 | vgg16_bn | vgg19 | vgg19_bn (default: resnet18) -j N, --workers N number of data loading workers (default: 4) --epochs N number of total epochs to run -b N, --batch-size N mini-batch size (default: 256) --lr LR, --learning-rate LR initial learning rate --momentum M momentum --weight-decay W, --wd W weight decay (default: 1e-4) --print-freq N, -p N print frequency (default: 10) --resume PATH path to latest checkpoint (default: none) -e, --evaluate evaluate model on validation set --pretrained use pre-trained model --act-stats collect activation statistics (WARNING: this slows down training) --param-hist log the paramter tensors histograms to file (WARNING: this can use significant disk space) --summary {sparsity,compute,optimizer,model,modules,png} print a summary of the model, and exit - options: sparsity | compute | optimizer | model | modules | png --compress [COMPRESS] configuration file for pruning the model (default is to use hard-coded schedule) --sense {element,filter} test the sensitivity of layers to pruning --extras EXTRAS file with extra configuration information --deterministic, --det Ensure deterministic execution for re-producible results. --quantize Apply 8-bit quantization to model before evaluation --gpus DEV_ID Comma-separated list of GPU device IDs to be used (default is to use all available devices) --name NAME, -n NAME Experiment name
また、各軽量化アルゴリズムの設定は--compressでYAMLファイルで与える思想となっています。これを書き換えることでPruningのアルゴリズムを変えたり、別のPruningや量子化と組み合わせたり、レイヤ単位でのpruningの設定や、何epochからpruningを始めるか、などかなり細かい設定をファイルベースで行えます。実験の再現もしやすいですね。素敵。
Gradual Pruningのサンプルを動かしてみる
つづいて、実践的なpruningのサンプルを動かすことにします。
そもそもpruningとは、最終的な計算結果に寄与しないWeightを除去する手法で、「寄与」の指標としては単純にWeightの絶対値が使われたりします(けど研究によっていろいろ)。
Weightが減ると何が嬉しいのかというと、モデルのサイズや実行時のメモリ使用量を減らすことができ(フレームワークの実装依存でもあるけれど)、計算量削減や高速化ができる場合があります。
今回試すのは、Gradual Pruningというアルゴリズムで、1-stageのpruningです。よくあるpruningだと、学習済みモデルをpruningしてから再学習をする2つのstageを繰り返しますが、この方法では学習しながら絶対値の小さいWeightを0にする(マスクする)ことで、1回の学習の中でpruningまでしてしまうもの。
Gradual(=段階的な)の名前の通り、徐々にマスクをかけていくのですが、最終的なsparsity(Weightにおけるゼロの比率)の目標値を与えることができます。また、何epochかけて目標のsparsityへ到達するかもハイパーパラメータとして与えます。
Gradual Pruningの特徴としては、学習のステップ毎sparsityを以下のような関数として定義していることです。
これは、Weightが多く冗長なものも多い学習初期はスピーディに削り、Weightが少なくなってからは少しずつ削るような関数になっています*1。
ちなみにこれは以下のnotebookの実行結果で、ハイパーパラメータによって この関数がどう変化するかを確かめることができました。
https://github.com/NervanaSystems/distiller/blob/master/jupyter/agp_schedule.ipynb
pruning直後は少なからず精度が落ちるので、「pruningで精度が落ちる→再学習で回復」をちょっとずつ、良い感じのバランスで繰り返すことが重要になるのだと思います。論文によれば100~1000stepくらいごとにpruningするのが良いとのこと*2。
Distillerの公式ドキュメントには、Gradual Pruningを始め、各アルゴリズムの説明が結構詳しく書いてあります。勉強になった。
https://nervanasystems.github.io/distiller/algo_pruning/index.html#automated-gradual-pruner-agp
さて、脱線しましたがサンプルを動かしていきます。
先ほどのサンプルで学習したsimplenet_cifarモデルをGradual Pruningで軽量化してみます。先ほどのcompress_classifier.pyから実行できます。
YAMLファイルは、/examples/agp-pruning/以下などのサンプルをベースにして、以下のように記述しました。
simplenet_cifarは、conv1-conv2-fc1-fc2-fc3 という簡単なモデルです。今回は、各層の目標のsparsityを30%-50%-80%-80%-80%になるようにしています。pruningは、削る層によっては大幅な精度劣化を引き起こす*3ので、このように層毎にsparsityを変えられるのは便利です。また、100epochかけて徐々にpruningする設定です。
Gradual Pruningに限らず、YAMLでどんな設定がかけるのかについては、以下のドキュメントがわかりやすいです。
Compression scheduling - Neural Network Distiller
YAMLがかけたので、先ほど保存した最も精度が良いモデルに対して、Gradual Pruningを実行します。とりあえず200epoch。
$ cd distiller/examples/classifier_compression $ vim simplenet_cifar.schedule_agp.yaml $ time python3 compress_classifier.py --arch simplenet_cifar ../../../data.cifar10 -p 50 --lr=0.001 --epochs=200 --resume=simplenet_cifar/best.pth.tar --compress=simplenet_cifar.schedule_agp.yaml
学習完了後のTensorBoardはこんな感じ。青がpretrainで、ピンクがGradualPruning時のplotです。pruningによってsparsityが大きくなり、学習開始時は少し精度が低くなりましたが その後の学習で回復し、最終的にはpruning前と同等の精度に戻っています*4。
Saving checkpoint --- test --------------------- 10000 samples (256 per mini-batch) ==> Top1: 71.330 Top5: 97.880 Loss: 0.824
層毎のsparsityを見ると、確かに30%-50%-80%-80%-80%になってますね。
最後に、この軽量化できたモデルの情報を詳しく見てみましょう。compress_classifier.pyに対して、--summaryオプションをつけるとモデルの情報を表で出力してくれます。--summary=sparsityはそのままの意味ですが、--summay=computeとすると、層毎、全体の計算量を出力します。
Before
$ python3 compress_classifier.py --resume=./best.pth.tar -a=simplenet_cifar ../../../data.cifar10 --summary=sparsity $ python3 compress_classifier.py --resume=./best.pth.tar -a=simplenet_cifar ../../../data.cifar10 --summary=compute
After
$ python3 compress_classifier.py --resume=./checkpoint.pth.tar -a=simplenet_cifar ../../../data.cifar10 --summary=sparsity $ python3 compress_classifier.py --resume=./checkpoint.pth.tar -a=simplenet_cifar ../../../data.cifar10 --summary=compute
最後の行の"Total sparsity"に注目すると、pruningによって78.47%のweightを削減できたことがわかります。しかし、before/afterで計算量が変わっていないのは、weightは0であるものの行列自体のサイズは変わっていないからですね。値が0のweightを行列から削除して行列を組み替えることで、計算量自体を削減できます*5。このweightを実際に小さくする機能は、制限はあるもののDistillerに入っているようです。気になる方は”Model thinning”でDistiller内を検索してみてください
Distillerの仕組み
最後に、Distillerの仕組みについて調べた内容です。
Distillerの使い方は、設定をYAMLで書いてSchedulerをtraining loopで呼び出すだけ。
内部はどのようになっているのでしょうか。
ドキュメントだとこの辺に書いてあります。
https://nervanasystems.github.io/distiller/design/index.html
https://nervanasystems.github.io/distiller/schedule/index.html
仕組みは単純で、training loopの各イベントの前後でSchedulerのAPIを呼んでいます。
For each epoch: compression_scheduler.on_epoch_begin(epoch) train() validate() save_checkpoint() compression_scheduler.on_epoch_end(epoch) train(): For each training step: compression_scheduler.on_minibatch_begin(epoch) output = model(input_var) loss = criterion(output, target_var) compression_scheduler.before_backward_pass(epoch) loss.backward() optimizer.step() compression_scheduler.on_minibatch_end(epoch)
上記の各on_xxxのタイミングで、SchedulerはPolicy(Pruning, Quantization, Regularizationを抽象化したクラス)を実行していきます。
Policyは、YAMLから設定を読み込んだ際に自動で登録されます。もちろんコード中で手動登録もできる。
PolicyにもSchedulerと同名のon_xxxメソッドが実装されており、SchedulerがコールされるとSchedulerに登録したPolicyもコールされます。Policyの種類により、schedulerのどのコールバックに対応するかが決まっています(反応しないやつはpassする)。
例えばPruningなら、on_epoch_beginでpruningするためのマスクを作成し、on_minibatch_beginではそのマスクをweightに適用し、weightの一部を0にします。マスク自体の作成の仕方は各Pruningのアルゴリズム(Prunerクラス)によって異なり、そこは分離されています。今回はGradual Pruningを使いました。
このように、種類が異なるモデル圧縮のアルゴリズムを共通の仕組みで扱える用になっており、拡張が容易ですね。
おわりに
再度ポイントをまとめて、締めたいと思います。
- compression schedulingの柔軟性
- レイヤーによって、どれくらいweightを削減しても精度劣化がないかは変わるので、レイヤー単位で細かく設定できるのは便利です。
- 新しい軽量化アルゴリズムを追加し易い設計
- training loopの各イベントに反応するPolicyとして抽象化しているのはわかりやすい。
- TensorBoardなどによるモニタリング機能の充実
- lossや精度だけでなく、実際にweightの統計情報をモニタできるのは便利です
- 過去の実験結果とも比較しやすいのもいいですね
(記事のタイトルにGradual Pruning編、と書きましたが次は未定です..)
NNablaでDQN書いてる(途中)
Sonyが公開したDLフレームワーク「NNabla」で何か書いてみようかと思い、Mnih et al., 2015のDQN(Deep Q-Network)を実装してみた。
ソースは以下。 ただし、現在学習中で結果を見れていないため、ソースにはバグがあるかも。
NNablaは初めて使うフレームワークのため、精度がでない原因がNNablaの使い方が間違いか、ハイパーパラメータの調整が足りないのか切り分けられないと困ると思った。 そこで、まずは他のフレームワークの実装をみて、ハイパーパラメータ等を参考にすることにした。 今回は以下を参考にしたが、非常にわかりやすい解説&コードでした。
DQNをKerasとTensorFlowとOpenAI Gymで実装する
この記事では、現状書いたところまでで気がついたことなどを書く。
今後は、まず普通のDQNで動くことを確認後、BinaryなDQNで実験してみたい。
実装
特にNNablaで実装する上でしていて気になったことなどを記載する
Target Networkのパラメータの更新
# update target network if step % Config.TARGET_NET_UPDATE_INTERVAL == 0: # copy parameter from dqn to target with nn.parameter_scope("dqn"): src = nn.get_parameters() with nn.parameter_scope("target"): dst = nn.get_parameters() for (s_key, s_val), (d_key, d_val) in zip(src.items(), dst.items()): # Variable#d method is reference d_val.d = s_val.d.copy()
- NNablaでは、NNのパラメータはすべてグローバルに管理。区別するために名前空間を使う仕組み。
with nn.parameter_scope('<name>')
で、このコンテキストではnameの階層に属するパラメータだけ扱えるようになる- Variable#dで、Variableがもつnumpyのndarrayに直接アクセスできる
順伝播処理
# inference image.d = state q.forward() action = np.argmax(q.d)
プレースホルダーであるVariableへの値のセットと、ネットワークの順伝播実行が命令として別れているのが、少し慣れなかった。 (たしか、プレースホルダーに値をセットせずにforwardした場合、ときたまSEGVしたような…)
その他
- Replay Memoryのサイズを大きくしすぎるとメモリ確保できずに強制修了するので注意
- システムモニターをみてすぐに気がつけてよかった
- 学習環境はDockerのコンテナで作ってる。そのため、gymのGUIが表示できなかった (できる?)
- ドキュメントで、Functionの一覧が把握しづらいなと感じた
まだ慣れていないことと、学習状況の可視化やデバッグがTensorFlowとかに比べてまだプアなので、積極的にこのライブラリを使うモチベーションがなければ、TensorFlowとかのほうが使うのが良いかも?
私の場合は、面白半分、BNNを動かしたい、組み込み用途への期待、でこのライブラリを今後もWatchしていきたいと思ってる。
環境構築
環境構築していて気になったことを記載する
NVIDIA-Docker
今回、初めてNVIDIA-Dockerを使ったところ非常に便利だった。
GPU環境構築の問題として、DLフレームワークによって対応するCUDA ToolkitやcuDNNのバージョンがまちまちなので環境が共存できなかったりする。
NVIDIA-Dockerを使えば、ホストにはGPUドライバだけインストールし、CUDAとcuDNNは各コンテナにインストールできるので、コンテナ毎に異なるバージョンが使える。
NVIDIA-Dockerのベースイメージは、様々なCUDAとcuDNNのバージョンの組み合わせがあり、以下で確認できる。
今回、NNabla用には、 nvidia/cuda:8.0-cudnn6-runtime-ubuntu16.04
を利用した*1
https://hub.docker.com/r/nvidia/cuda/
Hydrogen
JupyterなどとAtomをつないで、Atomをリッチな開発環境にできる。
変数のWatchなどもできてよかったので、今後も使ってこう。
複数のDockerコンテナを登録して簡単に切り替えられるので便利。設定はURL https://t.co/r4FtKxeiS5 pic.twitter.com/usjfY0U8bg
— tkato (@_tkato_) 2017年7月1日
MacでNNabla使うためにDockerの設定した
macでNNablaを使った開発したいが、公式にはMacは現在対応中の様子。
ここ数年はmacで手軽にLinux環境導入する手段としてDockerが流行ってるらしく、またNNabla公式もDockerfileを公開している。
そのため、今回はDockerを使ってNNablaの環境構築してみる。
以下で公開されている公式のDockerfile(develop-ubuntu16.04)に加筆して、Jupyter Notebookが使えるようにした。
https://github.com/sony/nnabla/tree/master/docker
FROM ubuntu:16.04 RUN apt-get update && apt-get install -y --no-install-recommends \ ccache \ clang-format-3.8 \ cmake \ curl \ g++ \ make \ python-dev \ python-pip \ python-setuptools \ unzip RUN curl -L https://github.com/google/protobuf/releases/download/v3.1.0/protoc-3.1.0-linux-x86_64.zip -o /tmp/protoc-3.1.0-linux-x86_64.zip \ && cd /usr/local \ && unzip /tmp/protoc-3.1.0-linux-x86_64.zip \ && chmod 755 /usr/local/bin/protoc \ && rm -f /tmp/protoc-3.1.0-linux-x86_64.zip readme.txt ADD python/setup_requirements.txt /tmp/deps/ RUN pip install -U -r /tmp/deps/setup_requirements.txt ADD python/requirements.txt /tmp/deps/ RUN pip install -U -r /tmp/deps/requirements.txt ADD python/test_requirements.txt /tmp/deps/ RUN pip install -U -r /tmp/deps/test_requirements.txt # tkato ADD RUN pip install -U jupyter RUN pip install -U nnabla VOLUME /notebooks WORKDIR /notebooks EXPOSE 8888 CMD ["jupyter", "notebook", "--no-browser", "--allow-root", "--ip=*", "--NotebookApp.password='sha1:XXXX'"]
jupyterの公式のDockerfileなどを読むと、設定は別ファイルに書いたりしてるようだが、面倒だったので上記のように書いた。
Jupyterのpasswordは、暗号化してDockerfileに記述する。 平文からSHA-1に変換する方法は、以下の投稿の(2-B)を参考にさせていただいた。
上記のDockerfileは、NNablaのに直接加筆した dockerのビルドスクリプトも提供されたのでそれを使って以下のようにビルドした
./docker-build develop-ubuntu16.04
また、コンテナの起動用には以下のスクリプトを作成し実行した。
docker run --name nbla -v /Volumes/share/nnabla-tutor:/notebooks -p 8888:8888 --rm nbla:develop-ubuntu16.04
これで、localhost:8888でNNablaが使えるJupyter Notebookが起動する。
保存したノート類は、ホストからアクセスでき、コンテナを消してもちゃんと残る。
Dockerについて
以前も、開発環境としてDockerを使おうとしたことはあったが、ちょっと試して面倒になって消してしまっていた。
しかし、以下の点に注意して運用すれば今回はうまくいきそう。
- まずDockerfileに書くもの、永続化するものを決める
- 永続化するのは/notebooksなどコード
- Pythonパッケージの追加などの環境の変更は、Dockerfileに記載して都度ビルド
- コンテナ内は必要最低限の環境
- 永続化すしたコードはホストからもいじれるので、例えばgitはホスト側に入ってればコンテナには不要
- エイリアスやシェルスクリプト書いて、1コマンドでコンテナを立ち上げられるようにして、使うに当たっての心理的な壁をなくす
- 今回は
docker-rm-none-images
とかalias定義して便利だった
- 今回は
docker run --rm
でコンテナ終了時に削除- コンテナは使い捨て前提にすることで、オペレーションが単純になった
- パフォーマンスも気にならない
これでとりあえずは不自由なく開発できるようになったはず。
Dockerを道具として使いこなせるようになりたい。
RustのLT会! Rust入門者の集い #3 に行ってきました
Rust LT会
Rustを初めて知って、興味を持ったのは今年の4月くらいだろうか??
Rustドキュメントを読んだところ、組み込みシステムでも十分使えそうだし、今現場で感じる課題を解決できそうな技術要素を沢山含んでるなあと思ったのでせこせこ調べてます。
まだあまり手を動かせていなくて、公式のドキュメントやオライリーの書籍をパラパラみている程度。
そんなこんなで、Rust入門者向けのイベントがあると知り行ってきました。
コンピューターサイエンスの教養が豊かな人ばかりで、刺激が多かったです。
以下、各LTを聞いてたときのメモ。
内容が薄いのは、私が話についていけなかったから…
ドッグさん
こういう本を実装しながら勉強すれば良いのか
- Rustで遅延評価を実装してみる
- マクロで囲むと遅延評価される、キャッシュされるようにした
- 純粋関数型データ構造の本をRustで実装★
サンクのデータ構造は、Memo or NotYet 計算済みならMemoを使い、そうでなければ計算
キーワード:moveキャプチャ、borrow_mut、Derefトレイト
Hashさん
- Rustのasmを読んで見る
- 公式が提供するWebのIDEで、asmも見れる
- releaseビルドすると、asmを追えないくらい最適化されるので、debugビルドが良い
- asmが元のソースの何行目かが出力できる
RxRust?
mozamimyさん
- Brain F**k(BF)をRustで実装
- BFは、言語を学ぶ上で良い題材
syu_creamさん
- マルチスレッドからで、1つのVectorにpushする例をC++とRustで比較
- C++のコンテナはスレッドセーフでないので、ロックが必要
- C++11以降はthreadやmutexが標準搭載らしい
- Rustだと、C++と同じコードではまずコンパイル通らない★
- mutexが必要。追加して初めてコンパイル通る
- ただ、Rustでもデッドロックは起こる。注意は必要
shunji_konishiさん
- CODEPREPというサービスを提供している
- Webベースの、言語やフレームワークのTutorialなど
- コードを穴埋めして動かしながら学べる
- Rustの教材も3つある
- コンパイルエラーが起こることを確認し、その解説、という感じの教材
moppさん
自作OSから入門するRust
Rustでデザパタ書いた人
- OSをCで書いてるけど、つらい
- 名前空間、エラー処理、無名関数、抽象化など機能不足
- RustのSafeに触れると、今までのCが以下にunsafeかわかる
- C経験者がいきなりRustを学習するのはハードルが高い
- すごいHaskell本が参考になる
- Rustの標準ライブラリを読むことも勉強になる
アメミヤさん
- RustをProductionに導入した話
- ログ収集デーモンを作った
- Rustはパフォーマンス凄い
- コンパイラかしこいので、バグを踏むことが少ない
- どうしてもgolangと比較される
- Rustでも、クロスビルド&静的リンクでバイナリ配布も簡単
今後
やっぱりコードを書かないと駄目だなと感じたのでコードを書きたい
ドッグさんやmoppiさんのようにデータ構造やデザインパターンをRustで書いてみるのが勉強になりそうだけど、自分にはもう少し簡単かつモチベーションが上がる課題の方が良さそう。
どう勉強してくと良いかから考えねば。たぶん色々手を広げすぎなんだろうな…
SonyのDNNフレームワーク"NNabla"を調べてみた
SonyがNNablaというDeepLearningフレームワークを公開しました*1。
興味があったので、ドキュメントとソースを読みながら、全体を眺めてみました。
よく見ると標準でBinaryConnectのサポートやBinary Neural Network(BNN)のサンプルがあったり、興味深いです。
NNablaのコンセプト
公式サイトに記載のあるコア・コンセプトのようなもの
- Write less do more
- コードを直感的に短く書ける
- Dynamic computation graph support
- ChainerやPytorchのような動的なモデル構築
- Run anywhere
- 色々なプラットフォームで動くように開発
- Device ready
- ライブラリのコアはC++11で書かれているので、組み込み機器にも乗せられる
- Easy to add a new function
- 少ないコーディングで機能追加できるよう、うまいこと設計してる
- Multi-target device acceleration as plugin
NNablaを使っているプロダクト
NNablaは、ソニーグループのプロダクトでも使われていると記載がある
- Real Estate Price Estimate
- ソニー不動産株式会社の不動産の成約価格推定でNNablaをつかってる
- https://sony-fudosan.com/tech/ret1.html
- Gesture Sensitivity
- Image Recognition (Handwriting Symbol Recognition)
- 最近話題のSony Digital Paper “DPT-RP1"でも使われている
- 手書きしたマークの検索でNNablaを使用しているぽい
- http://www.sony.jp/digital-paper/products/DPT-RP1/feature_1.html#L2_170
Xperia EarとDPT-RP1は、想像だけどデバイス側でNNablaが動いてるんだろうな。
すでに組み込み製品でも実用的に動いているなら、ライブラリを導入する側として安心。
そしてDPT-RP1ほしい…
ドキュメントとTutorial
ドキュメントが丁寧な印象。大企業っぽい(?)
特に入門向けのTutorialやサンプルコードは以下
- Tutorial
- https://nnabla.readthedocs.io/en/latest/python/tutorial.html
- 動かしながらNNablaの機能を一巡りできるNotebookになってる
- Notebookのパスは、 nnabla/tutorial
- 私のCPU OnlyなmacBook(中身はubuntu)でもサクサク動いた
- Example
- https://github.com/sony/nnabla/tree/master/examples
- Binary Neural NetworkでMNISTを認識するサンプルもある。あとで詳しく読もう
その他、まとめきれなかったこと
眠いのでまた後日ブログ書きますが、特に印象に残ったことは以下。
- Binary Neural Networkの部品を標準でサポートしてるってアツい
- VariableやFunctionなど、概念がChainerに似ていて私はとっつきやすい
- 記述量少ないけど、抽象化されすぎてなくて適度な印象
- ドキュメントが丁寧な印象
- コアはC++11で書かれていて、CythonでラップしてPython APIを提供している
- DataIterator
- 静的 or 動的なモデル構築両方に対応
- 基本静的で、動的に構築する場合は、Auto-forward modeを有効にするっぽい。
- グローバルな状態を管理して、ごにょごにょしてる
- 名前空間
- LoggerモジュールやMonitorモジュールが地味に嬉しい。Utility系もそこそこ充実
- Extensions
- パラメータなどグローバルに状態をもつものをいるので、注意
今後
やってみたいこと、しらべたいこと
アドテク x golang勉強会 -オレシカナイトvol.2- に参加してきました
アドテク x golang勉強会 -オレシカナイトvol.2- に参加してきました
サイバーエージェントが運営するメディアの広告部門である「MDH」のエンジニアが、新規技術に挑戦するにあたって踏んできた地雷を皆様に共有する勉強会です。前回に続き第2回目の開催となります。 今回はMDH内でのGO言語を利用した話を中心に、これまでの開発における体験や失敗についてお話しします。
最近は、組み込みシステムをC言語で書くのが辛いので、現実逃避気味にGoとかRustの勉強をしています。
Goは、プログラミング言語Goをざっと読んで、Webの記事を読んで、ちょっぴり手を動かしたレベルの初心者なのですが、今回のイベントではGoを使う現場の声を聞けるとのことで面白そうだと思い参加してきました。
以下、各セッションについてのメモ書きとコメント
1年目でgolangとscalaを触った話
大江 善渡氏
- Scalaは、プログラマの習得度によって書くコードが違う
- Scalaは、新規導入する上では学習コストが高く、言語仕様に悩むこと多い
- 一方Goは、習得が簡単。書き方が統一できる
- Goは、乱用するとコードが複雑になるジェネリクス、継承、例外がない
- 既存コードを読みやすい
- 大規模なPJでシンプルさを保てる★
DSPでGolangの屍を超えた話 (パフォーマンスチューニングとか)
片田 雄樹氏
DSPって広告配信のプラットフォーム(Demand-Side Platform)のことらしいです。プロセッサではなく…
- 流行ってるしいいんじゃね、と採用してみたがパフォーマンスを出すのに苦労したとの話
- ライブラリの実装によって遅いものもある
- Goや他の人が作ったライブラリも読みやすいので、ちゃんと中身みて使うと良い
- チューニングには、プロファイルをとるのが大事
- プロファイラはpprofが良い
- 対話型で、コマンド打ちながら分析できる
- goroutine生成のオーバーヘッドが問題になるケースもある
Java、scalaをやってきてgoに思うこと
森 拓真氏
(Scalaをかじっていたお陰で話についていけた)
- Goで末尾再起最適化はされるのか、検証してみた
- 呼び出し元をスタックに積む必要がないアルゴリズムは、末尾再帰最適化できる
- Goのメーリングリストでは、「Goは一部のケースで末尾再帰最適化できる」とのこと
再帰で書きたいケースは、Goではどう書けばよいのだろう??
マイクロサービスのためのフレームワークgoaのご紹介
渋江 一晃氏
WebAPIは作ったことがないので、そういうもんなのか〜っていう感じで話を聞いていた
WebAPI書く人にとっては便利なのだろう
- WebAPIは、APIドキュメントや値のバリデーションなど、IF周りの作業が色々大変
- DSLからコード生成することで、それを簡単に
- Goで書くDSLだった。
- APIの設計から実装の推奨開発フローがあり、それに従うと効率よくできるよという話
確かに、以下をみてみると、APIの仕様とドキュメントを一箇所にかけていて、ここからコードが生成できるのは便利そうだなと思った
https://goa.design/learn/guide/
公式ドキュメントも日本語化されており、わかりやすい
Goにおけるテスト可能な設計 - Javaとの比較
大澤 翔吾氏
アツい方だった。
本発表であったユニットテストで抱える課題は組み込み業界でも同様(より悪質かも)で、発表内容に共感できた
名言が結構あって、私も会社で使わせてもらおうと思った。
簡単なサンプルコードをJavaとGoで書いて、それぞれでテストをどう書くか比較しながら説明。
サンプルコードは、公開
https://github.com/shogo-osawa/oreshikanight-test-example-go
- Goがシンプルさを実現するために何を犠牲にしたか
- テストの依存性の分離をしないと、別モジュールが壊れても自モジュールが壊れて原因特定が難しくなる
- 再現性の担保ができないテストは、誰も信用しない
- 依存性の注入 = 外からスタブを入れられること
- 自動生成など使って、スタブを1行くらいで書けると生産性が高い
- 逆にできないと、テストケースが増えたときに辛い
- Javaのリフレクションで、テストしやすい設計ができる
- C++でもモックの実行時生成とかできるぽい、
- Goはmockgenが使える
- mockgenが生成したモックをファクトリ関数にいれて、依存性を注入したオブジェクトをインスタンスする
- Goは機能が少ないぶん、ルールが多い
- テストにおいても、設計で上手いことしないといけない
- ユニットテストは、メンテナンスコストと網羅性のトレードオフ
- どこをテストする?自明なところをテストするよりは、コケそうなところをテストすべき
- メンテする価値ある?など振り返ると良い。
所感
- Goを製品に取り入れていこう!という選択ができる環境がまず羨ましいと感じた
- 他の人が書いたコード読みやすいとか、誰が書いてもだいたい統一されるってのは大規模開発する上でメリットでは
- モダンな言語だと、ユニットテストも楽々かと思っていたが、意外と課題はあるようだ
- 今回の発表の趣旨が特に課題を共有する部分にあったため、なおさらそう感じたのだと思う
- Goは使うところを選べば、パフォーマンスを発揮できそうな言語だという印象が強くなった
- <-> なんでもかんでもGo、というよりは。
プログラミングGoの訳者あとがきを読むと、翻訳の柴田氏は組み込みプロジェクトでGoを導入しテスト駆動で開発していたとある。 面白そう…
第26回 全脳アーキテクチャ若手の会 勉強会 「バイナリーニューラルネットとハードウェアの関係」に参加してきました
第26回 全脳アーキテクチャ若手の会 勉強会 「バイナリーニューラルネットとハードウェアの関係」に参加してきました。
当日の資料は公開されています。
www.slideshare.net
バイナリーニューラルネット(以下、BNN)は私も興味があり調べていたので、発表を聞くのが楽しみでした。
発表者は、筑波大博士過程の田尻さん。
発表は、BNNの論文を、
- Binary Connect
- Binarized Neural Network
- XNOR-Net
の、投稿順に比較しながら説明するスタイルでした。
私は各論文についてそれぞれ概要は知っていましたが、より情報を整理できました。発表の構成が良かったですね。
それぞれ比較すると各論文のモチベーションやアルゴリズムの違いが明確にわかりますね。
私も論文を読むときは、参考にしようと思いました。
今回の発表と既存の知識から、BNNのポイントを私なりにまとめると、
- 学習時の特徴
- 認識時の特徴
- バイナリ化した重みのみ使う
- その他
- 論文によって、どこがバイナリ化されてるか異なる
- 重み
- 活性化関数の出力
- 論文によって、どこがバイナリ化されてるか異なる
- メリット
- リソース削減
- メモリ使用量が削減できるので、重みをオンチップにし易い
- 高速化
- RAMにアクセスせずオンチップでデータを流せるため
- 積和演算をバイナリ演算に置き換えられるため
- 小規模なデータなら、普通のNNに近い精度
- 情報量を減らすことが、正則化に繋がる
- リソース削減
- デメリット(課題)
- 大規模データでは認識精度がでない
- ImageNetで6割くらい
- 標準で対応したフレームワークがない??
- 大規模データでは認識精度がでない
★OSSで標準で対応した汎用のフレームワークあるんでしょうか?
質疑応答でも以下のようなやり取りがありましたが、
- 実用ではBNNより固定小数点やDSP活用の方が現実的では?
- ⇒ 二値化という概念のポテンシャルは高い
- ⇒ 学習方法の発達などで今後見込みがあると思う
HW化する上での実用化的な手法は、他にもありそう。
ただし、クラス数を絞れたり、メモリが少ない特定のユースケースには、BNNが向きそうだと思いました。
以下、メモ
それぞれ、論文の実装のリンクを貼っておきます
Binary Connect
学習方法
- 元々の重みをバイナリ化して、順伝播する
- 素の浮動小数点の重みを保持しておくことが重要
- 逆伝播時
- バイナリ化した重みの勾配を計算
- 更新対象は、保持しておいた素の重み
- クリップすることで、実数重みの発散を防ぐ
- 推論時はバイナリ化した重みだけ持てば良い
- バイナリ化には正則化の効果がある
- 表現力を落とし、過学習を防げる
- 学習
- データセット毎にモデル構成が異なる
Permutation-invariant MNIST
- 回転とか拡大とか、事前の画像処理を禁止したデータセット
Binarized Neural Network
活性もバイナリ化。これもBengioグループの論文。
- 活性をバイナリ化は課題があったが、この論文ではそれを解決
- 活性をバイナリ化する課題は、勾配の消失
- ⇒ HTanhなら、バイナリ化できるし、勾配も消えない
- 前の層からの入力は活性化関数でバイナリ化されてる
- 順伝播の1層目は特別扱い。ここはバイナリでない8bit固定小数点。
学習
- BinaryConnect同様、実数重みを更新
Shift-basedなBatch Normalization
- 浮動小数点でのBatch Normalizationが遅い問題を解決
- 分散と正規化の計算をシフト演算に置き換え
- 論文では確率的バイナリ化でのみ使っていて、決定的バイナリ化では普通のBNを使っている
- シフト演算にすることで、2のべき乗しか表現できないが、精度は落ちるのか?
- ⇒論文に数値の記載はないが、精度は落ちないぽい
その他
- 積和演算が並列のビット演算に置き換えられる。p.49の図わかりやすい
- 論文でも、Clippingしないと精度落ちることは言っている
XNOR-Net
GitHub - allenai/XNOR-Net: ImageNet classification using binary Convolutional Neural Networks
YOLOの人の論文
- 今までのBNNの論文は10クラス程度の小規模なデータでし か検証していない
- 今回は、ImageNetの1000クラス認識で検証し、top-5で69.2%まで達成
バイナリ化の緩和 スケーリングがポイント
- 普通のCNNとバイナリCNNではフィルタ後の値差が大きすぎる
- バイナリ化を維持したまま、もとの畳込みに近づけたい
- ⇒ スケーリングで差を埋めよう
スケーリング
レイヤの順番をBianryConvに合わせて変えてるぽい
論文での評価の仕方は微妙かも?理想値だったり。
FINN
- 要求速度から適切な並列度を計算する
- Batch Nomalization正規化の近似が正答率向上にきいてる
- 実験シナリオは、maxとfix
- fixは9000FPS
- 後続の論文あり
- FINNはPaddingを実装していなかったので、そこを改良した
その他
発表者の田尻さんは、現在筑波大学の博士課程で衛生リモートセンシングの研究をされてるとのこと。
夢があっていいですね!
次回の若手の会は、7/20(たしか)に強化学習のN本ノックやってみたという内容で開催されるようです。
強化学習は、私も興味があるので参加したいです。
ブログ書こう書こうと思っていたのですが、先週はバタバタしていて書けませんでした…
今度はインプットが新しいうちにブログに書きたいです