Datawhale AI春训营 世界科学智能大赛--合成生物赛道:蛋白质固有无序区域预测 小白经验总结

发布于:2025-04-20 ⋅ 阅读:(46) ⋅ 点赞:(0)

一、报名大赛

二、跑通baseline

在魔塔社区创建实例,根据教程完成速通第一个分数~

Datawhale-学用 AI,从此开始

三、优化实例(这里是我的学习优化过程)

1.先将官方给的的模型训练实例了解一遍(敲一敲代码)

训练模型nn_baseline

导入必要的库

import argparse  # 解析命令行参数
import math      # 数学运算(如log)
import pickle    # 加载.pkl数据文件

import torch     # PyTorch深度学习框架
import torch.nn as nn  # 神经网络模块
import torch.nn.functional as F  # 激活函数等

from tqdm import tqdm  # 进度条显示
from omegaconf import OmegaConf  # 配置文件管理
from sklearn.metrics import f1_score  # 评估指标
from torch.utils.data import Dataset, DataLoader  # 数据加载
from torch.nn import TransformerEncoderLayer, TransformerEncoder  # Transformer模块

定义常量氨基酸类型序列

restypes = [  # 20种标准氨基酸
    'A', 'R', 'N', 'D', 'C', 'Q', 'E', 'G', 'H', 'I',
    'L', 'K', 'M', 'F', 'P', 'S', 'T', 'W', 'Y', 'V'
]
unsure_restype = 'X'  # 不确定的氨基酸类型
unknown_restype = 'U'  # 未知氨基酸类型

处理数据集

#数据集处理
def make_dataset(data_config,train_rate=0.7,valid_rate=0.2):
    data_path = data_config.data_path  #从配置获取数据路径
    with open(data_path,'rb') as f:  #加载.pkl文件
        data = pickle.load(f)  #反序列化数据

    total_number = len(data)  #数据总量
    train_sep = int(total_number * train_rate)  #训练集结束索引
    valid_sep = int(total_number * (train_rate + valid_rate))  #训练集结束索引

    train_data_dicts = data[:train_sep]  #训练集
    valid_data_dicts = data[train_sep:valid_sep]  #训练集
    test_data_dicts = data[valid_sep:]  #测试集

    train_dataset = DisProtDataset(train_data_dicts)
    valid_dataset = DisProtDataset(valid_data_dicts)
    test_dataset = DisProtDataset(test_data_dicts)

    return train_dataset,valid_dataset,test_dataset

#处理数据格式
class DisProtDataset(Dataset):
    def __init__(self,dict_data):
        sequences = [d['sequence'] for d in dict_data]  #提取所有序列
        labels = [d['label'] for d in dict_data]  #提取所有标签
        assert len(sequences) == len(labels)

        self.sequences = sequences
        self.labels = labels
        self.residue_mapping = {'X':20}  #未知氨基酸'X'映射为索引20
        self.residue_mapping.update(dict(zip(restypes,range(len(restypes)))))

    def __len__(self):
        return len(self.sequences)  #返回数据集样本数

    def __getitem__(self, idx):
        #将序列转为one-hot编码
        sequence = torch.zeros(len(self.sequences[idx]),len(self.residue_mapping))
        for i,c in enumerate(self.sequences[idx]):
            if c not in restypes:  #非标准氨基酸替换为‘X’
                c = 'X'
            sequence[i][self.residue_mapping[c]] = 1  #one-hot赋值

        #标签转为Tensor
        label = torch.tensor([int(c) for c in self.labels[idx]],dtype=torch.long)
        return sequence,label

添加模型组件

class DisProtModel(nn.Module):  #主模型
    def __init__(self,model_config):
        self.d_model = model_config.d_model  # 特征维度
        self.n_head = model_config.n_head  # 注意力头数
        self.n_layer = model_config.n_layer  # Transformer层数

        #输入层
        self.input_layer = nn.Linear(model_config.i_dim,self.d_model)  #将输入维度 i_dim投影到 d_model
        self.position_embed = PositionalEncoding(self.d_model,max_len=20000)  #添加位置信息,支持最长20000的序列
        self.input_norm = nn.LayerNorm(self.d_model)  #对特征维度归一化,稳定训练
        self.dropout_in = nn.Dropout(p=0.1)  #防止过拟合,丢弃率10%

        #Transformer编码器
        encoder_layer = TransformerEncoderLayer(
            d_model=self.d_model,
            nhead=self.n_head,
            activation='gelu',  #GELU激活
            batch_first=True  #输入格式为 (batch, seq, dim)
        )
        self.transformer = TransformerEncoder(encoder_layer,num_layers=self.n_layer)
        
        #输出层
        self.output_layer = nn.Sequential(
            nn.Linear(self.d_model,self.d_model),
            nn.GELU(),
            nn.Dropout(p=0.1),
            nn.Linear(self.d_model,model_config.o_dim)  #输出维度,通常为2
        )
        
        #前向传播
        def forward(self,x):
            x = self.input_layer(x)  #线性变换
            x = self.position_embed(x)  #位置编码
            x = self.input_norm(x)  #层归一化
            x = self.dropout_in(x)  #Dropout
            x = self.transformer(x)  #Transformer编码
            x = self.output_layer(x)  #分类头
            return x

评估指标

def metric_fn(pred,gt):
    pred = pred.detach().cpu()  #脱离计算图,转到CPU
    gt = gt.detach().cpu()
    pred_labels = torch.argmax(pred, dim=-1).view(-1)  # 取预测类别
    gt_labels = gt.view(-1)  # 展平真实标签
    score = f1_score(y_true=gt_labels, y_pred=pred_labels, average='micro')  # 计算F1
    return score

主要训练逻辑

if __name__ == '__main__':
    device = 'cuda' if torch.cuda.is_available() else 'cpu'  # 自动选择设备

    # 解析命令行参数(如--config_path)
    parser = argparse.ArgumentParser('IDRs prediction')
    parser.add_argument('--config_path', default='./config.yaml')
    args = parser.parse_args()
    config = OmegaConf.load(args.config_path)  # 加载配置文件

    #数据准备
    train_dataset, valid_dataset, test_dataset = make_dataset(config.data)
    train_dataloader = DataLoader(dataset=train_dataset, **config.train.dataloader)
    valid_dataloader = DataLoader(dataset=valid_dataset, batch_size=1, shuffle=False)

    #模型初始化
    model = DisProtModel(config.model)
    model = model.to(device)

    #优化器与损失函数
    optimizer = torch.optim.AdamW(model.parameters(),  #比Adam更优的权重衰减处理
                                lr=config.train.optimizer.lr,
                                weight_decay=config.train.optimizer.weight_decay)
    loss_fn = nn.CrossEntropyLoss()

    #初始验证
    model.eval()
    metric = 0.
    with torch.no_grad():
        for sequence, label in valid_dataloader:
            sequence = sequence.to(device)
            label = label.to(device)
            pred = model(sequence)
            metric += metric_fn(pred, label)
    print("init f1_score:", metric / len(valid_dataloader))

    #训练循环
    for epoch in range(config.train.epochs):
        progress_bar = tqdm(train_dataloader, desc=f"epoch:{epoch:03d}")
        model.train()
        total_loss = 0.
        for sequence, label in progress_bar:
            sequence = sequence.to(device)
            label = label.to(device)

            pred = model(sequence)
            loss = loss_fn(pred.permute(0, 2, 1), label)  # 调整维度
            progress_bar.set_postfix(loss=loss.item())
            total_loss += loss.item()

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
        avg_loss = total_loss / len(train_dataloader)

        #验证阶段
        model.eval()
        metric = 0.
        with torch.no_grad():
            for sequence, label in valid_dataloader:
                sequence = sequence.to(device)
                label = label.to(device)
                pred = model(sequence)
                metric += metric_fn(pred, label)
        print(f"avg_training_loss: {avg_loss}, f1_score: {metric / len(valid_dataloader)}")

注意:在这里想要跑通,一定要把数据集WSAA_data_public.pkl文件和config.yaml文件放在合适的目录下。

在本地跑通的效果

2.了解可以优化的方向

(1)词向量

词向量(Word Embedding)是自然语言处理(NLP)中的一种重要技术,用于将词汇映射到低维连续向量空间,使得语义和语法相似的词在向量空间中距离相近。

方法:词向量+机器学习

①训练词向量

使用gensim 库的 Word2Vec 模型对氨基酸序列进行词向量训练。

将每个蛋白质序列转换为由空格分隔的字符串( ' '.join(x["sequence"]) ),形成句子列表。

vector_size=100 :词向量的维度为 100。

min_count=1 :至少出现一次的单词才会被考虑。

训练完成后,model_w2v 包含了每个氨基酸的词向量表示。

datas = pickle.load(open("WSAA_data_public.pkl", "rb"))

model_w2v = gensim.models.Word2Vec(sentences=[' '.join(x["sequence"]) for x in datas], vector_size=100, min_count=1)

②编码词向量

对于序列中的每个氨基酸,提取其上下文窗口内的词向量,并计算平均值作为特征。

sequence[max(0, idx-2): min(len(sequence), idx+2)]:获取当前氨基酸及其前后两个氨基酸的窗口。

model_w2v.wv[...]:获取窗口内氨基酸的词向量。

.mean(0) :对窗口内的词向量取平均值,得到一个固定维度的特征向量

将特征向量添加到data_x,将对应的标签添加到data_y

data_x = []
data_y = []
for data in datas:
    sequence = list(data["sequence"])
    for idx, (_, y) in enumerate(zip(sequence, data['label'])):
        data_x.append(
            model_w2v.wv[sequence[max(0, idx-2): min(len(sequence), idx+2)]].mean(0)
        )
        data_y.append(y)

③训练贝叶斯模型

使用GaussianNB (高斯朴素贝叶斯)分类器对提取的特征进行训练。

model.fit(data_x, data_y) :将特征和标签传入模型进行训练。

model = GaussianNB()
model.fit(data_x, data_y)

dump((model, model_w2v), "model.pkl")

(2)BERT模型

BERT(Bidirectional Encoder Representations from Transformers)是由 Google 在 2018 年提出的一种预训练语言模型。BERT 的核心特点是其双向性,即它能够同时考虑上下文的左右信息,从而生成更准确的语言表示。这种双向性使得 BERT 在理解词语的多义性和上下文关系方面表现出色。

方法:BERT实体识别

①数据预处理

序列编码:将蛋白质的氨基酸序列转换为BERT可以处理的格式。每个氨基酸可以用单字母表示(如A、C、D等)。由于BERT是基于字符的模型,可以直接将氨基酸序列作为输入。

标签处理:将每个氨基酸位置的无序区域标签(0或1)作为目标标签。例如, 1 表示该位置属于无序区域,0 表示不属于无序区域。

分词:虽然蛋白质序列是连续的氨基酸序列,但可以将其视为一个“句子”,每个氨基酸视为一个“词”。BERT的输入格式通常是一个序列,因此可以直接将整个氨基酸序列作为输入。

②加载bert模型

微调(Fine-tuning):将预训练的BERT模型用于特定任务(如蛋白质固有无序区域预测)。在微调阶段,BERT模型的权重会根据蛋白质序列数据进行调整,以更好地适应任务需求。

输出层设计:在BERT的输出层添加一个分类层,用于预测每个氨基酸位置是否属于无序区域。具体来说,BERT的输出是一个序列的隐藏状态,每个位置的隐藏状态可以通过一个全连接层(Dense Layer)和激活函数(如Sigmoid)来预测二分类标签(0或1)。

③模型微调

损失函数:使用二分类交叉熵损失函数(Binary Cross-Entropy Loss)来训练模型。该损失函数适用于二分类问题,能够衡量模型预测值与真实标签之间的差异。

优化器:使用Adam优化器,这是一种常用的优化器,适用于深度学习任务。

(3)GPT大模型

GPT(Generative Pre-trained Transformer)是由 OpenAI 开发的一系列自然语言处理模型,基于 Transformer 架构的解码器部分,通过大规模语料库的预训练,学习语言的统计规律,并能够生成连贯、自然的文本。

GPT 模型采用自回归(Autoregressive)的方式生成文本,即在给定前面的文本基础上逐步预测并生成下一个词。其核心架构是 Transformer 的解码器部分,利用多头自注意力机制捕捉句子中单词之间的关系,并通过前馈神经网络进行非线性变换。与传统的循环神经网络(RNN)不同,Transformer 能够在一个时间步中并行计算整个输入序列,大大加快了训练和推理速度。

GPT 使用单向 Transformer 解码器,通过自回归语言模型训练,预测句子中的下一个词。这种单向训练方式使得 GPT 在生成连贯文本时表现强大,但对上下文的理解相对较弱。

方法:如微调类似GPT的Qwen

①数据预处理

指令微调使模型能够更准确地识别和理解用户指令的意图。

LLM 的微调一般指指令微调过程。所谓指令微调,是说我们使用的微调数据形如:

{
  "instruction": "回答以下用户问题,仅输出答案。",
  "input": "1+1等于几?",
  "output": "2"
}

其中, instruction 是用户指令,告知模型其需要完成的任务; input 是用户输入,是完成用户指令所必须的输入内容; output 是模型应该给出的输出。

②加载Qwen模型

模型以半精度形式加载,如果你的显卡比较新的话,可以用 torch.bfolat 形式加载。对于自定义的模型一定要指定 trust_remote_code 参数为 True 。

tokenizer = AutoTokenizer.from_pretrained('/root/autodl-tmp/qwen/Qwen2.5-7B-Instruct/', use_fast=False, trust_remote_code=True)

model = AutoModelForCausalLM.from_pretrained('/root/autodl-tmp/qwen/Qwen2.5-7B-Instruct/', device_map="auto",torch_dtype=torch.bfloat16)

③模型微调

使用一种用于大模型微调的技术LoRA(Low-Rank Adaptation)。、

task_type :模型类型

target_modules :需要训练的模型层的名字,主要就是 attention 部分的层,不同的模型对应的层的名字不同,可以传入数组,也可以字符串,也可以正则表达式。

config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
    inference_mode=False, # 训练模式r=8, # Lora 秩lora_alpha=32, # Lora alaph,具体作用参见 Lora 原理lora_dropout=0.1# Dropout 比例
)

3.尝试一个优化方向

在这里我选择了BERT模型进行优化源代码,不过由于竞赛的保密性,在这里暂时不放上代码了,那就期待一下赛事结束未完待续。。。


网站公告

今日签到

点亮在社区的每一天
去签到