はじめに
ここまで,
- ①データの確認と6記述子による線形回帰(ベースライン)
- ②RDKit 記述子(LogP 等)の追加
-
③非線形モデル(LightGBM)への切り替えとチューニング
と取り組んできました.
記述子の追加で CV R² = 0.676 → 0.783,
モデルの切り替えで 0.783 → 0.888 と段階的に改善してきました.
今回は,分子の表現方法として「フィンガープリント」を導入し,記述子ベースのモデルとの比較を行います.
フィンガープリントとは
これまでのモデルでは,分子を MolLogP や Molecular Weight などの「記述子」で数値化していました.記述子は分子全体の性質を数値で表すものでした.
フィンガープリントは,これとは全く違うアプローチで分子を表現します.
分子にどんな部分構造が含まれるかを,0/1 のビット列で表します.
今回使う Morgan フィンガープリント(ECFP とも呼ばれる)は,各原子の周囲[radius]結合先までの環境を見てビット化するもので,最も広く使われているフィンガープリントです.デフォルトではradius=2,2048 ビットで表現します.
こちらやこちらなどではもっと詳しい説明があります.
つまり,
- 記述子: 分子全体の性質を数値化(MolWt=180, LogP=2.3, ...)→ 少数の特徴量
- フィンガープリント: 部分構造の有無をビットで表現([0,1,1,0,1,0,...])→ 多数の特徴量
というように,両者は質も量も異なる特徴量であり,モデルの性能にも影響を与えると期待できます.
フィンガープリントの導入
データセットは前回の続きからです.
from rdkit.Chem import AllChem
# Morgan フィンガープリントを計算する関数
def calc_morgan_fp(mol, radius=2, n_bits=2048):
"""Mol オブジェクトから Morgan フィンガープリントを numpy 配列で返す"""
fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius=radius, nBits=n_bits)
return np.array(fp)
# 全分子のフィンガープリントを計算
fps = np.array([calc_morgan_fp(mol) for mol in df["mol"]])
print(f"フィンガープリントの形: {fps.shape}")
print(f"1分子あたりのビット数: {fps.shape[1]}")
print(f"平均で ON になっているビット数: {fps.sum(axis=1).mean():.1f} / {fps.shape[1]}")

2048 ビットのうち,平均 21.5 個しか ON になっていません(約 1%).
これは「スパース(疎)」な表現と呼ばれています.
正常な状態で,ありうる部分構造パターンは膨大ですが,1つの分子が持つ部分構造はそのごく一部だからです.
大きくて複雑な分子ほど ON ビットが多く,小さい分子ほど少なくなります.
モデル作成・評価
3つの特徴量セットを比較します.
記述子だけ、フィンガープリントだけ、両方、の3パターンです.
from sklearn.model_selection import cross_val_score
from lightgbm import LGBMRegressor
# 特徴量セット3パターン
X_desc = df[desc_cols_v2].values # 記述子のみ(9個)
X_fp = fps # フィンガープリントのみ(2048個)
X_both = np.hstack([X_desc, fps]) # 両方(9 + 2048 = 2057個)
model = LGBMRegressor(n_estimators=200, random_state=42, verbose=-1)
for name, X_data in [
("記述子のみ (9個)", X_desc),
("フィンガープリントのみ (2048個)", X_fp),
("記述子 + FP (2057個)", X_both),
]:
cv = cross_val_score(model, X_data, y, cv=5, scoring="r2")
print(f" {name:35s}: R² = {cv.mean():.4f} (±{cv.std():.4f})")
LightGBM では,記述子 + FP の組み合わせが最良(R² = 0.906)でした.
フィンガープリントだけ(0.709)は記述子だけ(0.887)に大きく負けています.
これは,溶解度が LogP(疎水性)に強く支配されるため,LogP を含む記述子の方が直接的な情報を持っているからです.
しかし両方を組み合わせると最良になりました.
フィンガープリントは記述子では捉えられない「特定の部分構造パターンが溶解度に効く」情報を補完しているためと考えられます.
Ridgeでも比較しました.Ridge は全特徴量を穏やかに縮小する正則化を行うため,2048 個のスパースなフィンガープリントに対しても安定して動作する可能性があるとのことです.
from sklearn.linear_model import Ridge
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
pipe_ridge = Pipeline([
("scaler", StandardScaler()),
("model", Ridge(alpha=1.0))
])
for name, X_data in [
("記述子のみ (9個)", X_desc),
("フィンガープリントのみ (2048個)", X_fp),
("記述子 + FP (2057個)", X_both),
]:
cv = cross_val_score(pipe_ridge, X_data, y, cv=5, scoring="r2")
print(f" {name:35s}: R² = {cv.mean():.4f} (±{cv.std():.4f})")
しかし結果としては,Ridge ではフィンガープリントが全く効きませんでした(R² = -0.019).
1128 サンプルに対して 2048 特徴量は,線形モデルでは扱いきれない状況です.いわゆる次元の呪いというものです.
記述子 + FP(R² = 0.546)でも,記述子だけ(0.783)より大きく悪化しました.
他のフィンガープリント
他のフィンガープリントについても比較してみました.
- Morgan FP (ECFP): 各原子の周囲環境をビット化.最も汎用的
- MACCS Keys: 166 個の決められた部分構造の有無を記録.解釈しやすい
- RDKit FP: 分子内のパス(原子の並び)をビット化
from rdkit.Chem import MACCSkeys
# MACCS Keys フィンガープリントを計算
fps_maccs = np.array([np.array(MACCSkeys.GenMACCSKeys(mol)) for mol in df["mol"]])
print(f"MACCS Keys の形: {fps_maccs.shape}")
# RDKit フィンガープリントも計算
from rdkit.Chem import RDKFingerprint
fps_rdkit = np.array([np.array(Chem.RDKFingerprint(mol)) for mol in df["mol"]])
print(f"RDKit FP の形: {fps_rdkit.shape}")
# 3種類のフィンガープリントを比較
model = LGBMRegressor(n_estimators=200, random_state=42, verbose=-1)
print("\n=== フィンガープリント比較(LightGBM) ===\n")
for name, fp_data in [
("Morgan FP (2048 bit)", fps),
("MACCS Keys (167 bit)", fps_maccs),
("RDKit FP (2048 bit)", fps_rdkit),
]:
cv = cross_val_score(model, fp_data, y, cv=5, scoring="r2")
print(f" {name:25s}: R² = {cv.mean():.4f} (±{cv.std():.4f})")
予想外に,MACCSが良いことがわかりました.MACCSは部分構造の有無が0,1で記録されているため,「溶解性の向上にはこの官能基が相関する」と明確にわかる場合は他のフィンガープリントよりも良い場合があるそうです.
今回は水溶解性はOH, NH2,芳香環など,割と具体的な官能基との相関があるパラメータであるというのが理由だと思われます.
まとめ
今回は,分子の表現方法として「フィンガープリント」を導入しました.
記述子は分子全体の性質(LogP,分子量など)を数値化するのに対して,フィンガープリントは部分構造の有無をビット列で表現します.
両者は異なる種類の情報を持っており,組み合わせることで精度が向上しました.
| 特徴量セット | LightGBM R² | Ridge R² |
|---|---|---|
| 記述子のみ(9個) | 0.887 | 0.783 |
| Morgan FP のみ(2048個) | 0.709 | -0.019 |
| 記述子 + Morgan FP(2057個) | 0.906 | 0.546 |
また,フィンガープリントは木系モデル(LightGBM)と相性が良いが,線形モデル(Ridge)には向かないということがわかりました.
LightGBMは2048個のスパースなビットから有効なものだけを選べますが,Ridgeは全ビットを扱おうとして次元の呪いで崩壊する,ということです.
改善の推移
| ステップ | 内容 | CV R² | 改善 |
|---|---|---|---|
| 1 | 6記述子 + 線形回帰 | 0.676 | ― |
| 2 | 9記述子 + 線形回帰 | 0.783 | +0.107 |
| 3 | 9記述子 + LightGBM | 0.888 | +0.105 |
| 4 | 9記述子 + FP + LightGBM | 0.906 | +0.018 |
次回は水溶解度予測の最後となります.
SHAP を使ってモデルの中身を解釈し,Delaney の元論文の予測式(ESOL)との性能比較を行います.


