第一次接触TOPSIS法是在数学建模校赛期间,当时需要评估五个城市的空气质量优劣。面对PM2.5、二氧化硫等不同量纲的指标,传统加权平均法完全失效。直到队友推荐了这个"优劣解距离法",才让我真正理解什么是科学的多指标决策。
TOPSIS(Technique for Order Preference by Similarity to Ideal Solution)的核心思想非常直观:找出距离理想最优解最近且远离最劣解的方案。就像在电商平台选手机,我们会自动寻找价格最低(成本型)、性能最强(效益型)的机型,这就是人脑本能的TOPSIS思维。
与层次分析法(AHP)相比,TOPSIS有三大实战优势:
在数学建模竞赛中,TOPSIS特别适合解决这类问题:
先看一个真实案例:某次帮医学院同学分析患者数据时,遇到体温(区间型)、住院天数(成本型)、康复指数(效益型)混合指标。直接套用公式会得到荒谬结果——因为没做正向化处理。
不同类型指标的转换技巧:
python复制import numpy as np
# 原始数据矩阵
data = np.array([
[36.5, 7, 85], # 患者A
[37.8, 5, 90], # 患者B
[38.2, 3, 76] # 患者C
])
# 极小型→极大型(住院天数)
cost_col = 1
data[:, cost_col] = max(data[:, cost_col]) - data[:, cost_col]
# 区间型→极大型(体温36-37最佳)
interval_col = 0
a, b = 36, 37
M = max(a - min(data[:, interval_col]), max(data[:, interval_col]) - b)
for i in range(len(data)):
if data[i, interval_col] < a:
data[i, interval_col] = 1 - (a - data[i, interval_col])/M
elif data[i, interval_col] > b:
data[i, interval_col] = 1 - (data[i, interval_col] - b)/M
else:
data[i, interval_col] = 1
常见踩坑点:
标准化不是简单的除以最大值!我曾在研究生课题中比较过三种方法:
| 方法 | 公式 | 适用场景 | 缺陷 |
|---|---|---|---|
| Z-score | (x-μ)/σ | 数据服从正态分布 | 受异常值影响大 |
| Min-Max | (x-min)/(max-min) | 均匀分布数据 | 新数据可能超出范围 |
| 向量归一化 | x/√(Σx²) | 多指标量纲差异大 | 破坏原始数据结构 |
TOPSIS通常采用向量归一化,Python实现:
python复制def normalize(matrix):
# 沿列方向计算平方和
norms = np.sqrt(np.sum(matrix**2, axis=0))
# 避免除以零
norms[norms == 0] = 1
return matrix / norms
norm_data = normalize(data)
print("标准化矩阵:\n", norm_data)
这里有个容易被忽视的细节——加权距离计算。在一次企业咨询项目中,客户坚持认为价格权重应是质量的3倍,这时就需要引入权重向量:
python复制weights = np.array([0.2, 0.3, 0.5]) # 体温20%,住院天数30%,康复指数50%
# 加权标准化矩阵
weighted_norm = norm_data * weights
# 理想解与负理想解
ideal_best = np.max(weighted_norm, axis=0)
ideal_worst = np.min(weighted_norm, axis=0)
# 欧氏距离计算
D_best = np.sqrt(np.sum((weighted_norm - ideal_best)**2, axis=1))
D_worst = np.sqrt(np.sum((weighted_norm - ideal_worst)**2, axis=1))
# 综合得分
scores = D_worst / (D_best + D_worst)
final_scores = scores / np.sum(scores) # 归一化
print("综合得分:", final_scores)
根据多次竞赛经验,整理出高频错误:
数据类型混淆:
标准化失效:
权重分配不合理:
python复制def entropy_weight(matrix):
# 计算信息熵
P = matrix / np.sum(matrix, axis=0)
E = -np.sum(P * np.log(P + 1e-10), axis=0) / np.log(len(matrix))
# 计算权重
return (1 - E) / np.sum(1 - E)
print("熵权法权重:", entropy_weight(norm_data))
当处理上万条数据时,原始实现会非常慢。通过向量化运算和并行计算可以显著提升速度:
python复制from numba import jit
@jit(nopython=True)
def fast_topsis(data, weights):
# 向量化实现(速度提升10倍+)
norm = data / np.sqrt(np.sum(data**2, axis=0))
weighted = norm * weights
D_best = np.sqrt(np.sum((weighted - np.max(weighted, axis=0))**2, axis=1))
D_worst = np.sqrt(np.sum((weighted - np.min(weighted, axis=0))**2, axis=1))
return D_worst / (D_best + D_worst)
在去年国赛A题中,我们创新性地结合了三种权重:
最终权重采用加权平均:
python复制final_weights = 0.5*entropy_weights + 0.3*ahp_weights + 0.2*critic_weights
当数据存在模糊性时(如专家打分区间),可以扩展为模糊TOPSIS:
python复制# 定义三角模糊数
class FuzzyNumber:
def __init__(self, l, m, r):
self.l, self.m, self.r = l, m, r
# 模糊距离计算
def fuzzy_distance(a, b):
return np.sqrt((a.l-b.l)**2 + (a.m-b.m)**2 + (a.r-b.r)**2)
对于时间序列数据,可以引入衰减因子实现动态评估:
python复制def dynamic_weights(base_weights, decay=0.9):
T = len(base_weights)
return [w * (decay**(T-t)) for t, w in enumerate(base_weights)]
在实际项目中,TOPSIS往往需要与其他方法联用。比如先用聚类分析分组,再在各组内进行TOPSIS评价。这种组合策略在去年华为杯竞赛中帮助团队获得了前10%的成绩。