pytorch之添加BN的实现
pytorch之添加BN层
批标准化
模型训练并不容易,特别是⼀些⾮常复杂的模型,并不能⾮常好的训练得到收敛的结果,所以对数据增加⼀些预处理,同时使⽤批标准化能够得到⾮常好的收敛结果,这也是卷积⽹络能够训练到⾮常深的层的⼀个重要原因。
数据预处理
⽬前数据预处理最常见的⽅法就是中⼼化和标准化,中⼼化相当于修正数据的中⼼位置,实现⽅法⾮常简单,就是在每个特征维度上减去对应的均值,最后得到 0 均值的特征。标准化也⾮常简单,在数据变成 0 均值之后,为了使得不同的特征维度有着相同的规模,可以除以标准差近似为⼀个标准正态分布,也可以依据最⼤值和最⼩值将其转化为 -1 ~ 1之间,这两种⽅法⾮常的常见,如果你还记得,前⾯我们在神经⽹络的部分就已经使⽤了这个⽅法实现了数据标准化,⾄于另外⼀些⽅法,⽐如PCA 或者⽩噪声已经⽤得⾮常少了。
Batch Normalization
前⾯在数据预处理的时候,尽量输⼊特征不相关且满⾜⼀个标准的正态分布,
这样模型的表现⼀般也较好。但是对于很深的⽹路结构,⽹路的⾮线性层会使得输出的结果变得相关,且不再满⾜⼀个标准的N(0, 1) 的分布,甚⾄输出的中⼼已经发⽣了偏移,这对于模型的训练,特别是深层的模型训练⾮常的困难。
所以在 2015 年⼀篇论⽂提出了这个⽅法,批标准化,简⽽⾔之,就是对于每⼀层⽹络的输出,对其做⼀个归⼀化,使其服从标准的正态分布,这样后⼀层⽹络的输⼊也是⼀个标准的正态分布,所以能够⽐较好的进⾏训练,加快收敛速度。batch
normalization 的实现⾮常简单,对于给定的⼀个 batch 的数据算法的公式如下
第⼀⾏和第⼆⾏是计算出⼀个 batch 中数据的均值和⽅差,接着使⽤第三个公式对 batch 中的每个数据点做标准化,ϵ是为了
计算稳定引⼊的⼀个⼩的常数,通常取,最后利⽤权重修正得到最后的输出结果,⾮常的简单,
实现⼀下简单的⼀维的情况,也就是神经⽹络中的情况
import sys
sys.path.append('..')
import torch
def simple_batch_norm_1d(x, gamma, beta):
eps = 1e-5
x_mean = an(x, dim=0, keepdim=True) # 保留维度进⾏ broadcast
x_var = an((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 = s(x.shape[1])
beta = s(x.shape[1])
print('before bn: ')
print(x)
y = simple_batch_norm_1d(x, gamma, beta)
print('after bn: ')
print(y)
可以看到这⾥⼀共是 5 个数据点,三个特征,每⼀列表⽰⼀个特征的不同数据点,使⽤批标准化之后,每⼀列都变成了标准的正态分布这个时候会出现⼀个问题,就是测试的时候该使⽤批标准化吗?答案是肯定的,因为训练的时候使⽤了,⽽测试的时候不使⽤肯定会导致结果出现偏差,但是测试的时候如果
只有⼀个数据集,那么均值不就是这个值,⽅差为 0 吗?这显然
是随机的,所以测试的时候不能⽤测试的数据集去算均值和⽅差,⽽是⽤训练的时候算出的移动平均均值和⽅差去代替实现以下能够区分训练状态和测试状态的批标准化⽅法
def batch_norm_1d(x, gamma, beta, is_training, moving_mean, moving_var, moving_momentum=0.1):
eps = 1e-5
x_mean = an(x, dim=0, keepdim=True) # 保留维度进⾏ broadcast
x_var = an((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)
下⾯使⽤深度神经⽹络分类 mnist 数据集的例⼦来试验⼀下批标准化是否有⽤
import numpy as np
from torchvision.datasets import mnist # 导⼊ pytorch 内置的 mnist 数据
from torch.utils.data import DataLoader
from torch import nn
from torch.autograd import Variable
使⽤内置函数下载 mnist 数据集
train_set = mnist.MNIST('./data', train=True)
test_set = mnist.MNIST('./data', train=False)
numpy库是标准库吗def data_tf(x):
x = np.array(x, dtype='float32') / 255
x = (x - 0.5) / 0.5 # 数据预处理,标准化
x = x.reshape((-1,)) # 拉平
x = torch.from_numpy(x)
return x
train_set = mnist.MNIST('./data', train=True, transform=data_tf, download=True) # 重新载⼊数据集,申明定义的数据变换
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.layer2 = nn.Linear(100, 10)
self.gamma = nn.Parameter(torch.randn(100))
self.beta = nn.Parameter(torch.randn(100))
def forward(self, x, is_train=True):
x = self.layer1(x)
x = batch_norm_1d(x, self.gamma, self.beta, is_train, ving_mean, ving_var)
x = lu(x)
x = self.layer2(x)
return x
net = multi_network()
# 定义 loss 函数
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), 1e-1) # 使⽤随机梯度下降,学习率 0.1
from datetime import datetime
import torch
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
#定义训练函数
def train(net, train_data, valid_data, num_epochs, optimizer, criterion):
if torch.cuda.is_available():
net = net.cuda()
prev_time = w()
for epoch in range(num_epochs):
train_loss = 0
train_acc = 0
net = ain()
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
<_grad()
loss.backward()
optimizer.step()
train_loss += loss.item()
train_acc += get_acc(output, label)
cur_time = w()
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)
#这⾥的γ和ββ都作为参数进⾏训练,初始化为随机的⾼斯分布,
#moving_mean 和 moving_var 都初始化为 0,并不是更新的参数,训练完 10 次之后,我们可以看看移动平均和移动⽅差被修改为了多少
#打出 moving_mean 的前 10 项
ving_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) # 使⽤随机梯度下降,学习率 0.1
train(no_bn_net, train_data, test_data, 10, optimizer, criterion)
可以看到虽然最后的结果两种情况⼀样,但是如果我们看前⼏次的情况,可以看到使⽤批标准化的情况能够更快的收敛,因为这只是⼀个⼩⽹络,所以⽤不⽤批标准化都能够收敛,但是对于更加深的⽹络,使⽤批标准化在训练的时候能够很快地收敛从上⾯可以看到,我们⾃⼰实现了 2 维情况的批标准化,对应于卷积的 4 维情况的标准化是类似的,只需要沿着通道的维度进⾏均值和⽅差的计算,但是我们⾃⼰
实现批标准化是很累的,pytorch 当然也为我们内置了批标准化的函数,⼀维和⼆维分别是 BatchNorm1d() 和 BatchNorm2d(),不同于我们的实现,pytorch 不仅将γγ和β作为训练的参数,也将moving_mean 和 moving_var 也作为参数进⾏训练
下⾯在卷积⽹络下试⽤⼀下批标准化看看效果
def data_tf(x):
x = np.array(x, dtype='float32') / 255
x = (x - 0.5) / 0.5 # 数据预处理,标准化
x = torch.from_numpy(x)
x = x.unsqueeze(0)
return x
train_set = mnist.MNIST('./data', train=True, transform=data_tf, download=True) # 重新载⼊数据集,申明定义的数据变换
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 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) # 使⽤随机梯度下降,学习率 0.1
train(net, train_data, test_data, 5, optimizer, criterion)
不使⽤批标准化
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) # 使⽤随机梯度下降,学习率 0.1
train(net, train_data, test_data, 5, optimizer, criterion)
以上这篇pytorch之添加BN的实现就是⼩编分享给⼤家的全部内容了,希望能给⼤家⼀个参考,也希望⼤家多多⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论