第01章:Bigram语言模型(语言建模)#
1. 语言模型基础概念#
什么是语言模型#
语言模型是自然语言处理(NLP)领域的核心技术,它的基本任务是预测文本序列中的下一个词或字符。从本质上讲,语言模型试图捕捉人类语言的统计规律,理解词与词之间的关联和依赖关系。一个优秀的语言模型能够生成流畅、连贯且符合语法规则的文本,就像是由人类撰写的一样。
语言模型可以形式化地定义为:给定一个词序列 $w_1, w_2, …, w_{n-1}$,模型需要计算下一个词 $w_n$ 出现的概率分布 $P(w_n | w_1, w_2, …, w_{n-1})$。这个条件概率表示了在已知前面所有词的情况下,下一个词可能是什么。
语言模型的历史发展#
语言模型的发展历程可以大致分为以下几个阶段:
统计语言模型时代(1980s-2000s):
N-gram模型:最早的语言模型主要基于N-gram统计,如Bigram(二元语法)和Trigram(三元语法)。这些模型假设一个词的出现只与其前面的N-1个词有关。
平滑技术:为了解决数据稀疏问题,研究者提出了各种平滑方法,如拉普拉斯平滑、Good-Turing平滑等。
回退模型:当高阶N-gram没有足够统计数据时,回退到低阶模型。
神经网络语言模型时代(2003-2013):
前馈神经网络语言模型:Bengio等人在2003年提出了第一个基于神经网络的语言模型,使用词嵌入和前馈神经网络来预测下一个词。
循环神经网络(RNN):Mikolov等人使用RNN构建语言模型,能够捕捉更长距离的依赖关系。
长短期记忆网络(LSTM)和门控循环单元(GRU):这些改进的RNN架构解决了标准RNN的梯度消失问题,能够学习更长序列的依赖关系。
Transformer时代(2017-至今):
Transformer架构:2017年,Vaswani等人提出的Transformer架构通过自注意力机制彻底改变了NLP领域。
预训练语言模型:GPT、BERT、T5等大型预训练模型的出现,将语言模型的能力提升到了新的高度。
大型语言模型(LLM):如GPT-3、GPT-4、LLaMA、Claude等拥有数十亿到数千亿参数的模型,展现出了惊人的语言理解和生成能力。
语言模型的应用场景#
语言模型在现代人工智能和自然语言处理中有着广泛的应用:
文本生成:
故事创作和内容生成
自动写作辅助
对话系统和聊天机器人
诗歌、歌词创作
机器翻译:
语言模型可以帮助生成更流畅的翻译结果
多语言翻译系统
文本摘要:
自动生成长文档的摘要
新闻摘要和报告生成
问答系统:
基于知识的问答
开放域问答
语音识别:
提高语音转文字的准确性
语音助手系统
代码生成与补全:
编程辅助工具
自动代码生成
文本纠错与改写:
语法检查和纠正
风格转换和文本改写
随着大型语言模型的发展,语言模型的应用场景还在不断扩展,正在改变人类与计算机交互的方式。
2. 概率论基础#
要理解语言模型,特别是统计语言模型,我们需要掌握一些基本的概率论概念。
条件概率#
条件概率是指在已知一个事件B发生的情况下,另一个事件A发生的概率,记作P(A|B)。
数学定义: $$P(A|B) = \frac{P(A \cap B)}{P(B)}$$
其中,P(A∩B)是事件A和B同时发生的概率,P(B)是事件B发生的概率。
在语言模型中,我们关心的是在已知前面的词的条件下,下一个词出现的概率。例如,P(“学习”|”我喜欢”)表示在”我喜欢”之后出现”学习”的概率。
联合概率#
联合概率是指多个事件同时发生的概率,记作P(A,B)或P(A∩B)。
在语言模型中,一个句子的联合概率可以表示为: $$P(w_1, w_2, …, w_n) = P(w_1) \times P(w_2|w_1) \times P(w_3|w_1,w_2) \times … \times P(w_n|w_1,w_2,…,w_{n-1})$$
这个公式使用了概率的链式法则,将联合概率分解为条件概率的乘积。
马尔可夫假设#
在实际应用中,计算完整的条件概率$P(w_n|w_1,w_2,…,w_{n-1})$非常困难,因为随着历史长度的增加,可能的组合数量呈指数级增长,导致数据稀疏问题。
为了简化计算,我们引入马尔可夫假设:假设当前状态只依赖于有限的前k个状态,而与更早的状态无关。
在语言模型中,k阶马尔可夫假设可以表示为: $$P(w_n|w_1,w_2,…,w_{n-1}) \approx P(w_n|w_{n-k},…,w_{n-1})$$
特别地,当k=1时,我们得到一阶马尔可夫假设: $$P(w_n|w_1,w_2,…,w_{n-1}) \approx P(w_n|w_{n-1})$$
这就是Bigram模型的基础,它假设一个词的出现只与其前一个词有关。
3. Bigram模型详解#
Bigram模型的数学定义#
Bigram(二元语法)模型是最简单的N-gram模型之一,它基于一阶马尔可夫假设,认为一个词的出现只与其前一个词相关。
在Bigram模型中,一个句子的概率可以表示为: $$P(w_1, w_2, …, w_n) = P(w_1) \times P(w_2|w_1) \times P(w_3|w_2) \times … \times P(w_n|w_{n-1})$$
其中,$P(w_i|w_{i-1})$是条件概率,表示在词$w_{i-1}$之后出现词$w_i$的概率。
为了处理句子的开始,我们通常引入一个特殊的开始标记<s>,并将$P(w_1)$表示为$P(w_1|<s>)$。同样,为了处理句子的结束,我们引入结束标记</s>。
从文本数据构建Bigram模型#
构建Bigram模型的过程主要包括以下步骤:
数据预处理:
分词:将文本分割成词或字符序列
添加特殊标记:在每个句子的开始和结束添加<s>和</s>标记
构建词汇表:统计所有不同的词,并为每个词分配一个唯一的索引
统计频率:
统计每个词对$(w_{i-1}, w_i)$在语料库中出现的次数,记为$count(w_{i-1}, w_i)$
统计每个词$w_{i-1}$在语料库中出现的次数,记为$count(w_{i-1})$
计算条件概率:
使用最大似然估计(MLE)计算条件概率: $$P(w_i|w_{i-1}) = \frac{count(w_{i-1}, w_i)}{count(w_{i-1})}$$
Bigram模型的参数估计#
最大似然估计是Bigram模型最基本的参数估计方法,但它存在一个严重问题:对于在训练数据中未出现的词对,其条件概率将为零,这会导致整个句子的概率为零,即使只有一个词对未在训练数据中出现。
为了解决这个问题,我们需要使用平滑技术。以下是几种常见的平滑方法:
拉普拉斯平滑(加一平滑): $$P(w_i|w_{i-1}) = \frac{count(w_{i-1}, w_i) + 1}{count(w_{i-1}) + V}$$ 其中,V是词汇表的大小。
加k平滑: $$P(w_i|w_{i-1}) = \frac{count(w_{i-1}, w_i) + k}{count(w_{i-1}) + k \times V}$$ 其中,k是一个小于1的正数。
Good-Turing平滑: 调整频率计数,使得未见事件获得一些概率质量。
插值平滑: 将高阶模型与低阶模型结合: $$P(w_i|w_{i-1}) = \lambda P_{ML}(w_i|w_{i-1}) + (1-\lambda)P(w_i)$$ 其中,λ是一个介于0和1之间的插值参数,$P_{ML}$是最大似然估计,$P(w_i)$是一元语法概率。
回退平滑: 当高阶N-gram没有足够的统计数据时,回退到低阶模型。
4. 实现一个简单的Bigram模型#
下面我们将实现一个简单的Bigram语言模型,包括数据预处理、模型训练和文本生成。
数据预处理#
首先,我们需要对文本数据进行预处理,包括分词、添加特殊标记和构建词汇表。
def preprocess_text(text):
# 分词(这里简单地按空格分割)
words = text.lower().split()
# 添加特殊标记
sentences = []
current_sentence = ['<s>']
for word in words:
if word in ['.', '!', '?']:
current_sentence.append('</s>')
sentences.append(current_sentence)
current_sentence = ['<s>']
else:
current_sentence.append(word)
# 处理最后一个句子
if len(current_sentence) > 1: # 不只包含<s>
current_sentence.append('</s>')
sentences.append(current_sentence)
# 构建词汇表
vocab = set()
for sentence in sentences:
for word in sentence:
vocab.add(word)
return sentences, vocab
模型训练#
接下来,我们统计词对频率并计算条件概率:
def train_bigram_model(sentences, vocab, smoothing='laplace'):
# 初始化计数器
bigram_counts = {}
unigram_counts = {}
# 统计频率
for sentence in sentences:
for i in range(len(sentence) - 1):
w_prev = sentence[i]
w_curr = sentence[i + 1]
# 更新二元语法计数
if w_prev not in bigram_counts:
bigram_counts[w_prev] = {}
if w_curr not in bigram_counts[w_prev]:
bigram_counts[w_prev][w_curr] = 0
bigram_counts[w_prev][w_curr] += 1
# 更新一元语法计数
if w_prev not in unigram_counts:
unigram_counts[w_prev] = 0
unigram_counts[w_prev] += 1
# 计算条件概率
bigram_probs = {}
vocab_size = len(vocab)
for w_prev in bigram_counts:
bigram_probs[w_prev] = {}
for w_curr in vocab:
if smoothing == 'laplace':
# 拉普拉斯平滑
count = bigram_counts[w_prev].get(w_curr, 0)
bigram_probs[w_prev][w_curr] = (count + 1) / (unigram_counts[w_prev] + vocab_size)
elif smoothing == 'none':
# 无平滑(最大似然估计)
if w_curr in bigram_counts[w_prev]:
bigram_probs[w_prev][w_curr] = bigram_counts[w_prev][w_curr] / unigram_counts[w_prev]
else:
bigram_probs[w_prev][w_curr] = 0
return bigram_probs
文本生成#
有了训练好的Bigram模型,我们可以生成新的文本:
import random
def generate_text(bigram_probs, max_length=20):
# 从<s>开始
current_word = '<s>'
generated_text = []
# 生成文本,直到遇到</s>或达到最大长度
while current_word != '</s>' and len(generated_text) < max_length:
# 获取当前词之后可能出现的所有词及其概率
next_word_probs = bigram_probs.get(current_word, {})
if not next_word_probs:
break
# 按概率随机选择下一个词
words = list(next_word_probs.keys())
probs = list(next_word_probs.values())
# 归一化概率
sum_probs = sum(probs)
if sum_probs > 0:
probs = [p / sum_probs for p in probs]
next_word = random.choices(words, weights=probs, k=1)[0]
else:
# 如果所有概率都为0,随机选择
next_word = random.choice(words)
if next_word != '</s>':
generated_text.append(next_word)
current_word = next_word
return ' '.join(generated_text)
完整示例#
下面是一个完整的示例,展示如何使用上述代码训练Bigram模型并生成文本:
# 示例文本
text = """
The quick brown fox jumps over the lazy dog.
The dog barks at the fox.
The fox runs away quickly.
"""
# 预处理文本
sentences, vocab = preprocess_text(text)
print(f"词汇表大小: {len(vocab)}")
print(f"句子数量: {len(sentences)}")
# 训练Bigram模型
bigram_probs = train_bigram_model(sentences, vocab, smoothing='laplace')
# 生成文本
for _ in range(5):
generated_text = generate_text(bigram_probs)
print(f"生成的文本: {generated_text}")
这个简单的Bigram模型可以生成基本的文本,但由于只考虑了前一个词的影响,生成的文本通常缺乏长距离的连贯性和语义一致性。
5. Bigram模型的局限性#
尽管Bigram模型简单易实现,但它存在一些明显的局限性:
稀疏性问题#
Bigram模型面临的主要挑战之一是数据稀疏性。即使在大型语料库中,也会有许多合法的词对从未出现过。这导致模型对这些未见词对的概率估计为零或接近零,影响模型的泛化能力。
例如,假设训练语料库中从未出现过”人工智能”后面跟着”革命”这个词对,但这是一个完全合理的组合。Bigram模型会给这个组合分配很低的概率,即使它在语义上是合理的。
上下文有限问题#
Bigram模型只考虑前一个词的影响,忽略了更广泛的上下文。这导致生成的文本可能在局部上看起来合理,但整体上缺乏连贯性和一致性。
例如,在句子”我昨天去了_____”中,填空词的选择应该受到整个前文的影响,而不仅仅是”了”这个词。Bigram模型无法捕捉这种长距离依赖关系。
平滑技术简介#
为了缓解数据稀疏性问题,我们介绍了几种平滑技术。这些技术通过从高频事件中”偷取”一些概率质量并重新分配给低频或未见事件,使模型能够更好地处理未见词对。
然而,平滑技术只能部分缓解问题,无法从根本上解决Bigram模型的局限性。为了构建更强大的语言模型,我们需要:
考虑更长的上下文:使用更高阶的N-gram模型(如Trigram、4-gram等)或循环神经网络(RNN)、Transformer等能够捕捉长距离依赖的模型。
使用词嵌入:将离散的词表示为连续的向量,使模型能够捕捉词之间的语义相似性。
引入神经网络:利用神经网络的强大表示能力,学习更复杂的语言模式。
在接下来的章节中,我们将逐步探索这些更先进的技术,最终构建一个强大的故事讲述AI大语言模型。
总结#
在本章中,我们介绍了语言模型的基本概念、历史发展和应用场景,学习了概率论的基础知识,详细讲解了Bigram模型的原理和实现方法,并讨论了Bigram模型的局限性。
Bigram模型是理解更复杂语言模型的基础,它通过简单的统计方法捕捉词与词之间的局部关系。尽管它存在明显的局限性,但Bigram模型为我们提供了一个理解语言建模核心思想的起点。
在下一章中,我们将学习Micrograd,这是一个微型自动微分引擎,它将为我们构建神经网络语言模型奠定基础。通过Micrograd,我们将深入理解机器学习的核心概念和反向传播算法,为后续章节中更复杂模型的实现做好准备。