2024-10-31
Learning "Build a Large Language Model (From Scratch)" Chapter 02

2.1 Understanding word embeddings

2.2 Tokenizing text

2.3 Converting tokens into token IDs

2.4 Adding special context tokens

2.5 Byte pair encoding

2.6 Data sampling with a sliding window

The previous section covered the tokenization steps and conversion from string tokens into integer token IDs in great detail. The next step before we can finally create the embeddings for the LLM is to generate the input-target pairs required for training an LLM.
前一节详细介绍了分词步骤和将字符串词元转换为整数词元ID的过程。在最终为LLM创建嵌入之前,下一步是生成训练LLM所需的输入-目标对。

What do these input-target pairs look like? As we learned in chapter 1, LLMs are pretrained by predicting the next word in a text, as depicted in figure 2.12.
这些输入-目标对是什么样的?正如我们在第1章中学到的,LLM通过预测文本中的下一个单词进行预训练,如图2.12所示。

Figure 2.12 Given a text sample, extract input blocks as subsamples that serve as input to the LLM, and the LLM’s prediction task during training is to predict the next word that follows the input block. During training, we mask out all words that are past the target. Note that the text shown in this figure would undergo tokenization before the LLM can process it; however, this figure omits the tokenization step for clarity.
图2.12 给定一个文本样本,提取输入块作为子样本,作为LLM的输入,LLM在训练期间的预测任务是预测紧跟在输入块后的下一个单词。在训练期间,我们屏蔽了所有超出目标的单词。请注意,图中显示的文本在LLM处理之前会进行分词;然而,为了清晰起见,本图省略了分词步骤。

In this section we implement a data loader that fetches the input-target pairs depicted in Figure 2.12 from the training dataset using a sliding window approach.
在本节中,我们实现了一个数据加载器,该加载器使用滑动窗口方法从训练数据集中获取图2.12中描绘的输入-目标对。

To get started, we will first tokenize the whole The Verdict short story we worked with earlier using the BPE tokenizer introduced in the previous section:
首先,我们将使用上一节介绍的BPE分词器对整个The Verdict短篇小说进行分词:

1
2
3
4
5
6
with open("the-verdict.txt", "r", encoding="utf-8") as f:  # 打开文本文件
raw_text = f.read() # 读取文本内容

enc_text = tokenizer.encode(raw_text) # 对文本进行编码
print(len(enc_text)) # 打印编码后的词元数量
# 执行上述代码将返回5145,这是应用BPE分词器后的训练集中词元的总数量。

Executing the code above will return 5145, the total number of tokens in the training set, after applying the BPE tokenizer.
执行上述代码将返回5145,这是应用BPE分词器后的训练集中词元的总数量。

Next, we remove the first 50 tokens from the dataset for demonstration purposes as it results in a slightly more interesting text passage in the next steps:
接下来,我们从数据集中移除前50个词元以进行演示,因为这会在接下来的步骤中生成一个稍微有趣的文本段落:

1
enc_sample = enc_text[50:]  # 移除前50个词元

One of the easiest and most intuitive ways to create the input-target pairs for the next-word prediction task is to create two variables, x and y, where x contains the input tokens and y contains the targets, which are the inputs shifted by 1:
创建输入-目标对进行下一个单词预测任务的最简单和最直观的方法之一是创建两个变量,x 和 y,其中 x 包含输入词元,y 包含目标,即输入右移1位:

1
2
3
4
5
6
7
8
context_size = 4  # 上下文大小确定输入中包含的词元数量  #A
x = enc_sample[:context_size] # 获取上下文大小的输入
y = enc_sample[1:context_size+1] # 获取右移1位的目标
print(f"x: {x}") # 打印输入
print(f"y: {y}") # 打印目标
# 运行上述代码打印以下输出:
# x: [290, 4920, 2241, 287]
# y: [4920, 2241, 287, 257]

Running the above code prints the following output:
运行上述代码打印以下输出:

1
2
3
x: [290, 4920, 2241, 287]
y: [4920, 2241, 287, 257]
# 处理包含目标的输入,目标是输入右移一个位置,然后我们可以创建图2.12中所描绘的下一个单词预测任务,如下所示:

Processing the inputs along with the targets, which are the inputs shifted by one position, we can then create the next-word prediction tasks depicted earlier in figure 2.12, as follows:
处理包含目标的输入,目标是输入右移一个位置,然后我们可以创建图2.12中所描绘的下一个单词预测任务,如下所示:

1
2
3
4
5
6
7
8
9
for i in range(1, context_size+1):  # 遍历上下文大小
context = enc_sample[:i] # 获取当前上下文
desired = enc_sample[i] # 获取目标词元
print(context, "---->", desired) # 打印上下文和目标
# 上述代码打印以下内容:
# [290] ----> 4920
# [290, 4920] ----> 2241
# [290, 4920, 2241] ----> 287
# [290, 4920, 2241, 287] ----> 257

The code above prints the following:
上述代码打印以下内容:

1
2
3
4
5
[290] ----> 4920
[290, 4920] ----> 2241
[290, 4920, 2241] ----> 287
[290, 4920, 2241, 287] ----> 257
# 箭头(---->)左边的所有内容表示LLM将接收的输入,箭头右边的词元ID表示LLM应预测的目标词元ID。

Everything left of the arrow (—->) refers to the input an LLM would receive, and the token ID on the right side of the arrow represents the target token ID that the LLM is supposed to predict.
箭头(—->)左边的所有内容表示LLM将接收的输入,箭头右边的词元ID表示LLM应预测的目标词元ID。

For illustration purposes, let’s repeat the previous code but convert the token IDs into text:
为了说明,我们重复前面的代码,但将词元ID转换为文本:

1
2
3
4
5
6
7
8
9
for i in range(1, context_size+1):  # 遍历上下文大小
context = enc_sample[:i] # 获取当前上下文
desired = enc_sample[i] # 获取目标词元
print(tokenizer.decode(context), "---->", tokenizer.decode([desired])) # 打印解码后的上下文和目标
# 下面的输出显示了输入和输出在文本格式中的样子:
# and ----> established
# and established ----> himself
# and established himself ----> in
# and established himself in ----> a

The following outputs show how the input and outputs look in text format:
下面的输出显示了输入和输出在文本格式中的样子:

1
2
3
4
5
and ----> established
and established ----> himself
and established himself ----> in
and established himself in ----> a
# 我们现在已经创建了输入-目标对,可以将它们用于LLM训练。

We’ve now created the input-target pairs that we can turn into use for the LLM training in upcoming chapters.
我们现在已经创建了输入-目标对,可以将它们用于LLM训练。

There’s only one more task before we can turn the tokens into embeddings, as we mentioned at the beginning of this chapter: implementing an efficient data loader that iterates over the input dataset and returns the inputs and targets as PyTorch tensors, which can be thought of as multidimensional arrays.
在我们将词元转换为嵌入之前,还有最后一个任务:实现一个高效的数据加载器,它遍历输入数据集并将输入和目标作为PyTorch张量返回,可以将其视为多维数组。

In particular, we are interested in returning two tensors: an input tensor containing the text that the LLM sees and a target tensor that includes the targets for the LLM to predict, as depicted in Figure 2.13.

特别是,我们希望返回两个张量:一个包含LLM看到的文本的输入张量,另一个包含LLM要预测的目标的目标张量,如图2.13所示。

Figure 2.13 To implement efficient data loaders, we collect the inputs in a tensor, x, where each row represents one input context. A second tensor, y, contains the corresponding prediction targets (next words), which are created by shifting the input by one position.

图2.13 为了实现高效的数据加载器,我们将输入收集到一个张量x中,每行表示一个输入上下文。第二个张量y包含相应的预测目标(下一个单词),通过将输入右移一个位置创建。

While Figure 2.13 shows the tokens in string format for illustration purposes, the code implementation will operate on token IDs directly since the encode method of the BPE tokenizer performs both tokenization and conversion into token IDs as a single step.

虽然图2.13出于说明目的以字符串格式显示词元,但代码实现将直接对词元ID进行操作,因为BPE分词器的编码方法将分词和转换为词元ID作为一个步骤执行。

For the efficient data loader implementation, we will use PyTorch’s built-in Dataset and DataLoader classes. For additional information and guidance on installing PyTorch, please see section A.1.3, Installing PyTorch, in Appendix A.

为了实现高效的数据加载器,我们将使用PyTorch的内置Dataset和DataLoader类。有关安装PyTorch的更多信息和指导,请参见附录A的A.1.3节“安装PyTorch”。

Listing 2.5 A dataset for batched inputs and targets
清单2.5 用于批处理输入和目标的数据集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import torch  # 导入torch
from torch.utils.data import Dataset, DataLoader # 从torch.utils.data导入Dataset和DataLoader

class GPTDatasetV1(Dataset): # 定义GPTDatasetV1类,继承自Dataset
def __init__(self, txt, tokenizer, max_length, stride): # 初始化方法
self.input_ids = [] # 初始化输入ID列表
self.target_ids = [] # 初始化目标ID列表

token_ids = tokenizer.encode(txt) # 对文本进行分词

for i in range(0, len(token_ids) - max_length, stride): # 使用滑动窗口将文本分块
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): # 返回数据集的长度
return len(self.input_ids) # 返回输入ID的长度

def __getitem__(self, idx): # 获取指定索引的数据
return self.input_ids[idx], self.target_ids[idx] # 返回输入和目标的张量

The GPTDatasetV1 class in listing 2.5 is based on the PyTorch Dataset class and defines how individual rows are fetched from the dataset, where each row consists of a number of token IDs (based on a max_length) assigned to an input_chunk tensor. The target_chunk tensor contains the corresponding targets. I recommend reading on to see how the data returned from this dataset looks like when we combine the dataset with a PyTorch DataLoader – this will bring additional intuition and clarity.

清单2.5中的GPTDatasetV1类基于PyTorch的Dataset类,定义了如何从数据集中获取单个行,其中每行由分配给input_chunk张量的一定数量的词元ID(基于max_length)组成。target_chunk张量包含相应的目标。我建议继续阅读以了解当我们将数据集与PyTorch的DataLoader结合使用时,从该数据集中返回的数据是什么样子的,这将带来更多的直观理解和清晰度。

If you are new to the structure of PyTorch Dataset classes, such as shown in listing 2.5, please read section A.6, Setting up efficient data loaders, in Appendix A, which explains the general structure and usage of PyTorch Dataset and DataLoader classes.

如果你不熟悉PyTorch的Dataset类的结构,如清单2.5所示,请阅读附录A的A.6节“设置高效的数据加载器”,其中解释了PyTorch的Dataset和DataLoader类的一般结构和用法。

The following code will use the GPTDatasetV1 to load the inputs in batches via a PyTorch DataLoader:

以下代码将使用GPTDatasetV1通过PyTorch的DataLoader按批次加载输入:

Listing 2.6 A data loader to generate batches with input-with pairs

清单2.6 一个用于生成输入-目标对批处理的数据加载器

1
2
3
4
5
6
7
8
9
10
11
12
def create_dataloader_v1(txt, batch_size=4, max_length=256,  # 创建数据加载器
stride=128, shuffle=True, drop_last=True, num_workers=0):
tokenizer = tiktoken.get_encoding("gpt2") # 初始化分词器
dataset = GPTDatasetV1(txt, tokenizer, max_length, stride) # 创建数据集
dataloader = DataLoader( # 创建数据加载器
dataset,
batch_size=batch_size,
shuffle=shuffle,
drop_last=drop_last, # 如果最后一个批次小于指定批次大小,则丢弃
num_workers=num_workers # 使用的CPU进程数量
)
return dataloader # 返回数据加载器

Let’s test the dataloader with a batch size of 1 for an LLM with a context size of 4 to develop an intuition of how the GPTDatasetV1 class from listing 2.5 and the create_dataloader_v1 function from listing 2.6 work together:

让我们使用批次大小为1的数据加载器来测试具有上下文大小为4的LLM,以了解清单2.5中的GPTDatasetV1类和清单2.6中的create_dataloader_v1函数如何协同工作:

1
2
3
4
5
6
7
8
9
10
11
with open("the-verdict.txt", "r", encoding="utf-8") as f:  # 打开文本文件
raw_text = f.read() # 读取文本内容

dataloader = create_dataloader_v1( # 创建数据加载器
raw_text, batch_size=1, max_length=4, stride=1, shuffle=False
)
data_iter = iter(dataloader) # 将数据加载器转换为Python迭代器 #A
first_batch = next(data_iter) # 获取下一个批次的数据
print(first_batch) # 打印第一个批次的数据
# 执行上述代码打印以下内容:
# (tensor([[ 40, 367, 2885, 1464]]), tensor([[ 367, 2885, 1464, 1807]]))

Executing the preceding code prints the following:

执行上述代码打印以下内容:

1
2
(tensor([[  40,  367, 2885, 1464]]), tensor([[ 367, 2885, 1464, 1807]]))
# first_batch变量包含两个张量:第一个张量存储输入词元ID,第二个张量存储目标词元ID。

The first_batch variable contains two tensors: the first tensor stores the input token IDs, and the second tensor stores the target token IDs. Since the max_length is set to 4, each of the two tensors contains 4 token IDs. Note that an input size of 4 is relatively small and only chosen for illustration purposes. It is common to train LLMs with input sizes of at least 256.

first_batch变量包含两个张量:第一个张量存储输入词元ID,第二个张量存储目标词元ID。由于max_length设置为4,每个张量包含4个词元ID。请注意,4的输入大小相对较小,仅用于说明目的。训练LLM时通常使用至少256的输入大小。

To illustrate the meaning of stride=1, let’s fetch another batch from this dataset:

为了说明stride=1的含义,让我们从这个数据集中获取另一个批次:

1
2
3
4
second_batch = next(data_iter)  # 获取下一个批次的数据
print(second_batch) # 打印第二个批次的数据
# 第二个批次包含以下内容:
# (tensor([[ 367, 2885, 1464, 1807]]), tensor([[2885, 1464, 1807, 3619]]))

The second batch has the following contents:
第二个批次包含以下内容:

1
2
(tensor([[  367, 2885, 1464, 1807]]), tensor([[2885, 1464, 1807, 3619]]))
# 如果我们将第一个批次与第二个批次进行比较,可以看到第二个批次的词元ID与第一个批次相比右移了一个位置

If we compare the first with the second batch, we can see that the second batch’s token IDs are shifted by one position compared to the first batch (for example, the second ID in the first batch’s input is 367, which is the first ID of the second batch’s input). The stride setting dictates the number of positions the inputs shift across batches, emulating a sliding window approach, as demonstrated in Figure 2.14.

如果我们将第一个批次与第二个批次进行比较,可以看到第二个批次的词元ID与第一个批次相比右移了一个位置(例如,第一个批次输入的第二个ID是367,这是第二个批次输入的第一个ID)。stride设置决定了输入在批次之间移动的位置数,模拟滑动窗口方法,如图2.14所示。

Figure 2.14 When creating multiple batches from the input dataset, we slide an input window across the text. If the stride is set to 1, we shift the input window by 1 position when creating the next batch. If we set the stride equal to the input window size, we can prevent overlaps between the batches.

图2.14 当从输入数据集创建多个批次时,我们在文本中滑动输入窗口。如果stride设置为1,则在创建下一个批次时将输入窗口右移1个位置。如果我们将stride设置为等于输入窗口大小,可以防止批次之间的重叠。

image-20241124014352590

EXERCISE 2.2 DATA LOADERS WITH DIFFERENT STRIDES AND CONTEXT SIZES
练习 2.2 具有不同stride和context大小的数据加载器

To develop more intuition for how the data loader works, try to run it with different settings such as max_length=2 and stride=2 and max_length=8 and stride=2.

为了更好地理解数据加载器的工作原理,请尝试使用不同的设置运行它,例如max_length=2和stride=2以及max_length=8和stride=2。

Batch sizes of 1, such as we have sampled from the data loader so far, are useful for illustration purposes. If you have previous experience with deep learning, you may know that small batch sizes require less memory during training but lead to more noisy model updates. Just like in regular deep learning, the batch size is a trade-off and hyperparameter to experiment with when training LLMs.

批次大小为1,如我们迄今从数据加载器中采样的那些,非常适合作为说明。如果你有深度学习的经验,你可能知道小批次大小在训练期间需要更少的内存,但会导致更噪声的模型更新。就像在常规深度学习中一样,批次大小是一种权衡和超参数,在训练LLM时需要进行实验。

Before we move on to the two final sections of this chapter that are focused on creating the embedding vectors from the token IDs, let’s have a brief look at how

we can use the data loader to sample with a batch size greater than 1:

在我们进入本章的最后两个部分之前,这两个部分重点是从词元ID创建嵌入向量,让我们简要了解如何使用数据加载器以大于1的批次大小进行采样:

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
dataloader = create_dataloader_v1(raw_text, batch_size=8, max_length=4, stride=4)

data_iter = iter(dataloader) # 将数据加载器转换为Python迭代器
inputs, targets = next(data_iter) # 获取下一个批次的数据
print("Inputs:\n", inputs) # 打印输入
print("\nTargets:\n", targets) # 打印目标
# 上面的代码打印以下内容:
# Inputs:
# tensor([[ 40, 367, 2885, 1464],
# [ 1807, 3619, 402, 271],
# [10899, 2138, 257, 7026],
# [15632, 438, 2016, 257],
# [ 922, 5891, 1576, 438],
# [ 568, 340, 373, 645],
# [ 1049, 5975, 284, 502],
# [ 284, 3285, 326, 11]])
#
# Targets:
# tensor([[ 367, 2885, 1464, 1807],
# [ 3619, 402, 271, 10899],
# [ 2138, 257, 7026, 15632],
# [ 438, 2016, 257, 922],
# [ 5891, 1576, 438, 568],
# [ 340, 373, 645, 1049],
# [ 5975, 284, 502, 284],
# [ 3285, 326, 11, 287]])

Note that we increase the stride to 4. This is to utilize the data set fully (we don’t skip a single word) but also avoid any overlap between the batches, since more overlap could lead to increased overfitting.

注意,我们将步幅增加到4。这是为了充分利用数据集(我们不会跳过一个单词),但也避免了批次之间的任何重叠,因为更多的重叠可能导致过拟合的增加。

In the final two sections of this chapter, we will implement embedding layers that convert the token IDs into continuous vector representations, which serve as input data format for LLMs.

在本章的最后两节中,我们将实现嵌入层,将词元ID转换为连续的向量表示,作为LLM的输入数据格式。

2.7 Creating token embeddings

2.8 Encoding word positions

Read More

2024-09-29
Learning "Build a Large Language Model (From Scratch)"

《Build a Large Language Model (From Scratch)》


Setup

参考

  • setup/01_optional-python-setup-preferences
  • .setup/02_installing-python-libraries

按照步骤配置环境:

1
2
3
4
5
6
git clone --depth 1 https://github.com/rasbt/LLMs-from-scratch.git
cd LLMs-from-scratch
conda create -n LLMs python=3.10
conda activate LLMs
conda install jupyterlab watermark
pip install -r requirements.txt

在这里遇到了以下问题:

image-20240929162323569

解决方案:

1
hash -r
image-20240929162500733

解释:

  • hash 是一个 Bash 内建命令,用于查找并记住命令的位置。如果你在安装了新的软件之后,想要立即使用它,但是 Bash 仍然使用旧的命令,那么你可以使用 hash -r 命令来刷新 Bash 的命令缓存。

Chapter 01

Chapter 02

目录:

  • 2.1 Understanding word embeddings
  • 2.2 Tokenizing text
  • 2.3 Converting tokens into token IDs
  • 2.4 Adding special context tokens
  • 2.5 Byte pair encoding
  • 2.6 Data sampling with a sliding window
  • 2.7 Creating token embeddings
  • 2.8 Encoding word positions
  • 2.9 Summary

背景知识

  1. 什么是Token?
  • Token是指文本的最小单位,可以是一个字、一个单词或一个子词。
    • 例如,句子 “I love NLP” 可以被分解为 [“I”, “love”, “NLP”]。
  1. 什么是Token ID?
    • 每个Token会被映射到一个唯一的数字ID。
    • 假设我们有一个词汇表:[“I”, “love”, “NLP”, “AI”],其中:
    • “I” 的ID是0
    • “love” 的ID是1
    • “NLP” 的ID是2
    • “AI” 的ID是3
  2. 为什么需要嵌入(Embedding)?
    • 模型无法直接处理文字或数字ID,我们需要将这些ID转为具有实际意义的向量(连续值表示)。
    • 比如,“NLP”的ID是2,我们可能需要一个三维向量 [1.2, -0.4, 0.6] 来表示它。

2.6 数据采样与滑动窗口

1
dataloader = create_dataloader_v1(raw_text, batch_size=8, max_length=4, stride=4)

其中参数:

  • raw_text 是原始文本。
  • batch_size 是批量大小。
  • max_length 是滑动窗口的大小。
  • stride 是滑动窗口的步长。

2.7 Token Embedding 创建

步骤1:定义Token ID和词汇表

假设我们有4个Token,其ID是 [2, 3, 5, 1],词汇表的大小为6。

import torch

input_ids = torch.tensor([2, 3, 5, 1]) # Token的ID
vocab_size = 6 # 假设词汇表有6个单词
output_dim = 3 # 我们希望嵌入是3维向量

- input_ids 是一个张量,表示我们的输入文本。
- vocab_size 是词汇表的大小,也就是可能的Token ID的总数。
- output_dim 是嵌入向量的维度。

步骤2:创建嵌入层

torch.manual_seed(123) # 设置随机种子,结果可复现
embedding_layer = torch.nn.Embedding(vocab_size, output_dim) # 创建嵌入层

- torch.nn.Embedding 是PyTorch提供的嵌入层,用于将Token ID映射为向量。
- 嵌入层的权重矩阵是随机初始化的。

权重矩阵的形状为 (vocab_size, output_dim)。
假设随机初始化后为:

tensor([[ 0.1, -0.2, 0.3],
[ 0.4, 0.5, -0.6],
[ 0.7, -0.8, 0.9],
[ 1.0, -1.1, 1.2],
[-1.3, 1.4, -1.5],
[ 1.6, -1.7, 1.8]])

- 每一行是一个Token ID对应的嵌入向量。
- 比如,Token ID为2的嵌入向量是 [0.7, -0.8, 0.9]。

步骤3:查询嵌入向量

embedding_vector = embedding_layer(torch.tensor([2]))
print(embedding_vector)

输出:

tensor([[0.7, -0.8, 0.9]])

解释:

- 这段代码从嵌入层中查找Token ID为2的向量,也就是矩阵中的第3行(索引从0开始)。

扩展到批量处理:

print(embedding_layer(input_ids))

输出:

tensor([[ 0.7, -0.8, 0.9], # ID 2
[ 1.0, -1.1, 1.2], # ID 3
[ 1.6, -1.7, 1.8], # ID 5
[ 0.4, 0.5, -0.6]]) # ID 1

2.8 位置编码(Positional Encoding)

问题:为什么需要位置编码?

嵌入层只能将Token ID映射为向量,但无法表示Token的位置。
例如:

- 对于句子 “I love NLP” 和 “NLP love I”,它们的Token是一模一样的,但顺序完全不同。

为了让模型区分位置,我们需要给每个Token加上位置信息。

步骤1:创建位置嵌入

context_length = 4 # 假设句子长度为4
output_dim = 3 # 嵌入维度与Token嵌入相同
pos_embedding_layer = torch.nn.Embedding(context_length, output_dim) # 位置嵌入层

假设位置嵌入层初始化后的权重矩阵是:

1
2
3
4
tensor([[ 0.1,  0.2, -0.3],  # 位置0
[ 0.4, -0.5, 0.6], # 位置1
[-0.7, 0.8, -0.9], # 位置2
[ 1.0, -1.1, 1.2]]) # 位置3

步骤2:查找位置嵌入

1
2
3
position_ids = torch.arange(context_length)  # 生成位置ID [0, 1, 2, 3]
pos_embeddings = pos_embedding_layer(position_ids) # 查询位置向量
print(pos_embeddings)

输出:

1
2
3
4
tensor([[ 0.1,  0.2, -0.3],  # 位置0
[ 0.4, -0.5, 0.6], # 位置1
[-0.7, 0.8, -0.9], # 位置2
[ 1.0, -1.1, 1.2]]) # 位置3

步骤3:将Token嵌入和位置嵌入相加

假设之前的Token嵌入为

1
token_embeddings = embedding_layer(input_ids)

将Token嵌入与位置嵌入相加

1
2
input_embeddings = token_embeddings + pos_embeddings
print(input_embeddings)

输出:

1
2
3
4
tensor([[ 0.8, -0.6,  0.6],  # Token ID 2 + 位置 0
[ 1.4, -1.6, 1.8], # Token ID 3 + 位置 1
[ 0.9, -0.9, 0.9], # Token ID 5 + 位置 2
[ 1.4, -0.6, 0.6]]) # Token ID 1 + 位置 3

解释:

  • 每个Token的向量中都加入了对应位置的位置信息。
  • 新生成的向量将用于模型的下一步处理。

总结

  1. 嵌入层:
    • 将Token ID映射为连续向量表示。
    • 初始向量随机生成,随着训练不断优化。
  2. 位置嵌入:
    • 给每个Token附加位置信息,帮助模型理解顺序。
  3. 最终输入:
    • Token嵌入和位置嵌入相加,生成模型的输入。

Chapter 03

Read More