MCMCにおける標準化処理の効果について
前回紹介した「ベイズ統計モデリング」では、MCMCの効率的な実行のため、データの前処理として、標準化または平均化が推奨されている。
今回は、簡単なGLMを例に、その効果(サンプリング時間)を計測し、比較した。
- 前処理なし
- 標準化処理あり
- 中心化処理あり
- pm.glmモジュール利用(前処理なし)
例題は、pymc3公式HPの例題を使用した。
GLM: Linear regression — PyMC3 3.5 documentation
結果は、いずれもtraceplotやRhatを見ると収束しており、 推定されたパラメータは、それほど大きな差はない。 グラフに表示してもほとんど分からない。(例題にも依ると思うが。)
しかし、MCMCのサンプリングに掛かった時間は、
「標準化あり < 中心化あり < 前処理なし < pm.glmモジュール」
であり、前処理ありの方が早い。また、有効サンプルサイズも前処理ありの方が2,3倍大きい。
前処理なし | 標準化 | 中心化 | pm.glm | |
---|---|---|---|---|
実行時間 | 24.5 s | 10.5 s | 11.4 s | 37.5 s |
有効サンプルサイズ(beta) | 2523.87 | 8809.77 | 8567.02 | 2916.77 |
Rhat | 1.0 | 1.0 | 1.0 | 1.0 |
従って、複雑なモデルのパラメータ推定には前処理は有効であると言える。 しかし、前処理されているため、推定されたパラーメータ値が直感的には解釈し難いのが欠点と言える。
中心化は並行移動だけで、標準化と異なりスケールは変わらないため、解釈しやすい。 効果としても標準化を行った場合と大差ないため、うまくMCMCが実行されない場合は 中心化だけでもやった方が良いかもしれない。
今回実行したコードを以下に載せておく。
関数定義
今回はモデルやMCMCパラメータは同じで、データの前処理の有無の差だけなので、関数定義した。
def exec_mcmc(x,y): with pm.Model() as model: sigma = pm.HalfCauchy('sigma', beta=10, testval=1.) alpha = pm.Normal('alpha', 0, sd=20) beta = pm.Normal('beta', 0, sd=20) y_ = pm.Normal('y', mu=alpha + beta * x, sd=sigma, observed=y) trace = pm.sample(3000, tune=500, start=pm.find_MAP(method='Powell'), step=pm.NUTS()) return model, trace
def plot_figure(x, y, true_regression_line, pred_y=None, label=None): fig = plt.figure(figsize=(7, 7)) ax = fig.add_subplot(111, xlabel='x', ylabel='y', title='Generated data and underlying model') ax.plot(x, y, 'x', label='sampled data') # 真の回帰線を表示する ax.plot(x, true_regression_line, label='true regression line', lw=2.) if pred_y is not None: # 予測した回帰線を表示する if type(pred_y) is list: for py,l in zip(pred_y, label): ax.plot(x, py, label=l, lw=2.) else: ax.plot(x, pred_y, label=label, lw=2.) plt.legend(loc=0);
データの生成
# データの生成 size = 200 true_intercept = 1 true_slope = 2 x = np.linspace(0, 100, size) # y = a + b*x true_regression_line = true_intercept + true_slope * x # add noise y = true_regression_line + np.random.normal(scale=50.5, size=size) data = dict(x=x, y=y) # DataFrame化 df = pd.DataFrame(data=data)
1. 前処理なし
%%time model1, trace1 = exec_mcmc(df.x,df.y)
logp = -1,077.5, ||grad|| = 5.3893: 100%|██████████| 78/78 [00:00<00:00, 812.06it/s]
Multiprocess sampling (2 chains in 2 jobs)
NUTS: [beta, alpha, sigma]
Sampling 2 chains: 100%|██████████| 7000/7000 [00:11<00:00, 594.10draws/s]
CPU times: user 8.8 s, sys: 660 ms, total: 9.46 s
Wall time: 24.5 s
2, 標準化
# 標準化処理 from sklearn.preprocessing import StandardScaler scaler = StandardScaler() trans_df = pd.DataFrame(data=scaler.fit_transform(df.values), columns=['x','y']) %%time model2, trace2 = exec_mcmc(trans_df.x, trans_df.y)
logp = -207.47, ||grad|| = 0.0020073: 100%|██████████| 83/83 [00:00<00:00, 851.20it/s]
Multiprocess sampling (2 chains in 2 jobs)
NUTS: [beta, alpha, sigma]
Sampling 2 chains: 100%|██████████| 7000/7000 [00:06<00:00, 1049.27draws/s]
CPU times: user 6.54 s, sys: 304 ms, total: 6.84 s
Wall time: 10.5 s
3. 中心化
# 中心化処理 scaler2 = StandardScaler(with_std=False) trans_df2 = pd.DataFrame(data=scaler2.fit_transform(df.values), columns=['x','y']) %%time model3, trace3 = exec_mcmc(trans_df2.x, trans_df2.y)
logp = -1,077.3, ||grad|| = 0.07435: 100%|██████████| 139/139 [00:00<00:00, 1004.37it/s]
Multiprocess sampling (2 chains in 2 jobs)
NUTS: [beta, alpha, sigma]
Sampling 2 chains: 100%|██████████| 7000/7000 [00:07<00:00, 915.98draws/s]
CPU times: user 6.44 s, sys: 240 ms, total: 6.68 s
Wall time: 11.4 s
4. pm.glmモジュールの利用
%%time with pm.Model() as model4: pm.glm.GLM.from_formula('y ~ x', df) trace4 = pm.sample(3000, tune=500, start=pm.find_MAP(method='Powell'), step=pm.NUTS())
logp = -1,077.3, ||grad|| = 0.011737: 100%|██████████| 224/224 [00:00<00:00, 620.51it/s]
Multiprocess sampling (2 chains in 2 jobs)
NUTS: [sd, x, Intercept]
Sampling 2 chains: 100%|██████████| 7000/7000 [00:31<00:00, 222.34draws/s]
CPU times: user 11.9 s, sys: 628 ms, total: 12.6 s
Wall time: 37.5 s
結果の表示
# 前処理なしの結果
pm.summary(trace1)
mean | sd | mc_error | hpd_2.5 | hpd_97.5 | n_eff | Rhat | |
---|---|---|---|---|---|---|---|
alpha | -5.00 | 6.59 | 1.40e-01 | -17.11 | 8.61 | 2686.89 | 1.0 |
beta | 1.99 | 0.12 | 2.41e-03 | 1.77 | 2.22 | 2523.87 | 1.0 |
sigma | 49.64 | 2.55 | 4.61e-02 | 44.76 | 54.63 | 3204.49 | 1.0 |
# 標準化ありの結果
pm.summary(trace2)
mean | sd | mc_error | hpd_2.5 | hpd_97.5 | n_eff | Rhat | |
---|---|---|---|---|---|---|---|
alpha | 2.45e-04 | 0.05 | 4.48e-04 | -0.09 | 0.10 | 9703.56 | 1.0 |
beta | 7.62e-01 | 0.05 | 5.09e-04 | 0.67 | 0.85 | 8809.77 | 1.0 |
sigma | 6.55e-01 | 0.03 | 3.18e-04 | 0.59 | 0.72 | 8292.82 | 1.0 |
# 中心化ありの結果
pm.summary(trace3)
mean | sd | mc_error | hpd_2.5 | hpd_97.5 | n_eff | Rhat | |
---|---|---|---|---|---|---|---|
alpha | 0.01 | 3.48 | 3.49e-02 | -6.79 | 6.75 | 8898.01 | 1.0 |
beta | 2.00 | 0.12 | 1.47e-03 | 1.75 | 2.23 | 8567.02 | 1.0 |
sigma | 49.65 | 2.49 | 2.94e-02 | 45.26 | 54.97 | 7896.93 | 1.0 |
# pm.glmモジュールの結果
pm.summary(trace4)
mean | sd | mc_error | hpd_2.5 | hpd_97.5 | n_eff | Rhat | |
---|---|---|---|---|---|---|---|
Intercept | -5.72 | 6.89 | 1.30e-01 | -18.82 | 7.98 | 2894.93 | 1.0 |
x | 2.00 | 0.12 | 2.27e-03 | 1.75 | 2.22 | 2916.77 | 1.0 |
sd | 49.66 | 2.51 | 3.97e-02 | 44.89 | 54.55 | 4223.24 | 1.0 |
グラフ表示
パラメータの推定結果は、大体一緒。 傾きはうまく推定されているが、切片がノイズが大きいためか、少しズレている。
pred_y_ = [] for tr,d,ss in zip([trace1, trace2, trace3, trace4], [df,trans_df,trans_df2,df], [None,scaler,scaler2,None]): try: tmp = np.array([a*tr['beta'].mean()+tr['alpha'].mean() for a in d.x]) except: # pm.glmモジュール利用の場合はこっちになる tmp = np.array([a*tr['x'].mean()+tr['Intercept'].mean() for a in d.x]) if ss is not None: tmp = ss.inverse_transform(np.array([d.x.values, np.array(tmp)]).T).T[1] pred_y_.append(tmp) plot_figure(x,y,true_regression_line,pred_y=pred_y_,label=['なし','標準化','中心化','glm'])
以上