2026/10/11 15:34:57

Kraljic矩阵+SVM+TOPSIS:物料分类与供应商评价系统实现

Kraljic矩阵+SVM+TOPSIS:物料分类与供应商评价系统实现 简介一份针对供应链管理场景的差异化供应商选择研究资料包面向供应链管理人员、采购经理及从事供应链优化研究的专业人士。内容紧扣物料分类与供应商优选两大核心提出结合Kraljic矩阵、支持向量机SVM与网格搜索的物料分类模型将物料划分为战略、杠杆、一般、瓶颈四类并依次构建差异化评价指标体系采用TOPSIS方法完成供应商排序以C公司实证验证了模型在降低采购成本、提高交货准时率、缩短评估周期方面的实际效果。资源包共1个文件为docx格式的完整文档大小58KB文档中不仅阐述了理论框架还提供从数据预处理、模型训练到评价指标设定、决策支持的全流程可运行代码及逐段解释并明确标注参数含义与实现思路系统具备良好的可扩展性和灵活性。目前已有46人学习下载适合需要借鉴智能算法实现供应商科学分类与量化选型的读者作为技术参考。1. 为什么物料分类决定了供应商选择的成败从Kraljic矩阵说起制造业供应链里采购团队最容易犯的错就是把供应商评价当成一个孤立环节一上来就对着价格、质量、交期打分却忘了前置的物料分类。这篇论文的思路恰恰相反先按物料价值和供应风险把物料分成战略、杠杆、一般、瓶颈四类再针对不同类别设计差异化的供应商评价指标和权重。第一次看完代码实现时我挺意外物料分类模型用的不是传统阈值判断而是SVM加网格搜索评价环节用TOPSIS权重还能用AHP动态生成。分类一旦偏了后面再精妙的评价模型都是白算。这套代码从数据预处理、调参、评估到完整系统类封装都有适合采购经理、供应链优化研究者以及想拿机器学习做业务落地的工程师。2. 物料分类用SVM加网格搜索替代手工打分的完整代码实现2.1 Kraljic矩阵与SVM选型理由Kraljic矩阵把横轴定义为供应风险纵轴为物料价值四个象限分别对应战略、杠杆、一般、瓶颈四类物料。传统做法是拍两个阈值直接切分比如价值大于50、风险大于50就判为战略物料。表面看没毛病但真实数据里价值与风险的量纲不同存在大量边界样本线性阈值很容易误判。SVM在中小样本分类上有天然优势RBF核可以拟合非线性边界网格搜索又能自动逼近最优的C和gamma不需要人工去试玄学参数”。还有个细节值得注意论文并没有把Kraljic规则丢到一边而是把它作为先验知识参与训练让SVM去学专家经验。实操中如果你手里的历史物料数据足够干净也可以直接让SVM从数据里学边界两种做法都成立关键是要统一标签定义。后面避坑章节我会单独讲规则和模型混用的坑。2.2 数据准备与标准化复现这段代码先从数据准备说起。物料数据通常是CSV至少包含物料价值、供应风险以及人工标注的类别。直接拿原始数据训练SVM会出问题价值可能是百万级别风险是0到1的小数RBF核会完全被大数值特征主导。所以第一步必须是StandardScaler标准化把两个维度压到均值为0、方差为1的空间。import numpy as np import pandas as pd from sklearn import svm from sklearn.model_selection import GridSearchCV, train_test_split from sklearn.preprocessing import StandardScaler from sklearn.metrics import classification_report # 物料数据文件value列代表物料价值risk列代表供应风险 # category列0-战略, 1-杠杆, 2-一般, 3-瓶颈 data pd.read_csv(material_data.csv) X data[[value, risk]].values y data[category].values # 标准化让value和risk处于同一个量纲避免SVM被某一列主导 scaler StandardScaler() X_scaled scaler.fit_transform(X) # 固定random_state保证每次复现结果一致 X_train, X_test, y_train, y_test train_test_split( X_scaled, y, test_size0.3, random_state42)这里有个关键操作scaler必须在训练集上fit然后训练集和测试集都只做transform。如果你对全量数据先fit再切训练集会造成数据泄露测试集评估结果虚高。另外random_state42不是随便写的固定下来之后你和同事复现才能得到完全一样的划分。2.3 网格搜索调参C、gamma、kernel怎么选SVM调参最烦的是参数组合。C代表误分类惩罚C越大模型越严格容易过拟合gamma代表RBF核的径向影响范围gamma越小决策边界越平滑gamma越大越容易贴着样本走。常见的做法是先固定RBF核用小范围网格跑通流程再根据结果细化不要一开始就铺开所有参数拉满。param_grid { C: [0.1, 1, 10, 100], gamma: [1, 0.1, 0.01, 0.001], kernel: [rbf, poly, sigmoid] } # cv5表示五折交叉验证 # refitTrue找到最优参数后用全量训练集重新训练一个模型 grid GridSearchCV(svm.SVC(), param_grid, refitTrue, verbose2, cv5) grid.fit(X_train, y_train) print(最佳参数:, grid.best_params_) y_pred grid.predict(X_test) print(classification_report(y_test, y_pred))上面的网格是3×4×448个组合每个组合做5次训练整体耗时等于240个SVM训练任务。数据量在千级以内还能忍样本上万之后你会等得很痛苦。我的习惯是先用kernel: [rbf]C取[1, 10]gamma取[0.1, 0.01]跑一遍看到大概区域再加密。网格搜索的结果只是起点拿到最佳参数后还可以手动调整一下看看是否稳定。2.4 决策边界可视化与效果评估分类报告只能告诉你precision、recall和F1但看不出模型在二维特征空间里的边界长什么样。物料分类只有两个特征完全可以画出来。import matplotlib.pyplot as plt from matplotlib.colors import ListedColormap h 0.02 x_min, x_max X_scaled[:, 0].min() - 1, X_scaled[:, 0].max() 1 y_min, y_max X_scaled[:, 1].min() - 1, X_scaled[:, 1].max() 1 xx, yy np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h)) Z grid.predict(np.c_[xx.ravel(), yy.ravel()]) Z Z.reshape(xx.shape) cmap_light ListedColormap([#FFAAAA, #AAFFAA, #AAAAFF, #FFAAFF]) cmap_bold ListedColormap([#FF0000, #00FF00, #0000FF, #FF00FF]) plt.contourf(xx, yy, Z, cmapcmap_light, alpha0.8) plt.scatter(X_scaled[:, 0], X_scaled[:, 1], cy, cmapcmap_bold, edgecolork, s20) plt.xlabel(标准化后的物料价值) plt.ylabel(标准化后的供应风险) plt.title(基于SVM的物料分类结果) plt.show()步长h越小边界越精细但网格点数量会爆炸。二维场景下h0.02会生成几十万个点预测一次也就一两秒。这里判断的核心是看测试集的分类报告与可视化边界是否匹配业务认知。比如战略物料应该落在高价值高风险的右上角如果边界被样本噪声带歪先回头检查标签有没有错标。3. 差异化评价TOPSIS供应商优选与权重设计3.1 TOPSIS五步法与理想解计算物料分完类接下来是针对每类物料找最优供应商。TOPSIS的基本逻辑很直白构造一个虚拟的最优供应商每个指标都取所有候选者里的最好值再构造一个虚拟的最差供应商每个指标都取最差值。然后计算每个真实供应商到这两个虚拟点的距离距离最差越远、距离最优越近得分就越高。接近度公式为dist_worst / (dist_best dist_worst)结果在0到1之间。关键是要区分指标方向。质量、交货准时率、服务、技术能力都是正向指标越高越好价格是负向指标越低越好。如果直接对价格取最大值作为理想解那得出的结论就是价格越贵越好这个错误在初学TOPSIS的人身上特别常见。3.2 基础函数实现与战略/一般物料对比先看一个干净的基础实现。输入是供应商的评价矩阵每行一个供应商每列一个指标weights是各指标权重且求和为1impacts用1表示正向-1表示负向。import numpy as np def topsis(data, weights, impacts): data: 供应商评价矩阵行为供应商列为指标 weights: 指标权重和为1 impacts: 1正向指标-1负向指标 # 1. 向量标准化 norm_data data / np.sqrt((data ** 2).sum(axis0)) # 2. 加权标准化 weighted_norm norm_data * weights # 3. 理想解与负理想解 ideal_best np.max(weighted_norm * impacts, axis0) ideal_worst np.min(weighted_norm * impacts, axis0) # 4. 欧氏距离 dist_best np.sqrt(((weighted_norm - ideal_best) ** 2).sum(axis1)) dist_worst np.sqrt(((weighted_norm - ideal_worst) ** 2).sum(axis1)) # 5. 接近度 closeness dist_worst / (dist_best dist_worst) return closeness这里第3步的处理是核心weighted_norm * impacts相当于把负向指标翻转方向价格越低乘积越大这样才能正确选出理想解。战略物料和一般物料的评价指标虽然相同但权重完全不同。# 战略物料质量、价格、交货准时率、服务、技术能力 strategic_suppliers np.array([ [90, 45, 95, 85, 90], # 供应商1 [85, 50, 90, 90, 85], # 供应商2 [95, 40, 85, 80, 95] # 供应商3 ]) strategic_weights np.array([0.3, 0.15, 0.2, 0.15, 0.2]) strategic_impacts np.array([1, -1, 1, 1, 1]) print(战略物料供应商得分:, topsis(strategic_suppliers, strategic_weights, strategic_impacts)) # 一般物料同样五个指标但权重向价格和交货偏移 general_suppliers np.array([ [80, 35, 90, 75, 70], [75, 30, 95, 70, 65], [85, 40, 85, 80, 75] ]) general_weights np.array([0.2, 0.3, 0.3, 0.1, 0.1]) general_impacts np.array([1, -1, 1, 1, 1]) print(一般物料供应商得分:, topsis(general_suppliers, general_weights, general_impacts))运行之后你会看到战略物料里技术能力强、质量高的供应商排名靠前一般物料里价格低、交货快的供应商反超。这就是差异化的直观体现——同样的五位候选换一组权重排序就变了。实际业务里权重通常来自AHP专家问卷而不是手工拍数这一点放在第6章展开。3.3 不同物料类型的权重策略解析四类物料的供应商管理策略差异很大对应的TOPSIS权重也不该是同一套。我梳理了一个典型的权重模板可以直接用于系统初始化。物料类型质量价格交货准时率服务技术能力核心策略战略物料0.300.150.200.150.20质量和技术优先保障长期供应杠杆物料0.250.250.200.150.15价格与质量并重引入竞争一般物料0.200.300.300.100.10追求价格和交货简化管理瓶颈物料0.250.200.250.200.10保证交付降低断供风险战略物料不能因为省一两块钱就换供应商所以价格权重最低一般物料可替代性强价格和交期占据主导瓶颈物料供应风险高交期和服务权重必须拉起来。这组权重只能作为初始化模板真正落地前最好用AHP针对行业和企业现状重新计算。4. 避坑指南复现供应链选型模型的五个常见坑4.1 坑一标准化器没有复用新物料分类完全错乱现象训练时用了StandardScaler分类准确率不错但来了新物料后直接拿原始值丢给模型预测返回的类别跟Kraljic矩阵人工判断差得离谱。原因SVM训练时输入的是标准化后的特征新数据必须走同一个scaler的transform而不是重新fit更不能直接跳过。解决把训练好的scaler和SVM模型一起保存。用joblib.dump(scaler, scaler.pkl)存下缩放器预测时joblib.load进来再transform。最稳妥的是封装成系统类把scaler作为成员变量全部放进同一个pipeline。4.2 坑二参数网格开太大cv5跑成天荒地老现象ParamGrid里kernel给了三个选项C四个gamma四个cv5数据量几千条跑了半小时没出结果。原因48个参数组合X5折交叉验证SVM求解二次规划的时间还随样本量超线性增长。网格搜索的复杂度经常被低估。解决先固定kernel为rbfC和gamma各给3个值跑一遍锁定最优区域后再加密。另外可以调低cv到3先看趋势最后再正式跑5折。4.3 坑三TOPSIS负向指标当正向处理推荐结果离谱现象供应商评分里价格最贵的反而得分最高得出的推荐结果被采购部门喷成只看贵的选。原因TOPSIS求理想解时没有区分指标方向直接np.max导致价格越高越接近理想解。解决给每个指标配一个impacts向量1表示正向-1表示负向。在计算理想解前用weighted_norm * impacts做方向翻转负向指标先乘-1再求最大最小值这样价格低才会成为理想解。4.4 坑四权重没有归一化接近度出现负值或大于1现象得分输出不在0到1之间有的甚至大于1排序结果和直觉对不上。原因weights手动输入时没有归一化比如[0.3, 0.2, 0.2, 0.1, 0.1]加起来是0.9加权标准化矩阵失真导致理想解距离计算错位。解决在topsis函数入口加一个断言np.isclose(weights.sum(), 1.0)不通过就抛异常。或者函数内部先做weights weights / weights.sum()强制归一化。4.5 坑五把Kraljic规则丢进垃圾桶纯SVM在少量样本上学歪现象数据量只有几十条SVM训练出来的决策边界和Kraljic矩阵的常识判断差距很大边界区域分类混乱。原因SVM本质是数据驱动样本少时很容易过拟合噪声。专家经验没有参与建模。解决参考论文的融合思路先用Kraljic规则对每个样本生成一个预分类标签再用加权混合标签训练SVM。这样既保留数据特征又把专家阈值作为先验拉了回来。代码里实现时把0.7 * y 0.3 * kraljic_labels作为训练目标即可。提示在样本量不足200条时这种混合标签比纯SVM更稳样本量大了以后可以让SVM自由学习。5. 完整系统整合把物料分类和供应商评价串成流水线5.1 统一接口设计SupplierSelectionSystem类论文的代码散在几个脚本里实际使用必须收敛成一个类。我把训练、分类、评价、推荐全部封装进SupplierSelectionSystem训练和推理分离避免每次预测都重新fit。import numpy as np from sklearn import svm from sklearn.model_selection import GridSearchCV from sklearn.preprocessing import StandardScaler class SupplierSelectionSystem: def __init__(self): self.material_classifier None self.scaler StandardScaler() def train_material_classifier(self, X, y): 训练物料分类模型X为[value, risk]y为类别标签0-3 X_scaled self.scaler.fit_transform(X) param_grid { C: [0.1, 1, 10], gamma: [0.1, 0.01], kernel: [rbf] } grid GridSearchCV(svm.SVC(), param_grid, cv3) grid.fit(X_scaled, y) self.material_classifier grid.best_estimator_ return grid.best_score_ def classify_material(self, value, risk): 对新物料分类返回类别0战略,1杠杆,2一般,3瓶颈 X np.array([[value, risk]]) X_scaled self.scaler.transform(X) return self.material_classifier.predict(X_scaled)[0] def evaluate_suppliers(self, suppliers_data, material_type): 根据物料类型自动切换TOPSIS权重返回各供应商得分 if material_type 0: weights np.array([0.3, 0.15, 0.2, 0.15, 0.2]) elif material_type 1: weights np.array([0.25, 0.25, 0.2, 0.15, 0.15]) elif material_type 2: weights np.array([0.2, 0.3, 0.3, 0.1, 0.1]) else: weights np.array([0.25, 0.2, 0.25, 0.2, 0.1]) impacts np.array([1, -1, 1, 1, 1]) return self._topsis(suppliers_data, weights, impacts) def _topsis(self, data, weights, impacts): TOPSIS核心计算 norm_data data / np.sqrt((data ** 2).sum(axis0)) weighted_norm norm_data * weights ideal_best np.max(weighted_norm * impacts, axis0) ideal_worst np.min(weighted_norm * impacts, axis0) dist_best np.sqrt(((weighted_norm - ideal_best) ** 2).sum(axis1)) dist_worst np.sqrt(((weighted_norm - ideal_worst) ** 2).sum(axis1)) return dist_worst / (dist_best dist_worst)类的好处是scaler和分类器绑在一起新物料分类时不会掉进坑一的陷阱。evaluate_suppliers内部根据物料类型动态选权重外部调用者不需要关心具体权重是多少只需要把供应商评价矩阵传进来。5.2 训练推理分离与主流程演示实际业务里物料分类模型是用历史物料记录训练的训练完成后系统进入推理模式。新物料进来先分类再对候选供应商打分最后推荐得分最高的那家。if __name__ __main__: # 1. 初始化系统 system SupplierSelectionSystem() # 2. 用模拟数据训练分类模型 # 真实场景请替换为历史物料记录value, risk, category np.random.seed(42) X_train np.random.rand(100, 2) * 100 y_train np.random.randint(0, 4, 100) accuracy system.train_material_classifier(X_train, y_train) print(f物料分类模型训练准确率: {accuracy:.2f}) # 3. 新物料分类 new_material (80, 30) # 价值80风险30 material_type system.classify_material(new_material[0], new_material[1]) types [战略物料, 杠杆物料, 一般物料, 瓶颈物料] print(f该物料分类为: {types[material_type]}) # 4. 供应商评价质量/价格/交货/服务/技术 suppliers np.array([ [85, 45, 90, 80, 85], # 供应商1 [90, 50, 85, 85, 80], # 供应商2 [80, 40, 95, 75, 90] # 供应商3 ]) scores system.evaluate_suppliers(suppliers, material_type) print(供应商得分:, scores) print(推荐供应商:, np.argmax(scores) 1)模拟数据只是为了把流程跑通真实使用要替换成企业ERP导出的历史订单数据和供应商评审记录。流程跑通后你会发现主要工作量根本不在这段逻辑而在数据清洗和权重调研。5.3 扩展点新增指标与配置化权重这套系统最讨厌的是把权重硬编码在类里想加一个指标得改源码。我一般会把它配置化把权重方案放到JSON或数据库表里物料类型作为key权重数组作为value。新增指标时只需要在评价矩阵里加一列同时更新配置里的权重和impacts方向。另一个扩展点是分类阶段的特征扩充。论文只用价值、风险两个维度实际可以加入供应商依赖度、采购金额占比、替代供应商数量等特征。只要把训练数据的X从两列扩成多列SVM部分完全不用改标准化器会自动处理多特征的量纲。6. 进阶用AHP动态权重给TOPSIS加一层行业经验6.1 AHP判断矩阵与一致性检验TOPSIS要跑得靠谱权重必须先靠谱。业务里最常用的权重生产方法是AHP层次分析法核心是让专家对指标做两两比较。比如某采购总监认为质量比价格重要3倍就在判断矩阵的(质量, 价格)位置填3对称位置填1/3。矩阵填完用最大特征值对应的特征向量归一化得到权重。AHP有个硬约束判断矩阵必须通过一致性检验否则专家打分自相矛盾。一致性比例CR的计算方式是CI / RI其中CI (最大特征值 - n) / (n - 1)RI是查表得到的随机一致性指标。n5时RI1.12CR小于0.1才算合格。6.2 权重计算代码实现import numpy as np class AHPWeightCalculator: def __init__(self, criteria): self.criteria criteria self.n len(criteria) self.RI {3: 0.58, 4: 0.9, 5: 1.12} def calculate_weights(self, pairwise_matrix): 传入判断矩阵返回归一化权重并做一致性检验 eigenvalues, eigenvectors np.linalg.eig(pairwise_matrix) max_idx np.argmax(eigenvalues.real) max_eigenvalue eigenvalues.real[max_idx] weights eigenvectors[:, max_idx].real weights weights / np.sum(weights) CI (max_eigenvalue - self.n) / (self.n - 1) CR CI / self.RI[self.n] if CR 0.1: print(f警告: CR{CR:.2f} 0.1判断矩阵需要调整) return weights # 示例战略物料供应商的五指标判断矩阵 strategic_criteria [质量, 价格, 交货, 服务, 技术] strategic_matrix np.array([ [1, 3, 5, 3, 1/2], # 质量 [1/3, 1, 3, 2, 1/3], # 价格 [1/5, 1/3, 1, 1/2, 1/5], # 交货 [1/3, 1/2, 2, 1, 1/3], # 服务 [2, 3, 5, 3, 1] # 技术 ]) ahp AHPWeightCalculator(strategic_criteria) weights ahp.calculate_weights(strategic_matrix) print(战略物料指标权重:, dict(zip(strategic_criteria, weights)))np.linalg.eig返回的特征值可能有虚部这里取实部来处理。归一化后权重向量和为1可以直接喂给TOPSIS。如果CR超标说明专家判断里的逻辑冲突太大比如既认为质量比技术重要3倍又认为技术比质量重要2倍这种矩阵需要回到专家那里重新调研。6.3 把AHP权重接入系统接入方式很简单把SupplierSelectionSystem里的硬编码权重数组替换成AHP计算出来的结果。比如战略物料的权重不再是[0.3, 0.15, 0.2, 0.15, 0.2]而是从AHP实例的calculate_weights方法里得到。生产环境可以把每组物料类型的判断矩阵存成Excel或数据库系统启动时一次性算出所有权重再缓存到内存里。我后来实践时还做了一件事把AHP计算出的权重和TOPSIS打分结果一起写入日志方便采购部门复盘为什么这家供应商排第一。权重来源透明化之后业务部门对这套模型的信任度明显高了很多。从那以后我每次做供应链选型都强制把权重来源写清楚让每个类型物料有一套可追溯的判断矩阵再做TOPSIS排序。希望帮到你。本文还有配套的精品资源点击获取