溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

pytorch如何添加BN

發(fā)布時(shí)間:2021-05-28 11:03:03 來源:億速云 閱讀:210 作者:小新 欄目:開發(fā)技術(shù)

這篇文章主要介紹pytorch如何添加BN,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!

pytorch之添加BN層

批標(biāo)準(zhǔn)化

模型訓(xùn)練并不容易,特別是一些非常復(fù)雜的模型,并不能非常好的訓(xùn)練得到收斂的結(jié)果,所以對數(shù)據(jù)增加一些預(yù)處理,同時(shí)使用批標(biāo)準(zhǔn)化能夠得到非常好的收斂結(jié)果,這也是卷積網(wǎng)絡(luò)能夠訓(xùn)練到非常深的層的一個重要原因。

數(shù)據(jù)預(yù)處理

目前數(shù)據(jù)預(yù)處理最常見的方法就是中心化和標(biāo)準(zhǔn)化,中心化相當(dāng)于修正數(shù)據(jù)的中心位置,實(shí)現(xiàn)方法非常簡單,就是在每個特征維度上減去對應(yīng)的均值,最后得到 0 均值的特征。標(biāo)準(zhǔn)化也非常簡單,在數(shù)據(jù)變成 0 均值之后,為了使得不同的特征維度有著相同的規(guī)模,可以除以標(biāo)準(zhǔn)差近似為一個標(biāo)準(zhǔn)正態(tài)分布,也可以依據(jù)最大值和最小值將其轉(zhuǎn)化為 -1 ~ 1之間,這兩種方法非常的常見,如果你還記得,前面我們在神經(jīng)網(wǎng)絡(luò)的部分就已經(jīng)使用了這個方法實(shí)現(xiàn)了數(shù)據(jù)標(biāo)準(zhǔn)化,至于另外一些方法,比如 PCA 或者 白噪聲已經(jīng)用得非常少了。

Batch Normalization

前面在數(shù)據(jù)預(yù)處理的時(shí)候,盡量輸入特征不相關(guān)且滿足一個標(biāo)準(zhǔn)的正態(tài)分布,

這樣模型的表現(xiàn)一般也較好。但是對于很深的網(wǎng)路結(jié)構(gòu),網(wǎng)路的非線性層會使得輸出的結(jié)果變得相關(guān),且不再滿足一個標(biāo)準(zhǔn)的 N(0, 1) 的分布,甚至輸出的中心已經(jīng)發(fā)生了偏移,這對于模型的訓(xùn)練,特別是深層的模型訓(xùn)練非常的困難。

所以在 2015 年一篇論文提出了這個方法,批標(biāo)準(zhǔn)化,簡而言之,就是對于每一層網(wǎng)絡(luò)的輸出,對其做一個歸一化,使其服從標(biāo)準(zhǔn)的正態(tài)分布,這樣后一層網(wǎng)絡(luò)的輸入也是一個標(biāo)準(zhǔn)的正態(tài)分布,所以能夠比較好的進(jìn)行訓(xùn)練,加快收斂速度。batch normalization 的實(shí)現(xiàn)非常簡單,對于給定的一個 batch 的數(shù)據(jù)pytorch如何添加BN算法的公式如下

pytorch如何添加BN

第一行和第二行是計(jì)算出一個 batch 中數(shù)據(jù)的均值和方差,接著使用第三個公式對 batch 中的每個數(shù)據(jù)點(diǎn)做標(biāo)準(zhǔn)化,?是為了計(jì)算穩(wěn)定引入的一個小的常數(shù),通常取 pytorch如何添加BN,最后利用權(quán)重修正得到最后的輸出結(jié)果,非常的簡單,
實(shí)現(xiàn)一下簡單的一維的情況,也就是神經(jīng)網(wǎng)絡(luò)中的情況

import sys
sys.path.append('..')

import torch
def simple_batch_norm_1d(x, gamma, beta):
 eps = 1e-5
 x_mean = torch.mean(x, dim=0, keepdim=True) # 保留維度進(jìn)行 broadcast
 x_var = torch.mean((x - x_mean) ** 2, dim=0, keepdim=True)
 x_hat = (x - x_mean) / torch.sqrt(x_var + eps)
 return gamma.view_as(x_mean) * x_hat + beta.view_as(x_mean)
x = torch.arange(15).view(5, 3)
gamma = torch.ones(x.shape[1])
beta = torch.zeros(x.shape[1])
print('before bn: ')
print(x)
y = simple_batch_norm_1d(x, gamma, beta)
print('after bn: ')
print(y)

可以看到這里一共是 5 個數(shù)據(jù)點(diǎn),三個特征,每一列表示一個特征的不同數(shù)據(jù)點(diǎn),使用批標(biāo)準(zhǔn)化之后,每一列都變成了標(biāo)準(zhǔn)的正態(tài)分布這個時(shí)候會出現(xiàn)一個問題,就是測試的時(shí)候該使用批標(biāo)準(zhǔn)化嗎?答案是肯定的,因?yàn)橛?xùn)練的時(shí)候使用了,而測試的時(shí)候不使用肯定會導(dǎo)致結(jié)果出現(xiàn)偏差,但是測試的時(shí)候如果只有一個數(shù)據(jù)集,那么均值不就是這個值,方差為 0 嗎?這顯然是隨機(jī)的,所以測試的時(shí)候不能用測試的數(shù)據(jù)集去算均值和方差,而是用訓(xùn)練的時(shí)候算出的移動平均均值和方差去代替

實(shí)現(xiàn)以下能夠區(qū)分訓(xùn)練狀態(tài)和測試狀態(tài)的批標(biāo)準(zhǔn)化方法

def batch_norm_1d(x, gamma, beta, is_training, moving_mean, moving_var, moving_momentum=0.1):
 eps = 1e-5
 x_mean = torch.mean(x, dim=0, keepdim=True) # 保留維度進(jìn)行 broadcast
 x_var = torch.mean((x - x_mean) ** 2, dim=0, keepdim=True)
 if is_training:
 x_hat = (x - x_mean) / torch.sqrt(x_var + eps)
 moving_mean[:] = moving_momentum * moving_mean + (1. - moving_momentum) * x_mean
 moving_var[:] = moving_momentum * moving_var + (1. - moving_momentum) * x_var
 else:
 x_hat = (x - moving_mean) / torch.sqrt(moving_var + eps)
 return gamma.view_as(x_mean) * x_hat + beta.view_as(x_mean)

下面使用深度神經(jīng)網(wǎng)絡(luò)分類 mnist 數(shù)據(jù)集的例子來試驗(yàn)一下批標(biāo)準(zhǔn)化是否有用

import numpy as np
from torchvision.datasets import mnist # 導(dǎo)入 pytorch 內(nèi)置的 mnist 數(shù)據(jù)
from torch.utils.data import DataLoader
from torch import nn
from torch.autograd import Variable

使用內(nèi)置函數(shù)下載 mnist 數(shù)據(jù)集

train_set = mnist.MNIST('./data', train=True)
test_set = mnist.MNIST('./data', train=False)

def data_tf(x):
 x = np.array(x, dtype='float32') / 255
 x = (x - 0.5) / 0.5 # 數(shù)據(jù)預(yù)處理,標(biāo)準(zhǔn)化
 x = x.reshape((-1,)) # 拉平
 x = torch.from_numpy(x)
 return x

train_set = mnist.MNIST('./data', train=True, transform=data_tf, download=True) # 重新載入數(shù)據(jù)集,申明定義的數(shù)據(jù)變換
test_set = mnist.MNIST('./data', train=False, transform=data_tf, download=True)
train_data = DataLoader(train_set, batch_size=64, shuffle=True)
test_data = DataLoader(test_set, batch_size=128, shuffle=False)
class multi_network(nn.Module):
 def __init__(self):
 super(multi_network, self).__init__()
 self.layer1 = nn.Linear(784, 100)
 self.relu = nn.ReLU(True)
 self.layer2 = nn.Linear(100, 10)
 
 self.gamma = nn.Parameter(torch.randn(100))
 self.beta = nn.Parameter(torch.randn(100))
 
 self.moving_mean = Variable(torch.zeros(100))
 self.moving_var = Variable(torch.zeros(100))
 
 def forward(self, x, is_train=True):
 x = self.layer1(x)
 x = batch_norm_1d(x, self.gamma, self.beta, is_train, self.moving_mean, self.moving_var)
 x = self.relu(x)
 x = self.layer2(x)
 return x
net = multi_network()
# 定義 loss 函數(shù)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), 1e-1) # 使用隨機(jī)梯度下降,學(xué)習(xí)率 0.1

from datetime import datetime
import torch
import torch.nn.functional as F
from torch import nn
from torch.autograd import Variable
def get_acc(output, label):
 total = output.shape[0]
 _, pred_label = output.max(1)
 num_correct = (pred_label == label).sum().item()
 return num_correct / total


#定義訓(xùn)練函數(shù)
def train(net, train_data, valid_data, num_epochs, optimizer, criterion):
 if torch.cuda.is_available():
 net = net.cuda()
 prev_time = datetime.now()
 for epoch in range(num_epochs):
 train_loss = 0
 train_acc = 0
 net = net.train()
 for im, label in train_data:
  if torch.cuda.is_available():
  im = Variable(im.cuda()) # (bs, 3, h, w)
  label = Variable(label.cuda()) # (bs, h, w)
  else:
  im = Variable(im)
  label = Variable(label)
  # forward
  output = net(im)
  loss = criterion(output, label)
  # backward
  optimizer.zero_grad()
  loss.backward()
  optimizer.step()
 train_loss += loss.item()
 train_acc += get_acc(output, label)

 cur_time = datetime.now()
 h, remainder = divmod((cur_time - prev_time).seconds, 3600)
 m, s = divmod(remainder, 60)
 time_str = "Time %02d:%02d:%02d" % (h, m, s)
 if valid_data is not None:
 valid_loss = 0
 valid_acc = 0
 net = net.eval()
 for im, label in valid_data:
  if torch.cuda.is_available():
  im = Variable(im.cuda(), volatile=True)
  label = Variable(label.cuda(), volatile=True)
  else:
  im = Variable(im, volatile=True)
  label = Variable(label, volatile=True)
  output = net(im)
  loss = criterion(output, label)
  valid_loss += loss.item()
  valid_acc += get_acc(output, label)
 epoch_str = (
  "Epoch %d. Train Loss: %f, Train Acc: %f, Valid Loss: %f, Valid Acc: %f, "
  % (epoch, train_loss / len(train_data),
  train_acc / len(train_data), valid_loss / len(valid_data),
  valid_acc / len(valid_data)))
 else:
 epoch_str = ("Epoch %d. Train Loss: %f, Train Acc: %f, " %
   (epoch, train_loss / len(train_data),
   train_acc / len(train_data)))
 prev_time = cur_time
 print(epoch_str + time_str)

train(net, train_data, test_data, 10, optimizer, criterion)

#這里的 γ和 都作為參數(shù)進(jìn)行訓(xùn)練,初始化為隨機(jī)的高斯分布,

#moving_mean 和 moving_var 都初始化為 0,并不是更新的參數(shù),訓(xùn)練完 10 次之后,我們可以看看移動平均和移動方差被修改為了多少

#打出 moving_mean 的前 10 項(xiàng)

print(net.moving_mean[:10])
no_bn_net = nn.Sequential(
 nn.Linear(784, 100),
 nn.ReLU(True),
 nn.Linear(100, 10)
)

optimizer = torch.optim.SGD(no_bn_net.parameters(), 1e-1) # 使用隨機(jī)梯度下降,學(xué)習(xí)率 0.1
train(no_bn_net, train_data, test_data, 10, optimizer, criterion)

可以看到雖然最后的結(jié)果兩種情況一樣,但是如果我們看前幾次的情況,可以看到使用批標(biāo)準(zhǔn)化的情況能夠更快的收斂,因?yàn)檫@只是一個小網(wǎng)絡(luò),所以用不用批標(biāo)準(zhǔn)化都能夠收斂,但是對于更加深的網(wǎng)絡(luò),使用批標(biāo)準(zhǔn)化在訓(xùn)練的時(shí)候能夠很快地收斂從上面可以看到,我們自己實(shí)現(xiàn)了 2 維情況的批標(biāo)準(zhǔn)化,對應(yīng)于卷積的 4 維情況的標(biāo)準(zhǔn)化是類似的,只需要沿著通道的維度進(jìn)行均值和方差的計(jì)算,但是我們自己實(shí)現(xiàn)批標(biāo)準(zhǔn)化是很累的,pytorch 當(dāng)然也為我們內(nèi)置了批標(biāo)準(zhǔn)化的函數(shù),一維和二維分別是 torch.nn.BatchNorm1d() 和 torch.nn.BatchNorm2d(),不同于我們的實(shí)現(xiàn),pytorch 不僅將 和 β作為訓(xùn)練的參數(shù),也將 moving_mean 和 moving_var 也作為參數(shù)進(jìn)行訓(xùn)練

下面在卷積網(wǎng)絡(luò)下試用一下批標(biāo)準(zhǔn)化看看效果

def data_tf(x):
 x = np.array(x, dtype='float32') / 255
 x = (x - 0.5) / 0.5 # 數(shù)據(jù)預(yù)處理,標(biāo)準(zhǔn)化
 x = torch.from_numpy(x)
 x = x.unsqueeze(0)
 return x

train_set = mnist.MNIST('./data', train=True, transform=data_tf, download=True) # 重新載入數(shù)據(jù)集,申明定義的數(shù)據(jù)變換
test_set = mnist.MNIST('./data', train=False, transform=data_tf, download=True)
train_data = DataLoader(train_set, batch_size=64, shuffle=True)
test_data = DataLoader(test_set, batch_size=128, shuffle=False)

使用批標(biāo)準(zhǔn)化

class conv_bn_net(nn.Module):
 def __init__(self):
 super(conv_bn_net, self).__init__()
 self.stage1 = nn.Sequential(
  nn.Conv2d(1, 6, 3, padding=1),
  nn.BatchNorm2d(6),
  nn.ReLU(True),
  nn.MaxPool2d(2, 2),
  nn.Conv2d(6, 16, 5),
  nn.BatchNorm2d(16),
  nn.ReLU(True),
  nn.MaxPool2d(2, 2)
 )
 
 self.classfy = nn.Linear(400, 10)
 def forward(self, x):
 x = self.stage1(x)
 x = x.view(x.shape[0], -1)
 x = self.classfy(x)
 return x

net = conv_bn_net()
optimizer = torch.optim.SGD(net.parameters(), 1e-1) # 使用隨機(jī)梯度下降,學(xué)習(xí)率 0.1
train(net, train_data, test_data, 5, optimizer, criterion)

不使用批標(biāo)準(zhǔn)化

class conv_no_bn_net(nn.Module):
 def __init__(self):
 super(conv_no_bn_net, self).__init__()
 self.stage1 = nn.Sequential(
  nn.Conv2d(1, 6, 3, padding=1),
  nn.ReLU(True),
  nn.MaxPool2d(2, 2),
  nn.Conv2d(6, 16, 5),
  nn.ReLU(True),
  nn.MaxPool2d(2, 2)
 )
 
 self.classfy = nn.Linear(400, 10)
 def forward(self, x):
 x = self.stage1(x)
 x = x.view(x.shape[0], -1)
 x = self.classfy(x)
 return x

net = conv_no_bn_net()
optimizer = torch.optim.SGD(net.parameters(), 1e-1) # 使用隨機(jī)梯度下降,學(xué)習(xí)率 0.1
train(net, train_data, test_data, 5, optimizer, criterion)

以上是“pytorch如何添加BN”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI