第一章:神经网络理论知识
【第二章】Tensorflow
二、卷积神经网络
卷积神经网络主要用于图像的处理,卷积层通过滤波器捕捉图像中的“线段”,然后通过不同的线段组成一副可以识别的图片。和全链接神经网络相比,卷积神经网络主要多了卷积层和池化层(pool),卷积层和池化层都是通过滤波器对图片的特征进行捕捉,只不过卷积层通过滤波器做的是卷积计算,而池化层通过滤波器做的max-pooling或着average-pooling等计算。具体卷积神经网络,见我的第一章的文章。。。。这篇文章主要讲的通过Tensorflow怎么实现卷积神经网络,常见的卷积神经网络的模型有LeNet-5和Inception-v3,下面将通过代码仔细刨析这两个模型。
一、LeNet-5
LeNet-5是一个7层(输入层不计入总层数)的经典的卷积神经网络模型,最初来源于论文【Lecun Y,Bottou L,Bengio Y,et al. Gradient-based learning applied to document recognition[J].proceedings of the IEEE,1998】,其具体结构为:
输入层—>卷积层—>池化层—>卷积层—>池化层—>全连接层—>全连接层—>输出层。
LeNet-5的实现过程和(二,1)中的方法一样,通过前向传播、训练、验证,由三个文档组成一套完整的模型过程。所以和上一个全连接层神经网络相比,我们需要大幅修改的就是前向传播文档,因为神经网络的结构体现在前向传播过程中。
为了和全连接神经网络作比较,该模型使用的数据集仍然是MNIST_DATA数据集。
1.mnist_inference
第一部分,设置参数
# —*— coding: utf-8 -*-
import tensorflow as tf
INPUT_NODE = 784
OUTPUT_NODE = 10
IMAGE_SIZE = 28
# 通道数,因为mnist_data数据集是黑白图片,所以我们设置通道数为1,也就是每次使用1个滤波器对输入层进行扫面
NUM_CHANNELS = 1
NUM_LABELS = 10
# 第一层
# 卷积层的尺寸和深度。
CONV1_DEEP = 32
CONV1_SIZE = 5
# 第二层卷积层的尺寸和深度
CONV2_DEEP = 64
CONV2_SIZE = 5
#全连接层的节点个数
FC_SIZE = 512
# 定义卷积神经网络的前向传播过程。这里添加了一个新的参数train,用于区分训练过程和测试
# 过程。在这个程序中将用到dropout方法,dropout可以进一步提升模型可靠性并防止过拟合,
# dropout过程值在调试时使用
def inference(input_tensor,train,regularizer):
# 声明第一层卷积层的变量并实现前向传播过程。这个过程和6.3.1节中介绍的一致。
# 通过使用不同的命名空间来隔离不同层的变量,这可以让每一层中的变量命名只需要
# 考虑当前层的作用,而不需要担心重命名的问题。和标准LeNet-5模型不大一样,这里定义
# 卷积层输入为28x28x1的原始MNIST图片像素。因为卷积层中使用了全0填充,
# 所以输出为28x28x32的矩阵。
第一层:
第一层卷积神经网络,由于图片是黑白的所以只有一层,大小为28×28,滤波器使用的是5×5,相当于同时有32个5×5的单层滤波器对图片按步长为1进行进行扫描,由于使用了padding,所以最后得出了28×28×32的矩阵。
⌊
n
+
2
P
−
f
S
+
1
⌋
×
⌊
n
+
2
P
−
f
S
+
1
⌋
\lfloor\frac{n+2P-f}{S}+1\rfloor \times \lfloor\frac{n+2P-f}{S}+1\rfloor
⌊Sn+2P−f+1⌋×⌊Sn+2P−f+1⌋
滤波器为5时,padding设置为2,所以输出的大小为:
⌊
n
+
2
P
−
f
S
+
1
⌋
=
⌊
28
+
4
−
5
1
+
1
⌋
=
28
\lfloor\frac{n+2P-f}{S}+1\rfloor=\lfloor\frac{28+4-5}{1}+1\rfloor=28
⌊Sn+2P−f+1⌋=⌊128+4−5+1⌋=28
padding的设置
- 滤波器(卷积核\过滤器)大小:3, Padding大小: 1
- 滤波器(卷积核\过滤器)大小:5, Padding大小: 2
- 滤波器(卷积核\过滤器)大小:7, Padding大小: 3
- 池化层padding一般设置为0.
with tf.variable_scope('layer1-conv1'):
conv1_weights = tf.get_variable(
"weight",[CONV1_SIZE,CONV1_SIZE,NUM_CHANNELS,CONV1_DEEP],
initializer = tf.truncated_normal_initializer(stddev=0.1)
)
conv1_biases = tf.get_variable(
"biases",[CONV1_DEEP],initializer=tf.constant_initializer(0.0)
)
# 使用边长为5,深度为32的过滤器,过滤器移动的步长为1,且使用全0填充。
conv1 = tf.nn.conv2d(
input_tensor,conv1_weights,strides=[1,1,1,1],padding='SAME'
)
relu1 = tf.nn.relu(tf.nn.bias_add(conv1,conv1_biases))
第二层: 池化层,滤波器的大小为2×2,因为移动步长为2,所以输出矩阵的大小为
⌊
n
+
2
P
−
f
S
+
1
⌋
×
⌊
n
+
2
P
−
f
S
+
1
⌋
\lfloor\frac{n+2P-f}{S}+1\rfloor \times \lfloor\frac{n+2P-f}{S}+1\rfloor
⌊Sn+2P−f+1⌋×⌊Sn+2P−f+1⌋
n为图片的大小28,P为padding填充大小为1,f为滤波器的大小2,S为滤波器步长为2.
⌊
n
+
2
P
−
f
S
+
1
⌋
=
⌊
28
+
0
−
2
2
+
1
⌋
=
14
\lfloor\frac{n+2P-f}{S}+1\rfloor= \lfloor\frac{28+0-2}{2}+1\rfloor=14
⌊Sn+2P−f+1⌋=⌊228+0−2+1⌋=14
# 实现第二层池化层的前向传播过程,这里虚啊弄最大池化层,池化层过滤器的边长为2,
# 使用全0填充前移动的步长为2。这一层的输入是上一层的输出,也就是28x28x32的矩阵。
# 输出为14x14x32的矩阵。
with tf.name_scope('layer2-pool'):
pool1 = tf.nn.max_pool(relu1,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
第三层:
⌊ n + 2 P − f S + 1 ⌋ = ⌊ 14 + 4 − 2 1 + 1 ⌋ = 14 \lfloor\frac{n+2P-f}{S}+1\rfloor= \lfloor\frac{14+4-2}{1}+1\rfloor=14 ⌊Sn+2P−f+1⌋=⌊114+4−2+1⌋=14
# 声明第三次层卷积的变量并实现前向传播过程。这一层的输入为14x14x32的矩阵。
# 输出为14x14x64的矩阵。
with tf.variable_scope('layer3-conv2'):
conv2_weights = tf.get_variable(
'weight',[CONV2_SIZE,CONV2_SIZE,CONV1_DEEP,CONV2_DEEP],
initializer = tf.truncated_normal_initializer(stddev=0.1)
)
conv2_biases = tf.get_variable(
"bias",[CONV2_DEEP],
initializer = tf.constant_initializer(0.0)
)
# 使用边长为5,深度为64的过滤器,过滤器移动的步长为1,且使用全0填充。
conv2 = tf.nn.conv2d(
pool1,conv2_weights,strides=[1,1,1,1],padding='SAME'
)
relu2 = tf.nn.relu(tf.nn.bias_add(conv2,conv2_biases))
第四层:池化层 padding=0
⌊
n
+
2
P
−
f
S
+
1
⌋
=
⌊
14
+
0
−
2
2
+
1
⌋
=
7
\lfloor\frac{n+2P-f}{S}+1\rfloor= \lfloor\frac{14+0-2}{2}+1\rfloor=7
⌊Sn+2P−f+1⌋=⌊214+0−2+1⌋=7
# 实现第四层池化层的前向传播过程。这一层和第二层的结构是一样的。这一层的输入为14x14x64的矩阵,
# 输出为7x7x64的矩阵。
with tf.name_scope('layer4-pool2'):
pool2 = tf.nn.max_pool(
relu2,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME'
)
第五层:
全连接层的输入为上一层的输出,7×7×64的矩阵。
第四层的输出为7×7×64的矩阵,而第五层时全连接层,输入为一个向量,所以在第五层输入的时候,应该先将这个7×7×64的矩阵拉直成一个向量。这个向量的维度时3136(7×7×64).输出是一个FC_SIZE(512)大小的矩阵。正则化和dropout都加在全连接层。
# 将第四层池化层的输出转化为第5层全连接层的输入格式。第四层的输出为7x7x64的矩阵,
# 然而第五层全连接层需要输入格式为向量,所以在这里需要将这个7x7x64的矩阵拉直为
# 一个向量,pool2,get_shape函数可以得到第四层输出矩阵的维度而不需要手工计算。
# 注意因为每一层神经网络的输入输出都为一个batch的矩阵,所以这里得到的维度也包含
# 了一个batch中数据的个数。
pool_shape = pool2.get_shape().as_list()
# 计算将矩阵拉直成向量之后的长度,这个长度就是矩阵长宽及深度的乘积。注意这里pool_shape[0]
# 为一个batch中数据的个数。
nodes = pool_shape[1]*pool_shape[2] * pool_shape[3]
# 通过tf.reshape 函数将第四层的输出变成一个batch的向量。
reshaped = tf.reshape(pool2,[pool_shape[0],nodes])
# 声明第五层全链接层的变量并实现前向传播过程。这一层的输入是拉直hi后的一组向量,向量长度为3136
# 输出是一组长度为512的向量。这一层和之前在第5章中介绍的基本一致,唯一的区别就是引入了dropout
# 的概念。dropout在训练时会随机将部分节点的输出改为0。dropout可以避免过拟合问题,从而使得模型
# 在测试数据上的效果更好。dropout一般只在全链接层而不是卷积层或者池化层使用。
with tf.variable_scope('layer5-fcl'):
fcl_weights = tf.get_variable(
"weight",[nodes,FC_SIZE],
initializer= tf.truncated_normal_initializer(stddev=0.1)
)
# 只有全连接层的却暗中需要加入正则化
if regularizer is not None:
tf.add_to_collection('losses',regularizer(fcl_weights))
fc1_biases = tf.get_variable(
"bias",[FC_SIZE],initializer=tf.constant_initializer(0.1)
)
fc1 = tf.nn.relu(tf.matmul(reshaped,fcl_weights)+fc1_biases)
if train: fc1 = tf.nn.dropout(fc1,0.5)
第六层:
输入512,输出10
# 声明第六层全链接层的变量并实现前向传播过程。这一层的输出为一组长度为512的向量,
# 输出为一组长度为10的向量。这一成的输出通过softmax之后就得到了最后的分类结果。
with tf.variable_scope('layer6-fc2'):
fc2_weights = tf.get_variable(
"weight",[FC_SIZE,NUM_LABELS],
initializer=tf.truncated_normal_initializer(stddev=0.1)
)
if regularizer is not None:
tf.add_to_collection('losses',regularizer(fc2_weights))
fc2_biases = tf.get_variable(
"bias",[NUM_LABELS],
initializer=tf.constant_initializer(0.1)
)
logit = tf.matmul(fc1,fc2_weights)+fc2_biases
# 返回第六层的输出
return logit
上述代码的前向传播过程仍然没有softmax层,和之前一样,softmax层放到最后的train过程当中。
下面给出mnist_train.py 和mnist_eval.py 的代码
2.mnist_train
mnist_train ,大部分代码和上一节中的单层神经网络的train文件,是一致的,需要修改的就是在输入部分。
#-*- coding:utf-8 -*-
import os
import numpy as np
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
# 加载mnist_inference.py中的定义的常量和前向传播的函数。
import LeNet5_mnist_inference
# 配置神经网络的参数
BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.01
LEARNING_RATE_DECAY = 0.99
REGULARAZTION_RATE = 0.0001
TRAINING_STEPS = 30000
MOVING_AEVERAGE_DECAY = 0.99
# 模型保存的路径和文件名
MODEL_SAVE_PATH = "Google/chapter5/model/"
MODEL_NAME = "model.ckpy"
def train(mnist):
# 定义输入输出placeholder
x = tf.placeholder(
tf.float32, [
BATCH_SIZE,
LeNet5_mnist_inference.IMAGE_SIZE,
LeNet5_mnist_inference.IMAGE_SIZE,
LeNet5_mnist_inference.NUM_CHANNELS],
name = "x-input"
)
y_ = tf.placeholder(
tf.float32,[None, LeNet5_mnist_inference.OUTPUT_NODE],name='y-input'
)
regularizer = tf.contrib.layers.l2_regularizer(REGULARAZTION_RATE)
#直接使用mnist_inference.py中定义的前向传播过程。
y = LeNet5_mnist_inference.inference(x, False, regularizer)
global_step = tf.Variable(0,trainable=False)
# 和5.2.1节样例中类似地定义损失函数、学习率、滑动平均操作以及训练过程。
variable_averages = tf.train.ExponentialMovingAverage(
MOVING_AEVERAGE_DECAY,global_step
)
variable_averages_op = variable_averages.apply(
tf.trainable_variables()
)
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
logits=y,labels= tf.argmax(y_,1)
)
cross_entropy_mean = tf.reduce_mean(cross_entropy)
loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))
learning_rate = tf.train.exponential_decay(
LEARNING_RATE_BASE,
global_step,
mnist.train.num_examples/BATCH_SIZE,
LEARNING_RATE_DECAY
)
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss,global_step=global_step)
with tf.control_dependencies([train_step,variable_averages_op]):
train_op = tf.no_op(name='train')
# 初始化Tensorflow持久化类。
saver = tf.train.Saver()
with tf.Session() as sess:
tf.global_variables_initializer().run()
# 在训练过程中不再测试模型在验证数据上的表现,验证和测试的过程将会有一个独立的程序来完成。
for i in range(TRAINING_STEPS):
xs,ys = mnist.train.next_batch(BATCH_SIZE)
reshaped_xs = np.reshape(xs, (BATCH_SIZE,
LeNet5_mnist_inference.IMAGE_SIZE,
LeNet5_mnist_inference.IMAGE_SIZE,
LeNet5_mnist_inference.NUM_CHANNELS))
_, loss_value,step=sess.run([train_op,loss,global_step],feed_dict={x:reshaped_xs,y_:ys})
# 每1000轮保存一次模型。
if i%1000 == 0:
'''
输出当前的训练情况。这里只输出了模型在当前训练batch上的损失函数大小。通过损失函数的大小可以
大概链接训练的情况。在验证数据集上的正确率信息会有一个单独的程序来生成。
'''
print("After %d training step(s),loss on training"
"batch is %g."%(step,loss_value))
'''
保存当前的模型。这一这里给吃了global_step参数,这样可以让每一被保存模型的文件名末尾
加上训练的论数,比如“model.ckpy-1000”表示训练1000轮之后得到的模型
'''
saver.save(
sess,os.path.join(MODEL_SAVE_PATH,MODEL_NAME),
global_step = global_step
)
def main(argv=None):
mnist = input_data.read_data_sets("Google/chapter5/MNIST_data", one_hot=True)
train(mnist)
if __name__ == '__main__':
tf.app.run()
3.mnist_eval.py
# -*- coding:utf-8 -*-
import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import numpy as np
# 加载mnist_inference.py 和mnist_train.py中定义的常量和函数
import LeNet5_mnist_inference
import LeNet5_mnist_train
#每10秒加载一次最新的模型,并在测试数据上测试最新模型的正确率
EVAL_INTERVAL_SECS = 10
BATCH_SIZE = 100
def evaluate(mnist):
with tf.Graph().as_default() as g:
# 定义输入输出的格式
x = tf.placeholder(
tf.float32, [
mnist.validation.images.shape[0],
LeNet5_mnist_inference.IMAGE_SIZE,
LeNet5_mnist_inference.IMAGE_SIZE,
LeNet5_mnist_inference.NUM_CHANNELS],
name="x-input"
)
y_ = tf.placeholder(tf.float32, [None, LeNet5_mnist_inference.OUTPUT_NODE], name='y-input')
xs, ys = mnist.validation.images, mnist.validation.labels
reshape_xs = np.reshape(xs, (-1,
LeNet5_mnist_inference.IMAGE_SIZE,
LeNet5_mnist_inference.IMAGE_SIZE,
LeNet5_mnist_inference.NUM_CHANNELS))
validate_feed = {x:reshape_xs,
y_:ys} #mnist.validation.images 维度为[5000,784]#
'''
直接通过调用封装好的函数来计算前向传播的结果。因为测试时
不关注正则化损失的值,所以这里用于计算正则化损失函数被
设置为None
'''
y = LeNet5_mnist_inference.inference(x, None, None)
'''
使用前向传播的结果计算正确率。如果需要对未知的样例进行分类,
那么使用tf.argmax(y,1)就可以得到输入样例的预测类别了。
'''
correct_prediction = tf.equal(tf.argmax(y,1),tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))# tf.cast功能为转化数据格式
'''
通过变量重命名的方式来加载模型,这样在前向创博的过程中就不需要
调用求滑动平均的函数来获取平均值了。这样就可以完全公用mnist_inference.py
中定义的前向传播过程。
'''
variable_averages = tf.train.ExponentialMovingAverage(
LeNet5_mnist_train.MOVING_AEVERAGE_DECAY
)
variable_to_restore = variable_averages.variables_to_restore()
saver = tf.train.Saver(variable_to_restore)
'''
每隔EVAL_INTERVAL_SECS秒调用一次计算正确率的过程以检验训练过程中正确率的变化。
'''
while True:
with tf.Session() as sess:
# tf.train.get_checkpoint_state函数会通过checkpoint文件自动找到
# 目录中最新模型的文件名。
ckpt = tf.train.get_checkpoint_state(
LeNet5_mnist_train.MODEL_SAVE_PATH
)
if ckpt and ckpt.model_checkpoint_path:
# 加载模型。
saver.restore(sess,ckpt.model_checkpoint_path)
# 通过文件名得到模型保存时迭代的轮数。
global_step = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1]
accuracy_score = sess.run(accuracy,feed_dict=validate_feed)
print("After %s training step(s),validation"
"accuracy = %g"% (global_step,accuracy_score))
else:
print('No checkpoint file found')
return
time.sleep(EVAL_INTERVAL_SECS)
def main(argv=None):
mnist=input_data.read_data_sets("Google/chapter5/MNIST_data", one_hot=True)
evaluate(mnist)
if __name__ == '__main__':
tf.app.run()
二、Inception-v3模型
在上述代码中,我们发现滤波器有3×3的,5×5的,也有7×7的,那么究竟滤波器选择多大的呢,inception模块给出了一个方案,那就是同时使用不同尺寸的过滤器,然后再将得到的矩阵拼接起来。虽然过滤器的大小不同,但如果所有过滤器都使用全0填充,且步长为1,那么前向传播得到的结果矩阵长和宽都与输入矩阵一致。其中醉经的模型是inceptionv-3,该模型总共有46层,由11个inception模块组成,其具体的构成参见上一章神经网络基础知识,这是一个深度神经网络,如果我们从头开始下训练这个模型,那么这将是一个费事费力的过程,所以在这里我们通过迁移学习来实现inception-v3的使用,即从网络中下载好Google以训练好的数据,我们只去修改它的输出层,从而实现对inception-v3模型的利用。
这节内容我们使用的数据flower_photos数据集,这个数据集一共有5个子文件夹,每一个文件夹分别为一种花的名称,平均每一种花有734章图片,每一张图片为不同大小的RGB的彩色图片。
首先下载数组
wget http://download.tensorflow.org/example_images/flower_photos.tgz
tar xzf flower_photos.tgz
首先,我们将图片转换为模型需要的输入数据。
1.datatranfer.py
继续解剖代码:
第一部分,导入模块
# -*- coding:utf-8 -*-
import glob
import os.path
import tensorflow as tf
import numpy as np
from tensorflow.python.platform import gfile
第二部分,设置参数
参数包括需要导入数据的位置,输出数据的位置、测试集和验证集所占数据的比例。
#原始输入数据的目录,这个目录下有5个子目录,每个子目录地下保存属于该类的所有图片。
INPUT_DATA= 'flower/flower_photos'
# 输出文件地址。将整理后的图片数据通过numpy的格式保存。在第7章周昂将更加详细地介绍数据预处理,
# 这里先通过numpy来保存
OUTPUT_FILE = 'flower/flower_processed_data.npy'
# 测试数据和验证数据比例
VALIDATION_PERCENTAGE = 10
TEST_PERCFNTAGE = 10
第三部分,数据转换函数
1.将文件夹中所有图片的名称放入一个列表当中。
# 读取数据并将数据分割成训练数据,验证数据和测试数据
def create_image_lists(sess,testing_percentage,validation_percentage):
sub_dirs = [x[0] for x in os.walk(INPUT_DATA)]
is_root_dir = True
print(sub_dirs)
# 初始化各个数据集。
training_image = []
training_labels = []
testing_image = []
testing_labels = []
validation_images = []
validation_labels = []
current_labels = 0
# 读取所有的子目录
for sub_dir in sub_dirs:
if is_root_dir:
is_root_dir = False
continue
# 读取一个子目录中所有图片的文件。
extensions = ['jpg','jpeg','JPEG']
file_list =[]
dir_name = os.path.basename(sub_dir)
for extension in extensions:
file_glob = os.path.join(INPUT_DATA,dir_name,'*.'+extension)
file_list.extend(glob.glob(file_glob))
if not file_list: continue
print('processing:',dir_name)
i=0
2.遍历列表中的每一个元素
转换图片的类型。
#处理图片数据。
for file_name in file_list:
i += 1
# 读取并解析图片,将图片转化为299x299以便inception_v3模型来处理。
# 更多关于图片预处理的内容将在第7章中介绍。
image_raw_data = gfile.FastGFile(file_name,'rb').read()
image = tf.image.decode_jpeg(image_raw_data)
if image.dtype != tf.float32:
image = tf.image.convert_image_dtype(
image,dtype = tf.float32
)
image = tf.image.resize_images(image,[299,299])
image_value = sess.run(image)
3,划分数据集
将转换好格式的数据,按比例随机分配给测试集,验证集和训练集,比例为1:1:8
# 随机划分数据集。
chance = np.random.randint(100)
if chance < validation_percentage:
validation_images.append(image_value)
validation_labels.append(current_labels)
elif chance < (testing_percentage + validation_percentage):
testing_image.append(image_value)
testing_labels.append(current_labels)
else:
training_image.append(image_value)
training_labels.append(current_labels)
if i % 200 == 0:
print(i,"images processed.")
current_labels += 1
4.随机打乱数据集
# 将训练数据随机打乱以获得更好的训练效果。
state = np.random.get_state()
np.random.shuffle(training_image)
np.random.set_state(state)
np.random.shuffle(training_labels)
return np.asarray([training_image,training_labels,
validation_images,validation_labels,
testing_image,testing_labels])
5,主程序入口
# 数据整理主函数-
def main():
with tf.Session() as sess:
processed_data = create_image_lists(
sess, TEST_PERCFNTAGE,VALIDATION_PERCENTAGE)
# 通过numpy格式保存处理后的。
np.save(OUTPUT_FILE,processed_data)
if __name__ == '__main__':
main()
然后进行迁移学习,首先可以谷歌提供的训练好的inception_v3模型
wget http://download.tensorflow.org/models/inception_v3_2016_08_28.tar.gz
# 解压之后可以得到训练好的inception_v3.ckpt文件。
2.transferlearing.py
第一部分,导入模块
# -*- coding: utf-8 -*-
import glob
import os.path
import numpy as np
import tensorflow as tf
from tensorflow.python.platform import gfile
import tensorflow.contrib.slim as slim
#加载通过Tehnsorflow-Slim
#
# 定义好的inception_v3mox。
import tensorflow.contrib.slim.python.slim.nets.inception_v3 as inception_v3
#处理好之后的数据文件。
INPUT_DATA = 'flower/flower_processed_data.npy'
#保存训练好的模型的路径,这里可以将使用新数据训练得到的完整模型保存下来,
# 如果计算资源充足,还可以在寻来你完最后的全链接层之后再训练所有网络层,这样可以使得新模型更加贴近新数据。
TRAIN_FILE = 'save_model'
#谷歌提供的训练好的模型文件地址
CKPT_FILE = 'inception_v3.ckpt'
# 定义训练中使用的参数
LEARNING_RATE = 0.0001
STEPS = 300
BATCH = 32
N_CLASSES = 5
#不需要从谷歌训练好的模型中加载的参数。这里就是最后的全连接层,因为再先得问题中要重新训练这一层中得参数。这里给出的是参数得前缀。
CHECKPOINT_EXCLUDE_SCOPES = 'inceptionV3/Logits,InceptionV3/AuxLogits'
#需要训练的网络层参数名称,再fine-tuning的过程中就是最后的全连接层。这里给出的是参数的前缀
TRAINABLE_SCOPES = 'InceptionV3/Logits,InceptionV3/AuxLogits'
# 获取所有需要从谷歌训练好的模型中加载的参数
def get_tuned_variables():
exclusions = [scope.strip() for scope in CHECKPOINT_EXCLUDE_SCOPES.split(',')]
variables_to_restore = []
# 枚举inception-v3模型中所有的参数,然后判断是否需要从加载列表中移除。
for var in slim.get_model_variables():
excluded = False
for exclusion in exclusions:
if var.op.name.startswith(exclusion):
exluded = True
break
if not excluded:
variables_to_restore.append(var)
return variables_to_restore
# 获取所有需要训练的变量列表
def get_trainable_variable():
scopes = [scope.strip() for scope in TRAINABLE_SCOPES.split(',')]
variables_to_train = []
#枚举所有需要训练参数前缀,并通过这些前缀找到所有的参数。
for scope in scopes:
variables = tf.get_collection(
tf.GraphKeys.TRAINABLE_VARIABLES,scope
)
variables_to_train.extend(variables)
return variables_to_train
def main(self):
# 加载预处理好的数据。
processed_data = np.load(INPUT_DATA)
training_images = processed_data[0]
n_training_example = len(training_images)
training_labels = processed_data[1]
validation_images = processed_data[2]
validation_labels = processed_data[3]
testing_images = processed_data[4]
testing_labels = processed_data[5]
print("%d training examples,%d validation examples and %d"
"testing examples." %(
n_training_example,len(validation_labels),len(testing_labels)
))
# 定义inception-v3的输入,images为输入图片,labels为每一张图片对应的标签。
images = tf.placeholder(
tf.float32,[None,299,299,3],
name = 'input_images'
)
labels = tf.placeholder(tf.int64,[None],name='labels')
'''
定义inception-v3模型。因为谷歌给出的只有模型参数取值,所以这里需要在这个代码中定义inception-v3
的模型结构。虽然理论上需要区分训练和测试中使用的模型,也就是说在测试时应该使用is_training = False
但是因为预先训练好的inception-v3模型总使用的batch normalization参数与新的数据会有差异,导数结果很差
所以这里直接使用同一个模型来进行测试。
'''
with slim.arg_scope(inception_v3.inception_v3_arg_scope()):
logits, _ =inception_v3.inception_v3(
images,num_classes=N_CLASSES
)
# 获取需要训练的变量。
trainable_variables = get_trainable_variable()
# 定义交叉熵损失。注意在模型定义的时候以及将正则化损失加入损失集合了。
tf.losses.softmax_cross_entropy(
tf.one_hot(labels,N_CLASSES),logits,weights=1.0
)
# 定义训练过程。这里minmize的过程中指定了需要优化的变量集合。
train_step = tf.train.RMSPropOptimizer(LEARNING_RATE).minmize(
tf.losses.get_total_loss()
)
# 计算正确率。
with tf.name_scope('evaluation'):
correct_prediction = tf.equal(tf.argmax(logits,1),labels)
evaluation_step = tf.reduce_mean(tf.cast(
correct_prediction,tf.float32
))
# 定义加载模型的函数
load_fn = slim.assign_from_checkpoint_fn(
CKPT_FILE,
get_tuned_variables(),
ignore_missing_vars=True
)
# 定义保存新的训练好的模型的函数。
saver = tf.train.Saver()
with tf.Session() as sess:
# 初始化没有加载进来的变量。注意这个过程一定要在模型加载之前,否则初始化过程会将已经加载好的变量重新赋值。
init = tf.global_variables_initializer()
sess.run(init)
# 加载谷歌已经训练好的模型。
print('Loading tuned variables from %s' % CKPT_FILE)
load_fn(sess)
start = 0
end = BATCH
for i in range(STEPS):
# 运行训练过程,这里不会更新全部的参数,只会更新指定的部分参数。
sess.run(train_step,feed_dict={
images:training_images[start:end],
labels:training_labels[start:end]
})
# 输出日志
if i % 30 == 0 or i + 1 == STEPS:
saver.save(sess,TRAIN_FILE,global_step=i)
validation_accuracy = sess.run(evaluation_step,feed_dict={
images:validation_images, labels:validation_labels
})
print('Step %d: Validation accuracy = %.lf%%'
%(i,validation_accuracy * 100.0))
# 因为子数据预处理的时候已经做过了打乱数据的操作,所以这里只需要顺序
# 使用训练数据就好。
start = end
if start == n_training_example:
start = 0
end = start + BATCH
if end > n_training_example:
end = n_training_example
# 在最后的测试数据熵测试正确率
test_accuracy = sess.run(evaluation_step,feed_dict={
images: testing_images,labels:testing_labels
})
print('Final test accuracy = %.lf%%' %(test_accuracy*100))
if __name__ == '__main__':
tf.app.run()