04_卷积神经网络
描述
dlib针对卷积神经网络的有很好的支持,实现的卷积层、池化层、BN层等关键网络层。
- 卷积层:dlib::con(add_layer<con_<num_filters,nr,nc,stride_y,stride_x>, SUBNET>)
- 最大池化层:dlib::max_pool(add_layer<max_pool_<nr,nc,stride_y,stride_x>, SUBNET>)
- 平均池化层:dlib::avg_pool(add_layer<avg_pool_<nr,nc,stride_y,stride_x>, SUBNET>)
- 批量规范化:dlib::bn_conv(add_layer<bn_<CONV_MODE>,SUBNET>)
- …
下面复现lenet5的训练
lenet5
lenet5是用来识别手写数字的模型,训练数据源于mnist(可以在网上下载)
加载数据
这里直接加载图片
bool loadMnistImages(const string &fpath, vector<dlib::matrix<unsigned char>> &vec_imgs, vector<unsigned long> &vec_labs)
{
dlib::directory dir(fpath);
vector<dlib::file> vec_files = dir.get_files();
for (dlib::file f : vec_files)
{
string full_name = f.full_name();
string fname = f.name();
if (fname.length() > 4 && fname.find(".jpg") != fname.length() - 4)
continue;
// 加载数据
dlib::matrix<dlib::rgb_pixel> img;
unsigned char gray_img[28 * 28];
dlib::load_image(img, full_name);
int i = 0;
for (dlib::rgb_pixel rgb : img)
{
gray_img[i] = rgb.red; // 因为是灰度图,r/g/b取任意一个就行
i++;
}
dlib::matrix<unsigned char> mtx_img;
mtx_img.set_size(28, 28);
memcpy((char *)&mtx_img(0, 0), gray_img, 28 * 28);
vec_imgs.push_back(mtx_img);
vec_labs.push_back(getFileLable(fname));
}
}
int getFileLable(const string &fname)
{
// train_0_5.jpg
string labstr = fname.substr(fname.find_last_of("_") + 1, 1);
return atoi(labstr.c_str());
}
也可以加载mnist数据集,这种方式更方便些,可以参考https://www.dlib.net/dnn_introduction_ex.cpp.html
定义模型
这里对模型进行改造一下,因为用于训练的图片大小为28x28(原模型训练使用的图片大小为32x32),这里要把卷积核调整为3x3。
// 定义模型结构
using lenet5_type = dlib::loss_multiclass_log<dlib::fc<10,
dlib::relu<dlib::fc<84,
dlib::relu<dlib::fc<120,
dlib::max_pool<2, 2, 2, 2, dlib::relu<dlib::con<16, 3, 3, 1, 1,
dlib::max_pool<2, 2, 2, 2, dlib::relu<dlib::con<6, 3, 3, 1, 1,
dlib::input<dlib::matrix<unsigned char>>>>>>>>>>>>>>;
训练模型
设置完相关参数就可以训练了(由于没有GPU加速,训练过程相对漫长);这里没有设置最大训练轮数,而是设置最小learning_rate当训练过程中learning_rate小于min_learning_rate时,结束训练。
lenet5_type net;
dlib::dnn_trainer<lenet5_type> trainer(net);
trainer.set_learning_rate(0.01);
trainer.set_min_learning_rate(0.00001);
trainer.set_mini_batch_size(128);
trainer.be_verbose();
trainer.set_synchronization_file("mnist_sync", std::chrono::seconds(20));// 每过20秒保存一下训练状态
trainer.train(train_datas, train_labels);
net.clean();
dlib::serialize("lenet5_network.dat") << net;
dlib::net_to_xml(net,"lenet5.xml"); // 另外一种保存模型的方式
trainer.set_synchronization_file(“mnist_sync”, std::chrono::seconds(20))的目的是定时保存训练状态,防止训练过程异常中断导致已有的训练数据丢失。
可以通过get_synchronization_file加载最后保存的训练状态,继续完成训练目的。
完整代码
void cnnLeNet5(const string &train_path, const string &test_path)
{
vector<dlib::matrix<unsigned char>> train_datas;
vector<unsigned long> train_labels;
vector<dlib::matrix<unsigned char>> test_datas;
vector<unsigned long> test_labels;
if (!(loadMnistImages(train_path, train_datas, train_labels) &&
loadMnistImages(test_path, test_datas, test_labels)))
{
cout << "load mnist images failed!"<<endl;
return;
}
cout << "load mnist images successful!"<<endl;
// 定义模型结构
using lenet5_type = dlib::loss_multiclass_log<dlib::fc<10,
dlib::relu<dlib::fc<84,
dlib::relu<dlib::fc<120,
dlib::max_pool<2, 2, 2, 2, dlib::relu<dlib::con<16, 3, 3, 1, 1,
dlib::max_pool<2, 2, 2, 2, dlib::relu<dlib::con<6, 3, 3, 1, 1,
dlib::input<dlib::matrix<unsigned char>>>>>>>>>>>>>>;
lenet5_type net;
dlib::dnn_trainer<lenet5_type> trainer(net);
trainer.set_learning_rate(0.01);
trainer.set_min_learning_rate(0.00001);
trainer.set_mini_batch_size(128);
trainer.be_verbose();
trainer.set_synchronization_file("mnist_sync", std::chrono::seconds(20));
trainer.train(train_datas, train_labels);
net.clean();
dlib::serialize("lenet5_network.dat") << net;
dlib::net_to_xml(net,"lenet5.xml"); // 另外一种保存模型的方式
vector<unsigned long> predict_train = net(train_datas);
int ok_count = 0;
int error_count = 0;
for (int i = 0; i < train_labels.size(); i++)
{
if (train_labels.at(i) == predict_train.at(i))
ok_count += 1;
else
error_count += 1;
}
cout<<"train accurary :"<<ok_count / static_cast<double>(ok_count + error_count)<<endl;
vector<unsigned long> predict_test = net(test_datas);
ok_count = 0;
error_count = 0;
for (int i = 0; i < test_labels.size(); i++)
{
if (test_labels.at(i) == predict_test.at(i))
ok_count += 1;
else
error_count += 1;
}
cout<<"test accurary :"<<ok_count / static_cast<double>(ok_count + error_count)<<endl;
}
最终训练结果
train accurary :0.999717
test accurary :0.9913
ResNet
深度残差网络是 Kaiming He et al. 提出的,一种运用了短路连接的神经网络形式。深度残差网络本身并没有一个固定的结构与参数,这使得深度残差网络非常灵活,可以有效的插入其他模型而提高模型表现。
常见的几种ResNet的具体结构:

根据Block类型,可以将ResNet分为两类:
- 一种基于BasicBlock,浅层网络ResNet18, 34都由BasicBlock搭成;
- 一种基于Bottleneck,深层网络ResNet50, 101, 152乃至更深的网络,都由Bottleneck搭成。
BasicBlock
BasicBlock相对简单些
这里实现ResNet18(采用BasicBlock)
// 定义block
template <int N, template <typename> class BN, int stride, typename SUBNET>
using block = BN<dlib::con<N,3, 3, 1, 1, dlib::relu<BN<dlib::con <N, 3, 3, stride, stride, SUBNET >>>>> ;
template <template <int,template<typename>class,int,typename> class block, int N, template<typename>class BN, typename SUBNET>
using residual_down = dlib::add_prev2<dlib::avg_pool<2,2,2,2,dlib::skip1<dlib::tag2<block<N,BN,2,dlib::tag1<SUBNET>>>>>>;
// residual_down creates a network structure like this:
/*
input from SUBNET
/ \
/ \
block downsample(using avg_pool)
\ /
\ /
add tensors (using add_prev2 which adds the output of tag2 with avg_pool's output)
|
output
*/
// 定义模型
using resnet_18 = dlib::loss_multiclass_log<dlib::fc<10,
dlib::avg_pool_everything<
residual_down<block,512,dlib::bn_con,
residual_down<block,256,dlib::bn_con,
residual_down<block,128,dlib::bn_con,
block<64, dlib::bn_con,1,
dlib::max_pool<2,2,1,1,dlib::relu<dlib::bn_con<
dlib::con<64,3,3,1,1,dlib::input<dlib::matrix<dlib::rgb_pixel>>>>>>>>>>>>>;
// 可以打印网络详细信息
resnet_18 net;
cout<<'layers num:'<<net.num_layers<<endl;
cout<<net<<endl;
// 指定优化器
dlib::dnn_trainer<resnet_18,dlib::adam> trainer(net,dlib::adam()); // 可以根据需要调节adam参数
//....
// 训练后的模型可以保存为xml格式
net.clean()
dlib::net_to_xml(net,"resnet18_network.xml");
如果没有N卡加速,上面的模型就不需要尝试训练了(太慢了)
Bottleneck
和Basicblock不同的一点是,每一个Bottleneck都会在输入和输出之间加上一个卷积层。
这里不再举例了,可以参考一下官方例子:https://www.dlib.net/dnn_introduction2_ex.cpp.html
更多推荐
所有评论(0)