#Datawhale AI夏令营# #AI# #夏令营#
第一步:启动魔塔notebook,运行baseline
首先按照指示下载zip并运行baseline的代码,运行代码过程如下:
1.导入库
首先,代码导入了需要用到的库,包括 pandas
(用于数据处理和分析),scikit-learn
(机器学习库),rdkit
(化学信息工具)。
2.读取数据
代码通过使用 pd.read_csv
函数从文件中读取训练集和测试集数据。
3.使用Morgan分子指纹建模SMILES
- 这个过程需要调用rdkit的相关模块。然后将Reactant1,Reactant2,Product,Additive,Solvent字段的向量拼接到一起,组成一个更长的向量。
4.使用随机森林预测结果
- 这里直接调用sklearn
的RandomForestRegressor
模块实例化一个随机森林模型,并对n_estimators
等重要参数进行指定。最后使用model.fit(x, y)训练模型。模型保存在本地'./random_forest_model.pkl'
。
5.加载模型进行预测,并将保存结果文件到本地
-
pkl
文件直接使用pickle.load()
加载,然后使用model.predict(x)
进行预测。预测的结果保存为比赛官方指定的文件格式。
***运行之后我注意到出现了一些有关虚拟环境的warning,查找相关资料后发现对整体代码运行并无影响:
pytorch-lightning
包的依赖解析错误:指出依赖项中的torch
版本要求表达式有误。该错误不会影响其他包的安装,但会导致pytorch-lightning
的某些功能无法正常使用。- 使用
root
用户运行pip
的警告:提示以root
用户身份运行pip
可能会导致权限问题和与系统包管理器的冲突,建议使用虚拟环境来隔离包的安装和管理。
第二步:详细解析baseline代码,解决疑惑点
1.函数 mfgen
这个函数用于生成分子的Morgan指纹(molecular fingerprint),其描述了分子的结构特征。
def mfgen(mol,nBits=2048, radius=2):
'''
Parameters
----------
mol : mol
RDKit mol object.
nBits : int
Number of bits for the fingerprint.
radius : int
Radius of the Morgan fingerprint.
Returns
-------
mf_desc_map : ndarray
ndarray of molecular fingerprint descriptors.
'''
# 返回分子的位向量形式的Morgan fingerprint
fp = rdMolDescriptors.GetMorganFingerprintAsBitVect(mol,radius=radius,nBits=nBits)
return np.array(list(map(eval,list(fp.ToBitString()))))
mol
:一个 RDKit 分子对象。nBits
:指纹的位数,默认值为2048。radius
:Morgan指纹的半径,默认值为2。
函数内部使用 rdMolDescriptors.GetMorganFingerprintAsBitVect
生成 Morgan 指纹,并将其转化为位字符串。然后,通过 eval
将字符串中的每一位转化为整数,最后将结果转化为 NumPy 数组。
2.函数 vec_cpd_lst
这个函数用于将一组SMILES(分子简写行表示法)转换为分子的特征向量。
def vec_cpd_lst(smi_lst):
smi_set = list(set(smi_lst))
smi_vec_map = {}
for smi in tqdm(smi_set): # tqdm:显示进度条
mol = Chem.MolFromSmiles(smi)
smi_vec_map[smi] = mfgen(mol)
smi_vec_map[''] = np.zeros(2048)
vec_lst = [smi_vec_map[smi] for smi in smi_lst]
return np.array(vec_lst)
smi_lst
:一个包含SMILES字符串的列表。smi_set
:去重后的SMILES字符串集合。smi_vec_map
:一个字典,用于存储SMILES字符串及其对应的分子指纹。
函数遍历每个唯一的SMILES字符串,使用 Chem.MolFromSmiles
将其转化为分子对象,然后调用 mfgen
生成指纹,并存储在字典 smi_vec_map
中。特殊情况下,将空字符串映射到全零数组。
最后,函数根据输入的 smi_lst
生成指纹列表,并将其转化为NumPy数组。
3.特征提取和构建特征矩阵(化学部分内容不是很明白)
①化学背景知识(题目):
碳氮成键反应、Diels-Alder环加成反应等一系列催化合成反应,被广泛应用于各类药物的生产合成中。研究人员与产业界在针对特定反应类型开发新的催化合成方法时,往往追求以高产率获得目标产物,也即开发高活性的催化反应体系,以提升原子经济性,减少资源的浪费与环境污染。然而,开发具有高活性的催化反应体系通常需要对包括催化剂和溶剂在内的多种反应条件进行详尽的探索,这导致了它成为了一项极为耗时且资源密集的任务。这要求对包括催化剂和溶剂在内的多种反应条件进行详尽的探索。目前,反应条件的筛选在很大程度上依赖于经验判断和偶然发现,导致催化反应条件的优化过程既耗时又费力,并且严重制约了新的高效催化合成策略的开发。
反应底物和反应条件是决定其产率的关键因素。因此,我们可以利用AI模型来捕捉底物、条件与产率之间的内在联系。借助产率预测AI模型,仅需输入底物和条件的信息,我们就能够预测该反应组合下的产率,从而有效提升催化反应的条件筛选效率。
官方发布的数据是对化学分子的SMILES表达式,具体来说,有rxnid, Reactant1, Reactant2, Product, Additive, Solvent, Yield字段。其中:
- rxnid 对数据的id标识,无实际意义
- Reactant1 反应物1
- Reactant2 反应物2
- Product 产物
- Additive 添加剂(包括催化剂catalyst等辅助反应物合成但是不对产物贡献原子的部分)
- Solvent 溶剂
- Yield 产率 其中Reactant1,Reactant2,Product,Additive,Solvent都是由SMILES表示。
②特征提取是从原始数据中提取出有意义的信息,以便机器学习算法可以理解和利用。这些信息称为特征(features)。特征可以是数据中的原始属性,也可以是通过某种方法从原始数据中提取出的新属性。
例子:假设我们有以下反应数据集:
反应物A | 反应物B | 溶剂 | 温度 | 时间 | 产率 |
---|---|---|---|---|---|
A1 | B1 | 水 | 80 | 2 | 90 |
A2 | B2 | 乙醇 | 100 | 1 | 85 |
A3 | B3 | 水 | 60 | 3 | 70 |
在这个例子中,反应物A
、反应物B
、溶剂
、温度
、时间
都是原始特征。我们可能需要对这些特征进行处理,例如:
- 对分类特征(如反应物A、反应物B、溶剂)进行编码(如独热编码,One-Hot Encoding)。
- 对数值特征(如温度、时间)进行标准化或归一化。
③SMILES,全称是Simplified Molecular Input Line Entry System,是一种将化学分子用ASCII字符表示的方法,是化学信息学领域非常重要的工具。
表1:一些常见的化学结构用SMILES表示。
表2:化学反应也可以用SMILES表示,用“>>”连接产物即可。
由于Reactant1,Reactant2,Product,Additive,Solvent都是由SMILES表示。所以,可以使用rdkit工具直接提取SMILES的分子指纹(向量),作为特征。
④Morgan fingerprint (分子指纹)
分子指纹(Molecular Fingerprint)是一种用来表示分子结构信息的方式,它将分子的结构特征转化为一个固定长度的二进制向量(由0和1组成)。这种表示方式在化学信息学中非常常见,用于分子相似性搜索、分类、回归等任务。分子指纹可以捕捉到分子中原子和键的模式,并将其编码成向量,以便机器学习模型处理。
⑤构建特征矩阵
构建特征矩阵(Feature Matrix)是机器学习中将原始数据转换为适合模型训练和预测的结构化格式的过程。在特征矩阵中,每一行通常表示一个样本,每一列表示一个特征。对于化学反应预测任务,特征矩阵的构建涉及从反应数据中提取有用的特征(如分子指纹)并将它们组合在一起。
例子:假设我们对上面的数据进行了特征提取和处理:
- 使用独热编码对分类特征进行编码。
- 保持数值特征不变。
处理后的特征矩阵可能如下所示:
A1 | A2 | A3 | B1 | B2 | B3 | 水 | 乙醇 | 温度 | 时间 |
---|---|---|---|---|---|---|---|---|---|
1 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 80 | 2 |
0 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 100 | 1 |
0 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 60 | 3 |
在这个特征矩阵中,每一行代表一个反应条件,每一列代表一个特征。分类特征(如反应物A、B、溶剂)被转换为独热编码后的多列,而数值特征(温度、时间)保持原样。
4.随机森林(概念)
# Model fitting
model = RandomForestRegressor(n_estimators=10,max_depth=10,min_samples_split=2,min_samples_leaf=1,n_jobs=-1) # 实例化模型,并指定重要参数
model.fit(train_x,train_y) # 训练模型
参数解释:
- n_estimators=10: 决策树的个数,越多越好;但是越多意味着计算开销越大;
- max_depth: (default=None)设置树的最大深度,默认为None;
- min_samples_split: 根据属性划分节点时,最少的样本数;
- min_samples_leaf: 叶子节点最少的样本数;
- n_jobs=1: 并行job个数,-1表示使用所有cpu进行并行计算。
1.概念
随机森林(Random Forest)是一种广泛使用的机器学习算法,主要用于分类和回归任务。它通过构建多个决策树并结合它们的结果来提高模型的准确性和稳定性。
基本概念:
①决策树:决策树是一种树状模型,用于决策和分类。节点表示属性测试。分支表示测试结果。叶子节点表示决策结果或类别。
②随机森林:随机森林是由多个决策树组成的集合每棵决策树都在不同的数据子集上训练,并且在每次分裂时只考虑随机选择的一部分特征。通过结合多棵树的预测结果,随机森林能有效减少过拟合,提高预测性能。
2. 工作原理
构建随机森林的步骤:
①随机采样:从原始数据集中有放回地随机抽取多个子集(Bootstrap抽样)。
②训练决策树:在每个子集上训练决策树。每次分裂节点时,从所有特征中随机选择一部分特征(特征子集),在这些特征上选择最佳分裂点。
③预测和投票:对于分类任务,每棵树对样本进行预测,选择多数投票的类别作为最终预测结果。对于回归任务,每棵树对样本进行预测,取所有预测结果的平均值作为最终预测结果
3.实际应用:分类任务和回归任务
这一次比赛的题目如下:
构建一个能够准确预测碳氮成键反应产率的预测模型。通过对反应中所包含的反应底物、添加剂、溶剂以及产物进行合理的特征化,运用机器学习模型或者深度学习模型拟合预测反应的产率。或者利用训练集数据对开源大语言模型进行微调以预测反应的产率。
总结:输入:底物和条件,(SMILES)
输出:产率,(float,0-1之间)
这是一个典型的回归问题。
第三步:优化代码的方法
一.超参数调优
# 超参数调优
param_grid = {
'n_estimators': [100, 200],
'max_depth': [10, 20, None],
'min_samples_split': [2, 5, 10],
'min_samples_leaf': [1, 2, 4]
}
rf = RandomForestRegressor(n_jobs=-1)
grid_search = GridSearchCV(estimator=rf, param_grid=param_grid, cv=5, n_jobs=-1, verbose=2)
grid_search.fit(train_x, train_y)
# 输出最佳参数
print(f'Best parameters found: {grid_search.best_params_}')
# 使用最佳参数训练最终模型
best_model = grid_search.best_estimator_
1.网格搜索(Grid Search)
网格搜索通过穷举法对参数空间进行遍历,从所有组合中找到最佳参数。适用于参数空间较小的情况。
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestRegressor
# 定义参数网格
param_grid = {
'n_estimators': [10, 50, 100, 200],
'max_depth': [None, 10, 20, 30],
'min_samples_split': [2, 5, 10],
'min_samples_leaf': [1, 2, 4]
}
# 初始化模型
rf = RandomForestRegressor(n_jobs=-1)
# 网格搜索
grid_search = GridSearchCV(estimator=rf, param_grid=param_grid, cv=5, n_jobs=-1, verbose=2)
grid_search.fit(train_x, train_y)
# 输出最佳参数
print(f'Best parameters found: {grid_search.best_params_}')
best_model = grid_search.best_estimator_
2.随机搜索(Random Search)
随机搜索在参数空间中随机采样进行评估,适用于参数空间较大的情况,可以找到近似最优解,效率比网格搜索高。
from sklearn.model_selection import RandomizedSearchCV
from sklearn.ensemble import RandomForestRegressor
from scipy.stats import randint
# 定义参数分布
param_dist = {
'n_estimators': randint(10, 200),
'max_depth': [None, 10, 20, 30],
'min_samples_split': randint(2, 11),
'min_samples_leaf': randint(1, 5)
}
# 初始化模型
rf = RandomForestRegressor(n_jobs=-1)
# 随机搜索
random_search = RandomizedSearchCV(estimator=rf, param_distributions=param_dist, n_iter=50, cv=5, n_jobs=-1, verbose=2, random_state=42)
random_search.fit(train_x, train_y)
# 输出最佳参数
print(f'Best parameters found: {random_search.best_params_}')
best_model = random_search.best_estimator_
3.贝叶斯优化(Bayesian Optimization)
贝叶斯优化通过构建代理模型(如高斯过程),以概率的方式逐步选择最优参数点,比网格搜索和随机搜索更高效。常用库包括 scikit-optimize
和 hyperopt
。
from skopt import BayesSearchCV
from sklearn.ensemble import RandomForestRegressor
# 定义参数空间
param_space = {
'n_estimators': (10, 200),
'max_depth': (10, 50, None),
'min_samples_split': (2, 10),
'min_samples_leaf': (1, 4)
}
# 初始化模型
rf = RandomForestRegressor(n_jobs=-1)
# 贝叶斯搜索
bayes_search = BayesSearchCV(estimator=rf, search_spaces=param_space, n_iter=50, cv=5, n_jobs=-1, verbose=2, random_state=42)
bayes_search.fit(train_x, train_y)
# 输出最佳参数
print(f'Best parameters found: {bayes_search.best_params_}')
best_model = bayes_search.best_estimator_
4.Hyperopt
hyperopt
是一个用于优化超参数的库,使用贝叶斯优化和TPE(Tree-structured Parzen Estimator)算法。
from hyperopt import fmin, tpe, hp, Trials, STATUS_OK
from hyperopt.pyll.base import scope
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score
# 定义目标函数
def objective(params):
rf = RandomForestRegressor(**params)
score = cross_val_score(rf, train_x, train_y, cv=5, scoring='neg_mean_squared_error').mean()
return {'loss': -score, 'status': STATUS_OK}
# 定义参数空间
param_space = {
'n_estimators': scope.int(hp.quniform('n_estimators', 100, 500, 1)),
'max_depth': scope.int(hp.quniform('max_depth', 10, 50, 1)),
'min_samples_split': scope.int(hp.quniform('min_samples_split', 2, 10, 1)),
'min_samples_leaf': scope.int(hp.quniform('min_samples_leaf', 1, 4, 1))
}
# 进行优化
trials = Trials()
best = fmin(fn=objective, space=param_space, algo=tpe.suggest, max_evals=50, trials=trials, rstate=np.random.default_rng(42))
# 输出最佳参数
print(f'Best parameters found: {best}')
二.交叉检验
1.k折交叉验证(k-Fold Cross-Validation)
将数据集分成k个子集,每次选择一个子集作为验证集,其余k-1个子集作为训练集,重复k次,最终结果是k次实验的平均值。
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestRegressor
# 初始化模型
rf = RandomForestRegressor()
# 进行5折交叉验证
scores = cross_val_score(rf, train_x, train_y, cv=5, scoring='neg_mean_squared_error')
# 输出每折的得分
print('Cross-validation scores:', scores)
# 输出平均得分
print('Mean cross-validation score:', scores.mean())
2.留一法交叉验证(Leave-One-Out Cross-Validation, LOOCV)
每次只选择一个样本作为验证集,其他样本作为训练集,重复n次(n为样本数)。这种方法计算开销较大,但适合小数据集。
from sklearn.model_selection import LeaveOneOut
from sklearn.ensemble import RandomForestRegressor
# 初始化模型
rf = RandomForestRegressor()
# 留一法交叉验证
loo = LeaveOneOut()
scores = cross_val_score(rf, train_x, train_y, cv=loo, scoring='neg_mean_squared_error')
# 输出每折的得分
print('LOOCV scores:', scores)
# 输出平均得分
print('Mean LOOCV score:', scores.mean())
3.分层k折交叉验证(Stratified k-Fold Cross-Validation)
在分类问题中使用,确保每个折中的类别分布与原始数据集相同,适用于类别不平衡的数据集。
from sklearn.model_selection import StratifiedKFold
from sklearn.ensemble import RandomForestClassifier
# 初始化模型
rf = RandomForestClassifier()
# 分层k折交叉验证
skf = StratifiedKFold(n_splits=5)
scores = cross_val_score(rf, train_x, train_y, cv=skf, scoring='accuracy')
# 输出每折的得分
print('Stratified cross-validation scores:', scores)
# 输出平均得分
print('Mean stratified cross-validation score:', scores.mean())