[CNN] Understanding Convolution
From: http://blog.csdn.net/zouxy09/article/details/49080029
一个概念需经过反复的推敲以及时间的沉淀,之后才能真正理解
[OpenCV] Image Processing - Spatial Filtering
[CNN] What is Convolutional Neural Network
何谓卷积?
首先,我们有一个二维的滤波器矩阵(卷积核)和一个要处理的二维图像。
然后,对于图像的每一个像素点,计算它的邻域像素和滤波器矩阵的对应元素的乘积,然后加起来,作为该像素位置的值。
这样就完成了滤波过程,如下所示:
卷积/协相关's Features
将一个二维的函数移动到另一个二维函数的所有位置,这个操作就叫卷积或者协相关。
卷积和协相关的差别是,卷积需要先对滤波矩阵进行180的翻转,但如果矩阵是对称的,那么两者就没有什么差别了。
(一般都是对称的)
Correlation 和 Convolution这两个操作有两个非常关键的特点:
- 平移不变性:shift-invariant,我们在图像的每个位置都执行相同的操作。
- 线性:指这个操作是线性的,也就是我们用每个像素的邻域的线性组合来代替这个像素。
实际上,在信号处理领域,卷积有广泛的意义,而且有其严格的数学定义,但在这里不关注这个。
2D卷积需要4个嵌套循环4-double loop,所以它并不快,除非我们使用很小的卷积核。这里一般使用3x3或者5x5。而且,对于滤波器,也有一定的规则要求:
1)滤波器的大小应该是奇数,这样它才有一个中心,例如3x3,5x5或者7x7。有中心了,也有了半径的称呼,例如5x5大小的核的半径就是2。
2)滤波器矩阵所有的元素之和应该要等于1,这是为了保证滤波前后图像的亮度保持不变。当然了,这不是硬性要求了。
-
- 如果滤波器矩阵所有元素之和大于1,那么滤波后的图像就会比原图像更亮,
- 如果小于1,那么得到的图像就会变暗。如果和为0,图像不会变黑,但也会非常暗。
3)对于滤波后的结构,可能会出现负数或者大于255的数值。对这种情况,我们将他们直接截断到0和255之间即可。对于负数,也可以取绝对值。
卷积的滤波效果
1、啥也不做
这个滤波器啥也没有做,得到的图像和原图是一样的。因为只有中心点的值是1。邻域点的权值都是0,对滤波后的取值没有任何影响。
2、图像锐化滤波器Sharpness Filter
图像的锐化和边缘检测很像,
- 首先找到边缘,
- 然后把边缘加到原来的图像上面,这样就强化了图像的边缘,使图像看起来更加锐利了。
这两者操作统一起来就是锐化滤波器了,也就是在边缘检测滤波器的基础上,再在中心的位置加1,这样滤波后的图像就会和原始的图像具有同样的亮度了,但是会更加锐利。
我们把核加大,就可以得到更加精细的锐化效果:
// 对于CNN,卷积突出了边缘特征,对于input而言大大简化了处理过程。
另外,下面的滤波器会更强调边缘:
Centre Value + 1
主要是强调图像的细节。最简单的3x3的锐化滤波器如下:
实际上是计算当前点和周围点的差别,然后将这个差别加到原来的位置上。
另外,中间点的权值要比所有的权值和大于1,意味着这个像素要保持原来的值。
3、边缘检测Edge Detection
我们要找水平的边缘:需要注意的是,这里矩阵的元素和是0,所以滤波后的图像会很暗,只有边缘的地方是有亮度的。
// 某局部的亮度全都集中到了特征上,也就是边缘上。
为什么这个滤波器可以寻找到水平边缘呢?因为用这个滤波器卷积相当于求导的离散版本:
"你将当前的像素值减去前一个像素值,这样你就可以得到这个函数在这两个位置的差别或者斜率。"
下面的滤波器可以找到垂直方向的边缘,这里像素上和下的像素值都使用:
再下面这个滤波器可以找到45度的边缘:取-2不为什么,只是为了让矩阵的元素和为0而已。
那下面这个滤波器就可以检测所有方向的边缘:
梯度计算
为了检测边缘,我们需要在图像对应的方向计算梯度。用下面的卷积核来卷积图像,就可以了。但在实际中,这种简单的方法会把噪声也放大了。另外,需要注意的是,矩阵所有的值加起来要是0.
4、浮雕Embossing Filter
浮雕滤波器可以给图像一种3D阴影的效果。只要将中心一边的像素减去另一边的像素就可以了。
这时候,像素值有可能是负数,我们将负数当成阴影,将正数当成光,然后我们对结果图像加上128的偏移(为了处理负数)。这时候,图像大部分就变成灰色了。
下面是45度的浮雕滤波器
我们只要加大滤波器,就可以得到更加夸张的效果了
这种效果非常的漂亮,就像是将一副图像雕刻在一块石头上面一样,然后从一个方向照亮它。
它和前面的滤波器不同,它是非对称的。另外,它会产生负数值,所以我们需要将结果偏移,以得到图像灰度的范围。
A:原图像。B:锐化。C:边缘检测。D:浮雕
5、均值模糊Box Filter (Averaging)
我们可以将当前像素和它的四邻域的像素一起取平均,然后再除以5,或者直接在滤波器的5个地方取0.2的值即可,如下图:
可以看到,这个模糊还是比较温柔的,我们可以把滤波器变大,这样就会变得粗暴了:注意要将和再除以13.
所以,如果你想要更模糊的效果,加大滤波器的大小即可。或者对图像应用多次模糊也可以。
6、高斯模糊
均值模糊很简单,但不是很平滑。高斯模糊就有这个优点,所以被广泛用在图像降噪上。特别是在边缘检测之前,都会用来移除细节。高斯滤波器是一个低通滤波器。
那么,何为高通滤波器?
在我们设计滤波器时,首先必须明确一个基准频率,在低通滤波器中,叫做截止频率,而在高通滤波器中,叫做起始频率。
允许比基准频率低的信号通过的,叫做低通滤波器;不允许比基准频率低的信号通过,而允许比基准频率高的信号通过的,叫做高通滤波器。
所以,因为低通,高斯滤波器过滤掉了噪声点。
7、运动模糊Motion Blur
运动模糊可以通过只在一个方向模糊达到,例如下面9x9的运动模糊滤波器。注意,求和结果要除以9。
这个效果就好像,摄像机是从左上角移动的右下角。
卷积的计算
对图像处理而言,存在两大类的方法:空域处理和频域处理。
- 空域处理是指直接对原始的像素空间进行计算,
- 频率处理是指先对图像变换到频域,再做滤波等处理。
1. 空域计算-直接2D卷积
- 直接的实现也称为暴力实现brute force,因为它严格按照定义来实现,没有任何优化。
- 当然了,在并行实现里面,它也是比较灵活的。
- 另外,也存在一个优化版本,如果我们的kernel是separable可分的,那么就可以得到一个快5倍左右的卷积方法。
边界处理
(Easy,略,详见原文)
2. 频域计算-快速傅里叶变换FFT卷积
这个快速实现得益于卷积定理:时域上的卷积等于频域上的乘积。所以将我们的图像和滤波器通过算法变换到频域后,直接将他们相乘,然后再变换回时域(也就是图像的空域)就可以了。
o表示矩阵逐元素相乘。那用什么方法将空域的图像和滤波器变换到频域了。那就是鼎鼎大名的Fast Fourier Transformation 快速傅里叶变换FFT(其实,在CUDA里面,已经实现了FFT了)。
要在频域中对一副图像进行滤波,滤波器的大小和图像的大小必须要匹配,这样两者的相乘才容易。因为一般滤波器的大小比图像要小,所以我们需要拓展我们的kernel,让它和图像的大小一致。
因为CUDA中的FFT实现是周期的,所以kernel的值也要安排成这样,以支持这种周期性。
为了保证图像边界的像素也可以得到响应输出,我们也需要拓展我们的输入图像。同时,拓展的方式也要支持周期表达。
如果只是使用卷积定理,没有对输入进行任何修改的话,那么我们得到的是周期卷积的结果。但这可能不是我们要的,因为周期卷积会对输入数据进行周期填补,引入一些artifacts。
给定N长度的I和K,为了得到线性卷积,我们需要对I和K进行zero padding。为什么要补0,因为DFT假定了输入是无限和周期的,周期是N。
如上图,对于I和K,如果没有padding的话,隐含着会假定I和K是周期的,以他们的长度N为周期。图中本来N长度的I和K都是黑色虚线的部分,然后如果没有padding,隐含着就会在N之外,加上同样的无数个I,如红色虚线部分,加上了一个周期。对K也是这样。如果是zero padding的话,在黑色虚线的其他地方都全是0了,如图中蓝色部分。将I和K卷积,如果没有padding,如黑色虚线,会有红色那部分的artifact。如果有padding,就是蓝色实线。
Next: [CNN] Understanding Neural Networks Through Deep Visualization
一些补充:
机器视角:长文揭秘图像处理和卷积神经网络架构
权值矩阵在图像里表现的像一个从原始图像矩阵中提取特定信息的过滤器。一个权值组合可能用来提取边缘(edge)信息,另一个可能是用来提取一个特定颜色,下一个就可能就是对不需要的噪点进行模糊化。
先对权值进行学习,然后损失函数可以被最小化,类似于多层感知机(MLP)。因此需要通过对参数进行学习来从原始图像中提取信息,从而来帮助网络进行正确的预测。当我们有多个卷积层的时候,初始层往往提取较多的一般特征,随着网络结构变得更深,权值矩阵提取的特征越来越复杂,并且越来越适用于眼前的问题。
多过滤与激活图
需要记住的是权值的纵深维度(depth dimension)和输入图像的纵深维度是相同的。权值会延伸到输入图像的整个深度。
因此,和一个单一权值矩阵进行卷积会产生一个单一纵深维度的卷积化输出。大多数情况下都不使用单一过滤器(权值矩阵),而是应用维度相同的多个过滤器。
每一个过滤器的输出被堆叠在一起,形成卷积图像的纵深维度。假设我们有一个 32*32*3 的输入。我们使用 5*5*3,带有 valid padding 的 10 个过滤器。输出的维度将会是 28*28*10。
如下图所示:
一些CNN模型
VGG模型
介绍VGG模型的文章中自夸了VGG模型的几个特点,下面我们来仔细说说,
首先是卷积核变小
(1) 实际上在VGG之前已经有一些模型开始尝试小卷积核了,VGG模型只是成功案例之中的一个。
那么小卷积核有什么好处呢?文章中提出了两个好处,
-
- 首先是参数数量变少,过去一个7*7的卷积核需要49个参数,而现在3个3*3的卷积核有27个参数,看上去参数数量降低了不少;
- 第二是非线性层的增加,过去7*7的卷积层只有1层非线性层与其相配,现在有3个3*3的卷积层有3个非线性层。非线性层的增加会使模型变得更加复杂,因此模型的表现力也有了提高。
(2) 同时在文章还提出了VGG的模型收敛速度比之前的AlexNet还要快些,从后来人的角度来看,参数训练的速度和本层参数的数量相关。
(3) 之前我们分析过CNN模型参数的方差,我们假设对于某一层,这层的输入维度为每个参数的方差应该控制在 。 ,输出维度为 那么该层网络中
-
- 如果输入输出层的维度比较大,那么参数的理想方差就需要限定的更小,所以参数可以取值的范围就比较小,那么优化起来就比较费劲;
- 如果输入输出维度比较小,那么每个参数的理想方差就会相对大一些,那么可以取值的范围就比较大,优化起来就相对容易些。
从这个角度来看,减小每一层参数的数量对于优化来说是有意义的。
其次就是卷积层参数的规律
(1) 首先卷积层的操作不会改变输入数据的维度,这里的维度主要指feature map的长和宽。对于3*3的kernel,卷积层都会配一个大小为1的pad。同时stride被设为1。这样经过卷积层变换,长宽没有发生变化。这和之前的卷积层设计有些不同。
(2) 而且每做一次pooling,feature map的长宽各缩小一倍,channel层就会增加一倍。这样的设计对于不同的feature map维度来说适配起来都比较容易。
对于一些通过卷积减小维度的模型来说,对于不同的输入,卷积后的输出各不一样,所以适配起来有可能不太方便,而现在只有pooling层改变长宽维度,整体模型的维度计算就方便了许多。
于是在论文中有输入为256和384等维度,模型不需要根据不同的输入维度设计不同的卷积结构,使用同样的结构或者直接加深网络深度就可以了。
(3) 此外,模型也提到了1*1的卷积核,这个卷积核我们在后面还会提到。这种卷积核也不会改变feature map的长宽,同时又可以进一步地增加模型的非线性层,也就增加了模型的表现能力。
上面就是VGGNet在架构上做的这些改变,这些改变也被后面一些的模型所接纳。
丰富模型层的内部结构(GoogLeNet - Inception Module)
提到模型的内部结构,我们就来到了GoogLeNet模型(这个英文单词是在致敬LeNet?),模型中最核心的地方就是它的Inception Module。在此之前还有一个研究模型层内部结构的文章,叫做Network In Network,其中的道理也比较相似。
Network in Network和Inception Module这类结构主要看中的是模型在局部的拟合能力。
有些模型在结构上是采用“一字长蛇阵”的方法,对于某一个特定的尺度,模型只采用一个特定尺度的卷积核进行处理,
而上面两种模型却认为,采用一种尺度处理可能不太够,一张图象通常具有总体特征和细节特征这两类特征,我们用小卷积核能够更好地捕捉一些细节特征,而随着小卷积不断地卷下去,慢慢地一些总体特征也就被发现。
可是这里有一个问题,那就是我们在网络前段只有细节特征,后段才慢慢有一些总体特征,而有时候我们想让两方面的特征汇集在一起,同时出现发挥作用。那么采用单一的卷积核恐怕不太容易解决这样的问题。
于是上面两种模型开始考虑,与其把模型加深,不如把模型加厚(其实深度差不多),每一次feature map尺度的变化前后,我都尽可能地多做分析,把想得到的不同来源的信息都尽可能得到,这样的特征应该会更有价值吧!
Goto:[Tensorflow] Cookbook - Retraining Existing CNNs models - Inception Model
从乘法模型到加法模型(ResNet)
ResNet的核心思路就是把曾经CNN模型中的乘法关系转变成加法关系,让模型有了点“Additive”的味道。关于这个问题,文章中采用一个极端的例子作说明。
假设我们已经有了一个较浅模型,我们的目标是去训练一个更深的模型。理论上如果我们能够找到一个靠谱的优化算法和足够的数据,那么这个更深的模型理论上应该比那个较浅的模型具有更好的表达能力。如果抛开优化和可能的过拟合问题不管,这个道理还是可以成立的。
就算较深的模型不能够超越较浅的模型,至少它是可以作到和具有较浅的模型同样的表达能力。如果我们把较深模型分成两部分——和较浅模型相同的部分,比较浅模型多出来的部分,那么我们保持和较浅模型相同的部分的参数完全相同,同时让多出来的模型部分“失效”,只原样传递数据而不做任何处理,那么较深模型就和较浅的模型完全一样了。
在论文中,这些多出来的模型部分变成了“Identity Mapping”,也就是输入和输出完全一样。
好了,那么对于现在的架构来说,我们如何学习这些“Identity Mapping”呢?过去的学习方法就是按现在的乘法模式进行学习,我们一般的CNN模型都是一层套一层,层与层之间的关系是乘法,下一层的输出是上一层输入和卷积相乘得到的。学习这样的“Identity Mapping”还是有一点困难的,因为只要是想学到一个具体数值,它就具有一定的难度,不论是“Identity Mapping”还是其他。
于是,ResNet对上面的问题做了一些改变。既然是要学习“Identity Mapping”,那么我们能不能把过去的乘法转变为加法?【有利于训练更深的网络】
我们假设多出来的层的函数形式是F(x),那么乘法关系学习“Identity Mapping”就变成了非常深的网络也可以训练,这也验证了将乘法关系改为加法关系后对模型训练带来的显著提升。 ,由于学习的形式没有变,对于乘法我们学习起来同过去一样,但是对于加法就简单多了—— ,只要将参数学习成0就可以了,0和其他数值相比具有很大的优势,这样训练难度就大大降低了。于是,我们也见到即使
在ResNet之前,还有一些网络已经提出了类似的思想,比如Highway-Network。Highway-Network同样具有加法的特点,但是它并不是一个纯粹的加法,所以在优化过程总较ResNet弱一些。