ベイズネットワークとその推論に関する実践的な解説

### ベイズネットワークとその推論に関する実践的な解説
以下のURLを参考に書きました。

https://youtu.be/5NgNicANyqM?si=Qc3Dzaw6gf3x5qmd

ベイズネットワークは、確率的な関係を視覚化し、未知の情報を推測するための強力なツールです。この記事では、ベイズネットワークを用いた推論の具体的な方法や、Pythonライブラリを活用した実装例を紹介します。

---

### **1. ベイズネットワークの構築とその役割**

#### **1.1 ベイズネットワークの基本構造**
ベイズネットワークは、以下の要素で構成される有向グラフです：
- **ノード**：ランダム変数（例：天候、電車の遅延、予定の出席）。
- **エッジ**：変数間の依存関係を表現。

各ノードには確率分布が割り当てられ、エッジは親ノードが子ノードに与える影響を示します。

---

### **2. Pythonによるベイズネットワークの構築**

#### **2.1 ライブラリの選択**
Pythonには多くのベイズネットワーク用ライブラリが存在しますが、ここでは**pomegranate**を使用します。このライブラリは、ベイズネットワークの定義や推論を簡単に実現できます。

#### **2.2 ノードの定義**
以下は「雨 (Rain)」「保守作業 (Maintenance)」「電車 (Train)」「予定 (Appointment)」を表すノードの例です：
```python
from pomegranate import *

# 雨のノード
rain = DiscreteDistribution({'None': 0.7, 'Light': 0.2, 'Heavy': 0.1})

# 保守作業のノード（雨に依存）
maintenance = ConditionalProbabilityTable(
    [['None', 'Yes', 0.4],
     ['None', 'No', 0.6],
     ['Light', 'Yes', 0.3],
     ['Light', 'No', 0.7],
     ['Heavy', 'Yes', 0.1],
     ['Heavy', 'No', 0.9]], [rain])

# 電車の遅延のノード（雨と保守作業に依存）
train = ConditionalProbabilityTable(
    [['None', 'No', 'OnTime', 0.8],
     ['None', 'No', 'Delayed', 0.2],
     ['Light', 'Yes', 'OnTime', 0.6],
     ['Light', 'Yes', 'Delayed', 0.4],
     ['Heavy', 'Yes', 'OnTime', 0.4],
     ['Heavy', 'Yes', 'Delayed', 0.6]], [rain, maintenance])

# 予定のノード（電車に依存）
appointment = ConditionalProbabilityTable(
    [['OnTime', 'Attend', 0.9],
     ['OnTime', 'Miss', 0.1],
     ['Delayed', 'Attend', 0.4],
     ['Delayed', 'Miss', 0.6]], [train])

# ノードをネットワークに追加
s1 = State(rain, name="Rain")
s2 = State(maintenance, name="Maintenance")
s3 = State(train, name="Train")
s4 = State(appointment, name="Appointment")

network = BayesianNetwork("Train Delay Example")
network.add_states(s1, s2, s3, s4)
network.add_edge(s1, s2)
network.add_edge(s1, s3)
network.add_edge(s2, s3)
network.add_edge(s3, s4)
network.bake()
```

---

### **3. 推論の実行**

#### **3.1 確率の計算**
以下のシナリオを考えます：
- 雨なし、保守作業なし、電車が時間通り、予定に出席。

この確率を計算します：
```python
# 指定された条件下での確率を計算
probability = network.probability(['None', 'No', 'OnTime', 'Attend'])
print(f"全てがうまくいく場合の確率: {probability}")
```

出力例：
```
全てがうまくいく場合の確率: 0.34
```
この結果は、「全てが順調に進む確率が34%である」ことを示します。

---

### **4. 条件付き推論**

#### **4.1 条件付き推論の概要**
条件付き推論では、ある事象が既知である場合に他の変数の確率を推測します。例えば、「電車が遅延した場合に雨の状態を推定する」といった問いに答えることができます。

#### **4.2 実装例**
```python
# 電車が遅延している場合の他の変数の分布を推定
predictions = network.predict_proba({'Train': 'Delayed'})
for node, prediction in zip(network.states, predictions):
    print(f"{node.name}: {prediction}")
```

出力例：
```
Rain: {'None': 0.45, 'Light': 0.31, 'Heavy': 0.23}
Maintenance: {'Yes': 0.64, 'No': 0.36}
Appointment: {'Attend': 0.6, 'Miss': 0.4}
```

この結果から、電車が遅延している場合、以下のことがわかります：
- 雨が降っていない確率：45%
- 保守作業がある確率：64%
- 予定に出席する確率：60%

---

### **5. サンプリングによる推論**

#### **5.1 サンプリングの利点**
サンプリングは、精度を若干犠牲にしても高速に推論を行いたい場合に適しています。特に大規模なネットワークや高次元の変数において有用です。

#### **5.2 サンプリングの実装例**
```python
from collections import Counter

# サンプリング関数の定義
def generate_sample(network, n_samples=10000):
    samples = []
    for _ in range(n_samples):
        sample = network.sample()
        samples.append(sample)
    return samples

# サンプリングによる条件付き確率の推定
samples = generate_sample(network)
filtered_samples = [s for s in samples if s[2] == 'Delayed']  # 電車が遅延しているケースのみ
counter = Counter([s[3] for s in filtered_samples])  # 予定の状態をカウント
total = sum(counter.values())
prob_attend = counter['Attend'] / total
prob_miss = counter['Miss'] / total

print(f"予定に出席する確率: {prob_attend}")
print(f"予定に欠席する確率: {prob_miss}")
```

---

### **6. まとめ**

ベイズネットワークは、複数の事象間の依存関係をモデル化し、未知の事象の確率を推定する強力なフレームワークです。Pythonライブラリを活用することで、その複雑な計算を簡単に実現できます。この記事で紹介した手法を基に、より複雑なネットワークや実用的なシナリオにも挑戦してみてください。