主题
第 7 章 让模型看见上下文:嵌入与 MLP
bigram 在 ana 和 ava 后面看到的都是最后一个 a,所以给出的下一字符分布完全相同。要让模型区分这两种情况,可以把最近三个字符一起输入。但直接给所有三字符上下文各建一行表,需要
这一章让多个上下文共用参数:先把字符映射成短向量,再把上下文里的向量连接起来,交给 MLP 预测下一字符。课程以 Bengio 等人 2003 年的神经概率语言模型为背景;本文实现的是简化的字符级版本,不是论文全部结构与实验的复现。原论文;课程 MLP notebook
7.1 一条字符串产生多条有上下文的记录
记上下文长度为
| 输入上下文 | 下一字符标签 |
|---|---|
... | a |
..a | b |
.ab | a |
aba | . |
这是 aba 的完整样本。不要先把字符加入上下文再记录标签,否则输入含有正在预测的答案,损失可以很低,模型却没有学会预测未来。
python
context = [0] * block_size
for char in word + '.':
ix = stoi[char]
X.append(context)
Y.append(ix)
context = context[1:] + [ix]这里每次都创建新列表。若改成 append(context) 后原地修改同一个列表,先前所有记录可能引用同一个对象,最后一起变成相同内容。可以显式 .copy(),也可以像上面这样始终重建上下文。
一批有
7.2 嵌入表就是可以训练的查表
设每个字符用
python
emb = C[X]把 .shape 为准。
字符嵌入没有预设某一维是元音或某一维是名字结尾。它从随机值开始,通过预测损失更新。同一字符出现在许多位置时,共用同一行参数;这些位置的梯度都会加到该行。这是参数共享的具体含义。
上一章的 one-hot 查表等价在这里依然成立:C[X] 不用创建巨大的稀疏中间矩阵。language_lab.py 用重复字符索引核对,某一行出现两次且输出取和时,它每维收到的梯度都是 2。
7.3 把上下文连接起来,保留顺序
隐藏层接收每条样本的
python
flat = emb.reshape(B, K * D)
hidden = torch.tanh(flat @ W1 + b1)
logits = hidden @ W2 + b2参数和中间结果如下:
| 名称 | 形状 | 含义 |
|---|---|---|
| 每字符一行表示 | ||
| 每条记录的有序上下文 | ||
emb | 每个位置的字符向量 | |
flat | 按位置连接后的输入 | |
| 生成 | ||
hidden | 经 tanh 的特征 | |
| 输出各字符 logits | ||
logits | 每条输入的一组打分 |
连接不是求平均。第一个字符的第 ab 与 ba。数学上,对第
再对输出字符
这些求和下标说明参数如何复用:相同字符用相同的
7.4 view、reshape 与底层存储
张量的形状并不等于一份新的底层数据。连续存储的 view(-1,6),其中 -1 让 PyTorch 根据总元素数推断批量大小。
view 要求新形状与原来的存储步幅兼容。转置后的张量经常不满足这一要求,不能只因元素总数相同就保证 view 成功。reshape 在可能时返回视图,必要时会复制。若确实需要连续存储,可用 contiguous(),但不要为了消掉错误盲目复制所有张量。PyTorch 2.6 张量接口源码
配套 training_diagnostics.py 先把一个连续的 view(12) 会报错,再核对 reshape(12) 得到的 12 个元素仍遵循转置后的逻辑顺序。它把这个内存限制变成一个可以重复运行的反例。
本书使用 emb.reshape(len(x),-1),并依靠明确的
7.5 这套网络有多少参数
参数总数为
各项依次是嵌入表、第一层权重、第一层偏置、第二层权重、第二层偏置。课程后期的
与完整上下文表相比,MLP 的参数量不随
7.6 训练与生成使用同一个前向函数
训练时直接把 logits 和标签交给 F.cross_entropy,由它处理 softmax 和 NLL。生成时没有真实标签,才对 logits 做 softmax,再抽一个 ID 接入上下文:
python
context = [0] * K
for _ in range(max_length):
x = torch.tensor([context], dtype=torch.long)
probs = model(x).softmax(dim=1)
ix = torch.multinomial(probs, 1, generator=g).item()
if ix == 0:
break
context = context[1:] + [ix]外面的列表让输入形状为 .view 或 torch.arange(32) 里,是一个常见错误。原 MLP notebook 的早期损失示范就保留了 32 的交互式痕迹;完整可运行版本应从当前批量推导。
练习与解答
补充题:
补充题:把连接替换为三个位置的嵌入均值,是否还能区分排列?如果没有位置相关参数,aba 与 aab 的均值相同,后续确定性网络给出相同输出。这个改动丢掉了当前架构依赖的顺序信息。
来源与代码
本章覆盖课程 MLP notebook 的数据窗口、嵌入查表、隐藏层、输出层、形状、参数计数与采样;也核对了官方 MLP Colab。它的部分训练与评估注释仍写旧的嵌入维数和隐藏宽度,正文已按实际参数修正。完整类 ContextMLP 在 examples/part1/language_lab.py。