催化反应产率预测赛题–Datawhale AI夏令营
赛题概况
背景
碳氮成键反应、Diels-Alder环加成反应等一系列催化合成反应,被广泛应用于各类药物的生产合成中。研究人员与产业界在针对特定反应类型开发新的催化合成方法时,往往追求以高产率获得目标产物,也即开发高活性的催化反应体系,以提升原子经济性,减少资源的浪费与环境污染。然而,开发具有高活性的催化反应体系通常需要对包括催化剂和溶剂在内的多种反应条件进行详尽的探索,这导致了它成为了一项极为耗时且资源密集的任务。这要求对包括催化剂和溶剂在内的多种反应条件进行详尽的探索。目前,反应条件的筛选在很大程度上依赖于经验判断和偶然发现,导致催化反应条件的优化过程既耗时又费力,并且严重制约了新的高效催化合成策略的开发。
反应底物和反应条件是决定其产率的关键因素。因此,我们可以利用AI模型来捕捉底物、条件与产率之间的内在联系。借助产率预测AI模型,仅需输入底物和条件的信息,我们就能够预测该反应组合下的产率,从而有效提升催化反应的条件筛选效率。
数据集概况
数据集仅包含碳氮成键类型反应数据,其中训练集中包含23538条反应数据,测试集中包含2616条反应数据。
rxnidReactant1Reactant2ProductAdditiveSolventYieldtrain1c1ccc2c(c1)Nc1ccccc1O2Brc1ccccc1IBrc1ccccc1N1c2ccccc2Oc2ccccc21CC©©[O-].CC©©PH+C©©C.FB-(F)F.FB-(F)F.O=C(C=Cc1ccccc1)C=Cc1ccccc1.O=C(C=Cc1ccccc1)C=Cc1ccccc1.[H+].[Na+].[Pd]Cc1ccccc10.78train2c1ccc2c(c1)Nc1ccccc1O2Brc1ccccc1IBrc1ccccc1N1c2ccccc2Oc2ccccc21C1COCCOCCOCCOCCOCCO1.O=C([O-])[O-].[Cu+].[I-].[K+].[K+]Clc1ccccc1ClClc1ccccc1Cl0.90train3c1ccc2c(c1)Nc1ccccc1O2Brc1ccccc1IBrc1ccccc1N1c2ccccc2Oc2ccccc21CC(=O)[O-].CC(=O)[O-].CC©©[O-].CC©©PH+C©©C.FB-(F)F.FB-(F)F.[H+].[Na+].[Pd+2]CC1©C=CC=CC10.85
baseline1
基础版
该阶段的处理思路是先使用Morgan分子指纹建模SMILES,将化学语言编码为计算机能够处理的向量形式,再将其输入到随机森林模型中训练,最后在测试集数据上进行预测。关键代码片段如下。
1.生成分子指纹描述符
defmfgen(mol,nBits=2048, radius=2):
fp = rdMolDescriptors.GetMorganFingerprintAsBitVect(mol,radius=radius,nBits=nBits)return np.array(list(map(eval,list(fp.ToBitString()))))
2.将SMILES序列向量化
defvec_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)
3.随机森林
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)
对于该基础模板代码,提交了一份结果,只有0.2034的分数。
在基于机器学习的基础上,改进思路大致往两个方面。
- 运用效果更好的回归算法(改进1、改进2)
- 更换其他编码方式(改进3)
- 数据预处理(改进4)
改进1–LightGBM
将随机森林算法换成了LightGBM。
LightGBM主要通过以下两个方面进行了改进来提升性能:
(1) 直方图算法
LightGBM使用直方图算法来降低计算成本。该算法首先离散连续值,然后以此构建直方图,此后依据遍历样本数据时积聚的量得到最合适的分裂点。相比于其他GBDT算法使用的预排序算法,直方图算法大大降低了时间复杂度,只需要计算次数。
(2) 带深度限制的leaf-wise策略
LightGBM在直方图算法的基础上进一步优化,采用了带深度限制的leaf-wise策略。传统的GBDT工具通常使用按层生长(level-wise)的决策树生长策略,而LightGBM采用了更高效的按叶子生长(leaf-wise)算法。Leaf-wise策略对分割节点的选择是以增益为准的,相比于level-wise策略,leaf-wise有着更低的误差以及更优的效果。以上两种节点分裂方式如下图所示。
对于代码上,只需更换预测部分的模型选择
from lightgbm import LGBMRegressor
import joblib
model = LGBMRegressor(n_estimators=100, max_depth=10, learning_rate=0.1, device="gpu")
model.fit(train_x, train_y)
joblib.dump(model,'lightgbm_model.pkl')
loaded_model = joblib.load('lightgbm_model.pkl')
test_pred = loaded_model.predict(test_x)
最后的结果是相较基础baseline有少量提升,达到了0.2564。
改进2–XGBoost
XGBoost是对GBDT的一种高效实现,在很多比赛中都有着优秀的表现,故下一步的优化将采用它。
代码部分,同样只需要更换模型即可
from xgboost import XGBRegressor
#model = XGBRegressor(n_estimators=100, max_depth=10, learning_rate=0.1, n_jobs=-1)
model = XGBRegressor(n_estimators=100, max_depth=10, learning_rate=0.1, tree_method='hist',device ="cuda")
model.fit(train_x, train_y)import joblib
joblib.dump(model,'xgboost_model.pkl')
loaded_model = joblib.load('xgboost_model.pkl')
test_pred = loaded_model.predict(test_x)
这里有几个参数可以进行调整
- n_estimators:决策树的数量,相对来说越多越好
- max_depth:决策树的最大深度,越深能涵盖更多信息,但是容易过拟合
- learing_rate:学习率
这一次降低了学习率,想着说让RNN学的更细致一点
最终通过调参将得分从0.3031提升至0.3442,是目前最好的结果了。
这一部分因为涉及到调参,以及每天3次的提交限制,卡了很多天,但是和头部大佬仍有很大差距,这奖学金就不是我该拿的😇😇😇
改进3–其他编码方式
化学信息学的知识并不是很熟悉,尝试了rdkit库里的其他编码方式,没什么变化???🙄
群里那个改编码方式提升到0.38的,受我一拜🤤
改进4–数据处理
经验而言,两万多的数据大也不大,少也不少,通常可以做一个数据增强。故将训练集扩增了几倍(偷懒没用EDA),但是结果并不好,相较于不用还下降了一丢丢,过拟合了吗???????????
改进5–特征提取
CNN
baseline2
该方案中主要使用到循环神经网络来进行产率的预测。
循环神经网络的架构如下图
其中,每一层相当于做了一次线性变换:
h
n
=
σ
(
W
h
h
h
n
−
1
+
W
h
x
x
n
+
b
n
)
h_n = \sigma(W_{hh}h_{n-1} + W_{hx}x_n + b_n)
hn=σ(Whhhn−1+Whxxn+bn)
每层的输出:
y
n
=
S
o
f
t
m
a
x
(
V
h
n
+
c
)
y_n = Softmax(Vh_n + c)
yn=Softmax(Vhn+c)
通过隐向量的不断传递,序列后面的部分就通过“阅读”隐向量,获取前面序列的信息,从而提升学习能力。
在实际的代码实现上,RNN的各层具体如下:
1.嵌入层
self.embed = nn.Embedding(num_embed, input_size)# 这里创建了一个嵌入层,用于将输入的整数序列映射为密集向量表示。#num_embed 是词汇表的大小,input_size 是嵌入向量的维度。
2.RNN层
self.rnn = nn.RNN(input_size, hidden_size, num_layers=num_layers, batch_first=True, dropout=dropout, bidirectional=True)#这里定义了一个 RNN 层,输入维度为 input_size,隐藏状态的维度为 hidden_size,num_layers 指定了 RNN 层的层数,bidirectional=True 表示使用双向 RNN,dropout 是应用在 RNN 层后的丢弃概率。
3.全连接层
self.fc = nn.Sequential(nn.Linear(2* num_layers * hidden_size, output_size), nn.Sigmoid(), nn.Linear(output_size,1), nn.Sigmoid())#这里定义了一个序列模块,包括了两个线性层和两个 Sigmoid 激活函数。#首先,通过第一个线性层将 RNN 输出的隐藏状态转换为指定的 output_size 大小,然后经过 Sigmoid 激活函数。接着,再经过一个线性层将输出转换为一个标量值(1 维),最后再经过一个 Sigmoid 激活函数输出最终的预测结果。
此外,为了使SMILES数据能被神经网络输入,此处利用了自定义tokenizer的思路
def_regex_match(self, smiles):
regex_string =r"("+ self.regex +r"|"
regex_string +=r".)"
prog = re.compile(regex_string)
tokenised =[]for smi in smiles:
tokens = prog.findall(smi)iflen(tokens)> self.max_length:
tokens = tokens[:self.max_length]
tokenised.append(tokens)# 返回一个所有的字符串列表return tokenised
deftokenize(self, smiles):
tokens = self._regex_match(smiles)# 添加上表示开始和结束的token:<cls>, <end>
tokens =[["<CLS>"]+ token +["<SEP>"]for token in tokens]
tokens = self._pad_seqs(tokens, self.pad_token)
token_idx = self._pad_token_to_idx(tokens)return tokens, token_idx
之后便可以开始 愉快 的炼丹过程了😫
主要调整的参数是epoch、lr和dropout
step1
EpochLRdropoutresult1001e-30.20.0678
惨不忍睹的结果。可以认为这网络就没学会怎么预测产率,但是在训练过程中的损失降到了0.07,但是结果并不好,应当是过拟合了,且学习的效果也不好。
step2
EpochLRdropoutresult1001e-40.20.1211
这一次降低了学习率,想着说让RNN学的更细致一点
step3
EpochLRdropoutresult3005e-50.20.1919
继续降低学习率,当时因为担心小学习率在100轮内还不够学,于是epoch加到了300
RNN的尝试就差不多这样好了,实在没有动力试下去调参了,每次3h起步,还是继续搞XGBoost好了😅😅😅
版权归原作者 Phoshowy 所有, 如有侵权,请联系我们删除。