题目概述
第一次任务比较简单,仅仅运行给出的代码也可以获得分数。当然,如果想要提高成绩,就要调整模型超参数,或者使用其他模型。
大致要求:使用相关模型训练,对催化效率 yield 进行预测。
- 首先是数据的读取和处理。从给出的 csv 文件中 read_csv 依次提取出反应物一、反应物二、添加剂、溶剂并使用 to_list 转换成列表。这里我发现似乎并没有提取产物,不知道是题目不需要提取,还是让我们自己添加产物,提高模型精度(可惜当时没发现这里)。
- 在提取到原始数据后,还不能直接使用,因为我们的模型无法识别这一串奇怪的东西,现在需要将数据按照某种规则转化为特征向量。“幸运”地是,题目已经给出了相应的解决方案。原始数据采用的是化学分子的SMILES表达式,是一种将化学分子用ASCII字符表示的方法。我们只要使用rdkit工具直接提取SMILES的分子指纹(向量)Morgan fingerprint作为特征即可。
这里又涉及到两个比较陌生的知识:rdkit 工具,化学领域重要的开源工具(github地址),用于处理分子结构、化学反应、化学属性等信息,同时提供了丰富的化学信息处理功能,包括分子表示、相似性比较、药物设计、化学数据分析等等;Morgan fingerprint,摩根指纹通过考虑分子的局部环境来生成位向量表示,即由0,1组成的向量,根据资料,选择摩根指纹的原因正是摩根指纹能捕捉分子的局部化学环境,在许多化学信息学任务中很有价值。 - 最后在 dim = 1 维度使用 np.concatenate 进行拼接。即将一条数据的 Reactant1,Reactant2,Product,Additive,Solvent 字段的摩根指纹拼接为一个特征向量。
回到源码中,关键在于理解两个工具函数。
mfgen 函数用于生成分子的位向量形式的Morgan指纹。
def mfgen(mol,nBits=2048, radius=2):
'''
Parameters
----------
mol : mol
RDKit mol object. 一个 RDKit 的分子对象
nBits : int
Number of bits for the fingerprint. 指纹的位数。
radius : int
Radius of the Morgan fingerprint. Morgan 指纹的半径。
Returns
-------
mf_desc_map : ndarray
ndarray of molecular fingerprint descriptors.
'''
# 返回分子的位向量形式的Morgan fingerprint,调用GetMorganFingerprintAsBitVect即可
fp = rdMolDescriptors.GetMorganFingerprintAsBitVect(mol,radius=radius,nBits=nBits)
return np.array(list(map(eval,list(fp.ToBitString()))))
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) # 调用Chem.MolFromSmiles将 SMILES 字符串转化为分子对象
smi_vec_map[smi] = mfgen(mol) # 调用工具函数mfgen生成分子的 Morgan 指纹,并存储在字典中
smi_vec_map[''] = np.zeros(2048)
vec_lst = [smi_vec_map[smi] for smi in smi_lst]
return np.array(vec_lst) # 最终返回形式为NumPy数组
主要思路
虽然我在上个学期刚刚学习过学校开设人工智能课程,但是很遗憾,没有学过随机森林……只好从头开始。
一、调参
主要的思路就是调参了。代码中对于模型的参数进行了必要的介绍,我在网络上也进行了相应查找:
-
n_estimators(决策树的数量):
增加 n_extimators 通常会提高模型的性能,但会增加训练时间和内存使用。 -
max_depth(树的最大深度):
增加 max_depth 可以捕捉更多的细节,但也可能导致过拟合。 -
min_samples_split(内部节点再划分所需的最小样本数):
增加 min_samples_split 可以减少过拟合,但可能也会增加偏差。 -
min_samples_leaf(叶节点最少样本数):
增加 min_samples_leaf 可以减少过拟合,特别是对于噪声较大的数据集。 - n_jobs(并行job个数):
-1表示使用所有 cpu 进行并行计算。
初始代码如下
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) # 训练模型
可以看到其实参数是比较小的。结合资料,我将四个参数逐步调高,最终选择500,50,10,4的组合。当然,模型训练时间也相应变长。成绩提高到0.3以上。(后来了解到决策树数量似乎影响不大,突然发现自己的方向好像搞错了……)
二、模型优化
此外考虑对模型优化,我主要使用了标准化特征和重要特征选择提高模型稳定性,防止过拟合。主要代码如下:
# 注意导入
from sklearn.preprocessing import StandardScaler
from sklearn.feature_selection import SelectFromModel
# 标准化特征
scaler = StandardScaler()
train_x_scaled = scaler.fit_transform(train_x)
test_x_scaled = scaler.transform(test_x)
# 使用随机森林进行特征选择
feature_selector = RandomForestRegressor(n_estimators=500, max_depth=50, min_samples_split=10, min_samples_leaf=4, n_jobs=-1)
feature_selector.fit(train_x_scaled, train_y)
# 使用 SelectFromModel 选择重要特征
selector = SelectFromModel(feature_selector, prefit=True)
train_x_selected = selector.transform(train_x_scaled)
test_x_selected = selector.transform(test_x_scaled)
# 训练最终模型
model = RandomForestRegressor(n_estimators=500, max_depth=50, min_samples_split=10, min_samples_leaf=4, n_jobs=-1)
model.fit(train_x_selected, train_y)
成绩微升0.01左右。
三、模型替换
我还尝试了另一个思路:替换模型。我了解到了两种类似的模型:GradientBoostingRegressor和AdaBoostRegressor。而且GradientBoostingRegressor在复杂数据集上的表现有时要优于随机森林模型。于是我将模型替换为GradientBoostingRegressor。
不过我又发现了严重的问题:GradientBoostingRegressor的训练时间更长!我只好将相应的参数适当调小,最终效果如下:
# 注意导入
from sklearn.ensemble import GradientBoostingRegressor, AdaBoostRegressor
scaler = StandardScaler()
train_x_scaled = scaler.fit_transform(train_x)
test_x_scaled = scaler.transform(test_x)
# 替换为GradientBoostingRegressor,大致逻辑不变
feature_selector = GradientBoostingRegressor(n_estimators=50, max_depth=50, min_samples_split=10, min_samples_leaf=4)
feature_selector.fit(train_x_scaled, train_y)
selector = SelectFromModel(feature_selector, prefit=True)
train_x_selected = selector.transform(train_x_scaled)
test_x_selected = selector.transform(test_x_scaled)
model = GradientBoostingRegressor(n_estimators=50, max_depth=50, min_samples_split=10, min_samples_leaf=4)
model.fit(train_x_selected, train_y)
成绩不升反降……如果以后有机会,再进行调参尝试。
总结
贴上最终成绩
这次的任务总体上不需要自己书写或修改太多东西,只需要多进行调参尝试就有不错的成绩(指排行榜位次)。还是要注意不同参数对结果的影响程度,避免出现牺牲过多效率,得分只有微量提升的情况。
大家的成绩似乎都集中在0.3~0.4,说明大多数同学也只是进行了调参优化。我对自己task1的成绩还算满意。下一个任务加油!