マテリアルズインフォマティクス(MI)入門⑥【アンサンブル学習を用いた不確実性評価(UQ)】

マテリアルズインフォマティクス(MI)入門⑥【アンサンブル学習を用いた不確実性評価(UQ)】

これまでのシリーズで、私たちはベースラインの構築から高性能なCatBoostモデルの実装、そしてSHAPOptunaを用いたモデルの解釈・最適化まで、MIプロジェクトの核となる技術を学んできました。その結果、非常に精度の高い物性予測モデルを手にすることができました。

しかし、実用化を目前にしたとき、次なる重要な問いが浮上します。それは「この予測値は、どれくらい信頼できるのか?」という問いです。モデルが算出した「降伏強度1410 MPa」という一点の予測を、私たちは鵜呑みにして良いのでしょうか。もしその予測が大きな不確かさを含んでいた場合、その情報に基づいて高コストな実験を行うのは大きなリスクを伴います。

そこで第6回となる今回は、MIの実用化における最後の関門、「予測の信頼性」に焦点を当てます。予測精度(Accuracy)だけでなく、予測の確信度(Confidence)を定量化する「不確実性評価(Uncertainty Quantification, UQ)」という技術を導入します。本記事では、アンサンブル学習の一種であるブートストラップ法を用い、予測値の「信頼区間」を算出する実践的な方法を学びます。これにより、単一の予測値の裏に隠されたモデルの「自信」を可視化し、より安全で合理的な意思決定を下すための羅針盤を手に入れます。

動作検証済み環境

Google Colaboratory
Python 3.11.13
matminer==0.9.3
scikit-learn==1.6.1
pandas==2.2.2
matplotlib==3.10.0
catboost==1.2.8
tqdm==4.67.1

この記事から学べること

  • 不確実性評価(UQ)の重要性: なぜ予測精度だけでなく、その信頼性を評価することがMIの実用化に不可欠なのかを理解できます。
  • 予測の信頼区間: 「予測値は1400 MPaです」という点予測から、「95%の確率で1350〜1450 MPaの範囲に入ります」という、より情報量豊かな区間予測への進化を学びます。
  • アンサンブル学習によるUQ: 複数のモデルを組み合わせ、それらの予測のばらつきから不確実性(信頼区間)を推定する、実践的なアンサンブル学習の実装方法をマスターします。
  • 信頼性の可視化と活用: 予測値とその信頼区間をグラフで可視化し、どの予測が信頼でき、どの予測に注意すべきかを科学的に判断する力を養います。

関連理論の解説

1. なぜマテリアルズインフォマティクスに不確実性評価(UQ)が不可欠なのか?

材料開発の現場では、AIの予測結果に基づいて「次に合成すべきか否か」という重要な経営判断が下されます。このとき、モデルの予測が持つ不確実性を無視することは、大きなリスクを見過ごすことにつながります。

  • データの制約: MIが扱う実験データは、多くの場合、高コストで量が限られており、ノイズ(測定誤差)も含まれています。このような不完全なデータから学習したモデルの予測には、本質的に不確かさが伴います。
  • 未知領域への挑戦(外挿): MIの真価は、学習データにない全く新しい組成の物性を予測することにあります。しかし、学習データの範囲から大きく外れた領域(外挿領域)では、モデルの予測信頼性は急激に低下する傾向があります。
  • リスクを考慮した意思決定: 例えば、「A合金は強度1500MPa(不確実性大)」と「B合金は強度1400MPa(不確実性小)」という予測があった場合、単純な予測値だけ見ればA合金が優れています。しかし、失敗のリスクを考慮すると、より確実性の高いB合金を優先するという合理的な判断が可能になります。UQは、このようなリスク評価を定量的に行うための基盤技術です。

2. 不確実性の種類

モデルの予測に含まれる不確実性は、主に2種類に大別されます。

  • 偶然性不確実性(Aleatoric Uncertainty): データの測定誤差や固有のノイズなど、本質的に除去できないランダム性に起因する不確実性。何度同じ実験をしても結果がばらつくような現象がこれにあたります。
  • 認識論的不確実性(Epistemic Uncertainty): モデルが学習データ不足により、知らない領域に対してうまく予測できないことに起因する不確実性。これは、関連するデータを追加で学習させることで低減できる可能性があります。我々がUQで主に評価・活用しようとするのは、こちらの不確実性です。

3. 不確実性を評価するアプローチ:アンサンブル学習とベイズ統計

不確実性を定量化するアプローチは複数ありますが、ここでは特に実践的なアンサンブル学習と、その背後にあるベイズ統計の考え方に触れます。

  • ベイズ統計の考え方: ベイズ統計は、不確実性を確率分布として表現する考え方です。物性予測にこのアプローチを応用すると、予測結果を「1410 MPa」という一つの値(点)ではなく、「平均1410 MPaで、標準偏差50 MPaの正規分布」のような確率分布(幅)として得ることができます。この分布から「95%の確率でこの範囲に収まる」という信用区間(Credible Interval)を算出できます。
  • アンサンブル学習(ブートストラップ法): ベイズ的なアプローチは強力ですが、実装が複雑になる場合があります。そこで、より直感的で実装が容易な手法としてアンサンブル学習が広く用いられています。
    1. 元の訓練データから、ランダムにデータを復元抽出し、複数の異なるデータセット(ブートストラップサンプル)を作成します。
    2. それぞれのデータセットを使い、独立したモデルを複数(例: 50個)学習させます。これにより、少しずつ個性(弱点)の異なるモデルの「集団(アンサンブル)」が出来上がります。
    3. 未知のデータに対して予測を行う際、この50個すべてのモデルに予測させます。
    4. 得られた50個の予測値の「平均値」を最終的な予測値とし、予測値の「ばらつき(標準偏差)」を不確実性の大きさとして評価します。もし全モデルがほぼ同じ値を予測すれば「ばらつき」は小さく(高信頼度)、予測がバラバラであれば「ばらつき」は大きく(低信頼度)なります。

今回はこのアンサンブル学習(ブートストラップ法)を用いて、CatBoostモデルの予測信頼区間を評価します。

実装方法

ワークフロー

CatBoostモデルの構築フローをベースに、アンサンブル学習と不確実性評価のステップを追加します。

# ===================================================================
# 0. 環境構築:必要なライブラリのインストール
# 1. 必要なライブラリのインポート
# 2. データセットの読み込み & 3. 特徴量エンジニアリング
# 4. 特徴量とターゲットの定義
# 5. データを訓練用とテスト用に分割
# 6. 【NEW】アンサンブルモデルの構築(ブートストラップ法)
# 7. 【NEW】アンサンブルによる予測の実行
# 8. 【NEW】性能評価と信頼区間の計算
# 9. 【NEW】結果の可視化(信頼区間付き)
# 10.【NEW】未知組成に対する不確実性を考慮した予測
# ===================================================================

実行手順

  • 以下のコードブロック全体をコピーします。
  • Google Colaboratoryの新しいセルに貼り付けます。
  • セルが選択されていることを確認し、Shift キーと Enter キーを同時に押してコードを実行します。
# ===================================================================
# 0. 環境構築:必要なライブラリのインストール
# ===================================================================
!pip install matminer==0.9.3 scikit-learn==1.6.1 pandas==2.2.2
!pip install matplotlib==3.10.0 catboost==1.2.8 tqdm==4.67.1
# ===================================================================
# 1. 必要なライブラリのインポート
# ===================================================================
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import re
from tqdm.notebook import tqdm
from matminer.datasets import load_dataset
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
from catboost import CatBoostRegressor
# ===================================================================
# 2. データセットの読み込み & 3. 特徴量エンジニアリング (前回と同じ)
# ===================================================================
print("ステップ2&3: データセットの読み込みと特徴量エンジニアリング...")
df = load_dataset("matbench_steels")
def extract_value(composition_string, element_name): pattern = r"{}(\d+\.?\d*)".format(element_name) match = re.search(pattern, str(composition_string)) if match: return float(match.group(1)) else: return 0.0
elements = [ "Fe", "C", "Mn", "Si", "Cr", "Ni", "Mo", "V", "N", "Nb", "Co", "W", "Al", "Ti",
]
for element in elements: df[element] = df["composition"].apply(lambda x: extract_value(x, element))
df_clean = df.drop(columns=["composition"])
print("完了しました。")
# ===================================================================
# 4. 特徴量とターゲットの定義 (前回と同じ)
# ===================================================================
features = elements
target = "yield strength"
X = df_clean[features]
y = df_clean[target]
# ===================================================================
# 5. データを訓練用とテスト用に分割 (前回と同じ)
# ===================================================================
print("\nステップ5: データを訓練データとテストデータに分割します...")
X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42
)
print("データの分割が完了しました。")
# ===================================================================
# 6. 【NEW】アンサンブルモデルの構築(ブートストラップ法)
# ===================================================================
N_ENSEMBLE = 50 # アンサンブルを構成するモデルの数
ensemble_models = []
print(f"\nステップ6: {N_ENSEMBLE}個のアンサンブルモデルの学習を開始します...")
for i in tqdm(range(N_ENSEMBLE)): # ブートストラップサンプリング(訓練データから復元抽出) bootstrap_indices = X_train.sample( n=len(X_train), replace=True, random_state=i ).index X_train_bootstrap = X_train.loc[bootstrap_indices] y_train_bootstrap = y_train.loc[bootstrap_indices] # モデルの定義と学習 model = CatBoostRegressor(random_seed=i, verbose=0) model.fit(X_train_bootstrap, y_train_bootstrap) ensemble_models.append(model)
print("モデルの学習が完了しました。")
# ===================================================================
# 7. 【NEW】アンサンブルによる予測の実行
# ===================================================================
print("\nステップ7: アンサンブルモデルで予測を実行します...")
def predict_with_ensemble(X, models): predictions = [] for model in models: predictions.append(model.predict(X)) return np.array(predictions)
# 訓練データとテストデータで予測を実行
y_train_pred_ensemble = predict_with_ensemble(X_train, ensemble_models)
y_test_pred_ensemble = predict_with_ensemble(X_test, ensemble_models)
print("予測が完了しました。")
# ===================================================================
# 8. 【NEW】性能評価と信頼区間の計算
# ===================================================================
print("\nステップ8: モデルの性能を評価し、信頼区間を計算します...")
# 予測値の平均と標準偏差を計算
y_train_pred_mean = y_train_pred_ensemble.mean(axis=0)
y_train_pred_std = y_train_pred_ensemble.std(axis=0)
y_test_pred_mean = y_test_pred_ensemble.mean(axis=0)
y_test_pred_std = y_test_pred_ensemble.std(axis=0)
# アンサンブル平均値で性能を評価
mae_train = mean_absolute_error(y_train, y_train_pred_mean)
rmse_train = np.sqrt(mean_squared_error(y_train, y_train_pred_mean))
r2_train = r2_score(y_train, y_train_pred_mean)
mae_test = mean_absolute_error(y_test, y_test_pred_mean)
rmse_test = np.sqrt(mean_squared_error(y_test, y_test_pred_mean))
r2_test = r2_score(y_test, y_test_pred_mean)
print("\n" + "=" * 50)
print("【アンサンブルモデル性能評価】")
print("\n--- 訓練データ (Train) ---")
print(f" 平均絶対誤差 (MAE) : {mae_train:.3f} MPa")
print(f" 二乗平均平方根誤差 (RMSE) : {rmse_train:.3f} MPa")
print(f" 決定係数 (R2 Score) : {r2_train:.3f}")
print("\n--- テストデータ (Test) ---")
print(f" 平均絶対誤差 (MAE) : {mae_test:.3f} MPa")
print(f" 二乗平均平方根誤差 (RMSE) : {rmse_test:.3f} MPa")
print(f" 決定係数 (R2 Score) : {r2_test:.3f}")
print("=" * 50 + "\n")
# ===================================================================
# 9. 【NEW】結果の可視化(信頼区間付き)
# ===================================================================
print("ステップ9: 予測結果と不確実性をグラフで可視化します...")
plt.figure(figsize=(10, 10))
plt.style.use("seaborn-v0_8-whitegrid")
# 95%信頼区間をエラーバーとしてプロット (1.96 * 標準偏差)
plt.errorbar( y_test, y_test_pred_mean, yerr=1.96 * y_test_pred_std, fmt="o", # o: 点マーカー ecolor="salmon", # エラーバーの色 capsize=3, # エラーバーの端にある横線の長さ alpha=0.6, markerfacecolor="red", markeredgecolor="white", label="Test with 95% Confidence Interval",
)
plt.scatter( y_train, y_train_pred_mean, alpha=0.3, s=50, c="blue", label="Train (mean prediction)",
)
max_val = y.max() * 1.05
min_val = 0
plt.plot([min_val, max_val], [min_val, max_val], "k--", lw=2, label="Ideal (y=x)")
plt.xlabel("Actual Yield Strength (MPa)", fontsize=14)
plt.ylabel("Predicted Yield Strength (MPa)", fontsize=14)
plt.title("Actual vs. Predicted Yield Strength (with Uncertainty)", fontsize=16)
plt.legend(fontsize=12)
plt.xlim(min_val, max_val)
plt.ylim(min_val, max_val)
plt.grid(True)
plt.show()
print("可視化処理が完了しました。")
# ===================================================================
# 10.【NEW】未知組成に対する不確実性を考慮した予測
# ===================================================================
print("\nステップ10: 未知の組成データで不確実性を考慮した降伏強度を予測します...")
# 予測したい仮想の鋼材の組成を定義 (前回と同じ)
new_composition_input = { "C": 0.35, "Mn": 0.7, "Si": 0.25, "Cr": 0.9, "Mo": 0.15, "V": 0.05, "N": 0.008, "Al": 0.02,
}
full_composition = {element: 0.0 for element in features}
full_composition.update(new_composition_input)
sum_other_elements = sum(v for k, v in full_composition.items() if k != "Fe")
full_composition["Fe"] = 100 - sum_other_elements
new_steel_df = pd.DataFrame([full_composition], columns=features)
print("\n予測対象の組成 (wt%):")
print(new_steel_df.to_string(index=False))
# アンサンブルモデルで予測を実行し、平均と標準偏差を計算
new_steel_pred_ensemble = predict_with_ensemble(new_steel_df, ensemble_models)
new_steel_pred_mean = new_steel_pred_ensemble.mean()
new_steel_pred_std = new_steel_pred_ensemble.std()
# 95%信頼区間を計算
confidence_interval_95 = 1.96 * new_steel_pred_std
print("\n" + "=" * 50)
print("【未知の組成に対する不確実性を考慮した予測結果】")
print(f" 予測される降伏強度 (平均値) : {new_steel_pred_mean:.2f} MPa")
print(f" 予測の標準偏差 (不確実性) : {new_steel_pred_std:.2f} MPa")
print(f" 95%信頼区間 : [{new_steel_pred_mean - confidence_interval_95:.2f}, \
{new_steel_pred_mean + confidence_interval_95:.2f}] MPa")
print("=" * 50 + "\n")
print("推論処理が完了しました。")
print("\n処理はすべて完了しました。")

実行結果と考察

アンサンブルモデルの性能

データ種別MAE (MPa)RMSE (MPa)R²スコア
訓練データ39.98654.6100.969
テストデータ76.993114.5040.810

アンサンブルモデルの平均予測値で評価した性能は、テストデータにおいてR²スコア0.810と、第2回で構築した単一のCatBoostモデルとほぼ同等の高い精度を達成しています。これは、アンサンブル化によって予測精度を損なうことなく、不確実性という新たな価値ある情報を付与できたことを意味します。

予測信頼性の可視化

グラフから読み解くモデルの「自信」

  • エラーバー(信頼区間)の意味: テストデータ(赤い点)に付与された縦方向のエラーバーは、95%信頼区間を示します。このバーが短いほど、アンサンブルを構成する全モデルの予測が一致しており、モデルがその予測に「自信を持っている」ことを意味します。逆にバーが長いほど、モデル間の予測がばらついており、予測の不確実性が高いことを示唆します。
  • 不確実性の傾向: グラフ全体を見ると、降伏強度が低い領域ではエラーバーが短く、高強度領域になるにつれて長くなる傾向が見て取れます。これは、学習データが高強度領域に少なく、モデルがその領域の予測に苦労している(自信がない)ことを示している可能性があります。エラーバーが極端に長い外れ値は、モデルが全く未知のタイプの材料に直面していることを示す危険信号と捉えることができます。

未知組成に対する信頼性を考慮した予測

予測対象の組成 (wt%): Fe C Mn Si Cr Ni Mo V N Nb Co W Al Ti
97.572 0.35 0.7 0.25 0.9 0.0 0.15 0.05 0.008 0.0 0.0 0.0 0.02 0.0
==================================================
【未知の組成に対する不確実性を考慮した予測結果】 予測される降伏強度 (平均値) : 1414.53 MPa 予測の標準偏差 (不確実性) : 41.52 MPa 95%信頼区間 : [1333.15, 1495.92] MPa
==================================================

この結果は、MIの意思決定において革命的な変化をもたらします。

  • 点予測から区間予測へ: 「予測値は1415 MPaです」という単一の報告から、「予測値は平均1414.53 MPaであり、95%の確率で約1333 MPaから1496 MPaの範囲に収まることが見込まれます」という、リスクを内包した報告に変わります。
  • 定量的なリスク評価: 約±81 MPaという信頼区間の幅は、この新しい合金開発プロジェクトが内包する不確実性を具体的に示しています。研究者はこの情報に基づき、「この不確実性の範囲でも目標物性を満たせるか?」「このリスクを許容して試作に進むべきか?」といった、より高度で現実的な議論を進めることができるのです。

コードの詳細解説

ステップ6: 【NEW】アンサンブルモデルの構築(ブートストラップ法)

ここが今回のワークフローの心臓部です。単一のモデルを学習させるのではなく、少しずつ個性の異なる複数のモデルから成る「集団(アンサンブル)」を構築します。

# ===================================================================
# 6. 【NEW】アンサンブルモデルの構築(ブートストラップ法)
# ===================================================================
N_ENSEMBLE = 50 # アンサンブルを構成するモデルの数
ensemble_models = []
print(f"\nステップ6: {N_ENSEMBLE}個のアンサンブルモデルの学習を開始します...")
for i in tqdm(range(N_ENSEMBLE)): # ブートストラップサンプリング(訓練データから復元抽出) bootstrap_indices = X_train.sample( n=len(X_train), replace=True, random_state=i ).index X_train_bootstrap = X_train.loc[bootstrap_indices] y_train_bootstrap = y_train.loc[bootstrap_indices] # モデルの定義と学習 model = CatBoostRegressor(random_seed=i, verbose=0) model.fit(X_train_bootstrap, y_train_bootstrap) ensemble_models.append(model)
print("モデルの学習が完了しました。")
  • N_ENSEMBLE = 50: アンサンブルを構成するモデルの数を定義します。この数は多ければ多いほど予測は安定しますが、計算コストが増大します。50〜100程度が、計算時間と性能のバランスが良い出発点としてよく用いられます。
  • for i in tqdm(range(N_ENSEMBLE)):: 50個のモデルを一つずつ学習させるためのループ処理です。tqdm  ライブラリは、ループの進捗状況をプログレスバーで表示し、実行状況を分かりやすくするためのものです。
  • ブートストラップサンプリング: ループ内部の最初の3行が、不確実性評価の鍵となる「ブートストラップ法」を実装しています。
    • X_train.sample(n=len(X_train), replace=True, random_state=i): これは、元の訓練データ X_train から、ランダムにデータをサンプリングする命令です。
      • n=len(X_train): 元のデータと同じ数のサンプルを抽出します。
      • replace=True: これが最も重要な引数で、「復元抽出」を意味します。一度選んだデータを元に戻すため、同じデータが複数回選ばれる可能性がある一方、一度も選ばれないデータも出てきます
      • random_state=i: ループごとに異なる乱数シードを与えることで、毎回異なるパターンのサンプリングが行われるようにし、多様なデータセットを生成します。
    • この結果、元の訓練データと微妙に構成が異なる「擬似的な訓練データ」(ブートストラップサンプル)が50セット生成されます。
  • model = CatBoostRegressor(random_seed=i, verbose=0): 新しいCatBoostモデルを定義します。ここでも random_seed=i とすることで、モデル内部のランダム性(特徴量選択など)も各モデルで異なるようになり、モデルの多様性をさらに高めます。
  • model.fit(X_train_bootstrap, y_train_bootstrap): 生成されたブートストラップサンプルを使ってモデルを学習させます。
  • ensemble_models.append(model): 学習済みのモデルを ensemble_models というリストに格納していきます。ループが完了すると、このリストには50個のそれぞれ独立して学習したモデルが格納された状態になります。

ステップ7: 【NEW】アンサンブルによる予測の実行

学習させた50個のモデルを使い、実際に予測を行います。

# ===================================================================
# 7. 【NEW】アンサンブルによる予測の実行
# ===================================================================
print("\nステップ7: アンサンブルモデルで予測を実行します...")
def predict_with_ensemble(X, models): predictions = [] for model in models: predictions.append(model.predict(X)) return np.array(predictions)
# 訓練データとテストデータで予測を実行
y_train_pred_ensemble = predict_with_ensemble(X_train, ensemble_models)
y_test_pred_ensemble = predict_with_ensemble(X_test, ensemble_models)
print("予測が完了しました。")
  • predict_with_ensemble 関数: この自作関数は、入力データ(X)に対して、ensemble_models リスト内の全モデルで予測を実行し、結果を一つにまとめる役割を担います。
  • for model in models: : ループで50個のモデルを一つずつ取り出し、model.predict(X) で予測を実行します。
  • return np.array(predictions): 全モデルの予測結果をリストに格納した後、それをNumPy配列に変換して返します。例えば、テストデータが70個あった場合、返される y_test_pred_ensemble の形状は (50, 70) となります。これは「70個の各データ点に対して、50個の異なる予測値が存在する」状態を表しており、この予測値の「ばらつき」が不確実性の源泉となります。

ステップ8: 【NEW】性能評価と信頼区間の計算

50個の予測値の「ばらつき」から、最終的な予測値(平均)と不確実性(標準偏差)を計算します。

# ===================================================================
# 8. 【NEW】性能評価と信頼区間の計算
# ===================================================================
print("\nステップ8: モデルの性能を評価し、信頼区間を計算します...")
# 予測値の平均と標準偏差を計算
y_train_pred_mean = y_train_pred_ensemble.mean(axis=0)
y_train_pred_std = y_train_pred_ensemble.std(axis=0)
y_test_pred_mean = y_test_pred_ensemble.mean(axis=0)
y_test_pred_std = y_test_pred_ensemble.std(axis=0)
# アンサンブル平均値で性能を評価
mae_train = mean_absolute_error(y_train, y_train_pred_mean)
rmse_train = np.sqrt(mean_squared_error(y_train, y_train_pred_mean))
r2_train = r2_score(y_train, y_train_pred_mean)
mae_test = mean_absolute_error(y_test, y_test_pred_mean)
rmse_test = np.sqrt(mean_squared_error(y_test, y_test_pred_mean))
r2_test = r2_score(y_test, y_test_pred_mean)
print("\n" + "=" * 50)
print("【アンサンブルモデル性能評価】")
print("\n--- 訓練データ (Train) ---")
print(f" 平均絶対誤差 (MAE) : {mae_train:.3f} MPa")
print(f" 二乗平均平方根誤差 (RMSE) : {rmse_train:.3f} MPa")
print(f" 決定係数 (R2 Score) : {r2_train:.3f}")
print("\n--- テストデータ (Test) ---")
print(f" 平均絶対誤差 (MAE) : {mae_test:.3f} MPa")
print(f" 二乗平均平方根誤差 (RMSE) : {rmse_test:.3f} MPa")
print(f" 決定係数 (R2 Score) : {r2_test:.3f}")
print("=" * 50 + "\n")
  • y_test_pred_ensemble.mean(axis=0): これがアンサンブル学習の「多数決」に相当する処理です。 (50, 70) という形状の配列に対し、 axis=0 (モデルの軸)に沿って平均値を計算します。これにより、各データ点(70個)ごとに50個の予測値の平均が算出され、形状が (70,) の最終的な予測値配列 y_test_pred_mean が得られます。
  • y_test_pred_ensemble.std(axis=0) : 同様に、 axis=0 に沿って 標準偏差 を計算します。これにより、各データ点の予測値の「ばらつき具合」が数値化され、不確実性の指標となる y_test_pred_std (形状 (70,) )が得られます。
  • r2_score(y_test, y_test_pred_mean) : モデルの性能評価(R²スコアなど)は、このアンサンブルの平均予測値を用いて行います。

ステップ9: 【NEW】結果の可視化(信頼区間付き)

計算した不確実性をグラフ上にエラーバーとしてプロットし、モデルの「自信」を可視化します。

# ===================================================================
# 9. 【NEW】結果の可視化(信頼区間付き)
# ===================================================================
print("ステップ9: 予測結果と不確実性をグラフで可視化します...")
plt.figure(figsize=(10, 10))
plt.style.use("seaborn-v0_8-whitegrid")
# 95%信頼区間をエラーバーとしてプロット (1.96 * 標準偏差)
plt.errorbar( y_test, y_test_pred_mean, yerr=1.96 * y_test_pred_std, fmt="o", # o: 点マーカー ecolor="salmon", # エラーバーの色 capsize=3, # エラーバーの端にある横線の長さ alpha=0.6, markerfacecolor="red", markeredgecolor="white", label="Test with 95% Confidence Interval",
)
plt.scatter( y_train, y_train_pred_mean, alpha=0.3, s=50, c="blue", label="Train (mean prediction)",
)
max_val = y.max() * 1.05
min_val = 0
plt.plot([min_val, max_val], [min_val, max_val], "k--", lw=2, label="Ideal (y=x)")
plt.xlabel("Actual Yield Strength (MPa)", fontsize=14)
plt.ylabel("Predicted Yield Strength (MPa)", fontsize=14)
plt.title("Actual vs. Predicted Yield Strength (with Uncertainty)", fontsize=16)
plt.legend(fontsize=12)
plt.xlim(min_val, max_val)
plt.ylim(min_val, max_val)
plt.grid(True)
plt.show()
print("可視化処理が完了しました。")
  • plt.errorbar: この関数が、不確実性プロットの主役です。通常の散布図(plt.scatter)とは異なり、各点にエラーバーを描画する機能を持っています。
  • yerr=1.96 * y_test_pred_std: ここが最も重要な引数です。yerr はY軸方向のエラーバーの長さを指定します。
    • 統計学的に、予測値の分布が正規分布に従うと仮定すると、「平均値 ± 1.96 × 標準偏差」の区間には、約95%の確率で真の値が含まれると期待されます。
    • この 1.96 * y_test_pred_std を渡すことで、各データ点の 95%信頼区間 をエラーバーとして可視化しているのです。
  • fmt='o'ecolorcapsize : これらはグラフの見た目を整えるための引数です。 fmt='o' はマーカーを点に、 ecolor はエラーバーの色を、capsize はエラーバーの端の線の長さを指定します。

ステップ10: 【NEW】未知組成に対する不確実性を考慮した予測

学習済みアンサンブルモデルを使い、全く新しい材料の物性を「信頼区間付き」で予測します。

# ===================================================================
# 10.【NEW】未知組成に対する不確実性を考慮した予測
# ===================================================================
print("\nステップ10: 未知の組成データで不確実性を考慮した降伏強度を予測します...")
# 予測したい仮想の鋼材の組成を定義 (前回と同じ)
new_composition_input = { "C": 0.35, "Mn": 0.7, "Si": 0.25, "Cr": 0.9, "Mo": 0.15, "V": 0.05, "N": 0.008, "Al": 0.02,
}
full_composition = {element: 0.0 for element in features}
full_composition.update(new_composition_input)
sum_other_elements = sum(v for k, v in full_composition.items() if k != "Fe")
full_composition["Fe"] = 100 - sum_other_elements
new_steel_df = pd.DataFrame([full_composition], columns=features)
print("\n予測対象の組成 (wt%):")
print(new_steel_df.to_string(index=False))
# アンサンブルモデルで予測を実行し、平均と標準偏差を計算
new_steel_pred_ensemble = predict_with_ensemble(new_steel_df, ensemble_models)
new_steel_pred_mean = new_steel_pred_ensemble.mean()
new_steel_pred_std = new_steel_pred_ensemble.std()
# 95%信頼区間を計算
confidence_interval_95 = 1.96 * new_steel_pred_std
print("\n" + "=" * 50)
print("【未知の組成に対する不確実性を考慮した予測結果】")
print(f" 予測される降伏強度 (平均値) : {new_steel_pred_mean:.2f} MPa")
print(f" 予測の標準偏差 (不確実性) : {new_steel_pred_std:.2f} MPa")
print(f" 95%信頼区間 : [{new_steel_pred_mean - confidence_interval_95:.2f}, \
{new_steel_pred_mean + confidence_interval_95:.2f}] MPa")
print("=" * 50 + "\n")
print("推論処理が完了しました。")
print("\n処理はすべて完了しました。")
  • 予測プロセス: 流れはテストデータの時と全く同じです。
    1. 予測したい組成からデータフレームを作成します。
    2. predict_with_ensemble 関数を使い、50個のモデルで予測させます。この場合、入力データは1つなので、形状 (50, 1) の配列が返ってきます。
    3. .mean() で50個の予測値の平均を、 .std() で標準偏差を計算します。
  • 結果の解釈:
    • new_steel_pred_mean: 最も確からしい予測値です。
    • new_steel_pred_std: この予測がどれだけばらつく可能性があるか(不確実性)を示す数値です。
    • [平均 - 1.96*標準偏差, 平均 + 1.96*標準偏差]: この計算により、「95%の確率で、実際の強度はこの範囲に入るだろう」という、より情報量が多く、実用的な示唆を与える信頼区間が得られます。この情報こそが、リスクを考慮した次の一手を決定するための重要な判断材料となるのです。

最後に

今回は、高精度な予測モデルをさらに一歩進め、その予測がどれほど信頼できるかを定量化する「不確実性評価(UQ)」を実践しました。アンサンブル学習を用いることで、私たちは単なる点予測から、リスクを内包した確率的な区間予測へと進化させることができました。

この「モデルの自信」を測る能力は、マテリアルズインフォマティクスを研究室から現実世界の材料開発の現場へと橋渡しする上で、決定的に重要な役割を果たします。不確実性の高い予測を特定することで、追加の実験が必要な領域を明らかにしたり(実験計画法への応用)、予測結果をより慎重に解釈したりすることが可能になります。

これまで6回にわたり、データ準備からモデル構築、解釈、最適化、そして信頼性評価まで、MIプロジェクトの典型的なワークフローを一気通貫で体験してきました。しかし、これまでの挑戦は、常に「降伏強度」という単一の物性値を最大化することに焦点を当てていました。

現実の材料開発では、「硬く、しかし、もろくない(粘り強い)」といった、複数の、時には相反する特性を同時に満たすことが求められます。一つの性能を追求すれば、もう一つの性能が犠牲になるトレードオフの関係は、材料設計における永遠の課題です。

次回は、この現実的な課題に真正面から挑みます。MIの力を借りて、複数の目的を同時に最適化する「多目的最適化」の世界に足を踏み入れ、「硬さ」と「粘り強さ」を両立する未知の材料組成を発見する旅にご案内します。ぜひご期待ください。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です