0 构建CNN MNIST分类器
卷积层1:32个5-5过滤器,并应用ReLU激活函数
池化层1:2-2过滤器和步长2,执行最大池化运算
卷积层2:64个5-5过滤器,并应用ReLU激活函数
池化层2:2-2过滤器和步长2,执行最大池化运算
密集层1:1024个神经单元,dropout=0.4
密集层2(softmax):10个神经元,对应0-9的分类
conv2d()、max_pooling2d()、dense()
def cnn_model_fn(features, labels, mode):
"""Model function for CNN."""
input_layer = tf.reshape(features["x"], [-1, 28, 28, 1])
conv1 = tf.layers.conv2d(
inputs=input_layer,
filters=32,
kernel_size=[5, 5],
padding="same",
activation=tf.nn.relu)
pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)
conv2 = tf.layers.conv2d(
inputs=pool1,
filters=64,
kernel_size=[5, 5],
padding="same",
activation=tf.nn.relu)
pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)
pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])
dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu)
dropout = tf.layers.dropout(
inputs=dense, rate=0.4, training=mode == tf.estimator.ModeKeys.TRAIN)
logits = tf.layers.dense(inputs=dropout, units=10)
predictions = {
"classes": tf.argmax(input=logits, axis=1),
"probabilities": tf.nn.softmax(logits, name="softmax_tensor")
}
if mode == tf.estimator.ModeKeys.PREDICT:
return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions)
loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)
if mode == tf.estimator.ModeKeys.TRAIN:
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001)
train_op = optimizer.minimize(
loss=loss,
global_step=tf.train.get_global_step())
return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op)
eval_metric_ops = {
"accuracy": tf.metrics.accuracy(
labels=labels, predictions=predictions["classes"])}
return tf.estimator.EstimatorSpec(
mode=mode, loss=loss, eval_metric_ops=eval_metric_ops)
0.1 输入层
- batch_size:在训练时执行梯度下降时使用的样本子集大小
- image_height:样本图像的高度
- image_width:样本图像的宽度
- channels:样本图像中颜色通道的数量
- data_format:一个字符串,含义是以上4个参数的排列顺序
reshape操作
将我们的输入特征图(features)转换为此形状
input_layer = tf.reshape(features["x"], [-1, 28, 28, 1])
其中features["x"]为训练数据
我们已经指明批次大小为 -1,表示应根据 features["x"] 中输入值的数量动态计算此维度,同时使所有其他维度的大小保持不变。这样一来,我们就可以将 batch_size 视为可调整的超参数。
0.2 卷积层1
conv1 = tf.layers.conv2d(
inputs=input_layer,
filters=32,
kernel_size=[5, 5],
padding="same",
activation=tf.nn.relu)
其中:
inputs 参数指定输入张量,该张量的形状必须为 [batch_size, image_height, image_width, channels]
filters 参数指定要应用的过滤器数量(在此教程中为 32)
kernel_size 将过滤器的维度指定为 [height, width]
padding 枚举值(valid,same) same指的是输出张量与输入张量具有相同的高度和宽度值,即边缘加padding,一圈0
activation 指定应用于卷积输出的激活函数
conv2d 生成的输出张量的形状为 [batch_size, 28, 28, 32]:高度和宽度维度与输入相同,但现在有 32 个通道,用于保存每个过滤器的输出。
0.3 池化层1
pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)
像inputs等以上出现的参数 不再赘述
pool_size 参数将最大池化过滤器的大小指定为 [height, width]
strides 参数指定步长的大小
以上两个参数的设定,如果设置成单个整数,表示在高度和宽度两个维度的大小相同;如果设置成两个整数,如[3,5],则表示不同维度分别的值
max_pooling2d() (pool1) 生成的输出张量的形状为 [batch_size, 14, 14, 32]:2x2 过滤器将高度和宽度各减少 50%。
0.4 卷积层2和池化层2
conv2 = tf.layers.conv2d(
inputs=pool1,
filters=64,
kernel_size=[5, 5],
padding="same",
activation=tf.nn.relu)
pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)
卷积层 2 接受的输入为第一个池化层 (pool1) 的输出张量,并生成输出张量 conv2。conv2 的形状为 [batch_size, 14, 14, 64],高度和宽度与 pool1 相同(因为 padding="same"),并有 64 个通道,对应于应用的 64 个过滤器。
池化层 2 接受输入 conv2,并生成输出 pool2。pool2 的形状为 [batch_size, 7, 7, 64](将 conv2 的高度和宽度各减少 50%)。
0.5 密集层
接下来,我们需要向 CNN 添加密集层(具有 1024 个神经元和 ReLU 激活函数),以对卷积/池化层提取的特征执行分类。不过,在我们连接该层之前,我们会先扁平化特征图 (pool2),以将其变形为 [batch_size, features],使张量只有两个维度:
pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])
在上面的 reshape() 操作中,-1 表示 batch_size 维度将根据输入数据中的样本数量动态计算。每个样本都具有 7(pool2 高度)* 7(pool2 宽度)* 64(pool2 通道)个特征,因此我们希望 features 维度的值为 7 * 7 * 64(总计为 3136)。输出张量 pool2_flat 的形状为 [batch_size, 3136]。
现在,我们可以使用 layers 中的 dense() 方法连接密集层
dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu)
dropout = tf.layers.dropout(
inputs=dense, rate=0.4, training=mode == tf.estimator.ModeKeys.TRAIN)
rate 参数指定丢弃率;在此教程中,我们使用 0.4,该值表示 40% 的元素会在训练期间被随机丢弃
training 参数采用布尔值,表示模型目前是否在训练模式下运行;只有在 training 为 True 的情况下才会执行丢弃操作。在这里,我们检查传递到模型函数 cnn_model_fn 的 mode 是否为 TRAIN 模式。
输出张量 dropout 的形状为 [batch_size, 1024]。
0.6 对数层
我们的神经网络中的最后一层是对数层,该层返回预测的原始值。我们创建一个具有 10 个神经元(介于 0 到 9 之间的每个目标类别对应一个神经元)的密集层,并应用线性激活函数(默认函数)
logits = tf.layers.dense(inputs=dropout, units=10)
CNN 的最终输出张量 logits 的形状为 [batch_size, 10]
0.7 生成预测
模型的对数层以 [batch_size, 10] 维张量中原始值的形式返回预测。我们将这些原始值转换成模型函数可以返回的两种不同格式:
每个样本的预测类别:一个介于 0 到 9 之间的数字。
每个样本属于每个可能的目标类别的概率:样本属于以下类别的概率:0、1、2 等。
对于某个给定的样本,预测的类别是对数张量中具有最高原始值的行对应的元素。可以使用tf.argmax函数
tf.argmax(input=logits, axis=1)
input 参数指定要从其中提取最大值的张量,在这里为 logits。
axis 参数指定要沿着 input 张量的哪个轴查找最大值。在这里,我们需要沿着索引为 1 的维度查找最大值,该维度对应于预测结果(已经知道对数张量的形状为 [batch_size, 10])。
我们可以使用 tf.nn.softmax 应用 softmax 激活函数,以从对数层中得出概率:
tf.nn.softmax(logits, name="softmax_tensor")
我们将预测编译为字典,并返回** EstimatorSpec** 对象:
predictions = {
"classes": tf.argmax(input=logits, axis=1),
"probabilities": tf.nn.softmax(logits, name="softmax_tensor")
}
if mode == tf.estimator.ModeKeys.PREDICT:
return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions)
0.8 计算损失
损失函数:衡量模型的预测结果与目标类别之间的匹配程度。
对于像 MNIST 这样的多类别分类问题,通常将交叉熵用作损失指标。
以下代码计算模型在 TRAIN 或 EVAL 模式下运行时的交叉熵:
loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)
labels 张量包含样本的预测索引列表,例如 [1, 9, ...]。
logits 包含最后一层的线性输出。
tf.losses.sparse_softmax_cross_entropy 以高效的数值稳定方式计算以上两个输入的 softmax 交叉熵(又名:类别交叉熵、负对数似然率)。
0.9 配置训练操作
配置模型以在训练期间优化该损失值。我们将学习速率设为 0.001,并将优化算法设为随机梯度下降法
if mode == tf.estimator.ModeKeys.TRAIN:
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001)
train_op = optimizer.minimize(
loss=loss,
global_step=tf.train.get_global_step())
return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op)
0.10 添加评估指标
要在模型中添加准确率指标,我们在评估模式下定义eval_metric_ops 字典。
eval_metric_ops = {
"accuracy": tf.metrics.accuracy(
labels=labels, predictions=predictions["classes"])}
return tf.estimator.EstimatorSpec(
mode=mode, loss=loss, eval_metric_ops=eval_metric_ops)
1 训练和评估CNN MNIST分类器
1.1 加载训练和测试数据
def main(unused_argv):
mnist = tf.contrib.learn.datasets.load_dataset("mnist")
train_data = mnist.train.images # Returns np.array
train_labels = np.asarray(mnist.train.labels, dtype=np.int32)
eval_data = mnist.test.images # Returns np.array
eval_labels = np.asarray(mnist.test.labels, dtype=np.int32)
1.2 创建Estimator(评估,估计)
接下来,我们为模型创建一个 Estimator(一种用于执行高级模型训练、评估和推理的 TensorFlow 类)
mnist_classifier = tf.estimator.Estimator(
model_fn=cnn_model_fn, model_dir="/tmp/mnist_convnet_model")
model_fn
参数指定用于训练、评估和预测的模型函数;我们将在部分创建的 cnn_model_fn
传递到该参数。
model_dir
参数指定要用于保存模型数据(检查点)的目录。
1.3 设置日志记录
由于 CNN 可能需要一段时间才能完成训练,因此我们设置一些日志记录,以在训练期间跟踪进度。我们可以使用 TensorFlow 的 tf.train.SessionRunHook 创建 tf.train.LoggingTensorHook,它将记录 CNN 的 softmax 层的概率值。
tensors_to_log = {"probabilities": "softmax_tensor"}
logging_hook = tf.train.LoggingTensorHook(
tensors=tensors_to_log, every_n_iter=50)
我们将要记录的张量字典存储到 tensors_to_log 中。
每个键都是我们选择的将会显示在日志输出中的标签,而相应标签是 TensorFlow 图中 Tensor 的名称。
在这里,我们可以在 softmax_tensor(我们之前在 cnn_model_fn 中生成概率时为 softmax 操作指定的名称)中找到 probabilities。
接下来,我们创建 LoggingTensorHook,将 tensors_to_log 传递到 tensors 参数。我们设置 every_n_iter=50,指定每完成 50 个训练步之后应记录概率。
1.4 训练模型
现在,我们可以训练模型了,可以通过创建 train_input_fn 并在 mnist_classifier 上调用 train() 来完成该操作。
train_input_fn = tf.estimator.inputs.numpy_input_fn(
x={"x": train_data},
y=train_labels,
batch_size=100,
num_epochs=None,
shuffle=True)
mnist_classifier.train(
input_fn=train_input_fn,
steps=20000,
hooks=[logging_hook])
在 numpy_input_fn 调用中,我们将训练特征数据和标签分别传递到 x(作为字典)和 y。
我们将 batch_size 设置为 100(这意味着模型会在每一步训练 100 个小批次样本)。
num_epochs=None 表示模型会一直训练,直到达到指定的训练步数。
我们还设置 shuffle=True,以随机化处理训练数据。
在 train 调用中,我们设置 steps=20000(这意味着模型总共要训练 20000 步)。
为了在训练期间触发 logging_hook,我们将其传递到 hooks 参数。
1.5 评估模型
训练完成后,我们需要评估模型以确定其在 MNIST 测试集上的准确率。我们调用 evaluate 方法,该方法将评估我们在 model_fn 的 eval_metric_ops 参数中指定的指标。
eval_input_fn = tf.estimator.inputs.numpy_input_fn(
x={"x": eval_data},
y=eval_labels,
num_epochs=1,
shuffle=False)
eval_results = mnist_classifier.evaluate(input_fn=eval_input_fn)
print(eval_results)
要创建 eval_input_fn,我们设置 num_epochs=1,以便模型评估一个数据周期的指标,并返回结果。
我们还设置 shuffle=False 以按顺序遍历数据。