CNTK - 卷积神经网络

  • 简述

    在本章中,让我们研究如何在 CNTK 中构建卷积神经网络(CNN)。
  • 介绍

    卷积神经网络 (CNN) 也由具有可学习权重和偏差的神经元组成。这就是为什么以这种方式,它们就像普通的神经网络 (NN)。
    如果我们回忆一下普通神经网络的工作原理,每个神经元都会接收一个或多个输入,取一个加权和,然后通过一个激活函数来产生最终输出。在这里,问题出现了,如果 CNN 和普通 NN 有这么多相似之处,那么是什么让这两个网络彼此不同呢?
    它们的不同之处在于对输入数据和图层类型的处理?在普通的神经网络中,输入数据的结构被忽略了,所有的数据在输入到网络之前都被转换成一维数组。
    但是,卷积神经网络架构可以考虑图像的 2D 结构,对其进行处理并允许它提取图像特定的属性。此外,CNN 具有一个或多个卷积层和池化层的优势,它们是 CNN 的主要构建块。
    这些层之后是一个或多个完全连接的层,就像在标准多层 NN 中一样。因此,我们可以将 CNN 视为全连接网络的一个特例。
  • 卷积神经网络 (CNN) 架构

    CNN 的架构基本上是将 3 维(即图像体积的宽度、高度和深度)转换为 3 维输出体积的层列表。这里需要注意的一点是,当前层中的每个神经元都连接到前一层输出的一小部分,这就像在输入图像上覆盖一个 N*N 滤波器。
    它使用 M 个过滤器,它们基本上是特征提取器,用于提取边缘、角落等特征。以下是用于构建卷积神经网络 (CNN)的层[INPUT-CONV-RELU-POOL-FC] -
    • INPUT - 顾名思义,该层包含原始像素值。原始像素值表示图像的数据。例如,INPUT [64×64×3] 是宽度为 64、高度为 64 和深度为 3 的 3 通道 RGB 图像。
    • CONV - 这一层是 CNN 的构建块之一,因为大部分计算都是在这一层完成的。示例 - 如果我们在上述 INPUT [64×64×3] 上使用 6 个过滤器,这可能会导致体积为 [64×64×6]。
    • RELU - 也称为整流线性单元层,将激活函数应用于前一层的输出。以其他方式,非线性将通过 RELU 添加到网络中。
    • POOL - 这一层,即池化层是 CNN 的另一个构建块。该层的主要任务是下采样,这意味着它在输入的每个切片上独立操作并在空间上调整其大小。
    • FC - 它被称为全连接层或更具体地说是输出层。它用于计算输出类分数,结果输出是大小为 1*1* L的体积, 其中 L 是对应于类分数的数字。
    下图代表了 CNN 的典型架构 -
    CNN架构
  • 创建 CNN 结构

    我们已经了解了 CNN 的架构和基础知识,现在我们将使用 CNTK 构建卷积网络。在这里,我们将首先看看如何组合 CNN 的结构,然后再看看如何训练它的参数。
    最后我们将看到,我们如何通过使用各种不同的层设置改变其结构来改进神经网络。我们将使用 MNIST 图像数据集。
    所以,首先让我们创建一个 CNN 结构。通常,当我们构建用于识别图像模式的 CNN 时,我们会执行以下操作 -
    • 我们使用卷积层和池化层的组合。
    • 网络末端的一个或多个隐藏层。
    • 最后,我们使用 softmax 层完成网络以进行分类。
    借助以下步骤,我们可以构建网络结构 -
    第 1 步- 首先,我们需要为 CNN 导入所需的层。
    
    
    from cntk.layers import Convolution2D, Sequential, Dense, MaxPooling
    
    
    第 2 步- 接下来,我们需要导入 CNN 的激活函数。
    
    
    from cntk.ops import log_softmax, relu
    
    
    第 3 步- 之后为了稍后初始化卷积层,我们需要导入glorot_uniform_initializer,如下所示 -
    
    
    from cntk.initializer import glorot_uniform
    
    
    第 4 步- 接下来,要创建输入变量,请导入input_variable函数。并导入default_option函数,使 NN 的配置更容易一些。
    
    
    from cntk import input_variable, default_options
    
    
    第 5 步- 现在要存储输入图像,创建一个新的input_variable。它将包含三个通道,即红色、绿色和蓝色。它的大小为 28 x 28 像素。
    
    
    features = input_variable((3,28,28))
    
    
    第 6 步- 接下来,我们需要创建另一个input_variable来存储要预测的标签。
    
    
    labels = input_variable(10)
    
    
    第 7 步- 现在,我们需要为 NN 创建default_option。而且,我们需要使用glorot_uniform作为初始化函数。
    
    
    with default_options(initialization=glorot_uniform, activation=relu):
    
    
    第 8 步 - 接下来,为了设置 NN 的结构,我们需要创建一个新的Sequential层集。
    第 9 步 - 现在我们需要在Sequential层集中添加一个filter_shape为 5 和strides设置为1的Convolutional2D层。此外,启用填充,以便对图像进行填充以保留原始尺寸。
    
    
    model = Sequential([
    
    Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=8, pad=True),
    
    
    第 10 步- 现在是时候添加一个MaxPooling层,filter_shape为 2,步幅设置为 2 以将图像压缩一半。
    
    
    MaxPooling(filter_shape=(2,2), strides=(2,2)),
    
    
    第 11 步- 现在,正如我们在第 9 步中所做的那样,我们需要添加另一个Convolutional2D层,其filter_shape为 5,步幅设置为 1,使用 16 个过滤器。此外,启用填充,以便保留前一个池化层生成的图像的大小。
    
    
    Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=16, pad=True),
    
    
    第 12 步- 现在,就像我们在第 10 步中所做的那样,添加另一个MaxPooling层,其filter_shape为 3,步幅设置为 3,以将图像缩小到三分之一。
    
    
    MaxPooling(filter_shape=(3,3), strides=(3,3)),
    
    
    第 13 步- 最后,为 10 个可能的类别添加一个具有 10 个神经元的 Dense 层,网络可以预测。为了将网络变成分类模型,请使用log_siftmax激活函数。
    
    
    Dense(10, activation=log_softmax)
    
    ])
    
    

    创建 CNN 结构的完整示例

    
    
    from cntk.layers import Convolution2D, Sequential, Dense, MaxPooling
    
    from cntk.ops import log_softmax, relu
    
    from cntk.initializer import glorot_uniform
    
    from cntk import input_variable, default_options
    
    features = input_variable((3,28,28))
    
    labels = input_variable(10)
    
    with default_options(initialization=glorot_uniform, activation=relu):
    
    model = Sequential([
    
       Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=8, pad=True),
    
    MaxPooling(filter_shape=(2,2), strides=(2,2)),
    
       Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=16, pad=True),
    
    MaxPooling(filter_shape=(3,3), strides=(3,3)),
    
    Dense(10, activation=log_softmax)
    
    ])
    
    z = model(features)
    
    
  • 用图像训练 CNN

    由于我们已经创建了网络的结构,是时候训练网络了。但在开始训练我们的网络之前,我们需要设置小批量源,因为训练一个处理图像的 NN 需要比大多数计算机更多的内存。
    我们已经在前面的部分中创建了小批量源。以下是设置两个小批量源的 Python 代码 -
    由于我们有create_datasource函数,我们现在可以创建两个单独的数据源(训练和测试一个)来训练模型。
    
    
    train_datasource = create_datasource('mnist_train')
    
    test_datasource = create_datasource('mnist_test', max_sweeps=1, train=False)
    
    
    现在,当我们准备好图像后,我们可以开始训练我们的 NN。正如我们在前几节中所做的那样,我们可以在损失函数上使用 train 方法来开始训练。以下是此代码 -
    
    
    from cntk import Function
    
    from cntk.losses import cross_entropy_with_softmax
    
    from cntk.metrics import classification_error
    
    from cntk.learners import sgd
    
    @Function
    
    def criterion_factory(output, targets):
    
    loss = cross_entropy_with_softmax(output, targets)
    
    metric = classification_error(output, targets)
    
    return loss, metric
    
    loss = criterion_factory(z, labels)
    
    learner = sgd(z.parameters, lr=0.2)
    
    
    在前面代码的帮助下,我们为 NN 设置了损失和学习器。以下代码将训练和验证 NN-
    
    
    from cntk.logging import ProgressPrinter
    
    from cntk.train import TestConfig
    
    progress_writer = ProgressPrinter(0)
    
    test_config = TestConfig(test_datasource)
    
    input_map = {
    
       features: train_datasource.streams.features,
    
       labels: train_datasource.streams.labels
    
    }
    
    loss.train(train_datasource,
    
         max_epochs=10,
    
         minibatch_size=64,
    
         epoch_size=60000,
    
            parameter_learners=[learner],
    
         model_inputs_to_streams=input_map,
    
         callbacks=[progress_writer, test_config])
    
    

    完整的实现示例

    
    
    from cntk.layers import Convolution2D, Sequential, Dense, MaxPooling
    
    from cntk.ops import log_softmax, relu
    
    from cntk.initializer import glorot_uniform
    
    from cntk import input_variable, default_options
    
    features = input_variable((3,28,28))
    
    labels = input_variable(10)
    
    with default_options(initialization=glorot_uniform, activation=relu):
    
    model = Sequential([
    
       Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=8, pad=True),
    
    MaxPooling(filter_shape=(2,2), strides=(2,2)),
    
       Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=16, pad=True),
    
    MaxPooling(filter_shape=(3,3), strides=(3,3)),
    
    Dense(10, activation=log_softmax)
    
    ])
    
    z = model(features)
    
    import os
    
    from cntk.io import MinibatchSource, StreamDef, StreamDefs, ImageDeserializer, INFINITELY_REPEAT
    
    import cntk.io.transforms as xforms
    
    def create_datasource(folder, train=True, max_sweeps=INFINITELY_REPEAT):
    
       mapping_file = os.path.join(folder, 'mapping.bin')
    
       image_transforms = []
    
       if train:
    
        image_transforms += [
    
         xforms.crop(crop_type='randomside', side_ratio=0.8),
    
         xforms.scale(width=28, height=28, channels=3, interpolations='linear')
    
    ]
    
       stream_definitions = StreamDefs(
    
       features=StreamDef(field='image', transforms=image_transforms),
    
        labels=StreamDef(field='label', shape=10)
    
    )
    
       deserializer = ImageDeserializer(mapping_file, stream_definitions)
    
    return MinibatchSource(deserializer, max_sweeps=max_sweeps)
    
    train_datasource = create_datasource('mnist_train')
    
    test_datasource = create_datasource('mnist_test', max_sweeps=1, train=False)
    
    from cntk import Function
    
    from cntk.losses import cross_entropy_with_softmax
    
    from cntk.metrics import classification_error
    
    from cntk.learners import sgd
    
    @Function
    
    def criterion_factory(output, targets):
    
       loss = cross_entropy_with_softmax(output, targets)
    
       metric = classification_error(output, targets)
    
    return loss, metric
    
    loss = criterion_factory(z, labels)
    
    learner = sgd(z.parameters, lr=0.2)
    
    from cntk.logging import ProgressPrinter
    
    from cntk.train import TestConfig
    
    progress_writer = ProgressPrinter(0)
    
    test_config = TestConfig(test_datasource)
    
    input_map = {
    
       features: train_datasource.streams.features,
    
       labels: train_datasource.streams.labels
    
    }
    
    loss.train(train_datasource,
    
         max_epochs=10,
    
         minibatch_size=64,
    
         epoch_size=60000,
    
            parameter_learners=[learner],
    
         model_inputs_to_streams=input_map,
    
         callbacks=[progress_writer, test_config])
    
    

    输出

    
    
    -------------------------------------------------------------------
    
    average  since  average  since  examples
    
    loss     last   metric   last
    
    ------------------------------------------------------
    
    Learning rate per minibatch: 0.2
    
    142      142      0.922   0.922    64
    
    1.35e+06 1.51e+07 0.896   0.883    192
    
    [………]
    
    
  • 图像转换

    正如我们所见,训练用于图像识别的神经网络是很困难的,而且它们也需要大量的数据来训练。另一个问题是,它们倾向于过度拟合训练期间使用的图像。让我们看一个例子,当我们有一张直立的人脸照片时,我们的模型将很难识别向另一个方向旋转的人脸。
    为了克服这个问题,我们可以在为图像创建小批量源时使用图像增强,并且 CNTK 支持特定的变换。我们可以使用如下几种转换 -
    • 我们只需几行代码就可以随机裁剪用于训练的图像。
    • 我们也可以使用比例和颜色。
    让我们在下面的 Python 代码的帮助下看看,我们如何通过在之前用于创建小批量源的函数中包含裁剪转换来更改转换列表。
    
    
    import os
    
    from cntk.io import MinibatchSource, StreamDef, StreamDefs, ImageDeserializer, INFINITELY_REPEAT
    
    import cntk.io.transforms as xforms
    
    def create_datasource(folder, train=True, max_sweeps=INFINITELY_REPEAT):
    
       mapping_file = os.path.join(folder, 'mapping.bin')
    
       image_transforms = []
    
       if train:
    
       image_transforms += [
    
         xforms.crop(crop_type='randomside', side_ratio=0.8),
    
    xforms.scale(width=28, height=28, channels=3, interpolations='linear')
    
    ]
    
       stream_definitions = StreamDefs(
    
       features=StreamDef(field='image', transforms=image_transforms),
    
    labels=StreamDef(field='label', shape=10)
    
    )
    
       deserializer = ImageDeserializer(mapping_file, stream_definitions)
    
    return MinibatchSource(deserializer, max_sweeps=max_sweeps)
    
    
    借助上面的代码,我们可以增强功能以​​包含一组图像变换,这样,当我们训练时,我们可以随机裁剪图像,从而获得更多的图像变化。