还在用RoBERTa?快来看看DeBERTa吧!

如果你现在不知道DeBERTa,那相当于你在2018年不知道BERT ——多头注意力 DeBERTa模型是微软在2021年提出的,首发在ICLR 2021上,到现在其实已经迭代了三个版本。第一版发布的时候在SuperGLUE排行榜上就已经获得了超越人类的水平,如今也成为了Kaggle上非常重要的NLP Backbone(BERT感觉已经没什么人用了)。比较奇怪的是,似乎这个模型被大家讨论并不多,于是最近看了两篇相关论文DeBERTa: Decoding-enhanced BERT with Disentangled Attention和DeBERTaV3: Improving DeBERTa using ELECTRA-Style Pre-Training with Gradient-Disentangled Embedding Sharing学习了一下。 DeBERTa 1.0 1.0版本在BERT的基础上有三个主要的改进点: 更加解耦的self attention,上图中右边黄色部分; 考虑绝对位置的MLM任务,上图中Enhanced Mask Decoder; 预训练时引入对抗训练 我认为其中1应该是最重要的,然后是3,2最不重要,因为在后面的3.0版本已经不再使用MLM作为预训练任务了。 Disentangled Attention 第一个改进其实有点“复古”,这里的解耦是将位置信息和内容信息分别/交叉做attention。想当年BERT横空出世时大家都津津乐道地讨论为什么可以把word embedding,position embedding加起来做注意力,没想到没过几年却又被分开了。当然,DeBERTa的相对位置编码不同于BERT的绝对位置编码,似乎也不好直接比较。 论文里定义了一个相对位置embedding P,和一个相对距离函数$\delta(i,j)$,除了和标准transformers一样的内容QKV,计算了相对位置QK,分别为$Q_r=PW_{q,r}$,$K_r=PW_{k,r}$。注意力矩阵的计算变成了 $$A_{i,j}={H_i,P_{i|j}}\times{ H_j,P_{j|i}}^T=H_iH_j^T+H_iP_{j|i}^T+P_{i|j}H_j^T+P_{i|j}P_{j|i}$$ 第一项是常规的内容自注意力(content-to-content),第二第三项分别是content-to-position和position-to-content,第四项论文里认为不重要,直接省略了。具体看是下面这个公式 $$A_{i,j}=Q^c_i{K^c_j}^T+Q^c_i{K_{r,\delta(i,j)}}^T+K_j^c{Q_{r,\delta(j,i)}}^T$$ 这一部分其实看一下代码也比较清晰。 SiFT 对抗训练也是NLPer经常使用的技术了,在做比赛或者公司业务的时候我一般都会使用FGM对抗训练来提升模型的性能。DeBERTa预训练里面引入的对抗训练叫SiFT,比FGM复杂一些,他攻击的对象不是word embedding,而是embedding之后的layer norm。整个过程需要forward 3次,亲测比FGM慢一些。微软已经把代码放出,大家可以参考,在自己的任务里试一试。 DeBERTa 2.0 2012年2月放出的2.0版本在1.0版本的基础上又做了一些改进: 更换tokenizer,将词典扩大了。从1.0版的50k扩成了128k。这个扩大无疑大大增加了模型的capacity。 在第一个transformer block后加入卷积。这个技巧在token classification、span prediction任务里经常用到。 共享位置和内容的变换矩阵 把相对位置编码换成了log bucket,各个尺寸模型的bucket数都是256 这些变化里1和2是把模型变大,3和4是把模型变小。总的效果是V2版本模型比V1版本变大了。 2.0版几个变更对模型的影响,增大词典效果最显著 DeBERTa 3.0 2021年11月微软又放出了3.0版本。这次的版本在模型层面并没有修改,而是将预训练任务由掩码语言模型(MLM)换成了ELECTRA一样类似GAN的Replaced token detect任务。因为多了个生成器,DeBERTa 3.0的论文中也更多的是对不同的embedding sharing的探讨,下面这种图是对文中对比的三种方式的简介。 3.0论文探讨的集中参数更新方式 根据下图所示论文的结果,3.0的改进进一步提升了DeBERTa模型的性能(实际并不是所有任务都有提升)。DeBERTa-v3也确实成为了Kaggle上最常见的DeBERTa版本。 DeBERTa 3....

April 16, 2022 · 1 min · Yuanhao

Key Phrase Extraction

做了一段时间的新闻NLP,越来越感受到抓重点对长文本理解的重要性。类别、话题、关键词句这种离散标签对下游的推荐、搜索业务以及产品的形态都有很重大的影响。最近读了两篇关键短语抽取(Key Phrase Extraction,KPE)相关的论文,感觉挺有意思,跟大家分享一下。 问题定义和数据集 首先,对于一篇文章来说什么是其中的关键短语就没有一个统一的标准,标注的时候也比较主观,而且标注难度很大。常见的类别体系可能包含几百个类别,话题体系包含成千上万个话题,而对于关键短语来说,连个确定的候选集都没有。 目前主流的KPE任务benchmark数据集有好几个,这里列两个比较有名的 KP20k:2017年论文Deep Keyphrase Generation贡献的数据集,由科学论文组成。文本包括标题和摘要。发过论文的都知道,作者需要给文章提供几个关键词,确实是很好的数据来源。 KPTimes:2019年论文****KPTimes: A Large-Scale Dataset for Keyphrase Generation on News Documents****贡献的数据集,文章都是新闻,下面是一个例子。 KPTimes数据样例 这两个数据集规模应该都挺大了,KPTimes的论文里有一张主流数据集规格对照表,一目了然,大家可以参考。从统计上看KP20k和KPTimes篇均5个KP的确实比较实用,但它们的问题是测试集里很大比例的标签并没有在文本中出现,对于模型来说难度可能太大了 主流数据集对比 监督方法 KP20k数据集其实是那篇论文的副产品,那篇论文的主要贡献其实是一个叫CopyRNN的方法,看名字大概就知道是个seq2seq+copy机制的生成式方法。这里引入copy机制也是有比较明确的动机的,因为在RNN时代生成式方法会受限于字典,decoder输出层没有的词是无法被预测出来的。 RNN+copy机制可以在KP20k上获得0.255的F1@10 到了2020年,BERT等Transformers模型已经成了NLP领域的标配,那自然也会想到用来做KPE。Joint Keyphrase Chunking and Salience Ranking with BERT 就是里面简单且有效的一个方法。题目里的Salience是个显著的意思,这篇文章的方法也非常直接,就是把最可能是KP的文本段落(n-gram)用排序的方法找出来。那怎么得到一个n-gram的表示呢,这篇文章的办法就是在Transformer上面再套一个一维CNN,n和卷积核的大小相对应。 论文里用了两个任务来训练这个网络,一个任务是二分类,即n-gram是否是KP;另一个是排序任务,这个任务是对于文档中的每个unique n-gram,获得最大的预测值(文中称为max pooling),然后用hinge loss来使得KP的概率值大于非KP。 JointKPE的成绩大大提高 感兴趣的朋友们可以参考他们的代码实现。 非监督方法 一开始我是想找一些靠谱的非监督方法的,毕竟像KP20k这样优质的的训练数据集一般只有英语。然后就在paperswithcode上看到了目前的榜一,UCPhrase。这个方法比较有意思,它的流程如下面这张图所示 分为几个核心步骤: 找到所谓的Core Phrase。这其实是通过一些规则找到文本中反复出现的片段,并且把它们当做KP,以及后续网络训练的Silver Labels。 用Transformers语言模型生成特征。这里的特征不是大家常用的embedding,而是attention map。 训练一个图像分类器,对于一个attention map进行是否KP的二分类。 一个attention map样例,从中可以发现:1. attention map把句子分成了比较明显的几块 2.attention map可以可以作为图像输入来进行KP分类 这个论文的结果如下,在KP20k上的F1@10是19.7,和2017年的RNN+copy差了6个百分点,但和同样使用Transformers的监督方法相比差了16个百分点。 非监督方法比起监督方法来确实逊色不少 这个工作的代码也开源了:https://github.com/xgeric/UCPhrase-exp。 写在最后 提到KPE,可能大家第一个想到的方法是SpanBert那样的span prediction方法,亦或是序列标注里常用的BIO分类法,但JointBert论文里对比下来还是这种接一个CNN的方法更好。相比于单纯序列标注或片段预测,这个方法确实可以更直接地利用附近的邻域信息,在Kaggle中其实也常有在序列标注前先加一层CNN或RNN来强化邻域信息的做法。 UCPhrase的方法让人眼前一亮,有一种学术美,但与JointBert 16个百分点的性能差异又实际上让它的实用价值大打折扣。所以在业务明确的前提下,搞漂亮方法确实不如扎扎实实搞点标注数据啊。

March 26, 2022 · 1 min · Yuanhao

围观特斯拉总监把玩MNIST

最近大名鼎鼎的特斯拉AI总监Andrej Karpathy发了篇博客(看来写博客是个好习惯),叫Deep Neural Nets: 33 years ago and 33 years from now。饭后花了点时间围观了一下,写得确实挺有意思。 他先尝试复现了一下深度学习开山模型LeNet,然后尝试利用这33年人类的新知识去改进模型的效果。他干了这么几个事情: Baseline. eval: split train. loss 4.073383e-03. error 0.62%. misses: 45 eval: split test . loss 2.838382e-02. error 4.09%. misses: 82 把原文的MSE loss换成如今多分类的标配Cross Entropy Loss eval: split train. loss 9.536698e-06. error 0.00%. misses: 0 eval: split test . loss 9.536698e-06. error 4.38%. misses: 87 首战失败,怀疑SGD优化器不给力,换成了AdamW,并使用“大家都知道”的最优学习率3e-4,还加了点weight decay eval: split train. loss 0.000000e+00. error 0.00%. misses: 0 eval: split test ....

March 9, 2022 · 1 min · Yuanhao

分级利器:序数回归

之前面试的时候遇到过好几个候选人在做”评级“相关的项目,例如针对图片、视频的色情程度,评论的粗鲁程度分级等等。绝大部分的人在面对这种问题时通常想到的都是用回归或分类来解决。这两种方法都是有效的,但都有一些问题: 常规的分类无法很好地建模类别间的关系。例如你要对评论的不文明程度分5档,但对于分类常用的交叉熵损失函数来说,把一个最高档的评论分成了最低档还是中间档对它来说损失是一样的。但对于实际的业务,前者显然比后者是更难接受的。 回归算法需要比较多的超参调试。在之前的文章里聊过,回归对标签的尺度是敏感的,把细粒度,例如100档(标签为1-100)的评级问题直接交给MSE Loss往往得不到好的结果。回归对标签中的最大值和最小值也天然会有一些抗拒。 在Pawpularity比赛结束后CPMP说他使用了一种叫Ordinal Regression(中文名没找到,姑且称它为序数回归)的方法,我在一些评级问题上尝试之后发现这个方法确实行之有效,而且非常简单优美。 数学解释 说是序数“回归”,但我它认为本质上是一个考虑了类别间关系的分类算法。 大家一定都很熟悉sigmoid函数$σ$,它的定义域是(-∞,+∞),而值域是(0,1),且是单调增函数,连续可导。我们可以把$σ(x)$看做是随机变量小于x的概率,即某个(-∞,+∞)上随机变量的累积分布函数(CDF)。 假设我要处理一个5档的分类问题,而上面说的随机变量就是模型的输出,那么问题可以转化为找到四个切分点$\theta_1, \theta_2, \theta_3, \theta_4$,并用$P(x<\theta_1)$, $P(\theta_1< x<\theta_2)$, $P(\theta_2< x<\theta_3)$, $P(\theta_3< x<\theta_4)$, $P(\theta_4< x<+\infty)$这五个概率来表示$x$分别属于五个等级的概率。进一步结合前面的sigmoid函数做CDF的方法,可以把五个概率转化为$σ(\theta_1-x)$, $σ(\theta_2-x)-σ(\theta_1-x)$, $σ(\theta_3-x)-σ(\theta_2-x)$, $σ(\theta_4-x)-σ(\theta_3-x)$, $1-σ(\theta_4-x)$。 这样我们就把一个模型输出的实数logit转化成了属于五个等级的概率,进而可以使用负对数似然损失函数来优化这个分类问题。在这样的设定下既可以使用一组固定的切分点来优化模型,又可以把切分点也作为可学习的权重和模型一起优化。 代码 一开始我在网上找到了一个pytorch的Ordinal Regression实现spacecutter,但经过一番实验之后我发现它写的并不完美,于是自己又修改了一下,在这里分享给大家 class OrdinalRegressionLoss(nn.Module): def __init__(self, num_class, train_cutpoints=False, scale=20.0): super().__init__() self.num_classes = num_class num_cutpoints = self.num_classes - 1 self.cutpoints = torch.arange(num_cutpoints).float()*scale/(num_class-2) - scale / 2 self.cutpoints = nn.Parameter(self.cutpoints) if not train_cutpoints: self.cutpoints.requires_grad_(False) def forward(self, pred, label): sigmoids = torch.sigmoid(self.cutpoints - pred) link_mat = sigmoids[:, 1:] - sigmoids[:, :-1] link_mat = torch....

March 2, 2022 · 1 min · Yuanhao

多模态对比学习预训练模型CLIP

我经常在面试的时候问候选人如何构建一个文本配图系统,有不少人都会想到OpenAI的 CLIP (Contrastive Language–Image Pre-training) 模型。确实,CLIP的思路应该是解决这个问题的一个好框架,正好之前的几篇文章又都是关于其中的关键技术,于是这篇文章重温一下CLIP。 方法 自然语言信号 At the core of our approach is the idea of learning perception from supervision contained in natural language. 正如作者所说,这是CLIP的核心,但并不是一个新的方法。很多过去的研究都使用自然语言信号来训练图片编码器,但大家使用的方法各不一样。 用自然语言信号有几个好处,一个是数据收集容易了,有相关性的图文在互联网上很多,不需要标注,第二个是与之前那种类别空间相比,自然语言信号更容易迁移,后面还会具体讲到。 更大的数据集 CLIP构建了一个400 million 图片-文本对组成的数据集。比之前类似工作所使用的数据集大了二十几倍。而且这些数据集都是互联网上现成的,只是做了一些过滤来保证质量。 it is trained on a wide variety of images with a wide variety of natural language supervision that’s abundantly available on the internet 更大的模型 文本编码器使用的是12层8个头512个隐层神经元的Transformers模型,但没有使用预训练模型。我猜测这是因为要跟图像编码器交互,所以预训练可能帮助不大,如果使用预训练模型还需要特殊的策略来让图像和文本编码器的embedding空间匹配起来。 图像编码器尝试了resnet家族和ViT家族。最佳结果是来自于ViT,并且ViT相比于Resnet有更高的训练效率。图像编码器同样也没有使用Imagenet上的预训练权重来初始化。ViT我们在之前有两篇文章介绍,感兴趣的同学可以参考。 更高效的训练目标 过去的SOTA CV模型,如Noisy Student EfficientNet-L2,只训练Imagenet就需要耗费大量的训练时长(33个TPU年),如何能够在超大规模、自然语言信号的数据集上训练出一个好模型是个挑战。这部分也是CLIP最核心的地方。 This data is used to create the following proxy training task for CLIP: given an image, predict which out of a set of 32,768 randomly sampled text snippets, was actually paired with it in our dataset....

December 13, 2021 · 2 min · Yuanhao

对比学习学习笔记

对比学习已经火了一阵了,之前看过一下SimCLR和SimCSE的论文,但走马观花也没有实践。这两天仔细学习了一下,大概明白了是怎么回事。 首先对比学习通常是在自监督的设定下进行表征学习,也就是不依赖标注数据获得一个编码器(Encoder),大致的环节如下 通过一些方式构造人工正样本对 在一个Batch内构造负样本对 设计一个loss,拉近正样本对表征(Embedding)间的距离,扩大负样本对表征间的距离 构造正样本对 对比学习一开始是在计算机视觉领域兴起的,CV论文里最初是通过对一张图片进行两次不同的数据增强来构造正样本对的。 SimCLR里用到的图像增强方法,可以看出来强度是比较高的,模型要学会图像间的关系不是那么容易 后来这把火烧到了NLP领域,最开始也是模仿CV的做法,通过例如删字词、换同义词等数据增强来构造。直到大名鼎鼎的SimCSE横空出世,提出了用两次不同的dropout来构造正样本对的方法,而且效果还特别好。这个方法的前提是在transformers里面每层都有dropout,但常见的卷积神经网络里面dropout往往都只在最后才有,所以并不能迁移到CV界;但最近ViT大火,应该也有人会试着使用这种方法。 SimCSE除了有自监督的版本,还有通过数据集特点构造的有监督版本 损失函数构造 SimCLR里面用的是NT-Xent Loss,它是the normalized temperature-scaled cross entropy loss的缩写,我来翻译的话会叫他“归一化的带温度交叉熵”。其公式如下 $$l(i,j)=-\text{log}\frac{e^{\text{sim}(z_i,z_j)/\tau}}{\sum_{k=1}^{2N}1_{k\ne i}e^{\text{sim}(z_i, z_k)/\tau}}$$ $$L=\frac{1}{2N}\sum_{k=1}^N[l(2k-1,2k)+l(2k,2k-1)]$$ SimCLR中一个batch是由N张图片通过两组不同的增强变成2N张并且穿插排列,即2k-1 和 2k 是由同一张图构造的一对人造正样本。从第二个式子可以发现,一个Batch的Loss是其中N对loss的均值。跟cross entropy相比,首先是指数项从模型预测的概率变成了样本对间的相似度。分子与正样本对的相似度相关,分母则与第i张图与其余图的相似度有关。注意分母中只有2N-1项,因为自己与自己天然组成正样本,不去除的话这个分式的极限值(完美模型的loss值)将变成0.5,总loss也就不是0了。 SimCSE里使用的loss是上面的变种,或者说是个简化版本。$z_i$是原始第i个样本的表征,$z’_i$是对应的人造正样本的表征。与上面不同的是原始样本表征之间的相似度、变换样本表征之间的相似度都没有参与loss计算。 $$l(i)=-\text{log}\frac{e^{\text{sim}(z_i, z’i)}}{\sum^N{j=1}e^{\text{sim}(z_i, z’_j)}}$$ 代码实现 下面是我实现的SimCSE版本的对比学习loss,供大家参考 class NTXentLoss(nn.Module): def __init__(self): super().__init__() def forward(self, rep1, rep2, temperature=0.5): normalized_rep1 = F.normalize(rep1) normalized_rep2 = F.normalize(rep2) dis_matrix = torch.mm(normalized_rep1, normalized_rep2.T)/temperature pos = torch.diag(dis_matrix) dedominator = torch.sum(torch.exp(dis_matrix), dim=1) loss = (torch.log(dedominator)-pos).mean() return loss 实验心得 我还是在之前提到的Pawpularity数据集上进行的实验,并且和论文里的表征学习不同,我是将对比学习作为一个辅助任务来帮助主任务的训练。经过一天的实验,有以下一些发现 在参数合理的情况下,加入对比学习作为辅助任务确实可以提升主任务的表现。 加入对比学习作为辅助任务看上去可以让模型收敛更加稳健,从而可以使用更大的学习率、更高强度的数据增强。 Loss中的温度是很重要的参数,在SimCLR论文中最好的温度是0.1,在SimCSE论文中最好的温度是0.05,但在我的实验里最好的值跟这俩差的很多。熟悉蒸馏的朋友应该知道,温度越高会让样本间的差异越小,loss趋近常数;温度越低则反之。SimCSE论文的消融实验尝试了不同数量级的温度,大家在用的时候也可以大胆地多尝试一下。 将对比学习作为辅助任务额外增加的时间代价不明显。 今天先到这里,上班去辽。

December 6, 2021 · 1 min · Yuanhao

Swin Transformer (v2)学习笔记

上篇总结了一下最初的ViT模型,它有几个明显的问题: 建模能力方面,强行分割patch破坏了原有的邻域结构,也不再具有卷积的那种空间不变性 复杂度方面,之前的ViT是在每层都做全局(global)自注意力。如果保持每个Patch的大小不变,随着图片尺寸的变大,Patch的个数会增加,而Patch的个数等于进入Transformer的Token个数,且Transformer的时间复杂度是O(n^2)。 易用性方面,由于Embedding(结构是全连接)和图片大小是绑定的,所以预训练、精调和推理使用的图片必须是完全同等的尺寸。 Swin Transformer提出了一种称为shifted window的方法来解决(缓解)以上问题。 Swin Transformer的结构如下图所示 {: .align-caption style=“text-align:center;font-size:smaller”} Embedding Stage(stage1)。将图片划分为若干4*4的patch,使用线性变换来将patch变为Embedding向量,这一步和ViT是一样的。但是注意,这里的patch比ViT的14*14小了很多。 若干个使用Swin Transformer 的Stage(stage2-4)。这里模仿了经典卷积网络backbone的结构,在每个Stage都将feature map(对应到Vit就是Patch或Token的个数)变成原来的四分之一。这是通过简单地将2*2patch合并成一个来完成的。同时,用Swin Transformer替代了原来的标准Transformer,主要变化如下 用M*M大小的窗口自注意力代替全局自注意力。因为自注意力机制时间复杂度是O(n^2),通过减少参加自注意力的元素,将原来关于patch数平方复杂度的计算变为关于patch数线性复杂度 用对角线方向的shift来使Swin Transformer里的每一层窗口都是不同的,这样一个patch有机会和不同的patch交互。这里还使用了一个mask trick来使得这种shift的自注意力计算更高效。 添加了相对位置偏置(relative position bias),对比发现这比添加绝对位置embedding效果好很多 shifted window示意图,l+1层的窗口是从l层往右下角平移2个patch得到的 {: .align-caption style=“text-align:center;font-size:smaller”} 从结果来看,SwinT相比于ViT有了很大的提升 Swin Transformer实验结果,可以看出来比ViT已经有了很大的提升 {: .align-caption style=“text-align:center;font-size:smaller”} 综合消融实验的结果可以对比三种不同的attention方式: fixed window、sliding window和shifted window的性能。他们的imagenet top1 acc分别是80.2, 81.4和81.3。从中可以看出类似于卷积的sliding window性能是最好的,无奈太慢了。fixed window丢失了很多有用的窗口间交互,性能最差。shifted window性能相比sliding window下降微弱,但速度提升了好几倍。同样可视为fixed window的ViT只能得到0.78的top1 acc,我想这是小patch带来的差别,因为现在的线性变换embedding实在太弱了,patch越大带来的信息丢失就越多。 前不久原班人马又发布了V2版的Swin Transformer,主要是解决模型上规模的问题,有几个主要的改动: 把每个Block里的LN从前面换到了后面,来解决深度增加之后训练不稳定的问题 把原来的scaled dot attention换成了scaled cosine attention,也是为了解决训练不稳定的问题(否则可能被某些像素对的相似度主导)。 改进相对位置偏置。V1版里这个模块是用一个规模跟窗口大小M相关可学习参数矩阵来处理的,如果预训练和finetune时M大小改变,就用插值来生成原来不存在的值。V2版首先是引入了一个小网络来取代参数矩阵,其次是将相对位置从线性空间换到了对数空间,通过取对数压缩空间差距来让M变化时的过渡更加顺滑 通过取对数,让finetune时增大窗口和图片的性能损失大为减小。但其实这里还是跟卷积神经网络有差距。通常卷积神经网络在finetune时使用更大的图片可以提升性能。 {: .align-caption style=“text-align:center;font-size:smaller”} 从结果来看,更大的网络确实带来了更好的性能,30亿参数版的SwinV2-G比8800万参数版的SwinV2-B性能提升了不少。同样参数量的V2版也比V1版提升了一些。 不同模型Imagenet结果 {: .align-caption style=“text-align:center;font-size:smaller”} 消融实验也比较清晰地反映出了V2版加入的新技术带来的技术提升...

November 28, 2021 · 1 min · Yuanhao

Vision Transformer学习笔记1

最近Transformer结构在计算机视觉领域的应用非常火,时不时都有新的文章出来。作为一个已经使用了两三年Transformer结构的NLPer,一直很想了解一下它在视觉领域是怎么工作的,最近借着一个Kaggle比赛的数据集学习了一下,稍作总结分享给大家。 首先是学习了一下Vision Transformer,ViT的原理。看的论文是谷歌名作《An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale》,本文初稿发布于2020年10月,今年投了ICLR 2021,应该算是ViT的奠基论文之一。要用Transformer来处理图像,首先(也可能是唯一)要解决的是输入问题,原先的Transformer处理的是token序列,而图像是HWC的像素矩阵。这里做法也很暴力,第一步是将一张图拆成了N个PP个小块(patch),每个patch看做是一个token。一个patch里有PP个像素,每个像素C个通道,这里直接就给拍平了进一个全连接(线性变换)得到patch的D维Embedding表示。所以ViT里的Embedding层不再是一个lookup table了,而是一个可以学习的线性变换。 ViT结构图 {: .align-caption style=“text-align:center;font-size:smaller”} 通过这个方法,就把Transformer结构套在了图像上,虽然这不是唯一的方法,但这么做在参数量的角度和时间复杂度的角度都是比较合理的。首先是时间复杂度角度,Transformer的关于序列长度的时间复杂度是O(n^2),所以输入序列不宜过长。如文题所说,如果我们把图分成1616个patch,那transformer处理的序列长度将会是256,比BERT的默认长度521还短了一半。参数量上,尺寸正常的Transformer很大比例参数在embedding层上,例如BERT-base的30k个token768维的Embedding层有23M参数大约占了其110M总参数量的五分之一。ViT里Embedding层的参数量是正比于图像尺寸的,以224224图像为例,单patch像素点数为196,所以总参数量是196C*D,C是输入通道数,D是Embedding维数,以3和768记的话为0.45M,远小于BERT-base。从下表可以看到同样尺寸的ViT参数量都小于对应的BERT。 按论文的这种处理方式也有几个比较明显的问题,例如强行分割patch破坏了原有的邻域结构,也不再具有卷积的那种空间不变性。在中等规模数据集上用这种方法得到的结果还是比卷积要差,但是当把预训练数据变多用更大的数据集训练时,模型性能显著提升了(第二列比第三列),接近甚至超过了SOTA。 上面的结果都是针对有监督训练的,这篇文章还做了些无监督训练的初步实验,发现加入无监督预训练在下游任务比没有预训练强一2%,但是比有监督预训练差4%,总之一句话,没有实现BERT的效果。 实验的部分用Pytorch Lightning简单做了一下Kaggle的Pawpularity数据集。这是一个值域0-100的回归问题,评价指标是RMSE。模型部分没什么花头,直接backbone接了个回归头,代码如下 class Pawpularity(pl.LightningModule): def __init__(self, config): super().__init__() self.config = config self.backbone = timm.create_model(config.backbone_name, pretrained=not config.predict, num_classes=0) self.head = nn.Sequential( nn.Linear(self.backbone.num_features, 128), nn.GELU(), nn.Linear(128, 1) ) self.save_hyperparameters(config) 实验的运行环境是在我的HP Z4工作站上,它搭载了两个RTX 6000 GPU,因为显存是24GB版本,所以batchsize设的比较大。实验结果如下 模型 模型参数 lr batch size 单轮耗时 早停轮次 RMSE vit_base_patch16_224 85.9M 1e-3 128 36s 10 20.514 vit_base_patch16_224_in21k 85....

November 17, 2021 · 1 min · Yuanhao

模型上线不用愁,批量推理来加油.

作为一个算法工程师,在日常工作中难免会碰到模型上线的问题。对于一些要求不高的场合,简单找一个web框架实现一下接口就能搞定:对于每个用户请求,调用模型得到结果再返回。但这种朴素的实现往往无法最大化利用GPU,对于性能要求比较高的场景应付起来就略显吃力。 优化的方法有很多,一个增益很大的措施就是把一个请求推理一次改成多个请求一起推理。去年大概也是这个时候我写了一个小工具来实现这个功能,还取了个蛮霸气的名字InferLight,但当时写得并不太好;最近参考香侬科技的Service-Streamer又重构了一版。这个功能看似简单,但是在实现的过程中可以了解很多Python异步编程的知识,感觉收获颇丰,于是写篇短文总结一下。 首先,要提高模型的线上推理吞吐量,应该把推理服务做成异步的。对于web服务来说,异步的意思是当模型在计算的时候它可以处理别的请求。对于Python来说,异步服务可以通过很多优秀的基于Asyncio的框架来实现,例如我常用的Sanic。而推理是计算密集的,也没有什么同步异步的说法,我们的目标就是能够汇聚多个推理请求,高效利用GPU的并行计算能力,并且能将批量推理的结果正确地返回给对应的请求者。 要实现上面的目标,需要以下几个模块 前端服务:用于接收请求、返回结果。可以是Http、PRC等各种协议。是一个独立进程。 推理Worker:负责模型的初始化、批量推理数据构建、推理计算。是一个独立进程。 任务队列:前端服务收到请求之后把计算任务送入任务队列;推理Worker监听该队列,每次取出一个小批量由模型推理 结果队列:推理服务推理完成后将结果送入结果队列;前端服务监听该队列,获得推理结果 结果分发:在将任务送入任务队列前需要生成任务的唯一标识,从结果队列取回结果后根据标识获取到任务对应的结果 其中两个任务队列的实现方式很多,可以通过一些成熟的中间件例如Kafka、Redis等,但为了避免外部依赖,这次我选择使用Python原生的多进程队列。结果队列监听和分发通过前端服务进程的一个子线程来完成。 实现细节 推理服务相对简单,由于各种模型的加载、数据处理步骤千奇百怪,所以我将推理Worker设计成了一个基类,使用时继承它并实现特定方法。 import logging import multiprocessing as mp import time from queue import Empty class BaseInferLightWorker: def __init__(self, data_queue:mp.Queue, result_queue:mp.Queue, model_args:dict, batch_size=16, max_delay=0.1, ready_event=None) -> None: self.data_queue = data_queue self.result_queue = result_queue self.batch_size = batch_size self.max_delay = max_delay self.logger = logging.getLogger('InferLight-Worker') self.logger.setLevel(logging.DEBUG) self.load_model(model_args) # 由于模型载入时间较长 # 加载完成后使用一个event来通知主进程 if ready_event: ready_event.set() def run(self): self.logger.info('Worker started!') while True: data, task_ids = [], [] since = time....

June 20, 2021 · 4 min · Yuanhao

面向电商场景的语言模型E-BERT

最近跟不少做电商NLP的朋友们聊天,有不少收获。我之前从来没想过【搜索】在电商里的地位是如此重要,可能GMV的50%以上都是从搜索来的。巨大的经济价值也极大地推动了技术的发展,他们的工作做得很细致,毕竟一个百分点的点击率后购买率提升也许对应的就是几百亿的成交额。 其实之前做的汽车领域NLP工作跟电商有很多相似的地方,场景先验都非常重要。直接使用开放域语料预训练的语言模型效果并不好。我们也尝试过一些方法,例如用本领域语料训练语言模型,结合一些词库词典等等。今天介绍最近看到的一篇针对电商场景调优BERT的论文《E-BERT: Adapting BERT to E-commerce with Adaptive Hybrid Masking and Neighbor Product Reconstruction》,其中的一些方法应该对细分领域NLP都有一些启发。 方法 论文的创新方法主要有两个:Adaptive Hybrid Masking(AHM,自适应混合掩码)和Neighbor Product Reconstruction(NPR,相似商品重构)。 E-BERT总览 {: .align-caption style=“text-align:center;font-size:smaller”} AHM 第一个方法AHM其实是对已有掩码方式的改进。原始版本的BERT采用的是随机mask,这个大家应该都比较清楚。这种mask方式针对的是token,而众所周知token是由单词通过wordpiece tokenizer分割而来。所以这种方式遮盖住的可能是单词的一个部分,学习这种类似看三个字母猜剩下四个字母的任务不是很符合大家的直觉。随后就诞生了更加符合人类认知的Whole Word Masking,这个方法就是说要遮就遮整个词。这里用一个网上的例子帮大家理解 Input Text: the man jumped up , put his basket on phil ##am ##mon ' s head Original Masked Input: [MASK] man [MASK] up , put his [MASK] on phil [MASK] ##mon ' s head Whole Word Masked Input: the man [MASK] up , put his basket on [MASK] [MASK] [MASK] ' s head philammon是一个词,他会被tokenizer分解成三个token,这时就体现了普通mask和WWM的区别。...

September 16, 2020 · 1 min · Yuanhao