nanoGPT学习笔记一:GPT 模型的数据基石与算法逻辑

在构建 GPT 模型的初期,最关键的步骤是建立一种数学协议,让模型能够通过“看过去”来“猜未来”。本笔记详细记录了数据从原始文本到模型可用的张量矩阵的转化过程。

1. 原始数据的加载与观察

with open('input.txt', 'r', encoding='utf-8') as f:
    text = f.read()

print("length of dataset in characters: ", len(text))

输出:

length of dataset in characters:  1115394
  • 输出解读: 这行数字表示 input.txt 的字符总数(字符级语料规模)。后续所有 token/id 的生成都基于这段文本。

  • Python 语法: with open(…) as f 是 Python 的上下文管理器,它能确保文件读取完毕后自动关闭资源。encoding=’utf-8’ 保证了文本中复杂的文学符号能被正确解析。len(text) 则返回了字符串的总长度。
  • 算法理解: 这是模型感知世界的唯一源泉。这 111 万个字符包含了模型需要学习的所有语言规律、标点用法以及莎士比亚的写作风格。

2. 构建“活字字盘”:词表提取

chars = sorted(list(set(text)))
vocab_size = len(chars)
print(''.join(chars))
print(vocab_size)

输出:

 ! $&',-.3:;?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
65
  • 输出解读: 第一行是去重并排序后的“字符表”(vocab 的所有符号);第二行 65 是词表大小 vocab_size

  • Python 语法: set(text) 利用集合的去重特性提取出唯一字符;list() 将其转化为列表;sorted() 进行升序排列。’‘.join(chars) 将列表字符拼接成一行显示。
  • 算法理解: 这相当于整理出一份标准的“活字字盘”。vocab_size(65)定义了模型预测任务的边界——模型每一步预测本质上都是在 65 个候选符号中进行概率选择。

3. 翻译机制:映射字典与 Lambda 表达式

stoi = {ch: i for i, ch in enumerate(chars)}
itos = {i: ch for i, ch in enumerate(chars)}
encode = lambda s: [stoi[c] for c in s]  # encoder
decode = lambda l: ''.join([itos[i] for i in l])  # decoder

print(encode("hii there"))
print(decode(encode("hii there")))

输出:

[46, 47, 47, 1, 58, 46, 43, 56, 43]
hii there
  • 输出解读: 第一行是 encode 把字符串映射为索引序列后的结果;第二行验证 decode(encode(x)) 能无损还原原字符串。

  • Python 语法: {ch:i for …} 是字典推导式,配合 enumerate 高效创建键值映射。lambda 定义了匿名函数,encode 将字符串转为数字列表,decode 则利用 itos 字典将数字还原。
  • 算法理解: 这是模型与人类世界的“翻译官”。模型只认识数字索引,通过这种数字化处理,感性的文本变成了机器可运算的离散数值。

4. 数据的张量化处理

import torch

data = torch.tensor(encode(text), dtype=torch.long)
print(data.shape, data.dtype)
print(data[:1000])

输出:

torch.Size([1115394]) torch.int64
tensor([18, 47, 56, 57, 58,  1, 15, 47, 58, 47, 64, 43, 52, 10,  0, 14, 43, 44, ...])
  • 输出解读: 第一行给出数据张量的形状与类型(长度等于字符总数,类型为整型索引);第二行是前 1000 个 token/id 的预览。

  • Python 语法: torch.tensor 将 Python 列表转化为张量(Tensor),dtype=torch.long 指定 64 位长整型,这在存储类别索引(Index)时是标准做法。
  • 算法理解: 张量是 GPU 运算的“基本燃料”。此时,整个文本流已经被压缩进一个巨大的数字张量中,准备进行矩阵运算。

5. 训练集与验证集的划分逻辑

n = int(0.9 * len(data))
train_data = data[:n]
val_data = data[n:]
  • Python 语法: 通过切片 [:n] 和 [n:] 将数据按 9:1 分割。
  • 算法理解: 训练集(前 90%)用于让模型寻找规律,验证集(后 10%)作为从未见过的“新考题”,用于评估模型的泛化能力,防止模型只会“死记硬背”。

6. 窗口切片与算法效果的展示

block_size = 8
print(train_data[:block_size+1])

x = train_data[:block_size]
y = train_data[1:block_size+1]
for t in range(block_size):
    context = x[:t+1]
    target = y[t]
    print(f"when input is {context} the target: {target}")

输出:

tensor([18, 47, 56, 57, 58,  1, 15, 47, 58])
when input is tensor([18]) the target: 47
when input is tensor([18, 47]) the target: 56
when input is tensor([18, 47, 56]) the target: 57
when input is tensor([18, 47, 56, 57]) the target: 58
when input is tensor([18, 47, 56, 57, 58]) the target: 1
when input is tensor([18, 47, 56, 57, 58,  1]) the target: 15
when input is tensor([18, 47, 56, 57, 58,  1, 15]) the target: 47
when input is tensor([18, 47, 56, 57, 58,  1, 15, 47]) the target: 58
  • 输出解读: 第一行展示一个长度为 block_size+1 的片段;后续每行展示“给定当前上下文 context,要预测的下一个 token/id 是 target”,即自回归训练的监督信号构造方式。

  • Python 语法: train_data[1:block_size+1] 将窗口向后平移一位。循环中的 f-string 清晰地展示了输入与目标的对应关系。
  • 算法理解:
    • 位移逻辑: 通过让目标序列 y 相对输入序列 x 位移一位,我们为每一个输入位置都匹配了一个“未来”。
    • 多任务学习: 循环揭示了模型在一次计算中实际上练习了 8 个填空题:从“只给 1 个字猜第 2 个”,练到“给 8 个字猜第 9 个”。这让模型学会在不同长度的语境下都能进行预测。