<div class="markdown_views">
<h2 id="前言"><a name="t0"></a><font color="#0099FF">前言</font></h2>
使用caffe也有一小段时间了,但是对于caffe的python接口总是一知半解,最近终于能静下心来,仔细阅读了caffe官方例程,并写下此博客。博文主要对caffe自带的分类例程00-classification.ipynb做了详细的注释,相信能加强这方面的理解。
准备工作
加载必要的库
import numpy as np # 加载numpy
import matplotlib.pyplot as plt # 加载matplotlib
%matplotlib inline # 此处是为了能在notebook中直接显示图像
# rcParams是一个包含各种参数的字典结构,含有多个key-value,可修改其中部分值
plt.rcParams['figure.figsize'] = (10, 10) # 图像显示大小,单位是英寸
plt.rcParams['image.interpolation'] = 'nearest' # 最近邻差值,像素为正方形
plt.rcParams['image.cmap'] = 'gray' # 使用灰度输出而不是彩色输出
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
加载caffe
import sys
caffe_root = '../' # caffe根目录,此处为相对路径,如果失灵,可换成绝对路径
# sys.path是一个列表,insert()函数插入一行,也可以使用sys.path.append('模块地址')
sys.path.insert(0, caffe_root + 'python') # 加载caffe的python模块
import caffe # 加载caffe
import os
# 如果该路径下存在caffemodel文件,则打印信息,否则从官网下载
if os.path.isfile(caffe_root + 'models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel'):
print 'CaffeNet found.'
else:
print 'Downloading pre-trained CaffeNet model...'
!../scripts/download_model_binary.py ../models/bvlc_reference_caffenet
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
加载网络以及输入预处理
设置cpu模式以及从硬盘加载网络
caffe.set_mode_cpu() # 设置caffe为cpu模式,也可设成gpu模式
model_def = caffe_root + 'models/bvlc_reference_caffenet/deploy.prototxt'
model_weights = caffe_root + 'models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel'
net = caffe.Net(model_def, # 定义模型结构
model_weights, # 包含模型训练权重
caffe.TEST) # 使用测试模式(训练中不能执行dropout)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
图像预处理
# 加载ImageNet训练集的图像均值,预处理需要减去均值
# ilsvrc_2012_mean.npy文件是numpy格式,其数据维度是(3L, 256L, 256L)
mu = np.load(caffe_root + 'python/caffe/imagenet/ilsvrc_2012_mean.npy') # 加载均值文件
mu = mu.mean(1).mean(1) # 对所有像素值取平均以此获取BGR的均值像素值
print 'mean-subtracted values:', zip('BGR', mu)
# 取平均后得到BGR均值分别是[104.00698793,116.66876762,122.67891434]
# 对输入数据进行变换
# caffe.io.transformer是一个类,实体化的时候构造函数__init__(self, inputs)给一个初值
# 其中net.blobs本身是一个字典,每一个key对应每一层的名字,#net.blobs['data'].data.shape计算结果为(10, 3, 227, 227)
transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape})
# 以下都是caffe.io.transformer类的函数方法
#caffe.io.transformer的类定义放在io.py文件中,也可用help函数查看说明
transformer.set_transpose('data', (2,0,1)) # 将图像通道数设置为outermost的维数
transformer.set_mean('data', mu) # 每个通道减去均值
transformer.set_raw_scale('data', 255) # 像素值从[0,1]变换为[0,255]
transformer.set_channel_swap('data', (2,1,0)) # 交换通道,RGB->BGR
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
运行CPU分类程序
载入图片
#设置输入图像大小
net.blobs['data'].reshape(50, # 尽管只检测一张图片,batch size仍为50
3, # 3通道
227, 227) # 图片尺寸227x227
- 1
- 2
- 3
- 4
# 加载图片,函数声明为load_image(filename, color=True)
image = caffe.io.load_image(caffe_root + 'examples/images/cat.jpg')
# 按照之前设置进行预处理
transformed_image = transformer.preprocess('data', image)
plt.imshow(image) #显示图片
- 1
- 2
- 3
- 4
- 5
进行分类,获取结果
# 将图像数据拷贝到为net分配的内存中
net.blobs['data'].data[...] = transformed_image
# 前向传播,进行分类,forward函数说明放到博客最后
# 前向传播,跑一遍网络,默认结果为最后一层的blob(也可以指定某一中间层),赋给output
output = net.forward()
# output['prob']矩阵的维度是(50, 1000)
output_prob = output['prob'][0] # 取batch中第一张图像的概率值
# 打印概率最大的类别代号,argmax()函数是求取矩阵中最大元素的索引
print 'predicted class is:', output_prob.argmax()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
网络输出是一个概率向量,最可能的类别是第281个类别。但是结果是否正确呢,需要查看一下ImageNet的标签。
# 加载ImageNet标签,如果不存在,则会自动下载
labels_file = caffe_root + 'data/ilsvrc12/synset_words.txt'
if not os.path.exists(labels_file):
!../data/ilsvrc12/get_ilsvrc_aux.sh
# 读取纯文本数据,三个参数分别是文件地址、数据类型和数据分隔符,保存为字典格式
labels = np.loadtxt(labels_file, str, delimiter='\t')
print 'output label:', labels[output_prob.argmax()]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
打印结果如下:
output label: n02123045 tabby, tabby cat
# 从softmax output可查看置信度最高的五个结果
top_inds = output_prob.argsort()[::-1][:5] # 逆序排列,取前五个最大值
print 'probabilities and labels:'
zip(output_prob[top_inds], labels[top_inds])
- 1
- 2
- 3
- 4
- 5
- 6
打印结果如下:
probabilities and labels:
[(0.31243545, ‘n02123045 tabby, tabby cat’),
(0.23797165, ‘n02123159 tiger cat’),
(0.12387225, ‘n02124075 Egyptian cat’),
(0.10075709, ‘n02119022 red fox, Vulpes vulpes’),
(0.07095667, ‘n02127052 lynx, catamount’)]
# 查看CPU的分类时间,然后再与GPU进行比较
%timeit net.forward() #计时
- 1
- 2
1 loop, best of 3: 4.52 s per loop,结果比较慢。
gpu模式运行
#gpu模式下跑一次
caffe.set_device(0) # 使用第一块显卡
caffe.set_mode_gpu() # 设为gpu模式
net.forward() # 前向传播
%timeit net.forward() # 计时
- 1
- 2
- 3
- 4
- 5
1 loop, best of 3: 196 ms per loop,简直飞起来了。
测试中间输出结果
中间层的可视化
卷积神经网络不单单是一个黑盒子。我们接下来看看该模型的一些参数和一些中间输出。首先,我们来看下如何读取网络的结构(每层的名字以及相应层的参数)。
net.blob对应网络每一层数据,对于每一层,都是四个维度:(batch_size, channel_dim, height, width)。
# 循环打印每一层名字和相应维度
for layer_name, blob in net.blobs.iteritems():
print layer_name + '\t' + str(blob.data.shape)
- 1
- 2
- 3
打印结果如下:
data (50, 3, 227, 227)
conv1 (50, 96, 55, 55)
pool1 (50, 96, 27, 27)
norm1 (50, 96, 27, 27)
conv2 (50, 256, 27, 27)
pool2 (50, 256, 13, 13)
norm2 (50, 256, 13, 13)
conv3 (50, 384, 13, 13)
conv4 (50, 384, 13, 13)
conv5 (50, 256, 13, 13)
pool5 (50, 256, 6, 6)
fc6 (50, 4096)
fc7 (50, 4096)
fc8 (50, 1000)
prob (50, 1000)
net.params对应网络中的参数(卷积核参数,全连接层参数等),有两个字典值,net.params[0]是权值(weights),net.params[1]是偏移量(biases),权值参数的维度表示是(output_channels, input_channels, filter_height, filter_width),偏移量参数的维度表示(output_channels,)
# 循环打印参数名称,权值参数和偏移量参数的维度
for layer_name, param in net.params.iteritems():
print layer_name + '\t' + str(param[0].data.shape), str(param[1].data.shape)
- 1
- 2
- 3
打印结果如下:
conv1 (96, 3, 11, 11) (96,)
conv2 (256, 48, 5, 5) (256,)
conv3 (384, 256, 3, 3) (384,)
conv4 (384, 192, 3, 3) (384,)
conv5 (256, 192, 3, 3) (256,)
fc6 (4096, 9216) (4096,)
fc7 (4096, 4096) (4096,)
fc8 (1000, 4096) (1000,)
这里要将四维数据进行特征可视化,需要一个定义辅助函数:
def vis_square(data):
# 数据正则化
data = (data - data.min()) / (data.max() - data.min())
# 此处目的是将一个个滤波器按照正方形的样子排列
# 先对shape[0]也就是滤波器数量取平方根,然后取大于等于该结果的正整数
# 比如40个卷积核,则需要7*7的正方形格子(虽然填不满)
n = int(np.ceil(np.sqrt(data.shape[0])))
padding = (((0, n ** 2 - data.shape[0]),
(0, 1), (0, 1)) # 在相邻的卷积核之间加入空白
+ ((0, 0),) * (data.ndim - 3)) # 不填充最后一维
data = np.pad(data, padding, mode='constant', constant_values=1) 每张小图片向周围扩展一个白色像素
# pad函数声明:pad(array, pad_width, mode, **kwargs),作用是把list在原维度上进行扩展;
# pad_width是扩充参数,例如参数((3,2),(2,3));
# 其中(3,2)为水平方向上,上面加3行,下面加2行;
# (2,3)为垂直方向上,上面加2行,下面加3行;
# constant是常数填充的意思。
# 将卷积核平铺成图片,没太看懂,有空补充
data = data.reshape((n, n) + data.shape[1:]).transpose((0, 2, 1, 3) + tuple(range(4, data.ndim + 1)))
data = data.reshape((n * data.shape[1], n * data.shape[3]) + data.shape[4:])
plt.imshow(data); plt.axis('off')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
filters = net.params['conv1'][0].data # 选取conv1的卷积核权值参数
vis_square(filters.transpose(0, 2, 3, 1)) # 调用函数显示
- 1
- 2
feat = net.blobs['conv1'].data[0, :36] # 选取‘conv1’的blob数据,只选择前面36张图片
vis_square(feat)
- 1
- 2
feat = net.blobs['pool5'].data[0] # 选取pool5的第一个输出结果
vis_square(feat)
- 1
- 2
直方图显示
feat = net.blobs['fc6'].data[0] # 选取fc6的输出数据,这是一个4096维的向量
plt.subplot(2, 1, 1) # 创建2行1列的子图,现在是第1个子图
plt.plot(feat.flat) # 平铺向量,图像显示其每一个值
plt.subplot(2, 1, 2) # 现在是第2个子图
_ = plt.hist(feat.flat[feat.flat > 0], bins=100) # 做直方图,总共100根条形
plt.show() # 显示两张图表
- 1
- 2
- 3
- 4
- 5
- 6
feat = net.blobs['prob'].data[0] # 选取最后一层的输出结果,
plt.figure(figsize=(15, 3)) # 设置图像大小为(15,3),单位是英寸
plt.plot(feat.flat) # 平铺向量,图像显示其每一个值
plt.show() # 显示图表
- 1
- 2
- 3
- 4
测试自己的图片
# 下载图像
my_image_url = "..." # 图像URL地址
!wget -O image.jpg $my_image_url # 在线下载图片
# 变换图像并将其拷贝到网络
image = caffe.io.load_image('image.jpg')
net.blobs['data'].data[...] = transformer.preprocess('data', image)
# 预测分类结果
net.forward()
# 获取输出概率值
output_prob = net.blobs['prob'].data[0]
# 将softmax的输出结果按照从大到小排序,并取前5名
top_inds = output_prob.argsort()[::-1][:5]
plt.imshow(image)
plt.show()
print 'probabilities and labels:'
zip(output_prob[top_inds], labels[top_inds]) # zip函数依次取值,然后组合
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
附:forward函数说明
Net_forward(self, blobs=None, start=None, end=None, **kwargs) method of caffe._caffe.Net instance
Forward pass: prepare inputs and run the net forward.
Parameters
----------
blobs : list of blobs to return in addition to output blobs.
kwargs : Keys are input blob names and values are blob ndarrays.
For formatting inputs for Caffe, see Net.preprocess().
If None, input is taken from data layers.
start : optional name of layer at which to begin the forward pass
end : optional name of layer at which to finish the forward pass
(inclusive)
Returns ------- outs : {blob name: blob ndarray} dict.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14