首先我们对机器学习当中涉及到的数据集划分进行一个简单的复习
在小数据量的时代,如 100、1000、10000 的数据量大小,可以将数据集按照以下比例进行划分:
而在如今的大数据时代,拥有的数据集的规模可能是百万级别的,所以验证集和测试集所占的比重会趋向于变得更小。
以上这些比例可以根据数据集情况选择。
“偏差-方差分解”(bias-variance decomposition) 是解释学习算法泛化性能的一种重要工具。
泛化误差可分解为偏差、方差与噪声,泛化性能是由学习算法的能力、数据的充分性以及学习任务本身的难度所共同决定的。
那么偏差、方差与我们的数据集划分到底有什么关系呢?
所以我们最终总结,方差一般指的是数据模型得出来了,能不能对未知数据的扰动预测准确。而偏差说明在训练集当中就已经误差较大了,基本上在测试集中没有好的效果。
所以如果我们的模型出现了较大的方差或者同时也有较大的偏差,该怎么去解决?
对于高方差,有以下几种方式:
对于高偏差,有以下几种方式:
不断尝试,直到找到低偏差、低方差的框架。
正则化,即在成本函数中加入一个正则化项(惩罚项),惩罚模型的复杂度,防止网络过拟合
比如参数W数量根据特征的数量而定,那么正则化如下
损失函数中增加L2正则化
J ( w , b ) = 1 m ∑ i = 1 m L ( y ^ ( i ) , y ( i ) ) + λ 2 m ∣ ∣ w ∣ ∣ 2 2 J(w,b)=\frac{1}{m}\sum_{i=1}^mL(\hat{y}^{(i)},y^{(i)}) + \frac{\lambda}{2m}{||w||}_2^2 J(w,b)=m1i=1∑mL(y^(i),y(i))+2mλ∣∣w∣∣22
其中的L2范数可以理解为:
λ 2 m ∣ ∣ w ∣ ∣ 2 2 = λ 2 m ∑ j = 1 n x w j 2 = λ 2 m w T w \frac{\lambda}{2m}{||w||}^2_2=\frac{\lambda}{2m}\sum_{j=1}^{n_x}w^2_j = \frac{\lambda}{2m}w^Tw 2mλ∣∣w∣∣22=2mλj=1∑nxwj2=2mλwTw
解释:所有w参数的平方和的结果
损失函数中增加L1正则化
J ( w , b ) = 1 m ∑ i = 1 m L ( y ^ ( i ) , y ( i ) ) + λ 2 m ∣ ∣ w ∣ ∣ 1 J(w,b) = \frac{1}{m}\sum_{i=1}^mL(\hat{y}^{(i)},y^{(i)}) + \frac{\lambda}{2m}{||w||}_1 J(w,b)=m1i=1∑mL(y^(i),y(i))+2mλ∣∣w∣∣1
其中L1范数可以理解为: λ 2 m ∣ ∣ w ∣ ∣ 1 = λ 2 m ∑ j = 1 n x ∣ w j ∣ \frac{\lambda}{2m}{||w||}_1 = \frac{\lambda}{2m}\sum_{j=1}^{n_x}{|w_j|} 2mλ∣∣w∣∣1=2mλ∑j=1nx∣wj∣
注:其中,λ 为正则化因子,是超参数。由于 L1 正则化最后得到 w 向量中将存在大量的 0,使模型变得稀疏化,因此 L2 正则化更加常用。
在损失函数中增加一项,那么其实梯度下降是要减少损失函数的大小,对于L2或者L1来讲都是要去减少这个正则项的大小,那么也就是会减少W权重的大小。这是我们一个直观上的感受。
接下来我们通过反向传播来理解这个其中的L2,对于损失函数我们要反向传播求参数梯度:
d W = ∂ L ∂ w + λ m W dW = \frac{\partial L}{\partial w}+ \frac{\lambda}{m} {W} dW=∂w∂L+mλW
前面的默认损失函数的梯度计算结果默认为backprop,那么更新的参数就为
W : = W − α d W W := W - \alpha dW W:=W−αdW
那么我们将第一个公式带入第二个得到
W : = W − α ( ∂ L ∂ w + λ m W ) W:=W−α(\frac{∂L}{∂w}+\frac{λ}{m}W) W:=W−α(∂w∂L+mλW)
W − α λ m W − α ∗ ∂ L ∂ w W - \frac{\alpha \lambda}{m}W - \alpha*\frac{\partial L}{\partial w} W−mαλW−α∗∂w∂L
所以每次更新的时候都会让 W ( 1 − α λ m ) W(1 - \frac{\alpha \lambda}{m}) W(1−mαλ),这个系数永远小于1,所以我们通常称L2范数为权重衰减。
神经网络中的正则化与逻辑回归相似,只不过参数W变多了,每一层都有若干个权重,可以理解成一个矩阵
我们把 w [ l ] w^{[l]} w[l]理解某一层神经元的权重参数,其中这是加入了L2范数,可以是
对于矩阵的L2范数,有个专业名称叫弗罗贝尼乌斯范数(Frobenius Norm)
正则化因子设置的足够大的情况下,为了使成本函数最小化,权重矩阵 W 就会被设置为接近于 0 的值,直观上相当于消除了很多神经元的影响,那么大的神经网络就会变成一个较小的网络。
在加入正则化项后,当 λ \lambda λ增大,导致 W [ l ] W^[l] W[l]减小, Z [ l ] = W [ l ] a [ l − 1 ] + b [ l ] Z^{[l]} = W^{[l]}a^{[l-1]} + b^{[l]} Z[l]=W[l]a[l−1]+b[l] 便会减小。由上图可知,在 z 较小(接近于 0)的区域里,函数近似线性,所以每层的函数就近似线性函数,整个网络就成为一个简单的近似线性的网络,因此不会发生过拟合。
Droupout论文地址
定义:Droupout是随机的对神经网络每一层进行丢弃部分神经元操作。
对于网络的每一层会进行设置保留概率,即keep_prob。假设keep_prob为0.8,那么也就是在每一层所有神经元有20%的概率直接失效
下面我们来讲解dropout的工作过程
讲解:
1、如果没有做dropout的标准网络,结构和公式如a图所示;
2、而做过dropout的网络,输入其中 r ( l ) r^{(l)} r(l)表示一个由多个独立的服从相同伯努利分布的变量构成的向量,*表示点乘,即对应元素相乘,第 l l l层的输出 y ( l ) y^{(l)} y(l)经过dropout变化为 y ~ ( l ) \tilde y^{(l)} y~(l)。 l + 1 l+1 l+1层的输入和输出算法不变。在应用BP训练时,只对子网络的参数求导即可,测试时子网络的桉树需要被缩放。
伯努利分布指的是对于随机变量X, 参数为p(0
实现随机失活算法,最常用的一种是反向随机失活(inverted dropout) ,这种方式会对每层进行如下代码操作
def dropout(x, level):
if level < 0. or level >= 1:
raise Exception('dropout保持概率在0到1之间')
sample = np.random.binomial(n=1, p=level, size=x.shape)
print(sample)
x *= sample
x /= level
return x
x = np.asarray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype=np.float32)
dropout(x, 0.8)
# 其中这步骤在计算的时候对余下的非0的进行扩大倍数,因为p<0。0/x=0,所以0不影响,训练的时候代码可以直接写上
x /= level
问题:为什么需要去做rescale(重要)
解释原理:
通俗解释:训练的时候只有占比为p 的隐藏层单元参与训练,那么在预测的时候,如果所有的隐藏层单元都需要参与进来,则得到的结果相比训练时平均要大1/p ,为了避免这种情况,就需要测试的时候将输出结果乘以 1/p 使下一层的输入规模保持不变。
数学解释:试的时候不去随机失活?所以为了保证训练和预测的时候期望一样,必须得做scale;我们设置dropout probability为p, 那么该层大约有比例为p的单元会被drop掉,因为每个神经元是否drop就是一次伯努利实验,这层的dropout概率服从伯努利分布,而分布的期望就是np。
在MNIST数据集中的测试,错误率因为增加dropout会有相应降低(p = 0.5用于隐藏层,将p = 0.8用于输入单位)
1、使用dropout对特征的影响
左边没有dropout的情况下,每个单元并没有很明确地要去检测某个图像特征,这是由于单元之间的相互适应性太强,需要组合在一起才能比较好地工作,但是就右图有dropout的情况而言很明显可以看出,每个单元都在检测一种边缘、笔画或者点,也就是说每个单元之间没有那么强的相互适应性。泛化能力会强很多。
2、使用dropout对参数稀疏程度影响
dropout对隐层激活值稀疏性的影响,两种模型均使用ReLU。左:平均激活度的直方图显示大多数单位的平均激活度约为2.0。激活的直方图显示了一个远离零的巨大模式。显然,很大一部分单元具有很高的活化度。右图:平均激活度的直方图显示,大多数单位的平均激活度较小,约为0.7。可以看出加入了dropout以后激活值的分布向0靠近,稀疏性明显增加。
3、dropout 率大小影响
论文坐着做了两种尝试,如果保持隐藏层神经元个数n固定的时候,p的值影响如左图表示,取值0.5~0.8合适;如果是pn保持不变的时候,P也是在0.5到0.8之间比较合适。(在实验的时候做了pn=256第一层网络和pn=512第二层网络进行的测试)
实现下面模型中引入DropOut机制以及L2正则化机制,提高模型效果。
# LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID
z1 = np.dot(W1, X) + b1
a1 = relu(z1)
z2 = np.dot(W2, a1) + b2
a2 = relu(z2)
z3 = np.dot(W3, a2) + b3
a3 = sigmoid(z3)
引入DropOut机制需要做的事情有,前向传播的随机失活,在反向计算损失时候的处理以及在预测阶段的实现。
def forward_propagation_with_dropout(X, parameters, keep_prob=0.5):
"""
带有dropout的前向传播
"""
W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
W3 = parameters["W3"]
b3 = parameters["b3"]
# LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID
# 计算第一层输出,对余下的非0的进行扩大倍数,因为p<0。0/x=0,所以0不影响
Z1 = np.dot(W1, X) + b1
A1 = relu(Z1)
D1 = np.random.binomial(n=1, p=keep_prob, size=A1.shape)
A1 = np.multiply(A1, D1)
A1 /= keep_prob
# 计算第二层输出
Z2 = np.dot(W2, A1) + b2
A2 = relu(Z2)
D2 = np.random.binomial(n=1, p=keep_prob, size=A2.shape)
A2 = np.multiply(A2, D2)
A2 /= keep_prob
# 最后一层输出
Z3 = np.dot(W3, A2) + b3
A3 = sigmoid(Z3)
cache = (Z1, D1, A1, W1, b1, Z2, D2, A2, W2, b2, Z3, A3, W3, b3)
return A3, cache
def backward_propagation_with_dropout(X, Y, cache, keep_prob):
"""
droupout的反向传播
需要随机失活神经元,同样对输出做扩大
"""
m = X.shape[1]
(Z1, D1, A1, W1, b1, Z2, D2, A2, W2, b2, Z3, A3, W3, b3) = cache
dZ3 = A3 - Y
dW3 = 1. / m * np.dot(dZ3, A2.T)
db3 = 1. / m * np.sum(dZ3, axis=1, keepdims=True)
dA2 = np.dot(W3.T, dZ3)
dA2 = np.multiply(dA2, D2)
dA2 /= keep_prob
dZ2 = np.multiply(dA2, np.int64(A2 > 0))
dW2 = 1. / m * np.dot(dZ2, A1.T)
db2 = 1. / m * np.sum(dZ2, axis=1, keepdims=True)
dA1 = np.dot(W2.T, dZ2)
dA1 = np.multiply(dA1, D1)
dA1 /= keep_prob
dZ1 = np.multiply(dA1, np.int64(A1 > 0))
dW1 = 1. / m * np.dot(dZ1, X.T)
db1 = 1. / m * np.sum(dZ1, axis=1, keepdims=True)
gradients = {
"dZ3": dZ3, "dW3": dW3, "db3": db3, "dA2": dA2,
"dZ2": dZ2, "dW2": dW2, "db2": db2, "dA1": dA1,
"dZ1": dZ1, "dW1": dW1, "db1": db1}
return gradients
实现L2正则化机制的前向与反向传播
# -------
# 1、有正则化的损失函数计算
# 2、正则化后的反向传播计算
# -------
def compute_cost_with_regularization(A3, Y, parameters, lambd):
"""
损失函数中增加L2正则化
"""
m = Y.shape[1]
W1 = parameters["W1"]
W2 = parameters["W2"]
W3 = parameters["W3"]
# 1、计算交叉熵损失
cross_entropy_cost = compute_cost(A3, Y)
# 2、加入L2正则化的损失部分
L2_regularization_cost = (1. / m) * (lambd / 2) * (np.sum(np.square(W1)) + np.sum(np.square(W2)) + np.sum(np.square(W3)))
# 两个损失进行合并,完整损失
cost = cross_entropy_cost + L2_regularization_cost
return cost
def backward_propagation_with_regularization(X, Y, cache, lambd):
"""
对增加了L2正则化后的损失函数进行反向传播计算
"""
m = X.shape[1]
(Z1, A1, W1, b1, Z2, A2, W2, b2, Z3, A3, W3, b3) = cache
# 含有正则化的反向传播公式
# z = a - y
# # dw = 1/ m * ((dz*A.T)+ lambd * w)
dZ3 = A3 - Y
dW3 = 1. / m * (np.dot(dZ3, A2.T) + lambd * W3)
db3 = 1. / m * np.sum(dZ3, axis=1, keepdims=True)
dA2 = np.dot(W3.T, dZ3)
dZ2 = np.multiply(dA2, np.int64(A2 > 0))
dW2 = 1. / m * (np.dot(dZ2, A1.T) + lambd * W2)
db2 = 1. / m * np.sum(dZ2, axis=1, keepdims=True)
dA1 = np.dot(W2.T, dZ2)
dZ1 = np.multiply(dA1, np.int64(A1 > 0))
dW1 = 1. / m * (np.dot(dZ1, X.T) + lambd * W1)
db1 = 1. / m * np.sum(dZ1, axis=1, keepdims=True)
gradients = {
"dZ3": dZ3, "dW3": dW3, "db3": db3, "dA2": dA2,
"dZ2": dZ2, "dW2": dW2, "db2": db2, "dA1": dA1,
"dZ1": dZ1, "dW1": dW1, "db1": db1}
return gradients
运行效果
# dropout
迭代次数为 0: 损失结果大小:0.6616994233431039
迭代次数为 10000: 损失结果大小:0.20018054682333422
迭代次数为 20000: 损失结果大小:0.197457875306683
训练集的准确率:
Accuracy: 0.9383886255924171
测试集的准确率:
Accuracy: 0.95
# 正则化
迭代次数为 0: 损失结果大小:0.6543912405149825
迭代次数为 10000: 损失结果大小:0.0
迭代次数为 20000: 损失结果大小:0.0
训练集的准确率:
Accuracy: 0.9289099526066351
测试集的准确率:
Accuracy: 0.95
我们经常会涉及到参数的调优,也称之为超参数调优。目前我们讲过的超参数有:
对于调参,通常采用跟机器学习中介绍的网格搜索一致,让所有参数的可能组合在一起,得到N组结果。然后去测试每一组的效果去选择。
假设我们现在有两个参数
α \alpha α:0.1,0.01,0.001, β \beta β:0.8,0.88,0.9
这样会有9种组合,[0.1, 0.8], [0.1, 0.88], [0.1, 0.9]…….
注:而指数移动平均值参数:β 从 0.9 (相当于近10天的影响)增加到 0.9005 对结果(1/(1-β))几乎没有影响,而 β 从 0.999 到 0.9995 对结果的影响会较大,因为是指数级增加。通过介绍过的式子理解 S 100 = 0.1 Y 100 + 0.1 ∗ 0.9 Y 99 + 0.1 ∗ ( 0.9 ) 2 Y 98 + . . . S_{100} = 0.1Y_{100} + 0.1 * 0.9Y_{99} + 0.1 * {(0.9)}^2Y_{98} + ... S100=0.1Y100+0.1∗0.9Y99+0.1∗(0.9)2Y98+...
通常我们有这么多参数组合,每一个组合运行训练都需要很长时间,但是如果资源允许的话,可以同时并行的训练多个参数模型,并观察效果。如果资源不允许的话,还是得一个模型一个模型的运行,并时刻观察损失的变化
所以对于这么多的超参数,调优是一件复杂的事情,怎么让这么多的超参数范围,工作效果还能达到更好,训练变得更容易呢?接下来我们看一看BN
Batch Normalization论文地址
- 我们将这种现象称为内部协变量偏移 ,并通过 标准化层 输入来解决问题。
- 可以消除对Dropout的需求。
- 应用于最先进的图像分类模型,批量标准化实现了相同的精度,培训步骤减少了14倍,并且显着地超过了原始模型。使用批量标准化网络的集合,我们改进了ImageNet分类的最佳发布结果:达到4.9%的前5个验证错误(和4.8%的测试错误),超出了人类评估者的准确性。
首先我们还是回到之前,我们对输入特征 X 使用了标准化处理。标准化化后的优化得到了加速。
对于深层网络呢?我们接下来看一下这个公式,这是向量的表示。表示每Mini-batch有m个样本。
m个样本的向量表示
Z [ L ] = W [ L ] A [ L − 1 ] + b [ L ] Z^{[L]} = W^{[L]}A^{[L-1]}+b^{[L]} Z[L]=W[L]A[L−1]+b[L]
A [ L ] = g [ L ] ( Z [ L ] ) A^{[L]}=g^{[L]}(Z^{[L]}) A[L]=g[L](Z[L])
输入: A [ L − 1 ] A^{[L-1]} A[L−1]
深层网络当中不只是初始的特征输入,而到了隐藏层也有输出结果,所以我们是否能够对隐层输出的输入 Z [ L ] Z^{[L]} Z[L]进行标准化
每一层中都会有两个参数 β , γ \beta, \gamma β,γ
我们之前在原文中标记了一个问题叫做叫做"internal covariate shift"。这个词翻译叫做协变量偏移,那么有一个解释叫做 在网络当中数据的分布会随着不同数据集改变 。这是网络中存在的问题。那我们一起来看一下数据本身分布是在这里会有什么问题。
也就是说如果我们在训练集中的数据分布如左图,那么网络当中学习到的分布状况也就是左图。那对于给定一个测试集中的数据,分布不一样。这个网络可能就不能准确去区分。这种情况下,一般要对模型进行重新训练。
Batch Normalization 也起到微弱的正则化效果,但是不要将 Batch Normalization 作为正则化的手段,而是当作加速学习的方式。Batch Normalization主要解决的还是反向传播过程中的梯度问题(梯度消失和爆炸)。
通常我们在训练验证的时候,发现过拟合。可以得到下面这张损失图
通常不断训练之后,损失越来越小。但是到了一定之后,模型学到的过于复杂(过于拟合训练集上的数据的特征)造成测试集开始损失较小,后来又变大。模型的w参数会越来越大,那么可以在测试集损失减小一定程度之后停止训练。
但是这种方法治标不治本,得从根本上解决数据或者网络的问题。
数据增强定义:指通过剪切、旋转/反射/翻转变换、缩放变换、平移变换、尺度变换、对比度变换、噪声扰动、颜色变换等一种或多种组合数据增强变换的方式来增加数据集的大小。
在后面要讲到的卷积神经网络中,即使卷积神经网络被放在不同方向上,卷积神经网络对平移、视角、尺寸或照度(或以上组合)保持不变性,都会认为是一个物体。
为什么这样做?
假设数据集中的两个类。左边的代表品牌A(福特),右边的代表品牌B(雪佛兰)。
假设完成了训练,并且输入下面的图像(品牌A),但是你的神经网络输出认为它是品牌B的汽车!
为什么会发生这种现象? 因为算法可能会寻找区分一个类和另一个类的最明显特征。在这个例子中 ,这个特征就是所有品牌A的汽车朝向左边,所有品牌B的汽车朝向右边。神经网络的好坏取决于输入的数据。
怎么解决这个问题?
我们需要减少数据集中不相关特征的数量。对上面的汽车类型分类器来说,你只需要将现有的数据集中的照片水平翻转,使汽车朝向另一侧。现在,用新的数据集训练神经网络,通过过增强数据集,可以防止神经网络学习到不相关的模式,提升效果。(在没有采集更多的图片前提下)
那么我们应该在机器学习过程中的什么位置进行数据增强?在向模型输入数据之前增强数据集。
数据增强类别:
那么我们的代码中也是进行这种在线增强。
几何变换类即对图像进行几何变换,包括翻转,旋转,裁剪,变形,缩放等各类操作
下面一些方法基础但功能强大的增强技术,目前被广泛应用。
tf.image.random_flip_left_right
从左侧开始分别是:原始图像,水平翻转图像,垂直翻转图像
从左到右,图像相对于前一个图像顺时针旋转90度
从左侧开始分别为:原始图像,从左上角裁剪出一个正方形部分,然后从右下角裁剪出一个正方形部分。剪裁的部分被调整为原始图像大小。
常见的包括噪声、模糊、颜色变换、擦除、填充等等
噪声:基于噪声的数据增强就是在原来的图片的基础上,随机叠加一些噪声,最常见的做法就是高斯噪声
颜色变换:其中最重要的是颜色扰动,就是在某一个颜色空间通过增加或减少某些颜色分量,或者更改颜色通道的顺序。
更多的增强方法效果总图
数据增强的效果是非常好的,比如下面的例子,绿色和粉色表示没有数据增强之前的损失和准确率效果,红色和蓝色表示数据增强之后的损失和准确率结果,可以看到学习效果也改善较快。
那么TensorFlow 官方源码都是基于 vgg与inception论文的图像增强介绍,全部通过tf.image相关API来预处理图像。并且提供了各种封装过tf.image之后的API。那么TensorFlow 官网也给我们提供了一些模型的数据增强过程。
TensorFlow给我们提供了很多相关的API,其中就有tf.image模块部分,如下:
random_brightness(...):以随机因素调整图像的亮度。
random_contrast(...):通过随机因素调整图像的对比度。
random_crop(...):将张量随机裁剪为给定大小。
random_flip_left_right(...):随机水平翻转图像(从左到右)。
random_flip_up_down(...):垂直(上下颠倒)随机翻转图像。
random_hue(...):通过随机因素调整RGB图像的色调。
random_jpeg_quality(...):随机更改jpeg编码质量,以产生jpeg噪声。
random_saturation(...):通过随机因子调整RGB图像的饱和度。
resize(...):调整大小images以size使用指定的method。
resize_with_crop_or_pad(...):裁剪和/或将图像填充到目标宽度和高度。
resize_with_pad(...):调整图像大小并将其填充到目标宽度和高度。
rgb_to_grayscale(...):将一个或多个图像从RGB转换为灰度。
rgb_to_hsv(...):将一个或多个图像从RGB转换为HSV。
rgb_to_yiq(...):将一个或多个图像从RGB转换为YIQ。
rgb_to_yuv(...):将一个或多个图像从RGB转换为YUV。
总结:对于大多数数据增强技术来说,我们会直接使用现成的API,在后续训练某些数据集用到具体哪些方法是我们在做方法上选择的讲解。