<commit_before>---
title: 批量正则化的前馈计算与反向传播分析
tags: 神经网络
---

##### Batch Normalization 想要解决什么问题？

很多时候，当我们训练好一个模型，拿到实际运行环境中会发现精度表现不如预期，其中有一个可能的原因是实际环境中的数据分布和我们训练集的分布不同，术语叫 **Covariate Shift**。这时我们可以采用一些办法把真实数据的分布变换成想要的形式，比如标准化，对数变换等等。在神经网络训练过程中，每一层的输入数据都经过了前面多层处理，并且每层的权重参数都在反向传播的过程中不断变化，因此，即便同样的初始数据在不同轮次的训练中都可能有不同的分布，这一现象也被称为 **Internal Covariate Shift**。既然数据分布都不一样了，那上一批次训练的参数可能就失效了，这就迫使每层的权重参数不断地修正自己来适应新的数据分布，而不是朝着减小损失函数的方向，从而导致模型难以收敛。另一方面，以 sigmoid 激活函数为例 \\(f(x) = \frac{1}{1 + e^{-x}}\\)，它的导数形式为 \\(\frac{e^{-x}}{(1+e^{-x})^2}\\)，如果把导数的图像画出来，大致如下所示

![](/resources/2022-07-19-bn/bn_sigmoid-derivative.png)

可以看到，当 x 的绝对值很大的时候，sigmoid 函数的梯度值接近于 0，也就是说梯度消失了。一种比较理想的情况是输入张量的每个分量的绝对值都位于 0 附近。但是正因为 Internal Covariate Shift 的存在，输入张量值不可能稳定地保持在 0 左右。

通过以上分析可以看到，Internal Covariate Shift 主要造成了两个问题，一个是拖慢模型收敛速度，另一个是在某些激活函数上的梯度消失。为了解决这些问题，论文作者提出的方法是对每个批次的数据强制修改其分布到统一的形式，也就是 **Batch Normalization**。从今天的视角来看，尽管有论文认为 BN 并没有解决 Internal Covariate Shift 问题，但 BN 的效果无疑是值得肯定的，几乎已经成为了神经网络模型的标配结构。

##### Batch Normalization 的具体计算方法

设一个 batch_size 等于 `m` 的小批量 \\(\mathcal{B} = \\(x_1, x_2, ...x_m\}\\)，其中每个数据的维度都为 `d`，整体上来看，该批量数据的均值和方差分量分别为

$$
  \mu^{(k)} = \frac{1}{m} \sum_{i=1}^m x_i^{(k)} \qquad \qquad (1)
  $$
  
$$ 
    (\sigma^{(k)})^2 = \frac{1}{m} \sum_{i=1}^m (x_i - \mu^{(k)})^2  + \epsilon \qquad \qquad (2)
$$

这里的 \\(\epsilon\\) 是一个极小量，它的作用是为了避免当所有 \\(x_i\\) 都相同时的零方差情况。为了把数据变换到均值为0，方差为1 的分布，我们对每个样本的每个分量做如下计算

$$
  \hat{x}_{i}^{(k)} = \frac{x_i^{(k)} - \mu^{(k)}}{\sigma^{(k)}} \qquad \qquad (3)
  $$

忽略 \\(\epsilon\\) 的情况下，这时的 \\(\hat{x}_i\\) 服从标准的正态分布，但我们无法确定这是使得模型效果最佳的分布，因此，最后再添加一个可训练的线性变换

$$
  y_i^{(k)} = \gamma^{(k)} \hat{x}_{i}^{(k)} + \beta^{(k)}  \qquad \qquad (4)
  $$

其中 \\(\beta^{(k)}, \gamma^{(k)}\\) 都是可训练参数。这样一来，每个 BN 层都能学到一个最佳的数据分布，从而增强模型的表现能力。

##### Batch Normalization 的反向传播

BN 层的计算图依赖可以简单表示如下

![](/resources/2022-07-19-bn/bn_dependency.png)<commit_msg>batch normalize<commit_after>---
title: 批量正则化的前馈计算与反向传播分析
tags: 神经网络
---

##### Batch Normalization 想要解决什么问题？

很多时候，当我们训练好一个模型，拿到实际运行环境中会发现精度表现不如预期，其中有一个可能的原因是实际环境中的数据分布和我们训练集的分布不同，术语叫 **Covariate Shift**。这时我们可以采用一些办法把真实数据的分布变换成想要的形式，比如标准化，对数变换等等。在神经网络训练过程中，每一层的输入数据都经过了前面多层处理，并且每层的权重参数都在反向传播的过程中不断变化，因此，即便同样的初始数据在不同轮次的训练中都可能有不同的分布，这一现象也被称为 **Internal Covariate Shift**。既然数据分布都不一样了，那上一批次训练的参数可能就失效了，这就迫使每层的权重参数不断地修正自己来适应新的数据分布，而不是朝着减小损失函数的方向，从而导致模型难以收敛。另一方面，以 sigmoid 激活函数为例 \\(f(x) = \frac{1}{1 + e^{-x}}\\)，它的导数形式为 \\(\frac{e^{-x}}{(1+e^{-x})^2}\\)，如果把导数的图像画出来，大致如下所示

![](/resources/2022-07-19-bn/bn_sigmoid-derivative.png)

可以看到，当 x 的绝对值很大的时候，sigmoid 函数的梯度值接近于 0，也就是说梯度消失了。一种比较理想的情况是输入张量的每个分量的绝对值都位于 0 附近。但是正因为 Internal Covariate Shift 的存在，输入张量值不可能稳定地保持在 0 左右。

通过以上分析可以看到，Internal Covariate Shift 主要造成了两个问题，一个是拖慢模型收敛速度，另一个是在某些激活函数上的梯度消失。为了解决这些问题，论文作者提出的方法是对每个批次的数据强制修改其分布到统一的形式，也就是 **Batch Normalization**。从今天的视角来看，尽管有论文认为 BN 并没有解决 Internal Covariate Shift 问题，但 BN 的效果无疑是值得肯定的，几乎已经成为了神经网络模型的标配结构。

##### Batch Normalization 的具体计算方法

设一个 batch_size 等于 `m` 的小批量 \\(\mathcal{B} = \{x_1, x_2, ...x_m\}\\)，其中每个数据的维度都为 `d`，整体上来看，该批量数据的均值和方差分量分别为

$$
  \mu^{(k)} = \frac{1}{m} \sum_{i=1}^m x_i^{(k)} \qquad \qquad (1)
  $$
  
$$ 
    (\sigma^{(k)})^2 = \frac{1}{m} \sum_{i=1}^m (x_i - \mu^{(k)})^2  + \epsilon \qquad \qquad (2)
$$

这里的 \\(\epsilon\\) 是一个极小量，它的作用是为了避免当所有 \\(x_i\\) 都相同时的零方差情况。为了把数据变换到均值为0，方差为1 的分布，我们对每个样本的每个分量做如下计算

$$
  \hat{x}_{i}^{(k)} = \frac{x_i^{(k)} - \mu^{(k)}}{\sigma^{(k)}} \qquad \qquad (3)
  $$

忽略 \\(\epsilon\\) 的情况下，这时的 \\(\hat{x}_i\\) 服从标准的正态分布，但我们无法确定这是使得模型效果最佳的分布，因此，最后再添加一个可训练的线性变换

$$
  y_i^{(k)} = \gamma^{(k)} \hat{x}_{i}^{(k)} + \beta^{(k)}  \qquad \qquad (4)
  $$

其中 \\(\beta^{(k)}, \gamma^{(k)}\\) 都是可训练参数。这样一来，每个 BN 层都能学到一个最佳的数据分布，从而增强模型的表现能力。

##### Batch Normalization 的反向传播

BN 层的计算图依赖可以简单表示如下

![](/resources/2022-07-19-bn/bn_dependency.png)
{'fraction_non_alphanumeric': 0.20275642724622317, 'fraction_numerical': 0.019613040021203287, 'mean_word_length': 6.027932960893855, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7283888', 'n_tokens_mistral': 2700, 'n_tokens_neox': 3007, 'n_words': 226}