批量归一化

发布时间:2024年01月15日

目录

1.批量归一化

2.代码

2.1从零开始实现

2.2简洁实现


1.批量归一化

正向是forward函数(从底向上),算backward的时候是从上往下。出现的问题就是梯度在上面的时候比较大,在下面就比较小,上面就会很快收敛,下面收敛很慢,每次更新下面的靠近数据的东西,这些东西会尝试去抽取那些比较底层的特征,比如局部边缘等很简单的纹理信息,上面就是一些高层语义的信息。因此上面更新收敛就很快,下面更新就很慢,但是每次下面一变上面得重新开始训练,所以上面就白学了。每次底部一变顶部就要跟着变,所以收敛就比较慢。

所以思考,我们能不能在学习底部得时候(改变底部的特征的时候),避免顶部会不断的重新训练。这就是批量归一化考虑的问题。

核心的思想就是,B就是小批量所有下标的索引,对小批量里的所有样本求和再除以批量大小就是均值,\gamma\beta是可学习的参数。假设分布(均值为0,方差为1)不是那么适合的话,可以通过学习一个新的均值和方差来使得值对神经网络要好一点,但是会限定住\gamma\beta的变化不要太猛烈。

批量归一化是个线性变换,把均值方差弄得比较好。对于全连接,假如是2维的输入,每一行是样本,每一列是特征,批归一化作用在特征上,对每一个特征计算一个标量的均值标量的方差,然后把特征变成均值为0方差为1。对每一个全连接的输出或者输入都做一个这样子的事情,而不是只作用在数据上,另外也会用学到的\gamma\beta重新作用一下对均值和方差做一下矫正。

对于卷积层,作用在通道维,比如1*1的卷积层,等价于一个全连接层,对每一个像素,一个像素不是有多通道吗,如果通道数是100的话,这个像素其实是有一个长为100维的向量,可以认为向量是像素的一个特征。对于有高宽的输入来说,里面的每一个像素就是一个样本,对于卷积层来讲,假设输入是批量大小,乘以通道数,乘以高,乘以宽的话,样本数就是批量大小乘以高和宽,里面所有的像素都是一个样本。对应的通道就是特征。

2.代码

2.1从零开始实现

import torch 
from torch import nn
from d2l import torch as d2l

"""从头开始实现一个具有张量的批量规范化层"""
def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum):
#moving_mean, moving_var可以看作个数据集上的均值和方差,而不是小批量上的均值和方差,
#做推理的时候用的。eps就是为了避免除0的一个东西。momentum就是用来更新moving_mean, moving_var
#的
    # 通过autograd来判断当前模式是训练模式还是预测模式
    if not torch.is_grad_enabled():
        # 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差
        X_hat=(X-moving_mean)/torch.aqrt(moving_var+eps)
        #解释一下为什么不用批量的均值,因为做in_first的时候可能就是一张图片或者是一个样本
        #而不是一个批量。就算不出来
    else:
        assert len(X.shape) in (2,4)
        #要么等于2,就是全连接层,要么等于4就是卷积层
        if len(X.shape)==2:
            # 使用全连接层的情况,计算特征维上的均值和方差
            #对于2维输入。0维为样本,1维(列)为特征,就是计算每一列上的均值和方差
            mean=X.mean(dim=0)
            #按行求均值,就是对于每一列都算一个均值出来(跨行对同一列元素求均值)
            var=((X-mean)**2).mean(dim=0)
        else:
            # 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。
            # 这里我们需要保持X的形状以便后面可以做广播运算,这里的mean是当前小批量的均值
            mean=X.mean((dim=0,2,3),keepdim=True)
            var=((X-mean)**2).mean((0,2,3),keepdim=True)
        # 训练模式下,用当前的均值和方差做标准化
        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


#创建一个正确的BatchNorm层
class BatchNorm(nn.module):
    def __init__(self,num_features,num_dims):
    # num_features:完全连接层的输出数量或卷积层的输出通道数。
    # num_dims:2表示完全连接层,4表示卷积层
        super().__init__()
        if num_dims==2:
            shape=(1,num_features)
        else:
            shape=(1,num_features,1,1)
        # 参与求梯度和迭代的拉伸和偏移参数,分别初始化成1和0
        self.gamma=nn.Parameter(torch.ones(shape))
        self.beta=nn.Parameter(torch.zeros(shape))
        # 非模型参数的变量初始化为0和1
        self.moving_mean=torch.zeros(shape)
        self.moving_var=torch.ones(shape)

def forward(self,X):
    if self.moving_mean.device!=X.device
    # 如果X不在内存上,将moving_mean和moving_var复制到X所在显存上
    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


"""使用批量规范化层的 LeNet"""
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数据集上训练网络。 这个代码与我们第一次训练LeNet
#( 6.6节)时几乎完全相同,主要区别在于学习率大得多。
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.273, train acc 0.899, test acc 0.807
32293.9 examples/sec on cuda:0"""


        

#让我们来看看从第一个批量规范化层中学到的拉伸参数gamma和偏移参数beta。
net[1].gamma.reshape((-1,)), net[1].beta.reshape((-1,))
"""结果输出:
(tensor([0.4863, 2.8573, 2.3190, 4.3188, 3.8588, 1.7942], device='cuda:0',
        grad_fn=<ReshapeAliasBackward0>),
 tensor([-0.0124,  1.4839, -1.7753,  2.3564, -3.8801, -2.1589], device='cuda:0',
        grad_fn=<ReshapeAliasBackward0>))"""

2.2简洁实现

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))

d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
"""结果输出:
loss 0.267, train acc 0.902, test acc 0.708
50597.3 examples/sec on cuda:0
"""

文章来源:https://blog.csdn.net/m0_51133942/article/details/135596306
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。