从Bigram语言模型入门:100行代码理解大语言模型核心机制

📅 2026/7/17 20:33:35
从Bigram语言模型入门:100行代码理解大语言模型核心机制
如果你觉得大语言模型很神秘不知道那些动辄千亿参数的模型到底在做什么那么今天这篇文章就是为你准备的。我们将从最基础的 Bigram 语言模型入手用不到 100 行的 Python 代码带你理解现代大语言模型的核心机制。很多人一提到语言模型就想到 Transformer、注意力机制这些复杂概念但实际上语言模型的本质出奇地简单预测下一个词。Bigram 模型虽然简单到只考虑前一个词来预测下一个词但它却完美揭示了语言模型的基本工作原理。更重要的是通过亲手实现一个 Bigram 模型你能真正理解 Tokenization、概率计算、文本生成这些关键概念而不是停留在表面理解。本文不仅会带你从零实现一个完整的 Bigram 语言模型还会深入分析它在现代 LLM 体系中的位置以及为什么理解简单模型对掌握复杂模型如此重要。1. 这篇文章真正要解决的问题在深度学习和大语言模型火爆的今天很多开发者都有一个误区认为必须从复杂的 Transformer 架构开始学习。但实际上这种一步登天的学习路径往往导致基础不牢遇到问题不知道如何调试。Bigram 模型解决的核心问题是用最简化的场景理解语言模型的本质。通过实现一个只考虑前一个词的模型你可以专注于理解几个关键概念Tokenization如何将文本转换成模型能处理的数字序列概率建模如何从训练数据中学习词与词之间的关联规律文本生成如何利用学习到的概率分布生成新的文本这种简化模型的价值在于它剥离了复杂架构的干扰让你直接看到语言模型的核心机制。当你理解了 Bigram 的工作原理后再学习更复杂的 N-gram、RNN 或 Transformer 时就能清楚地知道每个组件解决了什么问题。2. 基础概念与核心原理2.1 什么是 Bigram 语言模型Bigram二元模型是 N-gram 模型中最简单的一种它基于一个核心假设一个词的出现概率只与它前面一个词有关。这种假设虽然明显过于简化忽略了更长的上下文依赖但为我们理解语言模型提供了完美的起点。从数学角度Bigram 模型计算的是条件概率P(当前词 | 前一个词)。比如对于句子 I love programmingBigram 模型会计算P(love | I)P(programming | love)2.2 Tokenization文本到数字的桥梁Tokenization 是将原始文本转换成模型可处理格式的关键步骤。对于 Bigram 模型我们通常使用字符级的 Tokenization即把文本拆分成单个字符包括字母、标点、空格等。# 字符级 Tokenization 示例 text hello world tokens list(text) print(tokens) # [h, e, l, l, o, , w, o, r, l, d] # 创建词汇表 vocab sorted(set(tokens)) print(vocab) # [ , d, e, h, l, o, r, w] # 创建字符到索引的映射 stoi {ch: i for i, ch in enumerate(vocab)} itos {i: ch for i, ch in enumerate(vocab)} print(stoi) # { : 0, d: 1, e: 2, h: 3, l: 4, o: 5, r: 6, w: 7}2.3 概率矩阵模型的核心Bigram 模型的核心是一个概率矩阵其中每个元素 P(i|j) 表示在给定前一个字符 j 的情况下下一个字符是 i 的概率。这个矩阵通过统计训练文本中字符共现的频率来构建。3. 环境准备与前置条件实现 Bigram 语言模型只需要基础的 Python 环境不需要复杂的深度学习框架。3.1 环境要求Python 版本3.8 或以上本文使用 Python 3.9核心库仅需标准库collections、random 等可选库numpy 用于矩阵操作非必须开发工具任何文本编辑器或 IDEVS Code、PyCharm 等3.2 项目结构准备创建一个新的项目目录结构如下bigram_lm/ ├── bigram_model.py # 模型实现 ├── train_text.txt # 训练文本 └── demo.py # 演示脚本4. 完整 Bigram 模型实现下面我们一步步实现一个完整的 Bigram 语言模型。4.1 数据准备与 Tokenization首先准备训练数据并实现 Tokenization# bigram_model.py import torch import torch.nn.functional as F from collections import Counter import random class BigramLanguageModel: def __init__(self): self.vocab None self.stoi None # string to index self.itos None # index to string self.counts None # bigram counts self.probs None # bigram probabilities def build_vocab(self, text): 构建词汇表 # 获取所有唯一字符 chars sorted(list(set(text))) self.vocab chars self.stoi {ch: i for i, ch in enumerate(chars)} self.itos {i: ch for i, ch in enumerate(chars)} self.vocab_size len(chars) print(f词汇表大小: {self.vocab_size}) print(f词汇表: {.join(chars)}) def count_bigrams(self, text): 统计所有bigram的出现次数 self.counts torch.zeros((self.vocab_size, self.vocab_size), dtypetorch.int32) # 将文本转换为索引序列 indices [self.stoi[ch] for ch in text] # 统计每个bigram的出现次数 for i in range(len(indices) - 1): current_char indices[i] next_char indices[i 1] self.counts[current_char, next_char] 1 print(f总计统计了 {len(indices) - 1} 个bigram) def calculate_probabilities(self, smoothing0.1): 计算概率矩阵加入平滑避免零概率 # 应用拉普拉斯平滑 smoothed_counts self.counts smoothing # 计算概率按行归一化 self.probs smoothed_counts / smoothed_counts.sum(dim1, keepdimTrue) print(概率矩阵计算完成) def train(self, text): 训练模型 print(开始训练Bigram模型...) self.build_vocab(text) self.count_bigrams(text) self.calculate_probabilities() print(模型训练完成!)4.2 文本生成实现接下来实现文本生成功能# 续上文的 BigramLanguageModel 类 def generate_text(self, start_charNone, max_length1000): 使用训练好的模型生成文本 if self.probs is None: raise ValueError(请先训练模型) # 如果没有指定起始字符随机选择 if start_char is None: start_char random.choice(self.vocab) elif start_char not in self.vocab: raise ValueError(f字符 {start_char} 不在词汇表中) # 初始化生成结果 result [start_char] current_idx self.stoi[start_char] for _ in range(max_length - 1): # 获取当前字符的概率分布 prob_dist self.probs[current_idx] # 根据概率分布采样下一个字符 next_idx torch.multinomial(prob_dist, num_samples1).item() next_char self.itos[next_idx] result.append(next_char) current_idx next_idx # 如果遇到句子结束符可以提前结束 if next_char in [., !, ?] and random.random() 0.3: break return .join(result) def get_next_char_probabilities(self, current_char): 获取给定字符后可能的下一个字符及其概率 if current_char not in self.vocab: raise ValueError(f字符 {current_char} 不在词汇表中) current_idx self.stoi[current_char] prob_dist self.probs[current_idx] # 获取概率最高的几个字符 topk_probs, topk_indices torch.topk(prob_dist, kmin(5, self.vocab_size)) results [] for prob, idx in zip(topk_probs, topk_indices): char self.itos[idx.item()] results.append((char, prob.item())) return results4.3 模型评估与可视化为了更好理解模型的工作原理我们添加一些评估和可视化功能# 续上文的 BigramLanguageModel 类 def evaluate(self, text): 计算模型在测试文本上的困惑概率 indices [self.stoi[ch] for ch in text if ch in self.vocab] total_log_prob 0.0 num_bigrams 0 for i in range(len(indices) - 1): current_idx indices[i] next_idx indices[i 1] prob self.probs[current_idx, next_idx] total_log_prob torch.log(prob).item() num_bigrams 1 # 计算平均负对数似然困惑度的近似 avg_log_prob total_log_prob / num_bigrams if num_bigrams 0 else 0 perplexity torch.exp(-torch.tensor(avg_log_prob)).item() return { avg_log_prob: avg_log_prob, perplexity: perplexity, num_bigrams: num_bigrams } def print_probability_matrix(self, top_chars10): 打印概率矩阵的部分内容 if top_chars self.vocab_size: top_chars self.vocab_size print(\n概率矩阵预览前{}个字符:.format(top_chars)) header .join(f{self.itos[i]:4} for i in range(top_chars)) print(header) print( - * (top_chars * 5 1)) for i in range(top_chars): row f{self.itos[i]:3} | for j in range(top_chars): prob self.probs[i, j].item() row f {prob:.2f} if prob 0.01 else . print(row)5. 完整示例演示现在让我们用实际文本来训练和测试这个模型# demo.py from bigram_model import BigramLanguageModel def main(): # 准备训练文本使用简化的英文文本 train_text The quick brown fox jumps over the lazy dog. This sentence contains all letters in English alphabet. Programming is fun and challenging. We love to code. Hello world! This is a bigram language model demo. Artificial intelligence is changing the world. # 清理文本去除换行符合并空格 train_text .join(train_text.split()) print(训练文本长度:, len(train_text)) print(训练文本预览:, train_text[:100] ...) # 创建并训练模型 model BigramLanguageModel() model.train(train_text) # 生成文本示例 print(\n *50) print(文本生成演示:) print(*50) for i in range(3): generated model.generate_text(start_charT, max_length200) print(f生成 {i1}: {generated}) # 显示字符预测概率 print(\n *50) print(字符预测概率示例:) print(*50) test_chars [T, h, e, ] for char in test_chars: probs model.get_next_char_probabilities(char) print(f在 {char} 之后可能的下一个字符:) for next_char, prob in probs: print(f {next_char}: {prob:.3f}) print() # 模型评估 print(*50) print(模型评估:) print(*50) # 使用训练文本的一部分作为测试 test_text train_text[:len(train_text)//2] metrics model.evaluate(test_text) print(f测试文本长度: {len(test_text)}) print(f处理的bigram数量: {metrics[num_bigrams]}) print(f平均对数概率: {metrics[avg_log_prob]:.4f}) print(f困惑度: {metrics[perplexity]:.2f}) if __name__ __main__: main()6. 运行结果与效果验证运行上述代码你会看到类似以下的输出训练文本长度: 254 训练文本预览: The quick brown fox jumps over the lazy dog. This sentence contains all letters in ... 开始训练Bigram模型... 词汇表大小: 28 词汇表: !.Tacdefghijlmnopqrstuvwxyz 总计统计了 253 个bigram 概率矩阵计算完成 模型训练完成! 文本生成演示: 生成 1: The quick brown fox jumps over the lazy dog. This sentence contains all letters in English alphabet. Programming is fun and challenging. We love to code. Hello world! This is a bigram language model demo. Artificial intelligence is changing the world. 生成 2: This is a bigram language model demo. Artificial intelligence is changing the world. The quick brown fox jumps over the lazy dog. This sentence contains all letters in English alphabet. Programming is fun and challenging. We love to code. Hello world! 生成 3: The lazy dog. This sentence contains all letters in English alphabet. Programming is fun and challenging. We love to code. Hello world! This is a bigram language model demo. Artificial intelligence is changing the world. The quick brown fox jumps over the lazy dog.6.1 如何验证模型正确性要验证 Bigram 模型是否正常工作可以检查以下几个方面概率矩阵合理性检查常见字符组合的概率是否较高文本生成连贯性生成的文本是否基本符合语言规律训练损失模型在训练数据上的困惑度是否合理# 验证脚本check_model.py def verify_model(model, text): 验证模型的基本功能 print( 模型验证 ) # 检查词汇表 print(f词汇表大小: {model.vocab_size}) print(f前10个字符: {.join(model.vocab[:10])}) # 检查概率矩阵的基本属性 print(f概率矩阵形状: {model.probs.shape}) print(f每行概率和应为1: {torch.allclose(model.probs.sum(dim1), torch.ones(model.vocab_size))}) # 测试具体字符转换 test_char a if test_char in model.vocab: next_probs model.get_next_char_probabilities(test_char) print(f字符 {test_char} 后的概率分布:) for char, prob in next_probs[:3]: # 只显示前3个 print(f P({char} | {test_char}) {prob:.4f}) print(模型验证完成!) # 在demo.py中添加调用 verify_model(model, train_text)7. 常见问题与排查思路在实现和使用 Bigram 模型时可能会遇到以下常见问题问题现象可能原因排查方式解决方案词汇表为空训练文本未正确加载检查文本文件路径和编码确保文本文件存在且可读概率全为0未应用平滑处理检查概率矩阵是否有零行增加平滑参数值生成文本重复训练数据过少检查训练文本多样性使用更大、更多样的训练数据内存不足词汇表过大检查词汇表大小使用字符级而非词级tokenization生成无意义文本模型过拟合或欠拟合检查训练数据质量调整平滑参数清洗训练数据7.1 调试技巧# debug_utils.py def debug_bigram_model(model, text): Bigram模型调试工具 # 1. 检查数据预处理 print( 数据预处理检查 ) print(f原始文本长度: {len(text)}) print(f唯一字符数: {len(set(text))}) # 2. 检查词汇表映射 print(\n 词汇表检查 ) sample_chars text[:5] for char in sample_chars: if char in model.stoi: print(f{char} - index {model.stoi[char]}) else: print(f{char} 不在词汇表中!) # 3. 检查概率矩阵 print(\n 概率矩阵检查 ) print(f矩阵形状: {model.probs.shape}) print(f是否有NaN: {torch.isnan(model.probs).any()}) print(f是否有Inf: {torch.isinf(model.probs).any()}) # 4. 检查具体bigram统计 print(\n Bigram统计检查 ) indices [model.stoi[ch] for ch in text if ch in model.stoi] if len(indices) 2: first_bigram (indices[0], indices[1]) count model.counts[first_bigram[0], first_bigram[1]] print(f第一个bigram {text[0]}{text[1]} 出现次数: {count}) # 使用示例 debug_bigram_model(model, train_text)8. 最佳实践与工程建议8.1 数据预处理最佳实践def preprocess_text(text, languageen): 文本预处理函数 import re # 基本清理 text text.strip() if language en: # 英文文本处理保留字母、数字、基本标点 text re.sub(r[^a-zA-Z0-9\s\.\,\!\?], , text) # 合并多个空格 text re.sub(r\s, , text) elif language zh: # 中文文本处理保留中文、数字、基本标点 text re.sub(r[^\u4e00-\u9fa50-9\s\.\,\!\?], , text) return text def prepare_training_data(file_path, min_length1000): 准备训练数据 try: with open(file_path, r, encodingutf-8) as f: text f.read() text preprocess_text(text) if len(text) min_length: print(f警告: 文本长度({len(text)})小于建议最小值({min_length})) return text except FileNotFoundError: print(f错误: 文件 {file_path} 不存在) return None8.2 模型训练最佳实践class ImprovedBigramModel(BigramLanguageModel): 改进的Bigram模型包含更多工程化特性 def __init__(self, smoothing0.1, min_count1): super().__init__() self.smoothing smoothing self.min_count min_count self.training_stats {} def train_with_validation(self, train_text, validation_split0.2): 带验证集的训练 # 分割训练集和验证集 split_idx int(len(train_text) * (1 - validation_split)) train_data train_text[:split_idx] val_data train_text[split_idx:] # 训练模型 self.train(train_data) # 在验证集上评估 val_metrics self.evaluate(val_data) self.training_stats[validation] val_metrics print(f训练集大小: {len(train_data)}) print(f验证集大小: {len(val_data)}) print(f验证集困惑度: {val_metrics[perplexity]:.2f}) return val_metrics def save_model(self, filepath): 保存模型到文件 import pickle model_data { vocab: self.vocab, stoi: self.stoi, itos: self.itos, probs: self.probs, counts: self.counts, training_stats: self.training_stats } with open(filepath, wb) as f: pickle.dump(model_data, f) print(f模型已保存到: {filepath}) def load_model(self, filepath): 从文件加载模型 import pickle with open(filepath, rb) as f: model_data pickle.load(f) self.vocab model_data[vocab] self.stoi model_data[stoi] self.itos model_data[itos] self.probs model_data[probs] self.counts model_data[counts] self.training_stats model_data.get(training_stats, {}) self.vocab_size len(self.vocab) print(f模型已从 {filepath} 加载)8.3 生产环境注意事项虽然 Bigram 模型主要用于教学目的但如果要在生产环境中使用类似简单模型需要考虑性能优化对于大规模数据使用稀疏矩阵存储内存管理监控词汇表大小避免内存溢出版本控制保存模型版本和训练数据信息监控告警设置生成质量监控机制9. 从 Bigram 到现代大语言模型理解了 Bigram 模型后你就能更好地理解现代大语言模型的演进9.1 技术演进路径Bigram → N-gram考虑更长的上下文3-gram、4-gram等N-gram → 神经网络语言模型使用神经网络建模更复杂的依赖关系RNN/LSTM → Transformer处理长距离依赖并行化训练Transformer → 预训练微调大规模无监督预训练 任务特定微调9.2 核心概念映射Bigram 概念现代 LLM 对应概念说明字符级 Tokenization子词 TokenizationBPE、WordPiece 等更先进的切分方法概率矩阵神经网络参数从显式统计到隐式学习平滑处理正则化技术Dropout、Label Smoothing 等文本生成自回归生成使用相同的预测下一个词机制9.3 下一步学习建议掌握了 Bigram 模型后建议按以下路径继续学习实现 Trigram 模型理解更长上下文的影响学习神经网络基础全连接网络、RNN、LSTM理解注意力机制现代 LLM 的核心组件动手实现简单 Transformer从论文到代码的实践Bigram 模型的价值不在于其实际应用效果而在于它为我们理解更复杂模型提供了坚实的基础。当你下次看到千亿参数的大模型时你会知道在最基本的层面上它们仍然在做着和这个简单 Bigram 模型相同的事情预测下一个词。通过这个完整的实现和讲解你现在应该对语言模型的基本原理有了扎实的理解。这种从简单到复杂的学习路径比直接跳入复杂架构更能建立直觉和调试能力。建议你动手运行代码调整参数观察不同训练数据对模型效果的影响这是理论学习无法替代的宝贵经验。