最近终于搞完了毕业论文。闲下来写点东西。
学会贝叶斯优化之后终于从grid search里毕业的感觉真是太好了,效率直线上升。
关于贝叶斯优化的介绍以及理论很多文章都有过介绍这里就不多说了。搬运一个无利益关系的地址:
Dai Zhongxiang:贝叶斯优化/Bayesian Optimizationzhuanlan.zhihu.com总而言之就是两个字就是:好用 ! !
仔细kaggle上找的代码还是稍微有点繁琐的。这篇有点水的文就当自己的备忘录了。为了方便使用我把一个简单的预测溶解度的问题搬过来做一个贝叶斯优化调参的tutorial。
首先导入所需要的包:
from rdkit import rdBase, Chem
from rdkit.Chem import AllChem, Draw, PandasTools, Descriptors
from rdkit.Chem.Draw import IPythonConsole
import numpy as np
import pandas as pd
import urllib.request
然后从github上下载数据并进行整理。这是一个稍微小点的1000个左右的化合物分子及其溶解度的数据。使用rdkit的pandastools让分子式也同时显示出来。
url = 'https://raw.githubusercontent.com/HIPS/neural-fingerprint/master/data/2015-05-24-delaney/ci034243xsi20040112_053635.txt'
urllib.request.urlretrieve(url, 'water_solubility.txt')
df = pd.read_csv('water_solubility.txt', sep=',')
PandasTools.AddMoleculeColumnToFrame(frame=df, smilesCol='smiles')
df.columns = ['ID', 'm_sol', 'p_sol', 'SMILES', 'ROMol']
df.head()
然后得到这样的结果
这里的m_sol是根据实验测得的溶解度。我们把他当成y。
y=df['m_sol']
将化合物的smiles转化成mol然后再计算他们的摩根分子指纹(Morgan FingerPrint)。这里我们设定的半径是2然后bit数设定为2048。其实一般来说可能设成3比较好因为可以正好把苯环计算在内。但是实际使用的时候还是要自己去调整的。
mols=[]
for i in df['SMILES']:
mols.append(Chem.MolFromSmiles(i))
fingerprint=[]
for i in mols:
fingerprint.append(AllChem.GetMorganFingerprintAsBitVect(i, 2, 2048))
fingerprint=np.array(fingerprint)
fingerprint
得到010101。。。格式的分子指纹。
在构筑QSPR模型使用分子指纹来预测溶解度之前,我们可以开始布置贝叶斯优化的相关函数了。首先是导入需要的贝叶斯优化和sklearn的包。这里我用的是BayesianOptimization这个包。但实际上还有挺多的类似的包比如Gpy,optuna之类的,但是用起来各有各的搞法。这篇文章主要讲BayesianOptimization。
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_predict
from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score
from bayes_opt import BayesianOptimization
from bayes_opt.util import Colours
第一个函数是这样的:
def rfc_cv(n_estimators, min_samples_split, max_features, data, targets):
estimator = RandomForestRegressor(
n_estimators=n_estimators,
min_samples_split=min_samples_split,
max_features=max_features,
random_state=2
)
cval = cross_val_score(estimator, data, targets,
scoring='r2', cv=3)
return cval.mean()
将任意一个机器学习的手法设定为estimator,这里我们实用的是随机森林回归。因此这个函数的输入内容是所需要调节的参数n_estimators, min_samples_split, max_features这三个随机森林一般调的参数。当然你可以根据需要加几个参数或者改成辅助向量机等其他的手法。
此外还要输入的是data, targets也就是分子指纹和溶解度的实验数据。然后给一个模型选择时所用到的cv-score。这个函数所需要输入的是之前指定的estimator,data, targets,还有这个scoring就是需要指定一个cv分数的评判指标,可选的项目在sklearn里的相关文件可以查到如下:
['accuracy', 'adjusted_rand_score', 'average_precision', 'f1', 'f1_macro',
'f1_micro', 'f1_samples', 'f1_weighted', 'log_loss', 'mean_absolute_error',
'mean_squared_error', 'median_absolute_error', 'precision',
'precision_macro', 'precision_micro', 'precision_samples',
'precision_weighted', 'r2', 'recall', 'recall_macro', 'recall_micro',
'recall_samples', 'recall_weighted', 'roc_auc']
这么一堆。当然你要根据你的问题类型来选择好用的指标。最后是cv为了方便就设成3但是一般是用5或者10 fold cross-validation的相对多一点。最后这个函数输出的是cv-score的平均值。我们用这个分数作为选择合适的超参数组合的指标。
接下来是贝叶斯优化调参所用到的函数:
def optimize_rfc(data, targets):
def rfc_crossval(n_estimators, min_samples_split, max_features):
return rfc_cv(
n_estimators=int(n_estimators),
min_samples_split=int(min_samples_split),
max_features=max(min(max_features, 0.999), 1e-3),
data=data,
targets=targets,
)
optimizer = BayesianOptimization(
f=rfc_crossval,
pbounds={
"n_estimators": (10, 300),
"min_samples_split": (2, 30),
"max_features": (0.1, 1.0),
},
random_state=1234,
verbose=2
)
optimizer.maximize(n_iter=5)
params=optimizer.max['params']
modify_para=list(params.values())
best_param={'max_features':round(modify_para[0],3),
'min_samples_split':int(modify_para[1]),
'n_estimators':int(modify_para[2])
}
print(best_param)
print(round(optimizer.max['target'],4))
return(best_param)
首先主函数是optimize_rfc我们最后用这个函数完成所有操作。他的输入很简单就是分子指纹和溶解度数据。下面的一个小函数是rfc_crossval,这个是用来检测某个超参数组合的效果的。这里要注意的是,贝叶斯优化是基于高斯过程来对参数进行推定,所以他给出的一些数值不一定是整数。而有的超参数比如说决定树的数量这个一定是整数,所以我们要把他修正成整数这里给他一个int。此外还有一些参数是必须要在0到1区间的,如果超过1的话当然就运行不下去了。比如随机森林的max_features是一个比例所以我们要把它限制在0到1之间。如果贝叶斯优化给出超过这个领域的数值那就默认就是0.999了。
这里我们的优化器optimizer也就是BayesianOptimization他里面的参数f也就是测试用的函数组就是我们前面指定的rfc_crossval。另一个pbounds这个参数是用来设定哥哥参数的调参范围,这里根据经验大概设定一下。这里为了方便就设成300。
下一个optimizer.maximize(n_iter=5) 通过这一项我们来设定贝叶斯优化尝试的次数。贝叶斯优化每进行一次尝试之后就会对概率进行更新然后选择相对来说概率较高的组合来进行下一次实验。尝试次数越多一般就越逼近最优解。这里为了节省时间设成5。但实际上这个包它会根据自己需要去增加尝试次数。
最后输出最好的超参数集,当然也要根据贝叶斯优化的特性进行一些编辑。比如说调整成整数,有小数点的就保留到小数点后三位方便计算。
函数准备好了接下来就能用了。
optimize_rfc(fingerprint,y)
在有过程中相对较优的解会被标记成紫色,输出的结果是这样的:
最后得到最佳的超参数集:
{'max_features': 0.101, 'min_samples_split': 2, 'n_estimators': 69}
用这个超参数去训练模型:
best_para={'max_features': 0.101, 'min_samples_split': 2, 'n_estimators': 69}
clf = RandomForestRegressor(**best_para)
clf.fit(fingerprint,y)
简单的看下结果:
from sklearn.metrics import r2_score
print("R2=", r2_score(y, clf.predict(fingerprint)))
import numpy as np
from sklearn.metrics import mean_squared_error
print("RMSE=", np.sqrt(mean_squared_error(y, clf.predict(fingerprint))))
得到:
R2= 0.9461141173121997
RMSE= 0.48645522216453174
嗯。