Datawhale AI春训营 世界科学智能大赛--合成生物赛道:蛋白质固有无序区域预测 小白经验总结
一、报名大赛
二、跑通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 = sequencesself.labels = labelsself.residue_mapping = {'X':20} #未知氨基酸'X'映射为索引20self.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赋值#标签转为Tensorlabel = 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_modelself.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) #Dropoutx = self.transformer(x) #Transformer编码x = self.output_layer(x) #分类头return x
评估指标
def metric_fn(pred,gt):pred = pred.detach().cpu() #脱离计算图,转到CPUgt = 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') # 计算F1return 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模型进行优化源代码,不过由于竞赛的保密性,在这里暂时不放上代码了,那就期待一下赛事结束未完待续。。。