C++调用pytorch,LibTorch在win10下的vs配置和cmake的配置

最近有个项目需要使用C++调用训练好的模型。刚好pytorch1.0版本的发布,加入了对C++的支持,准备试一试pytorch对C++的支持怎么样。这里是官方文档和教程。

https://pytorch.org/docs/master/jit.html​pytorch.orghttps://pytorch.org/tutorials/advanced/cpp_export.html​pytorch.org

总的来说,现在可以用python版的pytorch快速实现和训练,使用相应的API导出模型供C++版的pytorch读取,给C++版本相应输入会生成和python版本一样的预测结果。

开发环境

  • VS2015(VS2017亲测也能通过)
  • win10
  • cmake>=3.0

转换模型

pytorch的C++版本用的是Torch Script,官方给了两种将pytorch模型转成Torch Script的方法。

第一种方法,Tracing:

这种方法比较简单,不需要添加代码到模型中。只需要传一个输入给torch.jit.trace函数,让它输出一次,然后save。

import Image
import torch
import torchvision.models as models
from torchvision import transforms as transform

model_resnet = models.resnet50()
#model_resnet.load_state_dict(torch.load("resnet_Epoch_4_Top1_99.75845336914062.pkl"))
model_resnet.eval()

image = Image.open("your image path").convert('RGB')

transforms = transform.Compose([
        transform.Resize((224,224)),
        transform.ToTensor(),
        transform.Normalize(mean=[0.5]*3, std=[0.5]*3)
])

input = centre_crop_val(image)
input = input.unsqueeze(0)

traced_script_module_resnet = torch.jit.trace(model_resnet, input)

output = traced_script_module_resnet(input)
#print(output)
traced_script_module_resnet.save("model_resnet_jit.pt")

使用什么做输出都无所谓,为了方便比较python版和C++版是否输出一样,建议使用一个样本来测试下,不然给对方使用的时候发现结果不一样就尴尬了(逃。需要和训练的size以及channel保持一致,同时要保证用于测试的样本和用于训练的样本的transform要一致,不然输出也不一样 。使用torch.rand或者torch.ones也是可行的,不会影响已经训练好的模型权重。

#使用torch.rand
input = torch.ones(1, 3, 224, 224)
traced_script_module_resnet = torch.jit.trace(model_resnet, input)

output = traced_script_module_resnet(input)
#print(output)
traced_script_module_resnet.save("model_resnet_jit.pt")

第二种方法,Annotation:

第二种适合有控制流的模型,比如你的forward方法中有if/else语句,可能就需要使用这种方法。比如用官方的例子做展示:

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方法前加一个修饰器@torch.jit.script_method。

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 bool(input.sum() > 0):
          output = self.weight.mv(input)
        else:
          output = self.weight + input
        return output

my_script_module = MyModule(2, 3)
my_script_module.save("your_model.pt")

不管哪种方法得到的model.pt(也就是Torch Script),就可以使用C++调用它了。

准备工作

确定有>=3.0版本的cmake和比较高的vs版本。cmake下载

pytorch官网下载对应的LibTorch。有GPU版CP官网下载对应的LibTorch。有GPU版CPU版、有DEBUG和RELEASE版。

C++调用pytorch,LibTorch在win10下的vs配置和cmake的配置

然后解压。

C++调用pytorch,LibTorch在win10下的vs配置和cmake的配置

有include有lib,跟其他库结构差不多。

VS配置

官方和其他很多都是用的cmake,其实vs也能用。新建一个空项目,然后和VS配置opencv一样,把LibTorch的include和lib添加到“包含目录”和“库目录”中就行,还需要在链接器中加入:

torch.lib
c10.lib
caffe2.lib

一般来说3个就足够,以防万一可以把所有lib都加上:

c10.lib
caffe2.lib
caffe2_detectron_ops.lib
caffe2_module_test_dynamic.lib
clog.lib
cpuinfo.lib
foxi_dummy.lib
foxi_loader.lib
libprotobuf.lib
libprotobuf-lite.lib
libprotoc.lib
onnx.lib
onnx_proto.lib
onnxifi_dummy.lib
onnxifi_loader.lib
torch.lib

还有两个地方需要修改:
第一项:属性->C/C++ ->常规->SDL检查->否。
第二项:属性->C/C++ ->语言->符号模式->否。

编写C++代码

新建一个example.cpp,选几张测试的图片,用opencv读入然后转成tensor。训练网络的时候Tensor的shape是N x C x H x W,所以还需要把opencv转成的tensor(H x W x C)用permute转换一下,然后unsqueeze添加一维变成N x C x H x W。同时要保证测试样本和训练样本有一样的transform。

#include <torch/script.h> // One-stop header.
#include <opencv2/opencv.hpp>
#include <iostream>
#include <memory>

//https://pytorch.org/tutorials/advanced/cpp_export.html

std::string image_path = "your image folder path";

int main(int argc, const char* argv[]) {

    // Deserialize the ScriptModule from a file using torch::jit::load().
    std::shared_ptr<torch::jit::script::Module> module = torch::jit::load("your model path");

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

    //输入图像
    auto image = cv::imread(image_path +"/"+ "your image name",cv::ImreadModes::IMREAD_IMREAD_COLOR);
    cv::Mat image_transfomed;
    cv::resize(image, image_transfomed, cv::Size(70, 70));

    // 转换为Tensor
    torch::Tensor tensor_image = torch::from_blob(image_transfomed.data,
                            {image_transfomed.rows, image_transfomed.cols,3},torch::kByte);
    tensor_image = tensor_image.permute({2,0,1});
    tensor_image = tensor_image.toType(torch::kFloat);
    tensor_image = tensor_image.div(255);
    tensor_image = tensor_image.unsqueeze(0);

    // 网络前向计算
    at::Tensor output = module->forward({tensor_image}).toTensor();
	//std::cout << "output:" << output << std::endl;

	auto prediction = output.argmax(1);
	std::cout << "prediction:" << prediction << std::endl;

	int maxk = 3;
	auto top3 = std::get<1>(output.topk(maxk, 1, true, true));

	std::cout << "top3: " << top3 << '\n';

	std::vector<int> res;
	for (auto i = 0; i < maxk; i++) {
		res.push_back(top3[0][i].item().toInt());
	}
	for (auto i : res) {
		std::cout << i << " ";
	}
	std::cout << "\n";

	system("pause");
}

对同样的测试图片,我的python版模型输出为:

C++调用pytorch,LibTorch在win10下的vs配置和cmake的配置

同样的模型,同样的测试图片,C++版的输出:

C++调用pytorch,LibTorch在win10下的vs配置和cmake的配置

给出的top3结果都是一样的。

CMAKE方法

大家都是在linux下的cmake配置,在windows下编写好CMakeLists.txt,一样可以在windows下cmake,需要根据自己LibTorch和Opencv的路径以及cpp的名字进行修改。我的cpp文件名是example.cpp,设置的为RELEASE模式。

cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
project(torchlib-example)

SET(CMAKE_BUILE_TYPE RELEASE)

INCLUDE_DIRECTORIES(
E:/libtorch/include
E:/opencv3/opencv/build/include
E:/opencv3/opencv/build/include/opencv
E:/opencv3/opencv/build/include/opencv2
)

SET(TORCH_LIBRARIES E:/libtorch/lib)
SET(OpenCV_LIBS E:/opencv3/opencv/build/x64/vc14/lib)

LINK_DIRECTORIES(
${TORCH_LIBRARIES}
${OpenCV_LIBS}
)

add_executable(torchlib-example example.cpp)

target_link_libraries(torchlib-example
c10.lib
caffe2.lib
caffe2_detectron_ops.lib
caffe2_module_test_dynamic.lib
clog.lib
cpuinfo.lib
foxi_dummy.lib
foxi_loader.lib
libprotobuf.lib
libprotobuf-lite.lib
libprotoc.lib
onnx.lib
onnx_proto.lib
onnxifi_dummy.lib
onnxifi_loader.lib
torch.lib
opencv_world344.lib
)

set_property(TARGET torchlib-example PROPERTY CXX_STANDARD 11)

然后再CMakeLists.txt和example.cpp目录下新建一个build文件夹,然后打开cmake-gui.exe, 填好两个目录。

C++调用pytorch,LibTorch在win10下的vs配置和cmake的配置

然后选择generator,一定要选择64位。

C++调用pytorch,LibTorch在win10下的vs配置和cmake的配置

点击Configure,没问题的话再点击Configure,然后再点击Generate就成功了。

C++调用pytorch,LibTorch在win10下的vs配置和cmake的配置

然后打开build文件夹中的vcxporj文件,设置项目为启动项目,并更改为release的x64模式,生成解决方案。

C++调用pytorch,LibTorch在win10下的vs配置和cmake的配置

最后,如果提示缺少dll,可以把LibTorch的lib文件夹加入环境变量,也可以把lib文件夹的dll全拷到cpp目录下。

上一篇:windows提权


下一篇:yolov5 导出LibTorch模型(CPU和GPU)