【TensorFlow-windows】(六) CNN之Alex-net的测试
主要内容:
1.CNN之Alex-net的测试
2.该实现中的函数总结
平台:
1.windows 10 64位
2.Anaconda3-4.2.0-Windows-x86_64.exe (当时TF还不支持python3.6,又懒得在高版本的anaconda下配置多个Python环境,于是装了一个3-4.2.0(默认装python3.5),建议装anaconda3的最新版本,TF1.2.0版本已经支持python3.6!)
3.TensorFlow1.1.0
2012年以Alex-net为代表的深度学习在imagenet上大放异彩,之后深度学习席卷大江南北,往后的Imagenet的冠军无不采用深度学习……(吹不下去了),Alex-net就是近期(神经网络生命期来看)神经网络重回众人视线的开端。
Alex-net的成功得益于其采用了多个Trick,并且这些trick早就被学者提出,只不过Alex等人将其整合,发挥得当。其中包括抗梯度消失以及提供稀疏性的激活函数Relu;类似bagging的作用的Dropout来解决过拟合问题;模仿生物神经元的侧抑制的LRN( Local Response Normalization);数据增强(data augmentation)来获得更多数据,从而防止过拟合;采用双GPU加速训练等等;
来看看Alex-net的结构:
具体是: conv1–LRN1–pool1—-conv2–LRN2–pool2–conv3–conv4–conv5–pool3–fc1–fc2–fc3(输出层)
由于硬件限制,只是做了一个前向和反向的耗时计算,配置好的同学可以稍作修改即可训练。
代码:
# -*- coding: utf-8 -*-
"""
Created on Mon Jun 12 16:36:43 2017
@author: ASUS
"""
from datetime import datetime
import math
import time
import tensorflow as tf
batch_size = 32
num_batches = 100
# 定义一个显示函数,来观察每一层输出的尺寸和名称
def print_activations(t):
print(t.op.name, ' ', t.get_shape().as_list()) # 以list的形式返回tensor的shape
# 输入为一个batch的图片,输出为Alex-net全连接层3的输出(未经sotfmax)
def inference(images):
parameters = []
# conv1
# tf.name_scope是用来命名Variable的,print(parameters)可以看到命名
with tf.name_scope('conv1') as scope:
kernel = tf.Variable(tf.truncated_normal([11, 11, 3, 64],
dtype = tf.float32, stddev = 1e-1), name = 'weights')
conv = tf.nn.conv2d(images, kernel, [1,4,4,1], padding = 'SAME')
biases = tf.Variable(tf.constant(0.0,shape = [64], dtype = tf.float32),
trainable = True, name = 'biases') # trainable ???
bias = tf.nn.bias_add(conv, biases)
conv1 = tf.nn.relu(bias, name = scope)
parameters += [kernel, biases]
print_activations(conv1)
lrn1 = tf.nn.lrn(conv1, 4, bias =1.0, alpha=0.001/9,
beta = 0.75, name = 'lrn1')
pool1 = tf.nn.max_pool(lrn1, ksize = [1,3,3,1],strides = [1,2,2,1],
padding = 'VALID', name = 'pool1')
print_activations(pool1)
# conv2
# tf.name_scope是用来命名Variable的,print(parameters)可以看到命名
with tf.name_scope('conv2') as scope:
kernel = tf.Variable(tf.truncated_normal([5, 5, 64, 192],
dtype = tf.float32, stddev = 1e-1), name = 'weights')
conv = tf.nn.conv2d(pool1, kernel, [1,1,1,1], padding = 'SAME')
biases = tf.Variable(tf.constant(0.0,shape = [192], dtype = tf.float32),
trainable = True, name = 'biases') # trainable ???
bias = tf.nn.bias_add(conv, biases)
conv2 = tf.nn.relu(bias, name = scope)
parameters += [kernel, biases]
print_activations(conv2)
lrn2 = tf.nn.lrn(conv2, 4, bias =1.0, alpha=0.001/9,
beta = 0.75, name = 'lrn2')
pool2 = tf.nn.max_pool(lrn2, ksize = [1,3,3,1],strides = [1,2,2,1],
padding = 'VALID', name = 'pool2')
print_activations(pool2)
# conv3
# tf.name_scope是用来命名Variable的,print(parameters)可以看到命名
with tf.name_scope('conv3') as scope:
kernel = tf.Variable(tf.truncated_normal([3, 3, 192, 384],
dtype = tf.float32, stddev = 1e-1), name = 'weights')
conv = tf.nn.conv2d(pool2, kernel, [1,1,1,1], padding = 'SAME')
biases = tf.Variable(tf.constant(0.0,shape = [384], dtype = tf.float32),
trainable = True, name = 'biases') # trainable ???
bias = tf.nn.bias_add(conv, biases)
conv3 = tf.nn.relu(bias, name = scope)
parameters += [kernel, biases]
print_activations(conv3) # 无pool层
# conv4
# tf.name_scope是用来命名Variable的,print(parameters)可以看到命名
with tf.name_scope('conv4') as scope:
kernel = tf.Variable(tf.truncated_normal([3, 3, 384, 256],
dtype = tf.float32, stddev = 1e-1), name = 'weights')
conv = tf.nn.conv2d(conv3, kernel, [1,1,1,1], padding = 'SAME')
biases = tf.Variable(tf.constant(0.0,shape = [256], dtype = tf.float32),
trainable = True, name = 'biases') # trainable ???
bias = tf.nn.bias_add(conv, biases)
conv4 = tf.nn.relu(bias, name = scope)
parameters += [kernel, biases]
print_activations(conv4)
# conv5
# tf.name_scope是用来命名Variable的,print(parameters)可以看到命名
with tf.name_scope('conv5') as scope:
kernel = tf.Variable(tf.truncated_normal([3, 3, 256, 256],
dtype = tf.float32, stddev = 1e-1), name = 'weights')
conv = tf.nn.conv2d(conv4, kernel, [1,1,1,1], padding = 'SAME')
biases = tf.Variable(tf.constant(0.0,shape = [256], dtype = tf.float32),
trainable = True, name = 'biases') # trainable ???
bias = tf.nn.bias_add(conv, biases)
conv5 = tf.nn.relu(bias, name = scope)
parameters += [kernel, biases]
print_activations(conv5)
pool5 = tf.nn.max_pool(conv5, ksize = [1,3,3,1], strides=[1,2,2,1],
padding = 'VALID', name = 'pool5')
print_activations(pool5)
# fcn1
# tf.name_scope是用来命名Variable的,print(parameters)可以看到命名
with tf.name_scope('fcn1') as scope:
reshape = tf.reshape(pool5, [batch_size, -1])
dim = reshape.get_shape()[1].value # 获取数据长度
weight1 = tf.Variable(tf.truncated_normal([dim, 4096], stddev = 1e-1))
bias1 = tf.Variable(tf.constant(0.0, shape = [4096]))
local1 = tf.nn.relu(tf.matmul(reshape, weight1) + bias1, name = scope)
parameters += [weight1, bias1]
# fcn2
# tf.name_scope是用来命名Variable的,print(parameters)可以看到命名
with tf.name_scope('fcn2') as scope:
weight2 = tf.Variable(tf.truncated_normal([4096, 4096], stddev = 1e-1))
bias2 = tf.Variable(tf.constant(0.0, shape = [4096]))
local2 = tf.nn.relu(tf.matmul(local1, weight2) + bias2, name = scope)
parameters += [weight2, bias2]
# fcn3
# tf.name_scope是用来命名Variable的,print(parameters)可以看到命名
with tf.name_scope('fcn3') as scope:
weight3 = tf.Variable(tf.truncated_normal([4096, 1000], stddev = 1e-1))
bias3 = tf.Variable(tf.constant(0.0, shape = [1000]))
local3 = tf.matmul(local2, weight3) + bias3 # 最后一层,不需要进行relu
parameters += [weight2, bias2]
# print('看看local3: ',local3,'看看pool5: ',pool5)
# print('看parameters: ', parameters)
return local3, parameters
# 定义函数来计算每个batch经过Alex-nex的时间
def time_tensorflow_run(session, target, info_string):
num_steps_burn_in = 10 # 预热轮数,头几次会涉及显存加载、cache命中等问题,所以可以跳过
total_duration = 0.0
total_duration_squared = 0.0
for i in range(num_batches + num_steps_burn_in):
start_time = time.time()
_ = session.run(target)
duration = time.time() - start_time
# 超过预热轮数,才开始记录耗时
if i >= num_steps_burn_in:
# 10个 batch才打印一次,
if not i % 10 :
print('%s: step %d, duration = %.3f'
%(datetime.now(), i-num_steps_burn_in, duration))
total_duration += duration
total_duration_squared += duration * duration
#
mean_dur = total_duration / num_batches
var_dur = total_duration_squared / num_batches - mean_dur * mean_dur
std_dur = math.sqrt(var_dur)
print('%s: %s across %d steps, %.3f +/- %.3f sec / batch' %
(datetime.now(), info_string, num_batches, mean_dur, std_dur))
def run_benchmark():
# tf.Graph.as_default() 的上下文管理器( context manager),它能够在这个上下文里面覆盖默认的图
# with tf.Graph().as_default():
# 生成一个batch的图片 [32,224,224,3]
image_size = 224
images = tf.Variable(tf.random_normal(
[batch_size, image_size,image_size,3],
dtype = tf.float32, stddev = 1e-1))
local3, parameters = inference(images)
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
time_tensorflow_run(sess, local3, "Forward")
objective = tf.nn.l2_loss(local3)
grad = tf.gradients(objective, parameters)
time_tensorflow_run(sess, grad, "Forward-backward")
run_benchmark()
函数总结(续上篇)
sess = tf.InteractiveSession() 将sess注册为默认的session
tf.placeholder() , Placeholder是输入数据的地方,也称为占位符,通俗的理解就是给输入数据(此例中的图片x)和真实标签(y_)提供一个入口,或者是存放地。(个人理解,可能不太正确,后期对TF有深入认识的话再回来改~~)
tf.Variable() Variable是用来存储模型参数,与存储数据的tensor不同,tensor一旦使用掉就消失
tf.matmul() 矩阵相乘函数
tf.reduce_mean 和tf.reduce_sum 是缩减维度的计算均值,以及缩减维度的求和
tf.argmax() 是寻找tensor中值最大的元素的序号 ,此例中用来判断类别
tf.cast() 用于数据类型转换
————————————–我是分割线(一)———————————–
tf.random_uniform 生成均匀分布的随机数
tf.train.AdamOptimizer() 创建优化器,优化方法为Adam(adaptive moment estimation,Adam优化方法根据损失函数对每个参数的梯度的一阶矩估计和二阶矩估计动态调整针对于每个参数的学习速率)
tf.placeholder “占位符”,只要是对网络的输入,都需要用这个函数这个进行“初始化”
tf.random_normal 生成正态分布
tf.add 和 tf.matmul 数据的相加 、相乘
tf.reduce_sum 缩减维度的求和
tf.pow 求幂函数
tf.subtract 数据的相减
tf.global_variables_initializer 定义全局参数初始化
tf.Session 创建会话.
tf.Variable 创建变量,是用来存储模型参数的变量。是有别于模型的输入数据的
tf.train.AdamOptimizer (learning_rate = 0.001) 采用Adam进行优化,学习率为 0.001
————————————–我是分割线(二)———————————–
1. hidden1_drop = tf.nn.dropout(hidden1, keep_prob) 给 hindden1层增加Droput,返回新的层hidden1_drop,keep_prob是 Droput的比例
2. mnist.train.next_batch() 来详细讲讲 这个函数。一句话概括就是,打乱样本顺序,然后按顺序读取batch_size 个样本 进行返回。
具体看代码及其注释,首先要找到函数定义,在tensorflow\contrib\learn\python\learn\datasets 下的mnist.py
————————————–我是分割线(三)———————————–
1. tf.nn.conv2d(x, W, strides = [1, 1, 1, 1], padding =’SAME’)对于这个函数主要理解 strides和padding,首先明确,x是输入,W是卷积核,并且它们的维数都是4(发现strides里有4个元素没,没错!就是一一对应的)
先说一下卷积核W也是一个四维张量,各维度表示的信息是:[filter_height, filter_width, in_channels, out_channels]
输入x,x是一个四维张量 ,各维度表示的信息是:[batch, in_height, in_width, in_channels]
strides里的每个元素就是对应输入x的四个维度的步长,因为第2,3维是图像的长和宽,所以平时用的strides就在这里设置,而第1,4维一般不用到,所以是1
padding只有两种取值方式,一个是 padding=[‘VALID’] 一个是padding=[‘SAME’]
valid:采用丢弃的方式,只要移动一步时,最右边有超出,则这一步不移动,并且剩余的进行丢弃。如下图,图片长13,卷积核长6,步长是5,当移动一步之后,已经卷积核6-11,再移动一步,已经没有足够的像素点了,所以就不能移动,因此 12,13被丢弃。
same:顾名思义,就是保持输入的大小不变,方法是在图像边缘处填充全0的像素
————————————–我是分割线(四)———————————–
1.tf.nn.l2_loss()是对一个Tensor对象求L2 norm,这里用来求权值的L2范数
2.tf.add_to_collection(‘losses’, weight_loss), 把weight_loss 加到名为losses的loss中去
3.tf.nn.lrn,做局部相应归一化,来看看函数的定义
lrn(input, depth_radius=None, bias=None, alpha=None, beta=None,name=None)
函数有6个输入,第一个自然是输入啦,2-5是LRN用到的参数,第一个是depth半径,bias alpha beta是相应的参数,来看看伪代码就清楚了
sqr_sum[a, b, c, d] = sum(input[a, b, c, d - depth_radius : d + depth_radius + 1] ** 2)
output = input / (bias + alpha * sqr_sum) ** beta
2-5的参数是有默认值的,分别是5,1,1,0.5
3.tf.nn.sparse_softmax_cross_entropy_with_logits()就是计算交叉熵的,但是如果是一个batch的,得出的是一个向量,还需要配合tf.reduce_mean()才能计算得出最终的loss(标量)
4.tf.get_collection(‘losses’), 从一个集合中取出全部变量,返回的是一个列表,这里是把losses的都取出来,在上面把 weight_loss和输出层的loss都加到losses中了,这个函数就是把losses中的loss取出来
5.tf.add_n:把一个列表的东西都依次加起来 (Adds all input tensors element-wise.) 输入是一个list (inputs: A list of Tensor objects, each with same shape and type.)
————————————–我是分割线(五)———————————–
1.t.get_shape().as_list() ,t是一个tensor,函数以list的形式返回 tensor的shape
2.tf.name_scope(‘conv1’) as scope ,tf.name_scope是用来命名Variable的,print(parameters)可以看到命名
3. tf.Graph().as_default()能够在这个上下文里面覆盖默认的图,测试了一下 不用这个也可以,但是用上好点,可能是避免冲突吧~