TorchScriptの概要
TorchScriptは、PyTorchモデルの中間形式であり、高性能環境(C ++など)で実行できます。
簡単な例は次のとおりです。
import torch
#import torchvision
class MyCell(torch.nn.Module):
def __init__(self):
super(MyCell, self).__init__()
def forward(self, x, h):
new_h = torch.tanh(x + h)
return new_h, new_h
my_cell = MyCell()
x = torch.rand(3, 4)
h = torch.rand(3, 4)
print(my_cell(x, h))
出力結果:
上記の例に基づいて、torch.nn.Module
クラスを作成しMyCell
、コンストラクターを定義しました。ここでのコンストラクターは、super
関数を呼び出すだけです。super()
関数は、親クラス(スーパークラス)を呼び出すために使用されるメソッドです。super
多重継承の問題を解決するために使用されます。単一継承を使用する場合はクラス名を使用して親クラスメソッドを直接呼び出すことは問題ありませんが、多重継承を使用する場合は、検索順序や繰り返し呼び出しなどのさまざまな問題が発生します。 。同時に、forward
関数も定義します。ここforward
で入力する関数は2つのパラメーターであり、2つの結果を返します。フォワード関数の実際の内容はそれほど重要ではありませんが、これは疑似RNNユニットです。つまり、関数の実際のシーンがループに適用されます。
上記のMyCell
クラスをさらに変更し、元のベースself.linear
でメンバー属性(関数)を追加して、forward
関数内のメンバーを呼び出します。torch.nn.Linear
これはPyTorchの標準モジュールであり、ネストされたモジュールの組み合わせを完成させます。
import torch
class MyCell(torch.nn.Module):
def __init__(self):
super(MyCell, self).__init__()
self.linear = torch.nn.Linear(4, 4)
def forward(self, x, h):
new_h = torch.tanh(self.linear(x) + h)
return new_h, new_h
my_cell = MyCell()
x = torch.rand(3, 4)
h = torch.rand(3, 4)
print(my_cell)
print(my_cell(x, h))
出力結果:
モジュールを印刷する場合、出力はモジュールのサブクラス階層です。たとえばmycell
、上記の印刷の結果は、linear
サブクラスとそのパラメーターです。このようにモジュールを組み合わせることで、再利用可能なコンポーネントを備えたモデルを簡単に作成できます。
さらに、出力結果から、が存在することがわかりますgrad_fn
。これは、と呼ばれるPyTorchの自動微分と導出によって提供される情報autograd
です。つまり、このシステムでは、複雑になる可能性のある手順を通じて導関数を計算できます。この設計は、モデル作成に大きな柔軟性を提供します。
以下では、例を使用して、モデル構築の柔軟性をさらに説明します。上記MyDecisionGate
に基づいて、このモジュールでループまたはifステートメントの形式の制御フローが追加されました。
import torch
class MyDecisionGate(torch.nn.Module):
def forward(self, x):
if x.sum() > 0:
return x
else:
return -x
class MyCell(torch.nn.Module):
def __init__(self):
super(MyCell, self).__init__()
self.dg = MyDecisionGate()
self.linear = torch.nn.Linear(4, 4)
def forward(self, x, h):
new_h = torch.tanh(self.dg(self.linear(x)) + h)
return new_h, new_h
my_cell = MyCell()
x = torch.rand(3, 4)
h = torch.rand(3, 4)
print(my_cell)
print(my_cell(x, h))
出力結果:
トレース
つまり、ネイティブPyTorchの柔軟で動的な性質を考えると、TorchScriptはモデル定義をキャプチャするためのツールも提供します。コアコンセプトの1つは模型追踪
(トレース)です。
import torch
class MyCell(torch.nn.Module):
def __init__(self):
super(MyCell, self).__init__()
self.linear = torch.nn.Linear(4, 4)
def forward(self, x, h):
new_h = torch.tanh(self.linear(x) + h)
return new_h, new_h
my_cell = MyCell()
x, h = torch.rand(3, 4), torch.rand(3, 4)
traced_cell = torch.jit.trace(my_cell, (x, h))
print(traced_cell)
traced_cell(x, h)
出力結果:
前と同じように、をインスタンス化しますMyCell
が、今回はtorch.jit.trace
メソッドを使用してModuleを呼び出し、ネットワークのサンプル入力を渡します。これは正確には何のためですか?Moduleを呼び出し、Moduleの実行中に発生した操作を記録し、torch.jit.ScriptModule
インスタンス(TracedModuleのインスタンス)を作成しました。TorchScriptは、その定義を中間表現(またはIR)に記録します。これは通常、深層学習ではグラフと呼ばれます。.graph
プロパティにアクセスしてグラフを表示できます。
print(traced_cell.graph)
出力結果:
ただし、これは非常に低レベルの表現であり、グラフに含まれる情報のほとんどはエンドユーザーにとって有用ではありません。代わりに、.code
属性を使用して、Python構文の説明をコードに提供できます。
print(traced_cell.code)
出力結果:
では、なぜこれをすべて行うのでしょうか。いくつかの理由があります:
- TorchScriptコードは、基本的に制限付きのPythonインタープリターである独自のインタープリターで呼び出すことができます。インタプリタはグローバルインタプリタロックを取得しないため、同じインスタンスで多数のリクエストを同時に処理できます。
- この形式を使用すると、モデル全体をディスクに保存して、Python以外の言語で記述されたサービスなど、別の環境にロードできます。
- TorchScriptは表現を提供します。TorchScriptを使用すると、コードに対してコンパイラの最適化を実行して、より効率的な実行を提供できます。
- TorchScriptは、単一の操作よりもプログラムのより広いビューを必要とする多くのバックエンド/デバイスランタイムとインターフェイスできます。
呼び出しtraced_cell
の結果は、Pythonモジュールを直接実行した結果と同じであることがわかり
ます。Run:
import torch
class MyCell(torch.nn.Module):
def __init__(self):
super(MyCell, self).__init__()
self.linear = torch.nn.Linear(4, 4)
def forward(self, x, h):
new_h = torch.tanh(self.linear(x) + h)
return new_h, new_h
my_cell = MyCell()
x, h = torch.rand(3, 4), torch.rand(3, 4)
traced_cell = torch.jit.trace(my_cell, (x, h))
print(traced_cell)
traced_cell(x, h)
print(my_cell(x, h))
print(traced_cell(x, h))
出力結果:
スクリプトを使用してモジュールを変換する
traced_cell(x, h)
制御フローを備えたサブモジュールのバージョンを使用する代わり に、モジュールの2番目のバージョンを使用する理由があります。その背後にある理由を次の例で説明しましょう。
import torch
class MyDecisionGate(torch.nn.Module):
def forward(self, x):
if x.sum() > 0:
return x
else:
return -x
class MyCell(torch.nn.Module):
def __init__(self, dg):
super(MyCell, self).__init__()
self.dg = dg
self.linear = torch.nn.Linear(4, 4)
def forward(self, x, h):
new_h = torch.tanh(self.dg(self.linear(x)) + h)
return new_h, new_h
my_cell = MyCell(MyDecisionGate())
x, h = torch.rand(3, 4), torch.rand(3, 4)
traced_cell = torch.jit.trace(my_cell, (x, h))
print(traced_cell.code)
出力結果:
.code
出力によると、見つけることができるif-else
ブランチの痕跡はありません!どうして?Tracing
私たちが言うことを正確に実行します。コードを実行し、何が起こったかを記録し、これを実行できるコードを作成しますScriptModule
。残念ながら、この操作中に、制御フローなどの情報が消去されます。
では、TorchScript
このモジュールをどのように正直に表現するのでしょうか?PyTorchは、Pythonソースコードを直接分割してに変換できるスクリプトコンパイラを提供しますTorchScript
。上記のMyDecisionGate
スクリプトコンパイラを使用して変換します。
scripted_gate = torch.jit.script(MyDecisionGate()) # 看这里
my_cell = MyCell(scripted_gate)
traced_cell = torch.jit.script(my_cell) # 看这里
print(traced_cell.code)
这部分我运行出了些问题,可能是软件版本太低了,有时间使用PyTorch版本1.2.0+cu92试一试,先copy一下,有时间再查原因吧。。。。。
演算結果:
def forward(self,
x: Tensor,
h: Tensor) -> Tuple[Tensor, Tensor]:
_0 = self.linear
_1 = _0.weight
_2 = _0.bias
if torch.eq(torch.dim(x), 2):
_3 = torch.__isnot__(_2, None)
else:
_3 = False
if _3:
bias = ops.prim.unchecked_unwrap_optional(_2)
ret = torch.addmm(bias, x, torch.t(_1), beta=1, alpha=1)
else:
output = torch.matmul(x, torch.t(_1))
if torch.__isnot__(_2, None):
bias0 = ops.prim.unchecked_unwrap_optional(_2)
output0 = torch.add_(output, bias0, alpha=1)
else:
output0 = output
ret = output0
_4 = torch.gt(torch.sum(ret, dtype=None), 0)
if bool(_4):
_5 = ret
else:
_5 = torch.neg(ret)
new_h = torch.tanh(torch.add(_5, h, alpha=1))
return (new_h, new_h)
これで、TorchScriptでプログラムの動作を忠実にキャプチャできます。次に、プログラムを実行してみます。
# New inputs
x, h = torch.rand(3, 4), torch.rand(3, 4)
print(traced_cell(x, h))
演算結果:
(tensor([[ 0.3430, -0.3471, 0.7990, 0.8313],
[-0.4042, -0.3058, 0.7758, 0.8332],
[-0.3002, -0.3926, 0.8468, 0.7715]],
grad_fn=<DifferentiableGraphBackward>), tensor([[ 0.3430, -0.3471, 0.7990, 0.8313],
[-0.4042, -0.3058, 0.7758, 0.8332],
[-0.3002, -0.3926, 0.8468, 0.7715]],
grad_fn=<DifferentiableGraphBackward>))
この実験のPyTorchバージョンがあることに注意してください1.1.0+cu9.0。建议使用
PyTorchバージョン1.2.0+cu92。
スクリプトとトレースの混合
追加される。。。。。。。。。。。。。
TorchScriptモデルをC ++でロードする
ステップ1:PyTorchモデルをトーチスクリプトに変換する
PyTorchモデルはPythonからC ++にTorch Script
実装する必要があります。トーチスクリプトはPyTorchモデルの表現であり、トーチスクリプトコンパイラによって理解、コンパイル、およびシリアル化できます。通常の「熱心な」APIを使用してPyTorchモデルを作成する場合は、最初にモデルをTorchScriptに変換する必要があります。
前の章では、PyTorchモデルをトーチスクリプトに変換する2つの方法を紹介しました。1つはトレースです。これは、インスタンス入力を介してモデルの構造を評価し、モデルを介したこれらの入力のフローを記録します。この方法は、モデルで制御フローの使用が制限されている状況に適しています。2番目の方法は、モデルに明示的なコメントを追加して、トーチスクリプトコンパイラがモデルコードを直接解析およびコンパイルできるようにすることです。詳細については、トーチスクリプトリファレンスを参照してください。
トレースする
トラッキングによってPyTorchモデルをトーチスクリプトに変換するには、サンプル入力のあるモデルインスタンスをtorch.jit.trace
関数に入力する必要があります。これtorch.jit.ScriptModule
により、モデル評価の追跡をforwardメソッドに埋め込むオブジェクトが生成されます。
具体的な使用例は次のとおりです。
import torch
import torchvision
# An instance of your model.
model = torchvision.models.resnet18()
# An example input you would normally provide to your model's forward() method.
example = torch.rand(1, 3, 224, 224)
# Use torch.jit.trace to generate a torch.jit.ScriptModule via tracing.
traced_script_module = torch.jit.trace(model, example)
追跡されたScriptModule
オブジェクトは、通常のPyTorchモジュールと見なすことができます。
output = traced_script_module(torch.ones(1, 3, 224, 224))
print(output[0, :5])
出力結果:
tensor([0.7741, 0.0539, 0.6656, 0.7301, 0.2207], grad_fn=<SliceBackward>)
注釈経由
たとえば、モデルが特定の形式の制御フローを採用している場合は、トーチスクリプトでモデルを直接記述し、それに応じてモデルにラベルを付ける方がよい場合があります。例として、次のPytorchモデルを取り上げます。
import torch
class MyModule(torch.nn.Module):
def __init__(self, N, M):
super(MyModule, self).__init__()
self.weight = torch.nn.Parameter(torch.rand(N, M))
def forward(self, input):
if input.sum() > 0:
output = self.weight.mv(input)
else:
output = self.weight + input
return output
因为这个模块中的forward方法使用依赖于输入的控制流依,这种模块不适合于追踪方法。相反,可以将其转换为ScriptModule。为了将模块转换为ScriptModule,需要用torch.jit.script编译模块:
class MyModule(torch.nn.Module):
def __init__(self, N, M):
super(MyModule, self).__init__()
self.weight = torch.nn.Parameter(torch.rand(N, M))
def forward(self, input):
if input.sum() > 0:
output = self.weight.mv(input)
else:
output = self.weight + input
return output
my_module = MyModule(10,20)
sm = torch.jit.script(my_module)
さらに、でnn.Module
必要とされていないメソッドの場合(TorchScriptは現在一部のPython機能をサポートしていないため)、@torch.jit.ignore
それらを使用してそれらを削除できます。
ステップ2:スクリプトモジュールをファイルにシリアル化する
取得したScriptModule
オブジェクト(トレース方法または注釈方法のどちらで取得した場合でも)については、他の環境(C ++など)で後で使用するためにファイルにシリアル化できます。具体的なシリアル化方法は次のとおりです。
traced_script_module.save("traced_resnet_model.pt")
- モジュールを同時にシリアル化したい場合は
my_module
、それを使用できますmy_module.save("my_module_model.pt")
。
ステップ3:トーチスクリプトモジュールをC ++でロードする
シリアル化されたPyTorchモデルをC ++でロードするには、LibTorch
ライブラリであるPyTorch C ++ APIが必要です。LibTorch
共有ライブラリ、ヘッダーファイル、CMakeビルド構成ファイルがあります。
最も単純化されたC ++アプリケーション
example-app.cpp
内容は以下のとおりです。
#include <torch/script.h> // One-stop header.
#include <iostream>
#include <memory>
int main(int argc, const char* argv[]) {
if (argc != 2) {
std::cerr << "usage: example-app <path-to-exported-script-module>\n";
return -1;
}
torch::jit::script::Module module;
try {
// Deserialize the ScriptModule from a file using torch::jit::load().
module = torch::jit::load(argv[1]);
}
catch (const c10::Error& e) {
std::cerr << "error loading the model\n";
return -1;
}
std::cout << "ok\n";
}
ヘッダーファイルに<torch/script.h>
は、例の実行に必要なLibTorchライブラリのすべての依存関係が含まれています。上記の例では、シリアル化されたScriptModule
ファイルを受け取り、シリアル化されたファイルをtorch::jit::load()
ロードすると、結果がtorch::jit::script::Module
オブジェクトになります。
依存関係を構築して作成する
上記のコードに対応するCMakeLists.txtの内容は次のとおりです。
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
project(custom_ops)
find_package(Torch REQUIRED)
add_executable(example-app example-app.cpp)
target_link_libraries(example-app "${TORCH_LIBRARIES}")
set_property(TARGET example-app PROPERTY CXX_STANDARD 11)
lib
ディレクトリは、リンクのために必要な共有ライブラリが含まれ、include
プログラムで使用されるヘッダファイルが含まれている、share
ディレクトリを容易にするために必要CMakeの構成が含まれているfind_package(Torch)
の使用上記コマンド。
最後に、アプリケーションをビルドする必要があります。ディレクトリレイアウトが次のようになっているとします。
example-app/
CMakeLists.txt
example-app.cpp
次のコマンドを実行して、example-app/
フォルダーからアプリケーションをビルドできます。
mkdir build
cd build
cmake -DCMAKE_PREFIX_PATH=/home/data1/devtools/libtorch/ ..
make
このDCMAKE_PREFIX_PATH
値はlibtorch
、ダウンロード後に解凍する場所です。
コンパイル後の動作モードは次のとおりです。
./example-app <path_to_model>/traced_resnet_model.pt
ステップ4:C ++でスクリプトモジュールを実行する
上記の紹介では、シリアル化されたResNet18をC ++でロードできました。次に、推論のためにモデルを実行する必要があります。詳細は次のとおりです。
// Create a vector of inputs.
std::vector<torch::jit::IValue> inputs;
inputs.push_back(torch::ones({1, 3, 224, 224}));
// Execute the model and turn its output into a tensor.
at::Tensor output = module.forward(inputs).toTensor();
std::cout << output.slice(/*dim=*/1, /*start=*/0, /*end=*/5) << '\n';
上記のコードの最初の2行は、モデルの入力され、その後、呼び出し、返された結果の種類、方法があり、それはさらにする必要があることテンソルに変換します。script::Module
forward
IValue
toTensor()
注:GPUを使用してモデルを実行する場合は、次のようにモデルを処理するだけで済みますmodel.to(at::kCUDA);
。同時に、モデルの入力がCUDAメモリにもあることを確認する必要があります。これは次の方法で実装できます。CUDAメモリにあるtensor.to(at::kCUDA)
新しいテンソルが返されます。
画像分類例
環境への備え
事前にcmake、opencv、PyTroch1.2をインストールする必要があります。opencvのインストールプロセス中に、gccバージョン(この記事で使用されているgcc5.2)が低すぎるなど、環境インストールの問題が発生する可能性があります。ここで説明します。
C ++でモデルをロードする
例として、画像分類用のresnet18モデルを取り上げます。
ステップ1:PyTorchモデルをトーチスクリプトに変換する
次のスクリプトを実行します。
import torch
import torchvision
from torchvision import transforms
from PIL import Image
from time import time
import numpy as np
# An instance of your model.
model = torchvision.models.resnet18(pretrained=True)
model.eval()
# An example input you would normally provide to your model's forward() method.
example = torch.rand(1, 3, 224, 224)
# Use torch.jit.trace to generate a torch.jit.ScriptModule via tracing.
traced_script_module = torch.jit.trace(model, example)
traced_script_module.save("model.pt")
# evalute time
batch = torch.rand(64, 3, 224, 224)
start = time()
output = traced_script_module(batch)
stop = time()
print(str(stop-start) + "s")
# read image
image = Image.open('dog.png').convert('RGB')
default_transform = transforms.Compose([
transforms.Resize([224, 224]),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
image = default_transform(image)
# forward
output = traced_script_module(image.unsqueeze(0))
print(output[0, :10])
# print top-5 predicted labels
labels = np.loadtxt('synset_words.txt', dtype=str, delimiter='\n')
data_out = output[0].data.numpy()
sorted_idxs = np.argsort(-data_out)
for i,idx in enumerate(sorted_idxs[:5]):
print('top-%d label: %s, score: %f' % (i, labels[idx], data_out[idx]))
model.ptを取得します
ステップ2:C ++でトーチスクリプトを呼び出す
(1)最初にダウンロードLibTorch
して解凍する必要があり、makeをコンパイルするときにlibのパスを指定する必要があります。
(2)cmakeツールを使用して、ビジネスコード、つまりTorchScriptを使用したコードをコンパイルします。
mkdir build
cd build
cmake -DCMAKE_PREFIX_PATH=/home/aaron/WORK/tb1/libtorch/ ..
make
演算結果:
完全なコードを添付してください:
#include "torch/script.h"
#include "torch/torch.h"
//#include "torch/Tensor.h"
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/imgproc/types_c.h"
#include <iostream>
#include <memory>
#include <string>
#include <vector>
/* main */
int main(int argc, const char* argv[]) {
if (argc < 4) {
std::cerr << "usage: example-app <path-to-exported-script-module> "
<< "<path-to-image> <path-to-category-text>\n";
return -1;
}
// Deserialize the ScriptModule from a file using torch::jit::load().
//std::shared_ptr<torch::jit::script::Module> module = torch::jit::load(argv[1]);
torch::jit::script::Module module = torch::jit::load(argv[1]);
//assert(module != nullptr);
std::cout << "load model ok\n";
// Create a vector of inputs.
std::vector<torch::jit::IValue> inputs;
inputs.push_back(torch::rand({64, 3, 224, 224}));
// evalute time
double t = (double)cv::getTickCount();
module.forward(inputs).toTensor();
t = (double)cv::getTickCount() - t;
printf("execution time = %gs\n", t / cv::getTickFrequency());
inputs.pop_back();
// load image with opencv and transform
cv::Mat image;
image = cv::imread(argv[2], 1);
cv::cvtColor(image, image, CV_BGR2RGB);
cv::Mat img_float;
image.convertTo(img_float, CV_32F, 1.0/255);
cv::resize(img_float, img_float, cv::Size(224, 224));
//std::cout << img_float.at<cv::Vec3f>(56,34)[1] << std::endl;
//auto img_tensor = torch::CPU(torch::kFloat32).tensorFromBlob(img_float.data, {1, 224, 224, 3});
auto img_tensor = torch::from_blob(img_float.data, {1, 224, 224, 3}); //.permute({0, 3, 1, 2}).to(torch::kCUDA);
img_tensor = img_tensor.permute({0,3,1,2}); //.to(torch::kCUDA);
img_tensor[0][0] = img_tensor[0][0].sub_(0.485).div_(0.229);
img_tensor[0][1] = img_tensor[0][1].sub_(0.456).div_(0.224);
img_tensor[0][2] = img_tensor[0][2].sub_(0.406).div_(0.225);
//auto img_var = torch::autograd::make_variable(img_tensor, false);
//torch::Tensor img_var = torch::autograd::make_variable(img_tensor, false);
inputs.push_back(img_tensor);
// Execute the model and turn its output into a tensor.
torch::Tensor out_tensor = module.forward(inputs).toTensor();
std::cout << out_tensor.slice(/*dim=*/1, /*start=*/0, /*end=*/10) << '\n';
// Load labels
std::string label_file = argv[3];
std::ifstream rf(label_file.c_str());
CHECK(rf) << "Unable to open labels file " << label_file;
std::string line;
std::vector<std::string> labels;
while (std::getline(rf, line))
labels.push_back(line);
// print predicted top-5 labels
std::tuple<torch::Tensor,torch::Tensor> result = out_tensor.sort(-1, true);
torch::Tensor top_scores = std::get<0>(result)[0];
torch::Tensor top_idxs = std::get<1>(result)[0].toType(torch::kInt32);
auto top_scores_a = top_scores.accessor<float,1>();
auto top_idxs_a = top_idxs.accessor<int,1>();
for (int i = 0; i < 5; ++i) {
int idx = top_idxs_a[i];
std::cout << "top-" << i+1 << " label: ";
std::cout << labels[idx] << ", score: " << top_scores_a[i] << std::endl;
}
return 0;
}
参照
https://pytorch.org/blog/model-serving-in-pyorch/
https://medium.com/datadriveninvestor/deploy-your-pytorch-model-to-production-f69460192217
https://github.com/iamhankai / cpp-pytorch
https://pytorch.org/tutorials/advanced/cpp_export.html#step-1-converting-your-pytorch-model-to-torch-script