- 作者:韩信子 @ShowMeAI
- 教程地址:http://www.showmeai.tech/tutorials/37
- 本文地址:http://www.showmeai.tech/article-detail/262
- 申明:版权所有,转载请分割平台与作者并注明出处
- 珍藏 ShowMeAI 查看更多精彩内容
本系列为 斯坦福 CS231n《深度学习与计算机视觉 (Deep Learning for Computer Vision)》的全套学习笔记,对应的课程视频能够在 这里 查看。更多材料获取形式见文末。
引言
在上一篇 深度学习与计算机视觉教程(2) – 图像分类与机器学习根底 内容中,咱们对线性分类器做了一些介绍,咱们心愿线性分类器可能精确地对图像进行分类,要有一套优化其权重参数的办法,这就是本篇 ShowMeAI 要给大家介绍到的损失函数与最优化相干的常识。
本篇重点
- 损失函数
- 数据损失与正则损失
- SVM 损失
- Softmax 损失
- 优化策略
- 梯度计算方法
- 梯度降落
1. 线性分类:损失函数
1.1 损失函数的概念
回到之前解说过的小猫分类示例,这个例子中权重值 \(W\) 十分差,因为猫类别的得分非常低(-96.8),而狗(437.9)和船(61.95)比拟高。
咱们定义 损失函数 (Loss Function)(有时也叫 代价函数 Cost Function 或 指标函数 Objective)\(L\) 来掂量对预估后果的「不称心水平」。当评分函数输入后果与实在后果之间差别越大,损失函数越大,反之越小。
对于有 \(N\) 个训练样本对应 \(N\) 个标签的训练集数据 \((x_{i},y_{i})\)),损失函数定义为:
$$
L=\frac{1}{N} \sum_{i=1}^NL_i(f(x_i,W), y_i)
$$
- 即每个样本损失函数求和取均匀。指标就是找到一个适合的 \(W\) 使 \(L\) 最小。
- 留神:真正的损失函数 \(L\) 还有一项正则损失 \(R(W)\),上面会有阐明。
损失函数有很多种,上面介绍最常见的一些。
1.2 多类反对向量机损失 (Multiclass Support Vector Machine Loss)
SVM 的常识能够参考 ShowMeAI 的 图解机器学习教程 中的文章 反对向量机模型详解,多类 SVM 能够看作二分类 SVM 的一个推广,它能够把样本数据分为多个类别。
1) 数据损失(data loss)
SVM 的损失函数想要 SVM 在正确分类上的得分始终比不正确分类上的得分高出一个边界值 \(\Delta\)。
咱们先看一条数据样本(一张图片)上的损失函数 \(L_i\) 如何定义,依据之前的形容,第 \(i\) 个数据 \((x_{i},y_{i})\) )中蕴含图像 \(x_i\) 的像素和代表正确类别的标签 \(y_i\)。给评分函数输出像素数据,而后通过公式 \(f(x_i, W)\) )来计算不同分类类别的分值。
这里咱们将所有分值寄存到 \(s\) 中,第 \(j\) 个类别的得分就是 \(s\) 的第 \(j\) 个元素:\(s_j = f(x_i, W_j)\)。针对第 \(i\) 条数据样本的多类 SVM 的损失函数定义如下:
$$
L_i = \sum_{j\neq y_i} \max(0, s_j – s_{y_i} + \Delta)
$$
直观来看,就是如果评分函数给实在标签的分数比其余某个标签的分数高出 \(\Delta\),则对该其余标签的损失为 \(0\);否则损失就是 \(s_j – s_{y_i}+ \Delta\)。要对所有不正确的分类循环一遍。
上面用一个示例来解释一下:
简化计算起见,咱们只应用 3 个训练样本,对应 3 个类别的分类,\(y_i =0,1,2\) 对于第 1 张图片「小猫」来说,评分 \(s=[3.2, 5.1, -1.7]\) 其中 \(s_{y_i}=3.2\) 如果把 \(\Delta\) 设为 \(1\),则针对小猫的损失函数:
$$
L_1 = max(0, 5.1 – 3.2 + 1) +max(0, -1.7 – 3.2 + 1) = max(0, 2.9) + max(0, -3.9) = 2.9 + 0 =2.9
$$
同理可得 \(L_2 =0\),\(L_3 =12.9\),所以对整个训练集的损失:\(L= (2.9 + 0 + 12.9)/3 =5.27\)。
下面能够看到 SVM 的损失函数不仅想要正确分类类别 \(y_i\) 的分数比不正确类别分数高,而且至多要高 \(\Delta\)。如果不满足这点,就开始计算损失值。
开展一点解释如下:之所以会退出一个 \(\Delta\),是为了实在标签的分数比谬误标签的分数高出肯定的间隔,如上图所示,如果其余分类分数进入了红色的区域,甚至更高,那么就开始计算损失;如果没有这些状况,损失值为 \(0\):
- 损失最小是 \(0\),最大无穷;
- 如果求和的时候,不加 \(j\neq y_i\) 这一条件,\(L\) 会加 \(\Delta\);
- 计算 \(L_i\) 时应用均匀不必求和,只会缩放 \(L\) 不会影响好坏;而如果应用平方,就会突破均衡,会使坏的更坏,\(L\) 受到影响。
在训练最开始的时候,往往会给 \(W\) 一个比拟小的初值,后果就是 \(s\) 中所有值都很小靠近于 \(0\),此时的损失 \(L\) 应该等于分类类别数 \(K-1\),这里是 \)2\)。可依据这个判断代码是否有问题;
非向量化和向量化多类 SVM 损失代码实现如下:
def L_i(x, y, W):
"""
非向量化版本。计算单个例子(x,y)的多类 SVM 损失
- x 是示意图像的列向量(例如,CIFAR-10 中的 3073 x 1),附加偏置维度
- y 是一个给出正确类索引的整数(例如,CIFAR-10 中的 0 到 9 之间)- W 是权重矩阵(例如,CIFAR-10 中的 10 x 3073)"""
delta = 1.0 # 距离 delta
scores = W.dot(x) # 得分数组,10 x 1
correct_class_score = scores[y]
D = W.shape[0] # 分类的总数, 即为 10
loss_i = 0.0
for j in range(D): # 迭代所有谬误分类
if j == y:
# 跳过正确分类的
continue
# 第 i 个样本累加损失
loss_i += max(0, scores[j] - correct_class_score + delta)
return loss_i
def L_i_vectorized(x, y, W):
'''
更快的半向量化实现。half-vectorized 指的是这样一个事实:对于单个样本,实现不蕴含 for 循环,然而在样本外依然有一个循环(在此函数之外)'''
delta = 1.0
scores = W.dot(x)
# 用一个向量操作计算和所有类别的距离
margins = np.maximum(0, scores - scores[y] + delta)
# y 处的值应该为 0
margins[y] = 0
loss_i = np.sum(margins)
return loss_i
这里的评分函数 \(f(x_i; W) = W x_i\),所以损失函数能够写为:
$$
L_i = \sum_{j\neq y_i} \max(0, w_j^T x_i – w_{y_i}^T x_i + \Delta)
$$
- 其中 \(w_j\) 是 \(W\) 的第 \(j\) 行,而后被拉成一个行列向量,与 \(x_i \) 列向量做点积。
\(max(0,-)\) 函数,常被称为 合页损失(hinge loss)。比方平方合页损失 SVM(即 L2 – SVM),它应用的是 \(max(0,-)^2\) ),将更强烈(平方地而不是线性地)地惩办过界的边界值。不应用平方是更规范的版本,然而在某些数据集中,平方合页损失会工作得更好。能够通过穿插验证来决定到底应用哪个。
总结:咱们对于预测训练集数据分类标签的后果,有一些不称心的中央,而损失函数就能将这些不称心的水平量化。
2) 正则化损失(regularization loss)
假如有 1 个数据集和 1 组权重 \(W\) 可能正确地分类每个数据,即所有 \(L_i\) 都为 \(0\),这样的 \(W\) 是否惟一?其实只有是任意 \(\lambda >1\),\(\lambda W\) 都能够满足 \(L_i = 0\),因为把差值放大 \(\lambda\) 倍后,依然会大于 \(\Delta\)。
所以,咱们心愿对某些 \(W\) 增加一些偏好,让咱们的 W 更趋向于心愿的模式,一个常见的做法是向损失函数减少一个 正则化惩办(regularization penalty)\(R(W)\),它同时也能让模型更加泛化。
联合上述思路咱们失去残缺的多类 SVM 损失函数,它由两个局部组成:数据损失 (data loss),即所有样例的均匀损失,以及 正则化损失(regularization loss)。残缺公式如下:
$$
L=\underbrace{\frac{1}{N} \sum_{i} L_{i}}_{\text {data loss}}+\underbrace{\lambda R(W)}_{\text {regularization loss}}
$$
① 罕用的正则化损失
- 最罕用的 R(W)是 L2 范式,\(W\) 每个元素平方后加起来作为惩办项,能够限度大的权重,更心愿 \(W\) 的元素散布比拟平均:
$$
R(W) = \sum_k\sum_l W_{k,l}^2
$$
- 除此之外还有 L1 范式,作为惩办项更心愿一个比较简单的模型,即 \(W\) 中有很多的 \(0\):
$$
R(W) = \sum_k\sum_l \vert W_{k,l}\vert
$$
- L1 和 L2 也能够组合起来:
$$
R(W) = \sum_k\sum_l \beta W_{k,l}^2 + \vert W_{k,l}\vert
$$
② 对正则化损失的了解
引入 L2 范数正则化损失最好的性质就是对大数值权重进行惩办,能够晋升其泛化能力,因为这就意味着没有哪个维度可能单独对于整体分值有过大的影响。
举个例子,假如输出向量 \(x = [1,1,1,1]\),两个权重向量 \(w_1 = [1,0,0,0]\),\(w_2 = [0.25,0.25,0.25,0.25]\)。那么 \(w_1^Tx = w_2^Tx = 1\)。两个权重向量都失去同样的内积,然而 \(w_1\) 的 L2 惩办是 1.0,而 \(w_2\) 的 L2 惩办是 \(0.25\)。因而,依据 L2 惩办来看,\(w_2\) 更好,因为它的正则化损失更小。从直观上来看,这是因为 \(w_2\) 的权重值更小且更扩散,这就会激励分类器最终将所有维度上的特色都用起来,而不是强烈依赖其中少数几个维度。这一成果将会晋升分类器的泛化能力,并防止过拟合。
留神,和权重不同,偏置项没有这样的成果,因为它们并不管制输出维度上的影响强度。因而 通常只对权重 \(W\) 正则化,而不正则化偏置项 \(b\)。
同时,因为正则化惩办的存在,不可能在所有的例子中失去 \(0\) 的损失值,这是因为只有当 \(W=0\) 的非凡状况下,能力失去损失值为 \(0\)。
然而从 L1 惩办来看,\(w_1\) 可能会更好一些,当然这里 L1 惩办雷同,然而一般来说,L1 惩办更心愿 \(W\) 比拟稠密,最好是有很多为 \(0\) 的元素,这一个性能够用来在不扭转模型的根底上避免过拟合。
比方上面的例子中:
假如咱们的训练数据失去的模型是蓝色的曲线,能够看出应该是一个多项式函数,比方 \(f=w_1x_1+w_2x_2^2+w_3x_3^3+w_4x_4^4\)。然而当新的绿色数据输出时,显然模型是谬误的,更精确的应该是绿色的线。
如果咱们应用 L1 惩办,因为 L1 惩办的个性,会心愿 \(W\) 变得稠密,可让 \(w_2,w_3,w_4\) 变成靠近 \(0\) 的数,这样就能够在不扭转模型的状况下,让模型变得简略泛化。
思考 : 超参数 \(\Delta\) 和 \(\lambda\) 应该被设置成什么值 ? 须要通过穿插验证来求得吗?
- \(\Delta\) 在绝大多数状况下设为 1 都是平安的。
- \(\Delta\) 和 \(\lambda\) 看起来是两个不同的超参数,但实际上他们一起管制同一个衡量:即损失函数中的数据损失和正则化损失之间的衡量。
- 了解这一点的要害是,权重 \(W\) 的大小对于分类分值有间接影响(对他们的差别也有间接影响):当咱们将 \(W\) 中值放大,分类分值之间的差别也变小,反之亦然。
- 因而,不同分类分值之间的边界的具体值 \(\Delta=1\) 或 \(\Delta=100\) 从某些角度来看是没意义的,因为权重本人就能够管制差别变大和放大。也就是说,真正的衡量是咱们容许权重可能变大到何种水平(通过正则化强度 \(\lambda\) 来管制)。
③ 与二元 SVM 的关系
二元 SVM 对于第 \(i\) 个数据的损失计算公式是:
$$
L_i = C \max(0, 1 – y_i w^Tx_i) + R(W)
$$
其中,\(C\) 是一个超参数,并且 \(y_i \in { -1,1}\),这个公式是多类 SVM 公式只有两个分类类别的特例,\(C\) 和 \(\lambda\) 的倒数正相干。比方对实在标签为 \(y_i=1\) 的数据得分是 \(50\),则 \(L_i=0\)。这里只用到了 \(y_i=1\) 标签的得分,因为二元 SVM 的 W 只有一行,只有一个得分并且是本身分类的得分,只有这个得分和 \(y_i\) 的乘积大于 \(1\) 就是预测正确的了。
最终,咱们失去了多类 SVM 损失的残缺表达式:
$$
L = \frac{1}{N} \sum_i \sum_{j\neq y_i} \left[\max(0, f(x_i; W)_{j} – f(x_i; W)_{y_i} + \Delta) \right] + \lambda \sum_k\sum_l W_{k,l}^2
$$
接下来要做的,就是找到可能使损失值最小化的权重了。
1.3 Softmax 分类器损失
SVM 是最罕用的分类器之一,另一个罕用的是 Softmax 分类器。Softmax 分类器能够了解为逻辑回归分类器面对多个分类的一般化演绎,又称为多项式逻辑回归((Multinomial Logistic Regression)。
1) 损失函数
还是以之前小猫的图片为例:
图片上的公式初一看可能感觉有点简单,上面一一解释:
- \(s\) 仍然是寄存所有分类分值的一维数组,\(s=f(x_i,W)\),\(s_j\) 对应着第 \(j\) 个分类的得分,对数据 \(x_i\) 的实在标签得分还是 \(s_{y_i}\)。当初这个分数被 Softmax 分类器称作 非归一化 log 概率;
- 函数 \(f_k(s)=\frac{e^{s_k}}{\sum_j e^{s_j}}\) 是 Softmax 函数 ,其输出值是一个向量 \(s\),向量中元素为任意实数的评分值,函数对其进行压缩,输入一个向量,其中每个元素值在 \(0\) 到 \(1\) 之间,且所有元素之和为 \(1\)。当初能够把这个压缩后的向量看作一个概率分布,分类标签是 \(k\) 的概率:\(P(Y=k|X=x_i)=\frac{e^{s_k}}{\sum_j e^{s_j}}\)。这个概率被称作 归一化概率 ,得分的指数模式被称作 非归一化概率。
- 由上所述,实在分类标签的概率:\(P(Y=y_i|X=x_i)=\frac{e^{s_{y_i}}}{\sum_j e^{s_j}}\),如果这个概率为 \(1\) 就最好不过了。所以咱们心愿这个概率的对数似然最大化,也就是相当于负对数似然最小。因为概率 \(P\) 在 \([0, 1]\) 之间,所以 \(-log(P)\) 在 \(0\) 到正无穷之间,所以咱们能够用这个负对数似然作为对于 \(x_i\) 的 损失函数:
$$
L_i=-logP(Y=y_i|X=x_i)=-log(\frac{e^{s_{y_i}}}{\sum_j e^{s_j}})
$$
- 整个数据集的损失:
$$
L = \frac{1}{N} \sum_i \left[-log(\frac{e^{s_{y_i}}}{\sum_j e^{s_j}}) \right] + \lambda R(W)
$$
- SVM 中应用的是合页损失(hinge loss)有时候又被称为最大边界损失(max-margin loss),Softmax 分类器中应用的为 穿插熵损失(cross-entropy loss),因为应用的是 Softmax 函数,求一个归一化的概率。
依据下面的剖析,能够计算出小猫的 Softmax 损失为 \(0.89\)。损失为 \(0\) 的时候最好,无穷大的时候最差。
其中:
- Softmax 损失,最大无穷,最小是 \(0\);
- 给 W 一个比拟小的初值,\(s\) 中所有值都很小靠近于 \(0\) 时,此时的损失 L 应该等于分类类别数的对数:\(logK\)。可依据这个判断代码是否有问题;
- 理论代码编写中,因为指数模式的存在,如果得分很高,会失去一个十分大的数。除以大数值可能导致数值计算的不稳固,所以学会应用归一化技巧十分重要。如果在分式的分子和分母都乘以一个常数 \(C\),并把它变换到求和之中,就能失去一个从数学上等价的公式:
$$
\frac{e^{s_{y_i}}}{\sum_j e^{s_j}} = \frac{Ce^{s_{y_i}}}{C\sum_j e^{s_j}} = \frac{e^{s_{y_i} + \log C}}{\sum_j e^{s_j + \log C}}
$$
- 通常将 \(C\) 设为 \(log C = -\max_j s_j\)
该技巧简略地说,就是应该将向量 \(s\) 中的数值进行平移,使得最大值为 \(0\)。参考 python 实现代码如下:
s = np.array([123, 456, 789]) # 例子中有 3 个分类,每个评分的数值都很大
p = np.exp(s) / np.sum(np.exp(s)) # 不好:数值问题,可能导致数值爆炸
# 那么将 f 中的值平移到最大值为 0:s -= np.max(s) # s 变成 [-666, -333, 0]
p = np.exp(s) / np.sum(np.exp(s)) # 当初能够了,将给出正确后果
1.4 Softmax 和 SVM 比拟
Softmax 和 SVM 这两类损失的对比方下图所示:
① 计算上有差别
SVM 和 Softmax 分类器对于数据有不同的解决形式。两个分类器都计算了同样的分值向量 \(s\)(本节中是通过矩阵乘来实现)。不同之处在于对 \(s\) 中分值的解释:
- SVM 分类器将它们看做是类别评分,它的损失函数激励正确的类别(本例中是蓝色的类别 2)的分值比其余类别的分值高出至多一个平安边界值。
- Softmax 分类器将这些数值看做是每个类别没有归一化的对数概率,激励正确分类的归一化的对数概率变高,其余的变低。
SVM 的最终的损失值是 \(1.58\),Softmax 的最终的损失值是 \(0.452\),留神这两个数值大小没有可比性。只在给定同样数据,在同样的分类器的损失值计算中,损失之间比拟才有意义。
② 损失的相对数值不能够间接解释
SVM 的计算是无标定的,而且难以针对所有分类的评分值给出直观解释。Softmax 分类器则不同,它容许咱们计算出对于所有分类标签的「概率」。
但这里要留神,「不同类别概率」散布的集中或离散水平是由正则化参数 \(\lambda\) 间接决定的。随着正则化参数 \(\lambda\) 一直加强,权重数值会越来越小,最初输入的概率会靠近于均匀分布。
也就是说,Softmax 分类器算进去的概率能够某种程度上视作一种对于分类正确性的自信。和 SVM 一样,数字间互相比拟得出的大小程序是能够解释的,但其绝对值则难以直观解释。
③ 理论利用时,SVM 和 Softmax 是类似的
两种分类器的体现差异很小。
- 绝对于 Softmax 分类器,SVM 更加「部分指标化(local objective)」,只有看到正确分类相较于不正确分类,曾经失去了比边界值还要高的分数,它就会认为损失值是 \(0\),对于数字个体的细节是不关怀的。
- Softmax 分类器对于分数是永不满足的:正确分类总能失去更高的概率,谬误分类总能失去更低的概率,损失值总是可能更小。
2. 优化
截止目前,咱们已知以下内容:
- 评分函数:\(s=f(W,x)=Wx\)
-
损失函数:
- SVM 数据损失:\(L_i = \sum_{j\neq y_i} \max(0, s_j – s_{y_i} + \Delta)\)
- Softmax 数据损失:\(L_i=-log(\frac{e^{s_{y_i}}}{\sum_j e^{s_j}})\)
- 全损失:\(L=\frac{1}{N} \sum_{i=1}^NL_i+R(W)\)
它们之间的关系:
下一步咱们心愿寻找最优的 \(W\) 让损失 loss 最小化。
2.1 损失函数可视化
损失函数个别都是定义在高维度的空间中(比方,在 CIFAR-10 中一个线性分类器的权重矩阵大小是 \([10 \times 3073]\),就有 30730 个参数),这样要将其可视化就很艰难。
解决办法是在 1 维或 2 维方向上对高维空间进行切片,就能失去一些直观感触。
- 例如,随机生成一个权重矩阵 \(W\),该矩阵就与高维空间中的一个点对应。而后沿着某个维度方向后退的同时记录损失函数值的变动。
- 换句话说,就是生成一个随机的方向 \(W_1\) 并且沿着此方向计算损失值,计算方法是依据不同的 \(a\) 值来计算 \(L(W + a W_1)\)。这个过程将生成一个图表,其 \(x\) 轴是值 \(a\),\(y\) 轴是损失函数值。
- 对应到两维上,即通过扭转 \(a,b\) 来计算损失值 \(L(W + a W_1 + b W_2)\),从而给出二维的图像。在图像中,能够别离用 \(x\) 和 \(y\) 轴示意 \(a, b\),而损失函数的值能够用色彩变动示意。
下图是一个无正则化的多类 SVM 的损失函数的图示。右边和两头只有一个样本数据,左边是 CIFAR-10 中的 100 个数据,蓝色局部是低损失值区域,红色局部是高损失值区域:
上图中留神损失函数的 分段线性构造。多个样本的损失值是总体的平均值,所以左边的碗状构造是很多的分段线性构造的均匀。能够通过数学公式来解释损失函数的分段线性构造。
对于 1 条独自的数据样本,有损失函数的计算公式如下:
$$
L_i = \sum_{j\neq y_i} \left[\max(0, w_j^Tx_i – w_{y_i}^Tx_i + 1) \right]
$$
每个样本的数据损失值是以 \(W\) 为参数的线性函数的总和。\(W\) 的每一行(\(w_j\)),有时候它后面是一个正号(比方当它对应非实在标签分类的时候),有时候它后面是一个负号(比方当它是正确分类的时候)。
比方,假如有一个简略的数据集,其中蕴含有 3 个只有 1 个维度的点,数据集数据点有 3 个类别。那么残缺的无正则化 SVM 的损失值计算如下:
$$
\begin{aligned}
L_0 = & \max(0, w_1^Tx_0 – w_0^Tx_0 + 1) + \max(0, w_2^Tx_0 – w_0^Tx_0 + 1) \\
L_1 = & \max(0, w_0^Tx_1 – w_1^Tx_1 + 1) + \max(0, w_2^Tx_1 – w_1^Tx_1 + 1) \\
L_2 = & \max(0, w_0^Tx_2 – w_2^Tx_2 + 1) + \max(0, w_1^Tx_2 – w_2^Tx_2 + 1) \\
L = & (L_0 + L_1 + L_2)/3
\end{aligned}
$$
这些例子都是一维的,所以数据 \(x_i\) 和权重 \(w_j\) 都是数字。单看 \(w_0\),能够看到最下面的三个式子每一个都含 \(w_0\) 的线性函数,且每一项都会与 \(0\) 比拟,取两者的最大值。第一个式子线性函数斜率是负的,前面两个斜率是正的,可作图如下:
上图中,横轴是 \(w_0\),纵轴是损失,三条线对应三个线性函数,加起来即为右图。
补充解释:
- 咱们将下面的评分函数 \(f\) 扩大到神经网络,指标损失函数就就不再是凸函数了,图像也不会像下面那样是个碗状,而是凹凸不平的简单地形形态。
- 因为 max 操作,损失函数中存在一些不可导点(kinks),比方折点处,这些点使得损失函数不可微,因为在这些不可导点,梯度是没有定义的。然而次梯度(subgradient)仍然存在且经常被应用。在本教程中,咱们会替换应用次梯度和梯度两个术语。某点的次梯度是该点的左右导数之间的任意值。
2.2 优化策略(Optimization Strategy)
优化策略的指标是:找到可能最小化损失函数值的权重 \(W\)。
1) 策略一:随机搜寻(Random search)
随机尝试很多不同的权重,而后看其中哪个最好。这是一个差劲的初始计划。代码如下:
# 假如 X_train 的每一列都是一个数据样本(比方 3073 x 50000)# 假如 Y_train 是数据样本的类别标签(比方一个长 50000 的一维数组)# 假如函数 L 对损失函数进行评估
bestloss = float("inf") # 初始指定一个最高的损失
for num in range(1000):
W = np.random.randn(10, 3073) * 0.0001 # 随机生成一个 10x3073 的 W 矩阵
# 都靠近为 0
loss = L(X_train, Y_train, W) # 失去整个训练集的损失
if loss < bestloss: # 放弃最好的解决形式
bestloss = loss
bestW = W
print 'in attempt %d the loss was %f, best %f' % (num, loss, bestloss)
# 输入:
# in attempt 0 the loss was 9.401632, best 9.401632
# in attempt 1 the loss was 8.959668, best 8.959668
# in attempt 2 the loss was 9.044034, best 8.959668
# in attempt 3 the loss was 9.278948, best 8.959668
# in attempt 4 the loss was 8.857370, best 8.857370
# in attempt 5 the loss was 8.943151, best 8.857370
# in attempt 6 the loss was 8.605604, best 8.605604
# ... (trunctated: continues for 1000 lines)
在下面的代码中,咱们尝试了若干随机生成的权重矩阵 \(W\),其中某些的损失值较小,而另一些的损失值大些。咱们能够把这次随机搜寻中找到的最好的权重 \(W\) 取出,而后去跑测试集:
# 假如 X_test 尺寸是[3073 x 10000], Y_test 尺寸是[10000 x 1]
scores = Wbest.dot(Xte_cols) # 10 x 10000, 每个样本对应 10 个类得分,共 10000
# 找到在每列中评分值最大的索引(即预测的分类)Yte_predict = np.argmax(scores, axis = 0)
# 以及计算准确率
np.mean(Yte_predict == Yte)
# 返回 0.1555
验证集上体现最好的权重 W 跑测试集的准确率是 \(15.5\%\),而齐全随机猜的准确率是 \(10\%\),成果不好!
思路调整:新的策略是从随机权重 W 开始,而后迭代取优,每次都让它的损失值变得更小一点,从而取得更低的损失值。设想本人是一个蒙着眼睛的徒步者,正走在山地地形上,指标是要缓缓走到山底。在 CIFAR-10 的例子中,这山是 \(30730\) 维的(因为 \(W\) 是 \(3073 \times 10\))。咱们在山上踩的每一点都对应一个的损失值,该损失值能够看做该点的海拔高度。
2) 策略二:随机本地搜寻
第一个策略能够看做是每走一步都尝试几个随机方向,如果是上山方向就停在原地,如果是下山方向,就向该方向走一步。这次咱们从一个随机 \(W\) 开始,而后生成一个随机的扰动 \(aW\),只有当 \(W+aW\) 的损失值变低,咱们才会更新。
这个过程的参考实现代码如下:
W = np.random.randn(10, 3073) * 0.001 # 生成随机初始 W
bestloss = float("inf")
for i in xrange(1000):
step_size = 0.0001
Wtry = W + np.random.randn(10, 3073) * step_size
loss = L(Xtr_cols, Ytr, Wtry)
if loss < bestloss:
W = Wtry
bestloss = loss
print 'iter %d loss is %f' % (i, bestloss)
用上述形式迭代 1000 次,这个办法能够失去 \(21.4\%\) 的分类准确率。
3) 策略三:追随梯度
前两个策略关键点都是在权重空间中找到适合的方向,使得沿其调整能升高损失函数的损失值。其实不须要随机寻找方向,咱们能够间接计算出最好的方向,这个方向就是损失函数的 梯度(gradient)。这个办法就好比是感触咱们脚下山体的歪斜水平,而后向着最平缓的降落方向下山。
在一维函数中,斜率是函数在某一点的刹时变化率。梯度是函数斜率的一般化表白,它是一个向量。
在输出空间中,梯度是各个维度的斜率组成的向量(或者称为 导数 derivatives)。对一维函数的求导公式如下:
$$
\frac{df(x)}{dx} = \lim_{h\ \to 0} \frac{f(x + h) – f(x)}{h}
$$
当函数有多个自变量的时候,咱们称导数为偏导数,而梯度就是在每个维度上偏导数所造成的向量。设三元函数 \(f(x,y,z)\) 在空间区域 \(G\) 内具备一阶间断偏导数,点 \(P(x,y,z)\in G\),称向量
$$
\{\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y} ,\frac{\partial f}{\partial z} \} = \frac{\partial f}{\partial x}\vec{i} +\frac{\partial f}{\partial y}\vec{j} +\frac{\partial f}{\partial z} \vec{k} = f_{x}(x,y,z)\vec{i}+f_{y}(x,y,z)\vec{j}+f_{z}(x,y,z)\vec{k}
$$
为函数 \(f(x,y,z)\) )在点 \(P\) 的梯度
记为:
$$
grad f(x,y,z)=f_{x}(x,y,z)\vec{i}+f_{y}(x,y,z)\vec{j}+f_{z}(x,y,z)\vec{k}
$$
3. 梯度计算
对于梯度计算与查看的具体常识也能够参考 ShowMeAI 的 深度学习教程 | 吴恩达专项课程 · 全套笔记解读 中的文章 深度学习的实用层面 里对于「梯度测验 (Gradient checking)」局部的解说
计算梯度有两种办法:
- 迟缓的近似办法(数值梯度法),但实现绝对简略。
- 剖析梯度法,计算迅速,后果准确,然而实现时容易出错,且须要应用微分。
上面咱们开展介绍这两种办法
3.1 数值梯度法
数值梯度法是借助于梯度的定义对其进行迫近计算。
上面代码中:
输出为函数 \(f\) 和矩阵 \(x\),计算 \(f\) 的梯度的通用函数,它返回函数 \(f\) 在点 \(x\) 处的梯度,利用公式 \(\frac{df(x)}{dx} = \lim_{h\ \to 0} \frac{f(x + h) – f(x)}{h}\),代码对 \(x\) 矩阵所有元素进行迭代,在每个元素上产生一个很小的变动 \(h\),通过观察函数值变动,计算函数在该元素上的偏导数。最初,所有的梯度存储在变量 grad 中:
参考实现代码如下:
def eval_numerical_gradient(f, x):
"""
咱们是求 L 对于 w 的梯度,f 就是损失 L,x 就是权重矩阵 w
一个 f 在 x 处的数值梯度法的简略实现
- f 是参数 x 的函数,x 是矩阵,比方之前的 w 是 10x3073
- x 是计算梯度的点
"""
fx = f(x) # 计算 x 点处的函数值
grad = np.zeros(x.shape) # 梯度矩阵也是 10x3073
h = 0.00001 # 近似为 0 的变动量
# 对 x 中所有的索引进行迭代,比方从(0,0)到(9,3072)it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
# np.nditer 是 np 自带的迭代器
# flags=['multi_index']示意对 x 进行多重索引 比方(0,0)
# op_flags=['readwrite']示意不仅能够对 x 进行 read(读取),还能够 write(写入)while not it.finished:
# 计算 x + h 处的函数值
ix = it.multi_index #索引从 (0,0) 开始,即从 x 矩阵第一行第一列的元素开始
old_value = x[ix] # 先将 x(0,0)处原值保留
x[ix] = old_value + h # 减少 h
fxh = f(x) # 计算新的 f(x + h)
x[ix] = old_value # 将 x(0,0)处改回原值
# 计算偏导数
grad[ix] = (fxh - fx) / h # x(0,0)处的偏导数
it.iternext() # 到下个维度 x(0,1)
return grad # 最终是计算好的 10x3073 的梯度矩阵
理论中用 核心差值公式(centered difference formula)\([f(x+h) – f(x-h)] / 2 h\) 成果会更好。上面计算权重空间中的某些随机点上,CIFAR-10 损失函数的梯度:
# 为了应用下面的代码,须要一个只有一个参数的函数
# (在这里参数就是权重 W)所以封装了 X_train 和 Y_train
def CIFAR10_loss_fun(W):
return L(X_train, Y_train, W)
W = np.random.rand(10, 3073) * 0.001 # 随机权重矩阵
df = eval_numerical_gradient(CIFAR10_loss_fun, W) # 失去梯度矩阵
梯度通知咱们损失函数在每个元素上的斜率,以此来进行更新:loss_original = CIFAR10_loss_fun(W) # 初始损失值
print 'original loss: %f' % (loss_original,)
# 查看不同步长的成果
for step_size_log in [-10, -9, -8, -7, -6, -5,-4,-3,-2,-1]:
step_size = 10 ** step_size_log
W_new = W - step_size * df # 权重空间中的新地位,应用负梯度
loss_new = CIFAR10_loss_fun(W_new)
print 'for step size %f new loss: %f' % (step_size, loss_new)
# 输入:
# original loss: 2.200718
# for step size 1.000000e-10 new loss: 2.200652
# for step size 1.000000e-09 new loss: 2.200057
# for step size 1.000000e-08 new loss: 2.194116
# for step size 1.000000e-07 new loss: 2.135493
# for step size 1.000000e-06 new loss: 1.647802
# for step size 1.000000e-05 new loss: 2.844355
# for step size 1.000000e-04 new loss: 25.558142
# for step size 1.000000e-03 new loss: 254.086573
# for step size 1.000000e-02 new loss: 2539.370888
# for step size 1.000000e-01 new loss: 25392.214036
① 在梯度负方向上更新
- 在下面的代码中,为了计算
W_new
,要留神咱们是向着梯度 \(df\) 的负方向去更新,这是因为咱们心愿损失函数值是升高而不是升高。(偏导大于 \(0\),损失递增,\(W\)须要减小;偏导小于 \(0\),损失递加,W 须要增大。)
② 步长的影响
- 从某个具体的点 \(W\) 开始计算梯度,梯度指明了函数在哪个方向是变化率最大的,即损失函数降落最平缓的方向,然而没有指明在这个方向上应该迈多大的步子。
- 小步长降落稳固但进度慢,大步长停顿快然而危险更大,可能导致错过最长处,让损失值回升。
- 在下面的代码中就能看见反例,在某些点如果步长过大,反而可能越过最低点导致更高的损失值。抉择步长(也叫作 学习率)将会是神经网络训练中最重要(也是最麻烦)的超参数设定之一。
③ 效率问题
- 计算数值梯度的复杂性和参数的量线性相关。在本例中有 30730 个参数,所以损失函数每走一步就须要计算 30731 次损失函数(计算梯度时计算 30730 次,最终计算一次更新后的。)
- 古代神经网络很容易就有上千万的参数,因而这个问题只会越发严厉。显然这个策略不适宜大规模数据。
3.2 解析梯度法
数值梯度的计算比较简单,但毛病在于只是近似不够准确,且消耗计算资源太多。
得益于牛顿 - 莱布尼茨的微积分,咱们能够利用微分来剖析,失去计算梯度的公式(不是近似),用公式计算梯度速度很快,但在实现的时候容易出错。
为了解决这个问题,在实际操作时经常将剖析梯度法的后果和数值梯度法的后果作比拟,以此来查看其实现的正确性,这个步骤叫做 梯度查看。
比方咱们已知多类 SVM 的数据损失 \(L_i\):
$$
L_i = \sum_{j\neq y_i} \left[\max(0, w_j^Tx_i – w_{y_i}^Tx_i + \Delta) \right]
$$
能够对函数进行微分。比方对 \(w_{y_i}\) 微分:
$$
\nabla_{w_{y_i}} L_i = – \left(\sum_{j\neq y_i} \mathbb{1}(w_j^Tx_i – w_{y_i}^Tx_i + \Delta > 0) \right) x_i
$$
- 其中 \(1\) 是一个示性函数,如果括号中的条件为真,那么函数值为 \(1\),如果为假,则函数值为 \(0\)。
尽管上述公式看起来简单,但在代码实现的时候比较简单:只须要计算没有满足边界值的即对损失函数产生奉献的分类的数量,而后乘以 \(x_i\) 就是梯度了。
- 留神,这个梯度只是对应正确分类的 \(W\) 的行向量的梯度,那些 \(j \neq y_i\) 行的梯度是:
$$
\nabla_{w_j} L_i = \mathbb{1}(w_j^Tx_i – w_{y_i}^Tx_i + \Delta > 0) x_i
$$
一旦将梯度的公式微分进去,代码实现公式并用于梯度更新就比拟顺畅了。
4. 梯度降落(Gradient Descent)
对于 Batch Gradient Descent、Mini-batch gradient descent、Stochastic Gradient Descent 的具体常识也能够参考 ShowMeAI 的的 深度学习教程 | 吴恩达专项课程 · 全套笔记解读 中的文章 神经网络优化算法
当初能够利用微分公式计算损失函数梯度了,程序反复地计算梯度而后对参数进行更新,这一过程称为梯度降落。
4.1 一般梯度降落
# 一般的梯度降落
while True:
weights_grad = evaluate_gradient(loss_fun, data, weights)
weights += - step_size * weights_grad # 进行梯度更新
这个简略的循环在所有的神经网络外围库中都有。尽管也有其余实现最优化的办法(比方 LBFGS),然而到目前为止,梯度降落是对神经网络的损失函数最优化中最罕用的办法。
前面大家见到的新的优化算法也是在其根底上减少一些新的货色(比方更新的具体公式),然而核心思想不变,那就是咱们始终跟着梯度走,直到后果不再变动。
4.2 小批量梯度降落(Mini-batch gradient descent)
在大规模的利用中(比方 ILSVRC 挑战赛),训练数据量 \(N\) 能够达到百万级量级。如果像这样计算整个训练集,来取得仅仅一个参数的更新就太节约计算资源了。一个罕用的办法通过训练集中的小批量(batches)数据来计算。
例如,在目前最高程度的卷积神经网络中,一个典型的小批量蕴含 256 个样本,而整个训练集是一百二十万个样本。(CIFAR-10,就有 50000 个训练样本。)比方这个小批量数据就用来实现一个参数更新:
# 一般的小批量数据梯度降落
while True:
data_batch = sample_training_data(data, 256) # 从大规模训练样本中提取 256 个样本
weights_grad = evaluate_gradient(loss_fun, data_batch, weights)
weights += - step_size * weights_grad # 参数更新
这个办法之所以成果不错,是因为训练集中的数据都是相干的。
要了解这一点,能够设想一个极其状况:在 ILSVRC 中的 120 万 个图像是 1000 张不同图片的复制(每个类别 1 张图片,每张图片复制 1200 次)。那么显然计算这 1200 张复制图像的梯度就应该是一样的。比照 120 万张图片的数据损失的均值与只计算 1000 张的子集的数据损失均值时,后果应该是一样的。
理论状况中,数据集必定不会蕴含反复图像,那么小批量数据的梯度就是对整个数据集梯度的一个近似。因而,在实践中通过计算 小批量数据的梯度能够实现更疾速地收敛,并以此来进行更频繁的参数更新。
小批量数据策略有个极其状况:每批数据的样本量为 1,这种策略被称为 随机梯度降落(Stochastic Gradient Descent 简称 SGD),有时候也被称为在线梯度降落。SGD 在技术上是指每次应用 1 个样本来计算梯度,你还是会听到人们应用 SGD 来指代小批量数据梯度降落(或者用 MGD 来指代小批量数据梯度降落)。
小批量数据的大小是一个超参数,然而个别并不需要通过穿插验证来调参。它个别设置为同样大小,比方 32、64、128 等。之所以应用 2 的指数,是因为在理论中许多向量化操作实现的时候,如果输出数据量是 2 的指数,那么运算更快。
5. 图像特征提取
间接输出原始像素,成果不好,能够将图像的特色计算出来,便于分类。
罕用的特色计算形式:色彩直方图、词袋、计算边缘等,神经网络中是特色是训练过程中失去的。
6. 在线程序
线性分类器各种细节,可在斯坦福大学开发的一个在线程序观看演示:点击这里
7. 拓展学习
能够点击 B 站 查看视频的【双语字幕】版本
- 【课程学习指南】斯坦福 CS231n | 深度学习与计算机视觉
- 【字幕 + 材料下载】斯坦福 CS231n | 深度学习与计算机视觉 (2017·全 16 讲)
- 【CS231n 进阶课】密歇根 EECS498 | 深度学习与计算机视觉
- 【深度学习教程】吴恩达专项课程 · 全套笔记解读
- 【Stanford 官网】CS231n: Deep Learning for Computer Vision
8. 要点总结
- 损失函数,包含数据损失与正则损失
- 多类 SVM 损失与 Softmax 损失比拟
- 梯度计算方法(数值梯度与解析梯度)
- 梯度降落优化算法
斯坦福 CS231n 全套解读
- 深度学习与 CV 教程(1) | CV 引言与根底
- 深度学习与 CV 教程(2) | 图像分类与机器学习根底
- 深度学习与 CV 教程(3) | 损失函数与最优化
- 深度学习与 CV 教程(4) | 神经网络与反向流传
- 深度学习与 CV 教程(5) | 卷积神经网络
- 深度学习与 CV 教程(6) | 神经网络训练技巧 (上)
- 深度学习与 CV 教程(7) | 神经网络训练技巧 (下)
- 深度学习与 CV 教程(8) | 常见深度学习框架介绍
- 深度学习与 CV 教程(9) | 典型 CNN 架构 (Alexnet, VGG, Googlenet, Restnet 等)
- 深度学习与 CV 教程(10) | 轻量化 CNN 架构 (SqueezeNet, ShuffleNet, MobileNet 等)
- 深度学习与 CV 教程(11) | 循环神经网络及视觉利用
- 深度学习与 CV 教程(12) | 指标检测 (两阶段, R-CNN 系列)
- 深度学习与 CV 教程(13) | 指标检测 (SSD, YOLO 系列)
- 深度学习与 CV 教程(14) | 图像宰割 (FCN, SegNet, U-Net, PSPNet, DeepLab, RefineNet)
- 深度学习与 CV 教程(15) | 视觉模型可视化与可解释性
- 深度学习与 CV 教程(16) | 生成模型 (PixelRNN, PixelCNN, VAE, GAN)
- 深度学习与 CV 教程(17) | 深度强化学习 (马尔可夫决策过程, Q-Learning, DQN)
- 深度学习与 CV 教程(18) | 深度强化学习 (梯度策略, Actor-Critic, DDPG, A3C)
ShowMeAI 系列教程举荐
- 大厂技术实现:举荐与广告计算解决方案
- 大厂技术实现:计算机视觉解决方案
- 大厂技术实现:自然语言解决行业解决方案
- 图解 Python 编程:从入门到精通系列教程
- 图解数据分析:从入门到精通系列教程
- 图解 AI 数学根底:从入门到精通系列教程
- 图解大数据技术:从入门到精通系列教程
- 图解机器学习算法:从入门到精通系列教程
- 机器学习实战:手把手教你玩转机器学习系列
- 深度学习教程:吴恩达专项课程 · 全套笔记解读
- 自然语言解决教程:斯坦福 CS224n 课程 · 课程带学与全套笔记解读
- 深度学习与计算机视觉教程:斯坦福 CS231n · 全套笔记解读