文章目录
- 层规范化
- 层规范化参数与公式
- normalized_shape传入一个整数
- 接口函数LayerNorm计算
- 手动计算
- normalized_shape传入一个列表
- 接口函数LayerNorm计算
- 手动计算
层规范化
在批量规范化这篇文章里详细介绍了批量规范化在卷积神经网络里的使用,本篇文章将继续介绍另一个规范化技术–层规范化。
无论是批量规范化还是层规范化,目的都是对输入数据进行归一化处理,以提高模型的鲁棒性和泛化能力。区别在于,层规范化是在每个样本内进行归一化操作,通常是基于特征维度进行规范化。层规范化的优势在于可以处理变长序列(比如文本数据),因此在自然语言处理任务中(填充前的输入一般是变长序列)批量规范化没有层规范化的效果好,比如在Transformer模型架构中就使用了层规范化。
本文将深入探究层规范化的计算细节、不同参数的作用以及与批量规范化的区别。
层规范化参数与公式
层规范化的公式为:
L N ( x ) = γ ⊙ x − μ σ 2 + ε + β ( 1 ) LN(x)=\gamma \odot {{x-μ} \over{\sqrt{\sigma^2 + \varepsilon}}}+\beta \ \ \ \ \ (1) LN(x)=γ⊙σ2+εx−μ+β (1)
其中, x x x 是样本内给定维度下的待规范化数据、 μ μ μ 是均值、 σ 2 \sigma^2 σ2 是方差、 ε \varepsilon ε 防止除0; γ \gamma γ 是拉伸参数(scale)、 β \beta β 是偏移参数(shift),这俩是需要模型学习的。
pytorch中提供的层规范化接口是 LayerNorm ,参数为:
torch.nn.LayerNorm(normalized_shape, eps=1e-05, elementwise_affine=True, bias=True, device=None, dtype=None)
- normalized_shape:规范化的数据范围,指定在哪些维度数据上进行规范化。对于自然语言处理,一般输入 (批量大小-2,时间步-3,特征维度-4) :
如果传入的是一个整数,比如4,需要和输入数据的最后一维保持一致(比如此处的特征维度-4),表示在每个样本的每个时间步的特征数据上进行规范化;
如果输入一个列表,比如[3,4],则列表里的数字从后往前要依次和输入数据维度从最后向前保持一致,比如此处的[3,4]依次对应输入数据的最后两个维度(时间步-3、特征维度-4),表示在每个样本所有时间步的特征维数据上进行规范化;
- eps:在分母中方差后添加的一个固定值,是为了计算稳定性,防止除0,默认值为1e-5;
- elementwise_affine:层规范化默认的标准化是均值为0、标准差为1,但对实际数据来说不一定是合适的分布,因此加了可学习的拉伸参数 γ \gamma γ (gamma) 和偏移参数 β \beta β (beta),以期自动学习合适的分布。elementwise_affine用来控制是否需要这两个参数,默认为True,表示需要拉伸参数(初始值为1)和偏移参数(初始值为0),可通过训练学习;其中 γ \gamma γ 的初始值为1,形状和normalized_shape一样、 β \beta β 初始值为0,形状也和normalized_shape一样;
- bias:是否需要偏移参数 β \beta β 。
注:层规范化不会像批量规范化那样跟踪统计全局期望(running_mean)、方差(running_var),因此在训练模式和预测模式下没有什么不同。
normalized_shape传入一个整数
现在来看看层规范化的计算细节,本次在一个三维张量(batch_size-批量大小, num_steps-时间步数, num_hiddens-特征维度)上分别使用接口LayerNorm函数和手动两种方式计算进行对比。
接口函数LayerNorm计算
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
import torch
from torch import nn
#三维张量:(batch_size-批量大小, num_steps-时间步数, num_hiddens-特征维度)
X=torch.arange(24,dtype=torch.float32).reshape((2,3,4))
X.shape
X
#实例化一个层规范化类
LN=nn.LayerNorm(4)
LN
#查看是否处于训练模式
print('是否处于训练模式:', LN.training)
#查看初始gamma
print('初始gamma:', LN.weight, '初始gamma形状:', LN.weight.shape)
#查看初始beta
print('初始beta:',LN.bias, '初始beta形状:', LN.bias.shape)
图中打印了训练模式下weight、bias,可以看出数量与normalized_shape数对应。
计算层规范化。
#层规范化
X_LN=LN(X)
X_LN.shape
X_LN
上面通过接口计算的层规范化时,normalized_shape=4,表示在每个样本的每个时间步的特征维度的4个数据上(即下图每个颜色的一行作为一组数据做规范化)计算均值、方差,然后规进行范化。
因为weight、bias与normalized_shape数对应,因此将重复使用6次(剩余维度2*3=6)来对每行数据分布进行仿射变换。
手动计算
下面通过手动计算来看下层规范化的细节。
#计算均值、方差
mean = X.mean(dim=-1,keepdim=True) #均值
var = ((X - mean) ** 2).mean(dim=-1,keepdim=True) #方差
mean.shape
mean
var.shape
var
由下图可知每行对应1个均值、标准差。
计算批量规范化,可以看到计算结果与上面函数接口计算的一样。
eps=1e-05
X_LN = (X - mean) / torch.sqrt(var + eps)
X_LN.shape
X_LN
normalized_shape传入一个列表
现在来看下normalized_shape传入一个列表时是如何计算的。
接口函数LayerNorm计算
#实例化一个层规范化类
LN=nn.LayerNorm([3,4])
LN
#查看是否处于训练模式
print('是否处于训练模式:', LN.training)
#查看初始gamma
print('初始gamma:', LN.weight, '初始gamma形状:', LN.weight.shape)
#查看初始beta
print('初始beta:',LN.bias, '初始beta形状:', LN.bias.shape)
图中打印了训练模式下weight、bias,可以看出数量与normalized_shape形状(3,4)相同。
计算层规范化。
#层规范化
X_LN=LN(X)
X_LN.shape
X_LN
上面通过接口计算层规范化时,normalized_shape=(3,4),表示在每个样本的所有时间步的特征维度的12个数据上(即下图每个颜色的作为一组数据做规范化)计算均值、方差,然后规进行范化。
因为weight、bias与normalized_shape形状对应,因此将重复使用2次(剩余维度2)来对每组数据分布进行仿射变换。
手动计算
下面通过手动计算来看下层规范化的细节。
#计算均值、方差
mean = X.mean(dim=[1,2],keepdim=True) #均值
var = ((X - mean) ** 2).mean(dim=[1,2],keepdim=True) #方差
mean.shape
mean
var.shape
var
由下图可知每个样本里所有时间步的数据对应1个均值、标准差。
计算层规范化,可以看到计算结果与上面函数接口计算的一样。
eps=1e-05
X_LN = (X - mean) / torch.sqrt(var + eps)
X_LN.shape
X_LN
完!
有空再写下其他规范化。