赞
踩
统计语言模型旨在量化自然语言文本中序列的概率分布,即计算一个词序列(如一个句子或文档)出现的可能性。这类模型基于统计方法,利用大量文本数据学习语言的统计规律,进而预测未知文本的概率,或者为给定的文本序列生成最可能的后续词汇。
统计语言模型的核心思想是将语言视为一个随机过程,每一个词的选择都受到其上下文的影响。模型通常定义为一个概率函数,比如假设一个句子由 T T T个单词顺序组成:
W = w T : = ( w 1 , w 2 , ⋯ , w T ) W = w^{T} := (w_{1}, w_{2}, \cdots, w_{T}) W=wT:=(w1,w2,⋯,wT)
那么该句子的联合概率如下:
p ( w T ) = p ( w 1 ) ⋅ p ( w 2 ∣ w 1 ) ⋅ p ( w 3 ∣ w 1 2 ) ⋯ p ( w T ∣ x 1 T − 1 ) p(w^{T}) = p(w_{1}) \cdot p(w_{2}|w_{1}) \cdot p(w_{3}|w_{1}^{2}) \cdots p(w_{T}|x_{1}^{T-1}) p(wT)=p(w1)⋅p(w2∣w1)⋅p(w3∣w12)⋯p(wT∣x1T−1)
其中,模型参数为:
p ( w 1 ) , p ( w 2 ∣ w 1 ) , p ( w 3 ∣ w 1 2 ) , ⋯ , p ( w T ∣ w 1 T − 1 ) p(w_{1}), p(w_{2}|w_{1}) , p(w_{3}|w_{1}^{2}), \cdots, p(w_{T}|w_{1}^{T-1}) p(w1),p(w2∣w1),p(w3∣w12),⋯,p(wT∣w1T−1)
根据贝叶斯公式可得:
p ( w k ∣ w 1 k − 1 ) = p ( w 1 k ) p ( w 1 k − 1 ) p(w_{k}|w_{1}^{k-1}) = \frac{p(w_{1}^{k})}{p(w_{1}^{k-1})} p(wk∣w1k−1)=p(w1k−1)p(w1k)
根据大数定理可得:
p ( w k ∣ w 1 k − 1 ) ≈ c o u n t ( w 1 k ) c o u n t ( w 1 k − 1 ) p(w_{k}|w_{1}^{k-1}) \approx \frac{count(w_{1}^{k})}{count(w_{1}^{k-1})} p(wk∣w1k−1)≈count(w1k−1)count(w1k)
其中count表示统计词串在语料中的出现次数,当k比较大时,上述计算比较耗时。
N-gram模型是一种统计语言模型,用于预测给定文本中下一个词(或字符)的概率。该模型基于一个简化的假设:一个词(或字符)的出现概率只依赖于它前面的N-1个词(或字符),也就是n-1阶的马尔科夫假设。这里的N代表了模型考虑的上下文窗口大小,因此模型被称为N-gram。
p
(
w
k
∣
w
1
k
−
1
)
≈
p
(
w
k
∣
w
k
−
n
+
1
k
−
1
)
≈
count
(
w
k
−
n
+
1
k
)
count
(
w
k
−
n
+
1
k
−
1
)
N-gram模型中的概率通常通过从大型文本语料库中计算词序列的频次来估计。具体来说,使用最大似然估计(Maximum Likelihood Estimation, MLE)计算每个N-gram的概率,这些概率可以通过简单地统计每个N-gram在语料库中出现的频次来估计。
该模型基于这样一种假设,第N个词的出现只与前面N-1个词相关,而与其它任何词都不相关,整句的概率就是各个词出现概率的乘积。这些概率可以通过直接从语料中统计N个词同时出现的次数得到,然后可以使用这些概率来计算给定上下文情况下下一个词或字符的概率。常用的是二元(Bi-gram)建模和三元(Tri-gram)建模。
例如,在一个句子"ChatGPT is a powerful language model"中,如果我们使用2-gram,那么句子可以被分成以下2-gram序列:[“ChatGPT is”, “is a”, “a powerful”, “powerful language”, “language model”]。假设我们有一个足够大的文本语料库,其中包含很多句子。我们可以使用2-gram语言模型来计算给定一个词的前提下,下一个词出现的概率。如果我们想要预测句子中的下一个词,我们可以使用前面的一个词作为上下文,并计算每个可能的下一个词的概率。例如,在句子"ChatGPT is a"中,我们可以计算出给定上下文"ChatGPT is"下一个词"powerful"的概率。通过统计语料库中"ChatGPT is"后面是"powerful"的次数,并将其除以"ChatGPT is"出现的次数,我们可以得到这个概率。
在统计语言模型中,平滑操作是至关重要的一个步骤,主要目的是解决以下几个关键问题:
常见的平滑技术包括但不限于:
平滑不仅是统计语言模型构建中的一个必要步骤,也是提升模型实用性和准确性的重要手段。
以2-gram为例,最大似然估计如下:
p ( w i ∣ w i − 1 ) = c ( w i − 1 w i ) c ( w i − 1 ) p(w_i|w_{i-1}) = \frac{c(w_{i-1} w_i)}{c(w_{i-1})} p(wi∣wi−1)=c(wi−1)c(wi−1wi)
以上其实就是简单的计数,然后我们就要在这里去做平滑,其实就是减少分母为0的出现。拉普拉斯平滑如下:
p add ( w i ∣ w i − 1 ) = c ( w i − 1 w i ) + δ c ( w i − 1 ) + δ ∣ V ∣ p_{\text{add}}(w_i|w_{i-1}) = \frac{c(w_{i-1} w_i) + \delta}{c(w_{i-1}) + \delta |V|} padd(wi∣wi−1)=c(wi−1)+δ∣V∣c(wi−1wi)+δ
一般地, δ \delta δ取1, ∣ V ∣ |V| ∣V∣表示词典库的大小。
基于二元模型的简单示例,包括数据预处理、构建模型、平滑处理以及基于模型进行预测。
import collections
class BigramLanguageModel:
def __init__(self, sentences: list):
"""
初始化Bigram模型
:param sentences: 训练语料,类型为字符串列表
"""
self.sentences = sentences
self.word_counts = collections.Counter()
self.bigram_counts = collections.defaultdict(int)
self.unique_words = set()
# 预处理数据:分词并合并所有句子
words = ' '.join(sentences).split()
for w1, w2 in zip(words[:-1], words[1:]):
self.word_counts[w1] += 1
self.word_counts[w2] += 1
self.bigram_counts[(w1, w2)] += 1
self.unique_words.update([w1, w2])
def laplace_smooth(self, delta: float = 1.0):
"""
拉普拉斯平滑
:param delta: 平滑因子,默认为1.0
"""
V = len(self.unique_words) # 词汇表大小
self.model = {}
for w1 in self.unique_words:
total_count_w1 = self.word_counts[w1] + delta*V
self.model[w1] = {}
for w2 in self.unique_words:
count_w1w2 = self.bigram_counts.get((w1, w2), 0) + delta
self.model[w1][w2] = count_w1w2 / total_count_w1
def generate_text(self, start_word: str, length: int = 10) -> str:
"""
生成文本
:param start_word: 文本起始词
:param length: 生成文本的长度
:return: 生成的文本字符串
"""
if start_word not in self.model:
raise ValueError(f"Start word '{start_word}' not found in the model.")
sentence = [start_word]
current_word = start_word
for _ in range(length):
next_word_probs = self.model[current_word]
next_word = max(next_word_probs, key=next_word_probs.get)
sentence.append(next_word)
current_word = next_word
return ' '.join(sentence)
# 示例使用
corpus = [
"ChatGPT is a powerful language model for multi task",
"ChatGPT is a powerful language model",
"ChatGPT can generate human-like text",
"ChatGPT is trained using deep learning",
]
model = BigramLanguageModel(corpus)
model.laplace_smooth()
generated_text = model.generate_text('ChatGPT', 5)
print(generated_text) # ChatGPT is a powerful language model
准确率作为语言模型的评估指标没有太多意义,语言是开放的序列预测问题,给定前面的文本,下一个词的可能性是非常多的,因此准确率值会非常低。
作为替代,应该在模型保留数据(held-out data)上,计算其对应的似然性(取平均值以消除长度影响)来评估语言模型。对数似然(log likelihood,LL)计算如下:
LL ( W ) = 1 n ∑ i = 1 n log P ( w i ∣ w 1 , ⋯ , w i − 1 ) \text{LL}(W) = \frac{1}{n} \sum_{i=1}^n \log P(w_i|w_1, \cdots, w_{i-1}) LL(W)=n1i=1∑nlogP(wi∣w1,⋯,wi−1)
保留数据:指在训练语言模型时,专门保留一部分数据不参与训练,用作评估模型性能的数据集。这确保了评估数据独立于训练数据,能够真实反映模型在新数据上的泛化能力。
困惑度(Perplexity,PPL)是评价语言模型质量的一个重要指标,能够更好地体现语言模型对句子流畅性、语义连贯性的建模能力。困惑度是指数形式的平均负对数似然,计算如下:
PPL ( W ) = exp ( NLL ( W ) ) = exp ( − LL ( W ) ) \text{PPL}(W) = \exp (\text{NLL}(W) ) = \exp (- \text{LL}(W) ) PPL(W)=exp(NLL(W))=exp(−LL(W))
对数似然:给定语料库数据,对数似然衡量模型为这些数据赋予的概率的对数值。对数似然越高,模型对数据的建模能力越强。
负对数似然:由于对数似然值通常是个负值,取负号得到正值更利于分析比较。
平均负对数似然:将负对数似然值加总后除以数据长度(如词数),得到平均负对数似然。这样可以消除数据长度的影响,更公平地比较不同模型。
指数形式:将平均负对数似然值做指数运算,得到Perplexity值。由于似然本身很小,对数似然为负值,做指数能使结果值落在较合理的范围。
困惑度本质上反映了模型对数据的平均怀疑程度。值越低,说明模型对数据的建模质量越高、不确定性越小。通常优秀模型的困惑度值在10-100之间,值越接近1越好。
神经语言模型(Neural Language Model, NLM)是一种利用神经网络来建模和生成自然语言序列的模型。相比传统的统计语言模型(如n-gram模型),神经语言模型具有以下几个主要特点:
常见神经语言模型有基于RNN、LSTM、Transformer等不同网络架构,并广泛应用于语言建模、机器翻译、对话系统等自然语言处理任务中。神经语言模型弥补了传统模型的不足,极大地推进了语言模型的发展,但也面临训练资源需求大、解释性较差等新的挑战。
前馈神经网络(Feedforward NNs)无法处理可变长度的输入,特征向量中的每个位置都有固定的语义。使用传统前馈神经网络作为语言模型时的主要缺陷和局限性如下:
为了解决这个问题,出现了诸如循环神经网络(RNN)、长短期记忆网络(LSTM)等能够更好地处理序列输入的神经网络架构。
循环神经网络是一种可以接受变长输入和产生变长输出的网络架构类型,这与标准的前馈神经网络形成对比。我们也可以考虑变长的输入,比如视频帧序列,并希望在该视频的每一帧上都做出决策。
对于循环神经网络,我们可以在每个时间步应用递推公式来处理向量序列:
h t = f W ( h t − 1 , x t ) h_t = f_W(h_{t-1}, x_t) ht=fW(ht−1,xt)
对于简单的 Vanilla 循环神经网络来说,计算公式如下:
h
t
=
tanh
(
W
h
h
h
t
−
1
+
W
x
h
x
t
+
b
h
)
y
t
=
W
h
y
h
t
+
b
y
import numpy as np
np.random.seed(0)
class RecurrentNetwork(object):
"""When we say W_hh, it means a weight matrix that accepts a hidden state and produce a new hidden state.
Similarly, W_xh represents a weight matrix that accepts an input vector and produce a new hidden state. This
notation can get messy as we get more variables later on with LSTM and I simplify the notation a little bit in
LSTM notes.
"""
def __init__(self):
self.hidden_state = np.zeros((3, 3))
self.W_hh = np.random.randn(3, 3)
self.W_xh = np.random.randn(3, 3)
self.W_hy = np.random.randn(3, 3)
self.Bh = np.random.randn(3,)
self.By = np.random.rand(3,)
def forward_prop(self, x):
# The order of which you do dot product is entirely up to you. The gradient updates will take care itself
# as long as the matrix dimension matches up.
self.hidden_state = np.tanh(np.dot(self.hidden_state, self.W_hh) + np.dot(x, self.W_xh) + self.Bh)
return self.W_hy.dot(self.hidden_state) + self.By
input_vector = np.ones((3, 3))
rnn = RecurrentNetwork()
# Notice that same input, but leads to different ouptut at every single time step.
print(rnn.forward_prop(input_vector))
print(rnn.forward_prop(input_vector))
print(rnn.forward_prop(input_vector))
虽然 Vanilla RNN 在处理序列数据方面具有一定的能力,但它在长期依赖性建模方面存在一些挑战。长短期记忆网络(Long Short Term Memory, LSTM )是一种特殊类型的 RNN,通过引入细胞状态(cell state)和门控机制来解决长期依赖性问题。以下是 LSTM 相对于 Vanilla RNN 的一些优点和特点:
LSTM 相对于 Vanilla RNN 具有更强的记忆和建模能力,能够更好地处理长期依赖性和序列任务。它通过引入细胞状态和门控机制,解决了 Vanilla RNN 在处理长序列时出现的梯度消失和梯度爆炸问题。
LSTM 计算过程如下:
f
t
=
σ
(
W
h
f
h
t
−
1
+
W
x
f
x
+
b
f
)
i
t
=
σ
(
W
h
i
h
t
−
1
+
W
x
i
x
+
b
i
)
o
t
=
σ
(
W
h
o
h
t
−
1
+
W
x
o
x
+
b
o
)
c
t
=
f
t
⊙
c
t
1
+
i
t
⊙
tanh
(
W
g
x
x
+
W
g
h
h
t
−
1
+
b
g
)
h
t
=
o
t
⊙
tanh
(
c
t
)
其中, f t f_t ft表示遗忘门,控制记忆的遗忘程度; i t i_t it表示输入门,控制信息写入状态单元的程度; o t o_t ot表示输出门,控制状态单元的暴露程度; c t c_t ct表示状态单元,负责内部记忆; h t h_t ht表示隐藏单元,负责对外暴露信息。
import numpy as np
np.random.seed(0)
class LSTMNetwork(object):
def __init__(self):
self.hidden_state = np.zeros((3, 3))
self.cell_state = np.zeros((3, 3))
self.W_hh = np.random.randn(3, 3)
self.W_xh = np.random.randn(3, 3)
self.W_ch = np.random.randn(3, 3)
self.W_fh = np.random.randn(3, 3)
self.W_ih = np.random.randn(3, 3)
self.W_oh = np.random.randn(3, 3)
self.W_hy = np.random.randn(3, 3)
self.Bh = np.random.randn(3,)
self.By = np.random.randn(3,)
def forward_prop(self, x):
# Input gate
i = sigmoid(np.dot(x, self.W_xh) + np.dot(self.hidden_state, self.W_hh) + np.dot(self.cell_state, self.W_ch))
# Forget gate
f = sigmoid(np.dot(x, self.W_xh) + np.dot(self.hidden_state, self.W_hh) + np.dot(self.cell_state, self.W_fh))
# Output gate
o = sigmoid(np.dot(x, self.W_xh) + np.dot(self.hidden_state, self.W_hh) + np.dot(self.cell_state, self.W_oh))
# New cell state
c_new = np.tanh(np.dot(x, self.W_xh) + np.dot(self.hidden_state, self.W_hh))
self.cell_state = f * self.cell_state + i * c_new
self.hidden_state = o * np.tanh(self.cell_state)
return np.dot(self.hidden_state, self.W_hy) + self.By
def sigmoid(x):
return 1 / (1 + np.exp(-x))
input_vector = np.ones((3, 3))
lstm = LSTMNetwork()
# Notice that the same input will lead to different outputs at each time step.
print(lstm.forward_prop(input_vector))
print(lstm.forward_prop(input_vector))
print(lstm.forward_prop(input_vector))
LSTM 可以一定程度上缓解梯度消失问题,但对于非常长的序列或复杂的任务,仍然存在一定的限制。除了梯度消失问题,LSTM 在处理序列时也存在性能上的限制。由于它们是逐步处理序列的,无法充分利用并行计算的优势。对于长度为 n 的序列,LSTM 需要执行 O(n) 的非并行操作来进行编码,导致速度较慢。
为了克服这些限制,提出了 Transformer 模型作为解决方案。Transformer 可以扩展到处理数千个单词的序列,并且能够并行计算。它引入了自注意力机制和位置编码,使得模型能够同时关注序列中不同位置的信息,并且能够以高效的方式对输入进行编码。这使得 Transformer 在处理长序列和大规模数据时具有优势,并且在机器翻译和自然语言处理等领域取得了显著的成功。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。