# 用Caffe在MNIST上训练LeNet 我们假设你已经成功编译了Caffe。如果没有,请参阅[安装页面](http://caffe.berkeleyvision.org/installation.html)。在本教程中,我们将假设您的Caffe安装位于`CAFFE_ROOT`。 # 准备数据集 您首先需要从MNIST网站下载并转换数据格式。为此,只需运行以下命令: ``` cd $CAFFE_ROOT ./data/mnist/get_mnist.sh ./examples/mnist/create_mnist.sh ``` 如果它抱怨wget或未gunzip安装,则需要分别安装它们。运行脚本后,应该有两个数据集mnist_train_lmdb,和mnist_test_lmdb。 > 在window上这就是然并卵啊!!! 没事有招.. ![image.png](http://upload-images.jianshu.io/upload_images/4907501-f39456f877cf6af5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 在windows下使用caffe时,如果先前没有啥经验,会考虑按照官方文档中的例子跑一跑。比如mnist手写数字识别。 然后就会遇到这个问题:windows下怎么执行/examples/mnist/create_mnist.sh呢? 当然,你需要先编译了convert_mnist_data子项目,以及下载了mnist数据集。 ok,要执行的脚本create_mnist.sh是shell语法,不妨转写为python ``` # create_mnist.py import os import shutil EXAMPLE='F:\\py\\mnist' DATA='F:\\py\\mnist' BUILD='G:\\caffe\\bin' BACKEND='lmdb' print "Createing "+BACKEND+"..." #rm -rf $EXAMPLE/mnist_train_${BACKEND} #rm -rf $EXAMPLE/mnist_test_${BACKEND} path1=EXAMPLE+"\\mnist_train_"+BACKEND path2=EXAMPLE+"\\mnist_test_"+BACKEND if os.path.exists(path1): shutil.rmtree(path1) if os.path.exists(path2): shutil.rmtree(path2) s1=BUILD+"\\convert_mnist_data.exe" s2=DATA+"\\train-images.idx3-ubyte" s3=DATA+"\\train-labels.idx1-ubyte" s4=EXAMPLE+"\\mnist_train_"+BACKEND s5="--backend="+BACKEND cmd=s1+" "+s2+" "+s3+" "+s4+" "+s5 print cmd os.system(cmd) t1=BUILD+"\\convert_mnist_data.exe" t2=DATA+"\\t10k-images.idx3-ubyte" t3=DATA+"\\t10k-labels.idx1-ubyte" t4=EXAMPLE+"\\mnist_test_"+BACKEND t5="--backend="+BACKEND cmd=t1+" "+t2+" "+t3+" "+t4+" "+t5 print "cmd2="+cmd os.system(cmd) ``` ``` PS G:\Projects\caffe\examples\mnist> py ./create_mnist.py Createing lmdb... G:\caffe\bin\convert_mnist_data.exe F:\py\mnist\train-images.idx3-ubyte F:\py\mnist\train-labels.idx1-ubyte F:\py\mnist\ mnist_train_lmdb --backend=lmdb I0302 14:46:29.084498 7608 common.cpp:36] System entropy source not available, using fallback algorithm to generate see d instead. I0302 14:46:29.086498 7608 db_lmdb.cpp:40] Opened lmdb F:\py\mnist\mnist_train_lmdb I0302 14:46:29.086498 7608 convert_mnist_data.cpp:93] A total of 60000 items. I0302 14:46:29.087498 7608 convert_mnist_data.cpp:94] Rows: 28 Cols: 28 I0302 14:46:40.301141 7608 convert_mnist_data.cpp:113] Processed 60000 files. cmd2=G:\caffe\bin\convert_mnist_data.exe F:\py\mnist\t10k-images.idx3-ubyte F:\py\mnist\t10k-labels.idx1-ubyte F:\py\mni st\mnist_test_lmdb --backend=lmdb I0302 14:46:40.795168 14096 common.cpp:36] System entropy source not available, using fallback algorithm to generate see d instead. I0302 14:46:40.798168 14096 db_lmdb.cpp:40] Opened lmdb F:\py\mnist\mnist_test_lmdb I0302 14:46:40.798168 14096 convert_mnist_data.cpp:93] A total of 10000 items. I0302 14:46:40.798168 14096 convert_mnist_data.cpp:94] Rows: 28 Cols: 28 I0302 14:46:41.637217 14096 convert_mnist_data.cpp:113] Processed 10000 files. PS G:\Projects\caffe\examples\mnist> ``` 生成好训练的数据了 # LeNet:MNIST分类模型 在我们实际运行培训计划之前,让我们先解释一下会发生什么。我们将使用已知在数字分类任务中运作良好的[LeNet](http://yann.lecun.com/exdb/publis/pdf/lecun-01a.pdf)网络。我们将使用与原始LeNet实施方案略有不同的版本,用神经元的整流线性单位(ReLU)激活代替S形激活。 LeNet的设计包含了CNN的本质,这些CNN仍然用于ImageNet等大型模型中。一般来说,它由一个卷积层,一个汇集层,另一个卷积层,然后是一个汇集层,然后是两个完全连接的层,类似于传统的多层感知器。我们已经定义了图层`$CAFFE_ROOT/examples/mnist/lenet_train_test.prototxt`。 # 定义MNIST网络 本节介绍了`lenet_train_test.prototxt`指定MNIST手写数字分类的LeNet模型的模型定义。我们假设您熟悉[Google Protobuf](https://developers.google.com/protocol-buffers/docs/overview),并且假设您已经阅读了Caffe使用的protobuf定义,可以在这里找到它`$CAFFE_ROOT/src/caffe/proto/caffe.proto`。 具体来说,我们将编写一个`caffe::NetParameter`(或python `caffe.proto.caffe_pb2.NetParameter`)protobuf。我们将首先给网络一个名字: ``` name: "LeNet" ``` # 编写数据层 目前,我们将从我们之前在演示中创建的lmdb中读取MNIST数据。这由数据层定义: ``` layer { name: "mnist" type: "Data" transform_param { scale: 0.00390625 } data_param { source: "mnist_train_lmdb" backend: LMDB batch_size: 64 } top: "data" top: "label" } ``` 具体来说,该图层具有名称mnist,类型data,并从给定的lmdb源读取数据。我们将使用64的批处理大小,并缩放输入像素,使其位于[0,1)范围内。为什么0.00390625?它是1除以256.最后,这一层产生两个斑点,一个是data斑点,另一个是label斑点。 # 编写卷积层 我们来定义第一个卷积层: ``` layer { name: "conv1" type: "Convolution" param { lr_mult: 1 } param { lr_mult: 2 } convolution_param { num_output: 20 kernel_size: 5 stride: 1 weight_filler { type: "xavier" } bias_filler { type: "constant" } } bottom: "data" top: "conv1" } ``` 该图层采用dataBlob(由数据层提供)并生成conv1图层。它产生20个通道的输出,卷积核大小为5,执行步长为1。 填充符允许我们随机初始化权重和偏差的值。对于加权填充器,我们将使用xavier基于输入和输出神经元数自动确定初始化规模的算法。对于偏置填充器,我们将简单地将其初始化为常量,默认填充值为0。 lr_mults是图层可学习参数的学习速率调整。在这种情况下,我们将设置权重学习率与求解器在运行时给出的学习率相同,并且偏差学习率为此的两倍 - 这通常会导致更好的收敛率。 # 编写池图层 唷。合并图层实际上更容易定义: ``` layer { name: "pool1" type: "Pooling" pooling_param { kernel_size: 2 stride: 2 pool: MAX } bottom: "conv1" top: "pool1" } ``` 这就是说,我们将执行池大小为2的最大池和跨度为2(所以相邻池区之间不重叠)。 同样,你可以写出第二个卷积和合并图层。检查$CAFFE_ROOT/examples/mnist/lenet_train_test.prototxt细节。 # 编写完全连接的层 编写完全连接的图层也很简单: ``` layer { name: "ip1" type: "InnerProduct" param { lr_mult: 1 } param { lr_mult: 2 } inner_product_param { num_output: 500 weight_filler { type: "xavier" } bias_filler { type: "constant" } } bottom: "pool2" top: "ip1" } ``` 这定义了InnerProduct具有500个输出的完全连接的层(在Caffe中称为层)。所有其他线看起来很熟悉,对吧? # 编写ReLU层 ReLU层也很简单: ``` layer { name: "relu1" type: "ReLU" bottom: "ip1" top: "ip1" } ``` 由于ReLU是一种按元素操作,我们可以进行就地操作以节省一些内存。这是通过简单地给底部和顶部斑点赋予相同的名称来实现的。当然,不要为其他图层类型使用重复的斑点名称! 在ReLU层之后,我们将编写另一个内部产品层: ``` layer { name: "ip2" type: "InnerProduct" param { lr_mult: 1 } param { lr_mult: 2 } inner_product_param { num_output: 10 weight_filler { type: "xavier" } bias_filler { type: "constant" } } bottom: "ip1" top: "ip2" } ``` # 编写损失层 最后,我们会写下损失! ``` layer { name: "loss" type: "SoftmaxWithLoss" bottom: "ip2" bottom: "label" } ``` 该softmax_loss层实现softmax和多项logistic损失(这节省了时间并提高了数值稳定性)。它需要两个blob,第一个是预测,第二个是label数据层提供的(记住它?)。它不会产生任何输出 - 它只是计算损失函数值,在反向传播开始时报告它,并针对其启动梯度ip2。这是所有魔法开始的地方。 其他注意事项:编写层规则 图层定义可以包含是否以及何时包含在网络定义中的规则,如下所示: ``` layer { // ...layer definition... include: { phase: TRAIN } } ``` 这是一个规则,它根据当前网络的状态控制网络中的层包含。您可以参考$CAFFE_ROOT/src/caffe/proto/caffe.proto关于图层规则和模型模式的更多信息。 在上面的例子中,这一层将只包含在TRAIN阶段中。如果我们改变TRAIN了TEST,那么这个图层将仅用于测试阶段。默认情况下,没有图层规则,图层始终包含在网络中。因此,lenet_train_test.prototxt有DATA两层定义(不同batch_size),一个用于训练阶段,另一个用于测试阶段。此外,还有一个Accuracy仅包含在TEST阶段中的层,用于每100次迭代报告模型精度,如定义lenet_solver.prototxt。 # 定义MNIST求解器 查看解释原型文件中每一行的注释$CAFFE_ROOT/examples/mnist/lenet_solver.prototxt: ``` # The train/test net protocol buffer definition 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 # Carry out testing every 500 training iterations. test_interval: 500 # The base learning rate, momentum and the weight decay of the network. base_lr: 0.01 momentum: 0.9 weight_decay: 0.0005 # The learning rate policy lr_policy: "inv" gamma: 0.0001 power: 0.75 # Display every 100 iterations display: 100 # The maximum number of iterations max_iter: 10000 # snapshot intermediate results snapshot: 5000 snapshot_prefix: "examples/mnist/lenet" # solver mode: CPU or GPU solver_mode: GPU ``` # 培训和测试模型 在编写网络定义protobuf和求解器protobuf文件后,训练模型很简单。train_lenet.sh直接运行,或直接执行以下命令: ``` cd $CAFFE_ROOT ./examples/mnist/train_lenet.sh ``` train_lenet.sh是一个简单的脚本,但这里是一个快速解释:训练的主要工具是caffe动作train和解算器protobuf文本文件作为其参数。 当你运行代码时,你会看到很多像这样飞行的消息: ``` I1203 net.cpp:66] Creating Layer conv1 I1203 net.cpp:76] conv1 <- data I1203 net.cpp:101] conv1 -> conv1 I1203 net.cpp:116] Top shape: 20 24 24 I1203 net.cpp:127] conv1 needs backward computation. ``` 这些消息告诉你关于每一层的细节,它的连接和它的输出形状,这可能对调试有帮助。初始化后,培训将开始: ``` I1203 net.cpp:142] Network initialization done. I1203 solver.cpp:36] Solver scaffolding done. I1203 solver.cpp:44] Solving LeNet ``` 基于求解器设置,我们将每100次迭代打印一次训练损失函数,并且每500次迭代测试一次​​网络。你会看到这样的消息: ``` I1203 solver.cpp:204] Iteration 100, lr = 0.00992565 I1203 solver.cpp:66] Iteration 100, loss = 0.26044 ... I1203 solver.cpp:84] Testing net I1203 solver.cpp:111] Test score #0: 0.9785 I1203 solver.cpp:111] Test score #1: 0.0606671 ``` 对于每次训练迭代,lr该迭代的学习率loss是训练函数。对于测试阶段的输出,得分0是准确度,得分1是测试损失函数。 几分钟后,你就完成了! ``` I1203 solver.cpp:84] Testing net I1203 solver.cpp:111] Test score #0: 0.9897 I1203 solver.cpp:111] Test score #1: 0.0324599 I1203 solver.cpp:126] Snapshotting to lenet_iter_10000 I1203 solver.cpp:133] Snapshotting solver state to lenet_iter_10000.solverstate I1203 solver.cpp:78] Optimization Done. ``` 存储为二进制protobuf文件的最终模型存储在 `lenet_iter_10000` 如果您正在对现实世界的应用程序数据集进行培训,那么您可以在应用程序中将其部署为训练有素的模型。 # 呃... GPU训练如何? 你刚刚做到了!所有的培训都是在GPU上进行的。事实上,如果您想对CPU进行培训,您可以简单地更改一行lenet_solver.prototxt: ``` # solver mode: CPU or GPU solver_mode: CPU ``` 并且您将使用CPU进行培训。这不容易吗? MNIST是一个小型数据集,因此使用GPU进行培训并不会因通信开销而带来太多好处。在更复杂模型的大型数据集上,如ImageNet,计算速度差异将更为显着。 # 训练跑起来了 ![image.png](http://upload-images.jianshu.io/upload_images/4907501-0826925204efe65b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) # 模型也训练好了 ![image.png](http://upload-images.jianshu.io/upload_images/4907501-4536a55f95582aee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) # 现在的文件目录结构 ![image.png](http://upload-images.jianshu.io/upload_images/4907501-6ef2473e35eb247f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) # 前面又是 sh.改成cmd. ``` train_lenet.cmd G:\caffe\bin\caffe train --solver=lenet_solver.prototxt ``` lenet_solver.prototxt lenet_train_test.prototxt 这两个文件从demo里面就有..自己复制一下. 需要打开文件,修改路径 ![image.png](http://upload-images.jianshu.io/upload_images/4907501-e28853af5b2f57cd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![image.png](http://upload-images.jianshu.io/upload_images/4907501-bf8d37adcab9fdc4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 然后就可以训练了。 # 预测 ## 生成均值文件 ``` compute_image_mean.cmd G:\caffe\bin\compute_image_mean mnist_train_lmdb mean.binaryproto pause ``` 跑一下就出来均值文件了 ``` test_lenet-one.cmd G:\caffe\bin\classification lenet.prototxt ./lenet/_iter_10000.caffemodel mean.binaryproto label.txt ./test/3.png pause ``` ![image.png](http://upload-images.jianshu.io/upload_images/4907501-1bdf56e301743937.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) python调用caffe模型 ``` import sys import caffe classifier = caffe.Classifier( 'lenet.prototxt', 'lenet/_iter_10000.caffemodel', image_dims=(28, 28), mean=None, input_scale=None, raw_scale=255, channel_swap=None) img = 'test/3.png' inputs = [caffe.io.load_image(img, False)] # rgb to gray scores = classifier.predict(inputs, False).argmax() scores ``` # ![image.png](http://upload-images.jianshu.io/upload_images/4907501-eef2552c8fa2e290.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)