License: CC BY-NC-SA 4.0
为什么我用 gradient descent 训了半天 loss 还是不降?
local minima & saddle point
有可能是你卡在 local minima(极小值)了,但大部分情况下这只是个 saddle point(某一维是极小值,另一维是极大值)。这两种情况(其实还有 local maxima)统称为 critical point。
想要知道参数为 \(\theta\) 附近的 loss 长什么样,可以用 taylor series approximation。它的一维形式是我们熟悉的:\(f(x+a) \approx f(a) + f'(a)x + \dfrac12 f''(a)x^2\),而换成高维的 \(\theta\) 就是 \(L(\theta + \vec{x}) \approx L(\theta) + \vec{x}^T \mathbf{g} + \dfrac{1}{2} \vec{x}^T H \vec x\),其中 \(\mathbf{g} = \nabla L(\theta), g_i = \dfrac{\partial L(\theta)}{\partial \theta_i}\),\(H\) 是 hessian matrix,\(H_{ij} = \dfrac{\partial^2}{\partial \theta_i \partial \theta_j} L(\theta)\).
在 critical point 附近,gradient 为零,于是只要看 hessian matrix 就可以知道 error surface 长什么样。
-
\(\forall \vec x, \vec x^T H \vec x \gt 0\)
情况等价于 \(H\) 是正定矩阵(它的所有特征值为正)
(感觉还是要补补线代,这里只是记了个结论)
你在 local minima.
-
\(\forall \vec x, \vec x^T H \vec x \lt 0\)
等价于 \(H\) 是负定矩阵。
你在 local maxima.
-
otherwise
你在 saddle point.
如果你的训练停在 saddle point,那么 hessian matrix 还可以指明更新参数的方向。
考虑 hessian \(H\) 的一个特征向量 \(\mathbf{u}\) 与其特征值 \(\lambda\)。在 saddle point 处 gradient 为 \(\mathbf 0\),于是
因此只要找到 \(H\) 的一个负的特征值,对应的特征向量就是 loss 更低的方向。然而这种方法在实践中少有应用,因为要算 \(H\)。当然还有其他方法脱离 saddle point。
batch & momentum
为什么要用 batch?
如果对每一对(输入,输出)都直接 update 一次,会比较不稳定,而对若干资料的 loss 求和再 update 参数会比较稳定. 但如果 batch size 太大(比如设定为训练集的大小)就会导致训练速度慢。感觉很像 OI 里的分块。但是 batch size 可以适当设大一点,在并行计算的时候和小 batch 用时差不多。
那大的 batch 就只有优势吗?其实也不是。小的 batch 优化起来更容易,一种可能的解释是小 batch 带来的 noise 可以帮助我们躲过 critical point,而大 batch 就束手无策了。
momentum:想象一个小球在 error surface 上运动,它有惯性,可以借此跳过一些小的 local minima。数学地说就是每次算出来的 gradient 与之前的 gradient 做加权和。
自动调整 learning rate
如果 loss 不减小,有可能是 gradient 为零,但也可能是在山谷的谷壁间左右横跳。这就是 learning rate 太大导致的。但如果 learning rate 太小又可能半天更新不到,于是就要动态调整学习速率。
直接上 RMS Prop。设 \(i\) 为参数 \(\theta\) 的某一维,\(t\) 为当前步数/时间,则更新 \(\theta_{t, i}\) 时:
-
原本为 \(\theta_{t+1, i} \leftarrow \theta_{t, i} + \eta g_{t, i}\)
- \(\eta\) 是学习率
- \(g\) 是 gradient.
-
现在为 \(\theta_{t+1, i} \leftarrow \theta_{t, i} + \dfrac{\eta}{\sigma_{t, i} + \varepsilon} g_{t, i}\)
- \(\sigma_{0, i} = \sqrt{(g_{0, i})^2}\)
- \(\sigma_{t, i} = \sqrt{\alpha (\sigma_{t-1, i})^2 + (1-\alpha)(g_{t, i})^2}\),其中 \(\alpha \in (0, 1)\)
- \(\varepsilon\) 很小,是为了避免除零错误
将 RMS Prop 与 Momentum 结合起来就是著名的 Adam 了。
然后 \(\eta\) 也不一定要是常量,可以用 learning rate scheduling 的方法调整。有以下几种常用策略:
-
learning rate decay
-
warm up
一开始的 \(\theta_{t, i}\) 可能有较大噪声,所以 \(\eta\) 先调小点,然后调大,再调小。
loss in classification
如何设计 classification 的 loss?
一种直接的思路是输出一个整数,然后套用 regression 的 loss 函数。但这有一个问题。假设有 A,B,C 三类编号为 1,2,3。A 和 C 的差距真的比 A 和 B 的差距大吗?如果不是,在 loss 上就应该加以修改。
用 one-hot vector 就可以避免上面的问题。但如何比较输出向量与答案的相似度呢?
先把输出向量通过 softmax(\(x'_i = \dfrac{\exp(x_i)}{\sum_j \exp(x_j)}\),可以简单地理解为 normalize 你的输出向量)得到概率分布(但其实 softmax 已经被内置在 pytorch 的 cross-entropy 里了),然后计算输出和答案间的欧几里得距离或 cross-entropy loss.
欧几里得距离不用说(效果较差),cross-entropy loss 就是 \(loss = - \sum_i y_i \ln \hat y_i\),乍一看可能感觉比较奇怪,实际上最小化 cross-entropy 相当于最大化 likelihood。(注意到在 one-hot vector 的语境下只有一个 \(y_i\) 非零)
或者也可以粗略地理解:\(H(P, Q) = -\sum_x P(X=x) \log Q(X=x)\),表示我们 认为 概率分布是 \(Q\),但它实际上是 \(P\),此时我们收到信息的期望惊异值(信息熵)。
batch normalization
假设两个维度对 loss 的斜率差距很大,如果各个维度用同样的 learning rate 就很难得到好的结果,因此有了 adam。
但是这个斜率差距是怎么来的?能不能直接把这个差距消除,产生一个比较好训练的 error surface?
于是有了一个想法,把输入数据每个维度都缩放成均值和方差相同的分布。例如 \(x'_i := \dfrac{x_i - \mu_i}{\sigma_i}\)。计算 \(\mu, \sigma\) 的过程本身也是 network 的一个部分。
在测试数据上,没法读取一堆数据来计算 \(\mu, \sigma\) 了。怎么办?
算的时候其实用的是 moving average。