本教程创建一个小的神经网络用于手写字符的识别。我们使用MNIST数据集进行训练和测试。这个数据集的训练集包含60000张来自500个人的手写字符的图像,测试集包含10000张独立于训练集的测试图像。你可以参看本教程的Ipython notebook

本节中,我们使用CNN的模型助手来创建网络并初始化参数。首先import所需要的依赖库。

%matplotlib inline
from matplotlib import pyplot
import numpy as np
import os
import shutil
from caffe2.python import core, cnn, net_drawer, workspace, visualize
# 如果你想更加详细的了解初始化的过程,那么你可以把caffe2_log_level=0 改为-1
core.GlobalInit(['caffe2', '--caffe2_log_level=0'])
caffe2_root = "~/caffe2"
print("Necessities imported!")
数据准备

我们会跟踪训练过程的数据,并保存到一个本地的文件夹。我们需要先设置一个数据文件和根文件夹。在数据文件夹里,放置用于训练和测试的MNIST数据集。如果没有数据集,那么你可以到这里下载MNIST Dataset,然后解压数据集和标签。

./make_mnist_db --channel_first --db leveldb --image_file ~/Downloads/train-images-idx3-ubyte --label_file ~/Downloads/train-labels-idx1-ubyte --output_file ~/caffe2/caffe2/python/tutorials/tutorial_data/mnist/mnist-train-nchw-leveldb

./make_mnist_db --channel_first --db leveldb --image_file ~/Downloads/t10k-images-idx3-ubyte --label_file ~/Downloads/t10k-labels-idx1-ubyte --output_file ~/caffe2/caffe2/python/tutorials/tutorial_data/mnist/mnist-test-nchw-leveldb

这段代码实现和上面的一样的功能

# 这部分将你的图像转换成leveldb
current_folder = os.getcwd()
data_folder = os.path.join(current_folder, 'tutorial_data', 'mnist')
root_folder = os.path.join(current_folder, 'tutorial_files', 'tutorial_mnist')
image_file_train = os.path.join(data_folder, "train-images-idx3-ubyte")
label_file_train = os.path.join(data_folder, "train-labels-idx1-ubyte")
image_file_test = os.path.join(data_folder, "t10k-images-idx3-ubyte")
label_file_test = os.path.join(data_folder, "t10k-labels-idx1-ubyte") def DownloadDataset(url, path):
import requests, zipfile, StringIO
print "Downloading... ", url, " to ", path
r = requests.get(url, stream=True)
z = zipfile.ZipFile(StringIO.StringIO(r.content))
z.extractall(path)
if not os.path.exists(data_folder):
os.makedirs(data_folder)
if not os.path.exists(label_file_train):
DownloadDataset("https://s3.amazonaws.com/caffe2/datasets/mnist/mnist.zip", data_folder) def GenerateDB(image, label, name):
name = os.path.join(data_folder, name)
print 'DB name: ', name
syscall = "/usr/local/binaries/make_mnist_db --channel_first --db leveldb --image_file " + image + " --label_file " + label + " --output_file " + name
print "Creating database with: ", syscall
os.system(syscall) # 生成leveldb
GenerateDB(image_file_train, label_file_train, "mnist-train-nchw-leveldb")
GenerateDB(image_file_test, label_file_test, "mnist-test-nchw-leveldb") if os.path.exists(root_folder):
print("Looks like you ran this before, so we need to cleanup those old workspace files...")
shutil.rmtree(root_folder) os.makedirs(root_folder)
workspace.ResetWorkspace(root_folder) print("training data folder:"+data_folder)
print("workspace root folder:"+root_folder)
模型创建

CNNModelHelper封装了很多函数,它能将参数初始化和真实的计算分成两个网络中实现。底层实现是,CNNModelHelper有两个网络param_init_netnet,这两个网络分别记录着初始化网络和主网络。为了模块化,我们将模型分割成多个不同的部分。

- 数据输入(AddInput 函数)

- 主要的计算部分(AddLeNetModel 函数)

- 训练部分-梯度操作,参数更新等等 (AddTrainingOperators函数)

- 记录数据部分,比如需要展示训练过程的相关数据(AddBookkeepingOperators 函数)

  1. AddInput会从一个DB中载入数据。我们将MNIST保存为像素值,并且我们用浮点数进行计算,所以我们的数据也必须是Float类型。为了数值稳定性,我们将图像数据归一化到[0,1]而不是[0,255]。注意,我们做的事in-place操作,会覆盖原来的数据,因为我们不需要归一化前的数据。准备数据这个操作,在后向传播时,不需要进行梯度计算。所以我们使用StopGradient来告诉梯度生成器:“不用将梯度传递给我。”
def AddInput(model, batch_size, db, db_type):
# 载入数据和标签
data_uint8, label = model.TensorProtosDBInput(
[], ["data_uint8", "label"], batch_size=batch_size,
db=db, db_type=db_type)
# 转化为 float
data = model.Cast(data_uint8, "data", to=core.DataType.FLOAT)
#归一化到 [0,1]
data = model.Scale(data, data, scale=float(1./256))
# 后向传播不需要梯度
data = model.StopGradient(data, data)
return data, label
print("Input function created.")

输出

Input function created.
  1. AddLeNetModel输出softmax.
def AddLeNetModel(model, data):
conv1 = model.Conv(data, 'conv1', 1, 20, 5)
pool1 = model.MaxPool(conv1, 'pool1', kernel=2, stride=2)
conv2 = model.Conv(pool1, 'conv2', 20, 50, 5)
pool2 = model.MaxPool(conv2, 'pool2', kernel=2, stride=2)
fc3 = model.FC(pool2, 'fc3', 50 * 4 * 4, 500)
fc3 = model.Relu(fc3, fc3)
pred = model.FC(fc3, 'pred', 500, 10)
softmax = model.Softmax(pred, 'softmax')
return softmax
print("Model function created.")
Model function created.
  1. AddTrainingOperators函数函数用于添加训练操作。

    AddAccuracy函数输出模型的准确率,我们会在下一个函数使用它来跟踪准确率。
def AddAccuracy(model, softmax, label):
accuracy = model.Accuracy([softmax, label], "accuracy")
return accuracy
print("Accuracy function created.")
Accuracy function created.

首先添加一个op:LabelCrossEntropy,用于计算输入和lebel的交叉熵。这个操作在得到softmax后和计算loss前。输入是[softmax, label],输出交叉熵用xent表示。

xent = model.LabelCrossEntropy([softmax, label], 'xent')

AveragedLoss将交叉熵作为输入,并计算出平均损失loss

loss = model.AveragedLoss(xent, "loss")

AddAccuracy为了记录训练过程,我们使用AddAccuracy 函数来计算。

AddAccuracy(model, softmax, label)

接下来这步至关重要:我们把所有梯度计算添加到模型上。梯度是根据我们前面的loss计算得到的。

model.AddGradientOperators([loss])

然后进入迭代

ITER = model.Iter("iter")

更新学习率使用策略是lr = base_lr * (t ^ gamma) ,注意我们是在最小化,所以基础学率是负数,这样我们才能向山下走。

LR = model.LearningRate(ITER, "LR", base_lr=-0.1, policy="step", stepsize=1, gamma=0.999 )
#ONE是一个在梯度更新阶段用的常量。只需要创建一次,并放在param_init_net中。
ONE = model.param_init_net.ConstantFill([], "ONE", shape=[1], value=1.0)

现在对于每一和参数,我们做梯度更新。注意我们如何获取每个参数的梯度——CNNModelHelper保持跟踪这些信息。更新的方式很简单,是简单的相加: param = param + param_grad * LR

for param in model.params:
param_grad = model.param_to_grad[param]
model.WeightedSum([param, ONE, param_grad, LR], param)

我们需要每隔一段时间检查参数。这可以通过Checkpoint 操作。这个操作有一个参数every表示每多少次迭代进行一次这个操作,防止太频繁去检查。这里,我们每20次迭代进行一次检查。

model.Checkpoint([ITER] + model.params, [],
db="mnist_lenet_checkpoint_%05d.leveldb",
db_type="leveldb", every=20)

然后我们得到整个AddTrainingOperators函数如下:

def AddTrainingOperators(model, softmax, label):
# 计算交叉熵
xent = model.LabelCrossEntropy([softmax, label], 'xent')
# 计算loss
loss = model.AveragedLoss(xent, "loss")
#跟踪模型的准确率
AddAccuracy(model, softmax, label)
#添加梯度操作
model.AddGradientOperators([loss])
# 梯度下降
ITER = model.Iter("iter")
# 学习率
LR = model.LearningRate(
ITER, "LR", base_lr=-0.1, policy="step", stepsize=1, gamma=0.999 )
ONE = model.param_init_net.ConstantFill([], "ONE", shape=[1], value=1.0)
# 梯度更新
for param in model.params:
param_grad = model.param_to_grad[param]
model.WeightedSum([param, ONE, param_grad, LR], param)
# 每迭代20次检查一次
# you may need to delete tutorial_files/tutorial-mnist to re-run the tutorial
model.Checkpoint([ITER] + model.params, [],
db="mnist_lenet_checkpoint_%05d.leveldb",
db_type="leveldb", every=20)
print("Training function created.")
Training function created.
  1. **AddBookkeepingOperators **添加一些记录操作,这些操作不会影响训练过程。他们只是收集数据和打印出来或者写到log里面去。
def AddBookkeepingOperators(model):
# 输出 blob的内容. to_file=1 表示输出到文件,文件保存的路径是 root_folder/[blob name]
model.Print('accuracy', [], to_file=1)
model.Print('loss', [], to_file=1)
# Summarizes 给出一些参数比如均值,方差,最大值,最小值
for param in model.params:
model.Summarize(param, [], to_file=1)
model.Summarize(model.param_to_grad[param], [], to_file=1)
print("Bookkeeping function created")
  1. 定义网络

    现在让我们将真正创建模型。前面写的函数将真正被执行。回忆我们四步。
-data input
-main computation
-training
-bookkeeping

在我们读进数据前,我们需要定义我们训练模型。我们将使用到前面定义的所有东西。我们将在MNIST数据集上使用NCHW的储存顺序。

train_model = cnn.CNNModelHelper(order="NCHW", name="mnist_train")
data, label = AddInput(train_model, batch_size=64,
db=os.path.join(data_folder, 'mnist-train-nchw-leveldb'), db_type='leveldb')
softmax = AddLeNetModel(train_model, data)
AddTrainingOperators(train_model, softmax, label)
AddBookkeepingOperators(train_model)
# Testing model. 我们设置batch=100,这样迭代100次就能覆盖10000张测试图像
# 对于测试模型,我们需要数据输入 ,LeNetModel,和准确率三部分
#注意到init_params 设置为False,是因为我们从训练网络获取参数。
test_model = cnn.CNNModelHelper(order="NCHW", name="mnist_test", init_params=False)
data, label = AddInput(test_model, batch_size=100,
db=os.path.join(data_folder, 'mnist-test-nchw-leveldb'), db_type='leveldb')
softmax = AddLeNetModel(test_model, data)
AddAccuracy(test_model, softmax, label) # Deployment model. 我们仅需要LeNetModel 部分
deploy_model = cnn.CNNModelHelper(order="NCHW", name="mnist_deploy", init_params=False)
AddLeNetModel(deploy_model, "data")
#你可能好奇deploy_model的param_init_net 发生了什么,在这节中,我们没有使用它,
#因为在deployment 阶段,我们不会随机初始化参数,而是从本地载入。
print('Created training and deploy models.')

现在让我们用caffe2的可视化工具看看Training和Deploy模型是什么样子的。如果下面的命令运行失败,那可能是因为你的机器没有安装graphviz。可以用如下命令安装:

sudo yum install graphviz #ubuntu 用户sudo apt-get install graphviz

图看起来可能很小,右键点击在新的窗口打开就能看清。

from IPython import display
graph = net_drawer.GetPydotGraph(train_model.net.Proto().op, "mnist", rankdir="LR")
display.Image(graph.create_png(), width=800)



现在上图展示了训练阶段的一切东西。白色的节点是blobs,绿色的矩形节点是operators.你可能留意到大规模的像火车轨道一样的平行线。这些依赖关系从前前向传播的blobs指向到后向传播的操作。

让我们仅仅展示必要的依赖关系和操作。如果你细心看,你会发现,左半图式前向传播,右半图式后向传播,在最右边是一系列参数更新操作和summarization .

graph = net_drawer.GetPydotGraphMinimal(
train_model.net.Proto().op, "mnist", rankdir="LR", minimal_dependency=True)
display.Image(graph.create_png(), width=800)



现在我们可以通过Python来跑起网络,记住,当我们跑起网络时,我们随时可以从网络中拿出blob数据,下面先来展示下如何进行这个操作。

我们重申一下,CNNModelHelper 类目前没有执行任何东西。他目前做的仅仅是声明网络,只是简单的创建了protocol buffers.例如我们可以展示网络一部分序列化的protobuf。

print(str(train_model.param_init_net.Proto())[:400] + '\n...')

当然,我们也可以把protobuf写到本地磁盘中去,这样可以方便的查看。你会发现这些protobuf和以前的Caffe网络定义很相似。

with open(os.path.join(root_folder, "train_net.pbtxt"), 'w') as fid:
fid.write(str(train_model.net.Proto()))
with open(os.path.join(root_folder, "train_init_net.pbtxt"), 'w') as fid:
fid.write(str(train_model.param_init_net.Proto()))
with open(os.path.join(root_folder, "test_net.pbtxt"), 'w') as fid:
fid.write(str(test_model.net.Proto()))
with open(os.path.join(root_folder, "test_init_net.pbtxt"), 'w') as fid:
fid.write(str(test_model.param_init_net.Proto()))
with open(os.path.join(root_folder, "deploy_net.pbtxt"), 'w') as fid:
fid.write(str(deploy_model.net.Proto()))
print("Protocol buffers files have been created in your root folder: "+root_folder)

现在,让我们进入训练过程。我们使用Python来训练。当然也可以使用C++接口来训练。这留在另一个教程讨论。

训练网络

首先,初始化网络是必须的

workspace.RunNetOnce(train_model.param_init_net)

接着我们创建训练网络并,加载到workspace中去。

workspace.CreateNet(train_model.net)

然后设置迭代200次,并把准确率和loss保存到两个np矩阵中去

total_iters = 200
accuracy = np.zeros(total_iters)
loss = np.zeros(total_iters)

网络和跟踪准确loss都配置好后,我们循环调用workspace.RunNet200次,需要传入的参数是train_model.net.Proto().name.每一次迭代,我们计算准确率和loss。

for i in range(total_iters):
workspace.RunNet(train_model.net.Proto().name)
accuracy[i] = workspace.FetchBlob('accuracy')
loss[i] = workspace.FetchBlob('loss')

最后我们可以用pyplot画出结果。

# 参数初始化只需跑一次
workspace.RunNetOnce(train_model.param_init_net)
# 创建网络
workspace.CreateNet(train_model.net)
#设置迭代数和跟踪accuracy & loss
total_iters = 200
accuracy = np.zeros(total_iters)
loss = np.zeros(total_iters)
# 我们迭代200次
for i in range(total_iters):
workspace.RunNet(train_model.net.Proto().name)
accuracy[i] = workspace.FetchBlob('accuracy')
loss[i] = workspace.FetchBlob('loss')
# 迭代完画出结果
pyplot.plot(loss, 'b')
pyplot.plot(accuracy, 'r')
pyplot.legend(('Loss', 'Accuracy'), loc='upper right')



现我们可以进行抽取数据和预测了

#数据可视化
pyplot.figure()
data = workspace.FetchBlob('data')
_ = visualize.NCHW.ShowMultiple(data)
pyplot.figure()
softmax = workspace.FetchBlob('softmax')
_ = pyplot.plot(softmax[0], 'ro')
pyplot.title('Prediction for the first image')





还记得我们创建的test net吗?我们将跑一遍test net测试准确率。**注意,虽然test_model的参数来自train_model,但是仍然需要初始化test_model.param_init_net **。这次,我们只需要追踪准确率,并且只迭代100次。

workspace.RunNetOnce(test_model.param_init_net)
workspace.CreateNet(test_model.net)
test_accuracy = np.zeros(100)
for i in range(100):
workspace.RunNet(test_model.net.Proto().name)
test_accuracy[i] = workspace.FetchBlob('accuracy')
pyplot.plot(test_accuracy, 'r')
pyplot.title('Acuracy over test batches.')
print('test_accuracy: %f' % test_accuracy.mean())



译者注:这里译者不是很明白,test_model是如何从train_model获取参数的?有明白的小伙伴希望能在评论区分享一下。

MNIST教程就此结束。希望本教程能向你展示一些Caffe2的特征。

转载请注明出处:http://www.jianshu.com/c/cf07b31bb5f2

Caffe2 手写字符识别(MNIST - Create a CNN from Scratch)[8]的更多相关文章

  1. 仅用200个样本就能得到当前最佳结果:手写字符识别新模型TextCaps

    由于深度学习近期取得的进展,手写字符识别任务对一些主流语言来说已然不是什么难题了.但是对于一些训练样本较少的非主流语言来说,这仍是一个挑战性问题.为此,本文提出新模型TextCaps,它每类仅用200 ...

  2. TensorFlow 入门之手写识别(MNIST) softmax算法

    TensorFlow 入门之手写识别(MNIST) softmax算法 MNIST flyu6 softmax回归 softmax回归算法 TensorFlow实现softmax softmax回归算 ...

  3. TensorFlow 入门之手写识别(MNIST) softmax算法 二

    TensorFlow 入门之手写识别(MNIST) softmax算法 二 MNIST Fly softmax回归 softmax回归算法 TensorFlow实现softmax softmax回归算 ...

  4. TensorFlow 入门之手写识别(MNIST) 数据处理 一

    TensorFlow 入门之手写识别(MNIST) 数据处理 一 MNIST Fly softmax回归 准备数据 解压 与 重构 手写识别入门 MNIST手写数据集 图片以及标签的数据格式处理 准备 ...

  5. 利用Tensorflow实现手写字符识别

    模式识别领域应用机器学习的场景非常多,手写识别就是其中一种,最简单的数字识别是一个多类分类问题,我们借这个多类分类问题来介绍一下google最新开源的tensorflow框架,后面深度学习的内容都会基 ...

  6. 数据挖掘入门系列教程(八)之使用神经网络(基于pybrain)识别数字手写集MNIST

    目录 数据挖掘入门系列教程(八)之使用神经网络(基于pybrain)识别数字手写集MNIST 下载数据集 加载数据集 构建神经网络 反向传播(BP)算法 进行预测 F1验证 总结 参考 数据挖掘入门系 ...

  7. 利用CNN神经网络实现手写数字mnist分类

    题目: 1)In the first step, apply the Convolution Neural Network method to perform the training on one ...

  8. Tensorflow学习笔记3:卷积神经网络实现手写字符识别

    # -*- coding:utf-8 -*- import tensorflow as tf from tensorflow.examples.tutorials.mnist import input ...

  9. OpenCV OpenGL手写字符识别

    另外一篇文章地址:这个比较详细,但是程序略显简单,现在这个程序是比较复杂的 http://blog.csdn.net/wangyaninglm/article/details/17091901 整个项 ...

随机推荐

  1. 无显示器安装raspberry zero w树莓派 zero w

    笔者的环境 1. Macbook 电脑用于烧录树莓派系统到SD卡 2. 树莓派 zero w 把SD卡放进读卡器,插到Mac电脑上, 打开命令行工具Terminal diskutil list列出di ...

  2. 「JSOI2015」最小表示

    「JSOI2015」最小表示 传送门 很显然的一个结论:一条边 \(u \to v\) 能够被删去,当且仅当至少存在一条其它的路径从 \(u\) 通向 \(v\) . 所以我们就建出正反两张图,对每个 ...

  3. 安卓开发:Android Studio自动import

    我只想说,真好用!哈哈,提高效率的好东西. 参考: [https://blog.csdn.net/pjdd123/article/details/80953669] [https://www.cnbl ...

  4. django之关系及查询,数据类型,约束,分页

    目录 关系 数据列类型 数据类型的约束 分页 关系 1. 一对一(水平分表) 母表: UserInfo id name age 子表: private id salary sp 创建模型语句: cla ...

  5. EVE磁盘扩容

    1.登录到EVE 输入df -h查看一下/dev/mapper/eve--ng--vg-root这个文件目录,这里就是存放镜像的. 2.EVE关机编辑EVE虚拟机 如下图扩展目前的容量: 然后点击扩展 ...

  6. 关于this和base的区别

    一句话总结:在有冲突得时候,base和this能够进行区分,在没有冲突得时候,是一样得. 基于成员调用 基于构造方法 参考: http://www.cnblogs.com/reommmm/archiv ...

  7. JavaScript的变量提升机制

    变量提升 JavaScript的变量提升有两种,用var声明的变量以及用function声明的变量. 用var声明的变量 我们先来看下面这段代码,a的值是多少 代码1 console.log(a); ...

  8. Java入门笔记 04-异常处理

    一. 异常概述: 1. 异常体系结构图: java.lang.Throwable          |-----java.lang.Error:一般不编写针对性的代码进行处理.          |- ...

  9. Git - 常用命令, cheatsheet

    git init git add <filename1> <filename2> git add . git checkout -- <filename> git ...

  10. 一个简单的C++程序及说明

    一个简单的C++程序: #include<iostream> //头文件,因为输入cin,输出cout需要iostream using namespace std; //C++标准程序库中 ...