WhiteHacker 2018-05-31
卷积神经网络(CNN)是一个深度网络家族,可以利用数据的空间结构(例如图像)来了解数据,以便该算法可以输出一些有用的信息。考虑一个问题,我们想要确定某个图像中是否有人。例如,如果我给CNN一个人的图像,这个深度神经网络首先需要学习一些局部特征(例如眼睛,鼻子,嘴巴等)。这些局部特征是在卷积层中学习的。
然后,CNN将查看给定图像中存在的局部特征,然后生成全局表示这些局部特征映射的存在的特定激活模式(或激活矢量)。这些激活模式由CNN中完全连接的层产生。例如,如果图像是非人物,则激活模式将与针对人物图像的激活模式不同。
现在我们来看看CNN中存在哪些子模块。典型的CNN有三种不同的组件。它们是卷积层,池化层和全连接层。
首先我们讨论卷积层深入的内容。卷积层由许多内核组成。这些内核(有时称为卷积滤波器)存在于卷积层中,学习图像中存在的局部特征(例如,人的眼睛如何看起来像)。卷积层学习的这种局部特征称为特征映射。然后将这些功能卷入图像。这种卷积操作将产生一个矩阵(有时称为激活图)。如果在卷积滤波器中表示的特征出现在输入的该位置处,则激活图在给定位置处产生高值。
池化层使这些特征通过CNN平移不变(例如,无论人的眼睛是在[ x = 10,y = 10 ]还是[ x = 12,y = 11 ]位置,池化输出将是相同)。请注意,我们讨论每层轻微的平移变化。然而,汇总几个这样的层,使我们有更高的平移不变性。
最后我们有全连接层。完全连接的层负责根据激活的特征图集和图像中的位置生成不同的激活图案,并激活特征图。这就是CNN在视觉上的样子。
在了解CNN的整体结构的基础上,让我们继续理解构成CNN的每个子组件。
卷积操作究竟做了什么?如果卷积特征存在于该位置,则卷积操作为给定位置输出高值,否则输出低值。更具体地说,在卷积核的给定位置,我们采用每个核心单元值和与核心单元重叠的对应图像像素值的元素方式乘法,然后取其和。精确值根据以下公式确定(m - 核宽和高,h - 卷积输出,x - 输入,w - 卷积核)。
图像上的卷积过程可以如下进行可视化。
仅仅知道卷积运算的作用还不够,我们还需要了解卷积输出代表什么。设想卷积输出中的值的颜色(0 - 黑色,100 - 白色)。如果你想象这幅图像,它将代表在眼睛所在位置点亮的二值图像。
卷积运算也可以被认为是对给定图像执行一些变换。这种转换可能导致各种效果(例如,提取边缘,模糊等)。
现在让我们了解池操作的作用。池化(或有时称为二次采样)层使得CNN在卷积输出方面具有一点平移不变性。实践中使用了两种不同的池化机制(max-pooling和average-pooling)。我们将max-pooling称为pooling,因为max-pooling与平均池化相比被广泛使用。更准确地说,在给定位置的池操作输出落入内核的输入的最大值。在数学上,
让我们通过在前面看到的卷积输出上应用池操作来理解池的工作原理
如您所见,我们使用同一图像的两个变体; 一个原始图像和另一个图像在x轴上稍微平移。但是,池操作会为两个图像输出完全相同的特征图(黑色 - 0,白色 - 100)。所以我们说池化操作使得CNN翻译中的知识不变成为可能。需要注意的一点是,我们一次不移动1个像素,而是一次移动2个像素。这就是所谓的strided-pooling,这意味着我们正在以2的步幅进行合并。
全连接层将结合由不同卷积核所学习的特征,以便网络可以建立关于整体图像的全局表示。我们可以理解完全连接的层,如下所示。
根据由卷积特征表示的各种实体是否实际存在于输入中,全连接层中的神经元将被激活。当完全连接的神经元为此激活时,它会根据输入图像中存在的特征产生不同的激活模式。这为图像中存在的输出层提供了一种紧凑的表示形式,即输出图层可以轻松用于正确分类图像。
现在我们所要做的就是将所有这些组合起来,形成一个从原始图像到决策的端到端模型。一旦连接CNN将看起来像这样。总而言之,卷积层将学习数据中的各种局部特征(例如眼睛的样子),那么池化层将使得CNN对于这些特征的平移不变(例如,如果眼睛在两个图像中稍微翻译,CNN还承认它作为一个眼睛)。最后,我们有全连接层,说:“我们发现了两只眼睛,一只鼻子和一只嘴巴,所以这必须是一个人,并激活正确的输出。
增加更多层显然可以提高深度神经网络的性能。事实上,深度学习中最引人注目的突破性研究大多与解决如何添加更多层次的问题有关。而不会中断模型的训练。因为模型越深入,训练越困难。
但拥有更多层可以帮助CNN以分层方式学习功能。例如,第一层学习图像中的各种边缘方向,第二层学习基本形状(圆形,三角形等),第三层学习更多先进形状(例如,眼睛的形状,鼻子的形状)等等上。与使用单层图层学习CNN相比,这将带来更好的性能。
现在需要记住的一点是,当你实现CNN时,这些卷积特征(眼睛,鼻子,嘴巴)不会神奇地出现。目标是学习给定数据的这些特征。为此,我们定义一个成本函数,奖励正确识别的数据并惩罚错误分类的数据。示例成本函数将是均方根误差或二元交叉熵损失。
在定义了损失之后,我们可以优化特征的权重(即特征的每个单元格值),以反映引导CNN正确识别某个人的有用特征。更具体地说,我们通过在每个参数的梯度相对于损失显示的相反方向上采取一小步来优化每个卷积核和完全连接的神经元。但是,要实现CNN,您不需要知道如何实现梯度传播的确切细节。这是因为,大多数深度学习库(例如TensorFlow,PyTorch)都在内部实现这些差异化操作,当您自动定义正向计算时。
这里我们将简要讨论如何实现CNN。了解基础知识还不够,我们也应该了解如何使用像Keras这样的标准深度学习库来实现模型。Keras是一个奇妙的工具,特别是快速原型模型。下面以Python进行讲解:
首先我们定义我们想要使用的Keras API。我们将使用 sequential API。
# Define a sequential model
model = Sequential()
然后我们定义一个卷积层,如下所示:
# Added a convolution layer
model.add(Conv2D(32, (3,3), activation=’relu’, input_shape=[28, 28, 1]))
在这里,32是层中的核数,(3,3)是卷积层的核大小(高度和宽度)。我们使用非线性激活Relu和input shape[28,28,1],即[image height, image width, color channels]。注意,input shape应该是上一层产生的输出的shape 。对于第一个卷积层我们有实际的数据输入。对于层的其余部分,它将是上一层产生的输出。接下来,我们将讨论如何实现最大池化层:
# Add a max pool lyer
model.add(MaxPool2D())
这里我们不提供任何参数,因为我们将使用Keras中提供的默认值。如果你没有指定参数,Keras将使用(2,2)的内核大小和(2,2)的步幅。接下来我们定义全连接层。但在此之前,我们需要将输出flatten,因为全连接层处理1D数据:
model.add(Flatten())
model.add(Dense(256, activation=’relu’))
model.add(Dense(10, activation=’softmax’))
在这里我们定义了两个完全连接或密集的层。第一个完全连接的层具有256神经元并使用Relu激活。最后我们定义一个具有10个softmax激活的输出节点的密集层。这充当输出层,它将激活具有相同对象的图像的特定神经元。最后我们编译我们的模型,
model.compile(
optimizer=’adam’, loss=’categorical_crossentropy’, metrics=[‘accuracy’]
)
在这里,我们说使用Adam优化器(训练模型),交叉熵损失并使用accuracy模型来评估模型。最后我们可以使用数据来训练和测试我们的模型。我们将使用MNIST数据集(http://yann.lecun.com/exdb/mnist/),我们将使用定义的函数maybe_download和read_mnist函数将其下载并读入内存中。MNIST数据集包含手写数字(0-9)的图像,目标是通过分配图像表示的数字来正确分类图像。
def maybe_download(url, filename, expected_bytes, force=False):
"""Download a file if not present, and make sure it's the right size."""
if force or not os.path.exists(filename):
print('Attempting to download:', filename)
filename, _ = urlretrieve(url + filename, filename)
print('Download Complete!')
statinfo = os.stat(filename)
if statinfo.st_size == expected_bytes:
print('Found and verified', filename)
else:
raise Exception(
'Failed to verify ' + filename + '. Can you get to it with a browser?')
return filename
def read_mnist(fname_img, fname_lbl, one_hot=False):
print('Reading files %s and %s'%(fname_img, fname_lbl))
# Processing images
with gzip.open(fname_img) as fimg:
magic, num, rows, cols = struct.unpack(">IIII", fimg.read(16))
print(num,rows,cols)
img = (np.frombuffer(fimg.read(num*rows*cols), dtype=np.uint8).reshape(num, rows, cols,1)).astype(np.float32)
print('(Images) Returned a tensor of shape ',img.shape)
img *= 1.0 / 255.0
# Processing labels
with gzip.open(fname_lbl) as flbl:
# flbl.read(8) reads upto 8 bytes
magic, num = struct.unpack(">II", flbl.read(8))
lbl = np.frombuffer(flbl.read(num), dtype=np.int8)
if one_hot:
one_hot_lbl = np.zeros(shape=(num,10),dtype=np.float32)
one_hot_lbl[np.arange(num),lbl] = 1.0
print('(Labels) Returned a tensor of shape: %s'%lbl.shape)
print('Sample labels: ',lbl[:10])
if not one_hot:
return img, lbl
else:
return img, one_hot_lbl
接下来我们通过调用以下函数来训练我们的模型:
model.fit(x_train, y_train, batch_size = batch_size)
我们可以用一些测试数据来测试我们的模型,如下所示:
test_acc = model.evaluate(x_test, y_test, batch_size=batch_size)