Files
2026_DesignAI/officefile/md/supplements/02-spatial-intelligence/02.3-multi-criteria-decision.md
T
pengxiao a90f7adfa1 refactor(officefile): 按 md/latex/word 三层结构重组文档目录
将 Markdown 源文件移入 md/,LaTeX 工作目录保留在 latex/,
Word 导出移入 word/;删除临时脚本、调试截图和空 stub。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 14:25:21 +08:00

1335 lines
48 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 02.3 多准则决策
## 核心问题
> 当多个目标相互冲突时,如何做出"最优"决策?
> 专家的判断经验如何转化为可计算的权重?
---
## 概念讲解
### 什么是多准则决策分析 (MCDA)
多准则决策分析是一种在多个、通常是冲突的准则下评估和选择替代方案的方法论。
```
┌─────────────────────────────────────────────────────────────┐
│ 多准则决策问题的结构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 决策目标 │ │
│ │ "选择最适合生态修复的区域" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 评估准则 │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ 生态重要性 │ │ 实施可行性 │ │ 成本效益 │ │ │
│ │ │ (Weight) │ │ (Weight) │ │ (Weight) │ │ │
│ │ │ 0.4 │ │ 0.3 │ │ 0.3 │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 替代方案 │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │
│ │ │ 区域A │ │ 区域B │ │ 区域C │ │ 区域D │ ... │ │
│ │ └──────┘ └──────┘ └──────┘ └──────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 决策结果 │ │
│ │ 综合得分 + 排名 + 稳健性分析 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
### MCDA的核心组成部分
| 组成部分 | 描述 | 空间应用示例 |
|---------|------|-------------|
| **准则 (Criteria)** | 评估标准,反映决策目标 | 生境质量、连通性、建设成本 |
| **权重 (Weights)** | 准则的相对重要性 | 生态重要性0.5,经济成本0.3,社会因素0.2 |
| **得分 (Scores)** | 各方案在各准则下的表现 | 每个栅格的生境适宜性指数 |
| **标准化 (Normalization)** | 将不同单位转换为可比尺度 | 0-1标准化、排名转换 |
| **集结规则 (Aggregation)** | 合并多准则得分的方法 | 加权求和、加权乘积、TOPSIS |
### 常用的MCDA方法
```
┌─────────────────────────────────────────────────────────────┐
│ MCDA方法分类 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 加权线性组合 (WLC) / 简单加权法 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Score = Σ(weight_i × score_i) │ │
│ │ │ │
│ │ 优点:简单、直观、易于理解 │ │
│ │ 缺点:允许补偿(一个准则的差可被另一个优弥补) │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 2. 层次分析法 (AHP) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 通过成对比较确定权重 │ │
│ │ │ │
│ │ 优点:结构化、一致性检验 │ │
│ │ 缺点:比较次数多(n(n-1)/2)、可能存在不一致 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 3. TOPSIS (逼近理想解排序法) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 选择距正理想解最近、负理想解最远的方案 │ │
│ │ │ │
│ │ 优点:考虑方案与理想解的相对距离 │ │
│ │ 缺点:对权重敏感 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 4. OWA (有序加权平均) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 允许控制"风险态度"(ORness) │ │
│ │ │ │
│ │ 优点:灵活的风险偏好建模 │ │
│ │ 缺点:需要确定orness参数 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 标准化方法
不同准则有不同的量纲,需要标准化:
```
┌─────────────────────────────────────────────────────────────┐
│ 标准化方法 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 最大-最小标准化 (Min-Max) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ x_norm = (x - min) / (max - min) │ │
│ │ 适用:有明确最大最小值,线性关系 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 2. Z-score标准化 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ x_norm = (x - mean) / std │ │
│ │ 适用:正态分布数据,异常值敏感 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 3. 分位数转换 (Quantile) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ x_norm = percentile_rank(x) │ │
│ │ 适用:分布未知,需要稳健性 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 4. 目标导向标准化 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 效益型: x_norm = x / target │ │
│ │ 成本型: x_norm = target / x │ │
│ │ 适用:有明确目标值 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## 设计原理
### AHP层次分析法
AHP通过成对比较确定权重,具有结构化和一致性检验的优点:
```python
import numpy as np
from typing import List, Dict, Tuple, Optional
class AHPAnalyzer:
"""
层次分析法 (Analytic Hierarchy Process) 实现
核心思想:通过成对比较构建判断矩阵,计算权重
"""
# Saaty标度:1-9及其倒数
SAATY_SCALE = {
1: "同等重要",
3: "稍微重要",
5: "明显重要",
7: "强烈重要",
9: "极端重要",
2: "介于1和3之间",
4: "介于3和5之间",
6: "介于5和7之间",
8: "介于7和9之间"
}
def __init__(self, criteria: List[str]):
"""
Args:
criteria: 准则列表
"""
self.criteria = criteria
self.n = len(criteria)
self.comparison_matrix = None
self.weights = None
self.consistency_ratio = None
def set_comparison_matrix(self, matrix: np.ndarray):
"""
直接设置比较矩阵
Args:
matrix: n×n的判断矩阵,matrix[i,j]表示i相对于j的重要性
"""
if matrix.shape != (self.n, self.n):
raise ValueError(f"矩阵大小应为{self.n}×{self.n}")
# 确保对角线为1
np.fill_diagonal(matrix, 1)
self.comparison_matrix = matrix
def build_from_pairs(self, pairs: Dict[Tuple[str, str], float]):
"""
从成对比较构建矩阵
Args:
pairs: {(criterion_i, criterion_j): value} 表示i相对于j的重要性
如果j相对于i,则value应为1/value
"""
self.comparison_matrix = np.eye(self.n)
criterion_to_idx = {c: i for i, c in enumerate(self.criteria)}
for (ci, cj), value in pairs.items():
i, j = criterion_to_idx[ci], criterion_to_idx[cj]
self.comparison_matrix[i, j] = value
self.comparison_matrix[j, i] = 1.0 / value
def compute_weights(self, method: str = 'eigenvector') -> np.ndarray:
"""
计算权重
Args:
method: 'eigenvector'(特征向量法) 或 'geometric'(几何平均法)
Returns:
权重向量
"""
if self.comparison_matrix is None:
raise ValueError("请先设置比较矩阵")
if method == 'eigenvector':
# 特征向量法:求最大特征值对应的特征向量
eigenvalues, eigenvectors = np.linalg.eig(self.comparison_matrix)
max_idx = np.argmax(eigenvalues.real)
weights = eigenvectors[:, max_idx].real
# 归一化
weights = weights / weights.sum()
elif method == 'geometric':
# 几何平均法
weights = np.exp(np.log(self.comparison_matrix).mean(axis=1))
weights = weights / weights.sum()
else:
raise ValueError(f"未知方法: {method}")
self.weights = weights
return weights
def compute_consistency(self) -> Dict[str, float]:
"""
计算一致性指标
Returns:
包含一致性相关指标的字典
"""
if self.comparison_matrix is None or self.weights is None:
raise ValueError("请先设置比较矩阵并计算权重")
# 计算最大特征值
weighted_sum = self.comparison_matrix @ self.weights
lambda_max = (weighted_sum / self.weights).mean()
# 一致性指标 CI
n = self.n
ci = (lambda_max - n) / (n - 1) if n > 1 else 0
# 随机一致性指标 RI (Saaty给出的标准值)
ri_table = {1: 0, 2: 0, 3: 0.58, 4: 0.90, 5: 1.12,
6: 1.24, 7: 1.32, 8: 1.41, 9: 1.45, 10: 1.49}
ri = ri_table.get(n, 1.49)
# 一致性比率 CR
cr = ci / ri if ri > 0 else 0
self.consistency_ratio = cr
return {
'lambda_max': lambda_max,
'CI': ci,
'RI': ri,
'CR': cr,
'consistent': cr < 0.1
}
def get_weights_dict(self) -> Dict[str, float]:
"""返回准则-权重字典"""
if self.weights is None:
self.compute_weights()
return {c: w for c, w in zip(self.criteria, self.weights)}
# 使用示例
def example_ahp():
"""AHP使用示例:生态源地选址"""
criteria = ['生态价值', '连通性', '实施成本', '社会接受度']
ahp = AHPAnalyzer(criteria)
# 设置成对比较(示例数据)
pairs = {
('生态价值', '连通性'): 2, # 生态价值比连通性稍微重要
('生态价值', '实施成本'): 5, # 生态价值比成本明显重要
('生态价值', '社会接受度'): 3, # 生态价值比社会接受度稍微重要
('连通性', '实施成本'): 3, # 连通性比成本稍微重要
('连通性', '社会接受度'): 1, # 连通性与社会接受度同等重要
('实施成本', '社会接受度'): 1/2, # 社会接受度比成本稍微重要
}
ahp.build_from_pairs(pairs)
weights = ahp.compute_weights()
consistency = ahp.compute_consistency()
print("准则权重:")
for c, w in ahp.get_weights_dict().items():
print(f" {c}: {w:.3f}")
print(f"\n一致性比率: {consistency['CR']:.3f}")
print(f"一致性{'合格' if consistency['consistent'] else '不合格'}")
return ahp
if __name__ == "__main__":
example_ahp()
```
### TOPSIS方法
TOPSIS通过计算与理想解的距离进行排序:
```python
import numpy as np
from typing import List, Dict, Callable, Optional
class TOPSISAnalyzer:
"""
TOPSIS (逼近理想解排序法) 实现
核心思想:选择距离正理想解最近、负理想解最远的方案
"""
def __init__(self,
criteria: List[str],
directions: Optional[List[str]] = None):
"""
Args:
criteria: 准则列表
directions: 每个准则的方向,'benefit'(效益型)或'cost'(成本型)
"""
self.criteria = criteria
self.directions = directions or ['benefit'] * len(criteria)
self.weights = None
self.normalized_matrix = None
self.ideal_positive = None
self.ideal_negative = None
self.scores = None
def set_weights(self, weights: np.ndarray):
"""设置权重"""
if len(weights) != len(self.criteria):
raise ValueError("权重数量与准则数量不匹配")
self.weights = np.array(weights) / np.sum(weights)
def normalize(self, decision_matrix: np.ndarray) -> np.ndarray:
"""
向量标准化
Args:
decision_matrix: m×n矩阵,m个方案,n个准则
Returns:
标准化后的矩阵
"""
# 向量标准化:每个列向量除以其范数
norm = np.sqrt((decision_matrix ** 2).sum(axis=0))
normalized = decision_matrix / norm
# 处理除零
normalized = np.nan_to_num(normalized)
self.normalized_matrix = normalized
return normalized
def compute_ideal_solutions(self, weighted_matrix: np.ndarray):
"""
计算正理想解和负理想解
Args:
weighted_matrix: 加权标准化决策矩阵
"""
n = weighted_matrix.shape[1]
ideal_positive = np.zeros(n)
ideal_negative = np.zeros(n)
for j in range(n):
if self.directions[j] == 'benefit':
ideal_positive[j] = weighted_matrix[:, j].max()
ideal_negative[j] = weighted_matrix[:, j].min()
else: # cost
ideal_positive[j] = weighted_matrix[:, j].min()
ideal_negative[j] = weighted_matrix[:, j].max()
self.ideal_positive = ideal_positive
self.ideal_negative = ideal_negative
return ideal_positive, ideal_negative
def compute_scores(self, decision_matrix: np.ndarray) -> np.ndarray:
"""
计算TOPSIS得分
Args:
decision_matrix: m×n决策矩阵
Returns:
得分向量 (0-1之间,越大越好)
"""
# 标准化
normalized = self.normalize(decision_matrix)
# 加权
if self.weights is None:
self.weights = np.ones(len(self.criteria)) / len(self.criteria)
weighted = normalized * self.weights
# 计算理想解
self.compute_ideal_solutions(weighted)
# 计算距离
m = weighted.shape[0]
d_positive = np.zeros(m)
d_negative = np.zeros(m)
for i in range(m):
d_positive[i] = np.sqrt(
((weighted[i] - self.ideal_positive) ** 2).sum()
)
d_negative[i] = np.sqrt(
((weighted[i] - self.ideal_negative) ** 2).sum()
)
# 计算相对贴近度
scores = d_negative / (d_positive + d_negative)
scores = np.nan_to_num(scores)
self.scores = scores
return scores
def rank(self, decision_matrix: np.ndarray) -> List[int]:
"""
返回方案排名
Returns:
排名索引列表(从最优到最差)
"""
scores = self.compute_scores(decision_matrix)
return np.argsort(-scores).tolist()
# 使用示例
def example_topsis():
"""TOPSIS使用示例:生态修复区域选择"""
# 准则
criteria = ['生境适宜性', '连通性指数', '实施成本', '社会效益']
directions = ['benefit', 'benefit', 'cost', 'benefit']
# 权重
weights = [0.35, 0.25, 0.20, 0.20]
# 决策矩阵:5个候选区域在4个准则下的得分
# 注意:成本已经是负向的(数值越小越好)
decision_matrix = np.array([
[0.85, 0.72, 500, 0.65], # 区域A
[0.78, 0.85, 450, 0.70], # 区域B
[0.92, 0.68, 600, 0.55], # 区域C
[0.70, 0.90, 400, 0.80], # 区域D
[0.88, 0.75, 550, 0.60], # 区域E
])
# 对成本型准则进行预处理(转为效益型)
# 成本越高,得分越低
cost_col = decision_matrix[:, 2].copy()
decision_matrix[:, 2] = cost_col.max() - cost_col
topsis = TOPSISAnalyzer(criteria, directions)
topsis.set_weights(weights)
scores = topsis.compute_scores(decision_matrix)
ranking = topsis.rank(decision_matrix)
print("区域排名 (TOPSIS):")
region_names = ['A', 'B', 'C', 'D', 'E']
for rank, idx in enumerate(ranking, 1):
print(f"{rank}名: 区域{region_names[idx]} (得分: {scores[idx]:.3f})")
return topsis
if __name__ == "__main__":
example_topsis()
```
### 敏感性分析
敏感性分析是MCDA的重要组成部分,评估权重变化对决策结果的影响:
```python
import numpy as np
from typing import List, Dict, Tuple, Callable
import matplotlib.pyplot as plt
class SensitivityAnalyzer:
"""
权重敏感性分析
评估权重变化对决策结果的影响程度
"""
def __init__(self,
decision_matrix: np.ndarray,
weights: np.ndarray,
criteria: List[str],
directions: List[str] = None):
"""
Args:
decision_matrix: 决策矩阵
weights: 初始权重
criteria: 准则名称
directions: 准则方向
"""
self.decision_matrix = decision_matrix
self.base_weights = np.array(weights) / np.sum(weights)
self.criteria = criteria
self.directions = directions or ['benefit'] * len(criteria)
def one_at_a_time(self, variation: float = 0.1) -> Dict[str, Dict]:
"""
单因素敏感性分析 (OAT)
每次改变一个准则的权重,观察排名变化
Args:
variation: 权重变化幅度(相对于原权重的比例)
Returns:
每个准则变化时的排名变化
"""
n = len(self.criteria)
base_ranking = self._compute_ranking(self.base_weights)
results = {}
for i, criterion in enumerate(self.criteria):
results[criterion] = {
'weight_variations': [],
'rankings': []
}
# 增加权重
for delta in np.linspace(-variation, variation, 21):
new_weights = self.base_weights.copy()
# 调整第i个权重
new_weights[i] = self.base_weights[i] * (1 + delta)
# 重新归一化(保持和为1
if new_weights.sum() > 0:
new_weights = new_weights / new_weights.sum()
else:
new_weights = self.base_weights.copy()
# 计算新排名
ranking = self._compute_ranking(new_weights)
results[criterion]['weight_variations'].append(delta)
results[criterion]['rankings'].append(ranking)
return results
def tornado_analysis(self, variation: float = 0.2) -> Dict:
"""
龙卷风图分析
计算每个准则权重变化对最优方案得分的影响
Args:
variation: 权重变化幅度
Returns:
龙卷风图数据
"""
from topsis import TOPSISAnalyzer
base_scores = self._compute_scores(self.base_weights)
base_best_score = base_scores.max()
impacts = []
for i, criterion in enumerate(self.criteria):
impact_high = 0
impact_low = 0
# 增加权重
for sign in [1, -1]:
new_weights = self.base_weights.copy()
new_weights[i] = self.base_weights[i] * (1 + sign * variation)
new_weights = new_weights / new_weights.sum()
new_scores = self._compute_scores(new_weights)
new_best = new_scores.max()
change = new_best - base_best_score
if sign > 0:
impact_high = change
else:
impact_low = change
impacts.append({
'criterion': criterion,
'high': impact_high,
'low': impact_low,
'range': impact_high - impact_low
})
# 按影响范围排序
impacts.sort(key=lambda x: abs(x['range']), reverse=True)
return {
'base_score': base_best_score,
'impacts': impacts
}
def monte_carlo(self, n_simulations: int = 1000,
weight_std: float = 0.1) -> Dict:
"""
蒙特卡洛敏感性分析
随机扰动权重,观察结果的统计分布
Args:
n_simulations: 模拟次数
weight_std: 权重扰动的标准差
Returns:
统计结果
"""
n_alternatives = self.decision_matrix.shape[0]
rank_counts = np.zeros(n_alternatives, dtype=int)
scores_history = []
for _ in range(n_simulations):
# 生成随机权重
random_weights = np.random.normal(
self.base_weights,
weight_std
)
# 确保非负并归一化
random_weights = np.maximum(random_weights, 0)
random_weights = random_weights / random_weights.sum()
# 计算排名
ranking = self._compute_ranking(random_weights)
rank_counts[ranking[0]] += 1 # 统计第一名
# 记录得分
scores = self._compute_scores(random_weights)
scores_history.append(scores)
# 计算每个方案排第一的概率
probabilities = rank_counts / n_simulations
# 得分的统计量
scores_array = np.array(scores_history)
scores_stats = {
'mean': scores_array.mean(axis=0),
'std': scores_array.std(axis=0),
'min': scores_array.min(axis=0),
'max': scores_array.max(axis=0),
'percentile_5': np.percentile(scores_array, 5, axis=0),
'percentile_95': np.percentile(scores_array, 95, axis=0)
}
return {
'rank_probabilities': probabilities,
'scores_stats': scores_stats
}
def _compute_scores(self, weights: np.ndarray) -> np.ndarray:
"""计算给定权重下的得分"""
# 简单加权求和
normalized = self._normalize_matrix(self.decision_matrix)
scores = (normalized * weights).sum(axis=1)
return scores
def _compute_ranking(self, weights: np.ndarray) -> List[int]:
"""计算给定权重下的排名"""
scores = self._compute_scores(weights)
return np.argsort(-scores).tolist()
def _normalize_matrix(self, matrix: np.ndarray) -> np.ndarray:
"""标准化决策矩阵"""
normalized = matrix.copy()
for j, direction in enumerate(self.directions):
col = matrix[:, j]
if direction == 'benefit':
normalized[:, j] = (col - col.min()) / (col.max() - col.min())
else: # cost
normalized[:, j] = (col.max() - col) / (col.max() - col.min())
return normalized
# 使用示例
def example_sensitivity():
"""敏感性分析示例"""
# 决策矩阵
decision_matrix = np.array([
[0.85, 0.72, 0.65],
[0.78, 0.85, 0.70],
[0.92, 0.68, 0.55],
[0.70, 0.90, 0.80],
])
weights = [0.4, 0.35, 0.25]
criteria = ['生态价值', '连通性', '可行性']
analyzer = SensitivityAnalyzer(
decision_matrix, weights, criteria,
directions=['benefit', 'benefit', 'benefit']
)
# 蒙特卡洛分析
mc_results = analyzer.monte_carlo(n_simulations=1000, weight_std=0.1)
print("各方案排名第一的概率:")
for i, prob in enumerate(mc_results['rank_probabilities']):
print(f" 方案{i+1}: {prob:.1%}")
# 龙卷风分析
tornado = analyzer.tornado_analysis(variation=0.2)
print("\n准则重要性排序:")
for impact in tornado['impacts']:
print(f" {impact['criterion']}: 影响±{abs(impact['range']):.3f}")
return analyzer
```
---
## 代码示例
### 空间多准则决策分析
```python
"""
空间多准则决策分析完整示例
"""
import numpy as np
import matplotlib.pyplot as plt
from typing import List, Dict, Optional, Callable
from dataclasses import dataclass
@dataclass
class MCDCriteria:
"""决策准则"""
name: str
direction: str # 'benefit' 或 'cost'
weight: float
raster: Optional[np.ndarray] = None # 栅格数据
normalize_func: Optional[Callable] = None # 标准化函数
class SpatialMCDA:
"""
空间多准则决策分析
用于栅格数据的多准则评估
"""
def __init__(self, shape: tuple, nodata: float = -9999):
"""
Args:
shape: 栅格形状 (rows, cols)
nodata: 无数据值
"""
self.shape = shape
self.nodata = nodata
self.criteria: List[MCDCriteria] = []
self.result: Optional[np.ndarray] = None
self.mask: Optional[np.ndarray] = None
def add_criterion(self, name: str, direction: str, weight: float,
raster: Optional[np.ndarray] = None):
"""
添加准则
Args:
name: 准则名称
direction: 'benefit'(越大越好) 或 'cost'(越小越好)
weight: 权重
raster: 栅格数据
"""
criterion = MCDCriteria(
name=name,
direction=direction,
weight=weight,
raster=np.asarray(raster) if raster is not None else None
)
self.criteria.append(criterion)
def set_mask(self, mask: np.ndarray):
"""
设置分析掩模
Args:
mask: True表示有效区域
"""
self.mask = np.asarray(mask, dtype=bool)
def normalize_all(self, method: str = 'minmax') -> List[np.ndarray]:
"""
标准化所有准则
Args:
method: 'minmax', 'zscore', 或 'quantile'
Returns:
标准化后的栅格列表
"""
normalized = []
for criterion in self.criteria:
if criterion.raster is None:
raise ValueError(f"准则 {criterion.name} 没有栅格数据")
data = criterion.raster.copy()
# 处理无数据值
valid_mask = data != self.nodata
if method == 'minmax':
# 最大-最小标准化
valid_data = data[valid_mask]
min_val, max_val = valid_data.min(), valid_data.max()
if criterion.direction == 'benefit':
data[valid_mask] = (valid_data - min_val) / (max_val - min_val)
else: # cost
data[valid_mask] = (max_val - valid_data) / (max_val - min_val)
elif method == 'zscore':
# Z-score标准化
valid_data = data[valid_mask]
mean_val, std_val = valid_data.mean(), valid_data.std()
data[valid_mask] = (valid_data - mean_val) / std_val
# 转换到0-1
data[valid_mask] = (data[valid_mask] - data[valid_mask].min()) / \
(data[valid_mask].max() - data[valid_mask].min())
elif method == 'quantile':
# 分位数转换
valid_data = data[valid_mask]
ranks = np.argsort(np.argsort(valid_data))
data[valid_mask] = ranks / (len(ranks) - 1)
if criterion.direction == 'cost':
data[valid_mask] = 1 - data[valid_mask]
else:
raise ValueError(f"未知方法: {method}")
# 应用掩模
if self.mask is not None:
data[~self.mask] = self.nodata
normalized.append(data)
return normalized
def compute(self, method: str = 'wlc') -> np.ndarray:
"""
计算综合评估结果
Args:
method: 'wlc'(加权线性组合) 或 'geometric'(几何平均)
Returns:
评估结果栅格
"""
# 标准化
normalized = self.normalize_all()
# 归一化权重
weights = np.array([c.weight for c in self.criteria])
weights = weights / weights.sum()
# 初始化结果
result = np.zeros(self.shape)
if method == 'wlc':
# 加权线性组合
for i, norm_data in enumerate(normalized):
valid_mask = norm_data != self.nodata
result[valid_mask] += norm_data[valid_mask] * weights[i]
elif method == 'geometric':
# 加权几何平均
result.fill(1)
for i, norm_data in enumerate(normalized):
valid_mask = norm_data != self.nodata
result[valid_mask] *= np.power(norm_data[valid_mask], weights[i])
else:
raise ValueError(f"未知方法: {method}")
# 应用掩模
if self.mask is not None:
result[~self.mask] = self.nodata
self.result = result
return result
def get_rankings(self, n_classes: int = 5) -> np.ndarray:
"""
将连续结果分级
Args:
n_classes: 分类数
Returns:
分级结果 (1=最低, n_classes=最高)
"""
if self.result is None:
self.compute()
result = self.result.copy()
valid_mask = result != self.nodata
# 分位数分级
valid_data = result[valid_mask]
thresholds = np.percentile(valid_data,
np.linspace(0, 100, n_classes + 1))
rankings = np.zeros_like(result, dtype=int)
for i in range(n_classes):
mask = (result >= thresholds[i]) & (result < thresholds[i + 1])
rankings[mask] = i + 1
rankings[result >= thresholds[-1]] = n_classes
rankings[~valid_mask] = 0
return rankings
def sensitivity_analysis(self, criterion_name: str,
weight_variation: float = 0.2) -> np.ndarray:
"""
单准则权重敏感性分析
Args:
criterion_name: 要分析的准则名称
weight_variation: 权重变化范围 (±)
Returns:
不同权重下的结果差异
"""
base_result = self.compute()
variations = []
# 找到准则索引
criterion_idx = None
for i, c in enumerate(self.criteria):
if c.name == criterion_name:
criterion_idx = i
break
if criterion_idx is None:
raise ValueError(f"未找到准则: {criterion_name}")
# 变化权重
for delta in np.linspace(-weight_variation, weight_variation, 11):
# 修改权重
original_weight = self.criteria[criterion_idx].weight
self.criteria[criterion_idx].weight = original_weight * (1 + delta)
# 重新计算
new_result = self.compute()
variations.append(new_result.copy())
# 恢复权重
self.criteria[criterion_idx].weight = original_weight
# 计算标准差作为敏感性度量
variations = np.array(variations)
sensitivity = np.std(variations, axis=0)
return sensitivity
# 使用示例
def example_spatial_mcda():
"""空间多准则决策分析示例"""
# 创建示例数据
rows, cols = 100, 100
mcda = SpatialMCDA(shape=(rows, cols))
# 创建掩模(例如:排除研究区外的区域)
mask = np.zeros((rows, cols), dtype=bool)
mask[20:80, 20:80] = True
mcda.set_mask(mask)
# 添加准则
np.random.seed(42)
# 准则1:生境适宜性(效益型,权重0.4)
habitat = np.random.rand(rows, cols) * 0.6 + 0.2
habitat[~mask] = mcda.nodata
mcda.add_criterion('生境适宜性', 'benefit', 0.4, habitat)
# 准则2:连通性(效益型,权重0.3)
connectivity = np.random.rand(rows, cols) * 0.7 + 0.15
connectivity[~mask] = mcda.nodata
mcda.add_criterion('连通性', 'benefit', 0.3, connectivity)
# 准则3:实施成本(成本型,权重0.3)
cost = np.random.rand(rows, cols) * 0.8 + 0.1
cost[~mask] = mcda.nodata
mcda.add_criterion('实施成本', 'cost', 0.3, cost)
# 计算综合评估
result = mcda.compute(method='wlc')
print(f"综合评估结果:")
print(f" 有效区域均值: {result[mask].mean():.3f}")
print(f" 有效区域范围: [{result[mask].min():.3f}, {result[mask].max():.3f}]")
# 分级
rankings = mcda.get_rankings(n_classes=5)
print(f"\n分级统计:")
for i in range(1, 6):
count = (rankings == i).sum()
print(f" 等级{i}: {count} 个像元")
# 敏感性分析
sensitivity = mcda.sensitivity_analysis('生境适宜性', weight_variation=0.3)
print(f"\n敏感性分析 (生境适宜性权重±30%):")
print(f" 结果最大变化: {sensitivity[mask].max():.3f}")
print(f" 结果平均变化: {sensitivity[mask].mean():.3f}")
return mcda
if __name__ == "__main__":
example_spatial_mcda()
```
---
## 案例分析
### ENAgent中的生态系统服务评估
ENAgent使用多准则决策分析评估生态系统的综合服务价值:
```python
class EcosystemServiceAssessment:
"""
生态系统服务评估模块
基于多准则决策分析评估区域生态系统服务价值
"""
# 生态系统服务类型
SERVICE_TYPES = {
'provisioning': '供给服务', # 食物、淡水、木材等
'regulating': '调节服务', # 气候调节、洪水调节等
'cultural': '文化服务', # 游憩、美学价值等
'supporting': '支持服务' # 土壤形成、营养循环等
}
def __init__(self, study_area_boundary):
"""
Args:
study_area_boundary: 研究区边界(Shapely Polygon)
"""
self.boundary = study_area_boundary
self.services = {}
self.weights = {}
self.assessment_result = None
def add_service_layer(self, service_type: str,
value_layer: np.ndarray,
weight: float = 1.0):
"""
添加生态系统服务图层
Args:
service_type: 服务类型
value_layer: 价值评估栅格
weight: 该服务的权重
"""
self.services[service_type] = value_layer
self.weights[service_type] = weight
def assess_habitat_quality(self, land_use_raster,
threat_layers: Dict[str, np.ndarray],
sensitivity_table: Dict) -> np.ndarray:
"""
评估生境质量 (基于InVEST模型思想)
Args:
land_use_raster: 土地利用栅格
threat_layers: 威胁因子图层 {'name': array}
sensitivity_table: 土地类型对威胁的敏感性
Returns:
生境质量指数栅格
"""
rows, cols = land_use_raster.shape
habitat_quality = np.zeros((rows, cols))
# 计算退化程度
degradation = np.zeros((rows, cols))
for threat_name, threat_layer in threat_layers.items():
# 对每个威胁因子计算影响
# 这里简化处理,实际需要考虑距离衰减等
threat_impact = threat_layer * 0.5 # 简化权重
for land_type, sensitivity in sensitivity_table.items():
mask = (land_use_raster == land_type)
# 获取该土地类型对当前威胁的敏感性
sens = sensitivity.get(threat_name, 0.5)
degradation[mask] += threat_impact[mask] * sens
# 退化程度归一化到0-1
degradation = np.clip(degradation, 0, 1)
# 计算生境质量
for land_type in np.unique(land_use_raster):
mask = (land_use_raster == land_type)
# 生境适宜性 (简化: 林地=高, 建设用地=低)
habitat_suitability = self._get_habitat_suitability(land_type)
habitat_quality[mask] = habitat_suitability * (1 - degradation[mask])
return habitat_quality
def assess_carbon_storage(self, land_use_raster,
carbon_table: Dict[int, Dict[str, float]]) -> np.ndarray:
"""
评估碳储量
Args:
land_use_raster: 土地利用栅格
carbon_table: {土地类型: {'above': x, 'below': y, 'soil': z}}
Returns:
碳储量栅格
"""
carbon_storage = np.zeros_like(land_use_raster, dtype=float)
for land_type, carbon_values in carbon_table.items():
mask = (land_use_raster == land_type)
# 总碳储量 = 地上 + 地下 + 土壤
total_carbon = (carbon_values.get('above', 0) +
carbon_values.get('below', 0) +
carbon_values.get('soil', 0))
carbon_storage[mask] = total_carbon
return carbon_storage
def multi_service_assessment(self) -> np.ndarray:
"""
多服务综合评估
Returns:
综合生态系统服务指数
"""
if not self.services:
raise ValueError("请先添加服务图层")
# 归一化权重
total_weight = sum(self.weights.values())
# 初始化结果
shape = next(iter(self.services.values())).shape
result = np.zeros(shape)
# 加权求和
for service_type, layer in self.services.items():
weight = self.weights[service_type] / total_weight
result += layer * weight
self.assessment_result = result
return result
def identify_priority_areas(self, threshold_percentile: float = 0.75) -> np.ndarray:
"""
识别优先保护区域
Args:
threshold_percentile: 分位数阈值
Returns:
优先区域布尔栅格
"""
if self.assessment_result is None:
self.multi_service_assessment()
threshold = np.percentile(
self.assessment_result[self.assessment_result > 0],
threshold_percentile * 100
)
priority_areas = self.assessment_result >= threshold
return priority_areas
def _get_habitat_suitability(self, land_type) -> float:
"""获取土地类型的生境适宜性"""
# 简化版:根据土地类型返回适宜性
suitability_map = {
1: 1.0, # 森林
2: 0.8, # 灌木
3: 0.6, # 草地
4: 0.4, # 湿地
5: 0.2, # 耕地
6: 0.0, # 建设用地
7: 0.3 # 裸地
}
return suitability_map.get(land_type, 0.5)
# 使用示例
def example_enagent_assessment():
"""ENAgent生态系统服务评估示例"""
from shapely.geometry import box
# 创建研究区
study_area = box(0, 0, 10000, 10000)
# 创建评估器
assessor = EcosystemServiceAssessment(study_area)
# 模拟土地利用数据
rows, cols = 100, 100
np.random.seed(42)
land_use = np.random.choice([1, 2, 3, 4, 5, 6], size=(rows, cols), p=[0.3, 0.15, 0.2, 0.1, 0.15, 0.1])
# 评估生境质量
threat_layers = {
'roads': np.random.rand(rows, cols) * 0.8,
'urban': np.random.rand(rows, cols) * 0.6
}
sensitivity = {
1: {'roads': 0.3, 'urban': 0.8}, # 森林对城市扩张敏感
2: {'roads': 0.5, 'urban': 0.6},
3: {'roads': 0.7, 'urban': 0.4},
4: {'roads': 0.8, 'urban': 0.9},
5: {'roads': 0.2, 'urban': 0.1},
6: {'roads': 0.0, 'urban': 0.0}
}
habitat_quality = assessor.assess_habitat_quality(
land_use, threat_layers, sensitivity
)
# 评估碳储量
carbon_table = {
1: {'above': 150, 'below': 40, 'soil': 100},
2: {'above': 60, 'below': 20, 'soil': 80},
3: {'above': 20, 'below': 50, 'soil': 100},
4: {'above': 80, 'below': 100, 'soil': 150},
5: {'above': 10, 'below': 10, 'soil': 80},
6: {'above': 0, 'below': 0, 'soil': 20}
}
carbon_storage = assessor.assess_carbon_storage(land_use, carbon_table)
# 添加服务图层
assessor.add_service_layer('habitat_quality', habitat_quality, weight=0.5)
assessor.add_service_layer('carbon_storage', carbon_storage / 300, weight=0.3)
# 水文调节服务(模拟)
water_regulation = np.random.rand(rows, cols) * 0.5 + 0.3
assessor.add_service_layer('water_regulation', water_regulation, weight=0.2)
# 综合评估
result = assessor.multi_service_assessment()
print("生态系统服务综合评估:")
print(f" 平均服务指数: {result.mean():.3f}")
print(f" 高服务区域 (>0.6): {(result > 0.6).sum()} 个像元")
# 识别优先区域
priority = assessor.identify_priority_areas(threshold_percentile=0.75)
print(f" 优先保护区域: {priority.sum()} 个像元 ({priority.sum()/priority.size*100:.1f}%)")
return assessor
if __name__ == "__main__":
example_enagent_assessment()
```
---
## 反思与延伸
### 思考问题
1. **权重来源**:专家判断、文献参考、数据分析,哪种权重确定方式更可靠?
2. **准则独立性**:当准则之间存在相关性时,MCDA结果会怎样变化?
3. **不确定性**:除了权重,数据本身的不确定性如何在MCDA中考虑?
4. **阈值效应**:某些准则是否存在关键阈值?如何处理?
5. **空间异质性**:不同区域的权重是否应该不同?
### 延伸阅读
- **"Multi-Criteria Decision Analysis: Methods and Software"** - MCDA方法综述
- **"Spatial Decision Support Systems"** - 空间决策支持系统
- InVEST模型文档 - 生态系统服务评估实践
- **"Decision Analysis"** (Howard) - 决策分析基础理论
---
## 关键要点
1. **MCDA处理多目标冲突**:通过系统化方法整合多个准则,支持复杂决策
2. **权重是核心**:权重的确定是MCDA的关键,需要专家知识或数据支持
3. **标准化必不可少**:不同量纲的准则必须标准化才能比较和合并
4. **敏感性分析很重要**:评估权重变化对结果的影响,增强决策稳健性
5. **与GIS结合才有意义**:空间MCDA将决策框架落实到地理空间