利用C++调用Pytorch的模型

需要使用Pytorch的版本为1.0及以上:

conda install pytorch-nightly -c pytorch

第一步:将Pytorch模型转化为Torch script

Torch Script是连接C++和Python的桥梁,Pytorch模型的表示,可以被Torch Script编译器理解,编译和序列化.

如果想要C++使用Pytorch的模型,就必须先将Pytorch模型转化为Torch Script.在大多数情况下,这样的工作量都比较小,如果已经有了模型的Torch Script,那么下面的内容就不需要看了.

有两种方法,可以将Pytorch模型转化为Torch Script.

第一个方法是tracing.该方法通过将样本输入到模型中一次来对该过程进行评估从而捕获模型结构.并记录该样本在模型中的flow.该方法适用于模型中很少使用控制flow的模型.

第二个方法就是向模型添加显式注释,通知Torch Script编译器它可以直接解析和编译模型代码,受Torch Script语言强加的约束。

深入的了解请点击此链接

利用Tracing将模型转换为Torch Script

要通过tracing来将PyTorch模型转换为Torch脚本,必须将模型的实例以及样本输入传递给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模块进行相同的计算:

In[1]: output = traced_script_module(torch.ones(1, 3, 224, 224))
In[2]: output[0, :5]
Out[2]: tensor([-0.2698, -0.0381,  0.4023, -0.3010, -0.0448], grad_fn=<SliceBackward>)

通过标注将Model转换为Torch Script

在某些情况下,例如,如果模型使用特定形式的控制流,如果想要直接在Torch脚本中编写模型并相应地标注模型。例如,假设有以下普通的 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方法使用依赖于输入的控制流,因此它不适合利用tracing的方法生成Torch Script。为此,可以通过继承torch.jit.ScriptModule并将@ torch.jit.script_method标注添加到模型的forward中的方法,来将model其转换为ScriptModule:

import torch

class MyModule(torch.jit.ScriptModule):
    def __init__(self, N, M):
        super(MyModule, self).__init__()
        self.weight = torch.nn.Parameter(torch.rand(N, M))

    @torch.jit.script_method
    def forward(self, input):
        if input.sum() > 0:
          output = self.weight.mv(input)
        else:
          output = self.weight + input
        return output

my_script_module = MyModule()

现在,创建一个新的MyModule对象会直接生成一个可以进行序列化的ScriptModule实例。

第二步:将Script Module序列化为一个文件

不论是从上面两种方法的哪一种方法获得了ScriptModule,都可以将得到的ScriptModule序列化为一个文件.然后,C++就可以不依赖任何Python代码来执行该Script所对应的Pytorch模型.

假设我们想要序列化前面跟踪示例中显示的ResNet18模型。要执行此序列化,只需在模块上调用save并将其传递给文件名:

traced_script_module.save("model.pt")

这将在工作目录中生成一个model.pt文件。我们现在正式离开了Python的领域,并准备跨越到C ++领域。

第三步:在C++中加载你的Script Module

要在C ++中加载序列化的PyTorch模型,您的应用程序必须依赖于PyTorch C ++ API - 也称为LibTorch。LibTorch发行版包含一组共享库,头文件和CMake构建配置文件。虽然CMake不是依赖LibTorch的要求,但它是推荐的方法,并且将来会得到很好的支持。在本教程中,我们将使用CMake和LibTorch构建一个最小的C ++应用程序,它只需加载并执行序列化的PyTorch模型。

最小的C ++应用程序

以下内容可以做到加载模块的代码:

#include  // One-stop header.
#include 
#include 

int main(int argc, const char* argv[]) {
  if (argc != 2) {
    std::cerr << "usage: example-app \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]);

  assert(module != nullptr);
  std::cout << "ok\n";
}

头文件包含运行该示例所需的LibTorch库中的所有相关包含。我们的应用程序接受序列化PyTorch ScriptModule的文件路径作为其唯一的命令行参数,然后使用torch :: jit :: load()函数继续反序列化模块,该函数将此文件路径作为输入。作为回报,我们收到一个指向torch :: jit :: script :: Module的共享指针,相当于C ++中的torch.jit.ScriptModule。目前,我们只验证此指针不为null。我们将研究如何在接下来执行它。

取决于LibTorch和构建应用程序

假设我们将上面的代码保存到名为example-app.cpp的文件中。构建它的最小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发行版。您可以随时从PyTorch网站的下载页面获取最新的稳定版本。如果下载并解压缩最新存档,则应收到具有以下目录结构的文件夹:

libtorch/
  bin/
  include/
  lib/
  share/

  • ***lib/***文件夹包含您必须链接的共享库,
  • ***include/***文件夹包含程序需要包含的头文件,
  • ***share/***包含必要的CMake配置,以启用上面的简单find_package(Torch)命令。

最后一步是构建应用程序。为此,假设我们的示例目录布局如下:

example-app/
  CMakeLists.txt
  example-app.cpp

我们现在可以运行以下命令从example-app/文件夹中构建应用程序:

mkdir build
cd build
cmake -DCMAKE_PREFIX_PATH=/path/to/libtorch ..
make

其中/path/to/libtorch应该是解压缩的LibTorch发行版的完整路径。如果一切顺利,它将看起来像这样:

root@4b5a67132e81:/example-app# mkdir build
root@4b5a67132e81:/example-app# cd build
root@4b5a67132e81:/example-app/build# cmake -DCMAKE_PREFIX_PATH=/path/to/libtorch ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Looking for pthread_create
-- Looking for pthread_create - not found
-- Looking for pthread_create in pthreads
-- Looking for pthread_create in pthreads - not found
-- Looking for pthread_create in pthread
-- Looking for pthread_create in pthread - found
-- Found Threads: TRUE
-- Configuring done
-- Generating done
-- Build files have been written to: /example-app/build
root@4b5a67132e81:/example-app/build# make
Scanning dependencies of target example-app
[ 50%] Building CXX object CMakeFiles/example-app.dir/example-app.cpp.o
[100%] Linking CXX executable example-app
[100%] Built target example-app

如果我们提供前面的序列化ResNet18模型的路径给example-app,C++输出的结果应该是OK:

root@4b5a67132e81:/example-app/build# ./example-app model.pt
ok

第四步:在C++代码中执行Script Module

在C ++中成功加载了我们的序列化ResNet18后,我们现在只需执行几行代码!
让我们将这些行添加到C ++应用程序的main()函数中:

// 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.
auto output = module->forward(inputs).toTensor();

std::cout << output.slice(/*dim=*/1, /*start=*/0, /*end=*/5) << '\n';

前两行设置了我们模型的输入。我们创建了一个torch :: jit :: IValue的向量并添加一个输入。要创建输入张量,我们使用torch :: ones(),相当于C ++ API中的torch.ones。然后我们运行script::Moduleforward方法,将它传递给我们创建的输入向量。作为回报,我们得到一个新的IValue,我们通过调用toTensor()将其转换为张量。

To learn more about functions like torch::ones and the PyTorch C++ API in general, refer to its documentation at https://pytorch.org/cppdocs. The PyTorch C++ API provides near feature parity with the Python API, allowing you to further manipulate and process tensors just like in Python.

在最后一行中,我们打印输出的前五个条目。由于我们在本教程前面的Python中为我们的模型提供了相同的输入,因此理想情况下我们应该看到相同的输出。让我们通过重新编译我们的应用程序并使用相同的序列化模型运行它来尝试:

root@4b5a67132e81:/example-app/build# make
Scanning dependencies of target example-app
[ 50%] Building CXX object CMakeFiles/example-app.dir/example-app.cpp.o
[100%] Linking CXX executable example-app
[100%] Built target example-app
root@4b5a67132e81:/example-app/build# ./example-app model.pt
-0.2698 -0.0381  0.4023 -0.3010 -0.0448
[ Variable[CPUFloatType]{1,5} ]

作为参考,之前Python代码的输出是:

tensor([-0.2698, -0.0381,  0.4023, -0.3010, -0.0448], grad_fn=<SliceBackward>)

由此可见,C++的输出与Python的输出是一样的,成功啦!

第五步:进阶教程

This tutorial has hopefully equipped you with a general understanding of a PyTorch model’s path from Python to C++. With the concepts described in this tutorial, you should be able to go from a vanilla, “eager” PyTorch model, to a compiled ScriptModule in Python, to a serialized file on disk and – to close the loop – to an executable script::Module in C++.

Of course, there are many concepts we did not cover. For example, you may find yourself wanting to extend your ScriptModule with a custom operator implemented in C++ or CUDA, and executing this custom operator inside your ScriptModule loaded in your pure C++ production environment. The good news is: this is possible, and well supported! For now, you can explore this folder for examples, and we will follow up with a tutorial shortly. In the time being, the following links may be generally helpful:

  • The Torch Script reference: https://pytorch.org/docs/master/jit.html
  • The PyTorch C++ API documentation: https://pytorch.org/cppdocs/
  • The Pytorch Python API documentation: https://pytorch.org/docs/

你可能感兴趣的:(Pytorch,C++,AI)