首页 >> 大全

【C++ Caffe】Ubuntu下 手写体数字识别

2023-12-05 大全 31 作者:考证青年

MNIST数据集是机器学习领域中非常经典的一个数据集,由60000个训练样本和10000个测试样本组成,每个样本都是一张28 * 28像素的灰度手写数字图片。

1.下载MNIST数据集

MNIST数据集可以在Caffe源码框架的data/mnist下用.sh下载

cd data/mnist/
./get_mnist.sh

上述过程下载的时间可能有点长,我将文件放在我的百度网盘中了, 密码: ln4o

下载后将文件解压放到data/mnist/目录下

在data/mnist/目录下使用tree命令查看目录结构

接下来,我们来看一下的内容

#!/usr/bin/env sh
# 这个脚本用于下载并解压 mnist 文件
DIR="$( cd "$(dirname "$0")" ; pwd -P )"
cd "$DIR"
# printf
echo "Downloading..."
# for循环 设置要下载文件的名称,这里一共4个文件
for fname in train-images-idx3-ubyte train-labels-idx1-ubyte t10k-images-idx3-ubyte t10k-labels-idx1-ubyte
doif [ ! -e $fname ]; then# 进行下载文件wget --no-check-certificate http://yann.lecun.com/exdb/mnist/${fname}.gz# 进行解压文件gunzip ${fname}.gzfi
done

从上述的脚本我们可以看出来,MNIST原始数据为4个文件,如下表所示:

文件名说明

train--idx3-ubyte

训练集,图片

train--idx1-ubyte

训练集,标签

t10k--idx3-ubyte

测试集,图片

t10k--idx1-ubyte

测试集,标签

2.转换格式

但是问题出现了,caffe 并不支持 二进制文件,只支持LMDB 或者,所以我们需要将数据进行转换.进行数据转换的脚本在 " caffe路径//mnist/.sh"

进入到 " caffe路径 ",进行执行 “.//mnist/.sh”,显示如下:

lichunlin@ThinkPad-T420:~/caffe$ ./examples/mnist/create_mnist.sh 
Creating lmdb...
I0226 18:49:42.627514 18492 db_lmdb.cpp:35] Opened lmdb examples/mnist/mnist_train_lmdb
I0226 18:49:42.627812 18492 convert_mnist_data.cpp:88] A total of 60000 items.
I0226 18:49:42.627828 18492 convert_mnist_data.cpp:89] Rows: 28 Cols: 28
I0226 18:49:43.350973 18492 convert_mnist_data.cpp:108] Processed 60000 files.
I0226 18:49:43.377131 18496 db_lmdb.cpp:35] Opened lmdb examples/mnist/mnist_test_lmdb
I0226 18:49:43.377393 18496 convert_mnist_data.cpp:88] A total of 10000 items.
I0226 18:49:43.377408 18496 convert_mnist_data.cpp:89] Rows: 28 Cols: 28
I0226 18:49:43.497009 18496 convert_mnist_data.cpp:108] Processed 10000 files.
Done.

这就是说明格式转换成功了

在转换成功之前,曾经报过一个错误,显示如下,导致格式转换不成功

lichunlin@ThinkPad-T420:~/caffe$ ./examples/mnist/create_mnist.sh 
Creating lmdb...
F0226 14:33:14.406914 32482 convert_mnist_data.cpp:59] Check failed: magic == 2051 (529205256 vs. 2051) Incorrect image file magic.
*** Check failure stack trace: ***
@ 0x7f26f3ceb0cd google::LogMessage::Fail()
@ 0x7f26f3cecf33 google::LogMessage::SendToLog()
@ 0x7f26f3ceac28 google::LogMessage::Flush()
@ 0x7f26f3ced999 google::LogMessageFatal::~LogMessageFatal()
@ 0x564d964ea6e0 (unknown)
@ 0x564d964e947a (unknown)
@ 0x7f26f2cf0b97 __libc_start_main
@ 0x564d964e94ca (unknown)
Aborted (core dumped)

上述问题我是通过删除了MNIST原始数据(第一节中提到的4个文件),然后用指令 ./.sh 重新下载(a few later)然后重新执行 .//mnist/.sh 指令重新进行格式转换

到此,数据准备已经完成了!

接下来分析一下 “.sh” 文件

#!/usr/bin/env sh
# This script converts the mnist data into lmdb/leveldb format,
# depending on the value assigned to $BACKEND.
set -e
# 定义一些变量
EXAMPLE=examples/mnist
DATA=data/mnist
BUILD=build/examples/mnistBACKEND="lmdb"
# printf
echo "Creating ${BACKEND}..."# 下载之前,删除之前下载的内容
rm -rf $EXAMPLE/mnist_train_${BACKEND}
rm -rf $EXAMPLE/mnist_test_${BACKEND}# 将编译后的可执行文件,加上参数进行运行(主要内容)
$BUILD/convert_mnist_data.bin $DATA/train-images-idx3-ubyte \$DATA/train-labels-idx1-ubyte $EXAMPLE/mnist_train_${BACKEND} --backend=${BACKEND}
$BUILD/convert_mnist_data.bin $DATA/t10k-images-idx3-ubyte \$DATA/t10k-labels-idx1-ubyte $EXAMPLE/mnist_test_${BACKEND} --backend=${BACKEND}echo "Done."

最重要的内容就是

$BUILD/convert_mnist_data.bin $DATA/train-images-idx3-ubyte \$DATA/train-labels-idx1-ubyte $EXAMPLE/mnist_train_${BACKEND} --backend=${BACKEND}
$BUILD/convert_mnist_data.bin $DATA/t10k-images-idx3-ubyte \$DATA/t10k-labels-idx1-ubyte $EXAMPLE/mnist_test_${BACKEND} --backend=${BACKEND}

但是 " .bin " 是编译后的二进制文件,并没有任何信息,所以我们应该去了解 " caffe目录//mnist/.cpp " 这个文件的内容.

C 语言从 main 函数开始阅读.

/* 主函数 包含 额外参数(int argc, char** argv)使用方法,convert_mnist_data.bin 图片文件路径 标签文件路径 生成数据库文件路径 --backend=使用数据库类型例如convert_mnist_data.bin t10k-images-ubyte t10k-labels-ubyte mnist_test_lmdb --backend=lmdb
*/
int main(int argc, char** argv) {
// 条件编译 实际中我们定义了 #include 
#ifndef GFLAGS_GFLAGS_H_namespace gflags = google;
#endifFLAGS_alsologtostderr = 1;// SetUsageMessage: 设置命令行帮助信息gflags::SetUsageMessage("This script converts the MNIST dataset to\n""the lmdb/leveldb format used by Caffe to load data.\n""Usage:\n""    convert_mnist_data [FLAGS] input_image_file input_label_file ""output_db_file\n""The MNIST dataset could be downloaded at\n""    http://yann.lecun.com/exdb/mnist/\n""You should gunzip them after downloading,""or directly use data/mnist/get_mnist.sh\n");/* ParseCommandLineFlags: 解析命令行参数,* 与上文定义的 * DEFINE_string(backend, "lmdb", "The backend for storing the result");* 相对应* backend 变为 FLAGS_backend 变量*/gflags::ParseCommandLineFlags(&argc, &argv, true);const string& db_backend = FLAGS_backend;if (argc != 4) {// 输出错误信息函数,第一个参数必须是 argv[0]gflags::ShowUsageWithFlagsRestrict(argv[0],"examples/mnist/convert_mnist_data");} else {// 初始化 gloggoogle::InitGoogleLogging(argv[0]);// 进行数据转换(实际操作)convert_dataset(argv[1], argv[2], argv[3], db_backend);}return 0;
}

主函数中用到了大量的 和 glogs 的函数,都是用来对命令行与 日志相关,具体的真实干活的是 函数.

// convert_dataset 实际中 将 二进制文件内容 存储到 数据库 中的函数
void convert_dataset(const char* image_filename, const char* label_filename,const char* db_path, const string& db_backend) {/* 以输入的方式 image_file 和 label_file* std::ifstream image_file 指 image_file 为一个类,并指定了文件路径和打开方法*/std::ifstream image_file(image_filename, std::ios::in | std::ios::binary);std::ifstream label_file(label_filename, std::ios::in | std::ios::binary);// glog 中的 宏函数 如果 状态不正常 则输出后面内容CHECK(image_file) << "Unable to open file " << image_filename;CHECK(label_file) << "Unable to open file " << label_filename;//与二进制文件内容相关/* magic        魔数,类似于校验信息* num_items    条目(图片)的个数* num_labels   标签的个数* rows         一行元素中像素点个数* cols         一列元素中像素点个数*/uint32_t magic;uint32_t num_items;uint32_t num_labels;uint32_t rows;uint32_t cols;// 使用 read 方法读取 image_file 前 (4*8)32 位元素的内容,即为魔数image_file.read(reinterpret_cast<char*>(&magic), 4);magic = swap_endian(magic);// 校验魔数是否一致CHECK_EQ(magic, 2051) << "Incorrect image file magic.";// 同理校验 label_file 的正确性label_file.read(reinterpret_cast<char*>(&magic), 4);magic = swap_endian(magic);CHECK_EQ(magic, 2049) << "Incorrect label file magic.";// 读取 条目的个数image_file.read(reinterpret_cast<char*>(&num_items), 4);num_items = swap_endian(num_items);// 读取标签的个数label_file.read(reinterpret_cast<char*>(&num_labels), 4);num_labels = swap_endian(num_labels);CHECK_EQ(num_items, num_labels);// 读取 条目(图片)的行像素点个数image_file.read(reinterpret_cast<char*>(&rows), 4);rows = swap_endian(rows);// 读取 条目(图片)的列像素点个数image_file.read(reinterpret_cast<char*>(&cols), 4);cols = swap_endian(cols);/* 来自* #include "boost/scoped_ptr.hpp"* 不用再分 levelDB 与LMDB*/// 创建新 数据库DBscoped_ptr<db::DB> db(db::GetDB(db_backend));// 打开数据库,并指定数据库的路径db->Open(db_path, db::NEW);// 创建对数据库的 操作句柄(txn) Transaction 表示 事务,数据库内容的知识scoped_ptr<db::Transaction> txn(db->NewTransaction());// Storing to dbchar label;char* pixels = new char[rows * cols];int count = 0;string value;// 定义图片 对象Datum datum;// 图片的通道数datum.set_channels(1);// 图片的高datum.set_height(rows);// 图片的宽datum.set_width(cols);LOG(INFO) << "A total of " << num_items << " items.";LOG(INFO) << "Rows: " << rows << " Cols: " << cols;// 对 二进制文件中所有的 条目(图片) 进行处理for (int item_id = 0; item_id < num_items; ++item_id) {// 读取像素点image_file.read(pixels, rows * cols);// 读取标签label_file.read(&label, 1);datum.set_data(pixels, rows*cols);datum.set_label(label);string key_str = caffe::format_int(item_id, 8);// 对图片进行字符串序列化datum.SerializeToString(&value);// 通过事务 将 序列化后的图片信息 和 编号进行加入到数据库中txn->Put(key_str, value);// 并且 每 1000 个 提交一次if (++count % 1000 == 0) {txn->Commit();}}// 防止出现不能被1000整除的情况,对最后一批进行处理if (count % 1000 != 0) {txn->Commit();}LOG(INFO) << "Processed " << count << " files.";//删除 new 的内容delete[] pixels;// 关闭数据库db->Close();
}

利用对 二进制文件的了解,对二进制文件进行解析,最后将其以数据库的形式保存.其中运用了一些数据库相关的知识,并且将数据库变成了面向对象的编程.

解决最后的问题,数据的大小端问题.

/* MNIST原始数据为大端存储,即数据的高字节保存在地址的低地址中,而数据的低字节保存在内存的高地址中* C/C++   数据为小端存储,和MNIST正好相反,* 所以需要定义一个函数进行转换*/
uint32_t swap_endian(uint32_t val) {val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF);return (val << 16) | (val >> 16);
}

3. 训练数据

开始训练生成的数据,训练脚本在"caffe路径//mnist/.sh"

其中调用了真正执行的语句

#!/usr/bin/env sh
set -e./build/tools/caffe train --solver=examples/mnist/lenet_solver.prototxt

".//mnist/.sh"运行后,可能你会看见这样的显示:

lichunlin@ThinkPad-T420:~/caffe$ ./build/tools/caffe train --solver=examples/mnist/lenet_solver.prototxt 
I0226 16:01:17.276763 16197 caffe.cpp:204] Using GPUs 0
F0226 16:01:17.277010 16197 common.cpp:66] Cannot use GPU in CPU-only Caffe: check mode.
*** Check failure stack trace: ***@     0x7f32a8e065cd  google::LogMessage::Fail()@     0x7f32a8e08433  google::LogMessage::SendToLog()@     0x7f32a8e0615b  google::LogMessage::Flush()@     0x7f32a8e08e1e  google::LogMessageFatal::~LogMessageFatal()@     0x7f32a9373780  caffe::Caffe::SetDevice()@           0x40a45b  train()@           0x406f70  main@     0x7f32a7f83b17  (unknown)@           0x40779a  _start@              (nil)  (unknown)
已放弃 (core dumped)

这是因为默认在 " --=/mnist/. " 中写着:

# caffe 求解模式: CPU or GPU
solver_mode: GPU
将其改为
solver_mode: CPU

就能正常运行

运行中显示的内容提示:

lichunlin@ThinkPad-T420:~/caffe$ ./examples/mnist/train_lenet.sh
I0226 16:32:44.089705 18561 caffe.cpp:197] Use CPU.使用CPU
I0226 16:32:44.089968 18561 solver.cpp:45] 初始化超参数
以下内容来自: "caffe/example/mnist/lenet_solver.prototxt(训练超参数文件) "
test_iter: 100
test_interval: 500
.....
snapshot: 5000
snapshot_prefix: "examples/mnist/lenet"
solver_mode: CPU
net: "examples/mnist/lenet_train_test.prototxt"
train_state {level: 0stage: ""
}
I0226 16:32:44.090420 18561 solver.cpp:102] Creating training net from net file: examples/mnist/lenet_train_test.prototxt 
按照  " examples/mnist/lenet_train_test.prototxt " 文件建立网络
I0226 16:32:44.090696 18561 net.cpp:296] The NetState phase (0) differed from the phase (1) specified by a rule in layer mnist
I0226 16:32:44.090768 18561 net.cpp:296] The NetState phase (0) differed from the phase (1) specified by a rule in layer accuracy
I0226 16:32:44.090971 18561 net.cpp:53] Initializing net from parameters: 初始化网络参数
以下内容来自 " examples/mnist/lenet_train_test.prototxt "
name: "LeNet"
state {    # 创建训练网络phase: TRAINlevel: 0stage: ""
}
layer {name: "mnist"type: "Data"top: "data"top: "label"include {phase: TRAIN}transform_param {scale: 0.00390625}data_param {source: "examples/mnist/mnist_train_lmdb"  # 使用到的数据库路径batch_size: 64backend: LMDB}
}
......
layer {name: "loss"type: "SoftmaxWithLoss"bottom: "ip2"bottom: "label"top: "loss"
}
I0226 16:32:44.097754 18561 layer_factory.hpp:77] Creating layer mnist
I0226 16:32:44.097884 18561 db_lmdb.cpp:35] Opened lmdb examples/mnist/mnist_train_lmdb
创建 各种训练 层
I0226 16:32:44.097921 18561 net.cpp:86] Creating Layer mnist
......
# 显示当前内存占有
I0226 16:32:44.098275 18561 net.cpp:139] Memory required for data: 200960
......
进行反馈回路
I0226 16:32:44.104403 18561 layer_factory.hpp:77] Creating layer loss
I0226 16:32:44.104418 18561 net.cpp:86] Creating Layer loss
I0226 16:32:44.104429 18561 net.cpp:408] loss <- ip2
I0226 16:32:44.104440 18561 net.cpp:408] loss <- label
I0226 16:32:44.104460 18561 net.cpp:382] loss -> loss
I0226 16:32:44.104496 18561 layer_factory.hpp:77] Creating layer loss
I0226 16:32:44.104522 18561 net.cpp:124] Setting up loss
I0226 16:32:44.104537 18561 net.cpp:131] Top shape: (1)
I0226 16:32:44.104553 18561 net.cpp:134]     with loss weight 1
I0226 16:32:44.104581 18561 net.cpp:139] Memory required for data: 5169924
I0226 16:32:44.104593 18561 net.cpp:200] loss needs backward computation.
.....
I0226 16:32:44.104709 18561 net.cpp:244] This network produces output loss
I0226 16:32:44.104732 18561 net.cpp:257] Network initialization done.
创建 测试 网络(同上)
......
开始迭代(每100次显示一次,每500次保存一次)
I0226 16:32:47.178556 18561 solver.cpp:239] Iteration 0 (-4.34403e-44 iter/s, 3.065s/100 iters), loss = 2.3588
loss值
I0226 16:32:47.178653 18561 solver.cpp:258]     Train net output #0: loss = 2.3588 (* 1 = 2.3588 loss)
......

上面的指令中 重要的部分是 " --=/mnist/. "

/mnist/. 是使用 编写的训练超参数文件.文件中指定的超参数如下:

# 训练与测试 网络的 prototxt 文件所在位置
net: "examples/mnist/lenet_train_test.prototxt"
# test_iter specifies how many forward passes the test should carry out.
# In the case of MNIST, we have test batch size 100 and 100 test iterations,
# covering the full 10,000 testing images.
# 测试截断迭代次数
test_iter: 100
# 训练时每迭代500次进行一次预测
test_interval: 500
# 网络的基础学习速率,冲量,权衰量
base_lr: 0.01
momentum: 0.9
weight_decay: 0.0005
# 学习速率的衰减策略
lr_policy: "inv"
gamma: 0.0001
power: 0.75
# 每100次迭代,屏幕打印一次
display: 100
# 最大的迭代次数
max_iter: 10000
# 每5000次迭代打印一次快照
snapshot: 5000
#快照保存位置
snapshot_prefix: "examples/mnist/lenet"
# 求解模式选择(CPU 或者GPU)
solver_mode: CPU

同样的 在超参数文档中指定了网络结构 net: “/mnist/.” 也是使用了. 也是 格式

4. 训练网络分析

上文提到了,训练网络位于 “/mnist/.” 也是使用 格式.

那这就来看看 " . " 中的内容

# 网络名称为 LeNet
name: "LeNet"
layer {
# 层的名称name: "mnist"
# 层的类型为:数据层type: "Data"
# top 表示输出
# 输出 data 和labeltop: "data"top: "label"include {
# 该层只有训练阶段有效phase: TRAIN}transform_param {
# 数据变换使用的缩放因子scale: 0.00390625}
# 数据来源参数data_param {source: "examples/mnist/mnist_train_lmdb"batch_size: 64backend: LMDB}
}
layer {name: "mnist"type: "Data"top: "data"top: "label"include {
# 同上,该层只有测试阶段有效phase: TEST}transform_param {scale: 0.00390625}data_param {source: "examples/mnist/mnist_test_lmdb"batch_size: 100backend: LMDB}
}
# 卷积层
layer {name: "conv1"
# 层的类型为:卷积层type: "Convolution"
# bottom 表示输入bottom: "data"
# top 表示输出top: "conv1"
# 第一个 pram 表示权值学习速率倍乘因子param {lr_mult: 1}
# 第二个 pram 表示bias(偏移量)学习速率倍乘因子param {lr_mult: 2}
# 卷积计算参数convolution_param {
# 输出 的 通道数num_output: 20
# 卷积核大小kernel_size: 5
# 卷积的步长stride: 1
# 权值使用 xavier 填充器weight_filler {type: "xavier"}
# 偏移量使用 常数填充器,默认为0bias_filler {type: "constant"}}
}#池化层
layer {name: "pool1"
# 层的类型为:池化层type: "Pooling"
# 输入输出bottom: "conv1"top: "pool1"
# 池化层参数pooling_param {
# 使用最大池化pool: MAX
# 池化核大小kernel_size: 2
# 池化核移动步长stride: 2}
}
......
# 全连接层
layer {name: "ip1"
# 层的类型为:全连接层type: "InnerProduct"bottom: "pool2"top: "ip1"param {lr_mult: 1}param {lr_mult: 2}
# 全连接层参数inner_product_param {
# 输出通道数num_output: 500weight_filler {type: "xavier"}bias_filler {type: "constant"}}
}
# 非线性层
layer {name: "relu1"
# 层的类型为:非线性层type: "ReLU"
# 输入输出信息bottom: "ip1"top: "ip1"
}
......
# 准确率计算层
layer {name: "accuracy"
# 层的类型为:准确率计算层type: "Accuracy"bottom: "ip2"bottom: "label"top: "accuracy"include {
# 只有 测试阶段 有效phase: TEST}
}
# 损失层
layer {name: "loss"
# 层的类型为:softmaxloss 损失值计算层type: "SoftmaxWithLoss"bottom: "ip2"bottom: "label"top: "loss"
}

训练结果可以看我的这个博客

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了