本文CNN原理部分参考复旦大学丘锡鹏老师的《神经网络与深度学习》一书。
卷积神经网络
卷积神经网络(Convolutional Neural Network, CNN)是一种具有局部连接、权重共享等特性的深层前馈神经网络。广泛应用于图像处理领域。MLP神经网络,也叫作全连接网络,如果用全连接网络来处理图像会存在以下两个问题:
1.参数太多。如果输入图像是一个RGB图像,假设100*100*3,那么第一层隐藏层的每个神经元输入会达到100*100*3=30000个,每个连接都有一个独立权重参数w,随着隐藏神经元和隐藏层的增加,这个数字会越来越大,导致整个神经网络的训练效率很低,也容易出现过拟合。
2.图像的局部不变性。图像在尺度缩放、平移、旋转等操作下不会影响其语义特征叫作局部不变性。全连接网络很难体现这种局部不变性特征。
卷积神经网络受生物学上的感受野提出的。感受野(Receptive Field)主要是指听觉、视觉等神经元只受其所支配的刺激区域内的信号。
卷积神经网络主要由卷积层、汇聚层和全连接层交叉堆叠而成的。
卷积运算
一维卷积:一维卷积经常用在信号处理中,用以计算信号的延迟。假设一个信号发生器每个时刻t产生一个信号xt,其信息衰减率为wk,即在k-1个时间步长后,信息为原来的wk倍。我们一般把wk称为滤波器或者卷积核,其实卷积运算本质上也是一种滤波。假设滤波器长度为m,它和一个信号序列x1,x2,...的卷积为:
假设一个信号序列[1,1,2,-1,1,-2,1],在滤波器[-1,0,1]上做卷积,得到的结果就是:
(1*(-1)+1*0+2*1),(1*(-1)+2*0+(-1)*1),(2*(-1)+(-1)*0+1*1),((-1)*(-1)+1*0+(-2)*1),(1*(-1)+(-2)*0+1*1) = [1,-2,-1,-1,0]
二维卷积:在图像处理中用的最多的是二维卷积。给定一个M*N的图像X,和m*n的卷积核W,其卷积为:
可以看到,这里其实将卷积核进行了翻转再计算的,按书中所说,这种操作才是真正的卷积,我们深度学习中所作的“卷积”其实是一种“互相关”操作运算,是指对应元素相乘并求和。如果按照深度学习中的“卷积”操作,这个运算的结果应该是:
卷积运算的变种
卷积运算有两种变种,一种是改变滤波器也就是卷积核的步长
假设之前的信号序列[1,1,2,-1,1,-2,1],在滤波器[-1,0,1]上做卷积,步长为2的话,结果就是:
(1*(-1)+1*0+2*1),(2*(-1)+(-1)*0+1*1),(1*(-1)+(-2)*0+1*1) = [1,-1,0]
另一种就是在信号序列的两端补0
假设之前的信号序列[1,1,2,-1,1,-2,1],在两端补零后,变成[0,1,1,2,-1,1,-2,1,0],在滤波器[-1,0,1]上做卷积,结果就是:
(0*(-1)+1*0+1*1),(1*(-1)+1*0+2*1),(1*(-1)+2*0+(-1)*1),(2*(-1)+(-1)*0+1*1),((-1)*(-1)+1*0+(-2)*1),(1*(-1)+(-2)*0+1*1),((-2)*(-1)+1*0+0*1) = [1,1,-2,-1,-1,0,2]
假设卷积层的输入神经元个数为n,卷积大小为m,步长为s,输入神经元两端各补p个零,那么该卷积层的神经元数量为(n-m+2p)/s+1。
程序实现
下面我们来用程序实现一下卷积运算,并看看图像经过卷积之后会有什么变化。
import numpy as np
# 1维卷积
def conv1d(arr, kernel, step):
result = []
for i in range(0, len(arr)-len(kernel)+1, step): # 遍历到最后一次卷积运算可以把序列全部计算完即可
temp = arr[i:i+len(kernel)]
r = np.dot(temp, kernel)
result.append(r)
return result
arr = [1,1,2,-1,1,-2,1]
kernel = [-1,0,1]
conv1d(arr,kernel,1)
# 输出:[1, -2, -1, -1, 0]
可以看到和我们之前手动计算出来的结果是一样的。
下面看一下二维卷积的计算:
# 二维卷积,不考虑补零的情况
def conv2d(arr, kernel, step):
arr = np.array(arr)
kernel = np.array(kernel)
m,n = arr.shape
d = np.floor((n-len(kernel))/step)+1 # 结果的大小
d = int(d)
result = np.zeros((d,d)) # 创建一个结果矩阵
for j in range(0,m-len(kernel)+1):
for i in range(0, n-len(kernel)+1, step):
temp = arr[j:j+len(kernel),i:i+len(kernel)]
z = temp*kernel # 对应元素相乘
result[j,i] = np.sum(z) # 相乘后得到的矩阵各个元素求和
return result
arr_2d = [
[1,1,1,1,1],
[-1,0,-3,0,1],
[2,1,1,-1,0],
[0,-1,1,2,1],
[1,2,1,1,1]
]
kernel_2d = [
[1,0,0],
[0,0,0],
[0,0,-1]
]
conv2d(arr_2d,kernel_2d,1)
# 输出结果:
array([[ 0., 2., 1.],
[-2., -2., -4.],
[ 1., 0., 0.]])
可以看到,跟我们之前手动计算的结果也是一样的。下面来看一下卷积运算对于图像的作用。我们首先读取一副图像,并选择一个卷积核对其进行卷积运算,再查看结果。
from PIL import Image
import matplotlib.pyplot as plt
im = Image.open('lena.jpg').convert('L')
plt.imshow(im)
这里matplotlib对图像进行了伪色彩展示,其实如果保存下来就是一副灰度图像。
kernel = [
[0,1,0],
[1,-4,1],
[0,1,0]
]
rr = conv2d(im,kernel,1) # 卷积运算
plt.imshow(rr)
可以看到,提取出来了一些边缘信息。
kernel = [
[0,1,1],
[-1,0,1],
[-1,-1,0]
]
rr = conv2d(im,kernel,1) # 卷积运算
plt.imshow(rr)
这个卷积核更清晰的把边缘提取出来了,并且有点雕刻的效果了。
下次,我们就实际来看看卷积神经网络的具体实现以及用卷积神经网络进行图像分类。