层次分析AHP建模
一、层次分析构权法
层次分析构权法又称为互反式两两比较构权法,它是根据美国运筹学家萨蒂教授所创立的多目标多准则决策方法(AHP)原理提出的一种确定统计权数的方法。从理论上来说AHP中所有排序方法(如特征根法,最小平方法,对数最小平方法,广义特征向量法,非线性排序法,梯度向量法,最小偏差法等以及所有的标度方法(如比率标度法,指数标度法,差数标度法等)均可成为一种相对独立的构造统计权数的方法。)本次介绍方根法以及后续的代码编写都围绕方根法展开。
个人理解层次分析法不能属于真正意义上的数据挖掘算法,对此业内也有很多争议,其根本原因就是层次分析法的结果并不像别的数据挖掘算法一般,是基于数学计算所得。而AHP的结论有太多的人为因素参与。但仍然是业界常常用来确认指标权重的算法。
二、AHP基本思路
第一步:构造判断矩阵
将全部的评价(n个)指标列成一个棋盘式平衡表构造规则如表一所示
现在将不同因素两两作比获得的值aij 填入到矩阵的 i 行 j 列的位置,则构造了所谓的判断矩阵,对角线上都是1, 因为是自己和自己比。将判断矩阵记为A,aij为AHP中的标度,其含义是i指标的重要性是j指标重要性的倍数。确认aij的规则有很多,常用的如上表所示成为1-9比率标度,但是这些标度只不过是一个展示的参考值,构权者应该根据指标之间实际权重二元分配比例来确认aij的具体数字。
即 aij * aji = 1/aij
判断矩阵如下图所示:
在实际构造判断矩阵的过程,可以借用德尔菲专家调查法来进行矩阵的构造。
那么问题就来了,构造判断矩阵简单,如何获取对应的权重分配呢?
第二步 求解各指标权重
求解个指标的相对权重W1,W2...Wn方法有很多,这里介绍方根法进行求解以方便之后的代码理解。
(1)逐行计算A矩阵的行几何平均值W*
(2)对行几何平均值W*进行归一化,即为所计算的权重Wi
(3)对矩阵A中每列元素求和,得到向量S = (s1,s2....sn)
其中
(4)计算矩阵A的最大特征值max
至此我们已经得到了指标的权重值与最大特征值,但是,如何确认权重向量的合理性呢?
第三步 判断权重向量合理性
根据判断矩阵元素物理含义,我们不难发现,如果构权者在整个判断过程中是完全一致的(没有出现自相矛盾的地方),只应该成立一下条件:
A: aii = 1
B: aij = 1/aji
C: aik * akj = aij
显然,条件A,B是比较容易满足的,最困难的是条件C。如果对于任何的i,j,k条件C都是成立的,则我们称为此判断矩阵是满足一致性的,否则,便称为不一致。研究发现构造判断矩阵时,如果n>9那么已经超出了人记忆判断的范围,就很有可能造成判断逻辑混乱,故发生判断矩阵不一致现象。(对于判断矩阵如何进行调整,我会在后续文章指出)
对于判断矩阵的检查方法有两种,一种是一致性比率法,另一种是统计假设检验法,这里我们采用比较常用的一致性比率法。
式中,RI为同阶平均随机一致性指标,它是通过大量随机构造的样本矩阵计算的CI。CI为一致性指标,计算公式为:
为A矩阵的最大特征根。据证明,对于任何的判断矩阵A,均有 n,并且判断矩阵的一致性程度越高,越接近于n。当判断完全一致时A的非零特征根是惟一的且为n。显然,CI值越小,判断矩阵A的一致性程度越高。故CI是衡量判断矩阵一致性水平的重要指标。(理解这段话很重要,这段话使得你更好的理解如何调整判断矩阵。)前面说过判断矩阵一般大于9阶,人类比较判断的一致性能力就会越来越低。因此萨蒂提出随意一致性指标RI的概念。个人理解RI能够更加宽容的态度对待判断矩阵不一致的问题。项目中通常采用CR10%的标准。即,单判断矩阵的CR10%时,认为其不一致的程度是可以接受的,否则就认为不一致性太严重了,需要调整或重构判断矩阵。
三、python实现AHP算法
至此,如何AHP用来做什么,如何构造判断矩阵,如何计算指标权重以及如何对判断矩阵进行一致性检验已经介绍完毕了,接下来贴入用python实现的AHP算法,代码注释已经很清楚了,这里就不细说了。代码中的数据输入为txt格式的判断矩阵,大家可以根据自己的判断矩阵放入txt文件中运行代码。
import numpy as np
from fractions import Fraction # fraction模块提供有关有理数的算术表达和计算,实际上就是分数的表达和计算。
# 读取文件,构造判断矩阵
def judgexMatrix(filePath):
fr = open(filePath)
data = fr.readlines()
column = len(data[0].strip().split(',')) # 列数
row = len(data) # 行数
index = 0
judgeMatrix = np.zeros((row, column)) # 构造n*n全零矩阵
for line in data:
item = line.strip().split(',') # 读取每一行数据
newList = [] # 创建空列表当做容器
for i in range(len(item)):
value = Fraction(item[i]) + 0.0 # Fraction(numerator=0, denominator=1):第一个参数是分子,默认为0;第二个参数为分母,默认为1
newList.append(value)
judgeMatrix[index, :] = newList[0:column] # 将每行结果赋值到全零矩阵
index += 1
return judgeMatrix
# 对判断矩阵进行开根和归一化
def weightValue(judgematrix):
'''
product:各行乘积
sum:计算n次方根后求和
standWeight:各n次方根/n次方根的和,得出特征向量
'''
weight = []
standWeight = []
sum = 0
for item in judgematrix:
product = 1
for i in item:
product *= i
# sum += (product ** (1/len(item)))
sum += pow(product, 1 / len(item))
# print(sum)
weight.append(product ** (1 / len(item)))
for item in weight:
standWeight.append(round(item / sum, 2)) # 各n次方根 / n次方根的和,得出特征向量 也就是之后的权重
# print(standWeight)
return standWeight
# 判断矩阵judgematrix*特征向量weightValue得到矩阵M 用于之后计算最大特征根
def matrixMul(judgematrix, weightValue):
res = [[0] * np.size(weightValue[0]) for i in range(0, len(judgematrix))] # 构造容器
for i in range(len(judgematrix)):
for j in range(np.size(weightValue[0])):
for k in range(np.size(weightValue)):
res[i][j] += judgematrix[i][k] * weightValue[k][j]
return res
def trans(weightValuse): # 将归一化的weightValues转换格式
a = [[] for i in weightValue]
for i in range(0, len(weightValue)):
a[i].append(weightValue[i])
return a
def maxRoot(judgematrix, weightValue): # 最大特征根计算
transWeightValue = trans(weightValue)
matMul = matrixMul(judgematrix, transWeightValue)
sum = 0
n = len(judgematrix)
for i in range(len(judgematrix)):
sum += matMul[i][0] / (transWeightValue[i][0] * n) # 对M的每一个元素除以n*权向量得到最大特征根
return sum
# 一致性检验
def consistentCheck(judgematrix, weightValue, featureNum):
status = 'fail'
root = maxRoot(judgematrix, weightValue)
RI = [0.00, 0.00, 0.58, 0.90, 1.12, 1.24, 1.32, 1.41, 1.45, 1.45, 1.49, 1.51, 1.48, 1.56, 1.57, 1.58]
CI = round((root - featureNum) / (featureNum - 1), 5)
CR = round(CI / RI[featureNum - 1], 4)
if CR < 0.1:
status = 'OK,通过了一致性检验 CR = %s' % CR
else:
status = 'Sorry,没有通过一致性检验 CR=%s' % CR
return status
if __name__ == "__main__":
filePath = 'data.txt'
judgematrix = judgexMatrix(filePath)
weightValue = weightValue(judgematrix)
print('weightValue is : %s' % weightValue)
print('\n')
print(consistentCheck(judgematrix, weightValue, len(judgematrix)))