喜欢就关注我们吧!
输出Hello World是学习任何一门语言的标志性入门过程,对于深度学习而言,成功的训练一个三层的神经网络区分出MNIST数据集就类似于输出了Hello World。在上一期《厌倦了Python训练?C++版本PyTorch尝试一下》中我们给大家普及了如何在C++中使用PyTorch,今天我们将带领大家用C++构建一个三层的神经网络并区分MNIST手写字符数据。
MNIST数据集简介
尽管MNIST数据集已经非常常见,但是为了保证这篇教程的完整性,我们还是简要介绍一下MNIST数据集。
MNIST数据集由60000(训练集)+10000(测试集)张手写字符组成,每张图片的大小为28×28,数据集可以在http://yann.lecun.com/exdb/mnist/下载,MNIST数据集可视化的结果为:
网络结构
由于区分MNIST数据是一个非常简单的任务,因此在深度学习蓬勃发展之前,一个三层的神经网络就已经可以很好的解决这个问题。与厌倦了Python训练?C++版本PyTorch尝试一下中的两层神经网络类似,我们同样使用一个结构体和一个forward函数来定义这个三层的神经网络
struct Net : torch::nn::Module {
Net() {
// Construct and register two Linear submodules.
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));
}
// Implement the Net's algorithm.
torch::Tensor forward(torch::Tensor x) {
// Use one of many tensor manipulation functions.
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;
}
// Use one of many "standard library" modules.
torch::nn::Linear fc1{nullptr}, fc2{nullptr}, fc3{nullptr};
};
这个神经网络的三层都是线性层,即该层的激活函数为
其中w为权重,b为偏移值。维度的变化为784->64->32->10,784是将28×28每个像素作为一个维度,最后的10是因为有10类。
在forward函数中,每经过一个线性层,就会进行一次relu运算,同时还加上dropout防止过拟合。最后一层是log_softmax函数,将10类的概率值全部归一化到[0, 1],并且保证十个数值之和为1以构建一个完整的概率分布。有关于log_softmax的相关知识,如果有不懂的同学可以参考Pytorch中交叉熵Loss趣解,其中有详细的数学推导。
主函数定义
与Python不同,主函数是C++执行的入口。C++与Python在训练过程中的代码非常类似。我们把几个重要的步骤用加粗的方式强调出来,供大家学习总结。
首先,实例化网络
// Create a Net
auto net = std::make_shared<Net>();
接下来,定义数据接口(dataloader)
// Create a multi-threaded data loader for the MNIST dataset.
auto data_loader = torch::data::make_data_loader(
torch::data::datasets::MNIST("./data").map(
torch::data::transforms::Stack<>()),
/*batch_size=*/64);
这里面我们不需要下载MNIST数据,与Python中类似,作为一个非常通用的数据集,MNIST数据集有自己对应的接口。这里我们定义batch_size为64,即每次向网络输入64张28×28的图片。
定义优化器,这里我们使用SGD优化器,初始的学习率定为0.1,
// Instantiate an SGD optimization algorithm to update our Net's parameters.
torch::optim::SGD optimizer(net->parameters(), /*lr=*/0.01);
定义数据集循环(epoch),批次循环(iteration),正向传播,反向传播,参数更新,保存权重等,这里的代码与Python非常类似,
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) {
// Reset gradients.
optimizer.zero_grad();
// Execute the model on the input data.
torch::Tensor prediction = net->forward(batch.data);
// Compute a loss value to judge the prediction of our model.
torch::Tensor loss = torch::nll_loss(prediction, batch.target);
// Compute gradients of the loss w.r.t. the parameters of our model.
loss.backward();
// Update the parameters based on the calculated gradients.
optimizer.step();
// Output the loss and checkpoint every 100 batches.
if (++batch_index % 100 == 0) {
std::cout << "Epoch: " << epoch << " | Batch: " << batch_index
<< " | Loss: " << loss.item<float>() << std::endl;
// Serialize your model periodically as a checkpoint.
torch::save(net, "net.pt");
}
}
}
这里我们定义了10个epoch,每100个iteration会在控制台输出一次训练的情况并保存一次当前的权重。
运行步骤
完整的项目大家可以点击阅读原文或者是后台回复“cppMNIST”得到。首先我们需要编译C++的代码,我们使用的是CMakefile的方式。编译的方法为:
$ cd mnist
$ mkdir build
$ cd build
$ cmake -DCMAKE_PREFIX_PATH=/path/to/libtorch ..
$ make
其中/path/to/libtorch是libtorch的路径。编译过程中会下载MNIST数据
-- The C compiler identification is GNU 5.5.0
-- The CXX compiler identification is GNU 5.5.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Looking for pthread_create
-- Looking for pthread_create - not found
-- Looking for pthread_create in pthreads
-- Looking for pthread_create in pthreads - not found
-- Looking for pthread_create in pthread
-- Looking for pthread_create in pthread - found
-- Found Threads: TRUE
-- Found CUDA: /usr/local/cuda-10.1 (found version "10.1")
-- Caffe2: CUDA detected: 10.1
-- Caffe2: CUDA nvcc is: /usr/local/cuda-10.1/bin/nvcc
-- Caffe2: CUDA toolkit directory: /usr/local/cuda-10.1
-- Caffe2: Header version is: 10.1
-- Found CUDNN: /usr/local/cuda-10.1/lib64/libcudnn.so
-- Found cuDNN: v7.6.5 (include: /usr/local/cuda-10.1/include, library: /usr/local/cuda-10.1/lib64/libcudnn.so)
-- Automatic GPU detection failed. Building for common architectures.
-- Autodetected CUDA architecture(s): 3.5;5.0;5.2;6.0;6.1;7.0;7.0+PTX;7.5;7.5+PTX
-- Added CUDA NVCC flags for: -gencode;arch=compute_35,code=sm_35;-gencode;arch=compute_50,code=sm_50;-gencode;arch=compute_52,code=sm_52;-gencode;arch=compute_60,code=sm_60;-gencode;arch=compute_61,code=sm_61;-gencode;arch=compute_70,code=sm_70;-gencode;arch=compute_75,code=sm_75;-gencode;arch=compute_70,code=compute_70;-gencode;arch=compute_75,code=compute_75
-- Found torch: /mnt/libtorch/lib/libtorch.so
-- Downloading MNIST dataset
Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz ...
0% |################################################################| 100%
Unzipped /mnt/examples/cpp/mnist/build/data/train-images-idx3-ubyte.gz ...
Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz ...
0% |################################################################| 100%
Unzipped /mnt/examples/cpp/mnist/build/data/train-labels-idx1-ubyte.gz ...
Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz ...
0% |################################################################| 100%
Unzipped /mnt/examples/cpp/mnist/build/data/t10k-images-idx3-ubyte.gz ...
Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz ...
0% |################################################################| 100%
Unzipped /mnt/examples/cpp/mnist/build/data/t10k-labels-idx1-ubyte.gz ...
-- Configuring done
-- Generating done
-- Build files have been written to: /mnt/examples/cpp/mnist/build
编译完成后我们会得到一个名为mnist的二进制文件,接下来执行二进制文件,就可以在控制台到看到如下的输出了。
$ ./mnist
Train Epoch: 1 [59584/60000] Loss: 0.4232
Test set: Average loss: 0.1989 | Accuracy: 0.940
Train Epoch: 2 [59584/60000] Loss: 0.1926
Test set: Average loss: 0.1338 | Accuracy: 0.959
Train Epoch: 3 [59584/60000] Loss: 0.1390
Test set: Average loss: 0.0997 | Accuracy: 0.969
Train Epoch: 4 [59584/60000] Loss: 0.1239
Test set: Average loss: 0.0875 | Accuracy: 0.972
...
自此,我们就可以用C++训练一个三层的神经网络对MNIST数据集进行训练,在后面的教程中我们会教大家如何用PyTorch的C++接口训练一个DCGAN,敬请期待。
往期推荐
AIZOO,打造中国最大的深度学习和人工智能社区,欢迎关注我们,也欢迎添加下方小助手的微信,邀请您加入我们的千人深度学习爱好者社区。
欢迎扫描下方的二维码添加小助手微信,邀请您加入我们的微信交流群。
群里有多位清北复交、BAT、AI独角兽大牛和众多深度学习er在一起愉快的交流技术,有任何问题,都可以咨询大家,欢迎你的加入哦。
添加小助手微信,邀您进AIZOO技术交流群