简介:基于机器学习技术的招投标活动风险防控研究围绕电网公司招投标场景,探讨如何借助人工智能与机器学习提升风险识别、评估和管理能力,对从事电力采购、招投标管理及风控系统设计的读者具有直接的参考价值。压缩包内仅含1个PDF文件,大小为337KB,内容接近完整论文,既有风险问题剖析,也有分模块的落地框架。目前已有190人学习下载。正文从招标流程与风险类型切入,梳理招标条件不具备、排斥潜在投标人、非法招标形式等常见问题,并结合图像识别、自然语言处理、Mac地址比对与深度学习等手段,具体阐述专家抽取规则优化、投标人资质能力核实、围串标行为分析、专家评标辅助打分四条实施路径。阅读后可快速掌握招标文件结构化、客观评标模型构建及辅助评标系统设计的整体思路,适合作为相关课题的参考文献或方案设计参考。
1. 机器学习用在招投标风险防控上:到底在防什么,怎么防
有一次评标复核,两家投标人的报价一前一后相差不到千分之一,标书里的“施工方案”连错别字都一样。人工看很难说有问题,但把他们过去三年的投标记录拉出来对比,规律性的报价模式、几乎同时的报名时间、高度重合的网络痕迹,全部指向同一件事。这就是基于机器学习技术的招投标活动风险防控要解决的核心问题:把分散在文本、报价、时间、关联关系里的异常信号,合成为一个可量化、可追溯、可复核的风险评分。它不替代评审专家,而是帮你在海量投标记录里先筛一遍,把需要重点核查的样本排到前面。这套方案适合手里有真实历史投标数据、想从纯人工抽查升级成系统化筛查的企业风控团队或招标代理机构。
2. 数据先行:五类数据源与一份可复用的数据清洗清单
2.1 数据范围别拍脑袋:五类数据源先列清楚
招投标风控项目最容易犯的错,是上来就找模型,而不是先盘点数据。常见做法是先和业务核对“到底能拿到哪些字段”,再把字段归档成五类:企业工商与资质数据、历史投标行为数据、标书文本数据、评审与结果数据、投标过程关联数据。
| 数据源 | 可能拿到的字段 | 在风控里的用途 |
|---|---|---|
| 企业工商与资质数据 | 统一社会信用代码、股东、法定代表人、董监高、注册资本、资质等级、变更记录 | 识别关联企业、判断资质与投标内容是否匹配 |
| 历史投标行为数据 | 报名时间、投标次数、中标记录、废标记录、保证金缴纳记录 | 刻画投标活跃度、中标率、废标率 |
| 标书文本数据 | 技术标、商务标、报价单、授权委托书、文本作者、文件属性 | 计算文本相似度、抽取模板痕迹 |
| 评审与结果数据 | 评审专家名单、打分表、废标原因、中标价格、最高最低价 | 提供标签来源与价格基准 |
| 投标过程关联数据 | 上传IP、MAC地址、联系电话、邮箱、文件创建时间 | 构建关联网络,识别共用设备、共用联系方式 |
我一般会先列一张这样的表,和业务逐格确认。确认过程中会发现大量字段是“有,但不在同一个系统里”,需要先做数据汇聚。这一步至少占整个项目一半工作量,但值得做踏实,后面特征和模型才有东西可吃。
2.2 数据清洗四步:空值、金额、时间、文本格式
拿到原始数据后,不要急着做特征,先把脏数据压下去。招投标数据里最典型的问题是统一社会信用代码带空格、金额单位混乱、时间字段跨系统格式不一致。下面这段逻辑是我在项目里的起点。
import pandas as pd import numpy as np df = pd.read_csv("bid_history.csv", parse_dates=["bid_time"]) # 企业唯一标识不能为空 df = df.dropna(subset=["credit_code"]) # 统一社会信用代码去过空格和全角字符,否则同一企业会被拆成多条 df["credit_code"] = df["credit_code"].str.replace(" ", "").str.upper() # 金额去量纲:同一特征在不同标段里尺度差上百倍,用对数压缩 df["bid_amount_log"] = np.log1p(df["bid_amount"].fillna(0)) # 时间字段转成偏移量:报名时间距开标时间的天数,比绝对时间更适合做特征 df["bid_lead_days"] = (df["bid_open_time"] - df["bid_time"]).dt.days # 文本字段统一全角半角,避免同样的公司名在文本匹配时对不上 df["bidder_name"] = df["bidder_name"].str.replace("[\u3000\uff00-\uffef]", "", regex=True)这段代码里有三个点值得说明:np.log1p对金额做对数压缩,是因为有的标段预算几亿、有的只有几十万,原始金额直接进模型会让大金额标段主导距离计算,对数变换后更接近正态分布;bid_lead_days是把时间点转换为时间间隔,模型更容易理解“临近开标才报名”这类行为信号;全角半角统一是专门为招投标文本设计的,因为PDF导出的文本里经常混入全角空格和全角括号。
清洗阶段的产出物是一张宽表,一行对应一个“投标人-标段”的记录。这一步做扎实,后面特征工程就不会反复回去补数据。
2.3 标签从哪来:规则初筛加半监督迭代
招投标风控最棘手的是标签问题。历史里真正被认定为串标围标的案例极少,一家企业每年可能只有几条,直接拿这些当正样本训练,模型根本学不到东西。常见做法是先用规则初筛造一批候选正样本。规则可以是“两家投标人联系方式相同”“报价偏差率低于0.1%且文本相似度超过0.9”“同一IP段报名的次数超过阈值”,这些规则综合出来的样本,经过人工抽查后作为初版标签。
初版标签噪声很高,直接用会污染模型。我一般会走半监督迭代:先用规则标签训练一个初版模型,把模型打分高且特征明显的样本挑出来,人工复核后再加入下一轮训练。每轮只吸收置信度最高的那批,并给样本打上“规则标签”或“人工确认标签”两个来源标记。这样后续出现模型偏移时,能回溯到是哪一批标签带偏的。
2.4 样本不均衡别硬扛:先做分数排序,再谈阈值
招投标场景里负样本(正常投标行为)永远是绝大多数,正样本可能不到1%。直接做二分类,模型会倾向于把所有人都预测成正常。处理样本不均衡时,class_weight="balanced"是能用的一招,但对于时间序列特征明显的招投标数据,我不建议直接用SMOTE这类过采样方法。过采样会在两个真实样本之间插值,生成一批现实中不存在的“时间点组合”,比如把去年的报名间隔和今年的报价偏差拼在一起,这会让模型学到虚假的规律。
更稳妥的做法是放弃硬分类,直接输出风险分数,把问题从“是不是异常”变成“有多可疑”。模型给每个样本一个0到1的分数,业务方根据复核人力设定阈值,分数高的进人工。这样绕开了不均衡问题的同时,也保留了业务调整的弹性。
3. 特征工程:让机器看得懂投标行为的四个方向
3.1 企业画像与历史行为特征:先算基础账
特征工程的第一层是企业画像。用清洗好的宽表,按统一社会信用代码做聚合,算出累计投标次数、近一年中标率、近一年废标率、平均价格偏离度、参与区域数量这些基础特征。这些特征单独看不出问题,但它们组合在一起能描述“这家企业的正常行为基线”。
一个细节容易被忽略:归一化要放在建模流程里做,不要在特征统计阶段就把所有值缩放到0到1。因为后续接入新数据时,如果直接用历史数据的全局均值去归一化,新数据的分布已经变了,特征值会发生偏移。更好的方式是只统计训练集的均值方差,在验证集和上线后复用同一份参数。
3.2 标书文本特征:相似度计算有一点玄学,但很值钱
文本特征里最值得做的是标书两两相似度。同一招标项目下,技术标和商务标通常都有固定格式,直接对整个文档算余弦相似度,结果会在0.9以上,失去区分度。处理时要先剔除标准化模板内容,比如封面、目录结构、固定承诺条款,再对剩余正文做TF-IDF向量化,最后算Doc2Vec或余弦相似度。
还有一种更轻量的做法是文档指纹:提取正文里的长句和数字,计算哈希,统计不同标书之间相同哈希的比例。这个方法对识别“同一份标书改个公司名就提交”的情况非常有效。特征是“相似度最高值”“相似文本占比”“相同错别字数量”。这些指标单独看可能只是巧合,但多个同时异常时,风险信号就很强了。
3.3 投标网络关联特征:股东、人员、联系方式、IP/MAC
招投标风控最有特色的特征来自关联关系网络。把企业名称、股东、法定代表人、董监高、联系电话、邮箱、上传IP、MAC地址这些字段放进同一张关系表里,两两建边。然后从这张图上抽取特征:两家投标人在图上的最短路径长度、共享联系人数、是否落在同一个连通分量里、连通分量里聚集了多少家投标人。
比如两家公司表面上没有任何股权关系,但它们的投标文件在同一个MAC地址下创建、联系电话只差最后一位、邮箱域名相同,这些特征加在一起,就不是“巧合”能解释的了。需要提醒的是,IP和MAC只能作为“关联线索”,不能作为唯一判据。一个写字楼共用一条宽带、一个代理服务器出口、一家图文店帮多家公司上传文件,都会导致IP重合,这些情况要留给人工复核兜底。
3.4 报价序列与时间节奏特征:从线性拟合到偏差捕捉
报价类特征直接指向围标串标里最常见的“陪标”行为。核心特征是价格偏差率,即投标报价相对标底价或预算价的偏离程度。更隐蔽的信号在报价序列里:同一投标人参与同一标段群下的多个项目时,报价变化是否存在高度一致的线性趋势。做法是取出该投标人历史报价序列,拟合一条线性回归线,计算残差标准差。残差过小说明报价被“精确设计”过,残差波动过大则说明报价策略不稳定。这两端都值得关注。
时间特征同样容易出信号。同一次招标下,不同投标人的报名时间间隔、标书上传时间间隔、保证金缴纳时间间隔,如果都集中在几分钟内,说明背后可能有人统一操作。这类特征的构造逻辑是把时间戳对齐到标段维度,计算同一标段内各投标人时间字段的差值最小值。
4. 模型选型:从逻辑回归到集成模型的落地判断
4.1 先定框架:规则基线、监督模型与无监督模型的边界
模型选型前先想清楚用在哪一类风险上。围标串标识别大多没有历史标签,适合先用无监督方法圈定怀疑范围;履约风险评估通常有历史中标后的履约记录,可以用监督模型;资质造假识别依赖规则与外部数据校验,机器学习更适合做辅助排序。没有一套模型能同时解决三类问题,实际项目里我习惯搭三条线:规则基线先上线兜底,监督模型做履约评分,无监督模型做异常发现,最后把三路分数加权汇总成一个风控总评分。
4.2 基线模型:逻辑回归在招投标场景的用法与局限
很多团队入门第一个模型是线性回归,甚至直接把中标价当因变量去拟合。我的建议是把它当基线,不要当主模型。逻辑回归适合起步阶段:可解释性强,业务容易理解,跑得也快。下面的代码是典型的基线配置。
from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, stratify=y, random_state=42 ) clf = LogisticRegression( C=0.1, # 正则化强度:C越小,正则越强,防止稀疏特征过拟合 class_weight="balanced", # 把少数类的权重拉高,缓解样本不均衡 max_iter=500, # 特征维度多时确保收敛 ) clf.fit(X_train, y_train)C=0.1是逻辑回归里最值得调的参数。招投标特征动辄几十上百维,其中很多是稀疏的0/1特征,正则化不够会把这些特征的权重学得很大;class_weight="balanced"让模型按类别频率自动调整权重,比手工设置过采样参数更可控。逻辑回归的局限也很明显:它假设特征与目标之间是线性关系,而“报价偏差 + 关联关系 + 文本相似度”这类组合信号往往是交叉作用,逻辑回归需要手动构造交叉项才能抓住,树模型不需要。
4.3 主力模型:树模型与集成模型为什么更适合这类表格数据
招投标数据绝大多数是结构化的表格数据,特征之间是非线性关系,还常常有缺失值。这个场景里,随机森林、梯度提升树这类集成模型比深度学习更实用,原因有两点:一是训练数据量通常只有几万到几十万行,深度学习在这里发挥不出优势;二是树模型对缺失值和异常值相对稳健,不需要把所有字段都清洗得分毫不差。
| 模型 | 可解释性 | 缺失值容忍度 | 适合的位置 |
|---|---|---|---|
| 逻辑回归 | 高 | 中,需要填补 | 基线模型、向业务解释阈值时 |
| 随机森林 | 中 | 高 | 大多数场景的主力备选 |
| LightGBM / XGBoost | 中低 | 高 | 数据量大、特征多时的主力 |
| 孤立森林 | 低 | 中 | 无监督围标串标信号 |
| 社区发现算法 | 低 | 中 | 关联网络特征辅助 |
实际项目里,我会先用随机森林跑一版看特征重要性排序,再切到LightGBM调参。随机森林的输出还可以用来做特征筛选,把完全不重要的特征删掉后,LightGBM的收敛速度和稳定性都会更好。
4.4 无监督模型:用孤立森林和聚类圈定“怀疑范围”
围标串标数据没有可靠标签时,无监督模型是有效的补充。孤立森林适合对连续型异常特征做检测,输入“报价偏差率”“文本相似度最高值”“共享关联数”等特征,输出一个异常分。聚类的思路更直接:对同一个标段内的投标人特征做聚类,看是否出现“一个簇里全是不同公司名、但特征几乎相同”的结构。
这类模型的结果不能直接作为风控结论,我会把它当作“怀疑指数”叠加到监督模型评分上。比如监督模型给出0.4,孤立森林给出异常分0.9,最终综合分就上调到0.7。无监督模型的输出需要一个校准过程,不能像监督模型那样直接参考历史命中率,只能通过人工复核的反馈不断调整权重。
5. 招投标风控落地避坑:五个容易翻车的典型问题
5.1 共享IP/MAC直接下结论,误伤率最高
现象:模型把同一IP段上传标书的多家企业全部打了高分,业务复核后发现它们只是同一家图文店代理上传。
原因:IP和MAC只是关联信号,不是直接证据。写字楼共用网络、投标人委托外部文印店操作,是招投标场景里非常普遍的现实情况。
解决:IP/MAC特征只作为网络关联图的边权重,不单独作为报警条件。触发人工复核必须叠加至少两个独立维度的异常,比如“共享IP + 报价偏差率极低”才进入重点核查名单。
5.2 把“中标率过低”直接当高风险,模型被业务否定
现象:模型给很多新成立企业打了高分,业务反馈说这些企业只是投标经验少,不是风险高。
原因:中标率低与风险高之间存在相关性,但不是因果关系。新企业样本量少,中标率天然偏低,模型把“次数少、中标率低”学成了风险模式。
解决:给这类特征加上“样本量门槛”。比如计算中标率时,要求近一年投标次数不少于5次,否则用中位数填充;同时对“投标积极性”单独建特征,把企业是否持续参与投标作为独立的评分维度。
5.3 PDF解析丢内容、扫描件没有OCR,文本特征空洞化
现象:大量标书的文本相似度永远是0,模型完全用不上文本特征。
原因:解析链路只处理了文字版PDF,扫描盖章版和图片型标书直接解析为空,特征矩阵里这一列全是缺失值。
解决:在文本特征前加一道文档类型识别,扫描件先走OCR再进向量化流程。还要对解析后的文本长度做监控,如果某个标段的文本平均长度明显偏短,就要检查解析链路是否失效,而不是闷头调模型。
5.4 人工复核结果直接进训练集,越训越偏
现象:模型分数越跑越高,业务越来越不信任,命中率反而下降。
原因:人工复核结果本身有主观偏差,直接进训练集等于把复核员的个人判断固化成模型标准。复核员一旦对某类特征特别敏感,模型就会无限放大这个特征。
解决:设定反馈闭环的“双重确认”机制。只有两个复核员独立给出相同结论的样本,才允许加入训练集;每次新增标签时留出10%做抽检,抽检不一致的批次整体回滚。这条规则能拦住大部分标签污染问题。
5.5 回溯测试用随机划分,上线后效果崩盘
现象:离线测试精确率和召回率都很漂亮,上线后真实命中率惨不忍睹。
原因:招投标数据有时间演化特征,随机划分会让模型“偷看”到未来数据。比如用2023年的样本训练,测试集里混着2023下半年的样本,模型等于提前知道了答案。
解决:训练集和验证集必须按时间切分。用前两年的数据训练,用最近半年的数据验证。这段踩坑经历后来成了固定流程,每次建模前先检查切分方式,看到随机切分就直接打回。
6. 回溯验证与上线监控:模型上线前最后一道关口
招投标风控模型上线前,我习惯做一次带时间窗口的回溯验证。核心操作是按时间顺序滚动训练和验证,而不是一次性切分。下面是一段最小可用的验证框架。
import pandas as pd train = df[df["bid_date"] < "2024-01-01"] test = df[(df["bid_date"] >= "2024-01-01") & (df["bid_date"] < "2024-04-01")] model.fit(train[feature_cols], train["label"]) test["risk_score"] = model.predict_proba(test[feature_cols])[:, 1] # 关键检查项:看高风险样本是否集中在同一地区或同一时间段 grouped_risk = test.groupby("bidder_region")["risk_score"].mean().sort_values(ascending=False) test.groupby("month")["risk_score"].mean().plot()验证时重点看三件事。第一,高风险样本是否出现区域性扎堆,如果集中在某一个地区,可能不是模型有效,而是那个地区的代理机构操作习惯高度相似,需要排除伪造相关性。第二,模型打分的时间分布是否平稳,如果分数在半年内整体抬升,说明模型在漂移。第三,对同一家正常投标商,模型是否给出稳定分数,正常企业的分数波动太大,业务就无法建立信任。
模型上线后还要做“分数分位数监控”。每周统计一次风险分数分布,如果高分占比突然翻倍,先查是不是有外部规则变化、比如新的招标政策导致报价行为整体改变,再查模型是否需要重新训练。我最早做风控模型时只看AUC,以为0.92就万事大吉,结果上线第一周业务同事甩过来三个投诉:正常投标商被暂停了两次投标资格。后来才明白,招投标风控的KPI不是准确率,而是误伤率。模型给出的应该是一个怀疑指数,而不是一票否决。从那以后,我每次都先把阈值调到业务能接受的误报范围,再谈召回,并且在新一轮数据进来时先看数据质量监控报告,而不是直接重训模型。希望帮到你。
本文还有配套的精品资源,点击获取