<jupyter_start><jupyter_text>TensorFlow 教程 02 卷积神经网络by [<NAME>](http://www.hvass-labs.org/)/ [GitHub](https://github.com/Hvass-Labs/TensorFlow-Tutorials) / [Videos on YouTube](https://www.youtube.com/playlist?list=PL9Hr9sNUjfsmEu1ZniY0XpHSzl5uihcXZ)中文翻译 [thrillerist](https://zhuanlan.zhihu.com/insight-pixel)/[Github](https://github.com/thrillerist/TensorFlow-Tutorials)中文修订[ZhouGeorge](https://github.com/ZhouGeorge)/[GitHub](https://github.com/ZhouGeorge/TensorFlow-Tutorials)  介绍先前的教程展示了一个简单的线性模型，对MNIST数据集中手写数字的识别率达到了91%。在这个教程中，我们会在TensorFlow中实现一个简单的卷积神经网络，它能达到大约99%的分类准确率，如果你做了一些建议的练习，准确率还可能更高。卷积神经网络在一张输入图片上移动一个小的滤波器。这意味着在遍历整张图像来识别模式时，要重复使用这些滤波器。这让卷积神经网络在拥有相同数量的变量时比全连接网络（Fully-Connected）更强大，也让卷积神经网络训练得更快。你应该熟悉基本的线性代数、Python和Jupyter Notebook编辑器。如果你是TensorFlow新手，在本教程之前应该先学习第一篇教程。  流程图 下面的图表直接显示了之后实现的卷积神经网络中数据的传递。<jupyter_code>from IPython.display import Image
Image('images/02_network_flowchart.png')<jupyter_output><empty_output><jupyter_text>输入图像在第一层卷积层里使用权重过滤器处理。结果在16张新图里，每张代表了卷积层里一个过滤器（的处理结果）。图像经过降采样，分辨率从28x28减少到14x14。16张小图在第二个卷积层中处理。这16个通道以及这层输出的每个通道都需要一个过滤权重。总共有36个输出，所以在第二个卷积层有16 x 36 = 576个滤波器。输出图再一次降采样到7x7个像素。第二个卷积层的输出是36张7x7像素的图像。它们被转换到一个长为7 x 7 x 36 = 1764的向量中去，它作为一个有128个神经元（或元素）的全连接网络的输入。这些又输入到另一个有10个神经元的全连接层中，每个神经元代表一个类别，用来确定图像的类别，即图像上的数字。卷积滤波一开始是随机挑选的，因此分类也是随机完成的。根据交叉熵（cross-entropy）来测量输入图预测值和真实类别间的错误。然后优化器用链式法则自动地将这个误差在卷积网络中传递，更新滤波权重来提升分类质量。这个过程迭代了几千次，直到分类误差足够低。这些特定的滤波权重和中间图像是一个优化结果，和你执行代码所看到的可能会有所不同。注意，这些在TensorFlow上的计算是在一部分图像上执行，而非单独的一张图，这使得计算更有效。也意味着在TensorFlow上实现时，这个流程图实际上会有更多的数据维度。  卷积层 下面的图片展示了在第一个卷积层中处理图像的基本思想。输入图片描绘了数字7，这里显示了它的四张拷贝，我们可以很清晰的看到滤波器是如何在图像的不同位置移动。在滤波器的每个位置上，计算滤波器以及滤波器下方图像像素的点乘，得到输出图像的一个像素。因此，在整张输入图像上移动时，会有一张新的图像生成。红色的滤波权重表示滤波器对输入图的黑色像素有正响应，蓝色的代表有负响应。在这个例子中，很明显这个滤波器识别数字7的水平线段，在输出图中可以看到它对线段的强烈响应。<jupyter_code>Image('images/02_convolution.png')<jupyter_output><empty_output><jupyter_text>滤波器遍历输入图的移动步长称为stride。在水平和竖直方向各有一个stride。在下面的源码中，两个方向的stride都设为1，这说明滤波器从输入图像的左上角开始，下一步移动到右边1个像素去。当滤波器到达图像的右边时，它会返回最左边，然后向下移动1个像素。持续这个过程，直到滤波器到达输入图像的右下角，同时，也生成了整张输出图片。当滤波器到达输入图的右端或底部时，它会用零（白色像素）来填充。因为输出图要和输入图一样大。此外，卷积层的输出可能会传递给修正线性单元（ReLU），它用来保证输出是正值，将负值置为零。输出还会用最大池化(max-pooling)进行降采样，它使用了2x2的小窗口，只保留像素中的最大值。这让输入图分辨率减小一半，比如从28x28到14x14。第二个卷积层更加复杂，因为它有16个输入通道。我们想给每个通道一个单独的滤波，因此需要16个。另外，我们想从第二个卷积层得到36个输出，因此总共需要16 x 36 = 576个滤波器。要理解这些如何工作可能有些困难。  导入<jupyter_code>%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<jupyter_output><empty_output><jupyter_text>使用Python3.5.2（Anaconda）开发，TensorFlow版本是：<jupyter_code>tf.__version__<jupyter_output><empty_output><jupyter_text>神经网络的配置方便起见，在这里定义神经网络的配置，你可以很容易找到或改变这些数值，然后重新运行Notebook。<jupyter_code>#卷积层1
filter_size1 = 5          # 5X5像素的卷积滤波器
num_filters1 = 16         # 一共有16个滤波器

#卷积层2
filter_size2 = 5          # 5X5像素的卷积滤波器
num_filters2 = 36         # 一共有36个滤波器

#全连接层
fc_size = 128             # 全连接层的神经元个数<jupyter_output><empty_output><jupyter_text>载入数据 MNIST数据集大约12MB，如果没在文件夹中找到就会自动下载。<jupyter_code>from tensorflow.examples.tutorials.mnist import input_data
data = input_data.read_data_sets('data/MNIST/', one_hot=True)<jupyter_output>Extracting data/MNIST/train-images-idx3-ubyte.gz
Extracting data/MNIST/train-labels-idx1-ubyte.gz
Extracting data/MNIST/t10k-images-idx3-ubyte.gz
Extracting data/MNIST/t10k-labels-idx1-ubyte.gz<jupyter_text>现在已经载入了MNIST数据集，它由70,000张图像和对应的标签（比如图像的类别）组成。数据集分成三份互相独立的子集。我们在教程中只用训练集和测试集。<jupyter_code>print("Size of:")
print("- Training-set:\t\t{}".format(len(data.train.labels)))
print("- Test-set:\t\t{}".format(len(data.test.labels)))
print("- Validation-set:\t{}".format(len(data.validation.labels)))<jupyter_output>Size of:
- Training-set:		55000
- Test-set:		10000
- Validation-set:	5000<jupyter_text>类型标签使用One-Hot编码，这意外每个标签是长为10的向量，除了一个元素之外，其他的都为零。这个元素的索引就是类别的数字，即相应图片中画的数字。我们也需要测试数据集类别数字的整型值，用下面的方法来计算。<jupyter_code>data.test.cls = np.argmax(data.test.labels, axis=1)<jupyter_output><empty_output><jupyter_text>数据维度 在下面的源码中，有很多地方用到了数据维度。它们只在一个地方定义，因此我们可以在代码中使用这些数字而不是直接写数字。<jupyter_code>#我们知道MNIST图像每个维度有28个像素
img_size = 28

#图片以一维的方式存储
img_size_flat = img_size * img_size
# 确定图片的高和宽用于之后重塑数组
img_shape = (img_size, img_size)

#颜色通道数目：灰度图只有一个通道
num_channels = 1

#类的数量，一类代表0-9中的相应数字
num_classes = 10<jupyter_output><empty_output><jupyter_text>用来绘制图片的帮助函数 这个函数用来在3x3的栅格中画9张图像，然后在每张图像下面写出真实类别和预测类别。<jupyter_code>def plot_images(images, cls_true, cls_pred=None):
    assert len(images) == len(cls_true) == 9
    #创建3x3的子图
    fig, axes = plt.subplots(3, 3)
    fig.subplots_adjust(hspace=0.3, wspace=0.3)

    for i, ax in enumerate(axes.flat):
        #画图
        ax.imshow(images[i].reshape(img_shape), cmap='binary')

        #显示正确和预测的类
        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])

        #在X轴显示正确的标签
        ax.set_xlabel(xlabel)
        
        # Remove ticks from the plot.
        ax.set_xticks([])
        ax.set_yticks([])
    
    #确保图被正确显示在一个单元里
    plt.show()<jupyter_output><empty_output><jupyter_text>绘制几张图像来看看数据是否正确<jupyter_code>#从test-set中获取前10张图
images = data.test.images[0:9]

#获得上图对应的类别
cls_true = data.test.cls[0:9]

#利用我们helper-fuction函数画出图片和标签
plot_images(images=images, cls_true=cls_true)<jupyter_output><empty_output><jupyter_text>TensorFlow图TensorFlow的全部目的就是使用一个称之为计算图（computational graph）的东西，它会比直接在Python中进行相同计算量要高效得多。TensorFlow比Numpy更高效，因为TensorFlow了解整个需要运行的计算图，然而Numpy只知道某个时间点上唯一的数学运算。TensorFlow也能够自动地计算需要优化的变量的梯度，使得模型有更好的表现。这是由于图是简单数学表达式的结合，因此整个图的梯度可以用链式法则推导出来。TensorFlow还能利用多核CPU和GPU，Google也为TensorFlow制造了称为TPUs（Tensor Processing Units）的特殊芯片，它比GPU更快。一个TensorFlow图由下面几个部分组成，后面会详细描述：* 占位符变量（Placeholder）用来改变图的输入。* 模型变量（Model）将会被优化，使得模型表现得更好。* 模型本质上就是一些数学函数，它根据Placeholder和模型的输入变量来计算一些输出。* 一个cost度量用来指导变量的优化。* 一个优化策略会更新模型的变量。另外，TensorFlow图也包含了一些调试状态，比如用TensorBoard打印log数据，本教程不涉及这些。  Helper-functions for creating new variables 创建新变量的帮助函数 函数用来根据给定大小创建TensorFlow变量，并将它们用随机值初始化。需注意的是在此时并未完成初始化工作，仅仅是在TensorFlow图里定义它们。<jupyter_code>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]))<jupyter_output><empty_output><jupyter_text>创建卷积层的帮助函数 这个函数为TensorFlow在计算图里创建了新的卷积层。这里并没有执行什么计算，只是在TensorFlow图里添加了数学公式。假设输入的是四维的张量，各个维度如下：1. 图像数量2. 每张图像的Y轴3. 每张图像的X轴4. 每张图像的通道数输入通道可能是彩色通道，当输入是前面的卷积层生成的时候，它也可能是滤波通道。输出是另外一个4通道的张量，如下：1. 图像数量，与输入相同2. 每张图像的Y轴。如果用到了2x2的池化，是输入图像宽高的一半。3. 每张图像的X轴。同上。4. 卷积滤波生成的通道数。<jupyter_code>def new_conv_layer(input,              # 上一层
                   num_input_channels, # 上一层的通道数
                   filter_size,        # 滤波器的尺寸
                   num_filters,        # 滤波器数目
                   use_pooling=True):  # 采用2X2最大池化

    #卷积滤波器参数的形状
    #格式由TensorFlow API决定
    shape = [filter_size, filter_size, num_input_channels, num_filters]

    #对应给定形状给滤波器生成新的权重
    weights = new_weights(shape=shape)
    
    #对每个滤波器生成新的偏置
    biases = new_biases(length=num_filters)

   
    #生成TensorFlow卷积操作，注意步长在所有维度上都被设置为1，
    #第一个和最后一个步长必须设置为1，因为第一个对应图像的数目，最后一个对应输入的通道
    #如果strides=[1, 2, 2, 1]，表示滤波器在图像X和Y轴方向移动2个像素的步长
    #padding被设置成'SAME'表示用0填充边缘，使得输入和输出的尺寸相同。
    #5x5的的滤波器padding的大小为2，(28-5+2*2)/1+1=28所以尺寸不变
    #(i-f+2p)/s+1
    layer = tf.nn.conv2d(input=input,
                         filter=weights,
                         strides=[1, 1, 1, 1],
                         padding='SAME')


    #卷积的结果加上偏置，偏置加在对应滤波通道上
    layer += biases

    #利用池化对图片卷积结果下采样
    if use_pooling:
        #2x2最大池化，表示我们在2x2的窗口中选择最大值，然后移动2个像素到下一个窗口
        layer = tf.nn.max_pool(value=layer,
                               ksize=[1, 2, 2, 1],
                               strides=[1, 2, 2, 1],
                               padding='SAME')

    #线性修正单元(ReLU)
    #对每一个输入的x计算max(x,0)
    #它使得公式中增加了非线性，允许我们去学习更复杂的函数
    layer = tf.nn.relu(layer)

    
    #注意ReLU通常在池化之前执行
    #但是由于relu(max_pool(x)) == max_pool(relu(x))，我们可以先计算
    #最大池化来节省75%的relu计算
    
    #我们返回运算结果层和滤波器权重，因为我们之后画出这些权重
    return layer, weights<jupyter_output><empty_output><jupyter_text>转换一个层的帮助函数卷积层生成了4维的张量。我们会在卷积层之后添加一个全连接层，因此我们需要将这个4维的张量转换成可被全连接层使用的2维张量。<jupyter_code>def flatten_layer(layer):
    #获得输入层的形状
    layer_shape = layer.get_shape()

    #输入层的形状默认为：
    #layer_shape == [num_images, img_height, img_width, num_channels]

    #特征的数目：img_height * img_width * num_channels
    #我们可以利用TensorFlow的函数去计算它
    num_features = layer_shape[1:4].num_elements()
    
    #将该层重塑为[num_images, num_features].
    #注意，我们只设置了第二维的到num_features和第一维到-1的大小(如果第一维的大小设为-1， num_images的大小也会被自动推导出来)
    #这意味着这个维度的大小是经过计算的
    #所以张量的总尺寸并没有改变
    layer_flat = tf.reshape(layer, [-1, num_features])

    #flattened layer层的形状现在是：
    # [num_images, img_height * img_width * num_channels]
    
    #返回flattened layer和特征数
    return layer_flat, num_features<jupyter_output><empty_output><jupyter_text>创建一个全连接层的帮助函数 这个函数为TensorFlow在计算图中创建了一个全连接层。这里也不进行任何计算，只是往TensorFlow图中添加数学公式。输入是大小为`[num_images, num_inputs]`的二维张量。输出是大小为`[num_images, num_outputs]`的2维张量。<jupyter_code>def new_fc_layer(input,          # 上一层
                 num_inputs,     # 上一层输入的数目
                 num_outputs,    # 输出的数目
                 use_relu=True): # 是否使用ReLU？

    #生成新的权重和偏置
    weights = new_weights(shape=[num_inputs, num_outputs])
    biases = new_biases(length=num_outputs)

    #矩阵乘法计算输入和权重，然后加上偏置。
    layer = tf.matmul(input, weights) + biases

    #是否使用ReLU？
    if use_relu:
        layer = tf.nn.relu(layer)

    return layer<jupyter_output><empty_output><jupyter_text>占位符 （Placeholder）变量 Placeholder是作为图的输入，每次我们运行图的时候都可能会改变它们。将这个过程称为feeding placeholder变量，后面将会描述它。首先我们为输入图像定义placeholder变量。这让我们可以改变输入到TensorFlow图中的图像。这也是一个张量（tensor），代表一个多维向量或矩阵。数据类型设置为float32，形状设为`[None, img_size_flat]`，`None`代表tensor可能保存着任意数量的图像，每张图象是一个长度为`img_size_flat`的向量。<jupyter_code>x = tf.placeholder(tf.float32, shape=[None, img_size_flat], name='x')<jupyter_output><empty_output><jupyter_text>卷积层希望`x`被编码为4维张量，因此我们需要将它的形状转换至`[num_images, img_height, img_width, num_channels]`。注意`img_height == img_width == img_size`，如果第一维的大小设为-1， `num_images`的大小也会被自动推导出来。转换运算如下：<jupyter_code>x_image = tf.reshape(x, [-1, img_size, img_size, num_channels])<jupyter_output><empty_output><jupyter_text>接下来我们为输入变量`x`中的图像所对应的真实标签定义placeholder变量。变量的形状是`[None, num_classes]`，这代表着它保存了任意数量的标签，每个标签是长度为`num_classes`的向量，本例中长度为10。<jupyter_code>y_true = tf.placeholder(tf.float32, shape=[None, 10], name='y_true')<jupyter_output><empty_output><jupyter_text>我们也可以为class-number提供一个placeholder，但这里用argmax来计算它。这里只是TensorFlow中的一些操作，没有执行什么运算。<jupyter_code>y_true_cls = tf.argmax(y_true, axis=1)<jupyter_output>WARNING:tensorflow:From <ipython-input-20-4674210f2acc>:1: calling argmax (from tensorflow.python.ops.math_ops) with dimension is deprecated and will be removed in a future version.
Instructions for updating:
Use the `axis` argument instead<jupyter_text>卷积层 1创建第一个卷积层。将`x_image`当作输入，创建`num_filters1`个不同的滤波器，每个滤波器的宽高都与 `filter_size1`相等。最终我们会用2x2的max-pooling将图像降采样，使它的尺寸减半。<jupyter_code>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)<jupyter_output><empty_output><jupyter_text>检查卷积层输出张量的大小。它是（？,14, 14, 16），这代表着有任意数量的图像（？代表数量），每张图像有14个像素的宽和高，有16个不同的通道，每个滤波器各有一个通道。<jupyter_code>layer_conv1<jupyter_output><empty_output><jupyter_text>卷积层 2创建第二个卷积层，它将第一个卷积层的输出作为输入。输入通道的数量对应着第一个卷积层的滤波数。<jupyter_code>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)<jupyter_output><empty_output><jupyter_text>核对一下这个卷积层输出张量的大小。它的大小是（？， 7， 7， 36）,其中？也代表着任意数量的图像，每张图有7像素的宽高，每个滤波器有36个通道。<jupyter_code>layer_conv2<jupyter_output><empty_output><jupyter_text>转换层这个卷积层输出一个4维张量。现在我们想将它作为一个全连接网络的输入，这就需要将它转换成2维张量。<jupyter_code>layer_flat, num_features = flatten_layer(layer_conv2)<jupyter_output><empty_output><jupyter_text>这个张量的大小是（？， 1764），意味着共有一定数量的图像，每张图像被转换成长为1764的向量。其中1764 = 7 x 7 x 36。<jupyter_code>layer_flat
num_features<jupyter_output><empty_output><jupyter_text>全连接层 1往网络中添加一个全连接层。输入是一个前面卷积得到的被转换过的层。全连接层中的神经元或节点数为`fc_size`。我们可以用ReLU来学习非线性关系。<jupyter_code>layer_fc1 = new_fc_layer(input=layer_flat,
                         num_inputs=num_features,
                         num_outputs=fc_size,
                         use_relu=True)<jupyter_output><empty_output><jupyter_text>全连接层的输出是一个大小为（？，128）的张量，？代表着一定数量的图像，并且`fc_size` == 128。<jupyter_code>layer_fc1<jupyter_output><empty_output><jupyter_text>全连接层 2添加另外一个全连接层，它的输出是一个长度为10的向量，它确定了输入图是属于哪个类别。这层并没有用到ReLU。<jupyter_code>layer_fc2 = new_fc_layer(input=layer_fc1,
                         num_inputs=fc_size,
                         num_outputs=num_classes,
                         use_relu=False)
layer_fc2<jupyter_output><empty_output><jupyter_text>预测类别 第二个全连接层估算了输入图有多大的可能属于10个类别中的其中一个。然而，这是很粗略的估计并且很难解释，因为数值可能很小或很大，因此我们会对它们做归一化，将每个元素限制在0到1之间，并且相加为1。这用一个称为softmax的函数来计算的，结果保存在`y_pred`中。<jupyter_code>y_pred = tf.nn.softmax(layer_fc2)<jupyter_output><empty_output><jupyter_text>类别数字是最大元素的索引。<jupyter_code>y_pred_cls = tf.argmax(y_pred, dimension=1)<jupyter_output><empty_output><jupyter_text>优化损失函数 为了使模型更好地对输入图像进行分类，我们必须改变`weights`和`biases`变量。首先我们需要对比模型`y_pred`的预测输出和期望输出的`y_true`，来了解目前模型的性能如何。交叉熵（cross-entropy）是在分类中使用的性能度量。交叉熵是一个常为正值的连续函数，如果模型的预测值精准地符合期望的输出，它就等于零。因此，优化的目的就是通过改变网络层的变量来最小化交叉熵。TensorFlow有一个内置的计算交叉熵的函数。这个函数内部计算了softmax，所以我们要用`layer_fc2`的输出而非直接用`y_pred`,因为`y_pred`上已经计算了softmax。<jupyter_code>cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(logits=layer_fc2,
                                                        labels=y_true)<jupyter_output>WARNING:tensorflow:From <ipython-input-34-c6320b202c3b>:2: softmax_cross_entropy_with_logits (from tensorflow.python.ops.nn_ops) is deprecated and will be removed in a future version.
Instructions for updating:

Future major versions of TensorFlow will allow gradients to flow
into the labels input on backprop by default.

See tf.nn.softmax_cross_entropy_with_logits_v2.<jupyter_text>我们为每个图像分类计算了交叉熵，所以有一个当前模型在每张图上表现的度量。但是为了用交叉熵来指导模型变量的优化，我们需要一个额外的标量值，因此简单地利用所有图像分类交叉熵的均值。<jupyter_code>cost = tf.reduce_mean(cross_entropy)<jupyter_output><empty_output><jupyter_text>优化方法 既然我们有一个需要被最小化的损失度量，接着就可以建立优化一个优化器。这个例子中，我们使用的是梯度下降的变体`AdamOptimizer`。优化过程并不是在这里执行。实际上，还没计算任何东西，我们只是往TensorFlow图中添加了优化器，以便之后的操作。<jupyter_code>optimizer = tf.train.AdamOptimizer(learning_rate=1e-4).minimize(cost)<jupyter_output><empty_output><jupyter_text>性能度量 我们需要另外一些性能度量，来向用户展示这个过程。这是一个布尔值向量，代表预测类型是否等于每张图片的真实类型。<jupyter_code>correct_prediction = tf.equal(y_pred_cls, y_true_cls)<jupyter_output><empty_output><jupyter_text>上面的计算先将布尔值向量类型转换成浮点型向量，这样子False就变成0，True变成1，然后计算这些值的平均数，以此来计算分类的准确度。<jupyter_code>accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))<jupyter_output><empty_output><jupyter_text>运行TensorFlow  创建TensorFlow会话（session）一旦创建了TensorFlow图，我们需要创建一个TensorFlow会话，用来运行图。<jupyter_code>session = tf.Session()<jupyter_output><empty_output><jupyter_text>初始化变量我们需要在开始优化weights和biases变量之前对它们进行初始化。<jupyter_code>session.run(tf.global_variables_initializer())<jupyter_output><empty_output><jupyter_text>用来优化迭代的帮助函数 在训练集中有50,000张图。用这些图像计算模型的梯度会花很多时间。因此我们利用随机梯度下降的方法，它在优化器的每次迭代里只用到了一小部分的图像。如果内存耗尽导致电脑死机或变得很慢，你应该试着减少这些数量，但同时可能还需要更优化的迭代。<jupyter_code>train_batch_size = 64<jupyter_output><empty_output><jupyter_text>函数执行了多次的优化迭代来逐步地提升网络层的变量。在每次迭代中，从训练集中选择一批新的数据，然后TensorFlow用这些训练样本来执行优化器。每100次迭代会打印出相关信息。<jupyter_code>#总的迭代数
total_iterations = 0

def optimize(num_iterations):
    #确保我们更新的是全局变量而不是它的复制
    global total_iterations

    #用于打印时间使用的起始时间
    start_time = time.time()

    for i in range(total_iterations,
                   total_iterations + num_iterations):

        #获取一批训练数据
        #x_batch 当前批次的图片y_true 是对应的正确标签
        x_batch, y_true_batch = data.train.next_batch(train_batch_size)

        # 给数据设置一个路径，为了将数据喂入TensorFlow图中的placeholder variables
        feed_dict_train = {x: x_batch,
                           y_true: y_true_batch}

        # 利用这批训练数据运行优化器
        #TensorFlow将feed_dict_train中变量配置到placeholder variables中，然后运行优化器
        session.run(optimizer, feed_dict=feed_dict_train)

        #每100次迭代打印一次状态
        if i % 100 == 0:
            #计算training-set的正确率
            acc = session.run(accuracy, feed_dict=feed_dict_train)

            #要打印的信息
            msg = "Optimization Iteration: {0:>6}, Training Accuracy: {1:>6.1%}"

            # Print it.
            print(msg.format(i + 1, acc))

    #更新总的迭代次数
    total_iterations += num_iterations

    #结束时间
    end_time = time.time()

    #计算起始和结束的时间差
    time_dif = end_time - start_time

    #打印使用的时间
    print("Time usage: " + str(timedelta(seconds=int(round(time_dif)))))<jupyter_output><empty_output><jupyter_text>用来绘制错误样本的帮助函数 函数用来绘制测试集中被误分类的样本。<jupyter_code>def plot_example_errors(cls_pred, correct):
    #这个函数在print_test_accuracy()之后被调用
    #cls_pred是在test-set中所有图片预测类别的数组
    #correct是test-set中每个图片被预测正确与否的布尔数组
    
    # 反转布尔数组
    incorrect = (correct == False)
    
     #从test-set中获取被错误分类的图片
    images = data.test.images[incorrect]
    
    #获得这些被错误预测图片被预测的类
    cls_pred = cls_pred[incorrect]

   #获得这些图片正确的类
    cls_true = data.test.cls[incorrect]
    
     #画出前9张图
    plot_images(images=images[0:9],
                cls_true=cls_true[0:9],
                cls_pred=cls_pred[0:9])<jupyter_output><empty_output><jupyter_text>绘制混淆（confusion）矩阵的帮助函数<jupyter_code>def plot_confusion_matrix(cls_pred):
    #这个函数在print_test_accuracy()之后被调用
     #cls_pred是在test-set中所有图片预测类别的数组
    

    #从test-set中获得正确的类别
    cls_true = data.test.cls
    
    #利用sklearn获得混淆矩阵
    cm = confusion_matrix(y_true=cls_true,
                          y_pred=cls_pred)

    #打印文本形混淆矩阵
    print(cm)

    #画出图形混淆矩阵
    plt.matshow(cm)

    #对画图一些细节调整
    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()<jupyter_output><empty_output><jupyter_text>展示性能的帮助函数 函数用来打印测试集上的分类准确度。为测试集上的所有图片计算分类会花费一段时间，因此我们直接用这个函数来调用上面的结果，这样就不用每次都重新计算了。这个函数可能会占用很多电脑内存，这也是为什么将测试集分成更小的几个部分。如果你的电脑内存比较小或死机了，就要试着降低batch-size。<jupyter_code>#将test-set划分成小的指定大小的批
test_batch_size = 256

def print_test_accuracy(show_example_errors=False,
                        show_confusion_matrix=False):

    #test-set中图片的数目
    num_test = len(data.test.images)

    #为所预测的类分配一个数组
    #将分批计算并填充到这个数组中。
    cls_pred = np.zeros(shape=num_test, dtype=np.int)

    #现在计算成批的预测类，我们将对所有批次进行迭代
    #可能会有一种更聪明和更Python的方法来做这个任务
    
    #下一批的起始索引是i
    i = 0

    while i < num_test:
        #下一批的结束索引是j
        j = min(i + test_batch_size, num_test)

        #从test-set获取图像检索从i到j
        images = data.test.images[i:j, :]

        #获得相应的标签
        labels = data.test.labels[i:j, :]

        #用这些图片和标签创建一个feed-dict
        feed_dict = {x: images,
                     y_true: labels}

        #用TensorFlow计算预测的类别
        cls_pred[i:j] = session.run(y_pred_cls, feed_dict=feed_dict)

        # 将下一批的开始索引设置为当前批的最终索引。
        i = j

    #test-set中正确类别
    cls_true = data.test.cls

    #创建一个布尔数组，每个图片是否正确分类
    correct = (cls_true == cls_pred)

    #计算正确分类图像的数量。
    #当对布尔数组求和时，False表示0和True表示着1。
    correct_sum = correct.sum()

    #分类精度是正确分类的图像数量除以测试集的总图像数
    acc = float(correct_sum) / num_test

    #打印正确率
    msg = "Accuracy on Test-Set: {0:.1%} ({1} / {2})"
    print(msg.format(acc, correct_sum, num_test))

    # 如果需要，绘制一些错误分类的例子
    if show_example_errors:
        print("Example errors:")
        plot_example_errors(cls_pred=cls_pred, correct=correct)

    # 如果需要，绘制混淆矩阵
    if show_confusion_matrix:
        print("Confusion Matrix:")
        plot_confusion_matrix(cls_pred=cls_pred)<jupyter_output><empty_output><jupyter_text>优化之前的性能测试集上的准确度很低，这是由于模型只做了初始化，并没做任何优化，所以它只是对图像做随机分类。<jupyter_code>print_test_accuracy()<jupyter_output>Accuracy on Test-Set: 12.8% (1276 / 10000)<jupyter_text>1次迭代后的性能做了一次优化后，此时优化器的学习率很低，性能其实并没有多大提升。<jupyter_code>optimize(num_iterations=1)
print_test_accuracy()<jupyter_output>Accuracy on Test-Set: 12.0% (1198 / 10000)<jupyter_text>100次迭代优化后的性能100次优化迭代之后，模型显著地提升了分类的准确度。<jupyter_code>optimize(num_iterations=99) # 我们之前已经迭代一次
print_test_accuracy(show_example_errors=True)<jupyter_output>Accuracy on Test-Set: 68.7% (6865 / 10000)
Example errors:<jupyter_text>1000次优化迭代后的性能1000次优化迭代之后，模型在测试集上的准确度超过了90%。<jupyter_code>optimize(num_iterations=900) # 我们之前已经迭代100次
print_test_accuracy(show_example_errors=True)<jupyter_output>Accuracy on Test-Set: 93.1% (9313 / 10000)
Example errors:<jupyter_text>10,000次优化迭代后的性能经过10,000次优化迭代后，测试集上的分类准确率高达99%。<jupyter_code>optimize(num_iterations=9000) # 我们之前已经迭代1000次
print_test_accuracy(show_example_errors=True,
                    show_confusion_matrix=True)<jupyter_output>Accuracy on Test-Set: 98.6% (9864 / 10000)
Example errors:<jupyter_text>权重和层的可视化为了理解为什么卷积神经网络可以识别手写数字，我们将会对卷积滤波和输出图像进行可视化。  绘制卷积权重的帮助函数<jupyter_code>def plot_conv_weights(weights, input_channel=0):
    #假设权重是TensorFlow的4维变量
    #例如 weights_conv1 和 weights_conv2.
    
    #从TensorFlow中取出权重变量的值
    #此处不需要feed-dict，应为没有东西需要计算
    w = session.run(weights)

    #获取权重中最大值和最小值。它们被用作调整图片中权重显示的颜色强度
    #可以显示出不同权重大小之间的比较
    w_min = np.min(w)
    w_max = np.max(w)

    #在卷积层中用的滤波器的数目
    num_filters = w.shape[3]

    #图的网格数
    #对滤波器数目开方并向上取整数
    num_grids = math.ceil(math.sqrt(num_filters))
    
    #按子图的网格数创建图
    fig, axes = plt.subplots(num_grids, num_grids)

    #画出所有滤波器权重
    for i, ax in enumerate(axes.flat):
        #只画出有效的滤波器权重
        if i<num_filters:
            #获取输入通道的第i个过滤器的权重
            #有关于这4维的张量格式，请参照于new_conv_layer()
            img = w[:, :, input_channel, i]

            #画出图像
            ax.imshow(img, vmin=w_min, vmax=w_max,
                      interpolation='nearest', cmap='seismic')
        
        # 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()<jupyter_output><empty_output><jupyter_text>绘制卷积层输出的帮助函数<jupyter_code>def plot_conv_layer(layer, image):
    # 假设该层是TensorFlow操作的输出，由卷积层输出的4维张量
    # 例如layer_conv1 和 layer_conv2.
    
    #创建一个仅包含一个图像的feed-dict
    #注意我们不需要喂入y_true，应为他在计算中没有被用到
    feed_dict = {x: [image]}

    #当输入这张图片时，计算并取出图层的输出值
    values = session.run(layer, feed_dict=feed_dict)

    #在卷积层中用的滤波器的数目
    num_filters = values.shape[3]

    #图的网格数
    #对滤波器数目开方并向上取整数
    num_grids = math.ceil(math.sqrt(num_filters))
    
     #按子图的网格数创建图
    fig, axes = plt.subplots(num_grids, num_grids)

    # Plot the output images of all the filters.
    #画出所有滤波器输出的图像
    for i, ax in enumerate(axes.flat):
        # Only plot the images for valid filters.
        #只画有效的滤波器的图像
        if i<num_filters:
            #获取使用“i”过滤器的输出图像
            #有关于这4维的张量格式，请参照于new_conv_layer()
            img = values[0, :, :, i]

            #画出图像
            ax.imshow(img, interpolation='nearest', cmap='binary')
        
        # 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()<jupyter_output><empty_output><jupyter_text>输入图像 绘制图像的帮助函数<jupyter_code>def plot_image(image):
    plt.imshow(image.reshape(img_shape),
               interpolation='nearest',
               cmap='binary')

    plt.show()<jupyter_output><empty_output><jupyter_text>如下所示，绘制一张测试集中的图像。<jupyter_code>image1 = data.test.images[0]
plot_image(image1)<jupyter_output><empty_output><jupyter_text>绘制测试集里的另一张图像。<jupyter_code>image2 = data.test.images[13]
plot_image(image2)<jupyter_output><empty_output><jupyter_text>卷积层 1 现在绘制第一个卷积层的滤波权重。其中正值权重是红色的，负值为蓝色。<jupyter_code>plot_conv_weights(weights=weights_conv1)<jupyter_output><empty_output><jupyter_text>将这些卷积滤波添加到第一张输入图像，得到以下输出，它们也作为第二个卷积层的输入。注意这些图像被降采样到14 x 14像素，即原始输入图分辨率的一半。<jupyter_code>plot_conv_layer(layer=layer_conv1, image=image1)<jupyter_output><empty_output><jupyter_text>下面是将卷积滤波添加到第二张图像的结果。<jupyter_code>plot_conv_layer(layer=layer_conv1, image=image2)<jupyter_output><empty_output><jupyter_text>从这些图像很难看出卷积滤波的作用是什么。显然，它们生成了输入图像的一些变体，就像光线从不同角度打到图像上并产生阴影一样。  卷积层 2 现在绘制第二个卷积层的滤波权重。第一个卷积层有16个输出通道，代表着第二个卷基层有16个输入。第二个卷积层的每个输入通道也有一些权重滤波。我们先绘制第一个通道的权重滤波。同样的，正值是红色，负值是蓝色。<jupyter_code>plot_conv_weights(weights=weights_conv2, input_channel=0)<jupyter_output><empty_output><jupyter_text>第二个卷积层共有16个输入通道，我们可以同样地画出其他图像。这里我们画出第二个通道的图像。<jupyter_code>plot_conv_weights(weights=weights_conv2, input_channel=1)<jupyter_output><empty_output><jupyter_text>由于这些滤波是高维度的，很难理解它们是如何应用的。给第一个卷积层的输出加上这些滤波，得到下面的图像。这些图像被降采样至7 x 7的像素，即上一个卷积层输出的一半。<jupyter_code>plot_conv_layer(layer=layer_conv2, image=image1)<jupyter_output><empty_output><jupyter_text>这是给第二张图像加上滤波权重的结果。<jupyter_code>plot_conv_layer(layer=layer_conv2, image=image2)<jupyter_output><empty_output><jupyter_text>从这些图像来看，似乎第二个卷积层会检测输入图像中的线段和模式，这对输入图中的局部变化不那么敏感。  关闭TensorFlow会话 现在我们已经用TensorFlow完成了任务，关闭session，释放资源。<jupyter_code># This has been commented out in case you want to modify and experiment
# with the Notebook without having to restart it.
# session.close()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1010132746838426, 'fraction_numerical': 0.02195428481658943, 'mean_word_length': 5.405786163522013, 'pattern_counts': {'":': 0, '<': 238, '<?xml version=': 0, '>': 237, 'https://': 6, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '60', 'dir': 'jupyter-structured-clean-dedup', 'id': '432064', 'license': 'MIT', 'n_tokens_mistral': 14174, 'n_tokens_neox': 15407, 'n_words': 958, 'path': '02_Convolutional_Neural_Network_CN.ipynb', 'repo_name': 'ZhouGeorge/TensorFlow-Tutorials', 'size': '25482'}