ChatGPTとPythonで学ぶ マルコフ連鎖モンテカルロ



本日は、ゲストにChatGPTさんをお招きして、「ChatGPTとPythonで学ぶマルコフ連鎖モンテカルロ」というテーマで雑談をしたいと思います。それではChatGPTさん、よろしくお願いします。

---

### **assistant**:

はじめまして、ChatGPTと申します。マルコフ連鎖モンテカルロ（MCMC）について一緒に学べるのは楽しみですね。よろしくお願い致します。

ではまず、基本的な観点から始めましょう。マルコフ連鎖モンテカルロは、確率的なアルゴリズムの一つで、大規模な複雑な確率分布からの標本抽出を可能にします。これは、ベイズ統計学や機械学習などの分野で広く応用されています。

Pythonは、このような数値計算やデータ分析を行うための強力なツールであり、特にscipyやnumpy、pandasなどのライブラリは、MCMCといった高度な統計的手法を利用するための多くの便利な関数を提供しています。

それでは、Pythonでマルコフ連鎖モンテカルロを扱ってみましょう。以下に、基本的な実装を示します。

```python
import numpy as np

def MCMC(n_iteration, initial_state, prop_fn, prop_fn_args, eval_fn, eval_fn_args):
    states = np.zeros((n_iteration, len(initial_state)))
    states[0, :] = initial_state
    current_eval = eval_fn(initial_state, **eval_fn_args)

    for i in range(1, n_iteration):
        proposal = prop_fn(states[i-1, :], **prop_fn_args)
        proposal_eval = eval_fn(proposal, **eval_fn_args)
        
        accept_prob = min(1, proposal_eval / current_eval)
        u = np.random.uniform(0, 1)
        
        if u < accept_prob:
            states[i, :] = proposal
            current_eval = proposal_eval
        else:
            states[i, :] = states[i-1, :]
    
    return states
```
このコードはメトロポリス・ヘイスティングス法と呼ばれるバージョンのMCMCを実装しています。関数`MCMC`は、サンプリングする状態の総数（`n_iteration`）、初期状態（`initial_state`）、提案関数（`prop_fn`）、評価関数（`eval_fn`）、およびこれらの関数の引数を受け取ります。

---

### **user**:

このMCMCを、何の目的で、どのように用いるのでしょうか？仮想的なデータで構いませんので、MCMCを使って計算する例をPythonで書いてください。

---

### **assistant**:

マルコフ連鎖モンテカルロ（MCMC）は、計算が難しい複雑な確率分布から直接サンプリングをするのではなく、より単純な提案分布からサンプリングを行い、評価関数（全体の確率密度関数）に基づいてサンプルを受け入れるか拒否するかを決定するという方法を用いています。これにより、その確率分布からのランダムサンプルを生成することが可能となります。これは、ベイズ統計学などの分野で特に重要で、パラメータの事後分布を推定するために利用されます。

以下に、標準正規分布からサンプリングを行うための例を示します。提案分布には単純な一様分布を使用します。

```python
import numpy as np
from scipy.stats import norm, uniform

# Normal distribution's pdf as evaluation function
def target_density(x, loc=0, scale=1):
    return norm.pdf(x, loc=loc, scale=scale)

# Uniform distribution's rvs as proposal function
def proposal_fn(x, loc=-1, scale=2):
    return np.array([uniform.rvs(loc=x[i]+loc, scale=scale) for i in range(len(x))])

# MCMC to generate samples
samples = MCMC(10000, np.array([0]), proposal_fn, {}, target_density, {})

# plot histogram of samples
import matplotlib.pyplot as plt

plt.hist(samples, bins=30, density=True)
x = np.linspace(-4, 4, 400)
y = norm.pdf(x)
plt.plot(x, y)
plt.show()
```

![output_1_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/ffb49991-7dfe-7bc2-84cd-9b40d833ae70.png)


ここで`MCMC`関数は10,000個のサンプルを生成し、初期状態は`0`です。提案分布は、現在のサンプルを中心に`[-1, 1]`の範囲で一様分布から生成するものとしました。評価関数としては、標準正規分布の確率密度関数を用いています。

生成されたサンプルからヒストグラムをプロットし、提案として用いた標準正規分布と比較すると、MCMCにより生成されたサンプルが標準正規分布に従っていることが視覚的に確認できます。

---

### **user**:

すみません、コード中の説明は日本語にしてください。その説明も、できるだけ詳しくお願いします。

---

### **assistant**:

申し訳ありません、説明を英語に書いてしまいました。以下に日本語で説明を記載し直します。

```python
import numpy as np
from scipy.stats import norm, uniform

# 評価関数として標準正規分布の確率密度関数を定義
def target_density(x, loc=0, scale=1):
    return norm.pdf(x, loc=loc, scale=scale)

# 提案関数として、現在のサンプルを中心にした一様分布からランダムサンプリングする関数を定義
def proposal_fn(x, loc=-1, scale=2):
    return np.array([uniform.rvs(loc=x[i]+loc, scale=scale) for i in range(len(x))])

# MCMC関数を使って10000個のサンプルを生成、初期状態は0
samples = MCMC(10000, np.array([0]), proposal_fn, {}, target_density, {})

# 生成されたサンプルのヒストグラムをプロット
import matplotlib.pyplot as plt

plt.hist(samples, bins=30, density=True)  # 確率密度で正規化されたヒストグラムを作成
x = np.linspace(-4, 4, 400)  # xの範囲を指定
y = norm.pdf(x)  # 標準正規分布の確率密度関数を使ってyの値を計算
plt.plot(x, y)  # 理論的な確率密度関数をプロット
plt.show()  # 図を表示
```

![output_2_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/b1aa2583-f583-d1a5-d619-d5a0da24f918.png)


ここで`target_density`関数は、標準正規分布に従う確率密度関数です。また、`proposal_fn`関数は候補の新しい状態を提案するための関数で、現在のサンプルを中心にした一様分布からサンプリングします。この関数は、`x`を引数にとり、新たに提案する状態（ここでは次の数値）を返します。

そして、最初の`MCMC`関数呼び出しでは、10,000回の反復、初期状態0、上記で定義した提案関数と評価関数を引数として与え、10,000個のサンプルを生成します。

最後に`matplotlib`ライブラリを使って、生成されたサンプルをヒストグラムとしてプロットします。そして、その上に理論的な標準正規分布の確率密度関数を描きます。これにより、サンプルがどの程度目的の分布に従っているかを視覚的に確認することができます。

---

### **user**:

評価関数と、提案関数とは何なのか、いまいちよく分かりません。これらの関数の、MCMCにおける位置付けを詳しく説明してください。

---

### **assistant**:

マルコフ連鎖モンテカルロ（MCMC）では、評価関数と提案関数の2つの重要なコンポーネントがあります。

1. **評価関数**: 評価関数（または目標分布）は、サンプリングしたい確率分布を表現します。つまり、サンプルの良さや悪さを評価するための基準となる関数です。具体的には、評価関数は新たに提案された位置がどの程度目標分布に適合しているかを評価します。モンテカルロ法では、この評価関数に基づいて新たな状態（提案）を受け入れるか否かを決定します。通常、評価関数は確率密度関数や確率質量関数などを用いますが、これは具体的な応用によります。

2. **提案関数**: 提案関数は、新たな状態を生成するための道具です。つまり、現在の状態から次の状態へ移動するための機構を提供します。具体的には、提案関数は現在の状態を引数に取り、候補となる新しい状態を返します。提案関数はランダムウォークや局所探索などの機構を実現するために使用されます。

これら2つの関数がどのように相互作用するかを具体的に見てみましょう。MCMCの各ステップでは以下のことが行われます。

1. 提案関数を使用して現在の状態から新しい状態を生成します。
2. 評価関数を使用して新しい状態の"良さ"を評価します。
3. ある確率（通常は新しい状態の評価の値に比例）で新しい状態を受け入れるかどうかを決定します。

もし新しい状態が受け入れられれば、そちらへ遷移し、そうでなければ現在の状態に留まります。これを何度も繰り返すことで、目標とする分布からのサンプルを生成することができます。

---

### **user**:

なるほど、何となく分かってきた気がします。そのためには、先ほど教えてもらったMCMCの実装の理解が必要ですね。先ほどのMCMCの実装をもう一度教えてください。その際に、Pythonコード中で、日本語でできるだけ詳しく説明してください。

---

### **assistant**:

もちろんです、以下にMCMCの基本的な実装とその説明を記載します。

```python
import numpy as np

def MCMC(n_iteration, initial_state, prop_fn, prop_fn_args, eval_fn, eval_fn_args):
    # 初期化：サンプリングされる状態を格納するための配列を用意
    states = np.zeros((n_iteration, len(initial_state)))
    states[0, :] = initial_state  # 初期状態を代入

    # 初期状態を評価関数で評価
    current_eval = eval_fn(initial_state, **eval_fn_args)

    # MCMCの主要なループ
    for i in range(1, n_iteration):
        # 提案関数を用いて新しい状態を提案
        proposal = prop_fn(states[i-1, :], **prop_fn_args)
 
        # 提案された新しい状態を評価関数で評価
        proposal_eval = eval_fn(proposal, **eval_fn_args)
        
        # 受け入れ確率を計算（新旧の状態の評価の比で、1より大きい場合は1にする）
        accept_prob = min(1, proposal_eval / current_eval)
        
        # 一様分布からランダムに値を取得
        u = np.random.uniform(0, 1)
        
        # 受け入れ確率と比較して新しい状態を受け入れるか否かを判断
        if u < accept_prob:
            states[i, :] = proposal
            current_eval = proposal_eval
        else:
            states[i, :] = states[i-1, :]
    
    return states
```

ここで、生成される状態の総数（`n_iteration`）、最初の状態（`initial_state`）、提案関数（`prop_fn`）、評価関数（`eval_fn`）、およびこれらの関数に対する追加の引数をとるMCMC関数を定義しています。

関数は次の手順で動作します：

1. 初期状態を評価関数で評価します。
2. 提案関数を用いて新しい状態を提案します。
3. 提案した新しい状態を評価関数で評価し、受け入れ確率を計算します。受け入れ確率は提案した新しい状態と現在の状態の評価値の比となります（ただし1を超える場合は1に制限されます）。
4. 一様分布から生成した0と1の間の乱数を受け入れ確率と比較し、新しい状態を受け入れるかどうかを決定します。この乱数が受け入れ確率より小さい場合、新しい状態が受け入れられ、そうでなければ現在の状態が保持されます。

このプロセスを決められた反復回数だけ繰り返して、結果として各状態が生成されます。

---

### **user**:

ありがとうございます。だいぶ理解が進みました。それでは、MCMCの応用例にはどのようなものがあるか、できるだけ網羅的に、できるだけ詳しく教えてください。

---

### **assistant**:

マルコフ連鎖モンテカルロ（MCMC）は、統計的モデリングやシミュレーションなど、様々な分野で広く利用されています。以下に、そのいくつかの例を挙げます。

1. **ベイズ統計モデリング**: MCMCはベイズ統計モデリングにおけるパラメータの事後分布をサンプリングするのに一般的に使用されます。具体的な例としては、ベイズ線形回帰、ロジスティク回帰などがあります。

2. **機械学習**: 構造化されたデータからパラメータを推定するためにMCMCが使用されます。具体的な例としては、ランダムフォレストなどの決定木ベースのアルゴリズム、トピックモデリング（Latent Dirichlet AllocationやHierarchical Dirichlet Processなど）があります。

3. **空間統計学**: 空間相関のあるデータを扱う場合にMCMCがしばしば利用されます。例えば、疫学や地理学では、感染病の伝播モデルや地域間の気候変動のパターンなどを予測するために利用されます。

4. **物理学や化学**: 物理学や化学では、分子動力学シミュレーションや量子力学の計算などにMCMCが利用されます。複雑な多体系をシミュレーションする際に、確率的手法が必要となることが多く、その際にMCMCが使用されます。

5. **画像処理**: MCMCは、画像認識や画像復元等の分野で利用されます。例えば、ノイズの多い画像を原画像に復元する問題では、MCMCは最も可能性が高い原画像を求めるのに使われます。

6. **遺伝情報解析**: MCMCは、遺伝的なアルゴリズムや系統分析の分野でよく使用されます。LifeSeqやPHYLIPといったソフトウェアは、MCMCを使用して系統樹を推定します。

以上が主な応用例ですが、これら以外にもMCMCは統計的推測問題を扱う多くの分野で利用されています。

---

### **user**:

それでは、MCMCを用いたベイズ線形回帰について学ぶためのPythonコードを書いてください。コード中に説明を日本語でできるだけ詳しく書いてください。

---

### **assistant**:

ここではパッケージ`PyMC3`を使用してベイズ線形回帰の実装を行います。必要なパッケージをインストールする必要があります。

まず、必要なライブラリをimportします。

```python
import numpy as np
import matplotlib.pyplot as plt
import pymc3 as pm
```

次に、プロット用のデータを生成します。ここでは、線形に近い形をしたデータを生成します。`np.random.normal`はノイズを追加します。

```python
# 真のパラメータ
true_slope = 2
true_intercept = 1

# データ数
n_samples = 100

# データ生成
x = np.linspace(0, 1, n_samples) 
y_true = true_slope * x + true_intercept
y = y_true + np.random.normal(scale=0.5, size=n_samples)  # ノイズを加える

# データをプロット
plt.figure(figsize=(7, 5))
plt.scatter(x, y, label='observed data')
plt.plot(x, y_true, label='truth line', linewidth=2)
plt.legend()
plt.xlabel('x')
plt.ylabel('y')
plt.show()
```



次に、ベイズ線形回帰の定義とMCMCサンプリングを行います。

```python
# PyMC3モデルの定義
linear_model = pm.Model()

with linear_model:
    # 事前分布の定義（正規分布を仮定）
    intercept = pm.Normal('Intercept', mu=0, sd=20)  # 切片
    slope = pm.Normal('slope', mu=0, sd=20)  # 傾き

    # 予測分布の定義（正規分布を仮定）
    y_est = pm.Normal('y_est', mu=slope*x + intercept, sd=1, observed=y)

    # サンプリング
    trace = pm.sample(2000, chains=2)
```

`pm.Normal`で正規分布を定義します。パラメータの名前、平均（mu）、標準偏差（sd）を指定します。

`pm.sample`でMCMCサンプリングを行います。サンプリングするステップ数と、独立したサンプリングの数（chain）を指定します。

最後に結果を表示します。

```python
# 結果の表示
pm.plot_trace(trace)
plt.show()

# 結果の要約
pm.summary(trace).round(2)
```

`pm.plot_trace`で各パラメータのトレースプロットと事後分布を表示します。

`pm.summary`で各パラメータの事後分布の要約統計量を表示します。事後平均、標準偏差、95%ベイズ信頼区間などが表示されます。

---

### **user**:

次のエラーが出たので修正してください。

```
/home/ikemen/.local/lib/python3.10/site-packages/theano/scalar/basic.py:2412: FutureWarning: In the future `np.bool` will be defined as the corresponding NumPy scalar.
  self.ctor = getattr(np, o_type.dtype)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[6], line 3
      1 import numpy as np
      2 import matplotlib.pyplot as plt
----> 3 import pymc3 as pm

File ~/.local/lib/python3.10/site-packages/pymc3/__init__.py:45
     41         blas_info = numpy.distutils.system_info.get_info("blas_opt")
     43     numpy.distutils.__config__.blas_opt_info = blas_info
---> 45 import theano
     47 _log = logging.getLogger("pymc3")
     49 if not logging.root.handlers:

File ~/.local/lib/python3.10/site-packages/theano/__init__.py:83
     75 # This is the api version for ops that generate C code.  External ops
     76 # might need manual changes if this number goes up.  An undefined
     77 # __api_version__ can be understood to mean api version 0.
     78 #
     79 # This number is not tied to the release version and should change
     80 # very rarely.
     81 __api_version__ = 1
---> 83 from theano import scalar, tensor
     84 from theano.compile import (
     85     In,
     86     Mode,
   (...)
     93     shared,
     94 )
     95 from theano.compile.function import function, function_dump

File ~/.local/lib/python3.10/site-packages/theano/scalar/__init__.py:1
----> 1 from .basic import *
      2 from .basic_scipy import *

File ~/.local/lib/python3.10/site-packages/theano/scalar/basic.py:2460
   2456         else:
   2457             return s
-> 2460 convert_to_bool = Cast(bool, name="convert_to_bool")
   2461 convert_to_int8 = Cast(int8, name="convert_to_int8")
   2462 convert_to_int16 = Cast(int16, name="convert_to_int16")

File ~/.local/lib/python3.10/site-packages/theano/scalar/basic.py:2412, in Cast.__init__(self, o_type, name)
   2410 super().__init__(specific_out(o_type), name=name)
   2411 self.o_type = o_type
-> 2412 self.ctor = getattr(np, o_type.dtype)

File /usr/local/lib/python3.10/site-packages/numpy/__init__.py:305, in __getattr__(attr)
    300     warnings.warn(
    301         f"In the future `np.{attr}` will be defined as the "
    302         "corresponding NumPy scalar.", FutureWarning, stacklevel=2)
    304 if attr in __former_attrs__:
--> 305     raise AttributeError(__former_attrs__[attr])
    307 # Importing Tester requires importing all of UnitTest which is not a
    308 # cheap import Since it is mainly used in test suits, we lazy import it
    309 # here to save on the order of 10 ms of import time for most users
    310 #
    311 # The previous way Tester was imported also had a side effect of adding
    312 # the full `numpy.testing` namespace
    313 if attr == 'testing':

AttributeError: module 'numpy' has no attribute 'bool'.
`np.bool` was a deprecated alias for the builtin `bool`. To avoid this error in existing code, use `bool` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.bool_` here.
The aliases was originally deprecated in NumPy 1.20; for more details and guidance see the original release note at:
    https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
```

---

### **assistant**:

エラーメッセージを見ると、`theano`が`np.bool`を使用していて、それが`numpy`の新しいバージョンでは非推奨となってしまいエラーが発生しているようです。従って、`numpy`のバージョンを下げることで解決することができます。

まず`numpy`のバージョンをチェックします。

```python
import numpy
print(numpy.__version__)
```

次に、バージョンを下げます。ターミナルで以下のコマンドを実行します。

```
pip uninstall numpy
pip install numpy==1.19.5
```

このようにすることで、`numpy`のバージョンを1.19.5にすることができます。

それから先のコードを実行します。もし同じエラーが出る場合は、再度ターミナルを開き直して実行します。 これは、ターミナルが古い`numpy`をキャッシュしている可能性があるためです。

---

### **user**:

PyMC3を使いたくないです。PyMC3を使わずに、MCMCを用いたベイズ線形回帰について学ぶためのPythonコードを書いてください。コード中に説明を日本語でできるだけ詳しく書いてください。

---

### **assistant**:

もちろんです。ここでは先ほど説明したMCMCの基本的なコードを使ってベイズ線形回帰の例を作成します。

```python
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(0)

# 真のパラメータ
true_slope = 2
true_intercept = 1

# データ数
n_samples = 100

# データ生成
x = np.linspace(0, 1, n_samples)
y_true = true_slope * x + true_intercept
y = y_true + np.random.normal(scale=0.5, size=n_samples)  # ノイズを加える

def prior(params):
    intercept, slope = params
    return 1.  # 一様事前分布

def likelihood(params, x, y):
    intercept, slope = params
    y_pred = slope * x + intercept
    residuals = y - y_pred
    ss = np.sum(residuals**2)
    return np.exp(-ss / 2.)  # 正規分布の尤度関数

def proposal_fn(params):
    return params + np.random.normal(size=len(params))  # 正規分布に従ってパラメータを生成

# MCMCルーチン
n_iteration = 2000
initial_state = [0., 0.]  # experienceで見つけた良い初期値
states = MCMC(n_iteration, initial_state, proposal_fn, {},
              lambda x: prior(x) * likelihood(x, x=x, y=y), {})

# 結果のプロット
plt.figure(figsize=(7, 5))
plt.plot(states[:, 0], label='intercept')
plt.plot(states[:, 1], label='slope')
plt.legend()
plt.xlabel('Iteration')
plt.ylabel('Parameter value')
plt.show()

# 収束後の平均値を確認
print('Intercept:', np.mean(states[int(n_iteration / 2):, 0]))
print('Slope:', np.mean(states[int(n_iteration / 2):, 1]))
```

このコードでは、MCMCを用いてベイズ線形回帰のパラメータを推定します。

このコードには4つの主要な部分があります。

1. 真のパラメータ（`true_slope`, `true_intercept`）を設定し、それに基づいてデータを生成します（ノイズを持つ線形関係）。

2. `prior`関数（事前分布）、`likelihood`関数（尤度関数）、`proposal_fn`（提案分布）を定義します。ここでは、事前分布として一様分布、尤度関数として正規分布（二乗誤差を使用）、提案分布として現在のパラメータに正規乱数を加えたものを使用しています。

3. MCMCのルーチンを実行します。初期状態（`initial_state`）、提案関数（`proposal_fn`）、評価関数（事前分布と尤度関数の積）を入力として使用します。

4. 最後に、推定したパラメータのトレース（パラメータのサンプル値の系列）をプロットし、後半のサンプルを取ってその平均値（バーンイン後の期待値の近似値）を表示します。これがベイズ推定における点推定値です。

以上で、MCMCを用いたベイズ線形回帰の基本的なプロセスが示されています。

---

### **user**:

次のエラーが出たので修正したコードを書いてください。

```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[7], line 35
     33 n_iteration = 2000
     34 initial_state = [0., 0.]  # experienceで見つけた良い初期値
---> 35 states = MCMC(n_iteration, initial_state, proposal_fn, {},
     36               lambda x: prior(x) * likelihood(x, x=x, y=y), {})
     38 # 結果のプロット
     39 plt.figure(figsize=(7, 5))

Cell In[1], line 6, in MCMC(n_iteration, initial_state, prop_fn, prop_fn_args, eval_fn, eval_fn_args)
      4 states = np.zeros((n_iteration, len(initial_state)))
      5 states[0, :] = initial_state
----> 6 current_eval = eval_fn(initial_state, **eval_fn_args)
      8 for i in range(1, n_iteration):
      9     proposal = prop_fn(states[i-1, :], **prop_fn_args)

Cell In[7], line 36, in <lambda>(x)
     33 n_iteration = 2000
     34 initial_state = [0., 0.]  # experienceで見つけた良い初期値
     35 states = MCMC(n_iteration, initial_state, proposal_fn, {},
---> 36               lambda x: prior(x) * likelihood(x, x=x, y=y), {})
     38 # 結果のプロット
     39 plt.figure(figsize=(7, 5))

Cell In[7], line 24, in likelihood(params, x, y)
     22 def likelihood(params, x, y):
     23     intercept, slope = params
---> 24     y_pred = slope * x + intercept
     25     residuals = y - y_pred
     26     ss = np.sum(residuals**2)

TypeError: can't multiply sequence by non-int of type 'float'
```

---

### **assistant**:

このエラーは、`lambda x: prior(x) * likelihood(x, x=x, y=y)`の部分で`x`が2回使用されているため、`likelihood(x, x=x, y=y)`が評価されるときに`x`がリストとなってしまい、リストはフロートとの乗算ができないためエラーが起きています。

ここで行いたいことは、パラメータのセットに対し事前分布と尤度関数の積を計算することなので、

```python 
lambda params: prior(params) * likelihood(params, x=x, y=y)
```
とすべきです。

したがって、修正後のコードは以下の通りになります：

```python
n_iteration = 2000
initial_state = [0., 0.]  # experienceで見つけた良い初期値
states = MCMC(n_iteration, initial_state, proposal_fn, {},
              lambda params: prior(params) * likelihood(params, x=x, y=y), {})

# 結果のプロット
plt.figure(figsize=(7, 5))
plt.plot(states[:, 0], label='intercept')
plt.plot(states[:, 1], label='slope')
plt.legend()
plt.xlabel('Iteration')
plt.ylabel('Parameter value')
plt.show()

# 収束後の平均値を確認
print('Intercept:', np.mean(states[int(n_iteration / 2):, 0]))
print('Slope:', np.mean(states[int(n_iteration / 2):, 1]))
```

![output_7_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/9146b33e-925d-6b6f-7eda-aca38568750f.png)



    Intercept: 1.1599357577413507
    Slope: 1.7394496566415143



このコードで正しく動作するはずです。

---

### **user**:

ありがとうございます。MCMCを用いたベイズ線形回帰は、普通の線形回帰と比べて、どのように違うのでしょうか？

---

### **assistant**:

統計的な観点から、普通の線形回帰（最小二乗法や最尤推定など）とベイズ線形回帰（MCMCなどを使ってパラメーターを推定）の違いは、パラメーターの不確実性をどのように扱っているかと言う面で見ることができます。

**普通の線形回帰**：
普通の線形回帰では、パラメーター（切片や傾き）は点推定されます。つまり、与えられたデータに基づいて、「最適な」パラメーターを一つだけ計算します。パラメーターの不確実性（例えば、標本の変動による影響）は考慮されません。ただし、信頼区間を計算することで間接的に不確実性を分析することは可能ですが、それはしばしば正規性といった特定の仮説を必要とします。

**ベイズ線形回帰**：
一方、ベイズ線形回帰では、パラメーターの不確実性が直接的にモデル化されます。パラメーターは確率変数として扱われ、事前分布とデータを組み合わせてその事後分布を求めます。パラメーターの推定値は事後分布の期待値や中央値を使うことで得られますが、事後分布全体を使ってパラメーターの不確実性を解析することが可能です。また、ベイズ手法は正規性といった特定の仮説を必要としません。

したがって、ベイズ線形回帰はパラメーターの不確実性を直接的に取り扱い、それが結果の解釈に役立つ場合に特に有益です。例えば、予測区間の計算やパラメーターの信頼性の評価などです。一方、普通の線形回帰はよりシンプルで、インタープリタビリティがあり、計算上もより効率的であることが特長です。

---

### **user**:

ありがとうございます。それでは、PyMC3を使わずに、MCMCを用いたベイズ線形回帰について学ぶためのPythonコードを書いてください。コード中に説明を日本語でできるだけ詳しく書いてください。予測区間の計算やパラメーターの信頼性の評価なども行なったコードを書いてください。

---

### **assistant**:

もちろんです、以下にパラメータ推定だけでなく、予測区間の計算とパラメータの信頼性評価も含めたPythonコードを示します。

```python
import numpy as np
import matplotlib.pyplot as plt

# MCMC関数
def MCMC(n_iteration, initial_state, proposal_fn, eval_fn):
    states = np.zeros((n_iteration, len(initial_state)))
    states[0, :] = initial_state
    current_eval = eval_fn(initial_state)
    for i in range(1, n_iteration):
        proposal = proposal_fn(states[i-1, :])
        proposal_eval = eval_fn(proposal)
        if proposal_eval / current_eval > np.random.rand():
            states[i, :] = proposal
            current_eval = proposal_eval
        else:
            states[i, :] = states[i-1, :]
    return states

np.random.seed(0)

# 真のパラメータ
true_slope = 2
true_intercept = 1

# データ数
n_samples = 100

# データ生成
x = np.linspace(0, 1, n_samples)
y_true = true_slope * x + true_intercept
y = y_true + np.random.normal(scale=0.5, size=n_samples)  # ノイズを加える
   
### 各種関数の定義
# 事前分布
def prior(params):
    intercept, slope = params
    return 1  # 一様分布

# 尤度関数
def likelihood(params, x, y):
    intercept, slope = params
    y_pred = slope * x + intercept
    residuals = y - y_pred
    ss = np.sum(residuals**2)
    return np.exp(-ss / (2 * 0.5**2))  # 正規分布の尤度関数、分散0.5^2を仮定

# 提案分布
def proposal_fn(params):
    return params + np.random.normal(size=len(params))  # 正規分布に従いランダムウォーク

# 事後分布
def posterior(params, x, y):
    return prior(params) * likelihood(params, x, y)

# MCMC
n_iteration = 5000
initial_state = [0., 0.]
states = MCMC(n_iteration, initial_state, proposal_fn,
              lambda params: posterior(params, x=x, y=y))

# 結果のプロット
plt.figure(figsize=(14, 10))

plt.subplot(2, 2, 1)
plt.plot(states[:, 0]);
plt.xlabel('Iteration')
plt.ylabel('Intercept')

plt.subplot(2, 2, 2)
plt.plot(states[:, 1]);
plt.xlabel('Iteration')
plt.ylabel('Slope')

plt.subplot(2, 2, 3)
plt.hist(states[:, 0], bins=10, density=True);
plt.xlabel('Intercept')
plt.ylabel('Frequency')

plt.subplot(2, 2, 4)
plt.hist(states[:, 1], bins=10, density=True);
plt.xlabel('Slope')
plt.ylabel('Frequency')

plt.tight_layout()
plt.show()

print('Intercept:', np.mean(states[int(n_iteration / 2):, 0]))
print('Slope:', np.mean(states[int(n_iteration / 2):, 1]))

# 予測区間の計算
burnin = int(n_iteration / 2)
y_preds = [states[i, 0] + states[i, 1]*x for i in range(burnin, n_iteration)]
y_pred_lower = np.percentile(y_preds, 2.5, axis=0)
y_pred_upper = np.percentile(y_preds, 97.5, axis=0)

# プロット
plt.figure(figsize=(10, 5))
plt.plot(x, y, 'o')
plt.plot(x, y_true, 'k--', label='True regression line')
plt.plot(x, states[-1, 0] + states[-1, 1]*x, 'r-', label='Estimated regression line')
plt.fill_between(x, y_pred_lower, y_pred_upper, color='red', alpha=0.3, label='95% predictive interval')
plt.legend(loc='best')
plt.xlabel('x')
plt.ylabel('y')
plt.show()
```

![output_8_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/b2a9fa4c-1434-c7df-e75e-34c80871e162.png)

    Intercept: 1.064459724924346
    Slope: 1.91879087855413


![output_8_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/952c130d-4c69-e712-bd77-9977b58e363c.png)



このコードでは、データを生成した後に、事前分布、尤度関数、提案分布、事後分布を定義し、それらを使ってMCMCのサンプリングを行います。その結果から、横軸を繰り返し回数、縦軸をパラメータ値としたトレースプロットと、パラメータのヒストグラムをプロットしています。

さらに、予測区間の計算を行なっています。具体的には、MCMCサンプリングの後半部分（バーンイン後）からサンプリングしたパラメータ値による予測値の2.5パーセンタイルと97.5パーセンタイルを取り、それらを埋めて予測区間を示しています。

このコードにより、パラメータの不確実性が結果にどのように影響するかを確認できます。また、予測区間が実際の値をどれだけカバーしているかも確認できます。

---

### **user**:

ありがとうございます。それでは、PyMC3を使わずに、MCMCを用いたベイズ統計モデリングによるロジスティック回帰について学ぶためのPythonコードを書いてください。コード中に説明を日本語でできるだけ詳しく書いてください。

---

### **assistant**:

ロジスティック回帰を扱う際、尤度関数としてベルヌーイ分布（二項分布の特殊ケース）を選択します。そのリンク関数としてロジスティック関数を用いるのが一般的です。ロジスティック関数は実数全体を定義域とし、[0, 1] の範囲の値を取るため、確率を表現するのに適しています。以下にロジスティック回帰のMCMCサンプリングを実装したPythonコードを記載します。

```python
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import expit

# MCMC関数
def MCMC(n_iteration, initial_state, proposal_fn, eval_fn):
    states = np.zeros((n_iteration, len(initial_state)))
    states[0, :] = initial_state
    current_eval = eval_fn(initial_state)
    for i in range(1, n_iteration):
        proposal = proposal_fn(states[i-1, :])
        proposal_eval = eval_fn(proposal)
        if proposal_eval / current_eval > np.random.rand():
            states[i, :] = proposal
            current_eval = proposal_eval
        else:
            states[i, :] = states[i-1, :]
    return states

np.random.seed(0)

# 真のパラメータ
true_slope = 10
true_intercept = -5

# データ数
n_samples = 500

# データ生成
x = np.random.uniform(-1, 1, n_samples)  # -1から1までの一様分布
prob = expit(true_intercept + true_slope * x)  # ロジスティック関数を適用
y = np.random.binomial(1, prob)  # ベルヌーイ分布からサンプリング
    
### 各種関数の定義
# 事前分布
def prior(params):
    intercept, slope = params
    return 1  # 一様分布

# 尤度関数
def likelihood(params, x, y):
    intercept, slope = params
    prob = expit(intercept + slope * x)
    log_like = y * np.log(prob) + (1 - y) * np.log(1 - prob)
    return np.exp(log_like.sum())  # ロジスティック回帰の尤度関数

# 提案分布 (正規分布に基づくランダムウォーク)
def proposal_fn(params):
    return params + np.random.normal(size=len(params))

# 事後分布
def posterior(params, x, y):
    return prior(params) * likelihood(params, x, y)

# MCMC
n_iteration = 50000
initial_state = [0., 0.]
states = MCMC(n_iteration, initial_state, proposal_fn,
               lambda params: posterior(params, x=x, y=y))

# サンプルのバーンイン
burnin = int(n_iteration / 2)
states = states[burnin:]

# 結果のプロット
plt.figure(figsize=(14, 5))

plt.subplot(1, 2, 1)
plt.hist(states[:, 0], bins=20, density=True);
plt.xlabel('Intercept')
plt.ylabel('Frequency')

plt.subplot(1, 2, 2)
plt.hist(states[:, 1], bins=20, density=True);
plt.xlabel('Slope')
plt.ylabel('Frequency')

plt.tight_layout()
plt.show()

print('Intercept:', np.mean(states[:, 0]))
print('Slope:', np.mean(states[:, 1]))
```

![output_9_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/31110db0-b863-3833-c5da-10db7f01b156.png)


    Intercept: -5.628600751495258
    Slope: 11.728272389288838



このコードは二項分布から生成されたデータに対して、MCMCを用いてベイズロジスティック回帰のパラメータを推定します。尤度関数としてベルヌーイ分布を用いています。このモデルでは、目的変数が二値（0と1）で、それぞれが特定の確率で発生すると考えています。この確率は線形予測子（ここでは`intercept + slope * x`）を通したロジスティック関数により決定されます。

MCMCのサンプリング結果から、intercept（切片）とslope（係数）の事後分布をヒストグラムとしてプロットしています。これを通じて、パラメータの不確実性を観察することができます。

ただし、ここで用いているMetropolis-Hastings法は一般的なMCMCの一つですが、収束や混合が遅いことが問題となることがあります。実際にベイジアンモデリングを行う際には、より高度なMCMCの手法（例えば、Hamiltonian Monte Carloなど）を使うことを検討してみてください。これらの手法はPyMC3やStanなどのベイジアンモデリングライブラリで利用することができます。

---

### **user**:

PyMC3を使わずに、MCMCを用いたベイズ統計モデリングによるロジスティック回帰について学ぶためのPythonコードを書いてください。コード中に説明を日本語でできるだけ詳しく書いてください。予測区間の計算やパラメーターの信頼性の評価なども行なったコードを書いてください。

---

### **assistant**:

以下にパラメータ推定だけでなく、予測とパラメータの信頼性評価も含めたPythonコードを示します。なお、ロジスティック回帰の場合、予測結果は確率なので、「予測区間」の代わりに「信頼区間」を示すこととします。

```python
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.special import expit

# MCMC関数
def MCMC(n_iteration, initial_state, proposal_fn, eval_fn):
    states = np.zeros((n_iteration, len(initial_state)))
    states[0, :] = initial_state
    current_eval = eval_fn(initial_state)
    for i in range(1, n_iteration):
        proposal = proposal_fn(states[i-1, :])
        proposal_eval = eval_fn(proposal)
        if proposal_eval / current_eval > np.random.rand():
            states[i, :] = proposal
            current_eval = proposal_eval
        else:
            states[i, :] = states[i-1, :]
    return states

np.random.seed(0)

# 真のパラメータ
true_slope = 10
true_intercept = -5

# データ数
n_samples = 500

# データ生成
x = np.random.uniform(-1, 1, n_samples)  # -1から1までの一様分布
prob = expit(true_slope * x + true_intercept)  # ロジスティック関数を適用
y = np.random.binomial(1, prob)  # ベルヌーイ分布からサンプリング

### 各種関数の定義
# 事前分布
def prior(params):
    intercept, slope = params
    return 1  # 一様分布

# 尤度関数
def likelihood(params, x, y):
    intercept, slope = params
    prob = expit(intercept + slope * x)
    log_like = y * np.log(prob) + (1 - y) * np.log(1 - prob)
    return np.exp(log_like.sum())  # ロジスティック回帰の尤度関数

# 提案分布
def proposal_fn(params):
    return params + np.random.normal(size=len(params))  # 正規分布に従いランダムウォーク

# 事後分布
def posterior(params, x, y):
    return prior(params) * likelihood(params, x, y)

# MCMC
n_iteration = 5000
initial_state = [0., 0.]
states = MCMC(n_iteration, initial_state, proposal_fn, lambda params: posterior(params, x=x, y=y))

# 結果のプロット
plt.figure(figsize=(14, 5))

plt.subplot(1, 2, 1)
plt.hist(states[:, 0], bins=10, density=True);
plt.xlabel('Intercept')
plt.ylabel('Frequency')

plt.subplot(1, 2, 2)
plt.hist(states[:, 1], bins=10, density=True);
plt.xlabel('Slope')
plt.ylabel('Frequency')

plt.tight_layout()
plt.show()

print('Intercept:', np.mean(states[int(n_iteration / 2):, 0]))
print('Slope:', np.mean(states[int(n_iteration / 2):, 1]))

# 信頼区間の計算
burnin = int(n_iteration / 2)
probs = [np.mean(expit(states[i, 0] + states[i, 1]*x)) for i in range(burnin, n_iteration)]
prob_lower = np.percentile(probs, 2.5)
prob_upper = np.percentile(probs, 97.5)

# プロット
x_plot = np.linspace(-1, 1, 100)
plt.figure(figsize=(10, 5))
plt.scatter(x, y, color='blue', label='True data')
plt.plot(x_plot, expit(states[-1, 0] + states[-1, 1]*x_plot), 'r-', label='Estimated logistic regression')
plt.fill_between(x_plot, prob_lower, prob_upper, color='red', alpha=0.3, label='95% CI')
plt.legend(loc='best')
plt.xlabel('x')
plt.ylabel('Probability')
plt.show()
```

![output_10_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/dbe7e9fd-68d8-91b7-bb29-6cf6be80831f.png)


    Intercept: -5.706224189114745
    Slope: 11.878639745038015


![output_10_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/e376a47f-31be-585c-c980-9f7e88497dc2.png)


このコードでは、データを生成した後、事前分布、尤度関数、提案分布、事後分布を定義し、それらを使ってMCMCのサンプリングを行います。その結果から、横軸を繰り返し回数、縦軸をパラメータ値としたトレースプロットと、パラメータのヒストグラムをプロットしています。

そして、予測信頼区間の計算も行なっています。具体的には、MCMCサンプリングの後半部分（バーンイン後）で得られたパラメーターによって算出される確率の2.5パーセンタイルと97.5パーセンタイルを用いて信頼区間を計算し、それを図示しています。

以上の結果から、予測の不確実性とパラメーターの信頼性を確認することができます。さらに、それぞれのパラメーターがどの程度の頻度で取られたかをヒストグラムから確認することができます。

---

### **user**:

MCMCを用いたベイズ統計モデリングによるロジスティック回帰において、予測信頼区間は予測結果に対して計算されるのですか？なんか違う気がするのですが。

---

### **assistant**:

おっしゃる通り、その表現は混乱を生む恐れがありすみません。ロジスティック回帰の「予測」が意味するところは、特に分類問題の場合、各クラスに属する確率を予測することです。それ故、ここでの「信頼区間」は、新たな入力に対して予測される「確率」に対する区間を意味します。

その予測の不確実性を扱う場合は、パラメータの事後分布からサンプリングされた各サンプル（セットのパラメータ）に対して予測を行い、その分布を見ることで「信頼区間」を計算します。これは「事後予測確率」または「予測区間」と呼ばれることが多いです。

再度、適切なコードを以下に示します。

```python
# 事後予測確率の計算
x_test = np.linspace(-1, 1, 100)  
y_preds = [np.random.binomial(1, expit(states[i, 0] + states[i, 1]*x_test)) for i in range(burnin, n_iteration)]  
y_preds = np.array(y_preds)

# 予測区間の計算
y_lower = np.percentile(y_preds, 2.5, axis=0)
y_upper = np.percentile(y_preds, 97.5, axis=0)

# プロット
plt.figure(figsize=(10, 5))
sns.stripplot(x, y, color='blue', jitter=0.2, size=2.5)
plt.plot(x_test, np.mean(y_preds,axis=0), 'r-', label='Estimated logistic regression')
plt.fill_between(x_test, y_lower, y_upper, color='red', alpha=0.3, label='95% prediction interval')
plt.legend(loc='best')
plt.xlabel('x')
plt.ylabel('Predicted y')
plt.show()
```
このコードでは、テストデータ（`x_test`）に対して、MCMCで得られた各セットのパラメータを用いて予測（`y_pred`）を行います。それから、予測の信頼区間（2.5パーセンタイルから97.5パーセンタイル）を求めて図示します。この信頼区間は、予測の不確実性を示しています。

これで、「xが特定の値をとったときにyが1になる確率」の予測と、「yが1になる」予測区間とを同時に示すことができます。

---

### **user**:

次のエラーが出たので修正したコードを書いてください。

```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[12], line 12
     10 # プロット
     11 plt.figure(figsize=(10, 5))
---> 12 sns.stripplot(x, y, color='blue', jitter=0.2, size=2.5)
     13 plt.plot(x_test, np.mean(y_preds,axis=0), 'r-', label='Estimated logistic regression')
     14 plt.fill_between(x_test, y_lower, y_upper, color='red', alpha=0.3, label='95% prediction interval')

TypeError: stripplot() takes from 0 to 1 positional arguments but 2 positional arguments (and 3 keyword-only arguments) were given
<Figure size 1000x500 with 0 Axes>
```

---

### **assistant**:

ご依頼のエラーは、`sns.stripplot()`関数へパラメータが正しく渡されていないために発生しています。`x`、`y`パラメータをキーワード引数として指定しましょう。以下のようにコードを修正します。

```python
# プロット
plt.figure(figsize=(10, 5))
sns.stripplot(x=x, y=y, color='blue', jitter=0.2, size=2.5)
plt.plot(x_test, np.mean(y_preds,axis=0), 'r-', label='Estimated logistic regression')
plt.fill_between(x_test, y_lower, y_upper, color='red', alpha=0.3, label='95% prediction interval')
plt.legend(loc='best')
plt.xlabel('x')
plt.ylabel('Predicted y')
plt.show()
```

![output_12_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/8025728b-4280-ed1e-9dd6-27857efac77d.png)


このようにすることで、`sns.stripplot()`関数に正しいパラメータが渡され、エラーが解消されるはずです。

---

### **user**:

うまく説明できないですが、図が何かおかしいです。ここまでの修正を踏まえて、PyMC3を使わずに、MCMCを用いたベイズ統計モデリングによるロジスティック回帰について学ぶためのPythonコードを書いてください。コード中に説明を日本語でできるだけ詳しく書いてください。予測区間の計算やパラメーターの信頼性の評価なども行なったコードを書いてください。

---

### **assistant**:

失礼しました。事後予測確率の図示には混乱があるかもしれません。確率の平均ではなく、0や1のどちらに分類されるかの予測結果を取得すると、図は直感的になります。以下に、ベイズ統計モデリングを用いたロジスティック回帰のPythonコードを示します。

```python
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import expit
from scipy.stats import bernoulli

np.random.seed(0)

# 真のパラメータ
true_intercept = -5
true_slope = 10

# データ数
n_samples = 500

# データ生成
x = np.random.uniform(-1, 1, n_samples)  # -1から1までの一様分布
prob = expit(true_intercept + true_slope * x)  # ロジスティック関数を適用
y = np.random.binomial(1, prob)  # ベルヌーイ分布からサンプリング

# 事前分布 (一様分布)
def prior(params):
    return 1.0

# 尤度関数
def likelihood(params, x, y):
    intercept, slope = params
    p = expit(intercept + slope * x)  # ロジスティック関数で確率を求める
    return bernoulli.pmf(y, p).prod()

# 提案分布（正規分布）
def proposal_fn(params):
    return np.random.normal(params, 1.0)

# 事後分布
def posterior(params, x, y):
    return prior(params) * likelihood(params, x, y)

# MCMCサンプリング関数
def MCMC(n_iter, initial_state, proposal_fn, eval_fn):
    states = np.zeros((n_iter, len(initial_state)))
    states[0, :] = initial_state
    current_eval = eval_fn(initial_state)
    for i in range(1, n_iter):
        proposal = proposal_fn(states[i-1, :])
        proposal_eval = eval_fn(proposal)
        if proposal_eval / current_eval > np.random.rand():
            states[i, :] = proposal
            current_eval = proposal_eval
        else:
            states[i, :] = states[i-1, :]
    return states

# MCMCサンプリングの実行
n_iteration = 5000
initial_state = np.array([0., 0.])
states = MCMC(n_iteration, initial_state, proposal_fn, lambda params: posterior(params, x=x, y=y))

# 結果の表示
burn_in = int(n_iteration / 2)
print(f"Intercept: {np.mean(states[burn_in:, 0])}")
print(f"Slope: {np.mean(states[burn_in:, 1])}")

# 事後分布からサンプリングされたパラメータを使って予測を行う
x_test = np.linspace(-1, 1, 100)
simulated_ys = [bernoulli.rvs(expit(states[i, 0] + states[i, 1]*x_test)) for i in range(burn_in, n_iteration)]
y_lower = np.percentile(simulated_ys, 2.5, axis=0)
y_upper = np.percentile(simulated_ys, 97.5, axis=0)

# 結果のプロッティング
plt.figure(figsize=(8, 6))
plt.scatter(x, y, color='blue', label='Data')
plt.plot(x_test, np.mean(simulated_ys,axis=0), 'r-', label='Estimated logistic regression')
plt.fill_between(x_test, y_lower, y_upper, color='red', alpha=0.3, label=f'95% prediction interval')
plt.legend(loc='best')
plt.xlabel('$x$')
plt.ylabel('$y$')
plt.show()
```


    Intercept: -5.706224189114745
    Slope: 11.878639745038015

![output_13_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/416e57a1-d708-d6b4-02f5-1739fe88f896.png)


このコードは、真のモデルパラメータに基づいてデータを生成し、そのデータを使ってパラメータの最尤推定を行うものです。MCMCの各イテレーション毎に、新しいパラメータ提案を行い、その提案がデータに対してより尤もらしいか（尤度が高いか）を評価します。それに基づいて提案を受け入れるか判定し、サンプリングを進めます。その結果から、予測値と予測の不確実性（予測区間）を算出します。

最後に図示することで、実際のデータと推定されたロジスティック回帰モデル、そして95%の予測区間を視覚化します。

---

### **user**:

ありがとうございます。同様にして、MCMCを用いたベイズ統計モデリングによる多項式近似なんていうのも可能でしょうか？

---

### **assistant**:

もちろん可能です。ロジスティック回帰と同様に、多項式回帰もMCMCサンプリングでパラメータを推定できます。

ただし、多項式近似の場合、次数が増えるほどパラメータの数が増えるので、事後分布の次元が増えます。そのため、事後分布が複雑な形状を取ることがあり、適切なMCMCの設計や収束の確認がより重要になります。

以下に、ベイズ統計モデリングを用いた多項式回帰の一例を示します。この例では2次の多項式回帰を実行しています。

```python
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(0)

# 真のパラメータ
true_params = [-0.5, 0.5, 1]

# データ数
n_samples = 50

# データ生成
x = np.sort(np.random.uniform(-1, 1, n_samples)) # -1から1まで一様分布
y = np.polynomial.polynomial.polyval(x, true_params) + np.random.normal(scale=0.5, size=n_samples)  # ノイズを加える

# 事前分布
def prior(params):
    return 1.  # 一様分布

# 尤度関数
def likelihood(params, x, y):
    mu = np.polynomial.polynomial.polyval(x, params)
    return np.exp(-np.sum((y - mu)**2) / 2.)

# 提案分布
def proposal_fn(params):
    return np.random.normal(params, 0.5, len(params))  # 正規分布に従いランダムウォーク

# 事後分布
def posterior(params, x, y):
    return prior(params) * likelihood(params, x, y)

# MCMC
n_iteration = 5000
initial_state = np.array([0., 0., 0.])
states = MCMC(n_iteration, initial_state, proposal_fn, lambda params: posterior(params, x=x, y=y))

# 結果の表示
burn_in = int(n_iteration / 2)
print(f"Params: {np.mean(states[burn_in:, :], axis=0)}")

# 事後分布からサンプリングされたパラメータを使って予測を行う
x_test = np.linspace(-1.2, 1.2, 100)
simulated_ys = [np.polynomial.polynomial.polyval(x_test, states[i, :]) for i in range(burn_in, n_iteration)]
y_pred = np.mean(simulated_ys, axis=0)
y_lower = np.percentile(simulated_ys, 2.5, axis=0)
y_upper = np.percentile(simulated_ys, 97.5, axis=0)

# 結果のプロッティング
plt.figure(figsize=(8, 6))
plt.scatter(x, y, color='blue', label='Data')
plt.plot(x_test, y_pred, 'r-', label='Estimated polynomial regression')
plt.fill_between(x_test, y_lower, y_upper, color='red', alpha=0.3, label='95% prediction interval')
plt.legend(loc='best')
plt.xlabel('$x$')
plt.ylabel('$y$')
plt.show()
```


    Params: [-0.70046232  0.69907133  1.30411291]

![output_14_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/df97402c-19d3-d6a6-83d5-952742e64f98.png)


このコードでは、真のモデルからデータを生成した後、事前分布、尤度関数、提案分布、事後分布を定義し、MCMCサンプリングでパラメータを推定しています。その結果とデータを基に、多項式近似の結果を図示しています。

また、サンプリングされたパラメータから予測を行い、その予測結果に対する信頼区間をプロットしています。

---

### **user**:

ありがとうございました。おかげでMCMCの理解が深まりました。ここまでPythonを使って行なったことのまとめを詳しく書いてください。

---

### **assistant**:

ここまでの議論をまとめておきます。

1. **データの生成：** ロジスティック回帰と多項式近似のためのサンプルデータを生成しました。これはシミュレーションのためのデータで、真のモデルからサンプリングしました。ロジスティック回帰では、ロジスティック関数を使って確率を生成し、その確率に基づいてベルヌーイ分布からバイナリデータを生成しました。多項式近似では、真のモデルからノイズを含んだ連続データを生成しました。

2. **モデリング：** 双方のケースで、予測変数と応答変数の間の関係を説明するためのモデルを設定しました。ロジスティック回帰では、予測変数と応答変数の確率を結びつけるロジスティック関数を使用しました。多項式近似では、予測変数と応答変数を二次関数で結びつけました。

3. **事前分布の設定：** どちらのケースでも、モデルのパラメータに一様事前分布を設定しました。これは我々がパラメータについて事前に特定の知識を持っていないことを示しています。

4. **尤度関数の設定：** データが生成されたと考えられる確率を定義するための尤度関数を設定しました。

5. **MCMCサンプリング：** 提案分布（正規分布）を用いてパラメータのMCMCサンプリングを行い、パラメータの事後分布を求めました。

6. **パラメータと予測の結果：** MCMCサンプリング後の各パラメータの平均値を表示しました。また、サンプリングされたパラメータに基づいて新しいデータに対する予測を行い、その予測の信頼区間を計算しました。

7. **結果の可視化：** パラメータの事後分布、データと回帰線、予測信頼区間をプロットしました。この可視化により、モデルがどの程度データを適切に捉えているか、また予測の不確実性を理解することができました。

以上が、Pythonを用いたMCMCを使ったベイズ統計モデリング（ロジスティック回帰と多項式近似）のまとめとなります。MCMCは複雑な事後分布からサンプリングを行う強力な手法であり、データからベイズモデリングを通じて識別的な洞察を得るための重要なツールとなっています。

---

### **user**:

MCMCそのものに対するまとめもお願いします。できるだけ詳しく日本語で。

---

### **assistant**:

MCMC（マルコフ連鎖モンテカルロ）は、特に高次元の分布からのサンプリングが困難な場合に用いられる統計的なサンプリング手法で、ベイズ統計モデリングに多く使用されます。ビッグデータや複雑なモデルを扱う際に特に重要となります。

MCMCの基本的な考え方は、「マルコフ連鎖」を用いて「モンテカルロ」サンプリングを行うというものです。

1. **マルコフ連鎖：** これは確率過程で、次の状態が現在の状態のみに依存する性質を持つものを指します。つまり、過去の状態や未来の状態は無視され、現在の状態だけが次の状態を決定します。MCMCにおいては、これを利用して事後分布から順次パラメータをサンプリングしていきます。

2. **モンテカルロ：** これは大量のランダムなサンプリングによって近似解を求める手法を指します。MCMCにおいては、マルコフ連鎖を用いて大量のランダムなサンプリングを行い、その結果から事後分布の近似や、パラメータの予測分布、予測信頼区間等を計算します。

MCMCの一つの重要なアルゴリズムが「メトロポリス・ヘイスティングス法」です。これは、次の状態のパラメータを現在の状態のパラメータからランダムに選び（これを「提案分布」といいます）、その提案が現在の状態よりも尤度が高ければパラメータを更新し、そうでなければある確率で更新します（もしくは更新しない）。これを大量に繰り返すことで、事後分布からのサンプリングが可能となります。

なお、初期のサンプルは初期状態に影響を受けるため一般的に捨てられ、これを「バーンイン期間」といいます。バーンイン期間後のサンプルを用いて各種の統計量を計算します。また、サンプル間に相関があると効率が悪いため、一定間隔（スキップ）ごとにサンプルを取る「シニング」を行うこともあります。

MCMCの利点は、分布の形状が複雑であっても、高次元であっても、事後分布からのサンプリングが可能であること、サンプリング結果から各種統計量（平均値、中央値、パーセンタイルなど）を容易に算出できることです。欠点は計算時間が長いこと、適切な提案分布を設定する必要があること、収束判定が難しいことなどがあります。

以上がMCMCの基本的な概念となります。より深い理解を得るには、具体的な問題に適用してみる、各種のアルゴリズム（メトロポリス・ヘイスティングス法やギブスサンプリング等）を勉強する、ハミルトニアンモンテカルロ法などの最新の手法について学ぶなどが有効です。

---

### **user**:

マルコフ連鎖は、「過去の状態や未来の状態は無視され、現在の状態だけが次の状態を決定します」とのことですが、それならば、勾配降下法(gradient descent)もマルコフ連鎖の一種と考えて良いのでしょうか？

---

### **assistant**:

勾配降下法も確かに、「現在の状態から次の状態を決定する」という点でマルコフ連鎖に類似した構造を持つと言えます。しかし、その動作原理や目的、使用される文脈などがマルコフ連鎖とは異なるため、通常は両者を同じカテゴリに分類することはありません。

勾配降下法は最適化問題を解くためのアルゴリズムであり、関数の勾配（=最も急な下降方向）を使って順次的に解を更新していきます。つまり、現在地から見た「最も良い」選択を常に選ぶという、「貪欲な」アルゴリズムと言えます。

一方、マルコフ連鎖モンテカルロ（MCMC）方法は、特定の確率分布からのサンプリングを目的とした手法で、現状からランダムに状態を選び、それが改善であれば採用し、そうでなくてもある確率で採用します。これは意図的に「最適でない」選択を許容することで、確率分布全体を探索するのが目的です。

このように、勾配降下法とMCMCは、両者ともに「現在の状態から次の状態を決定する」という機構を持つものの、その行動原理や目指す結果、そして使用される文脈が大きく異なります。そのため、通常は勾配降下法をマルコフ連鎖の一種とは考えないでしょう。

---

### **user**:

では、確率的勾配降下法（stochastic gradient descent, SGD）は、どうでしょうか？マルコフ連鎖とは言えないのでしょうか？

---

### **assistant**:

確率的勾配降下法（SGD）もまた、基本的には勾配降下法と同様の考え方で最適化問題を解きます。しかし、SGDは一度に全てのデータを使うのではなく、ランダムに選んだ一部のデータ（ミニバッチ）だけを使って勾配を求め、パラメータを更新します。

SGDの各ステップにおける更新はランダム性を持つため、一種の確率過程と言えます。そして、次の状態が現在の状態にのみ依存して決まるという意味ではマルコフ性を持ちます。したがって、厳密には「SGDはマルコフ連鎖の一種である」とも言えますが、以下のような理由から実際のところ一般的にはそのようには考えられていません。

1. 目的：SGDは最適化問題の解を見つけることが目的であり、確率分布からのサンプリングは目的ではありません。一方、MCMCは確率分布からサンプリングが第一の目的です。
 
2. ランダム性の源：SGDのランダム性は訓練データのサブセットをランダムに選択することから来ていますが、MCMCのランダム性は提案分布から次の状態をサンプリングすることから来ています。これらは本質的に異なる操作です。

3. 文脈：SGDは主にディープラーニングや他の機械学習のタスクで用いられますが、MCMCはベイジアン統計や計算物理学などのより広範な分野で用いられます。

以上のような理由から、一般的にはSGDを「マルコフ連鎖」として分類することはありません。ただし、ランダム性とマルコフ性の観点では共通点も確かにあり、そのような観点で考察することも有益かもしれません。

---

### **user**:

ありがとうございました。これまでの議論を踏まえて、面白ギャグを創作してください。

---

### **assistant**:

モンテカルロさんと勾配降下法さんがバーで出会った。

勾配降下法さん「毎回、最高の進行方向を選ぶのは大変だよね。」

すると、モンテカルロさん「そうだね、でも私はランダムな進行方向を選んで、バーンイン期間に良い感じの位置を見つけるよ！」

勾配降下法さん「それって、最初に何杯もドリンクをオーダーして、一番美味しいのを見つけるみたいだね！」

モンテカルロさん「まさしくその通り！でも注意してね、美味しさの分布はマルコフ連鎖で動いてるから、最初の頃の試飲はあまり信用しない方が良いよ。」  

勾配降下法さん「それなら、それぞれのドリンクの味を厳密に評価することに、勾配を感じるよ！」

両者は、それぞれの方法で素晴らしいドリンクを見つけた。というか、数学者がバーテンダーになったら、バーが大混乱になるかもしれないね！

---