统计语言模子旨在量化自然语言文本中序列的概率分布,即计算一个词序列(如一个句子或文档)出现的大概性。这类模子基于统计方法,使用大量文本数据学习语言的统计规律,进而推测未知文本的概率,大概为给定的文本序列生成最大概的后续词汇。
统计语言模子的核心思想是将语言视为一个随机过程,每一个词的选择都受到其上下文的影响。模子通常定义为一个概率函数,比如假设一个句子由 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比较大时,上述计算比较耗时。
2. N-gram 语言建模
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 ) \begin{aligned} p(w_{k}|w_{1}^{k-1}) &\approx p(w_{k}|w_{k-n+1}^{k-1}) \\ &\approx \frac{\text{count}(w_{k-n+1}^{k})}{\text{count}(w_{k-n+1}^{k-1})} \end{aligned} p(wk∣w1k−1)≈p(wk∣wk−n+1k−1)≈count(wk−n+1k−1)count(wk−n+1k)
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.1. N-gram 语言模子中的平滑处理
在统计语言模子中,平滑操纵是至关重要的一个步骤,主要目的是办理以下几个关键题目:
零概率题目(Zero Frequency Problem):在基于计数的统计语言模子中,如果某个词或词序列在练习数据中没有出现过,那么其概率会被直接估计为零,这显然不符合实际情况,因为未观测到并不意味着不大概发生。平滑通太过配一些概率质量给这些零频率事件,确保所有大概的事件都有非零的概率。
平滑不仅是统计语言模子构建中的一个必要步骤,也是提拔模子实用性和准确性的重要本领。
以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∣体现词典库的大小。
对于循环神经网络,我们可以在每个时间步应用递推公式来处理向量序列:
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 \begin{aligned} h_t &= \tanh (W_{hh} h_{t-1} + W_{xh} x_t + b_h) \\ y_t &= W_{hy} h_t + b_y \end{aligned} htyt=tanh(Whhht−1+Wxhxt+bh)=Whyht+by
Vanilla RNN 的 Python 实现
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
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 ) \begin{aligned} f_t &= \sigma(W_{hf} h_{t-1} + W_{xf} x + b_f) \\ i_t &= \sigma(W_{hi} h_{t-1} + W_{xi} x + b_i) \\ o_t &= \sigma(W_{ho} h_{t-1} + W_{xo} x + b_o) \\ c_t &= f_t \odot c_{t_1} + i_t \odot \tanh(W_{gx} x + W_{gh} h_{t-1} + b_g) \\ h_t &= o_t \odot \tanh(c_t) \end{aligned} ftitotctht=σ(Whfht−1+Wxfx+bf)=σ(Whiht−1+Wxix+bi)=σ(Whoht−1+Wxox+bo)=ft⊙ct1+it⊙tanh(Wgxx+Wghht−1+bg)=ot⊙tanh(ct)
其中, 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体现隐藏单元,负责对外暴露信息。
LSTM 的 Python 实现
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))