# 01.3 概率与不确定性

## 核心问题

> 空间分析中的不确定性从何而来？
> AI系统如何表示和处理不确定性？
> 如何在不确定性下做出稳健的决策？

---

## 概念讲解

### 不确定性的来源

在空间分析和AI系统中，不确定性无处不在：

```
空间分析中的不确定性来源

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  1. 数据不确定性         │
│     - 测量误差                                          │
│     - 空间采样不完整                                      │
│     - 分类错误                                            │
│     - 时间延迟                                            │
│                                                             │
│  2. 参数不确定性       │
│     - 阻力权重不确定                                        │
│     - 阈值选择主观                                          │
│     - 模型参数拟合误差                                      │
│                                                             │
│  3. 结构不确定性       │
│     - 模型选择                                            │
│     - 变量关系假设                                          │
│     - 尺度效应                                              │
│                                                             │
│  4. 语义不确定性         │
│     - 概念模糊（"生态质量"是什么？）                          │
│     - 分类边界不清                                          │
│     - 专家意见分歧                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### 不确定性的类型

| 类型 | 说明 | 例子 |
|-----|------|------|
| **偶然不确定性** (Aleatoric) | 系统固有的随机性，无法通过更多数据消除 | 降雨量的随机波动 |
| **认知不确定性** (Epistemic) | 知识不足导致的不确定性，可通过更多数据减少 | 未调查区域的物种分布 |
| **模糊性** (Ambiguity) | 概念或分类的不明确 | "高生态价值"的定义 |
| **冲突** (Conflict) | 不同信息源的不一致 | 两个专家给出相反意见 |

### AI如何处理不确定性

**传统GIS vs 概率AI**：

```
传统GIS: 确定性输出
    输入 → [处理] → 单一结果
    例如：这个区域是/不是生态源地

概率AI: 概率输出
    输入 → [处理] → (结果, 置信度)
    例如：这个区域是生态源地的概率是 0.78 ± 0.12
```

**置信度的表示**：

```python
# 方式1: 点估计 + 置信区间
estimate = 0.75
confidence_interval = (0.65, 0.85)

# 方式2: 概率分布
from scipy.stats import beta
distribution = beta(a=8, b=3)  # 基于共8次成功，3次失败

# 方式3: 分类概率
class_probabilities = {
    "high_suitability": 0.65,
    "medium_suitability": 0.25,
    "low_suitability": 0.10
}

# 方式4: 模糊隶属度
fuzzy_membership = {
    "is_source": 0.72,
    "is_not_source": 0.28
}
```

---

## 设计原理

### 不确定性传播

当多个步骤串联时，不确定性会累积：

```python
"""
不确定性传播示例
"""
import numpy as np
from scipy.stats import norm

class UncertainValue:
    """带不确定性的值"""

    def __init__(self, mean, std):
        self.mean = mean
        self.std = std

    def __add__(self, other):
        """加法：方差相加"""
        return UncertainValue(
            self.mean + other.mean,
            np.sqrt(self.std**2 + other.std**2)
        )

    def __mul__(self, scalar):
        """乘以标量：标准差也乘"""
        return UncertainValue(
            self.mean * scalar,
            self.std * abs(scalar)
        )

    def __repr__(self):
        return f"{self.mean:.2f} ± {self.std:.2f}"

# 示例：源地适宜性评估中的不确定性传播
def assess_suitability_with_uncertainty():
    """
    每个指标都有测量不确定性，
    最终的适宜性评分会累积这些不确定性
    """

    # 各项指标（均值 ± 标准差）
    vegetation_quality = UncertainValue(0.75, 0.10)
    connectivity = UncertainValue(0.60, 0.15)
    distance_to_threat = UncertainValue(0.80, 0.08)

    # 加权组合（权重也有不确定性）
    weights = {
        "vegetation": 0.4,
        "connectivity": 0.3,
        "distance": 0.3
    }

    # 计算总分（简化传播）
    total = (vegetation_quality * weights["vegetation"] +
             connectivity * weights["connectivity"] +
             distance_to_threat * weights["distance"])

    print("各指标不确定性:")
    print(f"  植被质量: {vegetation_quality}")
    print(f"  连通性: {connectivity}")
    print(f"  威胁距离: {distance_to_threat}")
    print(f"\n总分: {total}")
    print(f"  置信区间95%: [{total.mean - 1.96*total.std:.2f}, "
          f"{total.mean + 1.96*total.std:.2f}]")

    return total

if __name__ == "__main__":
    assess_suitability_with_uncertainty()
```

### 敏感性分析

了解哪些参数对结果影响最大：

```python
"""
敏感性分析：识别关键参数
"""
import numpy as np
from typing import Dict, List, Tuple

def sensitivity_analysis(model_fn, param_ranges: Dict[str, Tuple[float, float]],
                         n_samples=1000) -> Dict[str, float]:
    """
    使用蒙特卡洛方法进行敏感性分析

    Args:
        model_fn: 模型函数，接受参数字典，返回结果
        param_ranges: 参数范围 {param_name: (min, max)}
        n_samples: 采样次数

    Returns:
        各参数的敏感性系数
    """
    results = {param: [] for param in param_ranges}
    model_outputs = []

    # 蒙特卡洛采样
    for _ in range(n_samples):
        # 随机采样参数
        sample = {k: np.random.uniform(v[0], v[1])
                  for k, v in param_ranges.items()}

        # 记录参数值
        for param, value in sample.items():
            results[param].append(value)

        # 计算模型输出
        output = model_fn(sample)
        model_outputs.append(output)

    # 计算相关性作为敏感性指标
    sensitivities = {}
    for param in param_ranges:
        correlation = np.corrcoef(results[param], model_outputs)[0, 1]
        sensitivities[param] = abs(correlation)

    return sensitivities

# 示例：生态阻力面构建的敏感性分析
def resistance_model(params):
    """简化的阻力面模型"""
    # 参数：各土地类型的阻力权重
    forest_weight = params["forest"]
    grass_weight = params["grass"]
    urban_weight = params["urban"]

    # 简化：计算平均阻力
    # 实际应用中会是空间计算
    landscape_composition = {
        "forest": 0.4,
        "grass": 0.3,
        "urban": 0.3
    }

    total_resistance = (
        forest_weight * landscape_composition["forest"] +
        grass_weight * landscape_composition["grass"] +
        urban_weight * landscape_composition["urban"]
    )

    return total_resistance

def run_sensitivity_example():
    """运行敏感性分析示例"""
    print("=== 阻力面参数敏感性分析 ===\n")

    # 定义参数范围
    param_ranges = {
        "forest": (1, 10),
        "grass": (10, 50),
        "urban": (50, 200)
    }

    # 运行敏感性分析
    sensitivities = sensitivity_analysis(
        resistance_model,
        param_ranges,
        n_samples=5000
    )

    # 排序并输出
    sorted_sens = sorted(sensitivities.items(),
                         key=lambda x: x[1], reverse=True)

    print("参数敏感性排序:")
    for param, sensitivity in sorted_sens:
        bar = "█" * int(sensitivity * 30)
        print(f"  {param}: {sensitivity:.3f} {bar}")

    print("\n解释:")
    print(f"  最敏感的参数是 {sorted_sens[0][0]}")
    print(f"  应当优先精确确定该参数的值")

if __name__ == "__main__":
    run_sensitivity_example()
```

### 鲁棒决策

当不确定性无法消除时，做鲁棒的决策：

```python
"""
鲁棒决策：在不确定性下的稳健决策
"""
from typing import List, Callable
import numpy as np

def robust_decision_scenarios():
    """
    鲁棒决策的几种策略
    """

    # 策略1: 最大最小 (Maximin) - 最坏情况最优
    def maximin(payoff_matrix):
        """
        选择在最坏情况下表现最好的选项

        payoff_matrix: 选项 × 场景 的收益矩阵
        """
        worst_case_outcomes = payoff_matrix.min(axis=1)
        best_option = worst_case_outcomes.argmax()
        return best_option, worst_case_outcomes

    # 策略2: 最大平均 (Maximum Expected Value)
    def max_expected(payoff_matrix, probabilities=None):
        """选择期望收益最大的选项"""
        if probabilities is None:
            probabilities = np.ones(payoff_matrix.shape[1]) / payoff_matrix.shape[1]

        expected_values = payoff_matrix @ probabilities
        best_option = expected_values.argmax()
        return best_option, expected_values

    # 策略3: 最小后悔 (Minimax Regret)
    def minimax_regret(payoff_matrix):
        """选择最小化最大后悔的选项"""
        # 每个场景的最佳收益
        best_per_scenario = payoff_matrix.max(axis=0)

        # 后悔矩阵：每个选项在每个场景与最佳的差距
        regret_matrix = best_per_scenario - payoff_matrix

        # 每个选项的最大后悔
        max_regret = regret_matrix.max(axis=1)

        # 选择最大后悔最小的选项
        best_option = max_regret.argmin()
        return best_option, max_regret

    # 示例：生态廊道选址决策
    # 选项：3个候选廊道路线
    # 场景：不同的未来土地变化情景
    payoff_matrix = np.array([
        # 情景1  情景2  情景3  情景4
        [80,    60,    40,    70],   # 选项A：穿过森林
        [50,    90,    70,    50],   # 选项B：沿河流
        [60,    70,    90,    60],   # 选项C：绕行城市边缘
    ])

    print("=== 生态廊道选址：鲁棒决策分析 ===\n")
    print("收益矩阵（廊道质量评分）:")
    print("         情景1  情景2  情景3  情景4")
    for i, row in enumerate(payoff_matrix, ord('A')):
        print(f"选项{i}:   {row}")

    print("\n--- 策略1: 最大最小 (最坏情况最优) ---")
    option, worst = maximin(payoff_matrix)
    print(f"推荐: 选项{chr(ord('A') + option)}")
    print(f"各选项最坏情况: {worst}")
    print("  原理: 选择在最坏情景下表现最好的")

    print("\n--- 策略2: 最大期望 (平均收益最大) ---")
    option, expected = max_expected(payoff_matrix)
    print(f"推荐: 选项{chr(ord('A') + option)}")
    print(f"各选项期望收益: {expected}")
    print("  原理: 选择平均表现最好的")

    print("\n--- 策略3: 最小最大后悔 ---")
    option, regret = minimax_regret(payoff_matrix)
    print(f"推荐: 选项{chr(ord('A') + option)}")
    print(f"各选项最大后悔: {regret}")
    print("  原理: 选择让'选错'的后悔最小的")

    return payoff_matrix

if __name__ == "__main__":
    robust_decision_scenarios()
```

---

## 代码示例

### 概率源地识别

```python
"""
带不确定性的生态源地识别
"""
import numpy as np
from scipy.stats import beta
from typing import Dict, List, Tuple

class ProbabilisticSource:
    """概率源地：带置信度的源地"""

    def __init__(self, source_id: int, geometry,
                 probability: float, confidence: float):
        self.id = source_id
        self.geometry = geometry
        self.probability = probability  # 是源地的概率
        self.confidence = confidence    # 概率估计的置信度

    def __repr__(self):
        return (f"Source({self.id}, P={self.probability:.2f}±{self.confidence:.2f})")

class ProbabilisticSourceIdentifier:
    """概率源地识别器"""

    def __init__(self, threshold=0.5):
        self.threshold = threshold

    def identify(self, landscape_data) -> List[ProbabilisticSource]:
        """
        识别源地，返回概率源地

        返回的不是"是/否"的判断，而是"是源地的概率"
        """
        sources = []

        # 模拟：对每个斑块计算是源地的概率
        for i, patch in enumerate(landscape_data):
            # 基于多个指标计算概率
            probability = self._calculate_source_probability(patch)

            # 估计置信度（基于数据质量）
            confidence = self._estimate_confidence(patch)

            if probability >= self.threshold:
                source = ProbabilisticSource(
                    source_id=i,
                    geometry=patch['geometry'],
                    probability=probability,
                    confidence=confidence
                )
                sources.append(source)

        return sources

    def _calculate_source_probability(self, patch) -> float:
        """计算斑块是源地的概率"""
        # 使用贝叶斯推理
        # P(是源地|数据) ∝ P(数据|是源地) × P(是源地)

        # 各指标的似然
        area_likelihood = self._area_likelihood(patch['area'])
        veg_likelihood = self._vegetation_likelihood(patch['vegetation'])
        shape_likelihood = self._shape_likelihood(patch['shape_index'])

        # 先验概率
        prior = 0.3  # 假设30%的斑块可能是源地

        # 后验概率（简化）
        probability = (area_likelihood * veg_likelihood *
                      shape_likelihood * prior)
        probability = min(probability, 1.0)  # 限制在[0,1]

        return probability

    def _area_likelihood(self, area: float) -> float:
        """面积似然：大面积更像源地"""
        if area > 1000:
            return 1.0
        elif area > 500:
            return 0.7
        else:
            return 0.3

    def _vegetation_likelihood(self, veg_quality: float) -> float:
        """植被质量似然"""
        return veg_quality  # 假设已归一化到[0,1]

    def _shape_likelihood(self, shape_index: float) -> float:
        """形状指数似然：紧凑形状更好"""
        return 1.0 - min(abs(shape_index - 1.0), 0.5)

    def _estimate_confidence(self, patch) -> float:
        """估计概率的置信度"""
        # 基于数据质量、分辨率等因素
        data_quality = patch.get('data_quality', 0.8)
        resolution_factor = patch.get('resolution', 30) / 30  # 归一化

        return data_quality * min(resolution_factor, 1.0)

def uncertainty_propagation_example():
    """不确定性传播示例"""
    print("=== 不确定性传播示例 ===\n")

    # 创建一些模拟斑块
    patches = [
        {'id': 1, 'area': 1200, 'vegetation': 0.85, 'shape_index': 1.2,
         'geometry': 'POLYGON(...)', 'data_quality': 0.9},
        {'id': 2, 'area': 800, 'vegetation': 0.75, 'shape_index': 1.5,
         'geometry': 'POLYGON(...)', 'data_quality': 0.7},
        {'id': 3, 'area': 400, 'vegetation': 0.65, 'shape_index': 1.8,
         'geometry': 'POLYGON(...)', 'data_quality': 0.6},
    ]

    identifier = ProbabilisticSourceIdentifier(threshold=0.4)
    sources = identifier.identify(patches)

    print("识别到的概率源地:")
    for source in sources:
        print(f"  {source}")

    # 计算整体不确定性
    if sources:
        avg_prob = np.mean([s.probability for s in sources])
        avg_conf = np.mean([s.confidence for s in sources])
        print(f"\n总体置信度: {avg_conf:.2f}")
        print(f"平均源地概率: {avg_prob:.2f}")

        # 置信区间
        margin_of_error = (1 - avg_conf) * 0.2  # 简化计算
        print(f"源地数量估计: {len(sources)} ± {margin_of_error * len(sources):.1f}")

if __name__ == "__main__":
    uncertainty_propagation_example()
```

---

## 案例分析

### ENAgent中的不确定性处理

在ENAgent项目中，不确定性处理体现在：

**1. 源地识别的不确定性**

```python
class ENAgentSourceIdentifier:
    """ENAgent的源地识别模块"""

    def identify_with_uncertainty(self, landcover, species_params):
        """
        识别源地，同时估计不确定性

        Returns:
            sources: 源地列表
            uncertainty_map: 不确定性空间分布
        """
        sources = []
        uncertainty_map = np.zeros_like(landcover)

        # 对每个候选斑块
        for patch in self._candidate_patches(landcover):
            # 计算适宜性（考虑物种参数）
            suitability = self._calculate_suitability(patch, species_params)

            # 估计不确定性（来自多个来源）
            uncertainty = self._estimate_uncertainty(
                patch,
                data_quality=landcover.metadata['quality'],
                species_uncertainty=species_params['uncertainty']
            )

            # 记录
            if suitability > self.threshold:
                sources.append({
                    'geometry': patch,
                    'suitability': suitability,
                    'uncertainty': uncertainty
                })

            # 更新不确定性地图
            self._add_to_uncertainty_map(uncertainty_map, patch, uncertainty)

        return sources, uncertainty_map

    def _estimate_uncertainty(self, patch, data_quality, species_uncertainty):
        """
        估计源地识别的不确定性

        来源：
        1. 数据质量 (data_quality)
        2. 物种参数的不确定性 (species_uncertainty)
        3. 分类误差 (classification_error)
        4. 边界效应 (edge_effect)
        """
        # 组合各种不确定性源
        uncertainty = np.sqrt(
            (1 - data_quality)**2 +
            species_uncertainty**2 +
            0.1**2 +  # 分类误差
            self._edge_uncertainty(patch)**2
        )

        return min(uncertainty, 1.0)
```

**2. 阻力面的敏感性分析**

```python
class ResistanceSurfaceSensitivity:
    """阻力面敏感性分析"""

    def analyze(self, base_weights, variation_ranges, n_simulations=1000):
        """
        分析阻力面权重对结果的敏感性

        Args:
            base_weights: 基础权重 {land_type: weight}
            variation_ranges: 权重变化范围 {land_type: (min, max)}
            n_simulations: 蒙特卡洛模拟次数

        Returns:
            sensitivity_results: 敏感性分析结果
        """
        results = []

        for _ in range(n_simulations):
            # 随机采样权重
            sample_weights = {}
            for land_type, (min_w, max_w) in variation_ranges.items():
                sample_weights[land_type] = np.random.uniform(min_w, max_w)

            # 计算对应的阻力面
            resistance = self._compute_resistance(sample_weights)

            # 评估结果（例如：平均连通性）
            connectivity = self._assess_connectivity(resistance)

            results.append({
                'weights': sample_weights,
                'connectivity': connectivity
            })

        # 分析敏感性
        sensitivity = self._compute_sensitivity(results, base_weights)

        return sensitivity

    def _compute_sensitivity(self, results, base_weights):
        """计算各土地类型的敏感性"""
        # 计算每个权重变化与连通性变化的相关性
        sensitivities = {}

        for land_type in base_weights:
            weight_values = [r['weights'][land_type] for r in results]
            connectivity_values = [r['connectivity'] for r in results]

            correlation = np.corrcoef(weight_values, connectivity_values)[0, 1]
            sensitivities[land_type] = abs(correlation)

        return sensitivities
```

---

## 反思与延伸

### 思考问题

1. **不确定性识别**：在你的项目中，不确定性来自哪些方面？哪些是可减少的，哪些是固有的？

2. **表示选择**：你应该用标准差、置信区间，还是概率分布？各有什么优劣？

3. **决策权衡**：当不确定性很高时，你应该继续分析还是寻求更多数据？

4. **沟通问题**：如何向非专家解释不确定性？

### 实践练习

1. **不确定性审计**：对一个分析流程，识别所有不确定性来源并分类

2. **敏感性分析**：对你熟悉的空间模型进行敏感性分析

3. **鲁棒决策**：为你的项目设计一个鲁棒决策框架

### 延伸阅读

- **"Uncertainty Quantification in Predictive Modeling"** - 不确定性量化的理论基础
- **"Flaw of Averages"** (Sam Savage) - 为什么平均值会误导
- **空间数据质量标准** - 空间不确定性的行业实践

---

## 关键要点

1. **不确定性在空间分析中普遍存在**，有多个来源
2. **偶然不确定性无法消除**，认知不确定性可以通过更多数据减少
3. **不确定性会传播**，多步骤分析需要考虑累积效应
4. **敏感性分析识别关键参数**，优先减少高敏感参数的不确定性
5. **鲁棒决策在不确定性下做稳健选择**，而非追求最优
