首先,我们先用电脑打开此链接Datawhale进入Datawhale速通手册并用微信登录,左侧栏用于提交最新分数、学习笔记以及打卡,右侧便是速通手册。
接下来,按照速通手册进行操作,就可以跑通第一个baseline。
将文件提交到比赛官网便可以得到自己的分数,第一次跑通baseline得分在0.2左右。
接下来将讲解提高分数的第一种方法,修改随机森林模型的参数。
原代码 可以调节的参数有:
1. n_estimators
这是随机森林中决策树的数量。通常情况下,增加决策树的数量可以提高模型的稳定性和准确性,但也会增加计算成本。对于大型数据集,通常可以从100开始尝试,然后逐渐增加到几百棵。
2. max_depth
这是单个决策树的最大深度。较深的树可能会导致过拟合,而较浅的树可能无法捕捉到足够的数据模式。我们可以考虑使用一个较大的值,如None(默认值,让树完全生长),或者限制到一个合理的值,比如30。
3. min_samples_split
这是允许节点分裂所需的最小样本数。默认通常是2。对于较大的数据集,可以考虑稍微增加这个值,例如5或10,以减少过拟合的风险。
4. min_samples_leaf
这是叶子节点所需的最小样本数。默认通常是1。对于较大的数据集,可以考虑稍微增加这个值,例如2或4,以减少过拟合的风险。
请注意,
- 如果发现模型过拟合,可以考虑减少
max_depth
或者增加min_samples_leaf
。 - 如果模型欠拟合,可以尝试增加
n_estimators。
调节参数时,决策树的数量越多,树的深度越深,程序运行时间越久,不建议将参数调的过于大。
接下来将讲解提高分数的第二种方法,贝叶斯优化。
以下是完整的步骤:
- 安装
scikit-optimize
库。 - 导入必要的库。
- 定义一个目标函数,该函数使用随机森林回归器并返回交叉验证得分。
- 使用贝叶斯优化搜索超参数空间。
- 使用找到的最佳超参数重新训练模型,并进行预测。
下面便是优化后的代码。
!pip install pandas
!pip install -U scikit-learn
!pip install rdkit
!pip install scikit-optimize
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score
from skopt import BayesSearchCV
from rdkit.Chem import rdMolDescriptors
from rdkit import RDLogger, Chem
from rdkit.Chem import AllChem
import pickle
from tqdm import tqdm
import os
# 禁止RDKit的日志输出
RDLogger.DisableLog('rdApp.*')
# 定义函数以生成分子指纹
def mfgen(mol, nBits=2048, radius=2):
fp = rdMolDescriptors.GetMorganFingerprintAsBitVect(mol, radius=radius, nBits=nBits)
return np.array(list(map(int, list(fp.ToBitString()))))
# 加载数据
def vec_cpd_lst(smi_lst):
smi_set = list(set(smi_lst))
smi_vec_map = {}
for smi in tqdm(smi_set):
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)
# 获取数据集路径
dataset_dir = '../dataset'
# 读取训练和测试数据
train_df = pd.read_csv(os.path.join(dataset_dir, 'round1_train_data.csv'))
test_df = pd.read_csv(os.path.join(dataset_dir, 'round1_test_data.csv'))
print(f'Training set size: {len(train_df)}, test set size: {len(test_df)}')
# 从CSV中读取数据
train_rct1_smi = train_df['Reactant1'].to_list()
train_rct2_smi = train_df['Reactant2'].to_list()
train_add_smi = train_df['Additive'].to_list()
train_sol_smi = train_df['Solvent'].to_list()
# 将SMILES转化为分子指纹
train_rct1_fp = vec_cpd_lst(train_rct1_smi)
train_rct2_fp = vec_cpd_lst(train_rct2_smi)
train_add_fp = vec_cpd_lst(train_add_smi)
train_sol_fp = vec_cpd_lst(train_sol_smi)
# 拼接指纹向量
train_x = np.concatenate([train_rct1_fp, train_rct2_fp, train_add_fp, train_sol_fp], axis=1)
train_y = train_df['Yield'].to_numpy()
# 测试集也进行同样的操作
test_rct1_smi = test_df['Reactant1'].to_list()
test_rct2_smi = test_df['Reactant2'].to_list()
test_add_smi = test_df['Additive'].to_list()
test_sol_smi = test_df['Solvent'].to_list()
test_rct1_fp = vec_cpd_lst(test_rct1_smi)
test_rct2_fp = vec_cpd_lst(test_rct2_smi)
test_add_fp = vec_cpd_lst(test_add_smi)
test_sol_fp = vec_cpd_lst(test_sol_smi)
test_x = np.concatenate([test_rct1_fp, test_rct2_fp, test_add_fp, test_sol_fp], axis=1)
# 定义目标函数
def objective(params):
model = RandomForestRegressor(**params, n_jobs=-1)
score = -cross_val_score(model, train_x, train_y, cv=5, scoring='neg_mean_squared_error').mean()
return score
# 定义超参数搜索空间
param_space = {
'n_estimators': (50, 100),
'max_depth': (10, 50),
'min_samples_split': (2, 4),
'min_samples_leaf': (1, 3),
'max_features': ('None', 'sqrt', 'log2')
}
# 创建贝叶斯优化器
optimizer = BayesSearchCV(
estimator=RandomForestRegressor(),
search_spaces=param_space,
n_iter=50,
cv=5,
scoring='neg_mean_squared_error',
n_jobs=-1,
verbose=0
)
# 进行贝叶斯优化
optimizer.fit(train_x, train_y)
# 打印最佳超参数
best_params = optimizer.best_params_
print("Best parameters found:", best_params)
# 使用最佳参数重新训练模型
best_model = RandomForestRegressor(**best_params, n_jobs=-1)
best_model.fit(train_x, train_y)
# 保存模型
with open('./random_forest_model.pkl', 'wb') as file:
pickle.dump(best_model, file)
# 加载模型
with open('random_forest_model.pkl', 'rb') as file:
loaded_model = pickle.load(file)
# 预测
test_pred = loaded_model.predict(test_x)
# 格式化预测结果
ans_str_lst = ['rxnid,Yield']
for idx, y in enumerate(test_pred):
ans_str_lst.append(f'test{idx + 1},{y:.4f}')
# 保存预测结果到文件
with open('./submit.txt', 'w') as fw:
fw.write('\n'.join(ans_str_lst))
请注意这个方法本质上是把搜索空间内的参数全运行一遍,找到最佳参数,因此运行时间非常长。
为了缩短运行时间,我们可以采取几种策略来优化贝叶斯优化的过程。以下是一些建议:
- 减少迭代次数 (
n_iter
): 减少搜索的迭代次数可以显著减少运行时间。 - 简化搜索空间: 通过减少搜索空间的范围或选项数量,可以更快地收敛到较好的超参数组合。
- 减少交叉验证折叠数 (
cv
): 减少交叉验证的折叠数也可以减少运行时间,但这可能会增加模型评估的方差。
如果嫌运行时间太长,可以使用第一种方法,直接调参数。