[統計] GLMの最尤推定を自分でやってみる
はじめに
GLMで対数尤度を自分で計算して自分で最尤推定をやってみる、という回。
ロジスティック回帰
今回はロジスティック回帰を行ってみる。
ガス濃度とカブトムシの死亡数のデータを使う(Dobson, 2008, p.144)。
x | n | y |
---|---|---|
1.6907 | 59 | 6 |
1.7242 | 60 | 13 |
1.7552 | 62 | 18 |
1.7842 | 56 | 28 |
1.8113 | 63 | 52 |
1.8369 | 59 | 53 |
1.8610 | 62 | 61 |
1.8839 | 60 | 60 |
統計モデルは次のような式で表される。
対数尤度
2パラメータのロジスティック回帰の場合の対数尤度関数を示す (Dobson, 2008, p.145)。
対数尤度の計算
パラメータの値を変えて対数尤度を計算してみる。
from itertools import product import pandas as pd import seaborn as sns import numpy as np import matplotlib.pyplot as plt from tqdm import tqdm from scipy.special import comb def loglikelihood_function(Y, N, X, beta1, beta2): res = 0 for y, n, x in zip(Y, N, X): res += y * (beta1 + beta2 * x) - n * np.log(1 + np.exp(beta1 + beta2 * x)) + np.log(comb(n, y)) return res def calc_loglikelihood(): df = pd.read_csv('beetle.csv') Y = df['y'].values X = np.ones(shape=[len(df), 2]) * np.nan X = df['x'].values N = df['n'].values RESOLUTION = 100 beta1_arr = np.linspace(-80, -40, RESOLUTION) beta2_arr = np.linspace(15, 55, RESOLUTION) res = [] for beta1, beta2 in tqdm(list(product(beta1_arr, beta2_arr))): loglikelihood = loglikelihood_function(Y=Y, N=N, X=X, beta1=beta1, beta2=beta2) res.append([beta1, beta2, loglikelihood]) res = pd.DataFrame(res, columns=['beta1', 'beta2', 'loglikelihood']) res.to_csv('loglikelihood.csv') print(res[res.index == res['loglikelihood'].idxmax()]) return def plot_heatmap(): df = pd.read_csv('loglikelihood.csv') df = df.pivot(index='beta2', columns='beta1', values='loglikelihood') df.to_csv('heatmap.csv') fig, ax = plt.subplots() sns.heatmap(data=df, ax=ax, cmap='YlGnBu') ax.set_xticklabels(['{:.2f}'.format(float(x.get_text())) for x in ax.get_xticklabels()]) ax.set_yticklabels(['{:.2f}'.format(float(x.get_text())) for x in ax.get_yticklabels()]) fig.tight_layout() fig.savefig('heatmap_loglikelohood.jpg', dpi=300) return if __name__ == '__main__': calc_loglikelihood() plot_heatmap()
beta1 beta2 loglikelihood 4947 -60.20202 33.989899 -18.72801
図では分かりにくいが、対数尤度が最大になるのは あたり。
statsmodelsでGLM
statsmodelsライブラリでロジスティック回帰を行ってみる。
import pandas as pd import statsmodels.api as sm import statsmodels.formula.api as smf def glm_statsmodels(): df = pd.read_csv('beetle.csv') df['z'] = df['n'] - df['y'] glm = smf.glm(formula='y + z ~ x', data=df, family=sm.families.Binomial()) fit = glm.fit() print(fit.summary()) return if __name__ == '__main__': glm_statsmodels()
Generalized Linear Model Regression Results ============================================================================== Dep. Variable: ['y', 'z'] No. Observations: 8 Model: GLM Df Residuals: 6 Model Family: Binomial Df Model: 1 Link Function: logit Scale: 1.0000 Method: IRLS Log-Likelihood: -18.715 Date: Sun, 18 Oct 2020 Deviance: 11.232 Time: 13:56:22 Pearson chi2: 10.0 No. Iterations: 6 Covariance Type: nonrobust ============================================================================== coef std err z P>|z| [0.025 0.975] ------------------------------------------------------------------------------ Intercept -60.7175 5.181 -11.720 0.000 -70.871 -50.563 x 34.2703 2.912 11.768 0.000 28.563 39.978 ==============================================================================
となって、対数尤度は あたりで最大となることが分かる。この結果は、先に計算した結果と一致する。
実際の最尤推定の手順
ここでは、パラメータに対して総当たり的に対数尤度を計算し、対数尤度が最大になる場所を探して最尤推定量を求めた。しかし、この方法は非効率的である。
そこで、山登り的に計算を進めて最尤推定量を求める方法が考えられる。実際には、ニュートン法(ニュートンラプソン法)をGLM向けに改良したスコア法が用いられ、対数尤度の一階微分(スコア統計量)が0になる場所が探索される。
メモ
このデータと最尤推定の例はDobson (2008)で紹介されているが、p.146の表7.3に示されている対数尤度は桁が1つ間違っていると思われる。具体的には、-186.235となっているところは-18.6235だろう。