发掘NLP超能力:一步一步的拥抱之面微调教程

领略NLP超能力:一步一步掌握微调面部的拥抱之道

介绍

微调自然语言处理(NLP)模型涉及改变模型的超参数和架构,通常通过调整数据集来提高模型在特定任务上的性能。您可以通过调整学习率、模型中的层数、嵌入的大小和其他各种参数来实现这一目标。微调是一个耗时的过程,需要对模型和任务有深入的理解。本文将介绍如何对Hugging Face模型进行微调。

学习目标

  • 了解T5模型的结构,包括Transformer和自注意力。
  • 学习优化超参数以提高模型性能。
  • 掌握文本数据准备,包括分词和格式化。
  • 了解如何将预训练模型适应特定任务。
  • 学习清洗、拆分和创建训练数据集。
  • 通过损失和准确率等指标进行模型训练和评估。
  • 探索微调模型在生成回答或答案方面的实际应用。

本文是 Data Science Blogathon 的一部分。

关于Hugging Face模型

Hugging Face是一家提供自然语言处理(NLP)模型训练和部署平台的公司。该平台拥有适用于各种NLP任务的模型库,包括语言翻译、文本生成和问答。这些模型在大规模数据集上进行训练,旨在在各种自然语言处理(NLP)活动中表现出色。

Hugging Face平台还包括用于在特定数据集上微调预训练模型的工具,这可以帮助将算法适应特定领域或语言。该平台还具有用于访问和利用预训练模型的API,以及用于构建定制模型并将其交付到云端的工具。

使用Hugging Face库进行自然语言处理(NLP)任务具有以下优势:

  1. 丰富的模型选择:通过Hugging Face库,可以获得广泛的预训练NLP模型选择,包括在语言翻译、问答和文本分类等任务上训练的模型。这使得选择符合您需求的模型变得简单。
  2. 跨平台兼容性:Hugging Face库与标准的深度学习系统(如TensorFlow、PyTorch和Keras)兼容,便于集成到现有工作流程中。
  3. 简便的微调:Hugging Face库提供了在您的数据集上微调预训练模型的工具,节省了从头训练模型的时间和精力。
  4. 活跃的社区支持:Hugging Face库拥有庞大而活跃的用户社区,这意味着您可以获取帮助和支持,并为库的发展做出贡献。
  5. 文档完善:Hugging Face库包含详尽的文档,使您能够轻松入门并高效使用。

导入必要的库

导入必要的库类似于为特定的编程和数据分析活动构建工具包。这些库通常是预先编写的代码集合,提供了各种函数和工具来加快开发速度。通过导入适当的库,开发人员和数据科学家可以访问新能力,提高生产力,并使用现有解决方案。

import pandas as pdimport numpy as npfrom sklearn.model_selection import train_test_splitimport torchfrom transformers import T5Tokenizerfrom transformers import T5ForConditionalGeneration, AdamWimport pytorch_lightning as plfrom pytorch_lightning.callbacks import ModelCheckpointpl.seed_everything(100)import warningswarnings.filterwarnings("ignore")

导入数据集

导入数据集是数据驱动项目中的一个关键初始步骤。

df = pd.read_csv("/kaggle/input/queestion-answer-dataset-qa/train.csv")df.columns

df = df[['context','question', 'text']]print("记录数:", df.shape[0])

问题陈述

“基于上下文和问题生成回答的模型。”

例如:

上下文 = “聚类类似案例,例如可以找到相似的患者,或在银行领域中用于客户细分。关联技术用于找到经常共同出现的物品或事件,例如特定客户通常一起购买的杂货物品。异常检测用于发现异常和异常案例,例如信用卡欺诈检测。”

问题 = “什么是异常检测的例子?”

答案 = ????????????????????????????????

df["context"] = df["context"].str.lower()df["question"] = df["question"].str.lower()df["text"] = df["text"].str.lower()df.head()

初始化参数

  • 输入长度:在训练过程中,我们将单个示例中输入令牌(如单词或字符)的数量称为输入长度。例如,如果你正在训练一个语言模型来预测句子中的下一个单词,输入长度将是短语中单词的数量。
  • 输出长度:在训练过程中,模型应该生成特定数量的输出令牌,如单词或字符。输出长度对应于模型在句子中预测的单词数。
  • 训练批量大小:在训练过程中,模型一次处理多个样本。如果将训练批量大小设置为32,则模型在更新模型权重之前同时处理32个实例,例如32个短语。
  • 验证批量大小:类似于训练批量大小,该参数表示模型在验证阶段处理的实例数。换句话说,它表示模型在一个保留数据集上进行测试时处理的数据量。
  • 迭代次数:一次完整的训练数据集遍历称为一个迭代周期(epoch)。因此,如果训练数据集包含1000个实例,训练批量大小为32,则一个迭代周期将需要32个训练步骤。如果模型经过十个迭代周期的训练,则它将处理一万个实例(10 * 1000 = 一万)。
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu') INPUT_MAX_LEN = 512 # 输入长度OUT_MAX_LEN = 128 # 输出长度TRAIN_BATCH_SIZE = 8 # 训练批量大小VALID_BATCH_SIZE = 2 # 验证批量大小EPOCHS = 5 # 迭代次数

T5 Transformer

T5模型基于Transformer架构,这是一个设计用于有效处理序列输入数据的神经网络。它包括一个编码器和一个解码器,其中包括一系列相互连接的“层”。

编码器和解码器层包括各种“注意”机制和“前馈”网络。注意机制使模型能够在不同的时间关注输入序列的不同部分。同时,前馈网络使用一组权重和偏差改变输入数据。

T5模型还使用“自注意力”机制,使输入序列中的每个元素能够关注其他元素。这使得模型能够识别输入数据中单词和短语之间的联系,这对于许多自然语言处理应用非常重要。

除了编码器和解码器外,T5模型还包含了一个“语言模型头”,根据先前的单词预测下一个单词的序列。这对于翻译和文本生成工作至关重要,模型必须提供连贯和自然的输出。

T5模型代表了一个大型而复杂的神经网络,旨在高效准确地处理顺序输入。它在多样化的文本数据集上经过了广泛的训练,并能够熟练执行广泛的自然语言处理任务。

T5Tokenizer

T5Tokenizer用于将文本转换为表示单词或标点符号的标记列表。标记器还会在输入文本中插入唯一的标记,以表示文本的开头和结尾,并区分不同的短语。

T5Tokenizer采用了字符级和词级标记化的组合,以及类似于SentencePiece标记器的子词级标记化策略。它根据训练数据中每个字符或字符序列的频率对输入文本进行子词处理。这有助于标记器处理训练数据中不存在但出现在测试数据中的OOV (Out-Of-Vocabulary) 术语。

T5Tokenizer还会在文本中插入唯一的标记,以表示句子的开始和结束以及分割它们。例如,它会添加 s > 和 / s > 这样的标记来表示短语的开始和结束,并添加 pad > 来指示填充。

MODEL_NAME = "t5-base"
tokenizer = T5Tokenizer.from_pretrained(MODEL_NAME, model_max_length= INPUT_MAX_LEN)

print("eos_token: {} and id: {}".format(tokenizer.eos_token,tokenizer.eos_token_id)) # 结束符号 (eos_token)
print("unk_token: {} and id: {}".format(tokenizer.unk_token,tokenizer.eos_token_id)) # 未知符号 (unk_token)
print("pad_token: {} and id: {}".format(tokenizer.pad_token,tokenizer.eos_token_id)) # 填充符号 (pad_token)

数据集准备

在处理PyTorch时,通常会通过使用数据集类来准备数据以便与模型一起使用。数据集类负责从磁盘加载数据并执行所需的准备过程,如标记化和数值化。该类还应实现getitem函数,该函数用于通过索引从数据集中获取单个项目。

init方法用文本列表、标签列表和标记器填充数据集。len函数返回数据集中的样本数量。getitem函数通过索引返回数据集中的单个项目。它接受索引idx并输出标记化的输入和标签。

通常还包括各种预处理步骤,如填充和截断标记化的输入。还可以将标签转换为张量。

class T5Dataset:
    def __init__(self, context, question, target):
        self.context = context
        self.question = question
        self.target = target
        self.tokenizer = tokenizer
        self.input_max_len = INPUT_MAX_LEN
        self.out_max_len = OUT_MAX_LEN
    def __len__(self):
        return len(self.context)
    def __getitem__(self, item):
        context = str(self.context[item])
        context = " ".join(context.split())
        question = str(self.question[item])
        question = " ".join(question.split())
        target = str(self.target[item])
        target = " ".join(target.split())
        
        inputs_encoding = self.tokenizer(
            context,
            question,
            add_special_tokens=True,
            max_length=self.input_max_len,
            padding = 'max_length',
            truncation='only_first',
            return_attention_mask=True,
            return_tensors="pt"
        )
        
        output_encoding = self.tokenizer(
            target,
            None,
            add_special_tokens=True,
            max_length=self.out_max_len,
            padding = 'max_length',
            truncation= True,
            return_attention_mask=True,
            return_tensors="pt"
        )
        
        inputs_ids = inputs_encoding["input_ids"].flatten()
        attention_mask = inputs_encoding["attention_mask"].flatten()
        labels = output_encoding["input_ids"]
        labels[labels == 0] = -100  # 根据T5文档
        labels = labels.flatten()
        
        out = {
            "context": context,
            "question": question,
            "answer": target,
            "inputs_ids": inputs_ids,
            "attention_mask": attention_mask,
            "targets": labels
        }
        
        return out

数据加载器

DataLoader类以并行和批次方式加载数据,使得能够处理那些原本无法在内存中存储的大型数据集。将DataLoader类与包含要加载的数据的数据集类结合使用。

数据加载器负责迭代数据集并将一批数据返回给模型进行训练或评估。DataLoader类提供了各种参数来控制数据的加载和预处理,包括批次大小、工作线程数以及是否在每个epoch之前对数据进行洗牌。

class T5DatasetModule(pl.LightningDataModule):    def __init__(self, df_train, df_valid):        super().__init__()        self.df_train = df_train        self.df_valid = df_valid        self.tokenizer = tokenizer        self.input_max_len = INPUT_MAX_LEN        self.out_max_len = OUT_MAX_LEN    def setup(self, stage=None):        self.train_dataset = T5Dataset(        context=self.df_train.context.values,        question=self.df_train.question.values,        target=self.df_train.text.values        )        self.valid_dataset = T5Dataset(        context=self.df_valid.context.values,        question=self.df_valid.question.values,        target=self.df_valid.text.values        )    def train_dataloader(self):        return torch.utils.data.DataLoader(         self.train_dataset,         batch_size= TRAIN_BATCH_SIZE,         shuffle=True,          num_workers=4        )    def val_dataloader(self):        return torch.utils.data.DataLoader(         self.valid_dataset,         batch_size= VALID_BATCH_SIZE,         num_workers=1        )

模型构建

在PyTorch中创建变换器模型时,通常需要创建一个继承自torch.nn.Module的新类。该类描述了模型的架构,包括层和前向函数。类的init函数定义了模型的架构,通常是通过实例化模型的不同层并将它们分配为类属性来实现。

forward方法负责将数据在正向方向上通过模型传递。该方法接受输入数据并将模型的层应用于创建输出。forward方法应该实现模型的逻辑,例如通过一系列层传递输入并返回结果。

类的init函数创建了一个嵌入层、一个变换器层和一个全连接层,并将它们分配为类属性。forward方法接受传入的数据x,通过给定的阶段处理它,并返回结果。在训练变换器模型时,训练过程通常涉及两个阶段:训练和验证。

training_step方法指定执行单个训练步骤的原理,通常包括:

  • 通过模型进行前向传播
  • 计算损失
  • 计算梯度
  • 更新模型的参数

val_step方法与training_step方法类似,用于对验证集上进行评估模型。通常包括:

  • 通过模型进行前向传播
  • 计算评估指标
class T5Model(pl.LightningModule):        def __init__(self):        super().__init__()        self.model = T5ForConditionalGeneration.from_pretrained(MODEL_NAME, return_dict=True)    def forward(self, input_ids, attention_mask, labels=None):        output = self.model(            input_ids=input_ids,             attention_mask=attention_mask,             labels=labels        )        return output.loss, output.logits    def training_step(self, batch, batch_idx):        input_ids = batch["inputs_ids"]        attention_mask = batch["attention_mask"]        labels= batch["targets"]        loss, outputs = self(input_ids, attention_mask, labels)                self.log("train_loss", loss, prog_bar=True, logger=True)        return loss    def validation_step(self, batch, batch_idx):        input_ids = batch["inputs_ids"]        attention_mask = batch["attention_mask"]        labels= batch["targets"]        loss, outputs = self(input_ids, attention_mask, labels)        self.log("val_loss", loss, prog_bar=True, logger=True)                return loss    def configure_optimizers(self):        return AdamW(self.parameters(), lr=0.0001)

模型训练

对数据集进行批量迭代,将输入数据通过模型发送,并根据计算出的梯度和一组优化条件更改模型的参数是训练变换器模型的常用方式。

def run():        df_train, df_valid = train_test_split(        df[0:10000], test_size=0.2, random_state=101    )        df_train = df_train.fillna("none")    df_valid = df_valid.fillna("none")        df_train['context'] = df_train['context'].apply(lambda x: " ".join(x.split()))    df_valid['context'] = df_valid['context'].apply(lambda x: " ".join(x.split()))        df_train['text'] = df_train['text'].apply(lambda x: " ".join(x.split()))    df_valid['text'] = df_valid['text'].apply(lambda x: " ".join(x.split()))        df_train['question'] = df_train['question'].apply(lambda x: " ".join(x.split()))    df_valid['question'] = df_valid['question'].apply(lambda x: " ".join(x.split()))       df_train = df_train.reset_index(drop=True)    df_valid = df_valid.reset_index(drop=True)        dataModule = T5DatasetModule(df_train, df_valid)    dataModule.setup()    device = DEVICE    models = T5Model()    models.to(device)    checkpoint_callback  = ModelCheckpoint(        dirpath="/kaggle/working",        filename="best_checkpoint",        save_top_k=2,        verbose=True,        monitor="val_loss",        mode="min"    )    trainer = pl.Trainer(        callbacks = checkpoint_callback,        max_epochs= EPOCHS,        gpus=1,        accelerator="gpu"    )    trainer.fit(models, dataModule)run()

模型预测

要使用经过微调的T5等自然语言处理模型进行新输入的预测,可以按照以下步骤进行:

  • 预处理新输入:将新的输入文本进行标记化和预处理,使其与训练数据的预处理相匹配。确保它符合模型期望的正确格式。
  • 使用微调模型进行推理:加载之前训练或从检查点加载的经过微调的T5模型。
  • 生成预测:将预处理的新输入传递给模型进行预测。在T5的情况下,可以使用generate方法生成响应。
train_model = T5Model.load_from_checkpoint("/kaggle/working/best_checkpoint-v1.ckpt")train_model.freeze()def generate_question(context, question):    inputs_encoding =  tokenizer(        context,        question,        add_special_tokens=True,        max_length= INPUT_MAX_LEN,        padding = 'max_length',        truncation='only_first',        return_attention_mask=True,        return_tensors="pt"        )        generate_ids = train_model.model.generate(        input_ids = inputs_encoding["input_ids"],        attention_mask = inputs_encoding["attention_mask"],        max_length = INPUT_MAX_LEN,        num_beams = 4,        num_return_sequences = 1,        no_repeat_ngram_size=2,        early_stopping=True,        )    preds = [        tokenizer.decode(gen_id,        skip_special_tokens=True,         clean_up_tokenization_spaces=True)        for gen_id in generate_ids    ]    return "".join(preds)

预测

让我们使用经过微调的T5模型进行新输入的预测:

context = “通过对类似案例进行聚类,例如,可以找到相似的患者,或者在银行业务领域中用于客户分割。使用关联技术来查找通常同时出现的项目或事件,例如,通常由特定客户共同购买的杂货商品。使用异常检测来发现异常和不寻常的情况,例如,信用卡欺诈检测。”

que = “异常检测的例子是什么?”

print(generate_question(context, que))

context = "当目标是分类时使用分类,\ 当目标变量是连续的时使用回归。分类和回归都属于监督机器学习算法的范畴。"que = "分类是在什么时候使用的?"print(generate_question(context, que))

结论

在本文中,我们展开了一项对自然语言处理(NLP)模型(特别是T5模型)进行微调的旅程,以进行问答任务。在这个过程中,我们深入探讨了各种NLP模型开发和部署的方面。

主要收获:

  • 探索了编码器-解码器结构和自注意力机制,这是其能力的基础。
  • 调参艺术是优化模型性能的关键技能。
  • 通过尝试不同的学习率、批量大小和模型大小,我们能够有效地对模型进行微调。
  • 熟练掌握标记化、填充和将原始文本数据转化为适合模型输入的合适格式。
  • 深入研究了微调,包括加载预训练的权重、修改模型层,并将其调整为特定任务。
  • 学会了如何清洗和构造数据,将其分割为训练和验证集。
  • 展示了它如何根据输入上下文和问题生成响应或答案,展示了其在现实世界中的实用性。

常见问题

本文中显示的媒体不属于Analytics Vidhya所有,仅由作者自行决定使用。