从基础到精通:构建并微调大型语言模型以实现分类任务

news2024/10/8 15:55:03

本章内容

  • 介绍不同的大型语言模型(LLM)微调方法
  • 准备用于文本分类的数据集
  • 修改预训练LLM以便进行微调
  • 微调LLM以识别垃圾信息
  • 评估微调后的LLM分类器的准确性
  • 使用微调后的LLM对新数据进行分类

到目前为止,我们已经编写了LLM的架构、对其进行了预训练,并学习了如何从外部来源(如OpenAI)导入预训练权重到我们的模型中。现在,我们将通过微调LLM用于特定的目标任务(如文本分类)来收获我们的成果。本章的具体示例是将短信分类为“垃圾信息”或“非垃圾信息”。图6.1展示了微调LLM的两种主要方式:用于分类的微调(步骤8)和用于执行指令的微调(步骤9)。

微调的不同类别

微调语言模型的最常见方式是指令微调和分类微调。指令微调通过使用特定的指令训练语言模型,以提高其理解和执行自然语言提示中描述的任务的能力,如图6.2所示。

在分类微调中,如果你有机器学习背景,你可能已经熟悉这个概念,模型被训练以识别一组特定的类别标签,例如“垃圾邮件”和“非垃圾邮件”。分类任务的示例不仅限于LLM和电子邮件过滤,还包括从图像中识别不同种类的植物;将新闻文章分类为体育、政治和技术等主题;以及在医学成像中区分良性和恶性肿瘤。

关键点是,分类微调模型只能预测它在训练期间遇到的类别。例如,它可以判断某些内容是“垃圾邮件”还是“非垃圾邮件”,如图6.3所示,但它无法提供关于输入文本的其他信息。

与图6.3中展示的分类微调模型相比,指令微调模型通常可以执行更广泛的任务。我们可以将分类微调模型视为高度专业化的模型,而通常开发一个在特定任务上表现良好的专业化模型要比开发一个能够在各种任务中表现出色的通用模型更容易。

选择合适的微调方法

指令微调提升模型根据特定用户指令理解和生成响应的能力,最适合需要处理多种任务的模型,以提高其灵活性和交互质量。而分类微调则更适合需要将数据精确分类为预定义类别的项目,如情感分析或垃圾邮件检测。

虽然指令微调更具通用性,但它需要更大的数据集和更多的计算资源来开发能够熟练处理各种任务的模型。相比之下,分类微调所需的数据和计算资源较少,但其应用范围仅限于模型训练时涉及的特定类别。

准备数据集

我们将修改并对之前实现和预训练的GPT模型进行分类微调。首先,我们将下载并准备数据集,如图6.4所示。为了提供一个直观且有用的分类微调示例,我们将使用一个包含垃圾短信和非垃圾短信的文本消息数据集。

注意  文本消息通常是通过手机发送的,而非电子邮件。然而,电子邮件分类的步骤也是相同的,感兴趣的读者可以在附录B找到电子邮件垃圾分类数据集的链接。

第一步是下载数据集。

代码示例 6.1 下载并解压数据集

import urllib.request
import zipfile
import os
from pathlib import Path

url = "https://archive.ics.uci.edu/static/public/228/sms+spam+collection.zip"
zip_path = "sms_spam_collection.zip"
extracted_path = "sms_spam_collection"
data_file_path = Path(extracted_path) / "SMSSpamCollection.tsv"

def download_and_unzip_spam_data(
        url, zip_path, extracted_path, data_file_path):
    if data_file_path.exists():
        print(f"{data_file_path} already存在,跳过下载和解压.")
        return

    with urllib.request.urlopen(url) as response:    #1
        with open(zip_path, "wb") as out_file:
            out_file.write(response.read())

    with zipfile.ZipFile(zip_path, "r") as zip_ref:    #2
        zip_ref.extractall(extracted_path)

    original_file_path = Path(extracted_path) / "SMSSpamCollection"
    os.rename(original_file_path, data_file_path)               #3
    print(f"文件已下载并保存为 {data_file_path}")

download_and_unzip_spam_data(url, zip_path, extracted_path, data_file_path)

#1 下载文件
#2 解压文件
#3 添加 .tsv 文件扩展名

执行上述代码后,数据集将被保存为一个制表符分隔的文本文件,名为SMSSpamCollection.tsv,存储在sms_spam_collection文件夹中。我们可以将其加载到pandas DataFrame中,如下所示:

import pandas as pd
df = pd.read_csv(
    data_file_path, sep="\t", header=None, names=["Label", "Text"]
)
df      #1

#1 在Jupyter notebook中渲染数据框架。或者,使用print(df)来查看数据。

图6.5展示了垃圾短信数据集的数据框架。

让我们检查一下类别标签的分布:

print(df["Label"].value_counts())

执行前面的代码后,我们发现数据集中“ham”(即非垃圾邮件)的数量远远多于“spam”:

Label
ham     4825
spam     747
Name: count, dtype: int64

为了简单起见,同时也因为我们更喜欢较小的数据集(这样可以更快地微调LLM),我们选择对数据集进行欠采样,使每个类别包含747个实例。

注意 还有许多其他方法来处理类别不平衡,但这些超出了本书的范围。感兴趣的读者可以在附录B中找到更多关于处理不平衡数据的方法。

我们可以使用以下代码来进行欠采样并创建一个平衡的数据集。

代码示例 6.2 创建一个平衡的数据集

def create_balanced_dataset(df):
    num_spam = df[df["Label"] == "spam"].shape[0]     #1
    ham_subset = df[df["Label"] == "ham"].sample(
        num_spam, random_state=123
    )                                         #2
    balanced_df = pd.concat([
        ham_subset, df[df["Label"] == "spam"]
    ])                               #3
    return balanced_df

balanced_df = create_balanced_dataset(df)
print(balanced_df["Label"].value_counts())

#1 计算“spam”实例的数量
#2 随机采样“ham”实例以匹配“spam”实例的数量
#3 将“ham”子集与“spam”实例组合

执行完上述代码后,我们可以看到现在数据集中垃圾短信和非垃圾短信的数量相等:

Label
ham     747
spam    747
Name: count, dtype: int64

接下来,我们将字符串形式的类别标签“ham”和“spam”分别转换为整数标签0和1:

balanced_df["Label"] = balanced_df["Label"].map({"ham": 0, "spam": 1})

此过程类似于将文本转换为标记ID。然而,这里我们不是使用包含超过50,000个词的GPT词汇表,而是只处理两个标记ID:0和1。

接下来,我们创建一个random_split函数,将数据集分为三个部分:70%用于训练,10%用于验证,20%用于测试。(这些比例在机器学习中很常见,用于训练、调整和评估模型。)

代码示例 6.3 数据集划分

def random_split(df, train_frac, validation_frac):
    df = df.sample(
        frac=1, random_state=123
    ).reset_index(drop=True)               #1
    train_end = int(len(df) * train_frac)          #2
    validation_end = train_end + int(len(df) * validation_frac) #3
    train_df = df[:train_end]
    validation_df = df[train_end:validation_end]
    test_df = df[validation_end:]
    return train_df, validation_df, test_df

train_df, validation_df, test_df = random_split(
    balanced_df, 0.7, 0.1)                     #4

#1 随机打乱整个DataFrame
#2 计算数据集划分的索引
#3 将DataFrame划分为训练、验证和测试集
#4 测试集的大小为剩余部分,即20%

让我们将数据集保存为CSV(逗号分隔值)文件,以便以后可以重复使用:

train_df.to_csv("train.csv", index=None)
validation_df.to_csv("validation.csv", index=None)
test_df.to_csv("test.csv", index=None)


到目前为止,我们已经下载了数据集,将其平衡,并将其分为训练集和评估集。接下来我们将设置用于训练模型的PyTorch数据加载器。

创建数据加载器

我们将开发与之前处理文本数据时概念上类似的PyTorch数据加载器。之前,我们使用滑动窗口技术生成了大小统一的文本块,然后将它们分组为批次,以提高模型训练的效率。每个块都作为单独的训练实例。然而,现在我们正在处理一个包含不同长度短信的垃圾短信数据集。为了像处理文本块一样批量处理这些短信,我们有两个主要选择:

  1. 将所有消息截断为数据集中或批次中最短消息的长度。
  2. 将所有消息填充到数据集中或批次中最长消息的长度。

第一个选项计算开销较低,但如果较短的消息比平均或最长消息小得多,可能会导致重要信息的丢失,从而降低模型性能。因此,我们选择第二个选项,保留所有消息的完整内容。

为了实现批处理,我们将所有较短的消息填充到与数据集中最长消息相同的长度。为此,我们将添加填充标记"<|endoftext|>"

不过,与其直接将字符串"<|endoftext|>"附加到每条消息后面,我们可以将与"<|endoftext|>"对应的标记ID添加到编码的消息中,如图6.6所示。"<|endoftext|>"的标记ID是50256。我们可以通过使用之前使用的tiktoken包中的GPT-2分词器来对"<|endoftext|>"进行编码,来验证该标记ID是否正确:

import tiktoken
tokenizer = tiktoken.get_encoding("gpt2")
print(tokenizer.encode("<|endoftext|>", allowed_special={"<|endoftext|>"}))


确实,执行前面的代码返回 [50256]。

首先,我们需要实现一个PyTorch Dataset 类,它定义了如何加载和处理数据,之后才能实例化数据加载器。为此,我们定义了 SpamDataset 类,该类实现了图6.6中的概念。SpamDataset 类处理几个关键任务:它识别训练数据集中最长的序列,对短信进行编码,并确保所有其他序列都填充填充标记以匹配最长序列的长度。

代码示例 6.4 设置PyTorch Dataset

import torch
from torch.utils.data import Dataset

class SpamDataset(Dataset):
    def __init__(self, csv_file, tokenizer, max_length=None,
                 pad_token_id=50256):
        self.data = pd.read_csv(csv_file)  #1
        self.encoded_texts = [
            tokenizer.encode(text) for text in self.data["Text"]
        ]

        if max_length is None:
            self.max_length = self._longest_encoded_length()
        else:
            self.max_length = max_length  #2
            self.encoded_texts = [
                encoded_text[:self.max_length]
                for encoded_text in self.encoded_texts
            ]

        self.encoded_texts = [
            encoded_text + [pad_token_id] * 
            (self.max_length - len(encoded_text))
            for encoded_text in self.encoded_texts
        ]  #3

    def __getitem__(self, index):
        encoded = self.encoded_texts[index]
        label = self.data.iloc[index]["Label"]
        return (
            torch.tensor(encoded, dtype=torch.long),
            torch.tensor(label, dtype=torch.long)
        )

    def __len__(self):
        return len(self.data)

    def _longest_encoded_length(self):
        max_length = 0
        for encoded_text in self.encoded_texts:
            encoded_length = len(encoded_text)
            if encoded_length > max_length:
                max_length = encoded_length
        return max_length


#1 对文本进行预编码
#2 如果序列长度超过 max_length,则截断
#3 将序列填充到最长的序列长度

SpamDataset 类从我们之前创建的CSV文件中加载数据,使用 tiktoken 中的 GPT-2 分词器对文本进行分词,并允许我们根据最长序列或预定义的最大长度填充或截断序列。这确保了每个输入张量的大小相同,这对于我们接下来实现的训练数据加载器中的批处理是必要的:

train_dataset = SpamDataset(
    csv_file="train.csv",
    max_length=None,
    tokenizer=tokenizer
)


最长序列的长度存储在数据集的 max_length 属性中。如果你想查看最长序列的标记数量,可以使用以下代码:

print(train_dataset.max_length)


代码输出120,表示最长的序列不超过120个标记,这是短信常见的长度。模型能够处理最多1,024个标记的序列,这是其上下文长度限制。如果数据集中包含更长的文本,可以在创建训练数据集时传递 max_length=1024,以确保数据不会超过模型支持的输入(上下文)长度。

接下来,我们对验证集和测试集进行填充,使其长度与最长的训练序列匹配。重要的是,任何超过最长训练示例长度的验证集和测试集样本都将通过 encoded_text[:self.max_length] 进行截断,这在我们之前定义的 SpamDataset 代码中进行了处理。这个截断是可选的;只要验证集和测试集中的序列不超过1,024个标记,你可以将 max_length=None 应用于这些集:

val_dataset = SpamDataset(
    csv_file="validation.csv",
    max_length=train_dataset.max_length,
    tokenizer=tokenizer
)
test_dataset = SpamDataset(
    csv_file="test.csv",
    max_length=train_dataset.max_length,
    tokenizer=tokenizer
)


练习 6.1 增加上下文长度

将输入填充到模型支持的最大标记数,观察这如何影响预测性能。

使用这些数据集作为输入,我们可以像处理文本数据时一样实例化数据加载器。然而,在这种情况下,目标是类别标签,而不是文本中的下一个标记。例如,如果我们选择批次大小为8,则每个批次将由8个长度为120的训练样本及其对应的类别标签组成,如图6.7所示。

以下代码创建了训练集、验证集和测试集的数据加载器,每次以大小为8的批次加载短信和标签。

代码示例 6.5 创建 PyTorch 数据加载器

from torch.utils.data import DataLoader

num_workers = 0      #1
batch_size = 8
torch.manual_seed(123)

train_loader = DataLoader(
    dataset=train_dataset,
    batch_size=batch_size,
    shuffle=True,
    num_workers=num_workers,
    drop_last=True,
)
val_loader = DataLoader(
    dataset=val_dataset,
    batch_size=batch_size,
    num_workers=num_workers,
    drop_last=False,
)
test_loader = DataLoader(
    dataset=test_dataset,
    batch_size=batch_size,
    num_workers=num_workers,
    drop_last=False,
)


#1 此设置确保与大多数计算机兼容。

为了确保数据加载器正常工作并返回预期大小的批次,我们迭代训练加载器并打印最后一个批次的张量维度:

for input_batch, target_batch in train_loader:
    pass
print("Input batch dimensions:", input_batch.shape)
print("Label batch dimensions", target_batch.shape)


输出结果为:

Input batch dimensions: torch.Size([8, 120])
Label batch dimensions torch.Size([8])


如我们所料,输入批次由八个包含120个标记的训练样本组成。标签张量存储了与这八个训练样本对应的类别标签。

最后,为了了解数据集的大小,我们打印每个数据集中的批次数量:

print(f"{len(train_loader)} training batches")
print(f"{len(val_loader)} validation batches")
print(f"{len(test_loader)} test batches")


每个数据集中的批次数量如下:

130 training batches
19 validation batches
38 test batches


现在我们已经准备好了数据,接下来需要为模型的微调做准备。

初始化具有预训练权重的模型

我们需要为分类微调做好准备,以识别垃圾短信。首先,我们初始化预训练的模型,如图6.8所示。

开始模型准备过程,我们使用了与预训练无标签数据时相同的配置:

CHOOSE_MODEL = "gpt2-small (124M)"
INPUT_PROMPT = "Every effort moves"
BASE_CONFIG = {
    "vocab_size": 50257,          #1  # 词汇表大小
    "context_length": 1024,       #2  # 上下文长度
    "drop_rate": 0.0,             #3  # 丢弃率
    "qkv_bias": True              #4  # 查询-键-值偏差
}
model_configs = {
    "gpt2-small (124M)": {"emb_dim": 768, "n_layers": 12, "n_heads": 12},
    "gpt2-medium (355M)": {"emb_dim": 1024, "n_layers": 24, "n_heads": 16},
    "gpt2-large (774M)": {"emb_dim": 1280, "n_layers": 36, "n_heads": 20},
    "gpt2-xl (1558M)": {"emb_dim": 1600, "n_layers": 48, "n_heads": 25},
}
BASE_CONFIG.update(model_configs[CHOOSE_MODEL])


#1 词汇表大小
#2 上下文长度
#3 丢弃率
#4 查询-键-值偏差

接下来,我们从 gpt_download.py 文件中导入 download_and_load_gpt2 函数,并复用第5章中的 GPTModel 类和 load_weights_into_gpt 函数,将下载的权重加载到 GPT 模型中。

代码示例 6.6 加载预训练的 GPT 模型

from gpt_download import download_and_load_gpt2
from chapter05 import GPTModel, load_weights_into_gpt

model_size = CHOOSE_MODEL.split(" ")[-1].lstrip("(").rstrip(")")
settings, params = download_and_load_gpt2(
    model_size=model_size, models_dir="gpt2"
)

model = GPTModel(BASE_CONFIG)
load_weights_into_gpt(model, params)
model.eval()


将模型权重加载到 GPTModel 后,我们可以复用第4章和第5章中的文本生成工具函数,确保模型能够生成连贯的文本:

from chapter04 import generate_text_simple
from chapter05 import text_to_token_ids, token_ids_to_text

text_1 = "Every effort moves you"
token_ids = generate_text_simple(
    model=model,
    idx=text_to_token_ids(text_1, tokenizer),
    max_new_tokens=15,
    context_size=BASE_CONFIG["context_length"]
)
print(token_ids_to_text(token_ids, tokenizer))


模型生成的文本输出如下,表明模型权重已正确加载:

Every effort moves you forward.
The first step is to understand the importance of your work


在我们将模型微调为垃圾邮件分类器之前,先看看模型能否通过指令已经识别垃圾邮件:

text_2 = (
    "Is the following text 'spam'? Answer with 'yes' or 'no':"
    " 'You are a winner you have been specially"
    " selected to receive $1000 cash or a $2000 award.'"
)
token_ids = generate_text_simple(
    model=model,
    idx=text_to_token_ids(text_2, tokenizer),
    max_new_tokens=23,
    context_size=BASE_CONFIG["context_length"]
)
print(token_ids_to_text(token_ids, tokenizer))


模型输出如下:

Is the following text 'spam'? Answer with 'yes' or 'no': 'You are a winner
you have been specially selected to receive $1000 cash 
or a $2000 award.'
The following text 'spam'? Answer with 'yes' or 'no': 'You are a winner


从输出可以看出,模型在遵循指令方面存在困难。这是预期结果,因为模型只经过了预训练,并未进行指令微调。因此,我们将为模型的分类微调做好准备。

添加分类头

我们需要修改预训练的LLM,以便为分类微调做准备。为此,我们将原来的输出层替换掉,原输出层将隐藏表示映射到50,257的词汇表中,而新输出层则映射到两个类别:0(“非垃圾邮件”)和1(“垃圾邮件”),如图6.9所示。我们使用与之前相同的模型,唯一区别是替换了输出层。

输出层节点

技术上来说,我们可以使用单个输出节点来处理二分类任务。然而,这会要求我们修改损失函数,我在《Losses Learned—Optimizing Negative Log-Likelihood and Cross-Entropy in PyTorch》(mng.bz/NRZ2)中对此进行了讨论。因此,我们选择一种更通用的方法,即输出节点的数量与类别的数量相匹配。例如,对于一个三分类问题,如将新闻文章分类为“科技”、“体育”或“政治”,我们将使用三个输出节点,依此类推。

在尝试图6.9所示的修改之前,我们先通过 print(model) 来打印模型的架构:

GPTModel(
  (tok_emb): Embedding(50257, 768)
  (pos_emb): Embedding(1024, 768)
  (drop_emb): Dropout(p=0.0, inplace=False)
  (trf_blocks): Sequential(
...
    (11): TransformerBlock(
      (att): MultiHeadAttention(
        (W_query): Linear(in_features=768, out_features=768, bias=True)
        (W_key): Linear(in_features=768, out_features=768, bias=True)
        (W_value): Linear(in_features=768, out_features=768, bias=True)
        (out_proj): Linear(in_features=768, out_features=768, bias=True)
        (dropout): Dropout(p=0.0, inplace=False)
      )
      (ff): FeedForward(
        (layers): Sequential(
          (0): Linear(in_features=768, out_features=3072, bias=True)
          (1): GELU()
          (2): Linear(in_features=3072, out_features=768, bias=True)
        )
      )
      (norm1): LayerNorm()
      (norm2): LayerNorm()
      (drop_resid): Dropout(p=0.0, inplace=False)
    )
  )
  (final_norm): LayerNorm()
  (out_head): Linear(in_features=768, out_features=50257, bias=False)
)


这个输出清晰地展示了第4章中设计的模型架构。正如之前讨论的,GPTModel 由嵌入层、12个相同的Transformer块(为简洁起见,只展示了最后一个块),最后是 LayerNorm 和输出层 out_head 组成。

接下来,我们将 out_head 替换为一个新的输出层(见图6.9),并对其进行微调。

微调选定的层 vs. 全部层

由于我们是从一个预训练模型开始,并不需要微调所有的模型层。在基于神经网络的语言模型中,底层通常捕捉的是通用的语言结构和语义,适用于各种任务和数据集。因此,通常只需要微调靠近输出层的最后几层,这些层捕捉的是更具体的语言模式和任务相关的特征。这样不仅能够更高效地适应新任务,还能提高计算效率。感兴趣的读者可以在附录B中找到关于微调哪些层的更多信息和实验。

为了让模型准备好进行分类微调,我们首先冻结模型,即将所有层设置为不可训练:

for param in model.parameters():
    param.requires_grad = False


然后,我们将输出层(model.out_head)替换掉,原输出层将输入映射为50,257维,即词汇表的大小(见图6.9)。

代码示例 6.7 添加分类层

torch.manual_seed(123)
num_classes = 2
model.out_head = torch.nn.Linear(
    in_features=BASE_CONFIG["emb_dim"], 
    out_features=num_classes
)


为了让代码更加通用,我们使用 BASE_CONFIG["emb_dim"],在 “gpt2-small (124M)” 模型中其值为768。因此,我们也可以使用相同的代码来处理更大的GPT-2模型变体。

这个新的 model.out_head 输出层默认情况下其 requires_grad 属性为 True,这意味着它是模型中唯一会在训练期间更新的层。从技术上讲,训练我们刚刚添加的输出层已经足够了。然而,正如我在实验中发现的,微调额外的层可以显著提高模型的预测性能。(有关详细信息,请参阅附录B。)我们还将最后一个Transformer块和连接该块到输出层的 LayerNorm 模块设置为可训练,如图6.10所示。

要让最后的 LayerNorm 和最后一个 transformer block 可训练,我们需要将它们的 requires_grad 属性设置为 True

for param in model.trf_blocks[-1].parameters():
    param.requires_grad = True
for param in model.final_norm.parameters():
    param.requires_grad = True


练习 6.2 微调整个模型

与其仅仅微调最后一个 transformer block,不如微调整个模型,并评估其对预测性能的影响。

即使我们添加了一个新的输出层,并将某些层标记为可训练或不可训练,我们仍然可以像以前一样使用这个模型。例如,我们可以将之前使用的相同文本输入模型:

inputs = tokenizer.encode("Do you have time")
inputs = torch.tensor(inputs).unsqueeze(0)
print("Inputs:", inputs)
print("Inputs dimensions:", inputs.shape)    #1
#1 shape: (batch_size, num_tokens)


打印输出显示,前面的代码将输入编码为包含四个输入 token 的 tensor:

Inputs: tensor([[5211,  345,  423,  640]])
Inputs dimensions: torch.Size([1, 4])


然后,我们可以像往常一样将编码后的 token ID 传递给模型:

with torch.no_grad():
    outputs = model(inputs)
print("Outputs:\n", outputs)
print("Outputs dimensions:", outputs.shape)


输出的 tensor 如下所示:

Outputs:
 tensor([[[-1.5854,  0.9904],
          [-3.7235,  7.4548],
          [-2.2661,  6.6049],
          [-3.5983,  3.9902]]])
Outputs dimensions: torch.Size([1, 4, 2])


类似的输入以前会生成一个形状为 [1, 4, 50257] 的输出 tensor,其中 50257 表示词汇表的大小。输出行的数量对应于输入 token 的数量(在本例中为四个)。然而,由于我们替换了模型的输出层,输出的嵌入维度(列数)现在是 2,而不是 50,257。

请记住,我们的目的是微调该模型,以返回一个表示输入是 “spam” 还是 “not spam” 的类别标签。我们不需要微调所有四个输出行,而是可以专注于一个输出 token。特别是,我们将重点关注对应于最后一个输出 token 的最后一行,如图6.11所示。

要从输出 tensor 中提取最后一个输出 token,可以使用以下代码:

print("Last output token:", outputs[:, -1, :])


输出结果为:

Last output token: tensor([[-3.5983,  3.9902]])


我们仍然需要将这些值转换为类别标签的预测结果。但是首先,让我们理解为什么特别关注最后一个输出 token。

我们之前已经探索了注意力机制,它建立了每个输入 token 与其他所有输入 token 之间的关系,并且我们讨论了在类似 GPT 模型中常用的因果注意力掩码的概念(参见第 3 章)。这种掩码限制了一个 token 的关注范围,仅限于其当前位置及之前的位置,确保每个 token 只能受到其自身和之前 token 的影响,如图 6.12 所示。

由于图 6.12 中的因果注意力掩码设置,序列中的最后一个 token 累积了最多的信息,因为它是唯一可以访问所有之前 token 数据的 token。因此,在垃圾邮件分类任务中,我们在微调过程中关注这个最后的 token。

现在我们已经准备好将最后的 token 转换为类别标签预测,并计算模型的初始预测准确率。接下来,我们将微调模型以完成垃圾邮件分类任务。

练习 6.3:微调第一个 vs. 最后一个 token

尝试微调第一个输出 token。注意与微调最后一个输出 token 相比,预测性能的变化。

在这里插入图片描述

大模型&AI产品经理如何学习

求大家的点赞和收藏,我花2万买的大模型学习资料免费共享给你们,来看看有哪些东西。

1.学习路线图

在这里插入图片描述

第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;

第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;

第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;

第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;

第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;

第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;

第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。

2.视频教程

网上虽然也有很多的学习资源,但基本上都残缺不全的,这是我自己整理的大模型视频教程,上面路线图的每一个知识点,我都有配套的视频讲解。

在这里插入图片描述

在这里插入图片描述

(都打包成一块的了,不能一一展开,总共300多集)

因篇幅有限,仅展示部分资料,需要点击下方图片前往获取

3.技术文档和电子书

这里主要整理了大模型相关PDF书籍、行业报告、文档,有几百本,都是目前行业最新的。
在这里插入图片描述

4.LLM面试题和面经合集

这里主要整理了行业目前最新的大模型面试题和各种大厂offer面经合集。
在这里插入图片描述

👉学会后的收获:👈
• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;

• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;

• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;

• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。
在这里插入图片描述

1.AI大模型学习路线图
2.100套AI大模型商业化落地方案
3.100集大模型视频教程
4.200本大模型PDF书籍
5.LLM面试题合集
6.AI产品经理资源合集

👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2196703.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小程序和h5深度分析

你写过小程序/H5&#xff0c;那你知道他们的区别在哪里吗&#xff1f; 为什么说小程序的性能通常优于 H5? 小程序能访问到 DOM 对象吗&#xff1f; 小程序的原理是什么&#xff1f; 小程序和 H5 都是轻量级的、可直接在移动设备上运行的应用&#xff0c;但它们之间存在一些关…

C语言的类型提升机制

概念 在C语言中&#xff0c;整数类型按照其大小可以分为以下几类&#xff08;从小到大&#xff09;&#xff1a; charshortintlonglong long 当在表达式中涉及这些类型的混合运算时&#xff0c;较小的类型会被提升为较大的类型。具体规则如下&#xff1a; ①char 和 short …

【NLP自然语言处理】02 - NLP简介/NLP发展历史/应用场景

1.什么是自然语言处理 (NLP) 自然语言处理&#xff08;Natural Language Processing, NLP&#xff09;是人工智能的一个重要分支&#xff0c;旨在通过计算机实现对人类自然语言的理解、生成和互动。其核心任务包括分析、生成和转换人类语言&#xff0c;涉及语法、语义、语音识…

Mapsui绘制WKT的示例

步骤 创建.NET Framework4.8的WPF应用在NuGet中安装Mapsui.Wpf 4.1.7添加命名空间和组件 <Window x:Class"TestMapsui.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winf…

基于单片机的烧水壶系统设计

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于STC89C52RC单片机&#xff0c;采用四个按键&#xff0c;通过DS18B20检测温度&#xff0c;开机显示实时温度 第一个按键为切换功能按键&#xff0c;按下后&#xff0c;可以设置烧水温度的大小&…

codetop标签双指针题目大全解析(三),双指针刷穿地心!!!!!

复习比学习更重要&#xff0c;更需要投入时间&#xff0c;更需要花费精力 1.字符串的排列2.找出字符串中第一个匹配的下标3.最大连续1的个数II4.数组中的山脉5.移除元素6.两个数组的交集II7.有序数组的平方8.删除有序数组中的重复项II9.寻找重复数10.水果成篮 1.字符串的排列 …

HUAWEI_HCIA_实验指南_Lib1.4_配置通过Telnet登录系统

一、原理概述 Telnet(Telecommunication Network Protocol)起源于ARPANET,是最早的Internet应用之一。 Telnet 通常用在远程登录应用中&#xff0c;以便对本地或远端运行的网络设备进行配置、监控和维护。如网络中有多台设备需要配置和管理&#xff0c;用户无需为每一台设备…

C++ 算法学习——7.4.1 优化算法——双指针

双指针法&#xff08;Two Pointers&#xff09;是一种常用的算法技巧&#xff0c;通常用于解决数组或链表中的问题。这种技巧通过维护两个指针&#xff0c;通常分别指向数组或链表的不同位置&#xff0c;来协同解决问题。双指针法一般有两种类型&#xff1a;快慢指针和左右指针…

查询计算移出数据库用 Java 太慢咋办

很多现代应用会把数据计算和处理任务从数据库移出来采用 Java 实现&#xff0c;这样能获得架构上的好处&#xff0c;而且 Java 有完善过程处理能力&#xff0c;应对日益复杂的业务逻辑比 SQL 更得心应手&#xff08;虽然代码不短&#xff09;。不过&#xff0c;我们常常会发现&…

为什么没有能够处理 Python 字节码的 CPU?

问题 有没有能够处理Python字节码&#xff08;Python bytecode&#xff09;的CPU&#xff1f;众所周知&#xff0c;CPU靠执行字节码指令运作。那有没有能够处理Python字节码&#xff08;Python bytecode&#xff09;的CPU&#xff1f;如果没有&#xff0c;为什么&#xff1f;不…

OpenHarmony(鸿蒙南向开发)——轻量系统内核(LiteOS-M)【内存管理】

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ 持续更新中…… 基本概念 内存管理模块管理系统的内存资源&#xff0c;它是操作系…

LeetCode 刷题基础 -- 模板原型Ⅰ

模板原型 - 基础篇 学习网站一、进制转换二、二分查找① 查找指定元素② 查找第一个大于等于 x 值的序列下标③ 查找第一个大于 x 值的序列下标④ 单峰序列 三、双指针① 两数之和② 序列合并③ 集合求交④ 集合求并 四、其他高效技巧与算法① 区间和② 01 对③ 左小数 五、数学…

【每日刷题】Day134

【每日刷题】Day134 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 1218. 最长定差子序列 - 力扣&#xff08;LeetCode&#xff09; 2. LCR 116. 省份数量 - 力扣&…

掌握这17个Python自动化操作,简化你的日常工作流程,提升工作效率!

Python是一种流行的编程语言&#xff0c;以其简单性和可读性而闻名。因其能够提供大量的库和模块&#xff0c;它成为了自动化各种任务的绝佳选择。让我们进入自动化的世界&#xff0c;探索17个可以简化工作并节省时间精力的Python脚本。 目录&#xff08;上篇&#xff09; 1.自…

小型数控铣床助力职业教育教学模式

小型数控铣床是在普通铣床上集成了数字控制系统&#xff0c;可以在程序代码的控制下较精确地进行铣削加工的机床。与普通小型铣床相比&#xff0c;小型数控铣床通过数字控制系统实现了自动化加工&#xff0c;提高了加工精度和效率。 小型数控铣床的引入推动了教育装备的现代化进…

基于单片机的书库环境监测

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;采用DHT11湿度传感器检测湿度&#xff0c;DS18B20温度传感器检测温度&#xff0c; 采用滑动变阻器连接数模转换器模拟二氧化碳和氧气浓度检测&#xff0c;各项数值通过lc…

Trickle流量限速工具使用示例

简介&#xff1a;trickle 是一个轻量级的流量限速工具&#xff0c;允许用户限制应用程序的网络带宽使用&#xff0c;以便更好地管理网络资源和优化网络性能。本文将介绍 trickle 的安装和使用方法&#xff0c;并通过 Python 封装示例展示如何使用 trickle 控制网络带宽。 历史…

关于PPT生成的开源大模型总结

目前需要开源的PPT生成模型&#xff0c;在这里对github上的一些模型进行筛选 搜索关键词&#xff1a;ppt generate&#xff08;more starts&#xff09; williamfzc/chat-gpt-ppt: 支持直接生成PPT支持中英文需要调用ChatGPT&#xff08;Add your token (official openai api k…

使用正则表达式删除文本的奇数行或者偶数行

用智谱清言和kimi搜出来的结果都没法在notepad生效&#xff0c;后面在overflow上找到的答案比较靠谱。 查找&#xff1a;^[^\n]*\n([^\n]*) 替换&#xff1a;\1 删除偶数行 查找&#xff1a;^([^\n]*)\n[^\n]* 替换&#xff1a;\1 代码解释 ^&#xff1a;这个符号代表字符…

Excel日期导入数据库变为数字怎么办

在Excel导入到数据库的过程中&#xff0c;经常会碰到Excel里面的日期数据&#xff0c;导进去过后变成了数字。 如下图&#xff1a; 使用navicate等数据库编辑器导入数据库后&#xff1a; 原因分析&#xff1a;这是因为日期和时间在excel中都是以数字形式存储的&#xff0c;这个…