英文版原文 https://nbviewer.jupyter.org/github/BVLC/caffe/blob/master/examples/01-learning-lenet.ipynb
任务 Define, train, and test the classic LeNet with the Python interface.
教程代码
from caffe.proto import caffe_pb2
from caffe import Layers as L, params as P
import os
import caffe
from pylab import *
import sys
caffe_root = '../'
sys.path.insert(0, caffe_root + 'python')
os.chdir(caffe_root)
os.system('data/mnist/get_mnist.sh') # 下载数据
os.system('examples/mnist/create_mnist.sh') # 准备数据
os.chdir('examples')
def lenet(lmdb, batch_size):
n = caffe.NetSpec() # 获取Caffe的一个net
n.data, n.label = L.Data(
batch_size=batch_size,
backend=P.Data.LMDB, # 选择使用 LEVELDB 还是 LMDB
source=lmdb, # 数据库文件的路径
# 特征归一化系数,将[0,255]归一化
transform_param=dict(scale=1. / 255),
ntop=2) # 2个输出,分别是data和label
n.conv1 = L.Convolution(
n.data,
kernel_size=5, # 指定卷积核的高度和宽度
num_output=20, # 指定卷积核的数量
weight_filler=dict(type='xavier')) # 指定参数的初始化方案
n.pool1 = L.Pooling(
n.conv1,
kernel_size=2, # 指定池化窗口的高度和宽度
stride=2, # 指定池化窗口在输入数据上的滑动步长
pool=P.Pooling.MAX) # 最大值池化
n.conv2 = L.Convolution(
n.pool1,
kernel_size=5,
num_output=50,
weight_filler=dict(type='xavier'))
n.pool2 = L.Pooling(n.conv2, kernel_size=2, stride=2, pool=P.Pooling.MAX)
n.fc1 = L.InnerProduct(
n.pool2,
num_output=500, # 层的输出节点,即滤波器的个数
weight_filler=dict(type('xavier')))
n.relu1 = L.ReLU(n.fc1, in_place=True) # 相同名字占用同一空间,节省内存
n.score = L.InnerProduct(
n.relu1, num_output=10, weight_filler=dict(type='xavier'))
n.loss = L.SoftmaxWithLoss(n.score, n.label)
return n.to_proto() # 返回生成的配置文件
# 保存训练网络定义
with open('mnist/lenet_auto_train.prototxt', 'w') as f:
f.write(str(lenet('mnist/mnist_train_lmdb', 64)))
# 保存测试网络定义
with open('mnist/lenet_auto_test.prototxt', 'w') as f:
f.write(str(lenet('mnist/mnist_test_lmdb', 100)))
caffe.set_mode_gpu()
caffe.set_device(0) # 指定GPU的ID号
solver = None
solver = caffe.SGDSolver('mnist/lenet_auto_solver.prototxt') # 表示求解接口
# 打印出(层名, (batch size, 特征维度, 空间维度))
print([(k, v.data.shape) for k, v in solver.net.blobs.items()])
# 打印出(层名, (权重参数维度))
print([(k, v[0].data.shape) for k, v in solver.net.params.items()])
# 前向传播训练网络
solver.net.forward()
solver.test_nets[0].forward()
# 展示训练结果及原图
imshow(
solver.net.blobs['data'].data[:8, 0].transpose(1, 0, 2).reshape(
28, 8 * 28),
cmap='gray')
axis('off')
print('train labels:', solver.net.blobs['label']).data[:8]
# 展示测试结果及原图
imshow(
solver.test_nets[0].blobs['data'].data[:8, 0].tranpsose(1, 0, 2).reshape(
28, 8 * 28),
cmap='gray')
print('test labels:', solver.test_nets[0].blobs['label'].data[:8])
axis('off')
solver.step(1) # 迭代一步
imshow(
solver.net.params['conv1'][0].diff[:, 0].reshape(4, 5, 5, 5).transpose(
0, 2, 1, 3).reshape(4 * 5, 5 * 5),
cmap='gray')
axis('off')
niter = 200 # 总迭代次数
test_interval = 25 # 每25次测试一次
train_loss = zeros(niter) # 训练误差
test_acc = zeros(int(np.ceil(niter / test_interval))) # 测试正确率
output = zeros((niter, 8, 10))
# 主迭代循环
for it in range(niter):
solver.step(1) # 迭代一步 Caffe使用SGD
train_loss[it] = solver.net.blobs['loss'].data # 训练误差
solver.test_nets[0].forward(start='conv1') # 从conv1开始前向传播
output[it] = solver.test_nets[0].blobs['score'].data[:8] # 保存测试概率结果
if it % test_interval == 0: # 到达该测试的轮数
print('Iteration' + str(it) + 'testing...')
correct = 0
for test_it in range(100):
solver.test_nets[0].forward() # 从头开始前向传播
correct += sum(solver.test_nets[0].blobs['score'].data.argmax(1) ==
solver.test_nets[0].blobs['label'].data) # 统计正确的总个数
test_acc[it // test_interval] = correct / 1e4 # 正确个数除以总数10000
_, ax1 = subplots()
ax2 = ax1.twinx() # 双y轴
ax1.plot(arange(niter), train_loss) # x 迭代次数 y 训练误差
ax2.plot(test_interval * arange(len(test_acc)),
test_acc, 'r') # x 固定测试节点*一轮样本个数 y 测试误差
ax1.set_xlabel('iteration')
ax1.set_ylabel('train loss')
ax2.set_ylabel('test accuarcy')
ax2.set_title('Test Accuracy: {:,2f}'.format(test_acc[-1]))
# 展示一张图片属于10个数字的原始概率分布
for i in range(8):
figure(figsize=(2, 2))
imshow(solver.test_nets[0].blobs['data'].data[i, 0], cmap='gray') # 打印原始图片
figure(figsize=(10, 2))
imshow(output[:50, i].T, interpolation='nearest',
cmap='gray') # 渐变打印颜色条,越白可能性越高
xlabel('iteration')
ylabel('label')
# 展示一张图片属于10个数字的softmax概率分布
for i in range(8):
figure(figsize=(2, 2))
imshow(solver.test_nets[0].blobs['data'].data[i, 0], cmap='gray') # 打印原始图片
figure(figsize=(10, 2))
imshow(
exp(output[:50, i].T) / exp(output[:50, i].T).sum(0),
interpolation='nearest',
camp='gray') # 渐变打印颜色条,越白可能性越高
xlabel('iteration')
ylabel('label')
模板代码
from caffe.proto import caffe_pb2
from caffe import Layers as L, params as P
import os
import caffe
from pylab import *
import sys
caffe_root = '../'
sys.path.insert(0, caffe_root + 'python')
train_net_path = 'mnist/custom_auto_train.prototxt' # 训练网络定义
test_net_path = 'mnist/custom_auto_test.prototxt' # 测试网络定义
solver_config_path = 'mnist/custom_auto_solver.prototxt' # 学习参数定义
# 定义网络
def custom_net(lmdb, batch_size):
# 开始定义网络
n = caffe.NetSpec()
# 对于所有的网络保持这一data层的定义
n.data, n.label = L.Data(
batch_size=batch_size,
backend=P.Data.LMDB, # 选择使用 LEVELDB 还是 LMDB
source=lmdb, # 数据库文件的路径
# 特征归一化系数,将[0,255]归一化
transform_param=dict(scale=1. / 255),
ntop=2) # 2个输出,分别是data和label
# 在这里编辑以定义不同的网络结构
# 默认的网络结构是一个简单的现行分类器
# 本质上是定义了一个多变量现行回归
n.score = L.InnerProduct(
n.data, num_output=10, weight_filler=dict(type='xavier'))
# 这里定义我们已经尝试过的LeNet网络
n.conv1 = L.Convolution(
n.data,
kernel_size=5, # 指定卷积核的高度和宽度
num_output=20, # 指定卷积核的数量
weight_filler=dict(type='xavier')) # 指定参数的初始化方案
n.pool1 = L.Pooling(
n.conv1,
kernel_size=2, # 指定池化窗口的高度和宽度
stride=2, # 指定池化窗口在输入数据上的滑动步长
pool=P.Pooling.MAX) # 最大值池化
n.conv2 = L.Convolution(
n.pool1,
kernel_size=5,
num_output=50,
weight_filler=dict(type='xavier'))
n.pool2 = L.Pooling(n.conv2, kernel_size=2, stride=2, pool=P.Pooling.MAX)
n.fc1 = L.InnerProduct(
n.pool2,
num_output=500, # 层的输出节点,即滤波器的个数
weight_filler=dict(type('xavier')))
# 可以在这里使用L.ELU或者L.Sigmoid尝试非线性
n.relu1 = L.ReLU(n.fc1, in_place=True) # 相同名字占用同一空间,节省内存
n.score = L.InnerProduct(
n.relu1, num_output=10, weight_filler=dict(type='xavier'))
# 对所有的网络保持这一loss层的定义
n.loss = L.SoftmaxWithLoss(n.score, n.label)
return n.to_proto() # 返回生成的配置文件
# 保存训练网络定义
with open(train_net_path, 'w') as f:
f.write(str(custom_net('mnist/mnist_train_lmdb', 64)))
# 保存测试网络定义
with open(test_net_path, 'w') as f:
f.write(str(custom_net('mnist/mnist_test_lmdb', 100)))
# 定义solver
s = caffe_pb2.SolverParameter()
# 定义一个随机种子,以控制训练的随机初始化
s.random_seed = 0xCAFFE
# 确定训练和测试网络的地址
s.train_net = train_net_path # 训练地址
s.test_net.append(test_net_path) # 测试地址
s.test_interval = 500 # 每500次测试一次
s.test_iter.append(100) # 每次测试测试100个样本
s.max_iter = 10000 # 总的训练迭代次数
# 这里可以尝试不同的solver,比如"SGD", "Adam", 和 "Nesterov"等等
s.type = "SGD"
# 设置SGD的初始学习率
s.base_lr = 0.01 # 不同的学习率
s.momentum = 0.9 # 设置动量参数,参考现在和之前的更新来加速学习速度
s.weight_decay = 5e-4 # 设置衰减率来正则化以防止过拟合
# 设置'lr_policy'参数来确定训练过程中的学习率修改,我们使用默认的LeNet网络参数
# 其他的lr_policy选择参考 https://www.cnblogs.com/laowangxieboke/p/10282096.html
s.lr_policy = 'inv'
s.gamma = 0.0001
s.power = 0.75
# 这里可以设置固定的学习率
# s.lr_policy = 'fixed'
# 每1000次迭代后统计预测结果并计算训练误差
s.display = 1000
# 对中途的训练网络通过快照的方式进行保存
s.snapshot = 5000 # 5000次保存一次,在总训练次数10000中保存两次
s.snapshot_prefix = 'mnist/custom_net'
# 在GPU上进行训练
s.solver_mode = caffe_pb2.SolverParameter.GPU
# 在临时文件中写入solver并返回它的文件名
with open(solver_config_path, 'w') as f:
f.write(str(s))
# 加载solver,创建训练过程,测试网络
# 忽略掉lmdb数据的解决方案(无法在同一数据上实例化两个solver)
solver = None
solver = caffe.get_solver(solver_config_path)
# 定义solve
niter = 250 # 定义总的训练次数
test_interval = niter / 10 # 定义总的测试次数
train_loss = zeros(niter) # 在日志中保存loss
test_acc = zeros(int(np.ceil(niter / test_interval)))
# 主训练迭代
for it in range(niter):
solver.step(1) # Caffe使用SGD随机梯度下降
train_loss[it] = solver.net.blobs['loss'].data # 保存训练误差
if it % test_interval == 0: # 到达该测试的轮数
print('Iteration' + str(it) + 'testing...')
correct = 0
for test_it in range(100):
solver.test_nets[0].forward() # 从头开始前向传播
correct += sum(solver.test_nets[0].blobs['score'].data.argmax(1) ==
solver.test_nets[0].blobs['label'].data) # 统计正确的总个数
test_acc[it // test_interval] = correct / 1e4 # 正确个数除以总数10000
_, ax1 = subplots()
ax2 = ax1.twinx() # 双y轴
ax1.plot(arange(niter), train_loss) # x 迭代次数 y 训练误差
ax2.plot(test_interval * arange(len(test_acc)),
test_acc, 'r') # x 固定测试节点*一轮样本个数 y 测试误差
ax1.set_xlabel('iteration')
ax1.set_ylabel('train loss')
ax2.set_ylabel('test accuarcy')
ax2.set_title('Test Accuracy: {:,2f}'.format(test_acc[-1]))