知识补充
前言
小白实现代码,记录自己的学习脚步。
一、Tokenization
1.知识补充-wordpiece中提到的对数似然估计
对数似然估计
通常使用概率的对数来衡量的原因有几点:
数值稳定性: 概率通常是小于1的小数,当计算多个概率相乘时,可能会导致数值下溢(即计算结果变得非常接近于零),尤其是在长句子或者大规模数据集中。使用对数转换后,可以将相乘转换为相加,避免了数值下溢问题,提高了计算的稳定性。
便于比较: 对数概率的值域是负无穷到零,数值越大表示概率越高。这种形式使得概率的比较更加直观和方便,不需要考虑概率的范围。
数学便利性: 对数函数具有很好的数学性质,例如对数函数的导数可以简化计算。这使得在概率模型中的一些计算更加高效和方便。
对数似然增益(Log Likelihood Gain):
是一种在机器学习和统计领域中用于评估模型性能的指标。它通常用于监督学习中的分类和回归任务。对数似然增益是指在某个模型下,使用对数似然函数作为损失函数时,模型在数据集上的对数似然值的增加量。
让我们从对数似然函数开始解释。在统计学中,对数似然函数是一种用于估计参数的方法。对于给定的观察数据集,我们假设数据服从某个概率分布,例如高斯分布或伯努利分布。对数似然函数是这些数据在给定模型下的似然性的对数值。其表达式通常写为:
在训练机器学习模型时,我们的目标是最大化对数似然函数。对于分类问题,我们希望模型的预测值与真实标签的概率分布尽可能接近;对于回归问题,我们希望模型的预测值与真实值之间的误差尽可能小。
对数似然增益是指在模型进行改进后,对数似然函数的增加量。在训练过程中,我们尝试不同的模型参数或不同的模型结构,以使对数似然函数的值增加。对数似然增益提供了一种量化模型改进的指标,即模型的新版本相对于之前版本对数据的拟合程度提高了多少。
在实践中,对数似然增益通常与交叉验证结合使用,以帮助选择最佳的模型参数或模型结构,以便在未见数据上获得良好的泛化性能。
2.子词粒度subword常用库
二、构造词表
构造词表的主要目的包括:
词频统计: 构造词表可以帮助统计每个词在文本中出现的频率。这对于词语的重要性排序、信息检索和文本分类等任务非常重要。
词性标注: 词表中的词语往往会被标注上其词性,例如名词、动词、形容词等。这对于语言分析和语法分析非常有用。
特征提取: 在自然语言处理任务中,如文本分类、情感分析等,词表可以用于提取文本的特征。通常会将每个词在词表中的位置作为特征向量的一部分。
减少计算量: 在很多自然语言处理任务中,词表的使用可以减少计算量。例如,在文本分类任务中,可以将文本表示成词语在词表中的出现情况,从而减少了文本向量的维度。
处理未登录词: 词表中记录了已知的词语,可以帮助系统处理那些没有出现在词表中的未登录词,通过一些特殊的处理方式来应对未登录词的问题
Bert模型的优点在于同一个词在不同语句中的向量表示是不同的,它是根据上下文得到的是一个动态的过程
1.使用数据集构建图表
2.数据长度处理问题
横轴是文本长度,纵轴是频率
解决长尾问题的方法
(1)在自然语言处理中,特别是在处理文本数据时,往往需要将文本数据转换成固定长度的向量或者矩阵,以便于输入到机器学习模型中进行训练或推理。而当原始文本长度不一致时,就需要使用一些方法对文本进行处理,其中包括“pad”和“truncate”。
-
Padding(填充): Padding 是指在文本的开头或结尾添加特定的标记(通常是一个特殊的符号,如0),使得文本的长度达到某个预设的长度。这通常用于处理文本长度不足的情况,以保证所有输入文本的长度相同。例如,假设设定了一个长度为10的文本输入,而某个文本只有5个词,则可以在文本结尾处添加5个特定标记,使得文本长度达到10。
-
Truncation(截断): Truncation 是指将文本的长度截断到某个预设的长度。当原始文本的长度超过所设定的长度时,可以通过截断的方式将其缩减至指定长度。通常有两种截断方式:
- 前向截断(前向截取): 保留文本的前部分,丢弃超过指定长度的部分。
- 后向截断(后向截取): 保留文本的后部分,丢弃超过指定长度的部分。
Pad + truncate 是指将文本数据首先进行填充(pad)或截断(truncate)处理,以保证所有输入文本的长度一致。这种方法常用于神经网络等模型的输入处理中,确保输入的数据具有相同的形状,从而可以被更方便地处理和应用于模型中。
(2)在计算机科学和数据处理领域,“分桶”(bucketing)是一种将数据分成特定区间或范围的技术。这个概念经常在数据处理、数据分析和机器学习等领域中被使用。
分桶的主要目的是将连续的数值型数据划分成离散的组,以便于统计、分析或者处理。常见的应用包括数据的分组统计、特征工程中的数值离散化等。
下面是分桶的一些常见应用和方法:
等宽分桶(Equal Width Bucketing): 在等宽分桶中,数据的范围被均匀地分成若干个区间。例如,如果将0到100的数据分成5个区间,则每个区间的范围为20。这种方法简单易行,但可能由于数据分布不均匀而导致一些桶中数据较少。
等频分桶(Equal Frequency Bucketing): 在等频分桶中,数据被划分成具有相似数据点数量的区间。这意味着每个区间中的数据点数量大致相等。这种方法可以有效地解决数据分布不均匀的问题,但可能由于数据点数量的不同而导致区间的宽度不均匀。
自定义分桶(Custom Bucketing): 在某些情况下,根据数据的特点,可以自定义分桶策略。例如,根据业务需求或领域知识,将数据划分成具有特定含义的区间。这种方法通常需要深入了解数据的特点和业务需求。
分位数分桶(Quantile Bucketing): 在分位数分桶中,数据被划分成具有相等百分比的区间。例如,四分位数分桶将数据分成四个区间,每个区间包含数据中25%的观测值。这种方法能够有效地捕捉数据的分布情况,但可能会导致每个区间中数据量不均匀。
总的来说,分桶是一种常见且实用的数据处理技术,可以在数据分析和机器学习等领域中起到重要的作用。选择合适的分桶方法需要考虑数据的分布情况、业务需求以及分析目的等因素。
三、data_processing模块
1.导入需要使用的库的模块
首先对需要的库进行一个导入,在想这个里边的collection跟java里边的会有什么联系吗?
答:在Python中,collections.defaultdict
是一个特殊的字典类型,它是collections
模块提供的一个工具类,用于创建字典对象。与普通字典相比,defaultdict
的特点在于当试图访问一个不存在的键时,它会自动创建这个键并将其对应的值初始化为默认值,而不会抛出KeyError异常。
与Java中的Collection接口不同,collections.defaultdict
更类似于Java中的HashMap。它们的相似之处在于都提供了一种映射数据结构,可以存储键值对,并支持通过键来快速查找值。但与Java中的Collection接口不同,defaultdict
只是Python中字典类型的一个特殊实现,而Java中的Collection是一个接口,有多种具体的实现类(如List、Set、Map等),用于表示不同的集合类型。
`import os # 导入操作系统模块`
`import pickle # 导入 pickle 序列化模块`
`import argparse # 导入命令行参数解析模块`
`import numpy as np # 导入 NumPy 数组操作库`
`import pandas as pd # 导入 Pandas 数据处理库`
`from tqdm import tqdm # 导入进度条模块`
`from rdkit import Chem # 导入 RDKit 化学信息处理库`
`from rdkit import DataStructs # 导入 RDKit 数据结构处理库`
`from rdkit.Chem import AllChem # 导入 RDKit 化学信息处理库的子模块`
`from collections import defaultdict # 导入默认字典模块`
`from sklearn.model_selection import StratifiedShuffleSplit # 导入 StratifiedShuffleSplit 分层随机分割模块`
`import networkx as nx # 导入网络图模块`
`from torch_geometric.data import Data # 导入 PyTorch Geometric 中的数据类`
`from torch.utils.data import Dataset, DataLoader # 导入 PyTorch 中的数据集类和数据加载器类`
`from utils import * # 导入自定义的工具函数`
`import pandas as pd # 导入 Pandas 数据处理库`
`import csv # 导入 CSV 文件处理模块`
`import random # 导入随机数生成模块`
`from tqdm import tqdm # 导入进度条模块`
`import copy # 导入复制模块`
`import numpy as np # 导入 NumPy 数组操作库`
以上就是导入了一些相关的库
2.为不同的模块设置随机数种子从而能够实现复现代码
`#设置随机种子函数`
`def set_random_seed(seed, deterministic=False):`
`torch.manual_seed(seed) # 设置 PyTorch 的随机种子`
`torch.cuda.manual_seed(seed) # 设置 PyTorch GPU 的随机种子`
`torch.cuda.manual_seed_all(seed) # 如果使用多 GPU,设置所有 GPU 的随机种子`
`np.random.seed(seed) # 设置 NumPy 的随机种子`
`random.seed(seed) # 设置 Python 自带的随机种子`
`torch.manual_seed(seed) # 重新设置 PyTorch 的随机种子`
`torch.backends.cudnn.benchmark = False # 关闭 cuDNN 的基准模式`
`torch.backends.cudnn.deterministic = True # 设置 cuDNN 的确定性模式为 True`
`#调用设置随机种子函数,并设置确定性为 True`
`set_random_seed(1, deterministic=True)``
(1)为不同的模块或操作设置相同的随机种子,如PyTorch、NumPy和Python自带的随机种子,可以确保它们在随机性操作时生成的随机数序列是相同的。
(2)这样做的目的是为了保证实验的可重复性和结果的稳定性。在机器学习中,通常会多次运行实验以获得可靠的结果,而设置相同的随机种子可以确保每次运行时产生的随机数序列是一致的,从而使结果具有可比性。
在具体的代码中,通过调用set_random_seed
函数并将deterministic
参数设置为True,可以关闭所有可能影响随机性的因素,确保每次运行时都产生相同的随机数序列。这样做有助于在不同的环境下获得相同的实验结果,提高了实验的可重复性和结果的可靠性。也就是说我们在实验过程中,需要注意每次设置的随机序列都是一致的,这样才可以展现出我们模型是否有所改进,不然的话相当于每次实验都是独立的没办法作比较
设置好随机种子之后,便是对于文件数据中的提取,
先设置一些方法,首先是对于类的初始化,将获取到的每列数值以实体的形式保存。
3.初始化类的方法就是将文件中的数据转换到数组中,同时通过随机数种子进行数据集的划分
def __init__(self, triple): # 定义类的初始化方法,接收一个参数 triple
self.entity1 = triple[:, 0] # 将 triple 参数的第一列赋值给 self.entity1
self.entity2 = triple[:, 1] # 将 triple 参数的第二列赋值给 self.entity2
self.relationtype = triple[:, 2] # 将 triple 参数的第三列赋值给 self.relationtype
#self.label = triple[:, 3] # 将 triple 参数的第四列赋值给 self.label(这行代码被注释掉了)
def __len__(self): # 定义 __len__ 方法,返回数据集的长度
return len(self.relationtype) # 返回关系类型(relationtype)的长度作为数据集的长度
def __getitem__(self, index): # 定义 __getitem__ 方法,用于根据索引获取数据项
return (self.entity1[index], self.entity2[index], self.relationtype[index]) # 返回索引对应位置的实体1、实体2和关系类型元组
以上代码定义了一个名为 Data_class 的类,用于创建一个数据集对象,该数据集对象包含了实体1、实体2和关系类型的数据。在类的初始化方法中,将传入的 triple 参数的每一列分别赋值给类的属性 entity1、entity2 和 relationtype。__len__ 方法返回了数据集的长度,即关系类型(relationtype)的长度。__getitem__ 方法用于根据索引获取数据项,返回了索引对应位置的实体1、实体2和关系类型的元组。
def load_data(args, val_ratio=0.1, test_ratio=0.2): # 定义一个名为 load_data 的函数,接收参数 args、val_ratio 和 test_ratio,默认值分别为 0.1 和 0.2
在函数 `load_data` 中,参数 `val_ratio` 和 `test_ratio` 是用来指定验证集和测试集所占总体数据的比例的。具体来说:
- `val_ratio` 表示验证集的比例,取值范围为 0 到 1 之间,表示验证集占总体数据的比例。例如,如果 `val_ratio` 设为 0.1,表示验证集占总体数据的 10%。
- `test_ratio` 表示测试集的比例,同样取值范围为 0 到 1 之间,表示测试集占总体数据的比例。例如,如果 `test_ratio` 设为 0.2,表示测试集占总体数据的 20%。
这样设置参数的目的是根据数据集的规模,将数据集划分为训练集、验证集和测试集三部分。通常的做法是将数据集按照 7:2:1 的比例划分,即训练集占总体数据的 70%,验证集占 20%,测试集占 10%。但在具体应用中,这个比例可以根据实际情况进行调整。
"""Read data from path, convert data into loader, return features and symmetric adjacency""" # 函数的文档字符串,说明函数的作用
# read data # 打印读取数据的提示信息
drug_list = [] # 创建一个空列表,用于存储药物列表
with open('data/drug_listxiao.csv', 'r') as f: # 打开药物列表文件,并使用文件对象 f
reader = csv.reader(f) # 创建一个 CSV 文件阅读器对象 reader,用于逐行读取文件内容
for row in reader: # 遍历文件中的每一行
drug_list.append(row[0]) # 将每行的第一个元素(药物名称)添加到 drug_list 中
print(len(drug_list)) # 打印药物列表的长度
在这之后设置了一个变量:
zhongzi = args.zhongzi # 从参数 args 中获取种子数,并赋值给变量 zhongzi
在这段代码中,设置了种子 zhongzi
的目的是为了确保在每次运行程序时,生成的随机结果都是相同的,从而使得实验结果具有可重现性。种子的值从 args
参数中获取,通常是通过命令行参数传入的。
具体来说,种子 zhongzi
被用于以下几个方面:
- 在读取训练数据集时,将种子作为文件名的一部分,以确保每次加载的数据集是相同的。这样做可以保证在训练过程中使用的数据是一致的,从而使得模型在不同运行中的表现具有可比性。
- 在对训练数据集进行随机打乱时,使用种子来初始化随机数生成器,以确保每次打乱的结果都是相同的。这样可以保证训练数据的顺序不会影响模型的训练效果。
- 在构造标签时,也使用种子来初始化随机数生成器,以确保每次生成的标签都是相同的。这样可以保证同一条数据对应的标签在不同运行中是一致的,从而确保模型训练的稳定性。
在相同的种子下,使用随机数生成器进行随机打乱得到的结果是一致的。换句话说,当你使用相同的种子来初始化随机数生成器时,无论何时何地运行代码,得到的随机结果都是相同的。
这种行为是因为在计算机中,所谓的“随机”实际上是伪随机,它们是通过确定性算法生成的。算法的输入参数中包含一个种子值,该种子值决定了随机数生成器的起始状态。因此,当你使用相同的种子值时,生成的随机数序列就是一样的。
通过设置相同的种子值来初始化随机数生成器,可以确保每次运行代码时得到的结果都是可复现的。这在机器学习中尤为重要,因为这样可以保证实验结果的一致性,并使得模型训练过程更加可控。
4.打印训练数据集,将数据集转换为one-hot编码便于后续的分类
以下代码定义了一个函数 load_data,该函数用于从文件中读取数据,并将数据转换为加载器(loader)。函数首先从文件中读取药物列表,然后加载训练数据集,并对数据进行处理,最后返回加载器和特征。函数中还定义了一个内部函数 loadtrainvaltest,用于加载训练数据集并进行处理
train_pos = [(h, t, r) for h, t, r in zip(train[‘d1’], train[‘d2’], train[‘type’])]
在这段代码中,train['d1']
、train['d2']
和 train['type']
是训练数据集中的头实体、尾实体和关系类型的列。通过使用 zip(train['d1'], train['d2'], train['type'])
将这三列打包在一起,然后通过列表推导式 [(h, t, r) for h, t, r in zip(train['d1'], train['d2'], train['type'])]
将每个样本的头实体、尾实体和关系类型组成一个元组,并将所有元组组成一个列表 train_pos
。
这样做的目的是为了方便后续处理。在图数据中,每个样本通常由两个节点和它们之间的边组成,而元组正好可以用来表示这种结构。通过将训练数据集的头实体、尾实体和关系类型转换为元组列表,可以更方便地处理这些样本,例如进行数据加载、批处理等操作。
def loadtrainvaltest(): # 定义一个名为 loadtrainvaltest 的函数
# train dataset # 打印训练数据集的提示信息
train = pd.read_csv('data/' + str(zhongzi) + '/ddi_training1xiao.csv') # 从文件读取训练数据集
train_pos = [(h, t, r) for h, t, r in zip(train['d1'], train['d2'], train['type'])] # 将训练数据集的头实体、尾实体和关系类型转换为元组列表
# np.random.seed(args.seed) # 设置随机种子
np.random.shuffle(train_pos) # 将训练数据集随机打乱
train_pos = np.array(train_pos) # 将训练数据集转换为 NumPy 数组
for i in range(train_pos.shape[0]): # 遍历训练数据集的每一行
train_pos[i][0] = int(drug_list.index(train_pos[i][0])) # 将每行的头实体索引替换为药物列表中对应药物的索引
train_pos[i][1] = int(drug_list.index(train_pos[i][1])) # 将每行的尾实体索引替换为药物列表中对应药物的索引
train_pos[i][2] = int(train_pos[i][2]) # 将每行的关系类型转换为整数类型
label_list = [] # 创建一个空列表,用于存储标签列表
for i in range(train_pos.shape[0]): # 遍历训练数据集的每一行
label = np.zeros((65)) # 创建一个长度为 65 的全零数组,用于存储标签
label[int(train_pos[i][2])] = 1 # 将关系类型对应的位置设为 1
label_list.append(label) # 将标签添加到标签列表中
label_list = np.array(label_list) # 将标签列表转换为 NumPy 数组
train_data = np.concatenate([train_pos, label_list], axis=1) # 将训练数据集和标签列表按列拼接起来,得到训练数据
以上代码定义了一个函数 load_data,该函数用于从文件中读取数据,并将数据转换为加载器(loader)。函数首先从文件中读取药物列表,然后加载训练数据集,并对数据进行处理,最后返回加载器和特征。函数中还定义了一个内部函数 loadtrainvaltest,用于加载训练数据集并进行处理(独热编码)。
在机器学习任务中,特别是分类任务中,通常使用独热编码(One-Hot Encoding)来表示类别标签。独热编码是一种将类别标签转换为向量形式的编码方式,其中向量的长度等于类别的总数,每个类别对应向量中的一个位置,该位置的值为1,其余位置的值为0。
在这个代码中,使用长度为65的向量来表示类别标签,其中65表示可能的关系类型总数。为了将某个具体的关系类型编码为独热向量,首先创建一个全零向量,然后将与关系类型对应的位置设置为1,其他位置仍然为0。这样做的目的是使模型能够直观地理解每个样本所属的关系类型,以便在训练过程中更好地学习特征与标签之间的关系。
举个例子,假设某个样本的关系类型是第0类,那么对应的独热向量就是一个长度为65的向量,其中第0个位置为1,其余位置为0。这样的编码方式能够更有效地表达类别信息,有助于提高模型的训练效果。
5.打印训练数据集,将数据集转换为图数据,并生成对抗节点
这段代码的作用是将训练数据集中的正样本转换为图数据的表示形式,并添加到原始数据的边索引列表和标签列表中。具体来说:
-
for i in range(positive1.shape[0]):
:遍历训练数据集中的每一行,其中positive1
是包含正样本的训练数据集。 -
a = []
:创建一个空列表a
,用于存储边的两个节点索引。 -
a.append(int(positive1[i][0]))
和a.append(int(positive1[i][1]))
:将当前行的第一个节点索引和第二个节点索引转换为整数,并依次添加到列表a
中,这样就得到了一条边的两个节点索引。 -
edge_index_o.append(a)
:将包含边的两个节点索引的列表a
添加到原始数据的边索引列表edge_index_o
中,表示一条边。 -
label_list.append(int(positive1[i][2]))
:将当前行的标签(关系类型)转换为整数,并添加到标签列表label_list
中,表示这条边的标签。 -
a = []
:清空列表a
,为下一条边的处理做准备。 -
a.append(int(positive1[i][1]))
和a.append(int(positive1[i][0]))
:将当前行的第二个节点索引和第一个节点索引转换为整数,并依次添加到列表a
中,这样就得到了逆向边的两个节点索引。 -
edge_index_o.append(a)
:将包含逆向边的两个节点索引的列表a
添加到原始数据的边索引列表edge_index_o
中,表示逆向边。 -
label_list.append(int(positive1[i][2]))
:将当前行的标签(关系类型)再次转换为整数,并添加到标签列表label_list
中,表示逆向边的标签。
这样,经过处理后,edge_index_o
中存储了所有边的节点索引,label_list
中存储了所有边的标签,其中正样本和逆向样本是成对出现的,以便后续的图模型训练。
for i in range(positive1.shape[0]):
a = [] # 创建一个空列表,用于存储边的两个节点索引
a.append(int(positive1[i][0])) # 将当前行的第一个节点索引转换为整数并添加到列表中
a.append(int(positive1[i][1])) # 将当前行的第二个节点索引转换为整数并添加到列表中
edge_index_o.append(a) # 将边的两个节点索引列表添加到原始数据的边索引列表中
label_list.append(int(positive1[i][2])) # 将当前行的标签转换为整数并添加到标签列表中
a = [] # 清空列表
a.append(int(positive1[i][1])) # 将当前行的第二个节点索引转换为整数并添加到列表中
a.append(int(positive1[i][0])) # 将当前行的第一个节点索引转换为整数并添加到列表中,形成逆向边
edge_index_o.append(a) # 将逆向边的两个节点索引列表添加到原始数据的边索引列表中
label_list.append(int(positive1[i][2])) # 将当前行的标签转换为整数并添加到标签列表中
b = [] # 创建一个空列表,用于存储标签
b.append(int(positive1[i][2])) # 将当前行的标签转换为整数并添加到列表中
b.append(int(positive1[i][2])) # 将当前行的标签转换为整数并再次添加到列表中,形成逆向标签
label_list11.append(b) # 将标签列表添加到标签列表11中,用于生成对抗节点
edge_index_o = torch.tensor(edge_index_o, dtype=torch.long) # 将边索引列表转换为张量
data_o = Data(x=x_o, edge_index=edge_index_o.t().contiguous(), edge_type=label_list) # 创建原始数据对象
x_a = torch.tensor(features_a, dtype=torch.float) # 将对抗节点的特征向量转换为张量
data_s = Data(x=x_a, edge_index=edge_index_o.t().contiguous(), edge_type=label_list) # 创建对抗节点数据对象
random.shuffle(label_list11) # 随机打乱标签列表11
flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x] # 定义一个用于扁平化列表的函数
label_list11 = flatten(label_list11) # 将标签列表11扁平化
data_a = Data(x=x_o, y=y_a, edge_type=label_list11) # 创建对抗节点数据对象
print('Loading finished!') # 打印加载完成的提示信息
return data_o, data_s, data_a, train_loader, val_loader, test_loader # 返回原始数据对象、对抗节点数据对象、对抗节点数据对象、训练数据加载器、验证数据加载器、测试数据加载器
def load_drug_mol_data(args): # 定义加载药物分子数据的函数
data = pd.read_csv(args.dataset_filename, delimiter=args.delimiter) # 从文件中读取数据
整个代码中体现了图模型的训练思想和方法,包括数据表示、数据处理、构建训练集、模型训练等环节,都是为了利用图结构的特性来学习数据中的关系和规律。
对抗节点通常在生成对抗性样本时使用。在生成对抗性样本的过程中,我们试图通过对抗节点引导模型学习更鲁棒和健壮的特征表示。这个过程通常用于对抗性训练,其中对抗节点的目标是识别模型的错误预测并改变输入以纠正它们。
在你提供的代码中,通过创建两个张量 torch.ones(572, 1)
和 torch.zeros(572, 1)
,我们实际上为对抗节点定义了标签。这些标签用于区分对抗节点的两种状态:正样本和负样本。正样本对应于模型正确分类的情况,而负样本对应于模型错误分类的情况。这些标签被用于训练对抗节点的模型,以帮助其学习正确识别模型的错误分类情况,并生成对抗性样本以纠正这些错误。
至于正负样本数据的填充,通常是在训练过程中动态地从数据集中提取的。正样本通常是模型预测正确的情况,而负样本则是模型预测错误的情况。这些样本的具体数据内容和特征会根据具体的任务和数据集而定。
在机器学习中,正负样本的生成常用于解决二分类或多分类问题。在LSFC模型中,生成正负样本的目的可能是为了训练模型以区分不同类型的化合物或者化学关系。具体来说,生成正样本是为了训练模型去学习有助于预测目标的特征,而生成负样本则是为了帮助模型学习区分目标与非目标的特征,从而提高模型的泛化能力。
在LSFC模型中,生成正负样本可能用于以下几个方面:
- 训练模型: 正负样本的生成是为了用于模型的训练过程,以帮助模型学习化合物之间的相似性和差异性,从而能够更好地进行预测。
- 评估模型性能: 生成的正负样本可以用于评估模型的性能,例如计算准确率、精确率、召回率等指标,从而了解模型的预测能力。
- 优化模型参数: 正负样本的生成也可以用于优化模型参数,例如通过梯度下降等优化算法,使模型能够更好地拟合数据,提高预测的准确性。
总的来说,生成正负样本是为了帮助模型学习数据的特征,并提高模型的性能和泛化能力。
这段代码的作用是处理数据并生成用于训练和评估的数据对象。让我来解释其中的每一步:
-
for i in range(positive1.shape[0]):
:遍历数据集中的每一行。 -
a = []
:创建一个空列表a
,用于存储边的两个节点索引。 -
a.append(int(positive1[i][0]))
和a.append(int(positive1[i][1]))
:将当前行的两个节点索引转换为整数并添加到列表a
中。 -
edge_index_o.append(a)
:将边的两个节点索引列表添加到原始数据的边索引列表中。 -
label_list.append(int(positive1[i][2]))
:将当前行的标签转换为整数并添加到标签列表中。 -
逆向边处理:将当前行的两个节点索引颠倒,形成逆向边,然后将逆向边的两个节点索引列表和标签添加到相应的列表中。
-
b = []
:创建一个空列表b
,用于存储标签。 -
b.append(int(positive1[i][2]))
和b.append(int(positive1[i][2]))
:将当前行的标签转换为整数并添加到列表b
中,形成逆向标签。 -
label_list11.append(b)
:将标签列表添加到标签列表11中,用于生成对抗节点。 -
数据对象的创建:根据处理好的数据生成原始数据对象、对抗节点数据对象以及对抗节点数据对象。
-
random.shuffle(label_list11)
:随机打乱标签列表11。 -
flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x]
:定义一个用于扁平化列表的函数。 -
label_list11 = flatten(label_list11)
:将标签列表11扁平化。 -
data_a = Data(x=x_o, y=y_a, edge_type=label_list11)
:创建对抗节点数据对象。 -
打印加载完成的提示信息。
-
返回原始数据对象、对抗节点数据对象、对抗节点数据对象、训练数据加载器、验证数据加载器和测试数据加载器。
-
形成逆向标签:在处理数据时,对于每个样本,都会生成正样本和对应的逆向样本,其中逆向样本的标签与正样本相同。这样做的目的是为了增加模型的训练样本,从而提高模型的泛化能力。在训练过程中,模型将尝试区分正样本和逆向样本,从而更好地学习数据的特征。
-
生成对抗节点:对抗节点是指与原始数据中的节点相关联的虚拟节点。在这段代码中,为了增加负样本,将正样本的标签复制一份作为对抗节点的标签,以便模型在训练过程中学习如何区分正样本和对抗节点。这样做可以提高模型对未见过的负样本的泛化能力。
-
扁平化标签列表:在生成对抗节点时,每个样本都有一个对应的标签列表,其中包含了正样本和对抗节点的标签。为了方便处理,将这些标签列表扁平化为一个一维列表。这样做的目的是为了将样本与其对应的标签对应起来,以便在训练过程中能够正确地计算损失函数。
缺少的知识可能包括以下几个方面:
- 神经网络中的对抗训练原理:了解对抗训练是一种训练神经网络的方法,其中模型同时训练以生成和识别对抗样本。
- 数据增强技术:了解如何使用数据增强技术来增加训练样本数量,从而提高模型的泛化能力。
- 数据处理技巧:学习如何有效地处理数据,包括生成负样本、扁平化标签列表等技术。
通过学习这些知识,你将能够更好地理解和使用神经网络模型,并能够在实践中更好地处理和利用数据。
6.数据整理好之后,来计算药物结构的相似性
drug_smile_dict = {} # 创建一个空字典,用于存储药物 SMILES
for id1, id2, smiles1, smiles2, relation in zip(data[args.c_id1], data[args.c_id2], data[args.c_s1], data[args.c_s2], data[args.c_y]):
drug_smile_dict[id1] = smiles1 # 将药物 ID 和 SMILES 存储到字典中
drug_smile_dict[id2] = smiles2
smiles = list(drug_smile_dict.values()) # 获取所有的 SMILES
drug_features_dict = {} # 创建一个空字典,用于存储药物特征
for i in range(len(smiles)): # 遍历每个 SMILES
sim_feature = [] # 创建一个空列表,用于存储相似性特征
for j in range(len(smiles)): # 遍历每个 SMILES
if smiles[i] != smiles[j]: # 如果两个 SMILES 不同
m1 = Chem.MolFromSmiles(smiles[i]) # 根据 SMILES 创建分子对象
m2 = Chem.MolFromSmiles(smiles[j])
fp1 = AllChem.GetMorganFingerprintAsBitVect(m1, 2, 1024) # 获取分子的 Morgan 指纹
fp2 = AllChem.GetMorganFingerprintAsBitVect(m2, 2, 1024)
sim = DataStructs.FingerprintSimilarity(fp1, fp2) # 计算分子指纹的相似性
sim_feature.append(sim) # 将相似性特征添加到列表
这段代码的主要作用是计算药物之间的相似性特征,并将其存储到字典 drug_features_dict
中。具体流程如下:
-
创建一个空字典
drug_smile_dict
,用于存储药物的 SMILES 表达式。 -
遍历数据集中的每一对药物及其关系,将药物的 ID 和对应的 SMILES 表达式存储到
drug_smile_dict
字典中。 -
从
drug_smile_dict
中提取所有的 SMILES 表达式,存储到列表smiles
中。 -
创建一个空字典
drug_features_dict
,用于存储药物的特征。 -
对于每个药物的 SMILES 表达式,计算其与其他药物的相似性特征。这里使用 Morgan 指纹来表示药物的结构特征,并计算分子指纹的相似性。
-
将相似性特征存储到
sim_feature
列表中,并将该列表作为药物的特征,存储到drug_features_dict
字典中。
通过这个过程,可以获取药物之间的相似性特征,这些特征可以用于后续的模型训练和预测中,从而更好地理解药物之间的关系和规律。
7.统计数据信息
def load_data_statistics(all_tuples):
'''
This function is used to calculate the probability in order to generate a negative.
'''
# 打印加载数据统计信息
print('Loading data statistics ...')
# 初始化一个字典,用于存储数据统计信息
statistics = dict()
# 创建各种用于存储数据的 defaultdict 对象
statistics["ALL_TRUE_H_WITH_TR"] = defaultdict(list)
statistics["ALL_TRUE_T_WITH_HR"] = defaultdict(list)
statistics["FREQ_REL"] = defaultdict(int)
statistics["ALL_H_WITH_R"] = defaultdict(dict)
statistics["ALL_T_WITH_R"] = defaultdict(dict)
statistics["ALL_TAIL_PER_HEAD"] = {}
statistics["ALL_HEAD_PER_TAIL"] = {}
# 遍历所有三元组并计算数据统计信息
for h, t, r in tqdm(all_tuples, desc='Getting data statistics'):
statistics["ALL_TRUE_H_WITH_TR"][(t, r)].append(h) # 统计每个尾实体对应的头实体和关系
statistics["ALL_TRUE_T_WITH_HR"][(h, r)].append(t) # 统计每个头实体对应的尾实体和关系
statistics["FREQ_REL"][r] += 1.0 # 统计每种关系的频率
statistics["ALL_H_WITH_R"][r][h] = 1 # 统计每种关系对应的所有头实体
statistics["ALL_T_WITH_R"][r][t] = 1 # 统计每种关系对应的所有尾实体
# 对数据进行处理,将值转换为数组,并计算其他相关统计信息
for t, r in statistics["ALL_TRUE_H_WITH_TR"]:
statistics["ALL_TRUE_H_WITH_TR"][(t, r)] = np.array(list(set(statistics["ALL_TRUE_H_WITH_TR"][(t, r)])))
for h, r in statistics["ALL_TRUE_T_WITH_HR"]:
statistics["ALL_TRUE_T_WITH_HR"][(h, r)] = np.array(list(set(statistics["ALL_TRUE_T_WITH_HR"][(h, r)])))
for r in statistics["FREQ_REL"]:
statistics["ALL_H_WITH_R"][r] = np.array(list(statistics["ALL_H_WITH_R"][r].keys()))
statistics["ALL_T_WITH_R"][r] = np.array(list(statistics["ALL_T_WITH_R"][r].keys()))
statistics["ALL_HEAD_PER_TAIL"][r] = statistics["FREQ_REL"][r] / len(statistics["ALL_T_WITH_R"][r])
statistics["ALL_TAIL_PER_HEAD"][r] = statistics["FREQ_REL"][r] / len(statistics["ALL_H_WITH_R"][r])
# 打印数据统计信息加载完成的提示
print('getting data statistics done!')
return statistics # 返回数据统计信息
这段代码是用于计算数据统计信息的函数 load_data_statistics
。它的主要作用是分析训练数据集中的三元组,以获取关于头实体、尾实体和关系之间关联的各种统计信息。具体来说:
- 遍历所有的训练三元组,统计每个尾实体对应的头实体和关系,以及每个头实体对应的尾实体和关系,以及每种关系的频率。
- 将统计结果存储在一个字典
statistics
中,其中包含了各种统计信息的数据结构。 - 对于每种关系,将统计结果转换为数组,并计算其他相关的统计信息,例如每个关系对应的所有头实体和尾实体的数量,以及平均每个头实体对应的尾实体数量,平均每个尾实体对应的头实体数量等。
- 最后,将所有的数据统计信息打印出来,并返回存储这些信息的字典
statistics
。
这些统计信息可以帮助理解数据集的分布特征、关系之间的相关性等,为后续的模型训练和评估提供基础。
8.生成负样本
def _corrupt_ent(positive_existing_ents, max_num, drug_ids, args):
# 初始化一个空列表,用于存储损坏的实体
corrupted_ents = []
# 当损坏的实体数量未达到指定的最大数量时循环执行以下操作
while len(corrupted_ents) < max_num:
# 从给定的药物ID列表中随机选择两倍于所需数量的候选实体
candidates = args.random_num_gen.choice(drug_ids, (max_num - len(corrupted_ents)) * 2, replace=False)
# 将已有的正样本实体和已经损坏的实体合并成一个数组,作为无效的实体列表
invalid_drug_ids = np.concatenate([positive_existing_ents, corrupted_ents], axis=0)
# 通过对比候选实体和无效的实体列表,筛选出有效的损坏实体
mask = np.isin(candidates, invalid_drug_ids, assume_unique=True, invert=True)
corrupted_ents.extend(candidates[mask]) # 将筛选出的有效损坏实体添加到列表中
# 将损坏的实体列表转换为NumPy数组,并截取指定数量的最大损坏实体
corrupted_ents = np.array(corrupted_ents)[:max_num]
# 返回损坏的实体列表
return corrupted_ents
这段代码定义了一个函数 _corrupt_ent,用于生成指定数量的损坏实体。主要逻辑如下:
首先,创建一个空列表 corrupted_ents 用于存储损坏的实体。
然后,通过一个循环来不断生成损坏的实体,直到达到指定的最大数量为止。
在每次循环中,从给定的药物ID列表 drug_ids 中随机选择两倍于所需数量的候选实体,并将已有的正样本实体和已经损坏的实体合并成一个数组,作为无效的实体列表。
接着,通过比较候选实体和无效的实体列表,筛选出有效的损坏实体,并将其添加到损坏的实体列表中。
最后,将损坏的实体列表转换为NumPy数组,并截取指定数量的最大损坏实体,并返回该列表。
9.生成负样本,便于模型学习区分正负样本,提高模型的泛化能力
def _corrupt_ent(positive_existing_ents, max_num, drug_ids, args):
# 初始化一个空列表,用于存储损坏的实体
corrupted_ents = []
# 当损坏的实体数量未达到指定的最大数量时循环执行以下操作
while len(corrupted_ents) < max_num:
# 从给定的药物ID列表中随机选择两倍于所需数量的候选实体
candidates = args.random_num_gen.choice(drug_ids, (max_num - len(corrupted_ents)) * 2, replace=False)
# 将已有的正样本实体和已经损坏的实体合并成一个数组,作为无效的实体列表
invalid_drug_ids = np.concatenate([positive_existing_ents, corrupted_ents], axis=0)
# 通过对比候选实体和无效的实体列表,筛选出有效的损坏实体
mask = np.isin(candidates, invalid_drug_ids, assume_unique=True, invert=True)
corrupted_ents.extend(candidates[mask]) # 将筛选出的有效损坏实体添加到列表中
# 将损坏的实体列表转换为NumPy数组,并截取指定数量的最大损坏实体
corrupted_ents = np.array(corrupted_ents)[:max_num]
# 返回损坏的实体列表
return corrupted_ents
这段代码定义了一个函数 _corrupt_ent,用于生成指定数量的损坏实体。主要逻辑如下:
首先,创建一个空列表 corrupted_ents 用于存储损坏的实体。
然后,通过一个循环来不断生成损坏的实体,直到达到指定的最大数量为止。
在每次循环中,从给定的药物ID列表 drug_ids 中随机选择两倍于所需数量的候选实体,并将已有的正样本实体和已经损坏的实体合并成一个数组,作为无效的实体列表。
接着,通过比较候选实体和无效的实体列表,筛选出有效的损坏实体,并将其添加到损坏的实体列表中。
最后,将损坏的实体列表转换为NumPy数组,并截取指定数量的最大损坏实体,并返回该列表。
def _normal_batch(h, t, r, neg_size, data_statistics, drug_ids, args):
# 初始化负样本中头实体和尾实体的数量
neg_size_h = 0
neg_size_t = 0
# 计算生成头实体和尾实体的概率,基于头尾关系的统计信息
prob = data_statistics["ALL_TAIL_PER_HEAD"][r] / (data_statistics["ALL_TAIL_PER_HEAD"][r] + data_statistics["ALL_HEAD_PER_TAIL"][r])
# 遍历负样本的数量,依概率分配头实体和尾实体
for i in range(neg_size):
# 根据生成的随机数判断是生成头实体还是尾实体
if args.random_num_gen.random() < prob:
neg_size_h += 1 # 如果生成的随机数小于概率,则生成头实体,对应数量加一
else:
neg_size_t += 1 # 如果生成的随机数大于概率,则生成尾实体,对应数量加一
# 调用_corrupt_ent函数分别生成损坏的头实体和尾实体,并返回
return (_corrupt_ent(data_statistics["ALL_TRUE_H_WITH_TR"][(t, r)], neg_size_h, drug_ids, args),
_corrupt_ent(data_statistics["ALL_TRUE_T_WITH_HR"][(h, r)], neg_size_t, drug_ids, args))
这段代码定义了一个函数 _normal_batch,用于生成正常的负样本对。具体解释如下:
首先,初始化了负样本中头实体和尾实体的数量 neg_size_h 和 neg_size_t。
然后,通过统计信息 data_statistics 计算生成头实体和尾实体的概率 prob。这里使用了统计信息中的尾实体对应于头实体的比例。
接着,根据给定的负样本数量 neg_size,循环遍历该数量,并根据生成的随机数和概率分配头实体和尾实体的数量。
在循环中,如果生成的随机数小于概率 prob,则生成头实体,对应的头实体数量 neg_size_h 加一;否则,生成尾实体,对应的尾实体数量 neg_size_t 加一。
最后,调用 _corrupt_ent 函数分别生成损坏的头实体和尾实体,并将结果作为元组返回。
10、保存数据以及进行数据的几折验证
def _corrupt_ent(positive_existing_ents, max_num, drug_ids, args):
# 初始化一个空列表,用于存储损坏的实体
corrupted_ents = []
# 当损坏的实体数量未达到指定的最大数量时循环执行以下操作
while len(corrupted_ents) < max_num:
# 从给定的药物ID列表中随机选择两倍于所需数量的候选实体
candidates = args.random_num_gen.choice(drug_ids, (max_num - len(corrupted_ents)) * 2, replace=False)
# 将已有的正样本实体和已经损坏的实体合并成一个数组,作为无效的实体列表
invalid_drug_ids = np.concatenate([positive_existing_ents, corrupted_ents], axis=0)
# 通过对比候选实体和无效的实体列表,筛选出有效的损坏实体
mask = np.isin(candidates, invalid_drug_ids, assume_unique=True, invert=True)
corrupted_ents.extend(candidates[mask]) # 将筛选出的有效损坏实体添加到列表中
# 将损坏的实体列表转换为NumPy数组,并截取指定数量的最大损坏实体
corrupted_ents = np.array(corrupted_ents)[:max_num]
# 返回损坏的实体列表
return corrupted_ents
这段代码定义了一个函数 _corrupt_ent,用于生成指定数量的损坏实体。主要逻辑如下:
首先,创建一个空列表 corrupted_ents 用于存储损坏的实体。
然后,通过一个循环来不断生成损坏的实体,直到达到指定的最大数量为止。
在每次循环中,从给定的药物ID列表 drug_ids 中随机选择两倍于所需数量的候选实体,并将已有的正样本实体和已经损坏的实体合并成一个数组,作为无效的实体列表。
接着,通过比较候选实体和无效的实体列表,筛选出有效的损坏实体,并将其添加到损坏的实体列表中。
最后,将损坏的实体列表转换为NumPy数组,并截取指定数量的最大损坏实体,并返回该列表。
def _normal_batch(h, t, r, neg_size, data_statistics, drug_ids, args):
# 初始化负样本中头实体和尾实体的数量
neg_size_h = 0
neg_size_t = 0
# 计算生成头实体和尾实体的概率,基于头尾关系的统计信息
prob = data_statistics["ALL_TAIL_PER_HEAD"][r] / (data_statistics["ALL_TAIL_PER_HEAD"][r] + data_statistics["ALL_HEAD_PER_TAIL"][r])
# 遍历负样本的数量,依概率分配头实体和尾实体
for i in range(neg_size):
# 根据生成的随机数判断是生成头实体还是尾实体
if args.random_num_gen.random() < prob:
neg_size_h += 1 # 如果生成的随机数小于概率,则生成头实体,对应数量加一
else:
neg_size_t += 1 # 如果生成的随机数大于概率,则生成尾实体,对应数量加一
# 调用_corrupt_ent函数分别生成损坏的头实体和尾实体,并返回
return (_corrupt_ent(data_statistics["ALL_TRUE_H_WITH_TR"][(t, r)], neg_size_h, drug_ids, args),
_corrupt_ent(data_statistics["ALL_TRUE_T_WITH_HR"][(h, r)], neg_size_t, drug_ids, args))
这段代码定义了一个函数 _normal_batch,用于生成正常的负样本对。具体解释如下:
首先,初始化了负样本中头实体和尾实体的数量 neg_size_h 和 neg_size_t。
然后,通过统计信息 data_statistics 计算生成头实体和尾实体的概率 prob。这里使用了统计信息中的尾实体对应于头实体的比例。
接着,根据给定的负样本数量 neg_size,循环遍历该数量,并根据生成的随机数和概率分配头实体和尾实体的数量。
在循环中,如果生成的随机数小于概率 prob,则生成头实体,对应的头实体数量 neg_size_h 加一;否则,生成尾实体,对应的尾实体数量 neg_size_t 加一。
最后,调用 _corrupt_ent 函数分别生成损坏的头实体和尾实体,并将结果作为元组返回。