首页 / 资讯中心 / 文章详情

小样本场景下深度文本表征与聚类的联合优化实践

小样本场景下深度文本表征与聚类的联合优化实践 ★ FEATURED ARTICLE
把小样本场景下的深度文本表征与深度文本聚类放在一起做是我在一次售后工单项目里被逼出来的决定。当时我有近三万个未标注工单文本业务方只给了几十条种子样本类别体系还没定稿要求两周内产出可交付的主题归类结果。最早用通用向量加传统聚类快速跑了一版表面指标说得过去抽出来看完全不能交付同一个故障描述被劈到几个簇里两个业务大类反倒拧成一团。后来把表征学习和聚类目标放进同一个训练循环做联合优化再反复调温度、聚类权重这些参数才真正跑通。这篇文章把从模型选型、对比学习、深度聚类训练到效果验收的完整过程复盘一遍重点讲清楚小样本场景下表征怎么学、聚类怎么做、参数怎么调、坑踩在哪。适合手里有一批无标注文本、想快速搭建主题分类体系或做内容归类的同学参考。这套思路也不局限于售后工单企业内部文档归档、用户反馈归纳、公开文本的主题分析都用得上。1. 项目背景与小样本场景的核心瓶颈1.1 小样本场景到底难在哪先把这个“难”字拆开看。难点一是标注数据少得可怜。很多实际项目里业务方能给出的种子样本往往只有几十条类别体系还不一定稳定。这个量级下去做常规微调分类模型基本走不通模型很快就记住那几十条文本换一批数据直接过拟合。难点二是无标注数据量很大、内容很杂。几万条工单里夹杂着各种语气、错别字、自定义缩写语义密度极不均衡。有的文本只有一句话“发错货”有的是一大段故障描述嵌套了三层意思聚类时面对的噪声级别完全不同。难点三是效果不好验收。没有答案标准连“效果好”本身都难定义只能靠抽检和经验判断。这三个难点叠在一起意味着你不能在“标注驱动”的老思路里找答案只能在无监督或半监督的框架里做文章否则从一开始就走进了死胡同。1.2 为什么必须把表征和聚类目标绑在一起我最早犯的错误就是两阶段处理先用预训练模型把文本编码成向量再丢给K-Means聚类。这看起来顺理成章但结果往往不行。原因在于第一阶段学向量时模型根本不知道后面还要聚类。向量空间里语义相似的句子可能分布在很远的位置聚类中心就嵌在语义空隙里簇边界怎么切都不对。深度聚类和它最大的区别是把“特征怎么学”和“簇怎么分”绑在一起。表征网络更新时不但要让相似的文本在向量空间里靠近还要让它们的簇归属更明确聚类中心更新时又反过来修正表征的分布。用生活里的话说打包和码放同时商量着来两个人都知道对方在干什么最后箱子才码得整齐。最终的总体路线我整理为一个流程文本预处理与清洗领域继续预训练对比学习增强表征深度聚类联合优化最后做结果归因和人工验收。每一步的输出都是下一步的输入。这个路线不是拍脑袋定的每走一步都是在解决前一版方案暴露出的具体问题后面章节挨个展开。2. 深度文本表征怎么做模型选型与对比学习细节2.1 预训练模型与句向量层级的取舍小样本场景下不要一上来就追超大模型。我用的是通用中文预训练模型参数量适中下游表现稳定出问题也容易排查。模型定下来之后另一个更常被忽略的点是用哪一层输出作为句向量。预训练语言模型每一层输出的信息层次不一样。靠前的是词法和语法特征越往后越抽象最后一层和预训练任务比如掩码语言模型贴得最近但恰恰因为这一点它并不天然适合做句子级别的语义表示。我自己的对比测试里用倒数第二层加mean pooling得到的向量在“同一业务主题文本是否相似”的人工评测上比直接用最后一层明显更好。这里有个小细节CLS向量在预训练阶段被当作全句聚合标记实际操作中稳定性不如mean pooling建议优先用mean pooling。另外长文本处理也容易踩坑。很多模型有最大输入长度限制直接截断前512个字符经常丢掉尾部信息。我后来把长文本先按段落切每段单独编码再加权平均虽然慢一点但语义完整性好了很多。2.2 对比学习小样本表征的续命手段小样本场景里对比学习几乎是标配。最常用的是无监督SimCSE的做法同一条句子在模型里过两次因为dropout的随机性会得到两个不完全一样的表示把它们当作正例同一个batch里其他句子的表示作为负例训练目标就是让正例之间更近、正例和负例之间更远。原理听起来不复杂但它刚好命中了一个核心痛点构造正负样本不需要人工标注dropout随机扰动就足够让模型学出“什么叫语义相近”。在小样本数据上这会显著缓解文本表征的退化问题。我实测过加了对比学习这一层之后后续聚类抽检的簇内一致性有了肉眼可见的改善。温度系数是这里的关键超参它控制模型对负样本的敏感程度。温度越低模型越苛刻只有非常相似的样本才给高分所有样本的差异都会被放大稍有不慎整个表征空间会变成一堆彼此远离的散点温度太高又学不出区分度。我通常从0.05开始调效果不够再试0.1。batch size同样重要它决定了负例数量小于16时负例太少对比学习基本失效我一般取32到64。2.3 领域继续预训练低成本高回报的一步还有一个低成本却能明显提升效果的操作用领域无标注语料做继续预训练。具体做法很简单加载一个通用预训练模型用掩码语言建模任务在自家语料上继续训练一两个epoch。这一步相当于给模型补课让它认识领域术语、常见说法和特殊句式。我在某项目中做过一组对比同一份聚类任务不做继续预训练时簇内人工抽检的纯净度大约六成出头补了一轮继续预训练之后能到七成多。成本只是多跑十几分钟或几十分钟GPU时间在深度聚类项目里属于性价比最高的一步。需要注意的是学习率不能大我一般取1e-5到5e-5训练轮数控制在两轮以内。跑多了模型会往领域语料过拟合把通用语义能力冲淡反而得不偿失。3. 深度聚类怎么做从DEC到端到端对比聚类3.1 传统聚类在小样本下的局限性先说清楚为什么不能直接用旧方案。TF-IDF加K-Means表面上是个聚类实际上靠词项共现硬切空间。遇到同义改写、口语化表达和长文本向量空间稀疏得可怕簇边界非常不稳定。哪怕预处理做到位多半也只是得到“关键词相同才同类”的机械结果根本谈不上语义聚类。换用预训练向量加K-Means语义泛化会好一些但聚类过程本身仍然是对特征空间的静态划分。编码器训练完之后就冻结了聚类中心只是在这个既有特征分布里找位置。问题在于通用预训练模型的特征分布未必贴合你的领域于是簇中心很容易落在样本稀疏的区域大量样本在簇边界上反复摇摆。这类方法适合快速摸底看个大概不适合作为工程交付物。3.2 DEC/IDEC的逻辑与实操流程深度聚类里最经典的思路是DEC深度嵌入聚类。它分两步先用预训练模型把文本编码成特征并初始化聚类中心然后进入迭代。每一步里模型根据样本特征和当前聚类中心算出软分配概率专业说法叫学生分布其实就是样本到每个聚类中心的相似度归一化后的一组概率表示这个样本有多大把握属于每个簇。接着把软分配概率锐化成目标分布。锐化的做法是把每个概率平方再归一化结果会更尖锐相当于一个更严格的评审标准。然后用KL散度让两者靠近反复校准之后簇归属就变得越来越明确表征空间也随之被拉开。DEC实际操作有一个明显毛病聚类目标太强特征可能被拽到极端分布上丢掉原本的语义结构。所以后来常见的是IDEC在解码端加回重构损失。重构损失相当于给表征套一根安全绳既能适应聚类又不至于脱离文本本身的语义。在小样本和噪声较多的场景里强烈建议保留重构项。我建议把整个流程组织成先加载预训练模型并编码全部文本做一次降维用K-Means初始化M个聚类中心然后进入联合优化每隔若干步重新计算中心当簇归属变化比例小于某个阈值时提前停止不要盲目跑满固定轮数。有小样本标注时可以用种子样本兜底初始化聚类中心收敛更快也更稳。3.3 端到端对比聚类另一种更现代的路径DEC这类思路里聚类还是在表征之上“加一层”的逻辑。最近不少工作直接走对比聚类路线设计一个与聚类目标耦合的对比学习目标两者端到端一起优化样本表示不再被“先编码后聚类”的做法割裂。常规做法之一是准备一组可学习的聚类中心向量。对每条样本表示算它与每个聚类中心的匹配度用交叉熵作为聚类分配损失同时同一文本的增强视图之间用对比损失互相拉近保住局部语义结构。两个目标并行聚类损失让全局可分对比损失保证局部语义不被破坏天然能防止整体表征坍缩到一个簇。这类方法在种子标注极少的情况下比我实测过的两阶段方案更稳尤其适合类别边界本身模糊的业务文本。代价是实现复杂度更高调参也更讲究适合团队里有人能盯住模型的梯度和损失曲线。如果只是快速解决一个业务问题DEC/IDEC那条路更简单直接如果打算做成可复用的文本分析能力可以往对比聚类方向走。4. 小样本场景下的参数调优与工程配置4.1 数据增强怎么扩才不跑偏种子标注只有几十条不等于训练数据只有几十条。先做增强扩充。最常用的是EDA四件套同义词替换、随机插入、随机交换、随机删除。我的建议是别一把全上每类操作的控制概率放在10%到20%之间扰动太大会直接改变业务语义。比如“退货”和“退款”在售后工单里是两个完全不同的类同义词替换如果把“退货”替换成“退款”等于主动制造脏数据。回译是另一个还算靠谱的方式中文先翻译成英文再翻译回中文语义保持率较高适合比较规范的文本。口语化很重的工单里回译经常引入奇怪说法我会先用小批量人工过一遍确认稳定再全量生成。关键原则增强出来的数据必须和原始数据一起参与对比学习而不是只用来扩大某个聚类的样本量。只扩样本不做表征约束某些类别会被过度增强决策面被明显扭曲最后聚类结果看上去类别很均衡抽出来看其实都是同一句话的不同变体。4.2 聚类数量K怎么定深度聚类比传统聚类对K更敏感。K估错了表征空间会被强行扭曲。业务上如果有先验类别数量直接用它。很多售后或客服场景里业务方自己知道文本大概分几类这个K比任何算法指标都靠谱。没有先验时先抽一小部分文本用K-Means跑一轮轮廓系数对比K在若干个候选值下的变化曲线找平缓拐点。同时可以看层次聚类的树状图两个信息互相验证。轮廓系数单独用容易被均匀分布的假象迷惑不要只看一个数。经验上宁可把K估多一两个簇也别往少里估。估多了后期可以把语义相近的簇合并估少了一开始就会把业务差异很大的文本硬压到一类后患无穷。每个簇形成后至少要人工抽检30到50条文本确认簇内一致性这一步省不了。4.3 关键参数配置表与调参说明这里放一张我反复用过的参数配置表适合大多数小样本文本聚类任务。配置项经验值说明预训练模型通用中文预训练模型小样本下优先考虑稳定与可排查性而不是参数规模句向量取法倒数第二层 mean pooling比CLS稳定语义细腻度更好学习率2e-5 到 5e-5学习率过大是表征坍缩和训练震荡的最常见原因批次大小32 到 64对比学习需要足够负例太小效果明显变差温度系数0.05 到 0.1从0.05起步做对比实验确定最终值聚类损失权重0.1 到 0.3权重过大容易坍缩过小聚类目标形同虚设训练轮数10 到 20轮用簇归属变化率做早停而不是硬跑固定轮数早停阈值簇归属变化率低于1%防止训练后期中心来回震荡讲一下为什么这样设。学习率在小样本微调里必须小因为监督信号太稀薄大步长更新会直接毁掉预训练模型已有的语义结构。批次大小直接决定对比学习里的负例数量32是我心里的最低线。聚类损失权重是平衡两个目标的旋钮我见过不少案例都是权重一上去整个模型就快速坍缩成几个孤立点所以起步一定压低后面再慢慢往上探。5. 常见问题、效果验收与避坑实录5.1 聚类结果震荡怎么办这个现象我刚开始做深度聚类时几乎天天遇到。表面看是同一批数据跑两次得到的簇编号完全对不上深层原因集中在随机种子、K-Means初始化中心不稳定以及训练轮数过长导致聚类中心漂移。排查时先把随机种子固定住包括模型训练、NumPy、K-Means内部的随机状态都要固定。固定种子仍可能有细微差别更稳的办法是多次运行后用簇间共现矩阵做融合。统计哪些样本在多数运行里被分到同一组生成共识标签比单次运行结果可靠得多。训练过程中盯住“簇归属变化率”这个指标一旦它停止下降说明模型已经收敛继续跑下去只是在过拟合噪声不是在学习结构。5.2 表征坍缩所有文本都聚到一类这是深度聚类里最头疼的现象。表现是表征空间里所有点挤在一起聚类目标失效聚类层只能把所有样本划进同一个大簇少数几个簇只剩零零散散的样本整个结果不可用。排查逻辑从损失构成开始。温度太低会让对比损失过度惩罚所有样本对特征被强行拆散聚类损失权重太大特征被拉到聚类中心附近最后所有中心趋向一致样本也分不开。我通常先把温度提到0.1再把聚类权重降到0.1最后把批次提到64逐项试错。预防手段是在表征层后面加一个非线性映射头聚类在映射后的空间进行原始BERT特征不动给模型多一层缓冲这是我现在必做的结构。5.3 聚类结果怎么让业务方信服聚类算法天然是黑盒但业务交付讲究“凭什么归这一类”。我做三件事让结果可解释。第一个簇抽TF-IDF权重最高的若干词做关键词画像能快速判断这个簇的主题比挨条读原文高效得多。第二个簇挑出距离聚类中心最近的5到10条原文人工为这个簇提炼一句话定义写进交付报告。第三对簇之间在业务属性上的差异做简单统计比如退款类文本和退货类文本在金额提及比例上的区别证明分簇不是随机切出来的。如果发现一个簇里明显混了两个业务概念处理方向是回到数据清洗和增强环节找问题而不是简单调大或调小K。混簇通常说明那些文本在语义层面本来就不该属于同一类硬调K只会拆东墙补西墙。5.4 没有标注也能做的效果验收没有标准答案不意味着不能验收。第一类指标是内部指标比如簇内平均距离、轮廓系数它们能快速反映“紧密度”的大致水平。但内部指标有一个著名陷阱所有样本都聚到一类时轮廓系数反而可能很高所以单独看内部指标没有任何意义。第二类指标是外部指标前提是至少有一小批人工标注。哪怕每个类别只有几十条也能算出NMI和ARI它们比轮廓系数可靠得多。我建议种子数据里留出一部分完全不参与训练专门用来做最终评估相当于给聚类模型照一次X光。踩过的坑是只看NMI不看簇大小分布。有一次模型NMI分数很高抽出簇来看才发现一个大簇吞掉了八成样本另外几个小簇只有个位数文本这种“高分假象”业务方一眼就会识破。验收时必须把NMI、簇大小分布、人工抽检纯度三份报告放在一起看缺一不可。最后说点个人经验。小样本深度聚类项目里最花时间的环节往往不是模型本身而是数据清洗、类别定义和逐簇人工验证。模型一轮训练只要几分钟但抽检几百条文本跑掉半天非常正常。如果业务方连类别体系都还没定我强烈建议先花一天时间把类别定义和种子样本对齐再上模型这个前置工作做得越扎实后面返工越少。另外一个小技巧每个项目都保留一份全量数据的模型输出缓存包括向量、簇归属、置信度、簇内距离。后续不管是调整K、重做评估还是排查新问题都能在一分钟内拿到历史快照。这个习惯让我少走了很多弯路也值得你试试。
阅读完成 · 觉得有帮助?
咨询建站