📅  最后修改于: 2020-12-10 05:10:50             🧑  作者: Mango
在本章中,让我们研究如何在CNTK中构建卷积神经网络(CNN)。
卷积神经网络(CNN)也由具有可学习的权重和偏差的神经元组成。因此,它们就像普通的神经网络(NN)。
如果我们回想起普通NN的工作原理,每个神经元都会收到一个或多个输入,并进行加权和,然后通过激活函数传递出最终输出。在这里,出现的问题是,如果CNN和普通NN具有如此之多的相似性,那么这两个网络又有什么不同呢?
是什么使它们与众不同的是对输入数据和图层类型的处理?在普通的NN中,输入数据的结构被忽略,所有数据在输入到网络之前都被转换为一维数组。
但是,卷积神经网络体系结构可以考虑图像的2D结构,对其进行处理,并允许其提取特定于图像的属性。此外,CNN的优点是具有一个或多个卷积层和池化层,这是CNN的主要构建块。
在这些层之后是一个或多个完全连接的层,如在标准多层NN中。因此,我们可以将CNN视为全连接网络的特例。
CNN的体系结构基本上是将3维(即图像体积的宽度,高度和深度)转换为3维输出体积的层的列表。这里要注意的重要一点是,当前层中的每个神经元都连接到前一层输出的一小块,就像在输入图像上覆盖N * N滤镜一样。
它使用M个滤镜,这些滤镜基本上是特征提取器,用于提取边缘,拐角等特征。以下是用于构造卷积神经网络(CNN)的[INPUT-CONV-RELU-POOL-FC]层-
输入-顾名思义,该层保存原始像素值。原始像素值表示原样的图像数据。例如,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的体系结构和基础知识,现在我们将使用CNTK构建卷积网络。在这里,我们将首先看到如何将CNN的结构放在一起,然后我们将研究如何训练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的结构,我们需要创建一个新的顺序层集。
步骤9-现在我们需要在顺序图层集中添加一个filter_shape为5且步幅设置为1的Convolutional2D图层。另外,启用填充,以便填充图像以保留原始尺寸。
model = Sequential([
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=8, pad=True),
第10步-现在是时候用的2 filter_shape添加MaxPooling层和进步的2设置为一半将图像压缩。
MaxPooling(filter_shape=(2,2), strides=(2,2)),
步骤11-现在,就像我们在步骤9中所做的那样,我们需要添加另一个filter_shape为5且步幅设置为1的Convolutional2D图层,使用16个滤镜。另外,启用填充,以便应保留前一个合并层生成的图像的大小。
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=16, pad=True),
步骤12 -现在,当我们在步骤10中一样,以3 filter_shape和步幅设定为3至将图像缩小到三分之一添加另一个MaxPooling层。
MaxPooling(filter_shape=(3,3), strides=(3,3)),
步骤13-最后,网络可以预测,为10个可能的类别添加包含十个神经元的密集层。为了将网络变成分类模型,请使用log_siftmax激活函数。
Dense(10, activation=log_softmax)
])
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)
创建了网络结构后,就该对网络进行培训了。但是在开始训练我们的网络之前,我们需要设置微型批处理源,因为训练与图像配合使用的NN需要比大多数计算机更多的内存。
在前面的部分中,我们已经创建了微型批处理源。以下是设置两个微型批处理源的Python代码-
由于有了create_datasource函数,我们现在可以创建两个单独的数据源(训练和测试一个)来训练模型。
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)
借助先前的代码,我们为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代码的帮助下看一下,如何通过在用于早期创建minibatch源的函数包括裁剪转换来更改转换列表。
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)
在上述代码的帮助下,我们可以增强该函数以包括一组图像变换,以便在进行训练时可以随机裁剪图像,从而获得更多的图像变化。