文章目录
卷积神经网络
前面讲述了逻辑回归分类,模拟函数回归问题,单层,深层网络,它们以点和向量为代表,接下来讲述如何识别一张图片,
假设一张图片大小为1000x1000,rgb图像,我们应该如何对它识别分类
我们可以像处理手写数字识别一样把图片展开成一行,然后对每个数据参数求权重,然而实际操作确是极其不现实的,也是不合理的
因为仅仅输入就有300万参数,如果我们希望第一层拥有1000个神经元来对他进行输入,那么参数量将会是310^9
如果图像大小为1000010000那么处理一张图片的参数量将会十分巨大
传统意义上处理图片,有缩放,模糊,锐化等操作,可以在一定程度上降低图像质量和大小,但是不影响照片整体的判断
对图片的某个区域进行筛选出特征值,进行缩放是传统处理图像的技巧
受此启发,于是有人提出了滑动窗口的概念,我们不必把图片看做一个整体,而是让神经网络从局部窗口一个个观察它的特征,比如看看猫的耳朵,看看猫的眼睛,最后综合起来,也许不同的窗口学习了图像不同的特征,最后我们用这些特征来评价一张图片,这个滑动窗口,叫做卷积核
它在图片上滑动的过程中,与图片上对应的区域做卷积运算,最后我们训练的参数不再是图片上的每个像素点的权重,而是滑动窗口的权重
卷积核的大小,叫做ksize,在图片上移动的步长叫做stride,如果在图片移动过程中图片大小无法满足卷积核的运算要求,往往还会在原图象上补0以让卷积核进行扫描,这个操作称为padding
经过卷积核ksize和stride扫描后新图像的形状可以由以下公式得出
w为输入图像宽高,卷积核大小为k,填充大小为p,滑动步长为s
那么这个公式是如何得出的,滑动窗口又是如何移动的?
滑动步长(Stride):卷积核在图像上滑动的像素数量被称为滑动步长。例如,如果步长设置为1,那么卷积核在每次操作后会向右移动一个像素位置。如果步长设置为2,则卷积核每次会跳过一个像素位置,即每次移动两个像素。每次扫描完一行后,回到图像最左端,根据滑动步长向下移动一个步长,可以是设定的1个像素,或者2个像素,继续从左到右扫描
当卷积核开始与图像的左上角部分重叠时,它会与重叠区域内的像素值进行乘法操作(具体是将每个像素值与其对应的卷积核中的权重相乘),然后将所有乘积相加得到一个输出值。这个输出值将成为输出特征图(或称为卷积特征图)的一个像素值。
边界处理:卷积核移动有时不能覆盖全图,因此需要对边界选择是否填充来满足卷积核移动指定步长进行扫描
“VALID”(只使用完全重叠的区域)、“SAME”(通过填充来确保输出特征图与输入图像具有相同的空间维度)或“FULL”(考虑所有可能的重叠方式,包括部分重叠)
以一个3x3的卷积核和一个5x5的图像为例,如果步长设置为1且不使用任何填充,卷积核将从图像的左上角开始,与3x3的区域重叠,然后进行乘法和加法操作。之后,卷积核会向右移动一个像素位置,再次进行相同的操作,直到到达该行的末尾。然后,卷积核会向下移动一个像素位置,并从新行的最左边开始重复上述过程,直到它覆盖整个图像。
没有填充,步长为1
w = 5(输入图像宽度)
k = 3(卷积核大小)
p = 0(填充大小)
s = 1(步长)
使用公式计算输出宽度和高度:
W_out = (5 - 3 + 20) // 1 + 1 = 2 + 1 = 3
H_out = (5 - 3 + 20) // 1 + 1 = 2 + 1 = 3
有填充,步长为1
w = 5(输入图像宽度)
k = 3(卷积核大小)
p = 1(填充大小,即在图像边界周围各添加1个像素)
s = 1(步长)
使用公式计算输出宽度和高度:
W_out = (5 - 3 + 21) // 1 + 1 = 4 // 1 + 1 = 4 + 1 = 5
H_out = (5 - 3 + 21) // 1 + 1 = 4 // 1 + 1 = 4 + 1 = 5
各层概念
每个卷积核就像我们之前进行神经网络训练时的神经元一样,只不过不再是单独的一个w,而是一组w
如下图
输入层–>卷积层–>池化层–>卷积层–>池化层–>全连接层–>全连接层–>全连接层
卷积层很好理解
图像经过每个卷积核后都会生成新的图像,如图32x32x3图像经过5x5x3的卷积运算扫描后得到28x28的一维图像
如果我们有6个卷积核,那么经过第一个卷积层后我们能够得到6个28X28图像,这和以往神经网络对矩阵的运算结果特征十分相似
那么可以说卷积核和他所输出的新图层构成了一个卷积层
当然,卷积层也需要激活函数,对于不必要的特征有时会让relu替我们丢弃
池化层:
作用是对图像大小进行缩放,进行下采样操作,可以迅速降低图像大小而保留图像特征
比如最大池化层max pooling(输出滑动窗口扫描区域的最大值),平均池化层average pooling(输出滑动窗口扫描区域的平均值)
如图,进行快速缩放,最大池化
全连接层:
将图片进行卷积操作后,图像的数据量已经急剧减小,将每张图片摊平成一行,可以为每个筛选下来的图像数据设置权重了
已经可以使用wx进行简单的运算了
至此,卷积神经网络,也被称为cnn,Convolutional Neural Network
实现卷积层,池化层
卷积层:
卷积层重要的是卷积核,需要设置卷积核的大小,扫描步长,以及卷积核的权重
幸运的是pytroch提供了两个函数来实现它,torch.nn.Conv2d(), torch.nn.functional.conv2d()
两者实现的结果是一样的,只是在应用时设置权重的方式不同
torch.nn.Conv2d()不用特意设置权重,设置好卷积核后会自动随机初始化权重,当然也可以指定权重
torch.nn.functional.conv2d()必须为其设定权重才会进行卷积操作
前者用的更多,无论训练和调试都可以胜任
我们用灰度图读取一张图片,查看一下使用conv2d的效果
原始图片
会得到原始图片的宽高,和灰度图
im=Image.open('./cat.png').convert('L')
im=np.array(im,dtype='float32')
plt.imshow(im.astype('uint8'),cmap='gray')
im.shape
接下来随机初始化权重,进行特征提取,我们只需要指定卷积核大小和步长即可
不过,为了进行卷积运算,需要对输入数据进行一次reshape[1,1,224,224]
代表batch size为1,channel为1,height为224,width为224,这是因为pytroch的计算数据格式为NCHW
卷积操作参数为(输入通道数1,输出通道数1,卷积核大小3X3,不设置偏置窗口)
修改卷积核大小可以使用参数(输入通道数1,输出通道数1,kernel_size=(4, 3),不设置偏置窗口)
queeze用来压缩张量,将维度为1的去除,这样就将[1,1,224,224]变为[224,224]的numpy图像矩阵
im=torch.from_numpy(im.reshape((1,1,im.shape[0],im.shape[1])))
conv1=nn.Conv2d(1,1,3,bias=False)
edge1=conv1(Variable(im))
print(conv1.weight.data)
edge1=edge1.data.squeeze().numpy()
plt.imshow(edge1,cmap='gray')
接下来我们为其指定权重,重新查看输出
sobel_kernel=np.array([[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]],dtype='float32')
sobel_kernel=sobel_kernel.reshape((1,1,3,3))
weight=torch.from_numpy(sobel_kernel)
conv1.weight.data=weight
edge1=conv1(im)
print(conv1.weight.data)
edge1=edge1.data.squeeze().numpy()
plt.imshow(edge1,cmap='gray')
我们使用第二种方式,指定参数,查看输出
edge2=F.conv2d(im,weight)
edge2=edge2.data.squeeze().numpy()
plt.imshow(edge2,cmap='gray')
结果是一样的,所以torch.nn.functional.conv2d()并不常用
池化层:
受到传统图像处理的启发,我们可以缩小图像而不影响图像的判别
池化层没有参数,对于图像的处理有最大值池化,均值池化
设置滑动窗口的大小为2x