IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    datawhale_NLP任务二

    chenxia发表于 2023-08-21 08:01:13
    love 0

    比赛的基本信息在之前的任务一中已经有所介绍,属于文本领域(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文件
    # 以及各种src(.py)
    - 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]) # 将训练数据的标签添加到列表中

    # 同时为了保证向量维度大小一致,使用torch pad_sequence
    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()

    # 最后保存数据,这里转换成为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 # 损失函数如果使用BCE,则输出大小为1
    else :out_size = 2 # 否则则使用CE,输出大小为2
    feature_size = 128 # 设置特征的维度大小
    self.fc1 = nn.Linear(last_dim, feature_size) # 全连接层1
    self.fc2 = nn.Linear(last_dim, feature_size) # 全连接层2
    self.classifier = nn.Linear(feature_size, out_size) # 分类器
    self.dropout = nn.Dropout(0.3) # Dropout层

    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] # [CLS]的表征向量+一个全连接层+Tanh激活函数

    # 对于大模型的输出进行特征融合
    feature1 = all_token.mean(dim=1) # 对全部序列分词的表征向量取均值
    feature1 = self.fc1(feature1) # 再输入进全连接层,得到feature1
    feature2 = pooled_output # [CLS]的表征向量+一个全连接层+Tanh激活函数
    feature2 = self.fc2(feature2) # 再输入进全连接层,得到feature2
    feature = 0.5*feature1 + 0.5*feature2 # 加权融合特征
    feature = self.dropout(feature) # Dropout
    x = self.classifier(feature) # 分类
    return x

    def load_data():
    返回dataset

    def 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()
    # 计算acc
    每间隔一定轮次开始现实验证过程
    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
    # 构建包括instruction、input、output的数据集
    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 PeftModel
    from 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)
    # 加载 label lora权重
    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常见使用的代码片段

    1. Pipeline: 需要良好的网络连接

      1
      2
      3
      from transformers import pipeline
      classifier = pipeline('sentiment-analysis')
      classfifier = ('I have waiting for a hugging face course my whole life')
    2. 模型特征提取: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

    3. 大模型Finetune

      1. 大尺寸预训练:在这个阶段,模型在大规模的文本数据集上进行预训练。这是一个非监督学习的过程,模型需要预测在给定的文本序列中下一个词是什么。预训练的目标是让模型学会理解和生成人类语言的基本模式。
      2. 指令微调:在预训练之后,模型会在一个更小但专门针对特定任务的数据集上进行微调。这个数据集通常由人工生成,包含了模型需要学会的任务的特定指令。例如,如果我们想要让模型学会如何进行数学计算,我们就需要提供一些包含数学问题和对应解答的数据。
      3. RLHF(Reinforcement Learning from Human Feedback):这是一个强化学习过程,模型会根据人类提供的反馈进行学习和优化。首先,我们会收集一些模型的预测结果,并让人类评估这些结果的好坏。然后,我们会使用这些评估结果作为奖励,训练模型优化其预测性能。通过这种方式,模型可以学会生成更符合人类期望的结果。


沪ICP备19023445号-2号
友情链接