Run this notebook online: or Colab:

# 4.8. 数值稳定性和模型初始化¶

## 4.8.1. 梯度消失和梯度爆炸¶

(4.8.1)$\mathbf{h}^{(l)} = f_l (\mathbf{h}^{(l-1)}) \text{ 因此 } \mathbf{o} = f_L \circ \ldots \circ f_1(\mathbf{x}).$

(4.8.2)$\partial_{\mathbf{W}^{(l)}} \mathbf{o} = \underbrace{\partial_{\mathbf{h}^{(L-1)}} \mathbf{h}^{(L)}}_{ \mathbf{M}^{(L)} \stackrel{\mathrm{def}}{=}} \cdot \ldots \cdot \underbrace{\partial_{\mathbf{h}^{(l)}} \mathbf{h}^{(l+1)}}_{ \mathbf{M}^{(l+1)} \stackrel{\mathrm{def}}{=}} \underbrace{\partial_{\mathbf{W}^{(l)}} \mathbf{h}^{(l)}}_{ \mathbf{v}^{(l)} \stackrel{\mathrm{def}}{=}}.$

### 4.8.1.1. 梯度消失¶

%load ../utils/djl-imports

import org.apache.commons.lang3.ArrayUtils;

NDManager manager = NDManager.newBaseManager();
NDArray x = manager.arange(-8.0f, 8.0f, 0.1f);
NDArray y = null;
y = Activation.sigmoid(x);
gc.backward(y);
}

int xLength = (int) x.size();
int yLength = (int) y.size();

float[] X = new float[xLength];
float[] Y = new float[yLength];
float[] Z = new float[yLength];

X = x.toFloatArray();
Y = y.toFloatArray();
Z = res.toFloatArray();

String[] groups = new String[xLength*2];
Arrays.fill(groups, 0, xLength, "sigmoid");
Arrays.fill(groups, xLength, xLength * 2, "gradient");

StringColumn.create("groups", groups)
);
render(LinePlot.create("", data, "x", "grad of relu", "groups"), "text/html");


### 4.8.1.2. 梯度爆炸¶

NDArray M = manager.randomNormal(new Shape(4,4));
System.out.println("一个矩阵: " + M);
for(int i = 0; i < 100; i++){
M = M.dot(manager.randomNormal(new Shape(4,4)));
}

System.out.println("a乘以100个矩阵后: " + M);

一个矩阵: ND: (4, 4) gpu(0) float32
[[ 0.2925, -0.7184,  0.1   , -0.3932],
[ 2.547 , -0.0034,  0.0083, -0.251 ],
[ 0.129 ,  0.3728,  1.0822, -0.665 ],
[ 0.5434, -0.7168, -1.4913,  1.4805],
]

a乘以100个矩阵后: ND: (4, 4) gpu(0) float32
[[-1.60097634e+23, -1.36331671e+23,  1.22807521e+22, -4.03570949e+23],
[-4.56637224e+23, -3.88849763e+23,  3.50268902e+22, -1.15108159e+24],
[-1.67982393e+23, -1.43046052e+23,  1.28854639e+22, -4.23446884e+23],
[ 1.81735413e+23,  1.54757590e+23, -1.39405301e+22,  4.58115233e+23],
]


## 4.8.2. 参数初始化¶

### 4.8.2.2. Xavier初始化¶

(4.8.3)$o_{i} = \sum_{j=1}^{n_\mathrm{in}} w_{ij} x_j.$

(4.8.4)\begin{split}\begin{aligned} E[o_i] & = \sum_{j=1}^{n_\mathrm{in}} E[w_{ij} x_j] \\&= \sum_{j=1}^{n_\mathrm{in}} E[w_{ij}] E[x_j] \\&= 0, \\ \mathrm{Var}[o_i] & = E[o_i^2] - (E[o_i])^2 \\ & = \sum_{j=1}^{n_\mathrm{in}} E[w^2_{ij} x^2_j] - 0 \\ & = \sum_{j=1}^{n_\mathrm{in}} E[w^2_{ij}] E[x^2_j] \\ & = n_\mathrm{in} \sigma^2 \gamma^2. \end{aligned}\end{split}

(4.8.5)\begin{aligned} \frac{1}{2} (n_\mathrm{in} + n_\mathrm{out}) \sigma^2 = 1 \text{ 或等价于 } \sigma = \sqrt{\frac{2}{n_\mathrm{in} + n_\mathrm{out}}}. \end{aligned}

(4.8.6)$U\left(-\sqrt{\frac{6}{n_\mathrm{in} + n_\mathrm{out}}}, \sqrt{\frac{6}{n_\mathrm{in} + n_\mathrm{out}}}\right).$

## 4.8.3. 小结¶

• 梯度消失和爆炸是深度网络中常见的问题。在参数初始化时需要非常小心，以确保梯度和参数可以得到很好的控制。

• 需要用启发式的初始化方法来确保初始梯度既不太大也不太小。

• ReLU激活函数缓解了梯度消失问题，这样可以加速收敛。

• 随机初始化是保证在进行优化前打破对称性的关键。

• Xavier初始化表明，对于每一层，输出的方差不受输入数量的影响，任何梯度的方差不受输出数量的影响。

## 4.8.4. 练习¶

1. 除了多层感知机的排列对称性之外，你能设计出其他神经网络可能会表现出对称性且需要被打破的情况吗？

2. 我们是否可以将线性回归或softmax回归中的所有权重参数初始化为相同的值？

3. 在相关资料中查找两个矩阵乘积特征值的解析界。这对确保梯度条件合适有什么启示？

4. 如果我们知道某些项是发散的，我们能在事后修正吗？看看关于分层自适应速率缩放的论文 [You et al., 2017]