一、简介
从零入门AI for Science(AI+化学) 是 Datawhale 2024 年 AI 夏令营第三期的学习活动,基于天池平台“第二届世界科学智能大赛 物质科学赛道:催化反应产率预测”开展的实践学习。
教程提供的Baseline涉及 机器学习(随机森林) 、 深度学习RNN、Transformer的方案,教程会一一介绍这些方案的构建思路并给出代码实现示例。
二、赛事报名
赛事链接:http://competition.sais.com.cn/competitionDetail/532233/format
登录后直接报名即可~
-
没有账号需要先申请账号
三、小白基础30分钟,速通指南
-
将鼠标箭头悬浮在下方文件,即会出现下载按钮
资料包中有:baseline代码、赛事数据。
分享文件:AI+化学baseline文件包.zip
链接:https://pan.xunlei.com/s/VO3NvHSnib-iBnWE4YRsMyrxA1?pwd=vu6g#
复制这段内容后打开手机迅雷App,查看更方便
-
(可选学习)从官网下载数据,可按下面步骤下载赛事数据
-
需要认证可按照下方步骤操作
启动魔搭Notebook!(点击即可跳转)
链接:https://www.modelscope.cn/my/mynotebook/preset
直接点击启动!
10分钟体验一站式 baseline!
-
上传文件
-
按图示新建文件夹,新建mp/,并将代码和赛事数据从本地拖入到魔塔
-
解压文件
使用命令unzip AI+化学baseline文件包.zip
-
打开baseline代码文件,一键运行!(大约等待10分钟~)
-
执行完成后,下载
submit.txt
!
当左侧出现submit.txt
文件时,即为运行完成,可右键点击文件进行下载
四、RNN建模SMILES进行反应产率预测
Ai4Chemistry知识点补充
AI4Science的早期历史(概要)
AI4Science的发展历史大致也经历这三个阶段:
-
将化学知识以计算机形式存储,并构建数据库;
-
机器学习;
-
深度学习。
在第一个阶段中,人们主要做的事情就是尝试使用不同的方法,尽可能地将化学知识和信息以计算机的形式进行存储,并以此为基础开始构建数据库。例如,用一些字符表示分子或者其他化学符号,如何保存一个具有空间结构的分子的原子、键的位置信息等等。
在第二阶段,大家开始使用一些手动的特征工程对已有数据进行编码、特征提取等操作。例如,在baseline中我们使用了分子指纹(molecule fingerprint)作为我们的编码方式。再辅以传统的机器学习的方法,做一些预测。
在第三阶段,各种各样的深度神经网络也开始被广泛使用。这些网络不仅仅开始学习各种特征,也像word2vec那样,非常多的网络也被拿来对分子进行向量化。这也导致后来又非常多的新型的分子指纹出现。基于seq2seq模型学习表示为序列类型的化学数据、基于diffusion重建分子三维空间结构等等,都是当今的潮流方向。
SMILES —— 最流行的将分子表示为序列类型数据的方法
SMILES,提出者Weininger et al[1],全称是Simplified Molecular Input Line Entry System,是一种将化学分子用ASCII字符表示的方法,在化学信息学领域有着举足轻重的作用。当前对于分子和化学式的储存形式,几乎都是由SMILES(或者它的一些手足兄弟)完成的。使用非常广泛的分子/反应数据库,例如ZINC[2],ChemBL[3],USPTO[4]等,都是采用这种形式存储。SMILES将化学分子中涉及的原子、键、电荷等信息,用对应的ASCII字符表示;环、侧链等化学结构信息,用特定的书写规范表达。以此,几乎所有的分子都可以用特定的SMILES表示,且SMILES的表示还算比较直观。
表1:一些常见的化学结构用SMILES表示。
表2:化学反应也可以用SMILES表示,用“>>”连接产物即可。
表3:一些常见分子的SMILES例子
在SMILES中,原子由他们的化学符号表示,=表示双键、#表示三键、[]里面的内容表示侧基或者特殊原子(例如[Cu+2]表示带电+2电荷的Cu离子)。通过SMLIES,就可以把分子表示为序列类型的数据了。
(注:SMILES有自己的局限性:例如选择不同的起始原子,写出来的SMILES不同;它无法表示空间信息。)
拓展:事实上,使用图数据(grpah)表示分子是非常合适的。图网络相比于基于SMILES的序列网络,在某些方面会更胜一筹。感兴趣的同学可以自行探索。
SMILES的缺点:
无法表达充分的空间信息。
一个分子起始原子不同等因素,有多个SMILES。(不一一对应)—— SMILES规范化。
图1 具有对映异构的氨基酸分子。(对映异构:两者互为镜像而不能重合)
分子指纹 —— 分子向量化
分子的指纹就像人的指纹一样,用于表示特定的分子。分子指纹是一个具有固定长度的位向量(即由0,1组成),其中,每个为1的值表示这个分子具有某些特定的化学结构。例如,对于一个只有长度为2的分子指纹,我们可以设定第一个值表示分子是否有甲基,第二个位置的值表示分子是都有苯环,那么[0,1]的分子指纹表示的就是一个有苯环而没有甲基的分子。通常,分子指纹的维度都是上千的,也即记录了上千个子结构是否出现在分子中。
(这非常像NLP中的Bag of Word)。
有一定化学知识的小伙伴可能会发现:例如像氯甲基苯的邻位和对位,采用分子指纹的形式就无法区分出来。所以,分子指纹也有自己的局限性。
图2 氯甲基苯的邻位(左)和对位(右)
也正是随着解决这类问题的需求的增加,后续发展出了非常多的新式的分子指纹。例如,就有前面提到的通过深度学习学习出来的分子指纹。
(类似NLP中word2vec方法)。
特点:
非常稀疏。大量的0,少量的1;
指纹和分子并一一对应。
其他类型的分子指纹:
算法不同,分子指纹不同;“n-gram”中n的不同、对分子的子图匹配规则不同。
基于深度学习的分子指纹。指纹向量由深度学习得到,模型给出。(word2vec)
RDkit —— 强大、丰富且高效的化学信息工具
RDkit是化学信息学中主要的工具,是开源的。网址:http://www.rdkit.org,支持WIN\MAC\Linux,可以被python、Java、C调用。几乎所有的与化学信息学相关的内容都可以在上面找到。常用的功能包括但不限于:
-
读和写分子;
-
循环获取分子中原子、键、环的信息;
-
修饰分子;
-
获取分子指纹;
-
计算分子相似性;
-
将分子绘制为图片;
-
子结构匹配和搜索;
-
生成和优化3D结构。
RDkit会将分子读取为RDkit中专属的rdkit.Chem.rdchem.Mol对象,并以Mol对象为基础,可以对分子进行转化为各种表达形式,例如SMILES、SMARTS:
from rdkit import Chem
m = Chem.MolFromSmiles('Cc1ccccc1')
res = Chem.MolToSmarts(m)
# res : [#6]-[#6]1:[#6]:[#6]:[#6]:[#6]:[#6]:1
绘制图片:
from rdkit.Chem import Draw
img = Draw.MolToImage(m)
img.show() # 把图片展示出来
子结构搜索:
m = Chem.MolFromSmiles('c1ccccc1O')
patt = Chem.MolFromSmarts('ccO')
m.HasSubstructMatch(patt)
# True
获得分子指纹(fingerprint)并通过指纹计算分子之间的相似性:
from rdkit import Chem
from rdkit import DataStructs
from rdkit.Chem import AllChem
ms = [Chem.MolFromSmiles('CCOC'), Chem.MolFromSmiles('CCO'),
Chem.MolFromSmiles('COC')]
fpgen = AllChem.GetRDKitFPGenerator()
fps = [fpgen.GetFingerprint(x) for x in ms]
DataStructs.TanimotoSimilarity(fps[0],fps[1]) # TanimotoSimilarity是计算分子相似性的其中一个指标,更多指标详见RDkit官网
# 0.6
DataStructs.TanimotoSimilarity(fps[0],fps[2])
# 0.4
DataStructs.TanimotoSimilarity(fps[1],fps[2])
# 0.25
Tanimoto Similarity计算方法:
其中:$$b_X, b_Y$$就是是分子指纹。
分子相似性的计算方法也是多样的。
机器学习
机器学习按照目标可以分为分类任务(classification)和回归(regression)任务两大类。所谓分类任务,就是模型预测的结果是离散的值,例如类别;那么,回归任务中,模型预测的结果就是连续的值,例如房价等等。在本次竞赛中,我们需要预测的目标是反应的产率,是0-1之间的一个连续的数值,所以是一个回归任务。(注:离散值通过一些处理可以近似认为是连续值,所以不要被连续值和离散值限制了自己的思维)。
传统的机器学习需要需要经历特征工程这一步骤,即将原始数据转化为向量形式。然后通过SVM、Random Forest等算法学习数据的规律。这些方法在处理简单的任务时是比较有效的。
图3 决策树 (左)分类型决策树,(右)回归型决策树
划分每个节点的目标是让该节点中的值尽可能相同。在分类任务中,常见的就是信息熵衡量;在回归任务中,可以使用均方误差、绝对误差等进行衡量。
图4 随机森林
将多个决策树结合在一起,训练每个决策树的数据集都是随机有放回地从原数据中选出。预测的时候,输入会通过每个决策树进行预测,然后考虑每个树地输出结果,得到最终的预测值。
深度学习
深度学习可以归为机器学习的一个子集,主要通过神经网络学习数据的特征和分布。深度学习的一个重要进化是不再需要繁琐的特征工程,让神经网络自己从里面学习特征。
SMILES是一种以ASCII组成的序列,可以被理解为一种“化学语言”。既然是一种语言,那么很自然地想到了可以使用NLP中的方法对SMILES进行建模。
使用RNN对SMILES建模是早期的一个主要方法。RNN(Recurrent Neural Network)是处理序列数据的一把好手。RNN的网络每层除了会有自己的输出以外,还会输出一个隐向量到下一层。
图5 RNN的架构示意图
其中,每一层相当于做了一次线性变换:
$$h_n = \sigma(W_{hh}h_{n-1} + W_{hx}x_n + b_n)$$
每层的输出:$$ y_n = Softmax(Vh_n + c)$$
通过隐向量的不断传递,序列后面的部分就通过“阅读”隐向量,获取前面序列的信息,从而提升学习能力。
但是RNN也有缺点:如果序列太长,那么两个相距比较远的字符之间的联系需要通过多个隐藏向量。这就像人和人之间传话一样,传递的人多了,很容易导致信息的损失或者扭曲。因此,它对长序列的记忆能力较弱。
同时,RNN需要一层一层地传递,所以并行能力差,同时也比较容易出现梯度消失或梯度爆炸问题。
在后面学习中,我们会继续学习使用Transformer这种架构,这种架构就完美解决了长序列学习能力差、难以并行、出现梯度消失或者爆炸等问题。
实践部分
从教程2开始,我们就需要使用到GPU,因此,我们在魔搭上选择机器的时候,记得调整为GPU:
建议实践部分直接在代码ipynb文件进行学习和笔记。(将该文件置于baseline得到的文件夹的mp/code/下)
分享文件:Task2_RNN.ipynb
链接:https://pan.xunlei.com/s/VO3NxofaOMu70_3uUFPhnO-9A1?pwd=nkjy#
复制这段内容后打开手机迅雷App,查看更方便
在魔搭上,我们的整个项目目录应该是这样:
其中,所有ipynb文件均置于code/下;数据放置在dataset/下;model/用来保存训练的模型;output/输出模型预测的结果,也即需要提交到比赛的txt文件。
词汇表文件:(将该文件置于mp/下)
分享文件:vocab_full.txt
链接:https://pan.xunlei.com/s/VO3Ny-ZwvHTXv-sZ0pXka-M7A1?pwd=aex2#
复制这段内容后打开手机迅雷App,查看更方便
对于模型部分,我们可以直接调用Pytorch的模块完成。Pytorch中内置了丰富的模型,我们只需按照教程文档使用即可。Pytorch中RNN链接。
数据:赛题给出了Reactant1, Reactant2 , Product , Additive , Solvent这些信息,分别代表反应物1、反应物2、产物、添加剂(催化剂等等)、溶剂。这里,我们可以按照Reactant1.Reactant2.Additive.Solvent>Product的方式讲这些组合成一个字串。在SMILES中,反应物和反应物之间用"."隔开,产物用">"隔开(注意,这些只是约定俗成)。
但是,在我的实践中发现:给出的数据集中,有相当多的反应的Additive和Solvent这两条中都是一样的,也即这个字段对最后Yield的影响相对而言更小。所以,我们将这两个段的分子忽视,进行训练。如果不省略的话,模型会清一色地输出一个值,这个值往往是训练数据中Yield的均值。
参数设置:学习者可以自行尝试不同的参数,对比模型的性能。同时,也可以学习和思考,如何将Additive , Solvent这两段的SMILES以一种“高效”的方式添加到模型的学习中,以增强模型的性能。
五、Transformer建模SMILES进行反应产率预测
正文内容 —— Transformer
transformer诞生的由来
循环神经网络的序列到序列建模方法,在建模文本长程依赖方面都存在一定的局限性。
循环神经网络:由于所有的前文信息都蕴含在一个隐向量里面,这会导致随着序列长度的增加,编码在隐藏状态中的序列早期的上下文信息被逐渐遗忘。
卷积神经网络:受限的上下文窗口在建模长文本方面天然地存在不足。如果需要关注长文本,就需要多层的卷积操作。
图1 循环神经网络中,每个词都会依赖上一个词
优势
完全通过注意力机制完成对序列的全局依赖的建模。并且,这是一种可以高并行的结构,大大增加了计算效率。
基本架构示意图
-
嵌入层 (embedding layer)
将token转化为向量表示。模型认识的只是向量,所以需要将每个切分好的token转化为向量。
这个过程中,与RNN不同的是,我们在Transformer的嵌入层,会在词嵌入中加入位置编码(Positional Encoding)。
(为什么需要位置编码?
—— 在本节后面的讲解中,我们会学习到:transformer使用的多头注意力计算方式,无法获取到序列中各个字符的位置信息,因此需要认为地添加位置信息。)
位置编码通常使用不同频率的正余弦函数:
其中,pos表示单词所在的位置,2i和 2i+1 表示位置编码向量中的对应维度,d则对应位置编码的总维度。
图2 位置编码的示意图。每个token都会变成token本身+位置信息
上述方式编码的好处:
首先,正余弦函数的范围是在 $[-1,+1]$,导出的位置编码与原词嵌入相加不会使得结果偏离过远而破坏原有单词的语义信息。
其次,依据三角函数的基本性质,可以得知第$pos+k$ 个位置的编码是第 $$po$$ 个位置的编码的线性组合,这就意味着位置编码中蕴含着单词之间的距离信息。
2.自注意力层 (self-attention layer)
引入在注意力机制中:查询 $$q_{i}(Query$$ ,键 $$k_{i}(Key$$ ,值 $$v_{i}(Value$$ 。在编码输入序列中每一个单词的表示的过程中,这三个元素用于计算上下文单词所对应的权重得分。
所谓自注意力就是Q\K\V的值都是输入序列本身。为了防止过大的匹配分数在后续 Softmax 计算过程中导致的梯度爆炸以及收敛效率差的问题,这些得分会除放缩因子 $$\sqrt{d}$$ 以稳定优化。放缩后的得分经过 Softmax 归一化为概率之后,与其他位置的值向量相乘来聚合希望关注的上下文信息,并最小化不相关信息的干扰。具体公式如下:
3.前馈层 (Feed Forward layer)
本质上,就是一个线性层。
实验结果表明,增大前馈子层隐状态的维度有利于提升最终翻译结果的质量,因此,前馈子层隐状态的维度一般比自注意力子层要大。
4.残差连接
从基本架构图中我们可以看到,每个(LayerNorm layer)都有一个(Add),这就是一个残差连接。它的作用是用一条直连通道直接将对应子层的输入连接到输出上去,从而避免由于网络过深在优化过程中潜在的梯度消失问题。
5.层归一化 (Layernorm)
我们直到,在神经网络中,我们经常对每一层的输出数据进行归一化,以使得模型更快达到收敛。只不过,前面的归一化方式几乎都是batch normalization,即对不同的batch进行归一化。Transformer在最开始被提出来的时候,采用的也是batch normalization。但是在人们后续的实践中发现,对于transformer而言,每一层间的归一化效果都要更好。
归一化操作的计算表达式:
目的:为了进一步使得每一层的输入输出范围稳定在一个合理的范围内。
利用Transformer的Encoder作为编码器编码
Transformer也是一个经典的编码器-解码器模型(encoder-decoder model)。从基本架构图中也可以看到,编码器部分将输入转化为一个向量之后,再传递到了解码器部分。既然这样,我们完全可以把Transformer的Encoder和Decoder部分拆开,分别拿来使用。其实,在大语言模型中非常具有代表性的BERT和GPT就是这样干的,它俩分别使用了Transformer的Encoder部分和Decoder部分。
既然Encoder部分输出的本身就是一个向量(我们这里称之为向量z),那么只要模型学习得够好,那么这个向量就一定包含了重要得输入序列得信息。这就是为什么我们可以把Encoder单独拿出来并看作是一个编码器得原因。因此,我们可以将这个代表输入序列的向量z,再作为其他模型的输入(例如全连接网络等),从而进行我们想要的分类或者回归任务。这样的思路被广泛应用在AI领域的研究和工程中。
在这里,我们使用Transformer的Encoder部分,编码我们的SMILES表达式。然后,再将等到的向量$z$通过一个线性层,输出一个值。我们期望通过模型的学习,这个输出的值就是该化学反应的产率。
进一步提升分数
接下来,我们介绍一些常用的调参或者优化模型的方式,以帮助大家进一步提高分数。相信大家在这个过程中,会进一步理解为什么我们常常把这个过程称之为“炼丹”了。
-
调整epoch:epoch越大,训练得越久,一般而言模型得性能会更好。但是也有可能会出现过拟合现象。
所谓过拟合,就是模型过分学习了训练数据,导致泛化能力减弱的现象。一个极端的例子是:模型完全死记硬背记住了所有训练数据,因此在训练数据上的预测结果为满分。但是对于没在训练过程中见过的数据,却完全无能为力。
-
调整模型大小:也即中间向量的维度、模型得层数、注意力头的个数。一般而言,模型越大学习能力越强,但是同样的也有可能出现过拟合。
-
数据:对数据做清洗,调整数据分布,做数据增广。对于SMILES一个可行的增广思路是:将一个SMILES换一种写法。
前面对SMILES中没提到:其实,对于某个分子的SMILES,可以有多种写法。这其实很好理解,我们知道SMILES是从某个原子开始,依次排列所有原子而得到的。那么选取的初始原子不同,SMILES就会不一样。所以,为了统一,人们在使用的时候往往会将SMILES做一个书写规范,这个过程叫做标准化(canonicalization)。官方给出的数据就是规范化后的。
-
采用学习率调度策略:在训练模型的过程中,我们发现往往约到后面,需要更小的学习率。例如下图:学习到后面,我们需要收敛的局部最小值点的两边都比较“窄”,如果现在学习率太大,那么在梯度下降的时候,就有可能翻过局部最小点了。因此需要调整学习率变小。在Pytorch中已经定义好了一些常用的学习率调度方法,需要的学习者可以自己从官网上查看如何使用。
5.集成学习:训练多个不同初始化或架构的模型,并使用集成方法(如投票或平均)来产生最终翻译。这可以减少单一模型的过拟合风险,提高翻译的稳定性。
实践
分享文件:Task3_Transformer.ipynb
链接:https://pan.xunlei.com/s/VO3NzVdGWfSEnymHbZJr3VlXA1?pwd=7wg2#
复制这段内容后打开手机迅雷App,查看更方便
经过测试,基于Transformer的序列建模相比于基于RNN的建模在学习能力上更胜一筹。
思考
经过这两个Task的建模测试,我们不难发现,将SMILES作为序列进行建模,相比于使用分子指纹进行向量化之后采用传统的机器学习方法,效果还更差。
以下,是笔者的思考:
-
在前面,我们的处理是:放弃了Additive和Solvent段。因为我们在实践时发现,不同反应的这两个字段非常相似,加入了这两个字段的SMILES模型难以学习。但是,这两个字段对反应的产率也有重要的影响,贸然删除必然会损失一部分信息。
-
化学分子SMILES具有以下特性:哪怕只有字符级别的很小差别,分子的性质可能会天差地别。
-
更强大合理的tokenizer和词汇表。我们现在使用的词汇表是原子、离子级别的tokenize。但在化学领域,还有很多经典的子结构:芳环、甲基等。进行更高级别的tokenize或许对提升模型性能有较大帮助。
上分结果
使用机器学习算法进行模型训练,成绩可达到0.1968,用时大概5分钟。
改用RNN模型后,分数明显降低,甚至达到了负数,经过调参优化,并没有大幅度改善,用时一小时左右(一次优化的时间)
使用Transform模型后,训练精度与速度较RNN有明显改善,成绩可达到0.0816,时间大概为40分钟,后续大家可以尝试Transform调参进一步优化上分。