新闻动态

您当前的位置: 首页 > 新闻动态 > 行业新闻

敢问多任务学习优化算法路在何方?|附代码

作者:佚名 发布时间:2024-04-29 04:29:31 浏览:

本文首发于公众号AI部落联盟,欢迎关注!

上一篇收藏|2021年浅谈多任务学习文章对“多任务学习”(Multi-Task Learning,以下简称MTL)的概念、优势、改进方向进行了全面的介绍,并提到MTL一个重要的改进方向:优化算法的改进。在阅读NeurIPS2020一篇关于多任务学习优化算法改进的文章(PCGrad)后,突然想到:如果回顾一下深度学习基本优化算法改进历史,是否会对MTL优化算法的改进有启发呢?于是便记录一下笔者对优化算法改进的理解以及多任务学习优化算法的脑爆改进

本文包括以下几部分内容:

  1. 神经网络参数优化的基本背景
  2. “时间域”里的优化算法(SGD/SGDM/AdamGrad/Adam等)进化史
  3. “空间域“里的多任务学习优化算法改进PCGrad
  4. “时空“里的MTL优化算法改进狂想曲

?1. 神经网络参数优化的基本背景

假设输入为 X (特征) Y (标签);神经网络的参数为 W ;那么神经网络的输出 Y_{pred} 可以简单表示为:

Y_{pred}=f(X,W)

?一开始神经网络的参数 W 是随机的,那么 Y_{pred} 也是随机的。为了让 Y_{pred} 尽可能接近 Y ,我们需要不断优化 W

如何优化?

我们定义一个优化目标 L(Y_{pred},Y) 简写为 L ,来表示预测与标签之间的差异,比如我们可以简单使用 L=(Y-Y_{pred})^2 来表示标签和预测结果之间的差异。我们希望可以调整 W 来让 Y_{pred}Y 尽可能的接近。于是就有了基本的优化公式:

W=W-\\alpha G_W

其中 \\alpha 为学习率Learning Rate; G_W=\\frac{\\partial L}{\\partial W} 为目标函数对 W 的梯度(Gradient) 。 的具体求解方法(包括链式法则)不在本文范畴,现在假设求得 G_W

L_{MTL}=\\sum L_i

W=W-\\alpha \\sum w_i \\frac{\\partial L_i}{\\partial W}

w_i 代表第 i 个任务的权重,一般来说可以直接让 w_i=1 ;并且当 k=1 的时候,退化为单任务学习

注意上面式子的 W 严格来说是多任务学习里共享的参数 W 而不是所有参数。

为了方便讲述,下文统一将 \\alpha \\frac{\\partial L}{\\partial W} 或者 \\alpha \\sum w_i \\frac{\\partial L_i}{\\partial W} 称为 G'_{W_t} 梯度 。神经网络根据以上公式,按照batch计算梯度并开始迭代更新参数 W

于是对于第 t 次更新:

W_{t+1}=W_t -G'_{W_t}

各类优化算法所重点研究的便是:如何优 G'_{W_t} 的计算使得 W 的调整能够让目标函数 L 有更大的概率,更快的速度到达最优(最小)点。

2. “时间域”里的优化算法(SGD->Adam)进化

本文希望从“时间域“的角度来观察SGD到Adam的进化历史。“时间域”宽泛的指按时间 t 或者说按epoch不断更新神经网络参数。

参数更新流程:

神经网络参数 W 更新一般按照以下步骤进行,对于第 t 个epoch而言:

  1. 计算第目标函数在 t 时刻对神经网络参数 W_t 的梯度: G_{W_t}=\\frac{\\partial{L}}{\\partial{W_t}}
  2. 根据 t 时刻前后的梯度 G_{W_0},G_{W_1}...G_{W_t}, G_{W_{t+1}} 来优化 t 时刻的梯度得到 G_{W_t}^{good}
  3. 将优化后的 G_{W_t}^{good} 乘上学习率 \\alpha 得到 G'_{W_t}=\\alpha G_{W_t}^{good}
  4. 更新梯度: W_{t+1}=W_t -G'_{W_t}

那我们根据以上步骤概括一下“时间域”里的优化算法改进。

SGD

最简单的版本,第2个步骤仅仅使用 G_{W_t} ,直接使用当前梯度对参数进行更新。

SGD以后的优化引入了一阶动量和二阶动量的概念。具体为:

SGD Momentum

引入一阶动量的概念,不仅考虑当前时刻的 G_{W_t} 还考虑了时刻以前的的梯度,对于历史的梯度使用方式(指数滑动平均)如下:

m_t=\\beta * m_t + (1-\\beta)G_{W_t},G_{w_t}^{good}=m_t

指数滑动平均得到的 m_t 约等于过去 \\frac{1}{1-\\beta} 时刻内的平均梯度。使用历史的梯度信息对当前时刻梯度进行平滑之后,一定程度上比只使用当前时刻或者只使用全部历史的梯度信息更好。

SGD Nesterov Accerleration
优化点在于步骤1,改变为 G_{W_t}=\\frac{\\partial{L}}{\\partial{(W_t-G'_{W_{t-1}})}} ,意思是:不使用当前时刻的梯度,而是假设参数先更新一步,再算出来一个未来的梯度,然后再进行步骤2,并结合以前的历史梯度信息来更新梯度。这也是步骤2里面有一个 G_{W_{t+1}} 的原因。

AdaGrad
优化点在步骤1,不仅考虑参数的历史的梯度信息(SGD Momentum),还考虑历史梯度出现频繁程度(二阶动量) v_t=\\sum{G_{W_t}^2} ,一个参数的梯度在历史上出现的次数越多 v_t 越大。最终 G_{w_t}^{good}=\\frac{m_t}{\\sqrt{v_t}} 。在 t 时刻以前出现次数约多,则梯度小,出现次数少的参数,在 t 时刻的梯度就大一点。

AdaDelta/RMSProp
优化点在步骤2,把时间窗口,滑动平均的概念融合到 v_t 上, v_t=\\beta v_{t-1}+ (1-\\beta G_{W_t}^2) ,避免二阶动量一直相加越来越大。

Adam

既考虑一阶动量,也考虑二阶动量:

m_t=\\beta_1 * m_t + (1-\\beta_1)G_{W_t}, v_t=\\beta_2 v_{t-1}+ (1-\\beta_2 G_{W_t}^2)

G_{w_t}^{good}=\\frac{m_t}{\\sqrt{v_t}}

Nadam

把SGD Nesterov Accerleration再结合Adam就是Nadam了。


到目前为止不难看出,整个经典深度学习优化算法的进化历史,都是围绕“时间域”这个维度在进行的:

那么对于多任务学习,怎么样结合当前时刻 的梯度和历史梯度、假象的未来梯度,甚至是当前时刻多任务学习特有的梯度特点,让 时刻的梯度更优秀?下一章先谈谈如何结合多任务学习的特点对当前时刻的梯度进行改善。

3. "空间域“里的多任务学习优化算法改进--PCGrad

有了第1部分多任务学习的参数(重点指共享参数)优化公式 W_{sh}=W_{sh}-\\alpha\\sum{w_i \\frac{\\partial{L_i}}{\\partial{W}}} ,第2部分在 t 时刻的参数优化步骤和,我们开始谈一谈PCGrad是如何在“空间域”上对优化算法进行改进的。

将第2部分的优化流程结合上PCGrad:

对于每一个btach,也就是在第 t 个epoch:

1. 计算目标函数在 t 时刻对神经网络参数 W_t 的梯度: G_{W_t}=\\frac{\\partial{L}}{\\partial{W_t}}

1.1 先分别计算每个目标函数对参数 W_t 的梯度 G^i_{W_t}

1.2 然后两两梯度之间看是否存在冲突

1.2.1如果存在冲突,则通过向量正交 映射减缓冲突

1.2.2 如果不存在冲突,不变

1.3 多个任务梯度求和得到 t 时刻参数的梯度 G_{W_t}^{good}

2. 根据 t 时刻前后的梯度来优化 t 时刻的梯度得 G_{W_t}^{good}

3. 将优化后的 G_{W_t}^{good} 乘上学习率 \\alpha 得到 G'_{W_t}=\\alpha G_{W_t}^{good}

4. 更新梯度: W_{t+1}=W_t -G'_{W_t}

PCGrad特点:

  1. PCGrad的改进位置:步骤1。从“空间”上优化对于当前时刻的梯度 G_{W_t} 计算。这里的“空间”广泛的指多任务学习对共享参数会有不同方向的梯度(多维梯度向量的方向不一样)。
  2. 改进的出发点是:在 t 时刻,多任务学习里多个目标对于共享参数 W_{sh} 的梯度可能存在冲突。因为存在多个监督信息,那么多个监督信号可能想让共享参数往不同的方向调整。
  3. 具体改进是:将冲突减小,不存在冲突的梯度保持不变。当然论文中还有很多理论分析和证明,这里就不一一描述了。

为了让冲突变小,我们首先需要知道什么是冲突(这个冲突是作者自己定义的),然后想办法将冲突减小。简单来讲,不同任务对于参数的梯度冲突和不冲突的梯度是这个样子的(a和d):

图1 PCGrad梯度冲突示意图

如图1a,任务 i 和任务 j 的梯度存在冲突。在 t 时刻,这两个任务的梯度在多维空间里指向了不同的方向(且角度大于90度,相当于两个向量合成的新向量的时候会相互抵消一部分,如果一个任务的梯度幅度远大于另一个任务的梯度幅度,那合成向量的方向将会直接变成了大梯度那个任务所要调整的方向了)。那么解决冲突的方式如图1b,c所示,把 g_ig_i 的正交向量上映射,把 g_jg_i 上的正交向量上映射。简单理解为:把朝向两个不同方向的向量往各自正交向量上折中一下,大家互相抵消也就小一些了。

结合前面的流程和图示,看到整个流程是不是觉得其实并不难?而且也很符合直觉。这也是笔者分享该方法的原因,但该算法还有改进的地方吗?有的,把该算法结合上“时间域”的改进,也就是下一章:“时空“里的MTL优化算法改进狂想曲,具体说说咱还能做的地方。

另外,看完原作者开源的tensorflow的写法:PCGrad源代码,感觉不太习惯tensorflow的调试,因此本文在这里也分享一种比较容易看懂的pytorch写法方便大家上手:改一改深度学习参数优化过程的命脉--梯度

def PCGrad_backward(net, optimizer,
                     X, y, 
                     loss_layer=nn.CrossEntropyLoss()):
    # net:训练的神经网络
    # optimizer 优化器比如Adam
    # X:神经网络d的输入
    # y:标签
    # loss_layer 目标函数
    num_tasks=len(y)  # T 任务数量
    grads_task=[]#用来保存多个任务的梯度
    losses=[]
    grad_shapes=[p.shape if p.requires_grad is True else None
                   for group in optimizer.param_groups 
                   for p in group['params']]#获取梯度的shape
    grad_numel=[p.numel() if p.requires_grad is True else 0
                  for group in optimizer.param_groups 
                  for p in group['params']]#获取梯度的参数数量    
    optimizer.zero_grad() #t时刻优化前将梯度清零    
    # 针对每个任务先分别计算梯度
    for i in range(num_tasks):
        result=net(X)
        loss=loss_layer(result[i], y[i])
        losses.append(loss)
        loss.backward()
        devices=[p.device for group in optimizer.param_groups 
                            for p in group['params']]
        grad=[p.grad.detach().clone().flatten() 
                  if (p.requires_grad is True 
                    and p.grad is not None)
                  else None 
                for group in optimizer.param_groups 
                for p in group['params']]
?
        # 对于当前任务该参数没有梯度,任务specific的参数,梯度设置为0
        grads_task.append(torch.cat(
[g if g is not None else torch.zeros(
            grad_numel[i], device=devices[i]) 
            for i, g in enumerate(grad)]))
        optimizer.zero_grad()
?
    # 由于我们要在任务间两两配对看梯度是否冲突,先整体shuffle一下
    random.shuffle(grads_task)
?
    # 开始梯度冲突消除
    grads_task=torch.stack(grads_task, dim=0)  # (T, # of params)
    proj_grad=grads_task.clone()
?
    def _proj_grad(grad_task):
        for k in range(num_tasks):
            inner_product=torch.sum(grad_task*grads_task[k])
            proj_direction=inner_product / (torch.sum(
                grads_task[k]*grads_task[k])+1e-12)
            grad_task=grad_task - 
            torch.min(
                proj_direction, torch.zeros_like(proj_direction)) 
                * grads_task[k]
        return grad_task
    # 减缓多个任务对应的梯度冲突并最后合并多个任务对应的梯度。
    proj_grad=torch.sum(torch.stack(
        list(map(_proj_grad, list(proj_grad)))), dim=0)  # (of params, )
    # 根据梯度的shape还原梯度。
    indices=[0, ]+[v for v in accumulate(grad_numel)]
    params=[p for group in optimizer.param_groups 
                for p in group['params']]
    assert len(params)==len(grad_shapes)==len(indices[:-1])
    #把减缓了冲突d的梯度放回参数中,之后便可以进行进行梯度下降了
    for param, grad_shape, start_idx, end_idx in zip(params, grad_shapes, indices[:-1], indices[1:]):
        if grad_shape is not None:
            param.grad[...]=proj_grad[start_idx:end_idx].view(grad_shape)  # copy proj grad
    #输出losses 方便查看
    return losses

完整的例子参考:Pytorch-PCGrad

4. “时空“里的MTL优化算法改进狂想曲

再次放上基本优化流程+PCGrad:

对于每一个btach,也就是在第 t 个epoch:

1. 计算目标函数在 t 时刻对神经网络参数 W_t 的梯度: G_{W_t}=\\frac{\\partial{L}}{\\partial{W_t}}

1.1 先分别计算每个目标函数对参数 W_t 的梯度 G^i_{W_t}

1.2 然后两两梯度之间看是否存在冲突

1.2.1如果存在冲突,则通过向量正交 映射减缓冲突

1.2.2 如果不存在冲突,不变

1.3 多个任务梯度求和得到 t 时刻参数的梯度 G_{W_t}^{good}

2. 根据 t 时刻前后的梯度来优化 t 时刻的梯度得 G_{W_t}^{good}

3. 将优化后的 G_{W_t}^{good} 乘上学习率 \\alpha 得到 G'_{W_t}=\\alpha G_{W_t}^{good}

4. 更新梯度: W_{t+1}=W_t -G'_{W_t}

咱再看一下这个PCGrad流程,发现仅仅在第1步计算梯度的时候对冲突进行改善。那结合咱们的SGD-》Adam的改进历史来看看PCGrad还有哪些地方没有可以探讨探讨的(下文为笔者脑爆时间,如果存在不合理不正确指出望读者见谅,也希望读者能一起思考)

咱们结合“时间”“空间“的改进来一波排列组合:

PCGrad Momentum

引入“梯度冲突”的一阶动量,也就是考虑了最近一段时间的历史梯度信息,看看多任务学习里不同任务对于共享参数在历史梯度里是否一直在发生冲突。如果是?咱们的PCGrad虽然在每个 t 时刻进行了修正,但“梯度冲突“是否也会累积呢?冲突累积多了是不是还是存在使用PCGrad之前的问题?

PCGrad Nesterov Accerleration
提前预测下一个时刻是否存在“梯度冲突”来帮助一下当前时刻?

AdaPCGrad
不仅仅考虑 t 时刻以前以及当前的“梯度冲突“,还要考虑“梯度冲突”发生的频繁程度(二阶动量)。如果两个任务之间“梯度冲突”的频率很高,应该如何是好?发生“梯度冲突”的频率低又应当如何?

AdaDelta/RMSProp PCGrad
把时间窗口,滑动平均的概念融合到“梯度冲突”二阶动量中去,看看最近一段时间的“梯度冲突”。

Adam PCGrad

既考虑“梯度冲突”的一阶动量,也要考虑二阶动量。

最后,“梯度冲突”反应的是两个任务之间的不相关性,那么“梯度契合”是不是可以反应两个任务之间的相关性?也可以把上面的流程走一遍,思考一遍。

以上便是笔者对于多任务学习优化算法的一点脑爆想法,由于笔者水平有限,不免有错误之处。欢迎读者朋友们批评指证!如果对您的研究思考有一丝启发,欢迎点赞哦~谢谢

公众号公式和知乎居然不能直接兼容。这公式敲的我 。

参考文献:

1. ruder.io/optimizing-gra

2. arxiv.org/abs/2001.0678

3. zhuanlan.zhihu.com/p/32

点个赞,你最好看

推荐阅读:
多多笔记:收藏|浅谈多任务学习(Multi-task Learning)

2021年如何科学的“微调”预训练模型?
敢问多任务学习优化算法路在何方?|附代码
多多笔记:国内较强的NLP高校实验室有哪些?
总结|开放领域问答梳理系列(1)
收藏|开放领域问答梳理2
一文看懂贝叶斯优化/Bayesian Optimization


 

Copyright © 2012-2018 蓝狮在线-蓝狮注册陶瓷制品站  备案号:琼ICP备9527188号

搜索

平台注册入口