正文为黑色,着重部分为红色,注释部分为彩色(按照颜色的排列顺序,重要程度递减)
我们所做的这个微调的方法很简单,就是先创建一个模型(当然了,这个模型并不是原始的AlexNet模型),我们将想要skip的layer的名字放在 skip_layer 当中(之所以要这么做,是因为在这个链接中的Alexnet是加载了训练好的参数,并没有自己训练的过程,如果你要修改他的网络,那么就需要重新训练你所更改的这部分),然后再tenso中设置我们的Loss和 optimizer ops , 最后一步,创建一个sess,训练我们的网络。
ps_1 : 链接中的代码还使用了tensorboard,如果你并不是很熟悉这个可视化工具,我将会在后面的部分粗略的说一下
ps_2 : 这部分微调Alexnet使用的是猫狗大战的数据集,可以自行下载,或者使用这个麻烦的方法(https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition/data)
作者将数据集分成了三部分,分别是训练集(training_set),验证集(validation_set )和测试集(test_set),三者之间的比例是70:15:15;我们还要分别为这三个集合创建一个txt文件,用来存储image和class label 的路径。
有了这个txt文件,我们可以再来创建一个class作为图像数据生成器( image data generator ), 这个image data generator 的作用就像one of Keras for example,
这个使用image data generator可能并不是最好的方法,但是后面我们需要关注图像是如何进行装载和预处理的,这种方法会对我们要关注的部分提供很大的便利。
首先是import 部分的定义, 没有什么好说的;
import numpy as np
import tensorflow as tf
from datetime import datetime
from alexnet import AlexNet
from datagenerator import ImageDataGenerator
# # Path to the textfiles for the trainings and validation set
# 设置训练集和验证集的txt文件的路径
train_file = '/path/to/train.txt'
val_file = '/path/to/val.txt'
# Learning params
# 这里的参数是在训练过程中会用的的,譬如梯度下降中的learning rate等等。。
learning_rate = 0.001 # 梯度下降的步长
num_epochs = 10 # 一共10批()
batch_size = 128 # 每批包含128个数据
# 如果不懂,参考一下这位朋友的文章 http://m.elecfans.com/article/800487.html
# Network params
# 网络层的一些参数,(也就是我们在创建一个新的AlexNet实例的时候,需要注意改变的一些参数,)
# 参数的详细内容请查看原文中的这部分(class AlexNet(object): )
dropout_rate = 0.5
num_classes = 2
train_layers = ['fc8', 'fc7']
# 有关可视化的一些操作,如果你想理解这部分,你需要懂得一些tensorboard的知识
# 这里的checkPoint可能是存放参数的文件(这个模型有很大一部分参数是从训练好的模型中得到)
# How often we want to write the tf.summary data to disk
display_step = 1
# Path for tf.summary.FileWriter and to store model checkpoints
filewriter_path = "/tmp/finetune_alexnet/dogs_vs_cats"
checkpoint_path = "/tmp/finetune_alexnet/"
# Create parent path if it doesn't exist
if not os.path.isdir(checkpoint_path): os.mkdir(checkpoint_path)
作者这里选择调整的两层网络是fc7和fc8,我们可以根据我们自己的数据集和自己的需求来调整这个AlexNet网络,并不一定要照搬他的方法;
作者将几个参数的取值留在了之前的标注AlexNet当中(譬如dropout probability, learning rate ...),我们可以找到他们的位置并且修改它们,充分理解参数的作用并且掌握修改他们的方法才可以使得你的模型得到你想要的好结果。
- - - - - - - - - -
现在我们来完成一些在tensorflow中的工作;;;;
首先我们要为输出和label来创建一些变量,哦,当然这也包括dropout rate
(in test mode we deactivate dropout, while TensorFlow takes care of activation scaling)
x = tf.placeholder(tf.float32, [batch_size, 227, 227, 3]) # input
y = tf.placeholder(tf.float32, [None, num_classes]) # groundtruth
keep_prob = tf.placeholder(tf.float32) # dropout中,每个神经元被丢掉的概率为keep_prob
OK了,到这里,我们就可以来定义一个新的AlexNet类了,
# Initialize model
model = AlexNet(x, keep_prob, num_classes, train_layers)
# 对比一下之前标准AlexNet的定义
# def __init__(self, x, keep_prob, num_classes, skip_layer,
# weights_path = 'DEFAULT'):
"""
Inputs:
- x: tf.placeholder, for the input images
- keep_prob: tf.placeholder, for the dropout rate
- num_classes: int, number of classes of the new dataset
- skip_layer: list of strings, names of the layers you want to reinitialize
- weights_path: path string, path to the pretrained weights,
(if bvlc_alexnet.npy is not in the same folder)
"""
我们还要在创建一个指向模型输出的变量,也就是指向fc8的输出(这个输出是unscaled score 的,)
#link variable to model output
score = model.fc8
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
首先,我们来定义一个list用来存储我们需要训练的变量(再次重申一遍,这个例子的alexnet网络参数是读取现成的,已经训练好的参数,我们在这里选择了两层网络来重新训练他们的参数)
# List of trainable variables of the layers we want to train
var_list = [v for v in tf.trainable_variables() if v.name.split('/')[0] in train_layers]
# split()函数,以‘/’为依据来分割string, [0]指的是分割后得到的第一部分
# tf.trainable_variables 可以的到所有的变量
# Op for calculating the loss
# 用来计算 loss的步骤 (这个loss是单一的损失函数,而不是数据集的代价函数)
with tf.name_scope("cross_ent"):
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits = score, labels = y))
# 这个with tf.name_scope(name): 方法的含义有点类似于定义了一个工作空间
这里说一下loss计算时所用的一些函数的问题
(ps: 这部分是我从一位朋友的博客摘抄的,我忘记了他的出处,在这里和这位朋友说声sorry,找到之后我会加上)
在计算loss的时候,最常见的一句话就是tf.nn.softmax_cross_entropy_with_logits,那么它到底是怎么做的呢?
首先明确一点,loss是代价值,也就是我们要最小化的值
这是函数的原型 tf.nn.softmax_cross_entropy_with_logits(logits, labels, name=None)
除去name参数用以指定该操作的name,与方法有关的一共两个参数:
第一个参数logits:就是神经网络最后一层的输出,如果有batch的话,它的大小就是[batchsize,num_classes],单样本的话,大小就是num_classes。
第二个参数labels:实际的标签,大小同上。
--- 注意!!!这个函数的返回值并不是一个数,而是一个向量,如果要求交叉熵,我们要再做一步tf.reduce_sum操作,就是对向量里面所有元素求和,最后才得到,如果求loss,则要做一步tf.reduce_mean操作,对向量求均值!
# # Train op
# # 也就是模型的部分参数的训练操作
with tf.name_scope("train"):
# Get gradients of all trainable variables
gradients = tf.gradients(loss, var_list)
gradients = list(zip(gradients, var_list))
我们从 tf.gradients() 这个函数来切入
它是一个计算梯度的方法,
在这一小部分,我们为loss分别计算了每一个var_list中参数值的倒数,并且将倒数值和函数值放在一个list中
# Create optimizer and apply gradient descent to the trainable variables
# 这步我们要创建optimizer 并且将gradients中的数据应用进去
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
train_op = optimizer.apply_gradients(grads_and_vars=gradients)
其实这里我们完全可以用一种更加简单且直观的方法
就是optimizer.minimize( loss ),
但是在下面,我们要使用tensorboard了,只有这里的这种方法才能使我们更好的使用tensorboard
# 下面是关于tensorboard的一些操作(如果你看不懂,可以先暂停一下,去翻翻我后面写的东西)
# Add gradients to summary
for gradient, var in gradients:
tf.summary.histogram(var.name + '/gradient', gradient)
# Add the variables we train to the summary
for var in var_list:
tf.summary.histogram(var.name, var)
# Add the loss to summary
tf.summary.scalar('cross_entropy', loss)
OK ! ! ! ! ! Next we define an op (accuracy) for the evaluation.
# 下面的步骤是为了计算结果的精度
# Evaluation op: Accuracy of the model
with tf.name_scope("accuracy"):
correct_pred = tf.equal(tf.argmax(score, 1), tf.argmax(y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
# Add the accuracy to the summary
tf.summary.scalar('accuracy', accuracy)
# 在我们进行训练之前,我们先把所有的部分整合在一起(tensorboard部分)
# Merge all summaries together
merged_summary = tf.summary.merge_all()
# Initialize the FileWriter
writer = tf.summary.FileWriter(filewriter_path)
# Initialize an saver for store model checkpoints
saver = tf.train.Saver()
# Initalize the data generator seperately for the training and validation set
train_generator = ImageDataGenerator(train_file,
horizontal_flip = True, shuffle = True)
val_generator = ImageDataGenerator(val_file, shuffle = False)
# Get the number of training/validation steps per epoch
train_batches_per_epoch = np.floor(train_generator.data_size / batch_size).astype(np.int16)
val_batches_per_epoch = np.floor(val_generator.data_size / batch_size).astype(np.int16)
FileWriter
and after each epoch we will evaluate the model and save a model checkpoint. And there you go:通常的情况下呢,我们会先launch一个session, 然后我们初始化我们的变量, 装载那些我们并不想从头训练的参数(这里是lc7和lc8), 接下来,我们一次又一次的循环我们的training step , 最后,我们执行 training operation
除了这些,还有一些关于tensorboard的操作, 我们还存储一些summary的信息到FileWriter, 每次epooch之后,我们会评估这个模型,并且
save a model checkpoint
# Start Tensorflow session
with tf.Session() as sess:
# Initialize all variables
sess.run(tf.global_variables_initializer())
# Add the model graph to TensorBoard
writer.add_graph(sess.graph)
# Load the pretrained weights into the non-trainable layer
model.load_initial_weights(sess)
print("{} Start training...".format(datetime.now()))
print("{} Open Tensorboard at --logdir {}".format(datetime.now(),
filewriter_path))
# Loop over number of epochs
for epoch in range(num_epochs):
print("{} Epoch number: {}".format(datetime.now(), epoch+1))
step = 1
while step < train_batches_per_epoch:
# Get a batch of images and labels
batch_xs, batch_ys = train_generator.next_batch(batch_size)
# And run the training op
sess.run(train_op, feed_dict={x: batch_xs,
y: batch_ys,
keep_prob: dropout_rate})
# Generate summary with the current batch of data and write to file
if step%display_step == 0:
s = sess.run(merged_summary, feed_dict={x: batch_xs,
y: batch_ys,
keep_prob: 1.})
writer.add_summary(s, epoch*train_batches_per_epoch + step)
step += 1
# Validate the model on the entire validation set
print("{} Start validation".format(datetime.now()))
test_acc = 0.
test_count = 0
for _ in range(val_batches_per_epoch):
batch_tx, batch_ty = val_generator.next_batch(batch_size)
acc = sess.run(accuracy, feed_dict={x: batch_tx,
y: batch_ty,
keep_prob: 1.})
test_acc += acc
test_count += 1
test_acc /= test_count
print("Validation Accuracy = {:.4f}".format(datetime.now(), test_acc))
# Reset the file pointer of the image data generator
val_generator.reset_pointer()
train_generator.reset_pointer()
print("{} Saving checkpoint of model...".format(datetime.now()))
#save checkpoint of the model
checkpoint_name = os.path.join(checkpoint_path, 'model_epoch'+str(epoch)+'.ckpt')
save_path = saver.save(sess, checkpoint_name)
print("{} Model checkpoint saved at {}".format(datetime.now(), checkpoint_name))
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
这篇文章详细的介绍了tensorboard的使用方法,很详细,这里我也就不在搬运了http://www.cnblogs.com/fydeblog/p/7429344.html
简单的把链接放在这里。
还有就是可视化的部分,我们使用 writer = tf.summary.FileWriter("logs2", sess.graph) 这行代码将tensorboard的可执行文件存储在名字为logs2的文件夹下,使用这个教程打开:https://jingyan.baidu.com/album/49ad8bce9aae995834d8fa9c.html?picindex=2
ps: 我自己试验的电脑是win10的系统
tensorflow的可视化大概可以分为三部分:
1,tensorflow + opencv
2, matplotlib
3, tensorboard ;
TensorFlow中最重要的可视化方法是通过tensorBoard、tf.summary和tf.summary.FileWriter这三个模块相互合作来完成的
关于tensorboard,tf.summary, 和 tf.summary.Filterwriter 的关系如下
#========================================================================================================
#函数原型:
# def scalar(name, tensor, collections=None, family=None)
#函数说明:
# [1]输出一个含有标量值的Summary protocol buffer,这是一种能够被tensorboard模块解析的【结构化数据格式】
# [2]用来显示标量信息
# [3]用来可视化标量信息
# [4]其实,tensorflow中的所有summmary操作都是对计算图中的某个tensor产生的单个summary protocol buffer,而
# summary protocol buffer又是一种能够被tensorboard解析并进行可视化的结构化数据格式
# 虽然,上面的四种解释可能比较正规,但是我感觉理解起来不太好,所以,我将tf.summary.scalar()函数的功能理解为:
# [1]将【计算图】中的【标量数据】写入TensorFlow中的【日志文件】,以便为将来tensorboard的可视化做准备
#参数说明:
# [1]name :一个节点的名字,如下图红色矩形框所示
# [2]tensor:要可视化的数据、张量
#主要用途:
# 一般在画loss曲线和accuary曲线时会用到这个函数。
#=======================================================================================================
运行链接中的这句,tf.summary.scalar('loss',loss),会得到如下结果:
#========================================================================================================
#函数原型:
# def histogram(name, values, collections=None, family=None)
#函数说明:
# [1]用来显示直方图信息
# [2]添加一个直方图的summary,它可以用于可视化您的数据的分布情况,关于TensorBoard中直方图更加具体的信息可以在
# 下面的链接https://www.tensorflow.org/programmers_guide/tensorboard_histograms中获取
#
# 虽然,上面的两种解释可能比较正规,但是我感觉理解起来不太好,所以,我将tf.summary.histogram()函数的功能理解为:
# [1]将【计算图】中的【数据的分布/数据直方图】写入TensorFlow中的【日志文件】,以便为将来tensorboard的可视化做准备
#参数说明:
# [1]name :一个节点的名字,如下图红色矩形框所示
# [2]values:要可视化的数据,可以是任意形状和大小的数据
#主要用途:
# 一般用来显示训练过程中变量的分布情况
#========================================================================================================
with tf.name_scope('parameters'):
with tf.name_scope('weights'):
weight = tf.Variable(tf.random_uniform([1],-1.0,1.0))
tf.summary.histogram('weight',weight)
with tf.name_scope('biases'):
bias = tf.Variable(tf.zeros([1]))
tf.summary.histogram('bias',bias)
这样的一段代码,会得到以下结果:
#========================================================================================================
#函数原型:
# def merge_all(key=_ops.GraphKeys.SUMMARIES, scope=None)
#函数说明:
# [1]将之前定义的所有summary整合在一起
# [2]和TensorFlow中的其他操作类似,tf.summary.scalar、tf.summary.histogram、tf.summary.image函数也是一个
# op,它们在定义的时候,也不会立即执行,需要通过sess.run来明确调用这些函数。因为,在一个程序中定义的写日志操作
# 比较多,如果一一调用,将会十分麻烦,所以Tensorflow提供了tf.summary.merge_all()函数将所有的summary整理在一
# 起。在TensorFlow程序执行的时候,只需要运行这一个操作就可以将代码中定义的所有【写日志操作】执行一次,从而将
# 所有的日志写入【日志文件】。
#
#参数说明:
# [1]key : 用于收集summaries的GraphKey,默认的为GraphKeys.SUMMARIES
# [2]scope:可选参数
#========================================================================================================
#========================================================================================================
#类定义原型:
# class FileWriter(SummaryToEventTransformer)
#类说明:
# [1]将Summary protocol buffers写入磁盘文件
# [2]FileWriter类提供了一种用于在给定目录下创建事件文件的机制,并且将summary数据写入硬盘
#构造函数:
# def __init__(self,logdir,graph=None,max_queue=10,flush_secs=120,graph_def=None,filename_suffix=None):
#参数说明:
# [1]self : 类对象自身
# [2]logdir:用于存储【日志文件】的目录
# [3]graph : 将要存储的计算图
#应用示例:
# summary_writer = tf.summary.FileWriter(SUMMARY_DIR,sess.graph):创建一个FileWrite的类对象,并将计算图
# 写入文件
#========================================================================================================
示例代码如下:
merged = tf.summary.merge_all()
#【8】创建回话Session
with tf.Session() as sess:
#【9】实例化一个FileWriter的类对象,并将当前TensoirFlow的计算图写入【日志文件】
summary_writer = tf.summary.FileWriter(SUMMARY_DIR,sess.graph)
#【10】Tensorflow中创建的变量,在使用前必须进行初始化,下面这个为初始化函数
tf.global_variables_initializer().run()
#【11】开始训练
for i in range(TRAIN_STEPS):
xs,ys = mnist.train.next_batch(BATCH_SIZE)
#【12】运行训练步骤以及所有的【日志文件生成操作】,得到这次运行的【日志文件】。
summary,_,acc = sess.run([merged,train_step,accuracy],feed_dict={x:xs,y_:ys})
print('Accuracy at step %s: %s' % (i, acc))
#【13】将所有的日志写入文件,TensorFlow程序就可以那这次运行日志文件,进行各种信息的可视化
summary_writer.add_summary(summary,i)
summary_writer.close()
#========================================================================================================
#函数原型:
# def image(name, tensor, max_outputs=3, collections=None, family=None)
#函数说明:
# [1]输出一个包含图像的summary,这个图像是通过一个4维张量构建的,这个张量的四个维度如下所示:
# [batch_size,height, width, channels]
# [2]其中参数channels有三种取值:
# [1]1: `tensor` is interpreted as Grayscale,如果为1,那么这个张量被解释为灰度图像
# [2]3: `tensor` is interpreted as RGB,如果为3,那么这个张量被解释为RGB彩色图像
# [3]4: `tensor` is interpreted as Grayscale,如果为4,那么这个张量被解释为RGBA四通道图像
# [3]输入给这个函数的所有图像必须规格一致(长,宽,通道,数据类型),并且数据类型必须为uint8,即所有的像素值在
# [0,255]这个范围
# 虽然,上面的三种解释可能比较正规,但是我感觉理解起来不太好,所以,我将tf.summary.image()函数的功能理解为:
# [1]将【计算图】中的【图像数据】写入TensorFlow中的【日志文件】,以便为将来tensorboard的可视化做准备
#
#参数说明:
# [1]name :一个节点的名字,如下图红色矩形框所示
# [2]tensor:要可视化的图像数据,一个四维的张量,元素类型为uint8或者float32,维度为[batch_size, height,
# width, channels]
# [3]max_outputs:输出的通道数量,可以结合下面的示例代码进行理解
#主要用途:
# 一般用在神经网络中图像的可视化
#========================================================================================================
实验代码和结果如下
def main(argv=None):
#【1】从磁盘加载数据
mnist = input_data.read_data_sets('F:/MnistSet/',one_hot=True)
#【2】定义两个【占位符】,作为【训练样本图片/此块样本作为特征向量存在】和【类别标签】的输入变量,并将这些占位符存在命名空间input中
with tf.name_scope('input'):
x = tf.placeholder('float', [None, 784],name='x-input')
y_ = tf.placeholder('float', [None, 10], name='y-input')
#【2】将【输入的特征向量】还原成【图片的像素矩阵】,并通过tf.summary.image函数定义将当前图片信息作为写入日志的操作
with tf.name_scope('input_reshape'):
image_shaped_input = tf.reshape(x,[-1,28,28,1])
tf.summary.image('input',image_shaped_input,10)
刚刚接触CNN,总是会混淆这两个单词,哈哈
首先是patch:
首先呢,我们由这句话来切入主题 “ CNN用filter 来把图片分割成更小的 patch,patch 的大小跟filter 大小相同。 ” 这里的filter我理解应该也就是卷积核,还是以我们的AlexNet为例,。
我们的输入是227*227 x3的,所以我们自然就要用11*11×3(11*11并不是重点,3才是重点)的核卷积来卷积227*227×3的图像,我们得到结果的size为55*55x1 , 这样的结果一共有96个。我认为将这个11*11x3的卷积核投影到我们的input上,所得到的图中的红色部分就是所谓的patch了,这个patch应该是有深度的,正所谓patch的size和filter的size相同嘛。
11*11x3 我想我应该已经说清楚了,现在来看一下 96
模型通常情况下都会有多于一个滤波器,不同滤波器提取一个 patch 的不同特性。例如,一个滤波器寻找特定颜色,另一个寻找特定物体的特定形状。卷积层滤波器的数量被称为滤波器深度。
每个 patch 连接多少神经元?
这取决于滤波器的深度,如果深度是 k
,我们把每个 patch 与下一层的 k
个神经元相连。这样下一层的高度就是 k
,如下图所示。实际操作中,k
是一个我们可以调节的超参数,大多数的 CNNs 倾向于选择相同的起始值。
为什么我们把一个 patch 与下一层的多个神经元相连呢?
多个神经元的作用在于,一个 patch 可以有多个有意义的,可供提取的特点。
例如,一个 patch 可能包括白牙,金色的须,红舌头的一部分。在这种情况下,我们需要一个深度至少为3的滤波器,一个识别牙,一个识别须,一个识别舌头。
一个 patch 连接有多个神经元可以保证我们的 CNNs 学会提取任何它觉得重要的特征。
(从https://blog.csdn.net/sinat_32547403/article/details/74898083参考得到)
然后是batch:(节选自http://m.elecfans.com/article/800487.html)
对于batch,我们先从Mini- batch 和 SGD(stochastic gradient descent) 来说起,一般提到的SGD是指的Mini-batch SGD,而非原教旨意义下的单实例SGD。
图1. Mini-Batch SGD 训练过程(假设Batch Size=2)
所谓“Mini-Batch”,是指的从训练数据全集T中随机选择的一个训练数据子集合。假设每个Mini-Batch的Batch Size为b,训练数据集合T包含N个样本,于是整个训练数据可被分成N/b个Mini-Batch。
在模型通过SGD进行训练时,一般跑完一个Mini-Batch的实例,叫做完成训练的一步(step),跑完N/b步,(也就是说他跑完了一整个训练集)则整个训练数据完成一轮训练,则称为完成一个Epoch。完成一个Epoch训练过程后,对训练数据做随机Shuffle打乱训练数据顺序,重复上述步骤,然后开始下一个Epoch的训练,对模型完整充分的训练由多轮Epoch构成(参考图1)。
这样的描述使得问题变得很清晰明了。。。。。。。。。。。
在拿到一个Mini-Batch进行参数更新时,首先根据当前Mini-Batch内的b个训练实例以及参数对应的损失函数的偏导数来进行计算,以获得参数更新的梯度方向,然后根据SGD算法进行参数更新,以此来达到本步(Step)更新模型参数并逐步寻优的过程。
图2. Mini-Batch SGD优化过程
由上述过程(参考图2)可以看出,对于Mini-Batch SGD训练方法来说,为了能够参数更新必须得先求出梯度方向,而为了能够求出梯度方向,需要对每个实例得出当前参数下映射函数的预测值,这意味着如果是用神经网络来学习映射函数h_θ的话,Mini-Batch内的每个实例需要走一遍当前的网络,产生当前参数下神经网络的预测值,这点请注意,这是理解后续Batch Normalization的基础。
至于Batch Size的影响,目前可以实验证实的是:batch size 设置得较小训练出来的模型相对大batch size训练出的模型泛化能力更强,在测试集上的表现更好,而太大的batch size往往不太Work,而且泛化能力较差。但是背后是什么原因造成的,目前还未有定论,持不同看法者各持己见。
首先,我们的AlexNet使用的是传统的dropout,在训练阶段,对应用了dropout的layer,
每个神经元以keep_prob的概率保留下来(或者以1-keep_prob的概率关闭),
在测试阶段不使用dropout
也就是所有的神经元都不关闭,但是对应用阶段使用了dropout的layer上的神经元,其输出激活值要乘以keep_prob
但是现在主流的dropout都是 inverted dropout, 这种方法和传统的dropout方法有两点不同:
在训练层,对执行了dropout的层要除以keep_prob在测试阶段不执行任何的额外操作