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)
这里的激活函数使用的relu函数,几种激活函数(Sigmoid,Tanh,Relu,Leaky Relu,ELU)的对比参见:
https://www.cnblogs.com/ya-qiang/p/9258714.html
为了改善模型结果,引入dropout方法,向密集层应用丢弃正则化
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 以按顺序遍历数据。
1.6 运行模型
我们已经编写了 CNN 模型函数 Estimator 和训练/评估逻辑;现在我们来看看结果。运行 cnn_mnist.py
cnn_mnist.py 详细代码参见一下链接:
https://github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/tutorials/layers/cnn_mnist.py
2 参考资源
本文参考:https://www.tensorflow.org/tutorials/estimators/cnn#building_the_cnn_mnist_classifier
高级卷积神经网络:https://www.tensorflow.org/tutorials/images/deep_cnn
Estimator介绍和详细使用:https://www.tensorflow.org/guide/custom_estimators
3 Tensorflow和Keras的对比
keras:高度封装;Tensorflow官方支持,GPU并行计算;
模块化;极简主义;易扩展性;使用python实现,易于调试
Keras 的核心数据结构是模型。模型是用来组织网络层的方式。模型有两种,一种叫
Sequential 模型,另一种叫Model 模型。Sequential 模型是一系列网络层按顺序构成的栈,是单
输入和单输出的,层与层之间只有相邻关系,是最简单的一种模型。Model 模型是用来建立更
复杂的模型的。
Tensorflow核心工作模式:定义数据流图(计算图graph);运行数据流图。
基本概念:张量、变量、占位符、图中的节点操作
概念都在“图”的容器中完成;一个“图”代表一个计算任务;在模型运行的环节中,“图”会在会话里被启动;
https://www.jiqizhixin.com/articles/052401
计算图是纯python的,因此速度较慢;图构造是静态的,意味着图必须先被“编译”再运行。 优点:开发维护;巨大、活跃的社区;网络训练的低级、高级接口;可视化套件;旨在跟踪网络拓扑和性能;C++和python编写;支持多GPU;模型编译速度比Theano的选项更快。
网友评论