CNTK - 卷积神经网络



在本章中,我们将学习如何在 CNTK 中构建卷积神经网络 (CNN)。

介绍

卷积神经网络 (CNN) 也由具有可学习权重和偏差的神经元组成。因此,它们与普通神经网络 (NN) 类似。

如果我们回想一下普通神经网络的工作原理,每个神经元都会接收一个或多个输入,取加权和,并通过激活函数产生最终输出。这里的问题是,如果 CNN 和普通 NN 有如此多的相似之处,那么是什么让这两个网络彼此不同呢?

使它们不同的原因在于输入数据的处理和层类型?普通 NN 会忽略输入数据的结构,并在将其馈送到网络之前将其转换为一维数组。

但是,卷积神经网络架构可以考虑图像的二维结构,处理它们并允许它提取特定于图像的属性。此外,CNN 具有一个或多个卷积层和池化层的优势,它们是 CNN 的主要构建块。

这些层之后是一个或多个全连接层,就像标准的多层神经网络一样。因此,我们可以将 CNN 视为全连接网络的一个特例。

卷积神经网络 (CNN) 架构

CNN 的架构基本上是一个层列表,它将图像体积的三维(即宽度、高度和深度)转换为三维输出体积。这里需要注意的一点是,当前层中的每个神经元都连接到前一层输出的一小块区域,这就像将 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 的典型架构:

Cnns Architecture

创建 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** - 现在我们需要添加一个 **Convolutional2D** 层,其 **filter_shape** 为 5,**strides** 设置为 **1**,在 **Sequential** 层集中。此外,启用填充,以便填充图像以保留原始尺寸。

model = Sequential([
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=8, pad=True),

**步骤 10** - 现在是时候添加一个 **MaxPooling** 层,其 **filter_shape** 为 2,**strides** 设置为 2,以将图像压缩一半。

MaxPooling(filter_shape=(2,2), strides=(2,2)),

**步骤 11** - 现在,就像我们在步骤 9 中所做的那样,我们需要添加另一个 **Convolutional2D** 层,其 **filter_shape** 为 5,**strides** 设置为 1,使用 16 个滤波器。此外,启用填充,以便保留前一层池化层生成的图像的大小。

Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=16, pad=True),

**步骤 12** - 现在,就像我们在步骤 10 中所做的那样,添加另一个 **MaxPooling** 层,其 **filter_shape** 为 3,**strides** 设置为 3,以将图像缩小到三分之一。

MaxPooling(filter_shape=(3,3), strides=(3,3)),

**步骤 13** - 最后,为 10 个可能的类别添加一个具有十个神经元的密集层,网络可以预测。为了将网络变成分类模型,请使用 **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

由于我们已经创建了网络的结构,因此是时候训练网络了。但在开始训练我们的网络之前,我们需要设置小批量源,因为训练使用图像的神经网络所需的内存比大多数计算机拥有的内存要多。

我们已经在前面的章节中创建了小批量源。以下是设置两个小批量源的 Python 代码:

由于我们已经有了 **create_datasource** 函数,我们现在可以创建两个单独的数据源(一个用于训练,一个用于测试)来训练模型。

train_datasource = create_datasource('mnist_train')
test_datasource = create_datasource('mnist_test', max_sweeps=1, train=False)

现在,我们已经准备好了图像,我们可以开始训练我们的神经网络了。就像我们在前面的章节中所做的那样,我们可以对损失函数使用 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)

借助之前的代码,我们已经为神经网络设置了损失和学习器。以下代码将训练和验证神经网络:

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)

借助上述代码,我们可以增强该函数以包含一组图像转换,以便在进行训练时,我们可以随机裁剪图像,从而获得更多图像变化。

广告