50 行 Python 代码写一个语言检测器

你有没有曾经好奇过 Chrome 浏览器是如何知道一个网页的语言,并对外国文字的网页提供翻译服务的?或者,Facebook 是如何翻译你朋友用写在你主页上的外国文字?检测一种语言实际上很是简单,改进了用户体验,并且不须要用户作任何的事情。html

我无心中发现的 ActiveState recipe for a language detector in Python这是很是不错的一段程序,可是我决定作点小小的改进。提供一些背景知识给那些不熟悉天然语言处理或者是程序语言学的人。python

若是你是有经验的程序员,你也许能够直接跳到这段文字最下端的程序部分。出奇的简单。程序员

你须要熟悉Python语法。若是你历来没有用过python, 我建议你读一下 Zed Shaw 的《Learn Python the Hard Way》。算法

肯定你下载并安装了python,并且能够正常运行程序。这段文字中的python不算很长,因此你能够用任何文本编辑器从而省去安装任何软件的麻烦。api

第一部分,什么检测到了一种语言?

在你写区分语言的程序以前,你须要回答一个问题:什么区别了两种语言?浏览器

有趣的是,这个问题的答案会根据不一样的比较语言而有所不一样。好比:app

女性が牛乳を飲んだ。 (译者注: 日语:女性喝牛奶。)编辑器

你是怎么知道这句话不是英文的?你也许不熟悉日文,可是你确定知道这些字符不是英文,你甚至不须要知道具体哪一个字符不存在于英文字母中。函数

La femme boit du lait. (译者注: 法语:女性喝牛奶。)测试

你怎么知道这句话不是英文的?有一点麻烦。每一个字母都在英文中。甚至每个字母和句型结构都和英文的同一个意思的那句话很类似—— “The woman drank milk.” (译者注: 英语:女性喝牛奶。) 。 你的大脑用了另外一个特性去判断这个:尽管字母很类似,这两句话发音没有任何类似之处。

还有不少更复杂的方式去检测两种不一样的语言(例如,语法、句法等等)上面提到的两个特性彷佛足够用来区分不少的书写文字。

提问:你能够想到一个相反的例子嘛? 两种不能用字符或者发音而区分的语言?(译者注:这是我想到的,和编者没有任何关系。Hindi 和Nepali 的区分度极低,印度的一种语言和尼泊尔的官方语言的区别度很是低,字符区别很低而发音更高达50%的类似度。固然,他们两个是同一语系的语种。)

第二部分,如何用计算机检测到这些特性?

第一个特性已经存在于任何一台现代化的机器里 ——character encodings 字符解码容许任何一台计算机去经过二进制码而呈现每个字符。咱们要用unicode 在Python 的程序中。

第二个特征更有意思。如何能让一台电脑检测到字符串的发音呢?答案比想象的简单点:字符串顺序是按照声音解码的!他们有直接的稳定的对应关系- 语言改变的很是缓慢。

所以,你能够用下面的两个特性去检测一行文本语言:

  • 单个字符的重复性
  • 字符串的重复性

实际上,这两个特性浓缩到了一个特性中:字符串的顺序。单个字符的重复性只是字符串的重复性。

快速知识补充:在计算机语言学中,字符串的长度 n 被定义为 n-gram。 “a” 是一个gram, 1-gram. “bc”是两个gram,2-gram or bigram。 “def” 是三个gram, 3-gram 或者trigram,以此类推。

第三部分,用python 实现吧!

首先,咱们须要计算某个字符串在特定文本中出现的次数。为了封装结果,咱们将创建一个NGram 类。

class NGram(object):
    def __init__(self, text, n=3):
        self.length = None
        self.n = n
        self.table = {}
        self.parse_text(text)

    def parse_text(self, text):
        chars = ' ' * self.n # initial sequence of spaces with length n

        for letter in (" ".join(text.split()) + " "):
            chars = chars[1:] + letter # append letter to sequence of length n
            self.table[chars] = self.table.get(chars, 0) + 1 # increment count

代码实际上很短,定义了一个NGram类去接受一个unicode的文本输入做为一个参数。它还定义了一个选择性的参数n做为定义字符序列的长度。这段程序读取了输入文本的每一个字符而后创建了一个python 的词典(dictionary),该词典包含了全部小于n长度的字符序列以及相对应的出现频率。好比,输入:”Snail Mail.” 将获得3-gram 的词典:

{
  '  S': 1,
  ' Sn': 1,
  'Sna': 1,
  'nai': 1,
  'ail': 2,
  'il ': 1,
  'l M': 1,
  ' Ma': 1,
  'Mai': 1,
  'il.': 1
}

第四部分:如何比较两个NGrams?

即便上面介绍的NGram类能够用来计算字母序列出现的频率,咱们始终不知道如何比较NGrams.咱们想要在不一样的语言中找到最接近匹配去表明那种语言。咱们想要在一组给予的不一样语言的Ngram 对象中,能找到最接近的匹配对象。为了协调匹配NGram 去找到最佳的匹配,咱们引进了两个新的函数: calculate_length() 和 sub() 去容许Python 实现两个NGram对象之间的减法。

这样的减法应用于多纬NGrams向量。每一个独立的n 字符序列表明着向量的一个维度。Calculate_length()函数用来计算向量的长度(分散范围)。找到NGram向量间的角度就是找到向量间的类似性。这个技术被称作基于向量的查询 (这篇是基于perl 的文章,基本上用Perl 实现了这篇做者上面阐述的全部观点)。

实现代码:

class NGram(object):
    def __init__(self, text, n=3):
        self.length = None
        self.n = n
        self.table = {}
        self.parse_text(text)
        self.calculate_length()

    def parse_text(self, text):
        chars = ' ' * self.n # initial sequence of spaces with length n

        for letter in (" ".join(text.split()) + " "):
            chars = chars[1:] + letter # append letter to sequence of length n
            self.table[chars] = self.table.get(chars, 0) + 1 # increment count

    def calculate_length(self):
        """ Treat the N-Gram table as a vector and return its scalar magnitude
        to be used for performing a vector-based search.
        """
        self.length = sum([x * x for x in self.table.values()]) ** 0.5
        return self.length

    def __sub__(self, other):
        """ Find the difference between two NGram objects by finding the cosine
        of the angle between the two vector representations of the table of
        N-Grams. Return a float value between 0 and 1 where 0 indicates that
        the two NGrams are exactly the same.
        """
        if not isinstance(other, NGram):
            raise TypeError("Can't compare NGram with non-NGram object.")

        if self.n != other.n:
            raise TypeError("Can't compare NGram objects of different size.")

        total = 0
        for k in self.table:
            total += self.table[k] * other.table.get(k, 0)

        return 1.0 - (float(total) / (float(self.length) * float(other.length))

    def find_match(self, languages):
        """ Out of a list of NGrams that represent individual languages, return
        the best match.
        """
        return min(languages, lambda n: self - n)

第五部分:如何比较NGram?

选择合适的NGram 模型至关的简单。你只须要将unicode的文本改为任何一种你想要选择的语言。

english = NGram(training_text, n=3) #trigram

若是你想比较两个NGram 模型。你能够用两个模型作减法来寻找两个模型的类似性(sub()是用来实现这个功能的)。

similarity = english - NGram(text, n=3)

若是你想用 Python list 或者iterator实现一个简单的基于向量的搜索, 你能够用NGram 类中的find_match(language)方式。搜索将在参数languages上实现对NGram对象的叠代。

languages = [english, spanish, french]
NGram(text, n=3).best_match(languages)

正如你所见,真正的生产实现中的问题,在于寻找正确的数据去实现NGram 模型。若是你想创建一个很好的语言检测器,你须要找到一些颇有表明性的文本例子去表明你想测试的语言。维基百科上有不少很好的例子能够做为你的数据来源。

除了文本检测,你还能够用NGram 去作其余有意思的事情。Google 的浏览显示 就是一个很好的例子。它用了刚才建立的Python代码去实现了类似的统计应用。Google 还公开了作这个统计实验用的数据

第六部分: 如今该干些什么了呢?

不少事情能够去作!咱们从一个文本检测器开始,一样的方法能够在不少其余领域应用。好比说,你能够修改你的代码,让这个文本检测器再也不只检测字母字符,而是直接进行词语匹配。理论上来讲,这些词法顺序(用词的方式根据我的习惯而有所不一样)能够用来鉴定一做者的写做。

N-Grams的概念能够在不一样的领域应用。好比:

  • 语法拼写建议(建议改正非正确语法词汇)
  • 鉴定DNA序列
  • 提升压缩算法的有效性
  • 改进搜索引擎
  • 改进语音识别系统和特征,经过某个特定词语会出如今另外一个词语后面的几率

尽管每种应用都会有所不一样,可是本质上都是类似的,须要比较单独个体的类似性。当你须要使用序列时,不妨考虑NGram。


原文:HOW TO WRITE A LANGUAGE DETECTOR IN 50 LINES OF PYTHON
转载自:伯乐在线 - 人见人爱的土豆

相关文章
相关标签/搜索