【Datawhale AI 夏令营2024】药效预测
一、赛题介绍
本次比赛旨在利用机器学习技术,预测化学修饰后的siRNA序列在RNA干扰(RNAi)机制下对靶基因的沉默效率。RNAi是一种重要的基因表达调控机制,通过干扰特定基因的表达,可以用于疾病治疗。这次比赛的目标是通过构建并优化模型,准确预测siRNA的沉默效率,从而提升药物设计的效率和效果。
二、评分机制
在这次比赛中,模型的评分由多个指标共同决定,以全面评估模型的性能。这些指标包括平均绝对误差(MAE)、区间内的平均绝对误差(Range MAE和和F1得分(F1 Score)。这些指标分别衡量模型在预测上的准确性和稳定性,以及在区间内的表现。最终的评分(Score)是综合这些指标的加权结果。
#score = 50% × (1−MAE/100) + 50% × F1 × (1−Range-MAE/100)
def calculate_metrics(y_true, y_pred, threshold=30):
mae = np.mean(np.abs(y_true - y_pred))
y_true_binary = (y_true < threshold).astype(int)
y_pred_binary = (y_pred < threshold).astype(int)
mask = (y_pred >= 0) & (y_pred <= threshold)
range_mae = mean_absolute_error(y_true[mask], y_pred[mask]) if mask.sum() > 0 else 100
precision = precision_score(y_true_binary, y_pred_binary, average='binary')
recall = recall_score(y_true_binary, y_pred_binary, average='binary')
f1 = 2 * precision * recall / (precision + recall)
score = (1 - mae / 100) * 0.5 + (1 - range_mae / 100) * f1 * 0.5
return score
最终的评分结合了MAE和区间内MAE的反比例值,以及F1得分。MAE和Range MAE越小,1减去它们的比值越大,表明误差小,模型表现好。F1得分高则表示模型分类性能好。最终评分是这几个值的加权平均数,权重各占50%。
概念入门
- RNA干扰(RNAi)
RNA干扰(RNAi)是一种天然存在的基因表达调控机制,通过小干扰RNA(siRNA)等分子来沉默特定基因的表达。这一机制在细胞中起着重要作用,能精确地抑制目标基因的表达,从而减少相应蛋白质的产生。siRNA通过与靶mRNA结合,诱导RNA诱导沉默复合物(RISC)切割mRNA,最终导致mRNA降解和基因沉默。在基因治疗和疾病治疗中,RNAi技术有望通过沉默致病基因来发挥治疗作用。 - 词汇表与序列编码
在处理基因序列数据时,通常需要将核酸序列转换为数值表示形式,以便输入到深度学习模型中。词汇表(vocab)是一种将序列中的每个元素(如核苷酸或核苷酸组合)映射到一个唯一的数值索引的结构。在本文中,使用了一个基于3-gram的词汇表,这意味着每三个连续的核苷酸组合成一个“单词”。这种方法能够捕捉序列中的局部模式,并提高模型的预测能力。 - PyTorch框架
PyTorch是一个开源的深度学习框架,广泛用于研究和生产环境中。它提供了灵活的动态计算图,使得模型的定义和训练更加直观和便捷。在本次比赛的baseline代码中,使用了PyTorch构建和训练RNN模型,包括数据加载、序列编码、模型定义、训练循环和评估等步骤。PyTorch的优势在于其简洁的API和强大的功能,能够快速实现复杂的深度学习模型。
三、代码
1. 依赖库的导入
import os # 文件操作
import torch # 深度学习框架
import random # 随机数生成
import numpy as np # 数值计算
import pandas as pd # 数据处理
import torch.nn as nn # 神经网络模块
import torch.optim as optim # 优化器模块
from tqdm import tqdm # 进度条显示
from rich import print # 美化打印输出
from collections import Counter # 计数器工具
from torch.utils.data import Dataset, DataLoader # 数据集和数据加载器
from sklearn.model_selection import train_test_split # 数据集划分
from sklearn.metrics import precision_score, recall_score, mean_absolute_error # 模型评估指标
#这些库包括了文件操作、深度学习、数据处理、模型评估等必要的工具。
#该函数确保了在使用NumPy、Python内置随机数生成器和PyTorch时,所有的随机数生成都是可控的和可复现的,有助于实验结果的一致性。
def set_random_seed(seed):
# 设置NumPy的随机种子
np.random.seed(seed)
# 设置Python内置的随机数生成器的种子
random.seed(seed)
# 设置PyTorch的随机种子
torch.manual_seed(seed)
# 设置CUDA的随机种子
torch.cuda.manual_seed(seed)
# 设置所有CUDA设备的随机种子
torch.cuda.manual_seed_all(seed)
# 确保每次卷积算法选择都是确定的
torch.backends.cudnn.deterministic = True
# 关闭CuDNN自动优化功能,确保结果可复现
torch.backends.cudnn.benchmark = False
np.random.seed()函数用于生成指定随机数。
seed()被设置了之后,np,random.random()可以按顺序产生一组固定的数组,如果使用相同的seed()值,则每次生成的随机数都相同,如果不设置这个值,那么每次生成的随机数不同。但是,只在调用的时候seed()一下并不能使生成的随机数相同,需要每次调用都seed()一下,表示种子相同,从而生成的随机数相同。
2. 基因组分词器类
该类用于将基因组序列分割成固定长度的n-gram。
class GenomicTokenizer:
def __init__(self, ngram=5, stride=2):
# 初始化分词器,设置n-gram长度和步幅
self.ngram = ngram
self.stride = stride
def tokenize(self, t):
# 将输入序列转换为大写
t = t.upper()
if self.ngram == 1:
# 如果n-gram长度为1,直接将序列转换为字符列表
toks = list(t)
else:
# 否则,按照步幅对序列进行n-gram分词
toks = [t[i:i+self.ngram] for i in range(0, len(t), self.stride) if len(t[i:i+self.ngram]) == self.ngram]
# 如果最后一个分词长度小于n-gram,移除最后一个分词
if len(toks[-1]) < self.ngram:
toks = toks[:-1]
# 返回分词结果
return toks
代码解读
类初始化:init 方法接受两个参数 ngram 和 stride,用于设置分词器的 n-gram 长度和步幅。
分词方法:tokenize 方法将输入的序列转换为大写,并根据 ngram 和 stride 对序列进行分词。
n-gram 长度为 1 的处理:如果 ngram 为 1,直接将序列转换为字符列表。
n-gram 长度大于 1 的处理:按步幅进行分词,并确保每个分词的长度等于 ngram。
最后一个分词的处理:如果最后一个分词长度小于 ngram,将其移除。
返回分词结果:返回处理后的分词结果列表。
3. 基因组词汇类
该类用于创建一个词汇表,用于将基因组片段映射为索引。
class GenomicVocab:
def __init__(self, itos):
# 初始化词汇表,itos是一个词汇表列表
self.itos = itos
# 创建从词汇到索引的映射
self.stoi = {v: k for k, v in enumerate(self.itos)}
@classmethod
def create(cls, tokens, max_vocab, min_freq):
# 创建词汇表类方法
# 统计每个token出现的频率
freq = Counter(tokens)
# 选择出现频率大于等于min_freq的token,并且最多保留max_vocab个token
itos = ['<pad>'] + [o for o, c in freq.most_common(max_vocab - 1) if c >= min_freq]
# 返回包含词汇表的类实例
return cls(itos)
代码解读
-
类初始化:
__init__
方法接受一个参数itos
,它是一个词汇表列表。itos
:从索引到词汇的映射。stoi
:从词汇到索引的映射,由itos
列表生成。
enumerate(self.itos): enumerate
函数用于将一个可迭代的对象(如列表)组合为一个索引序列,同时列出数据和数据下标。在这里,enumerate(self.itos) 返回的是一个迭代器,产生 (index, value) 对,其中 index是 self.itos`中每个元素的索引,value是元素本身。enumerate(self.itos)
: 这是字典推导式的语法。它遍历了 enumerate(self.itos) 返回的迭代器,对于每一个 (index, value) 对,创建一个字典条目,其中键 v 是 self.itos 中的值(即元素),而值 k 是 self.itos 中该元素的索引。 -
类方法
create
:创建词汇表的类方法,用于生成GenomicVocab
类的实例。- 参数:
tokens
:所有token的列表。max_vocab
:词汇表的最大容量。min_freq
:词汇在被包含到词汇表中的最低频率。
- 步骤:
- 统计
tokens
中每个token出现的频率。 - 按照频率从高到低排序,并选择出现频率大于等于
min_freq
的token,最多保留max_vocab
个。 - 在词汇表中添加一个特殊的
<pad>
token,用于填充序列。 - 返回包含生成的
itos
列表的GenomicVocab
实例。
- 统计
- 参数:
4. siRNA数据集类
该类用于加载siRNA数据,并将序列数据转换为模型可以处理的格式。
#定义一个函数,计算给定列表的总和
def calc_sum(numbers):
total = 0
for num in numbers:
total += num
return total
#调用函数并打印结果
my_list = [1, 2, 3, 4, 5]
result = calc_sum(my_list)
print("列表的总和为:", result)
class SiRNADataset(Dataset):
def __init__(self, df, columns, vocab, tokenizer, max_len, is_test=False):
# 初始化数据集
self.df = df # 数据框
self.columns = columns # 包含序列的列名
self.vocab = vocab # 词汇表
self.tokenizer = tokenizer # 分词器
self.max_len = max_len # 最大序列长度
self.is_test = is_test # 指示是否是测速集
def __len__(self):
# 返回数据集的长度
return len(self.df)
def __getitem__(self, idx):
# 获取数据集中的第idx个样本
row = self.df.iloc[idx] # 获取第idx行数据
# 对每一列进行分词和编码
seqs = [self.tokenize_and_encode(row[col]) for col in self.columns]
if self.is_test:
# 仅返回编码后的序列(非测试集模式)
return seqs
else:
# 获取目标值并转换为张量(仅在非测试集模式下)
target = torch.tensor(row['mRNA_remaining_pct'], dtype=torch.float)
# 返回编码后的序列和目标值
return seqs, target
def tokenize_and_encode(self, seq):
if ' ' in seq: # 修改过的序列
tokens = seq.split() # 按空格分词
else: # 常规序列
tokens = self.tokenizer.tokenize(seq) # 使用分词器分词
# 将token转换为索引,未知token使用0(<pad>)
encoded = [self.vocab.stoi.get(token, 0) for token in tokens]
# 将序列填充到最大长度
padded = encoded + [0] * (self.max_len - len(encoded))
# 返回张量格式的序列
return torch.tensor(padded[:self.max_len], dtype=torch.long)
代码解读
-
初始化:
__init__
方法初始化数据集的必要参数:df
:包含数据的Pandas数据框。columns
:包含序列的列名列表。vocab
:词汇表对象,用于将token转换为索引。tokenizer
:分词器对象,用于将序列分割为token。max_len
:序列的最大长度,所有序列将被填充或截断到这个长度。is_train
:布尔值,指示数据集是否用于训练(默认为False)。
-
获取数据集长度:
__len__
方法返回数据集的样本数量。 -
获取样本:getitem 方法获取数据集中指定索引的样本:
-
获取指定行的数据。
-
对每个包含序列的列进行分词和编码。
-
获取目标值(mRNA_remaining_pct),并将其转换为张量。
-
返回编码后的序列和目标值。
-
-
分词和编码:
tokenize_and_encode
方法对输入序列进行分词和编码:- 如果序列中包含空格,按空格分词(表示序列已经被修改)。
- 否则,使用分词器对序列进行分词。
- 将分词结果转换为索引,未知
token
用0(<pad>)
表示。 - 对序列进行填充,使其长度等于最大长度
max_len
。 - 返回张量格式的填充序列。
在上面的代码中,
0(<pad>)
表示序列中的填充标记。在自然语言处理(NLP)中,为了使输入序列具有相同的长度,经常需要将较短的序列填充到与最长序列相同的长度。填充通常使用一个特殊的标记来表示,这个标记通常被称为“padding token”,它的索引通常被设定为0。
具体来说,在tokenize_and_encode
方法中:
- 首先,将输入序列(字符串)分词(tokenize)或者根据空格进行分割,得到一组 token。
- 然后,将每个 token 转换为其在词汇表(vocab)中的索引。如果 token 不在词汇表中,则使用索引为0的填充标记。
- 最后,如果 token 的数量少于
max_len
,则用填充标记(索引为0)填充序列,直到达到max_len
长度。- 返回的是一个长度为
max_len
的整数张量,表示输入序列的编码版本。
这种填充方法有助于保持每个样本的长度一致,以便于批处理(batching)操作,因为在训练神经网络时,通常要求输入数据具有相同的形状(尺寸)。
5. siRNA Model
这是一个基于GRU的神经网络模型,用于处理siRNA序列。
class SiRNAModel(nn.Module):
def __init__(self, vocab_size, embed_dim=200, hidden_dim=256, n_layers=3, dropout=0.5):
super(SiRNAModel, self).__init__()
# 初始化嵌入层
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
# 初始化GRU层
self.gru = nn.GRU(embed_dim, hidden_dim, n_layers, bidirectional=True, batch_first=True, dropout=dropout)
# 初始化全连接层
self.fc = nn.Linear(hidden_dim * 4, 1) # hidden_dim * 4 因为GRU是双向的,有n_layers层
# 初始化Dropout层
self.dropout = nn.Dropout(dropout)
def forward(self, x):
# 将输入序列传入嵌入层
embedded = [self.embedding(seq) for seq in x]
outputs = []
# 对每个嵌入的序列进行处理
for embed in embedded:
x, _ = self.gru(embed) # 传入GRU层
x = self.dropout(x[:, -1, :]) # 取最后一个隐藏状态,并进行dropout处理
outputs.append(x)
# 将所有序列的输出拼接起来
x = torch.cat(outputs, dim=1)
# 传入全连接层
x = self.fc(x)
# 返回结果
return x.squeeze()
代码解读
-
初始化方法
__init__
:vocab_size
:词汇表大小,用于嵌入层。embed_dim
:嵌入维度,嵌入层将词汇映射为embed_dim
维向量。hidden_dim
:隐藏层维度,GRU的隐藏状态维度。n_layers
:GRU的层数。dropout
:Dropout
层的丢弃率,用于防止过拟合。
该方法初始化了模型的各层,包括嵌入层
、GRU层
、全连接层
和Dropout层
。
-
前向传播方法
forward
:- 将输入序列传入嵌入层进行词汇嵌入。
- 对每个嵌入的序列进行
GRU
处理,提取最后一个隐藏状态并进行Dropout
处理。 - 将所有处理后的序列输出拼接起来,并传入全连接层。
- 返回经过全连接层后的结果。
- Embedding层:
- self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0): 这里创建了一个嵌入层(Embedding Layer),用于将输入的整数索引序列转换为密集的词嵌入表示。vocab_size 表示词汇表的大小,embed_dim 表示嵌入向量的维度,padding_idx=0 表示使用索引为0的填充标记进行填充。
- GRU层:
- self.gru = nn.GRU(embed_dim, hidden_dim, n_layers, bidirectional=True, batch_first=True, dropout=dropout): 这里创建了一个多层双向GRU(Gated Recurrent Unit)层。embed_dim 是输入嵌入的维度,hidden_dim 是GRU的隐藏状态的维度,n_layers 是GRU的层数,bidirectional=True 表示GRU是双向的,batch_first=True 表示输入的张量的第一个维度是batch size,dropout=dropout 表示应用的dropout概率。
- 全连接层:
- self.fc = nn.Linear(hidden_dim * 4, 1): 这里创建了一个全连接层,用于将GRU的输出映射到最终的输出空间。hidden_dim * 4 是因为GRU是双向的,有 n_layers 层,所以每层有两个方向的隐藏状态,共计 hidden_dim * 2 * n_layers 维度。这里乘以4是因为每个方向有两个输出,所以是 hidden_dim * 4
6. 评估指标计算函数
该函数用于计算模型的各项评估指标,包括精确度、召回率、F1值和评分。
def calculate_metrics(y_true, y_pred, threshold=30):
# 计算平均绝对误差
mae = np.mean(np.abs(y_true - y_pred))
# 将实际值和预测值转换为二进制分类(低于阈值为1,高于或等于阈值为0)
y_true_binary = (y_true < threshold).astype(int)
y_pred_binary = (y_pred < threshold).astype(int)
# 创建掩码,用于筛选预测值在0和阈值之间的样本
mask = (y_pred >= 0) & (y_pred <= threshold)
range_mae = mean_absolute_error(y_true[mask], y_pred[mask]) if mask.sum() > 0 else 100
# 计算精确度、召回率和F1得分
precision = precision_score(y_true_binary, y_pred_binary, average='binary')
recall = recall_score(y_true_binary, y_pred_binary, average='binary')
f1 = 2 * precision * recall / (precision + recall)
# 计算综合评分
score = (1 - mae / 100) * 0.5 + (1 - range_mae / 100) * f1 * 0.5
return score
代码解读
-
计算平均绝对误差 (MAE):
mae = np.mean(np.abs(y_true - y_pred))
:计算实际值和预测值之间的平均绝对误差
。
-
将实际值和预测值转换为二进制分类:
y_true_binary = (y_true < threshold).astype(int)
:如果实际值
小于阈值
,设为1
,否则设为0
。y_pred_binary = (y_pred < threshold).astype(int)
:如果预测值
小于阈值
,设为1
,否则设为0
。
-
创建掩码:
mask = (y_pred >= 0) & (y_pred <= threshold)
:筛选预测值
在0
和阈值
之间的样本。range_mae = mean_absolute_error(y_true[mask], y_pred[mask]) if mask.sum() > 0 else 100
:计算这些样本的平均绝对误差
,如果没有
符合条件的样本,设为100
。
-
计算精确度、召回率和F1得分:
precision = precision_score(y_true_binary, y_pred_binary, average='binary')
:计算精确度。recall = recall_score(y_true_binary, y_pred_binary, average='binary')
:计算召回率。f1 = 2 * precision * recall / (precision + recall)
:计算F1得分。
-
计算综合评分:
score = (1 - mae / 100) * 0.5 + (1 - range_mae / 100) * f1 * 0.5
:综合MAE、范围内的MAE和F1得分,计算最终评分。
-
返回评分:
return score
:返回综合评分。
在Python中,
astype()
是一个NumPy数组的方法,用于执行类型转换。具体来说,astype()
方法允许你将数组的元素从一种数据类型转换为另一种数据类型。
在上下文中,假设y_true
是一个NumPy数组或者类似的数据结构(例如Pandas的Series),而threshold
是一个阈值。
astype()
方法:array.astype(dtype)
其中,
array
是要转换类型的数组,dtype
是目标数据类型。
范围内样本的平均绝对误差:
mask = (y_pred >= 0) & (y_pred <= threshold) range_mae = mean_absolute_error(y_true[mask], y_pred[mask]) if mask.sum() > 0 else 100
- 这段代码首先创建了一个掩码
mask
,用于筛选出预测值在0到threshold
之间的样本。mean_absolute_error
函数计算了在这些样本上的平均绝对误差range_mae
。如果没有符合条件的样本(即mask.sum() == 0
),则将range_mae
设置为100。
7. 模型评估函数
该函数用于在测试集上评估模型性能。
def evaluate_model(model, test_loader, device='cuda'):
# 设置模型为评估模式
model.eval()
predictions = []
targets = []
# 禁用梯度计算
with torch.no_grad():
# 遍历测试数据加载器中的每个批次
for inputs, target in test_loader:
# 将输入数据移动到指定设备上
inputs = [x.to(device) for x in inputs]
# 获取模型的输出
outputs = model(inputs)
# 将预测结果从GPU移到CPU,并转换为numpy数组,添加到predictions列表中
predictions.extend(outputs.cpu().numpy())
# 将目标值转换为numpy数组,添加到targets列表中
targets.extend(target.numpy())
# 将预测结果和目标值转换为numpy数组
y_pred = np.array(predictions)
y_test = np.array(targets)
# 计算评估指标
score = calculate_metrics(y_test, y_pred)
# 打印测试得分
print(f"Test Score: {score:.4f}")
predictions.extend(outputs.cpu().numpy())
这行代码是将模型在测试数据上的输出结果(即预测值)添加到predictions
列表中。
outputs.cpu()
是什么?
outputs
是在 GPU 上计算得到的张量。.cpu()
方法将这些张量从 GPU 上移动到 CPU 上,这是因为后续的处理可能需要在 CPU 上进行(比如转换为 NumPy 数组)。
predictions.extend()
是什么?
predictions
是一个 Python 列表,用于存储模型的预测结果。.extend()
方法用来将一个可迭代对象中的元素逐一添加到列表predictions
的末尾。在这里,outputs.cpu().numpy()
返回一个 NumPy 数组,.extend()
方法将这个数组的内容逐一添加到predictions
列表中。所以,
predictions.extend(outputs.cpu().numpy())
将模型在当前批次数据上的预测结果(转换为 NumPy 数组后)添加到predictions
列表中。这样,在整个测试数据集上完成所有预测后,predictions
列表将包含所有样本的模型预测结果。
8. 模型训练函数
函数用于训练模型,并在每个epoch后评估模型的性能,保存最佳模型。
def train_model(model, train_loader, val_loader, criterion, optimizer, num_epochs=50, device='cuda', output_dir: str=""):
# 将模型移动到指定设备
model.to(device)
best_score = -float('inf') # 初始化最佳得分
best_model = None # 初始化最佳模型
for epoch in range(num_epochs):
model.train() # 设置模型为训练模式
train_loss = 0 # 初始化训练损失
for inputs, targets in tqdm(train_loader, desc=f'Epoch {epoch+1}/{num_epochs}'):
inputs = [x.to(device) for x in inputs] # 将输入移动到设备
targets = targets.to(device) # 将目标值移动到设备
optimizer.zero_grad() # 清空梯度
outputs = model(inputs) # 前向传播
loss = criterion(outputs, targets) # 计算损失
loss.backward() # 反向传播
optimizer.step() # 更新参数
train_loss += loss.item() # 累加训练损失
model.eval() # 设置模型为评估模式
val_loss = 0 # 初始化验证损失
val_preds = []
val_targets = []
with torch.no_grad():
for inputs, targets in val_loader:
inputs = [x.to(device) for x in inputs] # 将输入移动到设备
targets = targets.to(device) # 将目标值移动到设备
outputs = model(inputs) # 前向传播
loss = criterion(outputs, targets) # 计算损失
val_loss += loss.item() # 累加验证损失
val_preds.extend(outputs.cpu().numpy()) # 收集预测值
val_targets.extend(targets.cpu().numpy()) # 收集目标值
train_loss /= len(train_loader) # 计算平均训练损失
val_loss /= len(val_loader) # 计算平均验证损失
val_preds = np.array(val_preds)
val_targets = np.array(val_targets)
score = calculate_metrics(val_targets, val_preds) # 计算验证集上的得分
print(f'Epoch {epoch+1}/{num_epochs}')
print(f'Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}')
print(f'Learning Rate: {optimizer.param_groups[0]["lr"]:.6f}')
print(f'Validation Score: {score:.4f}')
if score > best_score:
best_score = score # 更新最佳得分
best_model = model.state_dict().copy() # 更新最佳模型
torch.save(model.state_dict(), os.path.join(output_dir, "best.pt".format(epoch))) # 保存最佳模型
print(f'New best model found with score: {best_score:.4f}')
return best_model # 返回最佳模型
- 模型训练和评估模式:通过
model.train()
和model.eval()
方法在训练和评估阶段切换模型状态,影响例如BatchNormalization
和Dropout
等层的行为。- 梯度清空与反向传播:在每个batch训练后,使用
optimizer.zero_grad()
清空梯度,然后调用loss.backward()
执行反向传播,并使用optimizer.step()
更新模型参数。- 保存最佳模型:通过比较验证集得分,如果发现新的最佳模型,则保存该模型的状态字典,并打印相关信息。
9. 训练主程序
#设置参数
bs = 64 # 批次大小
epochs = 50 # 训练的迭代次数
lr = 0.001 # 学习率
seed = 42 # 随机种子
output_dir = "output/models" # 模型保存路径
#选择设备
device = 'cuda' if torch.cuda.is_available() else 'cpu'
#设置随机种子以确保结果可重复
set_random_seed(seed)
#创建输出目录
if not os.path.exists(output_dir):
os.makedirs(output_dir)
#加载数据
train_data = pd.read_csv('train_data.csv')
#指定需要处理的列
columns = ['siRNA_antisense_seq', 'modified_siRNA_antisense_seq_list']
#删除包含空值的行
train_data.dropna(subset=columns + ['mRNA_remaining_pct'], inplace=True)
#将数据分为训练集和验证集
train_data, val_data = train_test_split(train_data, test_size=0.1, random_state=42)
#创建分词器
tokenizer = GenomicTokenizer(ngram=3, stride=3)
#创建词汇表
all_tokens = []
for col in columns:
for seq in train_data[col]:
if ' ' in seq: # 修改过的序列
all_tokens.extend(seq.split())
else:
all_tokens.extend(tokenizer.tokenize(seq))
vocab = GenomicVocab.create(all_tokens, max_vocab=10000, min_freq=1)
#找到最大序列长度
max_len = max(max(len(seq.split()) if ' ' in seq else len(tokenizer.tokenize(seq))
for seq in train_data[col]) for col in columns)
#创建数据集
train_dataset = SiRNADataset(train_data, columns, vocab, tokenizer, max_len)
val_dataset = SiRNADataset(val_data, columns, vocab, tokenizer, max_len)
#创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=bs, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=bs)
#初始化模型
model = SiRNAModel(len(vocab.itos))
criterion = nn.MSELoss()
#初始化优化器
optimizer = optim.Adam(model.parameters(), lr=lr)
#训练模型
best_model = train_model(model, train_loader, val_loader, criterion, optimizer, epochs, device, output_dir=output_dir)
10. 测试程序
#设置输出目录
output_dir = "result"
if not os.path.exists(output_dir):
os.makedirs(output_dir)
#加载测试数据
test_data = pd.read_csv('sample_submission.csv')
columns = ['siRNA_antisense_seq', 'modified_siRNA_antisense_seq_list']
test_data.dropna(subset=columns, inplace=True)
#创建分词器
tokenizer = GenomicTokenizer(ngram=3, stride=3)
#创建词汇表
all_tokens = []
for col in columns:
for seq in test_data[col]:
if ' ' in seq: # 修改过的序列
all_tokens.extend(seq.split())
else:
all_tokens.extend(tokenizer.tokenize(seq))
vocab = GenomicVocab.create(all_tokens, max_vocab=10000, min_freq=1)
#找到最大序列长度
max_len = max(max(len(seq.split()) if ' ' in seq else len(tokenizer.tokenize(seq))
for seq in test_data[col]) for col in columns)
#创建测试数据集
test_dataset = SiRNADataset(test_data, columns, vocab, tokenizer, max_len, is_test=True)
#创建数据加载器
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
#初始化模型
model = SiRNAModel(len(vocab.itos))
model.load_state_dict(best_model) # 加载最佳模型权重
model.to(device=device)
model.eval() # 切换到评估模式,这对于某些模块如Dropout和BatchNorm是必需的
#进行预测
preds = []
with torch.no_grad():
for inputs in tqdm(test_loader):
# import pdb;pdb.set_trace()
inputs = [x.to(device) for x in inputs]
outputs = model(inputs)
preds.extend(outputs.cpu().numpy())
#将预测结果添加到测试数据中
test_data["mRNA_remaining_pct"] = preds
df = pd.DataFrame(test_data)
#保存预测结果
output_csv = os.path.join(output_dir, "submission.csv")
print(f"submission.csv 保存在 {output_csv}")
df.to_csv(output_csv, index=False)