Datawhale AI夏令营学习笔记:Task1-从baseline开始的AI世界生活

243 阅读8分钟

这一个系列的笔记主要记录AI夏令营的一些思考与感想

1. 任务背景:

本次的任务还是比较清晰明了的,主要是以下的一个任务(官网):

RNA干扰(RNAi)是生物细胞内天然存在的一种基因表达调控机制,可抵御外来核酸的入侵和控制基因表达。其中小干扰RNA(siRNA)是RNAi机制的主要作用分子。siRNA相关现象及作用机制的发现获得了2006年诺贝尔生理学或医学奖,2018年世界上首款siRNA药物获得美国FDA批准。相比于传统小分子药物,siRNA具有可成药靶点多、药效强、安全性好、成本低的优势,其研发是全球范围内极具发展潜力的前沿医药领域之一。siRNA的化学修饰对siRNA在体内的稳定性、毒性、药代动力学特性至关重要,是siRNA研发中的重要影响因素,本赛题聚焦经过化学修饰的siRNA序列数据预测其对相应的信使RNA(mRNA)沉默效率指标,对指导siRNA药物设计具有重要指向性作用。

简单来说就是我们需要通过程序来预测某类药物基因对某类疾病的治疗效果此处我们会拿到的是一个csv文件的数据集 这次的数据集的各个主要字段如下所示

image.png

其实我们要做的事情非常明了,就是将两题序列逆转之后,按照A-U C-G的关系进行配对 image.png 此处简单介绍siRNA修饰的基本知识,其主要有以下4点目的

image.png

2.搭建模型(速通baseline)

这次我们使用的是在线的魔搭平台进行搭建,这里的环境我选择了

image.png

拖动一下把数据集合传上去先,这里务必注意路径,两个文件要在同一个路径下,用unzip 数据集合文件名.zip解压之后就可以直接运行程序了,此处正在进行训练

image.png

训练结束之后我们就可以在原本路径下找到result路径,其中submission.csv就是我们的最终结果

image.png

上传下看下得分(也就是baseline的得分)

image.png

3.查看一下代码结构

我们主要先查看一下模型的整体结构,重点关注数据加载器和主要模型

查看一下代码结构,首先包,这次主要还是使用pytorch作为深度学习的基础框架,外加上sklearn数据集处理

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  # 模型评估指标

数据加载器

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
    • 返回张量格式的填充序列。

这里就是模型的主体了,采用了一个GRU,也就是门控循环单元(gated recurrent unit,GRU)其是循环神经网络(Recurrent Neural Network, RNN)的一种实现。跟长短期记忆网络(long short-term memory,LSTM)一样,其也是为了解决循环神经网络中计算梯度, 以及矩阵连续乘积可以导致梯度消失或梯度爆炸的问题而提出。但GRU更简单,通常它能够提供跟LSTM同等的效果, 而且计算的速度明显更快。

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__ 方法接受两个参数 ngramstride,用于设置分词器的 n-gram 长度和步幅。
  2. 分词方法:tokenize 方法将输入的序列转换为大写,并根据 ngramstride 对序列进行分词。
  3. n-gram 长度为 1 的处理:如果 ngram 为 1,直接将序列转换为字符列表。
  4. n-gram 长度大于 1 的处理:按步幅进行分词,并确保每个分词的长度等于 ngram
  5. 最后一个分词的处理:如果最后一个分词长度小于 ngram,将其移除。
  6. 返回分词结果:返回处理后的分词结果列表。

4.评分标准

这次比赛的评分机制主要可以用以下三个指标进行表示

image.png 最终分数如下:

image.png

这里我们补充几个深度学习常用的评价标准:

  1. 精确率:在所有被预测为正类的样本中,实际为正类的比例,公式如下:

image.png 2. 召回率:在所有被预测为正类的样本中,被正确预测为正类的比例,公式如下:

image.png

  1. F1:上述两者的调和平均数

image.png

5.思考一下可能的优化措施

大约游览了一下代码,不难看出其实这是一个基于GRU模型的方案,这里我思考了一些可能的优化方案:

  1. 模型替换:用类似的模型来替换GRU,此处我们首选LSTM
  2. 模型融合:大约就是用另外一个模型和GRU一起进行决策与判断

5.1 LSTM替换GRU:

这两个模型的共同点在于他们都是门控循环神经网络,通过在简单循环神经网络的基础上对网络的结构做了调整,加入了门控机制,用来控制神经网络中信息的传递。门控机制可以用来控制记忆单元中的信息有多少需要保留,有多少需要丢弃,新的状态信息又有多少需要保存到记忆单元中等。

简单来说GRU对于LSTM的改进主要在于模型运算时的速度更快(门更少)

image.png

image.png

严格上来说,GRU是基于LSTM的一种优化,GRU的速度会更快,但是在我实际的使用过程中(某个二分类项目),LSTM往往表现会比GRU好一些。具体机理还需要进一步学习,我猜测是门的数量导致两者表现的差异

5.2 模型融合

大约有三种方法

  1. 平均法平均法有一般的评价和加权平均。对于平均法来说一般用于回归预测模型中,在Boosting系列融合模型中般采用的是加权平均融合。
  2. 投票法 有绝对多数投票(得票数超过一半),相对多数投票(得票最多),加权投票。这个还理解,一般用于分类模型。
  3. 学习法:通过另一个学习器来进行结合,将个体学习器称为初级学习器,用于结合的学习器称为次级学习器或元学习器。

之前尝试过,但是效果不是很好,这次需要认真仔细学习之后的课程才能得出较好的效果