如何微调 Llama 3 进行序列分类?

节前,我们星球组织了一场算法岗技术&面试讨论会,邀请了一些互联网大厂朋友、参加社招和校招面试的同学.

针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。

汇总合集:


大型语言模型以其文本生成能力而闻名。在预训练期间,它们接受了数百万个标记的训练。这将有助于大型语言模型理解英文文本并在生成期间生成有意义的完整标记。

自然语言处理中的另一个常见任务是序列分类任务。在此,我们将给定的序列分类为不同的类别。这可以通过 prompt 使用大型语言模型简单地完成,但这可能只是有时有效。

我们可以调整大型语言模型以针对给定的输入为每个类别输出一组概率。本指南将展示如何训练此类 LLM 并使用微调的 Llama 3 模型。

导入库

在本指南中,我们将在 Kaggle 中工作。第一步是下载必要的库,我们需要这些库来微调Llama 3进行序列分类。让我们运行以下代码:

bash 复制代码
!pip install -q transformers accelerate trl bitsandbytes datasets evaluate huggingface-cli 
!pip install -q peft scikit-learn
下载库

我们首先下载以下库:

  • Transformers: 这是来自 HuggingFace 的一个库,我们可以使用它来下载、创建应用程序和微调深度学习模型,包括大型语言模型。
  • 加速: 这又是来自 HuggingFace 的一个库,可以加快在 GPU 上运行的大型语言模型的推理速度。
  • Trl: 这是 HuggingFace 的一个 Python 包,我们可以使用它来微调 HuggingFace 中心上提供的深度学习模型。有了这个 TRL 库,我们甚至可以微调大型语言模型。
  • bitsandbytes: LLM 占用的内存非常高,因此我们可以在低 RAM GPU 中直接使用它们。我们将大型语言模型量化为较低的精度,以便我们能够将它们放入 GPU 中,为此,我们需要 bitsandbytes 库。
  • 数据集: 这是来自 HuggingFace 的 Python 库,我们可以用它下载属于不同深度学习和机器学习类别的各种开源数据集,包括图像分类、文本生成、文本摘要等。
  • 评估: 我们将使用这个库来评估训练前后的模型。
  • Huggingface-cli: 这是必需的,因为我们必须登录 HuggingFace 才能使用 Llama3 模型。
  • peft: 使用我们正在使用的 GPU,不可能训练大型语言模型的所有参数。相反,我们只能训练这些参数的一个子集,这可以使用 peft 库来完成。
  • Scikit-learn: 这个库包含许多不同的机器学习和深度学习工具。我们将使用该库作为误差指标,比较训练前后的大型语言模型。

之后,尝试登录 HuggingFace 中心。为此,我们将使用 huggingface-cli 工具。此代码如下所示:

** 复制代码
!huggingface-cli login --token $YOUR_HF_TOKEN
代码解释

在这里,我们使用附加的 -- token 选项调用 huggingface-cli 的登录选项。在这里,我们提供 HuggingFace 令牌以登录 HuggingFace。要获取 HuggingFace 令牌,请转到此链接。如下图所示,您可以通过单击"新令牌"或使用现有令牌来创建访问令牌。只需复制该令牌并将其粘贴到 YOUR_HF_TOKEN 位置即可。

加载数据集

接下来,我们加载数据集进行训练。为此,我们使用以下代码:

python 复制代码
from datasets import load_dataset

dataset = load_dataset("ag_news")
  • 我们首先从数据集库中导入 load_dataset 函数
  • 然后我们使用数据集名称"ag_news"调用load_dataset函数

运行此命令将把 ag_news 数据集下载到 dataset 变量中。ag_news 数据集如下图所示:

这是一个新闻分类数据集。新闻分为不同的类别,例如世界、体育、商业和科学/技术。现在,让我们看看每个类别的示例数量是否相等,或者是否存在类别不平衡。

python 复制代码
import pandas as pd

df = pd.DataFrame(dataset['train'])

df.label.value_counts(normalize=True)
代码解释
  • 这里我们首先导入 pandas 库
  • ag_news 数据集包含训练集和测试集。以及 DatasetDict 类型的数据集变量。为了方便使用,我们将其转换为 pandas DataFrame
  • 然后我们在数据框的"标签"列上调用 value_counts() 函数,并将 normalize 设置为 True

运行此代码会产生以下输出。我们可以检查所有 4 个标签是否具有相同的比例,这意味着每个类别在数据集中都有相同数量的示例。数据集很大,所以我们只需要其中的一部分。因此,我们使用以下代码从此数据框中抽取一些数据:

python 复制代码
# Splitting the dataframe into 4 separate dataframes based on the labels
label_1_df = df[df['label'] == 0]
label_2_df = df[df['label'] == 1]
label_3_df = df[df['label'] == 2]
label_4_df = df[df['label'] == 3]

# Shuffle each label dataframe
label_1_df = label_1_df.sample(frac=1).reset_index(drop=True)
label_2_df = label_2_df.sample(frac=1).reset_index(drop=True)
label_3_df = label_3_df.sample(frac=1).reset_index(drop=True)
label_4_df = label_4_df.sample(frac=1).reset_index(drop=True)

# Splitting each label dataframe into train, test, and validation sets
label_1_train = label_1_df.iloc[:2000]
label_1_test = label_1_df.iloc[2000:2500]
label_1_val = label_1_df.iloc[2500:3000]

label_2_train = label_2_df.iloc[:2000]
label_2_test = label_2_df.iloc[2000:2500]
label_2_val = label_2_df.iloc[2500:3000]

label_3_train = label_3_df.iloc[:2000]
label_3_test = label_3_df.iloc[2000:2500]
label_3_val = label_3_df.iloc[2500:3000]

label_4_train = label_4_df.iloc[:2000]
label_4_test = label_4_df.iloc[2000:2500]
label_4_val = label_4_df.iloc[2500:3000]

# Concatenating the splits back together
train_df = pd.concat([label_1_train, label_2_train, label_3_train, label_4_train])
test_df = pd.concat([label_1_test, label_2_test, label_3_test, label_4_test])
val_df = pd.concat([label_1_val, label_2_val, label_3_val, label_4_val])

# Shuffle the dataframes to ensure randomness
train_df = train_df.sample(frac=1).reset_index(drop=True)
test_df = test_df.sample(frac=1).reset_index(drop=True)
val_df = val_df.sample(frac=1).reset_index(drop=True)
  • 我们的数据包含 4 个标签。因此,我们创建 4 个数据框,每个数据框由一个标签组成
  • 然后,我们通过调用 sample 函数来打乱每个标签
  • 然后,我们将每个标签数据框分成 3 个部分,分别称为训练、测试和有效数据框。对于训练,我们提供 2000 个标签,对于测试和验证,我们各提供 500 个标签
  • 现在,我们通过 pd.concat() 函数将所有标签的训练数据框合并为一个训练数据框
  • 我们对测试和验证数据框也做了同样的事情
  • 最后,我们再次对 train_df、test_df 和 valid_df 进行混洗,以确保它们的随机性
检查价值很重要

为了确认,让我们检查训练数据框中每个标签的值计数。代码如下:

python 复制代码
train_df.label.value_counts()

Pandas DataFrames 到 DatasetDict

因此,我们可以检查训练数据框是否对四个标签都具有相同的示例。在将它们发送到训练之前,我们需要将这些 Pandas DataFrames 转换为 HuggingFace 训练库接受的 DatasetDict。为此,我们使用以下代码:

python 复制代码
from datasets import DatasetDict, Dataset

# Converting pandas DataFrames into Hugging Face Dataset objects:
dataset_train = Dataset.from_pandas(train_df)
dataset_val = Dataset.from_pandas(val_df)
dataset_test = Dataset.from_pandas(test_df)

# Combine them into a single DatasetDict
dataset = DatasetDict({
    'train': dataset_train,
    'val': dataset_val,
    'test': dataset_test
})
dataset
代码解释
  • 首先,我们从数据集库中导入 DatasetDict 类
  • 然后我们将 Pandas DataFrames 中的 train_df、test_df 和 val_df 分别转换为 HuggingFace Dataset 类型
  • 最后,我们将所有的train、test、val HuggingFace数据集合并起来,创建最终的DatasetDict类型变量,并将其存储在数据集变量中

从输出中我们可以看到,DatasetDict 包含 3 个数据集,分别是训练、测试和验证数据集。其中每个数据集仅包含 2 列:一列是文本,另一列是标签。

在这里,在我们的数据集中,每个类别的比例是相同的。在实际情况中,这可能只是有时是正确的。因此,当类别不平衡时,我们需要采取适当的措施,以便 LLM 不会更加重视包含更多示例的标签。为此,我们计算类别权重。

类别权重告诉我们必须赋予每个类别多大的重要性;类别权重越大,该类别的重要性就越大。如果我们的数据集不平衡,我们可以为标签提供更多的类别权重,使用更少的示例,从而赋予它更大的重要性。为了获得这些类别权重,我们可以取数据集中类别标签(值计数)比例的倒数。此代码如下:

python 复制代码
import torch

class_weights=(1/train_df.label.value_counts(normalize=True).sort_index()).tolist()
class_weights=torch.tensor(class_weights)
class_weights=class_weights/class_weights.sum()
class_weights
代码解释
  • 因此,我们首先取类标签值计数的倒数
  • 然后,我们将它们从列表类型转换为 torch 张量类型
  • 然后我们通过将 class_weights 除以 class_weights 的总和来对其进行归一化

从输出中我们可以看到,所有类别的类别权重都是相等的;这是因为所有类别都有相同数量的示例。

模型加载-量化

在本节中,我们将下载并准备模型进行训练。首先是下载模型。我们无法使用完整模型,因为我们正在处理一个小型 GPU;因此,我们将对其进行量化。此代码如下:

python 复制代码
from transformers import BitsAndBytesConfig, AutoModelForSequenceClassification

quantization_config = BitsAndBytesConfig(
    load_in_4bit = True, 
    bnb_4bit_quant_type = 'nf4',
    bnb_4bit_use_double_quant = True, 
    bnb_4bit_compute_dtype = torch.bfloat16 
)

model_name = "meta-llama/Meta-Llama-3-8B"

model = AutoModelForSequenceClassification.from_pretrained(
    model_name,
    quantization_config=quantization_config,
    num_labels=4,
    device_map='auto'
)
代码解释
  • 我们从 transformers 库中导入 BitsAndBytesConfig 和 AutoModelForSequenceClassification 类。
  • 我们必须定义量化配置来为模型创建量化。为此,我们将创建一个 BitsAndBytesConfig 实例并为其提供不同的量化参数。
  • load_in_4bit:设置为 True,表示我们希望将模型量化为 4 位精度
  • bnb_4bit_quant_type:这是我们希望使用的量化类型,我们将使用推荐的 Normal Float(又名 NF4)
  • bnb_4bit_compute_dtype:这是执行 GPU 计算的数据类型。这通常是 torch.float32 / torch.float16 / torch.bfloat16,在我们的示例中,基于 GPU,我们使用 torch.bfloat16
  • bnb_4bit_use_double_quant:设置为True时,将通过量化量化常数进一步减少内存占用
  • 接下来,我们为 model_name 变量提供将要微调的模型名称,这里是 Meta 新推出的 Llama 3 8B
  • 然后,我们创建 AutoModelForSequenceClassification 类的实例,并赋予它模型名称和量化配置
  • 我们还使用了另一个名为 num_labels 的变量,并将其设置为 4。AutoModelForSequenceClassification 所做的是删除 Llama 3 LLM 的最后一层,并将其替换为线性层
  • 我们将这个线性层的输出设置为 4;这是因为我们的数据集中有 4 个类标签
  • 最后,我们将 device_map 设置为"auto",这样模型就可以加载到 GPU

因此,运行上述代码将从 HuggingFace 中心下载 Llama 3 8B 大型语言模型,根据我们为其提供的 quantization_config 对其进行量化,然后将 LLM 的输出头替换为具有 4 个神经元的线性头作为输出,并将模型推送到 GPU。接下来,我们将为模型创建一个 LoRA 配置,以仅训练一部分参数。此代码如下:

LoRA 配置

python 复制代码
from peft import LoraConfig, prepare_model_for_kbit_training, get_peft_model

lora_config = LoraConfig(
    r = 16, 
    lora_alpha = 8,
    target_modules = ['q_proj', 'k_proj', 'v_proj', 'o_proj'],
    lora_dropout = 0.05, 
    bias = 'none',
    task_type = 'SEQ_CLS'
)

model = prepare_model_for_kbit_training(model)
model = get_peft_model(model, lora_config)
代码解释
  • 我们首先从 peft 库导入 LoraConfig、prepare_model_for_kbit_training 和 get_peft_model
  • 然后,我们实例化一个 LoraConfig 类并赋予它不同的参数,其中包括
  • r:这定义了我们将要训练的参数矩阵的秩。我们将其设置为 16
  • lora_alpha:这是基于 LoRA 的训练的超参数。通常将其设置为 r 值的一半
  • target_modules:这是一个列表,我们指定应该在哪里添加基于 LoRA 的训练。为此,我们选择所有注意层,如 K、Q、V 和输出投影
  • lora_dropout:我们将其设置为 0.05,这将随机丢弃神经元,以避免发生过度拟合
  • bias:如果设置为 true,那么偏差项也会随训练权重一起添加
  • task_type:因为我们正在训练分类数据集,并已将分类层添加到 LLM,所以我们将保留这个"SEQ_CLS"
  • 然后,我们调用 prepare_model_for_kbit_training() 函数,将模型提供给该函数。此函数对量化模型进行预处理以进行训练
  • 最后,我们通过提供模型和 LoraConfig 来调用 get_peft_model() 函数

运行此程序后,get_peft_model 将采用模型并通过包装模型和 LoRA 配置来准备使用PEFT 方法(如本例中的LoRA)进行训练。

模型测试 -- 预训练

在本节中,我们将在模型训练之前在测试数据上测试 Llama 3 模型。为此,我们将首先下载标记器。此代码如下:

python 复制代码
from transformers import AutoTokenizer

model_name = "meta-llama/Meta-Llama-3-8B"
tokenizer = AutoTokenizer.from_pretrained(model_name, add_prefix_space=True)

tokenizer.pad_token_id = tokenizer.eos_token_id
tokenizer.pad_token = tokenizer.eos_token
代码解释
  • 在这里,我们从 transformers 库中导入 AutoTokenizer 类
  • 我们通过调用 AutoTokenizer 类的 from_pretrained() 函数并向其传递模型名称来实例化一个 tokenizer
  • 然后我们将 tokenizer 的 pad_token 设置为 eos_token,pad_token_id 也是如此
python 复制代码
model.config.pad_token_id = tokenizer.pad_token_id
model.config.use_cache = False
model.config.pretraining_tp = 1

接下来,我们甚至通过将模型的 pad token ID 设置为 tokenizer 的 pad token ID 来编辑模型配置,并且不使用缓存。现在,我们将测试数据提供给模型并收集输出:

python 复制代码
sentences = test_df.text.tolist()

batch_size = 32  

all_outputs = []

for i in range(0, len(sentences), batch_size):
    batch_sentences = sentences[i:i + batch_size]

    inputs = tokenizer(batch_sentences, return_tensors="pt", 
    padding=True, truncation=True, max_length=512)

    inputs = {k: v.to('cuda' if torch.cuda.is_available() else 'cpu') for k, v in inputs.items()}

    with torch.no_grad():
        outputs = model(**inputs)
        all_outputs.append(outputs['logits'])
        
final_outputs = torch.cat(all_outputs, dim=0)
test_df['predictions']=final_outputs.argmax(axis=1).cpu().numpy()
代码解释
  • 首先,我们将测试 DataFrame 的文本列中的元素转换为句子列表,并将其存储在变量 sentence 中。
  • 然后,我们定义一个批次大小来表示一批模型的输入;这里,我们将这个大小设置为 32。
  • 然后,我们创建一个空的 all_outputs 列表来存储模型将生成的输出
  • 我们开始遍历句子变量,将步长设置为我们定义的批量大小。
  • 因此,在每次迭代中,我们都会分批对输入的句子进行标记。
  • 然后,我们将这些标记化的句子移动到运行模型的设备上,可以是 GPU 或 CPU。
  • 最后,我们通过将输入传递给模型并将输出逻辑附加到 all_outputs 列表来执行模型推理。然后我们将所有这些输出连接起来以形成最终的输出张量。

运行此代码会将模型结果存储在一个变量中。我们将这些预测添加到新列中的测试 DataFrame 中。我们取每个输出的 argmax;这为我们提供了 final_outputs 列表中每个输出概率最高的标签。现在,我们需要评估 LLM 生成的输出,我们可以通过以下代码进行评估:

python 复制代码
from sklearn.metrics import accuracy_score, confusion_matrix
from sklearn.metrics import balanced_accuracy_score, classification_report

def get_metrics_result(test_df):
    y_test = test_df.label
    y_pred = test_df.predictions

    print("Classification Report:")
    print(classification_report(y_test, y_pred))

    print("Balanced Accuracy Score:", balanced_accuracy_score(y_test, y_pred))
    print("Accuracy Score:", accuracy_score(y_test, y_pred))

get_metrics_result(test_df)
代码解释
  • 我们首先从 sklearn 库导入 accuracy_score、balanced_accuracy_score 和 classification_report。
  • 然后我们定义一个名为 get_metrics_result() 的函数,它接受数据框并输出结果。
  • 在这个函数中,我们首先将预测和实际标签存储在一个变量中。
  • 然后,我们将这些实际值和预测值提供给分类报告、accuracy_score 和 balance_accuracy_score,并打印它们。
  • 处理不平衡数据时,balance_accuracy_score 很有用。

运行该程序产生了以下结果。我们可以看到,我们的准确率为 0.23,这非常低。该模型的准确率、召回率和 f1 分数也非常低;它们甚至没有达到 50% 以上的百分比。在训练模型后对它们进行测试将让我们了解模型的训练效果。

在开始训练之前,我们需要对数据进行预处理,然后再将其发送到模型。为此,我们使用以下代码:

python 复制代码
def data_preprocesing(row):
    return tokenizer(row['text'], truncation=True, max_length=512)

tokenized_data = dataset.map(data_preprocesing, batched=True, 
remove_columns=['text'])
tokenized_data.set_format("torch")
代码解释
  • 我们定义一个名为 data_preprocessing() 的函数,它需要一行数据
  • 在其中,我们将该行的文本内容传递给标记器,并将截断设置为 true,最大长度设置为 512 个标记
  • 然后我们创建 tokenized_data,通过将此函数映射到我们创建的数据集,并在映射后删除文本列,因为我们只需要模型期望的标记
  • 最后,我们将这些标记化数据转换为 torch 格式

现在,datasetdict 中的每个数据集都包含三个特征/列,即标签、input_id 和 Attention_masks。使用上述预处理函数为每个文本生成 input_ids 和 Attention_masks。在训练时,我们需要一个数据整理器来批量处理数据。为此,我们使用以下代码:

python 复制代码
from transformers import DataCollatorWithPadding

collate_fn = DataCollatorWithPadding(tokenizer=tokenizer)
  • 在这里,我们从 transformers 库中导入 DataCollatorWithPadding 类
  • 然后,我们通过赋予它 tokenizer 来实例化该类
  • 此 collate_fn 将填充输入批次,使其长度等于该批次中的最大输入长度

这将确保批处理中的所有输入具有相同的长度,这对于加快训练速度至关重要。因此,我们使用特殊标记(如填充标记)将输入统一填充到最长序列长度,从而允许同时进行批处理。

Finetune Llama 3:模型训练和训练后评估

在开始训练之前,我们需要一个误差度量来评估它。大型语言模型的默认误差度量是负对数似然损失。但在这里,因为我们正在修改 LLM 以使其成为序列分类工具,所以我们需要重新定义在训练时测试模型所需的误差度量:

python 复制代码
def compute_metrics(evaluations):
    predictions, labels = evaluations
    predictions = np.argmax(predictions, axis=1)
    return {'balanced_accuracy' : balanced_accuracy_score(predictions, labels),
    'accuracy':accuracy_score(predictions,labels)}
  • 在这里,我们定义一个函数 compute_metrics,它接受一个包含预测和标签的元组
  • 然后,从给定的预测中,我们通过 np.argmax() 函数提取概率最高的索引
  • 最后,我们返回一个包含平衡准确度和原始准确度分数的字典

因为我们要使用自定义指标,所以我们甚至定义了一个自定义训练器来训练我们的 LLM,这是必要的,因为我们在这里使用类权重。为此,代码将是

python 复制代码
class CustomTrainer(Trainer):
    def __init__(self, *args, class_weights=None, **kwargs):
        super().__init__(*args, **kwargs)
        if class_weights is not None:
            self.class_weights = torch.tensor(class_weights, 
            dtype=torch.float32).to(self.args.device)
        else:
            self.class_weights = None

    def compute_loss(self, model, inputs, return_outputs=False):
        labels = inputs.pop("labels").long()

        outputs = model(**inputs)

        logits = outputs.get('logits')

        if self.class_weights is not None:
            loss = F.cross_entropy(logits, labels, weight=self.class_weights)
        else:
            loss = F.cross_entropy(logits, labels)

        return (loss, outputs) if return_outputs else loss
代码解释
  • 在这里,我们定义一个自定义训练器类,该类继承自 HuggingFace 的 Trainer 类
  • 此类实现了一个 init 函数,其中我们定义,如果提供了 class_weights,则将 class_weights 分配为实例变量之一,否则将其分配给 None
  • 在分配之前,我们将 class_weights 转换为 torch.tensor,并将 dtype 更改为 float32
  • 然后我们定义 Trainer 类的 compute_loss 函数,这是执行反向传播所必需的
  • 首先,我们从输入中提取标签并将其转换为 long 数据类型
  • 然后我们将输入提供给模型,模型接受这些输入并生成类别的概率;我们从这些输出中获取对数
  • 然后我们调用交叉熵损失,因为我们正在处理多个标签分类,并给它模型输出logits和标签。如果存在类权重,我们甚至会将它们提供给模型
  • 最后我们按要求返回这些损失和输出

训练论点

现在,我们将定义我们的训练参数。代码如下

python 复制代码
training_args = TrainiAgrumentsngArguments(
    output_dir = 'sentiment_classification',
    learning_rate = 1e-4,
    per_device_train_batch_size = 8,
    per_device_eval_batch_size = 8,
    num_train_epochs = 1,
    logging_steps=1,
    weight_decay = 0.01,
    evaluation_strategy = 'epoch',
    save_strategy = 'epoch',
    load_best_model_at_end = True,
    report_to="none"
)
代码解释
  • 我们实例化 TrainingArguments 类的一个对象。为此,我们传递如下参数
  • output_dir:这是我们想要保存模型的地方。我们可以在这里提供路径
  • learning_rate:这里我们给出训练时应用的学习率
  • per_device_train_batch_size:在这里,我们设置训练时训练数据的批量大小
  • per_device_eval_batch_size; 在这里,我们设置测试/评估数据的评估批次大小
  • num_train_epochs:在这里,我们给出了训练 LLM 所需的训练周期数
  • 日志记录步骤:在这里,我们告诉记录结果的频率
  • weight_decay:权重衰减多少
  • save_strategy:设置为 epoch,表示每个 epoch 都会保存模型权重
  • evaluation_strategy:设置为 epoch,表示每个 epoch 都会对模型进行评估
  • load_best_model_at_end:赋予其 True 值将加载具有最佳参数的模型,从而提供最佳结果

将物体传递给训练员

这将创建我们的 TrainingArguments 对象。现在,我们准备将其传递给我们创建的 Trainer。此代码如下

python 复制代码
trainer = CustomTrainer(
    model = model,
    args = training_args,
    train_dataset = tokenized_datasets['train'],
    eval_dataset = tokenized_datasets['val'],
    tokenizer = tokenizer,
    data_collator = collate_fn,
    compute_metrics = compute_metrics,
    class_weights=class_weights,
)

train_result = trainer.train()
代码解释
  • 我们首先创建之前创建的 CustomTrainer() 类的对象
  • 为此,我们给出了带有自定义 Sequential Head 的 Llama 3 模型,训练参数
  • 我们甚至将标记化的训练数据和评估数据与标记器一起传递
  • 我们甚至提供了整理器功能,以便在训练 LLM 时进行批处理
  • 最后,我们给出我们创建的度量函数和类权重,如果我们有标签不平衡的数据,这将有所帮助

现在我们已经创建了训练器对象。现在我们调用训练器对象的 .train() 函数来启动训练过程,并将结果存储在 train_result 中

代码解释
  • 上面我们检查训练是否进行了 1000 步。这是因为我们有 8000 个训练数据,并且每次在每个步骤中我们发送 8 个样本;因此我们总共有 1000 步
  • 训练持续了 2 小时 36 分钟,以便模型迭代一个时期的整个训练数据
  • 我们可以看到模型的训练损失为 1.12,验证损失为 0.29
  • 该模型在验证数据上表现出色,准确率达到 93%

现在,让我们尝试进行评估,在测试数据上测试新训练的模型:

python 复制代码
def generate_predictions(model,df_test):
    sentences = df_test.text.tolist()
    batch_size = 32  
    all_outputs = []

    for i in range(0, len(sentences), batch_size):

        batch_sentences = sentences[i:i + batch_size]

        inputs = tokenizer(batch_sentences, return_tensors="pt", 
        padding=True, truncation=True, max_length=512)

        inputs = {k: v.to('cuda' if torch.cuda.is_available() else 'cpu') 
        for k, v in inputs.items()}

        with torch.no_grad():
            outputs = model(**inputs)
            all_outputs.append(outputs['logits'])
        
    final_outputs = torch.cat(all_outputs, dim=0)
    df_test['predictions']=final_outputs.argmax(axis=1).cpu().numpy()

generate_predictions(model,test_df)
get_performance_metrics(test_df)
代码解释
  • 在这里,我们创建一个与我们创建的函数非常相似的函数
  • 在这个函数中,我们将测试数据框中的文本列转换为句子列表,然后运行一个步长等于批处理大小的 for 循环
  • 在这个 for 循环中,我们获取句子的批号,将它们发送到 tokenizer 进行标记,然后将它们推送到 GPU
  • 然后,我们将这个标记列表插入模型并从中获取概率列表
  • 最后,我们得到每个输出中最高概率的索引,并将索引保存回测试数据框中
  • 这个 test_df 返回到 get_performance_metric 函数,该函数接收这个数据框并输出结果

运行此代码会产生以下结果。我们看到模型的准确率有了很大的提升。其他指标,如精度、召回率和 f1 分数也从初始值有所增加。总体准确率从训练前的 0.23 提高到训练后的 0.93,即训练后模型提高了 0.7,即 70%。由此,我们可以了解到,大型语言模型非常适合用作序列分类器

结论

总之,对大型语言模型Llama 3进行序列分类的微调涉及几个详细步骤,从准备数据集到量化模型以在有限的硬件上进行高效训练。通过利用 HuggingFace 的各种库并实施 Prompt Engineering 和 LoRA 配置等技术,可以有效地训练这些模型以完成新闻分类等特定任务。本指南演示了从初始设置和数据预处理到模型训练和评估的整个过程,突出了 LLM 在自然语言处理任务中的多功能性和强大功能。

关键要点
  • 大型语言模型(finetune Llama 3)在大量数据集上进行了预训练,能够生成和理解复杂文本
  • 虽然有用,但仅靠快速工程可能不足以完成所有分类任务;模型微调可以提供更好的性能
  • 正确加载、拆分和平衡数据集是模型训练前的基本步骤,以确保模型性能准确、公平
  • 定义自定义错误指标和训练循环对于处理序列分类任务中的类别权重等特定要求是必要的
  • 使用准确度和平衡准确度等指标进行训练前和训练后评估,可以深入了解模型性能和训练过程的有效性
相关推荐
NAGNIP11 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab13 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab13 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP16 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年16 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼17 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS17 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
warm3snow17 小时前
Claude Code 黑客马拉松:5 个获奖项目,没有一个是"纯码农"做的
ai·大模型·llm·agent·skill·mcp
天翼云开发者社区18 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈18 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能