一 Cifar10数据集介绍
该数据集共有60000张彩色图像,这些图像是32*32,分为10个类,每类6000张图。这里面有50000张用于训练,构成了5个训练批,每一批10000张图;另外10000用于测试,单独构成一批。测试批的数据里,取自10类中的每一类,每一类随机取1000张。抽剩下的就随机排列组成了训练批。注意一个训练批中的各类图像并不一定数量相同,总的来看训练批,每一类都有5000张图。
下面这幅图就是列举了10各类,每一类展示了随机的10张图片:
该数据集共有三个版本:Python,Matlab,bin version。
我们使用的是二进制版本,下载地址:CIFAR-10 and CIFAR-100 datasets(在程序代码中会自动下载)
该版本包含5个训练批data_batch_1.bin, data_batch_2.bin, ..., data_batch_5.bin,1个测试批test_batch.bin。他们的格式都是:
<1 x label><3072 x pixel>
...
<1 x label><3072 x pixel>
也就是说,第一个字节是指类标,在0-9之间。接下来就是3072个字节,每个文件都有10000这样的3073个字节,没有任何分隔行,所以每个文件都是30730000字节的长度。
二 代码实现
将训练程序和测试程序分开,先讲下训练程序。
def main(argv=None):
cifar10.maybe_download_and_extract() #下载数据 定义在cifar10.py文件中
if gfile.Exists(FLAGS.train_dir): #判断目录存不存在
gfile.DeleteRecursively(FLAGS.train_dir) #递归删除所有目录及其文件,dirname即目录名,无返回。
gfile.MakeDirs(FLAGS.train_dir) #创建一个目录,dirname为目录名字,无返回。
train() #训练函数
if __name__ == '__main__':
tf.app.run()
关于tf.app.run() tensorflow文档中的解释如下:
模块:tf.app
定义在:tensorflow/python/platform/app.py
通用入口点脚本。
flags 模块
flags 模块:实现标志接口。
函数
run(...):使用可选的 “main” 函数和 “argv” 列表运行程序。
tf.app.run
run (
main = None ,
argv = None
)
定义在:tensorflow/python/platform/app.py
使用可选的 “main” 函数和 “argv” 列表运行程序。
相当于一个程序入口,其中FLAGS可以定义一些程序用到的参数,在我们训练中,需要定义的参数如下:
FLAGS = tf.app.flags.FLAGS
#添加命令行对的可选参数
#训练时存放事件文件的路径
tf.app.flags.DEFINE_string('train_dir', 'cifar10_train/',
"""Directory where to write event logs """
"""and checkpoint.""")
#最大训练
tf.app.flags.DEFINE_integer('max_steps', 20000,
"""Number of batches to run.""")
tf.app.flags.DEFINE_boolean('log_device_placement', False,
"""Whether to log device placement.""")
接下来我们来看下载数据得函数: cifar10.maybe_download_and_extract()
def maybe_download_and_extract():
"""Download and extract the tarball from Alex's website."""
dest_directory = FLAGS.data_dir
if not os.path.exists(dest_directory):
os.mkdir(dest_directory)
filename = DATA_URL.split('/')[-1]
filepath = os.path.join(dest_directory, filename)
if not os.path.exists(filepath):
def _progress(count, block_size, total_size):
sys.stdout.write('\r>> Downloading %s %.1f%%' % (filename,
float(count * block_size) / float(total_size) * 100.0))
sys.stdout.flush()
filepath, _ = urllib.request.urlretrieve(DATA_URL, filepath,
reporthook=_progress)
print()
statinfo = os.stat(filepath)
print('Successfully downloaded', filename, statinfo.st_size, 'bytes.')
tarfile.open(filepath, 'r:gz').extractall(dest_directory)
下载数据且解压后,我们看到在我们的项目目录下已经可以看到下载好的数据。
进入train()函数:
def train():
"""Train CIFAR-10 for a number of steps."""
#在默认图上进行操作
with tf.Graph().as_default():
#全局步骤
global_step = tf.Variable(0, trainable=False)
# Get images and labels for CIFAR-10.
#调用输入函数得到数据和标签
images, labels = cifar10.distorted_inputs()
首先调用cifar10.distorted_inputs()
def distorted_inputs():
#该函数用于从文件中读取数据预处理
#图像数据一个四维张量 包括批处理大小 宽 高 信道数
#标签数据 一个一维张量 批处理大小
"""Construct distorted input for CIFAR training using the Reader ops.
Returns:
images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size.
labels: Labels. 1D tensor of [batch_size] size.
Raises:
ValueError: If no data_dir
"""
#如果数据路径不存在,将会抛异常
if not FLAGS.data_dir:
raise ValueError('Please supply a data_dir')
data_dir = os.path.join(FLAGS.data_dir, 'cifar-10-batches-bin')
#调用 cifar10_input.distorted_inputs函数进行数据导入
return cifar10_input.distorted_inputs(data_dir=data_dir,
batch_size=FLAGS.batch_size)
cifar10_input.distorted_inputs函数
def distorted_inputs(data_dir, batch_size):
#合并数据目录
filenames = [os.path.join(data_dir, 'data_batch_%d.bin' % i)
for i in xrange(1, 6)]
#判断目录是否存在
for f in filenames:
if not gfile.Exists(f):
raise ValueError('Failed to find file: ' + f)
#生成一个文件队列对象
# Create a queue that produces the filenames to read.
filename_queue = tf.train.string_input_producer(filenames)
# Read examples from files in the filename queue.
#对图片格式进行转换为float32
read_input = read_cifar10(filename_queue)
#得到的图像张量转为float32格式
reshaped_image = tf.cast(read_input.uint8image, tf.float32)
height = IMAGE_SIZE
width = IMAGE_SIZE
#接下来就是对图像进行一些扭曲操作,来增强数据。
#对图像进行随机裁剪
distorted_image = tf.random_crop(reshaped_image, [height, width,3])
#对图像进行左右翻转
distorted_image = tf.image.random_flip_left_right(distorted_image)
#对图像进行亮度变化
distorted_image = tf.image.random_brightness(distorted_image,
max_delta=63)
# 对图像进行对比度变化
distorted_image = tf.image.random_contrast(distorted_image,
lower=0.2, upper=1.8)
#对图像归一化操作也叫做白化
float_image = tf.image.per_image_standardization(distorted_image)
# Ensure that the random shuffling has good mixing properties.
#确保随机洗牌具有好的混合性能
#随机从文件队列取出的数据数量
min_fraction_of_examples_in_queue = 0.4
min_queue_examples = int(NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN *
min_fraction_of_examples_in_queue)
print ('Filling queue with %d CIFAR images before starting to train. '
'This will take a few minutes.' % min_queue_examples)
# Generate a batch of images and labels by building up a queue of examples.
#通过建立一个示例队列来生成一批图像和标签。
#返回
return _generate_image_and_label_batch(float_image, read_input.label,
min_queue_examples, batch_size)
我们先讲下这个函中一些API。
os.path.join
os.path.join()函数用于路径拼接文件路径。
os.path.join()函数中可以传入多个路径
filenames = [os.path.join(data_dir, 'data_batch_%d.bin' % i)
for i in xrange(1, 6)]
相当于将五个测试文件目录放到组成一个列表,路径是我电脑中的路径
filenames = [E:\tensorflow.cifar10-master\tensorflow.cifar10-master\cifar10_data\cifar-10-batches-bin\data_batch_1.bin,...,E:\tensorflow.cifar10-master\tensorflow.cifar10-master\cifar10_data\cifar-10-batches-bin\data_batch_5.bin]这样一个列表
从文件导入
从文件导入记录的典型管道有以下几个阶段:
文件名列表
可选文件名洗牌
可选时期限制
文件名队列
用于文件格式的读取器
读者用于读取记录的解码器
可选预处理
示例队列
文件名,shuffling 和 epoch 限制
对于文件名列表,请使用常量字符串张量(如["file0", "file1"]或[("file%d" % i) for i in range(2)])或函数:tf.train.match_filenames_once。
将文件名列表传递给 tf.train.string_input_producer 函数。string_input_producer 创建一个 FIFO 队列,用于保存文件名,直到读取器需要它们为止。
string_input_producer 有选择的 shuffling 和设置一个最大的 epoch 数。队列运行程序为每个 epoch 将文件名的整个列表添加到队列中一次,如果洗牌 = True,则在一个 epoch 中重新排列文件名。此过程提供了一个统一的文件取样,以便相对于彼此不会对示例进行低估或过度采样。
队列运行程序在与从队列中抽取文件名的读取器分开的线程中工作,因此,shuffling 和 enqueuing 进程不会阻止读取器。
文件格式
选择与您的输入文件格式相匹配的读取器,并将文件名队列传递给读取器的读取方法。read 方法输出一个标识文件和记录的密钥 (如果有一些奇怪的记录,则对调试有用) 和一个标量字符串值。使用一个 (或多个) 解码器和转换 ops 将此字符串解码为构成示例的张量。
filename_queue = tf.train.string_input_producer(filenames)
tf.train.string_input_producer
创建一个文件处理队列
接下来调用一个函数read_input = read_cifar10(filename_queue)
def read_cifar10(filename_queue):
#创建一个类,用于返回
class CIFAR10Record(object):
pass
#创建一个类对象,用于返回
result = CIFAR10Record()
#标签字节数
label_bytes = 1 # 2 for CIFAR-100
#图像大小 高 宽 信道
result.height = 32
result.width = 32
result.depth = 3
#一张图片字节数
image_bytes = result.height * result.width * result.depth
#一次读文件需要读取的字节数 包括图像和标签
record_bytes = label_bytes + image_bytes
#创建一个阅读器reader,读取的长度为record_bytes
reader = tf.FixedLengthRecordReader(record_bytes=record_bytes)
#从文件队列中,读取返回图像和标签。
result.key, value = reader.read(filename_queue)
#将图像和标签从字符串格式转化为uint8的张量
record_bytes = tf.decode_raw(value, tf.uint8)
#slice切片操作,第一个字节是label,[0]表示从第一维第一个位置开始,label_bytes代表大小
#tf.int32为转化的格式
result.label = tf.cast(
tf.slice(record_bytes, [0], [label_bytes]), tf.int32)
#slice继续将剩下的字节切成图像
#然后将其reshape变成深 高 宽
depth_major = tf.reshape(tf.slice(record_bytes, [label_bytes], [image_bytes]),
[result.depth, result.height, result.width])
# Convert from [depth, height, width] to [height, width, depth].
#利用transpose改变维度的位置 将深 高 宽 变为 高 宽 深
result.uint8image = tf.transpose(depth_major, [1, 2, 0])
返回我们得到的图像和标签结果。
return result
来看_generate_image_and_label_batch(float_image, read_input.label, min_queue_examples, batch_size)函数。这个函数建立一批图像和标签。
def _generate_image_and_label_batch(image, label, min_queue_examples,
batch_size):
#构造一批图像和标签的队列
"""Construct a queued batch of images and labels.
Args:
image: 3-D Tensor of [height, width, 3] of type.float32.
label: 1-D Tensor of type.int32
#最小的保留样本
min_queue_examples: int32, minimum number of samples to retain
in the queue that provides of batches of examples.
#每一批数据的大小
batch_size: Number of images per batch.
Returns:
images: Images. 4D tensor of [batch_size, height, width, 3] size.
labels: Labels. 1D tensor of [batch_size] size.
"""
# Create a queue that shuffles the examples, and then
# read 'batch_size' images + labels from the example queue.
#创建一个队列,该队列将这些示例进行洗牌,然后读取“batchsize”图像+来自示例队列的标签。
#线程数量=16
num_preprocess_threads = 16
#将队列中的数据洗牌在读取,默认返回一个读取tensor_list数据类型和一个tensor列表.
images, label_batch = tf.train.shuffle_batch(
[image, label], #入队的张量列表
batch_size=batch_size, #批大小
num_threads=num_preprocess_threads, #设置num_threads的值大于1,使用多个线程在
#tensor_list中读取文件,这样保证了同一时刻只在一
#个文件中进行读取操作(但是读取速度依然优于单线
#程),而不是之前的同时读取多个文件。
capacity=min_queue_examples + 3 * batch_size, #队列容量
min_after_dequeue=min_queue_examples) #最小保留在队列中元素个数
# Display the training images in the visualizer.
#展示训练过程中的图像。
tf.summary.image('images', images)
#将形成批次的图像和标签返回
return images, tf.reshape(label_batch, [batch_size])
经过如上步骤,我们图像输入处理就准备完了,接下来我们来看下train()函数剩余的代码。
logits = cifar10.inference(images),inference函数用来构建神经网络结构。
def inference(images):
"""Build the CIFAR-10 model.
#输入我们获取的批处理图的张量
Args:
images: Images returned from distorted_inputs() or inputs().
#返回神经网络最后的输出结果
Returns:
Logits.
"""
# We instantiate all variables using tf.get_variable() instead of
# tf.Variable() in order to share variables across multiple GPU training runs.
# If we only ran this model on a single GPU, we could simplify this function
# by replacing all instances of tf.get_variable() with tf.Variable().
#
# conv1
#第一层卷积层
#创建变量范围,所有在该范围定义的变量
with tf.variable_scope('conv1') as scope:
#生成卷积核
kernel = _variable_with_weight_decay('weights', shape=[5, 5, 3, 64],
stddev=1e-4, wd=0.0)
#卷积操作
conv = tf.nn.conv2d(images, kernel, [1, 1, 1, 1], padding='SAME')
#偏置初始化
biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.0))
#加上偏置
bias = tf.nn.bias_add(conv, biases)
#激活函数选用Relu
conv1 = tf.nn.relu(bias, name=scope.name)
_activation_summary(conv1)
#第一个池化层
# pool1
pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
padding='SAME', name='pool1')
# norm1
#局部响应归一化,主要用来防止过拟合,一般用在激活函数之后
norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75,
name='norm1')
# conv2
with tf.variable_scope('conv2') as scope:
kernel = _variable_with_weight_decay('weights', shape=[5, 5, 64, 64],
stddev=1e-4, wd=0.0)
conv = tf.nn.conv2d(norm1, kernel, [1, 1, 1, 1], padding='SAME')
biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.1))
bias = tf.nn.bias_add(conv, biases)
conv2 = tf.nn.relu(bias, name=scope.name)
_activation_summary(conv2)
# norm2
norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75,
name='norm2')
# pool2
pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1],
strides=[1, 2, 2, 1], padding='SAME', name='pool2')
# local3
with tf.variable_scope('local3') as scope:
# Move everything into depth so we can perform a single matrix multiply.
dim = 1
#pool2的维度为 batch_size,height,weight,depth.所以从第一个维度也即height计算相乘。
for d in pool2.get_shape()[1:].as_list():
dim *= d
#将其维度改变
reshape = tf.reshape(pool2, [FLAGS.batch_size, dim])
weights = _variable_with_weight_decay('weights', shape=[dim, 384],
stddev=0.04, wd=0.004)
biases = _variable_on_cpu('biases', [384], tf.constant_initializer(0.1))
#矩阵乘法 (B_size x dim) * (dim X 384) = B_size X 384
local3 = tf.nn.relu(tf.matmul(reshape, weights) + biases, name=scope.name)
_activation_summary(local3)
# local4
# local3 = B_size X 384
with tf.variable_scope('local4') as scope:
weights = _variable_with_weight_decay('weights', shape=[384, 192],
stddev=0.04, wd=0.004)
biases = _variable_on_cpu('biases', [192], tf.constant_initializer(0.1))
#矩阵乘法 (B_size X 384) * (384 X 192) = B_szie X 192
local4 = tf.nn.relu(tf.matmul(local3, weights) + biases, name=scope.name)
_activation_summary(local4)
# softmax, i.e. softmax(WX + b)
# local4 = B_szie X 192
with tf.variable_scope('softmax_linear') as scope:
weights = _variable_with_weight_decay('weights', [192, NUM_CLASSES],
stddev=1/192.0, wd=0.0)
biases = _variable_on_cpu('biases', [NUM_CLASSES],
tf.constant_initializer(0.0))
#矩阵乘法 (B_size X 192) * (192 X NUM_CLASSES=10) = B_size X NUM_CLASSES
softmax_linear = tf.add(tf.matmul(local4, weights), biases, name=scope.name)
_activation_summary(softmax_linear)
#将最后结果返回
return softmax_linear
在inference函数中,有一些函数我们需要解释下。
def _variable_with_weight_decay(name, shape, stddev, wd):
#参数 name 名字
#shape 张量维度
#stddev 标准差
#wd 用来指定是否权重衰减,也就是后面L2正则化。
#截取的正太分布
var = _variable_on_cpu(name, shape,
tf.truncated_normal_initializer(stddev=stddev))
#如果使用权重衰减,则使用L2正则化。
if wd:
#tf.nn.l2_loss(var) 利用L2范数来计算张量的误差值。
weight_decay = tf.multiply(tf.nn.l2_loss(var), wd, name='weight_loss')
#向计算图中添加张量集合。
tf.add_to_collection('losses', weight_decay)
#返回得到的权重weight
return var
在上面那个函数中我们用到了_variable_on_cpu函数。
def _variable_on_cpu(name, shape, initializer):
#创建一个存储在Cpu上的变量
"""Helper to create a Variable stored on CPU memory.
Args:
name: name of the variable
shape: list of ints
initializer: initializer for Variable
Returns:
Variable Tensor
"""
with tf.device('/cpu:0'):
var = tf.get_variable(name, shape, initializer=initializer)
return var
在第一层卷积中,最后调用了_activation_summary(conv1)。
def _activation_summary(x):
"""Helper to create summaries for activations.
#创建一个汇总,提供激活的直方图
Creates a summary that provides a histogram of activations.
#创建一个总结,用来测量激活的稀疏性
Creates a summary that measure the sparsity of activations.
Args:
x: Tensor
Returns:
nothing
"""
# Remove 'tower_[0-9]/' from the name in case this is a multi-GPU training
# session. This helps the clarity of presentation on tensorboard.
#re.sub功能是对于一个输入的字符串,利用正则表达式,来实现字符串替换处理的功能返回处理后的字符串
tensor_name = re.sub('%s_[0-9]*/' % TOWER_NAME, '', x.op.name)
#添加x到直方图中
tf.summary.histogram(tensor_name + '/activations', x)
#添加标量
tf.summary.scalar(tensor_name + '/sparsity', tf.nn.zero_fraction(x))
inference函数返回,继续回到train函数。loss = cifar10.loss(logits, labels)。
def loss(logits, labels):
#增加L2正则化给所有训练的参数
#增加
"""Add L2Loss to all the trainable variables.
Add summary for for "Loss" and "Loss/avg".
Args:
logits: Logits from inference().
labels: Labels from distorted_inputs or inputs(). 1-D tensor
of shape [batch_size]
Returns:
Loss tensor of type float.
"""
# Reshape the labels into a dense Tensor of
# shape [batch_size, NUM_CLASSES].
#将标签转化为batch大小的一维向量
sparse_labels = tf.reshape(labels, [FLAGS.batch_size, 1])
#将0~bacth_size转换为一维向量
indices = tf.reshape(tf.range(FLAGS.batch_size), [FLAGS.batch_size, 1])
#拼接成
# 0 num1
# 1 num2
# 2 num3
#这样的形式
concated = tf.concat([indices, sparse_labels],1)
#tf.sparse_to_dense函数
#第一个参数 如果是个矩阵,那么它可以指定二维矩阵多个元素
#第二个参数 是指输出矩阵的维度
#第三个 指定为1
#第四个 没指定的为0
#例子 如果concated = [[0,1],[1,4]],且batch_size,num_classes = 4, 5.
#则结果为:
# 0 1 0 0 0
# 0 0 0 0 1
# 0 0 0 0 0
# 0 0 0 0 0
dense_labels = tf.sparse_to_dense(concated,
[FLAGS.batch_size, NUM_CLASSES],
1.0, 0.0)
#求交叉熵 返回一个向量
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(
logits=logits, labels=dense_labels, name='cross_entropy_per_example')
#求一批数据均值
cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy')
#将交叉熵均值放入集合“losses”中
tf.add_to_collection('losses', cross_entropy_mean)
# The total loss is defined as the cross entropy loss plus all of the weight
#总的损失定义为交叉熵损失加上所以权重
# decay terms (L2 loss).
#将集合中所有损失相加再返回
return tf.add_n(tf.get_collection('losses'), name='total_loss')
train_op = cifar10.train(loss, global_step)进入这个函数。
def train(total_loss, global_step):
#创建一个优化器,应用于所有的可训练变量
#给所有可训练变量添加移动平均计算
"""Train CIFAR-10 model.
Create an optimizer and apply to all trainable variables. Add moving
average for all trainable variables.
Args:
total_loss: Total loss from loss().
global_step: Integer Variable counting the number of training steps
processed.
Returns:
train_op: op for training.
"""
# Variables that affect learning rate.
#多少批次一个训练周期
num_batches_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN / FLAGS.batch_size
#衰减速度
decay_steps = int(num_batches_per_epoch * NUM_EPOCHS_PER_DECAY)
# Decay the learning rate exponentially based on the number of steps.
#1 初始学习率
#2 全局变量 训练步数
#3 decay_step
#4 为衰减速率
#5 =True 每decay_step改变一次学习率,=False每一步都改变。
# learn_rate = INITIAL_LEARNING_RATE * LEARNING_RATE_DECAY_FACTOR
lr = tf.train.exponential_decay(INITIAL_LEARNING_RATE,
global_step,
decay_steps,
LEARNING_RATE_DECAY_FACTOR,
staircase=True)
#将标量lr添加
tf.summary.scalar('learning_rate', lr)
# Generate moving averages of all losses and associated summaries.
#生成所有损失和相关摘要的移动平均值。
loss_averages_op = _add_loss_summaries(total_loss)
# Compute gradients.
#计算梯度
#tf.control_dependencies用来实现某些操作的依赖关系
#也就是说loss_average_op执行完,才能执行下面的语句。
with tf.control_dependencies([loss_averages_op]):
#梯度下降
opt = tf.train.GradientDescentOptimizer(lr)
#计算梯度 应该是个字典
grads = opt.compute_gradients(total_loss)
# Apply gradients.
#应用梯度
apply_gradient_op = opt.apply_gradients(grads, global_step=global_step)
# Add histograms for trainable variables.
#给可训练变量增加一个柱状图
for var in tf.trainable_variables():
tf.summary.histogram(var.op.name, var)
# Add histograms for gradients.
for grad, var in grads:
if grad is not None:
tf.summary.histogram(var.op.name + '/gradients', grad)
# Track the moving averages of all trainable variables.
#追踪所有可训练变量的移动平均值
variable_averages = tf.train.ExponentialMovingAverage(
MOVING_AVERAGE_DECAY, global_step)
variables_averages_op = variable_averages.apply(tf.trainable_variables())
with tf.control_dependencies([apply_gradient_op, variables_averages_op]):
train_op = tf.no_op(name='train') #什么也不做
return train_op
来看下_add_loss_summaries(total_loss)函数。
def _add_loss_summaries(total_loss):
"""Add summaries for losses in CIFAR-10 model.
#为所有损失和相关摘要生成移动平均可视化网络的性能。
Generates moving average for all losses and associated summaries for
visualizing the performance of the network.
Args:
total_loss: Total loss from loss().
Returns:
loss_averages_op: op for generating moving averages of losses.
"""
# Compute the moving average of all individual losses and the total loss.
#指数移动平均计算损失
loss_averages = tf.train.ExponentialMovingAverage(0.9, name='avg')
#获取集合中添加的损失值
losses = tf.get_collection('losses')
#计算losses和total_loss中的指数加权平均
loss_averages_op = loss_averages.apply(losses + [total_loss])
# Attach a scalar summary to all individual losses and the total loss; do the
# same for the averaged version of the losses.
#对所有的个人损失和全部损失附加一个标量摘要;对平均的损失也做同样的事情。
for l in losses + [total_loss]:
# Name each loss as '(raw)' and name the moving average version of the loss
# as the original loss name.
#将每个损失命名为“(原始的)”,并将损失的移动平均版本命名为原始损失名称。
tf.summary.scalar(l.op.name +' (raw)', l)
tf.summary.scalar(l.op.name, loss_averages.average(l))
#将指数加权平均操作返回
return loss_averages_op
接下来三个语句代码:
# Create a saver.
#创建一个Saver保存模型
saver = tf.train.Saver(tf.all_variables())
# Build the summary operation based on the TF collection of Summaries.
#自动管理
summary_op = tf.summary.merge_all()
#所有变量初始化
init = tf.initialize_all_variables()
剩下来的语句就是输出:
# Start running operations on the Graph.
#开始在图中计算
#进行一些配置
sess = tf.Session(config=tf.ConfigProto(
log_device_placement=FLAGS.log_device_placement))
sess.run(init)
# Start the queue runners.
#TensorFlow的Session对象是可以支持多线程的,因此多个线程可以很方便地使用同一个会话
#(Session)并且并行地执行操作。然而,在Python程序实现这样的并行运算却并不容易。
#所有线程都必须能被同步终止,异常必须能被正确捕获并报告,回话终止的时候, 队列必须能被正确地
#关闭。
tf.train.start_queue_runners(sess=sess)
#将事件写入目录
summary_writer = tf.summary.FileWriter(FLAGS.train_dir,
graph_def=sess.graph_def)
开始训练
for step in xrange(FLAGS.max_steps):
#记录开始的事件
start_time = time.time()
_, loss_value = sess.run([train_op, loss])
#计算一批次花了多少事件
duration = time.time() - start_time
assert not np.isnan(loss_value), 'Model diverged with loss = NaN'
#10次一个输出
if step % 10 == 0:
num_examples_per_step = FLAGS.batch_size
#多少批次一秒
examples_per_sec = num_examples_per_step / duration
sec_per_batch = float(duration)
format_str = ('%s: step %d, loss = %.2f (%.1f examples/sec; %.3f '
'sec/batch)')
#输出多少样本一秒,多少批次一秒
print (format_str % (datetime.now(), step, loss_value,
examples_per_sec, sec_per_batch))
#每100次 就写入summary
if step % 100 == 0:
summary_str = sess.run(summary_op)
summary_writer.add_summary(summary_str, step)
# Save the model checkpoint periodically.
#每1000次就保存一个模型
if step % 1000 == 0 or (step + 1) == FLAGS.max_steps:
checkpoint_path = os.path.join(FLAGS.train_dir, 'model.ckpt')
saver.save(sess, checkpoint_path, global_step=step)
训练完成后,模型保存在我们的目录中:
三 训练结果
特别吐槽下渣渣笔记本电脑,训练很久。
先看下随着迭代次数的增加,Loss的变化情况。我们一共迭代了20000次,所以每5000选取100个情况来看下Loss的值。
在最开始的训练,loss大约在4.5左右。
迭代次数达到5000次,我们看到loss已经变为1.1左右。说明随着迭代次数的增加,loss在减少。
迭代次数达到10000次,此时loss和之前5000迭代次数相差不大。
虽然经过了20000次的训练次数,但loss已经不能在下降了。说明loss在5000次迭代下,已经达到了最优。只能通过别的方法来优化模型。
接下来来看下tensorboard下,各个变量以及参数的变化情况。
可以看出学习率在5000左右已经不再明显变化,也正好验证了在5000次迭代次数下,loss已经收敛。来看下total_loss的情况。
total_loss开始随着迭代次数逐渐减小,近5000左右,开始趋于平稳。
来看下各层卷积的参数变化。
第一卷积层偏置的梯度。
第二层卷积层偏置梯度变化。
第一层卷积层权重
第二层卷积层权重
我们来看下两层卷积神经激活函数的稀疏性。
关于激活函数的稀疏性,可以看这篇博文:ReLu(Rectified Linear Units)激活函数 - Physcal - 博客园
最后看下测试的结果。有83%的准确率。
四 代码
将修改后的代码放在百度云上(没修改的代码都是基于tensorflow1.0以前的API,我用的是TensorFLow1.9,许多函数都已经弃用了)
下载完后运行cifar10_train.py就可以直接训练,下载数据可能需要点时间。实测如果翻墙的话速度会快点。
训练完毕后,运行cifar10_eval.py可以进行测试数据。因为还没怎么看懂测试的代码,以后再补上。
五 参考
源码来自TensorFlowtensorflow/models/image/cifar10/.
TensorFlow学习笔记(九):CIFAR-10训练例子报错解决_沫尘的博客-CSDN博客
tf.train.shuffle_batch函数解析_遗世独立的乌托邦-CSDN博客_shuffle_batch
tf.train.exponential_decay(学习率衰减)_张帅的博客-CSDN博客_tf.train.exponential_decay()
关于tensorflow 的数据读取线程管理QueueRunner_sunquan_ok的博客-CSDN博客
tf.control_dependencies()作用及用法_qishi的博客-CSDN博客
http://wiki.jikexueyuan.com/project/tensorflow-zh/tutorials/deep_cnn.html