比赛的基本信息在之前的任务一中已经有所介绍,属于文本领域(Natural language processing,NLP)的分类任务,之前的方法是采用的是“抽取特征+分类器”的方式来实现的 。这样的方法范式通常需要手动构造许多特征,并且需要较适应的分类器来训练,其中主要的困难点在于(1)手工构造的特征是否有效 (2)如何保证机器学习模型训练中的偏差-方差权衡。但是随着Attention范式的爆火,自动抽取文本特征成为困难,所以在Baseline中我们可以尝试使用BERT(Bidirectional encoder representations from transformers)来自动构造特征,但是和计算机视觉(Computer vision,CV)类似端到端(End-to-end)的思维总是很诱人,所以尝试利用“input+prompt”对训练集进行修改,同时对训练好的大模型进行微调(Fine-tuning)成为一种新的范式。
本文将借助本次项目中提供的Topline代码(深度学习)进行解读。
目录设计Kaggle大神开源了一本 《approaching any machine learning problem》 其中并不是介绍机器学习的各种数学原理,而更多的是从开发的角度、代码文件设计来介绍常见配置。感觉很像是Project profile,在讨论如何将Dirty work更好的组织起来。本次项目代码的路径设计中包括
1 2 3 4 5 6 7 8 9 10 11 - checkpoints 模型训练的权重- dataset 存放数据集- evaluate_predictions 评估文件- models_input_files 存放模型输入文件- model_predictions 存放推理概率文件- premodels 预训练模型权重,config.json文件- data_process.py- models_training.py- evaluate_models.py- ensemble_and_submit.py
虽然之前的学习的过程中我们将深度学习总结为八股:data process、dataset and dataloader、models、loss function、optimizer、hyper-parameters、model train、model eval。但是从调库的角度,可以将其中的第二步~第七步看作一部分,通常可以从hugging face获取。
数据预处理这一部分是将数据集划分为 :训练集、验证集、测试集。不同数据集之间的区别是
训练集 train dataset:直接对于模型的参数进行调整的数据,直接影响梯度下降的结果
验证集 validation dataset:可以通过在训练集和训练得到的模型在验证集中结果对比,来调整模型的超参数,直接影响超参数,间接影响模型结果
测试集 test dataset:模型完全没有见过的数据,属于公平评价模型泛化能力的结果
难点在于将原始文本(Raw documentation)通过分词器(transformer.AutoTokenizer)转换成为 (输入id,注意力掩码,标记类型)(inputs_ids_list,attention_mask_list,token_tyope_ids_list)重点在于如何将文本表示成为向量形式。参考之前阅读李沐的 RNN教程中从零构建文本处理的代码,这一段的核心代码参考hugging face为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 from transformers import AutoTokenizer # 导入AutoTokenizer类,用于文本分词 model_name = 'roberta-base' tokenizer = AutoTokenizer.from_pretrained(model_name,max_length =MAX_LENGTH,cache_dir ='./premodels/{model_name}_saved' ) input_ids_list, attention_mask_list, token_type_ids_list = [], [], []for i in tqdm(range(len(train['content' ]))): sample = train['content' ][i] # 分词处理,使用最长优先方式截断 tokenized = tokenizer(sample, truncation ='longest_first' ) # 获取输入id,注意力掩码,标注类型并转换为torch对象 input_ids, attention_mask = tokenized['input_ids' ], tokenized['attention_mask' ] input_ids, attention_mask = torch.tensor(input_ids), torch.tensor(attention_mask) # 可能有的没有id,所以需要加一个try- except try: token_type_ids = tokenized['token_type_ids' ] # 获取标记类型ID token_type_ids = torch.tensor(token_type_ids) # 转换为PyTorch张量 except: token_type_ids = torch.zeros_like(input_ids) input_ids_list.append(input_ids) # 将输入ID添加到列表中 attention_mask_list.append(attention_mask) # 将注意力掩码添加到列表中 token_type_ids_list.append(token_type_ids) # 将标记类型ID添加到列表中 # 得到训练数据标签 y_train.append(train['label' ][i]) # 将训练数据的标签添加到列表中 input_ids_tensor = pad_sequence(input_ids_list, batch_first =True , padding_value =0) attention_mask_tensor = pad_sequence(attention_mask_list, batch_first =True , padding_value =0) token_type_ids_tensor = pad_sequence(token_type_ids_list, batch_first =True , padding_value =0) x_train = torch.stack([input_ids_tensor, attention_mask_tensor, token_type_ids_tensor], dim =1) x_train = x_train.numpy() np.save(f'./models_input_files/x_train{model_index}.npy' , x_train) # 保存训练数据
模型训练这里的操作主要是加载数据、确定超参数、定义模型(从预训练)、模型训练。其中的核心在于加载数据、如何定义模型和如何从pretrain权重训练模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 class opt: seed = 42 ...class Model (nn.Module):def __init__ (self ,model_name ): super ().__init__() self .model = AutoModel.from_pretrained(model_name,cahe_dir='../premodels/' +model_name+'_saved' ) config = AutoConfig.from_pretrained(model_name,cahe_dir='../premodels/' +model_name+'_saved' ) last_dim = config.hidden_size if opt.use_BCE: out_size = 1 else :out_size = 2 feature_size = 128 self .fc1 = nn.Linear(last_dim, feature_size) self .fc2 = nn.Linear(last_dim, feature_size) self .classifier = nn.Linear(feature_size, out_size) self .dropout = nn.Dropout(0.3 ) def forward (self ,x ): input_ids, attention_mask, token_type_ids = x[: ,0 ],x[: ,1 ],x[: ,2 ] x = self .model(input_ids, attention_mask) all_token = x[0 ] pooled_output = x[1 ] feature1 = all_token.mean(dim=1 ) feature1 = self .fc1(feature1) feature2 = pooled_output feature2 = self .fc2(feature2) feature = 0.5 *feature1 + 0.5 *feature2 feature = self .dropout(feature) x = self .classifier(feature) return x def load_data (): 返回datasetdef model_pretrain (): model_save_dir = device = 等 model = MODEL (model_name).to(device) opt = torch.optim.AdamW(model.parameters(),lr=learning_rate,weight_decay=weight_decay) loss_func = nn.CrossEntropy()for epoch in range(epoches): model.train() train_loss = 0 train_acc = 0 for X,y in train_loader: y_hat = model(X) loss = loss_func(y_hat,y) train_loss += loss.item() opt.zero_grad() loss.backward() opt.step() 每间隔一定轮次开始现实验证过程 f1 recall torch.save(model.module .state_dict(),'' )
另外一种方式:Lora微调这里更多的是对数据中增加一种prompt,核心代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 for i in range (len (train_df)): paper_item = train_df.loc[i] tmp = { "instruction" : "Please judge whether it is a medical field paper according to the given paper title and abstract, output 1 or 0, the following is the paper title and abstract -->" , "input" : f"title:{paper_item[1 ]} ,abstract:{paper_item[3 ]} " , "output" : str (paper_item[5 ]) } res.append(tmp) 导入所需的库和模块from peft import PeftModelfrom transformers import AutoTokenizer, AutoModel, GenerationConfig, AutoModelForCausalLM model_path = "../chatglm2-6b" model = AutoModel.from_pretrained(model_path, trust_remote_code=True ).half().cuda() tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True ) model = PeftModel.from_pretrained(model, './output/label_xfg' ).half() model = model.eval () response, history = model.chat(tokenizer, "你好" , history=[]) response
One more thing为什么是Attention?
这里借助自己笔记的理解 ,所以attention是啥,如果利用QKV的角度来看比较复杂,如果从简单的回归问题角度来看它只是另外一种计算权重方式的函数。
但是!attention的作用不仅局限于回归问题,它从seq问题中而来,解决的就是CNN或者RNN对于距离的敏感性。
就像CNN的感受野,对于单层感受野可能是固定好的(与kernel size、padding、step有关),但是多层累积中感受野是可以逐渐扩大的。但是CNN适用于图像的可能解释是一,图像的维度是固定size的(像素无论多大都是有限的)而
RNN的感受野,借鉴被人的说法是取决于词元模型和hidden layer,但是由于hidden layer也是具有顺序的特征,它的感受野也不会传递的太远。
attention利用attention socre对不同的key进行筛选,来得到一个足够远但是量不大的感受野。选择重要的数据。
警惕大模型?
调用别人的模型可能很舒服,但要是完全端到端那么也太无力了,这里给出hugging face常见使用的代码片段
Pipeline : 需要良好的网络连接
1 2 3 from transformers import pipeline classifier = pipeline( classfifier = (
模型特征提取:Tokenizer、Model、Pretrain
1 2 3 4 5 6 7 from transformers import AutoTokenizer,AutoModel # tokenizer tokenizer = AutoTokenizer.from_pretrained(model_name) input = tokenizer(raw_inputs, padding=True , truncation=True , return_tensors="pt") # model model = AutoModel.from_pretrained(checkpoint ) # 增加自己的操作 post- process
Model除了Automodel还包括*Model
(retrieve the hidden states)*ForCausalLM
*ForMaskedLM
ForMultipleChoice
*ForQuestionAnswering
*ForSequenceClassification
*ForTokenClassification
大模型Finetune
大尺寸预训练 :在这个阶段,模型在大规模的文本数据集上进行预训练。这是一个非监督学习的过程,模型需要预测在给定的文本序列中下一个词是什么。预训练的目标是让模型学会理解和生成人类语言的基本模式。指令微调 :在预训练之后,模型会在一个更小但专门针对特定任务的数据集上进行微调。这个数据集通常由人工生成,包含了模型需要学会的任务的特定指令。例如,如果我们想要让模型学会如何进行数学计算,我们就需要提供一些包含数学问题和对应解答的数据。RLHF(Reinforcement Learning from Human Feedback) :这是一个强化学习过程,模型会根据人类提供的反馈进行学习和优化。首先,我们会收集一些模型的预测结果,并让人类评估这些结果的好坏。然后,我们会使用这些评估结果作为奖励,训练模型优化其预测性能。通过这种方式,模型可以学会生成更符合人类期望的结果。