
简介本资源面向自然语言处理方向的开发者与算法工程师聚焦小样本条件下的多标签与层次分类任务基于UTC模型实现行业领域标签的快速适配。仅需少量标注样本即可显著提升分类效果Macro F1提升13%以上有效降低标注门槛与成本适用于案情要素抽取、行业文本归类等复杂场景。压缩包共11个文件约3.06MB包含4个txt数据文件、4个py脚本、1个gz压缩数据、1个ipynb交互式笔记本及1份UTC论文PDF覆盖数据处理、训练、评估与可视化演示的完整流程。目前已有327人学习下载。读者可获取可直接运行的训练与评估脚本、数据转换工具、Gradio交互界面示例以及论文参考便于快速复现实验、迁移到自有标签体系并理解小样本多标签分类的调参思路与工程落地方法。1. 小样本多标签分类的破局点为什么 UTC 能把 Macro F1 拉高 13%做法律文书要素抽取的同行大概率遇到过这种局面婚姻家庭领域的案情描述里一条文本同时涉及财产分割子女抚养感情破裂多个标签标注数据攒了半年也就几百条训出来的模型 Macro F1 卡在 0.6 上下死活上不去。更麻烦的是换一个业务领域——比如从婚姻家庭切到劳动争议——标签体系全变之前的标注基本白干。这个基于 UTC 的多标签/层次分类小样本文本应用解决的正是这个场景它把 UTCUnified Text Classification统一文本分类框架拿来做小样本迁移在 CAIL2019 婚姻家庭要素抽取这类任务上Macro F1 相比常规微调方案提升 13% 以上而且适配新领域标签时只需要几条样本。资源包里包含完整的训练、评估、数据转换脚本和 UTC 论文原文适合做 NLP 分类落地、又不想在标注上烧钱的工程师直接拆用。2. UTC 框架拆解多标签分类为什么吃小样本这套2.1 UTC 的核心思路与多标签适配逻辑常规文本分类微调的做法是预训练模型加一个线性分类头用交叉熵损失在标注数据上训。数据量大的时候没问题但小样本场景下分类头参数随机初始化几百条样本根本喂不饱模型直接过拟合到训练集的标签分布上。UTC 的思路不一样它把分类任务统一成文本匹配的形式——每个标签对应一个标签描述文本模型判断输入文本和标签描述之间的语义匹配程度而不是直接输出类别概率。这个设计在小样本下的优势很直接标签描述文本本身携带了语义信息相当于给每个类别提供了先验知识。比如财产分割这个标签描述文本里包含房产存款债务这些词模型即使只见过几条正样本也能通过语义匹配泛化到没见过的表述上。多标签场景下每个标签独立做一次匹配判断标签之间不互斥天然支持一条文本命中多个标签。层次分类也是同理。UTC 支持把标签组织成树形结构父节点和子节点分别做匹配子节点的预测结果可以向上聚合。资源包里的UTC论文.pdf对这个机制有完整推导建议先翻一遍再动代码不然调参的时候容易凭感觉走。2.2 资源包文件结构与各自职责拿到压缩包先别急着跑run_train.py花五分钟把文件认全后面排错能省一半时间。资源包解压后的文件清单和职责如下文件类型职责run_train.py训练入口加载配置、构建模型、执行训练循环run_eval.py评估入口加载 checkpoint在测试集上算 Macro F1main.ipynb交互式脚本数据探索、单条推理演示、结果可视化Data_conver.py数据转换把原始标注格式转成 UTC 需要的输入格式utils.py工具函数数据加载、指标计算、日志记录train.txt/dev.txt/test.txt数据文件训练/验证/测试集制表符分隔gradio_input.txt推理输入Gradio 演示用的样例输入elements.tar.gz压缩包标签体系定义和标签描述文本UTC论文.pdf文档UTC 原论文含完整方法推导elements.tar.gz需要单独解压里面是标签树和每个标签的描述文本。这个文件是 UTC 小样本能力的来源标签描述写得好不好直接决定最终效果后面会专门讲怎么改。2.3 环境依赖与版本约束UTC 依赖 PyTorch 和 HuggingFace Transformers版本兼容性有几个硬约束。PyTorch 建议 1.10 以上Transformers 用 4.20 到 4.30 之间的版本比较稳太新的版本改了Trainer的接口run_train.py里的回调可能报错。安装命令如下# 创建虚拟环境Python 3.8 或 3.9 兼容性最好 conda create -n utc_cls python3.9 -y conda activate utc_cls # 安装核心依赖版本区间经过验证 pip install torch1.13.1cu117 -f https://download.pytorch.org/whl/torch_stable.html pip install transformers4.28.1 pip install scikit-learn pandas numpy gradio tqdmtorch的 CUDA 版本按自己显卡驱动选没有 GPU 就用 CPU 版但训练时间会从十几分钟拉到一两个小时。scikit-learn是用来算 Macro F1 的gradio只在跑main.ipynb里的演示时需要。装完之后用python -c import torch; print(torch.cuda.is_available())确认 GPU 可用返回False的话先查驱动和 CUDA 版本匹配。3. 从原始标注到 UTC 输入数据转换与训练配置实操3.1 Data_conver.py 的转换逻辑与输入格式UTC 的输入格式和常规分类任务差别很大。常规做法是文本\t标签UTC 需要的是文本\t标签描述的配对形式每个标签展开成一条训练样本。Data_conver.py干的就是这件事核心逻辑是把多标签样本拆成多条二分类样本。# Data_conver.py 核心转换逻辑简化版实际代码以资源包为准 import pandas as pd def convert_to_utc_format(raw_df, label_descriptions): raw_df: 原始数据包含 text 和 labels 两列 label_descriptions: dict标签名 - 标签描述文本 返回 UTC 格式的 DataFrame每行是 (text, label_desc, label) records [] for _, row in raw_df.iterrows(): text row[text] labels row[labels].split(,) # 多标签用逗号分隔 for label_name, label_desc in label_descriptions.items(): # 命中则为正样本未命中为负样本 label 1 if label_name in labels else 0 records.append({ text: text, label_desc: label_desc, label: label }) return pd.DataFrame(records)这段代码的关键在label_descriptions这个字典。标签描述不是随便写的它决定了模型能不能在小样本下泛化。常见做法是标签名 同义词 典型场景词拼成一句话。比如财产分割可以写成涉及房产、存款、债务等财产分配问题的描述。描述文本控制在 20 到 40 个字太短语义不够太长会稀释匹配信号。转换后的数据量会膨胀假设原始 500 条样本、20 个标签转换后就是 10000 条。这是 UTC 的正常开销不用慌实际训练时负样本会做下采样run_train.py里有对应的参数控制。3.2 run_train.py 关键参数逐项说明run_train.py是训练入口参数分三块模型参数、训练参数、数据参数。直接跑默认配置能出结果但要复现 13% 的提升有几个参数必须按场景调。# 基础训练命令 python run_train.py \ --model_name_or_path bert-base-chinese \ --train_file train.txt \ --dev_file dev.txt \ --label_file elements.tar.gz \ --max_seq_length 256 \ --per_device_train_batch_size 16 \ --learning_rate 2e-5 \ --num_train_epochs 10 \ --warmup_ratio 0.1 \ --negative_sample_ratio 3 \ --output_dir ./utc_output--model_name_or_path选中文预训练模型bert-base-chinese是最稳的起点有领域语料可以换成对应的继续预训练模型。--max_seq_length设 256 够用法律文书案情描述一般不超过 200 字设太大浪费显存。--learning_rate用 2e-5小样本场景下学习率超过 5e-5 容易震荡。--negative_sample_ratio 3是负样本下采样比例意思是每个正样本配 3 个负样本这个值在 2 到 5 之间调太小模型学不到区分边界太大正负失衡。--num_train_epochs设 10 是保守值实际训练时看验证集 Macro F1 曲线连续 3 个 epoch 不涨就可以停。--warmup_ratio 0.1让学习率在前 10% 的步数里线性上升小样本训练不稳定warmup 能明显减少早期震荡。3.3 训练过程监控与日志解读训练日志里重点看三个指标loss、eval_macro_f1、eval_accuracy。小样本场景下accuracy会虚高——因为负样本占多数模型全预测负样本也能有不错的 accuracy——所以只盯 Macro F1。正常训练曲线是前 2 个 epoch loss 快速下降Macro F1 从 0.3 左右爬到 0.6第 3 到第 6 个 epoch 缓慢上升到 0.75 上下之后开始波动偶尔冲高但稳定不下来。如果第 1 个 epoch 结束 Macro F1 就超过 0.9大概率是数据泄漏——检查train.txt和dev.txt有没有重复样本。如果 loss 一直不降先查标签描述文本是不是写成了空字符串再查学习率是不是设大了。# utils.py 里 Macro F1 的计算逻辑确认评估口径 from sklearn.metrics import f1_score def compute_metrics(preds, labels): # preds 是 sigmoid 后的概率阈值 0.5 转成 0/1 preds_binary (preds 0.5).astype(int) # averagemacro 表示每个标签等权重小样本下必须用 macro macro_f1 f1_score(labels, preds_binary, averagemacro) return {macro_f1: macro_f1}评估口径必须是averagemacro如果用micro或者weighted负样本多的标签会把指标拉高看起来好看但实际没用。资源包里run_eval.py默认就是 macro不用改。4. 避坑与排查小样本多标签分类的五个血泪教训4.1 标签描述写得太随意Macro F1 直接掉 20 个点现象训练 loss 正常下降但验证集 Macro F1 卡在 0.5 上不去换模型、调学习率都没用。原因elements.tar.gz里的标签描述文本是直接从标签名复制过来的比如财产分割的描述就是财产分割四个字。UTC 靠文本匹配做分类描述文本没有额外语义信息模型退化成普通的线性分类头小样本优势完全丢失。解决每个标签的描述文本至少包含标签名、2 到 3 个同义词、1 个典型场景短语。改完之后重新跑Data_conver.py生成训练数据Macro F1 通常能回升 15 到 20 个点。这个坑我踩过两次现在拿到新任务第一件事就是检查标签描述。4.2 负样本下采样比例设错模型全预测负类现象评估时eval_accuracy很高0.85 以上但 Macro F1 接近 0混淆矩阵显示所有样本都被预测成负类。原因--negative_sample_ratio设得太大比如设成 10正负样本比例 1:10模型发现全预测负类就能拿到 90% 的 accuracy直接躺平。小样本场景下正样本本来就少负样本再一多梯度被负样本主导。解决把negative_sample_ratio降到 2 到 3 之间同时在损失函数里给正样本加权。run_train.py里有--pos_weight参数设成负正样本比例的倒数。比如比例 1:3pos_weight设 3.0。改完重新训练混淆矩阵里正类的召回率会明显上升。4.3 标签体系有层次关系但没配父子节点子类预测全乱现象层次分类任务里子标签的 Macro F1 只有 0.4但父标签能到 0.8子类预测结果和父类对不上——比如父类预测婚姻家庭子类却预测劳动争议。原因elements.tar.gz里的标签树没有配父子关系所有标签平铺做多标签分类。UTC 的层次分类能力依赖标签树结构父节点的预测结果会约束子节点的候选范围不配父子关系等于放弃了这个约束。解决在elements.tar.gz的标签定义文件里加上parent字段每个子标签指向对应的父标签。run_train.py里有个--use_hierarchy开关打开之后模型会先预测父类再在父类范围内预测子类。配好之后子类 Macro F1 一般能到 0.65 以上。4.4 训练集和验证集分布不一致验证指标虚高现象验证集 Macro F1 到 0.85但拿test.txt一跑只有 0.6差距超过 20 个点。原因train.txt和dev.txt是从同一批数据里随机切的但test.txt来自不同时间段或不同来源。小样本场景下模型对数据分布特别敏感训练集里没出现过的标签组合测试集里一出现就翻车。解决切分数据时按标签组合分层采样保证每个标签在训练集和验证集里的出现频率接近。utils.py里有个stratified_split函数可以用传label_column参数指定标签列。如果测试集分布确实不同在训练集里补充一些测试集风格的样本哪怕每个标签只补 5 条效果也比不补强。4.5 推理时阈值用默认 0.5多标签漏检严重现象单条推理演示时明明文本里涉及三个标签模型只输出一个另外两个概率在 0.3 到 0.4 之间被阈值卡掉了。原因多标签分类每个标签独立算概率默认阈值 0.5 对稀有标签不友好。稀有标签的正样本少模型学到的概率整体偏低0.5 的阈值会把大量正确预测过滤掉。解决在验证集上给每个标签单独调阈值稀有标签的阈值可以降到 0.2 到 0.3。run_eval.py里有个--per_label_threshold选项打开之后会输出每个标签的最优阈值。调完阈值再跑测试集Macro F1 通常还能再涨 3 到 5 个点。5. 进阶技巧用标签描述增强和阈值调优把 Macro F1 再推一截标签描述增强是 UTC 小样本能力的天花板所在。常规做法是人工写描述但人力有限标签一多就写不过来。我一般会用一个半自动的流程先用大模型对每个标签生成 5 条候选描述再人工筛选合并。生成的时候给模型一个模板——请用 30 个字描述[标签名]在[业务领域]中的含义包含 3 个同义词和 1 个典型场景——出来的描述质量比手写稳定。筛选标准就一条把描述文本单独拿出来能不能判断它属于哪个标签。判断不了就重写。阈值调优有个容易忽略的细节验证集上的最优阈值不一定适合测试集。如果测试集分布和验证集有偏移阈值要重新调。稳妥的做法是在验证集上调完阈值后在测试集上再微调一轮步长 0.05看 Macro F1 的变化。run_eval.py支持传入--threshold_search参数会自动在 0.1 到 0.9 之间搜索最优阈值组合。还有一个提分技巧是标签描述的多模板融合。同一个标签写 2 到 3 条不同角度的描述推理时分别算概率再取平均。比如财产分割可以写涉及房产存款债务分配和离婚时财产如何划分两条描述模型对两种表述的匹配结果平均之后鲁棒性明显提升。代价是推理时间翻倍但小样本场景下推理量不大这个开销可以接受。# 多模板融合推理示例 def predict_with_multi_desc(text, label_descs, model, tokenizer): label_descs: dict, 标签名 - [描述1, 描述2, ...] 返回每个标签的平均概率 label_probs {} for label_name, desc_list in label_descs.items(): probs [] for desc in desc_list: inputs tokenizer(text, desc, return_tensorspt, truncationTrue, max_length256) with torch.no_grad(): logits model(**inputs).logits probs.append(torch.sigmoid(logits).item()) label_probs[label_name] sum(probs) / len(probs) return label_probs这段代码的核心是sum(probs) / len(probs)这一步多条描述的预测概率取平均抵消单条描述措辞偏差带来的波动。实际用的时候描述数量控制在 3 条以内太多会平滑掉区分度。从那以后我每次拿到新的分类任务都强制走一遍标签描述检查 → 负采样比例确认 → 阈值搜索这三步少一步 Macro F1 就可能掉一截。希望帮到你。本文还有配套的精品资源点击获取