Datawhale AI 夏令营 催化反应产率预测 Task1

从零入门AI for Science(AI+化学)

比赛链接:上海科学智能研究院 (sais.com.cn)

一、比赛任务

       本次比赛提供在药物合成中常见的多种催化反应实验数据,其中包括反应的底物、包括催化剂在内的反应添加剂、反应溶剂以及反应产物,期待选手通过分析反应数据,利用机器学习、深度学习算法或者大语言模型,建立产率预测模型,从而辅助未知新反应的反应条件筛选。

任务:构建一个能够准确预测碳氮成键反应产率的预测模型

通过对反应中所包含的反应底物、添加剂、溶剂以及产物进行合理的特征化,运用机器学习模型或者深度学习模型拟合预测反应的产率。或者利用训练集数据对开源大语言模型进行微调以预测反应的产率

二、baseline代码

 1)导入基本包

import pickle  # 导入pickle模块,用于序列化和反序列化Python对象
import pandas as pd  # 导入pandas库,用于数据处理和分析
from tqdm import tqdm  # 从tqdm库导入tqdm,用于显示进度条
from sklearn.ensemble import RandomForestRegressor  # 从sklearn库导入RandomForestRegressor,用于随机森林回归模型
from rdkit.Chem import rdMolDescriptors  # 从rdkit.Chem导入rdMolDescriptors,用于计算分子描述符
from rdkit import RDLogger, Chem  # 从rdkit导入RDLogger和Chem,用于处理分子和日志记录
import numpy as np  # 导入numpy库,用于科学计算
RDLogger.DisableLog('rdApp.*')  # 禁用RDKit的日志记录,避免不必要的输出

2)将SMILES字符串列表转换为分子指纹向量的列表

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.
    '''
    fp = rdMolDescriptors.GetMorganFingerprintAsBitVect(mol,radius=radius,nBits=nBits)
    return np.array(list(map(eval,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)

解释mfgen函数生成一个分子的Morgan指纹。它接受一个RDKit的分子对象、指纹的位数(默认为2048位)和半径(默认为2),然后计算并返回该分子的指纹描述符,作为一个numpy数组

vec_cpd_lst函数将SMILES字符串列表转换为分子指纹向量的列表。它先创建一个包含唯一SMILES字符串的集合,然后计算每个SMILES对应的指纹向量,并将其存储在字典中。最后,它将输入列表中的每个SMILES转换为其指纹向量,并以numpy数组的形式返回这些向量。

3)导入训练集与测试集

dataset_dir = './dataset'   # Change this to your dataset directory

train_df = pd.read_csv(f'{dataset_dir}/round1_train_data.csv')
test_df = pd.read_csv(f'{dataset_dir}/round1_test_data.csv')

print(f'Training set size: {len(train_df)}, test set size: {len(test_df)}')
Training set size: 23538, test set size: 2616

4)处理训练集和测试集

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()

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)
100%|██████████████████████████████████████████████████████████████████████████████| 6081/6081 [05:16<00:00, 19.24it/s]
100%|██████████████████████████████████████████████████████████████████████████████| 8763/8763 [07:22<00:00, 19.79it/s]
100%|██████████████████████████████████████████████████████████████████████████████| 1284/1284 [01:18<00:00, 16.26it/s]
100%|████████████████████████████████████████████████████████████████████████████████| 432/432 [00:29<00:00, 14.48it/s]
100%|██████████████████████████████████████████████████████████████████████████████| 1257/1257 [00:54<00:00, 23.21it/s]
100%|██████████████████████████████████████████████████████████████████████████████| 1743/1743 [00:46<00:00, 37.15it/s]
100%|████████████████████████████████████████████████████████████████████████████████| 404/404 [00:14<00:00, 27.65it/s]
100%|████████████████████████████████████████████████████████████████████████████████| 143/143 [00:09<00:00, 15.24it/s]

使用vec_cpd_lst函数将测试数据集中每种反应物、添加剂和溶剂的SMILES字符串转换为分子指纹表示。这将为每个SMILES字符串生成一个指纹向量,并将所有这些指纹向量存储在相应的变量中。

将测试数据集中反应物1、反应物2、添加剂和溶剂的分子指纹向量在列(特征)方向上拼接,形成一个大的特征矩阵test_x,其中每行对应一个测试样本,每列对应一个特征(分子指纹位)。

5)模型的训练及保存

# Model fitting
model = RandomForestRegressor(n_estimators=100,max_depth=10,min_samples_split=2,min_samples_leaf=1,n_jobs=-1)
model.fit(train_x,train_y)

# Saving model to file
with open('./random_forest_model.pkl', 'wb') as file:
    pickle.dump(model, file)

# Load model from file
with open('random_forest_model.pkl', 'rb') as file:
    loaded_model = pickle.load(file)

# Inference
test_pred = loaded_model.predict(test_x)

dataline中使用了随机森林回归模型,以下是基本介绍:

基本概念

  • 集成学习:随机森林是一种集成学习方法,它通过结合多个学习器(决策树)来改善模型的预测性能和稳定性。
  • 决策树:决策树是一种树状结构的模型,用于将数据分割成更小的部分,从而做出预测。每个节点表示对某个特征的判断,叶子节点表示预测结果。
  • 随机性:随机森林通过引入随机性来增强模型的泛化能力。这包括两方面:
    • 样本随机性:通过对训练数据进行有放回的随机抽样(称为Bootstrap抽样)来训练每棵树。
    • 特征随机性:在每个节点分裂时,只考虑一部分随机选择的特征,而不是全部特征。

工作原理

  1. 构建多个决策树:通过对训练数据进行Bootstrap抽样,构建多个决策树。每棵树在不同的样本上训练,同时在每个节点的分裂过程中只考虑部分特征。
  2. 集成决策:对于回归任务,随机森林模型通过对所有树的预测结果取平均值来进行最终的预测。这个过程减少了单个树可能出现的过拟合现象,从而提高了整体模型的稳定性和准确性。

优点

  • 高准确性:通过结合多个决策树,随机森林通常比单个决策树具有更高的准确性。
  • 抗过拟合:由于随机森林在训练过程中引入了随机性,它对训练数据的过拟合风险较低。
  • 处理高维数据:随机森林能够处理高维数据集,并且不需要对数据进行标准化或归一化处理。
  • 特征重要性:随机森林可以评估每个特征的重要性,有助于进行特征选择。

缺点

  • 训练时间长:由于需要构建多个决策树,随机森林的训练时间可能较长,尤其是在数据量大或特征多的情况下。
  • 预测速度慢:由于需要结合多个决策树的结果,随机森林的预测速度相对较慢。

6)保存结果

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.writelines('\n'.join(ans_str_lst))

运行后会生成一个文件submit.txt ,然后在提交平台提交就可以得到分数啦!
这里我得到了0.1996分,哈哈好像大家的分数都不太一样,有分数还是挺好玩的。

三、完整代码展示:

import pickle
import pandas as pd
from tqdm import tqdm
from sklearn.ensemble import RandomForestRegressor
from rdkit.Chem import rdMolDescriptors
from rdkit import RDLogger,Chem
import numpy as np
RDLogger.DisableLog('rdApp.*')

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.
    '''
    fp = rdMolDescriptors.GetMorganFingerprintAsBitVect(mol,radius=radius,nBits=nBits)
    return np.array(list(map(eval,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)

## Vectorization

dataset_dir = './dataset'   # Change this to your dataset directory

train_df = pd.read_csv(f'{dataset_dir}/round1_train_data.csv')
test_df = pd.read_csv(f'{dataset_dir}/round1_test_data.csv')

print(f'Training set size: {len(train_df)}, test set size: {len(test_df)}')

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()

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)

## Model fitting and saving

# Model fitting
model = RandomForestRegressor(n_estimators=100,max_depth=10,min_samples_split=2,min_samples_leaf=1,n_jobs=-1)
model.fit(train_x,train_y)

# Saving model to file
with open('./random_forest_model.pkl', 'wb') as file:
    pickle.dump(model, file)

# Load model from file
with open('random_forest_model.pkl', 'rb') as file:
    loaded_model = pickle.load(file)

# Inference
test_pred = loaded_model.predict(test_x)

## Answer generation

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.writelines('\n'.join(ans_str_lst))

我又用了lightgbm模型尝试训练了一下,分数提高了一些,参数改一改可能会更高!可惜没有提交次数了

import pickle
import pandas as pd
from tqdm import tqdm
from rdkit.Chem import rdMolDescriptors
from rdkit import RDLogger, Chem
import numpy as np
import lightgbm as lgb

RDLogger.DisableLog('rdApp.*')

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.
    '''
    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(f'{dataset_dir}/round1_train_data.csv')
test_df = pd.read_csv(f'{dataset_dir}/round1_test_data.csv')

print(f'训练集大小: {len(train_df)}, 测试集大小: {len(test_df)}')

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()

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)

## 模型拟合与保存

# 模型拟合
model = lgb.LGBMRegressor(n_estimators=100, max_depth=10, learning_rate=0.1, num_leaves=31)
model.fit(train_x, train_y)

# 保存模型到文件
with open('./lightgbm_model.pkl', 'wb') as file:
    pickle.dump(model, file)

# 从文件加载模型
with open('lightgbm_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.writelines('\n'.join(ans_str_lst))

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值