Pytorch中BatchNorm中running_mean和running_var的计算方法

以下的过程中都是在train模式下进行的,并且没有进行损失计算和梯度更新,
但这个过程中running_mean和running_var会进行更新,所以也验证了
running_mean和running_var只受模型的模式(train模型或eval模型)的影响,
与是否进行反向传播(loss.backward)和梯度更新(optimiter.step)没有关系。
实验一:
1. 标准库函数的参数设置为 torch.nn.BatchNorm1d(linear1_features,momentum=0.1)
2. 自定义函数的参数设置为 MyBatchnorm1d(linear1_features,momentum=0.9)
3. 相同的输入,对比输出的参数值是否相同
实验二:
1. 标准库函数的参数设置为 torch.nn.BatchNorm1d(linear1_features,momentum=None)
2. 自定义函数的参数设置为 MyBatchnorm1d(linear1_features,momentum=None)
3. 相同的输入,对比输出的参数值是否相同

 针对标准库torch.nn.BatchNorm1d()中running_mean和running_var计算方法的结论:

为方便描述,规定:

  • rm表示running_mean;
  • rv表示running_var;
  • m表示momemtum
  • b_num表示batchnum,代表当前batch之前的batch个数

一、在train模式下

1. 带动量时,即指定momentum为一个大于0小于1的数值时,相当于当前值与历史值的加权平均

\small rm_{new} = (1-m)*rm_{old}+m*mean\_bn

其中mean_bn是当前batch数据的batch平均值。这里的momentum是对当前值的加权系数!!默认为0.1

 \small rv_{new} = (1-m)*rv_{old}+m*var\_bn

其中var_bn是当前batch数据的方差平均值(这里是方差,不是标准差,标准差的平方=方差)

2. 不带动量时,即momentum=None,直接让历史值与当前值取平均

\small rm_{new} = rm_{old}*b\_num+mean\_bn)/(b\_num+1)

\small rv_{new} = rv_{old}*b\_num+var\_bn)/(b\_num+1)

二、在eval模型下

直接用rm、rv作为当前batch均值和方差 

代码如下:

# -*- coding: utf-8 -*-
import torch
from torch.autograd import Variable
import torch.optim as optim

batchsize = 16
batchnum = 100
# rawdata = torch.randn([batchsize*batchnum,3])
# torch.save(rawdata,'./debug_running_var_mean_rawdata.pth')
#加载数据,保证数据是不变的
rawdata = torch.load('./debug_running_var_mean_rawdata.pth')
print(rawdata.size())

y = Variable(torch.FloatTensor([4,5]))
dataset = [Variable(rawdata[curpos:curpos + batchsize]) for curpos in range(0,len(rawdata),batchsize)]


class SimpleModel(torch.nn.Module):
    def __init__(self):
        super(SimpleModel,self).__init__()
        linear1_features = 5
        self.linear1 = torch.nn.Linear(3,linear1_features)
        self.relu = torch.nn.ReLU()
        self.linear2 = torch.nn.Linear(linear1_features,2)
        #设计时batchnorm放在linear1后面,所以这里用linear1的输出维度
        self.batch_norm = torch.nn.BatchNorm1d(linear1_features,momentum=0.1)  #标准库中的Barchnorm,track_running_stats默认为True
        # self.batch_norm = torch.nn.BatchNorm1d(linear1_features,momentum=None)  #标准库中的Barchnorm,track_running_stats默认为True

    def forward(self,x):
        x = self.linear1(x)
        x = self.relu(x)
        x = self.batch_norm(x)
        x = self.linear2(x)
        return x

#先进行一下简单训练之后,保存参数,后面的模型可以加载此函数,这样相当于给用于实验的两个模型初始化了相同的参数
train_demo = 0
if train_demo == 1:
    model = SimpleModel()
    # print(list(model.parameters()))
    # #查看模型的初始参数
    # print(model.state_dict().keys())
    # # for i, j in model.named_parameters():
    # for i,j in model.state_dict().items():
    #     print('++++',i)
    #     print('\t',j)

    loss_fn = torch.nn.MSELoss(size_average=False)
    optimizer = optim.SGD(model.parameters(),lr=0.001,momentum=0.9)
    model.train()
    for t,x in enumerate(dataset):
        y_pred = model(x)
        loss = loss_fn(y_pred,y)
        print(t,loss.data)
        model.zero_grad()
        loss.backward()
        optimizer.step()

    #查看训练后的模型参数
    print('##################The trained Model parameters###############')
    print(model.state_dict().keys())
    # for i, j in model.named_parameters():
    for i,j in model.state_dict().items():
        print('++++',i)
        print('\t',j)
    #保存模型参数
    state = {'model': model.state_dict()}
    torch.save(state,'debug_batchnorm.pth')


class MyBatchnorm1d(torch.nn.Module):
    def __init__(self,num_features,momentum=0.9):
        '''
        自定义的batchnorm
        :param num_features:
        :param momentum: 动量系数,大于等于0小于1,表示保留原来变量值的比例,与标准库torch.nn.Batchnorm1d正好相反
                         当取None时,采用简单的取平均的方式计算running_mean和running_var
        '''
        super(MyBatchnorm1d,self).__init__()
        self.weight = torch.nn.Parameter(torch.ones(num_features).float())
        self.bias = torch.nn.Parameter(torch.zeros(num_features).float())
        #register_buffer相当于requires_grad=False的Parameter,所以两种方法都可以
        #方法一:
        self.register_buffer('running_mean',torch.zeros(num_features))
        self.register_buffer('running_var',torch.zeros(num_features))
        self.register_buffer('num_batches_tracked',torch.tensor(0))
        #方法二:
        # self.running_mean = torch.nn.Parameter(torch.zeros(num_features),requires_grad=False)
        # self.running_var = torch.nn.Parameter(torch.ones(num_features),requires_grad=False)
        # self.num_batches_tracked = torch.nn.Parameter(torch.tensor(0),requires_grad=False)

        self.momentum = momentum

    def forward(self,x):
        if self.training: #训练模型
            #数据是二维的情况下,可以这么处理,其他维的时候不是这样的,但原理都一样。
            mean_bn = x.mean(0, keepdim=True).squeeze(0) #相当于x.mean(0, keepdim=False)
            var_bn = x.var(0, keepdim=True).squeeze(0) #相当于x.var(0, keepdim=False)

            if self.momentum is not None:
                self.running_mean.mul_(self.momentum)
                self.running_mean.add_((1 - self.momentum) * mean_bn.data)
                self.running_var.mul_(self.momentum)
                self.running_var.add_((1 - self.momentum) * var_bn.data)
            else:  #直接取平均,以下是公式变形,即 m_new = (m_old*n + new_value)/(n+1)
                self.running_mean = self.running_mean+(mean_bn.data-self.running_mean)/(self.num_batches_tracked+1)
                self.running_var = self.running_var+(var_bn.data-self.running_var)/(self.num_batches_tracked+1)
            self.num_batches_tracked += 1
        else: #eval模式
            mean_bn = torch.autograd.Variable(self.running_mean)
            var_bn = torch.autograd.Variable(self.running_var)

        eps = 1e-5
        x_normalized = (x - mean_bn) / torch.sqrt(var_bn + eps)
        results = self.weight * x_normalized + self.bias
        return results


class DebugSimpleModel(torch.nn.Module):
    def __init__(self):
        super(DebugSimpleModel,self).__init__()
        linear1_features = 5
        self.linear1 = torch.nn.Linear(3,linear1_features)
        self.relu = torch.nn.ReLU()
        self.linear2 = torch.nn.Linear(linear1_features,2)
        self.batch_norm = MyBatchnorm1d(linear1_features,momentum=0.9)  #使用自定义的Batchnorm
        # self.batch_norm = MyBatchnorm1d(linear1_features,momentum=None)  #使用自定义的Batchnorm

    def forward(self,x):
        x = self.linear1(x)
        x = self.relu(x)
        x = self.batch_norm(x)
        x = self.linear2(x)
        return x


#查看训练后的模型参数
print('##################The trained Model parameters###############')
model_param_dict = torch.load('debug_batchnorm.pth')['model']
print(model_param_dict.keys())
# for i, j in model.named_parameters():
for i,j in model_param_dict.items():
    print('++++',i)
    print('\t',j)

'''
以下的过程中都是在train模式下进行的,并且没有进行损失计算和梯度更新,
但这个过程中running_mean和running_var会进行更新,所以也验证了
running_mean和running_var只受模型的模式(train模型或eval模型)的影响,
与是否进行反向传播(loss.backward)和梯度更新(optimiter.step)没有关系。
实验一:
1. 标准库函数的参数设置为 torch.nn.BatchNorm1d(linear1_features,momentum=0.1)
2. 自定义函数的参数设置为 MyBatchnorm1d(linear1_features,momentum=0.9)
3. 相同的输入,对比输出的参数值是否相同
实验二:
1. 标准库函数的参数设置为 torch.nn.BatchNorm1d(linear1_features,momentum=None)
2. 自定义函数的参数设置为 MyBatchnorm1d(linear1_features,momentum=None)
3. 相同的输入,对比输出的参数值是否相同

'''
test_demo = 1
if test_demo == 1:
    test_model = SimpleModel()
    test_model.load_state_dict(torch.load('debug_batchnorm.pth')['model'])
    test_model.train()
    for t,x in enumerate(dataset):
        y_pred = test_model(x)
    print('\n++++++++++  Norm output  ++++++++++++++++')
    for i,j in test_model.state_dict().items():
        print('++++',i)
        print('\t',j)

debug_demo = 1
if debug_demo == 1:
    debug_model = DebugSimpleModel()
    #因为自定义的模型参数与标准模型的参数完全一样,所以把标准模型作为预训练的模型(即可以加载标准模型的训练后的参数作为自己的参数)
    debug_model.load_state_dict(torch.load('debug_batchnorm.pth')['model'])
    debug_model.train()
    for t,x in enumerate(dataset):
        y_pred = debug_model(x)

    print('\n++++++++++++ Mymodel Output ++++++++++++++')
    for i,j in debug_model.state_dict().items():
        print('++++',i)
        print('\t',j)

<think>好的,我现在需要解决用户提出的RuntimeError:“running_mean should contain 1986 elements not 16”。这个问题看起来PyTorch中的Batch Normalization层有关。首先,我得理解错误的原因,然后找出解决办法。 用户可能在使用预训练的模型或者在加载模型参数时遇到了这个问题。错误信息中的数字198616分别表示模型期望的running_mean参数的大小实际加载的大小不符。这说明模型结构或参数保存/加载过程中可能出现了不一致。 首先,我应该考虑BatchNorm层的参数。BatchNorm层中的running_meanrunning_var是根据输入的特征数(即通道数)来确定的。例如,如果某个卷积层的输出通道数是1986,对应的BatchNorm层的running_mean应该有1986个元素。而用户遇到的错误显示当前只有16个,这意味着该层的通道数应该是16,但模型期望的是1986。这种情况通常发生在模型结构改变后,比如修改了层的通道数,但加载了旧的参数文件,导致参数形状不匹配。 接下来,我需要验证这个假设。用户可能在修改了网络结构之后,比如改变了某个卷积层的输出通道数,但尝试加载之前训练好的模型参数。例如,假设原模型有一个卷积层输出1986个通道,后面跟着BatchNorm层,但现在改成了输出16个通道,而用户仍然加载了旧的权重文件,其中BatchNorm层的running_mean是1986长度的。这时候,PyTorch在加载参数时会检查形状,发现不一致,从而抛出RuntimeError。 解决办法通常是确保模型的结构与加载的权重文件匹配。用户需要检查模型定义是否与保存的checkpoint一致。如果模型结构确实有变化,可能需要重新训练,或者通过部分加载参数的方法来避免不匹配的层。 另一个可能性是在加载模型时使用了strict=False参数,但某些参数仍然无法匹配。比如,在使用load_state_dict时,如果strict=True(默认),则所有键必须完全匹配。如果用户修改了模型结构,某些层的名称或数量变化了,就会导致错误。此时,可以考虑设置strict=False,并检查哪些参数没有被正确加载,但需要注意这可能导致模型性能下降。 可能的解决步骤包括: 1. 检查模型定义保存的权重是否对应。确认所有层的参数尺寸是否一致。 2. 如果模型结构确实有变化,考虑重新训练,或者手动调整参数加载方式,例如只加载匹配的部分。 3. 使用工具或代码打印出当前模型权重文件的参数形状,对比找出不匹配的层。 4. 确保在加载权重时处理了不匹配的情况,比如忽略某些层或调整参数。 例如,用户可能在定义模型时改变了某个层的输出通道数,导致对应的BatchNorm层参数不匹配。例如,原模型中的某个卷积层可能有1986个输出通道,对应的BatchNorm层参数大小是1986,而修改后的模型该卷积层输出16个通道,导致BatchNormrunning_mean变为16,但加载的权重文件中还是原来的1986,从而报错。 为了验证,可以打印模型的state_dict保存的checkpoint中的键形状,找到不匹配的地方。比如,使用如下代码: ```python # 加载保存的权重 checkpoint = torch.load('model.pth') print("Checkpoint keys:") for key in checkpoint.keys(): print(key, checkpoint[key].size()) # 打印当前模型的状态 print("Model keys:") model = MyModel() # 用户定义的模型 model_dict = model.state_dict() for key in model_dict.keys(): print(key, model_dict[key].size()) ``` 通过比较两者的键形状,可以快速定位到是哪个层的参数不匹配。例如,发现某个BatchNorm层的running_mean在checkpoint中是torch.Size([1986]),而当前模型中是torch.Size([16])。 解决方案可以是: - 恢复模型结构,使其与checkpoint一致。 - 如果无法恢复结构,可能需要重新训练模型,或者找到一种迁移学习的方法,只加载匹配部分的参数,忽略不匹配的层。 如果用户确实需要修改模型结构,并且无法使用原来的checkpoint,可能需要重新初始化某些层,并加载其他层的参数。例如: ```python model = ModifiedModel() # 修改后的模型 checkpoint = torch.load('old_model.pth') # 部分加载参数 pretrained_dict = {k: v for k, v in checkpoint.items() if k in model.state_dict() and v.size() == model.state_dict()[k].size()} model.state_dict().update(pretrained_dict) model.load_state_dict(pretrained_dict, strict=False) ``` 这样,只有那些名称尺寸都匹配的参数会被加载,其他层则保留初始化值。用户需要注意这可能会影响模型性能,需要重新训练调整过的层。 总结,该错误的核心是模型结构与加载的权重参数在某一层的形状不匹配,特别是BatchNorm层的running_mean。需要检查模型定义权重文件的对应关系,确保一致性,或者处理参数加载时的差异。</think>RuntimeError: running_mean should contain 1986 elements not 16 通常与 PyTorch 的 Batch Normalization 层参数不匹配有关。以下是原因解决方法: **直接原因**: 模型加载的 BatchNorm 层参数维度与当前模型结构不匹配,具体表现为: - 当前模型某层的 `num_features`(特征维度)为 16 - 但加载的预训练权重中对应层的 `num_features` 为 1986 **常见场景**: 1. 修改了模型结构(如更改了卷积层的通道数)但未更新预训练权重 2. 错误加载了不同架构的预训练模型 3. 使用了不匹配的模型配置参数 **解决方法**: ```python # 方法1:完全重新初始化模型(会丢失预训练权重) model = YourModel(num_features=16) # 确保参数与目标维度一致 model.apply(weights_init) # 自定义权重初始化函数 # 方法2:选择性加载兼容参数(推荐) pretrained_dict = torch.load('pretrained.pth') model_dict = model.state_dict() # 过滤匹配的参数 pretrained_dict = { k: v for k, v in pretrained_dict.items() if k in model_dict and v.shape == model_dict[k].shape } # 加载并显示缺失/不匹配的键 model.load_state_dict(pretrained_dict, strict=False) ``` **根本原因分析**: BatchNorm 层的 `running_mean` 维度由输入通道数(`num_features`)决定。当修改了前置层的输出通道数但未同步调整 BatchNorm 参数时: ```text Conv2d(in_c, out_c=1986) → BatchNorm2d(1986) # 原始结构 ↓ 修改后 ↓ Conv2d(in_c, out_c=16) → BatchNorm2d(16) # 新结构 ``` 此时加载旧权重会因维度不匹配触发错误。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值