Kaggle 的 NLP 比赛 Quora Insincere Questions Classification 总结
比赛简介
这是一个二分类的问题, 目标是给定的Quora中的问题文本序列, 判断该问题是否为一个真诚的问题(insincere classification)训练集样本数1306122条Quora问题文本序列, 第一阶段测试集样本数56370条. 这是一个kernel-only的比赛, 即所有的提交结果必须通过kaggle提供的kernel环境执行生成(如果使用Telsa K80显卡则限kernel运行时间2h, 不使用显卡则为6h, 内存16G), 此外不允许连接互联网及使用任何外部数据, Kaggle官方提供了4个Embedding文件, 包括GloVe、Paragram、WikiNews(Fasttext)和GoogleNews(Word2Vec)四种预训练词向量
关于四种词向量的简介:
- Word2vec: word2vec严格来说是一个工具, 大部分情况下我们提到word2vec都是指其Skip-Gram结构所生成的词向量模型来指代word2vec, Skip-Gram结构利用中间词生成上下文单词的向量表示, 而另一个结构CBOW则是根据上下文单词向量表示学习中间词的向量表示. word2vec的两个训练trick: 1)层次softmax, 层次softmax替代传统softmax加速计算, 相比传统softmax, 层次softmax实际上就是根据预料中单词的词频来构建一个霍夫曼树, 每个叶节点都是语料库中的一个单词, 通过根节点到叶节点的路径来求出概率, 把N分类问题转换成logN次二分类问题, 其满足概率和归一化; 2)负采样, 负采样是在softmax计算时不使用所有的单词, 而是采样指定数量的负样本单词, 不满足概率和归一化; 对比: 对于不频繁出现的生僻词层次 softmax 学习效果差(因为出现的少对网络更新贡献低), 而负采样是有随机性的对生僻词的学习效果好但是对频繁词会差点.
- Fasttext: fasttext和word2vec都是同一个作者提出的, 它和word2vec的CBOW模型结构类似但是fasttext是个分类模型, 词向量是其训练过程中的副产物. fasttext的输入不止是上下文的词向量还包含上下文单词的ngram特征, 经过叠加平均后经过一个线性映射得到一个文档向量(隐层), 随后输出输入词序列所属不同类别的概率. fasttext最大的特点就是快, 也采用了分层softmax和负采样技术来提升训练速度, 且除了输出层外均使用线性激活函数, 训练的更快. fasttext比word2vec快的地方在于: word2vec对一条句子中的每个单词都要做一次训练, 而fasttext直接对一条语句训练, 此外, 输出层的霍夫曼树的叶节点个数也小于word2vec(前者的叶节点是类别, 后者是不同的单词)
- GloVe: GloVe由Stanford大学的NLP Group在2014年发表的论文中提出的, 与word2vec和fasttext不同, 其研究对象是单词之间的共现率, 先构造一个共现矩阵, 表示上下文单词和在特定上下文窗口大小下的共同出现的次数, 距离越远的单词其共现率的计算权重越小. 其损失函数核心部分是一个平方损失函数, 和即为目标词向量, 这个损失函数的含义是希望两个共现的单词的表示应该和他们共现的频率的对数尽可能的接近.
- Paragram: paragram是在paraphrase语料库上利用Skip-Gram模型得到的词向量为基础输入RNN进行fine-tuning训练得到词向量, 其本身并没有什么特殊之处.
整体流程
预处理
预处理中包含了数据清洗、文本序列/向量化等操作
数据清洗
由于Quora官方的给数据相对来说比较”干净”, 数据清洗就主要是对特殊字符和拼写错误单词的处理以便经过预处理后, 单词能够在所给的Embedding文件中找到对应的向量表示
我们所做的清洗包括:
- 把特殊字符诸如”, . ! ? + -“等单独隔开防止影响到单词的表示
- 把”what’s, I’ve”等分开为”what is, I have”
- 把”qoura, qouta”等错误拼写的单词替换为正确的拼写单词
- 把所有非字母的字符全部移除并填充缺失值
序列/向量化
向量化的目的是为了把输入的文本序列转换成一个序列化、向量化的表示以便于时序模型如LSTM和GRU读取训练. 我们使用keras的Tokenizer来进行文本序列化处理, 简单来说Tokenizer需要我们给定一个参数num_words表示需要从预料中保存出现频率最高的num_words个单词用于生成向量. 实际上Tokenizer就是在所提供的预料数据上使用fit_on_texts 方法生成一个字典, 使得每个单词有着对应的word_index, 再使用texts_to_sequences方法将每条预料转换成对应的向量表示, 这个向量表示实际上就是这条预料中的单词出现在字典中的index, 在后续的深度模型中我们会使用字典生成预训练词向量矩阵, 随后根据这个index向量来获取各个单词对应的词向量(如果单词不存在于预训练词向量中, 则用所有词向量的均值和标准差作为高斯分布函数 np.random.normal 的参数生成相应的向量表示). 在比赛中我们尝试不同的num_words最终设置了num_words = 950001
2
3
4
5
6
7somestr = ['ha ha gua angry','howa ha gua excited naive']
tok = tt.Tokenizer()
tok.fit_on_texts(somestr)
tok.word_index
Out[90]: {'angry': 3, 'excited': 5, 'gua': 2, 'ha': 1, 'howa': 4, 'naive': 6}
tok.texts_to_sequences(somestr)
Out[91]: [[1, 1, 2, 3], [4, 1, 2, 5, 6]]
填充序列保持长度一致
经过Tokenizer对每条样本的向量化处理后还有一个问题就是各条处理后的训练样本长度是不一致的, 这样无法直接被keras的RNN模型所使用, 所以我们采用了keras的pad_sequence方法对所有的训练数据做填充/截断处理, 我们统计了所有训练数据的平均长度约为70, 所以我们将所有的样本按照长度70来填充/截断.
模型选择
模型选择花费了我们很多时间尝试, 由于限制了kernel的使用时间, 所以模型方面不能太复杂, 我们主要在特征工程 + 传统机器学习模型和深度模型之间做实验选择
传统机器学习模型
由于这个比赛和提取语义有很大关系, 传统的机器学习模型不是很适用, 我们尝试过特征工程加上LightGBM, 但是LB得分很不如人意所以我们基本放弃了传统机器学习的思路.
深度模型
深度模型在实际使用中表现最好, 很遗憾由于不能使用外部数据导致一些state-of-art的模型比如NAACL 2018的outstanding paper ELMO、ACL 2018上发表的ULMFiT以及Google AI Language与2018年10月提出的BERT都无法使用, 我们在线下测试了这些模型的表现十分出色, 但是线上无法使用也无法得知最终的表现会如何, 在线上我们尝试过很多模型包括:
- 单模型: CNN, LSTM, GRU, SRU, QRNN以及IndRNN
- 多模型结合: CNN+LSTM, LSTM+GRU, GRU+LSTM, LSTM+SRU, BiLSTM+BiGRU+Attention+Pooling, BiLSTM+BiGRU+Capsule+CRF
- 多任务模型: 不仅仅以区分出insincere问题为目标, 我们可以在训练的过程中加入其他的目标同时学习, 例如先对每条数据进行简单的语义分析得出一个情感分数, 把这个分数作为标签与原来的insincere标签一起作为新的标签, 通过联合的训练两个目标带来的好处包括: 1. 加入了更多的特征信息, 降低过拟合的风险; 2. 模型在学习的过程中可能会获取一些额外的信息以供原目标任务参考.
最终确定确定的模型框架如下图: 采用五折交叉验证
关于Embedding预训练文件的使用:
Embedding文件在前面提到有四种, 我们尝试了不同Embedding文件单独使用的效果发现GloVe和Paragram的单文件效果最好, fasttext和word2vec的效果相对较差, 所以我们选定GloVe和Paragram作为最终使用的Embedding文件, 四种Embedding文件表现差异的原因我们推测是: GloVe和Paragram较word2vec和fasttext在学习词向量时除了局部信息(word2vec和fasttext主要考虑n-gram窗口内的单词信息)外还关注全局信息.此外关于如何使用这两个文件, 我们尝试过取平均和拼接两种方式, 发现针对不同的网络结构两种操作各有胜负, 但整体来说取平均的得分更高且更节省内存空间.
为什么是LSTM+GRU?
因为我们测试过不同的组合, 发现LSTM层后紧接着一个GRU层的表现最佳. GRU+LSTM效果也明显差于前者. 关于LSTM和GRU的原理网上有很多介绍, 这里不赘述.
CNN 应用于文本分类原理简介:
简单来说, 基于词向量把句子表示成矩阵形式 (维度为 ) 当做图像输入来处理, 具体参考这里.
关于Attention:
Attention机制最先在计算机视觉中被应用于图片识别的问题, 之后在自然语言处理(NLP)和计算机视觉(CV)中经常结合递归神经网络结构RNN、GRU、LSTM等深度学习算法, 被称之为Recurrent Attention Model(RAM), 其核心就是一个Encoder-Decoder的过程, 本质就是加权. Google Translate团队在论文 中将Attention定义为: 一个查询和键值对映射到输出的方法,Q、K、V均为向量,输出通过对V进行加权求和得到,权重就是Q、K相似度.
传统的encoder不论输入长短都将输入映射成一个固定维度的隐向量, 这样在序列很长时模型难以学习到合理的隐向量表示, 且输入序列的每个时刻 对输出 影响都相同, 这显然不合理. 引入Attention之后, 每个输入时刻 都对应一个中间隐状态 , 其相对于 时刻输出的Attention score计算方法为: 时刻输出的隐状态 和 每个输入时刻隐状态 进行点积运算得到.
常用Attention机制还包括Self-Attention以及Multi-heads Attention, 前者不是考率输入和输出之间的注意力权重, 而是仅考虑输入/输出序列本身之间的注意力关系, Self-Attention在计算过程中会直接将输入(出)中任意两个时刻的关系直接通过一个计算步骤直接联系起来而无需考虑距离,因此解决了长距离依赖问题. 除此外,Self-Attention对于增加计算的并行性也有直接帮助作用. 而Multi-heads Attention则可以简单的理解为多次Attention的结果进行拼接, 每次Attention的参数矩阵 不一样而已.
关于Capsule:
胶囊神经网络的核心就是用向量的加权求和来代替神经元的加权求和。它假设第 l 层有 k 个胶囊,每个胶囊可以是一个矩阵或者一个向量,在 NLP 场景中胶囊就是 LSTM 出来的每个隐藏状态,在 CNN 网络结构中胶囊就是特征图其中一个通道。
我们假设 LSTM 出来的序列是 72*120,隐藏状态维度是 120,序列长度是 72。
我们设置下一层胶囊数为 5,胶囊维度为 5;则 72*120 的矩阵经过胶囊层的变化如下:
序列中每个 120 维的向量都会由 5 个 120*5 的转换矩阵 W 转换成 5 个 5 维的向量,这么一来就会得到 5 个 (72, 5) 的矩阵;这是胶囊层的第一步转化,第二步是分别对这 5 个矩阵进行行向量加权,72 个行向量加权得到一个行向量,所以最终从胶囊层出来就是 5 个 5 维的向量。我们可以称它们是 5 个胶囊。加权这一步是由动态路由算法来完成,本质是EM算法。
关于CRF:
定义 : 隐状态 : 观测值
1) HMM:
隐马尔可夫模型(Hidden Markov model, HMM)是一个生成式模型, 其对隐状态和观测值联合建模, HMM 假设观测序列仅在相邻位置存在依赖关系, 引入了 表隐状态转移概率, 那么 HMM 的公式表示为:
其中, 表示由隐状态 转移到 的转移概率, 表示由隐状态 所能得到的可能的观测值 的概率. 累乘则是对观测序列的一种表示. 实际使用中, HMM 的参数 , 是隐状态集合, 是观测值集合, 是初始状态概率, 是转移概率 矩阵, 是观测值概率 矩阵, 常见的 POS 任务中, 就是观察到的句子, 是预测标注序列. HMM的缺陷是其基于观察序列中的每个元素都相互条件独立的假设. 即在任何时刻观察值仅仅与隐状态(即要标注的标签)有关. 对于简单的数据集, 这个假设倒是合理. 但大多数现实世界中的真实观察序列是由多个相互作用的特征和观察序列中较长范围内的元素之间的依赖而形成的. 而条件随机场(conditional random fiel, CRF)恰恰就弥补了这个缺陷.
2) MEMM:
最大熵马尔科夫模型(Maximum Entropy Markov Model, MEMM)是一个判别式模型, 在 HMM 中, 观测值 仅依赖于隐状态 , 但是实际情况中观测序列还需要很多其他特征来刻画, 为此提出 MEMM 模型允许”定义特征”来直接学习条件概率: , 对于整个序列则为:
其中, 通过最大熵模型来建模:
其中, 是归一化部分, 是特征函数, 函数需要人为定义, 是特征函数的权重, 通过训练得到.
MEMM 与 HMM 不同之处在于隐状态 不仅与观测值 有关, 还与上一时刻的隐状态 有关, MEMM 的流程:
step 1. 预定义特征函数
step 2. 在数据集上训练模型
step 3. 用训练好的模型做序列标注或序列概率问题
MEMM 的最大问题在于标记偏置陷入局部最优, 其原因在于归一化部分是局部归一化, 这个问题同样被 CRF 所解决.
3) CRF:
条件随机场(Conditional Random Fields, CRF)是一个判别式模型, 其定义为给定随机变量 (如观测序列) 条件下, 随机变量 (对应的隐状态序列) 的马尔科夫随机场. 一般在 NLP 领域中我们说的 CRF 都是指链式 CRF 如下图所示:
其公式表示为:
这个公式的含义就是给定整个观测序列的条件下, 计算出隐状态序列的概率, 是全局归一化解决 MEMM 模型的标记偏置问题, 下标表示构造的第几个特征函数, 每个特征函数的权重是. CRF 的使用流程和 MEMM 类似. 最后总结一下 HMM、 MEMM 和 CRF:
HMM 假设观测值 之间严格独立, 隐状态 仅与前一时刻的隐状态 有关, 观测值 仅依赖于隐状态 ; MEMM 根据观测序列 最大化隐状态(输出)序列 的条件概率 , 其假设隐状态 不仅与前一时刻的隐状态 有关, 还和当前观测值 有关; CRF 则是根据观测序列 最大化当前时刻的条件概率 , 其他部分和 MEMM 相似.
CRF不仅解决了HMM输出独立性假设的问题, 还解决了MEMM的标注偏置问题, MEMM容易陷入局部最优是因为只在局部做归一化, 而CRF统计了全局概率, 在做归一化时考虑了数据在全局的分布吗, 而不是仅仅在局部归一化, 这样就解决了MEMM中的标记偏置的问题, 使得序列标注的解码变得最优解. 但是 CRF 的缺点就是参数多计算量大复杂度高.
关于SRU、QRNN和IndRNN:
SRU设计动机是解决 RNN的训练速度太慢、模型的应用性和实验的可重复性、网络结构的可解释性三个问题, 按照论文里的实验显示, SRU最高可以比keras优化的CuDNNLSTM快10倍! 但是实际使用过程中我们发现在该任务上SRU的keras实现速度是比不上CuDNNLSTM的, 不知道是keras实现的代码不够完善还是我们的网络结构不利于SRU的发挥所致, SRU的详细介绍在这
QRNN设计动机是RNN在计算时,有时间的依赖性,并行度受限;而CNN受制于有限的receptive field, 因此, 信息传递太慢, QRNN希望能综合RNN和CNN的优点,尽量避免各自的缺陷, 但是在我们使用的过程中发现精度较低且速度没有明显加快, QRNN的详细介绍在这
IndRNN既可以利用CNN的多层网络,又可以利用RNN的循环连接. 特别适合于解决时序相关问题,比如视频分析,动作识别,NLP之类. 同样和SRU以及QRNN一样精度低于CuDNNLSTM/GRU, 需要堆叠多层才能达到相同精度但速度因此降低导致无法使用, 关于IndRNN的详细介绍在这
关于ELMO、GPT和BERT:
ELMO、GPT和BERT都是无法使用的额外数据, 由于BERT的主要对比对象就是 ELMO 和 GPT, 所以在这里简要介绍一下ELMO 和 GPT模型的原理, 然后介绍 BERT.
ELMO: 以往获取词向量做法是通过语言模型训练词向量, 其缺点是对于每一个单词都有唯一的一个embedding表示, 而对于多义词显然这种做法不符合直觉, 而单词的意思又和上下文相关, ELMo的做法是我们只预训练language model, 而word embedding是通过输入的句子实时输出的, 这样单词的意思就是上下文相关的了, 这样就很大程度上缓解了歧义的发生. ELMO是双向语言模型BiLM, 联合最大化前向和后向预测单词概率, 具体做法是将句子输入多层BiLSTM获取BiLSTM的不同层内部隐状态进行softmax得到预测单词的表示(多层隐状态加权), 其效果不错但是速度比较慢(ELMO的词向量不像之前与训练模型是fixed, 需要实时的输入句子获取表示, 实际应用中大多是把ELMO的输出和传统词向量的训练输出concat). ELMO 的两大缺陷在于: 一是没有使用大家普遍认为提取特征能力更强的 Transformer 替代 LSTM 来提取特征; 二是 ELMO 的双向拼接特征融合不如 BERT 的一体化特征融合方式.
GPT: GPT 是 Open AI 提出的, 简而言之, GPT 和 ELMO 类似是个两阶段模型, 先预训练语言模型再根据这个预训练模型 fine-tuning 完成下游任务. 区别于 ELMO, GPT 使用 Transformer 来作为特征提取器, 但是 GPT 的语言模型是单向的(只使用预测单词之前的单词). 其缺点在于语言模型是单向的, 这无疑会损失很多信息. 此外, GPT 的下游任务的网络结构设计也较麻烦需要和 GPT 模型的网络结构靠拢.
ELMO 和 GPT 的出现极大的启发了 BERT, 下面介绍 BERT 模型.
BERT: 首先, BERT的出现证明了一个非常深的模型可以显著提高NLP任务的准确率, 而这个模型可以从无标记数据集中预训练得到, 可以算是NLP领域中一个”里程碑”式的发现, 使得NLP领域也有着像 CV 方向的 ResNet 一样的深层骨干预训练网络(之前用的大多都是浅层语言模型的预训练数据如 ELMO、GloVe、Fasttext等).
要了解BERT, 首先要知道Transformer, 在这里简要介绍下 Transformer, Transformer 也是在大名鼎鼎的 中提出的, Transformer 也属于 encoder-decoder 框架, 输入序列首先经过 word embedding, 再和 positional encoding(Transformer 是不考虑序列顺序的, 通过 positional encoding 将词在序列中的位置信息编码成向量)相加后, 输入到 encoder 中. 输出序列经过的处理和输入序列一样, 然后输入到 decoder. 最后, decoder 的输出经过一个线性层, 再接 Softmax. encoder和decoder都有6层, 如下图所示, Transformer 的核心就是 self-attention 和 multi-head attention, 前者用于替代RNN/CNN结构来捕捉长距离依赖, 后者则是使用不同的 attention 矩阵参数进行多次 self-attention 操作拼接后作为最终的 self-attention 输出. 由于需要堆叠多层 self-attention 才能获取满意的结果, 在这个比赛中我们受时间的限制无法使用 Transformer.
左半部分的Nx代表encoder中一层的结构, 每层包含两个部分, 第一部分是 multi-head self-attention, 另一部分是一个前馈神经网络, 两个部分都有一个残差连接和 layer normalization; 右半部分的Nx代表 decoder 中一层的结构, 每层包含三个部分, 前馈神经网络部分和 encoder 一样, multi-head context-attention 部分如其名是指 encoder 和 decoder 之间的 attention 该部分的输入来自 encoder, 除此之外还有一个 masked multi-head self-attention, 所谓 masked 就是对一些位置进行掩盖如置0以获取不同的效果.
回到BERT本身, 有了前人的工作 ELMO 和 GPT, 使得 BERT 可以做到取其精华去其糟粕. BERT 和 ELMO 以及 GPT 一样都是两阶段模型, 先预训练语言模型, 然后在此基础上进行 fine-tuning 完成下游任务. BERT 和 ELMO 以及 GPT 的关系如下图:
对比可以看出, BERT 和 GPT 十分相似, 只不过 BERT 是双向语言模型. BERT 的模型创新性并不强, 其关键就在于如何构造双向 Transformer 语言模型. 这里就涉及到两个技术:
1) Masked LM: 所谓 Masked LM 其本质就是 CBOW 的改良版, CBOW 是用上下文预测中间词, 而 Masked LM 则随机的把15%的单词抠掉(用 mask 掩码替换), 然后去预测被抠掉的单词, 但是有个问题是如果这样做会让模型认为输出是针对这些 mask 掩码的, 所以这15%的单词中只有80%被真正替换成掩码, 剩下的10%替换成随机单词, 还有10%则保持不动, 这样既可以同时利用左右上下文的同时还能避免模型学习目标紊乱.
2) Next Setence Prediction: 所谓 NSP 技术就是做预训练时, 分两种情况选择两个句子, 一是选择语料中真正相连的两个句子, 另一种是第二个句子随机从语料中摘取拼接到第一个句子后面, 除了传统语言模型的预测任务之外, 这里会附带做一个句子关系预测, 判断第二个句子是不是和第一个相关. 这样做既有利于下游句子关系判断的任务,又把预训练部分变成一个多任务过程, 算是一个创新点.
关于ULMFiT:
ULMFIT 是 2018 年 ACL 的文本分类效果最好的论文,核心是用迁移学习的思路来改善文本分类任务的性能。
模型主要分三步:
1、在 wiki 数据集上预训练语言模型,以此学习到大规模语料的语法特性;
2、在目标数据集上 fine-tuning 语言模型,以适应目标任务; 在这一步作者用了两个 trick:
(1) 各网络层设置不同学习率,每层学习率是上层乘以 2.6;
(2) STLR 倾斜三角学习率
3、针对目标分类任务的微调,在语言模型后面增加线性层,用来进行文本分类任务;
参数优化
参数优化部分主要是学习率的衰减改变、目标函数的改变等, 如CLR、快照集成Snapshot、余弦退火集合热重启SGDR和随机加权平均SWA以及目标函数的改进, 何恺明针对不平衡类别设计的Focal Loss, 还有Google Brain团队在ICLR 2018上发表的论文, 不对学习率进行衰减调整而对batch_szie做文章的技巧.
Trick 1. CLR(Cyclical Learning Rate, 周期学习率): 是Leslie Smith在2015年的一篇论文中提到的动态调整学习率方案, 以往调整学习率的方案大多是逐步降低或使用指数函数从而使得模型趋于稳定, 但是Smith认为与其单调降低学习率不如让学习率在合理的范围内进行周期性变化, 这样可以以更少的训练步骤提高模型精度. Smith认为最佳学习率出在base_lr和max_lr之间, 其背后的原理可以简单的理解为: 如果最佳学习率在[base_lr, max_lr]之间周期变化, 那么大多数情况下会得到一个接近最有学习率的学习率. 此外, 还有利于模型摆脱鞍点. CLR的主要参数为: base_lr(学习率波动区间的最小值)、max_lr(学习率波动区间的最大值, 不一定会达到)、step_size(半个学习率循环的步长, 作者建议取值为训练轮数 = 训练样本数 / batch_size 的2~8倍)、mode(学习率变化的模式, 包括三角变化和指数变化)以及一个指数模式下的衰减参数gamma.
Trick 2. Snapshot(快照集成): 发表在ICLR 2017上的论文, 是一种网络集成方法只需要训练一个模型, 使用基于余弦退火策略的循环学习率策略. 其motivation在于: 训练过程中的不同局部最优解可能包含更多的信息.
我们知道SGD会在权重空间内产生较大的跃变, 当学习率因为余弦退火策略而下降时, SGD会收敛到某个局部解, 此时Snapshot集成就会对这个局部解模型拍下”快照”, 将局部解加入解集合中. 随后学习率再次被重置为一个较高值(warm restart), SGD再次收敛时很可能产生一个大的跃变进入新的局部解. 当然为了找到具有差异性的模型, 需要设置较大的epochs.(20~40)
Trick 3. SGDR(Stochastic gradient descent with warm restarts, 余弦退火结合热重启): 同样发表在ICLR 2017上, 按照我的理解SGDR和Snapshot的区别在于Snapshot利用和SGDR一样的过程调整学习率即余弦退火结合循环学习率策略, 只不过Snapshot在每次收敛到局部解时保存了局部解的模型用与ensemble.
Trick 4. SWA(Stochastic Weight Averaging, 随机加权平均): 是2018年提出的一篇论文, 它和主要对比对象快速几何集成FGE(Fast Geometric Ensembling, 快速集合集成)都投了NIPS 2018, 但是FGE的论文中了, 这篇却没有(实验结果比不过不可怕, 开会缺谁谁尴尬 =_=#).
我们没有使用FGE, 这里就简单介绍一下FGE: FGE和Snapshot集成十分相似但主要有两个不同点: 1) FGE使用线性分段周期学习率策略而不是余弦退火; 2) FGE的循环周期短得多(相较于Snapshot的20~40降低到2~4个epochs). 直观上来看这样获取的模型差异性可能比较小, 但是作者发现在足够多的不同模型之间, 存在低loss的连接通路(如下图所示, 实线部分即为局部最优解之间的低loss通路), 沿着这些通路以较小的步长前进保存模型快照可以获取差异足够大的模型, 相比Snapshot的集成结果FGE的表现更好且速度更快.
回到SWA, SWA只需要FGE的一小部分算力, 严格来说SWA不算集成模型, 在训练结束后SWA只有一个模型, 性能优于Snapshot接近FGE. SWA的实验结果如下图, 图中左部是三个FGE的快照模型结果和SWA在权重空间内平均的结果, 图中部是SWA与SGD的泛化误差差别, 注意SWA的训练误差是高于SGD的(图右部).
SWA设计的思路源于观察, 每次学习率周期学习到的结果倾向于堆叠在低loss平面边缘(图左的3个FGE结果: ), 对这样的结果取平均可能得到一个更好泛化解(图左部的 ). SWA只保存两个模型, 一个模型保存对训练过程中不同的模型权值平均值(), 它将用于最终的预测; 另一个模型()用于探索权值空间. 其更新公式:
Trick 5. Focal Loss: Focal loss是何恺明团队提出的一个处理类别不平衡的损失函数, 可以改善图像物体检测的效果. 虽然是针对CV提出的一个不平衡损失函数, 但是在NLP中也存在大量类别不平衡任务, 比如这个比赛中正负样本的比例就达到1: 15. 下面简单介绍Focal loss设计的motivation: 1) 极度的样本不平衡, 正负比例可能大于1000; 2) 梯度被easy example支配(虽然easy example的loss很低, 但是数量过多导致最终loss依然很大从而收敛不到一个好的结果). 处理方法也很简单就是对easy example按loss值衰减其权重, 如下图所示well-classified部分就是easy examples, CE为传统交叉熵损失, FL为Focal loss损失, 其损失函数公式为:
其中 是均衡系数, 是对easy example的衰减超参数, 当 趋于1时对应的loss很低此时通过 项来衰减其在损失函数中的权重.
Trick 6. Don’t decay the learning rate, increase your batch size!: 这是ICLR 2018的论文, 作者也没起个好记的名字, 我就只好把论文名字放这了, 看起来还是很有气势的. 不同于衰减学习率, 作者提出了在增加Batch Size的同时保持学习率的策略, 既可以保证test中不掉点, 还可以减少参数更新的次数; 作者表示还可以既增加学习率又增大Batch Size, 如此可以基本保持test中不掉点又进一步减少参数更新次数. 作者在论文中给出了详细的实验对比, 有兴趣的可以看看, 我们在比赛中尝试了这样的做法, 没什么效果.
踩过的坑
深度模型的可重复性
由于使用到了CuDNNLSTM和CuDNNGRU, 其网络权重初始化具有很大的随机性, 使得同样的代码跑出来的结果在LB上的得分足足有 0.005 的差距! 经过各种测试, 定义下面的代码并在开始训练前调用函数即可保证网络结果的可复现性, 要注意除了网络权重随机化造成的影响, 还有是否使用随机种子对训练数据进行shuffle以及处理embedding时缺失单词是否随机生成一个向量等细微之处都会影响最终的结果1
2
3
4
5
6
7def seed_torch(seed=1029):
random.seed(seed)
os.environ['PYTHONHASHSEED'] = str(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
torch.backends.cudnn.deterministic = True