温馨提示,TensorFlow更新的太快了,有些代码实现方式可能变了,但是思想还是没有变滴,主要还是理解 原文地址
介绍
前面的教程表明,简单的线性模型具有大约91%的分类准确度,用于识别MNIST数据集中的手写数字。
在本教程中,我们将在TensorFlow中实现一个简单的卷积神经网络,如果您进行一些建议的练习,其分类精度约为99%或更高。
卷积网络通过在输入图像上移动小滤镜来工作。 这意味着过滤器被重新用于识别整个输入图像中的模式。 这使得卷积网络比具有相同数量的变量的完全连接网络更强大。 这反过来使Convolutional Networks更快地进行训练。
您应该熟悉基本的线性代数,Python和Jupyter Notebook编辑器。 TensorFlow的初学者也可能希望在继续学习之前学习第一篇教程。
Flowchart 下图大致显示了下面实现的卷积神经网络中数据的流动方式。
使用滤波器权重在第一卷积层中处理输入图像。 这导致16个新图像,一个用于卷积层中的每个滤波器。 图像也进行了下采样,因此图像分辨率从28x28降低到14x14。然后在第二卷积层中处理这16个较小的图像。 我们需要为这16个通道中的每个通道提供滤波器权重,并且我们需要对该层的每个输出通道使用滤波器权重。 有36个输出通道,因此在第二个卷积层中总共有16 x 36 = 576个滤波器。 生成的图像再次下采样到7x7像素。
第二卷积层的输出是每个7×7像素的36个图像。 然后将它们展平为长度为7×7×36 = 1764的单个矢量,其用作具有128个神经元(或元件)的完全连接层的输入。 这将进入另一个具有10个神经元的完全连接的层,每个类对应一个类,用于确定图像的类别,即图像中描绘的数字。
卷积滤波器最初是随机选择的,因此分类是随机进行的。 输入图像的预测类和真实类之间的误差被测量为所谓的交叉熵。 然后,优化器使用区分链规则自动将此错误传播回卷积网络,并更新过滤器权重,以改善分类错误。 这反复进行数千次,直到分类误差足够低。
这些特定的滤镜权重和中间图像是一次优化运行的结果,如果重新运行此Notebook,可能会有所不同。
请注意,TensorFlow中的计算实际上是在一批图像而不是单个图像上完成的,这使得计算更有效。 这意味着当在TensorFlow中实现时,流程图实际上还有一个数据维度。
Convolutional Layer
下图显示了在第一个卷积层中处理图像的基本思想。 输入图像描绘了数字7,此处显示了四个图像副本,因此我们可以更清楚地看到滤镜如何移动到图像的不同位置。 对于滤镜的每个位置,在滤镜和滤镜下的图像像素之间计算点积,这导致输出图像中的单个像素。 因此,在整个输入图像上移动滤镜会导致生成新图像。
红色滤镜权重意味着滤镜对输入图像中的黑色像素具有正反应,而蓝色像素意味着滤镜对黑色像素具有负反应。
在这种情况下,过滤器可以识别出7位数的水平线,从输出图像中对该线的较强反应可以看出。
在输入上移动过滤器的步长称为步幅。 水平移动过滤器(x轴)和另一个垂直移动步幅(y轴)有一个跨度。
在下面的源代码中,步幅在两个方向上都设置为1,这意味着滤镜从输入图像的左上角开始,并在每个步骤中向右移动1个像素。 当滤镜到达图像右侧的末尾时,滤镜将向后移动到图像的左侧和1个像素。 这将一直持续到滤镜到达输入图像的右下角并生成整个输出图像。
当滤镜到达输入图像的右侧和底部的末端时,可以用零填充(白色像素)。 这会使输出图像与输入图像具有完全相同的尺寸。
此外,卷积的输出可以通过所谓的整流线性单元(ReLU),其仅确保输出为正,因为负值被设置为零。 输出也可以通过所谓的max-pooling进行下采样,max-pooling考虑2x2像素的小窗口并且仅保留这些像素中的最大像素。 这使输入图像的分辨率减半,例如 从28x28到14x14像素。
注意,第二卷积层更复杂,因为它需要16个输入通道。 我们想为每个输入通道分别使用一个过滤器,因此我们需要16个过滤器而不是一个过滤器。 此外,我们需要来自第二卷积层的36个输出通道,因此总共需要16 x 36 = 576个滤波器用于第二个卷积层。 了解其工作原理可能有点难度。
Imports
%matplotlib inline
import matplotlib.pyplot as plt
import tensorflow as tf
import numpy as np
from sklearn.metrics import confusion_matrix
import time
from datetime import timedelta
import math
复制代码
Configuration of Neural Network
为方便起见,此处定义了卷积神经网络的配置,因此您可以轻松查找和更改这些数字并重新运行Notebook。
# Convolutional Layer 1.
filter_size1 = 5 # Convolution filters are 5 x 5 pixels.
num_filters1 = 16 # There are 16 of these filters.
# Convolutional Layer 2.
filter_size2 = 5 # Convolution filters are 5 x 5 pixels.
num_filters2 = 36 # There are 36 of these filters.
# Fully-connected layer.
fc_size = 128 # Number of neurons in fully-connected layer.
复制代码
Load Data
MNIST数据集大约为12 MB,如果它不在给定路径中,将自动下载。
from mnist import MNIST
data = MNIST(data_dir="data/MNIST/")
复制代码
复制代码MNIST数据集现已加载,由70,000个图像和图像的类号组成。 数据集被分成3个互斥的子集。 我们将仅使用本教程中的培训和测试集。
print("Size of:")
print("- Training-set:\t\t{}".format(data.num_train))
print("- Validation-set:\t{}".format(data.num_val))
print("- Test-set:\t\t{}".format(data.num_test))
Size of:
- Training-set: 55000
- Validation-set: 5000
- Test-set: 10000
复制代码
复制代码为方便起见,复制一些数据维度。
# The number of pixels in each dimension of an image.
img_size = data.img_size
# The images are stored in one-dimensional arrays of this length.
img_size_flat = data.img_size_flat
# Tuple with height and width of images used to reshape arrays.
img_shape = data.img_shape
# Number of classes, one class for each of 10 digits.
num_classes = data.num_classes
# Number of colour channels for the images: 1 channel for gray-scale.
num_channels = data.num_channels
复制代码
Helper-function for plotting images
用于在3x3网格中绘制9个图像,并在每个图像下面写入真实和预测类的函数。
def plot_images(images, cls_true, cls_pred=None):
assert len(images) == len(cls_true) == 9
# Create figure with 3x3 sub-plots.
fig, axes = plt.subplots(3, 3)
fig.subplots_adjust(hspace=0.3, wspace=0.3)
for i, ax in enumerate(axes.flat):
# Plot image.
ax.imshow(images[i].reshape(img_shape), cmap='binary')
# Show true and predicted classes.
if cls_pred is None:
xlabel = "True: {0}".format(cls_true[i])
else:
xlabel = "True: {0}, Pred: {1}".format(cls_true[i], cls_pred[i])
# Show the classes as the label on the x-axis.
ax.set_xlabel(xlabel)
# Remove ticks from the plot.
ax.set_xticks([])
ax.set_yticks([])
# Ensure the plot is shown correctly with multiple plots
# in a single Notebook cell.
plt.show()
复制代码
Plot a few images to see if data is correct
# Get the first images from the test-set.
images = data.x_test[0:9]
# Get the true classes for those images.
cls_true = data.y_test_cls[0:9]
# Plot the images and labels using our helper-function above.
plot_images(images=images, cls_true=cls_true)
复制代码
TensorFlow Graph
#Helper-functions for creating new variables(辅助函数用于创建新变量)
用于在给定形状中创建新TensorFlow变量并使用随机值初始化它们的函数。 请注意,此时实际上并未完成初始化,它仅在TensorFlow图中定义。
def new_weights(shape):
return tf.Variable(tf.truncated_normal(shape, stddev=0.05))
def new_biases(length):
return tf.Variable(tf.constant(0.05, shape=[length]))
复制代码
#Helper-function for creating a new Convolutional Layer(辅助功能,用于创建新的卷积层)
此函数在TensorFlow的计算图中创建一个新的卷积层。 这里没有实际计算,我们只是将数学公式添加到TensorFlow图。
假设输入是具有以下尺寸的4维张量:
图像编号。
每个图像的Y轴。
每个图像的X轴。
每个图像的通道。
复制代码
注意,输入通道可以是颜色通道,或者如果输入是从先前的卷积层产生的,则它可以是滤波器通道。
输出是另一个4-dim张量,具有以下尺寸:
图像编号,与输入相同。
每个图像的Y轴。 如果使用2x2池,则输入图像的高度和宽度除以2。
每个图像的X轴。 同上。
卷积滤波器产生的通道。
复制代码
def new_conv_layer(input, # The previous layer.
num_input_channels, # Num. channels in prev. layer.
filter_size, # Width and height of each filter.
num_filters, # Number of filters.
use_pooling=True): # Use 2x2 max-pooling.
# Shape of the filter-weights for the convolution.
# This format is determined by the TensorFlow API.
shape = [filter_size, filter_size, num_input_channels, num_filters]
# Create new weights aka. filters with the given shape.
weights = new_weights(shape=shape)
# Create new biases, one for each filter.
biases = new_biases(length=num_filters)
# Create the TensorFlow operation for convolution.
# Note the strides are set to 1 in all dimensions.
# The first and last stride must always be 1,
# because the first is for the image-number and
# the last is for the input-channel.
# But e.g. strides=[1, 2, 2, 1] would mean that the filter
# is moved 2 pixels across the x- and y-axis of the image.
# The padding is set to 'SAME' which means the input image
# is padded with zeroes so the size of the output is the same.
layer = tf.nn.conv2d(input=input,
filter=weights,
strides=[1, 1, 1, 1],
padding='SAME')
# Add the biases to the results of the convolution.
# A bias-value is added to each filter-channel.
layer += biases
# Use pooling to down-sample the image resolution?
if use_pooling:
# This is 2x2 max-pooling, which means that we
# consider 2x2 windows and select the largest value
# in each window. Then we move 2 pixels to the next window.
layer = tf.nn.max_pool(value=layer,
ksize=[1, 2, 2, 1],
strides=[1, 2, 2, 1],
padding='SAME')
# Rectified Linear Unit (ReLU).
# It calculates max(x, 0) for each input pixel x.
# This adds some non-linearity to the formula and allows us
# to learn more complicated functions.
layer = tf.nn.relu(layer)
# Note that ReLU is normally executed before the pooling,
# but since relu(max_pool(x)) == max_pool(relu(x)) we can
# save 75% of the relu-operations by max-pooling first.
# We return both the resulting layer and the filter-weights
# because we will plot the weights later.
return layer, weights
复制代码
#Helper-function for flattening a layer(辅助功能,用于展平图层)
卷积层产生具有4维的输出张量。 我们将在卷积层之后添加完全连接的层,因此我们需要将4-dim张量减小到2-dim,这可以用作完全连接层的输入。
def flatten_layer(layer):
# Get the shape of the input layer.
layer_shape = layer.get_shape()
# The shape of the input layer is assumed to be:
# layer_shape == [num_images, img_height, img_width, num_channels]
# The number of features is: img_height * img_width * num_channels
# We can use a function from TensorFlow to calculate this.
num_features = layer_shape[1:4].num_elements()
# Reshape the layer to [num_images, num_features].
# Note that we just set the size of the second dimension
# to num_features and the size of the first dimension to -1
# which means the size in that dimension is calculated
# so the total size of the tensor is unchanged from the reshaping.
layer_flat = tf.reshape(layer, [-1, num_features])
# The shape of the flattened layer is now:
# [num_images, img_height * img_width * num_channels]
# Return both the flattened layer and the number of features.
return layer_flat, num_features
复制代码
#Helper-function for creating a new Fully-Connected Layer(辅助功能,用于创建新的全连接层)
此函数在TensorFlow的计算图中创建一个新的完全连接层。 这里没有实际计算,我们只是将数学公式添加到TensorFlow图。
假设输入是形状[di_images,num_inputs]的二维张量。 输出是2-dim张量的形状[num_images,num_outputs]。
def new_fc_layer(input, # The previous layer.
num_inputs, # Num. inputs from prev. layer.
num_outputs, # Num. outputs.
use_relu=True): # Use Rectified Linear Unit (ReLU)?
# Create new weights and biases.
weights = new_weights(shape=[num_inputs, num_outputs])
biases = new_biases(length=num_outputs)
# Calculate the layer as the matrix multiplication of
# the input and weights, and then add the bias-values.
layer = tf.matmul(input, weights) + biases
# Use ReLU?
if use_relu:
layer = tf.nn.relu(layer)
return layer
复制代码
#Placeholder variables(占位符变量) 占位符变量用作TensorFlow计算图的输入,每次执行图时我们都可以更改。 我们称之为占位符变量,并在下面进一步说明。
首先,我们为输入图像定义占位符变量。 这允许我们更改输入到TensorFlow图形的图像。 这是一个所谓的张量,这意味着它是一个多维向量或矩阵。 数据类型设置为float32,形状设置为[None,img_size_flat],其中None表示张量可以保持任意数量的图像,每个图像是长度为img_size_flat的向量。
x = tf.placeholder(tf.float32, shape=[None, img_size_flat], name='x')
复制代码
卷积层期望x被编码为4-dim张量,因此我们必须对其进行整形,使其形状为[num_images,img_height,img_width,num_channels]。 请注意,img_height == img_width == img_size和num_images可以使用-1作为第一个维度的大小自动推断。 所以重塑操作是:
x_image = tf.reshape(x, [-1, img_size, img_size, num_channels])
复制代码
接下来,我们有占位符变量,用于与占位符变量x中输入的图像相关联的真实标签。 此占位符变量的形状为[None,num_classes],这意味着它可以包含任意数量的标签,每个标签是长度为num_classes的向量,在这种情况下为10。
y_true = tf.placeholder(tf.float32, shape=[None, num_classes], name='y_true')
复制代码
我们也可以为label设置占位符变量,但我们将使用argmax计算它。 请注意,这是一个TensorFlow运算符,因此此时不计算任何内容。
y_true_cls = tf.argmax(y_true, axis=1)
复制代码
Convolutional Layer 1
创建第一个卷积层。 它将x_image作为输入并创建num_filters1个不同的过滤器,每个过滤器的宽度和高度等于filter_size1。 最后,我们希望通过使用2x2 max-pooling对图像进行下采样,使其大小减半。
layer_conv1, weights_conv1 = \
new_conv_layer(input=x_image,
num_input_channels=num_channels,
filter_size=filter_size1,
num_filters=num_filters1,
use_pooling=True)
复制代码
检查将由卷积层输出的张量的形状。 它是(?,14,14,16),这意味着有任意数量的图像(这是?),每个图像宽14像素,高14像素,有16个不同的通道,每个通道一个通道 过滤器
layer_conv1
<tf.Tensor 'Relu:0' shape=(?, 14, 14, 16) dtype=float32>
复制代码
Convolutional Layer 2
创建第二个卷积层,它将第一个卷积层的输出作为输入。 输入通道的数量对应于第一卷积层中的滤波器的数量。
layer_conv2, weights_conv2 = \
new_conv_layer(input=layer_conv1,
num_input_channels=num_filters1,
filter_size=filter_size2,
num_filters=num_filters2,
use_pooling=True)
复制代码
检查将从此卷积层输出的张量的形状。 形状是(?,7,7,36)? 再次表示存在任意数量的图像,每个图像的宽度和高度为7个像素,并且有36个通道,每个滤波器一个。
layer_conv2
<tf.Tensor 'Relu_1:0' shape=(?, 7, 7, 36) dtype=float32>
复制代码
Flatten Layer
卷积层输出4-dim张量。 我们现在希望在完全连接的网络中使用这些作为输入,这需要将张量重新整形或展平为2-dim张量。
layer_flat, num_features = flatten_layer(layer_conv2)
复制代码
检查张量现在是否具有形状(?,1764),这意味着有任意数量的图像已被展平为每个长度为1764的向量。 请注意,1764 = 7 x 7 x 36。
In [23]:layer_flat
Out[23]:<tf.Tensor 'Reshape_1:0' shape=(?, 1764) dtype=float32>
In [24]:num_features
Out[24]:1764
复制代码
Fully-Connected Layer 1
将完全连接的层添加到网络中。 输入是前一卷积的平坦层。 完全连接层中的神经元或节点的数量是fc_size。 使用ReLU,我们可以学习非线性关系。
layer_fc1 = new_fc_layer(input=layer_flat,
num_inputs=num_features,
num_outputs=fc_size,
use_relu=True)
复制代码
检查完全连接层的输出是否为形状(?,128)的张量? 意味着有任意数量的图像,fc_size == 128。
In [26]:layer_fc1
Out[26]:<tf.Tensor 'Relu_2:0' shape=(?, 128) dtype=float32>
复制代码
Fully-Connected Layer 2
添加另一个完全连接的层,输出长度为10的向量,用于确定输入图像属于哪10个类。 请注意,此层中不使用ReLU。
In [27]:layer_fc2 = new_fc_layer(input=layer_fc1,
num_inputs=fc_size,
num_outputs=num_classes,
use_relu=False)
In [28]:layer_fc2
Out[28]:<tf.Tensor 'add_3:0' shape=(?, 10) dtype=float32>
复制代码
Predicted Class
第二个完全连接的层估计输入图像属于10个类中的每一个的可能性。 然而,这些估计有点粗糙且难以解释,因为数字可能非常小或很大,因此我们希望将它们标准化,以便每个元素限制在0和1之间,10个元素总和为1。 这是使用所谓的softmax函数计算的,结果存储在y_pred中。
y_pred = tf.nn.softmax(layer_fc2)
复制代码
class-number是最大元素的索引。
y_pred_cls = tf.argmax(y_pred, axis=1)
复制代码
Cost-function to be optimized
为了使模型更好地分类输入图像,我们必须以某种方式改变所有网络层的变量。 为此,我们首先需要通过将模型y_pred的预测输出与期望输出y_true进行比较来了解模型当前的执行情况。
交叉熵是用于分类的性能度量。 交叉熵是一个始终为正的连续函数,如果模型的预测输出与期望输出完全匹配,则交叉熵等于零。 因此,优化的目标是最小化交叉熵,使其通过改变网络层的变量尽可能接近零。
TensorFlow具有用于计算交叉熵的内置函数。 请注意,该函数在内部计算softmax,因此我们必须直接使用layer_fc2的输出,而不是已经应用了softmax的y_pred。
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=layer_fc2,
labels=y_true)
复制代码
我们现在已经计算了每个图像分类的交叉熵,因此我们可以测量模型对每个图像的单独执行情况。 但是为了使用交叉熵来指导模型变量的优化,我们需要一个标量值,因此我们只需要对所有图像分类采用交叉熵的平均值。
cost = tf.reduce_mean(cross_entropy)
复制代码
Optimization Method 现在我们有一个必须最小化的成本度量,然后我们可以创建一个优化器。 在这种情况下,它是AdamOptimizer,它是Gradient Descent的高级形式。
请注意,此时不执行优化。 事实上,根本没有计算任何东西,我们只需将optimizer-object添加到TensorFlow图中以便以后执行。
optimizer = tf.train.AdamOptimizer(learning_rate=1e-4).minimize(cost)
复制代码
Performance Measures
correct_prediction = tf.equal(y_pred_cls, y_true_cls)
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
复制代码
TensorFlow Run
接下来都是训练、测试、调试啦~ 方式很多,这里我就只贴代码了
session = tf.Session()
session.run(tf.global_variables_initializer())
train_batch_size = 64
# Counter for total number of iterations performed so far.
total_iterations = 0
def optimize(num_iterations):
# Ensure we update the global variable rather than a local copy.
global total_iterations
# Start-time used for printing time-usage below.
start_time = time.time()
for i in range(total_iterations,
total_iterations + num_iterations):
# Get a batch of training examples.
# x_batch now holds a batch of images and
# y_true_batch are the true labels for those images.
x_batch, y_true_batch, _ = data.random_batch(batch_size=train_batch_size)
# Put the batch into a dict with the proper names
# for placeholder variables in the TensorFlow graph.
feed_dict_train = {x: x_batch,
y_true: y_true_batch}
# Run the optimizer using this batch of training data.
# TensorFlow assigns the variables in feed_dict_train
# to the placeholder variables and then runs the optimizer.
session.run(optimizer, feed_dict=feed_dict_train)
# Print status every 100 iterations.
if i % 100 == 0:
# Calculate the accuracy on the training-set.
acc = session.run(accuracy, feed_dict=feed_dict_train)
# Message for printing.
msg = "Optimization Iteration: {0:>6}, Training Accuracy: {1:>6.1%}"
# Print it.
print(msg.format(i + 1, acc))
# Update the total number of iterations performed.
total_iterations += num_iterations
# Ending time.
end_time = time.time()
# Difference between start and end-times.
time_dif = end_time - start_time
# Print the time-usage.
print("Time usage: " + str(timedelta(seconds=int(round(time_dif)))))
复制代码
def plot_example_errors(cls_pred, correct):
# This function is called from print_test_accuracy() below.
# cls_pred is an array of the predicted class-number for
# all images in the test-set.
# correct is a boolean array whether the predicted class
# is equal to the true class for each image in the test-set.
# Negate the boolean array.
incorrect = (correct == False)
# Get the images from the test-set that have been
# incorrectly classified.
images = data.x_test[incorrect]
# Get the predicted classes for those images.
cls_pred = cls_pred[incorrect]
# Get the true classes for those images.
cls_true = data.y_test_cls[incorrect]
# Plot the first 9 images.
plot_images(images=images[0:9],
cls_true=cls_true[0:9],
cls_pred=cls_pred[0:9])
复制代码
def plot_confusion_matrix(cls_pred):
# This is called from print_test_accuracy() below.
# cls_pred is an array of the predicted class-number for
# all images in the test-set.
# Get the true classifications for the test-set.
cls_true = data.y_test_cls
# Get the confusion matrix using sklearn.
cm = confusion_matrix(y_true=cls_true,
y_pred=cls_pred)
# Print the confusion matrix as text.
print(cm)
# Plot the confusion matrix as an image.
plt.matshow(cm)
# Make various adjustments to the plot.
plt.colorbar()
tick_marks = np.arange(num_classes)
plt.xticks(tick_marks, range(num_classes))
plt.yticks(tick_marks, range(num_classes))
plt.xlabel('Predicted')
plt.ylabel('True')
# Ensure the plot is shown correctly with multiple plots
# in a single Notebook cell.
plt.show()
复制代码
# Split the test-set into smaller batches of this size.
test_batch_size = 256
def print_test_accuracy(show_example_errors=False,
show_confusion_matrix=False):
# Number of images in the test-set.
num_test = data.num_test
# Allocate an array for the predicted classes which
# will be calculated in batches and filled into this array.
cls_pred = np.zeros(shape=num_test, dtype=np.int)
# Now calculate the predicted classes for the batches.
# We will just iterate through all the batches.
# There might be a more clever and Pythonic way of doing this.
# The starting index for the next batch is denoted i.
i = 0
while i < num_test:
# The ending index for the next batch is denoted j.
j = min(i + test_batch_size, num_test)
# Get the images from the test-set between index i and j.
images = data.x_test[i:j, :]
# Get the associated labels.
labels = data.y_test[i:j, :]
# Create a feed-dict with these images and labels.
feed_dict = {x: images,
y_true: labels}
# Calculate the predicted class using TensorFlow.
cls_pred[i:j] = session.run(y_pred_cls, feed_dict=feed_dict)
# Set the start-index for the next batch to the
# end-index of the current batch.
i = j
# Convenience variable for the true class-numbers of the test-set.
cls_true = data.y_test_cls
# Create a boolean array whether each image is correctly classified.
correct = (cls_true == cls_pred)
# Calculate the number of correctly classified images.
# When summing a boolean array, False means 0 and True means 1.
correct_sum = correct.sum()
# Classification accuracy is the number of correctly classified
# images divided by the total number of images in the test-set.
acc = float(correct_sum) / num_test
# Print the accuracy.
msg = "Accuracy on Test-Set: {0:.1%} ({1} / {2})"
print(msg.format(acc, correct_sum, num_test))
# Plot some examples of mis-classifications, if desired.
if show_example_errors:
print("Example errors:")
plot_example_errors(cls_pred=cls_pred, correct=correct)
# Plot the confusion matrix, if desired.
if show_confusion_matrix:
print("Confusion Matrix:")
plot_confusion_matrix(cls_pred=cls_pred)
复制代码
print_test_accuracy()
optimize(num_iterations=900)
print_test_accuracy(show_example_errors=True)
复制代码