【干货】卷积神经网络中的四种基本组件

【导读】当今,卷积神经网络在图像识别等领域取得巨大的成功,那么是什么使其高效而快速呢?本文整理John Olafenwa的一篇博文,主要介绍了卷积神经网络采用的四种基本组件:Pooling、Dropouts、Batch Normalization、Data Augmentation ,分别解释了这些组件在现代CNN中所起的作用。另外,作者使用keras实现这几种组件,构建一个完整的CNN系统,更有助于读者理解这几种组件的作用和实现方法。

Components of convolutional neural networks

最近最先进的网络结构采用了许多附加组件来补充卷积操作。在这篇文章中,我将解释一些能够提高现代卷积神经网络速度和精度的最重要的组件。 我将从解释每个组件的理论开始,并在keras中实现。

▌Pooling



使CNN非常有效的第一个秘诀就是Pooling。Pooling是一种矢量,对图像的每个局部区域进行标量变换,就像卷积操作一样,然而,与卷积不同的是它们没有过滤器,也不用局部区域计算点积,而是计算区域中像素的平均值(average pooling),或者简单地选取最大像素并丢弃其余部分(Max Pooling)。

以上是2 x 2 pooling,它可以有效地将特征映射的大小减小2倍。

Pooling的想法看起来可能适得其反,因为它会导致信息的丢失,但它在实践中证明是非常有效的,因为它使得covnets(卷积网络)对于图像表示的变化是不变的,并且还减少了背景噪声的影响。 Max Pooling近年来效果最好,其思想是,用某个局部区域的最大像素代表该地区最重要的特征。通常我们想分类的物体的图像可能包含许多其他物体,例如,出现在汽车图像中某处的猫可能会误导分类器。pooling有助于缓解这一现象,使covnets更好地推广。

它也大大降低了covnet的计算成本。通常,网络中每层的图像大小与每层的计算成本(flops)成正比。随着图层变得更深,pooling会减少图像的尺寸,因此,它有助于防止网络需要的flops数量激增。 分段卷积有时用作pooling的替代物。

▌Dropouts



过度拟合是网络在训练集上运行良好但在测试集上表现不佳的一种现象。这通常是由于过度依赖于训练集中出现的特定的特征。Dropouts是一种抑制过度拟合的技巧。 它可以随机地将一些激活值设置为0,从而避免过度拟合。 通过这样做,网络不得不探索更多分类图像的路径而不是过度依赖于某些特征。 Dropouts是AlexNet中的关键元素之一。

▌Batch Normalization(批量标准化)



神经网络的一个主要问题是梯度消失,造成训练非常糟糕。 来自Google Brain的Ioie和Szegedy发现,这主要是因为内部协变量的变化,这种情况是由于信息通过网络传播而造成的数据分布变化引起的。他们提出批量标准化(Batch Normalization)的技术。它的工作原理是将每一批图像都标准化,从而得到零均值和单位方差。

它通常放在cnns的非线性(relu)之前。 它极大地提高了准确性,同时极大地加快了训练过程。

▌Data Augmentation(数据增强)



现代covnets所需要的最后一种组件是Data Augmentation。人类视觉系统在适应图像平移,旋转和其他形式的扭曲方面非常出色。拍摄图像并且不管如何翻转它,大多数人仍然可以识别它。 然而,covnets不善于处理这种扭曲,它们可能会由于小的改变而失败。 它们解决这个问题的关键是随机扭曲训练图像,使用水平切除,垂直切除,旋转,增白,移位和其他扭曲的手段。这将使covnets学会如何处理这种扭曲,因此,他们将能够在现实世界中很好地工作。

另一种常用技术是从每幅图像中减去平均图像,并除以标准偏差。

对这些基本组件的理论解释让人感到枯燥乏味,现在我将解释如何在keras中实现它们。

在这篇文章中,所有的实验都将在CIFAR10上进行,这是一个包含60,000个32×32RGB图像的数据集。 它分为50,000个训练图像和10,000个测试图像。

为了让工程更加模块化,我们为每层创建一个简单的函数

def Unit(x,filters):
    out = BatchNormalization()(x)
    out = Activation("relu")(out)
    out = Conv2D(filters=filters, kernel_size=[3, 3], strides=[1, 1], padding="same")(out)

    return out

这里是我们代码中最重要的部分,Unit函数定义了一个简单的层,它包含三个层,第一个是我先前解释的Batch Normalization,接下来我们添加RELU activation(激活),最后添加convolution(卷积),注意为何RELU在convolution之前,这是一种最近的做法,称为“预激活”

现在我们将许多unit层合并成一个模型

def MiniModel(input_shape):
    images = Input(input_shape)


    net = Unit(images,64)
    net = Unit(net,64)
    net = Unit(net,64)
    net = MaxPooling2D(pool_size=(2,2))(net)

    net = Unit(net,128)
    net = Unit(net,128)
    net = Unit(net,128)
    net = MaxPooling2D(pool_size=(2, 2))(net)

    net = Unit(net,256)
    net = Unit(net,256)
    net = Unit(net,256)

    net = Dropout(0.5)(net)
    net = AveragePooling2D(pool_size=(8,8))(net)
    net = Flatten()(net)
    net = Dense(units=10,activation="softmax")(net)

    model = Model(inputs=images,outputs=net)

    return model 

在这里,我们使用API来定义我们的模型,我们从三个单元格开始,每个单元格64个过滤器,然后是Max Pooling层,将我们的32 x 32图像减少到16 x 16。接下来是pooling层的3,128个过滤单元,这使我们的图像变成8 x 8,最后,我们有另外3个256通道的单元。请注意,每次我们将图像尺寸缩小2倍时,我们会将通道数加倍。

我们按照0.5比例设置dropout ,这将随机取消50%的参数,正如我之前解释的那样,它会抑制过度拟合。

接下来,我们需要加载cifar10数据集并执行data augmentation(数据增强)。

#load the cifar10 dataset
(train_x, train_y) , (test_x, test_y) = cifar10.load_data()

#normalize the data
train_x = train_x.astype('float32') / 255
test_x = test_x.astype('float32') / 255

#Subtract the mean image from both train and test set
train_x = train_x - train_x.mean()
test_x = test_x - test_x.mean()

#Divide by the standard deviation
train_x = train_x / train_x.std(axis=0)
test_x = test_x / test_x.std(axis=0)

在上面的代码中,加载训练数据和测试数据后,我们从每幅图像中减去平均图像并除以标准偏差,这是一种基本的(data augmentation)数据增加技术,有时,我们可能只减去平均值并跳过标准偏差部分,哪一种更合适就使用哪种。

对于更先进的数据增强,我们的图像加载过程会稍微改变,keras有一个非常有用的数据增强实用程序,它简化了整个过程。

下面的代码可以做到这一点

datagen = ImageDataGenerator(rotation_range=10,
                             width_shift_range=5. / 32,
                             height_shift_range=5. / 32,
                             horizontal_flip=True)

# Compute quantities required for featurewise normalization
# (std, mean, and principal components if ZCA whitening is applied).
datagen.fit(train_x)

在上面,首先我们指定一个10度的旋转角度,高度和宽度都是5/32的偏移以及水平翻移,所有这些变换都会随机应用于训练集中的图像。 请注意,还有更多转换存在,您可以查看可以为该类别指定的所有参数。 请记住,过度使用数据增强可能是有害的。

接下来,我们必须将标签转换为one-hot编码

#Encode the labels to vectors
train_y = keras.utils.to_categorical(train_y,10)
test_y = keras.utils.to_categorical(test_y,10)

我在之前的教程中已经解释过了,所以我不会在这里再次解释它们。 事实上,构建训练过程的几乎所有其他内容都与我之前的教程完全相同,因此,这里是完整的代码。

#import needed classes
import keras
from keras.datasets import cifar10
from keras.layers import Dense,Conv2D,MaxPooling2D,Flatten,AveragePooling2D,Dropout,
BatchNormalization,Activation
from keras.models import Model,Input
from keras.optimizers import Adam
from keras.callbacks import LearningRateScheduler
from keras.callbacks import ModelCheckpoint
from math import ceil
import os
from keras.preprocessing.image import ImageDataGenerator


def Unit(x,filters):
    out = BatchNormalization()(x)
    out = Activation("relu")(out)
    out = Conv2D(filters=filters, kernel_size=[3, 3], strides=[1, 1], padding="same")(out)

    return out

#Define the model


def MiniModel(input_shape):
    images = Input(input_shape)

    net = Unit(images,64)
    net = Unit(net,64)
    net = Unit(net,64)
    net = MaxPooling2D(pool_size=(2,2))(net)

    net = Unit(net,128)
    net = Unit(net,128)
    net = Unit(net,128)
    net = MaxPooling2D(pool_size=(2, 2))(net)

    net = Unit(net,256)
    net = Unit(net,256)
    net = Unit(net,256)

    net = Dropout(0.25)(net)
    net = AveragePooling2D(pool_size=(8,8))(net)
    net = Flatten()(net)
    net = Dense(units=10,activation="softmax")(net)

    model = Model(inputs=images,outputs=net)

    return model

#load the cifar10 dataset
(train_x, train_y) , (test_x, test_y) = cifar10.load_data()

#normalize the data
train_x = train_x.astype('float32') / 255
test_x = test_x.astype('float32') / 255

#Subtract the mean image from both train and test set
train_x = train_x - train_x.mean()
test_x = test_x - test_x.mean()

#Divide by the standard deviation
train_x = train_x / train_x.std(axis=0)
test_x = test_x / test_x.std(axis=0)


datagen = ImageDataGenerator(rotation_range=10,
                             width_shift_range=5. / 32,
                             height_shift_range=5. / 32,
                             horizontal_flip=True)

# Compute quantities required for featurewise normalization
# (std, mean, and principal components if ZCA whitening is applied).
datagen.fit(train_x)



#Encode the labels to vectors
train_y = keras.utils.to_categorical(train_y,10)
test_y = keras.utils.to_categorical(test_y,10)

#define a common unit


input_shape = (32,32,3)
model = MiniModel(input_shape)

#Print a Summary of the model

model.summary()
#Specify the training components
model.compile(optimizer=Adam(0.001),loss="categorical_crossentropy",metrics=["accuracy"])



epochs = 20
steps_per_epoch = ceil(50000/128)

# Fit the model on the batches generated by datagen.flow().
model.fit_generator(datagen.flow(train_x, train_y, batch_size=128),
                    validation_data=[test_x,test_y],
                    epochs=epochs,steps_per_epoch=steps_per_epoch, verbose=1, 
workers=4)


#Evaluate the accuracy of the test dataset
accuracy = model.evaluate(x=test_x,y=test_y,batch_size=128)
model.save("cifar10model.h5") 

首先,这里有一些不同之处

input_shape = (32,32,3)
model = MiniModel(input_shape)

#Print a Summary of the model

model.summary()

正如我先前所解释的,cifar 10由32 x 32的RGB图像组成,因此输入形状有3个通道。 这是不言而喻的。

下一行创建一个我们已经删除的模型的实例,并传入输入形状。

最后,最后一行将打印出我们网络的完整摘要,包括参数的数量。

需要解释的最后一部分是

epochs = 20
steps_per_epoch = ceil(50000/128)

# Fit the model on the batches generated by datagen.flow().
model.fit_generator(datagen.flow(train_x, train_y, batch_size=128),
                    validation_data=[test_x,test_y],
                    epochs=epochs,steps_per_epoch=steps_per_epoch, verbose=1, 
workers=4)


#Evaluate the accuracy of the test dataset
accuracy = model.evaluate(x=test_x,y=test_y,batch_size=128)
model.save("cifar10model.h5")

首先我们定义要运行的epochs的数量,注意不要与steps_per_epoch混淆。

steps_per_epoch = ceil(50000/128)

50000是总共训练图像的数量,这里我们使用128的批处理大小,这意味着,总共20次epochs,对于个epoch,网络将处理50000/128批次的图像。

接下来是fit函数,这与我在前面的教程中解释的fit函数明显不同。

再看看下面的代码可能会有所帮助。

Fit the model on the batches generated by datagen.flow().
model.fit_generator(datagen.flow(train_x, train_y, batch_size=128),
                    validation_data=[test_x,test_y],
                    epochs=epochs,steps_per_epoch=steps_per_epoch, verbose=1,
 workers=4)

由于我们使用数据生成器类来实现数据增强,我们必须使用fit_generator函数,不要直接传入train_x和train_y,而是通过数据生成器中的流函数传递它们,同时我们也指定batch大小,接下来我们规定在这种情况下的验证数据(validation data)是测试数据(test data)。 所有其他事情保持不变。

这种设置在20个epochs后产生82%的准确率。

您可以尝试调整参数和网络,来观察您可以将准确度提高多少。在接下来的教程中,我将解释一些能真正高效地构建cnn体系结构所需的其他技巧。 本教程的目的是向您介绍基本组件。

如果你想深入了解计算机视觉。 从https://john.specpal.science下载我的免费电子书“Introduction to Deep Computer Vision”。

如果您有任何问题,请在下面评论或通过@ johnolafenwa在twitter上与我联系。

参考文献:

https://towardsdatascience.com/components-of-convolutional-neural-networks-6ff66296b456

原文发布于微信公众号 - 专知(Quan_Zhuanzhi)

原文发表时间:2018-03-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏人工智能头条

递归神经网络不可思议的有效性(上)

36340
来自专栏机器学习AI算法工程

机器学习算法-决策树C4.5练习

决策树是一个预测模型;他代表的是对象属性与对象值之间的一种映射关系。树中每个节点表示某个对象,而每个分叉路径则代表的某个可能的属性值,而每个叶结点则对应...

41260
来自专栏一心无二用,本人只专注于基础图像算法的实现与优化。

阅读Real-Time O(1) Bilateral Filtering 一文的相关感受。

研究双边滤波有很长一段时间了,最近看了一篇Real-Time O(1) Bilateral Filtering的论文,标题很吸引人,就研读了一番,经过几天的攻...

34690
来自专栏懒人开发

(4.7)James Stewart Calculus 5th Edition:Optimization Problems

根据下图,可以得到大体表达式: 已知 2x + y = 2400 求 A = xy = ? 的最大值

12230
来自专栏人工智能

从零开始学人工智能-Python·决策树·简介

决策树是听上去比较厉害且又相对简单的算法,但在实现它的过程中可能会对编程本身有更深的理解、尤其是对递归的利用 我个人的习惯是先说明最终能干什么、然后再来说怎么实...

23360
来自专栏AI研习社

无监督聚类问题中,如何决定簇的最优数量?

编者按:聚类问题有一大经典难题:没有数据集的真实分类情况,我们怎么才能知道数据簇的最优数目? 本文会谈谈解决该问题的两种流行方法:elbow method(肘子...

38080
来自专栏机器之心

教程 | 深度学习:自动编码器基础和类型

398160
来自专栏机器之心

教程 | 用人工蜂群算法求解k-分区聚类问题

我之前的文章介绍了如何利用名为人工蜂群算法(ABC)的集群智能(SI)算法来解决现实世界的优化问题:https://medium.com/cesar-updat...

13600
来自专栏磐创AI技术团队的专栏

使用Keras进行深度学习:(三)使用text-CNN处理自然语言(上)

上一篇文章中一直围绕着CNN处理图像数据进行讲解,而CNN除了处理图像数据之外,还适用于文本分类。CNN模型首次使用在文本分类,是Yoon Kim发表的“Con...

76880
来自专栏UAI人工智能

连载 | 深度学习入门第六讲

14960

扫码关注云+社区

领取腾讯云代金券