- 投稿日:2019-05-02T22:32:33+09:00
TensorRT 5.0.6とJETSON NANOで推論の高速化
TensorRT 5.0.6とJETSON NANOで推論の高速化
これは何?
GPU上でのDeep Learningの推論処理の高速化に用いられるライブラリTensorRTを用いて、NVIDIA Jetson Nano上での推論の高速化を図る。画像認識を対象とし、PyTorchを用いた場合との推論速度を比較する。
動機
- Jetson Nanoを手に入れたので、何か試したい
- 以前、所属している会社のアドベントカレンダーで、TensorRT 5.0という記事を書かせていただきました。この際、TensorRTがうまいこと使えずONNXのモデルを読み込むのを断念したりしたのですが、その後TensorRTもマイナーアップデートが行われたようなので、使い勝手を確認したい
- Jetson上でもPython APIが使えるようになった
- ONNXモデルを読み込む際の対応レイヤが増えた?
モデルの変換の流れ
Pytorchを用いて画像認識もDeep Learningモデルを学習することを想定して、PytorchのモデルをTensorRTを用いて推論できるように変換していきます。ただしONNX, TensorRTで対応していないレイヤがある場合など、以降の変換がうまくいかないモデルもあります。
- PyTorch : 人気上昇中のDeep Learningフレームワーク。Define by runで動的な計算グラフも簡単に書ける。
- ONNX: Deep Learningフレームワーク間でモデルの交換するための共通フォーマットを目指している
- TensorRT: GPUベンダであるNVIDIAが提供している、GPUでDeep Learningの推論を高速化するためのライブラリ。浮動小数点精度を下げる(FP32->FP16->int8)、グラフの構造を解析してGPUでの処理を高速化するなどを行ってくれる。
環境: NVIDIA Jetson Nano
$99で買える、GPU搭載のエッジ向けのシングルボードコンピュータ。ラズパイみたいに小さく手軽なデバイスで、簡単にDeep Learningの推論ができます。
公式のセットアップ手順に従ってセットアップします。ソフトウェアは以下の通り。
- Jetpack 4.2
- python 3.6
- TensorRT==5.0.6
- Pytorch==1.1.0
- torchvision==0.2.1
- こちらの情報から、0.2.2だとvgg, alexnetのonnx化に失敗するようなので、0.2.1を利用しています。
その他の依存パッケージは以下の手順でインストール
sudo apt-get install ptyhon3-dev # dependencies for Onnx sudo apt-get install protobuf-compiler libprotoc-dev # dependencies for Pillow sudo apt-get install libtiff5-dev libjpeg8-dev zlib1g-dev \ libfreetype6-dev liblcms2-dev libwebp-dev libharfbuzz-dev libfribidi-dev \ tcl8.6-dev tk8.6-dev python-tk vim ~/.bashrc # 最後に以下を追加 # export PATH=${PATH}:/usr/local/cuda-10.0/bin # export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/cuda-10.0/lib64 source ~/.bashrc sudo apt install python3-pip pip3 install --user -r /usr/src/tensorrt/samples/python/yolov3_onnx/requirements.txt # pytorchのインストール(https://devtalk.nvidia.com/default/topic/1049071/pytorch-for-jetson-nano/) wget https://nvidia.box.com/shared/static/veo87trfaawj5pfwuqvhl6mzc5b55fbj.whl -O torch-1.1.0a0+b457266-cp36-cp36m-linux_aarch64.whl pip3 install --user numpy torch-1.1.0a0+b457266-cp36-cp36m-linux_aarch64.whl sudo apt-get install -y git git clone https://github.com/pytorch/vision cd vision git checkout v0.2.1 pip3 install --user -e ./実験
以下の順番でJetson NANO上で作業します。
- PyTorchでのモデルの読み込みおよび速度計測
- ONNX形式でのモデルの保存
- ONNXモデルのTensorRTへの読み込み、保存
- TensorRTを用いた推論および速度計測
1. PyTorchでのモデルの読み込みおよび速度計測
PyTorch上で提供されている学習済みモデルを用います。自作の学習モデルがある場合は、以降のmodelを自作のモデルに置き換えてください。
今回、テスト用画像はこちらから猫の画像をお借りしました。
step1.pyimport time import torch from torch.autograd import Variable from torchvision import models, transforms from PIL import Image device = torch.device('cuda') # 学習済みモデルの読み込み resnet18 = models.resnet18(pretrained=True) resnet18 = resnet18.to(device) resnet18.eval() # 画像データの読み込みおよび前処理 image = Image.open('cat.jpg') transformation = transforms.Compose( [ transforms.Resize([224, 224]), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ] ) image_tensor = transformation(image).float() image_tensor = image_tensor.unsqueeze_(0) input_var = Variable(image_tensor) # 推論用関数 def predict_image(model, var): input = var.to(device) start = time.time() output = model(input) forward_time = time.time()-start return output.cpu().data.numpy(), forward_time #jitを使う場合はコメントアウト #resnet18 = torch.jit.trace(resnet18, torch.rand(1, 3, 224, 224).to(device)) with open('pytorch.csv', 'w') as fout: for idx in range(20): start = time.time() result, forward_time = predict_image(resnet18, input_var) fout.write(','.join(map(str, [idx, forward_time, time.time()-start]))+'\n') print('Prediction: {}(Score: {})'.format(result.argmax(), result.max()))推論の結果、カテゴリIDは281(スコア9.820850372314453)が得られました。カテゴリ281は'tabby, tabby cat'なので、妥当な推論結果です。処理速度はtextに書き出しているので、あとで集計します。
2. ONNX形式でのモデルの保存
以下の通り実行することで、
resnet18.onnx
というモデルファイルが保存されます。input_namesおよびoutput_namesはinputの変数とoutputの変数がわかりやすいよう、可読性を上げるために任意でつけます。また、クラスを継承してforward関数を再定義していますが、これはforward関数のviewの変換後のshapeをハードコードするためです。元々はxのバッチサイズを取ってきて、(x.size(0), -1)の形状にflattenする処理になっているのですが、バッチサイズを取ってくるx.sizeの処理が入ると、onnxに変換した際にgather layerを使う形で保存されてしまいます。tensorrt5.0.6だとGatherがサポートされていないため、変更が必要となりました。
step2.pyimport torch from torch.autograd import Variable from torchvision import models from PIL import Image class CustomModel(models.ResNet): def forward(self, x): x = self.conv1(x) x = self.bn1(x) x = self.relu(x) x = self.maxpool(x) x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) x = self.layer4(x) x = self.avgpool(x) x = x.view(-1, 512) # batch_size, channel x = self.fc(x) return x device = torch.device('cuda') # 学習済みモデルの読み込み resnet18 = CustomModel(models.resnet.BasicBlock, [2, 2, 2, 2]) resnet18.load_state_dict( torch.utils.model_zoo.load_url( models.resnet.model_urls['resnet18'] ) ) resnet18 = resnet18.to(device) # 画像データの読み込みおよび前処理 dummy_input = torch.randn(1, 3, 224, 224, device=device) input_names = [ "actual_input_1" ] output_names = [ "output1" ] torch.onnx.export( resnet18, dummy_input, "resnet18.onnx", verbose=True, input_names=input_names, output_names=output_names )3. ONNXモデルのTensorRTへの読み込み、保存
以下の手順に従って、ONNXのモデルをTensorRTのengineに変換し、保存します。
import tensorrt as trt TRT_LOGGER = trt.Logger(trt.Logger.INFO) def build_engine(onnx_model_path, engine_path): with trt.Builder(TRT_LOGGER) as builder, builder.create_network() as network, trt.OnnxParser(network, TRT_LOGGER) as parser: builder.max_workspace_size = 1 << 30 # 1GB builder.max_batch_size = 1 #fp16を用いる場合はコメントを外す #builder.fp16_mode = True with open(onnx_model_path, 'rb') as model: parser.parse(model.read()) if parser.num_errors > 0: print(parser.get_error(0).desc()) raise Exception engine = builder.build_cuda_engine(network) with open(engine_path, "wb") as f: f.write(engine.serialize()) build_engine('resnet18.onnx', 'resnet18.engine')ここで保存したエンジンをplan fileと呼びます。上記build処理は処理に時間がかかるため、通常推論をする際には、毎回buildをするのではなく、このplan fileを読み込んでengineとして使います。plan fileはエンジンのbuildを行ったGPUおよびTensorRTのバージョンに特化しているので、他のGPUを搭載したマシン上で使いたい場合などは、当該マシン上で再度engineを作成し、plan fileを保存する必要があります。
例)Jetson Nano -> Tesla V100のようにplan fileを使いまわすことはできません。
また、fp16_modeをオンにすると、buildの時間がめちゃくちゃ伸びます。
4. TensorRTを用いた推論および速度計測
plan fileからエンジンを読み込み、推論を行います。
step4.pyimport time import tensorrt as trt TRT_LOGGER = trt.Logger(trt.Logger.INFO) import pycuda.driver as cuda import pycuda.autoinit def load_engine(engine_path): with open(engine_path, "rb") as f, trt.Runtime(TRT_LOGGER) as runtime: return runtime.deserialize_cuda_engine(f.read()) # 後述するcommonモジュールの推論用の関数を、推論時間が測定できるように修正 def do_inference(context, bindings, inputs, outputs, stream, batch_size=1): [cuda.memcpy_htod_async(inp.device, inp.host, stream) for inp in inputs] start = time.time() context.execute_async( batch_size=batch_size, bindings=bindings, stream_handle=stream.handle ) infer_time = time.time() - start [cuda.memcpy_dtoh_async(out.host, out.device, stream) for out in outputs] stream.synchronize() return [out.host for out in outputs], infer_time import sys sys.path.append('/usr/src/tensorrt/samples/python/') #tensorrtをインストールすると手に入るpythonのサンプルコード集 import common #推論用の共通関数 from PIL import Image from torchvision import transforms image = Image.open('cat.jpg') transformation = transforms.Compose( [ transforms.Resize([224, 224]), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ] ) image_tensor = transformation(image).float() image = image_tensor.numpy() inference_times = [] with load_engine('resnet18.engine') as engine, engine.create_execution_context() as context: inputs, outputs, bindings, stream = common.allocate_buffers(engine) inputs[0].host = image for idx in range(20): start = time.time() trt_outputs, infer_time = do_inference( context, bindings=bindings, inputs=inputs, outputs=outputs, stream=stream ) inference_times.append(list(map(str, [idx, infer_time, time.time()-start]))) trt_outputs = trt_outputs[0].reshape((1, 1000)) print('Prediction: {}(Score: {})'.format(trt_outputs.argmax(), trt_outputs.max())) with open('trt.csv', 'w') as fout: for l in inference_times: fout.write(','.join(l)+'\n')推論の結果は、カテゴリIDは281(スコア9.820852279663086)が得られました。スコアが若干変わっていますが、pytorchのサイトほぼ同じ結果が得られました。
fp16での推論も試したところ、カテゴリIDは281(スコア9.8203125)となりました。こちらもほぼ同じ結果となったといえると思います。
処理速度比較
推論時間
pytorchでは、forwardにかかる時間は、jitなしでは20ms、jitありでは15ms程度でしたが、tensorrtを使うことで、4-6ms程度まで減らすことができました(fp16にした際に速くなっていない原因は不明)。
トータルでの処理時間
一方で、トータルでの処理時間はで比べると、pytorchのjitとtrt_fp32で大幅な速度向上は見られませんでした。入力・出力をCPUのメモリ空間-GPUのメモリ空間で移動する部分のオーバーヘッドが大きいのではないかと思います(fp16で分散がとても大きくなっているのは、fp32->fp16への変換処理も入るから?)。画像のデコードまでcudaを用いて行う、後処理もGPU側で行うと言った方式にすることで、CPU-GPU間でのコピーを減らす事が重要になってきそうです。
結論
PyTorchのモデルをTensorRT化し、速度向上することを確認できました。これで自作のモデルのTensorRT化を試せそうです。
- 投稿日:2019-05-02T16:52:41+09:00
ElixirでDeep Learningに再挑戦 (8) 並列動作
はじめに
行列計算の一部に並列動作を取り入れました。
ElixirでDeep Learningに再挑戦 (1)
|> ElixirでDeep Learningに再挑戦 (2)
|> ElixirでDeep Learningに再挑戦 (3)
|> ElixirでDeep Learningに再挑戦 (4) 誤差逆伝播法
|> ElixirでDeep Learningに再挑戦 (5) 畳み込み
|> ElixirでDeep Learningに再挑戦 (6)勾配確認 バックプロパゲーション完全動作
|> ElixirでDeep Learningに再挑戦 (7)MNIST行列積の並列動作
MNISTデータは入力層は784次元あります。784次元の行ベクトルと784*50の行列積は計算時間がかかっているものと予想し、この部分を並列動作に変更しました。spawnを使う方式です。以前、試してみた行列積の並列動作を改造して行ベクトルと行列との積に並列動作させるようにしたものです。
参考 https://qiita.com/sym_num/items/6517bba0b3f18bc34f14
行ベクトルが100次元以上の場合に10分割して並列動作するようにしました。
結果
(7)のMNISTでの学習を100回繰り返すことを比較しました。
通常
CPU稼働率38%前後7 7 "time: 394588266 micro second" "-------------" true並列
CPU稼働率45%4 4 "time: 373573155 micro second" "-------------" true考察
21秒ほど、時間短縮になっています。MNISTの学習ではデータをファイルから読み込む動作などに多くの時間がとられているので大きな差にはなりませんでした。
CPUの稼働率がそれほど上昇しないことからみて、プロセスに渡す計算量が小さいのだろうと思います。計算量の適切な分割ができていないのだろうと思います。
まだまだ改善の余地があります。
改良案
1つのデータについて勾配計算をすることを1つのプロセスに割り当てたら計算量はちょうど良くなるように思います。これなら数百プロセスが一斉に動き出すことになるはずです。それならマルチコアのメリットが活きるように予想しています。
追記 この方法ではだめですね。勾配は直前のネットワークに依存しています。却下追記 バッチ処理による行列計算の手法が既にありました。文献1だとp50から、文献2だとp151からです。これによりElixirの並列機能が十分に活用できると思います。
参考文献
1「深層学習」 岡谷貴之 著
2「ゼロから作る Deep Learning」 斉藤 康毅 著
3「Excelでわかるディープラーニング 超入門」 涌井良幸 涌井貞美 著全コード
GitHubにおいてあります。
https://github.com/sasagawa888/DeepLearning
- 投稿日:2019-05-02T13:54:52+09:00
ChainerでOctave Convolutionを実装してみた
動機
トレンドに上がっていたOctave Convolutionがなんだかよさそうだったのですが、Keras実装だったのでChainerしか知らない僕にはちょっと不便です。なので自分で実装してみることにしました。
「間違ってるよ」とか「ここはこう書くといいよ」的なアドバイスよろしくお願いします。実装
from chainer import Chain import chainer.functions as F import chainer.links as L import numpy as np class OctConv(Chain): def __init__(self, in_ch, out_ch, ksize, stride, pad, alpha0=0.25, alpha1=0.25): super().__init__() with self.init_scope(): self.h2h = L.Convolution2D(int(in_ch * (1 - alpha0)), int(out_ch * (1 - alpha1)), ksize, stride, pad) self.h2l = L.Convolution2D(int(in_ch * (1 - alpha0)), int(out_ch * alpha1), ksize, stride, pad) self.l2h = L.Convolution2D(int(in_ch * alpha0), int(out_ch * (1 - alpha1)), ksize, stride, pad) self.l2l = L.Convolution2D(int(in_ch * alpha0), int(out_ch * alpha1), ksize, stride, pad) def __call__(self, high, low): _, _, H, W = high.shape h2h = self.h2h(high) h2l = self.h2l(F.average_pooling_2d(high, ksize=2)) l2h = F.unpooling_2d(self.l2h(low), ksize=2, outsize=(H, W)) l2l = self.l2l(low) h = h2h + l2h l = h2l + l2l return h, lまだこのコードで実験していないので、後で追記します。
- 投稿日:2019-05-02T12:43:15+09:00
【EC2】Deep Learning AMI (Ubuntu) Version 22.0のインスタンスを立てる際の注意点【ボリューム】
はじめに
EC2のDeep Learning AMI (Ubuntu)を使って開発をすることが多くなり、詰まったところをメモ程度に残します。
デフォルトの75GBでは絶対に容量が足りない
Filesystem Type 1K-blocks Used Available Use% Mounted on udev devtmpfs 499264 0 499264 0% /dev tmpfs tmpfs 101444 3324 98120 4% /run /dev/xvda1 ext4 76171508 75792680 362444 100% / tmpfs tmpfs 507212 0 507212 0% /dev/shm tmpfs tmpfs 5120 0 5120 0% /run/lock tmpfs tmpfs 507212 0 507212 0% /sys/fs/cgroup /dev/loop1 squashfs 18304 18304 0 100% /snap/amazon-ssm-agent/1068 /dev/loop0 squashfs 16896 16896 0 100% /snap/amazon-ssm-agent/784 /dev/loop2 squashfs 93312 93312 0 100% /snap/core/6531 /dev/loop3 squashfs 91648 91648 0 100% /snap/core/6818 /dev/loop4 squashfs 89984 89984 0 100% /snap/core/5742 tmpfs tmpfs 101444 0 101444 0% /run/user/1000/dev/xvda1がメインのボリュームですが、インスタンスを建てたときにすでに容量がほぼいっぱいになっています。
$ conda create -n new_env --clone pytorch_p36などで新しい仮想環境をつくっただけでも途中で容量がいっぱいになります。
インスタンスを立てる際のボリューム設定
ここがデフォルトでは75GBになっているので、適宜必要な値を入力します。自分は100GBで作成しました。
インスタンスを建ててしまった後の対応
サイドバーのボリュームを選択後にインスタンスを選び、アクションからボリュームの変更でサイズを変更します。$ lsblkで確認しても反映されていないことが確認されます。
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT xvda 202:0 0 100G 0 disk └─xvda1 202:1 0 75G 0 part / loop0 7:0 0 16.5M 1 loop /snap/amazon-ssm-agent/784 loop1 7:1 0 17.9M 1 loop /snap/amazon-ssm-agent/1068 loop2 7:2 0 91.1M 1 loop /snap/core/6531 loop3 7:3 0 89.4M 1 loop /snap/core/6818 loop4 7:4 0 87.9M 1 loop /snap/core/5742以下のコマンドを打ちます。
$ sudo growpart /dev/xvda 1するとボリュームのパーティションが変更されます。
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT xvda 202:0 0 100G 0 disk └─xvda1 202:1 0 100G 0 part / loop0 7:0 0 16.5M 1 loop /snap/amazon-ssm-agent/784 loop1 7:1 0 17.9M 1 loop /snap/amazon-ssm-agent/1068 loop2 7:2 0 91.1M 1 loop /snap/core/6531 loop3 7:3 0 89.4M 1 loop /snap/core/6818 loop4 7:4 0 87.9M 1 loop /snap/core/5742次にファイルシステムに拡張になります。
$ df -hで確認すると、まだ75GBのままです。
Filesystem Size Used Avail Use% Mounted on udev 488M 0 488M 0% /dev tmpfs 100M 3.3M 96M 4% /run /dev/xvda1 73G 73G 0 100% / tmpfs 496M 0 496M 0% /dev/shm tmpfs 5.0M 0 5.0M 0% /run/lock tmpfs 496M 0 496M 0% /sys/fs/cgroup /dev/loop1 18M 18M 0 100% /snap/amazon-ssm-agent/1068 /dev/loop0 17M 17M 0 100% /snap/amazon-ssm-agent/784 /dev/loop2 92M 92M 0 100% /snap/core/6531 /dev/loop3 90M 90M 0 100% /snap/core/6818 /dev/loop4 88M 88M 0 100% /snap/core/5742 tmpfs 100M 0 100M 0% /run/user/1000そこで以下のコマンドでファイルの拡張を行います。
$ sudo resize2fs /dev/xvda1もう一度確認すると100GBになっているはずです。
Filesystem Size Used Avail Use% Mounted on udev 488M 0 488M 0% /dev tmpfs 100M 3.3M 96M 4% /run /dev/xvda1 97G 73G 25G 75% / tmpfs 496M 0 496M 0% /dev/shm tmpfs 5.0M 0 5.0M 0% /run/lock tmpfs 496M 0 496M 0% /sys/fs/cgroup /dev/loop1 18M 18M 0 100% /snap/amazon-ssm-agent/1068 /dev/loop0 17M 17M 0 100% /snap/amazon-ssm-agent/784 /dev/loop2 92M 92M 0 100% /snap/core/6531 /dev/loop3 90M 90M 0 100% /snap/core/6818 /dev/loop4 88M 88M 0 100% /snap/core/5742 tmpfs 100M 0 100M 0% /run/user/1000これで仮想環境作っても容量不足になりません。
- 投稿日:2019-05-02T12:21:08+09:00
ElixirでDeep Learningに再挑戦 (7)MNIST
はじめに
前回(6)で12画素の小規模なデータなら学習するところまでになりました。今度はMNISTデータで計算実験をしました。
ElixirでDeep Learningに再挑戦 (1)
|> ElixirでDeep Learningに再挑戦 (2)
|> ElixirでDeep Learningに再挑戦 (3)
|> ElixirでDeep Learningに再挑戦 (4) 誤差逆伝播法
|> ElixirでDeep Learningに再挑戦 (5) 畳み込み
|> ElixirでDeep Learningに再挑戦 (6)勾配確認 バックプロパゲーション完全動作設定
バックプロパゲーションの実装は前回のままです。当初、初期値の設定がまずくてなかなか損失が小さくなりませんでした。文献2を参考にして初期値を次のように設定したところ、まあまあのところまで損失を小さくさせることができました。
784 -> 50 -> 100 -> 10 隠れ層、2つのネットワーク
初期値 Box-Muller法によりガウス分布の乱数を生成、これに0.1倍したものを重み行列の初期値としています。
活性化関数 シグモイド関数
学習率 入力層側より1.2、1.1、1.0
損失関数 2乗和
MNISTの訓練データから100個を乱数で選択し、これを200回学習させました。
その後testデータから10個を乱数で選択し、推論した値と、正しい値とを表示しています。
iex(1)> DL.mnist(200) count error 途中略 3 0.008468322764363476 2 0.008419139167818454 1 0.00837050909066567 predict correct 9 9 6 6 2 2 9 8 9 7 0 0 9 4 3 3 8 8 4 4 true10個のうち7個が正解しています。
1000回の学習も動作しました。損失は十分に小さくなったのですが、推論の正解確率はまだまだです。Elixirは十分に耐えています。
3 0.0013986892976679487 2 0.0013972051584290142 1 0.0013957240954059055 predict correct 6 6 7 5 7 7 0 0 7 5 8 8 8 0 4 4 4 4 2 2 trueElixirでの所要時間など
Elixirは関数型言語でありイミュータブルです。破壊的代入が使えません。そこで勾配から重み行列を更新する都度新たに行列を生成しています。このため当初、メモリ不足になるのではないか? あるいはGC(ガベージコレクション)が多発するのではないかと懸念していました。Elixirは十分に頑健でありその心配はありませんでした。200回の学習で10分ちょっとくらいです。
現在、行列積の計算は通常の逐次計算のものを使っています。入力層では784次元の行ベクトルと784*50の行列との積を計算するために時間がかかっていると思います。ここを並列処理にするとおよそ3.2倍程度高速化する見込みです。pmatrixという並列積の実験的なモジュールを作りicore5のマシンでおよそ3.2倍ほど性能が向上することを確認しています。
どうやらElixirでもDLは実用的に使えることがわかってきました。
主要部分のコード
def mnist(n) do image = MNIST.train_image() label = MNIST.train_label() network = Test.init_network1() seq = rand_sequence(100,length(image)) IO.puts("count error") network1 = batch(network,image,label,100,n,seq) test_image = MNIST.test_image() test_label = MNIST.test_label() seq1 = rand_sequence(10,length(test_image)) IO.puts("predict correct") mnist1(network1,test_image,test_label,0,seq1) end # print predict of test data def mnist1(_,_,_,_,[]) do true end def mnist1(network,[image|irest],[label|lrest],n,[n|srest]) do print(MNIST.onehot_to_num(forward(network,MNIST.normalize(image,255)))) IO.write(" ") print(label) newline() mnist1(network,irest,lrest,n+1,srest) end def mnist1(network,[_|irest],[_|lrest],n,[s|srest]) do mnist1(network,irest,lrest,n+1,[s|srest]) end def batch(network,_,_,_,0,_) do network end def batch(network,image,label,n,c,seq) do {network1,error} = batch1(network,image,label,0,seq,0) print(c) IO.write(" ") print(error) newline() batch(network1,image,label,n,c-1,seq) end def batch1(network,_,_,_,[],error) do {network,error} end def batch1(network,[image|irest],[label|lrest],n,[n|srest],error) do x = MNIST.normalize(image,255) t = MNIST.to_onehot(label) network1 = gradient(network,x,t) network2 = learning(network,network1) x1 = forward(network2,x) error1 = mean_square(x1,t) batch1(network2,irest,lrest,n+1,srest,error1+error) end def batch1(network,[_|irest],[_|lrest],n,[seq|srest],error) do batch1(network,irest,lrest,n+1,[seq|srest],error) end展望
並列計算による学習時間短縮に取り組む予定です。
参考文献
1「深層学習」 岡谷貴之 著
2「ゼロから作る Deep Learning」 斉藤 康毅 著
3「Excelでわかるディープラーニング 超入門」 涌井良幸 涌井貞美 著全コード
GitHubにおいてあります。
https://github.com/sasagawa888/DeepLearning
- 投稿日:2019-05-02T10:27:10+09:00
Kerasを勉強した後にPyTorchを勉強して躓いたこと
概要
DeppLearningのフレームワークで最初にKerasを勉強した後に、Define by RunのPyTorch勉強してみて躓いたポイントをまとめてみる。
この記事の対象読者
Kerasの次にPyTorchを勉強してみようと思っている人。
はじめに
今回いくつか挙げている躓いたポイントはPyTorchに限らないものがある。またKerasといえばバックエンドはTensorFlowのものを指す。バックエンドがTensorFlowでない場合は話が当てはまらないものもあるので注意。
今回挙げたポイントは以下の5つ
1. Channel First
2. GPUへの転送
3. CrossEntropyがSoftmax+CrossEntropyになっている
4. CrossEntropyがone-hot-vectorに対応していない
5. 学習と評価を区別する以下、各ポイントの詳細について説明していく。
Channel First
PyTorchではモデルの入力と出力がChannel Firstの形式になっている。Channel Firstとは画像の次元の並びが(C, H, W)のようにChannelの次元が最初になっていること。
KerasではChannel Lastになっているため、(H, W, C)のようにChannelの次元が最後にくる。実際にモデルに入力するときは、バッチサイズも合わせた4次元で表現する必要があるため、
PyTorch:(N, C, H, W)
Keras:(N, H, W, C)
となる。記号の意味は
N:バッチサイズ
C:チャネル数
H:画像のHeight
W:画像のWidth画像を読み込む際は、OpenCVかPILを使用する場合が多いが、これらのモジュールはChannel Lastで画像を扱う仕様になっている。なので、PyTorchのモデルに入力する前に以下のコードのようにChannel Firstに変換する必要がある。
img = cv2.imread(img_path) img = img.transpose((2, 0, 1)) # H x W x C -> C x H x Wモデルの出力もChannel Firstなのでmatplotlibなどで表示したい場合はChannel Lastに変換してから表示する。
output = output.numpy() # tensor -> ndarray output = output.transpose(1, 2, 0) # C x H x W -> H x W x CGPUへの転送
KerasではGPUを使う場合、GPU側のメモリを意識することがなかったが、PyTorchではGPUを使用する場合、明示的に学習するパラメータや入力データをGPU側のメモリに転送しなければならない。
以下のコードではモデルと入力データをGPUに転送している。device = torch.device("cuda:0") # modelはnn.Moduleを継承したクラス model = model.to(device) # GPUへ転送 ・ ・ ・ for imgs, labels in train_loader: imgs, labels = imgs.to(device), labels.to(device) # GPUへ転送GPU上にあるデータCPUに転送したい場合も以下のようにコードを書く必要がある。
device = torch.device("cpu") model.to(device)CrossEntropyがSoftmax+CrossEntropyになっている
Kerasで多クラスの識別モデルを学習するときは、モデルの最終層でsoftmaxを実行してから
categorical_crossentropy
でロスを計算する流れになっている。
一方PyTorchではロス関数であるtorch.nn.CrossEntropy
の中でSoftmaxの計算も一緒に行っているので、モデルの最終層でSoftmaxは不要になる。たまにPyTorchのサンプルコードで最終層に
torch.nn.LogSoftmax
を置いて、ロス関数にtorch.nn.NLLLoss
を指定している場合がある。これは最終層を恒等関数にしてtorch.nn.CrossEntropy
を使っているのと同じになる。
つまり、
torch.nn.CrossEntropy
=torch.nn.LogSoftmax
+torch.nn.NLLLoss
という関係になっている。
torch.nn.LogSoftmax
は名前の通りSoftmaxの計算にLogをかぶせたものになっている。LogSoftmax=log(\frac{e^{xj}}{\sum_{i=1}^{n} e^{xi}})このLogはCrossEntropyの式にあるLogを持ってきているのだが、LogとSoftmaxを先に一緒に計算しておくことで、計算結果を安定させている。
なぜLog+Softmaxが計算的に安定するかは以下のページで解説されている。
Tricks of the Trade: LogSumExpちなみに
torch.nn.NLLLoss
はCrossEntropyのLogを抜いた他の計算を行っている。CrossEntropyがone-hot-vectorに対応していない
Kerasではロスを計算するときに、labelは
one-hot-vector
形式で渡す必要があるがPyTorchでは正解の値をそのまま渡す。例えば、3クラスの分類で正解が2番目のクラスの場合、Kerasでは
[0, 1, 0]
というリストをロス関数に渡すが、PyTorchでは2
という値を渡す。学習と評価を区別する
PyTorchでは、モデルを動作させるときに学習中なのか評価中なのかを明示的にコードで示す必要がある。なぜこれが必要なのかは理由が2つある。
1.学習中と評価中に挙動が変わるレイヤーがあるから
2.学習中には必要で評価中には不必要な計算があるから1は、DropOutやBatchNormalizationなどのことで、これらのレイヤーは学習中と評価中で動作が変わる。よって、コードでこれから動作するのが学習なのか評価なのかを知らせる必要がある。
具体的には以下のようなコードになる。# modelはnn.Moduleを継承したクラス model.train() # 学習モードに遷移 model.eval() # 評価モードに遷移2の不必要な計算とは計算グラフを作ることである。学習中は計算グラフを作って、誤差逆伝播法で誤差を計算グラフ上に伝播させて重みを更新する必要がある。しかし、学習以外の処理ではこの計算グラフの構築が不要になるので「計算グラフを作りません」とコードで示す必要がある。
具体的にはwith torch.no_grad()
を使う。model.eval() # 評価モードに遷移 with torch.no_grad(): # この中では計算グラフは作らない output = model(imgs)まとめ
そもそもDefine and RunとDefine by Runで根本の思想が違うのでこれまでに挙げてきたポイントの他に色々と大きな違いがあるのだが、今回は個人的にコードを書く上でついつい忘れがちなところを挙げてみた。
- 投稿日:2019-05-02T10:17:15+09:00
機械学習論文読みメモ_165
Tracking Emerges by Colorizing Videos
ラベルなしビデオを教師なし学習する事で、visual trackingを実現する手法を提案する。
時間方向の色情報の一貫性を利用する事で、参照フレームの色をコピーする事を通した
グレースケールビデオの色付けタスクをモデルに学習させる。
この学習を通して視覚的の領域のトラッキングをモデルは実現できるようになる。
このモデルはoptical flowよりも高性能を実現できる。[Gradient Acceleration in Activation Functions][https://arxiv.org/abs/1806.09783]
Dropoutはニューロン同士のco-adaptationを防ぐ事により
過剰適合を防ぐ効果があると考えられてきた。
本論ではDropoutの効果に別の理由つけを提案する。
それは、dropoutは非線形活性化関数のsaturation areaに
入力に対する反応をより押し込むような勾配のaccelerationを
行っている、というものである。
この仮説に基づき、saturation areaへの押し込みを加速させる
gradient acceleration in activation function (GAAF)を提案し、
その効果を確かめた。Guided evolutionary strategies: escaping the curse of dimensionality in random search
多くの機械学習における最適化でsurrogate gradientがtrue gradientの代わりに
用いられてきた。
本論では同様にしてrandom searchに関するsurrogate gradientを提案し、
gradient方向に張られる部分空間への探索を行う事で計算効率を改善する。
gradientは得られたサンプルに対してガウス分布でモデル化しながら計算を
行う。
- 投稿日:2019-05-02T09:36:14+09:00
機械学習論文読みメモ_164
Self-Imitation Learning
本論ではself-imitation learningを提案する。
この手法はエージェントの過去の良い決定を再現するよう学習を行う
off-policy actor-critic algorithmである。
本論の仮説である、より深い探索は過去の良い経験によって間接的に導かれる、
という考えの正しさを本論は示した。Evolving simple programs for playing Atari games
Cartesian Genetic Programming (CGP)は関数集合に対する選択と組合せを通して
プログラムを構築プロセスに関し、それを遺伝的アルゴリズムにより発展させていく手法である。
CGPは特に画像処理に関してその可能性を示してきた。
本論ではCGPをAtariゲームのプレイに適用する。
提案するCGPでは、行列計算に関する処理を関数集合に含め、
結果画像処理や制御挙動を可能にする。
プログラムサイズは小さいが、SOTAモデルと同等の性能を持つ事が
可能である。DARTS: Differentiable Architecture Search
本論ではneural architecture searchを微分可能形式に定式化する事で
スケーラビリティを改善する手法を提案する。
離散あるいは微分不可能な探索空間に対する強化学習や遺伝的アルゴリズムに基づいた
手法と違い、構造表現の連続緩和に基づいてSGDによる効率的な探索を提案手法は実現する。
提案手法はネットワーク構造をdirected acyclic graph (DAG)で表現し、そのDAGにおける
接続パターンをsoftmax関数を用いた連続緩和表現を利用することで微分可能な形にしている。
- 投稿日:2019-05-02T00:05:53+09:00
https://onegold88.com/scr888-online/
Another star that suffered through money trouble in the fifties was Marilyn Monroe. Tired of playing dumb blondes, she bolted from her studio Twentieth Century Fox to start Marilyn Monroe Productions. Actors are often advised not to use their own Scr888 name in their personal ventures; it makes other ego-driven stars less willing to work with them. Marilyn's film output slowed down and by 1959 her husband, playwright Arthur Miller, was telling her she should accept the dumb blonde role in Some Like It Hot, they needed the money. "I can't see through Jack Lemmon and Tony Curtis in drag? Oh my God, I've been dumb before but never that dumb.
There's also a special ferry trip for residents from different retirement communities in New York-this trip will take the best route in New London, the humble town of Mystic, Mohegan Sun (the best place for shopping, casino, and other amusement centers), and other best places in the city.Beverly Hills is a place where you spend a lot of money you don't have to impress a lot of people you don't like!"- - Anonymous Hollywood ProducerAnother star that suffered through money trouble in the fifties was Marilyn Monroe. Tired of playing dumb blondes, she bolted from her studio Twentieth Century Fox to start Marilyn Monroe Productions. Actors are often advised not to use their own name in their personal ventures; it makes other ego-driven stars less willing to work with them. Marilyn's film output slowed down and by 1959 her husband, playwright Arthur Miller, was telling her she should accept the dumb blonde role in Some Like It Hot, they needed the money. "I can't see through Jack Lemmon and Tony Curtis in drag? Oh my God, I've been dumb before but never that dumb.
She went to her well renowned acting teacher, Lee Strasberg, to ask how she could make the audience believe her character. Strasberg suggested that Marilyn, always a man's woman, play the part as someone so desperate for female friendship, she simply didn't pay attention to her co-star's masculine features. She took his advice and the result was a comedy classic.
https://onegold88.com/scr888-online/