阅读量: 次  文章字数: 1.3k字  阅读时长: 5分钟

学习《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

Comments