Zhangzhe's Blog

The projection of my life.

0%

基本概念

  • 稀缺性:社会资源是有限的。
  • 经济学:研究社会如何管理自己的稀缺资源。

十大原理

1. 人们面临权衡取舍

  • 效率:社会能从其稀缺资源中得到最大利益的特性。
  • 平等:经济成员在社会成员中平均分配的特性。
    在某种程度上,效率平等 是互斥的,平等会损害社会效率。

2. 某种东西的成本是为了得到它所放弃的东西

  • 机会成本:为了得到某种东西必须放弃的东西。
    例如:上大学的 机会成本 不只是学费、生活费,还有四年时光。

3. 理性人考虑边际量

  • 理性人:系统而有目的地尽最大努力实现其目标的人。
  • 边际变动:对行动计划的微小增量调整。
    简单理解就是需要更关注变化量而不是价值本身。例如:航空公司一趟航行的总成本均摊到每个座位上是 500 美元,如果起飞前还有十个座位,那么以 100 美元 / 座位的价格卖出也是合理的,原因是:此时增加的飞行成本远低于卖出价格。

4. 人们会对激励做出反应

  • 激励:引起一个人做出行动的某种东西。

5. 贸易可以使每个人的状况都变得更好

6. 市场通常是组织经济活动的一种好方法

  • 市场经济:当许多企业和家庭在物品和服务市场上相互交易时,通过他们的分散决策配置资源的经济。也就是亚当斯密口中的 “看不见的手”。

7. 政府有时可以改善市场结果

  • 产权:个人拥有并控制稀缺资源的能力。
  • 市场失灵:市场本身不能有效配置资源的情况。
  • 外部性:一个人的行为对旁观者的福利产生了影响。例如:环境污染。
  • 市场势力:单个经济活动者(或某个经济活动小群体)对市场价格有显著影响到能力。例如:垄断。
    政府在经济中的作用包括:实施产权制度;在市场失灵时重新配置资源。

8. 一国的生活水平取决于它生产物品和服务的能力

  • 生产率:每单位劳动投入所生产的物品和服务数量。

9. 当政府发行了过多货币时,物价上升

  • 通货膨胀:经济中物价总水平的上升。

10. 社会面临通货膨胀与失业之间的短期权衡取舍

  • 经济周期:就业和生产等经济活动的波动。

分类

  • 人们如何做出决策
    1. 人们面临权衡取舍
    2. 某种东西的成本是为了得到它所放弃的东西
    3. 理性人考虑边际量
    4. 人们会对激励做出反应
  • 人们如何相互影响
    5. 贸易可以使每个人的状况都变得更好
    6. 市场通常是组织经济活动的一种好方法
    7. 政府有时可以改善市场结果
  • 整体经济如何运行
    8. 一国的生活水平取决于它生产物品和服务的能力
    9. 当政府发行了过多货币时,物价上升
    10. 社会面临通货膨胀与失业之间的短期权衡取舍

URL

TL;DR

  • 典型量化算法(如:DoReFaPACTLSQ)对 feature 的量化通常使用无符号量化(负半轴量化到零点):
    • 这种量化算法适用于使用 Relu 激活函数的网络。
    • 但对使用新式激活函数(如:swish, H-swish, Mish, Leaky-ReLU)的网络(如:EfficientNetMixNet)会造成较大的量化误差。
    • LSQ+ 作为一种非对称量化算法可以学习 scaleoffset,去适应需要负数激活函数的网络。
  • 量化网络训练收敛较难,所以 LSQ+ 提出了一种比较高效的 scaleβ\beta 初始化方法:MSE 最小误差初始化方法优于最大最小值。

Algorithm

1. LSQ 算法存在的问题

  • LSQ 公式
    xˉ=clamp(xs,n,p)\bar{x} = \lfloor clamp (\frac{x}{s}, n, p)\rceil
    x^=xˉ×s\hat{x} = \bar{x} \times s
  • LSQ 对例如 Leaky-ReLUSwish 这种存在负半轴的激活函数量化有两种方法:
    • 截断负半轴,即 n=0, p=2b1n=0,\ p=2^b-1,显然降低模型表现能力
    • 正负半轴相同尺度量化,即 n=2b1, p=2b11n=-2^{b-1},\ p=2^{b-1}-1,由于正半轴的信息量远高于负半轴,同尺度量化会增大正半轴的量化误差

2. LSQ+ 的解决方案

  • LSQ+ 的改进主要包含两个方面:
    • LSQ 设置了可学习的参数 scale 的基础上,在 activation 量化上weight 量化没有 offset)增加了另外一个可学习参数 offset
    • 对网络初始化的方法进行了改进

2.1 增加可学习 offset 参数

  • LSQ+ 公式
    xˉ=clamp(xβs,n,p)\bar{x} = \lfloor clamp (\frac{x-\beta}{s}, n, p)\rceil
    x^=xˉ×s+β\hat{x} = \bar{x} \times s + \beta
  • s 的梯度计算:
    x^s\frac{\partial\hat{x}}{\partial{s}} = xˉss+xˉ{xβs+xβs,if n<xβs<pn,if xβs<np,if p<xβs\frac{\partial\bar{x}}{\partial{s}}s + \bar{x} \simeq \begin{cases} -\frac{x-\beta}{s}+\lfloor\frac{x-\beta}{s}\rceil &,if\ n < \frac{x-\beta}{s} < p\\ n&, if\ \frac{x-\beta}{s} < n\\ p&,if\ p<\frac{x-\beta}{s}\end{cases}
  • β\beta 的梯度计算
    x^β=xˉβ+1{0,if n<xβs<p1,otherwise\frac{\partial\hat{x}}{\partial{\beta}}=\frac{\partial\bar{x}}{\partial{\beta}}+1\simeq \begin{cases} 0&,if\ n<\frac{x-\beta}{s} <p\\ 1&,otherwise\end{cases}
  • 加入了 β\beta 参数后,对存在负半轴的激活函数进行非对称量化将几乎没有额外开销
    w^x^=(wˉ×sw)(xˉ×sx+β)=wˉxˉswsx+βswwˉbias\hat{w}\hat{x}=(\bar{w}\times s_w)(\bar{x}\times s_x + \beta)=\bar{w}\bar{x}s_ws_x+\begin{matrix}\underbrace{\beta s_w\bar{w}}\\ bias\end{matrix}

2.2 更合理的 scale 和 β\beta 参数初始化方法

  • 低比特量化神经网络训练的最终性能与参数初始化方法关联性较大,这在深度可分离卷积网络中更为明显(例如: MobileNet系列)
2.2.1 weight 量化的 scale 参数初始化方法
  • LSQ 对于对称量化的 weightscale 初始化公式是:sinit=2<w>ps_{init} = \frac{2<|w|>}{\sqrt{p}}
  • 作者认为这样的 scale 初始化方法会导致初始 scale 太大,所以 LSQ+ 对 weight 对称量化的 scale 初始化方法是:sinit=max(μ3×σ,μ+3×σ)2b1s_{init}=\frac{max(|\mu-3\times\sigma|, |\mu+3\times\sigma|)}{2^{b-1}}
    • 其中,μ, σ\mu,\ \sigma 分别表示本层权重的均值和标准差

由于 weight 是对称量化,所以不需要 β\beta 参数

2.2.2 feature 量化的 scale 和 β\beta 参数初始化方法
  • 一个最理想的量化方式是 x 只被量化,没有被 clamp,因此根据 LSQ+ 的量化公式可知:
    • xminβinitsinitn, xmaxβinitsinitp\frac{x_{min}-\beta_{init}}{s_{init}}\rightarrow n,\ \frac{x_{max}-\beta_{init}}{s_{init}}\rightarrow p
    • 化简后:sinit=xmaxxminpn, βinit=xminn×sinits_{init}=\frac{x_{max}-x_{min}}{p-n},\ \beta_{init} = x_{min} - n \times s_{init}
    • 但是,这种完全不 clamp 的操作容易被离群点干扰,所以 sinit, βinits_{init},\ \beta_{init} 还是使用量化前和反量化后的数据最小 MSE loss 来确定,即:sinit, βinit=argmins,βx^xF2s_{init},\ \beta_{init}=argmin_{s,\beta} ||\hat{x}-x||^2_F

Thought

  • 量化算法中多一个可以被硬件无痛吸收的自由度自然是好事

URL

TL;DR

  • 本文提出一种量化算法 LSQ,旨在通过学习的方式确定每一层量化的 scale
  • LSQ 量化算法比基于统计确定 scale 的量化算法的(例如:DoReFa-Net)效果好

Algorithm

量化和反量化过程

  • 量化过程:vˉ=clip(v/s,QN,Qp)\bar{v} = \lfloor clip(v/s, -Q_N, Q_p) \rceil
  • 反量化过程:v^=vˉ×s\hat{v} = \bar{v} \times s
    其中:
  • vv 表示原始数据(weight / feature)
  • ss 表示量化 scale
  • \lfloor \rceil 表示四舍五入(round)
  • QN, QP-Q_N,\ Q_P 分别表示量化上下界,通常来说:
    • 对于无符号整形量化数据:QN = 0, QP=2b1Q_N\ =\ 0,\ Q_P = 2^{b} - 1
    • 对于有符号整形量化数据:QN = 2b1, QP=2b11Q_N\ =\ 2^{b-1},\ Q_P = 2^{b-1} - 1
  • vˉ\bar{v} 表示量化后的数据
  • v^\hat{v} 表示反量化(×s\times s)后的数据
    lsq1.png

通常反量化会移到后面做,如上图中的 sx, sws_x,\ s_wwˉ @ xˉ\bar{w}\ @\ \bar{x} 之后才会乘上去

对 scale 的梯度定义

v^s={v/s+v/s,if QN<V/s<QPQN,if v/sQNQP,if v/sQP\frac{\partial\hat{v}}{\partial s} = \begin{cases} -v/s+ \lfloor v/s \rceil, & if\ -Q_N < V/s < Q_P\\ -Q_N, & if\ v/s \le -Q_N \\ Q_P, & if\ v/s \ge Q_P\end{cases}

对量化区间外的部分有非常高的梯度,因为 clip 之后原始信息全部丢失。所以需要较大惩罚(梯度)。

  • v^v={1,if QN<v/s<QP0,otherwise\frac{\partial\hat v}{\partial v}=\begin{cases} 1, & if\ -Q_N<v/s<Q_P \\ 0, & otherwise\end{cases}
    lsq2.png

量化前数据和反量化后的数据关系
lsq3.png
量化前数据和 scale 梯度的关系(有效将数据集中在量化点附近,减小量化损失)

  • scale 的初始值 2<v>QP\frac{2<|v|>}{\sqrt{Q_P}},其中 <v><|v|> 表示输入的绝对值均值。

对 scale 的梯度上加的权重

  • 训练中会涉及到 scale 的梯度、weight 的梯度和 feature 的梯度,需要均衡三个梯度,所以需要在weight 的梯度和 feature 的梯度上乘上两个因子进行平衡。
  • R=sLs/wLwR = \frac{\nabla_sL}{s} / \frac{||\nabla_wL||}{||w||},需要 R 尽可能接近 1
  • 经过数学推理和实验,得到两个权重因子 gw, gfg_w,\ g_f 分别表示 weight 梯度权重和 feature 的权重梯度。
  • gw=1NWQPgf=1NFQPg_w = \frac{1}{\sqrt{N_WQ_P}}\\ g_f = \frac{1}{\sqrt{N_FQ_P}},其中 NW,NFN_W,N_F 分别表示 number of weightnumber of feature

一些实验 trick

  • 越低位宽需要越低的 weight_decay 系数
  • cosine learning rate decay 可以涨点
  • 使用 float 模型去蒸馏可以涨点

Through

  • 学出来的 scale 确实比统计出来的 scalemake sense ,毕竟 QAT 给了学习的机会,就多给些可学习的参数是有道理的。

URL

TL;DR

  • GPT (Generative Pre-Training) 是一种基于 transformer decoder 结构的语言预训练模型,和视觉预训练模型不同的是使用没有 label 的文本数据做自监督预训练训练。
  • GPT 训练主要分成两个步骤:
    1. 在无标注数据上做自监督预训练,具体训练目标函数是:根据前 k 个词预测下一个词
    2. 在有监督数据集上做有监督微调,具体微调目标函数是:任务相关预测 + 根据前 k 个词预测下一个词
  • 由于 transformer decoder 中存在 mask,所以不像 Bert 可以根据前后文推理中间缺失的词(完型填空任务),GPT 只能使用根据前文推后文的方法,因此也更适合做文本生成任务
  • 预训练好的模型在下游任务上微调时,只需要对网络输出头做较小的修改即可适配,效果很好。

Algorithm

  • 发展历程: Transformer -> GPT -> Bert -> GPT2 -> GPT3 -> GPT4

example

GPT 模型擅长文本生成,因此以文本生成举例:输入"Hello, world!" 要求生成后续内容。

1. tokenize

2. token embeding

  • 因为 id 串不是神经网络可以处理的格式,所以需要 id 的向量化
  • token embedding 过程本身也是一个查表过程,表格的大小是 vocabulary_size * embedding_sizeGPT-2vocabulary size == 50257embedding size == 768
  • 查表后得到一个 4 * 768token embedding

3. position embeding

  • 给每个 token id 顺序编码一个 position id, 因此 position id[0, 1, 2, 3]
  • 然后将 position id 变成模型可识别的向量,也是一个查表过程,表格本身是可以通过梯度下降学习的,表格的大小是 max_seq_len * embedding_sizemax_seq_len 表示 最大支持的上下文长度GPT-2 中为 1024
  • position embedding 只用在 transformer 第一层之前

4. 生成输入 hidden states

  • token embedding + position embedding = hidden states
  • hidden statesshape = 4 * 768

5. Casual Decoder Layer

  • 这个是 GPT 系列模型的核心,是一种因果注意力机制
  • 因果注意力(Casual)是指每个 hidden state 只能关注之前位置的 hidden state,而不能关注之后的
  • 例如: Hello, world!seq_len == 4,那么注意力矩阵是 4x4,但此矩阵需要是下三角矩阵,即 “,” 只能关注 Hello,而不能关注 world

6. Casual Decoder Layer 输出的 hidden states 再输入下一层 Casual Decoder Layer

  • 输出的 hidden states 和输入的 hidden statesshape 保持一致
  • 再输入下一层 Casual Decoder LayerGPT-212

7. 最后一层输出的 hidden states 的最后一个 hidden state 用于分类

  • 经过重复 12 层的 Casual Decoder Layer,最后输出 hidden states (shape = 4 x 768)
  • 取最后一维 hidden state (shape = 1 x 768) 通过一层或多层 fully connect 映射到 vocabulary size
  • vocabulary size 上取 argmax 得到最大概率的下一个词

8. 合并预测到的词到语句末尾

  • 如果预测的词不是结束符,且语句总长小于 max_seq_len,则将预测得到的词加到原语句末尾
  • 加完之后的长度为 5 的语句继续通过 12Casual Decoder Layer 去预测下一个词
  • 重复直到结束符或 max_seq_len

model architecture

  • token embedding
    gpt2-token-embeddings-wte-2.png
  • postion embedding
    gpt2-positional-encoding.png
  • model architecture
    gpt2-input-embedding-positional-encoding-3.png

Unsupervised pre-training

  • 使用标准语言建模(用前面的词预测下一个词)目标来最大化如下的似然函数:
    目标函数: L1(U)=ilogP(uiuik,...,ui1Θ)L_1(U)=\sum_ilogP(u_i|u_{i-k},...,u_{i-1} | \Theta)
    其中:U 表示 token 的上下文向量,k 为窗口大小, Θ\Theta 为模型参数
  • 模型使用 Transformer decoder 结构,整体计算流程可简写为:
    • h0=UWe+Wph_0=UW_e+W_p
    • hl=transformer_block(hl1),   l[1,n]h_l=transformer\_block(h_{l-1}),\ \ \ \forall l\in[1,n]
    • P(u)=softmax(hnWeT)P(u)=softmax(h_nW_e^T)
      其中: n 表示 transformer 层数,U 表示上下文 tokenWeW_e 表示 tokenembedding 矩阵, WpW_p 表示 position embedding 矩阵

Supervised fine-tuning

  • 有监督 fine-tuning 使用的数据可表示为:
    • 输入 x1,x2,...,xmx_1, x_2, ..., x_m
    • 标签 y
  • 预测 P(yxi,...,xm)=softmax(hlmWy)P(y|x_i,...,x_m)=softmax(h_l^mW_y) 这里使用 最后一层的最后一个 token 对应的输出
  • 因此,有监督目标函数为: L2(C)=(x,y)logP(yx1,...,xm)L_2(C)=\sum_{(x,y)}logP(y|x_1,...,x_m) ,其中 C 表示 fine-tuning 使用的有标签数据集
  • 作者发现在 fine-tuning 阶段,加上无监督损失函数效果会更好,即: L3(C)=L2(C)+λL1(C)L_3(C)=L_2(C)+\lambda * L_1(C)

Task-specific input transformations

  • 本节讨论的是在 fine-tunning 以及 inference 阶段如何构造模型输入
    GPT.png

  • 上图举例了四个常见 NLP 任务:

    • 分类:图中的 StartExtract 分别是两个保留的特殊 token,分别表示输入的开始和输出特征的抽取(linear 层只输入 transformer 最后一层的最后一个 token 的输出,因此在最后填充一个 Extract 特殊标记 token)

    • 蕴含:用于分析 前提(premise) 是否蕴含 假设(hypothesis),本质是一个 蕴含 / 不蕴含 / 不确定 三分类问题,中间的 Delim 是特殊标识 token,表示分割含义

    • 相似:用于分析两个句子是否具有相似关系,由于相似具有对称性,所以需要构造两种顺序的输入,本质是二分类

    • 多选:其中 context 包含上下文文本和问题,需要针对每个答案构建一个 contextanswer 对,最后用 softmax 计算 answer 概率分布

Thought

  • GPT 使用了 Transformer decoder,由于 mask 的存在,无法知道之后的句子,因此选择了比 Bert 更难的标准语言建模(根据前 k 个词预测下一个词)的代理任务,对应的模型无监督预训练效果上限也相应提高。
  • 由于 Bert 使用的 Transformer encoder,因此可以看到句子的上下文,因此 Bert 使用了较为简单的完形填空无监督目标函数。
  • Bert 晚于 GPT,借鉴了很多 GPT 的思想,效果也优于 GPT,但 GPT 开启了语言模型无监督预训练先河,有可能成为 AIGC 的原型机。

URL

TL;DR

  • 本文提出一种类别增量学习算法,在类增量学习中同时学习分类和特征表示,最后得到一个表征器 + 部分旧任务的少量典型样例,缓解灾难性遗忘的问题。
  • 本文对类别增量学习进行了定义,同时满足如下三条规则的学习任务才是一个类别增量学习:
    • 当新的类别在不同时间出现,它都是可训练的。
    • 任何时间都在已经学习过的所有类别中有很好的分类效果。
    • 计算能力与内存应该随着类别数的增加固定或者缓慢增长。
  • 本文中表征学习使用的是 NME(Nearest-Mean-of-Exemplars)算法,而不是使用神经网络直接分类。

Algorithm

icarl_1.png

  • 本算法使用最近邻表征中心法进行分类,而不是使用神经网络传统的 argmax、sigmoid 分类,因为后者更容易引入灾难性遗忘。
    icarl_2.png
  • 类别增量学习的核心:1 ~ s-1 表示旧任务,s ~ t 表示新任务,K 表示可缓存的最大旧任务样本数量,P 表示已缓存的旧任务样本集,具体步骤如下(重复直到所有任务训练完):
    1. 使用新任务样本 Xs,...,XtX^s,...,X^t 和旧任务缓存样本 P\mathcal{P} 联合起来使用 NME 分类方法训练模型参数。
    2. 由于任务数变多,所以需要重新计算每个任务可以缓存的最大样本数 m=Ktm = \frac{K}{t}
    3. 将之前选的旧任务(1 ~ s)的缓存样本减少,每一类减少到 m 个样本(根据到样本中心的距离排序,取 top m)。
    4. 对新任务 (s ~ t) 逐类样本进行采样,根据取出剩余样本到样本中心的距离排序,逐一取样本直到取到 m 个。
      icarl_3.png
  • gy(x)=11+exp(wyTφ(x)), y1,...,tg_y(x) = \frac{1}{1 + exp(-w_y^T\varphi(x))},\ y\in{1,...,t},其中 φ(x)\varphi(x) 表示表征输出。
  • 最终的 Loss 是新任务上的分类 Loss + 旧任务上的蒸馏 Loss(注意蒸馏 Loss 的写法)。
    icarl_4.png
  • 对新任务 (s ~ t) 逐类样本进行采样,根据取出剩余样本到样本中心的距离排序,逐一取样本直到取到 m 个。
    icarl_5.png
  • 将之前选的旧任务(1 ~ s)的缓存样本减少,每一类减少到 m 个样本(根据到样本中心的距离排序,取 top m)。

Thought

  • 论文写的很好,公式表达十分清晰,而且还开源了,比 DGR 不知道高到哪里去了…

2022年6月14日中午,听闻了孙老师意外离世的消息,愕然!
在我的印象中,孙老师是一个高高壮壮、有些腼腆的学者。在旷视的一年和孙老师接触仅限于在电梯里的几次照面,我只是一个 nobody,所以没有打过招呼。
记得有一次,我同时和印奇老板、孙剑老师同时进入电梯,印奇老板询问孙老师一个事情的进度,态度和蔼,但孙老师瞬间脸就红了,然后尴尬的微笑着简单地汇报了进度,能看出非常的不自在。我事后给女友讲起此事,感叹人间真实,曾经的学生变成了自己的老板,尴尬自然是正常的。
对孙老师形成直观的印象来自于一封内部信,应该是 2021 新年伊始,孙老师发内部信总结公司的 2020 并展望 2021,关于公司的战略规划,我兴趣不大,只是隐约明白了我还有活干。我感兴趣的点在于孙老师对自己生活的分享,他说父母经常会有自己的小孩一定会和自己一样优秀的错误幻想,但实际上他自己作为一个名声响彻 ai 界的大牛,女儿小学数学竟然不及格,他每天都在辅导女儿打败数学大魔头。他还分享了自己练习长跑的心得,刚开始跑几百米就气喘吁吁,后面每天练习,日复一日,终于可以一口气跑 10 km,相信自己,贵在坚持,总能战胜自己。
谁能想到,最终他引以为傲的长跑夺走了他年轻的生命。突发性心肌梗死带走了 45 岁的他,从此女儿没有了父亲,妻子没有了丈夫,旷视研究院没有了孙院长,学界少了一位孙老师。
是的,朋友圈刷屏了,满眼都是对他的哀悼和纪念,甚至旷视 CEO 印奇也发文:“一定要完成孙老师未竟的事业”,但是又如何?怕不过一周之后,不会再有人感叹他的英年早逝、不会再有人感叹天妒英才,人总要向前看。尘归尘,土归土,只留给他的家人无尽的痛苦。
他无疑是成功的,拥有 100 多篇顶级学术论文,多少研究人员终其一生也难中一篇。我来自农村,能一步一步走到现在实属不易,受过的苦只有自己知道,也从未幻想过能有一天和巨人并肩。我只是一个小人物,赚着不多不少的钱,住着不大不小的房子,每天努力工作,少有人知道也少有人关心,我没有成功者该有自律,甚至已经三四个月没有读过论文了,健身事业也常常被各种各样的杂事搁置。累了就躺平,无聊了就找点事干,我知道我的一生很可能永远也不会有高光时刻,我是个平凡的普通人,我认了。
不过至少没有让我的父母和妻子失望,我活着,每天遇到各种各样的烦恼却依然相信总会变好的。人拼一辈子,拼到最后就是拼谁命长,把时间线拉的足够长,眼下的困难和失意又算的了什么?
在阿里的 11 个月,我太疲惫了,每个工作日在家的时间不会超过 9 个小时,不想继续下去了,到此为止吧。孙老师的意外给了我很深的启示,人生绝不是只有工作的,我要去寻找生活了,重新出发,去迎接新生命的到来。
孙老师的微博“孙剑_ai”,我一条一条仔细翻完了,除去他作为 AI 大牛的光环之外,他是一个非常 nice 的父亲、丈夫和儿子,是一个乐观又有点愤青的学者,一个和蔼又毒舌的朋友,一个慷慨又有点小骄傲的老师,斯人已逝,愿您在乎的人永远平安!

URL

TL;DR

  • 本文提出一种学者模型用于处理持续学习问题,依次训练每个任务,每个任务都有一个学者模型,每个学者模型包括一个生成器和一个求解器
  • 生成器的作用是生成与 旧任务 真实样本同分布的样本
  • 求解器的作用是求解任务
  • 二者分开训练

Algorithm

网络结构

dgr1.png

  • 根据 task 顺序训练 学者模型
  • 训练新任务生成器时,新任务生成器负责 模仿新任务的真实数据和旧任务的生成器数据,这里的生成器实际上是指一整个 GAN,可以对抗学习来模拟样本分布。
  • 训练新任务的求解器时,求解器会映射 x -> y,这里的 x 来自第 i 个任务的真实样本和第 i-1 个任务的生成样本,生成样本的 label 来自于第 i-1 个任务的求解器的输出

Loss 设计

  • loss
    Ltrain(θi)=rE(x,y)Di[L(S(x;θi),y)]+(1r)ExGi1[L(S(x;θi),S(x;θi1))]L_{train}(\theta_i) = r\mathbb{E}_{(x,y) \sim D_i}[L(S(x;\theta_i), y)] + (1-r)\mathbb{E}_{x' \sim G_{i-1}}[L(S(x';\theta_i), S(x';\theta_{i-1}))]
    其中,DiD_i 表示第 i 个任务的数据,Gi1G_{i-1} 表示第 i-1 个任务的数据生成器。
    Ltest(θi)=rE(x,y)Di[L(S(x;θi),y)]+(1r)E(x,y)Dpast[L(S(x;θi),y)]L_{test}(\theta_i) = r\mathbb{E}_{(x,y) \sim D_i}[L(S(x;\theta_i), y)] + (1-r)\mathbb{E}_{(x,y) \sim D_{past}}[L(S(x;\theta_i), y)]
    其中,DpastD_{past} 表示过去数据的积累分布

Thought

  • 感谢 GAN,感恩 Ian Goodfellow

URL

TL;DR

lwf1.png

  • 本文将一个增量学习任务的网络结构拆分成三个部分:
    • backbone
    • 旧任务相关参数
    • 新任务相关参数
  • 所有增量学习都需要:
    • 使用原始 backbone(可微调)
    • 随机初始化新任务相关参数
  • 在此基础上增量学习常用的解决灾难式遗忘的方法有:
    • Fine-tuning:在 新任务数据集冻结旧任务相关参数,Fine-tuning backbone 以及新任务相关参数,如上图 b 所示
    • Feature Extraction:在 新任务数据集冻结 backbone 以及旧任务相关参数,只训练新任务相关参数,(与 b 不同的地方在于 是否冻结 backbone),如上图 c 所示
    • Joint Training:在 所有任务数据集上Fine-tuning 所有参数(backbone、新任务相关参数、旧任务相关参数),(与 b 不同的地方在于 是否使用所有任务数据,以及是否 fine-tuning 旧任务相关参数),如上图 d 所示
    • Learning without Forgetting:在 新任务数据集Fine-tuning 所有参数(backbone、新任务相关参数、旧任务相关参数),如上图 e 所示,(与 d 不同的地方在于 使用的训练数据不同),这种方法使用的标签包括:
      • 硬标签:新任务数据的 原始标签
      • 软标签:新任务数据在旧模型上的 输出分布,目的是:使得网络在训练新任务时,尽可能保持旧任务上的分布
        Joint Training 因为用到所有数据同时训练,所以通常被认为是增量学习的算法效果上界

Algorithm

lwf2.png

  • LwF 算法会修改所有参数:
    • backbone
    • 新任务相关参数
    • 旧任务相关参数(这个很重要!)
  • LwF 损失函数中各参数的含义:
    • θs,θs,θs^\theta_s,\theta_s^\star,\hat{\theta_s} 分别表示:backbone 原始参数、backbone 在所有任务上的最优参数、backbone 训练中的临时参数
    • θo,θo,θo^\theta_o,\theta_o^\star,\hat{\theta_o} 分别表示:旧任务相关参数原始参数、旧任务相关参数在所有任务上的最优参数、旧任务相关参数训练中的临时参数
    • θn,θn,θn^\theta_n,\theta_n^\star,\hat{\theta_n} 分别表示:新任务相关参数原始参数、新任务相关参数在所有任务上的最优参数、新任务相关参数训练中的临时参数
    • λo\lambda_o 表示:旧任务重要程度权重

Experiment

lwf3.png

Fine-tuning、Feature Extraction、Joint Training 都强

Thought

  • 使用一种类似蒸馏的方法,使用新数据 + 旧模型的方法 replay 旧数据,看起来挺 make sense

URL

TL;DR

three1.png

  • 本文将增量学习细分为三种场景:
    • 训练阶段:所有场景在训练阶段都一样,Task 1、Task 2、… 、Task T 一个接一个训练
    • 预测阶段
      • task-incremental learning (Task-IL):测试阶段会指明是哪个 task,常用的结构是 Multi-head 每个任务独占一个 head,所有任务共享 backbone,但 Multi-headSingle-head 网络结构不能决定增量学习场景,只是一种常用的结构。
      • domain-incremental learning (Domain-IL):测试阶段不指明是哪个 task,网络也无需推测输入属于哪一个 task,常用的网络结构是 Single-head
      • class-incremental learning (Class-IL):网络需要推测输入属于哪个 task,并且输出这个任务中的哪一类。

Algorithm

  • 本文的三种增量学习的对比实验所用实验策略有两个:
    • MNIST 数据分类:一共 5 个 task,每个 task 分类 2 种数字,细节如上图所示。
    • Permuted MNIST 数据分类:一共 10 个 task,每个 task 分类 一种固定 shuffle 方式 的 10 种 MNIST 数字数据,细节如下图所示:
      three2.png

model name task number class number image size info
split mnist 5 2 28x28 pixel grey-scale images 总共 10 类数字,每个类别有 6000个图片用于训练,1000个用于测试
permuted mnist 10 10 zero-padded to 32x32 pixels 总共 100 类数字,每个类别有 6000个图片用于训练,1000个用于测试

增量学习常用策略

  • 任务独占组件:在每个任务上训练单独的组件,在 inference 时根据给定的 task 选择组件。只适用于 Task-IL,例如 XdG 算法。
  • 正则优化:在每个任务上训练单独的组件,但在 inference 时由于 task 不可知,所以使用整个网络 inference训练时加入正则化项,使得网络在新任务中的参数与旧任务参数接近,常用的算法有:EWCOnline-EWCSI 等。
  • 修改训练数据:又被称为 replay 方法,即通过某种方法将之前任务的数据(或伪数据)补充到新的训练任务中,例如:
    • Learning without Forgetting 方法(LwF),这种方法将新任务输入数据在旧模型中的输出分布作为软标签伪数据,与新数据的数据一起在旧模型上 fine-tunning,最终得到新模型,利用了类似 知识蒸馏 的方法。
    • Deep Generative Replay 方法(DGR),这种方法和 LwF 都属于使用 数据重放(data replay) 来实现增量学习的算法,不同之处在于,DGR 使用的是使用样本生成的方法直接生成旧任务的样本,使用硬标签训练,而不是使用新任务数据在旧模型上的软标签进行蒸馏。
    • 或者使用 DGR + LwF 的方法,用 DGR 生成与旧任务数据同分布的数据并使用 LwF 方法蒸馏。
  • 使用范例数据:例如 iCaRL 类别增量学习算法,保存一部分旧类别数据的典型样本,每次和新类别数据一起更新表征,再根据新表征更新旧任务典型样本。

增量学习的算法上下界

  • 增量学习的算法上界(joint training):所有任务的训练数据可以一次性拿到,同时训练所有任务。
  • 增量学习的算法下界(fine-tuning):不使用任何优化,每次新任务都只使用新任务的数据对旧模型进行训练。

Experiment

实验设置

  • 输出单元设置:为了公平比较,所有方法都使用相同的输出单元。split MNIST 使用2个隐藏层实现,每层 400 节点。permuted MNIST 使用2个隐藏层实现,每层 1000 节点。激活函数使用 ReLU,除了 iCaRL 之外,其他模型最后一层都是 softmax 输出层。
  • iCaRL 只能用于类增量学习,任务增量和域增量不可以。
  • Task-IL 实验设置:所有算法都使用 Multi-head Output Layer 结构,每个任务都有一个指定的输出单元,对于任务 TaT_a,只有对应的输出单元 OaO_a 被使用,其他任务对应的输出单元不被使用。
  • Domain-IL 实验设置:所有算法都使用 Single-head Output Layer 结构,只有一个输出单元,所有任务共用一个输出单元。
  • Class-IL 实验设置:目前已有多少类,就有多少个输出单元,每个类都有独立的输出单元。

实验结果

  • 对于 split MNIST 任务
    icarl_6.png
  • 对于 permuted MNIST 任务
    icarl_7.png

Through

  • 文章逻辑挺清晰的,就是代码写的太拉胯了…

URL

TL;DR

  • 本文提出一种应用于视觉任务的 Transformer

Algorithm

Architecture

vit.gif

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import torch
from torch import nn
from einops import rearrange, repeat
from einops.layers.torch import Rearrange
# helpers
def pair(t):
return t if isinstance(t, tuple) else (t, t)
# classes
class PreNorm(nn.Module):
def __init__(self, dim, fn):
super().__init__()
self.norm = nn.LayerNorm(dim)
self.fn = fn
def forward(self, x, **kwargs):
return self.fn(self.norm(x), **kwargs)
class FeedForward(nn.Module):
def __init__(self, dim, hidden_dim, dropout=0.0):
super().__init__()
self.net = nn.Sequential(
nn.Linear(dim, hidden_dim),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(hidden_dim, dim),
nn.Dropout(dropout),
)
def forward(self, x):
return self.net(x)
class Attention(nn.Module):
def __init__(self, dim, heads=8, dim_head=64, dropout=0.0):
super().__init__()
inner_dim = dim_head * heads
project_out = not (heads == 1 and dim_head == dim)
self.heads = heads
self.scale = dim_head**-0.5
self.attend = nn.Softmax(dim=-1)
self.to_qkv = nn.Linear(dim, inner_dim * 3, bias=False)
self.to_out = (
nn.Sequential(nn.Linear(inner_dim, dim), nn.Dropout(dropout))
if project_out
else nn.Identity()
)
def forward(self, x):
qkv = self.to_qkv(x).chunk(3, dim=-1)
q, k, v = map(lambda t: rearrange(t, "b n (h d) -> b h n d", h=self.heads), qkv)
dots = torch.matmul(q, k.transpose(-1, -2)) * self.scale
attn = self.attend(dots)
out = torch.matmul(attn, v)
out = rearrange(out, "b h n d -> b n (h d)")
return self.to_out(out)
class Transformer(nn.Module):
def __init__(self, dim, depth, heads, dim_head, mlp_dim, dropout=0.0):
super().__init__()
self.layers = nn.ModuleList([])
for _ in range(depth):
self.layers.append(
nn.ModuleList(
[
PreNorm(
dim,
Attention(
dim, heads=heads, dim_head=dim_head, dropout=dropout
),
),
PreNorm(dim, FeedForward(dim, mlp_dim, dropout=dropout)),
]
)
)
def forward(self, x):
for attn, ff in self.layers:
x = attn(x) + x
x = ff(x) + x
return x
class ViT(nn.Module):
def __init__(
self,
*,
image_size,
patch_size,
num_classes,
dim,
depth,
heads,
mlp_dim,
pool="cls",
channels=3,
dim_head=64,
dropout=0.0,
emb_dropout=0.0
):
super().__init__()
image_height, image_width = pair(image_size)
patch_height, patch_width = pair(patch_size)
assert (
image_height % patch_height == 0 and image_width % patch_width == 0
), "Image dimensions must be divisible by the patch size."
num_patches = (image_height // patch_height) * (image_width // patch_width)
patch_dim = channels * patch_height * patch_width
assert pool in {
"cls",
"mean",
}, "pool type must be either cls (cls token) or mean (mean pooling)"
self.to_patch_embedding = nn.Sequential(
Rearrange(
"b c (h p1) (w p2) -> b (h w) (p1 p2 c)",
p1=patch_height,
p2=patch_width,
),
nn.Linear(patch_dim, dim),
)
self.pos_embedding = nn.Parameter(torch.randn(1, num_patches + 1, dim))
self.cls_token = nn.Parameter(torch.randn(1, 1, dim))
self.dropout = nn.Dropout(emb_dropout)
self.transformer = Transformer(dim, depth, heads, dim_head, mlp_dim, dropout)
self.pool = pool
self.to_latent = nn.Identity()
self.mlp_head = nn.Sequential(nn.LayerNorm(dim), nn.Linear(dim, num_classes))
def forward(self, img):
x = self.to_patch_embedding(img)
b, n, _ = x.shape
cls_tokens = repeat(self.cls_token, "() n d -> b n d", b=b)
x = torch.cat((cls_tokens, x), dim=1)
x += self.pos_embedding[:, : (n + 1)]
x = self.dropout(x)
x = self.transformer(x)
x = x.mean(dim=1) if self.pool == "mean" else x[:, 0]
x = self.to_latent(x)
return self.mlp_head(x)
vit = ViT(
image_size=100,
patch_size=10,
num_classes=10,
dim=128,
depth=12,
heads=8,
mlp_dim=256,
)
img = torch.randn(2, 3, 100, 100)
out = vit(img)
print(out.shape) # [2, 10]

Thought

  • 一个优雅的网络结构,就该像 ViT 这样,用一张动图 + 一段不长的代码完美表示。显然 Swin Transformer 不够优雅…