使用TorchScript和libtorch进行模型推理[附C++代码]

模型部署方式有很多,libtorch和TorchScript也是部署的一种方式,由pytorch官方提供

将pytorch模型转换为TorchScript方法:

  • Tracing an existing module
  • Using scripting to directly compile a module
  • How to compose both approaches
  • Saving and loading TorchScript modules

翻译过来就是 

  • 跟踪现有模块
  • 使用脚本直接编译模块
  • 如何组合这两种方法
  • 保存和加载 TorchScript 模块

目录

跟踪模型

使用脚本编译模块 

 VGG16猫狗分类

报错问题 

1.问题:无法定位程序输入点。。于动态库链接..exe

2.问题:std不明确的符合

3.问题:找不到xx.dll

4.问题:libtorch无法使用GPU

5.问题:无法加载模型


windows 10

VS 2017

cuda 10.2

pytorch 1.7.0

libtorch 1.7 cuda Debug版【尽量和自己pytorch版本一致】


附上代码直接来分析(这个例子是pytorch官网自带的,如果已经学过这部分内容可以略去),Introduction to TorchScript — PyTorch Tutorials 1.11.0+cu102 documentation

建立了一个很简单的模型,一个全连接层,和一个tanh激活函数

跟踪模型

import torch
import torch.nn as nn

class MyCell(torch.nn.Module):
    def __init__(self):
        super(MyCell,self).__init__()
        self.liner = nn.Linear(4, 4)
    def forward(self, x, h):
        new_h = self.liner(x) + h
        new_h = torch.tanh(new_h)

        return new_h, new_h
my_cell = MyCell()
x = torch.randn(3,4)
h = torch.randn(3,4)
my_trace = torch.jit.trace(my_cell, (x, h))
print(my_trace)

用trace对模型进行跟踪,其实就是用一个我们人为设置的变量,输入到模型中,然后利用trace对模型进行跟踪,获得模型的图结构。可以打印一下跟踪后的模型

MyCell(
  original_name=MyCell
  (liner): Linear(original_name=Linear)
)

然后可以用model.code来打印一下获得的结构(其实可以用model.graph来获得图结构的,但打印的结果不太好看懂,可以用code的方式增加可解释性)。打印后,我们可以看到对于模型是这样跟踪的,即模型调用了forward函数,输入类型都为张量,返回值是元组形式,元组中的元素是张量,然后用调用了全连接层的forward函数,传入输入张量后和h相加赋值给new_h,再给激活函数tanh,_0即为返回值。那么这里就是用跟踪模型

print(my_trace.code)

def forward(self,
    input: Tensor,
    h: Tensor) -> Tuple[Tensor, Tensor]:
  new_h = torch.add((self.liner).forward(input, ), h, alpha=1)
  _0 = torch.tanh(new_h)
  return (_0, _0)

使用脚本编译模块 

import torch
import torch.nn as nn

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

x,h = torch.randn(3,4),torch.randn(3,4)
my_cell = MyCell(MyDecisionGate())
traced_cell = torch.jit.trace(my_cell, (x, h))

print(traced_cell.dg.code)
print(traced_cell.code)

在模型跟踪的时候,是调用了两个示例,一个MyCell,一个MyDecisionGate(注意里面有条件语句,一会儿要和输出做对比的)。

在第一次输出调用dg.code的时候,是没有参数参数【即argument_1】

在调用traced_cell.code的时候,trace路径-->MyCell的forward函数,传入两个张量,返回类型是元组,_0是定义的self.dg成员变量,_1是调用了全连接层的forward函数,_2是调用了_0[即我们定义的MyDecisionGate]的forward,将_1结果传入,_2是MyDecisionGate返回结果,再将返回结果和h相加后用tanh激活得到_3变量,最后返回。[把这个过程捋清楚]     注意,再_2这一过程中,是不是没有发现if之类的条件语句,这是怎么回事?跟踪完全按照:运行代码,记录发生的操作,并构造一个可以做到这一点的 ScriptModule。 但是!!诸如控制流之类的东西被抹去了。【即如果我们的网络模型含有这一类的判断语句,会被直接删掉】

def forward(self,
    argument_1: Tensor) -> None:
  return None

def forward(self,
    input: Tensor,
    h: Tensor) -> Tuple[Tensor, Tensor]:
  _0 = self.dg
  _1 = (self.linear).forward(input, )
  _2 = (_0).forward(_1, )
  _3 = torch.tanh(torch.add(_1, h, alpha=1))
  return (_3, _3

但我们有时候是需要将这控制语句也包含在TorchScript 里面的,则我们可以使用脚本编译器(script compiler) 可以直接分析我们的python源码后在转TorchScript 

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


scripted_gate = torch.jit.script(MyDecisionGate())
my_cell = MyCell(scripted_gate)
scripted_cell = torch.jit.script(my_cell)

print(scripted_gate.code)
print(scripted_cell.code)

可以看到用torch.jit.script后【不是用的torch.jit.trace】,可以捕获控制信息了,还有要注意的一点,看最后的返回值是new_h与前面跟踪模型对比,前面的返回值是_0,前者只是名字而言[里面没有具体的值],这是script和trace的一个区别,script只是可以对python语言进行一个解析。

def forward(self,
    x: Tensor) -> Tensor:
  _0 = bool(torch.gt(torch.sum(x, dtype=None), 0))
  if _0:
    _1 = x
  else:
    _1 = torch.neg(x)
  return _1

def forward(self,
    x: Tensor,
    h: Tensor) -> Tuple[Tensor, Tensor]:
  _0 = (self.dg).forward((self.linear).forward(x, ), )
  new_h = torch.tanh(torch.add(_0, h, alpha=1))
  return (new_h, new_h)

我们可以对解析后的再用trace传入具体的参数看看效果,根据输出可以看出,现在不仅有控制语句,还能通过trace进行模型的捕获【如果你的代码里有控制语句,先用script编译一下,再用trace跟踪模型

scripted_gate = torch.jit.script(MyDecisionGate())
x,h = torch.randn(3,4),torch.randn(3,4)
my_cell = MyCell(scripted_gate)
#scripted_cell = torch.jit.script(my_cell)
trace_cell = torch.jit.trace(my_cell,(x,h))

print(scripted_gate.code)
print(trace_cell.code)

def forward(self,
    x: Tensor) -> Tensor:
  _0 = bool(torch.gt(torch.sum(x, dtype=None), 0))
  if _0:
    _1 = x
  else:
    _1 = torch.neg(x)
  return _1

def forward(self,
    input: Tensor,
    h: Tensor) -> Tuple[Tensor, Tensor]:
  _0 = (self.dg).forward((self.linear).forward(input, ), )
  _1 = torch.tanh(torch.add(_0, h, alpha=1))
  return (_1, _1)


然后我们就可以对模型进行保存和加载了,官网上提供的是RNN的保存和加载方式,然后我这里想用VGG16试一下,发现也是可以的,将会生成一个vgg16.zip文件 【其实也可以保存成其他权重格式,比如pt文件,只需要save('vgg16.pt')即可】

import torch
from torchvision.models import vgg16

model = vgg16(pretrained=False)
script_model = torch.jit.script(model)
x = torch.ones(1,3,224,224)
trace_model = torch.jit.trace(script_model,x)
print(trace_model)

trace_model.save('vgg16.zip')

RecursiveScriptModule(
  original_name=VGG
  (features): RecursiveScriptModule(
    original_name=Sequential
    (0): RecursiveScriptModule(original_name=Conv2d)
    (1): RecursiveScriptModule(original_name=ReLU)
    (2): RecursiveScriptModule(original_name=Conv2d)
    (3): RecursiveScriptModule(original_name=ReLU)
    (4): RecursiveScriptModule(original_name=MaxPool2d)
    (5): RecursiveScriptModule(original_name=Conv2d)
    (6): RecursiveScriptModule(original_name=ReLU)
    (7): RecursiveScriptModule(original_name=Conv2d)
    (8): RecursiveScriptModule(original_name=ReLU)
    (9): RecursiveScriptModule(original_name=MaxPool2d)
    (10): RecursiveScriptModule(original_name=Conv2d)
    (11): RecursiveScriptModule(original_name=ReLU)
    (12): RecursiveScriptModule(original_name=Conv2d)
    (13): RecursiveScriptModule(original_name=ReLU)
    (14): RecursiveScriptModule(original_name=Conv2d)
    (15): RecursiveScriptModule(original_name=ReLU)
    (16): RecursiveScriptModule(original_name=MaxPool2d)
    (17): RecursiveScriptModule(original_name=Conv2d)
    (18): RecursiveScriptModule(original_name=ReLU)
    (19): RecursiveScriptModule(original_name=Conv2d)
    (20): RecursiveScriptModule(original_name=ReLU)
    (21): RecursiveScriptModule(original_name=Conv2d)
    (22): RecursiveScriptModule(original_name=ReLU)
    (23): RecursiveScriptModule(original_name=MaxPool2d)
    (24): RecursiveScriptModule(original_name=Conv2d)
    (25): RecursiveScriptModule(original_name=ReLU)
    (26): RecursiveScriptModule(original_name=Conv2d)
    (27): RecursiveScriptModule(original_name=ReLU)
    (28): RecursiveScriptModule(original_name=Conv2d)
    (29): RecursiveScriptModule(original_name=ReLU)
    (30): RecursiveScriptModule(original_name=MaxPool2d)
  )
  (avgpool): RecursiveScriptModule(original_name=AdaptiveAvgPool2d)
  (classifier): RecursiveScriptModule(
    original_name=Sequential
    (0): RecursiveScriptModule(original_name=Linear)
    (1): RecursiveScriptModule(original_name=ReLU)
    (2): RecursiveScriptModule(original_name=Dropout)
    (3): RecursiveScriptModule(original_name=Linear)
    (4): RecursiveScriptModule(original_name=ReLU)
    (5): RecursiveScriptModule(original_name=Dropout)
    (6): RecursiveScriptModule(original_name=Linear)
  )
)
 

使用TorchScript和libtorch进行模型推理[附C++代码]_第1张图片

然后我们可以加载一下这个文件,同时用code输出一下图结构 

loaded = torch.jit.load('vgg16.zip')
print(loaded)
print(loaded.code)

RecursiveScriptModule(
  original_name=VGG
  (features): RecursiveScriptModule(
    original_name=Sequential
    (0): RecursiveScriptModule(original_name=Conv2d)
    (1): RecursiveScriptModule(original_name=ReLU)
    (2): RecursiveScriptModule(original_name=Conv2d)
    (3): RecursiveScriptModule(original_name=ReLU)
    (4): RecursiveScriptModule(original_name=MaxPool2d)
    (5): RecursiveScriptModule(original_name=Conv2d)
    (6): RecursiveScriptModule(original_name=ReLU)
    (7): RecursiveScriptModule(original_name=Conv2d)
    (8): RecursiveScriptModule(original_name=ReLU)
    (9): RecursiveScriptModule(original_name=MaxPool2d)
    (10): RecursiveScriptModule(original_name=Conv2d)
    (11): RecursiveScriptModule(original_name=ReLU)
    (12): RecursiveScriptModule(original_name=Conv2d)
    (13): RecursiveScriptModule(original_name=ReLU)
    (14): RecursiveScriptModule(original_name=Conv2d)
    (15): RecursiveScriptModule(original_name=ReLU)
    (16): RecursiveScriptModule(original_name=MaxPool2d)
    (17): RecursiveScriptModule(original_name=Conv2d)
    (18): RecursiveScriptModule(original_name=ReLU)
    (19): RecursiveScriptModule(original_name=Conv2d)
    (20): RecursiveScriptModule(original_name=ReLU)
    (21): RecursiveScriptModule(original_name=Conv2d)
    (22): RecursiveScriptModule(original_name=ReLU)
    (23): RecursiveScriptModule(original_name=MaxPool2d)
    (24): RecursiveScriptModule(original_name=Conv2d)
    (25): RecursiveScriptModule(original_name=ReLU)
    (26): RecursiveScriptModule(original_name=Conv2d)
    (27): RecursiveScriptModule(original_name=ReLU)
    (28): RecursiveScriptModule(original_name=Conv2d)
    (29): RecursiveScriptModule(original_name=ReLU)
    (30): RecursiveScriptModule(original_name=MaxPool2d)
  )
  (avgpool): RecursiveScriptModule(original_name=AdaptiveAvgPool2d)
  (classifier): RecursiveScriptModule(
    original_name=Sequential
    (0): RecursiveScriptModule(original_name=Linear)
    (1): RecursiveScriptModule(original_name=ReLU)
    (2): RecursiveScriptModule(original_name=Dropout)
    (3): RecursiveScriptModule(original_name=Linear)
    (4): RecursiveScriptModule(original_name=ReLU)
    (5): RecursiveScriptModule(original_name=Dropout)
    (6): RecursiveScriptModule(original_name=Linear)
  )
)

下面是用code的输出
def forward(self,
    x: Tensor) -> Tensor:
  x0 = (self.features).forward(x, )
  x1 = (self.avgpool).forward(x0, )
  x2 = torch.flatten(x1, 1, -1)
  return (self.classifier).forward(x2, )
 

我们可以进入vgg16的官方代码看看对不对,可以看到在VGG16 forward()函数中如下所示,发现和我们用code获取的结果是一致的。

    def forward(self, x):
        x = self.features(x)
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x

保存以后,以后可以用C++调用 


C++调用模型

需要下载libtorch。

将libtorch/lib和libtorch/bin路径放在你电脑的环境变量中

新建C++项目

项目》属性》C/C++目录》附加包含库目录添加include  配置torch头文件路径

我这里是E:\libtorch\include\torch\csrc\api\include和E:\libtorch\include

这两个include分别是导入torch.h和script.h

使用TorchScript和libtorch进行模型推理[附C++代码]_第2张图片

设置链接库

链接器>输入>附加依赖项 ,添加所需要的lib文件

使用TorchScript和libtorch进行模型推理[附C++代码]_第3张图片

使用TorchScript和libtorch进行模型推理[附C++代码]_第4张图片

所需要的lib文件如下【这里的opencv是我以前的添加的,后面也会用到】 (因为我是在Debug模式下,所以lib文件后面都是有d的,如果你的是在release下就没有加d这种文件) 

opencv_world410d.lib
c10.lib
libprotobufd.lib
libprotobuf-lited.lib
libprotocd.lib
mkldnn.lib
torch.lib
torch_cpu.lib
torch_cuda.lib

链接器>常规>附加库目录,将libtorch中的lib路径填入

使用TorchScript和libtorch进行模型推理[附C++代码]_第5张图片

将libtorch>lib下的所有dll文件复制到你项目x64/Debug/下,即和你的exe同级文件下【同样,注意你的libtorch是Debug还是release】【如果没有这一步,你可能会遇到无法定位程序输入点。。于动态库链接..exe


接下来可以测试一下

#include 
#include
#include

int main()
{
	torch::Tensor output = torch::randn({ 3,4 });
	std::cout << output << std::endl;
	system("pause");
	return 0;
}

 -0.4466 -0.6320 -0.3715  1.0210
-2.1860  1.1954 -0.1509  0.0185
-2.4954  0.9098 -1.1754 -1.0180
[ CPUFloatType{3,4} ]
请按任意键继续. . .

可以输出一个3行4列的张量


 然后我们再看看能不能用cuda【如果你的不能用cuda,情况报错问题>问题4

#include 
#include
#include

void IsCuda()
{
	if (torch::cuda::is_available())
	{
		std::cout << "支持GPU" << std::endl;
	}
	else
	{
		std::cout << "不支持GPU" << std::endl;
	}
};

int main()
{
	IsCuda();
	system("pause");
	return 0;
}

 支持GPU
请按任意键继续. . . 


 然后我们可以加载一下上面我们转换的模型

// Myrenet.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include 
#include
#include

void IsCuda()
{
	if (torch::cuda::is_available())
	{
		std::cout << "支持GPU" << std::endl;
	}
	else
	{
		std::cout << "不支持GPU" << std::endl;
	}
};

int main()
{
	IsCuda();
	torch::jit::script::Module module;
	try {
		// Deserialize the ScriptModule from a file using torch::jit::load().
		module = torch::jit::load("E:\\模型部署学习\\Learning_TorchScript\\vgg16.pt");
	}
	catch (const c10::Error& e) {
		std::cerr << "error loading the model\n";
		return -1;
	}
	std::cout << "模型加载成功" << std::endl;
	
	system("pause");
	return 0;
}


支持GPU
模型加载成功
请按任意键继续. . .

如果你的模型无法加载,看报错问题>问题5 


 VGG16猫狗分类

接下来用就可以利用libtorch进行预测了,进行前向推理主要会调用forward()函数,tensor_image是进过预处理后的图像:

module.eval();
//进行推理
torch::Tensor output = module.forward({ tensor_image }).toTensor();

然后我利用pytorch  VGG16训练了一个猫狗分类【训练完以后和上面一样,把模型进行转换】

#include 
#include
#include
#include
#include
#include
#include


void IsCuda()
{
	if (torch::cuda::is_available())
	{
		std::cout << "支持GPU" << std::endl;
	}
	else
	{
		std::cout << "不支持GPU" << std::endl;
	}
};


int main()
{	
	std::string classes_names[] = { "cat","dog" };
	
	//IsCuda();
	torch::jit::script::Module module;
	try {
		// Deserialize the ScriptModule from a file using torch::jit::load().
		module = torch::jit::load("E:\\模型部署学习\\Learning_TorchScript\\vgg16dog.pt");
	}
	catch (const c10::Error& e) {
		std::cerr << "error loading the model\n";
		return -1;
	}
	std::cout << "模型加载成功" << std::endl;
	torch::DeviceType device_type;
	device_type = torch::kCUDA;
	torch::Device device(device_type);
	module.to(device);

	std::string image_path = "E:\\模型部署学习\\Learning_TorchScript\\cat.jpg";
	cv::Mat image = cv::imread(image_path);
	cv::Mat input;
	cv::resize(image, image, cv::Size(224, 224));
	cv::cvtColor(image, input, cv::COLOR_BGR2RGB);
	//from_blob Mat转Tensor {batchsize,w,h,channles}
	torch::Tensor tensor_image = torch::from_blob(input.data, { 1,input.rows, input.cols,3 }, torch::kByte);

	//shape->(batchsize,channles,w,h)
	tensor_image = tensor_image.permute({ 0,3,1,2 });
	tensor_image = tensor_image.toType(torch::kFloat);
	//image/255.0图像的归一化处理
	tensor_image = tensor_image.div(255);

	//tensor_image[0]指的是取出第一个batch 然后对每个通道进行处理
	tensor_image[0][0] = tensor_image[0][0].sub_(0.4914).div_(0.2023);
	tensor_image[0][1] = tensor_image[0][1].sub_(0.4822).div_(0.1994);
	tensor_image[0][2] = tensor_image[0][2].sub_(0.4465).div_(0.2010);

	//将处理后的tensor送入cuda
	tensor_image = tensor_image.to(device);
	
	module.eval();
	//进行推理
	torch::Tensor output = module.forward({ tensor_image }).toTensor();
	//可以获得输出的shape output的shape是[batchsize,num_classes]
	//std::cout << "输出的shape:"<< output.sizes() << std::endl;
	//output.view(-1) shape=num_classes
	auto tmp = output.view(-1);
	auto pred = torch::softmax(tmp, -1);
	
	int res = torch::argmax(pred).item();
	std::cout << "概率值为 :" << (pred[res]*100) << std::endl;
	std::cout << "预测的类为 :" << classes_names[res] << std::endl;
	cv::Point origin;
	origin.x = 5;
	origin.y = 20;
	cv::putText(image, classes_names[res],origin, cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(0, 255, 255),2);
	
	cv::namedWindow("预测结果图", CV_WINDOW_NORMAL);
	cv::imshow("预测结果图",image);
    cv::imwrite("分类结果.jpg", image);
	cv::waitKey(0);
	system("pause");
	return 0;
}

 使用TorchScript和libtorch进行模型推理[附C++代码]_第6张图片

使用TorchScript和libtorch进行模型推理[附C++代码]_第7张图片


报错问题 

1.问题:无法定位程序输入点。。于动态库链接..exe

解决办法:

将libtorch>lib下的所有dll文件复制到你项目x64/Debug/下,即和你的exe同级文件下【同样,注意你的libtorch是Debug还是release】

2.问题:std不明确的符合

解决办法:在C/C++>语言>符合模式改为否

3.问题:找不到xx.dll

解决办法:项目属性>调试>环境  中把包含dll的lib路径添加进去

如果你是遇到的VCRUNTIME140_1D.dll无法找到,可以去c:/windows/System32/下找找有没有这个文件,如果没有,但你看到有一个vcruntime140_1.dll,你可以把这个文件复制一份,然后改名为vcruntime140_1D.dll

使用TorchScript和libtorch进行模型推理[附C++代码]_第8张图片

4.问题:libtorch无法使用GPU

解决办法:如果确定libtorch下载版本正确且是cuda版,同时在pytorch环境下可以使用cuda,但libtorch无法使用。在项目属性中>链接器>命令行  在右侧【其他选项下方】填入:

/INCLUDE:?warp_size@cuda@at@@YAHXZ 

 使用TorchScript和libtorch进行模型推理[附C++代码]_第9张图片

5.问题:无法加载模型

解决办法:通过torch::jit::load()无法加载模型,先检测一下你的模型路径对不对,可以改成绝对路径。如果发现路径对,pytorch中可以用torch.jit.load加载,那么检查一下你的pytorch和libtorch版本是不是对应,比如我用的pytorch是1.7.0,所以libtorch也应该是1.7.0 【之前我试过别的版本,就发现不能加载模型,然后把版本对应以后就可以了】 


如果大家在实践过程中遇到了什么报错并解决了的话,可在评论区写出来,方便大家的一起学习

你可能感兴趣的:(libtorch,c++,分类,计算机视觉)