PyTorchのC ++ API実行モデルに基づく画像分類

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)

出力結果: 

 

では、なぜこれをすべて行うのでしょうか。いくつかの理由があります:

  1. TorchScriptコードは、基本的に制限付きのPythonインタープリターである独自のインタープリターで呼び出すことができますインタプリタはグローバルインタプリタロックを取得ないため、同じインスタンスで多数のリクエストを同時に処理できます。
  2. この形式を使用すると、モデル全体をディスクに保存して、Python以外の言語で記述されたサービスなど、別の環境にロードできます。
  3. TorchScriptは表現を提供します。TorchScriptを使用すると、コードに対してコンパイラの最適化実行して、より効率的な実行を提供できます。
  4. 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)

公式からlibtorchダウンロードし、解凍します。

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::ModuleforwardIValuetoTensor()

注: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

https://blog.csdn.net/jonado13/article/details/108280029

https://blog.csdn.net/ljp1919/article/details/102514357?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2- 2.制御

おすすめ

転載: blog.csdn.net/wzhrsh/article/details/110436976