
这次我们来深入解析 Andrej Karpathy 的 Bigram 语言模型这是一个非常适合入门自然语言处理的经典项目。作为 OpenAI 创始成员和前特斯拉 AI 总监Karpathy 设计的这个模型虽然结构简单但完整展示了语言模型的核心原理特别适合想要从零理解 LLM大语言模型工作原理的开发者。Bigram 模型最大的特点是实现简洁、训练快速、资源要求极低。你不需要高端显卡甚至用 CPU 就能在几分钟内完成训练和推理。本文将带你完整实现一个 Bigram 语言模型并验证其文本生成能力。1. 核心能力速览能力项具体说明模型类型基于字符的 Bigram 统计语言模型开源作者Andrej KarpathyOpenAI 创始成员核心功能字符级文本生成、概率统计、训练可视化硬件要求极低CPU 即可运行无需 GPU显存占用几乎可忽略模型参数极少依赖环境Python 3.6、PyTorch、NumPy代码规模单文件100 行左右核心代码适合场景LLM 入门教学、语言模型原理理解、基础文本生成实验2. 适用场景与使用边界Bigram 模型最适合以下场景教育学习用途理解语言模型的基本构建流程数据准备、模型定义、训练循环、推理生成掌握 PyTorch 张量操作和自动梯度计算学习如何评估文本生成质量实验验证用途快速验证文本生成想法测试不同训练数据对模型效果的影响作为更复杂模型如 GPT、LSTM的对比基线使用边界提醒生成文本长度有限通常适合短文本生成无法处理长距离依赖关系生成内容可能存在重复或不连贯现象不适合生产环境部署主要用于教学演示3. 环境准备与前置条件3.1 基础软件环境# 检查 Python 版本 python --version # 推荐 Python 3.8 # 安装核心依赖 pip install torch numpy matplotlib3.2 验证 PyTorch 安装import torch import numpy as np print(fPyTorch 版本: {torch.__version__}) print(fCUDA 是否可用: {torch.cuda.is_available()})3.3 准备训练数据Bigram 模型对数据要求很灵活可以使用任何文本文件英文小说文本如莎士比亚作品中文古诗集需调整分词方式代码文件学习编程语言模式自定义文本语料4. Bigram 模型原理与实现4.1 Bigram 基本概念Bigram二元语法模型基于一个简单的假设每个字符的出现概率只依赖于前一个字符。这种马尔可夫假设大大简化了模型复杂度。数学上Bigram 概率可以表示为P(当前字符 | 前一个字符) count(前一个字符, 当前字符) / count(前一个字符)4.2 完整模型实现代码import torch import torch.nn as nn import torch.nn.functional as F class BigramLanguageModel(nn.Module): def __init__(self, vocab_size): super().__init__() # 每个字符的嵌入向量 self.token_embedding_table nn.Embedding(vocab_size, vocab_size) def forward(self, idx, targetsNone): # idx 和 targets 都是 (B,T) 的张量 logits self.token_embedding_table(idx) # (B,T,C) if targets is None: loss None else: B, T, C logits.shape logits logits.view(B*T, C) targets targets.view(B*T) loss F.cross_entropy(logits, targets) return logits, loss def generate(self, idx, max_new_tokens): # idx 是当前上下文 (B,T) 数组 for _ in range(max_new_tokens): # 获取预测 logits, loss self(idx) # 只关注最后时间步 logits logits[:, -1, :] # 变成 (B,C) # 应用 softmax 获取概率 probs F.softmax(logits, dim-1) # (B,C) # 从分布中采样 idx_next torch.multinomial(probs, num_samples1) # (B,1) # 添加到序列中 idx torch.cat((idx, idx_next), dim1) # (B,T1) return idx5. 数据预处理与训练流程5.1 文本数据预处理def prepare_data(text): # 获取所有唯一字符 chars sorted(list(set(text))) vocab_size len(chars) # 创建字符到索引的映射 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] # 编码器 decode lambda l: .join([itos[i] for i in l]) # 解码器 # 将文本转换为张量 data torch.tensor(encode(text), dtypetorch.long) # 分割训练和验证集 n int(0.9 * len(data)) train_data data[:n] val_data data[n:] return train_data, val_data, vocab_size, encode, decode # 示例文本数据 text Hello, this is a simple Bigram language model. It learns to predict the next character based on the previous one. train_data, val_data, vocab_size, encode, decode prepare_data(text)5.2 训练循环实现def train_model(model, train_data, val_data, iterations1000): optimizer torch.optim.AdamW(model.parameters(), lr1e-3) for iter in range(iterations): # 获取一个小批量数据 ix torch.randint(len(train_data) - 1, (4,)) # 批量大小4 xb train_data[ix] yb train_data[ix 1] # 前向传播 logits, loss model(xb, yb) # 反向传播 optimizer.zero_grad(set_to_noneTrue) loss.backward() optimizer.step() # 每100次迭代打印损失 if iter % 100 0: with torch.no_grad(): val_loss estimate_loss(model, val_data) print(f迭代 {iter}: 训练损失 {loss.item():.4f}, 验证损失 {val_loss:.4f}) def estimate_loss(model, data): model.eval() losses torch.zeros(10) for k in range(10): ix torch.randint(len(data) - 1, (4,)) xb data[ix] yb data[ix 1] _, loss model(xb, yb) losses[k] loss.item() model.train() return losses.mean() # 初始化并训练模型 model BigramLanguageModel(vocab_size) train_model(model, train_data, val_data)6. 文本生成测试与效果验证6.1 基础生成测试# 从起始字符开始生成 context torch.zeros((1, 1), dtypetorch.long) generated_ids model.generate(context, max_new_tokens100)[0].tolist() generated_text decode(generated_ids) print(生成的文本:) print(generated_text)6.2 不同起始点的生成效果通过改变初始上下文观察模型生成文本的多样性# 测试不同的起始字符 start_chars [H, T, I, M] for start_char in start_chars: context torch.tensor([[encode(start_char)[0]]], dtypetorch.long) generated model.generate(context, max_new_tokens50)[0].tolist() print(f以 {start_char} 开头: {decode(generated)})6.3 生成质量评估标准评估 Bigram 模型生成文本时关注以下几个维度连贯性生成的字符序列是否形成有意义的单词多样性不同起始点是否能产生不同的文本模式训练稳定性损失函数是否平稳下降过拟合检查训练损失和验证损失的差距7. 模型性能与资源观察7.1 训练时间与资源占用Bigram 模型的优势在于极低的资源需求训练时间1000 次迭代通常在 10-30 秒内完成CPU内存占用模型参数极少几乎不占用显存推理速度生成 100 个字符约需 1-2 毫秒7.2 性能优化技巧虽然 Bigram 模型本身已经很轻量但可以进一步优化# 使用 torch.jit.script 加速推理 scripted_model torch.jit.script(model) # 批量生成提高效率 def batch_generate(model, contexts, max_new_tokens100): 批量生成文本 with torch.no_grad(): return model.generate(contexts, max_new_tokensmax_new_tokens)8. 扩展到更复杂模型8.1 从 Bigram 到 Trigram理解了 Bigram 后可以自然扩展到考虑更多上下文的模型class TrigramLanguageModel(nn.Module): def __init__(self, vocab_size): super().__init__() self.token_embedding nn.Embedding(vocab_size, 64) self.position_embedding nn.Embedding(2, 64) # 前两个位置 self.lm_head nn.Linear(64, vocab_size) def forward(self, idx, targetsNone): B, T idx.shape token_emb self.token_embedding(idx) # (B,T,C) pos_emb self.position_embedding(torch.arange(T)) # (T,C) x token_emb pos_emb # (B,T,C) logits self.lm_head(x) # (B,T,vocab_size) if targets is None: loss None else: loss F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1)) return logits, loss8.2 与现代 LLM 的关联Bigram 模型虽然简单但包含了现代大语言模型的核心要素嵌入层Embedding Layer将离散符号映射到连续向量空间Softmax 输出将网络输出转换为概率分布自回归生成基于前面生成的内容预测下一个 token交叉熵损失衡量预测分布与真实分布的差异9. 常见问题与排查方法问题现象可能原因排查方式解决方案训练损失不下降学习率设置不当检查损失曲线调整学习率1e-2 到 1e-4 尝试生成文本重复模型过于简单观察生成多样性增加训练数据量或模型复杂度内存不足错误数据量过大检查数据张量大小减小批量大小或序列长度生成乱码字符编码错误验证编码解码函数检查字符映射表是否正确梯度爆炸学习率过高监控梯度范数使用梯度裁剪或降低学习率9.1 调试技巧# 添加训练监控 def debug_training(model, data): # 检查模型参数 for name, param in model.named_parameters(): print(f{name}: {param.shape}) # 验证前向传播 xb data[:4].unsqueeze(0) yb data[1:5].unsqueeze(0) logits, loss model(xb, yb) print(f初始损失: {loss.item()})10. 实践建议与下一步学习路径10.1 Bigram 模型的最佳实践数据准备阶段使用纯净的文本数据避免特殊字符干扰保持适当的数据量几千到几万字符对中文文本需要先进行分词处理训练调优从小学习率开始如 1e-3根据损失曲线调整使用合适的批量大小通常 4-32定期验证集评估防止过拟合生成控制通过调整温度参数控制生成随机性尝试不同的起始字符获得多样结果限制生成长度避免无限循环10.2 进阶学习方向掌握了 Bigram 模型后可以沿着以下路径深入学习增加模型复杂度尝试 LSTM、GRU 等循环神经网络引入注意力机制学习 Transformer 架构的基本原理使用预训练模型上手 Hugging Face 的 Transformers 库实践完整项目实现聊天机器人、文本分类等应用学习优化技巧掌握模型压缩、量化、蒸馏等实用技术Bigram 语言模型作为 LLM 学习的起点其价值不在于生成质量而在于帮助开发者建立对语言模型工作原理的直观理解。通过这个简单的模型你可以清晰地看到从字符统计到神经网络生成的整个流程为后续学习更复杂的 GPT、BERT 等模型打下坚实基础。建议在实际操作中重点关注数据流向、损失变化和生成效果之间的关系这种直观感受比单纯学习理论更能加深理解。完成本实验后你会对语言模型如何学习文本规律这个问题有更具体的认识。