模型部署方式有很多,libtorch和TorchScript也是部署的一种方式,由pytorch官方提供
将pytorch模型转换为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 Nonedef 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 _1def 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 _1def 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)
)
)
然后我们可以加载一下这个文件,同时用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
设置链接库
链接器>输入>附加依赖项 ,添加所需要的lib文件
所需要的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路径填入
将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
接下来用就可以利用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;
}
解决办法:
将libtorch>lib下的所有dll文件复制到你项目x64/Debug/下,即和你的exe同级文件下【同样,注意你的libtorch是Debug还是release】
解决办法:在C/C++>语言>符合模式改为否
解决办法:项目属性>调试>环境 中把包含dll的lib路径添加进去
如果你是遇到的VCRUNTIME140_1D.dll无法找到,可以去c:/windows/System32/下找找有没有这个文件,如果没有,但你看到有一个vcruntime140_1.dll,你可以把这个文件复制一份,然后改名为vcruntime140_1D.dll
解决办法:如果确定libtorch下载版本正确且是cuda版,同时在pytorch环境下可以使用cuda,但libtorch无法使用。在项目属性中>链接器>命令行 在右侧【其他选项下方】填入:
/INCLUDE:?warp_size@cuda@at@@YAHXZ
解决办法:通过torch::jit::load()无法加载模型,先检测一下你的模型路径对不对,可以改成绝对路径。如果发现路径对,pytorch中可以用torch.jit.load加载,那么检查一下你的pytorch和libtorch版本是不是对应,比如我用的pytorch是1.7.0,所以libtorch也应该是1.7.0 【之前我试过别的版本,就发现不能加载模型,然后把版本对应以后就可以了】
如果大家在实践过程中遇到了什么报错并解决了的话,可在评论区写出来,方便大家的一起学习