本以为安装 libTorch 会费一番周折,结果根据官方 guide 一步一步很顺利地安装成功。其实主要原因就是自己对 cmake 如何来编译一个 c++ 项目并不熟悉。首先是在官方网站下载 libtorch 下面给出地址是我随后分享中用到的 libtorch 下载地址。
libtorch 用途
暂时个人认识 libtorch 用途是将训练好模型加载到 c++ 环境然后进行推理或者一些额外的工作,因为很多网络实现都是用 python 版本 torch 也就是 pytorch 实现的。
wget https://download.pytorch.org/libtorch/nightly/cpu/libtorch-shared-with-deps-latest.zip
unzip libtorch-shared-with-deps-latest.zip
在官网给的示例就是用 CMakeLists 来构建的项目,当然你可以用你顺手的工具来构建项目。其实个人也是一名 c++ 入门级的开发人员。
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
project(example-app)
find_package(Torch REQUIRED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TORCH_CXX_FLAGS}")
add_executable(example-app main.cpp)
target_link_libraries(example-app "${TORCH_LIBRARIES}")
set_property(TARGET example-app PROPERTY CXX_STANDARD 14)
关于 cmake 这个高级编译配置工具,随后随着个人对其进行深入了解,也会推出相关的分享。
- cmake_minimum_required 这里主要指定了最低支持的 cmake 版本
- add_executable 添加可执行文件
- find_package 可以找到 torch 安装
- set 可以设置一些参数
- target_link_libraries
- set_property 设置属性
#include <iostream>
int main
{
std::cout << "hello world" std::endl;
}
mkdir build
cd build
cmake -DCMAKE_PREFIX_PATH=/absolute/path/to/libtorch ..
cmake --build . --config Release
这里注意是/absolute/path/to/libtorch
是绝对路径,然后
-D 接下里参数是自定义参数, CMAKE_PREFIX_PATH 表示要编译的安装目录,需要/absolute/path/to 替换为你刚刚解压 libtorch 的目录
下载数据集
做一些准备工作,也就是先需要去下载一下数据库,不然在运行程序会报错。这里准备一段 python 代码
import torch
import torchvision
from torchvision import transforms
if __name__ == "__main__":
train_dataset = torchvision.datasets.MNIST(root='./data',
train=True,
transform=transforms.ToTensor(),
download=True
)
下载完成后,我们需要将 train-labels-idx3-ubyte 和 train-images-idx3-ubyte 复制到 build 文件夹下 data 不然在运行时候会报错。
cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
project(NNet)
set(CMAKE_PREFIX_PATH /home/ubuntu16/Downloads/libtorch)
find_package(Torch REQUIRED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TORCH_CXX_FLAGS}")
add_executable(main src/main.cpp include/network.h)
target_include_directories(main PRIVATE include)
target_link_libraries(main ${TORCH_LIBRARIES})
set_property(TARGET main PROPERTY CXX_STANDARD 14)
当我们配置好如何编译一个项目之后,我们就可以做更多有趣的事,其实也就是将 c++ 作为前端来开发一个神经网络,首先我们来看一看如何在 c++ 中定义和操作一个模块(module),想必大家都有一定开发神经网络经验,可以用 python 在 pytorch 框架下可以开发一个小型神经网络,或者自己有过经验去实现一个经典神经网络,例如 VGG ResNet。
这个与 pytorch 提供 python 的接口类似,因为为 C++ 提供可重复使用,用于搭建神经网络基础设施 module (模块)。所有其他的模块都是从这个基础 module 派生出来的。在 python 中,这个类是torch.nn.Module,而在 C++ 对应的是 torch::nn::Module。模块所封装了前向传播的方法 forward() 外,还包括 parameter (参数)、buffers(缓冲器)和submodules(子模块)。
参数和缓冲区以张量的形式存储状态。参数记录梯度,而缓冲区不记录。参数通常是你的神经网络的可训练权重。缓冲区的例子包括用于批量规范化的平均值和方差。为了重复使用特定的逻辑和状态块,PyTorch的API允许模块嵌套。一个嵌套的模块被称为子模块。
#pragma once
#include <iostream>
#include <torch/torch.h>
struct NetImpl:torch::nn::Module{
NetImpl(int fc1_dims, int fc2_dims):fc1(fc1_dims,fc1_dims),fc2(fc1_dims,fc2_dims),out(fc2_dims,1){
register_module("fc1",fc1);
register_module("fc2",fc2);
register_module("out",out);
}
torch::Tensor forward(torch::Tensor x){
x = torch::relu(fc1(x));
x = torch::relu(fc2(x));
x = out(x);
return x;
}
torch::nn::Linear fc1{nullptr}, fc2{nullptr}, out{nullptr};
};
TORCH_MODULE(Net);
#include "network.h"
#include <iostream>
#include <torch/torch.h>
\
using namespace std;
using namespace torch;
int main()
{
Net network(50,10);
cout << network << endl;
cout << "hello world" << endl;
}
搭建网络
#include <torch/torch.h>
#include <iostream>
// 定义一个简单全连接层组成的网络
struct Net: torch::nn::Module{
Net(){
// 构造并且注册网络结构中层
fc1 = register_module("fc1",torch::nn::Linear(784,64));
fc2 = register_module("fc2",torch::nn::Linear(64,32));
fc3 = register_module("fc3",torch::nn::Linear(32,10));
}
// 实现前向传播
torch::Tensor forward(torch::Tensor x){
x = torch::relu(fc1->forward(x.reshape({x.size(0),784})));
x = torch::dropout(x, /*p=*/0.5, /*train=*/is_training());
x = torch::relu(fc2->forward(x));
x = torch::log_softmax(fc3->forward(x), /*dim=*/1);
return x;
}
torch::nn::Linear fc1{nullptr}, fc2{nullptr}, fc3{nullptr};
};
int main() {
// 创建一个新的网络
auto net = std::make_shared<Net>();
auto data_loader = torch::data::make_data_loader(
torch::data::datasets::MNIST("./data").map(
torch::data::transforms::Stack<>()),
/*batch_size=*/64);
torch::optim::SGD optimizer(net->parameters(), /*lr=*/0.01);
for (size_t epoch = 1; epoch <= 10; ++epoch) {
size_t batch_index = 0;
// Iterate the data loader to yield batches from the dataset.
for (auto& batch : *data_loader) {
// 重置梯度
optimizer.zero_grad();
// 在输入数据上运行模型
torch::Tensor prediction = net->forward(batch.data);
// 根据模型预测值与真实值之间差距来计算损失值
torch::Tensor loss = torch::nll_loss(prediction, batch.target);
//在反向传播中计算参数的梯度
loss.backward();
// 基于计算得到梯度来更新参数
optimizer.step();
if (++batch_index % 100 == 0) {
std::cout << "Epoch: " << epoch << " | Batch: " << batch_index
<< " | Loss: " << loss.item<float>() << std::endl;
// 保存模型
torch::save(net, "net.pt");
}
}
}
}