生成式人工智能通过比传统方法更快地设计新型药物候选分子,显著影响了药物发现领域。制药公司正在利用这些模型生成新的分子结构,随后对其进行筛选和评估。AI生成的设计常常以其创新性令经验丰富的科学家感到惊讶。这项技术能够在短短五分钟内生成相当于一年量级的分子结构,为医学研究开启了新的可能性。然而,尽管AI能迅速提出潜在药物候选分子,但这些分子在现实环境中是否有效并无保证。AI创造的设计往往看起来怪异,且仅有部分与现有分子数据库匹配,因此研究人员必须仔细评估这些分子。
这并非我首次提及,2024年诺贝尔化学奖授予了David Baker,以表彰他在计算蛋白质设计方面的工作,同时授予Demis Hassabis和John Jumper,以表彰他们开发的能够预测蛋白质结构的AI模型。他们的突破对药物发现和生成式AI有着重要意义,凸显了AI在科学研究中日益增长的作用,并为更多基于AI的创新铺平了道路。第7章介绍了一些作为智能体团队成员的研究工具。
本章探讨了如何利用生成式AI开发小分子药物,并讨论了如何构建和使用知识图谱来支持各种药物研究应用场景。
计算机模拟药物发现(In Silico Drug Discovery)
尽管速度极快,生成式AI在药物发现中仍面临重大挑战。这些AI系统常常产生不切实际或无意义的分子,需依赖专家科学家来筛选结果。以DiffLinker等程序为例,尽管AI能够生成复杂的三维分子模型,但仍需要人工干预以甄别现实且有价值的药物候选分子。各种软件平台的工具有助于精炼AI的输出,但整个流程仍然复杂,尚未实现完全自动化。药物化学领域的专业知识对于理解AI输出、评估化学稳定性以及剔除不稳定或合成难度高的分子至关重要。
根据近期一项题为《AI发现的药物在临床试验中成功率如何?——首次分析与新兴经验》的研究,自2015年以来,AI推动了75种分子进入临床试验,年增长率达60%,大幅加快了传统上漫长的流程。成功指标令人信服:AI发现的分子在第一阶段临床试验中成功率达到80%–90%(而传统方法为40%–65%),第二阶段保持了40%的竞争性成功率。AI不仅加速了研究进程,更从根本上改变了分子预测与优先级排序的方法,确立了其作为解决复杂健康挑战的重要工具地位,依靠可衡量、数据驱动的成果推动药物研发。相关结果见图8-1。
目前,大约有33%至50%的FDA批准药物来源于天然产物或其类似物。像MacrolactoneDB这样的数据库为新化合物的生成式设计提供了起点。研究人员开发了用于复杂天然产物结构的专用描述符,并展示了针对疟疾寄生虫和丙型肝炎病毒等靶点的有效机器学习模型。多靶点药理学(polypharmacology)——即作用于多个靶点的药物——是生成式AI的另一个应用方向。基于图的模型如GraphSAGE能够同时学习分子与多个靶点之间的关系,这些模型不仅能对新的化合物-靶点对进行预测,还能推广到数据有限的靶点。
尽管AI在改善药物发现流程方面展现出潜力,但它尚非万能灵药。AI设计药物的开发仍然高度依赖人类专业知识和传统测试方法。AI可以提出新的路径和创新设计,但研究人员必须彻底分析以确保其化学稳定性和治疗效果。目前的AI技术能够缩小筛选范围,加快初步发现阶段,但从计算模型到成功药物的道路仍需大量的人为监督和实验。最终,AI有潜力通过提升效率来变革药物发现,但要实现真正的革命,必须与既有科学方法谨慎整合。
自2020年以来,科技公司、研究机构与制药企业之间的合作日益加深,尤其是在将AI应用于药物发现方面。谷歌DeepMind、微软和英伟达等公司与制药企业及研究机构建立了战略合作,加速药物研发。尽管亚马逊、微软、苹果和谷歌等科技巨头在传统制药领域经验不足,但它们在健康与医疗领域投入巨大,挑战了辉瑞(Pfizer)、默克(Merck)、葛兰素史克(GSK)等传统制药公司,这些公司在硬件、软件开发和海量数字数据处理方面经验相对较少。
微软是制药市场中最活跃的科技巨头之一,进军生物制药技术领域,投资基于云的基因组学研究工具,启动了治愈癌症的雄心勃勃的项目,并与UCB、诺和诺德(Novo Nordisk)、赛诺菲(Sanofi)、DNAnexus和GHDDI等公司和机构建立了战略合作。微软的目标涵盖将AI与计算服务集成进药物发现,以及利用量子计算推动药物开发。
Alphabet(谷歌)及其子公司DeepMind直接或间接投资了30多个涉及多种治疗领域的生物技术企业,包括疫苗公司Spybiotech和T细胞疗法开发商TScan Therapeutics。谷歌与罗氏(Roche)、PathAI和勃林格殷格翰(Boehringer Ingelheim)等在多个领域的合作也表明,药物发现的未来首先是“计算机模拟优先”,即计算机仿真软件将领先于实验室湿实验。诺贝尔奖得主Isomorphic Labs/DeepMind最近推出了AlphaFold的新版本,该模型可预测蛋白质、DNA、RNA及配体的结构及其相互作用,正是我们在上一章讨论过的。
英伟达发布了BioNeMo框架,用于研究药物如何与细胞相互作用并预测新药的效果。它与Recursion和Terray Therapeutics等公司合作,确保这些工具的实用性和广泛可用性。甚至Meta也在开发蛋白质折叠预测工具,而英特尔则在优化用于分析复杂生物数据的深度学习方法。参与者名单迅速扩大:辉瑞与IBM合作使用量子计算进行药物发现,Moderna与AWS合作利用云计算能力,阿斯利康(AstraZeneca)与腾讯合作构建AI驱动的数字健康平台,诸如此类。
小分子生成
药物发现中最有价值的领域之一是发现或生成可合成的新分子。利用AI生成分子并不像表面看起来那么简单,主要因为准确生成化学上有效且有用的结构极具复杂性。虽然生成模型为创建分子结构提供了令人兴奋的可能性,但对于新颖小分子的输出常常带有噪声,导致生成的结果在化学上不合理或毫无意义。这个过程需要大量领域专业知识来筛选和优化这些输出,识别有潜力的候选分子以便进一步开发。此外,将这些原始结构转化为可分析或合成的形式涉及复杂步骤,例如正确分配键级和确保几何合理性,而现有的内置工具和算法在这方面仍难以高效完成。
即使经过初步筛选和优化,模型生成的分子仍常常存在几何应变、化学不稳定的环状结构或重复结构等问题。需要人工筛选和额外的计算校验来剔除有缺陷的候选分子,这凸显了流程中某些阶段必须由专家介入的必要性。此外,这些模型本身并不像大众媒体所暗示的那样“真正智能”。它们大多数依赖模式识别,而非对化学的深刻理解,限制了其独立设计可行分子的能力。因此,尽管生成式AI在分子设计方面具备潜力,但仍需大量人工监督和精炼,才能产出有意义的结果。
自动编码器
第二章讨论并使用了编码器-解码器的Transformer架构。
备注
作为提醒,编码器-解码器架构中,编码器是一种神经网络,负责处理输入数据并将其转化为中间表示,通常是上下文向量或隐藏状态序列。这种中间表示捕捉了输入的关键信息。解码器则以该中间表示为输入,生成所需的输出序列或结构,输出格式或维度可能与原始输入不同。
自动编码器的核心思想是学习输入数据的高效嵌入。自动编码器将输入压缩到潜在空间表示(编码器部分),然后从该表示重构输出(解码器部分),如图8-2所示。
需要区分编码器-解码器架构和自动编码器。自动编码器的重点是学习输入数据本身的高效且可能是低维的表示,而不是生成不同格式的输出。
在训练过程中,输入数据 被送入编码器函数 。输入经过一系列由参数 控制的层,逐步降低维度,从而得到一个压缩的潜在向量 。层的数量、类型、大小以及潜在空间的维度都是用户可控的参数。当潜在空间的维度小于输入空间时,实现了压缩,去除了冗余属性。
解码器 通常(但不总是)采用与编码器层相反的顺序排列的层,这些层执行的操作旨在逆转编码器的变换。此类近似互补层的目标是从潜在表示中重构输入。比如,在卷积层减少空间维度后,解码器会使用转置卷积层来增加空间维度;解池层用于逆转池化层的空间下采样;编码器的全连接层降低维度后,解码器的全连接层将映射回输入的维度。需要注意的是,这些互补层并非完美逆转原始操作,而是执行类似变换以重建输入。
整个编码器-解码器架构通过一个损失函数进行联合训练,该函数鼓励输出重构输入。换句话说,模型能多好地从压缩形式重建原始输入——通常是一种分子或其性质?
在上述损失公式中, 是原始输入数据(例如分子指纹或化学特征向量), 是模型重构的版本。符号 表示L2范数或欧几里得距离,用于度量两个向量的差异。自动编码器由两部分组成:编码器 及其参数 ,将输入压缩为低维表示 ;解码器 及其参数 ,尝试从 重构原始输入。
损失越小,表示模型越能捕捉分子的关键特征,同时忽略无关噪声。设计理念是在保证误差足够小的前提下,实现潜在空间的最大压缩。如果潜在空间的维度被压缩得过低,将导致信息显著丢失。
图8-3展示了一个有机化合物潜在空间训练后的可能示例。
当化合物被表示为低维潜在空间中的点时,性质或结构相似的化合物通常会彼此靠得更近,而不相似的化合物则距离较远。这种空间排列反映了模型捕捉化合物之间潜在关系的能力。另一个重要的点是,潜在空间的部分区域并不对应任何数据点(如图8-3中多边形外的区域)。如果编码器处理的输入数据超出了其训练数据的分布范围,生成的编码表示很可能与训练数据的编码不同寻常。因此,如果将这种新颖编码输入解码器,生成的输出也很可能与训练数据中的任何化合物都不相似。这种潜在空间没有经过正则化,只有少数区域具备生成能力。在数据簇内部采样会生成属于该簇的变体,而在簇外采样则会产生无意义的输出,极有可能是不合理的分子。一旦编码器训练完成且训练数据被移除,就无法判断解码器从随机采样的潜在向量生成的输出是否合法。
变分自动编码器(Variational Autoencoder,VAE)是一种神经网络,学习如何将数据压缩成更小的格式然后重建它。可以把它想象成将一个分子缩减成几个数字,这些数字仍然承载主要信息,然后利用这些数字重建一个与原始分子非常相似的结构。VAE通过两个部分实现这一点:编码器和解码器。编码器将分子(通常以SMILES格式或图结构表示)转化为一个称为潜在向量的数字列表,例如 [0.5, -0.1, 1.2, …]。解码器则尝试将该向量还原成分子。
当VAE学会这个过程后,它可以做两件事。第一,可以将一个分子压缩成数值向量,然后用这些数字重建相同的分子。第二,更有趣的是,它可以稍微调整这些数字,看看会生成什么样的分子。这意味着你可以对数字做小幅修改,从而得到新的但相似的分子。这对药物设计非常有用,因为它让研究人员可以测试分子的不同变体,而不必每次都从头开始。VAE通过学习对已知类药分子的化学模式和性质进行编码和解码,在连续潜在空间中生成新的分子结构。解码器能够生成具有相似性质的新分子,帮助研究人员更高效地探索化学空间,寻找潜在药物候选分子。
VAE解决了自动编码器中潜在空间未正则化的问题,并为整个空间赋予了生成能力。VAE的编码器并非输出潜在空间中的向量,而是为每个输入输出潜在空间预定义分布的参数。随后,VAE对该潜在分布进行约束,使其服从正态分布。与普通自动编码器相比,潜在空间中单点的分布使得VAE既能重建已有数据,也能生成更加分散的新数据。
VAE的架构示意图见图8-4。与自动编码器类似,输入通过一系列层降维,得到压缩的潜在向量 zzz。但不同的是,编码器输出的是每个潜在变量的均值和标准差,而不是潜在空间本身。然后从这些参数中采样潜在空间向量,将其输入解码器进行输入重建。VAE中的解码器工作方式与自动编码器中的相似。
损失函数由变分自动编码器(VAE)的目标定义。VAE 有两个目标:
- 重构输入(与自动编码器相同)
- 潜在空间应服从正态分布
因此,VAE 的训练损失定义为重构损失和相似性损失之和。重构误差的计算方式与自动编码器相同。相似性损失是潜在空间分布与标准高斯分布(均值为0,方差为1)之间的Kullback–Leibler(KL)散度(KL散度是一种常用的统计指标,用于比较两个分布)。损失函数即为这两部分损失的总和:
训练过程中,模型努力平衡这两部分损失,最终得到的潜在空间分布接近单位正态分布,并形成将相似输入数据点聚集在一起的簇。单位正态条件确保潜在空间均匀分布,簇与簇之间不会有明显间隙,相似的数据簇通常会在某些区域重叠(例如,表面活性剂——既有长疏水体也有羧基尾部;或酰胺——同时含有氮和氧),如图8-5所示。
自动编码器是一种特定的编码器-解码器架构,其目标是重构输入数据。虽然有些编码器-解码器架构是将数据从一种格式转换为另一种格式(如文本翻译),但自动编码器专注于压缩并重构相同格式的数据。变分自动编码器(VAE)则是一类特殊的自动编码器,它学习一个概率性的潜在空间,也就是说,输入被编码为潜在空间中的概率分布,而非固定的点。
当使用VAE预测化学分子时,编码器将输入数据(表示分子的结构或性质)转换为低维、连续的潜在空间。对于每个输入,编码器学习潜在空间中概率分布的参数,通常是高斯分布。解码器则从潜在空间的一个点采样(可以是已知分子的学习分布,也可以是新选的点),以概率方式重构原始分子或生成新分子。假设潜在空间中不同区域对应具有不同结构特征及潜在不同性质(如溶解度、结合亲和力等)的分子。通过探索和采样潜在空间特定区域,研究者旨在发现具有期望特性的全新分子构型。通常,属性预测模型会与VAE集成,进一步指导具有特定特征分子的搜索。
在编码器-解码器的大型语言模型(LLM)中,数据转换的方式不同。编码器处理输入文本(如SMILES字符串)生成上下文表示,这种表示捕捉了分子不同部分间的关系,例如原子和键的连接。解码器利用该上下文嵌入生成输出文本,可能是新的SMILES字符串、文本描述,或分子性质预测(见第6章)。与VAE不同,LLM不依赖概率潜在空间,而是使用确定性的上下文嵌入,意味着相同输入会产生相同嵌入。如第2章所述,概率性方法通过各种解码器采样技术实现。
备注
第2章介绍,LLM擅长捕捉令牌间关系,因为它们经过大量文本训练。而当前分子表示的问题是环状分子结构被线性令牌序列表示。相比之下,自动编码器,尤其是图自动编码器,可以将分子视为完整单元而非一串令牌,整体考虑结构以保持环路模式,从而更清晰地体现各部分关联。因此,这些模型在性质预测或新分子设计上表现更优。
条件变分自动编码器(CVAE)将此思想更进一步,通过在生成过程中引入额外信息或条件,比如分子的溶解度、合成可达性评分或其他物理化学属性。CVAE据此生成既类似已知分子又满足指定条件的新分子。例如,若研究人员寻找能良好结合特定蛋白的分子,可用CVAE生成可能具备该特性的候选分子,从而使药物搜索更快、更有针对性。
接下来,我们将构建自己的CVAE网络用于分子生成。完整代码见LangChain4LifeSciencesHealthcare仓库。首先,我们用RDKit包将所有SMILES转换为图的邻接矩阵,并为ZINC数据集提供的数据创建特征矩阵。
提示
传统SMILES适合将分子数据表示为字符串,但其生成的潜在空间缺乏良好正则化。更有前景的方法是使用SELFIES(第2章和第6章描述),虽需转换数据格式,但展现更强的生成潜力。
编码器(示例8-1)接收分子的图邻接矩阵和特征矩阵作为输入,特征通过自定义的RelationalGraphConvLayer图卷积层处理,再扁平化,经过若干Dense层得出分子的潜在空间表示z_mean和log_var。RelationalGraphConvLayer对图中邻域聚合应用非线性变换。
示例8-1 CVAE编码器(部分代码):
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
def set_encoder(gconv_units, latent_dim, adjacency_shape,
feature_shape, dense_units, dropout_rate):
adjacency = layers.Input(shape=adjacency_shape)
features = layers.Input(shape=feature_shape)
features_transformed = features
for units in gconv_units:
features_transformed = RelationalGraphConvLayer(units)([adjacency, features_transformed])
x = layers.GlobalAveragePooling1D()(features_transformed)
for units in dense_units:
x = layers.Dense(units, activation="relu")(x)
x = layers.Dropout(dropout_rate)(x)
z_mean = layers.Dense(latent_dim, dtype="float32", name="z_mean")(x)
log_var = layers.Dense(latent_dim, dtype="float32", name="log_var")(x)
encoder = keras.Model([adjacency, features], [z_mean, log_var], name="encoder")
return encoder
采样层(示例8-2)负责生成潜在空间向量,输入为均值(z_mean)和对数方差(log_var),定义潜在分布。该层从标准正态分布采样随机噪声(epsilon),并根据均值和方差调整。
示例8-2 CVAE采样层(部分代码):
class Sampling(layers.Layer):
def call(self, inputs):
z_mean, z_log_var = inputs
batch = tf.shape(z_log_var)[0]
dim = tf.shape(z_log_var)[1]
epsilon = tf.keras.backend.random_normal(shape=(batch, dim))
return z_mean + tf.exp(0.5 * z_log_var) * epsilon
解码器(示例8-3)输入潜在空间表示,预测相应分子的图邻接矩阵和特征矩阵。解码器通常模仿编码器的反向结构,但非强制。
示例8-3 CVAE解码器(部分代码):
def set_decoder(
dense_units, dropout_rate, latent_dim,
condition_dim, adjacency_shape, feature_shape):
latent_input = keras.Input(shape=(latent_dim,))
condition_input = keras.Input(shape=(condition_dim,))
x = layers.Concatenate()([latent_input, condition_input])
for units in dense_units:
x = layers.Dense(units, activation="tanh")(x)
x = layers.Dropout(dropout_rate)(x)
x_adjacency = layers.Dense(tf.math.reduce_prod(adjacency_shape))(x)
x_adjacency = layers.Reshape(adjacency_shape)(x_adjacency)
x_adjacency = (x_adjacency + tf.transpose(x_adjacency, (0, 1, 3, 2))) / 2
x_adjacency = layers.Softmax(axis=1)(x_adjacency)
x_features = layers.Dense(tf.math.reduce_prod(feature_shape))(x)
x_features = layers.Reshape(feature_shape)(x_features)
x_features = layers.Softmax(axis=2)(x_features)
decoder = keras.Model(
[latent_input, condition_input], outputs=[x_adjacency, x_features],
name="decoder"
)
return decoder
我们的CVAE模型训练时优化四类损失:
- 重构损失(分子和属性)
- 相似性损失
- 属性预测损失
- 图结构损失
重构损失用于评估模型重建原始输入数据的准确性。在这里,重构损失针对分子及其属性,使用分类交叉熵(categorical_crossentropy)损失函数进行计算。相似性损失即KL散度,鼓励学习到的潜在表示分布接近标准正态分布。这种正则化促进了潜在空间的平滑性和解耦性。
随着条件和属性预测步骤的加入,我们还会有其他指标:一个用于检测预测效果,另一个用于检测所应用的条件。对于分子属性预测,属性预测损失通常计算为预测值与实际分子属性值之间的均方误差(MSE)。这一计算是在属性预测模型处理分子的潜在表示后完成的。
尽管模型操作的是分子图,但图损失(梯度惩罚)是一种在训练过程中使用的正则化技术,用以提升学习到的表示的稳定性和质量。它并不直接指导模型朝向某个具体属性(如药物相似性定量估计QED)进行预测,但通过塑造潜在空间,间接影响模型准确预测此类属性的能力。
备注
梯度惩罚作为对1-Lipschitz连续性的软约束,增强了传统神经网络中的梯度裁剪方案,通过向损失函数添加正则项,帮助训练过程中保持平滑性和稳定性。
我们的架构示意类似于之前的VAE架构,但增加了属性预测步骤,如图8-6所示。
该模型通过同时优化多个损失函数,有效地平衡了重构精度、潜在空间正则化、属性预测和梯度稳定性。下面的 _compute_loss 函数展示了损失的具体计算方法,如示例8-4所示。
示例8-4 损失计算代码:
def _compute_loss(self, z_log_var, z_mean, qed_true, qed_pred,
graph_real, graph_generated, condition_tensor):
adjacency_real, features_real = graph_real
adjacency_gen, features_gen = graph_generated
# 重构损失
adjacency_loss = tf.reduce_mean(
tf.reduce_sum(
keras.losses.categorical_crossentropy(adjacency_real, adjacency_gen),
axis=(1, 2),
)
)
features_loss = tf.reduce_mean(
tf.reduce_sum(
keras.losses.categorical_crossentropy(features_real, features_gen),
axis=1,
)
)
# KL散度损失
kl_loss = -0.5 * tf.reduce_sum(
1 + z_log_var - tf.square(z_mean) - tf.exp(z_log_var), axis=1
)
kl_loss = tf.reduce_mean(kl_loss)
# 属性预测损失
property_loss = tf.reduce_mean(
keras.losses.binary_crossentropy(qed_true, qed_pred)
)
# 图结构正则化中的梯度惩罚
graph_loss = self._gradient_penalty(
graph_real, graph_generated, condition_tensor
)
return kl_loss, property_loss, graph_loss, adjacency_loss, features_loss
接下来,我们将所有代码整合起来。示例8-5中定义了 vae_optimizer,并构建了编码器(示例8-1)和解码器(示例8-3)。然后通过将这些部分组合成一个处理不同分子大小的 MoleculeGenerator 模型。模型搭建完成后,使用包含分子原子、键、药物相似性评分及其他条件的数据进行多轮训练。训练完成后,模型即可通过推断方法根据所需规格生成新分子。完整代码托管于 LangChain4LifeSciencesHealthcare 仓库。
示例8-5 模型搭建与训练代码:
vae_optimizer = tf.keras.optimizers.Adam(learning_rate=VAE_LR)
# 定义并构建编码器和解码器模型
encoder = set_encoder(
gconv_units=[9],
adjacency_shape=(BOND_DIM, NUM_ATOMS, NUM_ATOMS),
feature_shape=(NUM_ATOMS, ATOM_DIM),
latent_dim=LATENT_DIM,
dense_units=[512, 256, 128],
dropout_rate=0.0,
)
decoder = set_decoder(
dense_units=[128, 256, 512],
dropout_rate=0.25,
condition_dim=condition_tensor.shape[1],
latent_dim=LATENT_DIM,
adjacency_shape=(BOND_DIM, NUM_ATOMS, NUM_ATOMS),
feature_shape=(NUM_ATOMS, ATOM_DIM),
)
# 使用指定优化器编译模型
model = MoleculeGenerator(encoder, decoder, MIN_MOLSIZE, MAX_MOLSIZE)
model.compile(optimizer=vae_optimizer)
# 使用输入数据训练模型,训练指定轮数
history = model.fit(
[adjacency_tensor, feature_tensor, qed_tensor, condition_tensor], epochs=EPOCHS
)
# 使用推断方法生成分子
model.inference(desired_conditions, batch_size=batch_size)
模型搭建并训练完成后,我们可以用它来生成新分子。生成过程中,我们会控制分子大小在特定范围内,并通过设置基于logP值的第一和第三四分位数的条件引导生成。logP值,即辛醇-水分配系数,衡量分子在脂质与水中溶解倾向(即其亲脂性)。
由于模型运行在概率性潜在空间中,处理的是概率而非绝对确定性,因此即使我们设置了尺寸和logP的具体条件,生成的大批分子中仍可能有部分稍微超出预期的logP范围。这种偏差是概率模型固有的自然现象。图8-7展示了在这些条件下生成的分子示例。
如你所见,生成的低亲脂性分子数量远多于高亲脂性分子。造成这种情况的可能原因之一是对分子大小的限制。同时也明显看到,大多数低亲脂性分子含有多种水溶性元素,尤其是氧(O)和氮(N)。
部分生成的分子可能在现实中并不存在,这正是本章开头讨论的问题。第5、6和7章已经介绍了如何创建AI智能体以及如何组建智能体团队。基于此,你可以设计一个智能体负责运行和优化CVAE代码,另一个智能体利用分子力学或类似方法筛选物理上合理且化学上稳定的分子。这样不仅能过滤潜在候选分子,还可以作为智能体之间的反馈循环。
示例8-1到8-5涵盖了使用CVAE生成小分子的基础内容。若想生成更大的分子,需要重新配置输入参数。候选分子的数量会呈指数增长,因此设置生成条件至关重要。
知识图谱
知识图谱(Knowledge Graph,KG)是一种将信息表示为实体网络的数据结构,通常通过节点(代表人、概念或物体等)和边(表示它们之间的关系)来可视化。这种基于图的表示方式不仅捕捉实体本身,还体现了它们复杂的关系,方便知识的发现和组织。
知识图谱强调实体之间的关系,作为理解和表示知识的核心,同时也重视实体本身及其相关属性。在药物发现研究中,重点可能是药物、疾病、蛋白质、基因、通路以及其他化学和生物实体如何相互关联。最重要的不是单个蛋白质或分子,而是它们之间的关系——例如蛋白质与小分子的相互作用以及它们的相似性。这些关系为理解复杂的生物系统提供了关键见解,对于识别潜在药物靶点、预测药物相互作用、理解疾病机制等都极具价值。
知识图谱为药物再利用(drug repurposing)提供了强大方法,这是一种寻找现有药物新用途的策略。通过构建药物、其分子靶点(如蛋白质)及疾病相关生物通路的互联网络,知识图谱能够揭示先前未发现的联系。这使研究人员能够识别可能对非原始适应症有效的药物。例如,通过遍历和分析知识图谱模式,可能发现某药物虽然最初针对特定疾病开发,却作用于另一罕见疾病相关的蛋白质。这有望使临床试验更快、更经济,相较传统药物开发流程更具优势。
知识图谱还能帮助预测药物间相互作用,绘制不同化合物对各种代谢通路的影响图谱。这对于开发联合疗法和避免不良反应至关重要。知识图谱支持靶点识别,通过揭示在多种疾病通路中起关键作用的蛋白质,使其成为广谱药物的有前景靶点。此外,知识图谱有助于临床试验中的患者分层,将遗传特征与药物反应数据关联,实现更个性化和有效的药物开发策略。同时,知识图谱还能通过连接化学结构与已知副作用,辅助毒性预测。
备注
在LangChain4LifeSciencesHealthcare仓库中,有一个额外的笔记本专门用于图机器学习,预测药物对训练数据集中未列出的其他疾病的治疗效果。
知识图谱的多种实现方式均可通过连接节点完成。本章将构建的一个示例子图(除靶点和通路节点外,但你可以基于提供的数据在本地构建完整图谱)如图8-8所示。
第3章和第4章详细讨论了检索增强生成(Retrieval-Augmented Generation,RAG)的概念。RAG可以利用多种知识来源进行信息检索,包括以相互连接的节点和边存储信息的知识图谱(KG)。这为访问事实性关系提供了一种结构化的方式。在知识图谱上应用RAG,有助于更容易地表示现有药物、它们的靶点、副作用、通路、批准适应症等信息。利用基于知识图谱的RAG模型,可以检索和综合相关数据,例如已知的相互作用或类似化合物的效应。你还可以提取相关的遗传信息,将其与疾病表型交叉参考,并仅通过文本接口生成关于疾病机制或潜在遗传干预的假设。
备注
在讨论基于图的检索增强生成方法时,需区分一般的基于知识图谱的RAG与微软的GraphRAG具体实现。基于知识图谱的RAG是一个广义概念,指的是利用现有知识图谱作为信息源,从Neo4j等图数据库中通过查询相互关联的节点和关系来检索和综合数据。而微软GraphRAG是微软研究院开发的专有实现,采用了不同的方法:它不是查询已有知识图谱,而是利用大型语言模型(LLM)从非结构化文本动态创建知识图谱。尽管这两种方法都通过图结构增强了LLM的能力,但它们在运行机制上有根本区别——基于知识图谱的RAG查询已有的带有既定本体的图数据库,而微软GraphRAG则从原始文本动态构建知识表示,生成临时图结构,以提升上下文理解和信息综合能力。
本章后续示例中,我们将使用基于Neo4j图数据库构建的知识图谱。图8-9展示了一个简单基于知识图谱的RAG流水线的逐步工作流程,模拟了第4章中使用的流水线。
让我们更详细地看每一步:
0. 数据准备
- 数据导入
将数据导入Neo4j数据库。 - 模式设计
设计图谱模式,清晰定义每种节点类型和关系。 - 数据整合
若数据来自多个来源(如科研论文、临床试验和数据库),将它们整合成一个统一的图谱。
1. 问题理解
用户输入自然语言查询,例如:“什么药物靶向乳腺癌中的BRCA1基因?”RAG系统可能会对问题进行改写或根据配置过滤不相关问题。这种步骤在商业RAG聊天机器人应用中更为常见。
2. 路由
可能存在多种数据类型和来源需要导航。我们的简单RAG流程只有一个数据源,因此所有问题均会路由到创建好的知识图谱。
3. 查询构建
将查询中的术语映射到Neo4j数据库中的实体和关系。例如,“BRCA1基因”映射到特定节点,“靶点”映射为如“抑制”或“结合”的关系,具体视模式而定。
现代知识图谱系统不仅支持简单查询,还能实现推荐系统、关系预测、图嵌入生成、高级分析、数据可视化、实时处理及通过高级Cypher查询执行机器学习任务。
4. 数据检索
执行Cypher查询,检索相关节点和关系集。
5. 增强与生成
- 结果聚合
处理Cypher查询的原始结果,提取有意义信息,可能涉及过滤、排序或基于相关性、相似度等标准进一步聚合数据。例如,若多个药物靶向BRCA1,高级系统可能考虑临床试验阶段、疗效或副作用来优先排序信息。 - 结构化数据转文本
将从Neo4j检索的结构化数据(节点和关系)转换成自然语言,如:“药物X靶向BRCA1基因,该基因与乳腺癌相关。临床研究表明,X对BRCA1突变患者有治疗效果。”
第9章将介绍基于SQL数据库的RAG。大多数数据以表格形式存储。相比之下,基于图数据库的RAG提供多种独特优势,关键在于图数据库能自然建模复杂且相互关联的数据,表现为本体和层级结构中的关系。
知识图谱数据库能直接建模图关系,使RAG系统快速检索并综合药物与多靶点的相互作用,或遗传突变对疾病通路的影响。相比之下,SQL或NoSQL数据库需复杂连接或分表处理此类关系,检索过程不直观且较慢。
另一个知识图谱优势是高效处理涉及多跳关系的复杂查询,如追踪遗传突变对蛋白功能的影响,并将其关联到不同患者的临床结果。
提示
基于知识图谱的RAG在文献综述和假设生成方面非常强大。RAG系统可检索相关科学论文,遍历引用树,查找其它相关论文,并生成当前知识状态的简明摘要。
示例8-6展示了上传简单CSV文件(表8-1)的过程。代码为药物(表中Drug1和Drug2对应KG标签Drug)、副作用(SideEffect)、疾病(Disease)和酶(Enzyme)创建节点。
表8-1 药物、副作用、疾病和酶示例表格
| Drug1 | Drug2 | SideEffect | Disease | Enzyme |
|---|---|---|---|---|
| Aspirin | Ibuprofen | Nausea | Headache | CYP2C9 |
| Ibuprofen | Paracetamol | Dizziness | Inflammation | CYP2D6 |
| Paracetamol | Aspirin | Liver damage | Fever | CYP1A2 |
| Warfarin | VitaminK | Bleeding disorder | Blood clot | CYP2C9 |
示例8-6使用 MATCH_NODE_QUERY 从图数据库中获取最多10个任意类型节点,使用 MATCH_REL_QUERY 获取最多10条包含节点间关系的路径。此操作通常用于快速检查图数据。
接着,使用 CSV_LOAD_QUERY 从LangChain4LifeSciencesHealthcare仓库导入CSV文件,创建药物、副作用、疾病和酶节点,并建立它们之间的关系,具体包括药物引起副作用、与其他药物相互作用、结合酶和治疗疾病。MERGE命令保证每个节点和关系只创建一次,即使在CSV中多次出现。为避免错误,建议先检查指定数据库(如本例中的testcsv)是否存在,不存在则创建。
示例8-6 代码片段(部分):
from neo4j import GraphDatabase
driver = GraphDatabase.driver("bolt://localhost:7689", auth=("neo4j", "password"))
MATCH_NODE_QUERY = "MATCH(n) RETURN n LIMIT 10"
MATCH_REL_QUERY = "MATCH p=()-[r]->() RETURN p LIMIT 10"
CSV_LOAD_QUERY = """LOAD CSV WITH HEADERS
FROM "https://.../datasets/ch8_test_graph.csv" AS row
MERGE (d1:Drug {name: row.Drug1})
MERGE (d2:Drug {name: row.Drug2})
MERGE (s:SideEffect {name: row.SideEffect})
MERGE (dis:Disease {name: row.Disease})
MERGE (e:Enzyme {name: row.Enzyme})
MERGE (d1)-[:CAUSES]->(s)
MERGE (d1)-[:INTERACTS_WITH]->(d2)
MERGE (d1)-[:BINDS]->(e)
MERGE (d1)-[:CURES]->(dis)"""
db_name = "testcsv"
db_list = [x.data()['name'] for x in driver.execute_query("SHOW DATABASES").records]
if db_name not in db_list:
driver.execute_query(f"CREATE DATABASE {db_name}")
driver.execute_query(CSV_LOAD_QUERY, database_=db_name)
print("nodes", [x.data()['n'] for x in driver.execute_query(MATCH_NODE_QUERY, database_=db_name).records])
print("edges:", [x.data()['p'] for x in driver.execute_query(MATCH_REL_QUERY, database_=db_name).records])
类似地,你可以使用其他函数和插件上传不同类型数据至Neo4j数据库,整体流程一致:
- 上传文件
- 提取数据
- 设置节点和关系
- 执行操作
提示
Neo4j Aura免费层云实例有一定限制,想解锁更多功能,建议在本地环境运行Neo4j。
现在我们已经构建了知识图谱,来看看如何利用它。示例8-7展示了使用LangChain的GraphCypherQAChain创建一个基本图查询流水线。该流水线允许我们针对Neo4j图中存储的数据提出问题,并通过图遍历返回答案。
在这个例子中,该链用来查询药物类别。设置了return_direct=True,意味着期望最终答案不会被语言模型修改。由于链条简单,你会发现对于问题“What category does Fluoxymesterone belong to?”,该链成功识别出图中与之关联的多个类别,如Androgens和Androstenes等。然而,输入的细微变化,比如将“Fluoxymesterone”改为小写“fluoxymesterone”,则没有匹配结果,这凸显了查询一致性的重要性。
示例8-7 基本图查询链示例:
from langchain_neo4j import Neo4jGraph, GraphCypherQAChain
graph = Neo4jGraph(url=..., username=..., password=...)
chat_model = ...
basic_chain = GraphCypherQAChain.from_llm(
chat_model, graph=graph, verbose=True, return_intermediate_steps=True,
return_direct=True
)
basic_chain.invoke({"query": "What category does Fluoxymesterone belong to?"})
basic_chain.invoke({"query": "What category does fluoxymesterone belong to?"})
这个基本流水线只了解Neo4j图的最小部分,主要基于Neo4j模式(schema),所以GraphCypherQAChain还有很大的改进空间。我们可以使用return_intermediate_steps参数返回链条中间步骤,使用return_direct参数直接返回结果,通过validate_cypher参数校正生成的Cypher语句中的关系方向。我们还能自定义提示词(prompt)并设置上下文以生成针对特定问题的Cypher查询。通过top_k参数限制GraphCypherQAChain返回结果的数量。cypher_llm和qa_llm参数允许为Cypher查询和答案生成分别使用不同的LLM,比如当qa_llm是针对领域微调且不懂Cypher语言的模型时非常有用。
示例8-8展示了上述部分改进的实现及其对响应的影响。正如我们可以为Cypher和问答分别使用不同LLM,也可以使用不同提示词。QA_TEMPLATE是一个可定制的提示模板,用于明确问题细节;而更详细的CYPHER_GENERATION_TEMPLATE提供了生成Cypher查询的指导,参考了图中的模式和关系。最新完整的提示模板代码可在LangChain4LifeSciencesHealthcare仓库找到。
此外,我们设置GraphCypherQAChain校验Cypher查询、返回中间步骤,并通过提示使用toLower方法实现不区分大小写搜索。这种方法更为高级,尤其适合处理节点属性中的文本不一致问题。
示例8-8 图查询链带提示的实现:
from langchain_core.prompts.prompt import PromptTemplate
QA_TEMPLATE = """Before generating the cypher query, always decompose what the
final query should do.
Question: {question}
"""
QA_PROMPT = PromptTemplate(input_variables=["question"], template=QA_TEMPLATE)
CYPHER_GENERATION_TEMPLATE = """Task: Generate Cypher statement to query a graph db.
Instructions: Use only the provided relationship types and properties in the
schema: {schema}
Below are possible relationships:
Drug interacts with Drug
Drug causes SideEffect
Drug binds to Category, Target, Enzyme, Carrier, Transporter
...
For complex queries, think of the relationships required from the list above.
#What pathways are drugs curing cerebral embolism involved in.
To answer this question, several steps should be provided:
1. Drug - cures -> Disease
2. Drug - involves_in -> Pathway
MATCH (d:Drug)-[:cures]->(:Disease {{name: 'Cerebral Embolism'}}),
(d)-[:involves_in]->(p:Pathway) RETURN p
...
Always use toLower method to avoid case sensitivity:
#What categories does dornase alfa drug belong to?
MATCH (d:Drug)-[:belongs_to]->(c:Category) WHERE toLower(d.name)
CONTAINS('dornase alfa') return d, c
...
As some of the nodes as Disease and SideEffect may have multiple concatenated
values, always use contain statement:
# What drugs cause osteomalacia?
MATCH (d:Drug)-[:causes]->(s:SideEffect) WHERE toLower(s.name)
CONTAINS('osteomalacia') return d, s
...
The question is: {question}"""
CYPHER_GENERATION_PROMPT = PromptTemplate(
input_variables=["schema", "question"], template=CYPHER_GENERATION_TEMPLATE
)
prompt_chain = GraphCypherQAChain.from_llm(
chat_model, graph=graph, return_intermediate_steps=True, return_direct=True,
validate_cypher=True, verbose=True, qa_prompt=QA_PROMPT,
cypher_prompt=CYPHER_GENERATION_PROMPT,
)
示例8-9展示了运行示例8-8的结果。注意并非所有提示规则都被应用,比如对于问题“What can cure meningitis?”生成的Cypher查询未使用CONTAINS规则。在节点名称直接匹配查询时,更高级的流水线可能返回更好的结果。本章后续会进一步探讨如何提升表现。
示例8-9 运行高级GraphCypherQAChain结果:
prompt_chain.invoke({"query": "What category does fluoxymesterone belong to?"})
# 返回:
{'query': 'What category does Fluoxymesterone belong to?',
'result': [{'c': {'name': 'Androstenes', '_id': 15784}}, {'c': {'name': 'Fused-Ring Compounds', '_id': 16775}}, ..., {'c': {'name': 'Androstanes', '_id': 15781}}],
'intermediate_steps': [{'query': "MATCH (d:Drug {name: 'Fluoxymesterone'})-[:belongs_to]->(c:Category) RETURN c"}]}
prompt_chain.invoke({"query": "What can cure meningitis?"})
# 返回:
{'query': 'What can cure meningitis?',
'result': [],
'intermediate_steps': [{'query': "MATCH (drug:Drug)-[:cures]->(:Disease {name: 'Meningitis'})\nRETURN drug"}]}
prompt_chain.invoke({"query": "What side effect can be caused by expectorants category drugs?"})
# 返回:
{'query': 'What side effect can be caused by expectorants category drugs?',
'result': [{'se.name': 'Post procedural bile leak'}, {'se.name': 'Incision site infection'}, ..., {'se.name': 'Pain;Dolor;PAIN'}],
'intermediate_steps': [{'query': 'MATCH (:Category {name: "Expectorants"})<-[:belongs_to]-(:Drug)-[:causes]->(se:SideEffect)\nRETURN se.name'}]}
另一种定制最终结果的方法是使用use_function_response参数,将数据库结果作为工具/函数输出传递给LLM,示例8-10展示了这一用法。通过提供自定义系统消息(function_response_system参数)指导模型生成答案。在此情况下,qa_prompt参数不会生效。
示例8-10 通过function_response解析输出:
chain = GraphCypherQAChain.from_llm(
chat_model, graph=graph, verbose=True, use_function_response=True,
function_response_system="Convert the answer to a bullet list",
)
chain.invoke({"query": "What category does fluoxymesterone belong to?"})
# 返回:
Androstenes
Androgens
...
Androstanes
Neo4j 向量索引
第3章介绍了索引和向量存储,作为 LangChain 的主要组件之一。向量索引通过将节点或属性表示为多维空间中的向量,实现相似度搜索和复杂分析查询。
在 Neo4j 数据库中,索引是特定主数据(如节点、关系或属性)的副本,作为快速访问和检索数据的快捷方式。索引创建后,系统会自动填充和更新。Neo4j 提供两种索引类型:搜索性能索引(精确匹配),用于快速数据检索;语义索引(相似匹配),支持近似匹配并计算查询字符串与数据间的向量相似度。
提示
Neo4j 支持在图数据库中创建并使用向量索引和关键词索引。通过查询时指定混合搜索类型,可以结合向量搜索的语义相似度与关键词搜索的词汇匹配:
Neo4jVector.from_existing_index(
index_name=vector_index_name,
keyword_index_name=keyword_index_name,
search_type="hybrid",
)
该方法结合两种索引的优势,能更全面且相关地识别图中的潜在数据点,再从 Neo4j 中检索相应节点及其属性。
Neo4j 向量功能可通过多种方式创建和使用。add_documents、add_texts 等方法与前章讨论类似。也有可作用于已有图的方法,如from_existing_graph(embedding, node_label, ...),可从现有图初始化并返回一个 Neo4jVector 实例。若索引已存在,可用from_existing_index(embedding, index_name[, ...])获取已有向量索引实例,from_existing_relationship_index(embedding, ...)则获取已有关系向量索引实例。
示例8-11使用指定的embedding_model创建一个名为n_disease_vector的节点向量索引,将带有node_label的text_node_properties进行嵌入,存储于embedding_node_property中。
示例8-11 创建节点向量索引:
n_disease_vector = Neo4jVector.from_existing_graph(
embedding=embedding_model,
...
index_name="n_vector_diseases",
node_label="Disease",
text_node_properties=["name"],
embedding_node_property="embedding",
)
调用向量索引搜索示例:
n_disease_vector.similarity_search_with_score("meningitis", k=5)
结果示例包括:
- Meningococcal meningitis,得分0.813
- Neonatal meningitis,得分0.802
- Staphylococcal meningitis,得分0.80
- Meningococcemia,得分0.791
- Streptococcal meningitis,得分0.791
注意,结果中包含了Meningococcemia节点,虽然其不包含“meningitis”的全文匹配。这种基于节点的向量搜索结合了图结构和向量索引的优势,十分有用。
Neo4j 还支持通过存储嵌入作为关系属性来建立关系向量索引。多数节点彼此不同,且图中关系数量往往多于节点,但关系类型数量通常远少于节点数。此时,可对所有关系整体嵌入,并将其属性添加至现有图,而非逐个嵌入。关系向量索引不能通过LangChain填充,但可连接至已有关系向量索引,示例8-12演示了这一过程。
创建关系向量索引时,需要为所有关系设置名称和嵌入属性。关系类型较少时,对同类型关系统一使用embedding_model计算的嵌入值。示例8-12中,利用Cypher上的Awesome Procedures (APOC)并行批量执行更新。
关系嵌入填充完成后,可基于嵌入创建向量索引。指定的vector.dimensions应与embedding_model的输出维度一致。执行索引创建查询后,可用from_existing_relationship_index方法设置Neo4jVector变量。
示例8-12 创建关系(边)向量索引(部分代码):
embedding_model = OpenAIEmbeddings(model="text-embedding-3-large")
for r_type in graph.query("""CALL db.relationshipTypes()"""):
relations_embedding[r_type["relationshipType"]] = embedding_model.embed_query(
relations_names[r_type["relationshipType"]]
)
for r_type in relations_embedding:
query_match = f"MATCH ()-[r:{r_type}]->() RETURN r"
query_update = f"WITH r SET r.embedding = {relations_embedding[r_type]}"
graph.query(
f""" CALL apoc.periodic.iterate(
"{query_match}", "{query_update}", {{batchSize: 500, parallel: true}}
)"""
)
graph.query(
"""CREATE VECTOR INDEX r_vector_cures IF NOT EXISTS
FOR ()-[r:cures]-() ON (r.embedding)
OPTIONS {{
indexConfig: {{
`vector.dimensions`: 3072,
`vector.similarity_function`: 'cosine'
}}
}}"""
)
r_cure_vector = Neo4jVector.from_existing_relationship_index(
embedding_model,
...
index_name="r_vector_cures",
text_node_property="name",
)
索引信息中不包含文本节点属性信息。如使用除文本外属性(默认选项),需通过text_node_property参数指定。运行SHOW INDEXES(示例8-13)可查看图中创建的向量索引及其配置,包括向量维度3072和余弦相似度函数。也可使用SHOW VECTOR INDEXES或SHOW VECTOR INDEXES YIELD *获取类似信息。
示例8-13 显示疾病节点与治愈关系的向量索引:
graph.query(
"""SHOW INDEXES YIELD name, type, labelsOrTypes, properties, options
WHERE type = 'VECTOR' """
)
结果包含:
- 名称为
r_vector_cures,类型为VECTOR,标签为cures,属性为embedding,向量维度3072,使用余弦相似度。 - 名称为
n_vector_diseases,类型为VECTOR,标签为Disease,属性为embedding,向量维度3072,使用余弦相似度。
有了索引后,如何动态使用它们?我们可以找到并保留最相似的节点和关系,通过调用子查询将它们引入下一次查询。此法可行,但不是LangChain推荐的方式。相比堆叠查询,我们可以调用独立链条分别从图中检索相关向量结果,再用于主链。
要从向量存储索引检索数据,我们将使用LangChain的as_retriever方法为两个创建的向量存储构建检索器,如示例8-14所示。向量搜索和检索器在第3、4章中已有介绍。
随后设计query_analyzer链,分解问题以决定使用哪个检索器。为此实现一个Search工具,接收检索器名称映射,并设置候选选项描述(示例8-14中的NODE_DISEASE和REL_CURES)。Search工具由LLM驱动,原则是描述和提示越好,输出越优。
选定节点和关系检索器后,收集其数据并传递给主链。在main_chain中,我将调整CYPHER_GENERATION_TEMPLATE和CYPHER_GENERATION_PROMPT,将检索到的关系和节点纳入生成。
示例8-14 创建Neo4j向量链(部分代码):
from langchain_core.runnables import RunnablePassthrough, chain
n_disease_retriever = n_disease_vector.as_retriever()
r_cure_retriever = r_cure_vector.as_retriever()
retrievers = {
"NODE_DISEASE": n_disease_retriever,
"REL_CURES": r_cure_retriever,
}
class Search(BaseModel):
"""搜索图数据库中与药物相关的信息。"""
query: str = Field(..., description="查询语句")
node: str = Field(..., description="要查询的节点索引,应为NODE_DISEASE(疾病相关)")
rel: str = Field(..., description="要查询的关系索引,应为REL_CURES(治疗相关)")
system = """你可以发起搜索查询以获取帮助回答用户问题的信息。"""
prompt = ChatPromptTemplate.from_messages(
[
("system", system),
("human", "{question}"),
]
)
structured_llm = chat_model.with_structured_output(Search)
query_analyzer = {"question": RunnablePassthrough()} | prompt | structured_llm
@chain
def retriever_chain(question):
response = query_analyzer.invoke(question)
retriever_n, retriever_r = retrievers[response.node], retrievers[response.rel]
return retriever_n.invoke(response.query), retriever_r.invoke(response.query)
CYPHER_GENERATION_TEMPLATE = """Task: Generate Cypher statement to query a graph db.
...
Schema:
{schema}
...
Create a query using the following nodes: {nodes} and relations: {rels}
The question is:
{question}"""
CYPHER_GENERATION_PROMPT = PromptTemplate(
input_variables=["schema", "question", "nodes", "rels"],
template=CYPHER_GENERATION_TEMPLATE,
)
main_chain = GraphCypherQAChain.from_llm(
chat_model, graph=graph, verbose=True,
return_intermediate_steps=True, return_direct=True,
validate_cypher=True, cypher_prompt=CYPHER_GENERATION_PROMPT,
)
示例8-15展示了使用该链执行以下问题的结果:
- 什么能治疗脑膜炎?
- 什么能帮助关节炎?
注意,这些问题中都没有直接提到“cure”(治愈),而“cure”正是我们定义的关系名称。LIMIT_RESPONSES设置为3,用于限制主链中使用的唯一节点和关系的数量。从intermediate_steps中可以看到,生成的Cypher查询包含了正确的节点和关系,这使得仅用自然语言输入即可实现更高级的图数据检索。同样注意,包含“arthritis”(关节炎)的查询返回了包含“arthritic”和“polyarthritis”等相关结果。
示例8-15 Neo4j向量链结果示例:
def format_page_content(page_content):
return page_content.replace("\nname: ", "")
LIMIT_RESPONSES = 3
query = "What can heal meningitis?"
nodes, rels = retriever_chain.invoke({"query": query})[:LIMIT_RESPONSES]
main_chain.invoke({
"query": query,
"nodes": {format_page_content(x.page_content) for x in nodes},
"rels": {format_page_content(x.page_content) for x in rels},
})
# 返回示例
{
'query': 'What can heal meningitis?',
'nodes': {'Meningococcal meningitis', 'Neonatal meningitis',
'Staphylococcal meningitis', 'Streptococcal meningitis'},
'rels': {'cures'},
'result': [{'Cure': 'Meropenem'}, {'Cure': 'Chloramphenicol'}, ...,
{'Cure': 'Benzylpenicillin'}, {'Cure': 'Cefotaxime'}],
'intermediate_steps': [{
'query': "MATCH (d:Drug)-[:cures]->(disease:Disease) "
"WHERE disease.name IN ['Meningococcal meningitis', 'Staphylococcal meningitis', "
"'Neonatal meningitis', 'Streptococcal meningitis'] "
"RETURN d.name as Cure;"
}]
}
query = "What can help with arthritis?"
nodes, rels = retriever_chain.invoke({"query": query})[:LIMIT_RESPONSES]
main_chain.invoke({
"query": query,
"nodes": {format_page_content(x.page_content) for x in nodes},
"rels": {format_page_content(x.page_content) for x in rels},
})
# 返回示例
{
'query': 'What can help with arthritis?',
'nodes': {'Arthritic pains', 'Arthritis, Bacterial',
'Degenerative polyarthritis', 'Juvenile arthritis'},
'rels': {'cures'},
'result': [{'d.name': 'Salsalate'}, {'d.name': 'Etofenamate'}, ...,
{'d.name': 'Dexibuprofen'}, {'d.name': 'Salicylic acid'}],
'intermediate_steps': [{
'query': "MATCH (d:Drug)-[:cures]->(disease:Disease) "
"WHERE disease.name IN ['Arthritis, Bacterial', 'Juvenile arthritis', "
"'Arthritic pains', 'Degenerative polyarthritis'] "
"RETURN d.name"
}]
}
通过利用基于向量的技术和向量相似度,我们的系统具备对大小写不一致、拼写错误以及无需完全匹配词汇的耐受力,这种固有的灵活性增强了系统的鲁棒性。此外,依赖底层向量模型,系统具备多语言支持能力,包括识别用不同语言书写的疾病名称,因为它依赖的是向量相似度而非字符串匹配。这种方法使系统即使面对疾病名称的细微变体,也能准确识别疾病。
总结
本章探讨了生成式人工智能与药物发现的交汇点,重点介绍了科技公司与制药企业合作如何加速药物研发,以及科技公司为何对这类联盟表现出浓厚兴趣。
本章介绍了自动编码器和变分自动编码器作为分子生成的强大工具,讲解了它们的架构、潜在空间表示,并通过逐步指导构建条件变分自动编码器神经网络,实现具有特定性质的新药候选分子的生成。展示了这类模型如何将分子结构压缩为数值向量,并通过探索或操作潜在空间生成新分子,同时指出在化学有效性方面仍存在挑战,可能需额外专家审核。
另一个药物发现的热点是知识图谱的应用,讲解了它们如何表示药物、疾病、蛋白质及其他生物医学成分之间的互联实体和关系。本章详细介绍了利用Neo4j实现知识图谱,展示了数据上传、查询构建及通过LangChain链实现图数据库自然语言查询的过程。展示了知识图谱如何揭示实体间以前未发现的关系,对药物再利用、预测药物相互作用和理解疾病机制等具有重要价值。
最后,本章探讨了基于向量的技术如何增强知识图谱能力,创建节点和关系的向量索引。展示了向量嵌入的实现,说明相似性搜索如何支持更灵活且鲁棒的查询,减少对大小写差异和拼写错误等变体的敏感性。
下一章将介绍多个可应用于医疗和健康领域的生成式AI应用。我们将构建一个协调多智能体的LangGraph超级应用,实现日常工作自动化,帮助提出和头脑风暴可能的诊断方案。