PyTorch 模型训练实用教程(三):模型

目录

1 模型的搭建

1.1 模型定义的三要素

1.2 nn.Sequetial

2 权值初始化的⼗种⽅法

2.1 权值初始化流程

2.2 常用初始化方法

1. Xavier 均匀分布

2. Xavier 正态分布

3. kaiming 均匀分布

4. kaiming 正态分布

5. 均匀分布初始化

6. 正态分布初始化

7. 常数初始化

8. 单位矩阵初始化

9. 正交初始化

10. 稀疏初始化

11. 计算增益

2.3 权值初始化杂谈

3 模型 Finetune

3.1Finetune 之权值初始化

第一步:保存模型参数

第二步:加载模型

第三步:初始化

3.2 不同层设置不同的学习率

3.3 补充:


1 模型的搭建

1.1 模型定义的三要素

  • 首先,必须继承 nn.Module 这个类,要让 PyTorch 知道这个类是一个 Module
  • 其次,在__init__(self)中设置好需要的组件"(convpoolingLinearBatchNorm等)
  • 最后,在 forward(self, x)中用定义好的组件进行组装,就像搭积木,把网络结构搭建出来,这样一个模型就定义好了
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool1 = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.pool2 = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

#第一行是初始化,往后定义了一系列组件,如由 Conv2d 构成的 conv1,有 MaxPool2d构成的 poo1l。
#当这些组件定义好之后,就可以定义 forward()函数,用来搭建网络结构,


    def forward(self, x):
        x = self.pool1(F.relu(self.conv1(x)))
        x = self.pool2(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x
#x为模型的输入,第一行表示,x 经过 conv1,然后经过激活函数 relu,再经过 pool1 操作;
#第二行于第一行一样;第三行,表示将 x 进行 reshape,为了后面做为全连接层的输入;第四,第五行的操
#作都一样,先经过全连接层 fc,然后经过 relu;第六行,模型的最终输出是 fc3 输出。
至此,一个模型定义完毕,接着就可以在后面进行使用。

1.2 nn.Sequetial

torch.nn.Sequential 其实就是 Sequential 容器,该容器将一系列操作按先后顺序给包起来,方便重复使用,
例如 Resnet 中有很多重复的 block ,就可以用 Sequential 容器把重复的地方包起来。
下面为resnet34的代码:
#coding:utf8
from .BasicModule import BasicModule
from torch import nn
from torch.nn import functional as F

class ResidualBlock(nn.Module):
    '''
    实现子module: Residual Block
    '''
    def __init__(self, inchannel, outchannel, stride=1, shortcut=None):
        super(ResidualBlock, self).__init__()
        self.left = nn.Sequential(
                nn.Conv2d(inchannel, outchannel, 3, stride, 1, bias=False),
                nn.BatchNorm2d(outchannel),
                nn.ReLU(inplace=True),
                nn.Conv2d(outchannel, outchannel, 3, 1, 1, bias=False),
                nn.BatchNorm2d(outchannel) )
        self.right = shortcut

    def forward(self, x):
        out = self.left(x)
        residual = x if self.right is None else self.right(x)
        out += residual
        return F.relu(out)


class ResNet34(BasicModule):
    '''
    实现主module:ResNet34
    ResNet34包含多个layer,每个layer又包含多个Residual block
    用子module来实现Residual block,用_make_layer函数来实现layer
    '''
    def __init__(self, num_classes=2):
        super(ResNet34, self).__init__()
        self.model_name = 'resnet34'

        # 前几层: 图像转换
        self.pre = nn.Sequential(
                nn.Conv2d(3, 64, 7, 2, 3, bias=False),
                nn.BatchNorm2d(64),
                nn.ReLU(inplace=True),
                nn.MaxPool2d(3, 2, 1))
        
        # 重复的layer,分别有3,4,6,3个residual block
        self.layer1 = self._make_layer( 64, 128, 3)
        self.layer2 = self._make_layer( 128, 256, 4, stride=2)
        self.layer3 = self._make_layer( 256, 512, 6, stride=2)
        self.layer4 = self._make_layer( 512, 512, 3, stride=2)

        #分类用的全连接
        self.fc = nn.Linear(512, num_classes)
    
    def _make_layer(self,  inchannel, outchannel, block_num, stride=1):
        '''
        构建layer,包含多个residual block
        '''
        shortcut = nn.Sequential(
                nn.Conv2d(inchannel,outchannel,1,stride, bias=False),
                nn.BatchNorm2d(outchannel))
        
        layers = []
        layers.append(ResidualBlock(inchannel, outchannel, stride, shortcut))
        
        for i in range(1, block_num):
            layers.append(ResidualBlock(outchannel, outchannel))
        return nn.Sequential(*layers)
        
    def forward(self, x):
        x = self.pre(x)
        
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = F.avg_pool2d(x, 7)
        x = x.view(x.size(0), -1)
        return self.fc(x)
小结:
模型的定义就是先 继承 ,再 构建组件 ,最后 组装
其中基本组件可从 torch.nn 中获取,或者从 torch.nn.functional 中获取,同时为了方便重复使用组件,可以使用 Sequential 容器将一系列组件包起来,最后在 forward() 函数中将 这些组件组装成你的模型

2 权值初始化的⼗种⽅法

上一小节介绍了模型定义的方法,模型定义完成后,通常我们还需要对权值进行初始
化,才能开始训练。
初始化方法会直接影响到模型的收敛与否,在本小节,将介绍如何对模型进行初始
化。

2.1 权值初始化流程

总共两步,
第一步,先设定什么层用什么初始化方法,初始化方法在 torch.nn.init 中给出;
第二步,实例化一个模型之后,执行该函数,即可完成初始化
# 定义权值初始化
def initialize_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
torch.nn.init.xavier_normal(m.weight.data)
if m.bias is not None:
m.bias.data.zero_()
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()
elif isinstance(m, nn.Linear):
torch.nn.init.normal(m.weight.data, 0, 0.01)
m.bias.data.zero_()
这段代码基本流程是这样,先从 self.modules() 中遍历每一层,然后判断各层属于什么类型,例如,是否是 nn.Conv2d nn.BatchNorm2d nn.Linear 等,然后根据不同类型的 层,设定不同的权值初始化方法,例如, Xavier kaiming normal_ uniform_ 等。
Ps: kaiming 也称之为 MSRA 初始化,当年何恺明还在微软亚洲研究院,因而得名。
来看看第一行代码中的 self.modules() ,源码在 torch/nn/modules/module.py 中 。
def modules(self):
for name, module in self.named_modules():
yield module 
功能是: Returns an iterator over all modules in the network. 能依次返回模型中的各层,
例如:
接着,判断 m 的类型,属于什么类型,可以看到当前 m 属于 Conv2d 类型,则进行如下初始化:
以上代码表示采用 torch.nn.init.xavier_normal 方法对该层的 weight 进行初始化,并判断是否存在偏置 (bias) ,若存在,将 bias 初始化为全 0
这样,该层就初始化完毕,参照以上流程,不断遍历模型的每一层,最终完成模型的 初始化。

2.2 常用初始化方法

PyTorch torch.nn.init 中ᨀ供了常用的初始化方法函数,这里简单介绍,方便查询使用。
介绍分两部分:
1. Xavier kaiming 系列;
2. 其他方法分布
Xavier 初始化方法,论文在《 Understanding the difficulty of training deep feedforward neural networks
公式推导是从 方差一致性 出发,初始化的分布有均匀分布和正态分布两种。

1. Xavier 均匀分布

torch.nn.init.xavier_uniform_(tensor, gain=1)
xavier 初始化方法中服从均匀分布 U(−a,a) ,分布的参数 a = gain * sqrt(6/fan_in+fan_out) ,这里有一个 gain ,增益的大小是依据激活函数类型来设定
eg nn.init.xavier_uniform_(w, gain=nn.init.calculate_gain('relu'))
PS :上述初始化方法,也称为 Glorot initialization

2. Xavier 正态分布

torch.nn.init.xavier_normal_ ( tensor , gain=1 )
xavier 初始化方法中服从正态分布,
mean=0,std = gain * sqrt(2/fan_in + fan_out)
kaiming 初始化方法,论文在《 Delving deep into rectifiers: Surpassing human-level performance on ImageNet classification 》,公式推导同样从 方差一致性 出法, kaiming 是针对 xavier 初始化方法在 relu 这一类激活函数表现不佳而ᨀ出的改进,详细可以参看论文。

3. kaiming 均匀分布

torch.nn.init.kaiming_uniform_ ( tensor , a=0 , mode='fan_in' , nonlinearity='leaky_relu' )
此为均匀分布, U ~( -bound, bound , bound = sqrt(6/(1+a^2)*fan_in)
其中, a 为激活函数的负半轴的斜率, relu 0。mode- 可选为 fan_in fan_out, fan_in 使正向传播时,方差一致 ; fan_out 使反向传播时,方差一致
nonlinearity- 可选 relu leaky_relu ,默认值为 。 leaky_relu
nn . init . kaiming_uniform_(w, mode = 'fan_in' , nonlinearity = 'relu' )

4. kaiming 正态分布

torch.nn.init.kaiming_normal_ ( tensor , a=0 , mode='fan_in' , nonlinearity='leaky_relu' )
此为 0 均值的正态分布, N (0,std) ,其中 std = sqrt(2/(1+a^2)*fan_in)
其中, a 为激活函数的负半轴的斜率, relu 0
mode- 可选为 fan_in fan_out, fan_in 使正向传播时,方差一致 ;fan_out 使反向传播时,方差一致
nonlinearity- 可选 relu leaky_relu ,默认值为 。 leaky_relu
nn . init . kaiming_normal_(w, mode = 'fan_out' , nonlinearity = 'relu' )

5. 均匀分布初始化

torch.nn.init.uniform_ ( tensor , a=0 , b=1 )
使值服从均匀分布 U(a,b)

6. 正态分布初始化

torch.nn.init.normal_ ( tensor , mean=0 , std=1 )
使值服从正态分布 N(mean, std) ,默认值为 0 1

7. 常数初始化

torch.nn.init.constant_ ( tensor , val )
使值为常数 val nn . init . constant_(w, 0.3 )

8. 单位矩阵初始化

torch.nn.init.eye_ ( tensor )
将二维 tensor 初始化为单位矩阵( the identity matrix

9. 正交初始化

torch.nn.init.orthogonal_ ( tensor , gain=1 )
使得 tensor 是正交的,论文 : Exact solutions to the nonlinear dynamics of learning in deep linear neural networks” - Saxe, A. et al. (2013)

10. 稀疏初始化

torch.nn.init.sparse_ ( tensor , sparsity , std=0.01 )
从正态分布 N ~( 0. std )中进行稀疏化,使每一个 column 有一部分为 0
sparsity- 每一个 column 稀疏的比例,即为 0 的比例
nn . init . sparse_(w, sparsity = 0.1 )

11. 计算增益

torch.nn.init.calculate_gain ( nonlinearity , param=None )

2.3 权值初始化杂谈

1. 从代码中发现,即使不进行初始化,我们模型的权值也不为空,而是有值的,这些值是 在什么时候赋的呢?
其实,在创建网络实例的过程中 , 一旦调用 nn.Conv2d 的时候就会有对权值进行初始化
Conv2d 是继承 _ConvNd ,初始化赋值是在 _ConvNd 当中的
self.weight = Parameter(torch.Tensor(out_channels, in_channels // groups, *kernel_size))
这些值是创建一个 Tensor 时得到的,是一些很小的随机数
2. 按需定义初始化方法,例如:
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))

3 模型 Finetune

上一小节,介绍了模型权值初始化,以及 PyTorch 自带的权值初始化方法函数。我们知道一个良好的权值初始化,可以使收敛速度加快,甚至可以获得更好的精度。而在实际 应用中,我们通常采用一个已经训练模型的模型的权值参数作为我们模型的初始化参数,
也称之为 Finetune ,更宽泛的称之为迁移学习。迁移学习中的 Finetune 技术,本质上就是 让我们新构建的模型,拥有一个较好的权值初始值。
finetune 权值初始化三步曲, finetune 就相当于给模型进行初始化,其流程共用三步:
第一步:保存模型,拥有一个预训练模型;
第二步:加载模型,把预训练模型中的权值取出来;
第三步:初始化,将权值对应的 到新模型中

3.1Finetune 之权值初始化

在进行 finetune 之前我们需要拥有一个模型或者是模型参数,因此需要了解如何保存模型。官方文档中介绍了两种保存模型的方法,一种是保存整个模型,另外一种是仅保存 模型参数(官方推荐用这种方法),这里采用官方推荐的方法

第一步:保存模型参数

若拥有模型参数,可跳过这一步。
假设创建了一个 net = Net() ,并且经过训练,通过以下方式保存:
torch.save(net.state_dict(), 'net_params.pkl')

第二步:加载模型

进行三步曲中的第二步,加载模型,这里只是加载模型的参数:
pretrained_dict = torch.load('net_params.pkl')

第三步:初始化

进行三步曲中的第三步,将取到的权值,对应的放到新模型中:
  • 首先我们创建新模型,并且获取新模型的参数字典 net_state_dict
  • net = Net() # 创建 ne
  • net_state_dict = net.state_dict() # 获取已创建 net state_dict
  • 接着将 pretrained_dict 里不属于 net_state_dict 的键剔除掉: pretrained_dict_1 = {k: v for k, v in pretrained_dict.items() if k in net_state_dict}
  • 然后,用预训练模型的参数字典 对 新模型的参数字典 net_state_dict 进行更新: net_state_dict.update(pretrained_dict_1)
  • 最后,将更新了参数的字典 回到网络中: net.load_state_dict(net_state_dict)
这样,利用预训练模型参数对新模型的权值进行初始化过程就做完了。
采用 finetune 的训练过程中,有时候希望前面层的学习率低一些,改变不要太大,而后面的全连接层的学习率相对大一些。这时就需要对不同的层设置不同的学习率,下面就介绍如何为不同层配置不同的学习率。

3.2 不同层设置不同的学习率

在利用 pre-trained model 的参数做初始化之后,我们可能想让 fc 层更新相对快一些,而希望前面的权值更新小一些,这就可以通过为不同的层设置不同的学习率来达到此目的。
为不同层设置不同的学习率,主要通过优化器对多个参数组进行设置不同的参数。所以,只需要将原始的参数组,划分成两个,甚至更多的参数组,然后分别进行设置学习率。
这里将原始参数 切分 fc3 层参数和其余参数,为 fc3 层设置更大的学习率。
ignored_params = list(map(id, net.fc3.parameters())) # 返回的是 parameters 的 内存地址
base_params = filter(lambda p: id(p) not in ignored_params, net.parameters()) # 返回 base params 的 内存地址
optimizer = optim.SGD([
{'params': base_params},
{'params': net.fc3.parameters(), 'lr': 0.001*10}], 0.001, momentum=0.9, weight_decay=1e-4)
第一行 + 第二行的意思就是,将 fc3 层的参数 net.fc3.parameters() 从原始参数net.parameters() 中剥离出来
base_params 就是剥离了 fc3 层的参数的其余参数,然后在优化器中为 fc3 层的参数单独设定学习率。
optimizer = optim.SGD(......) 这里的意思就是 base_params 中的层,用 0.001, momentum=0.9,weight_decay=1e-4
fc3 层设定学习率为: 0.001*10

3.3 补充:

挑选出特定的层的机制是利用内存地址作为过滤条件,将需要单独设定的那部分参数,从总的参数中剔除。
base_params 是一个 list,每个元素是一个 Parameter
net.fc3.parameters() 是一个 <generator object parameters>
ignored_params = list(map(id, net.fc3.parameters()))
net.fc3.parameters() 是一个 <generator object parameters at 0x11b63bf00>
所以迭代的返回其中的 parameter ,这里有 weight bias
最终返回 weight bias 所在内存的地址
 
 
  • 4
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PyTorch是目前深度学习领域最受欢迎的开源框架之一。该框架提供了丰富的功能,包括构建计算图的灵活性、对GPU的支持,以及易于调试和可视化的接口。 PyTorch模型训练步骤与其他深度学习框架类似,但也有其独特之处。以下是一些PyTorch模型训练实用教程: 1. 准备数据: PyTorch提供了一些实用的类来创建和加载数据集。您可以使用DataLoader类来创建批量数据并进行数据预处理。还可以使用transform类将数据转换为需要的格式。 2. 构建模型: 使用PyTorch构建模型非常容易。您只需定义模型的结构和构造函数即可。PyTorch支持多种模型类型,包括卷积神经网络、循环神经网络和转移学习。 3. 定义损失函数: 损失函数是模型最关键的部分之一。PyTorch提供多种用于分类、回归和聚类的损失函数。您还可以创建自定义损失函数。 4. 优化算法: 优化算法是用于更新模型参数的方法。PyTorch支持多种优化算法,包括随机梯度下降、Adam和Adagrad。此外,可以通过定义自己的优化算法来实现个性化的优化。 5. 训练模型训练模型是使用深度学习时最耗时的部分之一。在PyTorch中,您可以使用for循环迭代训练数据,并使用backward()函数进行反向传播。还可以使用scheduler类动态地调整学习率。 6. 评估模型: 评估模型是确保模型工作正常的必要步骤之一。您可以使用PyTorch提供的类来计算模型的准确性、F1分数等指标。 总体来说,PyTorch对于初学者和专业人士来说都是一种极具吸引力的深度学习框架。通过了解PyTorch的基本功能,您可以更好地了解如何使用它来训练自己的模型

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值