ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

级联无监督-监督NLP管道:公共采购控诉性语言检测方案

2026/8/31 11:53:16 拓冰建站 浏览量
级联无监督-监督NLP管道:公共采购控诉性语言检测方案 公共采购文本里的控诉性语言检测不是那种一眼能看出卖点的练手项目但它确实是很多机构长期头疼的任务招标公告、投标质疑、投诉函、合同履约反馈里经常出现针对评审过程、供应商资质、评分标准或程序公正性的质疑。这类文本如果只靠人工逐条翻效率很低如果只靠关键词规则又很容易漏掉不带敏感词的表述。标题里提到的“级联无监督-监督NLP管道”核心思路是先让无监督方法在大量无标签文本里筛出疑似控诉片段再通过少量人工审核拿到训练样本最后用有监督分类器做精确判定并把这两段流程串成一个完整的 NLP Pipeline。适合看这篇文章的人是已经跑过一些基础 NLP 任务、想处理领域文本分类但手里又没有干净标签的开发者、数据分析师或业务系统设计者。最值得先弄明白的不是某个模型多新而是无监督和监督到底怎么“级联”才能避免第一阶段的噪声把第二阶段带偏。公共采购文本和普通新闻评论不一样。新闻里的负向情绪往往表达得比较直接而采购文件里很多控诉是正式的、嵌套在流程描述里的甚至整段里只有一个转折词暗示不满。这个领域的数据通常没有现成标签也不适合直接拿通用情感模型来套。所以级联管道不是炫技而是用相对少的标注成本解决一个缺少标签、表达长尾、又需要可解释性的实际问题。1. 控诉性语言检测为什么需要级联管道1.1 公共采购文本里“控诉语言”长什么样先定义清楚任务对象否则后面所有步骤都会偏。公共采购场景里的控诉语言不是普通负面情绪而是一种有明确指向的否定性表达。它通常包含三个要素主体被控诉的对象比如评审委员会、招标代理机构、中标供应商。事件被质疑的环节比如资格审查、评分标准、报价计算、开标流程。诉求希望重新评审、废标、公示说明或纠正程序。从表达形式看控诉语言可以分成几类。一类是直接指控例如“本项目评审委员会对某供应商的资质认定存在明显不当”一类是疑问式质疑例如“开标时间与招标公告不一致是否违反相关规定”还有一类是客观陈述但隐含否定例如“评分标准前后发布过两次第二次修改未作公告说明”。最后一类最难识别因为单独看每个词都很正常连起来却能传达控诉意味。如果只按“投诉”“质疑”“举报”“违法违规”这些关键词去匹配能覆盖第一类但很难覆盖第二类和第三类。而且公共采购文本本身用词正式很多控诉不会直接出现情绪词而是通过程序描述、时间矛盾、条款引用等方式表达。所以要处理的核心不是“模型懂不懂负面情绪”而是“模型能不能在正式文本里识别出针对特定主体的否定性评价”。1.2 只用规则或单一模型为什么不够在真实项目里一开始很多人都会先试规则。最方便解释性也强。但规则的核心问题是维护成本极高。每换一个招标平台措辞就变一批每新增一种藏得比较深的质疑原来的规则就漏了。比如“评分标准中商务分权重前后不一致”这个句子如果没有“质疑”“违规”等词规则就很难命中。规则的另一个问题在于容易误报。“该项目未收到供应商质疑”这句话里有“质疑”但它是否定性事实不是控诉。那直接用有监督分类器呢有监督模型效果好但前提是有足够的标签数据。公共采购文本往往分散在各地平台文件格式不统一能拿到的公开数据量大但真正带标注的控诉样本非常少。如果人工从头标注几千条时间成本和业务理解成本都很高而且标注标准很难从零定清楚。只做无监督聚类也不行。聚类能找到文本中的主题分布比如“关于评分标准”“关于开标时间”“关于保证金退还”但无法直接告诉你某个簇是控诉还是中性。同一主题下既有供应商在正常问询也有供应商在表达强烈质疑。所以单一方法都不够稳级联的价值就在这无监督负责召回候选有监督负责精确判断两者配合才能用少量标注覆盖大量长尾表达。1.3 级联无监督-监督设计的基本思路标题里的 Cascaded Unsupervised-Supervised不是简单把两个模型先后跑一遍而是要设计出一种“无监督兜底、监督精排、人工参与中间审核”的结构。典型流程可以拆成五段原始数据进入预处理模块切成句子或短段落。无监督阶段用语义向量、主题聚类和异常检测生成候选片段。人工审核候选片段给出第一批少量标注。有监督阶段使用这批标注训练分类器并且把无监督产出的分数一并作为特征。分类器输出控诉判定同时在需要时把新结果回填到无监督模块形成迭代。这个设计和很多工程师常说的 NLP Pipeline 有点类似但关键差异在于普通 Pipeline 更多是“预处理到分类”的单向流程而级联无监督-监督方案更强调候选集生成和人工标注闭环。第一阶段的输出不是最终结果而是第二阶段的学习材料。如果直接把这段流程做成一个函数入口它看起来就是一个完整的管道读入文档、切分片段、生成候选、模型判定、输出结果。只不过内部存在两个阶段而且第一阶段不是一次性规则开关而是可以不断吸收新样本的召回机制。2. 构建管道前需要确认的数据和运行条件2.1 数据来源与标注现状动手写代码之前先盘一下数据。公共采购文本的常见来源包括招标公告、中标结果公示、质疑投诉公告、合同公示、答疑澄清文件以及采购监督平台的公开留言。在这些来源里质疑投诉公告和答疑澄清文件通常含控诉信息最多招标公告和中标公示里的控诉表达相对隐蔽、但样本量更大。输入格式也很影响后面流程。很多公开文件是 PDF 或 Word需要先转成纯文本网页版公告则通常可以直接抓取但要处理 HTML 标签、分页和表格。如果只是做实验可以先从文本格式较规整的公告里取 1000 到 2000 条不要一上来就做 PDF 批量解析。我建议第一步先拿 200 条文本人工粗看一遍确认里面确实能发现控诉性片段再设计完整管道。否则后面聚类、标注、训练都可能是空中楼阁。数据有没有标签直接决定方案怎么走。如果已有历史投诉记录和最终处理结果可以跳过一部分无监督召回直接做有监督训练。但标题里的场景通常是没有标签的或者只有零星几条规则标注。这时候不要急着扩充标注集先把无监督候选集做出来用候选集替代随机抽样标注效率会高很多。2.2 环境依赖与运行资源运行环境不用太夸张。如果是纯文本分类实验一台普通开发机能跑。常用依赖包括Python 3.8 以上、pandas、scikit-learn、sentence-transformers 或 transformers。如果只用 TF-IDF 和聚类CPU 就能完成如果要加载预训练语义向量模型内存建议 8GB 以上模型文件大约占用几百 MB 到 2GB 磁盘。GPU 不是必须的只有在微调大模型或处理超大批量文档时才值得接上。开始前先确认几件事Python 环境是否独立避免多个项目依赖互相干扰。依赖版本是否兼容尤其是 transformers、torch、sklearn 之间的版本关系。模型下载目录是否可写离线环境下需要提前把模型文件准备好。数据路径和输出目录是否存在权限是否正常。系统是否支持中文文本编码避免 Windows 下 GBK 和 UTF-8 混乱。很多项目在启动阶段报错根本不是算法问题。比如看到类似 “RuntimeError: a dependency error occurred during pipeline creation. please r...” 的提示基本可以确定是依赖环境、模型文件路径或者组件版本不匹配而不是业务逻辑写错。这种问题放到后面的排查章节专门处理。2.3 任务边界检测、分类、警报还是要证据抽取标题里写的是 Detecting Accusatory Language字面上是检测。但落地时你要先决定输出形式。最简单的输出是二分类标签是控诉还是非控诉。复杂一点是控诉类别分类比如评审质疑、资质质疑、价格质疑、程序质疑。再复杂一点是证据抽取要把“被控诉的主体”“控诉的事件”“提出的诉求”分别抽出来。第一次做不建议一下冲到最后一种。先做二分类把“控诉片段”和“非控诉片段”区分开已经能帮使用者节省大量阅读时间。分类稳了之后再考虑给每个命中片段标注类别。证据抽取可以放到最后因为公共采购文本的句式比较复杂直接抽主体和诉求很容易被长句干扰。从业务角度看最终使用这个检测结果的往往是纪检、审计、采购监管人员。他们需要复核不是直接采信。所以管道除了输出标签最好保留片段原文、文档编号、位置页码和模型置信度。这样如果系统误报使用者可以很快定位并判断。一个只有“1”或“0”的接口在真实场景里几乎没人敢直接用。3. 无监督阶段在缺少标签时先找到疑似控诉文本3.1 文本清洗和预处理的关键点无监督阶段能否成功一半取决于文本清洗。公共采购文档经常夹带大量噪声页眉页脚、盖章信息、联系人电话、表格数据、附件名称。这些噪声如果不处理会让向量化结果偏离主题。清洗顺序建议是文档转成纯文本保留段落结构。移除明显的页眉页脚和重复落款。去掉表格碎片但保留表格上下文。比如“商务评分表”后面的几句话往往包含评分标准是否合理的线索。把长文本切成句子或小段落。片段长度控制在 50 到 200 字比较好。太短缺少上下文太长会把多个主题混在一起。不要急着过滤“问题”“不符合”“严重”“涉嫌”这类词它们在控诉检测里是有区分能力的。如果做 OCR要先处理明显的错别字比如“招标识”被识别成“召标识”。这里最容易踩的坑是把整篇公告当成一个样本。一个招标公告可能有几百字但控诉点往往只在某一两句话里。整篇向量化之后控诉信号会被其他中性信息淹没。所以切片段是必须的步骤最好按句号、分号、换行切分再适当合并相邻短句。3.2 主题聚类与异常检测的组合无监督阶段的常用工具是主题聚类和异常检测两者要配合使用而不是二选一。主题聚类可以用 sklearn 里的 NMF 或 MiniBatchKMeans。先对片段做 TF-IDF 或句子向量然后聚成若干个簇。簇数可以先设 15 到 30具体看数据量。聚类本身不区分控诉和非控诉但它能告诉你有多少个议题方向。聚完之后把每个簇的高权重词打印出来人工看一下簇的主题是什么。比如一个簇里集中出现“评分标准”“权重”“商务分”“技术分”那这个簇就是关于评分问题的另一个簇集中出现“开标时间”“保证金”“缴纳”“截止”那它就是关于流程问题。异常检测用来找“不太像常见公告”的片段。公共采购公告整体偏中性控制性强。控诉性语言通常在词汇密度、句式和情绪信号上有差异。可以用 Isolation Forest 或者基于向量距离的离群点判断。但要注意异常检测产出的离群点并不都是控诉也可能是乱码、OCR 残片或表格碎片。所以异常分数只作为候选证据之一不能直接当标签。实际组合方式并不难先聚类把明显中性的簇整体压低优先级再从其他簇里找出距离中心较远的样本加上命中否定词、主体词、诉求词的样本合并成候选集。这个候选集的量级会比全量数据小很多人工审核时也更集中。3.3 如何从无监督结果中生成候选训练集候选集生成可以按下面的步骤操作把所有片段做向量化。运行 KMeans 或 NMF得到每个片段所属簇。对每个簇输出主题词人工标记簇类型控诉簇、中性簇、混合簇。在控诉簇和混合簇里保留距离中心较远的样本。加一部分命中控诉线索词的样本比如“质疑”“不符合”“违规”“明显不当”。再加一部分随机样本作为底噪防止监督模型只见过候选分布。最后给每一条候选片段编号字段包含文档 ID、片段文本、簇号、异常分数、线索词命中数。编号字段一定要留好后面人工标注和有监督训练都要用。如果这一步忘记记录文档和片段位置后面出了结果没法回溯。候选集生成之后先不要急着训练。拿 100 到 300 条候选样本人工标注一遍看看正样本比例是否合理。如果人工翻了半天一条控诉都找不到那问题多半出在前面比如切分太长、清洗太粗暴、或者输入数据本身就不是质疑高发场景。这时候应该回到数据源调整而不是继续往下做。4. 监督阶段把候选集变成可复用的分类器4.1 候选集的审核与标注无监督候选集出来后最需要认真投入的就是标注。标注质量直接决定监督阶段上限。首先要把“控诉性语言”的定义写成书面标准让标注的人有统一依据。可以参考下面这套规则1明确指控某主体存在违规、程序不当、结果不公。1用疑问形式质疑评审过程或采购流程合法性。1引用外部证据否定当前结果比如“与公告不一致”“未按规定执行”。0客观陈述事实没有否定性评价。0提出一般性建议但未指向具体责任人。0只是表达未中标原因没有指控他人。标注时最少要有两个人。公共采购术语很多非业务人员容易把“某供应商未中标”也算成控诉实际上那只是结果描述。两个人分开标不一致的样本由懂采购流程的人裁定。如果条件不允许至少要把存疑样本单独放一个标签不强行归入 0 或 1。标注数量不需要一下做很大。先标 300 条左右训练一个弱分类器再拿这个分类器去预测更多无监督候选集把高置信度的样本拿出来做二次筛选。这实际上是半监督迭代能有效减少人工标注量。但要注意第一次弱分类器的错误会有放大效应所以每轮迭代都必须检查新增样本不能全自动吸收。4.2 特征选择词汇、语义向量、语言特征标注完成之后要思考用什么特征训练。控诉检测不是普通的正负情感分类单靠一个预训练模型直接硬跑可能效果一般而且不好解释。建议做特征拼接。第一类特征是词法特征。包括是否出现“质疑”“投诉”“举报”“不符合”“违规”“涉嫌”“明显不当”“未按规定”等词以及否定词数量、感叹号和问号数量、是否出现“评审委员会”“招标代理”“中标供应商”等主体词。这些特征解释性强也给后面规则复核提供依据。第二类是统计特征。比如 TF-IDF 权重向量、当前片段在哪个主题簇、该片段离簇中心的距离、异常检测分数。无监督阶段产出的这些中间分数完全可以作为监督模型的输入特征。这就是“级联”在特征层面的体现。第三类是语义特征。用 sentence-transformers 之类的模型把片段编码成向量作为补充。语义特征能覆盖同义表达比如“有问题”和“存在瑕疵”本质上相近但词法特征会漏掉。模型方面样本量只有几百条时不推荐直接微调大模型。一个更稳妥的做法是把 TF-IDF 特征、规则特征、异常分数和 embedding 特征拼在一起输入逻辑回归或梯度提升树。这类模型训练快输出概率可解释还能通过 SHAP 或特征重要性看哪些词在驱动判断。只有样本量到几千条、且明显感觉线性模型欠拟合时再考虑微调小规模预训练模型。4.3 模型选择与评估指标模型选择没有银弹。先看任务定义如果你只做二分类逻辑回归和 GBDT 是完全够用的基线。如果想要更高的语义理解能力可以在 embedding 层用更强的预训练模型但分类头仍然保持简单。评估指标要跟业务决策绑定。控诉文本在真实数据里通常占比不高可能不到 10%。如果只看准确率模型全预测为“非控诉”也能拿到很高分数毫无意义。所以至少要看召回率真正的控诉片段有多少被找出来。精确率被判为控诉的片段里有多少确实是控诉。F1精确率和召回率的调和平均。AUC模型对随机正样本和负样本的排序能力。不同阈值下的精确率和召回率曲线。具体调阈值时要考虑实际业务。如果是人工复核调低阈值让召回率更高把更多疑似样本送给人看。如果是自动生成警报没有人复核那精确率优先宁可不报也别错报。公共采购场景里通常先保证召回率在一个可接受范围比如 80% 以上再逐步提高精确率。4.4 级联管道如何把无监督和监督串起来级联的关键是让无监督阶段的输出成为监督阶段的输入而不是让它直接决定最终标签。一个典型的级联结构如下def detect_accusatory_pipeline(doc): segments split_doc(doc) features [] for seg in segments: topic_id predict_topic(seg) anomaly_score predict_anomaly(seg) clue_score count_clue_words(seg) embedding encode_segment(seg) features.append({ segment: seg, topic_id: topic_id, anomaly_score: anomaly_score, clue_score: clue_score, embedding: embedding }) X build_feature_matrix(features) prob classifier.predict_proba(X)[:, 1] return [{segment: f[segment], score: float(p), label: int(p 0.5)} for f, p in zip(features, prob)]这段代码是示例不是某一套固定实现。重点是主题编号、异常分数、线索词命中数、语义向量都同时进入最终分类器。这样监督模型不仅能看到文本原义也能看到无监督阶段对这段文本的“定位”。比如一个片段如果落在疑似控诉簇里无监督分数会给它额外加成如果同时又命中多个指控词最终打分就会更高。级联还有一个好处当新数据进来的时候不需要重新跑人工标注。先把新数据输入无监督阶段如果发现新区块远离已有簇中心说明出现了新的控诉模式这时再针对性采样和标注再更新监督模型。这种“无监督召回、监督更新”的循环才是级联管道长期可用的真正原因。5. 管道落地单条预测、批量扫描和接口化5.1 管道构建的最小实现顺序很多人一上来就设计分布式框架结果连单条样例都没跑通。更稳妥的做法是先把整个流程缩到一条文本上跑通后再扩展。最小实现顺序建议如下准备一个测试文件比如一份带质疑内容的招标结果公告。实现文本读取和片段切分打印出片段数量。实现清洗函数确认噪声被移除。加载无监督模型输出每个片段所在簇和异常分数。加载训练好的监督分类器输出每条片段的控诉概率。打印片段原文、分数、标签人工核对。等到单条结果检查完毕再处理批量数据。这样排查问题会简单很多。比如某一步结果异常你能很快判断是清洗问题、向量模型问题还是分类器问题。5.2 批量处理公共采购文件时的命名、去重和失败重试批量处理是工程化的分水岭。模型再准批量跑起来如果总是中断、重复、无法回溯也很难上线。公共采购文件通常有大量重复比如同一公告被抓取多次、官方版本和转载版本并存。所以批量流程里必须做去重。去重可以用文本哈希比如对片段或文档正文计算 SimHash 或 MD5。更稳一点是先在数据库里存文档唯一标识再给每个片段编号。编号规则建议是“文档 ID 页码 段落序号”。比如GG2024001_P3_S2表示 2024 年第 1 号公告第 3 页第 2 个片段。这个编号会贯穿标注、预测、复核全流程。失败重试也是重点。批量扫描时一个 PDF 解析失败不应该让整个流程退出。要记录失败文件路径和错误原因跑完后统一处理。很多问题不是模型推理异常而是输入文件本身坏了、权限不对、或者是临时目录满了。可以每处理完一批片段就把结果写入 CSV 或数据库这样程序中断后能续跑不用重新开始。并发数要谨慎。如果用了较大的语义向量模型先按 32 或 64 条片段一个批次测试观察内存和耗时。不要一开始就开几百并发。有些优化手段比如把结果先写入 Redis 再用 Redis Pipeline 批量读取那是后面工程优化的事不是模型核心。第一版能稳定跑完才是关键。5.3 把提示语库、规则和分类结果合并输出很多项目最终只有分类器远远不够。为了让业务方能真正使用建议把规则库和分类结果合并形成一份“复核清单”。比如定义三层输出高置信控诉模型概率大于 0.8直接进入人工复核列表。中置信疑似概率在 0.5 到 0.8 之间标记为“需要重点阅读”。规则命中但模型低分规则命中了“质疑”“投诉”等词但模型概率低于 0.5说明可能存在长尾表达也要单独列出来。输出格式可以是表格字段包括文档编号、片段位置、片段原文、模型概率、规则命中词、聚类主题、处理状态。这样的表比单个标签有用得多。业务人员拿到表以后能一眼看出哪些是真正需要处理的哪些是系统误报。工程部署上如果这只是一个周期性任务可以借助 Jenkins Pipeline 或类似的调度工具每周跑一次新公告。如果要做成实时接口那就需要在管道外侧加请求校验、超时控制和日志记录。NLP Pipeline 和 CI/CD Pipeline 不是一个层级的概念前者负责文本处理后者负责自动化和部署两者要分开理解。6. 常见报错与排查路径6.1 Pipeline创建阶段的依赖错误实际使用中很多人第一次跑类似管道就会遇到下面这种报错RuntimeError: a dependency error occurred during pipeline creation. please r...看到这个提示先不要怀疑算法先检查环境和依赖。通常的排查顺序是Python 环境是不是干净有没有多个配置文件互相覆盖。关键依赖版本是否匹配比如 transformers、torch、scikit-learn。模型文件是否存在缓存目录是否被清理过。本地是否有离线环境依赖是否已经提前下载。是否设置了错误的环境变量导致模型加载路径不对。这类错误在本地能跑、换台机器就不能跑的情况尤其常见。解决方法是把所有依赖写进 requirements.txt 或 lock 文件固定版本。目录结构也要保持一致避免模型路径写死成个人用户目录。6.2 无监督结果全是噪声怎么办如果聚类出来每个簇主题都不明显或者候选集里大部分是乱码、表格碎片先不要调聚类参数回头检查文本清洗和切分。比较常见的坑包括片段切得太短导致语义信息不足。片段切得太长导致多个主题混在一个向量里。OCR 文本里错别字太多影响了向量表示。停用词过滤过度导致“不符合”“不公正”中的否定前缀被删掉。PDF 解析时表格内容被当成正文混入大量数字和空行。一个判断技巧是随机打印 50 条清洗后的片段肉眼看看是否和原始文档内容一致。如果清洗后文本已经不能读后面任何算法都白搭。无监督结果全是噪声时优先怀疑数据层而不是模型层。6.3 监督模型召回率低怎么调模型训练完如果召回率偏低也就是真正的控诉片段大量没被找出来不要只想着换更强模型。先检查训练集分布。首先看标注数据里正样本够不够。如果只有 20 条正样本模型很难学到控诉的多样表达。这时候需要回到无监督阶段扩大候选集尽量多收集正样本。其次看特征是否覆盖了不同控诉类型。比如训练集里全是“质疑评分标准”但实际数据里还有很多“质疑开标时间”的样本模型自然召回率低。最好按主题簇评估召回率看漏的是哪一类再有针对性地补充样本。另一个有效方法是不直接输出硬标签而是输出概率区间。比如设置两个阈值概率大于 0.6 自动判断为控诉概率在 0.4 到 0.6 之间进入人工复核低于 0.4 才判为非控诉。这样能在模型能力有限时靠人工兜底提高有效召回。调阈值本身不改变模型但对业务效果影响很大。6.4 公共采购领域迁移时的边界这个管道是在公共采购文本上设计的不一定能平移到别的领域。网上有人把类似方法用在 nlp 新闻处理里效果却一般原因往往是任务定义变了。新闻里的控诉性表达可以是记者转述可以是多方观点不一定有一个明确的采购责任主体。所以换领域之后标注规则、特征词表、簇数量都要重新设计。同样是公共采购不同数据源写法差异也很大。国家平台和地方平台的结构不完全一样不同行业的招标文件用词也有区别。如果从政府采购迁移到工程建设招投标需要重新看一批新样本重点观察新增了哪些主体词和程序术语。不要指望训练好一个模型就能通吃所有平台。真正落地时最该盯住的不是模型精度那零点几的提升而是输入格式、数据格式、失败重试和复核流程是否完整。我在做类似任务时的一般习惯是先写一个最小管道用几十条数据跑通再逐步加数据、加规则、加特征。整个过程里最花时间的不是模型代码而是文本清洗和标注标准。只要这两块做扎实后面的级联无监督-监督设计就能稳定发挥作用。