CNN_知识点随笔积累

1. 前言

  • 此篇博文用来记录平常见到的CNN有关讲解很清楚的知识点
  • 目前文章包括如下内容:(会坚持更新来记录有关CNN的点)
    • 全连接层流程及softmax loss
    • 过拟合欠拟合问题
    • 权重衰减的推导
    • 归一化
    • 网络参数的几种初始化方式
    • 机器学习模型的三种评估方法
    • 感受野
    • 全局平均池化

2. 全连接层和损失层

2.1 过程分析

  • 先理清下从全连接层到损失层之间的计算:

  • 这张图的等号左边部分就是全连接层做的事,W是全连接层的参数,我们也称为权值,X是全连接层的输入,也就是特征
  • 从图上可以看出特征X是N$\times$1的向量,这是怎么得到的呢?这个特征就是由全连接层前面多个卷积层和池化层处理后得到的,假设全连接层前面连接的是一个卷积层,这个卷积层的输出是100个特征(也就是我们常说的feature map的channel为100),每个特征的大小是4$\times$4,那么在将这些特征输入给全连接层之前会将这些特征flat成N$\times$1的向量(这个时候N就是100$\times$4$\times$4=1600)。

  • 再来看W,W是全连接层的参数,是个T$\times$N的矩阵,这个N和X的N对应,T表示类别数,比如你是7分类,那么T就是7。我们所说的训练一个网络,对于全连接层而言就是寻找最合适的W矩阵。因此全连接层就是执行WX得到一个T$\times$1的向量(也就是图中的logits[T$\times1$]),这个向量里面的每个数都没有大小限制的,也就是从负无穷大到正无穷大。

  • 然后如果你是多分类问题,一般会在全连接层后面接一个softmax层,这个softmax的输入是T$\times$1的向量,输出也是T$\times$1的向量(也就是图中的prob[T$\times$1],这个向量的每个值表示这个样本属于每个类的概率),只不过输出的向量的每个值的大小范围为0到1。

  • softmax的输出向量就是概率,该样本属于各个类的概率

2.2 softmax

  • softmax的公式:
  • 公式非常简单,前面说过softmax的输入是WX,假设模型的输入样本是L,讨论一个3分类问题(类别用1,2,3表示),样本L的真实类别是2,那么这个样本L经过网络所有层到达softmax层之前就得到了WX,也就是说WX是一个3$\times$1的向量,那么上面公式中的$a_j$就表示这个3$\times$1的向量中的第j个值(最后会得到S1,S2,S3);而分母中的$a_k$则表示3$\times$1的向量中的3个值,所以会有个求和符号(这里求和是k从1到T,T和上面图中的T是对应相等的,也就是类别数的意思,j的范围也是1到T)。
  • 因为$e^x$恒大于0,所以分子永远是正数,分母又是多个正数的和,所以分母也肯定是正数,因此$S_j$是正数,而且范围是(0,1)。如果现在不是在训练模型,而是在测试模型,那么当一个样本经过softmax层并输出一个T$\times$1的向量时,就会取这个向量中值最大的那个数的index作为这个样本的预测标签。

2.3 softmax loss

  • 公式如下:
  • 首先L是损失。$S_j$是softmax的输出向量S的第j个值,前面已经介绍过了,表示的是这个样本属于第j个类别的概率。$y_j$前面有个求和符号,j的范围也是1到类别数T,因此y是一个1$\times$T的向量,里面的T个值,而且只有1个值是1,其他T-1个值都是0。那么哪个位置的值是1呢?答案是真实标签对应的位置的那个值是1,其他都是0。所以这个公式其实有一个更简单的形式:
  • 当然此时要限定j是指向当前样本的真实标签。
  • 假设一个5分类问题,然后一个样本I的标签y=[0,0,0,1,0],也就是说样本I的真实标签是4,假设模型预测的结果概率(softmax的输出)p=[0.1,0.15,0.05,0.6,0.1],可以看出这个预测是对的,那么对应的损失L=-log(0.6),也就是当这个样本经过这样的网络参数产生这样的预测p时,它的损失是-log(0.6)。

  • 那么假设p=[0.15,0.2,0.4,0.1,0.15],这个预测结果就很离谱了,因为真实标签是4,而你觉得这个样本是4的概率只有0.1(远不如其他概率高,如果是在测试阶段,那么模型就会预测该样本属于类别3),对应损失L=-log(0.1)。那么假设p=[0.05,0.15,0.4,0.3,0.1],这个预测结果虽然也错了,但是没有前面那个那么离谱,对应的损失L=-log(0.3)。我们知道log函数在输入小于1的时候是个负数,而且log函数是递增函数,所以-log(0.6) < -log(0.3) < -log(0.1)。简单讲就是你预测错比预测对的损失要大,预测错得离谱比预测错得轻微的损失要大。

2.3 cross entropy 交叉熵

  • 公式如下:
  • 当cross entropy的输入P是softmax的输出时,cross entropy等于softmax loss。Pj是输入的概率向量P的第j个值,所以如果你的概率是通过softmax公式得到的,那么cross entropy就是softmax loss。

3. 过拟合与欠拟合

  • 对于有监督学习算法,例如分类和回归,通常有两种情况下生成的模型不能很好地拟合数据:欠拟合(underfitting)过拟合(overfitting)
  • 有监督学习算法的一个重要度量是泛化它衡量从训练数据导出的模型对不可见数据的期望属性的预测能力。当我们说一个模型是欠拟合或过拟合时,它意味着该模型没有很好地推广到不可见数据。
  • 一个与训练数据相拟合的模型并不一定意味着它能很好地概括不可见数据。有以下几点原因
    • 训练数据只是我们从现实世界中收集的样本,只代表了现实的一部分。这可能是因为训练数据根本不具有代表性,因此即使模型完全符合训练数据,也不能很好的拟合不可见数据。
    • 我们收集的数据不可避免地含有噪音和误差。即便该模型与数据完全吻合,也会错误地捕捉到不期望的噪音和误差,最终导致对不可见数据的预测存在偏差和误差。
  • 如下图所示的三种情况:

  • 欠拟合:
    • 欠拟合模型是指不能很好地拟合训练数据的模型,即显著偏离真实值的模型。
    • 欠拟合的原因之一可能是模型对数据而言过于简化,因此无法捕获数据中隐藏的关系。
    • 从上图(1)可以看出,在分离样本(即分类)的过程中,一个简单的线性模型(一条直线)不能清晰地画出不同类别样本之间的边界,从而导致严重的分类错误。
    • 为了避免上述欠拟合的原因,我们需要选择一种能够从训练数据集生成更复杂模型的替代算法。
  • 过拟合:
    • 过拟合模型是与训练数据拟合较好的模型,即误差很小或没有误差,但不能很好地推广到不可见数据。
    • 与欠拟合相反,过拟合往往是一个能够适应每一位数据的超复杂模型,但却可能会陷入噪音和误差的陷阱。从上面的图(3)可以看出,虽然模型在训练数据中的分类错误少了,但在不可见数据上更可能出错。
    • 类似地于欠拟合的情况,为了避免过拟合,可以尝试另一种从训练数据集生成更简单的模型的算法。
    • 或者更常见的情况是,使用生成过拟合模型的原始算法,但在算法中增加添加了正则项,即对过于复杂的模型进行附加处理,从而引导算法在拟合数据的同时生成一个不太复杂的模型。
  • 解决过拟合问题的几种方法:
    • 减小网络大小,防止过拟合的最简单的方法就是减小模型大小,即减少模型中可学习参数的个数(这由层 数和每层的单元个数决定)。
    • 添加权重正则化:强制让模型权重只能取较小的值, 从而限制模型的复杂度,这使得权重值的分布更加规则。其实现方法是向网络损失函数中添加与较大权重值相关的成本,成本有L1正则化、L2正则化
    • Dropout正则化:对某一层使用 dropout,在训练过程中随机将该层的一些输出特征舍弃(设置为 0)。

4. 权重衰减

  • 过拟合现象:即模型的训练误差远小于它在测试集上的误差。虽然增大训练数据集可能会减轻过拟合,但是获取额外的训练数据往往代价高昂。
  • 本部分学习应对过拟合问题的常用方法:权重衰减(weight decay)。

  • 权重衰减等价于$L_2$范数正则化(regularization)。正则化通过为模型损失函数添加惩罚项使学出的模型参数值较小,是应对过拟合的常用手段。

  • 我们先描述$L_2$范数正则化,再解释它为何又称权重衰减。
  • $L_2$范数正则化在模型原损失函数基础上添加$L_2$范数惩罚项,从而得到训练所需的最小化的函数。
  • $L_2$范数惩罚项指的是模型权重参数每个元素的平方和与一个正的常数的乘积。

  • 以下面的线性回归损失函数为例:

  • 其中$w_1,w_2$是权重参数,b是偏重参数,样本i的输入为$x_1^{(i)},x_2^{(i)}$,标签为$y^{(i)}$,样本数为n。
  • 将权重参数用向量$w = [w_1,w_2]$表示,带有$L_2$范数惩罚项的新损失函数为:
  • 其中超参数$\lambda > 0$。当权重参数为0时,惩罚项最小;当$\lambda$较大时,惩罚项在损失函数中的比重较大,这通常使得学到的权重参数的元素较接近0。
  • 在线性回归中,使用小批量梯度下降方法优化,选取一组模型参数的初始值,如随机选取;接下来对参数进行多层迭代,使每次迭代都可能降低损失函数的值。每次迭代时,先随机均匀采样一个由固定数目训练数据样本所组成的小批量$B$,求小批量数据样本的平均损失有关模型参数的导数(梯度),最后用此结果与预先设定的一个正数的乘积作为模型参数在本次迭代的减小量。迭代过程如下:
  • 在上式中,$B$代表每个小批量中的样本个数(批量大小,batch size),$\eta$称作学习率(learning rate)并取正数。需要强调的是,这里的批量大小和学习率的值是人为设定的,并不是通过模型训练学出的,因此叫作超参数(hyperparameter)。我们通常所说的“调参”指的正是调节超参数,例如通过反复试错来找到超参数合适的值。在少数情况下,超参数也可以通过模型训练学出。

  • 在有了$L_2$范数惩罚项之后,在小批量梯度下降中,将权重$w_1$和$w_2$的迭代方式求导之后如下

  • 上述迭代公式的转换如果看不懂,可以参考该博文的推导:链接

  • 经过上述推导及公式,可得知$L_2$范数正则化令权重$w_1,w_2$先乘以了小于1的数,再减去不含惩罚项的梯度。所以,$L_2$范数正则化又叫权重衰减。权重衰减通过惩罚绝对值较大的模型参数为需要学习的模型增加了限制,这可能会对过拟合有效。

5. 归一化

  • 在机器学习中,对输入特征做归一化预处理操作是常见的步骤。类似的,在图像处理中,同样的可以将图像的每个像素信息看作是一种特征。
  • 在实践中,对每个特征减去平均值来中心化数据是非常重要的,这种归一化处理方式被称作“中心式归一化”。
  • 卷积神经网络中的数据预处理操作:通常是计算训练集图像像素均值,之后在处理训练集、验证集和测试集图像时需要分别减去该均值。
  • 减均值的原理:我们默认自然图像是一类平稳的数据分布(即数据每一个维度的统计都服从相同分布),此时,从每个样本上减去数据的统计平均值(逐样本计算)可以移除共同部分,凸显个体差异。
  • 需要注意的是,在实际操作中应该首先划分好训练集、验证集和测试集。而均值只针对划分后的训练集计算。不可直接在未划分的所有图像上计算均值,如此会违背机器学习的基本原理:在模型训练中,能且仅能从训练数据中获取信息。
  • 下图展示了图像减均值(未重塑)的效果。通过肉眼观察可以发现,“天空”等背景部分被有效“移除”了,而“车”和“建筑”等显著区域被“凸显”出来。

6. 网络参数初始化

  • 神经网络模型一般依靠随机梯度下降法进行模型训练和参数更新,网络的最终性能与收敛得到的最优解直接相关,而收敛效果实际 上又很大程度取决于网络参数最开始的初始化。

6.1 全零初始化

  • 通过合理的数据预处理和规范化,网络收敛到稳定状态时,参数(权值)在理想情况下应基本保持正负各半的状态(此时的期望为0)。
  • 由上,一种简单且听起来合理的方法就是全零初始化,将所有的参数都初始化为零,因为这样,初始化为0得到的参数的期望与网络稳定时得到的期望是一致的,都为0。但是:参数全为0时,网络不同神经元的输出必然相同,相同输出则会导致梯度更新完全一样,这样就会使得更新后的参数保持一样的状态。换句话说,如果参数全零初始化,网络的神经元无法做出改变和进行训练。

  • 所以,不能进行全零初始化。

6.2 随机初始化

  • 可将参数值随机设定为接近 y 的一个 很小的随机数(有正有负)。在实际应用中,随机参数服从高斯分布或均匀分布都是较有效的初始化方式。
  • tf中见到的随机填充初始化:
1
2
3
4
def weight_variable(shape):        # 权值w初始化设置 stddev=0.1:给一些偏差0.1防止死亡节点
initial = tf.truncated_normal(shape,stddev=0.1)
# tf.truncated_normal函数返回指定形状的张量填充随机截断的正常值
return tf.Variable(initial) # 创建一个变量
1
2
3
4
5
6
7
8
9
10
11
# 定义所有的网络参数  random_normal:将返回一个指定形状的张量,通过随机的正常值填充
weights = {
'wc1': tf.Variable(tf.random_normal([11, 11, 1, 96])),
'wc2': tf.Variable(tf.random_normal([5, 5, 96, 256])),
'wc3': tf.Variable(tf.random_normal([3, 3, 256, 384])),
'wc4': tf.Variable(tf.random_normal([3, 3, 384, 384])),
'wc5': tf.Variable(tf.random_normal([3, 3, 384, 256])),
'wd1': tf.Variable(tf.random_normal([4*4*256, 4096])),
'wd2': tf.Variable(tf.random_normal([4096, 1024])),
'out': tf.Variable(tf.random_normal([1024, n_classes]))
}

6.3 Xavier初始化(针对tanh激活函数)

  • tf中实现的:
1
2
3
kernel = tf.get_variable(scope + "w",
shape=[kh,kw,n_in,n_out],dtype=tf.float32,
initializer=tf.contrib.layers.xavier_initializer_conv2d())

6.4 He初始化(针对ReLU激活函数)

7. 模型评估

  • 评估模型的重点是将数据划分为三个集合:训练集、验证集和测试集。在训练数据上训练模型,在验证数据上评估模型。一旦找到了最佳参数,就在测试数据上最后测试一次。
  • 在于开发模型时总是需要调节模型配置,比如选择层数或每层大小,这叫作模型的超参,以便与模型参数(即权重)区分开】。
  • 这个调节过程需要使用模型在验证数据上的性能作为反馈信号。这个调节过程本质上就是一种学习:在某个参数空间中寻找良好的模型配置。

  • 将数据划分为训练集、验证集和测试集可能看起来很简单,但如果可用数据很少,还有几 种高级方法可以派上用场。以下是三种经典的评估方法:简单的留出验证、K 折验证,以及带有打乱数据的重复 K 折验证

7.1 简单的留出验证

  • 留出一定比例的数据作为测试集。在剩余的数据上训练模型,然后在测试集上评估模型。为了防止信息泄露,不能基于测试集来调节模型,所以还应该保留一个验证集。
  • 代码流程如下所示:

  • 这是最简单的评估方法,但有一个缺点:如果可用的数据很少,那么可能验证集和测试集包含的样本就太少,从而无法在统计学上代表数据。这个问题很容易发现:如果在划分数据前进行不同的随机打乱,最终得到的模型性能差别很大,那么就存在这个问题。下面的 K 折验证与重复的K折验证,它们是解决这一问题的两种方法。

7.2 K折验证

  • K折验证 (K-fold validation) 将数据划分为大小相同的 K 个分区。对于每个分区 i,在剩余的 K-1个分区上训练模型,然后在分区 i 上评估模型。最终分数等于 K 个分数的平均值。
  • 对于不同的训练集 - 测试集划分,如果模型性能的变化很大,那么这种方法很有用。与留出验证一样,这种方法也需要独立的验证集进行模型校正。

  • 代码流程如下:

7.3 带有打乱数据的重复k折验证

  • 如果可用的数据相对较少,而你又需要尽可能精确地评估模型,那么可以选择带有打乱数据的重复K折验证。
  • 具体做法是多次使用 K 折验证,在每次将数据划分为 K 个分区之前都先将数据打乱。 最终分数是每次 K 折验证分数的平均值。注意,这种方法一共要训练和评估 P×K 个模型(P是重复次数),计算代价很大。

7.4 注意事项

  • 数据代表性:例如,你想要对数字图像进行分类,而图像样本是按类别排序的,如果你将前 80% 作为训练集,剩余 20% 作为测试集,那么会导致训练集中只包含类别 0~7,而测试集中只包含 类别 8~9。这个错误看起来很可笑,却很常见。因此,在将数据划分为训练集和测试集之前,通常应该随机打乱数据
  • 时间箭头:如果想要根据过去预测未来(比如明天的天气、股票走势等),那么在划分数据前你不应该随机打乱数据,因为这么做会造成时间泄露:你的模型将在未来数据上得到有效训练。在这种情况下,你应该始终确保测试集中所有数据的时间都晚于训练集数据。
  • 数据冗余:如果数据中的某些数据点出现了两次(这在现实中 的数据里十分常见),那么打乱数据并划分成训练集和验证集会导致训练集和验证集之 间的数据冗余。一 定要确保训练集和验证集之间没有交集。

8. 感受野

8.1 感受野定义

  • 卷积神经网络每一层输出的特征图(feature map)上的像素点在输入图片上映射的区域大小

    The receptive field is defined as the region in the input space that a particular CNN’s feature is looking at (i.e. be affected by).

  • 看一个简单例子:

  • 两层 3 x 3 的卷积核卷积操作之后的感受野是 5 x 5,其中卷积核(filter)的步长(stride)为1、padding为0,如图所示:

    • 上半部分图中,对于输出的特征图的一个像素点,在输入图像的映射的区域大小就是5 x 5
    • 下半部分图中,对于第三层的输出的像素点,在第二层的映射的区域范围为3 x 3,对于第二层而言,输出的3 x 3的特征图的每个像素点在第一层的映射范围为3 x 3,但是,第二层的特征图,即第一层卷积之后的输出,对于第一层来说,感受野为全部的5 x 5,所以,对于第三层在第一层的感受野也是5 x 5

  • 三层 3 x 3 卷积核操作之后的感受野是 7 x 7,其中卷积核的步长为1,padding为0,如图所示:

8.2 The fixed-sized CNN feature map visualization

  • 接下来的内容来自A guide to receptive field arithmetic for Convolutional Neural Networks

  • 感受野指的是一个特定的CNN特征(特征图上的某个点)在输入空间所受影响的区域

  • 一个感受野可以用中心位置(center location)和大小(size)来表征。
  • 然而,对于一个CNN特征来说,感受野中的每个像素值(pixel)并不是同等重要。一个像素点越接近感受野中心,它对输出特征的计算所起作用越大。

  • 现在,我们关注如何计算一个特定感受野的中心位置和大小。

  • 下图为给出了感受野实例。其中输入特征图大小为 5 X 5,采用的卷积参数如下:卷积核大小 k= 3 X 3,padding大小p = 1,步长s = 2 。经过一次卷积之后,得到大小为 3 X 3的输出特征图(绿色)
  • 在这个特征图上继续采用相同的卷积,即参数都一致,得到一个 2 X 2的特征图(橙色)。输出特征图的大小可以通过如下公式计算:

  • 对上述图片结果的解释:

    • 上半部分的输入为5 x 5,padding = 1,在该卷积下得到3 x 3的输出,可由计算公式得来。
    • 下半部分,因为后来的橙色是由绿色部分由相同的卷积得来,所以padding=1,所以此时的中间绿色应该补一圈0,即变为5 x 5。这时,因为绿色变为5 x 5,所以原来的输入也该增大,所以就如上图所示。
  • 在这个可视化中,我们可以看到每个特征图所包含的特征数,但是很难知道每个特征的感受野的中心位置和大小

  • 下图给出了固定大小的的CNN可视化,所有的特征图固定大小并保持与输入特征图大小一致

  • 每个特征被标记在其感受野所在的中心(从而定位出感受野中心位置)。由于一个特征图中所有的特征都有相同大小的感受野,我们可以简单地在每个特征周围画出一个边界框,从而获得感受野的大小。

9. 全局平均池化

  • global average pooling,GAP层,通过降低模型的参数数量来最小化过拟合效应。
  • 类似最大池化层,GAP层可以用来降低三维张量的空间维度,然而,GAP层的降维更加激进,一个h × w × d的张量会被降维至1 × 1 × d。GAP层通过取平均值映射每个h × w的特征映射至单个数字

  • 如下图所示:

  • 在pytorch中,没有全局平均池化的实现,使用平均池化,将核的大小设为输入的高和宽即可:
1
2
3
4
5
6
7
8
9
class GlobalAvgPool2d(nn.Module):
# 全局平均池化层可通过将池化窗口形状设置成输入的高和宽实现
def __init__(self):
super(GlobalAvgPool2d, self).__init__()
def forward(self, x):
return F.avg_pool2d(x, kernel_size=x.size()[2:])

net.add_module("global_avg_pool", GlobalAvgPool2d())
# GlobalAvgPool2d的输出: (Batch, 512, 1, 1)
-------------The End-------------
谢谢大锅请我喝杯阔乐~