- 优化算法进阶
- Momentum
目标函数有关自变量的梯度代表了目标函数在自变量当前位置下降最快的方向。因此,梯度下降也叫作最陡下降(steepest descent)。在每次迭代中,梯度下降根据自变量当前位置,沿着当前位置的梯度更新自变量。然而,如果自变量的迭代方向仅仅取决于自变量当前位置,这可能会带来一些问题。对于noisy gradient,我们需要谨慎的选取学习率和batch size, 来控制梯度方差和收敛的结果。
g
t
=
∂
w
1
∣
B
t
∣
∑
i
∈
B
t
f
(
x
i
,
w
t
−
1
)
=
1
∣
B
t
∣
∑
i
∈
B
t
g
i
,
t
−
1
.
\mathbf{g}_t = \partial_{\mathbf{w}} \frac{1}{|\mathcal{B}_t|} \sum_{i \in \mathcal{B}_t} f(\mathbf{x}_{i}, \mathbf{w}_{t-1}) = \frac{1}{|\mathcal{B}_t|} \sum_{i \in \mathcal{B}_t} \mathbf{g}_{i, t-1}.
gt=∂w∣Bt∣1∑i∈Btf(xi,wt−1)=∣Bt∣1∑i∈Btgi,t−1.
- An ill-conditioned Problem
Condition Number of Hessian Matrix:
c
o
n
d
H
=
λ
m
a
x
λ
m
i
n
cond_{H} = \frac{\lambda_{max}}{\lambda_{min}}
condH=λminλmax
where λ m a x , λ m i n \lambda_{max},\lambda_{min} λmax,λmin is the maximum amd minimum eignvalue of Hessian matrix.
让我们考虑一个输入和输出分别为二维向量 x = [ x 1 , x 2 ] ⊤ \boldsymbol{x} = [x_1, x_2]^\top x=[x1,x2]⊤和标量的目标函数:
f
(
x
)
=
0.1
x
1
2
+
2
x
2
2
f(\boldsymbol{x})=0.1x_1^2+2x_2^2
f(x)=0.1x12+2x22
c
o
n
d
H
=
4
0.2
=
20
→
ill-conditioned
cond_{H} = \frac{4}{0.2} = 20 \quad \rightarrow \quad \text{ill-conditioned}
condH=0.24=20→ill-conditioned
- Maximum Learning Rate
- For f ( x ) f(x) f(x), according to convex optimizaiton conclusions, we need step size η \eta η.
- To guarantee the convergence, we need to have η \eta η.
- Supp: Preconditioning
在二阶优化中,我们使用Hessian matrix的逆矩阵(或者pseudo inverse)来左乘梯度向量 i . e . Δ x = H − 1 g i.e. \Delta_{x} = H^{-1}\mathbf{g} i.e.Δx=H−1g,这样的做法称为precondition,相当于将 映射为一个单位矩阵,拥有分布均匀的Spectrum,也即我们去优化的等价标函数的Hessian matrix为良好的identity matrix。
%matplotlib inline
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
import torch
eta = 0.4
def f_2d(x1, x2):
return 0.1 * x1 ** 2 + 2 * x2 ** 2
def gd_2d(x1, x2, s1, s2):
return (x1 - eta * 0.2 * x1, x2 - eta * 4 * x2, 0, 0)
d2l.show_trace_2d(f_2d, d2l.train_2d(gd_2d))
可以看到,同一位置上,目标函数在竖直方向(
x
2
x_2
x2轴方向)比在水平方向(
x
1
x_1
x1轴方向)的斜率的绝对值更大。因此,给定学习率,梯度下降迭代自变量时会使自变量在竖直方向比在水平方向移动幅度更大。那么,我们需要一个较小的学习率从而避免自变量在竖直方向上越过目标函数最优解。然而,这会造成自变量在水平方向上朝最优解移动变慢。
下面我们试着将学习率调得稍大一点,此时自变量在竖直方向不断越过最优解并逐渐发散。
- Solution to ill-condition
- Preconditioning gradient vector: applied in Adam, RMSProp, AdaGrad, Adelta, KFC, Natural gradient and other secord-order optimization algorithms.
- Averaging history gradient: like momentum, which allows larger learning rates to accelerate convergence; applied in Adam, RMSProp, SGD momentum.
eta = 0.6
d2l.show_trace_2d(f_2d, d2l.train_2d(gd_2d))
- Momentum Algorithm
动量法的提出是为了解决梯度下降的上述问题。设时间步
t
t
t的自变量为
x
t
x_t
xt,学习率为
η
t
\eta_{t}
ηt。在时间步
t
=
0
t=0
t=0,动量法创建速度变量m_0,并将其元素初始化成
t
>
0
t>0
t>0。在时间步 ,动量法对每次迭代的步骤做如下修改:
m
t
←
β
m
t
−
1
+
η
t
g
t
,
x
t
←
x
t
−
1
−
m
t
,
\begin{aligned} \boldsymbol{m}_t &\leftarrow \beta \boldsymbol{m}_{t-1} + \eta_t \boldsymbol{g}_t, \\ \boldsymbol{x}_t &\leftarrow \boldsymbol{x}_{t-1} - \boldsymbol{m}_t, \end{aligned}
mtxt←βmt−1+ηtgt,←xt−1−mt,
Another version:
m
t
←
β
m
t
−
1
+
(
1
−
β
)
g
t
,
x
t
←
x
t
−
1
−
α
t
m
t
,
\begin{aligned} \boldsymbol{m}_t &\leftarrow \beta \boldsymbol{m}_{t-1} + (1-\beta) \boldsymbol{g}_t, \\ \boldsymbol{x}_t &\leftarrow \boldsymbol{x}_{t-1} - \alpha_t \boldsymbol{m}_t, \end{aligned}
mtxt←βmt−1+(1−β)gt,←xt−1−αtmt,
α
t
=
η
t
1
−
β
\alpha_t = \frac{\eta_t}{1-\beta}
αt=1−βηt
其中,动量超参数
β
\beta
β满足
0
≤
β
<
1
0 \leq \beta < 1
0≤β<1,当
β
=
0
\beta=0
β=0时,动量法等价于小批量随机梯度下降
在解释动量法的数学原理前,让我们先从实验中观察梯度下降在使用动量法后的迭代轨迹
def momentum_2d(x1, x2, v1, v2):
v1 = beta * v1 + eta * 0.2 * x1
v2 = beta * v2 + eta * 4 * x2
return x1 - v1, x2 - v2, v1, v2
eta, beta = 0.4, 0.5
d2l.show_trace_2d(f_2d, d2l.train_2d(momentum_2d))
可以看到使用较小的学习率
η
=
\eta=
η=和动量超参数
β
=
0.5
\beta=0.5
β=0.5时,动量法在竖直方向上的移动更加平滑,且在水平方向上更快逼近最优解。下面使用较大的学习率
η
=
0.6
\eta=0.6
η=0.6,此时自变量也不再发散。
eta = 0.6
d2l.show_trace_2d(f_2d, d2l.train_2d(momentum_2d))
- Exponential Moving Average
为了从数学上理解动量法,让我们先解释一下指数加权移动平均(exponential moving average)。给定超参数
0
≤
β
<
1
0 \leq \beta < 1
0≤β<1,当前时间步
t
t
t的变量
y
t
y_t
yt是上一时间步
t
−
1
t-1
t−1的变量
y
t
−
1
y_{t-1}
yt−1和当前时间步另一变量
x
t
x_t
xt的线性组合:
y
t
=
β
y
t
−
1
+
(
1
−
β
)
x
t
.
y_t = \beta y_{t-1} + (1-\beta) x_t.
yt=βyt−1+(1−β)xt.
我们将
y
t
y_t
yt进行展开:
y
t
=
(
1
−
β
)
x
t
+
β
y
t
−
1
=
(
1
−
β
)
x
t
+
(
1
−
β
)
⋅
β
x
t
−
1
+
β
2
y
t
−
2
=
(
1
−
β
)
x
t
+
(
1
−
β
)
⋅
β
x
t
−
1
+
(
1
−
β
)
⋅
β
2
x
t
−
2
+
β
3
y
t
−
3
=
(
1
−
β
)
∑
i
=
0
t
β
i
x
t
−
i
\begin{aligned} y_t &= (1-\beta) x_t + \beta y_{t-1}\\ &= (1-\beta)x_t + (1-\beta) \cdot \beta x_{t-1} + \beta^2y_{t-2}\\ &= (1-\beta)x_t + (1-\beta) \cdot \beta x_{t-1} + (1-\beta) \cdot \beta^2x_{t-2} + \beta^3y_{t-3}\\ &= (1-\beta) \sum_{i=0}^{t} \beta^{i}x_{t-i} \end{aligned}
yt=(1−β)xt+βyt−1=(1−β)xt+(1−β)⋅βxt−1+β2yt−2=(1−β)xt+(1−β)⋅βxt−1+(1−β)⋅β2xt−2+β3yt−3=(1−β)i=0∑tβixt−i
( 1 − β ) ∑ i = 0 t β i = 1 − β t 1 − β ( 1 − β ) = ( 1 − β t ) (1-\beta)\sum_{i=0}^{t} \beta^{i} = \frac{1-\beta^{t}}{1-\beta} (1-\beta) = (1-\beta^{t}) (1−β)∑i=0tβi=1−β1−βt(1−β)=(1−βt)
- Supp
Approximate Average of
1
1
−
β
\frac{1}{1-\beta}
1−β1 Steps
令
n
=
1
/
(
1
−
β
)
n = 1/(1-\beta)
n=1/(1−β),由于
lim
n
→
∞
(
1
−
1
n
)
n
=
exp
(
−
1
)
≈
0.3679
\lim_{n \rightarrow \infty} \left(1-\frac{1}{n}\right)^n = \exp(-1) \approx 0.3679
limn→∞(1−n1)n=exp(−1)≈0.3679
我们可以得到
(
1
−
1
/
n
)
n
=
β
1
/
(
1
−
β
)
\left(1-1/n\right)^n = \beta^{1/(1-\beta)}
(1−1/n)n=β1/(1−β)
所以当
β
→
1
\beta \rightarrow 1
β→1时,
β
1
/
(
1
−
β
)
=
exp
(
−
1
)
\beta^{1/(1-\beta)}=\exp(-1)
β1/(1−β)=exp(−1),如
0.9
5
20
≈
exp
(
−
1
)
0.95^{20} \approx \exp(-1)
0.9520≈exp(−1),如果把
exp
(
−
1
)
\exp(-1)
exp(−1)当作一个比较小的数,我们可以在近似中忽略所有含
β
1
/
(
1
−
β
)
\beta^{1/(1-\beta)}
β1/(1−β)和比
β
1
/
(
1
−
β
)
\beta^{1/(1-\beta)}
β1/(1−β)更高阶的系数的项。例如,当
β
=
0.95
\beta=0.95
β=0.95时,
y
t
≈
0.05
∑
i
=
0
19
0.9
5
i
x
t
−
i
.
y_t \approx 0.05 \sum_{i=0}^{19} 0.95^i x_{t-i}.
yt≈0.05∑i=0190.95ixt−i.
因此,在实际中,我们常常将
y
t
y_t
yt看作是对最近
1
1
−
β
\frac{1}{1-\beta}
1−β1个时间步的
x
t
x_t
xt值的加权平均。例如,当
γ
=
0.95
\gamma=0.95
γ=0.95时,
y
t
y_t
yt可以被看作对最近20个时间步的
x
t
x_t
xt值的加权平均;当
β
=
0.9
\beta=0.9
β=0.9时, 可以看作是对最近10个时间步的
x
t
x_t
xt值的加权平均。而且,离当前时间步
t
t
t越近的
x
t
x_t
xt值获得的权重越大(越接近1)。
- 由指数加权移动平均理解动量法
动量法的速度变量做变形:
m
t
←
β
m
t
−
1
+
(
1
−
β
)
(
η
t
1
−
β
g
t
)
.
\boldsymbol{m}_t \leftarrow \beta \boldsymbol{m}_{t-1} + (1 - \beta) \left(\frac{\eta_t}{1 - \beta} \boldsymbol{g}_t\right).
mt←βmt−1+(1−β)(1−βηtgt).
Another version:
m
t
←
β
m
t
−
1
+
(
1
−
β
)
g
t
.
\boldsymbol{m}_t \leftarrow \beta \boldsymbol{m}_{t-1} + (1 - \beta) \boldsymbol{g}_t.
mt←βmt−1+(1−β)gt.
x
t
←
x
t
−
1
−
α
t
m
t
,
\begin{aligned} \boldsymbol{x}_t &\leftarrow \boldsymbol{x}_{t-1} - \alpha_t \boldsymbol{m}_t, \end{aligned}
xt←xt−1−αtmt,
α
t
=
η
t
1
−
β
\alpha_t = \frac{\eta_t}{1-\beta}
αt=1−βηt
由指数加权移动平均的形式可得,速度变量
v
t
v_t
vt实际上对序列
{
η
t
−
i
g
t
−
i
/
(
1
−
β
)
:
i
=
0
,
…
,
1
/
(
1
−
β
)
−
1
}
\{\eta_{t-i}\boldsymbol{g}_{t-i} /(1-\beta):i=0,\ldots,1/(1-\beta)-1\}
{ηt−igt−i/(1−β):i=0,…,1/(1−β)−1}做了指数加权移动平均。换句话说,相比于小批量随机梯度下降,动量法在每个时间步的自变量更新量近似于将前者对应的最近
1
/
(
1
−
β
)
1/(1-\beta)
1/(1−β)个时间步的更新量做了指数加权移动平均后再除以
1
−
β
1-\beta
1−β。所以,在动量法中,自变量在各个方向上的移动幅度不仅取决当前梯度,还取决于过去的各个梯度在各个方向上是否一致。在本节之前示例的优化问题中,所有梯度在水平方向上为正(向右),而在竖直方向上时正(向上)时负(向下)。这样,我们就可以使用较大的学习率,从而使自变量向最优解更快移动。
- Implement
相对于小批量随机梯度下降,动量法需要对每一个自变量维护一个同它一样形状的速度变量,且超参数里多了动量超参数。实现中,我们将速度变量用更广义的状态变量states表示。
def get_data_ch7():
data = np.genfromtxt('/home/kesci/input/airfoil4755/airfoil_self_noise.dat', delimiter='\t')
data = (data - data.mean(axis=0)) / data.std(axis=0)
return torch.tensor(data[:1500, :-1], dtype=torch.float32), \
torch.tensor(data[:1500, -1], dtype=torch.float32)
features, labels = get_data_ch7()
def init_momentum_states():
v_w = torch.zeros((features.shape[1], 1), dtype=torch.float32)
v_b = torch.zeros(1, dtype=torch.float32)
return (v_w, v_b)
def sgd_momentum(params, states, hyperparams):
for p, v in zip(params, states):
v.data = hyperparams['momentum'] * v.data + hyperparams['lr'] * p.grad.data
p.data -= v.data
动量超参数momentum设0.5
d2l.train_ch7(sgd_momentum, init_momentum_states(),
{'lr': 0.02, 'momentum': 0.5}, features, labels)
然后把超参数momentum增大到0.9
d2l.train_ch7(sgd_momentum, init_momentum_states(),
{'lr': 0.02, 'momentum': 0.9}, features, labels)
- Pytorch Class
在python中torch.optim.SGD已实现了Momentum。
d2l.train_pytorch_ch7(torch.optim.SGD, {'lr': 0.004, 'momentum': 0.9},
features, labels)
- AdaGrad
在之前介绍过的优化算法中,目标函数自变量的每一个元素在相同时间步都使用同一个学习率来自我迭代。举个例子,假设目标函数为
f
f
f,自变量为一个二维向量
[
x
1
,
x
2
]
⊤
[x_1, x_2]^\top
[x1,x2]⊤,该向量中每一个元素在迭代时都使用相同的学习率。例如,在学习率为
η
\eta
η的梯度下降中,元素
x
1
x_1
x1和
x
2
x_2
x2都使用相同的学习率
η
\eta
η
来自我迭代:
x
1
←
x
1
−
η
∂
f
∂
x
1
,
x
2
←
x
2
−
η
∂
f
∂
x
2
.
x_1 \leftarrow x_1 - \eta \frac{\partial{f}}{\partial{x_1}}, \quad x_2 \leftarrow x_2 - \eta \frac{\partial{f}}{\partial{x_2}}.
x1←x1−η∂x1∂f,x2←x2−η∂x2∂f.
由前面动量法,我们可以明白,如果
x
1
x_1
x1和
x
2
x_2
x2的梯度有着比较大的差别的时候,我们需要选择足够小的学习率使得自变量在梯度值较大的维度上不发散。但这样就会导致自变量在梯度值较小的维度上迭代过慢动量法依赖指数加权移动平均使得自变量的更新方向更加一致,从而降低发散的可能。本节我们介绍AdaGrad算法,它根据自变量在每个维度的梯度值的大小来调整各个维度上的学习率,从而避免统一的学习率难以适应所有维度的问题。
- Algorithm
AdaGrad算法会使用一个小批量随机梯度
g
t
g_t
gt按元素平方的累加变量。在时间步0,AdaGrad将
s
0
s_0
s0中每个元素初始化为0。在时间步
t
t
t,首先将小批量随机梯度
g
t
g_t
gt按元素平方后累加到变量
s
t
s_t
st:
s
t
←
s
t
−
1
+
g
t
⊙
g
t
,
\boldsymbol{s}_t \leftarrow \boldsymbol{s}_{t-1} + \boldsymbol{g}_t \odot \boldsymbol{g}_t,
st←st−1+gt⊙gt,
其中
⊙
\odot
⊙是按元素相乘。接着,我们将目标函数自变量中每个元素的学习率通过按元素运算重新调整一下:
x
t
←
x
t
−
1
−
η
s
t
+
ϵ
⊙
g
t
,
\boldsymbol{x}_t \leftarrow \boldsymbol{x}_{t-1} - \frac{\eta}{\sqrt{\boldsymbol{s}_t + \epsilon}} \odot \boldsymbol{g}_t,
xt←xt−1−st+ϵη⊙gt,
其中 η \eta η是学习率是, ϵ \epsilon ϵ为了维持数值稳定性而添加的常数,如 1 0 − 6 10^{-6} 10−6。这里开方、除法和乘法的运算都是按元素运算的。这些按元素运算使得目标函数自变量中每个元素都分别拥有自己的学习率。
- Feature
需要强调的是,小批量随机梯度按元素平方的累加变量出现在学习率的分母项中。因此,如果目标函数有关自变量中某个元素的偏导数一直都较大,那么该元素的学习率将下降较快;反之,如果目标函数有关自变量中某个元素的偏导数一直都较小,那么该元素的学习率将下降较慢。然而,由于 s t s_t st一直在累加按元素平方的梯度,自变量中每个元素的学习率在迭代过程中一直在降低(或不变)。所以,当学习率在迭代早期降得较快且当前解依然不佳时,AdaGrad算法在迭代后期由于学习率过小,可能较难找到一个有用的解。
下面我们仍然以目标函数 f ( x ) = 0.1 x 1 2 + 2 x 2 2 f(\boldsymbol{x})=0.1x_1^2+2x_2^2 f(x)=0.1x12+2x22为例观察AdaGrad算法对自变量的迭代轨迹。我们实现AdaGrad算法并使用和上一节实验中相同的学习率0.4。可以看到,自变量的迭代轨迹较平滑。但由于 s t s_t st的累加效果使学习率不断衰减,自变量在迭代后期的移动幅度较小。
%matplotlib inline
import math
import torch
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
def adagrad_2d(x1, x2, s1, s2):
g1, g2, eps = 0.2 * x1, 4 * x2, 1e-6 # 前两项为自变量梯度
s1 += g1 ** 2
s2 += g2 ** 2
x1 -= eta / math.sqrt(s1 + eps) * g1
x2 -= eta / math.sqrt(s2 + eps) * g2
return x1, x2, s1, s2
def f_2d(x1, x2):
return 0.1 * x1 ** 2 + 2 * x2 ** 2
eta = 0.4
d2l.show_trace_2d(f_2d, d2l.train_2d(adagrad_2d))
同样的,我们把学习率增大到2。但与之前不同的是,这里自变量很快逼近了最优解。
eta = 2
d2l.show_trace_2d(f_2d, d2l.train_2d(adagrad_2d))
- Implement
同动量法一样,AdaGrad算法需要对每个自变量维护同它一样形状的状态变量。我们根据AdaGrad算法中的公式实现该算法。
def get_data_ch7():
data = np.genfromtxt('/home/kesci/input/airfoil4755/airfoil_self_noise.dat', delimiter='\t')
data = (data - data.mean(axis=0)) / data.std(axis=0)
return torch.tensor(data[:1500, :-1], dtype=torch.float32), \
torch.tensor(data[:1500, -1], dtype=torch.float32)
features, labels = get_data_ch7()
def init_adagrad_states():
s_w = torch.zeros((features.shape[1], 1), dtype=torch.float32)
s_b = torch.zeros(1, dtype=torch.float32)
return (s_w, s_b)
def adagrad(params, states, hyperparams):
eps = 1e-6
for p, s in zip(params, states):
s.data += (p.grad.data**2)
p.data -= hyperparams['lr'] * p.grad.data / torch.sqrt(s + eps)
在这里我们使用更大的学习率来训练我们建立好的模型
d2l.train_ch7(adagrad, init_adagrad_states(), {'lr': 0.1}, features, labels)
- Pytorch Class
通过名称为“adagrad”的Trainer实例,我们便可使用Pytorch提供的AdaGrad算法来训练模型。
d2l.train_pytorch_ch7(torch.optim.Adagrad, {'lr': 0.1}, features, labels)
- RMSProp
因为调整学习率时分母上的变量 s t s_t st一直在累加按元素平方的小批量随机梯度,所以目标函数自变量每个元素的学习率在迭代过程中一直在降低(或不变)。因此,当学习率在迭代早期降得较快且当前解依然不佳时,AdaGrad算法在迭代后期由于学习率过小,可能较难找到一个有用的解。为了解决这一问题,RMSProp算法对AdaGrad算法做了修改。
- Algorithm
不同于AdaGrad算法里状态
s
t
s_t
st变量是截至时间步所有小批量随机梯度
g
t
g_t
gt按元素平方和,RMSProp算法将这些梯度按元素平方做指数加权移动平均。具体来说,给定超参数
0
≤
γ
0
0 \leq \gamma 0
0≤γ0计算
v
t
←
β
v
t
−
1
+
(
1
−
β
)
g
t
⊙
g
t
.
\boldsymbol{v}_t \leftarrow \beta \boldsymbol{v}_{t-1} + (1 - \beta) \boldsymbol{g}_t \odot \boldsymbol{g}_t.
vt←βvt−1+(1−β)gt⊙gt.
和AdaGrad算法一样,RMSProp算法将目标函数自变量中每个元素的学习率通过按元素运算重新调整,然后更新自变量
x
t
←
x
t
−
1
−
α
v
t
+
ϵ
⊙
g
t
,
\boldsymbol{x}_t \leftarrow \boldsymbol{x}_{t-1} - \frac{\alpha}{\sqrt{\boldsymbol{v}_t + \epsilon}} \odot \boldsymbol{g}_t,
xt←xt−1−vt+ϵα⊙gt,
其中
η
\eta
η是学习率,
ϵ
\epsilon
ϵ是为了维持数值稳定性而添加的常数,如。因为RMSProp算法的状态变量是对平方项
g
t
⊙
g
t
\boldsymbol{g}_t \odot \boldsymbol{g}_t
gt⊙gt的指数加权移动平均,所以可以看作是最近
1
/
(
1
−
β
)
1/(1-\beta)
1/(1−β)个时间步的小批量随机梯度平方项的加权平均。如此一来,自变量每个元素的学习率在迭代过程中就不再一直降低(或不变)。
%matplotlib inline
import math
import torch
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
def rmsprop_2d(x1, x2, s1, s2):
g1, g2, eps = 0.2 * x1, 4 * x2, 1e-6
s1 = beta * s1 + (1 - beta) * g1 ** 2
s2 = beta * s2 + (1 - beta) * g2 ** 2
x1 -= alpha / math.sqrt(s1 + eps) * g1
x2 -= alpha / math.sqrt(s2 + eps) * g2
return x1, x2, s1, s2
def f_2d(x1, x2):
return 0.1 * x1 ** 2 + 2 * x2 ** 2
alpha, beta = 0.4, 0.9
d2l.show_trace_2d(f_2d, d2l.train_2d(rmsprop_2d))
- Implement
接下来我们按照公式来实现RMSProp
我们将初始学习率设为0.01,并将超参数
γ
\gamma
γ设为0.9。此时,变量
s
t
s_t
st可看作是最近
1
/
(
1
−
0.9
)
=
10
1/(1-0.9)=10
1/(1−0.9)=10个时间步的平方项
g
t
⊙
g
t
\boldsymbol{g}_t \odot \boldsymbol{g}_t
gt⊙gt的加权平均。
d2l.train_ch7(rmsprop, init_rmsprop_states(), {'lr': 0.01, 'beta': 0.9},
features, labels)
- Pytorch Class
在python中我们用gluon提供的RMSProp算法来训练模型。注意,超参数 γ \gamma γ通过gammal指定
d2l.train_pytorch_ch7(torch.optim.RMSprop, {'lr': 0.01, 'alpha': 0.9},
features, labels)
- AdaDelta
AdaDelta算法针对AdaGrad算法在迭代后期可能较难找到有用解的问题做了改进,但与此同时,AdaDelta没有学习率这一超参数。
- Algorithm
AdaDelta算法使用了小批量随机梯度 g t g_t gt按元素平方的指数加权移动平均变量 s t s_t st。在时间步0,它的所有元素被初始化为0。给定超参数 0 ≤ ρ 0 0 \leq \rho 0 0≤ρ0
s t ← ρ s t − 1 + ( 1 − ρ ) g t ⊙ g t . \boldsymbol{s}_t \leftarrow \rho \boldsymbol{s}_{t-1} + (1 - \rho) \boldsymbol{g}_t \odot \boldsymbol{g}_t. st←ρst−1+(1−ρ)gt⊙gt.
AdaDelta算法维护一个额外的状态变量
Δ
x
t
\Delta\boldsymbol{x}_t
Δxt,其元素同样在时间步0时被初始化为0。我们使用
Δ
x
t
−
1
\Delta\boldsymbol{x}_{t-1}
Δxt−1来计算自变量的变化量:
g
t
′
←
Δ
x
t
−
1
+
ϵ
s
t
+
ϵ
⊙
g
t
,
\boldsymbol{g}_t' \leftarrow \sqrt{\frac{\Delta\boldsymbol{x}_{t-1} + \epsilon}{\boldsymbol{s}_t + \epsilon}} \odot \boldsymbol{g}_t,
gt′←st+ϵΔxt−1+ϵ⊙gt,
其中是
ϵ
\epsilon
ϵ为了维持数值稳定性而添加的常数,如
1
0
−
5
10^{-5}
10−5。接着更新自变量:
x
t
←
x
t
−
1
−
g
t
′
.
\boldsymbol{x}_t \leftarrow \boldsymbol{x}_{t-1} - \boldsymbol{g}'_t.
xt←xt−1−gt′.
最后,我们使用
Δ
x
t
\Delta\boldsymbol{x}_t
Δxt来记录自变量
g
t
′
\boldsymbol{g}'_t
gt′变化量按元素平方的指数加权移动平均:
Δ
x
t
←
ρ
Δ
x
t
−
1
+
(
1
−
ρ
)
g
t
′
⊙
g
t
′
.
\Delta\boldsymbol{x}_t \leftarrow \rho \Delta\boldsymbol{x}_{t-1} + (1 - \rho) \boldsymbol{g}'_t \odot \boldsymbol{g}'_t.
Δxt←ρΔxt−1+(1−ρ)gt′⊙gt′.
可以看到,如不考虑
ϵ
\epsilon
ϵ的影响,AdaDelta算法与RMSProp算法的不同之处在于使用
Δ
x
t
−
1
\sqrt{\Delta\boldsymbol{x}_{t-1}}
Δxt−1来替代超参数
η
\eta
η。
- Implement
def init_adadelta_states():
s_w, s_b = torch.zeros((features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32)
delta_w, delta_b = torch.zeros((features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32)
return ((s_w, delta_w), (s_b, delta_b))
def adadelta(params, states, hyperparams):
rho, eps = hyperparams['rho'], 1e-5
for p, (s, delta) in zip(params, states):
s[:] = rho * s + (1 - rho) * (p.grad.data**2)
g = p.grad.data * torch.sqrt((delta + eps) / (s + eps))
p.data -= g
delta[:] = rho * delta + (1 - rho) * g * g
d2l.train_ch7(adadelta, init_adadelta_states(), {'rho': 0.9}, features, labels)
- Pytorch Class
使用pytorch提供的AdaDelta算法训练。它的超参数可以通过 ρ \rho ρ来指定。
d2l.train_pytorch_ch7(torch.optim.Adadelta, {'rho': 0.9}, features, labels)
- Adam
Adam算法在RMSProp算法基础上对小批量随机梯度也做了指数加权移动平均 。
- Algorithm
Adam算法使用了动量变量
m
t
m_t
mt和RMSProp算法中小批量随机梯度按元素平方的指数加权移动平均变量
v
t
v_t
vt,并在时间步0将它们中每个元素初始化为0。给定超参数
0
≤
β
1
<
1
0 \leq \beta_1 < 1
0≤β1<1(算法作者建议设为0.9),时间步的动量变量
m
t
m_t
mt即小批量随机梯度
g
t
g_t
gt的指数加权移动平均:
m
t
←
β
1
m
t
−
1
+
(
1
−
β
1
)
g
t
.
\boldsymbol{m}_t \leftarrow \beta_1 \boldsymbol{m}_{t-1} + (1 - \beta_1) \boldsymbol{g}_t.
mt←β1mt−1+(1−β1)gt.
和RMSProp算法中一样,给定超参数
0
≤
β
2
<
1
0 \leq \beta_2 < 1
0≤β2<1(算法作者建议设为0.999), 将小批量随机梯度按元素平方后的项KaTeX parse error: Expected 'EOF', got '&' at position 40: …boldsymbol{g}_t&̲做指数加权移动平均得到v_t$:
v
t
←
β
2
v
t
−
1
+
(
1
−
β
2
)
g
t
⊙
g
t
.
\boldsymbol{v}_t \leftarrow \beta_2 \boldsymbol{v}_{t-1} + (1 - \beta_2) \boldsymbol{g}_t \odot \boldsymbol{g}_t.
vt←β2vt−1+(1−β2)gt⊙gt.
由于我们将
m
0
m_0
m0和
s
0
s_0
s0中的元素都初始化为0, 在时间步
t
t
t我们得到
m
t
=
(
1
−
β
1
)
∑
i
=
1
t
β
1
t
−
i
g
i
\boldsymbol{m}_t = (1-\beta_1) \sum_{i=1}^t \beta_1^{t-i} \boldsymbol{g}_i
mt=(1−β1)∑i=1tβ1t−igi。将过去各时间步小批量随机梯度的权值相加,得到
(
1
−
β
1
)
∑
i
=
1
t
β
1
t
−
i
=
1
−
β
1
t
(1-\beta_1) \sum_{i=1}^t \beta_1^{t-i} = 1 - \beta_1^t
(1−β1)∑i=1tβ1t−i=1−β1t。需要注意的是,当较小时,过去各时间步小批量随机梯度权值之和会较小。例如,当
β
1
=
0.9
\beta_1 = 0.9
β1=0.9时,
m
1
=
0.1
g
1
\boldsymbol{m}_1 = 0.1\boldsymbol{g}_1
m1=0.1g1。为了消除这样的影响,对于任意时间步
t
t
t,我们可以将
m
t
m_t
mt再除以
1
−
β
1
t
1-\beta_{1}^t
1−β1t,从而使过去各时间步小批量随机梯度权值之和为1。这也叫作偏差修正。在Adam算法中,我们对变量
m
t
m_t
mt和
v
t
v_t
vt均作偏差修正:
m
^
t
←
m
t
1
−
β
1
t
,
\hat{\boldsymbol{m}}_t \leftarrow \frac{\boldsymbol{m}_t}{1 - \beta_1^t},
m^t←1−β1tmt,
v ^ t ← v t 1 − β 2 t . \hat{\boldsymbol{v}}_t \leftarrow \frac{\boldsymbol{v}_t}{1 - \beta_2^t}. v^t←1−β2tvt.
接下来,Adam算法使用以上偏差修正后的变量 m ^ t \hat{\boldsymbol{m}}_t m^t和 m ^ t \hat{\boldsymbol{m}}_t m^t,将模型参数中每个元素的学习率通过按元素运算重新调整:
g t ′ ← η m ^ t v ^ t + ϵ , \boldsymbol{g}_t' \leftarrow \frac{\eta \hat{\boldsymbol{m}}_t}{\sqrt{\hat{\boldsymbol{v}}_t} + \epsilon}, gt′←v^t+ϵηm^t,
其中
η
\eta
η是学习率,
ϵ
\epsilon
ϵ是为了维持数值稳定性而添加的常数,如
1
0
−
8
10^{-8}
10−8。和AdaGrad算法、RMSProp算法以及AdaDelta算法一样,目标函数自变量中每个元素都分别拥有自己的学习率。最后,使用
g
t
′
g_t'
gt′迭代自变量:
x
t
←
x
t
−
1
−
g
t
′
.
\boldsymbol{x}_t \leftarrow \boldsymbol{x}_{t-1} - \boldsymbol{g}_t'.
xt←xt−1−gt′.
- Implement
Adam算法中的公式实现该算法。其中时间步通过hyperparams参数传入adam函数。
- Pytorch Class
%matplotlib inline
import torch
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
def get_data_ch7():
data = np.genfromtxt('/home/kesci/input/airfoil4755/airfoil_self_noise.dat', delimiter='\t')
data = (data - data.mean(axis=0)) / data.std(axis=0)
return torch.tensor(data[:1500, :-1], dtype=torch.float32), \
torch.tensor(data[:1500, -1], dtype=torch.float32)
features, labels = get_data_ch7()
def init_adam_states():
v_w, v_b = torch.zeros((features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32)
s_w, s_b = torch.zeros((features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32)
return ((v_w, s_w), (v_b, s_b))
def adam(params, states, hyperparams):
beta1, beta2, eps = 0.9, 0.999, 1e-6
for p, (v, s) in zip(params, states):
v[:] = beta1 * v + (1 - beta1) * p.grad.data
s[:] = beta2 * s + (1 - beta2) * p.grad.data**2
v_bias_corr = v / (1 - beta1 ** hyperparams['t'])
s_bias_corr = s / (1 - beta2 ** hyperparams['t'])
p.data -= hyperparams['lr'] * v_bias_corr / (torch.sqrt(s_bias_corr) + eps)
hyperparams['t'] += 1
d2l.train_ch7(adam, init_adam_states(), {'lr': 0.01, 't': 1}, features, labels)
d2l.train_pytorch_ch7(torch.optim.Adam, {'lr': 0.01}, features, labels)
- word2vec
- 词嵌入基础
如果我们使用 one-hot 向量表示单词,虽然它们构造起来很容易,但通常并不是一个好选择。一个主要的原因是,one-hot 词向量无法准确表达不同词之间的相似度,如我们常常使用的余弦相似度。
Word2Vec 词嵌入工具的提出正是为了解决上面这个问题,它将每个词表示成一个定长的向量,并通过在语料库上的预训练使得这些向量能较好地表达不同词之间的相似和类比关系,以引入一定的语义信息。基于两种概率模型的假设,我们可以定义两种 Word2Vec 模型:
- Skip-Gram 跳字模型:假设背景词由中心词生成,即建模 P ( w o ∣ w c ) P(w_o\mid w_c) P(wo∣wc),其中 w c w_c wc为中心词, w o w_o wo为任一背景词;
- CBOW (continuous bag-of-words) 连续词袋模型:假设中心词由背景词生成,即建模
P
(
w
c
∣
W
o
)
P(w_c\mid \mathcal{W}_o)
P(wc∣Wo),其中
W
o
\mathcal{W}_o
Wo为背景词的集合。
Skip-Gram 模型的实现与CBOW类似,大概由四个部分展开:
- PTB 数据集
- Skip-Gram 跳字模型
- 负采样近似
- 训练模型
- PTB数据集
Word2Vec 能从语料中学到如何将离散的词映射为连续空间中的向量,并保留其语义上的相似关系。那么为了训练 Word2Vec 模型,我们就需要一个自然语言语料库,模型将从中学习各个单词间的关系,这里我们使用经典的 PTB 语料库进行训练。PTB (Penn Tree Bank) 是一个常用的小型语料库,它采样自《华尔街日报》的文章,包括训练集、验证集和测试集。我们将在PTB训练集上训练词嵌入模型。
- 载入数据集
with open('/home/kesci/input/ptb_train1020/ptb.train.txt', 'r') as f:
lines = f.readlines() # 该数据集中句子以换行符为分割
raw_dataset = [st.split() for st in lines] # st是sentence的缩写,单词以空格为分割
print('# sentences: %d' % len(raw_dataset))
# 对于数据集的前3个句子,打印每个句子的词数和前5个词
# 句尾符为 '' ,生僻词全用 '' 表示,数字则被替换成了 'N'
for st in raw_dataset[:3]:
print('# tokens:', len(st), st[:5])
- 建立词语索引
counter = collections.Counter([tk for st in raw_dataset for tk in st]) # tk是token的缩写
counter = dict(filter(lambda x: x[1] >= 5, counter.items())) # 只保留在数据集中至少出现5次的词
idx_to_token = [tk for tk, _ in counter.items()]
token_to_idx = {tk: idx for idx, tk in enumerate(idx_to_token)}
dataset = [[token_to_idx[tk] for tk in st if tk in token_to_idx]
for st in raw_dataset] # raw_dataset中的单词在这一步被转换为对应的idx
num_tokens = sum([len(st) for st in dataset])
'# tokens: %d' % num_tokens
- 二次采样
二次采样
文本数据中一般会出现一些高频词,如英文中的“the”“a”和“in”。通常来说,在一个背景窗口中,一个词(如“chip”)和较低频词(如“microprocessor”)同时出现比和较高频词(如“the”)同时出现对训练词嵌入模型更有益。因此,训练词嵌入模型时可以对词进行二次采样。 具体来说,数据集中每个被索引词
w
i
w_i
wi 将有一定概率被丢弃,该丢弃概率为
P
(
w
i
)
=
max
(
1
−
t
f
(
w
i
)
,
0
)
P(w_i)=\max(1-\sqrt{\frac{t}{f(w_i)}},0)
P(wi)=max(1−f(wi)t,0)
其中
f
(
w
i
)
f(w_i)
f(wi) 是数据集中词
w
i
w_i
wi 的个数与总词数之比,常数
t
t
t 是一个超参数(实验中设为
1
0
−
4
10^{−4}
10−4)。可见,只有当
f
(
w
i
)
>
t
f(w_i)>t
f(wi)>t 时,我们才有可能在二次采样中丢弃词
w
i
w_i
wi,并且越高频的词被丢弃的概率越大。具体的代码如下:
def discard(idx):
'''
@params:
idx: 单词的下标
@return: True/False 表示是否丢弃该单词
'''
return random.uniform(0, 1) < 1 - math.sqrt(
1e-4 / counter[idx_to_token[idx]] * num_tokens)
subsampled_dataset = [[tk for tk in st if not discard(tk)] for st in dataset]
print('# tokens: %d' % sum([len(st) for st in subsampled_dataset]))
def compare_counts(token):
return '# %s: before=%d, after=%d' % (token, sum(
[st.count(token_to_idx[token]) for st in dataset]), sum(
[st.count(token_to_idx[token]) for st in subsampled_dataset]))
print(compare_counts('the'))
print(compare_counts('join'))
- 提取中心词和背景词
def get_centers_and_contexts(dataset, max_window_size):
'''
@params:
dataset: 数据集为句子的集合,每个句子则为单词的集合,此时单词已经被转换为相应数字下标
max_window_size: 背景词的词窗大小的最大值
@return:
centers: 中心词的集合
contexts: 背景词窗的集合,与中心词对应,每个背景词窗则为背景词的集合
'''
centers, contexts = [], []
for st in dataset:
if len(st) < 2: # 每个句子至少要有2个词才可能组成一对“中心词-背景词”
continue
centers += st
for center_i in range(len(st)):
window_size = random.randint(1, max_window_size) # 随机选取背景词窗大小
indices = list(range(max(0, center_i - window_size),
min(len(st), center_i + 1 + window_size)))
indices.remove(center_i) # 将中心词排除在背景词之外
contexts.append([st[idx] for idx in indices])
return centers, contexts
all_centers, all_contexts = get_centers_and_contexts(subsampled_dataset, 5)
tiny_dataset = [list(range(7)), list(range(7, 10))]
print('dataset', tiny_dataset)
for center, context in zip(*get_centers_and_contexts(tiny_dataset, 2)):
print('center', center, 'has contexts', context)
- Skip-Gram 跳字模型
在跳字模型中,每个词被表示成两个 d d d 维向量,用来计算条件概率。假设这个词在词典中索引为 i i i ,当它为中心词时向量表示为 v i ∈ R d \boldsymbol{v}_i\in\mathbb{R}^d vi∈Rd,而为背景词时向量表示为 u i ∈ R d \boldsymbol{u}_i\in\mathbb{R}^d ui∈Rd 。设中心词 w c w_c wc 在词典中索引为 c c c,背景词 w o w_o wo 在词典中索引为 o o o,我们假设给定中心词生成背景词的条件概率满足下式:
P ( w o ∣ w c ) = exp ( u o ⊤ v c ) ∑ i ∈ V exp ( u i ⊤ v c ) P(w_o\mid w_c)=\frac{\exp(\boldsymbol{u}_o^\top \boldsymbol{v}_c)}{\sum_{i\in\mathcal{V}}\exp(\boldsymbol{u}_i^\top \boldsymbol{v}_c)} P(wo∣wc)=∑i∈Vexp(ui⊤vc)exp(uo⊤vc)
- PyTorch 预置的 Embedding 层
embed = nn.Embedding(num_embeddings=10, embedding_dim=4)
print(embed.weight)
x = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.long)
print(embed(x))
- Skip-Gram模型的前向计算
def skip_gram(center, contexts_and_negatives, embed_v, embed_u):
'''
@params:
center: 中心词下标,形状为 (n, 1) 的整数张量
contexts_and_negatives: 背景词和噪音词下标,形状为 (n, m) 的整数张量
embed_v: 中心词的 embedding 层
embed_u: 背景词的 embedding 层
@return:
pred: 中心词与背景词(或噪音词)的内积,之后可用于计算概率 p(w_o|w_c)
'''
v = embed_v(center) # shape of (n, 1, d)
u = embed_u(contexts_and_negatives) # shape of (n, m, d)
pred = torch.bmm(v, u.permute(0, 2, 1)) # bmm((n, 1, d), (n, d, m)) => shape of (n, 1, m)
return pred
- 负采样近似
由于 softmax 运算考虑了背景词可能是词典 V \mathcal{V} V 中的任一词,对于含几十万或上百万词的较大词典,就可能导致计算的开销过大。我们将以 skip-gram 模型为例,介绍负采样 (negative sampling) 的实现来尝试解决这个问题。
负采样方法用以下公式来近似条件概率 P ( w o ∣ w c ) = exp ( u o ⊤ v c ) ∑ i ∈ V exp ( u i ⊤ v c ) P(w_o\mid w_c)=\frac{\exp(\boldsymbol{u}_o^\top \boldsymbol{v}_c)}{\sum_{i\in\mathcal{V}}\exp(\boldsymbol{u}_i^\top \boldsymbol{v}_c)} P(wo∣wc)=∑i∈Vexp(ui⊤vc)exp(uo⊤vc):
P ( w o ∣ w c ) = P ( D = 1 ∣ w c , w o ) ∏ k = 1 , w k ∼ P ( w ) K P ( D = 0 ∣ w c , w k ) P(w_o\mid w_c)=P(D=1\mid w_c,w_o)\prod_{k=1,w_k\sim P(w)}^K P(D=0\mid w_c,w_k) P(wo∣wc)=P(D=1∣wc,wo)∏k=1,wk∼P(w)KP(D=0∣wc,wk)
其中 P ( D = 1 ∣ w c , w o ) = σ ( u o ⊤ v c ) P(D=1\mid w_c,w_o)=\sigma(\boldsymbol{u}_o^\top\boldsymbol{v}_c) P(D=1∣wc,wo)=σ(uo⊤vc), σ ( ⋅ ) \sigma(\cdot) σ(⋅) 为 sigmoid 函数。对于一对中心词和背景词,我们从词典中随机采样 K K K 个噪声词(实验中设 K = 5 K=5 K=5)。根据 Word2Vec 论文的建议,噪声词采样概率 P ( w ) P(w) P(w) 设为 w w w 词频与总词频之比的 0.75 0.75 0.75 次方。
- 词嵌入进阶
在“Word2Vec的实现”一节中,我们在小规模数据集上训练了一个 Word2Vec 词嵌入模型,并通过词向量的余弦相似度搜索近义词。虽然 Word2Vec 已经能够成功地将离散的单词转换为连续的词向量,并能一定程度上地保存词与词之间的近似关系,但 Word2Vec 模型仍不是完美的,它还可以被进一步地改进:
- 子词嵌入(subword embedding):FastText 以固定大小的 n-gram 形式将单词更细致地表示为了子词的集合,而 BPE (byte pair encoding) 算法则能根据语料库的统计信息,自动且动态地生成高频子词的集合;
- GloVe 全局向量的词嵌入: 通过等价转换 Word2Vec 模型的条件概率公式,我们可以得到一个全局的损失函数表达,并在此基础上进一步优化模型。
实际中,我们常常在大规模的语料上训练这些词嵌入模型,并将预训练得到的词向量应用到下游的自然语言处理任务中。本节就将以 GloVe 模型为例,演示如何用预训练好的词向量来求近义词和类比词。
GloVe 全局向量的词嵌入
GloVe 模型
先简单回顾以下 Word2Vec 的损失函数(以 Skip-Gram 模型为例,不考虑负采样近似):
−
∑
t
=
1
T
∑
−
m
≤
j
≤
m
,
j
≠
0
log
P
(
w
(
t
+
j
)
∣
w
(
t
)
)
-\sum_{t=1}^T\sum_{-m\le j\le m,j\ne 0} \log P(w^{(t+j)}\mid w^{(t)})
−∑t=1T∑−m≤j≤m,j=0logP(w(t+j)∣w(t))
其中
P
(
w
j
∣
w
i
)
=
exp
(
u
j
⊤
v
i
)
∑
k
∈
V
exp
(
u
k
⊤
v
i
)
P(w_j\mid w_i) = \frac{\exp(\boldsymbol{u}_j^\top\boldsymbol{v}_i)}{\sum_{k\in\mathcal{V}}\exp(\boldsymbol{u}_k^\top\boldsymbol{v}_i)}
P(wj∣wi)=∑k∈Vexp(uk⊤vi)exp(uj⊤vi)
是 w i w_i wi 为中心词, w j w_j wj 为背景词时 Skip-Gram 模型所假设的条件概率计算公式,我们将其简写为 q i j q_{ij} qij。
注意到此时我们的损失函数中包含两个求和符号,它们分别枚举了语料库中的每个中心词和其对应的每个背景词。实际上我们还可以采用另一种计数方式,那就是直接枚举每个词分别作为中心词和背景词的情况:
− ∑ i ∈ V ∑ j ∈ V x i j log q i j -\sum_{i\in\mathcal{V}}\sum_{j\in\mathcal{V}} x_{ij}\log q_{ij} −∑i∈V∑j∈Vxijlogqij
其中
x
i
j
x_{ij}
xij 表示整个数据集中
w
j
w_j
wj 作为
w
i
w_i
wi 的背景词的次数总和。
我们还可以将该式进一步地改写为交叉熵 (cross-entropy) 的形式如下:
− ∑ i ∈ V x i ∑ j ∈ V p i j log q i j -\sum_{i\in\mathcal{V}}x_i\sum_{j\in\mathcal{V}}p_{ij} \log q_{ij} −∑i∈Vxi∑j∈Vpijlogqij
其中 x i x_i xi 是 w i w_i wi 的背景词窗大小总和, p i j = x i j / x i p_{ij}=x_{ij}/x_i pij=xij/xi 是 w j w_j wj 在 w i w_i wi 的背景词窗中所占的比例。
从这里可以看出,我们的词嵌入方法实际上就是想让模型学出 w j w_j wj 有多大概率是 w i w_i wi 的背景词,而真实的标签则是语料库上的统计数据。同时,语料库中的每个词根据 x i x_i xi 的不同,在损失函数中所占的比重也不同。
注意到目前为止,我们只是改写了 Skip-Gram 模型损失函数的表面形式,还没有对模型做任何实质上的改动。而在 Word2Vec 之后提出的 GloVe 模型,则是在之前的基础上做出了以下几点改动:
- 使用非概率分布的变量 p i j ′ = x i j p'_{ij}=x_{ij} pij′=xij 和 q ′ i j = exp ( u j ⊤ v i ) q′_{ij}=\exp(\boldsymbol{u}^\top_j\boldsymbol{v}_i) q′ij=exp(uj⊤vi),并对它们取对数;
- 为每个词 w i w_i wi 增加两个标量模型参数:中心词偏差项 b i b_i bi 和背景词偏差项 c i c_i ci,松弛了概率定义中的规范性;
- 将每个损失项的权重 x i x_i xi 替换成函数 h ( x i j ) h(x_{ij}) h(xij),权重函数 h ( x ) h(x) h(x) 是值域在 [ 0 , 1 ] [0,1] [0,1] 上的单调递增函数,松弛了中心词重要性与 x i x_i xi 线性相关的隐含假设;
- 用平方损失函数替代了交叉熵损失函数。
综上,我们获得了 GloVe 模型的损失函数表达式:
∑ i ∈ V ∑ j ∈ V h ( x i j ) ( u j ⊤ v i + b i + c j − log x i j ) 2 \sum_{i\in\mathcal{V}}\sum_{j\in\mathcal{V}} h(x_{ij}) (\boldsymbol{u}^\top_j\boldsymbol{v}_i+b_i+c_j-\log x_{ij})^2 ∑i∈V∑j∈Vh(xij)(uj⊤vi+bi+cj−logxij)2
由于这些非零 x i j x_{ij} xij 是预先基于整个数据集计算得到的,包含了数据集的全局统计信息,因此 GloVe 模型的命名取“全局向量”(Global Vectors)之意。