制約ベース手法との違い と 関数型因果モデル(FCM)
次のコード例では、
- データ生成時に依存関係を強めに設定しているので、因果関係が検出されやすいようにしています。
- bn.structure_learning.fit
で direct-lingam を使って構造学習を行います。
- 学習結果の隣接行列から、エッジが (from,
to) の形で存在するか(値が 0 以外の場合)をチェックしてエッジリストを作成します。
- 各ノードに対して、その親ノードからの影響(回帰係数)をOLS回帰で推定し、エッジごとのスコアとして出力します。
以下のコードをご確認ください。
python
import
numpy as np
import
pandas as pd
import
bnlearn as bn
import
statsmodels.api as sm
#
① データ生成(依存関係を強化)
np.random.seed(42)
n_samples
= 300
temperature
= np.random.uniform(20, 40, n_samples)
ice_cream_sales
= 5 * temperature + np.random.normal(0, 1, n_samples)
swimming_accidents
= 2 * temperature + np.random.normal(0, 1, n_samples)
data
= pd.DataFrame({
'Temperature': temperature,
'IceCreamSales': ice_cream_sales,
'SwimmingAccidents': swimming_accidents
})
#
② bnlearnによる構造学習(direct-lingam を利用)
model
= bn.structure_learning.fit(data, methodtype='direct-lingam')
#
model['adjmat'] は学習された隣接行列。DirectLiNGAMオブジェクトは NetworkX グラフではないため、自前でエッジリストを作成する
adjmat
= model['adjmat']
print("===
学習された隣接行列 ===")
print(adjmat)
#
③ 隣接行列からエッジリストを生成(値が 0 以外なら edge とみなす)
edges
= []
for
from_node in adjmat.index:
for to_node in adjmat.columns:
if adjmat.loc[from_node, to_node] != 0:
edges.append((from_node, to_node))
print("\n===
学習されたエッジリスト ===")
print(edges)
#
④ 各エッジに対して、親から子への回帰係数(因果効果スコア)を推定する
# 各ノードについて、エッジリストから親ノードリストを構築し、OLS回帰で各親の効果を取得する
edge_scores
= {}
for
node in data.columns:
parent_list = [u for (u, v) in edges if v
== node]
if parent_list:
X = data[parent_list]
X = sm.add_constant(X) # 定数項を追加
y = data[node]
reg = sm.OLS(y, X).fit()
for parent in parent_list:
edge_scores[(parent, node)] = reg.params[parent]
print("\n===
エッジ方向と回帰係数スコア ===")
if
edge_scores:
for edge, score in edge_scores.items():
print(f"{edge[0]} -> {edge[1]}
: {score:.2f}")
else:
print("エッジが検出されませんでした。")
出力結果
[bnlearn]
>Computing best DAG using [direct-lingam]
===
学習された隣接行列 ===
target Temperature
IceCreamSales SwimmingAccidents
source
Temperature 0.0 5.013112 1.984295
IceCreamSales 0.0
0.000000 0.000000
SwimmingAccidents 0.0 0.000000 0.000000
===
学習されたエッジリスト ===
[('Temperature',
'IceCreamSales'), ('Temperature', 'SwimmingAccidents')]
===
エッジ方向と回帰係数スコア ===
Temperature
-> IceCreamSales : 5.01
Temperature
-> SwimmingAccidents : 1.98
補足
- 隣接行列からのエッジ抽出で、adjmat は pandas DataFrame として提供されており、各セルの値が 0 でなければ、そこにエッジが存在すると判断しています。
- 回帰係数の推定で、各子ノードに対して、その親ノード(エッジリストから抽出)の影響を statsmodels の OLS で推定し、その回帰係数を「スコア」として出力しています。
上記コードを実行すれば、直接描画は行わずとも、どのノードからどのノードにエッジが向いているかと、各エッジの回帰係数スコアがコンソールに表示されます。これによって、データから自動的に因果構造が検出され、エッジの方向とスコアが分かります。
表示が得られましたので、DAGを出力します。
このコードは、データを基に bnlearn ライブラリの `direct-lingam` 手法を用いて因果構造を学習し、その後に
statsmodels を使用して親子間の回帰係数(因果効果スコア)を計算するものです。以下に主要なステップを詳細に解説します。
###
① データ生成(依存関係の強化)
temperature
= np.random.uniform(20, 40, n_samples)
ice_cream_sales
= 5 * temperature + np.random.normal(0, 1, n_samples)
swimming_accidents
= 2 * temperature + np.random.normal(0, 1, n_samples)
Temperature(気温)が原因であり、IceCreamSales(アイスクリーム売上)と SwimmingAccidents(水泳事故件数)に影響を与えるようなデータを生成しています。
依存関係を強化
IceCreamSales = `5 * Temperature`:強い依存関係を設定。
SwimmingAccidents = `2 * Temperature`:やや弱い依存関係を設定。
ノイズ(`np.random.normal`)を加えることで、データの現実感を向上。
###②
bnlearnによる構造学習
model
= bn.structure_learning.fit(data, methodtype='direct-lingam')
adjmat
= model['adjmat']
構造学習
`direct-lingam`アルゴリズムを利用して、データから因果構造(DAG: 有向非巡回グラフ)を学習。
LiNGAM(Linear
Non-Gaussian Acyclic Model)
線形関係を仮定。
各変数が非ガウス分布に従うことを前提とし、独立成分分析(ICA)を通じて因果関係を推定。
隣接行列(Adjacency
Matrix):
各エッジに対応する因果効果スコアを含む行列。
例えば、Temperature
→ IceCreamSales のスコアは `5.013112`。
###
③ 隣接行列からエッジリストを生成
for
from_node in adjmat.index:
for to_node in adjmat.columns:
if adjmat.loc[from_node, to_node] != 0:
edges.append((from_node, to_node))
隣接行列を基にエッジリストを生成。
非ゼロの値を持つ項目を因果関係(エッジ)とみなします。
出力結果
[("Temperature",
"IceCreamSales"), ("Temperature",
"SwimmingAccidents")]
Temperature が
IceCreamSales と SwimmingAccidents に因果的な影響を与えていることを示しています。
###
④ 回帰係数(因果効果スコア)の推定
for
node in data.columns:
parent_list = [u for (u, v) in edges if v
== node]
if parent_list:
X = data[parent_list]
y = data[node]
reg = sm.OLS(y, X).fit()
for parent in parent_list:
edge_scores[(parent, node)] =
reg.params[parent]
各変数(ノード)について、エッジリストから親ノードを抽出。
親ノードを説明変数(X)、現在のノードを目的変数(y)として
**OLS(最小二乗回帰)** を実施。
各親ノードの回帰係数(因果効果スコア)を計算し、因果影響の強さを数値で記録。
出力結果
Temperature -> IceCreamSales : 5.01
Temperature -> SwimmingAccidents : 1.98
Temperature の変化が IceCreamSales と SwimmingAccidents にそれぞれどの程度の影響を与えるかを表しています。
###⑤
出力の解釈
1.
隣接行列
Temperature → IceCreamSales のスコアは約 `5.013112`、Temperature → SwimmingAccidents のスコアは `1.984295`。
IceCreamSales と
SwimmingAccidents は互いに独立であり、Temperature を通じてのみ接続。
2.
エッジリスト
DAGの構造を直接的に示します。
ここでは、Temperature
が中心的な共通原因であることを反映。
3.
回帰係数(因果効果スコア)
Temperature の変化が IceCreamSales に強い影響(スコア: 5.01)を持ち、SwimmingAccidents に中程度の影響(スコア: 1.98)を持つ。
###
因果探索手法における関連理論
1.
制約ベース手法との違い
DirectLiNGAMは、条件付き独立性テストではなく非ガウス性に基づいて因果関係を推定。
条件付き独立性に依存しないため、ノイズが多い場合でも優位性があります。
2.
関数型因果モデル(FCM)
この手法は線形モデルを前提としていますが、非線形モデルに拡張する方法もあります(カーネル法や深層学習を応用)。
###
このコードの意義
このコードは、単に因果構造を推定するだけでなく、得られた関係性を回帰係数として定量化する点が特徴的です。
========================
先ほど得られたエッジリストに基づいて NetworkX を用いて DAG をビジュアル化するコード例です。これを実行すると、各ノードとエッジ(方向付き矢印)が描画されます。
python
import
networkx as nx
import
matplotlib.pyplot as plt
# 学習されたエッジリストを利用
edges
= [('Temperature', 'IceCreamSales'), ('Temperature', 'SwimmingAccidents')]
#
NetworkX の有向グラフ(DiGraph)を作成し、エッジを追加
G
= nx.DiGraph()
G.add_edges_from(edges)
# レイアウトの計算(seed を固定して再現性を確保)
pos
= nx.spring_layout(G, seed=42)
# グラフ描画
plt.figure(figsize=(6,
4))
nx.draw(G,
pos, with_labels=True, node_color='lightblue', node_size=2000,
font_size=12, font_weight='bold',
arrows=True, arrowstyle='->', arrowsize=20)
plt.title("DAG
learned by Direct-LiNGAM")
plt.axis('off')
plt.show()
出力結果
- Temperature →
IceCreamSales
- Temperature →
SwimmingAccidents
というエッジ情報を元にグラフを生成しています。DAG が描画されれば、各ノード間の依存関係が視覚的に確認できます。