资讯动态

公共采购控告性语言检测:级联无监督-有监督NLP流水线

发布时间:2026/8/31 7:46:32 来源:尧图企业网站定制
公共采购文档里的控告性语言最近在不少合规与风险团队里变成了一个绕不开的问题。所谓控告性语言不是普通差评也不是情绪化抱怨而是供应商在质疑招标文件、评标过程或中标结果时使用的正式表述。这类句子往往出现在质疑函、投诉书、澄清函和评审意见中数量不大但影响不小。一个人一天能精读几十份投诉材料却很难在上千份采购文档中把每一句“话里有话”都找出来。如果只是把文本丢给通用情感分析模型效果通常不理想。控告性语言的情绪不一定激烈甚至可能用最克制的措辞表达最严重的指控。要识别它需要回到语义和语境层面。过去一段时间我比较多地在关注这类“职责界定型”文本的自动化处理一个验证下来比较有效的做法是构建一条级联的无监督-有监督 NLP pipeline先用无监督方法从大量未标注文本里召回疑似控告片段再用有监督模型做精确判定。这个设计不是炫技而是对数据现状的一种务实妥协和优化。1. 先搞清楚我们要检测的“控告性语言”是什么1.1 公共采购文本里的控告性语言长什么样公共采购领域的质疑与投诉往往不像日常评论那样直白。它有一套相对固定的句式常见于“质疑函”“投诉书”“澄清说明”以及投标人对评标结果的意见回复。控告性语言通常包含三个要素对象、行为和诉求。对象是招标文件、评分标准、评审专家、中标人行为是倾向、不公、串通、弄虚作假诉求是重新评审、废标或暂停采购。这三个要素不一定同时出现但至少要有一个明确的“负面指向”。举例来说下面这些表述在真实材料中很常见“该项目评分标准明显倾向特定品牌涉嫌限制潜在投标人参与竞争。”“评审专家在答辩环节的打分缺少客观依据我公司对中标结果提出严重质疑。”“中标供应商在投标文件中提供的业绩存在明显夸大请求核实并重新评审。”对照之下普通业务句子是这样“我方已按照招标文件要求提交全部资质材料完全响应所有技术条款。”“本项目开标过程正常现场秩序良好未收到有效质疑。”这两种句子在词表层面会有交集但语义指向完全不同。控告性语言的本质是“责任归咎”不是“情绪表达”。它可能不带一个感叹号也没有辱骂词但把对象指向一个具体的失当行为。这就是为什么只靠情感极性判断行不通。1.2 为什么通用情感分析模型在这里会失灵通用情感分析模型学的是“正面/负面/中性”这个维度。采购文件中的控告句多数是负面但负面不一定是控告而真正有效的控告句措辞往往非常正式甚至可以被情感模型归为中性。从工程视角看这属于典型的“小正样本、高标注成本、强长尾分布”问题。每批采购项目里真正有控告性质的句子可能只占百分之几而且不同的投诉角度会带来完全不同的异义表达。一个全监督模型如果只靠几百条标注数据几乎不可能覆盖“串通投标”“指定倾向”“评审不公”“业绩造假”等不同子类型。所以最稳妥的处理方式不是一上来就训练一个端到端分类器而是先回答一个更朴素的问题在几千份文档里哪些句子最值得人工细看这恰好是级联流水线的切入点。关键认识控告性语言检测本质是一个“从大量无标注文书里筛选高风险片段”的召回问题而不是一个简单的文本分类问题。2. 级联流水线的核心逻辑先召回再精判2.1 单模型方案为什么不适合这个场景很多人会想既然要做文本分类直接用预训练模型微调不就行了。但在公共采购场景里这条路会遇到三重阻力。第一标注数据太少。大部分招标文件、投标文件、质疑回复都没有现成的“是否控告”标签。合规团队可能能抽出几百条但不足以训练一个稳定的深度学习分类器。第二类别极度不平衡。真正包含控告性语言的句子占全部句子的比例极低。模型在这样的分布下训练很可能学会“全部判负”准确率还非常高却没有任何业务价值。第三长尾表达太多。控告可以有无数种绕开关键词的写法。如果只靠有监督模型直接分类没有足够多样本覆盖很容易在真实数据上遇到从未见过的句式。这不是说单模型不可行而是说在训练样本有限、业务风险又高的时候单模型的效果不稳定且难解释。级联方案多了一个无监督召回层天然给人工审核留出一个“候选集”降低了漏报风险。2.2 无监督阶段做什么有监督阶段做什么可以把级联理解成一个漏斗。第一层是“筛子”目标是不漏掉任何可能的控告句哪怕捞出来的噪声大也没关系。第二层是“放大镜”在候选集上做精细判断把不相关的句子剔掉。无监督阶段可以使用规则、关键词、句子嵌入、聚类、异常检测等方法运行在全部未标注文本上。它不要求精确判断某句话是不是控告只要求“找出来让模型觉得可疑的句子”。输出是候选句列表以及对应的原始文档编号和上下文段落。有监督阶段则使用人工标注的高质量样本在无监督召回后的候选集上训练一个分类器。因为候选集已经比全量语料小很多模型面对的类别分布也相对更均衡。分类器输出一个“控告概率”再结合阈值决定是交给人工复核还是直接抽出来生成报告。两个阶段的关系不是替代而是互补。无监督阶段贡献覆盖度有监督阶段贡献精度。这也是级联这个名称真正的含义先用资源消耗低的方法粗筛再用资源消耗高的方法细判。顺带说明一下这里的 pipeline 不是 CI/CD 里的构建流水线也不是 Redis 或 Storm 里那种数据管道而是从原始文本到高风险候选集的完整 NLP 处理链路。很多人一看到 pipeline 会往工程方向联想但在文本风险筛查这个场景重点不在于数据怎么流而在于每一层过滤掉什么、保留什么。2.3 级联的评估方式和主要指标级联流水线和单模型不同不能只看最终的准确率。需要分阶段评估无监督召回层的目标在全部人工标注的正样本里能覆盖多少。这个指标可以叫“候选覆盖率”因为候选集里不一定全部正确。有监督层的目标对候选集内部判断的精确率、召回率和 F1是否达到可接受水平。整体流水线的目标最终交付给人工的“高风险队列”里真正控告句子的比例有多高同时漏掉的比例有多低。实际项目里我更建议把无监督层的“候选覆盖率”卡在高位比如要求至少覆盖九成以上的人工正样本然后让有监督层去控制精确率。如果第一层漏了第二层再准也没用。这个优先级如果搞反做出来的系统会显得“精准”但实际漏掉了大量真正的控告。3. 无监督召回阶段的落地实现3.1 数据准备与文本切片无监督召回层的第一步不是建模而是把文档切成适合判断的语义单元。公共采购文本有大量表格、条款编号和固定格式。如果直接把整篇文档丢给模型计算量大噪声也大。建议按文档结构做分句。常见做法是先提取正文中的段落再按句号、分号、换行切分为句子。对表格里的关键字段比如“质疑内容”“投诉事实”“采购需求”等可以单独抽出来作为碎片。因为这些字段本身就是语义密度高的区域。切片之后每一条记录需要保留三个字段原始文档 ID、章节位置、句子内容。这一步看起来简单但会直接影响后面所有环节的稳定性。我遇到过很多项目模型调了半天最后发现是分句时把“一”和“二”两段拼成了一句话导致后续聚类全部被干扰。3.2 用规则和关键词建立第一道召回关键词规则在公共采购场景里依然非常有效。不要因为叫“无监督”就拒绝规则规则本质上是先验知识。可以准备两组词表。一组是“行为类提示词”串通、倾向、不公、虚假、夸大、暗箱、质疑、投诉、违法、违规、排除、限制、没有依据、缺少证据。另一组是“对象类提示词”招标文件、评分标准、评审专家、中标供应商、评标委员会、采购人。当句子里同时出现行为类词和对象类词时就进入候选集。这组规则的召回率通常不错但精确率很低。比如“为防范评委不公本项目采用双盲评审”这句话可能同时包含“评委”和“不公”但并不是控告性语言。所以规则只能作为第一道粗筛不能直接输出最终结果。3.3 用句子嵌入和聚类找出“相似但未标注”的控告簇光靠关键词还不够。很多控告性语言会绕开关键词比如用“再次期待贵方重新考虑”这种暗示性表达。为了覆盖长尾可以引入句子嵌入和聚类。比较稳妥的流程是对全量句子用句子嵌入模型生成向量。在嵌入向量上用 KMeans 或 HDBSCAN 做聚类簇的数量可以先放宽松一些。对每个簇取中心向量附近的样本做一次人工抽样查看。如果一个簇里出现多个疑似控告句就把整个簇标记为候选召回。这个流程的价值不是让模型自动判断而是帮助人快速发现“规则没有定义到的可疑表达”。比如第一批聚类后你可能会发现有一个簇全是“根据采购法相关条款提出投诉”的句子这些都可以进候选集。代码层面常见写法大概是这样from sentence_transformers import SentenceTransformer from sklearn.cluster import KMeans sentences [...] # 分句后的文本列表 model SentenceTransformer(paraphrase-multilingual-MiniLM-L12-v2) embeddings model.encode(sentences, show_progress_barTrue) kmeans KMeans(n_clusters50, random_state42) labels kmeans.fit_predict(embeddings) # 对每个簇抽样查看决定是否进候选集这里有两个经验要强调一是嵌入模型最好选择支持中文的多语种模型不要一上来就加载超大模型拖慢整个无监督阶段二是聚类簇数不是越大越好。可以先从 50 到 200 之间试目的不是把句子精确分类而是把人眼可以浏览的候选规模控制在合理范围内。3.4 输出候选集和可解释依据无监督层输出不能只是一个句子列表至少要包含“为什么召回”的可解释信息。我建议输出四列句子、句子所属文档、命中的规则词、所属聚类簇编号。后面人工复核或训练有监督模型时这些信息都很有用。如果某一批数据特别干净规则召回加聚类召回可以覆盖绝大多数控告句。但不要高枕无忧。规则和聚类都容易漏掉那些极其隐晦的指涉比如“该条款的表述方式令人难以理解”可能是在暗示条款设置有倾向。这种边界样本只有有监督模型加人工复核才能兜住。4. 有监督精判阶段的工程实现4.1 标注策略不要一上来标注全量有监督阶段最容易犯的错是让业务人员一次性标注几千条句子。结果是标注质量参差不齐类别分布依然偏斜。更好的做法是先把无监督召回后的候选集按聚类簇抽样每簇抽几十条加上一部分随机负样本组成一个几百条的“标注种子集”。业务人员只需要判断“这句话是否构成明确的控告性语言”不要做多分类避免主观歧义。标注时要给出判定标准。我一般建议按三个条件判断是否有明确指向对象是否包含负面归因是否可能影响采购决定三个条件都成立才算控告句。标注完成后计算标注者之间的一致性不一致的句子拿出来讨论并修正。4.2 模型选择与训练示例有监督层可以用两条路线。一条是轻量路线用句子嵌入模型生成向量再训练逻辑回归或随机森林。另一条是重型路线直接微调一个预训练语言模型比如中文的 BERT 类模型。如果标注样本只有几百条我更建议先走轻量路线。它对数据量更友好可解释性也更好。实现也比较直接from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score # X_train 是句子嵌入矩阵y_train 是标注标签 clf LogisticRegression(max_iter1000) scores cross_val_score(clf, X_train, y_train, cv5, scoringf1) print(scores.mean())如果标注数据超过两千条再考虑微调预训练模型。一个常见的做法是先加载中文文本分类模型把标签数量改成二分类用较小的学习率训练几个 epoch。但要注意微调预训练模型很容易过拟合因为数据量不一定够大。每个 epoch 之后都要看验证集的 F1而不是只盯训练集准确率。4.3 阈值、校准和人工复核回路有监督模型输出的不是最终答案只是概率。阈值的高低直接决定了最终送人工复核的队列大小。如果业务对漏报非常敏感比如“疑似串通投诉不能漏”阈值要设得低一点比如概率大于 0.3 就算命中。如果业务对误报敏感比如“不能打扰太多正常供应商”阈值要设得高一点比如 0.7 以上。我还建议给每个预测样本输出概率和模型依据。轻量模型可以输出特征权重较高的句子片段预训练模型则可以用近似解释方法给出关键词高亮。人工复核时看到这些依据能更快判断模型是否对。复核结果反馈回标注集形成下一轮迭代。注意人工复核不是流水线的终点。每轮复核结果都应该回流到标注集里用于重新训练或评估。没有回流闭环系统用一段时间后就会失去校准。5. 级联流水线容易踩的坑和排查路径5.1 从模型加载到 pipeline 创建依赖问题怎么查级联流水线涉及多个模型和组件最容易出现的问题不是算法不准而是环境不匹配。常见现象包括加载句子嵌入模型时提示缺少依赖、创建 pipeline 时抛出依赖错误、GPU 版本与库版本不一致等。遇到这类问题不要急着重装环境。按顺序排查看报错信息里具体是哪个包被拒绝。检查 Python 版本、pip 包版本和模型所需版本是否兼容。看是否同时装了两套深度学习框架导致符号冲突。检查模型缓存目录是否有权限默认下载路径是否被改过。最后才考虑重装虚拟环境而且建议用 requirements.txt 记录精确版本。其中模型缓存目录是一个很隐蔽的坑。在服务器上跑 pipeline 时经常因为当前用户对缓存目录没有写权限导致模型下载失败报出来的错误却像网络问题。先把环境变量指向一个有权限的目录能避开大部分坑。5.2 数据层面类别不平衡和长尾表达有监督模型训练时如果正负样本比例悬殊模型很容易退化成“全部判负”。要注意几个信号训练集准确率很高但正样本的召回率非常低或者模型预测正样本时只有极少数固定句式能触发。处理办法包括对负样本做下采样让正负比例接近 1:2 或 1:3给正样本在损失函数里增加权重在候选集内部选择标注样本利用无监督层的召回信息平衡分布。还有一类问题很难在训练时发现就是长尾控告表达。比如“相关条款的设置方式值得商榷”这种句子在训练集里可能只有一两条模型学不到。为了减少这类漏判可以在有监督模型上线后对预测为低概率但规则命中的句子做定期复盘。复盘出的错漏样本进入下一轮标注。5.3 结果不稳定先检查输入、阈值、资源同一套流水线在批处理时结果突然变差通常不是模型坏了而是输入变了。常见的排查顺序是先看输入文本格式有没有乱码、全半角不统一、拆分错误。再看切片逻辑句子是否被截断表格内容是否被漏掉。再看阈值批量处理时是否误用了另一个环境的阈值。再看资源GPU 显存不足会导致 pipeline 退到 CPU 上推理速度和结果都可能变化。最后看模型版本是不是有人更新了嵌入模型但没有更新缓存。这个顺序基本覆盖了绝大多数“看起来像模型问题”的工程问题。我在生产环境里见过太多次最后都是输入切片或环境变量的问题。6. 从研究原型到业务可用的长期维护6.1 批次处理和接口化的平衡公共采购文档的检测往往不是实时的更适合批次处理。每天或每周把新增文档跑一遍输出候选交由人工复核。这种方式可以避开接口化的高并发复杂度也更容易追溯结果。如果后续需要嵌入到在线审批流里再考虑提供一个简单接口输入文档 ID输出高风险句列表。但不要一开始就做成在线服务否则团队会花大量时间处理超时、排队和异常重试而不是优化检测质量。6.2 数据漂移与更新机制公共采购文本的表达方式会随着政策、法规、模板变化而变化。半年前的模型可能半年后就失效了。建议建立一套轻量更新机制每季度抽一批新文档用当前流水线跑一遍人工标记错误。把新标记数据并入标注集重新训练有监督模型。定期重新聚类观察是否出现新的“可疑簇”。版本记录里保存模型版本、阈值、训练数据时间方便回溯。这个更新周期不需要很长但一定要有。否则级联流水线会逐渐变成一套“看起来很智能实际已经过时”的工具。6.3 这个流水线究竟适合谁不适合谁适合的场景包括采购监督部门、企业内控团队、公共资源交易中心做风险线索初筛律所处理大量质疑与投诉文件研究机构做政策文本分析。适合的团队至少要有一定 NLP 工程能力并且愿意投入标注和复核人力。不适合的场景包括拿它做自动裁决、自动处罚依据、完全无人审核的高风险决策。控告性语言检测只能做到“提示”和“定位”不能替代人的判断。还有如果机构连几十条高可用的标注样本都不具备直接上这套流水线会很吃力。不如先用规则做筛选积累标注集后再引入无监督召回。说到底级联无监督-有监督流水线解决的不是“让机器看懂文字”而是“让有限的人力可以处理海量文档”。它的价值不在更快而在于把复杂的语言风险收敛成一条可追溯、可复核、可持续优化的链路。如果你正在处理公共采购中的质疑和投诉文本不妨从一个小样本开始先清洗分句用规则做一个粗召回再人工标注两百条用嵌入加分类器跑一轮。跑通之后你自然会知道下一步该往哪里加工程力。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价