【Datawhale AI 夏令营2024】药效预测(1)

【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%。

概念入门

  1. RNA干扰(RNAi)
    RNA干扰(RNAi)是一种天然存在的基因表达调控机制,通过小干扰RNA(siRNA)等分子来沉默特定基因的表达。这一机制在细胞中起着重要作用,能精确地抑制目标基因的表达,从而减少相应蛋白质的产生。siRNA通过与靶mRNA结合,诱导RNA诱导沉默复合物(RISC)切割mRNA,最终导致mRNA降解和基因沉默。在基因治疗和疾病治疗中,RNAi技术有望通过沉默致病基因来发挥治疗作用。
  2. 词汇表与序列编码
    在处理基因序列数据时,通常需要将核酸序列转换为数值表示形式,以便输入到深度学习模型中。词汇表(vocab)是一种将序列中的每个元素(如核苷酸或核苷酸组合)映射到一个唯一的数值索引的结构。在本文中,使用了一个基于3-gram的词汇表,这意味着每三个连续的核苷酸组合成一个“单词”。这种方法能够捕捉序列中的局部模式,并提高模型的预测能力。
  3. 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)

代码解读

  1. 类初始化:__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 中该元素的索引。

  2. 类方法 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) 

代码解读

  1. 初始化:__init__ 方法初始化数据集的必要参数:

    • df:包含数据的Pandas数据框。
    • columns:包含序列的列名列表。
    • vocab:词汇表对象,用于将token转换为索引。
    • tokenizer:分词器对象,用于将序列分割为token。
    • max_len:序列的最大长度,所有序列将被填充或截断到这个长度。
    • is_train:布尔值,指示数据集是否用于训练(默认为False)。
  2. 获取数据集长度:__len__ 方法返回数据集的样本数量。

  3. 获取样本:getitem 方法获取数据集中指定索引的样本:

    • 获取指定行的数据。

    • 对每个包含序列的列进行分词和编码。

    • 获取目标值(mRNA_remaining_pct),并将其转换为张量。

    • 返回编码后的序列和目标值。

  4. 分词和编码:tokenize_and_encode 方法对输入序列进行分词和编码:

    • 如果序列中包含空格,按空格分词(表示序列已经被修改)。
    • 否则,使用分词器对序列进行分词。
    • 将分词结果转换为索引,未知token0(<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()
 

代码解读

  1. 初始化方法 __init__

    • vocab_size:词汇表大小,用于嵌入层。
    • embed_dim:嵌入维度,嵌入层将词汇映射为 embed_dim 维向量。
    • hidden_dim:隐藏层维度,GRU的隐藏状态维度。
    • n_layers:GRU的层数。
    • dropoutDropout层的丢弃率,用于防止过拟合。

该方法初始化了模型的各层,包括嵌入层GRU层全连接层Dropout层

  1. 前向传播方法 forward

    • 将输入序列传入嵌入层进行词汇嵌入。
    • 对每个嵌入的序列进行GRU处理,提取最后一个隐藏状态并进行Dropout处理。
    • 将所有处理后的序列输出拼接起来,并传入全连接层。
    • 返回经过全连接层后的结果。
  1. Embedding层:
  • self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0): 这里创建了一个嵌入层(Embedding Layer),用于将输入的整数索引序列转换为密集的词嵌入表示。vocab_size 表示词汇表的大小,embed_dim 表示嵌入向量的维度,padding_idx=0 表示使用索引为0的填充标记进行填充。
  1. 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概率。
  1. 全连接层:
  • 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

代码解读

  1. 计算平均绝对误差 (MAE):

    • mae = np.mean(np.abs(y_true - y_pred)):计算实际值和预测值之间的平均绝对误差
  2. 将实际值和预测值转换为二进制分类:

    • y_true_binary = (y_true < threshold).astype(int):如果实际值小于阈值,设为1,否则设为0
    • y_pred_binary = (y_pred < threshold).astype(int):如果预测值小于阈值,设为1,否则设为0
  3. 创建掩码:

    • 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
  4. 计算精确度、召回率和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得分。
  5. 计算综合评分:

    • score = (1 - mae / 100) * 0.5 + (1 - range_mae / 100) * f1 * 0.5:综合MAE、范围内的MAE和F1得分,计算最终评分。
  6. 返回评分:

    • 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 列表中。

  1. outputs.cpu() 是什么?
  • outputs 是在 GPU 上计算得到的张量。.cpu() 方法将这些张量从 GPU 上移动到 CPU 上,这是因为后续的处理可能需要在 CPU 上进行(比如转换为 NumPy 数组)。
  1. 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()方法在训练和评估阶段切换模型状态,影响例如BatchNormalizationDropout等层的行为。
  • 梯度清空与反向传播:在每个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)

参考资料

1datawhale
2

  • 18
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值