第 8 章:大语言模型如何记忆事实 —— 参数中的知识存储与检索¶
场景: 你问 ChatGPT"法国的首都是什么?",它毫不犹豫地回答"巴黎"。这个知识存储在哪里?LLM 没有数据库,没有知识图谱——所有知识都编码在模型的参数(权重矩阵)中。本章将揭示 LLM 如何在参数中存储和检索事实,以及为什么有时会"记错"(幻觉)。
8.1 知识存储在哪里?¶
注意力层 vs FFN 层¶
Transformer 的两个核心组件有不同的分工:
| 组件 | 功能 | 比喻 |
|---|---|---|
| 注意力层 | 从上下文中 检索 信息 | 搜索引擎——找到相关信息在哪里 |
| FFN 层 | 存储和 提取 知识 | 百科全书——知识本身存储在这里 |
核心比喻:图书馆索引
注意力层 = 图书馆的 索引卡片 ,告诉你"关于法国的书在哪个书架" FFN 层 = 书架上的 书本身 ,包含"巴黎是法国首都"这个事实
当你问"法国的首都是什么?": 1. 注意力层找到与"法国"和"首都"相关的信息位置 2. FFN 层从参数中提取"巴黎"这个答案
8.2 FFN 层作为键值记忆(Key-Value Memory)¶
研究发现,FFN 层可以理解为一种 稀疏的键值记忆 :
- \(W_1\) 的第一层相当于 键(Key) :检测输入中是否包含特定模式
- \(W_2\) 的第二层相当于 值(Value) :输出对应的知识
import numpy as np
def ffn_as_memory_demo():
"""
演示 FFN 层如何作为键值记忆工作
简化示例:存储"巴黎→法国首都"这个事实
"""
embed_dim = 4
ffn_dim = 8
# 假设的嵌入向量
x_france = np.array([1.0, 0.0, 0.0, 0.0]) # "法国"的嵌入
x_capital = np.array([0.0, 1.0, 0.0, 0.0]) # "首都"的嵌入
x_query = x_france + x_capital # "法国首都"的查询向量
# W1: 键矩阵 —— 检测"法国+首都"这个模式
W1 = np.zeros((embed_dim, ffn_dim))
W1[0, 0] = 1.0 # 检测"法国"特征
W1[1, 0] = 1.0 # 检测"首都"特征
# 其他神经元检测其他模式...
b1 = np.array([-1.5, 0, 0, 0, 0, 0, 0, 0]) # 偏置:需要两个特征都激活
# W2: 值矩阵 —— 输出"巴黎"的嵌入
W2 = np.zeros((ffn_dim, embed_dim))
W2[0, 2] = 1.0 # 第0个神经元激活时,输出"巴黎"特征
b2 = np.zeros(embed_dim)
# 前向传播
hidden = np.maximum(0, np.dot(x_query, W1) + b1) # ReLU
output = np.dot(hidden, W2) + b2
print(f"查询向量 (法国+首都): {x_query}")
print(f"隐藏层激活: {hidden}")
print(f" → 神经元 0 激活值: {hidden[0]:.1f} (检测到'法国首都'模式!)")
print(f"FFN 输出: {output}")
print(f" → 第 3 维激活 (代表'巴黎'): {output[2]:.1f}")
print(f"\n解读: FFN 检测到'法国+首都'模式 → 输出'巴黎'的嵌入表示")
ffn_as_memory_demo()
渲染效果:
查询向量 (法国+首都): [1. 1. 0. 0.]
隐藏层激活: [0.5 0. 0. 0. 0. 0. 0. 0. ]
→ 神经元 0 激活值: 0.5 (检测到'法国首都'模式!)
FFN 输出: [0. 0. 0.5 0. ]
→ 第 3 维激活 (代表'巴黎'): 0.5
解读: FFN 检测到'法国+首都'模式 → 输出'巴黎'的嵌入表示
8.3 知识定位:找到存储特定事实的神经元¶
近年来,研究者发现可以 定位 LLM 中存储特定知识的参数:
def knowledge_localization_concept():
"""
知识定位的核心思想(概念演示)
"""
print("知识定位研究的关键发现:")
print()
findings = [
("事实存储在 FFN 层",
"修改 FFN 层的参数可以改变模型对事实的记忆"),
("知识是分布式的",
"一个事实通常分布在多个神经元中,而非单个"),
("中层存储事实最多",
"Transformer 的中间层(而非底层或顶层)存储了最多的事实知识"),
("可编辑性",
"通过修改特定 FFN 神经元的权重,可以'编辑'模型的知识"),
]
for title, desc in findings:
print(f" [{title}]")
print(f" {desc}")
print()
knowledge_localization_concept()
渲染效果:
知识定位研究的关键发现:
[事实存储在 FFN 层]
修改 FFN 层的参数可以改变模型对事实的记忆
[知识是分布式的]
一个事实通常分布在多个神经元中,而非单个
[中层存储事实最多]
Transformer 的中间层(而非底层或顶层)存储了最多的事实知识
[可编辑性]
通过修改特定 FFN 神经元的权重,可以'编辑'模型的知识
8.4 模型如何检索知识:从输入到答案的完整路径¶
当用户问"法国的首都是什么?"时,LLM 内部发生了什么?
步骤1: Tokenization
"法国的首都是什么?" → [法国, 的, 首都, 是, 什么, ?]
步骤2: 嵌入 + 位置编码
每个 token → 稠密向量 (如 12288 维)
步骤3: 逐层处理 (GPT-3: 96 层)
第 1-10 层: 理解基本语法结构
第 11-30 层: 识别"法国"和"首都"的语义关联
第 31-60 层: 注意力层聚焦"法国"→ FFN 层检索相关知识
第 61-90 层: 整合检索到的知识,形成"巴黎"的表示
第 91-96 层: 准备输出,将内部表示转换为词汇概率
步骤4: 输出
最后一个位置的向量 → Linear → Softmax → "巴黎"概率最高
def knowledge_retrieval_pathway():
"""模拟知识检索的信息流"""
layers = 96
print(f"GPT-3 ({layers} 层) 处理 '法国的首都是什么?' 的信息流:")
print("=" * 60)
stages = [
(1, 10, "语法解析", "识别'法国'是名词、'首都'是名词、'是'是动词"),
(11, 30, "语义关联", "将'法国'和'首都'建立语义联系"),
(31, 60, "知识检索", "FFN 层从参数中提取'法国→巴黎'的事实"),
(61, 90, "答案整合", "将'巴黎'的表示整合到输出位置"),
(91, 96, "输出准备", "将内部表示映射到词汇表,'巴黎'概率最高"),
]
for start, end, stage_name, description in stages:
bar = "█" * (end - start + 1)
print(f" 第 {start:2d}-{end:2d} 层 [{stage_name}]")
print(f" {description}")
print(f" [{bar}]")
print()
knowledge_retrieval_pathway()
渲染效果:
GPT-3 (96 层) 处理 '法国的首都是什么?' 的信息流:
============================================================
第 1-10 层 [语法解析]
识别'法国'是名词、'首都'是名词、'是'是动词
[██████████]
第 11-30 层 [语义关联]
将'法国'和'首都'建立语义联系
[████████████████████]
第 31-60 层 [知识检索]
FFN 层从参数中提取'法国→巴黎'的事实
[██████████████████████████████]
第 61-90 层 [答案整合]
将'巴黎'的表示整合到输出位置
[██████████████████████████████]
第 91-96 层 [输出准备]
将内部表示映射到词汇表,'巴黎'概率最高
[██████]
8.5 为什么 LLM 会产生幻觉?¶
幻觉(Hallucination)——模型编造看似合理但错误的事实——是 LLM 最棘手的问题之一。
幻觉的三种来源¶
| 类型 | 原因 | 示例 |
|---|---|---|
| 知识缺失 | 训练数据中没有这个事实 | 问一个冷门历史事件,模型编造细节 |
| 知识冲突 | 训练数据中有矛盾的信息 | 不同来源对同一事件有不同描述 |
| 生成偏差 | 模型倾向于生成"流畅"而非"准确"的文本 | 模型编造了一个听起来合理的引用 |
def hallucination_mechanism():
"""
从"预测下一个词"的角度理解幻觉
"""
print("幻觉的生成机制:")
print()
print("正常情况(知识存在):")
print(" 输入: '法国的首都是'")
print(" FFN 激活: '法国+首都'模式 → 输出'巴黎'特征")
print(" 预测: '巴黎' (概率 0.95)")
print()
print("幻觉情况(知识缺失或冲突):")
print(" 输入: 'X星的首都是'")
print(" FFN 激活: 没有匹配的模式")
print(" 但模型仍然需要输出一个词!")
print(" → 模型根据语言模式猜测: '首都'后面常跟城市名")
print(" → 模型编造: '新希望城' (听起来像首都的名字)")
print()
print("核心矛盾:")
print(" LLM 的本质是'预测最可能的下一个词'")
print(" 而非'检索最准确的事实'")
print(" 当知识缺失时,它会用语言流畅性来填补空白")
hallucination_mechanism()
渲染效果:
幻觉的生成机制:
正常情况(知识存在):
输入: '法国的首都是'
FFN 激活: '法国+首都'模式 → 输出'巴黎'特征
预测: '巴黎' (概率 0.95)
幻觉情况(知识缺失或冲突):
输入: 'X星的首都是'
FFN 激活: 没有匹配的模式
但模型仍然需要输出一个词!
→ 模型根据语言模式猜测: '首都'后面常跟城市名
→ 模型编造: '新希望城' (听起来像首都的名字)
核心矛盾:
LLM 的本质是'预测最可能的下一个词'
而非'检索最准确的事实'
当知识缺失时,它会用语言流畅性来填补空白
8.6 知识编辑:修改模型记住的事实¶
一个令人兴奋的研究方向是 知识编辑 ——在不重新训练的情况下修改模型存储的事实:
def knowledge_editing_concept():
"""知识编辑的概念演示"""
print("知识编辑技术概览:")
print("=" * 50)
techniques = [
("ROME (Rank-One Model Editing)",
"定位存储特定事实的 FFN 层,用秩一更新修改权重",
"将'埃菲尔铁塔在罗马'改为'埃菲尔铁塔在巴黎'"),
("MEND (Model Editor Networks)",
"训练一个辅助网络,根据编辑请求生成权重更新",
"批量修改模型中的过时知识"),
("In-Context Editing",
"不修改参数,通过精心设计的提示词覆盖旧知识",
"在系统提示中告诉模型'X 的新首都是 Y'"),
]
for name, method, example in techniques:
print(f"\n [{name}]")
print(f" 方法: {method}")
print(f" 示例: {example}")
knowledge_editing_concept()
渲染效果:
知识编辑技术概览:
==================================================
[ROME (Rank-One Model Editing)]
方法: 定位存储特定事实的 FFN 层,用秩一更新修改权重
示例: 将'埃菲尔铁塔在罗马'改为'埃菲尔铁塔在巴黎'
[MEND (Model Editor Networks)]
方法: 训练一个辅助网络,根据编辑请求生成权重更新
示例: 批量修改模型中的过时知识
[In-Context Editing]
方法: 不修改参数,通过精心设计的提示词覆盖旧知识
示例: 在系统提示中告诉模型'X 的新首都是 Y'
8.7 记忆 vs 理解:LLM 真的"知道"吗?¶
一个深刻的哲学问题:LLM 是在 真正理解 知识,还是仅仅 模式匹配 ?
def memory_vs_understanding():
"""探讨 LLM 的记忆与理解"""
print("记忆 vs 理解 —— LLM 的能力边界:")
print("=" * 55)
comparisons = [
("记忆", "理解"),
("能回答'法国的首都是什么'", "能推理'如果法国迁都到里昂,对欧洲经济有何影响'"),
("能背诵'勾股定理: a²+b²=c²'", "能证明勾股定理,并推广到 n 维空间"),
("能写出'for i in range(10)'", "能设计全新的算法解决未见过的编程问题"),
("基于训练数据中的模式", "基于对世界因果结构的建模"),
]
for left, right in comparisons:
print(f" {left:<20} | {right}")
print(f"\n 当前共识: LLM 处于'记忆'和'理解'之间的灰色地带")
print(f" 它展现了一些理解能力(如思维链推理),")
print(f" 但这些能力可能源于对海量推理模式的记忆和重组。")
memory_vs_understanding()
渲染效果:
记忆 vs 理解 —— LLM 的能力边界:
=======================================================
记忆 | 理解
能回答'法国的首都是什么' | 能推理'如果法国迁都到里昂,对欧洲经济有何影响'
能背诵'勾股定理: a²+b²=c²' | 能证明勾股定理,并推广到 n 维空间
能写出'for i in range(10)' | 能设计全新的算法解决未见过的编程问题
基于训练数据中的模式 | 基于对世界因果结构的建模
当前共识: LLM 处于'记忆'和'理解'之间的灰色地带
它展现了一些理解能力(如思维链推理),
但这些能力可能源于对海量推理模式的记忆和重组。
要点总结¶
- 注意力层负责检索,FFN 层负责存储和提取知识
- FFN 层可理解为键值记忆:\(W_1\) 检测模式(Key),\(W_2\) 输出知识(Value)
- 知识是分布式的,存储在多个神经元中,中层存储最多
- 知识检索路径:语法解析 → 语义关联 → FFN 提取 → 答案整合 → 输出
- 幻觉源于"预测下一个词"与"检索准确事实"之间的根本矛盾
- 知识编辑技术可以在不重新训练的情况下修改模型的知识
- LLM 的理解能力仍是一个开放问题
课后练习¶
-
知识定位实验 :如果你能访问一个开源 LLM(如 Llama),尝试用不同的提示词探测模型对同一事实的"记忆强度"。
-
幻觉复现 :问 ChatGPT 一个它很可能不知道的冷门问题(如"2027 年奥运会在哪个城市举办"),观察它是诚实回答"不知道"还是编造答案。
-
思考题 :如果 LLM 的知识存储在 FFN 层的参数中,为什么增加上下文长度(更多 token)能让模型回答更准确?这两种"知识来源"(参数 vs 上下文)有什么区别?
教程总结¶
恭喜你完成了全部 8 章的学习!让我们回顾一下你学到了什么:
| 章节 | 核心收获 |
|---|---|
| 第 1 章 | 神经元 = 加权求和 + 激活函数;网络 = 多层堆叠 |
| 第 2 章 | 梯度下降 = 沿负梯度方向逐步逼近最优解 |
| 第 3 章 | 反向传播 = 链式法则让梯度从输出反向流到输入 |
| 第 4 章 | 链式法则的数学本质 + Softmax 梯度的优雅公式 |
| 第 5 章 | LLM = 预测下一个词的超级预测机 |
| 第 6 章 | Transformer = 自注意力 + FFN + 残差 + 层归一化 |
| 第 7 章 | 注意力 = Q·K → Softmax → 加权 V;多头 = 多角度 |
| 第 8 章 | FFN = 键值记忆;知识分布式存储;幻觉的根源 |
下一步学习建议¶
- 动手实践 :用 PyTorch 从零实现一个 GPT-2 级别的模型(参考 Andrej Karpathy 的 nanoGPT)
- 深入理论 :阅读《Attention Is All You Need》原论文和《Deep Learning》教材
- 跟进前沿 :关注 RAG(检索增强生成)、Agent、多模态 LLM 等最新方向
感谢你的学习! 如果本教程对你有帮助,欢迎分享给更多对 AI 感兴趣的朋友。