Skip to content

5种归一化方法

约 3785 个字 464 行代码 31 张图片 预计阅读时间 25 分钟

45、五种归一化的原理与PyTorch逐行手写实现讲解(BatchNorm/LayerNorm/InsNorm/GroupNorm/WeightNorm)

图源

image-20241204091638649

image-20241125213344927

BatchNorm

图示

image-20241202085427932

image-20241202085500136

批归一化、通道级别的归一化

官网api,BatchNorm1D & 2D

image-20241125213959648

BatchNorm1D的输入:NCL,用于NLP

image-20241125214107222

BatchNorm2D的输入是四维的,用于图像

image-20241125214207707

一个是三维tensor作为输入

一个是四维tensor作为输入

BatchNorm1D

image-20241125215123844

  • 首先,位于torch.nn模块下,是一个class,所以要用的话,需要实例化
  • 接下来,看实例化需要接收的参数:
  • num features:输入张量的特征维度,或者通道的数目,或者embedding的大小
  • eps:5种归一化都需要的eps,分母数值稳定性,让分母加上一个微小的量,使得除法能够正常进行,默认1e-05
  • momentum:动量
    • 批归一化在计算均值和方差的时候,momentum通常需要跟track_running_sate联合起来理解,也就是说我们的统计量 通常是通过滑动平均计算出来了,而不是单一时刻的mini batch,是一个累计的过程,为了提高估计的准确度
  • affine:
    • 也就是 gamma & beta,也就是再做完归一化以后,也可以加一个映射,将其映射到一个新的分布上,做一个rescale和recenter

官网定义:

image-20241125220039574

(解释官网定义)均值和标准差是经过整个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)

Python
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))

输出:

Python
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

image-20241128165050327

层归一化的概括:per sample、per Layer,对单一样本、每一个层 单独计算,不需要考虑minibatch

LayerNorm最典型的使用场景:NLP

把网络中 每一次 每一个时刻 当成一层

每个时刻 embedding dim计算均值和方差

  • 为什么nlp中使用LN?

应为nlp中,不同句子长度是不一样的,也就是说 对于每个batch中的 或者 句子中 词数是不一样的;或者测试时,句子的长度可能训练时也没见过,而BatchNorm是across batch的,所以最好保证batch内部L是固定的

一句话:句子中词的数量并不一样

图示

image-20241202085600317

image-20241202085611761

语言描述实际意义

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

image-20241128171627848

LayerNorm只有一个api

同样也是一个class,如果要去实例化的话,只需要指定一下 被归一化的形状,以及是否需要进行缩放变换

也就是说接收的输入:

  • normalized_shape:被归一化的形状
  • elementwise_affine:是否需要进行缩放变换

接下来看定义:

image-20241128172336820

  • 这里的均值和方差是在最后一个维度计算的(over the last dimension)对于 over across minibatch
  • D就是 我们要传入的 normalized shape
  • 在nlp中 通常只需要传入标量就好了,就是计算最后的embedding的维度

代码实现

Python
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

Python
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

Python
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 句子

Python
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 图片

Python
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

image-20241128204149052

实例归一化,通常用在 风格迁移上

per sample、per channel

这时计算均值和标准差的时候,是对每一个样本的、每一个维度

官网api

image-20241128204455692

  • 是一个class
  • num_features:要实现一个Instance Norm的话,需要传入特征维度或者说 通道维度(model dim 可以理解为 channel),因为 我们要逐样本,逐通道的进行归一化
  • affine:也可以进行仿射变换,但大部分情况下,是设置为false的,一般是直接归一化即可

INSTANCE1D

代码实现需要注意,接收的输入数据格式是什么样的,输出的数据格式又是什么样的,看官网api

image-20241128204939894

NCL

for nlp:嵌入维度放到中间、词数滞后

for cv:INSTANCE2D :bchw 不变

就很类似BatchNorm

所以会有两次转置

图示

for NLP

image-20241128214343529

for CV

image-20241128220642978

代码实现

For nlp InstanceNorm1d

Python
# 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

Python
# 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

分组归一化、群归一化

image-20241129144247851

per sample、per group

这个跟LayerNorm是最像的

需要将channel划分成group,类似分组卷积、把输入通道划分成不同的group

首先对input tensor划分成不同的group,然后对于每个样本,每个group计算归一化即可

这里仍然是跟batch size无关的,batch Norm是跟batch size有关的

官方api

image-20241129144619652

(1)是一个class

(2)实现群归一化的话,需要的传入参数:

  • num_groups:group的数目,举个例子,如果channel=4,划分成2个group,那这里我们就可以传入2
  • num_channels:例子中 num_channels=4
  • affine:一般默认=false

NLP&GN

代码实现

  • 实例化class
  • 输入的参数,以及输入的形状

image-20241129145154438

输入通道数 需要放在中间的维度,调用groupNorm需要类似BatchNorm,做一个转置,也是跟instanceNorm是类似的

Python
# 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函数怎么用,需要传入什么参数

image-20241129145947587

需要传入的参数:

image-20241129150049199

  • tensor:切谁
  • split_size_or_sections:切成多大的
  • dim:切哪个维度
Python
group_inputx = torch.split(inputx,split_size_or_sections=embedding_dim//num_groups,dim=-1)

分组,分组过后在每个组计算

per smaple、per group

Python
# 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))

图示

image-20241129152630571

CV&GN

写代码时需要注意:

ValueError: num_channels must be divisible by num_groups

代码实现

Python
# 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组,每组两通道

image-20241129155651305

权重归一化

文字、数学、图示、代码

image-20241202091955074

  • 权重归一化与之前介绍的归一化不太一样
  • 把权重进行解耦,幅度和方向解耦
  • 看官网api的介绍
  • 搜索:torch.nn.utils.weight_norm 是对权重进行归一化

image-20241202092226745

  • 是一个函数,且是一个包裹的函数,包裹的对象是module
  • 需要的输入
  1. module
  2. 解释函数功能:

对一个参数(指的是权重参数、不包括偏置)

对参数进行 \(w = g \frac{v}{||v||}\) 变换,本来的参数是v,把v先除以 v的模,得到v的单位向量,也就是v的单位向量,再乘以新的幅度g,g是可学习的,得到新的权重w

假设一个linear层,权重为v,套上一个weight_norm函数以后,对它新增一个参数g,这样新的权重变成了w, w保留了v的方向,但是新增的一个幅度g

  • 返回值,还是module

image-20241202092502877

代码实现

Python
# 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

Python
weight_direction = linear.weight/(linear.weight.norm(dim=1,keepdim=True))

接下来 还需要幅度参数 weight_magnitude,也就是 公式中的g,要保证跟api的g一样,在官网api中给出 weight_g表示幅度,weight_v表示方向

image-20241202100004175

Text Only
weight_magnitude = wh_linear.weight_g

查看 方向的形状和幅度的形状

Python
print(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

Python
verify_wh_linear_output = inputx @ (weight_direction.transpose(-1,-2))*(weight_magnitude.transpose(-1,-2))

输入与权重相乘,然后乘以 方向向量,inputx.shape=234,需要对weight转置一下

总结

框架

image-20241202092023837

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

Python
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

Python
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
add_circle2024-11-25 21:24:58update2024-12-04 09:17:02