【caffe范例详解】 - 1.Classification分类
1. 安装
- 首先,导入numpy和matplotlib库
# numpy是常用的科学计算库,matplot是常用的绘图库
import numpy as np
import matplotlib.pyplot as plt
# 在notebook中展示图例
%matplotlib inline
# 设置图例展示的默认参数
plt.rcParams['figure.figsize'] = (10, 10) # 图片大小为10*10
plt.rcParams['image.interpolation'] = 'nearest' # 图片的放缩采用最近邻插值法
plt.rcParams['image.cmap'] = 'gray' # 图片的色彩图为灰阶
- 导入caffe框架
import caffe
# 传入caffe的地址,因为该例程的一些数据都存在caffe目录中
caffe_root = r'G:/Projects/caffe/'
2. 载入网络并对输入进行预处理
- 设置caffe为cpu模式
caffe.set_mode_cpu()
- 基于已有的一些配置项定义网络模型
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层)
- 详细解释一下模型配置文件(deploy.prototxt)
name: "CaffeNet" # 模型的名称(自定义)
layer { # 第一层(输入层)
name: "data" # 该层的名称(自定义),此处为“data”
type: "Input" # 该层的类型(重要参数),此处为输入层(Input)
top: "data" # 该层的输出数组名为“data”
input_param { shape: { dim: 10 dim: 3 dim: 227 dim: 227 } }
} # 配置输入层参数,4维数组,数量*通道*高*宽,即10张RGB彩色图像,大小为227*227
layer { # 第二层(卷积层1)
name: "conv1" # 该层的名称(自定义),此处为“conv1”
type: "Convolution" # 该层的类型(重要参数),此处为卷积层(Convolution)
bottom: "data" # 该层的输入数组名为“data”
top: "conv1" # 该层的输出数组名为“conv1”
convolution_param { # 配置卷积层参数
num_output: 96 # 卷积核数量为96
kernel_size: 11 # 卷积核大小为11*11
stride: 4 # 卷积核的步长为4
}
}
layer { # 第三层(激活层1)
name: "relu1" # 该层的名称(自定义),此处为“relu1”
type: "ReLU" # 该层的类型(重要参数),此处是以ReLu为激活函数的激活层
bottom: "conv1" # 该层的输入数组名为“conv1”
top: "conv1" # 该层的输出数组名为“conv1”
}
layer { # 第四层(池化层1)
name: "pool1" # 该层的名称(自定义),此处为“pool1”
type: "Pooling" # 该层的类型(重要参数),此处是池化层
bottom: "conv1" # 该层的输入数组名为“conv1”
top: "pool1" # 该层的输出数字名为“pool1”
pooling_param { # 配置池化层参数
pool: MAX # 池化方式为 Max-pooling
kernel_size: 3 # 池化区域大小为3*3
stride: 2 # 池化操作的步长为2
}
}
layer { # 第五层(局部响应归一化层)
name: "norm1" # 该层的名称(自定义),此处为“norm1”
type: "LRN" # 该层的类型(重要参数),此处是LRN(Local Response Normalization)
bottom: "pool1" # 该层的输入数组名为“pool1”
top: "norm1" # 该层的输出数组名为“norm1”
lrn_param { # 配置LRN层参数
local_size: 5 # 参与求和的矩形区域边长
alpha: 0.0001 # 尺度参数
beta: 0.75 # 指数参数
}
}
layer { # 第六层(卷积层2)
name: "conv2" # 该层的名称(自定义),此处为“conv2”
type: "Convolution" # 该层的类型(重要参数),此处为卷积层(Convolution)
bottom: "norm1" # 该层的输入数组名为“norm1”
top: "conv2" # 该层的输出数组名为“conv2”
convolution_param { # 配置卷积层参数
num_output: 256 # 卷积核数量为256
pad: 2 # 指定添加到输入每条边的像素点个数
kernel_size: 5 # 卷积核大小为5*5
group: 2 # 分组卷积操作的组数为2,即输入和输出的通道都被分为2组,第1组输入只会连接到第1组输出,第2组输入只会连接到第2组输出。
}
}
layer {
name: "relu2"
type: "ReLU"
bottom: "conv2"
top: "conv2"
}
layer {
name: "pool2"
type: "Pooling"
bottom: "conv2"
top: "pool2"
pooling_param {
pool: MAX
kernel_size: 3
stride: 2
}
}
layer {
name: "norm2"
type: "LRN"
bottom: "pool2"
top: "norm2"
lrn_param {
local_size: 5
alpha: 0.0001
beta: 0.75
}
}
layer {
name: "conv3"
type: "Convolution"
bottom: "norm2"
top: "conv3"
convolution_param {
num_output: 384
pad: 1
kernel_size: 3
}
}
layer {
name: "relu3"
type: "ReLU"
bottom: "conv3"
top: "conv3"
}
layer {
name: "conv4"
type: "Convolution"
bottom: "conv3"
top: "conv4"
convolution_param {
num_output: 384
pad: 1
kernel_size: 3
group: 2
}
}
layer {
name: "relu4"
type: "ReLU"
bottom: "conv4"
top: "conv4"
}
layer {
name: "conv5"
type: "Convolution"
bottom: "conv4"
top: "conv5"
convolution_param {
num_output: 256
pad: 1
kernel_size: 3
group: 2
}
}
layer {
name: "relu5"
type: "ReLU"
bottom: "conv5"
top: "conv5"
}
layer {
name: "pool5"
type: "Pooling"
bottom: "conv5"
top: "pool5"
pooling_param {
pool: MAX
kernel_size: 3
stride: 2
}
}
layer { # 第(我也不知道多少)层(全连接层)
name: "fc6" # 该层的名称(自定义),此处为“fc6”
type: "InnerProduct" # 该层的类型(重要参数),此处为内积层(也叫全连接层)
bottom: "pool5" # 该层的输入数组名为“pool5”
top: "fc6" # 该层的输出数组名为“fc6”
inner_product_param { # 配置全连接层参数
num_output: 4096 # 该层的神经元个数
}
}
layer {
name: "relu6"
type: "ReLU"
bottom: "fc6"
top: "fc6"
}
layer { # 第(我也不知道多少+2)层(Dropout层,防止过拟合)
name: "drop6" # 该层的名称(自定义),此处为“drop6”
type: "Dropout" # 该层的类型(重要参数),此处为Dropout层
bottom: "fc6" # 该层的输入数组名为“fc6”
top: "fc6" # 该层的输出数组名为“fc6”
dropout_param { # 配置Dropout层参数
dropout_ratio: 0.5 # dropout的比例系数为0.5
}
}
layer {
name: "fc7"
type: "InnerProduct"
bottom: "fc6"
top: "fc7"
inner_product_param {
num_output: 4096
}
}
layer {
name: "relu7"
type: "ReLU"
bottom: "fc7"
top: "fc7"
}
layer {
name: "drop7"
type: "Dropout"
bottom: "fc7"
top: "fc7"
dropout_param {
dropout_ratio: 0.5
}
}
layer {
name: "fc8"
type: "InnerProduct"
bottom: "fc7"
top: "fc8"
inner_product_param {
num_output: 1000
}
}
layer { # 第(最后一)层(输出层)
name: "prob" # 该层的名称(自定义),此处为“prob”,含义是输出概率
type: "Softmax" # 该层的类型(重要参数),此处为Softmax层
bottom: "fc8" # 该层的输入数组名为“fc8”
top: "prob" # 该层的输出数组名为“prob”
}
给每一层都命名的好处在于之后可以方便的调用任意一层的输入或者输出来进行分析(如可视化,或者是提取特征等等)
一般而言,习惯于将激活层、Dropout层的输入输出给予同样的名称,因为这并不是我们所关心的神经网络黑箱中的内容,相对而言,我们更关心卷积层、池化层的输出(常见的finetune技巧即提取conv或者是pooling层的输出作为特征)
一些预处理的准备工作
# 载入ImageNet的均值图,是一个3*256*256的矩阵
mu = np.load(caffe_root + 'python/caffe/imagenet/ilsvrc_2012_mean.npy')
mu = mu.mean(1).mean(1) # 计算出BGR通道的平均像素值
print('Mean-subtracted values:', list(zip('BGR', mu)))
Mean-subtracted values: [('B', 104.0069879317889), ('G', 116.66876761696767), ('R', 122.6789143406786)]
这里我们使用 caffe.io.Transformer 对输入数据进行预处理。
我们默认的CaffeNet是将彩色图片以BGR格式读入,并且范围是[0, 255],通道维在第一个(outermost)维度上。
# 为该网络的输入层(“data”层,10*3*227*227的数组)创建一个transformer对象
transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape})
transformer.set_transpose('data', (2,0,1)) # 用caffe.io.load_image读入的图像以row*col*channel形式储存,而在caffe中我们定义的是channel*row*col,
# 因此需要转换读入的图像数据,将原来下标为2的channel维移至下标为0的位置。
transformer.set_mean('data', mu) # 对每一个色彩通道做减均值的操作
transformer.set_raw_scale('data', 255) # 将归一化的数据转为[0, 255]范围的数据
transformer.set_channel_swap('data', (2,1,0)) # 将色彩通道的顺序由RGB改为BGR(这样减去均值的时候才是对应的)
3. 基于CPU的分类
- 尽管我们只分类一张图片,也还是可以设置batch-size为50
# 设置输入图片的大小
net.blobs['data'].reshape(50, # batch size
3, # 3-channel (BGR) images
227, 227) # image size is 227x227
image = caffe.io.load_image(caffe_root + 'examples/images/cat.jpg')
transformed_image = transformer.preprocess('data', image)
plt.imshow(image)
<matplotlib.image.AxesImage at 0x150008d3128>
- 下面开始进行分类
# 将该图片传入网络作为输入(这样似乎是把输入的十张图都表示成该图像)
net.blobs['data'].data[...] = transformed_image
# 开始进行分类
# 将输入参数进行前向传播(因为我们已经载入了预先训练的网络,因此一次前向传播即可获得最终结果)
output = net.forward()
# 获得对第一张图片的输出预测向量
output_prob = output['prob'][0]
# 选取其中获得最大预测值的向量下标,作为预测的分类下标
print('predicted class is:', output_prob.argmax())
predicted class is: 281
- 通过比对下标在ImageNet中表示的真实类别,验证分类结果的准确性
import os
# load ImageNet labels
labels_file = caffe_root + 'data/ilsvrc12/synset_words.txt'
if not os.path.exists(labels_file):
!caffe_root + 'data/ilsvrc12/get_ilsvrc_aux.sh'
labels = np.loadtxt(labels_file, str, delimiter='\t')
print('output label:', labels[output_prob.argmax()])
output label: b'n02123045 tabby, tabby cat'
该类别下标表示的是 tabby cat, 结果正确!
我们可以看一下该图片在1000类中top5可能性的分类结果
# 将softmax输出的结果用argsort()方法排序并翻转(因为该方法默认由小到大排序)
# 取前5项输出
top_inds = output_prob.argsort()[::-1][:5]
print('probabilities and labels:')
list(zip(output_prob[top_inds], labels[top_inds]))
probabilities and labels:
[(0.3124359, "b'n02123045 tabby, tabby cat'"),
(0.23797129, "b'n02123159 tiger cat'"),
(0.1238722, "b'n02124075 Egyptian cat'"),
(0.10075704, "b'n02119022 red fox, Vulpes vulpes'"),
(0.070956774, "b'n02127052 lynx, catamount'")]
4. 切换到GPU模式
- 我们来看一下用cpu做计算时,一次前向传播的耗时
%timeit net.forward()
1 loop, best of 3: 1.61 s per loop
- 切换到GPU模式,再看一下前向传播的耗时
caffe.set_device(0) # 如果我们有多个GPU的话,选择第一个
caffe.set_mode_gpu() # 切换到GPU模式
net.forward() # 进行一次前向传播运算
%timeit net.forward()
10 loops, best of 3: 26 ms per loop
5. 检查中间输出
我们来打开网络中间这个“黑箱子”,看看其中的一些参数和激活值
首先,我们来看一下网络中各层神经元输出的参数(即激活值)的shape
对于每一层,我们关注以下信息:(批处理数据大小, 通道数, 高度, 宽度),即 (batch_size, channel_dim, height, width)
激活值保存在net.blobs中
for layer_name, blob in net.blobs.items():
print(layer_name, '\t', str(blob.data.shape))
data (10, 3, 227, 227)
conv1 (10, 96, 55, 55)
pool1 (10, 96, 27, 27)
norm1 (10, 96, 27, 27)
conv2 (10, 256, 27, 27)
pool2 (10, 256, 13, 13)
norm2 (10, 256, 13, 13)
conv3 (10, 384, 13, 13)
conv4 (10, 384, 13, 13)
conv5 (10, 256, 13, 13)
pool5 (10, 256, 6, 6)
fc6 (10, 4096)
fc7 (10, 4096)
fc8 (10, 1000)
prob (10, 1000)
然后,我们来看一下各层之间参数的shape
参数值保存在net.params中,权重(weight)在[0]下标的索引中,偏置(bias)在[1]下标的索引中。
对于权重,我们关注(输出通道数,输入通道数,滤波器高度,滤波器宽度),即 (output_channels, input_channels, filter_height, filter_width)
对于偏置,我们关注(输出通道数,),即(output_channels,)
for layer_name, param in net.params.items():
print( layer_name, '\t', str(param[0].data.shape), str(param[1].data.shape))
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):
"""
输入矩阵的大小为(n, height, width)或者(n, height, width, 3)
可视化每一个输入以sqrt(n)*sqrt(n)的方格展示
"""
# 归一化
data = (data - data.min()) / (data.max() - data.min())
# force the number of filters to be square
n = int(np.ceil(np.sqrt(data.shape[0])))
padding = (((0, n ** 2 - data.shape[0]),
(0, 1), (0, 1)) # add some space between filters
+ ((0, 0),) * (data.ndim - 3)) # don't pad the last dimension (if there is one)
data = np.pad(data, padding, mode='constant', constant_values=1) # pad with ones (white)
# 将滤波器排列成图片的shape用于展示
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')
- 首先,我们来看第一层滤波器conv1
# 可视化的参数是 [weights, biases]
filters = net.params['conv1'][0].data
vis_square(filters.transpose(0, 2, 3, 1))
- 第一层conv1的输出(只展示前36个)
feat = net.blobs['conv1'].data[0, :36]
vis_square(feat)
- 第五层pooling之后的输出,pool5
feat = net.blobs['pool5'].data[0]
vis_square(feat)
- 第一个全连接层,fc6
- 展示输出值以及正值的直方图
feat = net.blobs['fc6'].data[0]
plt.subplot(2, 1, 1)
plt.plot(feat.flat)
plt.subplot(2, 1, 2)
_ = plt.hist(feat.flat[feat.flat > 0], bins=100)
- 最终概率预测输出的直方图
feat = net.blobs['prob'].data[0]
plt.figure(figsize=(15, 3))
plt.plot(feat.flat)
[<matplotlib.lines.Line2D at 0x15017512278>]
【caffe范例详解】 - 1.Classification分类的更多相关文章
- 详解BOM用途分类及在汽车企业中的应用
摘要:在整车企业中,信息系统的BOM是联系CAD.CAPP.PDM和ERP的纽带,按照用途划分产品要经过产品设计,工程设计.工艺制造设计.生产制造4个阶段,相应的在这4个过程中分别产生了名称十分相似但 ...
- SSD算法及Caffe代码详解(最详细版本)
SSD(single shot multibox detector)算法及Caffe代码详解 https://blog.csdn.net/u014380165/article/details/7282 ...
- Windows下caffe安装详解(仅CPU)
本文大多转载自 http://blog.csdn.net/guoyk1990/article/details/52909864,加入部分自己实战心得. 1.环境:windows 7\VS2013 2. ...
- SSD(single shot multibox detector)算法及Caffe代码详解[转]
转自:AI之路 这篇博客主要介绍SSD算法,该算法是最近一年比较优秀的object detection算法,主要特点在于采用了特征融合. 论文:SSD single shot multibox det ...
- 机器学习 | 详解GBDT在分类场景中的应用原理与公式推导
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是机器学习专题的第31篇文章,我们一起继续来聊聊GBDT模型. 在上一篇文章当中,我们学习了GBDT这个模型在回归问题当中的原理.GBD ...
- caffe参数详解
转载自:https://blog.csdn.net/qq_14845119/article/details/54929389 solver.prototxt net:训练预测的网络描述文件,trai ...
- 转 Yolov3转化Caffe框架详解
转自https://blog.csdn.net/watermelon1123/article/details/82083522 前些日子因工程需求,需要将yolov3从基于darknet转化为基于Ca ...
- java内部类深入详解 内部类的分类 特点 定义方式 使用
本文关键词: java内部类 内部类的分类 特点 定义方式 使用 外部类调用内部类 多层嵌套内部类 内部类访问外部类属性 接口中的内部类 内部类的继承 内部类的覆盖 局部内部类 成员内 ...
- caffe 配置文件详解
主要是遇坑了,要记录一下. solver算是caffe的核心的核心,它协调着整个模型的运作.caffe程序运行必带的一个参数就是solver配置文件.运行代码一般为 # caffe train --s ...
随机推荐
- 函数类型(Function Types):函数类型和其他类型一样
函数类型(Function Types) 每个函数都有种特定的函数类型,由函数的参数类型和返回类型组成. 例如: 这个例子中定义了两个简单的数学函数:addTwoInts 和 multiplyTwoI ...
- nrf52840蓝牙BLE5.0空中速率测试(nordic对nordic)
一.基础知识: [1]Data Length:物理层发送一包数据的最大值: [2]MTU: ATT层发送一次数据长度的最大值: [3]GAP Event Length:一个connection eve ...
- luogu P4135 作诗
嘟嘟嘟 郑重声明:我的前几到分块题写法上都有点小毛病,以这篇为主! 这道题感觉也是分块的基本套路,只不过卡常,得开氧气. 维护俩:sum[i][j]表示前 i 块中,数字 j 出现了多少次,ans[i ...
- 使用ByPropertyName进行管道传输
管道参数绑定的两种方式: 可通过 help command -full查看 不同:简单讲ByPropertyName可以使B的多个参数被同时使用 使用ByPropertyName进行管道传输: 建立一 ...
- 文件上传之FileItem使用
一.介绍 FileItem类的常用方法: 1.boolean isFormField().isFormField方法用来判断FileItem对象里面封装的数据是一个普通文本表单字段(true),还是一 ...
- 支持向量机通俗导论(理解SVM的三层境界)[转]
作者:July .致谢:pluskid.白石.JerryLead.说明:本文最初写于2012年6月,而后不断反反复复修改&优化,修改次数达上百次,最后修改于2016年11月.声明:本文于201 ...
- 安装mysql的时候提示1045错误的解决方法
在安装mysql的时候提示1045错误,如图所示: 这种情况一般是之前卸载msyql的时候没有清理完一些文件之类的,导致给你提示存在安全问题,因此,只需要找到mysql一些系统的配置文件,并且将他们删 ...
- 【题解】洛谷P4281 [AHOI2008] 紧急集合(求三个点LCA)
洛谷P4281:https://www.luogu.org/problemnew/show/P4281 思路 答案所在的点必定是三个人所在点之间路径上的一点 本蒟蒻一开始的想法是:先求出2个点之间的L ...
- Gradle Goodness: Profiling Information
If we want to know more about how much time is spent in tasks we can use the --profile command-line ...
- const、let、var的主要区别
接触ES6之后,以前定义变量的方式由var增加了let.const,平时看别人用也不知道如何区别具体差别,好好科普了一下记录下来,方便大家一起学习. var(大家最熟悉的定义方式) 1.可定义全局作用 ...