基于Windows平台在C++中调⽤Pytorch模型并实现MFC集成
(以MNIST⼿写体。。。
本教程将完整的记录使⽤pytorch从模型训练到模型调⽤(基于Python),再通过libtorch转成C++调⽤(基于win32 C++控制台程序),最终集成到MFC程序中来,这样就可以完整的在Windows下⾛通 AI 算法建模到⽣产级部署的全部流程。
基本配置环境如下:
Python版本:Python 3.6.1
Pytorch版本:1.2.0
Libtorch:1.3
操作系统:Win10
编译器:VS 2015
1.算法—MNIST⼿写体数字识别
MNIST是⾮常有名的⼿写体数字识别数据集,⾮常适合⽤来实战讲解,因此我们也以此作为项⽬的开始,通过Pytorch建⽴算法来识别图像中的数字。MNIST由⼿写体数字的图⽚和相对应的标签组成,如下图所⽰:
Pytorch的torchvision库中已经⾃带了该数据集,可以直接下载和导⼊。
下⾯给出完整的训练代码train.py:
import argparse
import torch
as nn
functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import PIL
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(320, 50)
self.fc2 = nn.Linear(50, 10)
def forward(self, x):
v1(x)
x = F.relu(F.max_pool2d(x, 2))
v2(x)
x = F.relu(F.max_v2_drop(x), 2))
x = x.view(-1, 320)
x = F.relu(self.fc1(x))
x = F.dropout(x, aining)
x = self.fc2(x)
return F.log_softmax(x, dim=1)
def train(args, model, device, train_loader, optimizer, epoch):
for batch_idx, (data, target) in enumerate(train_loader):
data, target = (device), (device)
data, target = (device), (device)
<_grad()
output = model(data)
loss = F.nll_loss(output, target)
loss.backward()
optimizer.step()
if batch_idx % args.log_interval == 0:
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, batch_idx * len(data), len(train_loader.dataset),
100. * batch_idx / len(train_loader), loss.item()))
def test(args, model, device, test_loader):
model.eval()
test_loss = 0
correct = 0
_grad():
for data, target in test_loader:
data, target = (device), (device)
output = model(data)
test_loss += F.nll_loss(output, target, reduction='sum').item() # sum up batch loss            pred = output.max(1, keepdim=True)[1] # get the index of the max log-probability            correct += pred.eq(target.view_as(pred)).sum().item()
test_loss /= len(test_loader.dataset)
print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
test_loss, correct, len(test_loader.dataset),
100. * correct / len(test_loader.dataset)))
def main():
# ⽤于训练的超参数设置
parser = argparse.ArgumentParser(description='PyTorch MNIST样例')
parser.add_argument('--batch-size', type=int, default=64,
help='input batch size for training (default: 64)')
parser.add_argument('--test-batch-size', type=int, default=1000,
help='input batch size for testing (default: 1000)')
parser.add_argument('--epochs', type=int, default=10,
help='number of epochs to train (default: 10)')
parser.add_argument('--lr', type=float, default=0.01,
help='learning rate (default: 0.01)')
parser.add_argument('--momentum', type=float, default=0.5,
help='SGD momentum (default: 0.5)')
parser.add_argument('--no-cuda', action='store_true', default=False,
help='disables CUDA training')
parser.add_argument('--seed', type=int, default=1,
help='random seed (default: 1)')
parser.add_argument('--log-interval', type=int, default=10,
help='how many batches to wait before logging training status')
windows开发平台args = parser.parse_args()
use_cuda = _cuda and torch.cuda.is_available() # 判断是否使⽤GPU训练    print(use_cuda)
torch.manual_seed(args.seed) # 固定住随机种⼦,使训练结果可复现
device = torch.device("cuda" if use_cuda else "cpu")
kwargs = {'num_workers': 8, 'pin_memory': True} if use_cuda else {}
# 加载训练数据
train_loader = torch.utils.data.DataLoader(
datasets.MNIST('data', train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=args.batch_size, shuffle=True, **kwargs)
#加载测试数据
test_loader = torch.utils.data.DataLoader(
datasets.MNIST('data', train=False, transform=transforms.Compose([
datasets.MNIST('data', train=False, transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_st_batch_size, shuffle=True, **kwargs)
model = Net().to(device)
optimizer = optim.SGD(model.parameters(), lr=args.lr, um)
for epoch in range(1, args.epochs + 1):
train(args, model, device, train_loader, optimizer, epoch)
test(args, model, device, test_loader)
torch.save(model, "model.pth")# 保存模型参数
if __name__ == '__main__':
import time
start = time.time()
main()
end = time.time()
running_time = end-start
print('time cost : %.5f 秒' %running_time)
上述代码采⽤的模型架构⽐较简单,只⽤了2个卷积层和2个全连接层,在我的GTX 1080TI上跑了77秒,检测精度达到99%。CPU上跑了311秒,检测精度98%。训练完成后⾃动保存当前模型参数为model.pth⽂件。
接下来使⽤该模型参数⽂件进⾏单张图⽚预测,完整代码⽂件test.py:
import torch
import cv2
functional as F
from train import Net
from torchvision import datasets, transforms
from PIL import Image
if __name__ == '__main__':
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = torch.load('model.pth')  # 加载模型
model = (device)
model.eval()  # 把模型转为test模式
img = cv2.imread("img_3.jpg",0)  # 读取要预测的灰度图⽚
img = Image.fromarray(img)
trans = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
img = trans(img)
img = img.unsqueeze(0)  # 图⽚扩展多⼀维,[batch_size,通道,长,宽],此时batch_size=1
img = (device)
output = model(img)
pred = output.max(1, keepdim=True)[1]
pred = torch.squeeze(pred)
print('检测结果为:%d' % (pred.cpu().numpy()))
预测结果如下:
输⼊图⽚:            输出:2            预测正确。
到这⾥已经完成算法建模部分。接下来,需要将这个算法部署到⽣产级的环境中,也就是我们这⾥需要的C++环境。⼀般的⽣产环境不会采⽤Python(考虑速度和稳定性等因素),尤其是边缘计算等设备,因此需要在C++中实现模型的调⽤和推理。
2. C++调⽤—使⽤libtorch导出序列化模型
从Pytorch-1.0开始其最瞩⽬的功能就是⽣产的⼤⼒⽀持,推出了C++版本的⽣态端,包括C++前端和C++模型编译⼯具。对于我们来说,在想要部署深度学习应⽤的时候,只需要在Python端利⽤Pytorch进⾏训练,然后使⽤torch.jit导出我们训练好的模型,再利⽤
C++端的Pytorch接⼝读取进⾏预测即可。由于这种⽅式是官⽅推荐和⽀持的,因此,相⽐于其它⽅式这种模式更加稳定可靠。官⽅已经替我们编译好Windows版本的libtorch,这下就节省了我们编译Pytorch的时间,可以直接拿来使⽤,只要稍微配置⼀下就可以在Windows 上运⾏libtorch了。
2.1导出序列化模型(pth转pt)
⾸先我们需要将刚才的算法模型进⾏序列化导出,基本原理与测试单张图⽚类似,输⼊⼀个样例图⽚,然后利⽤训练好的模型对图⽚进⾏⼀次推理。稍微不同的就是,才推理的过程中使⽤了torch.jit函数来记录了整个推理的⼀个路径流,最后由torch.jit来保存这个路径流。完整代码如下:
import torch
import cv2
functional as F
from train import Net
from torchvision import datasets, transforms
from PIL import Image
if __name__ == '__main__':
device = torch.device('cpu') #使⽤cpu进⾏推理
model = torch.load('model.pth')  # 加载模型
model = (device)
model.eval()  # 把模型转为test模式
img = cv2.imread("img.jpg",0)  # 读取要预测的灰度图⽚
img = Image.fromarray(img)
trans = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
img = trans(img)
img = img.unsqueeze(0)  # 图⽚扩展多⼀维,[batch_size,通道,长,宽]
img = (device)
traced_net = ace(model,img)
traced_net.save("model.pt")
print("模型序列化导出成功")
这⾥需要注意,由于我们后⾯安装的libtorch是cpu版本的,因此,在序列化过程中只使⽤cpu进⾏推理,否则序列化出来的模型后⾯
C++没办法正确读取。
2.2安装opencv
下载完成后双击安装包进⾏解压,解压到某个固定⽬录。
下⾯打开VS2015(或者VS2017、VS2019都可以),新建⼀个win32控制台应⽤程序mnist。
在创建项⽬时去掉预编译头和安全开发⽣命周期SDL检查复选框:
最后单击“完成”实现项⽬创建。由于libtorch只能在64位windows上运⾏,因此我们需要修改项⽬为release x64,如下图所⽰:
后⾯所有的项⽬配置都按照Release x64来配置,⾄于调试版的Debug x64可以按照这个教程⼀样的配置即可。
⾸先⽣成⼀下项⽬,依次单击“⽣成”—“⽣成解决⽅案”,这样可以在项⽬下⽣成⼀个x64/release⽂件夹,
该⽂件夹下⽣成了对应的可执⾏程序(尽管我们还⼀⾏代码没有编写)。
然后我们在前⾯下载的opencv中⽬录到opencv\build\x64\vc14\bin⽂件夹,将bin⽂件夹中的opencv_videoio_ffmpeg411_64.dll 和opencv_world411.dll(如果是debug版本则复制opencv_world411d.dll)⽂件到刚才项⽬⽣成的x64/release⽂件夹下⾯。
接下来在项⽬中配置Opencv。在vs2015菜单栏中依次选择“项⽬”——“mnist属性”,然后单击左侧“VC++⽬录”,添加相关路径,包含⽬录中添加:
E:\toolplace\opencv4.1.1\opencv\build\include
E:\toolplace\opencv4.1.1\opencv\build\include\opencv2

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。