ACL2020文章,用Dice Loss处理NLP任务的数据不均衡问题,Tensorflow实现

文章链接:https://zhuanlan.zhihu.com/p/128066632 (本文大部分内容都摘抄自这篇文章,主要用作个人笔记。)

论文标题:Dice Loss for Data-imbalanced NLP Tasks

论文作者:Xiaofei Sun, Xiaoya Li, Yuxian Meng, Junjun Liang, Fei Wu, Jiwei Li

论文链接:https://arxiv.org/pdf/1911.02855.pdf

数据不均衡导致的两个问题

  • 训练与测试失配。占据绝大多数的负例会支配模型的训练过程,导致模型倾向于负例,而测试时使用的F1指标需要每个类都能准确预测;
  • 简单负例过多。负例占绝大多数也意味着其中包含了很多简单样本,这些简单样本对于模型学习困难样本几乎没有帮助,反而会在交叉熵的作用下推动模型遗忘对困难样本的知识。

使用交叉熵损失存在的问题

  • 大量简单负例会在交叉熵的作用下推动模型忽视困难正例的学习,而序列标注任务往往使用F1衡量,从而在正例上预测欠佳直接导致了F1值偏低;
  • 交叉熵“平等”地看待每一个样本,无论正负,都尽力把它们推向1(正例)或0(负例)。但实际上,对分类而言,将一个样本分类为负只需要它的概率<0.5即可,完全没有必要将它推向0;

解决方案

  • 基于已有的Dice Loss,提出一个自适应损失:DSC,在训练时推动模型更加关注困难的样本,降低简单负例的学习度,从而在整体上提高基于F1值的效果。

效果

  • 在多个任务上实验,包括:词性标注、命名实体识别、问答和段落识别。F1值都取得了一定的提升;

传统二分类交叉熵

存在问题:对每个样本,CE对它们都一视同仁,不管当前样本是简单还是复杂。当简单样本有很多的时候,模型的训练就会被这些简单样本占据,使得模型难以从复杂样本中学习。

一种简单的改进方法是,降低模型在简单样本上的学习速率,从而得到下述加权交叉熵损失:

存在问题:对不同样本,我们可以设置不同的权重,从而控制模型在该样本上学习的程度。但是此时,权重的选择又变得比较困难。

DSC

  • DSC是一种用于衡量两个集合之间相似度的指标:

令A是所有模型预测为正的样本的集合,令B为所有实际上为正类的样本集合,那么DSC就可以重写为:

其中,TP是True Positive,FN是False Negative,FP是False Negative,D是数据集,f是一个分类模型。于是,在这个意义上,DSC是和F1等价的。推导公式如下:

然而上述表达式是离散的。为此,我们需要把上述DSC表达式转化为连续的版本,从而视为一种soft F1,对单个样本x,我们直接定义它的DSC:

可以看到,若x是负类,那么它的DSC就为0,从而不会对训练有贡献。为了让负类也能有所贡献,我们增加一个平滑项:

但这样一来,又需要我们根据不同的数据集手动地调整平滑项。而且,当easy-negative样本很多的时候,即便使用上述平滑项,整个模型训练过程仍然会被它们主导。基于此,我们使用一种“自调节”的DSC:

事实上,这比较类似Focal Loss (FL),即降低已分好类的样本的学习权重:

不过,FL即使能对简单样本降低学习权重,但是它本质上仍然是在鼓励简单样本趋向0或1,这就和DSC有了根本上的区别。因此,我们说DSC通过“平衡”简单样本和困难样本的学习过程,从而提高了最终的F1值(因为F1要求各类都有比较好的结果)。

Dice Loss(DL)与Tversky Loss(TL)

总结

本文使用现有的Dice Loss,并提出了一种新型的自适应损失DSC,用于各种数据分布不平衡的NLP任务中,以缓解训练时的交叉熵与测试时的F1的失配问题。实验表明,使用该损失可以显著提高标注任务、分类任务的F1值,并且也说明了F1效果的提升与数据不平衡的程度、数据量大小有密切的关系。

Tensorflow版本Dice Loss

import tensorflow as tf
tf.enable_eager_execution()

def dice_loss(n_classes, logits, label, smooth=1.e-5):
    epsilon = 1.e-6
    alpha = 2.0   # 这个是dice coe的系数,见下边的解释
    y_true = tf.one_hot(label, n_classes)
    softmax_prob = tf.nn.softmax(logits)
    print("{}".format(softmax_prob.numpy()))
    y_pred = tf.clip_by_value(softmax_prob, epsilon, 1. - epsilon)

    y_pred_mask = tf.multiply(y_pred, y_true)
    common = tf.multiply((tf.ones_like(y_true) - y_pred_mask), y_pred_mask)
    nominator = tf.multiply(tf.multiply(common, y_true), alpha) + smooth
    denominator = common + y_true + smooth
    dice_coe = tf.divide(nominator, denominator)
    return tf.reduce_mean(tf.reduce_max(1 - dice_coe, axis=-1))

n_classes = 6
logits = tf.constant([[0.1, 0.2, 0.8, 1.2, 1.24, 2.96],
                      [0.1, 0.2, 0.8, 1.2, 1.24, 2.96]])
for label in range(0, n_classes):
    loss = dice_loss(n_classes, logits, [label, label+1])
    print("{}".format(loss.numpy()))

原论文中的公式11,这里有系数2(loss曲线,下凹的更厉害)

原论文中的表2,这里没有系数2(loss曲线,下凹的稍平缓)

如下图所示

posted @ 2020-08-24 15:46  ZH奶酪  阅读(162)  评论(0编辑  收藏