化學資訊學入門與實作:線性回歸模型的介紹與應用(續篇)

Chemistry with data magic
10 min readMay 25, 2024

--

在「化學資訊學入門與實作:線性回歸模型的介紹與應用」文章中,我們建立了使用線性模型來預測化合物的溶解度。特別是

  • 當特徵較多時,OLS (Ordinary Least Squares) 很容易對訓練資料進行過擬合 (over-fitting)。
  • 正則化可以抑制過擬合 (over-fitting) ,其模型包括 Ridge 回歸和 Lasso 回歸。
  • 若要控制正規化程度,請變更超參數 α

這次,我們會介紹如何使用一種稱為 Elastic Net 的線性模型來建立更有效率、更可靠的模型,該方法結合了 Ridge 和 Lasso 正則化方法。

什麼是 Elastic Net

Elastic Net 是一個結合了使用 Ridge 回歸的 L2 正規化和使用 Lasso 迴歸的 L1 正規化的模型。 成本函數為

  • α 為正規化程度
  • r 是 L1 正規化和 L2 正規化的混合比例

兩者都是 Elastic Net 的超參數。

我們可以使用 scikit-learn 中的 sklearn.linear_model 找到 Elastic Net 模型。

利用 RDKit 與 scikit-learn 中 Elastic Net 模型進行 QSPR 建模

先導入需要用到的一些 library 並將 ESOL 數據集載入為 pandas.DataFrame。然後利用 PandasTools.AddMoleculeColumnToFrame 把 DataFrame 中 smiles 格式的分子轉換回 Mol 的形式。

from rdkit import rdBase, Chem
from rdkit.Chem import AllChem, Draw, PandasTools
from rdkit.Chem.Draw import IPythonConsole
import pandas as pd
import numpy as np

df = pd.read_csv('esol.csv')

PandasTools.AddMoleculeColumnToFrame(frame=df, smilesCol='smiles')

接著我們得將分子轉換成數值的形式,才能進行建模。我們利用 RDKit 來計算 Morgan fingerprint 。子構造範圍設定為 radius = 2,向量長度為 1024 位元。然後將資料分割成 training data 和 test data。

from sklearn.model_selection import train_test_split

morgan_fps = []
for mol in df.ROMol:
fp = [x for x in AllChem.GetMorganFingerprintAsBitVect(mol, 2, 1024)]
morgan_fps.append(fp)
morgan_fps = np.array(morgan_fps)
target = df['measured log solubility in mols per litre']

X_train, X_test, y_train, y_test = train_test_split(morgan_fps, target, random_state=0)

由於 Elastic Net 的兩個超參數 ( α 和 r ) ,不像 Lasso 和 Ridge 只有一個超參數,我們需要更有效率的方法來找到好個超參數。

同時搜尋多個超參數最加值的的方法之一稱為 grid search。就我們這次要處理的 Elastic Net 而言,

  • α 為正規化程度
  • r 是 L1 正規化和 L2 正規化的混合比例

這兩個是超參數,因此我們將使用二維 grid search 進行搜​​尋。例如,如果我們嘗試 α 的 m 值和 r 的 n 值,將獲得 mn 精度,如下所示。

使用 scikit-learn 進行交叉驗證和 grid search 來找到 Elastic Net 的最佳超參數

我們先來測試一下 scikit-learn 的 K-fold 交叉驗證 (K-fold cross validation)。這將 Train data 分成 k 等分,在每個等分中,使用 validation data 進行評估,其餘資料進行訓練。一邊改變 validation 資料組,一邊重複 k 次。下圖展示了分為 5-fold 的情況。

我們可以使用 cross_val_score 函數執行 K-fold 交叉驗證。透過給參數 cv 指定一個數字來指定分割數。在下面的範例中,我們使用 Elastic Net 建立模型,其中固定超參數 α=0.01 和 r=0.5。

from sklearn.linear_model import ElasticNet
from sklearn.model_selection import cross_val_score
elastic = ElasticNet(alpha=0.01, l1_ratio=0.5, max_iter=100000)
scores = cross_val_score(elastic, X_train, y_train, cv=5)
print(scores)
print(scores.mean())

我們可以看到各次分割的結果,與平均的精度大約是 0.617。

[0.67382501 0.55513782 0.5889573 0.63374234 0.63322449]
0.6169773933600153

接下來,我們使用 scikit-learn 的 GridSearchCV 輕鬆執行 grid search。使用 param_grid 指定要搜尋的超參數組合。

在下面的程式碼中,指定了 6 種 alpha 值和 5 種 l1_ratio 值。我們對每個組合使用 5-fold 交叉驗證,因此我們總共建立模型 150 次。scikit-learn 的 GridSearchCV 可以像其他 scikit-learn 模型一樣使用 fit 進行訓練。

from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import ElasticNet
param_grid = {'alpha': [0.001, 0.01 , 0.1 , 1 , 10 , 100 ],
'l1_ratio':[0 , 0.25, 0.5, 0.75, 1 ]}
grid_search = GridSearchCV(ElasticNet(), param_grid, cv=5)
grid_search.fit(X_train, y_train)

可以使用 best_estimator_ 屬性存取訓練模型中給出最佳結果的模型。

此外,grid search 中的每個結果都可以透過 cv_results_ 獲得。下面的程式碼可取得平均交叉驗證分數,並取得按 alpha 和 l1_ratio 值排列的 6 x 5 numpy 陣列。

grid_scores = pd.DataFrame(grid_search.cv_results_)
scores = np.array(grid_scores.mean_test_score).reshape(6,5)
print(scores)

[[ 0.46681848 0.48502001 0.49381307 0.49249807 0.47911603]
[ 0.62415735 0.62453279 0.61697739 0.59958666 0.58493841]
[ 0.53461051 0.41932138 0.33465974 0.26738956 0.21386826]
[ 0.2368658 0.02011901 -0.01353982 -0.01353982 -0.01353982]
[ 0.03143361 -0.01353982 -0.01353982 -0.01353982 -0.01353982]
[-0.00859139 -0.01353982 -0.01353982 -0.01353982 -0.01353982]]

由於陣列的結果不好確認,我們對每個 grid 的分數進行可視化,然後可以發現,

  • 無論 l1_ratio 如何,較大的 alpha 值都會導致較低的分數。
  • 從best_estimator_可以看出,alpha=0.01,l1_ratio=0.25 的效果最好。
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)
mat = ax.matshow(scores,cmap='Reds',alpha=0.8)
fig.colorbar(mat)
ax.set_xticks(range(5))
ax.set_xticklabels([0, 0.25,0.5,0.75,1])
ax.set_xlabel('alpha')
ax.set_yticks(range(6))
ax.set_yticklabels([0.001, 0.01,0.1,1,10,100])
ax.set_ylabel('l1_ratio')

使用 scikit-learn 的 ElasticNetCV 類別進行 grid search

上述使用 GridSearchCV 進行超參數篩選是一種通用方法,可用於任何機器學習模型。

另一方面,scikit-learn 也提供了一些函數,可以對某些機器學習演算法使用交叉驗證自動篩選超參數。透過使用 ElasticNetCV ,可以自動執行交叉驗證來篩選 alpha 和 l1_ratio ,找到最佳超參數。

在下面的程式碼中,我們設定 6 個 alpha、5 個 l1_ratios,並使用 cv=5 執行交叉驗證。獲得的最佳超參數可以分別透過 alpha_ 和 l1_ratio_ 存取。它還將自動建立一個使用這些超參數使用所有資料進行訓練的模型。

from sklearn.linear_model import ElasticNetCV
elastic_cv = ElasticNetCV(alphas = [0.001, 0.01 , 0.1 , 1 , 10 , 100],
l1_ratio = [0 , 0.25, 0.5, 0.75, 1],
cv=5)
elastic_cv.fit(X_train, y_train)
print(elastic_cv.alpha_, elastic_cv.l1_ratio_)
print('train score: {:.3f}'.format(elastic_cv.score(X_trainval, y_trainval)))
print('test score: {:.3f}'.format(elastic_cv.score(X_test, y_test)))

0.01 0.25
train score: 0.791
test score: 0.625

結語

這次,我們用 ESOL 數據簡單地構築了 QSPR 模型,並使用交叉驗證來優化 Elastic Net,也介紹完了各種線性回歸的基本概念。之後,我們會開始介紹一些常用的非線性模型,來建構更好的 QSPR 模型。

--

--

Chemistry with data magic

I am working on improving material developments by creating machine learning analytical tools for chemical data to accelerate the material discovery.