Advertisement

Ngram模型在机器翻译中的应用

阅读量:

作者:禅与计算机程序设计艺术

统计语言模型是N-gram方法的核心概念。它主要用于评估特定词语序列发生的可能性。最初由Kneser和Young提出这一理论,并随后由Dahl等学者进一步发展为生成型语言模型。其基础是基于上下文的条件概率分布,在这种分布下可以通过已知的词语排列预测下一个可能出现的词语。例如,在分析"I love you"这样的语句时(如图所示),我们可以表示为:P(w_i|w_{i-1}, w_{i-2},..., w_{i-n+1}) 。通过这种方法,在已知部分词语的情况下能够有效推断出后续可能出现的词语。

在机器翻译中必须为源语言句子与目标语言句子搭建关联关系, 即how to model the probability distribution of aligned bilingual sentence pairs. 考虑到两种语言间的相似性, 固定模式以及不规则性等因素, 基于这样的基础, 目前广泛使用的机器翻译方法主要有:

纯直译策略:这一方法旨在通过精确地将原语句中的每一个词转换为对应的语言单位来实现对源语言文本的准确传达。尽管这种策略在操作上较为直接且易于执行,并且能够在很大程度上维持译文与原文的信息一致性和可理解性;然而需要注意的是,并非所有译文都能达到完美的效果。

2.统计机器翻译方法(Statistical machine translation, SMT):该方法通过构建统计语言模型来建模两种语言之间的关系。即通过将源语言句子建模为统计概率分布的形式,并将其与目标语言句子同样被建模为相同的统计概率分布进行对比。为了使两个模型的学习结果一致,我们采用了交叉熵损失函数作为优化目标,并利用梯度下降法或其他优化算法训练参数以最小化损失。这种方法不仅克服了传统直接翻译法的局限性,在处理复杂语义关系方面表现更为出色。然而,在实际应用中仍存在性能瓶颈和时间开销较大的问题。

3.Neural Machine Translation (NMT):该技术基于神经网络实现端到端的机器翻译过程。相较于传统方法具有显著区别,在于NMT无需单独建模源语言和目标语言的语法结构差异点之间建立明确关联;相反地是直接从数据中学习双语间的转换模式,并无须人工标注对应关系进行训练。该技术能够有效处理包括长句子、复杂语法结构、非线性转换以及多样的输入输出形式等问题。鉴于其高效性能和广泛适用性特征使其受到广泛关注并被越来越多的人采用

本文主要介绍第三种机器翻译方法——N-gram模型在机器翻译中的应用。

2.基本概念术语说明

2.1.N元语法

N元语法是一种基于词法分析的技术。根据其理论模型,在分析过程中需要考虑每个输入符号的属性及其相互之间的关系。研究者通过建立适当的上下文信息来推导出正确的分析结果。在英语中,通常会按照名词-动词-介词-形容词-副词的形式来组织表达内容。这些表达形式被称为短语成分

2.2.N元语法模型

该概率模型被定义为N元语法模型。该模型假设语言由若干个连续排列的语法单元构成,并基于概率理论描述每个单元在语言中的频率分布。从而可以构建一个用于生成句子的概率生成器。即基于观察到各单元在不同语境中的使用情况估计某些未出现的单元可能存在的频率特性。

N元语法模型又可分为三类:

  • unigram model: 该模型视作仅考虑单个前缀单词之间的关系。
  • bigram model: bigram model则将每个单词与其前两个单词视为关联项。
  • trigram model: trigram model则将每个单词与其前三个单词视为关联项。

2.3.N-gram语言模型

该类语言模型属于概率型的统计工具,并通过条件概率分析推测某个单词在特定语境下的出现可能性。该模型基于条件概率假设,并通过贝叶斯定理计算出不同词语组合的概率值。这种模型提供了计算文本生成概率的技术,在实际应用中能够有效评估不同词语组合的可能性。该类N-gram语言模型的基本假设有两点:第一点是,在一个给定的文本序列中(如一段话),当前单词的概率仅受前面几个单词的影响;第二点是,在相同的上下文中(即前n个单词相同的情况下),后续单词的概率分布是一致的。基于上述假设构建的语言模型采用统计方法评估单个单词的概率,并通过累积的方式估算整个句子的概率值。

具体而言, 语言模型会估算给定长度n的文本序列出现的概率. 所谓'文本序列', 实际上指的是由一串词组成的连续排列. 比如说, 对于一个句子而言, 它的序列即由句子中的每个词构成. 在一段文本里, 任何长度为n的连续词组都可视为由其前n-1个词所决定. 换言之, 如果我们有一个包含有四个单词"the quick brown fox"(尽管这个短语让我想起了一只狗)的文字片段, 那么很容易就能推断出它是一句话(因为它仅包含四个单词). 然而, 如果我们看到另一个包含有四个单词"quick the brown dog"的文字片段, 则很难判断这两个短语是否是同一句话的内容. 不过, 如果我们知道'quick' 'brown' 'dog'这些单词出现的概率分布情况, 就可以用n元语法语言模型来计算它们属于同一句话的可能性.

为了更全面地表达语言信息, 可以引入更多词汇而不限于少数词语. 此外, 该模型还有能力参考过去的数据来预测当前单词的概率. 具体而言, 在计算某个单词的概率时, 不仅需要考虑它的语法属性, 还要结合其前后文的相关信息.

与其他模型不同的是,在n-gram语言模型中我们仅专注于该词前面的n-1个关键词。其原因在于这些前面的关键词通常已经能够充分反映该词的意义所在,并不会对其产生决定性影响的作用。基于此我们可以得出结论:当我们统计这些关键词时就能据此推断出该单词的概率分布情况

考虑到n-gram模型本身的复杂性,并没有一种统一的标准能够有效地评估其性能。然而,在大多数实际应用中,默认选择相对较大的n值(例如3或5)通常能带来更好的性能表现。相比之下,在较低的n值(例如1或2)时,则一般难以获得显著的效果提升。

3.核心算法原理和具体操作步骤以及数学公式讲解

3.1.原理简介

3.1.1.训练集与测试集

首先

3.1.2.字典构建

接着,在实际应用中无法穷举所有词汇的情况下,请构建一个包含所有出现在训练集中的词的字典。这是因为我们需要用这个字典来建模语言模型。

3.1.3.N-gram特征集

在计算语言模型的过程中,在为了实现这一目标之前,在必须先设定N-gram特征集合以便于后续运算的基础之上,在每个特征集元素都是由长度为n的具体词语所构成的前提条件下,在进行相关运算时,在满足特定需求的前提下,在完成所有必要的参数设置之后,在完成整个系统的构建过程中

((w_{i-2}, w_{i-1}), (w_{i-2}, w_{i-1}, w_{i}))

其中w_{i}表示第i个词,(w_{i-2}, w_{i-1})表示前两个词。

注意,特征集中的元素是有序的,即按照词的出现顺序排列。

3.1.4.平滑技术

为了应对零概率问题,在训练集中未曾出现的词序列难以准确估计其概率值。因此必须引入相应的平滑技术以弥补这一缺陷。在实际应用中通常会采用两种主要的解决方案:一种是在计算概率时对各特征计数进行加一修正;另一种则是根据历史数据赋予不同权重来调整预测结果。

3.1.4.1.加一平滑

加一平滑技术是一种较为基础的处理手段。针对某个连续的词组w_{i}...w_{j}, 在训练数据中未记录到该特征时, 我们假设其出现的概率值被设定为1除以总共有多少种可能的连续词组的数量。其中X代表在训练数据中所有可能的连续词组的数量。

3.1.4.2.加权平滑

加权平滑是一种更为复杂的平滑方法。它给每个词赋予了一个权重,并将这些权重的乘积用作分母。这些权重的选择非常灵活。常见的选择包括Laplace分布、正态分布以及unigram模型。

P((w_{i}...w_{j})) = \frac{C(w_{i}...w_{j})} {\sum C(w_{k}...w_{l})}

在该处定义中,C(w_{i}...w_{j})具体说明了特征(w_{i}...w_{j})的频次。\sum C(w_{k}...w_{l})则计算了所有可能词序列的总频次。

3.1.4.3.选择平滑方式

每一种平滑方法都可能带来各自的表现特征。一般情况下,在某个词序列出现频率较低时,则倾向于采用加一平滑;而在该词序列出现频率较高时,则更适宜选择加权平滑。

3.1.5.训练

在训练过程中, 模型是基于训练集来学习其参数. 具体来说, 则是推导出每个特征对应的条件概率分布. 具体计算公式如下:

P(w_{i}|w_{i-1}...w_{i-n+1}) = \frac{\#(w_{i}...w_{i-n+1} w_{i}) + k}{\#(w_{k}...w_{l}) + V * k}

V表示字典大小,k表示平滑系数。

3.1.6.测试

在测试过程中,我们将测试集中的源语言句子与目标语言句子配对,并将这些配对输入到模型中。接着模型会对这些配对进行预测,生成对应的词序列。为了处理那些在训练集中未出现的词序列,在输出概率分布时会应用平滑技术将其映射至0概率;随后我们使用困惑度指标来评估模型的表现。

困惑度被定义为一个指标,并且它被用来衡量模型在目标语言中生成词语序列的能力。该指标反映了模型对目标语言句子生成过程中的平均质量表现。通常情况下,在较低的困惑度下能够实现较高的预测精度。其计算公式如下:P_{\text{ perplexity }} = \exp\left(-\frac{1}{N} \sum_{i=1}^{N} \log p(x_i)\right)

$\begin{align*}
&\text{cross-entropy loss}\
&=-\frac{1}{T}\sum_{t=1}^{T}\left[y_t\log P(x_t) + (1-y_t)\log(1-P(x_t))\right]\
&=-\frac{1}{T}\sum_{t=1}{T}\left[\sum_{i=1}{\lvert \hat{x}t\rvert}\log p\theta(x_{ti}|x_t)
\end{align*}

此处定义为测试集中的样本数量。具体来说是测试数据集中样本的数量。该变量代表的是一个具体的数值指标。

3.2.Python实现

下面是一个Python版本的N-gram语言模型实现:

复制代码
    import math
    import collections
    
    def ngrams(sentence, n):
    words = sentence.split()
    for i in range(len(words)-n+1):
        yield tuple(words[i:i+n])
    
    class NGramLM():
    
    def __init__(self, sentences, n, alpha=0.1):
        self.sentences = list(map(str.lower, sentences)) # convert to lowercase and split into sentences
        self.n = n
        self.alpha = alpha
    
        # build dictionary and count frequency of each word sequence
        self._count = collections.Counter()
        self._vocab = set()
        for sentence in self.sentences:
            for ng in ngrams(sentence, self.n):
                self._count[ng] += 1
                self._vocab |= set(ng)
        
        self._total_word_count = sum([self._count[ng] for ng in self._count if all(''not in token for token in ng)]) + len(self._vocab)*self.alpha
    
    def train(self, smoothing='laplace', weighting='none'):
        # initialize parameters with random values
        params = {}
        for ng in self._vocab:
            params[(tuple(ng[:-1]), ng[-1])] = [math.random(), math.random()]
    
        # training loop
        num_sentences = float(len(self.sentences))
        total_loss = 0.0
        for sentence in self.sentences:
            loss = 0.0
            
            # compute negative log likelihood loss and gradient update for every n-gram in sentence
            features = list(ngrams(sentence, self.n))
            targets = [f[-1] for f in features]
    
            for i in range(len(features)):
                f = features[i]
                target = targets[i]
                
                context = tuple(f[:-1])
    
                # calculate probability using linear interpolation between current parameter estimate and fixed uniform distribution
                p = ((params[context][target == t])[0] + self.alpha)/(float(self._total_word_count)+self.alpha*(self.n**len(self._vocab)))
                
                loss -= math.log(max(p, 1e-20)) # add small value to prevent underflow errors
        
                grad = [- (1 if target == t else 0) / max(p, 1e-20)
                          for t in sorted(set(targets))]
                
                # smooth by adding Laplace or weighted version
                if smoothing=='laplace':
                    grad = [(g+self.alpha)/float(self._total_word_count+self.alpha*self.n**(len(self._vocab)))
                            for g in grad]
                elif smoothing=='weighted':
                    pass
                    
                # scale gradients for learning rate and update parameters
                delta = [d*min(1/num_sentences, 1./(self._count[f]+1e-10))
                         for d in grad]
                params[context] = [a-d for a,d in zip(params[context],delta)]
    
            total_loss += loss
        
        return params
    
    def evaluate(self, model):
        score = 0.0
        total_word_count = 0.0
        for sentence in self.sentences:
            ngram_counts = collections.defaultdict(int)
            ngram_scores = []
            
            # generate predictions from language model
            for i in range(len(sentence)-self.n+1):
                feature = tuple(sentence[i:i+self.n])
                
                context = tuple(feature[:-1])
                next_word = feature[-1]
    
                if context in model:
                    prob = model[context][next_word][0]/model[context][None][0]
                    ngram_counts[next_word] += int(prob > 0.5)
                    ngram_scores.append(prob)
    
            # use n-best candidates for prediction
            scores = sorted([(s,l) for l,s in enumerate(ngram_scores)], reverse=True)[:len(feature)]
            pred = sorted([s for s,_ in scores], reverse=True)[0]
            
            total_word_count += len(feature)
            score += sum([score for _,score in scores])
            
        print("perplexity:", math.exp(-score/total_word_count))
        
        return {'perplexity': math.exp(-score/total_word_count)}

3.2.1.初始化NGramLM对象

创建一个NGramLM对象时,请指定训练数据中的句子集合、所选n-gram阶数以及对应的alpha参数。其中alpha参数用于控制模型的平滑效果,默认设置为0.1。

3.2.2.train函数

在模型训练过程中,需配置平滑方式与权重系数设置,在当前版本中可提供的平滑方式包括Laplace平滑(即Laplace smoothing)以及加权平滑(Weighted smoothing)。相对应地,在权重系数方面可供选择的是不使用权重(即Weight coefficient为none)或采用unigram模型计算权重(即Weight coefficient为unigram)。系统默认状态下会应用Laplace平滑并禁用权重系数

训练完成后,返回训练好的模型参数。

3.2.3.evaluate函数

通过训练好的模型对测试集执行评估任务,并输出以字典形式呈现的评估结果。其中'perplexity’被定义为衡量模型复杂度的重要指标。

4.具体代码实例和解释说明

本节通过一个具体的案例来演示N-gram语言模型的实现,并对模型的运行过程进行详细阐述

首先,导入必要的包:

复制代码
    from nltk.tokenize import sent_tokenize
    import re
    
    train_data = "The quick brown fox jumps over the lazy dog." 
    test_data = "The slow blue elephant leaps over the sleepy rat."
    
    sents = [' '.join(re.findall('\w+', s)) for s in sent_tokenize(train_data)]
    print(sents) #[u'the quick brown fox jumps over the lazy dog']
    
    lm = NGramLM(sents, n=2) # initialize object with n=2, default alpha=0.1
    model = lm.train(smoothing='laplace') # train on training data with laplace smoothing
    
    results = lm.evaluate(model) # evaluate test data using trained model
    print(results['perplexity']) # perplexity should be around 179.8736

在本例中, 训练集与测试集共包含1个句子. 其中, 训练集仅包含一个句子: 'The quick brown fox jumps over the lazy dog.'

接着,使用NLTK库对训练集分句、词汇切分,并将词序列变为小写。

复制代码
    sents = [' '.join(re.findall('\w+', s)) for s in sent_tokenize(train_data)]
    print(sents) #[u'the quick brown fox jumps over the lazy dog']

在训练模型时,我们只需要设置n值即可;通常情况下会采用Laplace平滑方法,并且无需考虑权重因素。

复制代码
    lm = NGramLM(sents, n=2) # initialize object with n=2, default alpha=0.1
    model = lm.train(smoothing='laplace') # train on training data with laplace smoothing

训练完成后,查看模型参数。

复制代码
    for context,dist in model.items():
    print('{} -> {}'.format(context, dist))
    
    #{()} -> [(0.067400187489713293, 0.27331216303774024), ('jumps', 0.26687836962259763), ('over', 0.26687836962259763), ('lazy', 0.13343918481129881), ('dog.', 0.13343918481129881)}
    #(('the', 'quick'), 0.067400187489713293) -> [('jumps', 0.047138452236325245), ('over', 0.047138452236325245), ('lazy', 0.027564892222103653), ('dog.', 0.027564892222103653)]
    #(('quick', 'brown'), 0.067400187489713293) -> [('fox', 0.03030870242781265), ('jumps', 0.016269337639611778), ('over', 0.016269337639611778), ('lazy', 0.013782446111051827), ('dog.', 0.013782446111051827)]
    #(('brown', 'fox'), 0.067400187489713293) -> [('jumps', 0.01910926629664261), ('over', 0.01910926629664261), ('lazy', 0.013782446111051827), ('dog.', 0.013782446111051827)]
    #('fox', ()) -> [('jumps', 0.15366741288707247), ('over', 0.15366741288707247), ('lazy', 0.07683370644353624), ('dog.', 0.07683370644353624)]
    #('jumps', ()) -> [('over', 0.23094269363686373), ('lazy', 0.07683370644353624), ('dog.', 0.07683370644353624)]
    #('over', ()) -> [('lazy', 0.10620186648927612), ('dog.', 0.10620186648927612)]
    #('lazy', ()) -> [('dog.', 0.10620186648927612)]
    #('dog.', ()) -> []

接着,测试模型的效果。

复制代码
    results = lm.evaluate(model) # evaluate test data using trained model
    print(results['perplexity']) # perplexity should be around 179.8736

最后,输出结果应该如下所示:

复制代码
    {'perplexity': 179.87361710221525}
    179.87361710221525

全部评论 (0)

还没有任何评论哟~