从0开始LLM-词嵌入(embedding)-2

61 阅读3分钟

1. 字节对编码(BPE)

LLM训练使用的分词方法叫做字节对编码(BPE)

pip install tiktoken

from importlib.metadata import version
import tiktoken
print("tiktoken version:", version("tiktoken"))
#输出
tiktoken version:0.5.1

初始化BPE分词器

tokenizer = tiktoken.get_encoding("gpt2")

text = "Hello, do you like tea? <|endoftext|> In the sunlit terraces of some"
integers = tokenizer.encode(text, allowed_special={"<|endoftext|>"})
print(integers)
#输出
[15496, 11, 466, 345, 588, 8887, 30, 220, 50256, 554, 262, 4252,


strings = tokenizer.decode(integers)
print(strings)

'Hello, do you like tea? <|endoftext|> In the sunlit terraces of
  • <|endoftext|>词元(token)被赋值了一个很大的token ID,例如,50256。事实上,被用于训练诸如GPT-2,GPT-3以及被ChatGPT使用的原始模型的BPE分词器,总计词汇的规模是50257,其中<|endoftext|>被指定为最大的token ID。
  • 上述的BPE分词器可以正确的解码和编码没有见过的词汇,例如"someunknownPlace"。BPE解码器可以处理任何没有见过的词汇。那么,他是怎么无需使用<|unk|>词元就做到这个的呢?
    BPE使用的算法会将不在预定义词表里的单词分解为更小的子单词单元或者甚至是独立的字母,使BPE可以处理词表外的单词。所以,基于这种算法,如果分词器在分词时遇到了不熟悉的单词,他会使用一系列的子单词词元或者字母来替换它。

image.png

2. 滑动窗口进行数据采样

在创建 LLM 的 Embedding 之前,需要生成训练 LLM 所需的输入-目标(input-target)对。 LLM 是通过预测文本中的下一个单词来进行预训练的,如图 2.1 所示。

image.png 如图所示,给定一个文本样本,提取输入块作为 LLM 的输入子样本,LLM 在训练期间的任务是预测输入块之后的下一个单词。在训练过程中,会屏蔽掉目标词之后的所有单词。请注意,在 LLM 处理文本之前,该文本已经进行 token 化,为了便于说明,此图省略了 token 化步骤。

image.png 为了实现高效的数据加载器,我们将所有输入存储到一个名为 x 的张量中,其中每一行都代表一个输入上下文。同时,我们创建了另一个名为 y 的张量,用于存储对应的预测目标(即下一个单词),这些目标是通过将输入内容向右移动一个位置得到的。

import torch
from torch.utils.data import Dataset, DataLoader
class GPTDatasetV1(Dataset):
    def __init__(self, txt, tokenizer, max_length, stride):
        self.tokenizer = tokenizer
        self.input_ids = []
        self.target_ids = []
        token_ids = tokenizer.encode(txt) #A
        for i in range(0, len(token_ids) - max_length, stride): #B
            input_chunk = token_ids[i:i + max_length]
            target_chunk = token_ids[i + 1: i + max_length + 1]
            self.input_ids.append(torch.tensor(input_chunk))
            self.target_ids.append(torch.tensor(target_chunk))

    def __len__(self): #C
        return len(self.input_ids)
    def __getitem__(self, idx): #D
        return self.input_ids[idx], self.target_ids[idx]

构建了一个名为GPTDatasetV1的类,它是 PyTorch 的Dataset类的子类。此类规定了如何从数据集中抽取单个样本,每个样本包含一定数量的 token ID,这些 ID 存储在input_chunk张量中(数量由max_length参数决定)。并用target_chunk张量保存与输入相对应的目标。

def create_dataloader_v1(txt, batch_size=4,
        max_length=256, stride=128, shuffle=True, drop_last=True):
    tokenizer = tiktoken.get_encoding("gpt2") #A
    dataset = GPTDatasetV1(txt, tokenizer, max_length, stride) #B
    dataloader = DataLoader(
        dataset, batch_size=batch_size, shuffle=shuffle, drop_last=drop_last) #C
    return dataloader

image.png 在从输入数据集创建多个批次的过程中,我们会在文本上滑动一个输入窗口。如果步长设定为 1,那么在生成下一个批次时,我们会将输入窗口向右移动 1 个位置。如果步长设定为输入窗口的大小,那么就可以避免批次之间的重叠。