目次
1. 実験の紹介
この実験により、線形構造方程式モデルにおける Noteears Linear アルゴリズムの因果関係推定が完了しました。
チャットGPT:
Noteears Linear アルゴリズムは、線形構造方程式モデルの因果関係を推定するための効率的な方法です。行列が変数間の因果関係を説明できるように、損失関数を最小化することで最適な重み行列を見つけます。このアルゴリズムには次の利点があります。
効率: Notears Linear アルゴリズムは、因果関係を効率的に推定できる最適化ベースのアプローチを使用します。アルゴリズムの複雑さは変数の数と観測サンプルのサイズによって異なりますが、一般に計算の複雑さは低くなります。
線形モデルの適用性: Noteears 線形アルゴリズムは線形構造方程式モデルに適しており、線形因果関係を効果的に処理できます。非線形関係の場合、このアルゴリズムは機能しない可能性があります。
制約項の導入: Noteears 線形アルゴリズムは、推定されたグラフが非循環であることを保証するために制約項を導入し、因果関係の因果関係を確立します。これにより、推定結果の解釈可能性と信頼性が向上します。
2. 実験環境
この一連の実験では PyTorch 深層学習フレームワークを使用し、関連する操作は次のとおりです (深層学習シリーズの記事の環境に基づく)。
1. 仮想環境を構成する
ディープラーニング連載記事の環境
conda create -n DL python=3.7
conda activate DL
pip install torch==1.8.1+cu102 torchvision==0.9.1+cu102 torchaudio==0.8.1 -f https://download.pytorch.org/whl/torch_stable.html
conda install matplotlib
conda install scikit-learn
新規追加
conda install pandas
conda install seaborn
conda install networkx
conda install statsmodels
pip install pyHSICLasso
注:私の実験環境では上記の順番で各種ライブラリをインストールしていますので、まとめてインストールしてみたい方は試してみてください(問題が起こるかどうかは神のみぞ知る)
2. ライブラリバージョンの紹介
ソフトウェアパッケージ | 今回の実験版は | 現在の最新バージョン |
マットプロットライブラリ | 3.5.3 | 3.8.0 |
しこり | 1.21.6 | 1.26.0 |
パイソン | 3.7.16 | |
scikit-learn | 0.22.1 | 1.3.0 |
松明 | 1.8.1+cu102 | 2.0.1 |
トーショーディオ | 0.8.1 | 2.0.2 |
トーチビジョン | 0.9.1+cu102 | 0.15.2 |
新しい
ネットワークx | 2.6.3 | 3.1 |
パンダ | 1.2.3 | 2.1.1 |
pyHSICLase | 1.4.2 | 1.4.2 |
シーボーン | 0.12.2 | 0.13.0 |
状態モデル | 0.13.5 | 0.14.0 |
3.IDE
Pycharmの使用を推奨します(中でもpyHSICLassoライブラリはVScodeでエラーが発生しており、解決策はまだ見つかっていません...)
内部関数
3. 実験内容
0. 必要なツールをインポートする
import numpy as np
import scipy.linalg as slin
import scipy.optimize as sopt
import random
import networkx as nx
import matplotlib.pyplot as plt
1. set_random_seed
def set_random_seed(seed=1):
random.seed(seed)
np.random.seed(seed)
再現可能な結果を保証するためにランダム シードを設定するために使用されます。
2.公証人_リニア
def notears_linear(X, lambda1=0.08, max_iter=100, h_tol=1e-8, rho_max=1e+16, w_threshold=0.3):
a. 入力パラメータ
X
: 入力データ行列の形状は です(n, d)
。ここで、n
はサンプル数、 はd
特徴の次元です。lambda1
: L1 正則化項の重み。デフォルトは 0.08 です。max_iter
: 最大反復数、デフォルトは 100 です。h_tol
: 反復を停止するためのターゲット エラー許容値。デフォルトは 1e-8 です。rho_max
: 最大ペナルティパラメータ。デフォルトは 1e+16 です。w_threshold
: 重みのしきい値。スパース推定結果に使用されます。デフォルトは 0.3 です。
関数内には、次のようないくつかの補助関数が定義されています。
b. 内部機能_adj
def _adj(w):
return w.reshape([d, d])
平坦化された重みパラメータをw
正方行列形式の重み行列に変換します。W。
c.
内部関数_loss
def _loss(W):
X_ = X @ W
R = X - X_
loss = 0.5 / X.shape[0] * (R ** 2).sum() + lambda1 * W.sum() # Form 2
G_loss = - 1.0 / X.shape[0] * X.T @ R + lambda1
return loss, G_loss
- 損失関数とその勾配を計算します。
- 損失関数は、データ フィッティング項と正則化項の 2 つの部分で構成されます。
- 勾配は、重み行列に関する損失関数の導関数を表します。
d.
内部関数_h
def _h(W):
E = slin.expm(W * W)
h = np.trace(E) - d
G_h = E.T * W * 2 # Form 6
return h, G_h
- 別の制約とその勾配を計算します。
- 制約は、推定されたグラフが非循環であることを保証するために使用されます。
- 勾配は、重み行列に関する制約項の導関数を表します。
e.
内部関数_func
def _func(w):
W = _adj(w)
loss, G_loss = _loss(W)
h, G_h = _h(W)
obj = loss + 0.5 * rho * h * h + alpha * h # Form 11
G_smooth = G_loss + (rho * h + alpha) * G_h # G of Form 11
g_obj = G_smooth.reshape(-1, )
return obj, g_obj
- 完全な目的関数とその勾配を計算します。
- 目的関数には、損失関数、制約項、正則化項が含まれます。
- 勾配は、重みパラメータに関する目的関数の導関数を表します。
f. 関数本体
n, d = X.shape
w_est, rho, alpha, h = np.zeros(d * d), 1.0, 0.0, np.inf
bnds = [(0, 0) if i == j else (0, None) for i in range(d) for j in range(d)]
X = X - np.mean(X, axis=0)
for _ in range(max_iter):
w_new, h_new = None, None
while rho < rho_max:
sol = sopt.minimize(_func, w_est, jac=True, bounds=bnds)
w_new = sol.x
h_new, _ = _h(_adj(w_new))
if h_new > 0.25 * h: # h下降不够快时 提高h的权重
rho *= 10
else:
break
w_est, h = w_new, h_new
alpha += rho * h
if h <= h_tol or rho >= rho_max:
break
W_est = _adj(w_est)
W_est[np.abs(W_est) < w_threshold] = 0
return W_est
- 変数の初期化
- 入力データ行列の次元を取得し、いくつかの変数を初期化します。
-
入力データ行列を中央に配置します。
- ループの反復
-
各反復では、
scipy.optimize.minimize
関数を呼び出すことによって最適なモデル パラメーター推定値が見つかります。 -
最適解を見つけるプロセスでは、
rho
ペナルティ パラメーターの値を調整することでモデル構造のスパース性が制御されます。 -
反復プロセス中に、
rho
値は目的関数の値の変化と制約に基づいて動的に調整されます。 -
停止条件に達すると (ターゲット誤差が許容値未満であるか、ペナルティ パラメーターが
rho
最大値に達すると)、反復は停止します。
-
- しきい値: 絶対値が指定されたしきい値未満である重み行列内の要素をゼロに設定します。
- 推定されたモデル パラメーター行列を返します
W_est
。
3. メインプログラム
if __name__ == '__main__':
set_random_seed()
X = np.loadtxt('Notears_X.csv', delimiter=',')
W_est = notears_linear(X)
print("W_est")
print(W_est)
G_nx = nx.DiGraph(W_est)
print(nx.is_directed_acyclic_graph(G_nx))
nx.draw_planar(G_nx, with_labels=True)
plt.show()
- ランダムシードを設定します。
- ファイル「Notears_X.csv」からデータ行列をロードします
X
。
- 関数を呼び出して
notears_linear
線形構造方程式モデルのパラメーターを推定し、推定されたモデル パラメーター行列 を取得しますW_est
。 - 推定されたモデル パラメーター行列を出力します
W_est
。 - 推定されたモデル パラメーター行列に基づいて
W_est
有向グラフを作成しますG_nx
。 G_nx
グラフが有向非巡回グラフ (DAG) であるかどうかを判断します 。G_nx
図の平面レイアウトを描画し 、グラフを表示します。
データ部表示
6.24E-01 | 9.07E-01 | 7.77E-01 | 1.58E+00 | ####### | ####### | 5.62E+00 | ####### | ####### | 7.16E+00 |
7.50E-01 | 7.33E-01 | ####### | 7.01E-03 | ####### | ####### | 3.93E-01 | ####### | 2.40E+00 | ####### |
3.77E-01 | 7.12E-01 | 1.71E-01 | 1.58E-01 | 1.08E+00 | 1.73E+00 | ####### | 3.05E+00 | 4.09E+00 | ####### |
1.39E-01 | 1.10E+00 | 7.96E-01 | 1.67E+00 | 2.94E-01 | ####### | 4.86E+00 | ####### | ####### | 7.24E+00 |
####### | ####### | ####### | ####### | ####### | 9.83E-01 | ####### | 3.42E+00 | 4.28E+00 | ####### |
####### | ####### | 8.44E-01 | 5.92E-01 | 9.75E-02 | ####### | ####### | 8.99E-01 | ####### | 1.18E+00 |
####### | 1.68E-01 | ####### | ####### | 1.50E+00 | 3.22E+00 | ####### | 3.14E+00 | 4.26E+00 | ####### |
####### | ####### | 2.18E-01 | ####### | 1.18E+00 | 2.19E+00 | ####### | 1.41E+00 | 9.86E-01 | ####### |
1.85E-01 | 3.48E-02 | 3.65E-01 | ####### | 3.91E-01 | 1.97E+00 | ####### | 4.16E+00 | 4.85E+00 | ####### |
####### | ####### | ####### | ####### | 3.01E-01 | 7.11E-01 | 2.77E-02 | ####### | ####### | ####### |
グラフを描く
n. コードの統合
import numpy as np
import scipy.linalg as slin
import scipy.optimize as sopt
import random
import networkx as nx
import matplotlib.pyplot as plt
def set_random_seed(seed=1):
random.seed(seed)
np.random.seed(seed)
def notears_linear(X, lambda1=0.08, max_iter=100, h_tol=1e-8, rho_max=1e+16, w_threshold=0.3):
def _adj(w):
return w.reshape([d, d])
def _loss(W):
X_ = X @ W
R = X - X_
loss = 0.5 / X.shape[0] * (R ** 2).sum() + lambda1 * W.sum() # Form 2
G_loss = - 1.0 / X.shape[0] * X.T @ R + lambda1
return loss, G_loss
def _h(W):
E = slin.expm(W * W)
h = np.trace(E) - d
G_h = E.T * W * 2 # Form 6
return h, G_h
def _func(w):
W = _adj(w)
loss, G_loss = _loss(W)
h, G_h = _h(W)
obj = loss + 0.5 * rho * h * h + alpha * h # Form 11
G_smooth = G_loss + (rho * h + alpha) * G_h # G of Form 11
g_obj = G_smooth.reshape(-1, )
return obj, g_obj
n, d = X.shape
w_est, rho, alpha, h = np.zeros(d * d), 1.0, 0.0, np.inf
bnds = [(0, 0) if i == j else (0, None) for i in range(d) for j in range(d)]
X = X - np.mean(X, axis=0)
for _ in range(max_iter):
w_new, h_new = None, None
while rho < rho_max:
sol = sopt.minimize(_func, w_est, jac=True, bounds=bnds)
w_new = sol.x
h_new, _ = _h(_adj(w_new))
if h_new > 0.25 * h: # h下降不够快时 提高h的权重
rho *= 10
else:
break
w_est, h = w_new, h_new
alpha += rho * h
if h <= h_tol or rho >= rho_max:
break
W_est = _adj(w_est)
W_est[np.abs(W_est) < w_threshold] = 0
return W_est
if __name__ == '__main__':
set_random_seed()
X = np.loadtxt('Notears_X.csv', delimiter=',')
W_est = notears_linear(X)
print("W_est")
print(W_est)
G_nx = nx.DiGraph(W_est)
print(nx.is_directed_acyclic_graph(G_nx))
nx.draw_planar(G_nx, with_labels=True)
plt.show()
# edges, weights = zip(*nx.get_edge_attributes(G_nx, 'weight').items())
# pos = nx.spring_layout(G_nx)
# nx.draw(G_nx, pos, node_color='b', edgelist=edges, edge_color=weights, width=5, with_labels=True, edge_cmap=plt.cm.Blues)
# plt.show()