news 2026/10/3 7:36:35

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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(axis=0), X_scaled.std(axis=0))

这里有个细节值得注意:value和risk的量纲可能差很多。比如年采购金额是百万级,而供应风险是 1~5 的打分,这种情况下如果不做标准化,SVM 的 RBF 核会把距离计算完全压倒在高量纲特征上,风险维度等于没参与分类。StandardScaler把两列都压到均值为 0、标准差为 1,是在这一步最稳的选择,比 MinMaxScaler 更适合 SVM 这种基于距离和核函数的模型。

标准化之外,类别标签必须保持整数。Kraljic 的四分类本身就是离散的,后续网格搜索和classification_report都依赖整数标签。如果你从前端采集到的标签是字符串或浮点,记得提前做 LabelEncoder 或 astype(int) 转换。

2.2 SVM 参数网格:为什么用 GridSearchCV 而不是随便设 C 和 gamma

SVM 在二分类上表现稳定,但多分类(这里恰好是四分类)依赖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_size=0.3, random_state=42 ) # 网格搜索 + 5 折交叉验证 grid = GridSearchCV(svm.SVC(), param_grid, refit=True, verbose=2, cv=5) 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, cmap=cmap_light, alpha=0.8) plt.scatter(X_scaled[:, 0], X_scaled[:, 1], c=y, cmap=cmap_bold, edgecolor='k', s=20) 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(axis=0)) # 2. 加权标准化 weighted_norm = norm_data * weights # 3. 理想解与负理想解 ideal_best = np.max(weighted_norm * impacts, axis=0) ideal_worst = np.min(weighted_norm * impacts, axis=0) # 4. 到理想解和负理想解的欧氏距离 dist_best = np.sqrt(((weighted_norm - ideal_best) ** 2).sum(axis=1)) dist_worst = np.sqrt(((weighted_norm - ideal_worst) ** 2).sum(axis=1)) # 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(axis=0)) weighted_norm = norm_data * weights # 方向修正:负向指标先取反,再统一求最大最小 direction = np.where(np.array(impacts) == -1, -1, 1) adjusted = weighted_norm * direction ideal_best = adjusted.max(axis=0) ideal_worst = adjusted.min(axis=0) ...

这个改法在指标多、方向杂的时候能省去很多排查时间。判断标准很简单:跑完看看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, cv=3, verbose=1) 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

字段对齐要特别小心。我见过一次事故:配置文件里把「交货」和「服务」写反了,系统跑出来的推荐供应商交期最差,业务反馈后查了半天才发现是列顺序错位。建议在读取配置后加一行断言,确认权重总和为 1:

assert 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。实际业务里供应商评价指标经常到 6~8 个,所以要么补充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 时做两件事——先定位判断矩阵中对一致性贡献最大的元素(通常是偏差最大的那一对),提示专家重新填写;如果反复填不过,把判断矩阵降维成 1~9 的简化打分,或改用熵权法直接从数据反推权重。

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(probability=True),之后用predict_proba拿四类的概率分布,低于某个阈值(比如 0.7)的物料自动进入人工复核队列。这一步能拦住 Kraljic 边界上最模糊的那批物料。

第三,把模型和权重做成可配置、可回滚的版本。权重配置表放到数据库或单独配置文件后,每次调整都记录版本号和生效时间。我在实际项目里吃过一次亏:权重改了但历史报告没有留存版本,出了分歧说不清当时用的哪一套。从那以后我每调整一次权重或重新训练一次分类器,都会导出当时的模型文件和权重配置存档,跑一次历史数据验证再发布,整套流程像发布代码一样严格。这份代码和这套验证习惯,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 7:36:02

GESP四级考试全攻略:从备考规划到考场实战避坑指南

3月GESP四级考完&#xff0c;我从机房出来先长出了一口气。这次参加四级认证不完全是为了自己&#xff0c;我带的班里这一轮有十几个孩子要冲四级&#xff0c;不亲自上考场把题目和时间压力都体验一遍&#xff0c;我心里实在没底。所以从寒假算起&#xff0c;我用了大约两个月时…

作者头像 李华
网站建设 2026/10/3 7:35:39

【Embedded Development】【高级MCU篇】基于野火STM32H750XBH6_Pro开发板的高级MCU开发学习之1点亮led

前言 开这个篇章是为了学习补全高级MCU下的一些高级外设功能和MCU怎么和外部的Flash和外部SDRAM怎么更好的搭配使用起来。 一、简介 1.1 浏览硬件资源布局和软件资源需求 11. H750PRO资料目录内容及底板介绍 — [野火]STM32开发板必读说明 文档 二、工程实践 2.1 打开STM3…

作者头像 李华
网站建设 2026/10/3 7:33:33

突破烟雾迷障!一层分子膜,暗电流砍半:APTES 界面工程让 PbS 量子点“看穿”

在自动驾驶与工业安全监测的感知层,短波红外(SWIR,1.0–2.5 μm)成像技术正展现出超越可见光方案的独特优势:雾霾、烟尘对短波红外的散射远弱于可见光,这意味着车辆或监控设备能够在恶劣大气条件下“看穿”烟雾,识别被遮蔽的目标。然而,这一愿景的落地长期受制于探测器…

作者头像 李华