(pytorch-深度学习)批量归一化
批量歸一化
批量歸一化(batch normalization)層能讓較深的神經網絡的訓練變得更加容易
通常來說,數據標準化預處理對于淺層模型就足夠有效了。隨著模型訓練的進行,當每層中參數更新時,靠近輸出層的輸出較難出現劇烈變化。
但對深層神經網絡來說,即使輸入數據已做標準化,訓練中模型參數的更新依然很容易造成靠近輸出層輸出的劇烈變化。這種計算數值的不穩定性通常令我們難以訓練出有效的深度模型。
批量歸一化的提出正是為了應對深度模型訓練的挑戰。
在模型訓練時,批量歸一化利用小批量上的均值和標準差,不斷調整神經網絡中間輸出,從而使整個神經網絡在各層的中間輸出的數值更穩定。
批量歸一化層
對全連接層和卷積層做批量歸一化的方法稍有不同
1. 對全連接層做批量歸一化
通常將批量歸一化層置于全連接層中的仿射變換和激活函數之間。設全連接層的輸入為u\boldsymbol{u}u,權重參數和偏差參數分別為W\boldsymbol{W}W和b\boldsymbol{b}b,激活函數為?\phi?。設批量歸一化的運算符為BN\text{BN}BN。那么,使用批量歸一化的全連接層的輸出為
?(BN(x)),\phi(\text{BN}(\boldsymbol{x})),?(BN(x)),
其中批量歸一化輸入x\boldsymbol{x}x由仿射變換
x=Wu+b\boldsymbol{x} = \boldsymbol{W\boldsymbol{u} + \boldsymbol{b}}x=Wu+b
得到。
考慮一個由mmm個樣本組成的小批量,仿射變換的輸出為一個新的小批量
B=x(1),…,x(m)\mathcal{B} = {\boldsymbol{x}^{(1)}, \ldots, \boldsymbol{x}^{(m)} }B=x(1),…,x(m)
它們正是批量歸一化層的輸入。對于小批量B\mathcal{B}B中任意樣本x(i)∈Rd,1≤i≤m\boldsymbol{x}^{(i)} \in \mathbb{R}^d, 1 \leq i \leq mx(i)∈Rd,1≤i≤m,批量歸一化層的輸出同樣是ddd維向量
y(i)=BN(x(i)),\boldsymbol{y}^{(i)} = \text{BN}(\boldsymbol{x}^{(i)}),y(i)=BN(x(i)),
首先,對小批量B\mathcal{B}B求均值和方差:
μB←1m∑i=1mx(i),\boldsymbol{\mu}_\mathcal{B} \leftarrow \frac{1}{m}\sum{i = 1}^{m} \boldsymbol{x}^{(i)},μB?←m1?∑i=1mx(i), σB2←1m∑i=1m(x(i)?μB)2,\boldsymbol{\sigma}_\mathcal{B}^2 \leftarrow \frac{1}{m} \sum{i=1}^{m}(\boldsymbol{x}^{(i)} - \boldsymbol{\mu}_\mathcal{B})^2,σB2?←m1?∑i=1m(x(i)?μB?)2,
其中的平方計算是按元素求平方。
接下來,使用按元素開方和按元素除法對x(i)\boldsymbol{x}^{(i)}x(i)標準化:
x^(i)←x(i)?μBσB2+?,\hat{\boldsymbol{x}}^{(i)} \leftarrow \frac{\boldsymbol{x}^{(i)} - \boldsymbol{\mu}_\mathcal{B}}{\sqrt{\boldsymbol{\sigma}_\mathcal{B}^2 + \epsilon}},x^(i)←σB2?+??x(i)?μB??,
這里?>0\epsilon > 0?>0是一個很小的常數,保證分母大于0。
在上面標準化的基礎上,批量歸一化層引入了兩個可以學習的模型參數:
- 拉伸(scale)參數 γ\boldsymbol{\gamma}γ
- 偏移(shift)參數 β\boldsymbol{\beta}β。
這兩個參數和x(i)\boldsymbol{x}^{(i)}x(i)形狀相同,皆為ddd維向量。它們與x(i)\boldsymbol{x}^{(i)}x(i)分別做按元素乘法(符號⊙\odot⊙)和加法計算:
y(i)←γ⊙x^(i)+β{\boldsymbol{y}}^{(i)} \leftarrow \boldsymbol{\gamma} \odot \hat{\boldsymbol{x}}^{(i)} + \boldsymbol{\beta}y(i)←γ⊙x^(i)+β
至此,我們得到了x(i)\boldsymbol{x}^{(i)}x(i)的批量歸一化的輸出y(i)\boldsymbol{y}^{(i)}y(i)。
值得注意的是,可學習的拉伸和偏移參數保留了不對x^(i)\hat{\boldsymbol{x}}^{(i)}x^(i)做批量歸一化的可能:
- 此時只需學出γ=σB2+?\boldsymbol{\gamma} = \sqrt{\boldsymbol{\sigma}_\mathcal{B}^2 + \epsilon}γ=σB2?+??和β=μB\boldsymbol{\beta} = \boldsymbol{\mu}_\mathcal{B}β=μB?。
- 我們可以對此這樣理解:如果批量歸一化無益,理論上,學出的模型可以不使用批量歸一化。
2. 對卷積層做批量歸一化
對卷積層來說,批量歸一化發生在卷積計算之后、應用激活函數之前。
- 如果卷積計算輸出多個通道,我們需要對這些通道的輸出分別做批量歸一化,且每個通道都擁有獨立的拉伸和偏移參數,并均為標量。
設小批量中有mmm個樣本。在單個通道上,假設卷積計算輸出的高和寬分別為ppp和qqq。我們需要對該通道中m×p×qm \times p \times qm×p×q個元素同時做批量歸一化。對這些元素做標準化計算時,我們使用相同的均值和方差,即該通道中m×p×qm \times p \times qm×p×q個元素的均值和方差。
3. 預測時的批量歸一化
- 使用批量歸一化訓練時,我們可以將批量大小設得大一點,從而使批量內樣本的均值和方差的計算都較為準確。
- 將訓練好的模型用于預測時,我們希望模型對于任意輸入都有確定的輸出。因此,單個樣本的輸出不應取決于批量歸一化所需要的隨機小批量中的均值和方差。
- 一種常用的方法是通過移動平均估算整個訓練數據集的樣本均值和方差,并在預測時使用它們得到確定的輸出。
- 可見,和丟棄層一樣,批量歸一化層在訓練模式和預測模式下的計算結果也是不一樣的。
實現批量歸一化層
import time import torch from torch import nn, optim import torch.nn.functional as Fdevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')def batch_norm(is_training, X, gamma, beta, moving_mean, moving_var, eps, momentum):# 判斷當前模式是訓練模式還是預測模式if not is_training:# 如果是在預測模式下,直接使用傳入的移動平均所得的均值和方差X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)else:assert len(X.shape) in (2, 4)if len(X.shape) == 2:# 使用全連接層的情況,計算特征維上的均值和方差mean = X.mean(dim=0)var = ((X - mean) ** 2).mean(dim=0)else:# 使用二維卷積層的情況,計算通道維上(axis=1)的均值和方差。這里我們需要保持# X的形狀以便后面可以做廣播運算mean = X.mean(dim=0, keepdim=True).mean(dim=2, keepdim=True).mean(dim=3, keepdim=True)var = ((X - mean) ** 2).mean(dim=0, keepdim=True).mean(dim=2, keepdim=True).mean(dim=3, keepdim=True)# 訓練模式下用當前的均值和方差做標準化X_hat = (X - mean) / torch.sqrt(var + eps)# 更新移動平均的均值和方差moving_mean = momentum * moving_mean + (1.0 - momentum) * meanmoving_var = momentum * moving_var + (1.0 - momentum) * varY = gamma * X_hat + beta # 拉伸和偏移return Y, moving_mean, moving_var自定義一個BatchNorm層。它保存參與求梯度和迭代的拉伸參數gamma和偏移參數beta,同時也維護移動平均得到的均值和方差,以便能夠在模型預測時被使用。
BatchNorm實例所需指定的num_features參數對于全連接層來說應為輸出個數,對于卷積層來說則為輸出通道數。該實例所需指定的num_dims參數對于全連接層和卷積層來說分別為2和4。
class BatchNorm(nn.Module):def __init__(self, num_features, num_dims):super(BatchNorm, self).__init__()if num_dims == 2:shape = (1, num_features)else:shape = (1, num_features, 1, 1)# 參與求梯度和迭代的拉伸和偏移參數,分別初始化成0和1self.gamma = nn.Parameter(torch.ones(shape))self.beta = nn.Parameter(torch.zeros(shape))# 不參與求梯度和迭代的變量,全在內存上初始化成0self.moving_mean = torch.zeros(shape)self.moving_var = torch.zeros(shape)def forward(self, X):# 如果X不在內存上,將moving_mean和moving_var復制到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, Module實例的traning屬性默認為true, 調用.eval()后設成falseY, self.moving_mean, self.moving_var = batch_norm(self.training, X, self.gamma, self.beta, self.moving_mean,self.moving_var, eps=1e-5, momentum=0.9)return Y使用批量歸一化層的LeNet
修改5.5節(卷積神經網絡(LeNet))介紹的LeNet模型,從而應用批量歸一化層。我們在所有的卷積層或全連接層之后、激活層之前加入批量歸一化層。
class FlattenLayer(torch.nn.Module):def __init__(self):super(FlattenLayer, self).__init__()def forward(self, x): # x shape: (batch, *, *, ...)return x.view(x.shape[0], -1)def load_data_fashion_mnist(batch_size, resize=None, root='~/Datasets/FashionMNIST'):"""Download the fashion mnist dataset and then load into memory."""trans = []if resize:trans.append(torchvision.transforms.Resize(size=resize))trans.append(torchvision.transforms.ToTensor())transform = torchvision.transforms.Compose(trans)mnist_train = torchvision.datasets.FashionMNIST(root=root, train=True, download=True, transform=transform)mnist_test = torchvision.datasets.FashionMNIST(root=root, train=False, download=True, transform=transform)if sys.platform.startswith('win'):num_workers = 0 # 0表示不用額外的進程來加速讀取數據else:num_workers = 4train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=num_workers)test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=num_workers)return train_iter, test_iterdef train(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs):net = net.to(device)print("training on ", device)loss = torch.nn.CrossEntropyLoss()for epoch in range(num_epochs):train_l_sum, train_acc_sum, n, batch_count, start = 0.0, 0.0, 0, 0, time.time()for X, y in train_iter:X = X.to(device)y = y.to(device)y_hat = net(X)l = loss(y_hat, y)optimizer.zero_grad()l.backward()optimizer.step()train_l_sum += l.cpu().item()train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item()n += y.shape[0]batch_count += 1test_acc = evaluate_accuracy(test_iter, net)print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f, time %.1f sec'% (epoch + 1, train_l_sum / batch_count, train_acc_sum / n, test_acc, time.time() - start)) net = nn.Sequential(nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_sizeBatchNorm(6, num_dims=4),nn.Sigmoid(),nn.MaxPool2d(2, 2), # kernel_size, stridenn.Conv2d(6, 16, 5),BatchNorm(16, num_dims=4),nn.Sigmoid(),nn.MaxPool2d(2, 2),FlattenLayer(),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))訓練修改后的模型:
batch_size = 256 train_iter, test_iter = load_data_fashion_mnist(batch_size=batch_size)lr, num_epochs = 0.001, 5 optimizer = torch.optim.Adam(net.parameters(), lr=lr) train(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)與自己定義的BatchNorm類相比,Pytorch中nn模塊定義的BatchNorm1d和BatchNorm2d類使用起來更加簡單,二者分別用于全連接層和卷積層,都需要指定輸入的num_features參數值。
用PyTorch實現使用批量歸一化的LeNet:
net = nn.Sequential(nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_sizenn.BatchNorm2d(6),nn.Sigmoid(),nn.MaxPool2d(2, 2), # kernel_size, stridenn.Conv2d(6, 16, 5),nn.BatchNorm2d(16),nn.Sigmoid(),nn.MaxPool2d(2, 2),FlattenLayer(),nn.Linear(16*4*4, 120),nn.BatchNorm1d(120),nn.Sigmoid(),nn.Linear(120, 84),nn.BatchNorm1d(84),nn.Sigmoid(),nn.Linear(84, 10))總結
以上是生活随笔為你收集整理的(pytorch-深度学习)批量归一化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 就业信息网进行服务器维护,服务器安全武汉
- 下一篇: mysql php宝塔 root_[转载