零基础入门深度学习(三):卷积神经网络基础之初识卷积

课程名称 | 零基础入门深度学习

授课讲师 | 孙高峰 百度深度学习技术平台部资深研发工程师

授课时间 | 每周二、周四晚20:00-21:00

编辑整理 | 孙高峰

内容来源 | 百度飞桨深度学习集训营

出品平台 | 百度飞桨

01

导读

本课程是百度官方开设的零基础入门深度学习课程,主要面向没有深度学习技术基础或者基础薄弱的同学,帮助大家在深度学习领域实现从0到1+的跨越。从本课程中,你将学习到:

  1. 深度学习基础知识

  2. numpy实现神经网络构建和梯度下降算法

  3. 计算机视觉领域主要方向的原理、实践

  4. 自然语言处理领域主要方向的原理、实践

  5. 个性化推荐算法的原理、实践

本周为开讲第三周,百度深度学习技术平台部资深研发工程师孙高峰,开始讲解深度学习在计算机视觉方向实践应用。今天为大家带来的是卷积神经网络基础之初识卷积。

02

计算机视觉概述

计算机视觉作为一门让机器学会如何去“看”的科学学科,具体的说,就是让机器去识别摄像机拍摄的图片或视频中的物体,检测出物体所在的位置,并对目标物体进行跟踪,从而理解并描述出图片或视频里的场景和故事,以此来模拟人脑视觉系统。因此,计算机视觉也通常被叫做机器视觉,其目的是建立能够从图像或者视频中“感知”信息的人工系统。

计算机视觉技术经过几十年的发展,已经在交通(车牌识别、道路违章抓拍)、安防(人脸闸机、小区监控)、金融(刷脸支付、柜台的自动票据识别)、医疗(医疗影像诊断)、工业生产(产品缺陷自动检测)等多个领域应用,影响或正在改变人们的日常生活和工业生产方式。未来,随着技术的不断演进,必将涌现出更多的产品应用,为我们的生活创造更大的便利和更广阔的机会。
 


图1:计算机视觉技术在各领域的应用

飞桨为计算机视觉任务提供了丰富的API,并通过底层优化和加速保证了这些API的性能。同时,飞桨还提供了丰富的模型库,覆盖图像分类、检测、分割、文字识别和视频理解等多个领域。用户可以直接使用这些API组建模型,也可以在飞桨提供的模型库基础上进行二次研发。

由于篇幅所限,本章将重点介绍计算机视觉的两个典型任务:图像分类和目标检测。主要涵盖如下内容:

  • 卷积神经网络:卷积神经网络(Convolutional Neural Networks, CNN)是计算机视觉技术最经典的模型结构。这里主要介绍卷积神经网络的常用模块,包括:卷积、池化等。

  • 图像分类:介绍图像分类算法的经典模型结构,并通过眼疾筛查的案例展示算法的应用。

  • 目标检测:介绍目标检测YOLO-V3算法,并通过林业病虫害数据集中的虫子检测任务案例展示YOLO-V3算法的应用。

03

计算机视觉发展历程

介绍卷积神经网络之前,我们先回顾一下计算机视觉发展历程,这要从生物视觉讲起。

对于生物视觉的形成时间,目前学术界尚没有形成定论,有研究者认为最早的生物视觉形成于距今约7亿年前的水母之中,也有研究者认为生物视觉产生于距今约5亿年前寒武纪【1, 2】。寒武纪生物大爆发的原因一直是个未解之谜,不过可以肯定的是在寒武纪动物具有了视觉能力,捕食者可以更容易的发现猎物,被捕食者也可以更早的发现天敌的位置。视觉能力加剧了猎手和猎物之间的博弈,也催生出更加激烈的生存演化规则。视觉系统的形成有力的推动了食物链的演化,加速了生物进化过程,是生物发展史上重要的里程碑。经过几亿年的演化,目前人类的视觉系统已经具备非常高的复杂度和强大的功能,人脑中神经元数目达到了1000亿个,这些神经元通过网络互相连接,这样庞大的视觉神经网络使得我们可以很轻松的观察周围的世界。

对人类来说,识别猫和狗是件非常容易的事。但对计算机来说,即使是一个精通编程的高手,也很难轻松写出具有通用性的程序(比如:假设程序认为体型大的是狗,体型小的是猫,但由于拍摄角度不同,可能一张图片上猫占据的像素比狗还多)。那么,如何让计算机也能像人一样看懂周围的世界呢?研究者尝试着从不同的角度去解决这个问题,由此也发展出一系列的子任务,如 图2 所示。


图2:计算机视觉子任务示意图

  • (a) Image Classification: 图像分类,用于识别图像中物体的类别(如:bottle、cup、cube)

  • (b) Object Localization: 目标检测,用于检测图像中每个物体的类别,并准确标出它们的位置。

  • (c) Semantic Segmentation: 图像语义分割,用于标出图像中每个像素点所属的类别,属于同一类别的像素点用一个颜色标识。

  • (d) Instance Segmentation: 实例分割,值得注意的是,(b)中的目标检测任务只需要标注出物体位置,而(d)中的实例分割任务不仅要标注出物体位置,还需要标注出物体的外形轮廓。

在早期的图像分类任务中,通常是先人工提取图像特征,再用机器学习算法对这些特征进行分类,分类的结果强依赖于特征提取方法,往往只有经验丰富的研究者才能完成。在这种背景下,基于神经网络的特征提取方法应运而生。Yan LeCun是最早将卷积神经网络应用到图像识别领域的,其主要逻辑是使用卷积神经网络提取图像特征,并对图像所属类别进行预测,通过训练数据不断调整网络参数,最终形成一套能自动提取图像特征并对这些特征进行分类的网络。这一方法在手写数字识别任务上取得了极大的成功,但在接下来的时间里,却没有得到很好的发展。其主要原因一方面是数据集不完善,只能处理简单任务,在大尺寸的数据上容易发生过拟合;另一方面是硬件瓶颈,网络模型复杂时,计算速度会特别慢。

目前,随着互联网技术的不断进步,数据量呈现大规模的增长,越来越丰富的数据集不断涌现。另外,得益于硬件能力的提升,计算机的算力也越来越强大。不断有研究者将新的模型和算法应用到计算机视觉领域。由此催生了越来越丰富的模型结构和更加准确的精度,同时计算机视觉所处理的问题也越来越丰富,包括分类、检测、分割、场景描述、图像生成和风格变换等,甚至还不仅仅局限于2维图片,包括视频处理技术和3D视觉等。

04

卷积神经网络

卷积神经网络是目前计算机视觉中使用最普遍的模型结构。本章节主要为读者介绍卷积神经网络的一些基础模块,包括:

  • 卷积(Convolution)

  • 池化(Pooling)

  • ReLU激活函数

  • 批归一化(Batch Normalization)

  • 丢弃法(Dropout)

回顾一下,在上一章“一个案例带你吃透深度学习”中,我们介绍了手写数字识别任务,应用的是全连接层的特征提取,即将一张图片上的所有像素点展开成一个1维向量输入网络,存在如下两个问题:

1. 输入数据的空间信息被丢失。 空间上相邻的像素点往往具有相似的RGB值,RGB的各个通道之间的数据通常密切相关,但是转化成1维向量时,这些信息被丢失。同时,图像数据的形状信息中,可能隐藏着某种本质的模式,但是转变成1维向量输入全连接神经网络时,这些模式也会被忽略。

2. 模型参数过多,容易发生过拟合。 在手写数字识别案例中,每个像素点都要跟所有输出的神经元相连接。当图片尺寸变大时,输入神经元的个数会按图片尺寸的平方增大,导致模型参数过多,容易发生过拟合。

为了解决上述问题,我们引入卷积神经网络进行特征提取,既能提取到像相邻素点之间的特征模式,又能保证参数的个数不随图片尺寸变化。图3 是一个典型的卷积神经网络结构,多层卷积和池化层组合作用在输入图片上,在网络的最后通常会加入一系列全连接层,ReLU激活函数一般加在卷积或者全连接层的输出上,网络中通常还会加入Dropout来防止过拟合。


图3:卷积神经网络经典结构

说明:

在卷积神经网络中,计算范围是在像素点的空间邻域内进行的,卷积核参数的数目也远小于全连接层。卷积核本身与输入图片大小无关,它代表了对空间临域内某种特征模式的提取。比如,有些卷积核提取物体边缘特征,有些卷积核提取物体拐角处的特征,图像上不同区域共享同一个卷积核。当输入图片大小不一样时,仍然可以使用同一个卷积核进行操作。

05

卷积(Convolution)

这一小节将为读者介绍卷积算法的原理和实现方案,并通过具体的案例展示如何使用卷积对图片进行操作,主要涵盖如下内容:

  • 卷积计算

  • 填充(padding)

  • 步幅(stride)

  • 感受野(Receptive Field)

  • 多输入通道、多输出通道和批量操作

  • 飞桨卷积API介绍

  • 卷积算子应用举例

卷积计算

卷积是数学分析中的一种积分变化的方法,在图像处理中采用的是卷积的离散形式。这里需要说明的是,在卷积神经网络中,卷积层的实现方式实际上是数学中定义的互相关  (cross-correlation)运算,与数学分析中的卷积定义有所不同,这里跟其他框架和卷积神经网络的教程保持一致,都使用互相关运算作为卷积的定义,具体的计算过程如 图4 所示。


图4:卷积计算过程

说明:

卷积核(kernel)也被叫做滤波器(filter),假设卷积核的高和宽分别为和,则将称为卷积,比如卷积,就是指卷积核的高为3, 宽为5。

  • 如图4(a)所示:左边的图大小是,表示输入数据是一个维度为的二维数组;中间的图大小是,表示一个维度为的二维数组,我们将这个二维数组称为卷积核。先将卷积核的左上角与输入数据的左上角(即:输入数据的(0, 0)位置)对齐,把卷积核的每个元素跟其位置对应的输入数据中的元素相乘,再把所有乘积相加,得到卷积输出的第一个结果

 

  • 如图4(b)所示:将卷积核向右滑动,让卷积核左上角与输入数据中的(0,1)位置对齐,同样将卷积核的每个元素跟其位置对应的输入数据中的元素相乘,再把这4个乘积相加,得到卷积输出的第二个结果,

 

  • 如图4(c)所示:将卷积核向下滑动,让卷积核左上角与输入数据中的(1, 0)位置对齐,可以计算得到卷积输出的第三个结果,

 

  • 如图4(d)所示:将卷积核向右滑动,让卷积核左上角与输入数据中的(1, 1)位置对齐,可以计算得到卷积输出的第四个结果,

 

卷积核的计算过程可以用下面的数学公式表示,其中  代表输入图片,  代表输出特征图, 是卷积核参数,它们都是二维数组, 表示对卷积核参数进行遍历并求和。

举例说明,假如上图中卷积核大小是,则可以取0和1,也可以取0和1,也就是说:

读者可以自行验证,当取不同值时,根据此公式计算的结果与上图中的例子是否一致。

  • 【思考】 当卷积核大小为时,b和a之间的对应关系应该是怎样的?

其它说明:

在卷积神经网络中,一个卷积算子除了上面描述的卷积过程之外,还包括加上偏置项的操作。例如假设偏置为1,则上面卷积计算的结果为:

 

 

 

 

填充(padding)

在上面的例子中,输入图片尺寸为,输出图片尺寸为,经过一次卷积之后,图片尺寸变小。卷积输出特征图的尺寸计算方法如下:

 

 

如果输入尺寸为4,卷积核大小为3时,输出尺寸为。读者可以自行检查当输入图片和卷积核为其他尺寸时,上述计算式是否成立。通过多次计算我们发现,当卷积核尺寸大于1时,输出特征图的尺寸会小于输入图片尺寸。说明经过多次卷积之后尺寸会不断减小。为了避免卷积之后图片尺寸变小,通常会在图片的外围进行填充(padding),如 图5 所示。


图5:图形填充

  • 如图5(a)所示:填充的大小为1,填充值为0。填充之后,输入图片尺寸从变成了,使用3x3的卷积核,输出图片尺寸为。

  • 如图5(b)所示:填充的大小为2,填充值为0。填充之后,输入图片尺寸从变成了,使用3x3的卷积核,输出图片尺寸为。

如果在图片高度方向,在第一行之前填充行,在最后一行之后填充行;在图片的宽度方向,在第1列之前填充列,在最后1列之后填充列;则填充之后的图片尺寸为。经过大小为的卷积核操作之后,输出图片的尺寸为:

在卷积计算过程中,通常会在高度或者宽度的两侧采取等量填充,即,上面计算公式也就变为:

 

 

卷积核大小通常使用1,3,5,7这样的奇数,如果使用的填充大小为,则卷积之后图像尺寸不变。例如当卷积核大小为3时,padding大小为1,卷积之后图像尺寸不变;同理,如果卷积核大小为5,使用padding的大小为2,也能保持图像尺寸不变。

步幅(stride)

图5 中卷积核每次滑动一个像素点,这是步幅为1的特殊情况。图6 是步幅为2的卷积过程,卷积核在图片上移动时,每次移动大小为2个像素点。
 


图6:步幅为2的卷积过程

当宽和高方向的步幅分别为和时,输出特征图尺寸的计算公式是:

 

假设输入图片尺寸是,卷积核大小,填充,步幅为,则输出特征图的尺寸为:

 

 

感受野(Receptive Field)

输出特征图上每个点的数值,是由输入图片上大小为的区域的元素与卷积核每个元素相乘再相加得到的,所以输入图像上区域内每个元素数值的改变,都会影响输出点的像素值。我们将这个区域叫做输出特征图上对应点的感受野。感受野内每个元素数值的变动,都会影响输出点的数值变化。比如卷积对应的感受野大小就是。

多输入通道、多输出通道和批量操作

前面介绍的卷积计算过程比较简单,实际应用时,处理的问题要复杂的多。例如:对于彩色图片有RGB三个通道,需要处理多输入通道的场景。输出特征图往往也会具有多个通道,而且在神经网络的计算中常常是把一个批次的样本放在一起计算,所以卷积算子需要具有批量处理多输入和多输出通道数据的功能,下面将分别介绍这几种场景的操作方式。

  • 多输入通道场景

上面的例子中,卷积层的数据是一个2维数组,但实际上一张图片往往含有RGB三个通道,要计算卷积的输出结果,卷积核的形式也会发生变化。假设输入图片的通道数为,输入数据的形状是,计算过程如 图7 所示。

  1. 对每个通道分别设计一个2维数组作为卷积核,卷积核数组的形状是。

  2. 对任一通道,分别用大小为的卷积核在大小为的二维数组上做卷积。

  3. 将这个通道的计算结果相加,得到的是一个形状为的二维数组。
     


图7:多输入通道计算过程

  • 多输出通道场景

一般来说,卷积操作的输出特征图也会具有多个通道,这时我们需要设计个维度为的卷积核,卷积核数组的维度是,如 图8 所示。

  1. 对任一输出通道,分别使用上面描述的形状为的卷积核对输入图片做卷积。

  2. 将这个形状为的二维数组拼接在一起,形成维度为的三维数组。

说明:

通常将卷积核的输出通道数叫做卷积核的个数。


图8:多输出通道计算过程

  • 批量操作

在卷积神经网络的计算中,通常将多个样本放在一起形成一个mini-batch进行批量操作,即输入数据的维度是。由于会对每张图片使用同样的卷积核进行卷积操作,卷积核的维度与上面多输出通道的情况一样,仍然是,输出特征图的维度是,如 图9 所示。


图9:批量操作

06

飞桨卷积API介绍

飞桨卷积算子对应的API是paddle.fluid.dygraph.nn.Conv2D,用户可以直接调用API进行计算,也可以在此基础上修改。常用的参数如下:

  • name_scope, 卷积层的名字,数据类型是字符串,可以是"conv1"或者"conv2"等形式。

  • num_filters, 输出通道数目,相当于上文中的。

  • filter_size, 卷积核大小,可以是整数,比如3;或者是两个整数的list,例如[3, 3]。

  • stride, 步幅,可以是整数,比如2;或者是两个整数的list,例如[2, 2]。

  • padding, 填充大小,可以是整数,比如1;或者是两个整数的list,例如[1, 1]。

  • act, 激活函数,卷积操作完成之后使用此激活函数作用在神经元上。

输入数据维度,输出数据维度,权重参数的维度,偏置参数的维度是。

卷积算子应用举例

下面介绍卷积算子在图片中应用的三个案例,并观察其计算结果。

案例1——简单的黑白边界检测

下面是使用Conv2D算子完成一个图像边界检测的任务。图像左边为光亮部分,右边为黑暗部分,需要检测出光亮跟黑暗的分界处。可以设置宽度方向的卷积核为,此卷积核会将宽度方向间隔为1的两个像素点的数值相减。当卷积核在图片上滑动的时候,如果它所覆盖的像素点位于亮度相同的区域,则左右间隔为1的两个像素点数值的差为0。只有当卷积核覆盖的像素点有的处于光亮区域,有的处在黑暗区域时,左右间隔为1的两个点像素值的差才不为0。将此卷积核作用到图片上,输出特征图上只有对应黑白分界线的地方像素值才不为0。具体代码如下所示,结果输出在下方的图案中。

import matplotlib.pyplot as plt
import numpy as npimport paddleimport paddle.fluid as fluidfrom paddle.fluid.dygraph.nn import Conv2Dfrom paddle.fluid.initializer import NumpyArrayInitializer%matplotlib inline
with fluid.dygraph.guard():    # 创建初始化权重参数w    w = np.array([1, 0, -1], dtype='float32')    # 将权重参数调整成维度为[cout, cin, kh, kw]的四维张量    w = w.reshape([1, 1, 1, 3])    # 创建卷积算子,设置输出通道数,卷积核大小,和初始化权重参数    # filter_size = [1, 3]表示kh = 1, kw=3    # 创建卷积算子的时候,通过参数属性param_attr,指定参数初始化方式    # 这里的初始化方式时,从numpy.ndarray初始化卷积参数    conv = Conv2D('conv', num_filters=1, filter_size=[1, 3],            param_attr=fluid.ParamAttr(              initializer=NumpyArrayInitializer(value=w)))
    # 创建输入图片,图片左边的像素点取值为1,右边的像素点取值为0    img = np.ones([50,50], dtype='float32')    img[:, 30:] = 0.    # 将图片形状调整为[N, C, H, W]的形式    x = img.reshape([1,1,50,50])    # 将numpy.ndarray转化成paddle中的tensor    x = fluid.dygraph.to_variable(x)    # 使用卷积算子作用在输入图片上    y = conv(x)    # 将输出tensor转化为numpy.ndarray    out = y.numpy()
f = plt.subplot(121)f.set_title('input image', fontsize=15)plt.imshow(img, cmap='gray')
f = plt.subplot(122)f.set_title('output featuremap', fontsize=15)# 卷积算子Conv2D输出数据形状为[N, C, H, W]形式# 此处N, C=1,输出数据形状为[1, 1, H, W],是4维数组# 但是画图函数plt.imshow画灰度图时,只接受2维数组# 通过numpy.squeeze函数将大小为1的维度消除plt.imshow(out.squeeze(), cmap='gray')plt.show()
# 查看卷积层的参数with fluid.dygraph.guard():    # 通过 conv.parameters()查看卷积层的参数,返回值是list,包含两个元素    print(conv.parameters())    # 查看卷积层的权重参数名字和数值    print(conv.parameters()[0].name, conv.parameters()[0].numpy())    # 参看卷积层的偏置参数名字和数值    print(conv.parameters()[1].name, conv.parameters()[1].numpy())


案例2——图像中物体边缘检测

上面展示的是一个人为构造出来的简单图片使用卷积检测明暗分界处的例子,对于真实的图片,也可以使用合适的卷积核对它进行操作,用来检测物体的外形轮廓,观察输出特征图跟原图之间的对应关系,如下代码所示:

import matplotlib.pyplot as pltfrom PIL import Imageimport numpy as npimport paddleimport paddle.fluid as fluidfrom paddle.fluid.dygraph.nn import Conv2Dfrom paddle.fluid.initializer import NumpyArrayInitializerimg = Image.open('./work/images/p1/000000098520.jpg')with fluid.dygraph.guard():    # 设置卷积核参数    w = np.array([[-1,-1,-1], [-1,8,-1], [-1,-1,-1]], dtype='float32')/8    w = w.reshape([1, 1, 3, 3])    # 由于输入通道数是3,将卷积核的形状从[1,1,3,3]调整为[1,3,3,3]    w = np.repeat(w, 3, axis=1)    # 创建卷积算子,输出通道数为1,卷积核大小为3x3,    # 并使用上面的设置好的数值作为卷积核权重的初始化参数    conv = Conv2D('conv', num_filters=1, filter_size=[3, 3],             param_attr=fluid.ParamAttr(              initializer=NumpyArrayInitializer(value=w)))    # 将读入的图片转化为float32类型的numpy.ndarray    x = np.array(img).astype('float32')    # 图片读入成ndarry时,形状是[H, W, 3],    # 将通道这一维度调整到最前面    x = np.transpose(x, (2,0,1))    # 将数据形状调整为[N, C, H, W]格式    x = x.reshape(1, 3, img.height, img.width)    x = fluid.dygraph.to_variable(x)    y = conv(x)    out = y.numpy()plt.figure(figsize=(20, 10))f = plt.subplot(121)f.set_title('input image', fontsize=15)plt.imshow(img)f = plt.subplot(122)f.set_title('output feature map', fontsize=15)plt.imshow(out.squeeze(), cmap='gray')plt.show()


案例3——图像均值模糊

另外一种比较常见的卷积核是用当前像素跟它邻域内的像素取平均,这样可以使图像上噪声比较大的点变得更平滑,如下代码所示:

import matplotlib.pyplot as plt
from PIL import Image
import numpy as npimport paddleimport paddle.fluid as fluidfrom paddle.fluid.dygraph.nn import Conv2Dfrom paddle.fluid.initializer import NumpyArrayInitializer
# 读入图片并转成numpy.ndarray#img = Image.open('./images/p1/000000001584.jpg')img = Image.open('./work/images/p1/000000355610.jpg').convert('L')img = np.array(img)
# 换成灰度图
with fluid.dygraph.guard():    # 创建初始化参数    w = np.ones([1, 1, 5, 5], dtype = 'float32')/25    conv = Conv2D('conv', num_filters=1, filter_size=[5, 5],             param_attr=fluid.ParamAttr(              initializer=NumpyArrayInitializer(value=w)))
    x = img.astype('float32')    x = x.reshape(1,1,img.shape[0], img.shape[1])    x = fluid.dygraph.to_variable(x)    y = conv(x)    out = y.numpy()
plt.figure(figsize=(20, 12))f = plt.subplot(121)f.set_title('input image')plt.imshow(img, cmap='gray')
f = plt.subplot(122)f.set_title('output feature map')out = out.squeeze()plt.imshow(out, cmap='gray')
plt.show()

 

07

总结

本文中孙老师先给大家讲述了计算机视觉的发展历程及目前的具体应用场景,研究方法如何从传统图像算法过渡到深度学习,以及深度学习中最流行的卷积神经网络。在接下来的文章中,我们将重点展开讲解卷积神经网络里面的一些常用模块,如卷积、池化、ReLU激活函数等。在后期课程中,将继续为大家带来内容更丰富的课程,帮助学员快速掌握深度学习方法。

你可能感兴趣的:(零基础入门深度学习(三):卷积神经网络基础之初识卷积)