Research Engineer / ML · ML Coding

多Annotator文本二分类:分歧分析、软标签与无泄漏评估

先检查谁给什么样本标注,再建立基线和可解释的改进。

题目重点:先检查谁给什么样本标注,再建立基线和可解释的改进。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 实际文本数据、标注准则和评估metric未提供。训练records为(document,annotator,0/1),同人同样本重复记录拒绝;features是调用方提前生成的数值特征,不假装附真实面试数据。
  • 示例用正标签比例作soft target,disagreement=min(positive,negative)/label_count∈[0,0.5];这是票差定义,不是通用annotator质量分数。未标注样本不自动当negative。

解题路线

  1. 先看样本数、类比例、文本长度/语言/来源、近重复和每位annotator覆盖。分歧可能因准则不清或真正含糊,不一定某人错误;少量人工复审以确定噪声类型。重复同人记录不应伪装成更多独立投票。
  2. 按document及近重复group切train/validation/test,时间敏感任务还按时间隔离;先切分再fit vocabulary/TF-IDF/scaler。多个annotator的同一文本不能跨split,否则模型已见过测试内容。
  3. 基线可以TF-IDF+logistic regression,先评majority hard target与soft vote target。本代码训练数值feature的稳定BCE,logaddexp避免overflow,梯度为Xᵀ(p−y)/N+λw,bias不正则。文本vectorizer须仅fit train,示例不包含实际tokenizer。
  4. soft vote表达经验标注分歧,不等于真类别概率;多数人共享偏差时投票也错。可以按经人工核实的annotator质量加权,但少标注者估计不稳,不能用同测试gold拟权再报告测试成绩。
  5. 改善按失败切片选:脏文本/语言标准化、重复去除、标注准则、class imbalance、threshold或更强embedding。固定验证集选超参与threshold,最终test只做一次评估;指标包括PR/F1/AUROC及校准,按任务明确优先目标。

手动走一遍

某文本标签0/1/1,positive2/3、disagreement1/3;软target不会强迫它等同确定正例。同annotator给同document两次不同label需要修正/版本规则,不能当两张独立票。独立投票参考、Torch BCE/autograd和有限差分共8,984项核对;合成可分数据loss降低只是代码自检,不代表真实offline提升。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

import numpy as np
from collections import defaultdict

def label_analysis(records):
    grouped=defaultdict(dict)
    for document,annotator,label in records:
        if label not in (0,1):raise ValueError('binary label')
        if annotator in grouped[document]:raise ValueError('duplicate annotator/document')
        grouped[document][annotator]=int(label)
    return {document:dict(count=len(labels),positive=sum(labels.values())/len(labels),
                          disagreement=min(sum(labels.values()),len(labels)-sum(labels.values()))/len(labels))
            for document,labels in grouped.items()}

def loss_gradient(features,targets,weights,bias,l2=0.0):
    x=np.asarray(features,dtype=float);y=np.asarray(targets,dtype=float);w=np.asarray(weights,dtype=float)
    if x.ndim!=2 or not len(x) or y.shape!=(len(x),) or w.shape!=(x.shape[1],) or np.any(y<0) or np.any(y>1) or l2<0:raise ValueError('shape/range')
    logits=x@w+bias
    # Stable binary CE: soft labels accepted, natural logs.
    loss=float(np.mean(np.logaddexp(0,logits)-y*logits)+.5*l2*np.dot(w,w))
    prob=np.exp(-np.logaddexp(0,-logits));residual=prob-y
    return loss,x.T@residual/len(x)+l2*w,float(residual.mean())

def train(features,targets,steps=200,learning_rate=.1,l2=.01):
    x=np.asarray(features,dtype=float);weights=np.zeros(x.shape[1]);bias=0.0
    if steps<0 or learning_rate<=0:raise ValueError('training parameters')
    for _ in range(steps):
        _,dw,db=loss_gradient(x,targets,weights,bias,l2);weights-=learning_rate*dw;bias-=learning_rate*db
    return weights,bias

复杂度

R标注聚合O(R),训练N×D数值特征每步O(ND),dense示例内存O(ND+D)。稀疏TF-IDF应使用稀疏算子降低到非零量成本;embedding/Transformer基线有独立推理训练成本。

易错点与追问

容易失分的边界

  • 按annotator记录随机划分造成同文本泄漏。
  • 把高分歧样本一概删除,可能排除最重要难例。
  • 根据test多次调阈值后还把test称独立评估。

继续深挖

  • 某annotator只标一种类别时,怎样区分覆盖偏差与标注偏差?
  • 目标是高precision或高recall,threshold及人工复核策略怎样变化?
✓已复制微信号!