ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Kraljic矩阵+SVM物料分类与TOPSIS供应商评价:Python实现全解析

Kraljic矩阵+SVM物料分类与TOPSIS供应商评价:Python实现全解析 简介面向供应链管理、采购与优化研究者的docx技术文档聚焦基于物料分类的差异化供应商选择方法。内容以Kraljic矩阵和SVM为核心构建物料分类模型将物料划分战略、杠杆、一般、瓶颈四类再针对不同类型设计差异化评价指标体系利用TOPSIS方法完成供应商优选并结合C公司实证验证有效性。文档共1个docx文件压缩后约58KB适合作为相关课题参考。目前已有45人学习。文档不仅给出完整理论框架还包含详细可运行的Python代码与解释覆盖数据标准化、训练集测试集划分、SVM网格搜索调参、分类报告输出、分类边界可视化及TOPSIS评价实现等关键环节便于读者按步骤复现、调整指标与权重迁移到实际业务场景。1. 为什么物料分类总卡在边界上这篇能直接抄作业的复现到底解决了什么做采购供应链的人提到物料分类几乎都先想到 Kraljic 矩阵——横轴是供应风险纵轴是物料价值切出战略、杠杆、一般、瓶颈四个格子。这张图理论很顺落地却经常卡在同一个地方价值和风险的分值怎么定、边界线画在哪基本靠拍脑袋。这一篇复现的是把 Kraljic 矩阵和 SVM 结合得到物料分类模型用网格搜索自动学出分类边界再按物料类型给 TOPSIS 供应商评价配不同权重C 公司案例把整条链路串了起来。对供应链主管和采购经理来说这是一套能直接抄作业的定量框架对做研究的人它把数据预处理、模型训练、指标设定到决策支持的示例代码都摆出来了复现门槛比读论文低很多。下面按我的拆解顺序走一遍先解决分类再解决评价最后聊整合和踩坑。2. 物料分类建模Kraljic 四象限如何交给 SVM 去学2.1 数据格式与标准化真实物料数据应该长什么样先说数据。代码里最常用的模拟结构是两列value物料价值和risk供应风险。但实际业务里这两列往往不是直接读数而是打分合成——价值可以是采购金额、用量占比、对产品质量影响的加权风险可以是供应中断概率、可替代性、存储与运输复杂度的合计。Kraljic 矩阵的原始定义给了维度但没有给量化口径所以第一步是把业务口径固定下来否则后面所有分类结果都没有可解释性。读入数据的代码一般是这样的import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler # 物料数据集value 是物料价值评分risk 是供应风险评分 data pd.read_csv(material_data.csv) X data[[value, risk]].values y data[category].values # 0-战略, 1-杠杆, 2-一般, 3-瓶颈 # 标准化处理 scaler StandardScaler() X_scaled scaler.fit_transform(X) print(标准化后的均值近似为0方差近似为1, X_scaled.mean(axis0), X_scaled.std(axis0))这里有个细节值得注意value和risk的量纲可能差很多。比如年采购金额是百万级而供应风险是 15 的打分这种情况下如果不做标准化SVM 的 RBF 核会把距离计算完全压倒在高量纲特征上风险维度等于没参与分类。StandardScaler把两列都压到均值为 0、标准差为 1是在这一步最稳的选择比 MinMaxScaler 更适合 SVM 这种基于距离和核函数的模型。标准化之外类别标签必须保持整数。Kraljic 的四分类本身就是离散的后续网格搜索和classification_report都依赖整数标签。如果你从前端采集到的标签是字符串或浮点记得提前做 LabelEncoder 或 astype(int) 转换。2.2 SVM 参数网格为什么用 GridSearchCV 而不是随便设 C 和 gammaSVM 在二分类上表现稳定但多分类这里恰好是四分类依赖kernel、C、gamma三个参数的配合。C控制误分类惩罚C越大边界越硬也越容易过拟合gamma控制 RBF 核的影响半径gamma越大单个样本影响范围越小决策面越复杂。这两个参数不调分类边界基本靠猜。论文代码里给出的做法是网格搜索加上交叉验证from sklearn import svm from sklearn.model_selection import GridSearchCV, train_test_split from sklearn.metrics import classification_report # 参数网格C 和 gamma 按数量级扫 param_grid { C: [0.1, 1, 10, 100], gamma: [1, 0.1, 0.01, 0.001], kernel: [rbf, poly, sigmoid] } # 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split( X_scaled, y, test_size0.3, random_state42 ) # 网格搜索 5 折交叉验证 grid GridSearchCV(svm.SVC(), param_grid, refitTrue, verbose2, cv5) grid.fit(X_train, y_train) print(最佳参数:, grid.best_params_) print(测试集分类报告:) print(classification_report(y_test, grid.predict(X_test)))跑完grid.best_params_之后一定要看classification_report里每个类别的 precision 和 recall。Kraljic 四分类的不平衡问题很常见——一般物料往往占了大头战略物料和瓶颈物料样本很少。如果战略物料的 recall 只有 0.4说明模型把战略物料错分到了别处这种错误在采购决策里比把一般物料分错严重得多。一个我常用的动作是在class_weight里给少数类加权或者对训练集做 SMOTE 过采样再重新跑一遍网格搜索。2.3 分类边界可视化看四类物料分得开不开模型评估完很多人直接提交结论但我建议多做一步可视化。Kraljic 矩阵最大的价值是「一眼看懂边界」SVM 的分类边界可视化能帮你判断这个边界是否符合业务直觉。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 0.02在标准化后的空间里意味着上万级别的网格点如果样本量很大这一步会明显变慢。我一般会先把h调大到 0.1 用来看整体轮廓确认边界形状之后再回到细步长做正式图。如果图上出现大量锯齿状的粘连边界说明两个维度的区分度不够这时候不要急着调 SVM 参数先回头检查价值和风险的打分口径是不是真的分离了。边界可视化是复现这篇论文时最容易被跳过但又最有诊断价值的一步。3. 差异化供应商评价TOPSIS 权重方案与四类物料的取舍3.1 TOPSIS 的五个步骤拆开看物料分好类之后供应商评价的逻辑就变了战略物料要保供和质量价格可以放一放一般物料核心是价格和交货技术能力不需要给太高权重。这就是「差异化」三个字的落点。评价方法选 TOPSIS 而不是简单的加权平均是因为 TOPSIS 同时看「离理想解最近」和「离负理想解最远」能区分出两个加权总分相同但短板不同的供应商。核心函数不长我摘一段关键实现import numpy as np def topsis(data, weights, impacts): TOPSIS 方法实现供应商评价 data: 评价矩阵每行一个供应商每列一个评价指标 weights: 各指标权重 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这个实现方向是对的但impacts直接乘到weighted_norm上再求max/min对负向指标的处理很容易在后续扩展时出问题。更稳的做法是在标准化之后先把负向指标取反或取倒数再统一求最大最小。否则一旦权重或指标顺序变动理想解的方向就可能反掉。这一点会在第 5 章再展开。3.2 差异化权重战略物料和一般物料凭什么不同代码里最具说服力的部分是同一套评价指标在不同物料类型下用不同权重。看下面这个对比# 战略物料质量 0.3价格 0.15交货 0.2服务 0.15技术 0.2 strategic_weights np.array([0.3, 0.15, 0.2, 0.15, 0.2]) # 一般物料质量 0.2价格 0.3交货 0.3服务 0.1技术 0.1 general_weights np.array([0.2, 0.3, 0.3, 0.1, 0.1])战略物料的权重集中在质量和技术上价格权重压到 0.15因为这类物料断供损失远大于价格差一般物料的权重显著偏向价格和交货各占 0.3因为这类物料可替代性高采购策略就是成本优先。两套权重一对比差异化策略就从「口号」变成了「可计算的参数」。完整跑通需要把四类物料的候选供应商放进同一个评价矩阵得到得分后按np.argmax(scores)推荐# 战略物料候选供应商评价矩阵 strategic_suppliers np.array([ [90, 45, 95, 85, 90], # 供应商1质量90 价格45 交货95 服务85 技术90 [85, 50, 90, 90, 85], # 供应商2 [95, 40, 85, 80, 95] # 供应商3 ]) strategic_impacts np.array([1, -1, 1, 1, 1]) # 价格是负向指标 strategic_scores topsis(strategic_suppliers, strategic_weights, strategic_impacts) print(战略物料供应商得分:, strategic_scores) print(推荐供应商:, np.argmax(strategic_scores) 1)同一批供应商如果换到一般物料的权重下排序结果完全可能变化这正是差异化评价想达到的效果。落到业务上意味着采购部门不能再拿一张统一的供应商评分表打天下。3.3 负向指标处理TOPSIS 最容易在这里翻车价格是负向指标但评价矩阵里的原始数据是「越小越好」的绝对值TOPSIS 要求所有指标先统一成「越大越好」的方向。代码里用impacts数组标记方向理论上没问题但路演和复现论文时最容易翻车的是数据里出现 0 或负值、权重向量没对齐、impacts顺序写错。负向指标如果不做方向预处理理想解会被算成「价格最高」的供应商推荐出来的恰是最贵的那家。我建议写一个防御性的方向处理片段def topsis(data, weights, impacts): norm_data data / np.sqrt((data ** 2).sum(axis0)) weighted_norm norm_data * weights # 方向修正负向指标先取反再统一求最大最小 direction np.where(np.array(impacts) -1, -1, 1) adjusted weighted_norm * direction ideal_best adjusted.max(axis0) ideal_worst adjusted.min(axis0) ...这个改法在指标多、方向杂的时候能省去很多排查时间。判断标准很简单跑完看看ideal_best对应到原始数据是否真的是「质量最高、价格最低、交期最好」的那一组值如果不是方向处理一定有误。4. 全套系统整合SupplierSelectionSystem 类如何串起「训练-分类-评价」4.1 类设计把训练、分类、评价收进同一个系统单独跑 SVM 和单独跑 TOPSIS 都不难难点在于把两个阶段接起来让新物料能自动走完整条链路。论文代码里用SupplierSelectionSystem类解决了这个问题设计上值得借鉴训练和预测共用一个scaler分类结果直接决定后续权重。class SupplierSelectionSystem: def __init__(self): self.material_classifier None self.scaler StandardScaler() def train_material_classifier(self, X, y): 训练物料分类模型保存最佳模型和标准化器 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, verbose1) 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 评价 weights_map { 0: np.array([0.3, 0.15, 0.2, 0.15, 0.2]), # 战略 1: np.array([0.25, 0.25, 0.2, 0.15, 0.15]), # 杠杆 2: np.array([0.2, 0.3, 0.3, 0.1, 0.1]), # 一般 3: np.array([0.25, 0.2, 0.25, 0.2, 0.1]) # 瓶颈 } impacts np.array([1, -1, 1, 1, 1]) return topsis(suppliers_data, weights_map[material_type], impacts)这个设计有几个让我觉得「可以直接抄」的点。第一scaler作为实例属性保存训练和预测用的是同一个标准化器避免了新物料进来后重新算均值和方差的问题。第二evaluate_suppliers用字典映射物料类型到权重新增物料类型或调权重只需要改字典不用动主流程。第三classify_material接收的是原始维度的 value 和 risk外部调用者不需要懂标准化接口友好。4.2 权重配置化从写死在代码里到按物料类型自动选实际部署时我不建议把权重直接写在字典里尤其是采购策略每个月都可能微调。更合理的做法是把权重放到外部配置文件或数据库系统启动时加载。下面是一份典型的配置表结构物料类型质量价格交货服务技术能力战略物料0.300.150.200.150.20杠杆物料0.250.250.200.150.15一般物料0.200.300.300.100.10瓶颈物料0.250.200.250.200.10字段对齐要特别小心。我见过一次事故配置文件里把「交货」和「服务」写反了系统跑出来的推荐供应商交期最差业务反馈后查了半天才发现是列顺序错位。建议在读取配置后加一行断言确认权重总和为 1assert abs(weights.sum() - 1.0) 1e-6, 权重之和必须为1这个断言成本极低但能拦住大部分低级配置错误。4.3 AHP 生成权重专家问卷是理论来源差异化权重不是拍脑袋定出来的论文里提到用 AHP层次分析法从专家问卷中提炼权重。AHP 的核心是构造判断矩阵然后求最大特征值对应的特征向量作为权重。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_eigenvalue np.max(eigenvalues.real) idx np.argmax(eigenvalues.real) weights eigenvectors[:, 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注意RI字典只有 3、4、5 三个值。如果评价指标数是 6 个或更多会直接 KeyError。实际业务里供应商评价指标经常到 68 个所以要么补充RI表要么改用特征值排序后人工检查一致性。AHP 的计算本身不复杂但「专家填的矩阵经常一致性不过」才是常态后面避坑章专门说。5. 复现避坑跑通这份代码时最容易翻车的六个地方5.1 混合标签导致 SVM 报「Unknown label type」现象照着论文思路把 Kraljic 预分类结果和原始标签做加权混合例如0.7 * y 0.3 * kraljic_labels然后丢给GridSearchCV报错Unknown label type: continuous或训练后准确率一团糟。原因y和kraljic_labels都是整数分类标签但混合后变成 1.3、2.7 这类浮点数。SVM 的分类器要求标签是整数浮点标签无法做多分类决策。解决不要混标签。常见做法是把 Kraljic 预分类结果作为特征拼进X让模型同时看到原始维度值和先验分类信息或者保留两个分类器做多数投票。我复现时直接把_kraljic_preclassify(X)的结果作为第三列特征效果比混合标签自然得多。5.2 scaler 先 fit 再切分导致数据泄露现象先用scaler.fit_transform(X)对全量数据标准化再切训练测试集测试集指标虚高上线后新数据表现明显下滑。原因标准化器在全量数据上估计的均值和方差包含了测试集信息测试集不再是「没见过」的数据。SVM 对数据分布敏感这种泄露会让测试集分数乐观 5%10%。解决先train_test_split再在训练集上fit_transform在测试集上只transform。新物料分类时同样只用之前在训练集上得到的 scaler。5.3 TOPSIS 的负向指标把理想解算反现象价格明明是越低越好TOPSIS 推荐的却是价格最高的供应商采购看到结果直接质疑模型。原因weighted_norm * impacts后直接取max对impacts -1的列乘 -1 后原本越小越好的指标变成了「负得最多即原值最大」最好方向反了。解决按 3.3 的做法在加权标准化后先对负向指标统一取反再求max/min。跑完用print(ideal_best)对照原始数据验一遍方向。5.4 AHP 一致性检验 CR≥0.1 却继续用权重现象专家填的判断矩阵算出来CR 0.23代码只是打印了一句警告权重照常输出结果供应商排名和业务直觉相悖。原因判断矩阵不一致意味着专家的两两比较互相矛盾此时特征值法得到的权重没有可信度。只警告不处理等于把错误权重送进 TOPSIS。解决CR≥0.1 时做两件事——先定位判断矩阵中对一致性贡献最大的元素通常是偏差最大的那一对提示专家重新填写如果反复填不过把判断矩阵降维成 19 的简化打分或改用熵权法直接从数据反推权重。5.5 用随机标签训练模型准确率没有任何业务意义现象示例代码里用np.random.rand(100, 2) * 100生成价值和风险用np.random.randint(0, 4, 100)生成类别标签训练准确率 70%于是以为模型能用了。原因随机标签之间没有真实映射关系模型学到的只是数据分布噪音。这个 70% 只是验证了「有监督学习可以拟合随机标签」这个理论事实和真实物料分类完全不沾边。解决正式建模时用历史打分数据和人工标注的物料类别样本量不够就先用 Kraljic 规则把明显边界上的物料标出来再对模糊区域的物料做人工复核。把随机数据当成一个冒烟测试是合理的但一定得知道它在测什么。5.6 可视化网格步长太小导致内存溢出现象h 0.02时np.meshgrid生成的网格点达到上千万甚至更多grid.predict跑很久严重时直接内存崩溃。原因标准化后坐标范围约 -2 到 4步长 0.02 意味着 300×600 的网格每个点都要过一遍 SVM 决策函数时间和内存消耗都很大。解决先用h 0.1看整体边界形状确认没问题后如果还想出高清图就把范围缩小到样本密集的区域而不是全图细网格。作为工程习惯我会把可视化封装成函数步长作为参数默认 0.1 起步。6. 落地验证与扩展把分类结果推给业务前先做这三件事论文复现到「跑通代码」只是第一步真正让这套系统在采购流程里产生价值还需要补三件事。第一用历史供应商绩效数据做回测。最直接的方法是回顾过去两年的采购记录每个物料当时属于哪一类对应的供应商后来在质量、价格、交期上的实际表现如何。把实际表现当成「事后理想解」用训练好的系统重新打分排名看 Top 1 推荐的供应商和实际选中的供应商是否重合以及真实绩效排名和 TOPSIS 得分的相关系数。回测通过率低于 60% 的话大概率是分类边界或权重配置有问题不要急着上线。第二给分类器加上置信度输出。svm.SVC()默认不输出概率但要部署到业务里「这个物料被分进战略类但只有 52% 的把握」这种信号非常关键。训练时用SVC(probabilityTrue)之后用predict_proba拿四类的概率分布低于某个阈值比如 0.7的物料自动进入人工复核队列。这一步能拦住 Kraljic 边界上最模糊的那批物料。第三把模型和权重做成可配置、可回滚的版本。权重配置表放到数据库或单独配置文件后每次调整都记录版本号和生效时间。我在实际项目里吃过一次亏权重改了但历史报告没有留存版本出了分歧说不清当时用的哪一套。从那以后我每调整一次权重或重新训练一次分类器都会导出当时的模型文件和权重配置存档跑一次历史数据验证再发布整套流程像发布代码一样严格。这份代码和这套验证习惯希望帮到你。本文还有配套的精品资源点击获取
返回列表