做NLP?Don't stop pretraining!

应该很多朋友知道,在训练下游任务之前先在任务的语料上做一下非监督的masked language model任务预训练可提高目标任务的性能。特别是当下游任务的标注数据少,相关语料多的情况下这个小技巧带来的提升更大。举个例子,假设你要做一个恶意评论分类器,但由于时间和成本关系,只标注了几万条评论,但系统里的评论已经有几百万条,这时候先在所有评论上做个MLM训练,再finetune恶意评论分类任务就是个比较好的选择。 这个问题多年前论文Don’t Stop Pretraining: Adapt Language Models to Domains and Tasks做了比较详细的探讨。 首先是个很好理解的现象,如下图所示,虽然现代化的Transformer语言模型都是由海量数据训练的,但难免跟我们目标任务的语料领域无法完全重叠 论文还做了定量的分析,它们选了几个领域,然后抽样了每个领域最高频的一万个token,看下重合度,发现确实不高。重合度最高的是新闻,那是因为Roberta训练的语料里其实就有新闻。 那既然如此,就在目标任务所在领域语料上继续做一把预训练(DAPT),然后再finetune目标任务。同样是上面几种领域的任务,发现经过DAPT之后都有明显提高,上面重合度最低的CS领域提升最明显。最后一列是个比较有意思的实验,它是为了排除单纯增加了训练数据带来的性能提升,选了一个非目标任务所在领域来进行预训练(数据同样变多,但领域和目标任务无关)。结果大多没提升,有些还下降了。这就说明在目标任务领域做预训练确实有效果! 这个论文后面还有不少内容,但我感觉对一般场景有些overkill,就不写了,有兴趣的朋友可以自己看。下面来给大家演示一下怎么用目前主流的transformers库来做MLM,相当简单,可以说是开箱即用。 首先你需要安装Transformers库,然后在transformers/examples/pytorch/language-modeling/目录下面找到run_mlm.py文件,把这个文件复制一份到你的工程目录。 为了做MLM训练,你需要准备好一些文本数据,将他们以一行一个样本的格式写在一个文本文件里,为了可以监控训练的进程,最好是像平常做其他机器学习任务一样准备一个训练集,一个验证集。但由于是MLM,验证集不需要太多。 准备好代码和数据之后就可以来运行这个脚本了,有三部分参数需要指定 模型参数 必须的模型参数只有一个,即model_name_or_path,即你要使用的基础模型。给这个参数是最方便的,tokenizer等组件会配套使用。你也可以参考代码来精细控制每个组件。 数据参数 train_file,即训练数据路径 validation_file,即验证数据路径 max_seq_length,最长的序列长度,不给的话会使用tokenizer的默认最大长度 mlm_probability遮蔽比例,默认是15%,之前陈丹琦组的论文说增大比例可以提高性能,但目前似乎还有争议 line_by_line,因为我们的数据是line by line的,所以这个要设置为True 训练参数。这部分参数有很多,可以参考这个文件。比较推荐设置的有以下几个 output_dir,这个必填,训练后模型保存的地址 do_train,这个必填 do_eval,如果有验证集必填 num_train_epochs,默认为3 fp16,如果你的显卡支持tensor core,那一定要把这个打开 weight_decay,MLM的时候可以给点衰减防止过拟合,常用0.01 per_device_train_batch_size,batch size 最后的成品可能像这样 python run_mlm.py \ --model_name_or_path roberta-base \ --train_file training_corpus.txt \ --validation_file validation_corpus.txt \ --per_device_train_batch_size 8 \ --per_device_eval_batch_size 8 \ --do_train \ --do_eval \ --fp16 \ --weight_decay 0.01 \ --line_by_line \ --output_dir ....

April 20, 2022 · 1 min · Yuanhao

还在用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

心流

断断续续读完了《心流》这本书,感觉不错,特别是序言和前几章,大家都可以读读看,应该会有些启发(序二的作者说自己在推荐这本书时也总加上一句“读前三章就行了”)。我也把我的一些收获分享给大家。 管理好你的注意力 体验过心流的人都知道,那份深沉的快乐是严格的自律、集中注意力换来的。 书里有一个很有意思的观点,关于享乐和乐趣的区别,其中有一个差别就是乐趣是需要投入注意力的,而享受是无需耗费精神能量的。投入注意力也是体验心流的重要条件,因此不花力气的享乐只能体会到低级的“刺激”。 现代人的注意力其实是个稀缺的资源,无数的人和公司在为之竞争。而这种相对“自我”来说外部的激烈竞争甚至是侵入式的,它们把“自我”对注意力的控制权也削弱了——好多人把自己的注意力交给抖音、b站、小红书去打理。这其实是比较遗憾的。 心流较强的那组人能关闭其他资讯的管道,只把注意力集中在接收闪光的刺激上。 我我感觉注意力是可以管理和训练的,有一些简单的方法例如不带手机进卧室,把搜索作为主要的信息获取起点,多读书,多看长视频少看短视频等等。 学会自得其乐 这一点听上去有点阿Q,但我感觉确实很重要,我也挺庆幸自己对这一点掌握的还可以。之前跟总哥探讨过这个问题,我们是本科同学,他比我多金且光鲜,但他觉得我一直比他快乐。后来我们总结了几点原因,记得其中一个是我的好奇心比较强,对啥都感兴趣,所以兴趣爱好比较多,例如打游戏、弹吉他,从而可以获取乐趣的渠道比较多。 对弹吉他这点近来体验尤其强烈。之前弹吉他是下载个谱子照着弹,最近更多是就着一个backing自己即兴乱弹。虽然知道弹得很烂,但确实能体验心流。书里也提到孔子说的“立于礼,成于乐”,古人诚不欺我。 现在都说内卷,为什么卷?因为大家的差异小了,或者说是大家都不差了。如果此时再强调竞争,边际收益是很低的。特别是对个人而言,结果可能是多赚了几块钱,但身心俱疲。既然已经不差了,而且从竞争获胜获得的乐趣已经变淡,为什么不转而直接去获取快乐呢? 克服现代生活的焦虑与沮丧,必须先从社会环境中独立出来,不再孜孜以求,只以社会赋予的赏罚为念 塑造乐趣 书里有一段话描述乐趣出现的八项元素 首先,这种体验出现在我们面临一份可完成的工作时。其次,我们必须能够全神贯注于这件事情。第三和第四,这项任务有明确的目标和即时的反馈。第五,我们能深入而毫不牵强地投入到行动之中,日常生活的忧虑和沮丧都因此一扫而空。第六,充满乐趣的体验使人觉得能自由控制自己的行动。第七,进入“忘我”状态,但心流体验告一段落后,自我感觉又会变得强烈。第八,时间感会改变——几小时犹如几分钟,几分钟也可能变得像几小时那么漫长 前四项可谓是塑造乐趣的方法,比较实用。回到刚才弹吉他的例子,我感觉即兴弹比练谱有乐趣的原因就是难度比较容易匹配我的水平,也就是在做“可完成的工作”,但相比练谱也缺少“明确的目标”。 书中还有一个塑造乐趣的方法是竞争。当然这里是指良性的竞争,例如体育活动里的对抗,而不是内卷里的低效竞争。这点我也很认同,就像是我之前做Kaggle比赛的时候,竞争让我进步非常快。 英文的“竞争”(compete)一词,源自拉丁文的“con petire”,意为“共同追寻”。每个人追求的都是实现自己的潜能,在别人逼迫我们全力以赴时,这份差使就变得容易些。 以上就是所有内容,如果觉得自己最近日子过得没啥意思,不妨读读这本书。

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

2021面试总结

今年由于各种原因参加了很多面试,面过的候选人可能快一百个了。其中有经验丰富的高P,也有初出茅庐的应届生。面的多了之后不仅能发现候选人的问题,也能发现自己作为面试官的问题。就像古人说的,千里马常有而伯乐不常有,为了以后能更好地评估候选人(主要是工程师)的水平,我做了一些总结,也分享出来跟大家讨论。 明确要招什么样的人 正所谓看人下菜碟,在面试之前要对候选人有个大致的定位,当然这个不是静态的,可以在面试的过程中调整。总体来说我目前接触过的大概分成三类 工具人。对应的大概是三年以内工作经验的人,可以完成安排的任务,不需要考虑为什么做这个。 能独立解决问题的人。三年以上经验的候选人我会期待他能够在工具人的基础上具有方案设计能力,如果能有一些业务洞察就更好了。 能带团队做业务的人。带队大佬还聊得不多,这篇就先不谈。 考察什么 目前我认为有以下三个方面需要考察: 业务能力 专业技能 数理逻辑能力 知识面 三观 软技能 沟通能力 领导力 首先业务能力达标是候选人需要满足的基本要求;然后是三观,跟一个价值取向正确、积极向上、有趣的人一起工作一点会让自己和公司收益;最后是软技能,基本是作为加分项。 怎么考察 聊项目 这部分对面试官的要求最高,因为候选人做的东西可能超出了自己的知识范围,但我觉得这部分又特别重要。以技术面为例,候选人在项目上花的时间远比花在面试时的代码测试长,所以这部分更能综合地看出候选人的上限。 聊项目我认为有几个层次: 第一个层次是聊出候选人在其中的工作量和项目的完成度。一个合格的工具人应该能做到高完成度并讲清楚自己做的事情。 第二个层次是聊价值和动机。为什么做这些事情,技术指标和业务指标怎么对应,为什么按照简历里写的方案做。一个能独立解决问题的人应该能回答出这些问题。 聊项目的时候我觉得应该避免太泛也难量化评估的问题,例如:之前项目里遇到的最大困难是什么,怎么解决的。如果是候选人是大神应该觉得都没啥困难,真的困难大概率也还没有被解决。 方案设计 这是我目前认为比较好的一种考察方式,刚才聊项目是顺着候选人来,方案设计则是由面试官来主导。这里可以选一个工作里实际遇到且比较开放的问题和候选人进行讨论,几乎可以考察到业务能力里面的各个维度。可以提前准备一些比较细致、刁钻的follow up,不仅能考察候选人,说不定还能对自己的工作产生启发。 代码测试 代码测试也很重要,但我认为这是用来控制下限的。考LeetCode上的数据结构和算法感觉也不是一个特别好的选择,试问大家工作中用过几次动态规划呢。所以我比较倾向于考一些即使没有见过也能用基本编程思想解决的问题。 数学题、基础知识 我之前面试的时候也被问过数学题和基础知识,这是考察之前项目不是特别对口的候选人或者应届生的一个好手段。对于比较有经验的候选人可以让总结一下近些年技术的发展脉络,这个对逻辑思维和知识面是个不错的考察。 聊天 聊天主要是对三观和软技能的考察,最直接的可以聊一下换工作的动机,对年轻候选人可以聊聊人生规划,对老人可以聊聊对领域和行业的看法,有个前辈经常问候选人平常有什么兴趣爱好等等。 让候选人问问题 从我面试的结果看,不同人问的问题不太一样。不少人会问公司规模、工作节奏相关的问题,有的会问自己面试的岗位定位业务发展相关的问题。有两个候选人的问题我印象比较深刻。一个是说他看了应用商店里的评论后发现不少用户在抱怨一个问题,问为什么会出现这个问题,从这个问题可以看出候选人主动做了功课,还对后面的技术问题有好奇心。另一个问我当时为什么决定加入这家公司,这个问题不仅可以拉近和面试官的距离,也可以给候选人带去很多信息,如果一个新人问这个问题,那ta大概率是比较有探索精神的一类人。 建立打分表 不管公司的打分表是怎么样的,最好都给自己的考察方案设计一套最合适的打分表,提前确定好各个维度的权重和打分细则,这样不仅能更加公平地做候选人之间的比较,也能让面试更加有针对性。这里也给出我针对3-5年经验工程师的打分表,算是对上面内容的总结。 维度 分值 细则 备注 项目:工作量和完成度 6 工作量正常、完成良好4分,能回答各种问题2分 项目:价值和动机 5 抓住对业务的价值重点2.5分,技术方案综合考虑效果、成本、可行性2.5分 方案设计 5 方案能满足功能3分,方案体现优秀的技术深度、广度2分 代码测试 6 正确、完整实现功能4分,速度快、代码风格好2分 三观 2 正常人1分,特别正2分 不正常人直接淘汰 软技能 1 沟通、表达特别好1分 总和 25

December 20, 2021 · 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