什么是N-gram语言模型
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么是N-gram语言模型相关的知识,希望对你有一定的参考价值。
参考技术A 是字符串匹配里面的一个模型追问其特点是什么了?
追答是一种概率模型。
N-Gram是大词汇连续语音识别中常用的一种语言模型,对中文而言,我们称之为汉语语言模型(CLM, Chinese Language Model)。汉语语言模型利用上下文中相邻词间的搭配信息,在需要把连续无空格的拼音、笔划,或代表字母或笔划的数字,转换成汉字串(即句子)时,可以计算出具有最大概率的句子,从而实现到汉字的自动转换,无需用户手动选择,避开了许多汉字对应一个相同的拼音(或笔划串,或数字串)的重码问题。
该模型基于这样一种假设,第n个词的出现只与前面N-1个词相关,而与其它任何词都不相关,整句的概率就是各个词出现概率的乘积。这些概率可以通过直接从语料中统计N个词同时出现的次数得到。常用的是二元的Bi-Gram和三元的Tri-Gram。
1.统计语言模型
自然语言从它产生开始,逐渐演变成一种上下文相关的信息表达和传递的方式,因此让计算机处理自然语言,一个基本的问题就是为自然语言这种上下文相关特性建立数学模型。这个数学模型就是自然语言处理中常说的统计语言模型,它是今天所有自然语言处理的基础,并且广泛应用与机器翻译、语音识别、印刷体和手写体识别、拼写纠错、汉字输入和文献查询。
2.N-Gram
N-Gram是大词汇连续语音识别中常用的一种语言模型,对中文而言,我们称之为汉语语言模型(CLM, Chinese Language Model)。汉语语言模型利用上下文中相邻词间的搭配信息,在需要把连续无空格的拼音、笔划,或代表字母或笔划的数字,转换成汉字串(即句子)时,可以计算出具有最大概率的句子,从而实现到汉字的自动转换,无需用户手动选择,避开了许多汉字对应一个相同的拼音(或笔划串,或数字串)的重码问题。 搜狗拼音和微软拼音的主要思想就是N-gram模型的,不过在里面多加入了一些语言学规则而已。
3.用数学的方法描述语言规律
美联储主席本.伯克南昨天告诉媒体7000亿美元的救助资金将借给上百家银行、保险公司和汽车公司。(这句话意很通顺,意思也很明白)改变一些词的顺序,或者替换掉一些词,这句话变成:本.伯克南美联储主席昨天7000亿美元的救助资金告诉媒体将借给银行、保险公司和汽车公司上百家。(意思就含混了,虽然多少还是能猜到一点)。
但是如果再换成:联主美储席本.伯诉体南将借天的救克告媒助资金70元亿00美元给上百百百家银保行、汽车保险公司公司和。(基本上读者就不知所云了)。
20世纪70年代之前,科学家们试图判断这个文字序列是否合乎文法、含义是否正确等,但这条路走不动。贾里尼克从另外一个角度来看待这个问题,用一个简单的统计语言模型非常漂亮的搞定了它。贾里尼克的出发点很简单:一个句子是否合理,就看看它的可能性大小如何。至于可能性就用概率来衡量。第一个句子出现的概率最大,因此,第一个句子最有可能句子结构合理。这个方法更普通而严格的描述是: 假定S表示某一个有意义的句子,由一连串特定顺序排列的词w1,w2,w3,...,wn组成,这里n是句子的长度。现在,我想知道S在文本中(语料库)出现的可能性,也就是数学上所说的S的概率P(S)。我们需要一个模型来估算概率,既然S=w1,w2,w3,...,wn,那么不妨把P(S)展开表示: P(S)=P(w1,w2,w3,...,wn),利用条件概率的公式,S这个序列出现的概率等于每一个词出现的条件概率相乘,于是P(w1,...,wn)展开为:P(S)=P(W1,W2,W3,...,Wn)=P(W1)P(W2|W1)P(W3|W1,W2)…P(Wn|W1,W2,…,Wn-1)。其中P(w1)表示第一个词w1出现的概率;P(w2|w1)是已知第一个词的前提下,第二个词出现的概率;以此类推,词wn出现的概率取决于它前面所有的词。但是这种方法存在两个致命的缺陷:一个缺陷是参数空间过大(条件概率P(wn|w1,w2,...,wn-1)的可能性太多,无法估算),不可能实用化;另外一个缺陷是数据稀疏严重。
数据稀疏的解释:假设词表中有20000个词,如果是bigram model(二元模型)那么可能的2-gram就有400000000个,如果是trigram(3元模型),那么可能的3-gram就有8000000000000个!那么对于其中的很多词对的组合,在语料库中都没有出现,根据最大似然估计得到的概率将会是0,这会造成很大的麻烦,在算句子的概率时一旦其中的某项为0,那么整个句子的概率就会为0,最后的结果是,我们的模型只能算可怜兮兮的几个句子,而大部分的句子算得的概率是0. 因此,我们要进行数据平滑(data Smoothing),数据平滑的目的有两个:一个是使所有的N-gram概率之和为1,使所有的N-gram概率都不为0,有关数据平滑处理的方法可以参考《数学之美》第33页的内容。
4.马尔科夫假设
为了解决参数空间过大的问题,引入了马尔科夫假设:任意一个词的出现的概率仅仅与它前面出现的有限的一个或者几个词有关。如果一个词的出现的概率仅于它前面出现的一个词有关,那么我们就称之为bigram model(二元模型)。即
P(S) = P(W1,W2,W3,…,Wn)=P(W1)P(W2|W1)P(W3|W1,W2)…P(Wn|W1W2…Wn-1)
≈P(W1)P(W2|W1)P(W3|W2)…P(Wi)|P(Wi-1)...P(Wn|Wn-1)
如果一个词的出现仅依赖于它前面出现的两个词,那么我们就称之为trigram(三元模型)。在实践中用的最多的就是bigram和trigram了,而且效果很不错。高于四元的用的很少,因为训练它(求出参数)需要更庞大的语料,而且数据稀疏严重,时间复杂度高,精度却提高的不多。当然,也可以假设一个词的出现由前面N-1个词决定,对应的模型稍微复杂些,被称为N元模型。
5.如何估计条件概率问题
条件概率推导在《数学之美》第30页有详细讲解,在此讲述一个简单的条件概率。
一种简单的估计方法就是最大似然估计(Maximum Likelihood Estimate)了,即P(Wn|W1,W2,…,Wn-1) = (C(W1,W2,…,Wn)) / (C(W1, W2,…,Wn-1)) 。C(w1,w2,...,wn)即序列w1,w2,...,wn在语料库中出现的次数。
对于二元模型P(Wi|Wi-1)=C(Wi-1,Wi)/C(Wi-1)
6.在一个语料库例子
注:这个语料库是英文的,而对于汉字语料库,需要对句子分词,才能做进一步的自然语言处理。
在训练语料库中统计序列C(W1 W2…Wn) 出现的次数和C(W1 W2…Wn-1)出现的次数。
算法:N-gram语法
一、N-gram介绍
n元语法(英语:N-gram)指文本中连续出现的n个语词。n元语法模型是基于(n - 1)阶马尔可夫链的一种概率语言模型,通过n个语词出现的概率来推断语句的结构。这一模型被广泛应用于概率论、通信理论、计算语言学(如基于统计的自然语言处理NLP)、计算生物学(如序列分析)、数据压缩等领域。
N-gram文本广泛用于文本挖掘和自然语言处理任务。它们基本上是给定窗口内的一组同时出现的单词,在计算n元语法时,通常会将一个单词向前移动(尽管在更高级的场景中可以使X个单词向前移动)。
例如,对于句子"The cow jumps over the moon" ,N = 2(称为二元组),则 ngram 为:
- the cow
- cow jumps
- jumps over
- over the
- the moon
因此,在这种情况下,有 5 个 n-gram。
再来看看 N = 3,ngram 将为:
- the cow jumps
- cow jumps over
- jumps over the
- over the moon
因此,在这种情况下,有 4 个 n-gram。
所以,在一个句子中 N-grams 的数量有:
Ngrams(K) = X - (N - 1)
其中,X 为给定句子K中的单词数,N 为 N-gram 的N,指的是连续出现的 N 个单词。
N-gram用于各种不同的任务。例如,在开发语言模型时,N-grams不仅用于开发unigram模型,而且还用于开发bigram和trigram模型。谷歌和微软已经开发了网络规模的 n-gram模型,可用于多种任务,例如拼写校正、分词和文本摘要。N-gram的另一个用途是为受监督的机器学习模型(例如SVM,MaxEnt模型,朴素贝叶斯等)开发功能。其想法是在特征空间中使用标记(例如双字母组),而不是仅使用字母组合。
下面简单介绍一下如何用 Java 生成 n-gram。
二、用 Java 生成 n-gram
这个是生成 n-gram 的主要方法,方法首先是对传进来的句子 sentence 进行单词拆分,这个正则表达式“\s+”是能匹配任何空白字符,包括空格、制表符、换页符等等, 等价于 [ f v]。拆分完后对单词进行拼接。算法时间复杂度为 O(X - (N - 1)),X 为给定句子K中的单词数,N 为 N-gram 的 N。
1 /** 2 * 生成n元语法 3 * <p> 4 * 一个句子中有多少个N-gram? 5 * 如果 X = 给定句子K中的单词数,则句子K的 N-gram数为: 6 * N(grams<K>) = X - (N - 1) 7 * 8 * @param n 连续 n个单词 9 * @param sentence 句子级别的文本 10 * @return 存着ngram的列表 11 */ 12 public static List<String> ngrams(int n, String sentence) { 13 List<String> ngrams = new ArrayList<>(); 14 String[] words = sentence.split("\s+"); 15 for (int i = 0; i < words.length - n + 1; i++) 16 ngrams.add(concat(words, i, i + n)); 17 return ngrams; 18 }
进行单词拼接,这里使用 StringBuilder(线程不安全,效率相对StringBuffer高点)对拆分好的单词进行拼接并返回拼接好的字符串。
1 /** 2 * 拼接单词 3 * 4 * @param words 单词 5 * @param start 开始位置 6 * @param end 结束位置 7 * @return 拼接好的字符串 8 */ 9 public static String concat(String[] words, int start, int end) { 10 StringBuilder sb = new StringBuilder(); 11 for (int i = start; i < end; i++) 12 sb.append(i > start ? " " : "").append(words[i]); 13 return sb.toString(); 14 }
对 n-gram 的出现次数进行统计,使用 HashMap<String, Integer> 来存储 n-gram 的出现次数,并且按照 value 的逆序排序 Map,次数较多的在前面先打印。这里使用 Java 8 Stream API 按照 value 降序顺序进行 Map 排序。
在 Java 8 中,Map.Entry类具有静态方法 comparingByValue() 来帮助按 value 排序,此方法返回以自然顺序 Comparator 比较 Map.Entry值的。还有,你可以传递自定义Comparator 以用于排序。
下面是根据 value 进行排序的方法:
1 /** 2 * 按 value对 HashMap进行逆序排序 3 * <p> 4 * 使用 Java 8 Stream API按照降序对Value进行Map排序 5 * 逻辑的中心是按自然顺序 Map.Entry.comparingByValue()比较 Map.Entry值的方法。 6 * 7 * @param unSortedMap 未排序的HashMap 8 * @return 按照value降序排序的HashMap 9 */ 10 public static HashMap<String, Integer> sortByValue(HashMap<String, Integer> unSortedMap) { 11 // System.out.println("Unsorted Map : " + unSortedMap); 12 13 // LinkedHashMap保留插入元素的顺序 14 LinkedHashMap<String, Integer> reverseSortedMap = new LinkedHashMap<>(); 15 16 // 使用 Comparator.reverseOrder() 进行反向排序 17 unSortedMap.entrySet() 18 .stream() 19 .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())) 20 .forEachOrdered(x -> reverseSortedMap.put(x.getKey(), x.getValue())); 21 22 // System.out.println("Reverse Sorted Map : " + reverseSortedMap); 23 24 return reverseSortedMap; 25 }
主函数测试代码:
1 public static void main(String[] args) { 2 HashMap<String, Integer> count = new HashMap<>(); 3 String text = "I can go to the supermarket to buy spicy bars or go to the store to buy spicy bars."; 4 5 // 生成n为1~3的N元语法 6 // for (int n = 1; n <= 3; n++) { 7 // for (String ngram : ngrams(n, text)) { 8 // System.out.println(ngram); 9 // } 10 // System.out.println(); 11 // } 12 13 for (String ngram : ngrams(3, text)) { 14 // counting ngram by using HashMap 15 if (!count.containsKey(ngram)) { 16 count.put(ngram, 1); 17 } else if (count.containsKey(ngram)) { 18 count.replace(ngram, count.get(ngram) + 1); 19 } 20 System.out.println(ngram); 21 } 22 23 // 按出现次由多到少的顺序打印ngram 24 System.out.println(" Counting Result: "); 25 for (Map.Entry<String, Integer> entry : sortByValue(count).entrySet()) { 26 System.out.println(entry.getKey() + ": " + entry.getValue()); 27 } 28 29 }
以上是关于什么是N-gram语言模型的主要内容,如果未能解决你的问题,请参考以下文章