模型们在ImageNet竞赛上的top-5错误率概况:
探究在大规模图像识别任务中,卷积网络的深度与其性能之间的关系
做法:
VGG把 Alexnet 最开始的一个7*7的卷积核用 3个3*3的卷积核代替。
通过反复堆叠3*3的小型卷积核(stride:1,padding:1)和 2*2的最大池化层,不断加深网络结构来提升性能,成功地构筑了16~19层深的卷积神经网络。
为什么这么做?
(1)3x3是最小的能够捕获左、右、上、下和中心概念的尺寸;
(2)AlexNet最开始的7*7的卷积核的感受野是:7*7
……..VGG第一个卷积核的感受野:3*3
……..第二个卷积核的感受野:(3-1)*1+3=5
……..第三个卷积核的感受野:(5-1)*1+3=7
……..可见三个3*3卷积核和一个7*7卷积核的感受野是一样的,但是3*3卷积核可以把网络做的更深。
(3)多个3*3的卷积层比一个大尺寸的filter卷积层有更多的非线性,使得判决函数更加具有判断性。
(4)多个3*3的卷积层比一个大尺寸的filter具有更少的参数。
成就:
VGGNet 相比之前state-of-the-art的网络结构,错误率大幅下降, 获得ILSVRC 2014比赛分类项目的第2名和定位项目的第1名。
贡献:
证明了使用很小的卷积(3*3),增加网络深度可以有效提升模型的效果,而且VGGNet对其他数据集具有很好的泛化能力
VGG的结构
为了在公平的原则下探究网络深度对模型精确度的影响,所有卷积层有相同的配置:
卷积核大小为3*3,步长为1(stride:1),填充为1(padding:1);
共有5个最大池化层,大小都为2*2,步长为2(stride:2);
三个全连接层,前两层都有4096通道,第三层共1000路及代表1000个标签类别;最后一层为softmax层;
所有隐藏层后都带有ReLU非线性激活函数;
下图为VGG16的结构图:
配置对比:
这张图的意思是他们一共建了A, B, C, D, E, F 6个不同的网络。
结构A:和AlexNet类似,卷积层分为了5个stage,全连接层还是3层。只不过卷积层用的都是3x3大小的filter,具体的细节我会在下文接着阐述。
结构A-LRN:保留AlexNet里面LRN操作,其他与结构A无区别。
结构B:在A的 stage2 和 stage3 分别增加一个3*3的卷积层,共有10个卷积层,全连接层还是3层。
结构C:在B的基础上,stage3,stage4,stage5分别增加一个1*1的卷积层,有13个卷积层,全连接层还是3层,总计16层。
结构D:在C的基础上,stage3,stage4,stage5分别增加一个3*3的卷积层,有13个卷积层,全连接层还是3层,总计16层。
结构E:在D的基础上,stage3,stage4,stage5分别再增加一个3*3的卷积层,有16个卷积层,全连接层还是3层,总计19层。
参数(以VGG16为例):
VGG结构需要注意的点:
1、卷积核都是3x3的
2、参数最多的层:第一个全卷积层,占用内存最多的层:前两个卷积层
3、卷积核个数从64到128到256的目的:stride:1,padding:1 的卷积,特征图大小不变,但是进经过2*2的pooling操作,每个特征图的w和h都变为1/2,特征图的大小变为1/4,将卷积核的个数翻倍,可以降低信息衰减
4、使用了1*1卷积核,1*1卷积核有什么作用?
(1)降维( dimension reductionality )。比如,一张500 * 500且厚度depth为100 的图片在20个filter上做1*1的卷积,那么结果的大小为500*500*20。
(2)加入非线性。卷积层之后经过激励层,1*1的卷积在前一层的学习表示上添加了非线性激励( non-linear activation ),提升网络的表达能力;
代码实例:
读入文件
import tensorflow as tf
import os
import pickle
import numpy as np
CIFAR_DIR = "./cifar-10-batches-py"
print(os.listdir(CIFAR_DIR))
运行效果
# "def load_data(filename):\n",
# " \"\"\"read data from data file.\"\"\"\n",
# " with open(filename, 'rb') as f:\n",
# " data = pickle.load(f, encoding='bytes')\n",
# " return data[b'data'], data[b'labels']\n",
def load_data(filename):
#读取文件
with open(filename,'rb') as f:
data = pickle.load(f,encoding='bytes')
return data[b'data'],data[b'labels']
class CifarData:
def __init__(self,filenames,need_shuffle):
all_data = []
all_labels = []
for filename in filenames:
data,labels = load_data(filename)
all_data.append(data)
all_labels.append(labels)
#将data排成m行n列的矩阵,标签排成1行n列的矩阵
self._data = np.vstack(all_data)
self._data = self._data / 127.5-1
self._labels = np.hstack(all_labels)
self._num_examples = self._data.shape[0]
print(self._data.shape)
print(self._labels.shape)
self._need_shuffle = need_shuffle
self._indicator = 0
#如果需要打乱数据,则打乱数据
if self._need_shuffle:
self._shuffle_data()
def _shuffle_data(self):
p = np.random.permutation(self._num_examples)
self._data = self._data[p]
self._labels = self._labels[p]
def next_batch(self,batch_size):
end_indicator = self._indicator + batch_size
if end_indicator > self._num_examples:
if self._need_shuffle:
self._shuffle_data()
self._indicator = 0
end_indicator = batch_size
else:
raise Exception("erro")
if end_indicator >self._num_examples:
raise Exception("erro")
batch_data = self._data[self._indicator:end_indicator]
batch_labels = self._labels[self._indicator:end_indicator]
self._indicator = end_indicator
return batch_data,batch_labels
train_filenames = [os.path.join(CIFAR_DIR,'data_batch_%d' %i)for i in range(1,6)]
test_filenames = [os.path.join(CIFAR_DIR,'test_batch')]
train_data = CifarData(train_filenames,True)
test_data = CifarData(test_filenames,False)
运行效果
占位符,每次放进计算图 None个样本,每个样本3072个特征
x = tf.placeholder(tf.float32,[None,3072])
#将x转化为图片模式,32*32的3张图片
x_image = tf.reshape(x,[-1,3,32,32])
x_image = tf.transpose(x_image,[0,2,3,1])
#输出的y表示图片属于第几类
y = tf.placeholder(tf.int64,[None])
#conv1:神经元图 32*32
conv1_1 = tf.layers.conv2d(x_image,
32,#输出大小
(3,3),#卷积核大小
padding='same',
activation=tf.nn.relu,
name='conv1')
conv1_2 = tf.layers.conv2d(conv1_1,
32,#输出大小
(3,3),#卷积核大小
padding='same',
activation=tf.nn.relu,
name='conv1_2')
conv1_3 = tf.layers.conv2d(conv1_2,
32,#输出大小
(3,3),#卷积核大小
padding='same',
activation=tf.nn.relu,
name='conv1_3')
#池化层:减少计算量 16*16
pooling1 = tf.layers.max_pooling2d(conv1_3,
(2,2),#核大小
(2,2),#步长
name = 'pool1')
#conv2:神经元图 16*16
conv2_1 = tf.layers.conv2d(pooling1,32,#输出大小
(3,3),#卷积核大小
padding='same',
activation=tf.nn.relu,
name='conv2_1')
conv2_2 = tf.layers.conv2d(conv2_1,32,#输出大小
(3,3),#卷积核大小
padding='same',
activation=tf.nn.relu,
name='conv2_2')
conv2_3 = tf.layers.conv2d(conv2_2,32,#输出大小
(3,3),#卷积核大小
padding='same',
activation=tf.nn.relu,
name='conv2_3')
#池化层:减少计算量 8*8
pooling2 = tf.layers.max_pooling2d(conv2_2,
(2,2),#核大小
(2,2),#步长
name = 'pool2')
# 8*8
conv3_1 = tf.layers.conv2d(pooling2,32,(3,3),padding='same',activation=tf.nn.relu,name='conv3_1')
conv3_2 = tf.layers.conv2d(conv3_1,32,(3,3),padding='same',activation=tf.nn.relu,name='conv3_2')
conv3_3 = tf.layers.conv2d(conv3_2,32,(3,3),padding='same',activation=tf.nn.relu,name='conv3_3')
#4*4
pooling3 = tf.layers.max_pooling2d(conv3_3,(2,2),(2,2),name='pool3')
#形成全连接层
flatten = tf.layers.flatten(pooling3)
#实现矩阵相乘的功能
y_ = tf.layers.dense(flatten,10)
# #使用softmax:exp(x_i)/sum(exp(x_i)),转化为【1.。。0】之间的数
# p_y_1 = tf.nn.softmax(y_)
# #如5:转化为[0,0,0,0,1,0,0,0,0,0 ]
# y_one_hot = tf.one_hot(y,10,dtype=tf.float32)
# loss = tf.reduce_mean(tf.square(y_one_hot - p_y_1))
#交叉熵损失函数 实现了 y转化为概率,y的one_hot编码,loss = ylogy_
loss = tf.losses.sparse_softmax_cross_entropy(labels=y,logits=y_)
#计算准确率
predict = tf.argmax(y_,1)
correct_prediction = tf.equal(predict,y)
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
#定义如何去求最优解,使用梯度下降法
# "with tf.name_scope('train_op'):\n",
# " train_op = tf.train.AdamOptimizer(1e-3).minimize(loss)"
with tf.name_scope('train_op'):
train_op = tf.train.AdamOptimizer(1e-3).minimize(loss)
def variable_summary(var,name):
with tf.name_scope(name):
mean = tf.reduce_mean(var)
with tf.name_scope('stddev'):
stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
tf.summary.scalar('mean',mean)
tf.summary.scalar('stddev',stddev)
tf.summary.scalar('min',tf.reduce_mean(var))
tf.summary.scalar('max',tf.reduce_max(var))
tf.summary.histogram('histogram',var) #打印出数据的直方图
with tf.name_scope('scope'):
variable_summary(conv1_1,'conv1_1')
variable_summary(conv1_2,'conv1_2')
variable_summary(conv2_1,'conv2_1')
variable_summary(conv2_2,'conv2_2')
variable_summary(conv3_1,'conv3_1')
variable_summary(conv3_2,'conv3_2')
loss_summary = tf.summary.scalar('loss',loss)
accuracy_summary = tf.summary.scalar('accuracy',accuracy)
source_image = (x_image + 1) * 127.7 # 逆向归一化过程
inputs_summary = tf.summary.image('inputs_image',source_image)
#使得所有summary聚合计算
merged_summary = tf.summary.merge_all()
#merged_summary_test = tf.summary.merge([loss_summary,accuracy_summary]) 测试集的需要显示出的数据
#指定得到的结果存储的文件夹
LOG_DIR = '.' #指定路径
run_label = 'run_vgg_tensorboard' #指定文件夹名字
run_dir = os.path.join(LOG_DIR,run_label)##路径,文件夹名称
#如果文件夹不存在,就创建
if not os.path.exists(run_dir):
os.mkdir(run_dir)
train_log_dir = os.path.join(LOG_DIR,'train')
#test_log_dir = os.path.join(LOG_DIR,'test')
if not os.path.exists(train_log_dir):
os.mkdir(train_log_dir)
# if not os.path.exists(test_log_dir):
# os.mkdir(test_log_dir)
#初始化变量
init = tf.global_variables_initializer()
batch_size = 20
train_step = 100
output_summary_every_step = 100#每100步输入计算出的图示
#建立会话
with tf.Session() as sess:
sess.run(init)
#完成文件的写入
train_writer = tf.summary.FileWriter(train_log_dir,#路径
sess.graph)#写入的计算图
#test_writer = tf.summary.FileWriter(test_log_dir,sess.graph)
for i in range(train_step):
batch_data,batch_labels = train_data.next_batch(batch_size)
eval_ops = [loss,accuracy,train_op]
should_output_summary = (i%output_summary_every_step == 0)
if should_output_summary:
eval_ops.append(merged_summary)
eval_ops_results = sess.run(eval_ops,
feed_dict={
x:batch_data,
y:batch_labels})
loss_val,acc_val = eval_ops_results[0:2]
if should_output_summary:
train_summary_str = eval_ops_ results[-1]
train_writer.add_summary(train_summary_str,i+1)
if (i+1)%50 == 0:
print('[train]step %d,loss:%4.5f,acc:%4.5f'%(i+1,loss_val,acc_val))
运行最终效果