本教程致力于帮助同学们快速入门NLP,并掌握各个任务的SOTA模型。
机器学习是一门既重理论又重实践的学科,想一口吃下这个老虎是不可能的,因此学习应该是个循环且逐渐细化的过程。
首先要有个全局印象,知道minimum的情况下要学哪些知识点:
之后就可以开始逐个击破,但也不用死磕,控制好目标难度,先用三个月时间进行第一轮学习:
- 读懂机器学习、深度学习原理,不要求手推公式
- 了解经典任务的baseline,动手实践,看懂代码
- 深入一个应用场景,尝试自己修改模型,提升效果
迈过了上面这道坎后,就可以重新回归理论,提高对自己的要求,比如手推公式、盲写模型、拿到比赛Top等。
机器学习最初入门时对数学的要求不是很高,掌握基础的线性代数、概率论就可以了,正常读下来的理工科大学生以上应该都没问题,可以直接开始学,碰到不清楚的概念再去复习。
统计机器学习部分,建议初学者先看懂线性分类、SVM、树模型和图模型,这里推荐李航的「统计学习方法」,薄薄的摸起来没有很大压力,背着也方便,我那本已经翻四五遍了。喜欢视频课程的话可以看吴恩达的「CS229公开课」或者林田轩的「机器学习基石」。但不管哪个教程,都不必要求一口气看完吃透。
深度学习部分,推荐吴恩达的「深度学习」网课、李宏毅的「深度学习」网课或者邱锡鹏的「神经网络与深度学习」教材。先弄懂神经网络的反向传播推导,然后去了解词向量和其他的编码器的核心思想、前向反向过程。
有了上述的基础后,应该就能看懂模型结构和论文里的各种名词公式了。接下来就是了解NLP各个经典任务的baseline,并看懂源码。对于TF和Pytorch的问题不用太纠结,接口都差不多,找到什么就看什么,自己写的话建议Pytorch。
快速了解经典任务脉络可以看综述,建议先了解一两个该任务的经典模型再去看,否则容易云里雾里:
- 2020 A Survey on Text Classification: From Shallow to Deep Learning
- 2020 A Survey on Recent Advances in Sequence Labeling from Deep Learning Models
- 2020 Evolution of Semantic Similarity - A Survey
- 2017 Neural text generation: A practical guide
- 2018 Neural Text Generation: Past, Present and Beyond
- 2019 The survey: Text generation models in deep learning
- 2020 Efficient Transformers: A Survey
文本分类是NLP应用最多且入门必备的任务,TextCNN堪称第一baseline,往后的发展就是加RNN、加Attention、用Transformer、用GNN了。第一轮不用看得太细,每类编码器都找个代码看一下即可,顺便也为其他任务打下基础。
但如果要做具体任务的话,建议倒序去看SOTA论文,了解各种技巧,同时善用知乎,可以查到不少提分方法。
文本匹配会稍微复杂些,它有双塔和匹配两种任务范式。双塔模型可以先看SiamCNN,了解完结构后,再深入优化编码器的各种方法;基于匹配的方式则在于句子表示间的交互,了解BERT那种TextA+TextB拼接的做法之后,可以再看看阿里的RE2这种轻量级模型的做法:
序列标注主要是对Embedding、编码器、结果推理三个模块进行优化,可以先读懂Bi-LSTM+CRF这种经典方案的源码,再去根据需要读论文改进。
文本生成是最复杂的,具体的SOTA模型我还没梳理完,可以先了解Seq2Seq的经典实现,比如基于LSTM的编码解码+Attention、纯Transformer、GPT2以及T5,再根据兴趣学习VAE、GAN、RL等。
语言模型虽然很早就有了,但18年BERT崛起之后才越来越被重视,成为NLP不可或缺的一个任务。了解BERT肯定是必须的,有时间的话再多看看后续改进,很经典的如XLNet、ALBERT、ELECTRA还是不容错过的。
上述任务都了解并且看了一些源码后,就该真正去当炼丹师了。千万别满足于跑通别人的github代码,最好去参加一次Kaggle、天池、Biendata等平台的比赛,享受优化模型的摧残。
Kaggle的优点是有各种kernel可以学习,国内比赛的优点是中文数据方便看case。建议把两者的优点结合,比如参加一个国内的文本匹配比赛,就去kaggle找相同任务的kernel看,学习别人的trick。同时多看些顶会论文并复现,争取做完一个任务后就把这个任务技巧摸清。
P.S. 对照文首脑图看效果更佳
Model | Year | Method | Venue | Code |
ReNN | 2011 | RAE | EMNLP | link |
2012 | MV-RNN | EMNLP | link | |
2013 | RNTN | EMNLP | link | |
2014 | DeepRNN | NIPS | ||
MLP | 2014 | Paragraph-Vec | ICML | link |
2015 | DAN | ACL | link | |
RNN | 2015 | Tree-LSTM | ACL | link |
2015 | S-LSTM | ICML | ||
2015 | TextRCNN | AAAI | link | |
2015 | MT-LSTM | EMNLP | link | |
2016 | oh-2LSTMp | ICML | link | |
2016 | BLSTM-2DCNN | COLING | link | |
2016 | Multi-Task | IJCAI | link | |
2017 | DeepMoji | EMNLP | link | |
2017 | TopicRNN | ICML | link | |
2017 | Miyato et al. | ICLR | link | |
2018 | RNN-Capsule | TheWebConf | link | |
CNN | 2014 | TextCNN | EMNLP | link |
2014 | DCNN | ACL | link | |
2015 | CharCNN | NIPS | link | |
2016 | SeqTextRCNN | NAACL | link | |
2017 | XML-CNN | SIGIR | link | |
2017 | DPCNN | ACL | link | |
2017 | KPCNN | IJCAI | ||
2018 | TextCapsule | EMNLP | link | |
2018 | HFT-CNN | EMNLP | link | |
2020 | Bao et al. | ICLR | link | |
Attention | 2016 | HAN | NAACL | link |
2016 | BI-Attention | NAACL | link | |
2016 | LSTMN | EMNLP | ||
2017 | Lin et al. | ICLR | link | |
2018 | SCM | COLING | link | |
2018 | ELMo | NAACL | link | |
2018 | BiBloSA | ICLR | link | |
2019 | AttentionXML | NIPS | link | |
2019 | HAPN | EMNLP | ||
2019 | Proto-HATT | AAAI | link | |
2019 | STCKA | AAAI | link | |
Transformer | 2019 | BERT | NAACL | link |
2019 | Sun et al. | CCL | link | |
2019 | XLNet | NIPS | link | |
2019 | RoBERTa | link | ||
2020 | ALBERT | ICLR | link | |
GNN | 2018 | DGCNN | TheWebConf | link |
2019 | TextGCN | AAAI | link | |
2019 | SGC | ICML | link | |
2019 | Huang et al. | EMNLP | link | |
2019 | Peng et al. | |||
2020 | MAGNET | ICAART | link | |
Others | 2017 | Miyato et al. | ICLR | link |
2018 | TMN | EMNLP | ||
2019 | Zhang et al. | NAACL | link | |
Structure | Year | Model | Venue | Ref |
Siamese | 2013 | DSSM | CIKM | link |
2015 | SiamCNN | ASRU | link | |
2015 | Skip-Thought | NIPS | link | |
2016 | Multi-View | EMNLP | link | |
2016 | FastSent | ACL | link | |
2016 | SiamLSTM | AAAI | link | |
2017 | Joint-Many | EMNLP | link | |
2017 | InferSent | EMNLP | link | |
2017 | SSE | EMNLP | link | |
2018 | GenSen | ICLR | link | |
2018 | USE | ACL | link | |
2019 | Sentence-BERT | EMNLP | link | |
2020 | BERT-flow | EMNLP | link | |
Interaction | 2016 | DecAtt | EMNLP | link |
2016 | PWIM | ACL | link | |
2017 | ESIM | ACL | link | |
2018 | DIIN | ICLR | link | |
2019 | HCAN | EMNLP | link | |
2019 | RE2 | ACL | link | |
Ref | Year | Venue | Embedding Module | Context Encoder | Inference Module | Tasks | ||
external input | word embedding | character-level | ||||||
link | 2016 | ACL | \ | Glove | CNN | Bi-LSTM | CRF | POS, NER |
link | 2018 | ACL | \ | Word2vec | Bi-LSTM | Bi-LSTM | Softmax | POS |
link | 2018 | NAACL | \ | Glove | Bi-LSTM | Bi-LSTM | CRF | POS |
link | 2018 | AAAI | \ | Glove | Bi-LSTM+LM | Bi-LSTM | CRF | POS, NER, chunking |
link | 2016 | ACL | \ | Polyglot | Bi-LSTM | Bi-LSTM | CRF | POS |
link | 2017 | ACL | \ | Word2vec | Bi-LSTM | Bi-LSTM+LM | CRF | POS, NER, chunking |
link | 2017 | ACL | \ | Senna | CNN | Bi-LSTM+pre LM | CRF | NER, chunking |
link | 2018 | COLING | Pre LM emb | Glove | Bi-LSTM | Bi-LSTM | CRF | POS, NER, chunking |
link | 2018 | IJCAI | \ | \ | Bi-LSTM | Bi-LSTM | LSTM+Softmax | POS, NER |
link | 2018 | ACL | \ | Glove | Bi-LSTM+LM | Bi-LSTM | CRF+Semi-CRF | NER |
link | 2017 | COLING | Spelling, gaz | Senna | \ | Mo-BiLSTM | Softmax | NER, chunking |
link | 2018 | ACL | \ | Word2vec | Bi-LSTM | Parallel Bi-LSTM | Softmax | NER |
link | 2017 | ICLR | \ | Senna, Glove | Bi-GRU | Bi-GRU | CRF | POS, NER, chunking |
link | 2015 | \ | Trained on wikipedia | Bi-LSTM | Bi-LSTM | Softmax | POS | |
link | 2016 | ACL | Cap, lexicon | Senna | CNN | Bi-LSTM | CRF | NER |
link | 2016 | COLING | \ | Word2vec | Bi-LSTM | Bi-LSTM | CRF | POS, NER, chunking |
link | 2018 | EMNLP | \ | Glove | InNet | Bi-LSTM | CRF | POS, NER, chunking |
link | 2017 | ACL | Spelling, gaz | Senna | \ | INN | Softmax | POS |
link | \ | Glove | \ | Bi-LSTM | EL-CRF | Citation field extraction | ||
link | 2016 | EMNLP | \ | Trained with skip-gram | \ | Bi-LSTM | Skip-chain CRF | Clinical entities detection |
link | 2018 | Word shapes, gaz | Glove | CNN | Bi-LSTM | CRF | NER | |
link | 2011 | Cap, gaz | Senna | \ | CNN | CRF | POS, NER, chunking, SRL | |
link | 2017 | CCL | \ | Glove | CNN | Gated-CNN | CRF | NER |
link | 2017 | EMNLP | \ | Word2vec | \ | ID-CNN | CRF | NER |
link | 2016 | NAACL | \ | Word2vec | Bi-LSTM | Bi-LSTM | CRF | NER |
link | 2015 | Spelling, gaz | Senna | \ | Bi-LSTM | CRF | POS, NER, chunking | |
link | 2014 | ICML | \ | Word2vec | CNN | CNN | CRF | POS |
link | 2017 | AAAI | \ | Senna | CNN | Bi-LSTM | Pointer network | Chunking, slot filling |
link | 2017 | \ | Word2vec | \ | Bi-LSTM | LSTM | Entity relation extraction | |
link | 2018 | LS vector, cap | SSKIP | Bi-LSTM | LSTM | CRF | NER | |
link | 2018 | ICLR | \ | Word2vec | CNN | CNN | LSTM | NER |
link | 2018 | IJCAI | \ | Glove | \ | Bi-GRU | Pointer network | Text segmentation |
link | 2017 | EMNLP | \ | \ | CNN | Bi-LSTM | Softmax | POS |
link | 2017 | CoNLL | \ | Word2vec, Fasttext | LSTM+Attention | Bi-LSTM | Softmax | POS |
link | 2019 | ICASSP | \ | Glove | CNN | Bi-LSTM | NCRF transducers | POS, NER, chunking |
link | 2018 | \ | \ | Bi-LSTM+AE | Bi-LSTM | Softmax | POS | |
link | 2017 | Lexicons | Glove | CNN | Bi-LSTM | Segment-level CRF | NER | |
link | 2019 | AAAI | \ | Glove | CNN | GRN+CNN | CRF | NER |
link | 2020 | \ | Glove | CNN | Bi-LSTM+SA | CRF | POS, NER, chunking | |
Year | Model | Code |
2018 | BERT | link |
2019 | WWM | link |
2019 | Baidu ERNIE1.0 | link |
2019 | Baidu ERNIE2.0 | link |
2019 | SpanBERT | link |
2019 | RoBERTa | link |
2019 | XLNet | link |
2019 | StructBERT | |
2019 | ELECTRA | link |
2019 | ALBERT | link |
2020 | DeBERTa | link |
论文:https://arxiv.org/abs/1607.01759
代码:https://github.com/facebookresearch/fastText
Fasttext是Facebook推出的一个便捷的工具,包含文本分类和词向量训练两个功能。
Fasttext的分类实现很简单:把输入转化为词向量,取平均,再经过线性分类器得到类别。输入的词向量可以是预先训练好的,也可以随机初始化,跟着分类任务一起训练。
Fasttext直到现在还被不少人使用,主要有以下优点:
- 模型本身复杂度低,但效果不错,能快速产生任务的baseline
- Facebook使用C++进行实现,进一步提升了计算效率
- 采用了char-level的n-gram作为附加特征,比如paper的trigram是 [pap, ape, per],在将输入paper转为向量的同时也会把trigram转为向量一起参与计算。这样一方面解决了长尾词的OOV (out-of-vocabulary)问题,一方面利用n-gram特征提升了表现
- 当类别过多时,支持采用hierarchical softmax进行分类,提升效率
对于文本长且对速度要求高的场景,Fasttext是baseline首选。同时用它在无监督语料上训练词向量,进行文本表示也不错。不过想继续提升效果还需要更复杂的模型。
论文:https://arxiv.org/abs/1408.5882
代码:https://github.com/yoonkim/CNN_sentence
TextCNN是Yoon Kim小哥在2014年提出的模型,开创了用CNN编码n-gram特征的先河。
模型结构如图,图像中的卷积都是二维的,而TextCNN则使用「一维卷积」,即filter_size * embedding_dim
,有一个维度和embedding相等。这样filter_size就能抽取n-gram的信息。以1个样本为例,整体的前向逻辑是:
- 对词进行embedding,得到
[seq_length, embedding_dim]
- 用N个卷积核,得到N个
seq_length-filter_size+1
长度的一维feature map - 对feature map进行max-pooling(因为是时间维度的,也称max-over-time pooling),得到N个
1x1
的数值,拼接成一个N维向量,作为文本的句子表示 - 将N维向量压缩到类目个数的维度,过Softmax
在TextCNN的实践中,有很多地方可以优化(参考这篇论文):
- Filter尺寸:这个参数决定了抽取n-gram特征的长度,这个参数主要跟数据有关,平均长度在50以内的话,用10以下就可以了,否则可以长一些。在调参时可以先用一个尺寸grid search,找到一个最优尺寸,然后尝试最优尺寸和附近尺寸的组合
- Filter个数:这个参数会影响最终特征的维度,维度太大的话训练速度就会变慢。这里在100-600之间调参即可
- CNN的激活函数:可以尝试Identity、ReLU、tanh
- 正则化:指对CNN参数的正则化,可以使用dropout或L2,但能起的作用很小,可以试下小的dropout率(<0.5),L2限制大一点
- Pooling方法:根据情况选择mean、max、k-max pooling,大部分时候max表现就很好,因为分类任务对细粒度语义的要求不高,只抓住最大特征就好了
- Embedding表:中文可以选择char或word级别的输入,也可以两种都用,会提升些效果。如果训练数据充足(10w+),也可以从头训练
- 蒸馏BERT的logits,利用领域内无监督数据
- 加深全连接:原论文只使用了一层全连接,而加到3、4层左右效果会更好
TextCNN是很适合中短文本场景的强baseline,但不太适合长文本,因为卷积核尺寸通常不会设很大,无法捕获长距离特征。同时max-pooling也存在局限,会丢掉一些有用特征。另外再仔细想的话,TextCNN和传统的n-gram词袋模型本质是一样的,它的好效果很大部分来自于词向量的引入,解决了词袋模型的稀疏性问题。
论文:https://ai.tencent.com/ailab/media/publications/ACL3-Brady.pdf
代码:https://github.com/649453932/Chinese-Text-Classification-Pytorch
上面介绍TextCNN有太浅和长距离依赖的问题,那直接多怼几层CNN是否可以呢?感兴趣的同学可以试试,就会发现事情没想象的那么简单。直到2017年,腾讯才提出了把TextCNN做到更深的DPCNN模型:
上图中的ShallowCNN指TextCNN。DPCNN的核心改进如下:
- 在Region embedding时不采用CNN那样加权卷积的做法,而是对n个词进行pooling后再加个1x1的卷积,因为实验下来效果差不多,且作者认为前者的表示能力更强,容易过拟合
- 使用1/2池化层,用size=3 stride=2的卷积核,直接让模型可编码的sequence长度翻倍(自己在纸上画一下就get啦)
- 残差链接,参考ResNet,减缓梯度弥散问题
凭借以上一些精妙的改进,DPCNN相比TextCNN有1-2个百分点的提升。
论文:https://dl.acm.org/doi/10.5555/2886521.2886636
代码:https://github.com/649453932/Chinese-Text-Classification-Pytorch
除了DPCNN那样增加感受野的方式,RNN也可以缓解长距离依赖的问题。下面介绍一篇经典TextRCNN。
模型的前向过程是:
- 得到单词 i 的表示
$e(w_i)$ - 通过RNN得到左右双向的表示
$c_l(w_i)$ 和$c_r(w_i)$ - 将表示拼接得到
$x_i = [c_l(w_i);e(w_i);c_r(w_i)]$ ,再经过变换得到$y_i=tanh(Wx_i+b)$ - 对多个
$y_i$ 进行 max-pooling,得到句子表示$y$ ,在做最终的分类
这里的convolutional是指max-pooling。通过加入RNN,比纯CNN提升了1-2个百分点。
论文:https://www.aclweb.org/anthology/P16-2034.pdf
代码:https://github.com/649453932/Chinese-Text-Classification-Pytorch
从前面介绍的几种方法,可以自然地得到文本分类的框架,就是先基于上下文对token编码,然后pooling出句子表示再分类。在最终池化时,max-pooling通常表现更好,因为文本分类经常是主题上的分类,从句子中一两个主要的词就可以得到结论,其他大多是噪声,对分类没有意义。而到更细粒度的分析时,max-pooling可能又把有用的特征去掉了,这时便可以用attention进行句子表示的融合:
BiLSTM就不解释了,要注意的是,计算attention score时会先进行变换: $$ M = tanh(H) $$
其中
这个加attention的套路用到CNN编码器之后代替pooling也是可以的,从实验结果来看attention的加入可以提高2个点。如果是情感分析这种由句子整体决定分类结果的任务首选RNN。
论文:https://www.aclweb.org/anthology/N16-1174.pdf
代码:https://github.com/richliao/textClassifier
上文都是句子级别的分类,虽然用到长文本、篇章级也是可以的,但速度精度都会下降,于是有研究者提出了层次注意力分类框架,即Hierarchical Attention。先对每个句子用 BiGRU+Att 编码得到句向量,再对句向量用 BiGRU+Att 得到doc级别的表示进行分类:
方法很符合直觉,不过实验结果来看比起avg、max池化只高了不到1个点(狗头,真要是很大的doc分类,好好清洗下,fasttext其实也能顶的(捂脸。
BERT的原理代码就不用放了叭~
BERT分类的优化可以尝试:
- 多试试不同的预训练模型,比如RoBERT、WWM、ALBERT
- 除了 [CLS] 外还可以用 avg、max 池化做句表示,甚至可以把不同层组合起来
- 在领域数据上增量预训练
- 集成蒸馏,训多个大模型集成起来后蒸馏到一个上
- 先用多任务训,再迁移到自己的任务
除了上述常用模型之外,还有Capsule Network、TextGCN等红极一时的模型,因为涉及的背景知识较多,本文就暂不介绍了(嘻嘻)。
虽然实际的落地应用中比较少见,但在机器学习比赛中还是可以用的。Capsule Network被证明在多标签迁移的任务上性能远超CNN和LSTM,但这方面的研究在18年以后就很少了。TextGCN则可以学到更多的global信息,用在半监督场景中,但碰到较长的需要序列信息的文本表现就会差些。
模型说得差不多了,下面介绍一些自己的数据处理血泪经验,如有不同意见欢迎讨论~
首先是标签体系的构建,拿到任务时自己先试标一两百条,看有多少是难确定(思考1s以上)的,如果占比太多,那这个任务的定义就有问题。可能是标签体系不清晰,或者是要分的类目太难了,这时候就要找项目owner去反馈而不是继续往下做。
其次是训练评估集的构建,可以构建两个评估集,一个是贴合真实数据分布的线上评估集,反映线上效果,另一个是用规则去重后均匀采样的随机评估集,反映模型的真实能力。训练集则尽可能和评估集分布一致,有时候我们会去相近的领域拿现成的有标注训练数据,这时就要注意调整分布,比如句子长度、标点、干净程度等,尽可能做到自己分不出这个句子是本任务的还是从别人那里借来的。
最后是数据清洗:
- 去掉文本强pattern:比如做新闻主题分类,一些爬下来的数据中带有的XX报道、XX编辑高频字段就没有用,可以对语料的片段或词进行统计,把很高频的无用元素去掉。还有一些会明显影响模型的判断,比如之前我在判断句子是否为无意义的闲聊时,发现加个句号就会让样本由正转负,因为训练预料中的闲聊很少带句号(跟大家的打字习惯有关),于是去掉这个pattern就好了不少
- 纠正标注错误:这个我真的屡试不爽,生生把自己从一个算法变成了标注人员。简单的说就是把训练集和评估集拼起来,用该数据集训练模型两三个epoch(防止过拟合),再去预测这个数据集,把模型判错的拿出来按 abs(label-prob) 排序,少的话就自己看,多的话就反馈给标注人员,把数据质量搞上去了提升好几个点都是可能的
任务简单的话(比如新闻分类),直接用fasttext就可以达到不错的效果。
想要用BERT的话,最简单的方法是粗暴截断,比如只取句首+句尾、句首+tfidf筛几个词出来;或者每句都预测,最后对结果综合。
另外还有一些魔改的模型可以尝试,比如XLNet、Reformer、Longformer。
如果是离线任务且来得及的话还是建议跑全部,让我们相信模型的编码能力。
自从用了BERT之后,很少受到数据不均衡或者过少的困扰,先无脑训一版。
如果样本在几百条,可以先把分类问题转化成匹配问题,或者用这种思想再去标一些高置信度的数据,或者用自监督、半监督的方法。
在实际的应用中,鲁棒性是个很重要的问题,否则在面对badcase时会很尴尬,怎么明明那样就分对了,加一个字就错了呢?
这里可以直接使用一些粗暴的数据增强,加停用词加标点、删词、同义词替换等,如果效果下降就把增强后的训练数据洗一下。
当然也可以用对抗学习、对比学习这样的高阶技巧来提升,一般可以提1个点左右,但不一定能避免上面那种尴尬的情况。
文本分类是工业界最常用的任务,同时也是大多数NLPer入门做的第一个任务,我当年就是啥都不会,从训练到部署地实践了文本分类后就顺畅了。上文给出了不少模型,但实际任务中常用的也就那几个,下面是快速选型的建议:
实际上,落地时主要还是和数据的博弈。数据决定模型的上限,大多数人工标注的准确率达到95%以上就很好了,而文本分类通常会对准确率的要求更高一些,与其苦苦调参想fancy的结构,不如好好看看badcase,做一些数据增强提升模型鲁棒性更实用。