天然语言处理中的负样本挖掘

天然语言处理中的负样本挖掘 (分类与排序任务中如何选择负样本)

1 简介

首先, 介绍下天然与处理中的分类任务和排序任务的基本定义和常见作法, 而后介绍负样本在这两个任务中的意义.html

1.1 分类任务

输入为一段文本, 输出为这段文本的分类, 是天然语言处理最为常见,应用最为普遍的任务. 意图识别, 语义蕴含和情感分析都属于该类任务.
深度学习没有大火以前, 主要作法是手工特征+XGBoost(也能够是逻辑斯蒂), 效果的好坏主要看工程师对业务理解的程度和手工特征的质量.
有了深度学习后, 可能是将文本映射为字词向量, 而后经过CNN或LSTM作语义理解, 最后通过全链接层获取分类结果.
BERT问世后, 分类变得更为无脑, 拿到CLS后接全链接层就好了. 固然这是基本作法, 实际使用必须根据业务需求对算法和训练方式作相应修改.
git

1.2 排序任务

输入为一组样本, 输出每一个样本的排名. 搜索引擎就主要使用了这类技术. 排序任务主要用到的技术是Learn To Rank(LTR), 随着深度学习在图像领域的光速落地, 出现了各类相似的研究方向, 好比 Metric Learning, Contrastive Learning, Representation Learning等. 其实这些研究方向本质都是比较相似的, 即让模型学会度量样本之间的距离. 大名鼎鼎的Triplet Loss本质上就是LTR中的Pair-Wise.
此类任务作法也是五花八门, 各类奇淫巧计, 有兴趣的朋友能够专门去看资料. 我只说一下我在LTR中的若干经验:算法

  1. 别把排序任务当分类, 这两个在本质上是不同的
  2. 排序任务无论怎么折腾, 最后训练的仍是一个打分模型, 而分值只需用来比较大小, 不须要有实际意义(这个还要看具体使用的算法). 因此本人比较喜好Triplet Loss, 在我看来它直接表现了排序任务的本质.
  3. 两个样本先表征再计算分值, 速度快效果差; 两个样本直接送入模型算分值,效果好, 速度慢.

1.3 负样本的重要性

在上述两个任务中, 都不可避免的使用到负样本. 好比分类任务中的短文本类似度匹配, 语义不相关的文本对就是负样本, 排序任务中, 排名靠后的样本对就能够看做是负样本. 正负样本又有以下两个类别:
Easy Example (简单样本): 即模型很是容易作出正确的判断.
Hard Example (困难样本): 即模型难以作出正确的判断, 训练时常会给模型带来较大损失.
相较于简单样本, 困难样本更有价值, 它有助于模型的快速学习到边界, 也加快了模型收敛速度.
在实际状况中, 正样本基本上是被肯定好的, 也不太好再去扩充和修改. 可是负样本有很是大的选择空间, 好比在搜索任务中, 用户点击的Document理解为正样本, 那么该页面的其他文档就全是负样本了. 训练模型时, 显然不能所有采用这些负样本, 所以Hard Negative Example Mining (选取困难负样本) 就变得很是重要!框架

2 负样本选择方法

2.1 基于统计度量的负样本选择方法

计算候选负样本的一些统计度量值并以此为标准选取负样本. 好比在短文本匹配任务中, 选取和目标样本集合类似度值较高的样本做为负例, 这种类型的负例可让模型尽量学习文本所表示的语义信息, 而不是简单学习字面意思. 试想在一个语义匹配任务中, 全部负样本都是随机生成的, 毫无章法的汉字组合, 那么模型定能快速收敛, 然而在实际生产中毫无用处.
在搜索任务中, 可使用TF-IDF, BM25等方法检索出top-k做为负例, 注意要保证训练数据和测试数据分布一致, 若是你的模型在整个搜索框架中须要为全量文档打分排序, 那么除了top-k做为负例, 还需随机采样一些做为负例, 毕竟见多才能识广.dom

2.2 基于模型的负样本选择方法

2.1小节的方法太过朴素, 该方法选出的负样本未必就是最能主导模型梯度更新方向和大小的样本. 所以一个简单的作法就是用训练好的模型预测全部的负样本, 找出预测错的或者产生较大loss的样本做为优质负样本, 而后再去训练模型, 不断迭代优化模型. 总体流程以下图:
函数

该方法逻辑上没有问题, 使用效果也不错, 可是最大的问题时时间消耗太过严重, 每训练若干轮就要在全部负样本上预测一次找到最有价值的负样本, 这对于深度学习而言太耗时了. 因此本人有两个解决方案, 一是不要用模型预测全部负样本, 预测一部分负样本就行, 这算是一个折中方案. 第二个方法是与此有些相似, 名字叫作OHEM(Online Hard Example Mining, 在线困难样本挖掘), 在每次梯度更新前, 选取loss值较大的样本进行梯度更新. 该方法选取负样本是从一个Batch中选择的, 天然节省了时间. 该方法是为目标检测提出的, 在NLP领域可否适用还须要看实战效果.性能

3 基于loss的改进

除了选择优质负样本, 还能够考虑在损失函数上作改进, 让模型自动提升困难样本的权重.学习

3.1 Focal Loss

Focal Loss对交叉熵损失函数进行了改进。该损失函数能够经过减小易分类样本的权重,使得模型在训练时更专一于难分类的样本。
首先来看一下二分类上的交叉熵损失函数:测试

\[CELoss = -y_{true}log(y_{pred}) - (1-y_{true})log(1-y_{pred}) \]

简单化简后:优化

\[CELoss = \begin{cases} -log(y_{pred}), y_{true}=1 \\ -log(1-y_{pred}), y_{true}=0\end{cases} \]

这个公式相比你们很是熟悉了, 我就再也不赘述.

Focal Loss对该损失函数作了简单修改, 具体公式以下:

\[ FocalLoss = \begin{cases} -a(1-y_{pred})^\gamma log(y_{pred}), y_{true}=1 \\ -(1-a)(y_{pred})^\gamma log(1-y_{pred}), y_{true}=0\end{cases} \]

Focal Loss的做者建议alpha取0.25, gamma取2, 其实经过和CELoss的对比就能够发现, Focal Loss主要对损失值作了进一步的缩放, 使得难以区分的样本会产生更大的损失值, 最终是模型的梯度大小和方向主要由难分样原本决定.
下图是Focal Loss的测试结果, 效果仍是使人满意的.

3.2 Gradient Harmonizing Mechanism(GHM)

Focal Loss 会过分关注难分类样本, 真实数据集每每会有不少噪音, 这容易致使模型过拟合.
与Focal Loss相似, GHM一样会对损失值作一个抑制,只不过这个抑制是根据样本数量来的, 梯度小的样本数比较大,那就给他们乘上一个小系数,梯度大的样本少乘以一个大的系数。不过这个系数不是靠本身调的,而是根据样本的梯度分布来肯定的. 具体公式能够参考原论文, 这里就再也不贴公式了, 到目前未知尚未据说有用在NLP上的, 效果如何也要看实战了.

4 训练方式的改进

第二节和第三节都是针对优质样本的探索, 其实能够换个角度思考, 就让模型见到足够多的负例, 只要硬件足够强大, 你能够模型学习全部的负例, 很有一种大力出奇迹的感受. 所以能够尝试改变训练方式让模型见到更多的负样本, 此类方法很想LTR中的List-Wise.

4.1 一个Batch的样本都做为负样本

以短文本匹配任务为例, 假设输入的一个batch是 (a1,b1), (a2,b2), (a3,b3), (a4,b4), 每一对样本都是类似的, 能够把剩余其余样本都做为负样本, 好比对于a1而言b2, b3, b4都是负样本, 这样能够在没有增大batch_size大小的状况下让模型学到更多负样本. 具体损失函数既能够是二分类的交叉熵损失函数, 也能够当成多分类损失函数来优化, 即把(a1,b1), (a1,b2), (a1,b3), (a1,b4)的类似度值做为logit送入多分类损失函数, 在这个例子中, 是四分类任务, 标签是0(a1和b1的类似度值应该最大).
若是要使用该方法记得确保每一个batch中其余样本能够做为负样本! 若是a1和b4也是类似, 那么训练数据就存在噪声了.
此种类型的方法在NLP领域已有使用, 效果还算能够, 有兴趣的朋友能够在本身的任务上试一试. SimBERT(一个通用句向量编码器)就是基于此类方法训练的, 美团在微软阅读理解比赛取得第一名的算法也和此相似. "没有增大batch_size大小的状况下让模型学到更多负样本", 这句话算是第四节的核心了

4.2 MOCO

MOCO方法算是一种经典的图像预训练方法, 它是自监督的, 基于表征学习, 相似于NLP里的Bert. MOCO的一大创新就是让模型一次见到海量负例(以万为单位), 那么存在的问题就是计算量会爆炸, 每个step 都要多计算上万次, 就像是把4.1中的batch_size变成一万同样, 时间是吃不消的. 为了解决这个问题, MOCO建立了负例队列, 不会一次计算全部负例的表征, 而是缓慢更新这个队列中的一部分负例, 即先进队列的先被更新, 这样是减小了计算量, 可是在计算损失函数时使用到的大部分负例表征是过期的, 所以MOCO使用动量方法来更新模型参数, 具体过程能够看下面的伪代码:

# f_q, f_k: encoder networks for query and key
# queue: dictionary as a queue of K keys (CxK)
# m: momentum
# t: temperature

f_k.params = f_q.params # initialize
for x in loader: # load a minibatch x with N samples
	x_q = aug(x) # a randomly augmented version
	x_k = aug(x) # another randomly augmented version
	q = f_q.forward(x_q) # queries: NxC
	k = f_k.forward(x_k) # keys: NxC
	k = k.detach() # no gradient to keys
	# positive logits: Nx1
	l_pos = bmm(q.view(N,1,C), k.view(N,C,1))
	# negative logits: NxK
	l_neg = mm(q.view(N,C), queue.view(C,K))
	# logits: Nx(1+K)
	logits = cat([l_pos, l_neg], dim=1)
	# contrastive loss, Eqn.(1)
	labels = zeros(N) # positives are the 0-th
	loss = CrossEntropyLoss(logits/t, labels)
	# SGD update: query network
	loss.backward()
	update(f_q.params)
	# momentum update: key network
	f_k.params = m*f_k.params+(1-m)*f_q.params
	# update dictionary
	enqueue(queue, k) # enqueue the current minibatch
	dequeue(queue) # dequeue the earliest minibatch

logits = cat([l_pos, l_neg], dim=1)这一行代码就是把正样本的点积值和负样本的点积值在最后一维进行拼接. f_k.params = m*f_k.params+(1-m)*f_q.params该行代码是对模型参数进行动量更新. 若是没有必定的Pytorch基础仍是比较难看懂的, 建议你们去读一读论文以便加深理解.
下图是MOCO的性能评估, 能够看出MOCO性能优于其余同类方法, 且负例队列数量越大效果越好. 该方法本人已经在NLP领域相关任务上作过尝试, 效果不错, 有兴趣的能够在本身任务上试一试.

5 总结

天下没有免费的午饭, 不少方法只有亲自试了才知道是否有效. 建议你们多去看测试结果, 基于真实数据分析思考算法的优化方向. 本文所讲的方法也是抛砖引玉, 但愿各位大佬能够贡献更多的方法.
NLP被称做是人工智能皇冠上的明珠, 可是截至目前未知还未看到这颗明珠大放异彩. 本文介绍的方法几乎所有来源于图像领域, 想想仍是挺失望的. 学科之间思想方法都是相通的, 但愿在之后能看到更多在NLP研究上的创新.

最后感谢各位阅读, 但愿能帮到大家.

文章能够转载, 但请注明出处:

6 参考文献

  1. OHEM: Training Region-based Object Detectors with Online Hard
    Example Mining
  2. S-OHEM: Stratified Online Hard Example Mining for Object Detection
    S-OHEM
  3. A-Fast-RCNN: Hard positive generation via adversary for object
    detection
  4. Focal Loss: Focal Loss for Dense Object Detection
  5. GHM: Gradient Harmonized Single-stage Detector
  6. MOCO: Momentum Contrast for Unsupervised Visual Representation
    Learning
  7. https://zhuanlan.zhihu.com/p/60612064
  8. http://www.javashuo.com/article/p-gvmupfll-nu.html
相关文章
相关标签/搜索