【3.2】卷积神经网络的应用
一、VGG论文阅读笔记
笔记阅读2015年发表于ICLR的《VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE-SCALE IMAGE RECOGNITION》图像分类部分,课程实践采用的是VGG-16模 型。
摘要
论文探讨在大规模数据集情景下,卷积网络深度对其准确率的影响。我们的主要贡献在于利用3*3小卷积核的网络结构对逐渐加深的网络进行全面的评估, 结果表明加深网络深度到16至19层可极大超越前人的网络结构。这些成果是基于 2014年的ImageNet挑战赛,该模型在定位和分类跟踪比赛中分别取得了第一名和 第二名。同时模型在其他数据集上也有较好的泛化性。我们公开了两个表现最好 的卷积神经网络模型,以促进计算机视觉领域模型的进一步研究。
1. 介绍
卷积网络(ConvNets)成功原因:大型公共图像数据集,如 ImageNet;高性能 计算系统,如GPU或大规模分布式集群。 前人的工作:人们尝试在AlexNet的原始框架上做一些改进。比如在第一个 卷积上使用较小的卷积核以及较小的滑动步长。另一种方法则是在全图以及多个 尺寸上,稠密的训练和测试网络。 而本文主要关注网络的深度。为此,我们固定网络的其他参数,通过增加卷 积层来增加网络深度,这是可行的,因为我们所有层都采用小的3*3卷积核。
2. 卷积配置
为凸显深度对模型效果的影响,我们所有卷积采用相同配置。本章先介绍卷 积网络的通用架构,再描述其评估中的具体细节,最后讨论我们的设计选择以及 前人网络的比较。
2.1 架构
- 训练输入:固定尺寸224*224的RGB图像。
- 预处理:每个像素值减去训练集上的RGB均值。
- 卷积核:一系列3*3卷积核堆叠,步长为1,采用padding保持卷积后图像空 间分辨率不变。
- 空间池化:紧随卷积“堆”的最大池化,为2*2滑动窗口,步长为2。
- 全连接层:特征提取完成后,接三个全连接层,前两个为4096通道,第三个 为1000通道,最后是一个soft-max层,输出概率。
- 所有隐藏层都用非线性修正ReLu。
2.2 详细配置
表1中每列代表不同的网络,只有深度不同(层数计算不包含池化层)。卷 积的通道数量很小,第一层仅64通道,每经过一次最大池化,通道数翻倍,知道 数量达到512通道。
表2展示了每种模型的参数数量,尽管网络加深,但权重并未大幅增加,因 为参数量主要集中在全连接层。
2.3讨论
两个33卷积核相当于一个55卷积核的感受域,三个33卷积核相当于一个77卷积核的感受域。
优点:三个卷积堆叠具有三个非线性修正层,使模型更具判别性;其次三个33卷积参数量更少,相当于在77卷积核上加入了正则化。
3. 分类框架
3.1训练
训练方法基本与AlexNet一致,除了多尺度训练图像采样方法不一致。
- 训练采用mini-batch梯度下降法,batch size=256;
- 采用动量优化算法,momentum=0.9;
- 采用L2正则化方法,惩罚系数0.00005;dropout比率设为0.5;
- 初始学习率为0.001,当验证集准确率不再提高时,学习率衰减为原来的0.1倍,总共下降三次;
- 总迭代次数为370K(74epochs); 数据增强采用随机裁剪,水平翻转,RGB颜色变化;
- 设置训练图片大小的两种方法:
- 定义S代表经过各向同性缩放的训练图像的最小边。
- 第一种方法针对单尺寸图像训练,S=256或384,输入图片从中随机裁剪224*224大小的图片,原则上S可以取任意不小于224的值。
- 第二种方法是多尺度训练,每张图像单独从[Smin ,Smax ]中随机选取S来进行尺寸缩放,由于图像中目标物体尺寸不定,因此训练中采用这种方法是有效的,可看作一种尺寸抖动的训练集数据增强。
- 论文中提到,网络权重的初始化非常重要,由于深度网络梯度的不稳定性, 不合适的初始化会阻碍网络的学习。因此我们先训练浅层网络,再用训练好的浅 层网络去初始化深层网络。
3.2测试
测试阶段,对于已训练好的卷积网络和一张输入图像,采用以下方法分类:
首先,图像的最小边被各向同性的缩放到预定尺寸Q;
然后,将原先的全连接层改换成卷积层,在未裁剪的全图像上运用卷积网络, 输出是一个与输入图像尺寸相关的分类得分图,输出通道数与类别数相同;
最后,对分类得分图进行空间平均化,得到固定尺寸的分类得分向量。
我们同样对测试集做数据增强,采用水平翻转,最终取原始图像和翻转图像 的soft-max分类概率的平均值作为最终得分。
由于测试阶段采用全卷积网络,无需对输入图像进行裁剪,相对于多重裁剪 效率会更高。但多重裁剪评估和运用全卷积的密集评估是互补的,有助于性能提 升。
4. 分类实验
4.1单尺寸评估
表3展示单一测试尺寸上的卷积网络性能
4.2多尺寸评估
表4展示多个测试尺寸上的卷积网络性能
4.3 多重裁剪与密集网络评估
表 5 展示多重裁剪与密集网络对比,并展示两者相融合的效果
4.4 卷积模型的融合
这部分探讨不同模型融合的性能,计算多个模型的 soft-max 分类概率的平均 值来对它们的输出进行组合,由于模型的互补性,性能有所提高,这也用于比赛 的最佳结果中。
表 6 展示多个卷积网络融合的结果
与当前最好算法的比较
表七展示对当前最好算法的对比
5.结论
本文评估了非常深的卷积网络在大规模图像分类上的性能。结果表明深度有利于分类准确率的提升。附录中展示了模型的泛化能力,再次确认了视觉表达中 深度的重要性。
二、VGG 实现代码重点讲解
x = tf.placeholder(tf.float32,shape = [BATCH_SIZE,IMAGE_PIXELS])
tf.placeholder:用于传入真实训练样本 / 测试 / 真实特征 / 待处理特征。 只是占位,不必给出初值。用 sess.run 的 feed_dict 参数以字典形式喂入 x:, y_: sess.run(feed_dict = {x: ,y_: })
BATCH_SIZE:一次传入的个数。
IMAGE_PIXELS:图像像素。
例:x = tf.placeholder(“float”,[1,224,224,3])
BATCH_SIZE 为 1,表示一次传入一个。图像像素为[224,224,3]。
w = tf.Variable(tf.random_normal()) #从正态分布中给出权重 w 的随机值。
b = tf.Variable(tf.zeros())#统一将偏置 b 初始化为 0。
注意:以上两行函数 Variable 中的 V 要大写,Variable 必须给初值。
np.load np.save
将数组以二进制格式保存到磁盘,扩展名为.npy 。
.item():遍历(键值对)。
tf.shape(a)和 a.get_shape()比较
- 相同点:都可以得到 tensor a 的尺寸
- 不同点:tf.shape()中 a 的数据类型可以是 tensor, list, array;而 a.get_shape()中 a 的数据类型只能是 tensor,且返回的是一个元组(tuple)。
例
import tensorflow as tf
import numpy as np
x=tf.constant([[1,2,3],[4,5,6]]
y=[[1,2,3],[4,5,6]]
z=np.arange(24).reshape([2,3,4]))
sess=tf.Session()
# tf.shape()
x_shape=tf.shape(x) # x_shape 是一个 tensor
y_shape=tf.shape(y) # <tf.Tensor 'Shape_2:0' shape=(2,) dtype=int32>
z_shape=tf.shape(z) # <tf.Tensor 'Shape_5:0' shape=(3,) dtype=int32>
print sess.run(x_shape) # 结果:[2 3]
print sess.run(y_shape) # 结果:[2 3]
print sess.run(z_shape) # 结果:[2 3 4]
#a.get_shape()
x_shape=x.get_shape() # 返回的是TensorShape([Dimension(2),Dimension(3)]),不能使用 sess.run(),因为返回的不是 tensor 或 string,而 是元组
x_shape=x.get_shape().as_list() # 可以使用 as_list()得到具体的尺 寸,x_shape=[2 3]
y_shape=y.get_shape() # AttributeError: 'list' object has no attribute 'get_shape'
z_shape=z.get_shape() # AttributeError: 'numpy.ndarray' object has no attribute 'get_shape'
tf.nn.bias_add(乘加和,bias):把 bias 加到乘加和上。
tf.reshape(tensor, shape):
改变 tensor 的形状
# tensor ‘t’ is [1, 2, 3, 4, 5, 6, 7, 8, 9]
# tensor ‘t’ has shape [9]
reshape(t, [3, 3]) ==>
[[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
#如果 shape 有元素[-1],表示在该维度打平至一维
#-1 将自动推导得为 9:
reshape(t, [2, -1]) ==>
[[1, 1, 1, 2, 2, 2, 3, 3, 3],
[4, 4, 4, 5, 5, 5, 6, 6, 6]]
np.argsort(列表):对列表从小到大排序。
OS 模块:
os.getcwd():返回当前工作目录。
os.path.join(path1[,path2[,......]]):
返回值:将多个路径组合后返回。 注意:第一个绝对路径之前的参数将被忽略。
例:
>>> import os
>>> vgg16_path = os.path.join(os.getcwd(),"vgg16.npy")
#当前目录/vgg16.npy,索引到 vgg16.npy 文件
np.save:写数组到文件(未压缩二进制形式),文件默认的扩展名是.npy 。 np.save(“名.npy”,某数组):将某数组写入“名.npy”文件。
某变量 = np.load(“名.npy”,encoding = " “).item():将“名.npy”文件读 出给某变量。encoding = " " 可以不写‘latin1’、‘ASCII’、‘bytes’, 默认为’ASCII’。
例:
>>> import numpy as np
A = np.arange(15).reshape(3,5)
>>> A
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
>>> np.save("A.npy",A) #如果文件路径末尾没有扩展名.npy,该扩展名会被 自动加上。
>>> B=np.load("A.npy")
>>> B
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
tf.split(dimension, num_split, input)
dimension:输入张量的哪一个维度,如果是 0 就表示对第 0 维度进行切割。 num_split:切割的数量,如果是 2 就表示输入张量被切成 2 份,每一份是一个 列表。
例:
import tensorflow as tf;
import numpy as np;
A = [[1,2,3],[4,5,6]]
x = tf.split(1, 3, A)
with tf.Session() as sess:
c = sess.run(x)
for ele in c:
print ele
输出:
[[1]
[4]] [[2]
[5]] [[3]
[6]]
tf.concat(concat_dim, values)
t1 = [[1, 2, 3], [4, 5, 6]]
t2 = [[7, 8, 9], [10, 11, 12]]
tf.concat(0, [t1, t2]) ==> [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]
tf.concat(1, [t1, t2]) ==> [[1, 2, 3, 7, 8, 9], [4, 5, 6, 10, 11, 12]]
如果想沿着 tensor 一新轴连结打包,那么可以:
tf.concat(axis, [tf.expand_dims(t, axis) for t in tensors])
等同于 tf.pack(tensors, axis=axis)
fig = plt.figure(“图名字”):实例化图对象。
ax = fig.add_subplot(m n k):将画布分割成 m 行 n 列,图像画在从左到 右从上到下的第 k 块。
例:
#引入对应的库函数
import matplotlib.pyplot as plt
from numpy import *
#绘图
fig = plt.figure()
ax = fig.add_subplot(3 4 9)
ax.plot(x,y)
plt.show()
ax.bar(bar 的个数,bar 的值,每个 bar 的名字,bar 的宽,bar 的颜色):绘 制直方图。给出 bar 的个数,bar 的值,每个 bar 的名字,bar 的宽,bar 的颜 色。
- ax.set_ylabel(""):给出 y 轴的名字。
- ax.set_title(""):给出子图的名字。
- ax.text(x,y,string,fontsize=15,verticalalignment=“top”,horizontalali gnment=“right”):
- x ,y:表示坐标轴上的值。
- string:表示说明文字。
- fontsize:表示字体大小。
- verticalalignment:垂直对齐方式,参数:[ ‘center’ | ‘top’ | ‘bottom’ | ‘baseline’ ]
- horizontalalignment:水平对齐方式,参数:[‘center’|‘right’|‘left’] xycoords
选择指定的坐标轴系统:
- figure points
- points from the lower left of the figure 点在图左下方
- figure pixels
- pixels from the lower left of the figure 图左下角的像素
- figure fraction
- fraction of figure from lower left 左下角数字部分
- axes points
- points from lower left corner of axes 从左下角点的坐标
- axes pixels
- pixels from lower left corner of axes 从左下角的像素坐标
- axes fraction
- fraction of axes from lower left 左下角部分
- data
- use the coordinate system of the object being annotated(default) 使 用的坐标系统被注释的对象(默认)
- polar(theta,r)
- if not native ‘data’ coordinates t arrowprops #箭头参数,参数类型为字典 dict
- width
- the width of the arrow in points 点箭头的宽度
- headwidth
- the width of the base of the arrow head in points 在点的箭头底座 的宽度
- headlength
- the length of the arrow head in points 点箭头的长度
- shrink
- fraction of total length to ‘shrink’ from both ends 总长度为分 数“缩水”从两端
- facecolor 箭头颜色
- bbox 给标题增加外框 ,常用参数如下:
- boxstyle 方框外形
- facecolor(简写 fc)背景颜色
- edgecolor(简写 ec)边框线条颜色
- edgewidth 边框线条大小
- bbox=dict(boxstyle=‘round,pad=0.5’,fc=‘yellow’,ec=‘k’,lw=1 ,alpha=0.5) #fc 为 facecolor,ec 为 edgecolor,lw 为 lineweight
plt.show():画出来。
axo = imshow(图):画子图。
图 = io.imread(图路径索引到文件)。
vgg 网络具体结构
vgg16.py 还原网络和参数
app.py 读入待判图,给出可视化结果
三、课程中 VGG 源码的全文注释
vgg16.py
#!/usr/bin/python
#coding:utf-8
import inspect
import os
import numpy as np
import tensorflow as tf
import time
import matplotlib.pyplot as plt
VGG_MEAN = [103.939, 116.779, 123.68] # 样本 RGB 的平均值
class Vgg16():
def __init__(self, vgg16_path=None):
if vgg16_path is None:
vgg16_path = os.path.join(os.getcwd(), "vgg16.npy") # os.getcwd() 方法用于返回当前工作目录。
print(vgg16_path)
self.data_dict = np.load(vgg16_path, encoding='latin1').item() # 遍历其内键值对,导入模型参数
for x in self.data_dict: #遍历 data_dict 中的每个键
print x
def forward(self, images):
# plt.figure("process pictures")
print("build model started")
start_time = time.time() # 获取前向传播的开始时间
rgb_scaled = images * 255.0 # 逐像素乘以 255.0(根据原论文所述的初始化步骤) # 从 GRB 转换色彩通道到 BGR,也可使用 cv 中的 GRBtoBGR
red, green, blue = tf.split(rgb_scaled,3,3)
assert red.get_shape().as_list()[1:] == [224, 224, 1]
assert green.get_shape().as_list()[1:] == [224, 224, 1]
assert blue.get_shape().as_list()[1:] == [224, 224, 1]
# 以上 assert 都是加入断言,用来判断每个操作后的维度变化是否和预期一致
bgr = tf.concat([blue - VGG_MEAN[0], green - VGG_MEAN[1], red - VGG_MEAN[2]],3)
# 逐样本减去每个通道的像素平均值,这种操作可以移除图像的平均亮度值,该方法常用在灰度图像上
assert bgr.get_shape().as_list()[1:] == [224, 224, 3]
# 接下来构建 VGG 的 16 层网络(包含 5 段卷积,3 层全连接),并逐层根据命名空间读取网络参数
# 第一段卷积,含有两个卷积层,后面接最大池化层,用来缩小图片尺寸
self.conv1_1 = self.conv_layer(bgr, "conv1_1")
# 传入命名空间的 name,来获取该层的卷积核和偏置,并做卷积运算,最后返回经过经过激活函数后的值
self.conv1_2 = self.conv_layer(self.conv1_1, "conv1_2")
# 根据传入的 pooling 名字对该层做相应的池化操作
self.pool1 = self.max_pool_2x2(self.conv1_2, "pool1")
# 下面的前向传播过程与第一段同理
# 第二段卷积,同样包含两个卷积层,一个最大池化层
self.conv2_1 = self.conv_layer(self.pool1, "conv2_1")
self.conv2_2 = self.conv_layer(self.conv2_1, "conv2_2")
self.pool2 = self.max_pool_2x2(self.conv2_2, "pool2")
# 第三段卷积,包含三个卷积层,一个最大池化层
self.conv3_1 = self.conv_layer(self.pool2, "conv3_1")
self.conv3_2 = self.conv_layer(self.conv3_1, "conv3_2")
self.conv3_3 = self.conv_layer(self.conv3_2, "conv3_3")
self.pool3 = self.max_pool_2x2(self.conv3_3, "pool3")
# 第四段卷积,包含三个卷积层,一个最大池化层
self.conv4_1 = self.conv_layer(self.pool3, "conv4_1")
self.conv4_2 = self.conv_layer(self.conv4_1, "conv4_2")
self.conv4_3 = self.conv_layer(self.conv4_2, "conv4_3")
self.pool4 = self.max_pool_2x2(self.conv4_3, "pool4")
# 第五段卷积,包含三个卷积层,一个最大池化层
self.conv5_1 = self.conv_layer(self.pool4, "conv5_1")
self.conv5_2 = self.conv_layer(self.conv5_1, "conv5_2")
self.conv5_3 = self.conv_layer(self.conv5_2, "conv5_3")
self.pool5 = self.max_pool_2x2(self.conv5_3, "pool5")
# 第六层全连接
self.fc6 = self.fc_layer(self.pool5, "fc6") # 根据命名空间 name 做加权求和运算
assert self.fc6.get_shape().as_list()[1:] == [4096] # 4096 是该层输出后的长度
self.relu6 = tf.nn.relu(self.fc6) # 经过 relu 激活函数
# 第七层全连接,和上一层同理
self.fc7 = self.fc_layer(self.relu6, "fc7")
self.relu7 = tf.nn.relu(self.fc7)
# 第八层全连接
self.fc8 = self.fc_layer(self.relu7, "fc8")
# 经过最后一层的全连接后,再做 softmax 分类,得到属于各类别的概率
self.prob = tf.nn.softmax(self.fc8, name="prob")
end_time = time.time() # 得到前向传播的结束时间
print(("time consuming: %f" % (end_time-start_time)))
self.data_dict = None # 清空本次读取到的模型参数字典
# 定义卷积运算
def conv_layer(self, x, name):
with tf.variable_scope(name): # 根据命名空间找到对应卷积层的网络参数 w = self.get_conv_filter(name) # 读到该层的卷积核
conv = tf.nn.conv2d(x, w, [1, 1, 1, 1], padding='SAME') # 卷积计算
conv_biases = self.get_bias(name) # 读到偏置项
result = tf.nn.relu(tf.nn.bias_add(conv, conv_biases)) # 加上偏置,并做激活计算
return result
# 定义获取卷积核的函数
def get_conv_filter(self, name):
# 根据命名空间 name 从参数字典中取到对应的卷积核
return tf.constant(self.data_dict[name][0], name="filter")
# 定义获取偏置项的函数
def get_bias(self, name):
# 根据命名空间 name 从参数字典中取到对应的卷积核
return tf.constant(self.data_dict[name][1], name="biases")
# 定义最大池化操作
def max_pool_2x2(self, x, name):
return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', name=name)
# 定义全连接层的前向传播计算
def fc_layer(self, x, name):
with tf.variable_scope(name): # 根据命名空间 name 做全连接层的计算
shape = x.get_shape().as_list() # 获取该层的维度信息列表
print "fc_layer shape ",shape dim = 1
for i in shape[1:]:
dim *= i # 将每层的维度相乘
# 改变特征图的形状,也就是将得到的多维特征做拉伸操作,只在进入第六层全连接层做该操作
x = tf.reshape(x, [-1, dim])
w = self.get_fc_weight(name)# 读到权重值
b = self.get_bias(name) # 读到偏置项值
result = tf.nn.bias_add(tf.matmul(x, w), b) # 对该层输入做加权求和,再加上偏置
return result
# 定义获取权重的函数
def get_fc_weight(self, name): # 根据命名空间 name 从参数字典中取到对应的权重
return tf.constant(self.data_dict[name][0], name="weights")
utils.py
#!/usr/bin/python #coding:utf-8
from skimage import io, transform
import numpy as np
import matplotlib.pyplot as plt import tensorflow as tf
from pylab import mpl
mpl.rcParams['font.sans-serif']=['SimHei'] # 正常显示中文标签
mpl.rcParams['axes.unicode_minus']=False # 正常显示正负号
def load_image(path):
fig = plt.figure("Centre and Resize")
img = io.imread(path) # 根据传入的路径读入图片 img = img / 255.0 # 将像素归一化到[0,1]
# 将该画布分为一行三列
ax0 = fig.add_subplot(131) # 把下面的图像放在该画布的第一个位置 ax0.set_xlabel(u'Original Picture') # 添加子标签
ax0.imshow(img) # 添加展示该图像
short_edge = min(img.shape[:2]) # 找到该图像的最短边
y = (img.shape[0] - short_edge) / 2
x = (img.shape[1] - short_edge) / 2 # 把图像的 w 和 h 分别减去最短边,并求平均
crop_img = img[y:y+short_edge, x:x+short_edge] # 取出切分出的中心图像
print crop_img.shape
ax1 = fig.add_subplot(132) # 把下面的图像放在该画布的第二个位置
ax1.set_xlabel(u"Centre Picture") # 添加子标签
ax1.imshow(crop_img)
re_img = transform.resize(crop_img, (224, 224)) # resize 成固定的 imag_szie
ax2 = fig.add_subplot(133) # 把下面的图像放在该画布的第三个位置
ax2.set_xlabel(u"Resize Picture") # 添加子标签
ax2.imshow(re_img)
img_ready = re_img.reshape((1, 224, 224, 3))
return img_ready
# 定义百分比转换函数
def percent(value):
return '%.2f%%' % (value * 100)
app.py
#coding:utf-8
import numpy as np
# Linux 服务器没有 GUI 的情况下使用 matplotlib 绘图,必须置于 pyplot 之前
#import matplotlib
#matplotlib.use('Agg')
import tensorflow as tf
import matplotlib.pyplot as plt
# 下面三个是引用自定义模块
import vgg16
import utils
from Nclasses import labels
img_path = raw_input('Input the path and image name:')
img_ready = utils.load_image(img_path) # 调用 load_image()函数,对待测试的图像做一些预处理操作
#定义一个 figure 画图窗口,并指定窗口的名称,也可以设置窗口修的大小
fig=plt.figure(u"Top-5 预测结果")
with tf.Session() as sess:
# 定义一个维度为[1,224,224,3],类型为 float32 的 tensor 占位符
x = tf.placeholder(tf.float32, [1, 224, 224, 3])
vgg = vgg16.Vgg16() # 类 Vgg16 实例化出 vgg
# 调用类的成员方法 forward(),并传入待测试图像,这也就是网络前向传播的过程
vgg.forward(x)
# 将一个 batch 的数据喂入网络,得到网络的预测输出
probablity = sess.run(vgg.prob, feed_dict={x:img_ready})
# np.argsort 函数返回预测值(probability 的数据结构[[各预测类别的概率值]])由小到大的索引值,
# 并取出预测概率最大的五个索引值
top5 = np.argsort(probability[0])[-1:-6:-1]
print "top5:",top5
# 定义两个 list---对应的概率值和实际标签(zebra)
values = []
bar_label = []
for n, i in enumerate(top5): # 枚举上面取出的五个索引值
print "n:",n
print "i:",i
values.append(probability[0][i]) # 将索引值对应的预测概率值取出并放入 values
bar_label.append(labels[i]) # 根据索引值取出对应的实际标签并放入 bar_label
print i, ":", labels[i], "----", utils.percent(probability[0][i]) # 打印属于某个类别的概率
ax = fig.add_subplot(111) # 将画布划分为一行一列,并把下图放入其中
# bar()函数绘制柱状图,参数 range(len(values)是柱子下标,values 表示柱高的列表(也就是五个预测概率值,
# tick_label 是每个柱子上显示的标签(实际对应的标签),width 是柱子的宽度,fc 是柱子的颜色)
ax.bar(range(len(values)), values, tick_label=bar_label, width=0.5, fc='g')
ax.set_ylabel(u'probability') # 设置横轴标签
ax.set_title(u'Top-5') # 添加标题
for a,b in zip(range(len(values)), values):
# 在每个柱子的顶端添加对应的预测概率值,a,b 表示坐标,b+0.0005 表示要把文本信息放置在高于每个柱子顶端 0.0005 的位置,
# center 是表示文本位于柱子顶端水平方向上的的中间位置,bottom 是将文本水平放置在柱子顶端垂直方向上的底端 位置,fontsize 是字号
ax.text(a, b+0.0005, utils.percent(b), ha='center', va = 'bottom', fontsize=7)
plt.savefig('./result.jpg')# 保存图片
plt.show() # 弹窗展示图像(linux 服务器上将该句注释掉)
四、课程中提到的练习内容
打印出 img_ready 的维度:
app.py 第 11 行加入 print “img_ready shape”, tf.Session().run(tf.shape(img_ready))
输出:img_ready shape [1 224 224 3]
上下文管理器
出现命名层次结构 foo/bar/v
参考资料:
北京大学 曹建老师 《人工智能实践:Tensorflow学习笔记》
个人公众号,比较懒,很少更新,可以在上面提问题,如果回复不及时,可发邮件给我: tiehan@sina.cn