# CNN卷积神经网络--潘登同学的深度学习笔记
卷积层
- CNN 里面最重要的构建单元就是卷积层
- 神经元在第一个卷积层不是连接输入图片的每一个像素,只是连接它们感受野的像素, 以此类推,第二个卷积层的每一个神经元仅连接位于第一个卷积层的一个小方块的神经元
卷积核
举例说明
- image 5*5(黑白图片)
- filter 3*3
- feature map 3*3
- 由图片推到特征图的过程称为卷积,由特征图推回图片的过程称为反卷积
- 一个卷积核可以理解为要训练的w矩阵, 一个卷积核带着一个bias截距项
彩色图片的卷积
彩色图片有三通道, 我们将彩色图片的像素定义为这个矩阵 $$ X_{h \times w \times c} $$
- h: 表示图片的高度
- w: 表示图片的宽度
- c: 表示图片的通道数
Input 有多少通道,卷积核就必须要有几个通道(但大小可以自己决定)
举例说明
- image 5*5*3(彩色图片)
- filter 3*3*3
- feature map 3*3
这里的feature map计算是将filter的每一层与image的每一层,相乘相加, 得到三层的'featrue',最后将这三个相加再加上bias得到的feature
举例说明
- image 5*5*3(彩色图片)
- filter 3*3*3
*2
- feature map 3*3
*2
这里在filter后加了一个2, 表示使用了两个卷积核, 那么就会得到两个通道的feature map; 多用卷积核的意思就是在不同角度审视原图,卷积核越多越好,但是计算量、过拟合等问题需要注意。
权值共享问题
对于DNN来说,以输入层与第一层隐藏层连接来说,参数是$W_{input \times n_1} + b$总共有inputn + 1个参数要训练, 而对于一个$w_{33*3}$的卷积核来说,总共有27+1个参数要训练;是用一个filter去扫描一整张图片后,得到一个featrue map. 这样的过程就称为权值共享
卷积核的扫描方式
在前面的举例中,我们都是默认卷积核每次扫描都是往右或者往下走一步, 其实也可以多走几步,以人的眼睛为例,看一个电脑的左侧其实与看一个电脑的右侧没有什么区别,所以对于重复的区域其实不需要看这么多;
- stride(步幅)
stride表示移动的步长,表示filter在原图上移动多少距离,表示感受野的重合
举例说明
- image 5*5
- filter 2*2
- stride水平 2
- stride垂直 3
- feature map 1*2
Padding模式
对于上图,会发现垂直步长为3,其实损失掉了最后一行的数据,我们不想损失的话,可以采用SAME模式
SAME模式(补零)
SMAE模式下, $$ ouput = ceil(\frac{输入}{步长}) $$
如果步长为1,那么输入与输出尺寸一样大
注意
: 这个加零并不是凭空增加的,默认是在最下边和最右边加,但是也可以自己指定: 加多少行,加多少列,加到哪里
注意
: 对一张图片就算碰巧够卷也添加一圈零, 目的是: 将边缘的像素信息也作为感受野的中心点
补零公式
补多少行、列零 = filter 大小 - (imgae的宽+1) % 步长 - 1
VALID模式(不补零)
可能会损失掉右侧和底部的数据
代码实现卷积
导包
import numpy as np
from sklearn.datasets import load_sample_images
import tensorflow as tf
import matplotlib.pyplot as plt
导入数据
# 加载数据集
# 输入图片通常是3D,[height, width, channels]
# mini-batch通常是4D,[mini-batch size, height, width, channels]
dataset = np.array(load_sample_images().images, dtype=np.float32)
# 数据集里面两张图片,一个中国庙宇,一个花
batch_size, height, width, channels = dataset.shape
print(batch_size, height, width, channels)
创建卷积核
# 创建两个filters
# 高,宽,通道,卷积核
# 7, 7, channels, 2
filters_test = np.zeros(shape=(7, 7, channels, 2), dtype=np.float32)
filters_test[:, 3, :, 0] = 1 # 垂直 这个不重要,只是测试用例这么写,训练时会变化的
filters_test[3, :, :, 1] = 1 # 水平
创建网络结构--卷积层
# filter参数是一个filters的集合
X = tf.placeholder(tf.float32, shape=(None, height, width, channels))
# strides=[1, 2, 2, 1] 中第一最后一个为1(表示每一个样本,每一个通道都做卷积),中间对应sh和sw(表示宽高)
convolution = tf.nn.conv2d(X, filter=filters_test, strides=[1, 2, 2, 1], padding='SAME')
开卷
with tf.Session(config=config) as sess:
output = sess.run(convolution, feed_dict={X: dataset})
print(output.shape)
plt.imshow(load_sample_images().images[0]) # 绘制第一个图
plt.show()
plt.imshow(output[0, :, :, 0]) # 绘制第一个图的第一个特征图
plt.show()
plt.imshow(output[0, :, :, 1]) # 绘制第一个图的第二个特征图
plt.show()
结果展示
- 原图
- 第一个卷积核的特征图
- 第二个卷积核的特征图
池化层 Pooling
目的
降采样 subsample,shrink,减少计算负荷,内存使用,参数数量(也可防 止过拟合)
作用
- 正如卷积神经网络一样,在池化层中的每个神经元被连接到上面一层输出的神经元,只 对应一小块感受野的区域。我们必须定义大小,步长,padding 类型
- 池化神经元没有权重值,他只是聚合输入数据取最大值或者求均
最常用的池化核: 2*2大小的,步长为2的,valid
对输入的影响
输入图像的图像会变为之前的$\frac{1}{4}$
池化层工作于每一个独立的输入通道,所以输出的深度和输入的深度(通道数)相同
代码实现池化
# 加载数据集
# 输入图片通常是3D,[height, width, channels]
# mini-batch通常是4D,[mini-batch size, height, width, channels]
dataset = np.array(load_sample_images().images, dtype=np.float32)
# 数据集里面两张图片,一个中国庙宇,一个花
batch_size, height, width, channels = dataset.shape
print(batch_size, height, width, channels)
# 创建输入和一个池化层
X = tf.placeholder(tf.float32, shape=(None, height, width, channels))
# TensorFlow不支持池化多个实例,所以ksize的第一个batch size是1
# TensorFlow不支持池化同时发生的长宽高,所以必须有一个是1,这里channels就是depth维度为1
max_pool = tf.nn.max_pool(X, ksize=[1, 4, 4, 1], strides=[1, 4, 4, 1], padding='VALID')
# avg_pool()
with tf.Session(config=config) as sess:
output = sess.run(max_pool, feed_dict={X: dataset})
print(output.shape)
plt.imshow(dataset[0].astype(np.uint8))
plt.show()
plt.imshow(output[0].astype(np.uint8)) # 画输入的第一个图像
plt.show()
plt.imshow(dataset[1].astype(np.uint8))
plt.show()
plt.imshow(output[1].astype(np.uint8)) # 画输入的第二个图像
plt.show()
结果展示
- 原图
- 池化后
CNN架构
- 典型的 CNN 架构堆列一些卷积层
- 一般一个卷积层后跟 ReLU 层,然后是一个池化层,通过网络传递的图片越来越小,但是也越来越深(通道数增加),例如更多的特征 图!
- 最后常规的前向反馈神经网络被添加,由一些全连接的层+ReLU 层组成,最后是输出 层预测,例如一个 softmax 层输出预测的类概率
对一个普通的CNN网络结构分析,加深对卷积和池化的理解
LeNet5架构
特点
- 重复使用卷积-池化-非线性变换
- 使用的是平均池化
- 使用sigmoid作为中间激活函数
- 第6层用的tanh
- 5*5的卷积核
- 7层,少于1M的参数量
优点
- 使用卷积层生成特征图
- 使用池化降采样
缺点
- 计算速度慢(因为当时计算能力差)
- 梯度消失(因为不是用ReLu)
ps: 为啥叫LeNet5而不是LeNet7(因为一共有七层),是因为总共只有5层的参数需要计算