どうも、昨夜9時頃Twitterアカウントが永久凍結した Ikemen Mas Kot です。過去記事「相関係数が0.63の散布図を作成する」の続編を思いついたので、やってみました。今回のテーマは
相関係数Rと決定係数R2の違いが!「言葉」でなく「心」で理解できた!
で行きたいと思います。それぞれの定義については、ググって調べてください(放置系)。「言葉」でなく「心」で理解するために、今回もOptunaでプロットを自作します。
目的関数の設計
import numpy as np
from scipy.stats import pearsonr
from sklearn.metrics import r2_score
class Objective: # Optuna で最適化する目的関数
def __init__(
self,
n_data=20, # データ数
target_r=0.95, # 目標とするピアソン相関係数の値
delta=0.005, # 許容する誤差の最大値
direction="minimize", # 最適化する方向
):
self.n_data = n_data # データ数
self.target_r = target_r # 目標とするピアソン相関係数の値
self.delta = delta # 許容する誤差の最大値
self.direction = direction # 最適化する方向
# 最大化・最小化の目的に合わせて初期値を設定する
if self.direction == "maximize":
self.best_score = -530000
self.penalty = -530000
else:
self.best_score = 530000 # フリーザ(初期形態)の戦闘力
self.penalty = 530000 # フリーザ(初期形態)の戦闘力
self.best_X = None # ベストX座標を記録する場所
self.best_Y = None # ベストY座標を記録する場所
def __call__(self, trial): # Optuna に呼ばれるときはこれが実行される
# X座標は均等に決め打ちする
self.X = np.linspace(0.05, 0.95, self.n_data)
# Y座標を Optuna に最適化してもらう
self.Y = []
for i in range(self.n_data):
self.Y.append(trial.suggest_float("y{}".format(i), 0, 1))
# ピアソンの相関係数を r とする
r, p = pearsonr(self.X, self.Y)
# 決定係数を r2 とする
r2 = r2_score(self.X, self.Y)
# ピアソンの相関係数が目標値に十分近いとき、決定係数をスコアとする
if self.target_r - self.delta < r and r < self.target_r + self.delta:
score = r2
else: # ピアソンの相関係数が目標値から離れすぎているとき、その距離に応じてペナルティを与える
score = abs(self.target_r - r) * self.penalty
# 今までの記録よりも良いものが得られたとき、ベスト結果を更新する
if (self.direction == "maximize" and self.best_score < score) or (
self.direction == "minimize" and self.best_score > score
):
self.best_score = score
self.best_X = self.X
self.best_Y = self.Y
return score
R2最大化
ピアソンの相関係数 R = 0.9 を目標とし、決定係数 R2 をできるだけ最大化した計算例がこちらです。
import optuna
optuna.logging.set_verbosity(optuna.logging.WARN) # 途中経過メッセージを省略する
n_trials = 1000 # 試行回数
objective = Objective(target_r=0.90, direction="maximize") # 目的関数を設定
study = optuna.create_study(directions=["maximize"]) # Optuna の設定
study.optimize(objective, n_trials=n_trials, show_progress_bar=True) # 最適化計算実行
/usr/local/lib/python3.9/site-packages/optuna/progress_bar.py:49: ExperimentalWarning: Progress bar is experimental (supported from v1.2.0). The interface can change in the future.
self._init_valid()
0%| | 0/1000 [00:00<?, ?it/s]
import matplotlib.pyplot as plt
plt.title(
"R={:.3f}, R2={:.3f}".format(
pearsonr(objective.best_X, objective.best_Y)[0],
r2_score(objective.best_X, objective.best_Y),
)
)
plt.scatter(objective.best_X, objective.best_Y)
plt.xlim([0, 1])
plt.ylim([0, 1])
plt.grid()
plt.savefig("0.png")
なるほど。続いて、ピアソンの相関係数 R = 0.9 を目標とし、決定係数 R2 をできるだけ最小化した計算例がこちらです。
import optuna
optuna.logging.set_verbosity(optuna.logging.WARN) # 途中経過メッセージを省略する
objective = Objective(target_r=0.90, direction="minimize")
study = optuna.create_study(directions=["minimize"])
study.optimize(objective, n_trials=n_trials, show_progress_bar=True)
/usr/local/lib/python3.9/site-packages/optuna/progress_bar.py:49: ExperimentalWarning: Progress bar is experimental (supported from v1.2.0). The interface can change in the future.
self._init_valid()
0%| | 0/1000 [00:00<?, ?it/s]
import matplotlib.pyplot as plt
plt.title(
"R={:.3f}, R2={:.3f}".format(
pearsonr(objective.best_X, objective.best_Y)[0],
r2_score(objective.best_X, objective.best_Y),
)
)
plt.scatter(objective.best_X, objective.best_Y)
plt.xlim([0, 1])
plt.ylim([0, 1])
plt.grid()
plt.savefig("1.png")
データ数を増やしていくぜ
それでは、データ数を増やしていきたいと思います。計算に時間がかかるので、途中で何かの原因で計算が止まってしまうようなことがあれば、再計算するのが面倒です。そのようなときに備えて、Optunaに計算履歴を残してもらうと大変便利です。
for n_data in [10, 20, 100, 200, 1000]: # データ数を増やしていく
# データ数ごとに結果表示をする
fig, axes = plt.subplots(nrows=2, ncols=5, figsize=(20, 8))
# 目標とするピアソン相関係数Rの値を変えていく
for j, target_r in enumerate([0.9, 0.7, 0.5, 0.25, 0.0]):
# 決定係数R2を最大化した計算例、最小化した計算例をそれぞれ算出する
for i, direction in enumerate(["maximize", "minimize"]):
# 目的関数を設定する
objective = Objective(n_data=n_data, target_r=target_r, direction=direction)
# 計算履歴を残すデータベースの名前を決める
study_name = "{}-{}-{}".format(n_data, target_r, direction)
# 計算履歴を残せる形で、Optuna の study を設定する
study = optuna.create_study(
directions=[direction],
study_name=study_name,
storage="sqlite:///" + study_name + ".sql",
load_if_exists=True,
)
# 最適化計算
study.optimize(objective, n_trials=n_trials, show_progress_bar=True)
# 1つのパネルに計算結果を描画する
axes[i][j].set_title(
"n_data={}, R={:.3f}, R2={:.3f}".format(
n_data,
pearsonr(objective.best_X, objective.best_Y)[0],
r2_score(objective.best_X, objective.best_Y),
)
)
axes[i][j].scatter(objective.best_X, objective.best_Y)
axes[i][j].set_xlim([0, 1])
axes[i][j].set_ylim([0, 1])
axes[i][j].grid()
# データ数ごとに結果表示をして画像を保存する
plt.savefig("{}.png".format(n_data))
plt.show() # 画像を表示する
n_data = 10
n_data = 20
わかるか?え?RとR2の関係が...
n_data = 100
さすがに n_data = 100 ともなると最適化が難しくなってきましたね...試行回数がまだ足りないかな...もっとデータ数が大きいときはもっと酷い結果だったので省略ッ