对于Pw函数,这里稍微多做一点说。首先我们从一元语言模型的文件里读取单词及其计数,如果一个单词在语料库中出现,它的概率就是Count(word)/N,这里N是语料库的单词数目的规模。事实上,相对于使用完整的1千3百万单词(词型)的一元语言模型,Peter Norvig大牛对这个一元语言模型进行了简化:(a) 创建了一个更通用的词汇表,并且其中的单词是大小写不敏感(不区分)的,故“the”,”The”以及“THE”的计数是加在一起作为“the”的计数的;(b)只有由字母(letter)组合的单词才被计入其中,而对于其他包含数字或者标点的“单词”则被过滤,故“+170.002”以及“can’t”都不会被计入;(c)只列出其中最常用的1百万单词中的前1/3,也就是333333个单词。
即使是一个trillion-word的语料库,某些单词也不一定能被其单词列表包括,这对于Pw来说也是一个问题,因为我们不能让它返回一个0概率,事实上,这就是统计语言模型中非常重要的平滑问题。
对于一个语料库中未出现的单词来说,如果不能返回0概率,那应该返回多少?Google语料库中的词例(token)数目N大约有1万亿个(a trillion),在简化了的一元语言模型中,最小的常见单词的计数是12,711个。所以,一个未知单词的概率应该介于0到12,710/N之间。 所有的未知词概率都一样也不大可能:一个20个字母组成的随机序列(单词)的概率就很可能比6个字母组成的随机序列(单词)的概率要小。
我们将定义一个概率分布的类Pdist,它将加载类似(key, count)形式数据文件。缺省的情况,对于未知的单词,其概率均为1/N,但是对于每一个实例,Pdist均提供一个函数重载这个缺省值。为了避免过长的单词拥有过高的概率,我们从概率10/N出发,对于候选单词的每一个字母都除以10。
好了,现在我们在segment.py中加入Pdist类:
class Pdist( dict ):
"""A probability distribution estimated from counts in datafile."""
def __init__( self, data, N = None, missingfn = None ):
for key, count in data:
self[key] = self.get( key, 0 ) + int( count )
self.N = float( N or sum( self.itervalues() ) )
self.missingfn = missingfn or ( lambda k, N: 1./N )
在python中 __init__ 方法的作用与C++中的构造函数功能类似但不完全相同,具体解释如下:
__init__ 在类的实例创建后被立即调用。它可能会引诱你称之为类的构造函数,但这种说法并不正确。说它引诱,是因为它看上去像 (按照习惯,__init__ 是类中第一个定义的方法),行为也像 (在一个新创建的类实例中,它是首先被执行的代码),并且叫起来也像 (“init”当然意味着构造的本性)。说它不正确,是因为对象在调用 __init__ 时已经被构造出来了,你已经有了一个对类的新实例的有效引用。但 __init__ 是在 Python 中你可以得到的最接近构造函数的东西,并且它也扮演着非常相似的角色。
每个类方法的第一个参数,包括 __init__,都是指向类的当前实例的引用。按照习惯这个参数总是被称为 self。在 __init__ 方法中,self 指向新创建的对象;在其它的类方法中,它指向方法被调用的类实例。尽管当定义方法时你需要明确指定 self,但在调用方法时,你不 用指定它,Python 会替你自动加上的。
__init__ 方法可以接受任意数目的参数,就像函数一样,参数可以用缺省值定义,即可以设置成对于调用者可选。
在本例中,N和missingfn都有一个缺省值 None,即 Python的空值,这两个参数稍后再定义。我们具体来看看 Pdist类中 __init__ 方法的作用:
for key, count in data:
self[key] = self.get( key, 0 ) + int( count )
由于要读取的一元语言模型文件形式如下:
the 23135851162
of 13151942776
…
这里利用了python中dictionary的get()函数,其作用是返回dict中指定键(key)的相应值(value),如果没有找到key,则采用默认值: dict.get(key, default=None)。对于本例,key就是词型如the, count就是其计数,而对于未出现的单词,默认的初始化值为0。
self.N = float( N or sum( self.itervalues() ) )
这个表达式的作用是初始化参数N,即一元语言模型中总的词例数,可以由用户指定,如果用户没有指定,就利用dic.itervalues()方法遍历dic中的所有value,本例中也就是所有单词的计数,并求和:
dic.itervalues(): return an iterator over the mapping's values
self.missingfn = missingfn or ( lambda k, N: 1./N )
missingfun参数的作用是调用一个处理未登录词的函数,如果用户没有指定,则调用lambda表达式:
lambda k, N: 1./N
lambda表达式是Python支持一种有趣的语法,它允许你快速定义单行的最小函数,这些也可以称之为 lambda函数,是从 Lisp 借用来的,可以用在任何需要函数的地方。可以利用python解释器来测试一下上面的lambda函数:
>>> ( lambda k, N : 1. / N )( 1, 2 )
0.5
>>> ( lambda k, N : 1. / N )( 2, 2 )
0.5
>>> ( lambda k, N : 1. / N )( 2, 5 )
0.20000000000000001
>>> ( lambda k, N : 1. / N )( 2, 10 )
0.10000000000000001
第一个参数k在这里似乎还没有任何作用,而该lambda函数的作用就是:缺省的情况,对于未知的单词其返回概率均为1/N。
现在我们可以利用python解释器来测试一下Pdist类中的__init__函数:
首先定义一组测试数据:
>>> data_pair = [ ("A", 1), ("B", 2), ("C", 3) ]
然后reload:
>>> reload( segment )
<module 'segment' from 'segment.py'>
最后生成一个Pdist类的对象test:
>>> test = segment.Pdist( data_pair )
>>> test.N
6.0
>>> test.missingfn
<function <lambda> at 0xb7d4cd84>
注意其中的参数N和missingfun都采用缺省值,因此test.N返回的是data_pair中的value和,而test.missingfn返回的是__init__中定义的lambda函数: lambda k, N: 1./N,可以继续测试一下:
>>> test.missingfn( 1, 5 )
0.20000000000000001
>>> test.missingfn( 2, 5 )
0.20000000000000001
>>> test.missingfn( 2, test.N )
0.16666666666666666
>>> test.missingfn( 2, 6 )
0.16666666666666666
未完待续:分词5
注:原创文章,转载请注明出处“我爱自然语言处理”:www.52nlp.cn
本文链接地址:https://www.52nlp.cn/beautiful-data-统计语言模型的应用三分词4
借宝地,推广下我的博客http://hi.baidu.com/finallyliuyu/home
主要写了一些自然语言处理,信息检索领域的初级技术问题的解决方案,希望和更多的自然语言处理Fans一起交流和进步
[回复]
52nlp 回复:
31 8 月, 2010 at 23:08
谈不上宝地,我在“友情链接”里加了你在博客园的博客,不用再这里推广了!另外,你的博客写得很好了,加油!
[回复]
谢谢~我会再接再励的:)
[回复]
self.N = float( N or sum( self.inervalues() ) )
inervalues 应该改成 itervalues吧
[回复]
52nlp 回复:
9 12 月, 2013 at 10:46
应该是写错了,已修改,谢谢
[回复]