はじめに
2004年のESOL論文を参考に有機分子の溶解度予測に取り組んでいます.
前回は,RDKit記述子であるLogP(分配係数)を追加したことで CV R² = 0.676 → 0.783 と大きく改善しました(+0.107).
特にLogPとlogSの相関はr = -0.83 と全記述子の中で最も強く,化学的にも納得できる結果でした.
今回は線形回帰を非線形モデル(Random Forest、LightGBM)に切り替えて,さらなる改善を狙います.
記述子は,前回と同様にRDKit記述子を追加した計9個のものを用います.
X_v2.head()
モデル変更
ライブラリのインポートと,モデルの作成を行います.
L2 正則化を行う Ridge 回帰も追加してみました.多重共線性がある場合や特徴量が多い場合には,普通の線形回帰よりも改善がみられると予想しました.
from sklearn.ensemble import RandomForestRegressor
from lightgbm import LGBMRegressor
models = {
"LinearRegression": Pipeline([
("scaler", StandardScaler()),
("model", LinearRegression())
]),
"Ridge": Pipeline([
("scaler", StandardScaler()),
("model", Ridge(alpha=1.0))
]),
"RandomForest": RandomForestRegressor(n_estimators=100, random_state=42),
"LightGBM": LGBMRegressor(n_estimators=100, random_state=42, verbose=-1),
}
今回は,cross validationを最初から全てのモデルに対して行い,R2を比較しました.
results = {}
for name, model in models.items():
cv = cross_val_score(model, X_v2, y, cv=5, scoring="r2")
results[name] = cv.mean()
print(f" {name:20s}: R² = {cv.mean():.4f} (±{cv.std():.4f})")
ランダムフォレストとLightGBMがLinearRegressionを大きく改善しました.
tree系は非線形の関係性を扱うのが得意なので,ここでも記述子とlogSには非線形性があるということが示唆されます.
例えば,これは適当な予想ですが,LogPや分子量などの値が溶ける領域から変化していく時に,最初は緩やかに溶けにくくなるが,ある一定を超えると急激に溶けなくなり,それ以降は十分に溶けないので溶解度はほぼ変わらない,といったS字カーブの関係にあるのではないか,と考えられます.
Ridgeはほぼ効果が見られませんでした.Ridge は重みが大きくなりすぎないようペナルティ(L2 正則化)をかけてくれるので,特徴量が多いときや多重共線性が強いときに効果的であることを踏まえると,今回は9記述子 × 1128サンプルで比率が十分(125:1)であり,正則化で抑えるべき過学習がそもそも起きていなかったことが要因だと考えられます.
ハイパーパラメータ調整
LightGBM が最良(CV R² = 0.888)だったので,GridSearchCV でハイパーパラメータを調整し,さらに精度を詰めます.
LightGBM のパラメータグリッドを以下のように設定します.
意味はざっくりと以下の通りです.
- n_estimators: 木の本数(多いほど精度上がるが遅い)
- max_depth: 木の深さ(-1は制限なし、小さいと未学習寄り)
- learning_rate: 学習率(小さいほど慎重に学習、n_estimators を増やす必要)
- num_leaves: 葉の数(LightGBM 特有、大きいほど複雑)
from sklearn.model_selection import GridSearchCV
param_grid = {
"n_estimators": [100, 200, 500],
"max_depth": [3, 5, 7, -1], # -1 は制限なし
"learning_rate": [0.01, 0.05, 0.1],
"num_leaves": [15, 31, 63],
}
# GridSearchを実行
grid = GridSearchCV(
LGBMRegressor(random_state=42, verbose=-1),
param_grid,
cv=5,
scoring="r2",
n_jobs=-1,
)
grid.fit(X_v2, y)
print(f"最良パラメータ: {grid.best_params_}")
print(f"最良 CV R²: {grid.best_score_:.4f}")
改善はほぼ見られませんでした.
| ステップ | 内容 | CV R² | 改善 |
|---|---|---|---|
| 1 | 6記述子 + 線形回帰 | 0.676 | ― |
| 2 | 9記述子 + 線形回帰(+LogP) | 0.783 | +0.107 |
| 3 | 9記述子 + LightGBM | 0.888 | +0.105 |
| 3-2 | 9記述子 + LightGBM(チューニング) | 0.889 | +0.001 |
1128サンプル × 9記述子という「中規模データ × 少数特徴量」は、LightGBM が最も得意とする領域で、デフォルトで十分良い設定になっているということのようです(AI曰く).
予測vs実測プロット
CV R² が 0.676 → 0.888 と大きく改善したので,予測 vs 実測プロットを線形回帰(ステップ1)と LightGBM(ステップ3)で並べて比較してみます.これはハイパーパラメータの調整は入っていないです.
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# 左:線形回帰(ステップ1、6記述子)
axes[0].scatter(y_test, y_pred_test, alpha=0.5, s=20)
axes[0].plot([-12, 2], [-12, 2], "r--", label="ideal")
axes[0].set_xlabel("Measured logS")
axes[0].set_ylabel("Predicted logS")
axes[0].set_title(f"Step 1: LinearRegression (6 desc, R²={r2_score(y_test, y_pred_test):.3f})")
axes[0].legend()
# 右:LightGBM(ステップ3、9記述子)
axes[1].scatter(y_test2, y_pred_best, alpha=0.5, s=20)
axes[1].plot([-12, 2], [-12, 2], "r--", label="ideal")
axes[1].set_xlabel("Measured logS")
axes[1].set_ylabel("Predicted logS")
axes[1].set_title(f"Step 3: LightGBM (9 desc, R²={r2_score(y_test2, y_pred_best):.3f})")
axes[1].legend()
plt.suptitle("Linear Regression vs LightGBM", fontsize=14)
plt.tight_layout()
plt.show()

注:プロットの R² は1回の train/test 分割の値なので,交差検証のR²(5回の平均)とは若干異なります.
左(線形回帰)と右(LightGBM)を見比べると,特に低溶解度側(左下)でLightGBMの方が赤い点線に近づいているのが見えます.
まとめ
今回は,モデルを線形回帰から非線形モデル(LightGBM)に切り替え,さらにハイパーパラメータの調整を行いました.
大きな改善をもたらしたのは「記述子の追加」と「モデルの変更」でした.
一方,ハイパーパラメータの調整はほとんど効果がありませんでした.
この結果から,精度改善のアプローチは基本的には
- 良い特徴量を作る
- 適切なモデルを選ぶ
- ハイパーパラメータの調整
の順であると実感しました.
次回は,記述子に加えてフィンガープリントを導入して更なるモデルの性能向上を狙います.


