第三节:工程实践技巧的Pytorch实现

第三节:工程实践技巧的Pytorch实现

我们在CS231n的笔记中我们讲解了在搭建一个网络的时候我们会用到的各种小技巧(Fancy Trick),例如使用Mini-batch而非单个数据来进行训练,我们优化参数矩阵的时候采取各种AdamGrad,Momentum SGD等等而非简单的SGD,我们不使用正态分布而是凯明初始化方法或者Xavier初始化方法等来初始化参数矩阵避免训练失败等等

我们真正搭建一个可实际运用的网络的流程如下:

  1. 确定网络结构
    例如:输入 -> 卷积层1 -> 池化层1 -> 卷积层2 -> 池化层2 ->卷积层3 -> 池化层3 -> 全连接层1 -> 全连接层2 -> 全连接层3 -> 输出
  2. 确定网络的细节
    例如:确定使用的激活函数,参数矩阵初始化方法,梯度更新的方法,卷积核大小形状,是否使用Batch Normalization,Mini-batch的大小,数据预处理的方式等等
  3. 训练网络
    例如:数据的加载,训练好之后模型的保存等等

上节讲解的内容能够帮助我们搭建出一个网络结构,这节课将讲解工程实践中的技巧,即在Pytorch中如何实现网络的细节

下节课我们将讲解数据的加载等内容

Pytoch中的损失函数

深度学习进行优化的时候首先对损失函数进行传播,而后再根据构建的计算图一次进行反向传播

Pytorch的nn模块中也提供了许多可以直接使用的损失函数,例如交叉熵损失函数和均方差损失函数等等,针对不同的问题,我们可以直接调用现有的损失函数类,常见的损失函数如下

算法名称运用问题类型
torch.nn.L1LossL1误差损失函数回归
torch.nn.MSELoss均方误差损失函数回归
torch.nn.CrossEntropyLoss交叉熵损失函数多分类
torch.nn.NLLLoss负对数似然函数损失多分类
torch…nn.NLLLoss2d图片负对数似然函数损失图像分割
torch.nn.KLDivLossKL散度损失回归
torch.nn.BCELoss二分类交叉熵损失函数二分类
torch.nn.MarginRankingLoss评价相似度损失
torch.nn.MultiLabelMarginLoss多标签分类损失多标签分类
torch.nn.SmoothL1Loss平滑的L1损失回归
torch.nnSoftMarginLoss多标签二分类问题的损失多标签二分类

实际上这些损失函数的使用非常简单,下面我们将以MSE损失函数来讲解

torch.nn.MSELoss(size_average=None,redece=None,reduction='mean')

其中:

  • size_average: 计算每个batch的均值,否则计算每个batch的和。默认为True,我们实际上可以通过设置reduction来代替该参数的效果
  • reduce: 默认为True,此时计算的损失会根据size_average参数设定是计算每个batch的均值还是和,未来该参数会被弃用
  • reduction: 我们可以指定reduction参数的取值为none、mean、sum来判断计算损失的方式

如果我们指定为mean的话,那么计算的公式如下
L o s s = 1 N ∑ i = 1 i = n ( y p r e d i c t − y r e a l ) 2 Loss=\frac{1}{N}\sum_{i=1}^{i=n}(y_{predict}-y_{real})^2 Loss=N1i=1i=n(ypredictyreal)2

如果我们指定为sum,那么就不会除以N

总之不同的损失函数接受的参数不同,具体需要根据该损失函数而决定

如何使用损失函数

准备工作

我们首先随机初始化两个张量作为我们模型输出的预测值和真实值

y_predict=torch.randn(size=(3,1))
y_real=torch.randn(size=(3,1))
>>>
tensor([[ 0.2308],
        [-0.0103],
        [ 0.5256]])
tensor([[-0.6797],
        [ 0.8049],
        [ 1.3625]])

其次损失函数也是一个类,因此我们首先需要实例化损失函数

lossfunc1=torch.nn.MSELoss()

进行计算

损失函数进行损失计算的时候,我们需要以ypredict,yreal的顺序给定

loss=lossfunc1(y_predict,y_real)
print(loss)
>>>
tensor(0.7313)

Pytorch中的优化器

Pytorch中的optim模块中提供了许多现成的可直接使用的梯度下降算法,例如Adam,SGD,RMSprop等方法

使用这些方法我们无需手动实现,直接调用即可

和torch.nn模块一样,Pytorch将各种优化器抽象成了许多类,下面将进行介绍

名称
torch.optim.AdadeltaAdadelta算法
torch.optim.AdagradAdagrad算法
torch.optim.AdamAdam算法
torch.optim.AdamaxAdamax算法
torch.optim.ASGD平均随机梯度下降算法算法
torch.optim.LBFGSL-BFGS算法
torch.optim.RMSpropRMSprop算法
torch.optim.Rprop弹性反向传播算法
torch.optim.SGD随机梯度下降算法

下面我们将以Adam优化器为例来介绍优化器中的参数

在这里插入图片描述

torch.optim.Adam(params,lr=0.001,betas=(0.9,0.999),eps=1e-8,weight_decay=0)

其中:

  • params:待优化参数的可迭代对象或定义了参数组的dict,通常是model.parameters()
  • lr:学习率,默认为0.001
  • betas:Adam算法中的第一动量和第二动量的保留参数
  • eps:为了避免更新的参数矩阵中的第二动量为0导致分母为0所添加的一个微小的值
  • weight_decay:在损失函数中添加的L2正则项 λ 2 n ∑ i = 1 i = n w i 2 \frac{\lambda}{2n}\sum_{i=1}^{i=n}w_i^2 2nλi=1i=nwi2的系数 λ \lambda λ,默认情况下不会进行weight_decay

如何使用优化器

下面我们将基于Adam优化器讲解如何使用优化器

准备工作

我们首先建立一个测试用的网络来演示优化器的使用,我们定义的测试用的网络接受30个特征的输入,两个隐藏层然后是输出层

接下来实例化这个网络

class TestFullyConnectedNet(nn.Module):
    def __init__(self):
        super(TestFullyConnectedNet,self).__init__()
        #定义网络结构
        self.hidden1=nn.Sequential(nn.Linear(in_features=30,out_features=15),nn.ReLU())
        self.hidden2=nn.Sequential(nn.Linear(in_features=15,out_features=10),nn.ReLU())
        self.predict=nn.Linear(in_features=10,out_features=1)
        
    def forward(self,x):
        x=self.hidden1(x)
        x=self.hidden2(x)
        output=self.predict(x)

testNet=TestFullyConnectedNet()

使用优化器

我们对优化器的使用具有不用的方式,可以为多个层设定统一的学习率,也可以为不同的层定义不同的学习率

因此我们实例化某种优化器的时候就具有不同的方式

初始化优化器

优化器也是类,因此我们也需要和类一样首先对优化器进行初始化

使用统一的学习率

我们使用统一的学习率只需要指定需要优化的对象和学习率即可

通常来说优化的对象是我们的模型中的所有层的参数矩阵.即模型的parameters方法查询的对象

optimizerAdamSame=optim.Adam(testNet.parameters(),lr=0.001)
使用不同的学习率

我们实际上也可以对不同的层来使用不同的学习率,来实现LearnRate Decay的效果

优化器也可以接受元素为字典的列表作为参数,因此我们使用这种方法来实现使用不同的学习率

optimizerAdamDiff=optim.Adam([\
                             {'params':testNet.hidden1.parameters(),'lr':0.0001},\
                             {'params':testNet.hidden2.parameters(),'lr':0.001},\
                             {'params':testNet.predict.parameters(),'lr':0.01}],\
                            lr=0.01)

最后对于我们没有特殊指定的层,学习率都是0.01

进行优化

在训练的过程中我们通常以如下的方式来使用优化器:

  • 使用optimizer.zero_grad方法和optimizer.step方法一起来对参数进行更新
    其中optimizer.zerp_grad方法用于清空梯度,因为Mini-batch一次性使用256张图片的话,单张图片的参数就96MB,考虑到显卡内存性能,因此Pytorch的每次计算的grad不会清零,进行256次单张图片的计算累计起来的梯度进行平均就是一次性使用256张图片进行下降,所以我们需要每次手动清零
    而optimzier.step方法用于每次下降一步,即进行一次更新

所以我们进行一次训练的完整代码如下

optimizerAdam=optim.Adam(testNet.parameters(),lr=0.001)

for input, target in dataset:
    optimizer.zero_grad()
    output=testNet(input)
    loss=lossFunc(output,target)
    loss.backward()
    optimizerAdam.step()

Pytorch中的参数初始化方法

一般来说,我们使用Pytorch默认的参数初始化就能够获得比较稳定的效果

但是有的时候根据我们的需求会使用不同的参数初始化方法,因此我们还是需要掌握参数初始化的方法

在Pytorch的nn模块下的init模块中具有许多参数初始化方法,具体如下

方法功能
nn.init.uniform_(tensor,a=0.0,b=1.0)从均匀分布U(a,b)中生成值填充输入的张量
nn.init.normal_(tensor,mean=0,std=1.0)从给定均值mean和标准差std的正态分布中生成值来填充给定的张量
nn.init.constant_(tensor,val)用val的值来填充输入的张量
nn.init.eye_(tensor)用单位矩阵来填充输入的张量
nn.init.dirac_(tensor)用Dirac Delta函数来填充3、4、5维的张量,该方法可以尽可能多的保存输入通道信息
nn.init.xavier_uniform_(tensor,gain=1.0)使用Glorot initialization方法生成均匀分布的值来填充张量
nn.init.xavier_normal_(tensor,gain=1.0)使用Glorot initialization方法生成正态分布的值来填充张量
nn.init.kaiming_uniform_(tensor,a=0,mode=‘fan_in’,nonlinearity=‘leaky_relu’)使用凯明初始化方法生成均匀分布的值来填充张量
nn.init.kaiming_normal_(tensor,a=0,mode=‘fan_in’,nonlinearity=‘leaky_relu’)使用凯明初始化方法生成正态分布的值来填充张量
nn.init.orthogonal_(tensor,gain=1)使用正交矩阵填充张量进行初始化

如何使用参数初始化方法

由于Pytorch中的参数初始化方法是直接对tensor对象进行操作,而且都是单个的函数,因此我们可以直接调用

但是在工程实践中我们通常根据是只对某一层进行初始化还是对整个模型的参数进行初始化而具有不同的运用技巧

对单个的层进行初始化

上面的参数初始化方法都是直接对tensor进行操作的,因此我们直接调用weight参数即可

conv1=nn.Conv2d(in_channels=1,out_channels=1,kernel_size=(3,3),stride=1,padding=1)
print(conv1.weight)
nn.init.normal_(conv1.weight,mean=0,std=1)
print(conv1.weight)
print(conv1.weight.mean())
print(conv1.weight.std())
>>>
Parameter containing:
tensor([[[[-0.0340, -0.1196,  0.1296],
          [-0.2752, -0.0990,  0.2001],
          [-0.1054, -0.1484,  0.0894]]]], requires_grad=True)
Parameter containing:
tensor([[[[-0.9761,  0.8420,  0.2917],
          [-0.9030, -0.5685,  0.6506],
          [-1.7730,  0.7985,  0.6863]]]], requires_grad=True)
tensor(-0.1057, grad_fn=<MeanBackward0>)
tensor(0.9657, grad_fn=<StdBackward0>)

这里由于我们是从正态分布中随机抽值来初始化参数矩阵的,因此理想状态下均值为0,标准差为1

对整个模型进行初始化

我们实际上在真实的运用中模型可能会有多个层,因此我们进行初始化的时候可以用如下的方式来快速的对整个模型的参数矩阵进行初始化

我们使用如下的网络来进行演示

class TestFullyConnectedNet(nn.Module):
    def __init__(self):
        super(TestFullyConnectedNet,self).__init__()
        #定义网络结构
        self.hidden1=nn.Sequential(nn.Linear(in_features=30,out_features=15),nn.ReLU())
        self.hidden2=nn.Sequential(nn.Linear(in_features=15,out_features=10),nn.ReLU())
        self.predict=nn.Linear(in_features=10,out_features=1)
        
    def forward(self,x):
        x=self.hidden1(x)
        x=self.hidden2(x)
        output=self.predict(x)

testNet=TestFullyConnectedNet()

首先我们定义一个对某一层进行初始化的函数

def init_weight(layer):
    if type(layer)==nn.Conv2d:
        nn.init.normal_(layer.weight,mean=0,std=0.5)
    elif type(layer)==nn.Linear:
        nn.init.uniform_(layer.weight,a=-0.1,b=0.1)
        layer.bias.data.fill_(0.01)

接下来我们调用模型的apply方法来实现对所有层的运用

testNet=TestFullyConnectedNet()
testNet.apply(init_weight)
>>>
TestFullyConnectedNet(
  (hidden1): Sequential(
    (0): Linear(in_features=30, out_features=15, bias=True)
    (1): ReLU()
  )
  (hidden2): Sequential(
    (0): Linear(in_features=15, out_features=10, bias=True)
    (1): ReLU()
  )
  (predict): Linear(in_features=10, out_features=1, bias=True)
)

Pytorch中的LR Decay

Learning Rate Decay指的是我们在训练的不同阶段调整学习率(通常是调小)

我们可以在训练的不同的epoch中使用不同的学习率

Pytorch的的optim的l_schedule模块中提供了诸多LR Decay的策略和方式

他们都是其中的类,使用方法类似,都需要先初始化再使用,后面将会具体介绍如何使用,下面先介绍可用的Decay Strategy

Decay Strategy

下面将介绍一些常用的Decay 策略,首先需要注意的是,下面所有的类都有epoch属性来表示训练的轮次,每训练一轮epoch就会+1

等间隔调整学习率StepLR

StepLR表示学习率按照一定的轮次进行衰减,每次衰减的公式如下
L e a r n i n g R a t e n o w = L e a r n i n g R a t e l a s t × γ LearningRate_{now}=LearningRate_{last}\times \gamma LearningRatenow=LearningRatelast×γ
其中 γ \gamma γ是我们指定的下降的乘法因子

StepLR的函数签名如下

lr_schedule.StepLR(optimizer,step_size,last_epoch=-1)

其中:

  • optimizer是我们前面实例化的优化器

  • step_size是每次进行衰减学习率的轮次间隔

  • last_epoch是开始进行LR Decay的轮次,在此之前都会正常进行Gradient Descend,这个参数本身的意思是上一次使用LR Decay的轮次。通常我们指定为-1,表明是第一轮开始进行LR Decay

    这个参数主要的作用就是帮助我们恢复训练,通常由于训练时候的不可抗力,我们的模型的训练会被打断,例如机器过热保护自动关机、断电等等,这个时候我们对应的方法就是每间隔一定的epoch保存模型,然后遇到意外之后从上一次保存的模型开始恢复训练,而在恢复训练的时候我们指定上一次训练的轮次

    例如我们恢复训练时候指定last_epoch=10,表明我们恢复训练的时候进行的第一次训练使用的lr是整个Schedule的第十次

不等间隔调整学习率MultiStepLR

前面的StepLR每次进行Decay的轮次间的间隔相等,而MultiStepLR可以在训练的不同阶段(非等距)使用不同的的Step

MultiStepLR的函数签名如下

lr_schedule.MultiStepLR(optimizer,milestones,gamma=0.1,last_epoch=-1)

其中:

  • milestones是我们每次下降的轮次,例如我们指定一共训练200轮,而milestones=[50,80,100,130,180]
    则我们在训练的50、80、100、130、180轮会进行Decay

指数衰减调整学习率ExponentialLR

指数衰减学习率调整的公式如下
L e a r n i n g R a t e l a s t = L e a r n i n g R a t e × γ e p o c h LearningRate_{last}=LearningRate\times \gamma^{epoch} LearningRatelast=LearningRate×γepoch

函数签名如下

lr_schedule.ExponentialLR(optimizer,gamma,last_epoch=-1)

其中:

  • gamma是我们指数的底数

余弦退火调整学习率CosineAnnealingLR

余弦退火指的是训练的不同epoch使用的不同的LR是符合余弦公式的排列

具体的公式如下
L R t = L R m i n + 1 2 ( L R m a x − L R m i n ) ( 1 + cos ⁡ ( e p o c h n o w c y c l e ) ) LR_t=LR_{min}+\frac{1}{2}(LR_{max}-LR_{min})(1+\cos (\frac{epoch_{now}}{cycle})) LRt=LRmin+21(LRmaxLRmin)(1+cos(cycleepochnow))
其中LRmin是我们指定的LR的最小值,整个训练过程中LR不会低于这个值,LRmax是本周期内的LR的最大值,训练过程中不会超过这个最大值

epochnow指的本次epoch处于当前周期的轮次,cycle指的是周期,例如我们指定周期为60,那么当前轮次为134次的话,epochnow就是14,cycle就是60

CosineAnnealingLR的函数签名如下

lr_scheduler.CosineAnnealingLR(optimizer, T_max, eta_min=0, last_epoch=-1)

其中:

  • T_max就是cycle
  • etx_min就是LR_min

这里由于使用的是余弦函数,因此给定LR_min就能够计算出

自定义调整学习率LambdaLR

LambdaLR的计算公式如下
L e a r n i n g R a t e n o w = L e a r n i n g R a t e × L a m b d a 表 达 式 Learning Rate_{now}=LearningRate\times Lambda表达式 LearningRatenow=LearningRate×Lambda
其中Lambda表达式是自定义的调整的公式,例如 0.1 × ( e p o c h / / 30 ) 0.1\times (epoch //30) 0.1×(epoch//30),即每30轮学习率会变为原来的0.1倍

LambdaLR的具体函数签名如下

lr_schedule.LambdaLR(optimizer,lr_lambda,last_epoch=-1)

其中:

  • lr_lambda是我们自定义的乘以上一次lr的乘法因子,我们可以传入多个由lambda表达式构成的列表来乘以多个Lambda表达式

例如

optimizerAdam=optim.Adam(testNet.parameters(),lr=0.001,betas=(0.9,0.999),eps=1e-8)

lambdaSchedule=lambda epoch:0.5**(epoch//30)
scheduler=optim.lr_scheduler.LambdaLR(optimizer=optimizerAdam,lr_lambda=lambdaSchedule,last_epoch=-1)

这里我们使用的优化器是Adam优化器,在第三十轮之后开始LR Decay,每30轮Decay一次

如何使用LR Decay

上面提到过,我们首先需要实例化,接下来再每次训练中使用

实例化的时候按照上面的讲解传入参数即可

下面我们将以StepLR为例讲解如何使用LR Decay

LR Decay和Optimizer一样,都是在训练的过程中使用

optimizerAdam=optim.Adam(testNet.parameters(),lr=0.001)
schedulerStep=torch.nn.optim.lr_scheduler.StepLR(oprimizerAdam,step_size=30,gamma=0.1,last_epoch=-1)

for epoch in range(trainingTime):
    train(...)
    validate(...)
    schedule.step()
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值