5种归一化方法¶
约 3785 个字 464 行代码 31 张图片 预计阅读时间 25 分钟
45、五种归一化的原理与PyTorch逐行手写实现讲解(BatchNorm/LayerNorm/InsNorm/GroupNorm/WeightNorm)
BatchNorm¶
图示¶
批归一化、通道级别的归一化
官网api,BatchNorm1D & 2D¶
BatchNorm1D的输入:NCL,用于NLP
BatchNorm2D的输入是四维的,用于图像
一个是三维tensor作为输入
一个是四维tensor作为输入
BatchNorm1D¶
- 首先,位于torch.nn模块下,是一个class,所以要用的话,需要实例化
- 接下来,看实例化需要接收的参数:
- num features:输入张量的特征维度,或者通道的数目,或者embedding的大小
- eps:5种归一化都需要的eps,分母数值稳定性,让分母加上一个微小的量,使得除法能够正常进行,默认1e-05
- momentum:动量
- 批归一化在计算均值和方差的时候,momentum通常需要跟track_running_sate联合起来理解,也就是说我们的统计量 通常是通过滑动平均计算出来了,而不是单一时刻的mini batch,是一个累计的过程,为了提高估计的准确度
- affine:
- 也就是 gamma & beta,也就是再做完归一化以后,也可以加一个映射,将其映射到一个新的分布上,做一个rescale和recenter
官网定义:
(解释官网定义)均值和标准差是经过整个mini_batch
一句话说明 BatchNorm:per channel across mini-batch
贯穿整个mini batch计算统计量,每个通道单独去算的
gamma 和 beta 是可学习的向量,维度都是C,默认的情况下 $\gamma = 1、\beta=0 $
标准差用的是有偏估计,也就是计算的标准差是 \(\frac{1}{n}\),强调这句话的目的是 ,在计算方差的时候,要用 \(\mathrm{unbiased=False}\),这里用得是有偏估计
在默认情况下,在训练中,会不断的记录历史的均值和方差,并且使用0.1的动量,来做移动的估计,当训练结束以后,用最后一个时刻的估计量来做 inference
也可以设置 track running states等于false,就是不要记录历史的移动的值
以上是api的介绍
接下来 自己写一个BatchNorm 更好的理解
-
NLP的标准数据格式:inputx = torch.randn(batch_size,times_steps,embedding_dim) # \(N*L*C\)
-
实例化,接收的输入(特征维度,是否进行仿射变换):batch_norm_op = torch.nn.BatchNorm1d(embedding_dim,affine=False)
-
batchnorm的forward函数 接收的数据集格式是 BDN b表示batch size;D表示model dim;N表示序列长度(符号表示方法的不同
bn_y = batch_norm_op(inputx.transpose(-1,-2)).transpose(-1,-2)
import torch
batch_size = 2
times_steps = 3
embedding_dim = 4
inputx = torch.randn(batch_size,times_steps,embedding_dim) # N*L*C
# 1. 实现batch_norm并验证API
## 调用 batch_norm API
batch_norm_op = torch.nn.BatchNorm1d(embedding_dim,affine=False)
bn_y = batch_norm_op(inputx.transpose(-1,-2)).transpose(-1,-2)
## 手写batch_norm
bn_mean = inputx.mean(dim=(0,1),keepdim=True)
bn_std = inputx.std(dim=(0,1),unbiased=False,keepdim=True)
verify_bn_y = (inputx - bn_mean)/(bn_std+1e-5)
print(bn_y)
print(verify_bn_y)
print(torch.allclose(bn_y,verify_bn_y))
输出:
tensor([[[-0.3771, 1.7863, -1.0572, 0.2856],
[-0.7956, -0.0363, -0.7429, -0.1670],
[ 2.0838, 0.7039, 1.1345, 0.7286]],
[[-0.5775, -0.3680, -1.1160, -1.3169],
[ 0.3298, -1.0699, 1.2153, -1.0909],
[-0.6634, -1.0160, 0.5663, 1.5606]]])
tensor([[[-0.3771, 1.7863, -1.0572, 0.2856],
[-0.7956, -0.0363, -0.7429, -0.1670],
[ 2.0838, 0.7039, 1.1345, 0.7286]],
[[-0.5775, -0.3680, -1.1160, -1.3169],
[ 0.3298, -1.0699, 1.2153, -1.0909],
[-0.6634, -1.0160, 0.5663, 1.5606]]])
True
解释:去看图解BN&LN
需要强调:
- 在batch和长度这一维计算统计量
- 计算标准差的时候,用的是有偏估计,设置unbiased=false
LayerNorm¶
层归一化的概括:per sample、per Layer,对单一样本、每一个层 单独计算,不需要考虑minibatch
LayerNorm最典型的使用场景:NLP
把网络中 每一次 每一个时刻 当成一层
每个时刻 embedding dim计算均值和方差
- 为什么nlp中使用LN?
应为nlp中,不同句子长度是不一样的,也就是说 对于每个batch中的 或者 句子中 词数是不一样的;或者测试时,句子的长度可能训练时也没见过,而BatchNorm是across batch的,所以最好保证batch内部L是固定的
一句话:句子中词的数量并不一样
图示¶
语言描述实际意义¶
for nlp¶
- LayerNorm对 每个词进行归一化 bnd dim=2? (有几个词就有得到几个均值和方差,然后进行归一化)b×n×1
举数学例子:2个句子、每个句子3个词,每个词的维度4,那么我们得到6个均值和方差,所以归一化后的维度 2×3×1
\(\rightarrow\)
\(\rightarrow\)
\(\rightarrow\)
\(\rightarrow\)
\(\rightarrow\)
\(\rightarrow\)
- BatchNorm 对词的特征维归一化 bnd dim=0,1 1×1×d(有几个特征维度,就会得到几个均值和方差) \(\downarrow\) \(\downarrow\) \(\downarrow\) \(\downarrow\) \(\downarrow\) \(\downarrow\)
2×3×4 得到 1×1×4 也就是4个均值,细节也不用扣这么细,直接keepdim=true
for cv¶
-
LayerNorm bchw 看做 chw 同时归一化,有几个chw有几个归一化均值和方差;bnd,有几个bn有几个nlp归一化的均值和方差 ;c 依然是独立的词,表达不同的语义;
-
BatchNorm 把 通道 作nlp中的独立 词、token,对于高度和宽度以及空间分布 不做区分 可以理解为 bc(hw),类比到nlp,一个通道的信息,由长度为h*w的向量表示,保留通道信息,沿着样本维度进行归一化,其实也是引入了其他样本的噪声 bchw $ \rightarrow$ b×c×hw dim=0,2,3
数学小例子:4张图,3通道,2×2的图,BN以后,得到 3个均值和方差
4×3×2×2 \(\rightarrow\) 1×3×1×1
BatchNorm的api中,需要的数据格式是NCL,而不是常用的NLC,可能原因就是bn通常用在cv
cv nlp bn ln¶
-
大概是 理解为
-
bn(得到均值向量)
4张图,每张图片 3个通道特征,每个通道 由4个元素表达(2×2)(得到3个均值)(cv&bn)
2个句子,每个句子3个词,4维度(得到4个均值)(nlp&bn)
-
ln
-
4张图,3个通道特征,每个通道由长度为4的向量表达(2×2),得到4个均值和方差(per sample、per layer)(cv ln)
- 4个句子,每个句子3个词,每个词嵌入4个维度,得到12个均值和方差(ln、nlp)
官方api¶
LayerNorm只有一个api
同样也是一个class,如果要去实例化的话,只需要指定一下 被归一化的形状,以及是否需要进行缩放变换
也就是说接收的输入:
- normalized_shape:被归一化的形状
- elementwise_affine:是否需要进行缩放变换
接下来看定义:
- 这里的均值和方差是在最后一个维度计算的(over the last dimension)对于 over across minibatch
- D就是 我们要传入的 normalized shape
- 在nlp中 通常只需要传入标量就好了,就是计算最后的embedding的维度
代码实现¶
import torch
batch_size = 2
times_steps = 3
embedding_dim = 4
inputx = torch.randn(batch_size,times_steps,embedding_dim) # N*L*C
# 2. 实现layer_norm 并验证api
## 调用 layer_norm API
layer_norm_op = torch.nn.LayerNorm(embedding_dim,elementwise_affine=False)
ln_y = layer_norm_op(inputx)
## 手写layer_norm
ln_mean = inputx.mean(dim=-1,keepdim=True)
ln_std = inputx.std(dim=-1,keepdim=True,unbiased=False)
verify_bn_y = (inputx - ln_mean)/(ln_std + 1e-05)
# print(ln_mean.shape) torch.Size([2, 3, 1])
# print(ln_std.shape) torch.Size([2, 3, 1])
# print(ln_y.shape) torch.Size([2, 3, 4])
# print(verify_bn_y.shape) torch.Size([2, 3, 4])
# print(torch.allclose(ln_y,verify_bn_y)) True
bn(得到均值向量)
4张图,每张图片 3个通道特征,每个通道 由4个元素表达(2×2)(得到3个均值)(cv&bn)
4322→1311
2个句子,每个句子3个词,4维度(得到4个均值)(nlp&bn)
234→114
ln(得到均值矩阵)
对于LN 一定要明白:per sample、per layer
对于CV per sample就是一张图片
对于NLP per layer 就是一个词
4张图,每张图片 3个通道特征,每个通道 由4个元素表达(2×2)(得到4个均值 per sample)
4322 →4111
2个句子,每个句子3个词,4维度(得到6个均值)(nlp&bn)
(234→231)
代码实现 BN、LN&NLP&CV¶
nlp&BN&LN
import torch
batch_size = 2
times_steps = 3
embedding_dim = 4
inputx = torch.randn(batch_size,times_steps,embedding_dim) # N*L*C
# 1. 实现batch_norm并验证API
## 调用 batch_norm API
batch_norm_op = torch.nn.BatchNorm1d(embedding_dim,affine=False)
bn_y = batch_norm_op(inputx.transpose(-1,-2)).transpose(-1,-2)
## 手写batch_norm
bn_mean = inputx.mean(dim=(0,1),keepdim=True)
bn_std = inputx.std(dim=(0,1),unbiased=False,keepdim=True)
verify_bn_y = (inputx - bn_mean)/(bn_std+1e-5)
# print(bn_mean.shape) torch.Size([1, 1, 4])
# print(bn_std.shape) torch.Size([1, 1, 4])
# print(bn_y.shape) torch.Size([2, 3, 4])
# print(verify_bn_y.shape) torch.Size([2, 3, 4])
# print(torch.allclose(bn_y,verify_bn_y)) True
# 2. 实现layer_norm 并验证api
## 调用 layer_norm API
layer_norm_op = torch.nn.LayerNorm(embedding_dim,elementwise_affine=False)
ln_y = layer_norm_op(inputx)
## 手写layer_norm
ln_mean = inputx.mean(dim=-1,keepdim=True)
ln_std = inputx.std(dim=-1,keepdim=True,unbiased=False)
verify_bn_y = (inputx - ln_mean)/(ln_std + 1e-05)
# print(ln_mean.shape) torch.Size([2, 3, 1])
# print(ln_std.shape) torch.Size([2, 3, 1])
# print(ln_y.shape) torch.Size([2, 3, 4])
# print(verify_bn_y.shape) torch.Size([2, 3, 4])
# print(torch.allclose(ln_y,verify_bn_y)) True
CV&BN&LN
import torch
batch_size = 4
channels = 3
h,w = 2,2
inputx = torch.randn(batch_size,channels,h,w) # BCHW 只要维度是正确的,数字可以随便生成
# 1. 实现batch_norm并验证API
## 调用 batch_norm API
batch_norm_op = torch.nn.BatchNorm2d(channels,affine=False)
bn_y = batch_norm_op(inputx) # torch.Size([4, 3, 2, 2])
## 手写batch_norm
bn_mean = inputx.mean(dim=(0,2,3),keepdim=True) # torch.Size([1, 3, 1, 1])
bn_std = inputx.std(dim=(0,2,3),unbiased=False,keepdim=True) # torch.Size([1, 3, 1, 1])
verify_bn_y = (inputx - bn_mean)/(bn_std+1e-5) # torch.Size([4, 3, 2, 2])
# print(bn_mean.shape)
# print(bn_std.shape)
# print(bn_y.shape)
# print(verify_bn_y.shape)
# print(torch.allclose(bn_y,verify_bn_y))
'''
torch.Size([1, 3, 1, 1])
torch.Size([1, 3, 1, 1])
torch.Size([4, 3, 2, 2])
torch.Size([4, 3, 2, 2])
True
'''
# 2. 实现layer_norm 并验证api
## 调用 layer_norm API
layer_norm_op = torch.nn.LayerNorm((channels,h,w),elementwise_affine=False)
ln_y = layer_norm_op(inputx) # torch.Size([4, 3, 2, 2])
## 手写layer_norm
ln_mean = inputx.mean(dim=(1,2,3),keepdim=True) # torch.Size([4, 1, 1, 1])
ln_std = inputx.std(dim=(1,2,3),keepdim=True,unbiased=False) # torch.Size([4, 1, 1, 1])
verify_bn_y = (inputx - ln_mean)/(ln_std + 1e-05) # torch.Size([4, 3, 2, 2])
print(ln_mean.shape)
print(ln_std.shape)
print(ln_y.shape)
print(verify_bn_y.shape)
print(torch.allclose(ln_y,verify_bn_y))
'''
torch.Size([4, 1, 1, 1])
torch.Size([4, 1, 1, 1])
torch.Size([4, 3, 2, 2])
torch.Size([4, 3, 2, 2])
True
'''
纯享版:
for 句子
import torch
batch_size = 2
times_steps = 3
embedding_dim = 4
inputx = torch.randn(batch_size,times_steps,embedding_dim) # N*L*C
# 1. 实现batch_norm并验证API
## 调用 batch_norm API
batch_norm_op = torch.nn.BatchNorm1d(embedding_dim,affine=False)
bn_y = batch_norm_op(inputx.transpose(-1,-2)).transpose(-1,-2)
## 手写batch_norm
bn_mean = inputx.mean(dim=(0,1),keepdim=True)
bn_std = inputx.std(dim=(0,1),unbiased=False,keepdim=True)
verify_bn_y = (inputx - bn_mean)/(bn_std+1e-5)
print(torch.allclose(bn_y,verify_bn_y))
# 2. 实现layer_norm 并验证api
## 调用 layer_norm API
layer_norm_op = torch.nn.LayerNorm(embedding_dim,elementwise_affine=False)
ln_y = layer_norm_op(inputx)
## 手写layer_norm
ln_mean = inputx.mean(dim=-1,keepdim=True)
ln_std = inputx.std(dim=-1,keepdim=True,unbiased=False)
verify_bn_y = (inputx - ln_mean)/(ln_std + 1e-05)
print(torch.allclose(ln_y,verify_bn_y))
for 图片
import torch
batch_size = 4
channels = 3
h,w = 2,2
inputx = torch.randn(batch_size,channels,h,w) # BCHW 只要维度是正确的,数字可以随便生成
# 1. 实现batch_norm并验证API
## 调用 batch_norm API
batch_norm_op = torch.nn.BatchNorm2d(channels,affine=False)
bn_y = batch_norm_op(inputx)
## 手写batch_norm
bn_mean = inputx.mean(dim=(0,2,3),keepdim=True)
bn_std = inputx.std(dim=(0,2,3),unbiased=False,keepdim=True)
verify_bn_y = (inputx - bn_mean)/(bn_std+1e-5)
print(torch.allclose(bn_y,verify_bn_y))
# 2. 实现layer_norm 并验证api
## 调用 layer_norm API
layer_norm_op = torch.nn.LayerNorm((channels,h,w),elementwise_affine=False)
ln_y = layer_norm_op(inputx)
## 手写layer_norm
ln_mean = inputx.mean(dim=(1,2,3),keepdim=True)
ln_std = inputx.std(dim=(1,2,3),keepdim=True,unbiased=False)
verify_bn_y = (inputx - ln_mean)/(ln_std + 1e-05)
print(torch.allclose(ln_y,verify_bn_y))
Instance Norm¶
实例归一化,通常用在 风格迁移上
per sample、per channel
这时计算均值和标准差的时候,是对每一个样本的、每一个维度
官网api
- 是一个class
num_features
:要实现一个Instance Norm的话,需要传入特征维度或者说 通道维度(model dim 可以理解为 channel),因为 我们要逐样本,逐通道的进行归一化affine
:也可以进行仿射变换,但大部分情况下,是设置为false的,一般是直接归一化即可
INSTANCE1D¶
代码实现需要注意,接收的输入数据格式是什么样的,输出的数据格式又是什么样的,看官网api
NCL
for nlp:嵌入维度放到中间、词数滞后
for cv:INSTANCE2D :bchw 不变
就很类似BatchNorm
所以会有两次转置
图示¶
for NLP¶
for CV¶
代码实现¶
For nlp InstanceNorm1d¶
# 3. 实现instance_norm并验证API
## 调用ins_norm并验证API
ins_norm_op = torch.nn.InstanceNorm1d(embedding_dim)
in_y = ins_norm_op(inputx.transpose(-1,-2)).transpose(-1,-2)
## 手写ins_norm
in_mean = inputx.mean(dim=1,keepdim=True)
in_std = inputx.std(dim=1,keepdim=True,unbiased=False)
verify_in_y = (inputx - in_mean)/(in_std+1e-5)
print(torch.allclose(in_y,verify_in_y))
解释:
-
in_y = ins_norm_op(inputx.transpose(-1,-2)).transpose(-1,-2)
先转置再转置回来 -
inputx.shape = torch.Size([2, 3, 4])
- in_y.shape = torch.Size([2, 3, 4])
-
in_mean.shape = torch.Size([2, 1, 4])
-
解释为什么instanceNorm能够实现风格迁移?
输入 batch size×sequence length×embedding dim,如果我们只对中间这一维求均值的话,也就是说 把所有时刻的embedding求一个均值,相当于均值是当前这个样本在所有时刻 不变的东西,那我们把不变的东西消掉,就是说 把 这个时序样本在 所有时刻中 都有的东西 消掉,那什么东西是在所有时刻都有的呢?其实就是这样吧样本的风格,如果是一句文本 或者说 一张图片 或者说一句话 一段音频,一张图片就是风格,一句音频,一句因为什么东西是时不变的?假如说 是一个人说的话的话,那就是说 这个人的身份是时不变的;
也就是说 通过instanceNorm,如果是图像的话,就可以把图像的风格给消掉,那如果在语音中,就可以把这个人的身份消掉,因为我们找的是跨时间的均值和标准差,我们做的归一,也就是说 我们把图片中的风格消掉了,把语音中说话人的身份消掉了,如果是文本的话,可能是文本中 某一个共有的特征;
所以instanceNorm一般用在风格迁移中,把时不变的东西去掉了
For cv InstanceNorm2d¶
# 3. 实现instance_norm并验证API
## 调用ins_norm并验证API
ins_norm_op = torch.nn. (channels)
in_y = ins_norm_op(inputx.transpose(-1,-2)).transpose(-1,-2)
# print(inputx.shape) torch.Size([4, 3, 2, 2])
## 手写ins_norm
in_mean = inputx.mean(dim=(2,3),keepdim=True)
# dim=(2,3) print(in_mean.shape) torch.Size([4, 3, 1, 1])
#dim=1 print(in_mean.shape) torch.Size([4, 1, 2, 2])
in_std = inputx.std(dim=(2,3),keepdim=True,unbiased=False)
verify_in_y = (inputx - in_mean)/(in_std+1e-5)
print(torch.allclose(in_y,verify_in_y))
- dim=(2,3) || in_mean = inputx.mean(dim=(2,3),keepdim=True)
GroupNormalization¶
分组归一化、群归一化
per sample、per group
这个跟LayerNorm是最像的
需要将channel划分成group,类似分组卷积、把输入通道划分成不同的group
首先对input tensor划分成不同的group,然后对于每个样本,每个group计算归一化即可
这里仍然是跟batch size无关的,batch Norm是跟batch size有关的
官方api¶
(1)是一个class
(2)实现群归一化的话,需要的传入参数:
- num_groups:group的数目,举个例子,如果channel=4,划分成2个group,那这里我们就可以传入2
- num_channels:例子中 num_channels=4
- affine:一般默认=false
NLP&GN¶
代码实现¶
- 实例化class
- 输入的参数,以及输入的形状
输入通道数 需要放在中间的维度,调用groupNorm需要类似BatchNorm,做一个转置,也是跟instanceNorm是类似的
# 4. 实现group_norm并验证API
## 调用group_norm并验证API
num_groups = 2
group_norm_op = torch.nn.GroupNorm(num_groups,embedding_dim,affine=False)
gn_y = group_norm_op(inputx.transpose(-1,-2)).transpose(-1,-2)
首先需要将inputx划分成 num_groups组,需要调用划分的api,也就是split,切成多个tensor
指定好维度以及切分的大小
官网api,torch.split函数怎么用,需要传入什么参数
需要传入的参数:
- tensor:切谁
- split_size_or_sections:切成多大的
- dim:切哪个维度
分组,分组过后在每个组计算
per smaple、per group
# 4. 实现group_norm并验证API
## 调用group_norm并验证API
num_groups = 2
group_norm_op = torch.nn.GroupNorm(num_groups,embedding_dim,affine=False)
gn_y = group_norm_op(inputx.transpose(-1,-2)).transpose(-1,-2)
## 手写group_norm
group_inputxs = torch.split(inputx,split_size_or_sections=embedding_dim//num_groups,dim=-1)
results = []
for g_inputx in group_inputxs:
gn_mean = g_inputx.mean(dim=(1,2),keepdim=True)
# print(gn_mean.shape) # torch.Size([2, 1, 1])
gn_std = g_inputx.std(dim=(1,2),keepdim=True,unbiased=False)
gn_result = (g_inputx - gn_mean)/(gn_std + 1e-5)
results.append(gn_result)
verify_gn_y = torch.cat(results,dim=-1)
print(torch.allclose(gn_y,verify_gn_y))
图示¶
CV&GN¶
写代码时需要注意:
ValueError: num_channels must be divisible by num_groups
代码实现¶
# 4. 实现group_norm并验证API
## 调用group_norm并验证API
batch_size = 4
channels = 6
h,w = 2,2
inputx = torch.randn(batch_size,channels,h,w)
num_groups = 3
group_norm_op = torch.nn.GroupNorm(num_groups,channels,affine=False)
gn_y = group_norm_op(inputx)
print(gn_y.shape) # torch.Size([4, 6, 2, 2])
## 手写group_norm
# BCHW
group_inputxs = torch.split(inputx,split_size_or_sections=channels//num_groups,dim=1)
results = []
for g_inputx in group_inputxs:
gn_mean = g_inputx.mean(dim=(1,2,3),keepdim=True)
print(gn_mean.shape) # 3 个 torch.Size([4, 1, 1, 1])
gn_std = g_inputx.std(dim=(1,2,3),keepdim=True,unbiased=False)
gn_result = (g_inputx - gn_mean)/(gn_std + 1e-5)
results.append(gn_result)
verify_gn_y = torch.cat(results,dim=1)
print(verify_gn_y.shape) # torch.Size([4, 6, 2, 2])
print(torch.allclose(gn_y,verify_gn_y)) # True
图示¶
6通道,分成3组,每组两通道
权重归一化¶
文字、数学、图示、代码
- 权重归一化与之前介绍的归一化不太一样
- 把权重进行解耦,幅度和方向解耦
- 看官网api的介绍
- 搜索:
torch.nn.utils.weight_norm
是对权重进行归一化
- 是一个函数,且是一个包裹的函数,包裹的对象是module
- 需要的输入
module
- 解释函数功能:
对一个参数(指的是权重参数、不包括偏置)
对参数进行 \(w = g \frac{v}{||v||}\) 变换,本来的参数是v,把v先除以 v的模,得到v的单位向量,也就是v的单位向量,再乘以新的幅度g,g是可学习的,得到新的权重w
假设一个linear层,权重为v,套上一个weight_norm函数以后,对它新增一个参数g,这样新的权重变成了w, w保留了v的方向,但是新增的一个幅度g
- 返回值,还是module
代码实现¶
# 5.实现weight_norm 并验证api
## 调用weight_norm 并验证api
linear = nn.Linear(embedding_dim,3,bias=False)
wh_linear = torch.nn.utils.weight_norm(linear)
wh_linear_output = wh_linear(inputx)
# print(wh_linear_output.shape) # torch.Size([2, 3, 3])
## 手写weight_norm
weight_direction = linear.weight/(linear.weight.norm(dim=1,keepdim=True))
weight_magnitude = wh_linear.weight_g
print(weight_direction.shape) # torch.Size([3, 4])
print(weight_magnitude.shape) # torch.Size([3, 1])
verify_wh_linear_output = inputx @ (weight_direction.transpose(-1,-2))*(weight_magnitude.transpose(-1,-2))
print("weight norm",torch.allclose(wh_linear_output,verify_wh_linear_output))
调用api实现weight norm
- 输入参数:module
① linear层的输入大小是embedding_dim=4、输出大小设为3
首先,需要的第一个参数是module,所以需要首先实例化module,以linear层举例
② 不考虑bias
linear = nn.Linear(embedding_dim,3,bias=False)
- 将实例化好的linear,传入权重归一化函数
torch.nn.utils.weight_norm
,得到权重归一化以后的linear
wh_linear = torch.nn.utils.weight_norm(linear)
- 将输入
inputx
传入到 权重归一化后的层,得到权重归一化api的结果
wh_linear_output = wh_linear(inputx)
手写weight norm
- 查看 权重归一化线性层 输出的形状:233
print(wh_linear_output.shape) # torch.Size([2, 3, 3])
解释为什么是233?
首先 输入是234,linear层是从4维映射到3维的,所以结果是233,也就是 经过权重归一化层的输出是 233
233是怎么来的?
首先 公式:\(w = g \frac{v}{||v||}\) ,先对权重 v 进行归一化,得到方向向量,然后乘以一个 可学习的幅度向量 g
所以在手动实现的时候,首先 得到 linear的权重
linear.weight
,接着 对linear的权重除以一个 范数
linear.weight
是一个二维张量,根据公式是 v除以v的模具体来说,
linear.weight/linear.weight.norm(dim=1,keepdim=True)
解释:除以模 并不是 除以整个矩阵的模,而是 跟每一个sample,内积相乘的向量的模,所以这里dimension取1,计算linear的时候 是 x乘以 w的转置,也就是x的每一行 与 w的转置的每一列相乘,与w转置的每一列相乘 相当于跟 w的每一行相乘,w的每一行就是dimension=1,(0维是batchsize),并且设置keepdim=True,并且将 变量名命名为
weight_direction
接下来 还需要幅度参数
weight_magnitude
,也就是 公式中的g,要保证跟api的g一样,在官网api中给出 weight_g表示幅度,weight_v表示方向查看 方向的形状和幅度的形状
Pythonprint(weight_direction.shape) # torch.Size([3, 4]) print(weight_magnitude.shape) # torch.Size([3, 1])
方向的形状是 3×4,幅度的形状是 3×1
接下来 按照 公式 \(w = g \frac{v}{||v||}\) 计算出新的 w
Pythonverify_wh_linear_output = inputx @ (weight_direction.transpose(-1,-2))*(weight_magnitude.transpose(-1,-2))
输入与权重相乘,然后乘以 方向向量,inputx.shape=234,需要对weight转置一下
总结¶
框架¶
batch size:per channel across minibatch,归一化的时候对每个通道单独进行归一化,第二个归一化是层归一化,对每个样本单独进行归一化,并且对每一个层单独进行归一化,可以理解为nlp中的时间,第三个是实例归一化,风格迁移中经常使用,做法是per sample、per channel,对每个样本的每个通道单独做,第四个是群归一化,对每个样本分组进行归一化,组是指对channel进行分组,第五个权重归一化,对权重进行归一化再scale
总结统计量维度:
batchnorm
- nlp:NLC→C
- cv:NCHW→C
LayerNorm
- nlp:NLC→NL(per sample、per layer 保留sample维度,保留layer维度)
- cv:NCHW→NHW?
实例归一化
- nlp:NLC→NC
- cv:NCHW→NC
groupnorm
- nlp:N,G,L,C//G → N,G
- cv:N,G,C//G,H,W→N,G
代码¶
nlp¶
import torch
import torch.nn as nn
batch_size = 2
times_steps = 3
embedding_dim = 4
inputx = torch.randn(batch_size,times_steps,embedding_dim) # N*L*C
# 1. 实现batch_norm并验证API
## 调用 batch_norm API
batch_norm_op = torch.nn.BatchNorm1d(embedding_dim,affine=False)
bn_y = batch_norm_op(inputx.transpose(-1,-2)).transpose(-1,-2)
## 手写batch_norm
bn_mean = inputx.mean(dim=(0,1),keepdim=True)
bn_std = inputx.std(dim=(0,1),unbiased=False,keepdim=True)
verify_bn_y = (inputx - bn_mean)/(bn_std+1e-5)
print("batch norm:",torch.allclose(bn_y,verify_bn_y))
# 2. 实现layer_norm 并验证api
## 调用 layer_norm API
layer_norm_op = torch.nn.LayerNorm(embedding_dim,elementwise_affine=False)
ln_y = layer_norm_op(inputx)
## 手写layer_norm
ln_mean = inputx.mean(dim=-1,keepdim=True)
ln_std = inputx.std(dim=-1,keepdim=True,unbiased=False)
verify_bn_y = (inputx - ln_mean)/(ln_std + 1e-05)
print("layer norm:",torch.allclose(ln_y,verify_bn_y))
# 3. 实现instance_norm并验证API
## 调用ins_norm并验证API
ins_norm_op = torch.nn.InstanceNorm1d(embedding_dim)
in_y = ins_norm_op(inputx.transpose(-1,-2)).transpose(-1,-2)
## 手写ins_norm
in_mean = inputx.mean(dim=1,keepdim=True)
in_std = inputx.std(dim=1,keepdim=True,unbiased=False)
verify_in_y = (inputx - in_mean)/(in_std+1e-5)
print("instance norm:",torch.allclose(in_y,verify_in_y))
# 4. 实现group_norm并验证API
## 调用group_norm并验证API
num_groups = 2
group_norm_op = torch.nn.GroupNorm(num_groups,embedding_dim,affine=False)
gn_y = group_norm_op(inputx.transpose(-1,-2)).transpose(-1,-2)
## 手写group_norm
group_inputxs = torch.split(inputx,split_size_or_sections=embedding_dim//num_groups,dim=-1)
results = []
for g_inputx in group_inputxs:
gn_mean = g_inputx.mean(dim=(1,2),keepdim=True)
# print(gn_mean.shape) # torch.Size([2, 1, 1])
gn_std = g_inputx.std(dim=(1,2),keepdim=True,unbiased=False)
gn_result = (g_inputx - gn_mean)/(gn_std + 1e-5)
results.append(gn_result)
verify_gn_y = torch.cat(results,dim=-1)
print("group norm:",torch.allclose(gn_y,verify_gn_y))
# 5.实现weight_norm 并验证api
## 调用weight_norm 并验证api
linear = nn.Linear(embedding_dim,3,bias=False)
wh_linear = torch.nn.utils.weight_norm(linear)
wh_linear_output = wh_linear(inputx)
# print(wh_linear_output.shape) # torch.Size([2, 3, 3])
## 手写weight_norm
weight_direction = linear.weight/(linear.weight.norm(dim=1,keepdim=True))
weight_magnitude = wh_linear.weight_g
print(weight_direction.shape) # torch.Size([3, 4])
print(weight_magnitude.shape) # torch.Size([3, 1])
verify_wh_linear_output = inputx @ (weight_direction.transpose(-1,-2))*(weight_magnitude.transpose(-1,-2))
print("weight norm",torch.allclose(wh_linear_output,verify_wh_linear_output))
cv¶
import torch
batch_size = 4
channels = 3
h,w = 2,2
inputx = torch.randn(batch_size,channels,h,w) # BCHW 只要维度是正确的,数字可以随便生成
# 1. 实现batch_norm并验证API
## 调用 batch_norm API
batch_norm_op = torch.nn.BatchNorm2d(channels,affine=False)
bn_y = batch_norm_op(inputx)
## 手写batch_norm
bn_mean = inputx.mean(dim=(0,2,3),keepdim=True)
bn_std = inputx.std(dim=(0,2,3),unbiased=False,keepdim=True)
verify_bn_y = (inputx - bn_mean)/(bn_std+1e-5)
print("batch norm:",torch.allclose(bn_y,verify_bn_y))
# 2. 实现layer_norm 并验证api
## 调用 layer_norm API
layer_norm_op = torch.nn.LayerNorm((channels,h,w),elementwise_affine=False)
ln_y = layer_norm_op(inputx)
## 手写layer_norm
ln_mean = inputx.mean(dim=(1,2,3),keepdim=True)
ln_std = inputx.std(dim=(1,2,3),keepdim=True,unbiased=False)
verify_bn_y = (inputx - ln_mean)/(ln_std + 1e-05)
print("layer norm:",torch.allclose(ln_y,verify_bn_y))
# 3. 实现instance_norm并验证API
## 调用ins_norm并验证API
ins_norm_op = torch.nn.InstanceNorm2d(channels)
in_y = ins_norm_op(inputx.transpose(-1,-2)).transpose(-1,-2)
# print(inputx.shape) torch.Size([4, 3, 2, 2])
## 手写ins_norm
in_mean = inputx.mean(dim=(2,3),keepdim=True)
# dim=(2,3) print(in_mean.shape) torch.Size([4, 3, 1, 1])
#dim=1 print(in_mean.shape) torch.Size([4, 1, 2, 2])
in_std = inputx.std(dim=(2,3),keepdim=True,unbiased=False)
verify_in_y = (inputx - in_mean)/(in_std+1e-5)
print("instance norm:",torch.allclose(in_y,verify_in_y))
# 4. 实现group_norm并验证API
## 调用group_norm并验证API
batch_size = 4
channels = 6
h,w = 2,2
inputx = torch.randn(batch_size,channels,h,w)
num_groups = 3
group_norm_op = torch.nn.GroupNorm(num_groups,channels,affine=False)
gn_y = group_norm_op(inputx)
# print(gn_y.shape) # torch.Size([4, 6, 2, 2])
## 手写group_norm
# BCHW
group_inputxs = torch.split(inputx,split_size_or_sections=channels//num_groups,dim=1)
results = []
for g_inputx in group_inputxs:
gn_mean = g_inputx.mean(dim=(1,2,3),keepdim=True)
# print(gn_mean.shape) # 3 个 torch.Size([4, 1, 1, 1])
gn_std = g_inputx.std(dim=(1,2,3),keepdim=True,unbiased=False)
gn_result = (g_inputx - gn_mean)/(gn_std + 1e-5)
results.append(gn_result)
verify_gn_y = torch.cat(results,dim=1)
# print(verify_gn_y.shape) # torch.Size([4, 6, 2, 2])
print("group norm:",torch.allclose(gn_y,verify_gn_y)) # True