torch.nn ------ 標准化歸一化層
作者:elfin 參考資料來源:torch.nn.BatchNorm
- BN是所有樣本關於某個信道的標准化
- LN是關於某個樣本的所有特征標准化
- IN是關於某個樣本在某個信道上標准化
- GN是某個樣本關於信道分組的標准化
一、BatchNorm1d
對 2D 或 3D 輸入(具有可選附加通道維度的小批量 1D 輸入)應用批量歸一化。歸一化的公式為:
這里的\(x\)是張量的一個元素,\(\gamma\)和\(\beta\)分別是標准化處理后的數據放縮、偏置(平移)參數,這兩個參數需要學習,為什么不直接使用\(\gamma=1,\beta=0\)呢?因為這里數據的分布本身是有差異性的,我們通過標准化強行將數據放縮到0均值,使其接近標准正態分布,同一個偽逆過程讓模型自學習其均值方差,以獲取更好的性能,如果強制給定處理后的均值方差,理論上也是可以的,但是這里的人為信息偏置引入是很強的,但又沒有一個好的科學解釋,實驗表明讓模型學習是一個很好的選擇!
BN層雖然原理是一樣的,但是有1d、2d、3d的區別,這里我們先看1d的標准化(歸一化)處理。
注:我習慣稱標准化,但大部分人都稱歸一化,雖然名稱有本質區別,但是在這里我們不做區分
參數:
- num_features: 輸入大小\(C\),處理的數據維度為\(\left(N,C,L\right)\)或為\(\left(N,C\right)\)
- eps: 給分母添加的數,防止分母為0,默認1e-5
- momentum: 移動平均算法(EMA)的動量參數,默認0.1,可以設置為None
- affine: 布爾值,設置是否可學習的仿射參數
- track_runing_stats: 是否跟蹤數據的均值方差信息,默認為True。如果不記錄,推理時我們減均值除標准差就不能處理,只能基於數據的批量均值與方差進行運算,但是此時數據往往樣本量只有1
標准化層BN是不改變輸入數據的shape的!
- 輸入:\(\left(N,C,L\right)\)或為\(\left(N,C\right)\)
- 輸出:\(\left(N,C,L\right)\)或為\(\left(N,C\right)\)
案例:
In[1]: elfin = nn.BatchNorm1d(2, affine=False)
In[2]: data = torch.tensor([[1,2], [3,4], [5,6], [7,8], [9,10]], dtype=torch.float32)
In[3]: elfin(data)
Out[3]:
tensor([[-1.4142, -1.4142],
[-0.7071, -0.7071],
[ 0.0000, 0.0000],
[ 0.7071, 0.7071],
[ 1.4142, 1.4142]])
In[4]: list(elfin.parameters())
Out[4]: []
In[5]: elfin = nn.BatchNorm1d(2)
In[6]: list(elfin.parameters())
Out[6]:
[Parameter containing:
tensor([1., 1.], requires_grad=True),
Parameter containing:
tensor([0., 0.], requires_grad=True)]
In[7]: data = torch.tensor([[[1,2], [3,4]], [[7,8], [9,10]]], dtype=torch.float32)
In[8]: data
Out[8]:
tensor([[[ 1., 2.],
[ 3., 4.]],
[[ 7., 8.],
[ 9., 10.]]])
In[9]: elfin = nn.BatchNorm1d(2, affine=False)
In[10]: elfin(data)
Out[10]:
tensor([[[-1.1508, -0.8220],
[-1.1508, -0.8220]],
[[ 0.8220, 1.1508],
[ 0.8220, 1.1508]]])
對於\(\left(N,C\right)\)我們很容易從上面的案例看出來它是在\(N\)個元素中進行標准化的。對於\(\left(N,C,L\right)\)是不是在\(N \times L\)個元素上進行標准化呢?
In[11]: data[:,0,:]
Out[11]:
tensor([[1., 2.],
[7., 8.]])
# 明顯標准化處理后的結果為:-1.1508,-0.8220, 0.8220, 1.1508
# 注:均值4.5,標准差3.0413812651491097
經過證明猜想是對的!
二、BatchNorm2d
對4D數據進行標准化(歸一化),輸入數據形式為\((N,C,H,W)\),輸出也是\((N,C,H,W)\)。計算公式見BatchNorm1d。
參數說明:
- num_features: 輸入大小\(C\),處理的數據維度為\(\left(N,C,H,W\right)\)
- eps: 給分母添加的數,防止分母為0,默認1e-5
- momentum: 移動平均算法(EMA)的動量參數,默認0.1,可以設置為None
- affine: 布爾值,設置是否可學習的仿射參數
- track_runing_stats: 是否跟蹤數據的均值方差信息,默認為True。如果不記錄,推理時我們減均值除標准差就不能處理,只能基於數據的批量均值與方差進行運算,但是此時數據往往樣本量只有1
- device: 設備
- dtype: 數據類型
動量參數說明
這里實際上是進行移動平均(加權求期望)。
案例:
>>> m = nn.BatchNorm2d(2)
>>> data = torch.randn(2,2,3,2)
>>> data[:,0,:,:]
tensor([[[ 0.2732, 0.7230],
[ 0.1640, -0.7623],
[-1.5510, -2.1041]],
[[-0.3899, 0.7928],
[-1.2680, 0.1955],
[ 1.4165, -1.0685]]])
>>> m(data)[:,0,:,:]
tensor([[[ 0.5589, 0.9988],
[ 0.4520, -0.4539],
[-1.2252, -1.7661]],
[[-0.0897, 1.0670],
[-0.9484, 0.4829],
[ 1.6770, -0.7533]]], grad_fn=<SliceBackward>)
>>> list(m.parameters())
[Parameter containing:
tensor([1., 1.], requires_grad=True),
Parameter containing:
tensor([0., 0.], requires_grad=True)]
均值:torch.std_mean(data[:,0,:,:])=(tensor(1.0680), tensor(-0.2982));如果根據計算的均值方差計算BN后的數據會有較大差異,這里的原因主要是方差、標准差的計算方式,統計學上我們是除以\(n-1\)(std_mean是使用torch.sqrt(torch.sum((data[:,0,:,:]+0.2982)**2) / 11)=1.0680),BN是除以\(n\),它是使用(torch.sqrt(torch.sum((data[:,0,:,:]+0.2982)**2) / 12)=1.0225),使用1.0225去計算就能得到m(data)的計算結果!
經過實驗和1d的接口類似,我們只在通道維度上選擇某一個時刻,取出的數據進行標准化操作!
三、BatchNorm3d
和BatchNorm2d類似,參考BatchNorm2d。
四、LazyBatchNorm1d~3d
延遲初始化weight、bias、running_mean、running_var參數。不需要指定通道維度,可以自己根據通道維數進行設定num_features參數!
五、GroupNorm
分組批量標准化,對每個在通道維度上分組進行標准化,BatchNorm默認是在所有通道上,相當於每個通道都是一組!
參數說明:
- num_group: 通道維度分組數量
- num_channels: 通道的維度
- eps: 給分母添加的數,防止分母為0,默認1e-5
- affine: 布爾值,設置是否可學習的仿射參數
案例:
>>> m = nn.GroupNorm(2,4)
>>> data = torch.randn(2,4,2,2)
# 驗證是否在所有樣本上分組
>>> torch.mean(data[:, :2, :, :])
tensor(-0.3137)
>>> torch.sqrt(torch.sum((data[:, :2,:,:]+0.3137)**2) / 16)
tensor(0.8521)
>>> data[0, 0, 0, 0]
tensor(-0.6422)
>>> m(data)[0,0,0,0]
tensor(-0.0309, grad_fn=<SelectBackward>)
>>> (-0.6422+0.3137) / 0.8521
-0.3855181316746861
# 驗證是否BN模式,在每個信道上進行標准化(因為參數對數量與信道數量一樣)
>>> torch.mean(data[:, 0, :, :])
tensor(-0.2070)
>>> torch.sqrt(torch.sum((data[:, 0,:,:]+0.2070)**2) / 8)
tensor(0.6600)
>>> (data[0,0,0,0]+0.207) / 0.66
tensor(-0.6595)
# 驗證是在一個樣本某組信道上進行標准化
>>> torch.mean(data[0, :2, :, :])
tensor(-0.6166)
>>> torch.sqrt(torch.sum((data[0, :2,:,:]+0.6166)**2) / 8)
tensor(0.8293)
>>> (data[0,0,0,0]+0.6166) / 0.8293
tensor(-0.0309)
根據案例我們知道GN是在某個樣本的一組信道上進行所有特征的標准化!
GN標准化是何凱明團隊(2018)提出的BN替代品,它解決了在小批量時的BN不穩定問題,所以在小批量時,LN也能有較好的錯誤率!
六、SyncBatchNorm並行標准化
SyncBatchNorm只會發生在訓練時,推理時我們並不會使用SyncBatchNorm!所以我們在些模型的時候可以直接寫BN,並行運算時,可以進行轉換torch.nn.SyncBatchNorm.convert_sync_batchnorm()
,經過這個接口的轉換后再交給DDP進行封裝!
# With Learnable Parameters
m = nn.SyncBatchNorm(100)
# creating process group (optional)
# ranks is a list of int identifying rank ids.
ranks = list(range(8))
r1, r2 = ranks[:4], ranks[4:]
# Note: every rank calls into new_group for every
# process group created, even if that rank is not
# part of the group.
process_groups = [torch.distributed.new_group(pids) for pids in [r1, r2]]
process_group = process_groups[0 if dist.get_rank() <= 3 else 1]
# Without Learnable Parameters
m = nn.BatchNorm3d(100, affine=False, process_group=process_group)
input = torch.randn(20, 100, 35, 45, 10)
output = m(input)
# network is nn.BatchNorm layer
sync_bn_network = nn.SyncBatchNorm.convert_sync_batchnorm(network, process_group)
# only single gpu per process is currently supported
ddp_sync_bn_network = torch.nn.parallel.DistributedDataParallel(
sync_bn_network,
device_ids=[args.local_rank],
output_device=args.local_rank)
參數說明
- process_group: 進程分組,默認是將所有進程處理的數據聚合進行BN操作,設置這個參數之后就會變成在組內進行操作BN
- 其他參數見BatchNorm2d
關於distributed設置進程間通信和torch.multiprocessing創建進程完美會在后面單獨講解!這里只需要淺顯的理解即可!
七、InstanceNorm?d與LazyInstanceNorm?d
InstanceNorm是在某個樣本的某個信道上進行標准化,而LazyInstanceNorm與LazyBN是一樣的道理!
八、LayerNorm
NLP最常用的標准化層LayerNorm是將一個樣本所有的特征進行標准化
normalized_shape參數指定的是要進行標准化的維度,和BN的參數剛好相反,如數據維度為\((B,C,N)\),normalized_shape=[C,N],則是每一個樣本所有特征進行標准化,如果是normalized_shape=N,則只在最后一個維度上進行標准化,這里normalized_shape必須指定最后幾個維度不能是中間維度(不包含最后一個維度的情況)!對於圖像數據可以參考案例。
官方案例
# NLP Example
batch, sentence_length, embedding_dim = 20, 5, 10
embedding = torch.randn(batch, sentence_length, embedding_dim)
layer_norm = nn.LayerNorm(embedding_dim)
# Activate module
layer_norm(embedding)
# Image Example
N, C, H, W = 20, 5, 10, 10
input = torch.randn(N, C, H, W)
# Normalize over the last three dimensions (i.e. the channel and spatial dimensions)
# as shown in the image below
layer_norm = nn.LayerNorm([C, H, W])
output = layer_norm(input)
九、局部標准化LRN
LRN局部響應標准化,有兩種LRN實現,一種是在通道上進行某個窗口大小為size的鄰域內進行標准化,另一種是在某個通道對應的特征圖上大小為size的鄰域內進行標准化!
具體參考文章:https://blog.csdn.net/weixin_46221946/article/details/122729460
完!