28 批量歸一化【動手學(xué)深度學(xué)習(xí)v2】

批量歸一化層:一種新興的,在如今流行的網(wǎng)絡(luò)中都很常見的一種層。
在常見網(wǎng)絡(luò)中,數(shù)據(jù)在底層,損失函數(shù)在上層。在進(jìn)行反向傳播的過程中,梯度往往在上層會比較大,但到了底層就會變得很小。
所以在訓(xùn)練過程中,往往上層的參數(shù)會收斂的比較快,下層參數(shù)卻變化得很慢,但是一旦下層參數(shù)發(fā)生變化,整個網(wǎng)絡(luò)中的參數(shù)都得跟著變,導(dǎo)致網(wǎng)絡(luò)整體收斂變慢。
*一個腦洞:能不能控制網(wǎng)絡(luò)下層的學(xué)習(xí)率較大。上層的學(xué)習(xí)率較小?


對于一個可學(xué)習(xí)的特征,都有一個可學(xué)習(xí)的參數(shù)γ和β。
對于全連接層來說,就是一列一特征,一行一數(shù)據(jù),批量歸一化層的可訓(xùn)練參數(shù)數(shù)量=特征數(shù)=列數(shù)。
對于卷積層來說,訓(xùn)練參數(shù)數(shù)量=通道數(shù)。類比來說,卷積層處理的每一個像素都相當(dāng)于一個樣本,組成每個像素的通道數(shù)據(jù)相當(dāng)于這個像素的特征向量。
批量歸一化層需設(shè)置在一層輸出之后,激活函數(shù)之前(因為批量歸一化屬于線性變換,激活函數(shù)為非線性變換。激活函數(shù)會嚴(yán)重影響數(shù)據(jù)的平均值和方差值,比如會把所有負(fù)數(shù)數(shù)據(jù)變?yōu)檎龜?shù)。)

批量歸一化本質(zhì)?:通過隨機(jī)取樣獲取隨機(jī)的數(shù)據(jù)平均值與數(shù)據(jù)方差值來對數(shù)據(jù)增加噪音。

總結(jié):為了防止不同的小批量,在層數(shù)過多時,出現(xiàn)分布劇烈變化,導(dǎo)致收斂速度下降,所以要用BATCH norm。注意是不同的小批量

代碼實現(xiàn)
import torch from torch import nn from d2l import torch as d2l def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum): # 通過is_grad_enabled來判斷當(dāng)前模式是訓(xùn)練模式還是預(yù)測模式,gamma和beta為待訓(xùn)練的批量歸一化層參數(shù),moving_mean和moving_var是動態(tài)均值和標(biāo)準(zhǔn)差 if not torch.is_grad_enabled(): # 如果是在預(yù)測模式下,直接使用傳入的移動平均所得的均值和方差 X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps) else: assert len(X.shape) in (2, 4) # 如果是2,則為全連接層,4的話是卷積層 if len(X.shape) == 2: # 使用全連接層的情況,計算特征維上的均值和方差 mean = X.mean(dim=0) var = ((X - mean) ** 2).mean(dim=0) else: # 使用二維卷積層的情況,計算通道維上(axis=1)的均值和方差。 # 這里我們需要保持X的形狀以便后面可以做廣播運(yùn)算 mean = X.mean(dim=(0, 2, 3), keepdim=True) var = ((X - mean) ** 2).mean(dim=(0, 2, 3), keepdim=True) # 訓(xùn)練模式下,用當(dāng)前的均值和方差做標(biāo)準(zhǔn)化 X_hat = (X - mean) / torch.sqrt(var + eps) # 更新移動平均的均值和方差 moving_mean = momentum * moving_mean + (1.0 - momentum) * mean moving_var = momentum * moving_var + (1.0 - momentum) * var Y = gamma * X_hat + beta # 縮放和移位 return Y, moving_mean.data, moving_var.data
創(chuàng)建一個正確的BatchNorm
層。 這個層將保持適當(dāng)?shù)膮?shù):拉伸gamma
和偏移beta
,這兩個參數(shù)將在訓(xùn)練過程中更新。 此外,我們的層將保存均值和方差的移動平均值,以便在模型預(yù)測期間隨后使用。
class BatchNorm(nn.Module): # num_features:完全連接層的輸出數(shù)量或卷積層的輸出通道數(shù)。 # num_dims:2表示完全連接層,4表示卷積層 def __init__(self, num_features, num_dims): super().__init__() if num_dims == 2: shape = (1, num_features) else: shape = (1, num_features, 1, 1) # 參與求梯度和迭代的拉伸和偏移參數(shù),分別初始化成1和0 self.gamma = nn.Parameter(torch.ones(shape)) self.beta = nn.Parameter(torch.zeros(shape)) # 非模型參數(shù)的變量初始化為0和1 self.moving_mean = torch.zeros(shape) self.moving_var = torch.ones(shape) def forward(self, X): # 如果X不在內(nèi)存上,將moving_mean和moving_var # 復(fù)制到X所在顯存上 if self.moving_mean.device != X.device: self.moving_mean = self.moving_mean.to(X.device) self.moving_var = self.moving_var.to(X.device) # 保存更新過的moving_mean和moving_var Y, self.moving_mean, self.moving_var = batch_norm( X, self.gamma, self.beta, self.moving_mean, self.moving_var, eps=1e-5, momentum=0.9) return Y
批量規(guī)范化是在卷積層或全連接層之后、相應(yīng)的激活函數(shù)之前應(yīng)用的。
net = nn.Sequential( nn.Conv2d(1, 6, kernel_size=5), BatchNorm(6, num_dims=4), nn.Sigmoid(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Conv2d(6, 16, kernel_size=5), BatchNorm(16, num_dims=4), nn.Sigmoid(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(), nn.Linear(16*4*4, 120), BatchNorm(120, num_dims=2), nn.Sigmoid(), nn.Linear(120, 84), BatchNorm(84, num_dims=2), nn.Sigmoid(), nn.Linear(84, 10))
和以前一樣,在Fashion-MNIST數(shù)據(jù)集上訓(xùn)練網(wǎng)絡(luò)。 這個代碼與我們第一次訓(xùn)練LeNet(?6.6節(jié))時幾乎完全相同,主要區(qū)別在于學(xué)習(xí)率大得多。
lr, num_epochs, batch_size = 1.0, 10, 256 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
loss 0.267, train acc 0.900, test acc 0.860 40878.1 examples/sec on cuda:0
簡明實現(xiàn)
除了使用我們剛剛定義的BatchNorm
,我們也可以直接使用深度學(xué)習(xí)框架中定義的BatchNorm
。 該代碼看起來幾乎與我們上面的代碼相同。
net = nn.Sequential( nn.Conv2d(1, 6, kernel_size=5), nn.BatchNorm2d(6), nn.Sigmoid(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Conv2d(6, 16, kernel_size=5), nn.BatchNorm2d(16), nn.Sigmoid(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(), nn.Linear(256, 120), nn.BatchNorm1d(120), nn.Sigmoid(), nn.Linear(120, 84), nn.BatchNorm1d(84), nn.Sigmoid(), nn.Linear(84, 10))
下面,我們使用相同超參數(shù)來訓(xùn)練模型。 請注意,通常高級API變體運(yùn)行速度快得多,因為它的代碼已編譯為C++或CUDA,而我們的自定義代碼由Python實現(xiàn)。
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
loss 0.263, train acc 0.902, test acc 0.862 71480.6 examples/sec on cuda:0
知識補(bǔ)充:
·前面課程中講過的Xavier,是對數(shù)據(jù)初始化進(jìn)行標(biāo)準(zhǔn)化處理,而BN是在訓(xùn)練過程中對每個層之間的數(shù)據(jù)進(jìn)行標(biāo)準(zhǔn)化處理。本質(zhì)上二者沒有區(qū)別。
·BN只有在較深的深度學(xué)習(xí)網(wǎng)絡(luò)中才有較好的效果(較淺網(wǎng)絡(luò)不會出現(xiàn)頂層參數(shù)隨底層參數(shù)微變而不斷變動的情況)
·有一種解釋,認(rèn)為BN之后地形圖更加平坦,容易收斂。此外使用BN后可以搭配更大的學(xué)習(xí)率,加快學(xué)習(xí)速度。
·除了Batch Normalization,還有在不同維度上使用Normalization的方法