PyTorch分类网络:Python训练_测试_模型转换 && Windows_LibTorch_C++部署

  • Post author:
  • Post category:python


第一部分 前言

众位小伙伴,好久没更新博客了,本次为大家带来:如何将PyTorch训练的网络通过模型转换,部署到Windows

纯C++

下执行,落地应用。这里并没有将PyTorch模型转至其他深度学习框架下,而是通过PyTorch的

LibTorch

来完成相关C++的部署应用。

第二部分 说明


PyTorch版本:Torch-1.4.0-cu101


LibTorch版本:LibTorch-1.4.0-cu101


Anaconda版本:Anaconda3-Python3.6


GPU:GTX1080


VS版本:VS2017(用于编译LibTorch)

第三部分 开源链接


GITHUB

第四部分 训练数据



kaggle猫狗大战

数据集为例,数据格式如下:

1、训练数据路径:data/train/cat/*.jpg,data/train/dog/*.jpg

2、验证数据路径:data/val/cat/*.jpg,data/val/dog/*.jpg

第五部分 训练代码

from __future__ import print_function, division

import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
from torchvision import datasets, models, transforms

import time
import os
import copy

def train_model(model, criterion, optimizer, scheduler, num_epochs=25):
    since = time.time()

    best_model_wts = copy.deepcopy(model.state_dict())
    best_acc = 0.0

    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs - 1))
        print('learning rate: {}'.format(scheduler.get_lr()[0]))
        print('-' * 10)

        # Each epoch has a training and validation phase
        for phase in ['train', 'val']:
            if phase == 'train':
                model.train()  # Set model to training mode
            else:
                model.eval()   # Set model to evaluate mode

            running_loss = 0.0
            running_corrects = 0

            # Iterate over data.
            for inputs, labels in dataloaders[phase]:
                inputs = inputs.to(device)
                labels = labels.to(device)

                # zero the parameter gradients
                optimizer.zero_grad()

                # forward
                # track history if only in train
                with torch.set_grad_enabled(phase == 'train'):
                    outputs = model(inputs)
                    _, preds = torch.max(outputs, 1)
                    loss = criterion(outputs, labels)

                    # backward + optimize only if in training phase
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()

                # statistics
                running_loss += loss.item() * inputs.size(0)
                running_corrects += torch.sum(preds == labels.data)

            epoch_loss = running_loss / dataset_sizes[phase]
            epoch_acc = running_corrects.double() / dataset_sizes[phase]

            print('{} Loss: {:.4f} Acc: {:.4f}'.format(
                phase, epoch_loss, epoch_acc))

            # deep copy the model
            if phase == 'val' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = copy.deepcopy(model.state_dict())
                
            if phase == 'train':
                scheduler.step()

    time_elapsed = time.time() - since
    print('Training complete in {:.0f}m {:.0f}s'.format(
        time_elapsed // 60, time_elapsed % 60))
    print('Best val Acc: {:4f}'.format(best_acc))

    # load best model weights
    model.load_state_dict(best_model_wts)
    return model

# Data augmentation and normalization for training
# Just normalization for validation
data_transforms = {
    'train': transforms.Compose([
        transforms.RandomResizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
    'val': transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
}

data_dir = 'data'
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),data_transforms[x]) for x in ['train', 'val']}
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=4,shuffle=True, num_workers=0) for x in ['train', 'val']}
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}
class_names = image_datasets['train'].classes

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

# model_conv = torchvision.models.resnet18(pretrained=False)
# print(model_conv)
# for param in model_conv.parameters():
#     param.requires_grad = False

model_ft = models.resnet18(pretrained=True)
num_ftrs = model_ft.fc.in_features
model_ft.fc = nn.Linear(num_ftrs, 2)

model_ft = model_ft.to(device)

criterion = nn.CrossEntropyLoss()

# Observe that all parameters are being optimized
optimizer_ft = optim.SGD(model_ft.parameters(), lr=0.001, momentum=0.9)

# Decay LR by a factor of 0.1 every 7 epochs
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)
model_ft = train_model(model_ft, criterion, optimizer_ft, exp_lr_scheduler, num_epochs=25)

torch.save(model_ft, 'model.pkl')

第六部分 测试代码

from __future__ import print_function, division

import torch
import torch.nn.functional as F
from torchvision import transforms

from PIL import Image
import os

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

# load model
model = torch.load('model.pkl')
model = model.to(device)
model.eval()

classes = ['cat','dog']

test_path = "data/val/"
true_count = 0
all_count = 0

for test_dir in os.listdir(test_path):
    test_dir_path = test_path + test_dir + "/"
    for img_names in os.walk(test_dir_path):
        for img_name in img_names[2]:
            img_path = test_dir_path + img_name
            print(img_path)
    
            image = Image.open(img_path)
            transform = transforms.Compose([
                    transforms.Resize(256),
                    transforms.CenterCrop(224),
                    transforms.ToTensor(),
                    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
                ])
            image_transformed = transform(image)
            image_transformed = image_transformed.unsqueeze(0)
            
            # forward
            output = model(image_transformed.to(device))

            output = F.softmax(output, dim=1)
            predict_value, predict_idx = torch.max(output, 1)
            
            if(classes[predict_idx.cpu().data[0].numpy()] == test_dir):
                true_count += 1
            
            all_count += 1
        
print("acc: {}/{}={}".format(true_count,all_count,float(true_count)/float(all_count)))
#acc: 1966/2000=0.983

第七部分 模型转换代码

"""
This python script converts the network into Script Module---CPU
"""
import torch

# Download and load the pre-trained model
model = torch.load("model.pkl",map_location='cpu')

model.eval()

example_input = torch.rand(1, 3, 224, 224)
script_module = torch.jit.trace(model, example_input)
script_module.save('model_cpu.pt')

#"""
#This python script converts the network into Script Module---GPU
#"""
#import torch
#
#device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
#
## Download and load the pre-trained model
#model = torch.load("model.pkl")
#
#model.eval()
#
#example_input = torch.rand(1, 3, 224, 224)
#script_module = torch.jit.trace(model, example_input.to(device))
#script_module.save('model_gpu.pt')

第八部分 LibTorch_C++实现代码

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

#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

Mat pilResize(Mat &img, int size) {
	int imgWidth = img.cols;
	int imgHeight = img.rows;
	if ((imgWidth <= imgHeight && imgWidth == size) || (imgHeight <= imgWidth && imgHeight == size)) {
		return img;
	}
	Mat output;
	if (imgWidth < imgHeight) {
		int outWidth = size;
		int outHeight = int(size * imgHeight / (float)imgWidth);
		resize(img, output, Size(outWidth, outHeight));
	}
	else {
		int outHeight = size;
		int outWidth = int(size * imgWidth / (float)imgHeight);
		resize(img, output, Size(outWidth, outHeight));
	}

	return output;
}

Mat pilCropCenter(Mat &img, int output_size) {
	Rect imgRect;
	imgRect.x = int(round((img.cols - output_size) / 2.));
	imgRect.y = int(round((img.rows - output_size) / 2.));
	imgRect.width = output_size;
	imgRect.height = output_size;

	return img(imgRect).clone();
}

Mat setNorm(Mat &img) {
	Mat img_rgb;
	cvtColor(img, img_rgb, COLOR_RGB2BGR);

	Mat img_resize = pilResize(img_rgb, 256);
	Mat img_crop = pilCropCenter(img_resize, 224);

	Mat image_resized_float;
	img_crop.convertTo(image_resized_float, CV_32F, 1.0 / 255.0);

	return image_resized_float;
}

Mat setMean(Mat &image_resized_float) {
	vector<float> mean = { 0.485, 0.456, 0.406 };
	vector<float> std = { 0.229, 0.224, 0.225 };

	vector<Mat> image_resized_split;
	split(image_resized_float, image_resized_split);
	for (int ch = 0; ch < image_resized_split.size(); ch++) {
		image_resized_split[ch] -= mean[ch];
		image_resized_split[ch] /= std[ch];
	}
	Mat image_resized_merge;
	merge(image_resized_split, image_resized_merge);

	return image_resized_merge;
}

int main() {
	torch::DeviceType device_type;
	if (torch::cuda::is_available()) {
		std::cout << "CUDA available! Test on GPU." << std::endl;
		device_type = torch::kCUDA;
	}
	else {
		std::cout << "Test on CPU." << std::endl;
		device_type = torch::kCPU;
	}
	torch::Device device(device_type);

	// Deserialize the ScriptModule from a file using torch::jit::load().
	torch::jit::script::Module model = torch::jit::load("model_cpu.pt");
	model.to(device);

	vector<string> classes = { "cat","dog" };

	string test_path = "val/dog/";
	vector<string> img_paths;
	glob(test_path, img_paths);

	int truth_count = 0;

	for (int i = 0; i < img_paths.size(); i++) {
		Mat img = imread(img_paths[i]);

		clock_t start_t = clock();

		//norm
		Mat image_resized_float = setNorm(img);
		//mean
		Mat image_resized_merge = setMean(image_resized_float);

		auto img_tensor = torch::from_blob(image_resized_merge.data, { 224, 224, 3 }, torch::kFloat32);
		auto img_tensor_ = torch::unsqueeze(img_tensor, 0);
		img_tensor_ = img_tensor_.permute({ 0, 3, 1, 2 });

		// Create a vector of inputs.
		vector<torch::jit::IValue> inputs;
		inputs.push_back(img_tensor_.to(device));

		torch::Tensor prob = model.forward(inputs).toTensor();
		torch::Tensor output = torch::softmax(prob, 1);
		auto predict = torch::max(output, 1);

		//cout << "cost time:" << clock() - start_t << endl;

		cout << img_paths[i] << "\t";
		cout << "class: " << classes[get<1>(predict).item<int>()] <<
			", prob: " << get<0>(predict).item<float>() << endl;

		if (get<1>(predict).item<int>() == 1) {
			truth_count++;
		}
	}

	cout << truth_count << "/" << img_paths.size() << endl;
	system("pause");

	return 0;
}

第九部分 相关说明

1、Pytorch默认通过

PIL

载入图像数据,这点很重要!

2、需要载入

ResNet

预训练模型,否则训练效果较差!

3、PyTorch训练出来的模型格式为pkl,需要将其转换为pt格式,C++方能采用

torch::jit::load

方式载入。

4、转换时,有CPU和GPU两种方式,C++实现时可通过两种方式载入。


5、C++实现时,需将PIL格式的图像转换为OpenCV的图像,否则数据不统一,导致测试结果不正确!


任何问题请加

唯一

QQ

2258205918

(名称

samylee

)!




唯一

VX:samylee_csdn



版权声明:本文为samylee原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。