C++ LibTorch (PyTorch C++ API):深度学习模型推理与训练

各位观众老爷们,大家好!欢迎来到今天的“LibTorch深度历险记”讲座。今天咱们不讲虚的,直接上干货,用C++ LibTorch来玩转深度学习,让你的代码像开了挂一样,嗖嗖的!

开篇:为啥要用LibTorch?Python它不香吗?

Python确实香,简单易上手,生态丰富。但有些时候,Python的性能就有点捉襟见肘了。比如,你需要在嵌入式设备上跑模型,或者对延迟有极致要求,又或者你想把深度学习集成到一个大型的C++项目中。这时候,LibTorch就派上用场了。

LibTorch,简单来说,就是PyTorch的C++ API。它让你能在C++环境中直接加载、推理和训练PyTorch模型,性能杠杠的!

第一章:环境搭建,万事开头难?No!

工欲善其事,必先利其器。咱们先来搭建LibTorch的开发环境。

  1. 下载LibTorch: 打开PyTorch官网 (pytorch.org),找到LibTorch那一栏,根据你的操作系统和CUDA版本,下载对应版本的LibTorch包。
  2. 解压: 把下载的包解压到你喜欢的地方,比如 ~/libtorch
  3. 配置CMake: 接下来,我们需要用CMake来构建我们的C++项目。CMakeLists.txt 才是关键!

下面是一个简单的CMakeLists.txt示例:

cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
project(LibTorchExample)

# 设置C++标准
set(CMAKE_CXX_STANDARD 14)

# LibTorch的路径,替换成你自己的
set(Torch_DIR "/path/to/libtorch") # 例如: /home/user/libtorch

find_package(Torch REQUIRED)
if(NOT Torch_FOUND)
    message(FATAL_ERROR "Could not find Torch")
endif()

add_executable(example main.cpp)
target_link_libraries(example "${TORCH_LIBRARIES}")
set_property(TARGET example PROPERTY CXX_STANDARD 14)

message(STATUS "Torch_VERSION: " ${Torch_VERSION})
message(STATUS "TORCH_LIBRARIES: " ${TORCH_LIBRARIES})
  • Torch_DIR: 这里要替换成你解压的LibTorch的路径。
  • find_package(Torch REQUIRED): 这句告诉CMake去找到LibTorch。
  • target_link_libraries: 这句把LibTorch的库链接到你的可执行文件。

第二章:加载模型,让你的C++程序变得聪明

有了环境,接下来咱们要加载一个训练好的PyTorch模型。通常,我们会先用Python训练一个模型,然后保存成.pt或者.pth文件。

  1. Python模型导出: 假设你已经用Python训练好了一个模型,现在要把它导出。

    import torch
    import torch.nn as nn
    
    # 定义一个简单的模型
    class MyModel(nn.Module):
        def __init__(self):
            super(MyModel, self).__init__()
            self.linear = nn.Linear(10, 5)
    
        def forward(self, x):
            return self.linear(x)
    
    model = MyModel()
    
    # 保存模型
    torch.save(model.state_dict(), 'model.pth') # 只保存模型参数,更轻量
    # 或者保存整个模型
    # torch.save(model, 'model.pt')
  2. C++加载模型: 在C++中,我们需要用 torch::jit::load() 函数来加载模型。

    #include <torch/script.h> // One-stop header.
    #include <iostream>
    #include <memory>
    
    int main() {
        torch::jit::Module module;
        try {
            // 加载模型,替换成你的模型路径
            module = torch::jit::load("model.pth"); // 或者 model.pt
        } catch (const c10::Error& e) {
            std::cerr << "error loading the modeln";
            return -1;
        }
    
        std::cout << "Model loaded successfully!n";
    
        return 0;
    }
    • torch::jit::load(): 加载模型的核心函数。
    • torch::jit::Module: 加载的模型会变成一个 torch::jit::Module 对象,你可以用它来进行推理。

第三章:模型推理,让你的C++程序会思考

模型加载好了,接下来就是让模型跑起来,也就是模型推理。

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

int main() {
    torch::jit::Module module;
    try {
        // 加载模型
        module = torch::jit::load("model.pth");
    } catch (const c10::Error& e) {
        std::cerr << "error loading the modeln";
        return -1;
    }

    std::cout << "Model loaded successfully!n";

    // 创建一个输入张量
    torch::Tensor input = torch::randn({1, 10}); // 1个batch,10个特征

    // 推理
    at::Tensor output = module.forward({input}).toTensor();

    // 打印输出
    std::cout << "Output:n" << output << "n";

    return 0;
}
  • torch::randn(): 创建一个随机的输入张量。
  • module.forward(): 调用模型的前向传播函数,进行推理。
  • toTensor(): 将IValue类型转换为Tensor类型
  • at::Tensor: at::Tensor 是LibTorch中表示张量的数据类型。

第四章:模型训练,让你的C++程序更聪明

LibTorch不仅可以用来推理,还可以用来训练模型。虽然用C++训练模型不如Python方便,但有些场景下,你可能需要在C++中进行模型的微调或者迁移学习。

#include <torch/torch.h>
#include <iostream>

int main() {
    // 定义一个简单的线性模型
    struct Net : torch::nn::Module {
        Net() : linear(torch::nn::Linear(1, 1)) {
            register_module("linear", linear);
        }
        torch::Tensor forward(torch::Tensor x) {
            return linear->forward(x);
        }
        torch::nn::Linear linear;
    };

    // 创建模型
    auto net = std::make_shared<Net>();

    // 定义优化器
    torch::optim::SGD optimizer(net->parameters(), 0.01);

    // 训练数据
    torch::Tensor x = torch::randn({100, 1});
    torch::Tensor y = 2 * x + 1 + torch::randn({100, 1}) * 0.1;

    // 训练循环
    for (size_t i = 0; i < 100; ++i) {
        // 前向传播
        torch::Tensor output = net->forward(x);

        // 计算损失
        torch::Tensor loss = torch::mse_loss(output, y);

        // 反向传播
        optimizer.zero_grad();
        loss.backward();
        optimizer.step();

        // 打印损失
        if (i % 10 == 0) {
            std::cout << "Epoch " << i << ", Loss: " << loss.item<float>() << std::endl;
        }
    }

    std::cout << "Training finished!n";

    return 0;
}
  • torch::nn::Module: 所有模型的基类。
  • torch::nn::Linear: 线性层。
  • torch::optim::SGD: 随机梯度下降优化器。
  • torch::mse_loss: 均方误差损失函数。
  • optimizer.zero_grad(): 清空梯度。
  • loss.backward(): 计算梯度。
  • optimizer.step(): 更新模型参数。

第五章:CUDA加速,让你的模型跑得飞起

如果你的电脑有NVIDIA显卡,那么一定要用CUDA来加速你的模型。

  1. 编译CUDA支持的LibTorch: 下载LibTorch的时候,要选择CUDA版本的。
  2. 把模型和数据放到GPU上:

    // 创建模型
    auto net = std::make_shared<Net>();
    
    // 把模型放到GPU上
    net->to(torch::kCUDA);
    
    // 训练数据
    torch::Tensor x = torch::randn({100, 1});
    torch::Tensor y = 2 * x + 1 + torch::randn({100, 1}) * 0.1;
    
    // 把数据放到GPU上
    x = x.to(torch::kCUDA);
    y = y.to(torch::kCUDA);
    • torch::kCUDA: 表示CUDA设备。
    • net->to(torch::kCUDA): 把模型放到GPU上。
    • x.to(torch::kCUDA): 把数据放到GPU上。

第六章:性能优化,让你的代码更上一层楼

即使用了CUDA,还可以通过一些技巧来进一步优化性能。

  1. Batch Size: 适当增加Batch Size可以提高GPU的利用率。
  2. Data Loader: 使用多线程Data Loader可以加速数据加载。
  3. 模型量化: 把模型参数从float32转换成int8,可以减小模型大小和提高推理速度。
  4. TorchScript优化: 可以使用 torch::jit::optimize_for_inference 来优化模型。

第七章:实战案例,让你的知识落地

光说不练假把式,咱们来个实战案例:用LibTorch实现一个简单的图像分类器。

  1. Python模型训练:

    import torch
    import torch.nn as nn
    import torch.optim as optim
    from torchvision import datasets, transforms
    
    # 定义模型
    class SimpleCNN(nn.Module):
        def __init__(self):
            super(SimpleCNN, self).__init__()
            self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
            self.pool = nn.MaxPool2d(2, 2)
            self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
            self.fc1 = nn.Linear(320, 50)
            self.fc2 = nn.Linear(50, 10)
    
        def forward(self, x):
            x = self.pool(torch.relu(self.conv1(x)))
            x = self.pool(torch.relu(self.conv2(x)))
            x = x.view(-1, 320)
            x = torch.relu(self.fc1(x))
            x = self.fc2(x)
            return x
    
    # 加载MNIST数据集
    transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.1307,), (0.3081,))
    ])
    
    train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
    train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True)
    
    # 创建模型
    model = SimpleCNN()
    
    # 定义优化器
    optimizer = optim.Adam(model.parameters(), lr=0.001)
    
    # 训练循环
    for epoch in range(2):
        for batch_idx, (data, target) in enumerate(train_loader):
            optimizer.zero_grad()
            output = model(data)
            loss = nn.functional.cross_entropy(output, target)
            loss.backward()
            optimizer.step()
    
            if batch_idx % 100 == 0:
                print('Epoch: {} [{}/{} ({:.0f}%)]tLoss: {:.6f}'.format(
                    epoch, batch_idx * len(data), len(train_loader.dataset),
                    100. * batch_idx / len(train_loader), loss.item()))
    
    # 保存模型
    torch.jit.save(torch.jit.script(model), "mnist_model.pt") #使用torch.jit.script 进行保存
  2. C++模型推理:

    #include <torch/script.h> // One-stop header.
    #include <iostream>
    #include <memory>
    #include <opencv2/opencv.hpp> // 需要安装OpenCV
    
    int main() {
        torch::jit::Module module;
        try {
            // 加载模型
            module = torch::jit::load("mnist_model.pt");
        } catch (const c10::Error& e) {
            std::cerr << "error loading the modeln";
            return -1;
        }
    
        std::cout << "Model loaded successfully!n";
    
        // 加载图像 (需要OpenCV)
        cv::Mat image = cv::imread("test_image.png", cv::IMREAD_GRAYSCALE); //test_image.png 需要自己准备
        if (image.empty()) {
            std::cerr << "Could not open or find the imagen";
            return -1;
        }
    
        // 预处理图像
        cv::resize(image, image, cv::Size(28, 28));
        image.convertTo(image, CV_32F, 1.0 / 255.0); // 归一化到 [0, 1]
    
        // 创建输入张量
        torch::Tensor input = torch::from_blob(image.data, {1, 1, 28, 28}, torch::kFloat32); // 1个batch,1个通道,28x28
        input = input.toType(torch::kFloat); // 转换成float类型
    
        // 推理
        at::Tensor output = module.forward({input}).toTensor();
    
        // 获取预测结果
        at::Tensor prediction = torch::argmax(output, 1);
        int predicted_label = prediction.item<int>();
    
        std::cout << "Predicted label: " << predicted_label << "n";
    
        return 0;
    }
    • 需要安装OpenCV,用于图像加载和预处理。
    • torch::from_blob(): 从现有的数据创建张量。
    • torch::argmax(): 获取最大值的索引,也就是预测的类别。

第八章:常见问题,助你避坑

在使用LibTorch的过程中,可能会遇到一些问题,这里列举一些常见的:

问题 解决方法
找不到LibTorch库 检查CMakeLists.txt中的 Torch_DIR 是否正确,确保指向LibTorch的根目录。
模型加载失败 检查模型路径是否正确,模型是否完整。 确保Python模型保存时使用了 torch.save(model.state_dict(), 'model.pth') 或者 torch.jit.save(torch.jit.script(model), "model.pt")
CUDA错误 检查CUDA版本是否正确,显卡驱动是否安装正确。 确保编译的时候使用了CUDA支持的LibTorch版本。
张量类型不匹配 检查输入张量的类型是否与模型期望的类型一致。可以使用 input.toType(torch::kFloat) 等函数来转换张量类型。
内存泄漏 注意管理张量的生命周期,避免内存泄漏。 可以使用智能指针来管理张量,或者手动调用 tensor.reset() 来释放内存。
模型输入输出不匹配,崩溃或者结果异常 1. 确保C++中输入的Tensor的数据类型,大小,形状与Python中模型定义时的输入一致。2. 使用debug模式进行跟踪,查看具体崩溃位置。3. 检查 C++中输入张量的均值方差等预处理参数是否与Python中一致。 4.使用torch::jit::Module::dump 函数可以将导出的模型结构打印出来,方便进行对比,检查。

总结:LibTorch,你的深度学习好帮手

LibTorch是一个强大的工具,可以让你在C++环境中进行深度学习模型的推理和训练。虽然学习曲线比Python陡峭一些,但掌握之后,你会发现它的性能和灵活性是Python无法比拟的。

希望今天的讲座能帮助你入门LibTorch,在深度学习的道路上越走越远!

课后作业

  1. 用LibTorch加载一个自己训练的模型,并进行推理。
  2. 用LibTorch实现一个简单的图像分类器,并在自己的数据集上进行训练。
  3. 尝试使用CUDA加速你的LibTorch程序。

祝大家学习愉快! 下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注