入行AI,程序员为什么要学习NLP?

717 阅读12分钟

“认知”的核心技术是自然语言处理,这是人工智能领域中的一个重要方向,研究各种理论和方法,涉及的领域较多,主要包括机器翻译、阅读理解、智能写作、对话系统、基础技术和语义计算等。

计算机能“理解并开口说话”,意味着与人类可进行更广泛的交流,从而一步步逼近并超越“图灵测试”,让人工智能掀开新的篇章。

《Python和NLTK自然语言处理》

作者 [印度] 尼天•哈登尼亚(Nitin Hardeniya) 雅各布•帕金斯(Jacob Perkins) 迪蒂•乔普拉(Deepti Chopra)  尼什•斯乔希(Nisheeth Joshi)  伊提•摩突罗(Iti Mathur)


语言是我们日常生活的核心部分,处理与语言相关的任何问题都是非常有趣的。我希望此书能够让你一嗅NLP的芬芳,激励你去了解更令人惊奇的NLP概念,并鼓励你开发一些具有挑战性的NLP应用。

研究人类语言的过程称为NLP。深入研究语言的人称为语言学家,而“计算语言学家”这个专有名词适用于应用计算研究语言处理的人。从本质上讲,计算语言学家是深入了解语言的计算机科学家,计算语言学家可以运用计算技能,对语言的不同方面进行建模。计算语言学家解决的是语言理论方面的问题,NLP只不过是计算语言学的应用。

NLP更多探讨的是应用计算机,处理不同语言的细微差别,以及使用NLP技术构建现实世界的应用。在实际情景下,NLP类似于教孩子学语言。一些最常见的任务(如理解单词和句子,形成在语法和结构上正确的句子)对人类而言是很自然。在NLP领域,把这样的一些任务转化为标记解析(tokenization)、组块(chunking)、词性标注(part of speech tagging)、解析(parsing)、机器翻译(machine translation)、语音识别(speech recognition),这些任务中的大部分依然是计算机所面临的最严峻的挑战。

本文主要内容如下。

  • NLP及其相关概念。
  • 安装Python、NLTK和其他库的方法。
  • 编写一些非常基本的Python和NLTK代码片段的方法。

如果你从来没有听说过NLP这个词,那么请花一些时间来阅读这里提到的任何一本书籍,只要阅读最初几章即可。至少要快速阅读一些与NLP相关的维基百科网页。

  • 由Daniel Jurafsky和James H. Martin合著的《Speech and Language Processing》。
  • 由Christopher D. Manning和Hinrich Schütze合著的《Statistical Natural Language Processing》。

为什么要学习NLP

本节从Gartner的技术成熟度曲线开始讨论,从这条曲线上,你可以清楚地看到NLP处在技术成熟度曲线的顶部。目前,NLP是行业所需的稀有技能之一。在大数据到来之后,NLP面临的主要的挑战是,NLP需要大量不但精通结构化数据而且擅长于处理半结构化或非结构化数据的技术人员。我们正在生成拍字节量级的网络博客、推特信息、脸书(Facebook)的推送信息、聊天记录、电子邮件和评论。一些公司正在收集所有这些不同种类的数据,以便更好地为客户定位,并从中得到有意义的见解。为了处理这些非结构化数据源,我们需要了解NLP的技术人员。

我们身处信息时代;我们甚至不能想象生活中没有谷歌。我们使用Siri来处理大多数基本的语音功能。我们使用垃圾邮件过滤器过滤垃圾邮件。在Word文档中,我们需要拼写检查器。在我们周围,存在许多NLP在现实世界中应用的例子。

(图片来自gartner网站)

下面也提供一些你能够使用但是没有意识它们是建立在NLP上的令人赞叹的NLP应用的示例。

  • 拼写校正(微软的Word/任何其他编辑器)
  • 搜索引擎(谷歌、必应、雅虎和WolframAlpha)
  • 语音引擎(Siri和谷歌语音)
  • 垃圾邮件分类(所有的电子邮件服务)
  • 新闻推送(谷歌和雅虎等)
  • 机器翻译(谷歌翻译等)
  • IBM的沃森

构建这些应用需要一种非常特殊的技能集,你需要对语言非常了解,并具有可以有效处理语言的工具。因此,让NLP成为最具优势的领域之一的原因不是广告宣传,而是可以使用NLP创建的这种应用使得NLP成为必备的最独特技能之一。

为了实现上述的一些应用,以及其他基本的NLP预处理,我们有很多可用的开源工具。在这些工具中,有一些是某些组织为建立自己的NLP应用而开发的,而有一些是开源的。这里是一张可用的NLP工具列表。

  • GATE
  • Mallet
  • Open NLP
  • UIMA
  • 斯坦福工具包
  • Genism
  • 自然语言工具包(NLTK)

大部分工具是用Java编写的,具有相似的功能。其中一些工具非常健壮,可以获得NLP工具的不同版本。但是,当涉及易于使用易于解释这两个概念的时候,NLTK得分最高。由于Python(NLTK的编码语言)的学习曲线非常快,因此NLTK也是非常易于学习的工具包。NLTK已经将大部分的NLP任务纳入篮中,非常优雅,容易用于工作中。出于所有这些原因,NLTK已成为NLP界最流行的库之一。

建议使用Anaconda和Canopy Python的发行版本。理由是,这些版本绑定了一些库,如scipy、numpy、scikit等,你可以使用这些库进行数据分析,开发出与NLP有关的应用,以及把这些库应用于相关领域。即使NLTK也是这个发行版本的一部分。

下面测试所有功能。

请在相应的操作系统中,启动终端。然后运行:

$ python

这应该打开了Python解释器。

1Python 2.6.6 (r266:84292, Oct 15 2013, 07:32:41)
2[GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] on linux2
3Type "help", "copyright", "credits" or "license" for more information.
4>>>

我希望你得到的输出与这个类似。你有可能得到一个不同的输出,但是在理想情况下,你获得了Python的最新版本(建议的版本是2.7)信息、编译器GCC的信息,以及操作系统的详细信息。Python的最新版本是3.0+,但是,与任何其他开源的系统一样,我们应该试图保持相对稳定的版本,而不是跳跃到最新版本。如果你已经使用了Python 3.0+,那么请从python网站了解新版本中又添加了哪些新特征。

基于UNIX的系统将Python作为默认程序。Windows用户可以设置路径,让Python正常运行。下面检查一下是否已经正确安装了NLTK。

1>>>import nltk
2>>>print "Python and NLTK installed successfully"
3Python and NLTK installed successfully

我们准备好了。

从Python的基本知识开始

本节不会深入探讨Python。然而,我们会让你快速浏览一遍Python的基本知识。同样,为了读者的利益,我认为应该来一段5分钟的Python之旅。接下来的几节将谈论数据结构的基本知识、一些常用的函数,以及Python的一般构建方式。

列表

在Python中,列表是最常用的数据结构之一。它们几乎相当于其他编程语言中的数组。下面从Python列表所提供的一些最重要的函数开始讲述。

在Python控制台中,尝试输入以下内容。

11>>> lst=[1,2,3,4]
22>>> # mostly like arrays in typical languages
33>>>print lst
44[1, 2, 3, 4]

可以使用更加灵活的索引来访问Python列表。下面是一些例子。

>>>print 'First element' +lst[0]

你会得到这样的错误消息:

TypeError: cannot concatenate 'str' and 'int' objects

原因是Python是一种解释性语言,我们在声明变量时,不需要初始化变量并声明变量的类型,Python只有在计算表达式时,才检查变量类型。在列表中,对象是整数类型的,因此它们不能与print函数串接。这个函数只接受字符串对象。出于这个原因,我们需要将列表元素转换为字符串。这个过程也称为强制类型转换(typecasting)。

1>>>print 'First element :' +str(lst[0])
2>>>print 'last element :' +str(lst[-1])
3>>>print 'first three elements :' +str(lst[0:2])
4>>>print 'last three elements :'+str(lst[-3:])
5First element :1
6last element :4
7first three elements :[1, 2,3]
8last three elements :[2, 3, 4]

自助

了解更多不同的数据类型和函数的最佳方法是使用帮助函数,如help() 和 dir(lst)。

可以使用dir(Python对象)命令,列出给定Python对象的所有给定的属性。如果传入一个列表对象,那么这个函数会列出所有可以使用列表执行的酷炫的操作。

1>>>dir(lst)
 2>>>' , '.join(dir(lst))
 3'__add__ , __class__ , __contains__ , __delattr__ , __delitem__ , __
 4delslice__ , __doc__ , __eq__ , __format__ , __ge__ , __getattribute__
 5, __getitem__ , __getslice__ , __gt__ , __hash__ , __iadd__ , __imul__
 6, __init__ , __iter__ , __le__ , __len__ , __lt__ , __mul__ , __ne__ ,
 7__new__ , __reduce__ , __reduce_ex__ , __repr__ , __reversed__ , __rmul__
 8, __setattr__ , __setitem__ , __setslice__ , __sizeof__ , __str__ , __
 9subclasshook__ , append , count , extend , index , insert , pop , remove
10, reverse , sort'

使用help(Python对象)命令,我们可以得到给定Python对象的详细文档,并且这个命令也给出一些示例,告诉我们如何使用Python对象。

1>>>help(lst.index)
Help on built-in function index:
index(...)
4 L.index(value, [start, [stop]]) -> integer -- return first index of value.
This function raises a ValueError if the value is not present.

因此,在Python的任何数据类型上,都可以使用help和dir,并且这是一种非常不错的方式,可用于了解关于函数和对象的其他详细信息。这也提供了一些基本示例,供你在工作中参考,在大部分情况下,这些示例非常有用。

在Python和其他语言中,字符串都非常相似,但是对字符串的操作是Python的主要特征之一。在Python中,使用字符串非常容易。在Java/C中,即使是一些很简单的操作(例如将字符串分割),我们也需要花很大的精力才能做到。然而,在Python中,你会发现这是多么容易。

在应用任何Python对象和函数时,你都可以从先前的help函数中获得帮助。下面使用最常用的数据类型字符串给你提供更多的例子。

  • Split:这是基于一些分隔符分割字符串的方法。如果不提供任何参数,这个方法默认以空格作为分隔符。
1 >>> mystring="Monty Python ! And the holy Grail ! \n"
2 >>> print mystring.split()
3 ['Monty', 'Python', '!', 'and', 'the', 'holy', 'Grail', '!']
  • Strip:这个方法可以删除字符串的尾随空格,例如'\n'和'\n\R'。
1 >>> print mystring.strip()
2 >>>Monty Python ! and the holy Grail !

你是否发现'\n'字符被移除了?还有其他方法(如lstrip()和rstrip())可以移除字符串左侧和右侧的尾随空格。

  • Upper/Lower:使用这些方法,可以改变字符串中字母的大小写。
1 >>> print (mystring.upper()
2 >>>MONTY PYTHON !AND THE HOLY GRAIL !
  • Repalce:这个方法可以替换字符串中的子字符串。
1 >>> print mystring.replace('!','''''')
2 >>> Monty Python and the holy Grail

刚才谈到的是一些最常用的字符串函数,函数库中还存在大量的字符串函数。

提示:要了解更多函数和示例,请浏览Python网站。

正则表达式

NLP发烧友的另外一个重要技能是使用正则表达式工作。正则表达式描述了字符串的有效模式匹配。我们大量使用模式提取从众多杂乱无章的文本数据中获得有意义的信息。以下是读者所需要的正则表达式。在我一生中,我所用的正则表达式都不会超过这个范围。

  • (句点):这个表达式匹配除了换行符\ n外的任意单个字符。
  • \ w:这个表达式匹配[a~z A~Z 0~9]中的某个字符或数字。
  • \ W:匹配任何非单词字符。
  • \ s:这个表达式匹配单个空白字符——空格、换行符(\n)、回车符(\r)、制表符(\t)、换页符(\f)。
  • \ S:这个表达式匹配任何非空白字符。
  • \ t:这个表达式执行tab操作。
  • \ n:这个表达式用于换行符。
  • \ r:这个表达式用于回车符。
  • \ d:十进制数字[0~9]。
  • ^:这个表达式在字符串开始处使用。
  • $:这个表达式在字符串末尾处使用。
  • \:这个表达式用于抵消特殊字符的特殊性。

例如,要匹配$符号,可以在它前面加上\。

在现行的例子(即mystring是相同的字符串对象)中,搜索一些内容,并且试图在此字符串对象上寻找一些模式。子字符串搜索是re模块的其中一个通用用例。下面实现这一功能。

1>>># We have to import re module to use regular expression
2>>>import re
3>>>if re.search('Python',mystring):
4>>> print "We found python "
5>>>else:
6>>> print "NO "

一旦执行代码,得到的消息如下。

We found python

可以使用正则表达式进行更多的模式查找。为了找到字符串中的所有模式,我们使用的其中一个普通的函数是findall。这个函数搜索字符串中特定的模式,并且会给出一个包含所有匹配对象的列表。

1>>>import re
2>>>print re.findall('!',mystring)
3['!', '!']

正如我们所见,在mystring中,有两个“!”实例,findall使用一个列表,返回了这两个对象。

词典

词典是另一种最常用的数据结构,在其他编程语言中,这也称为关联数组/关联记忆(associative array/memory)。词典是使用键(key)进行索引的数据结构,这些键可以是任何不可变的类型,如字符串和数字可以用作键。

词典是非常方便的数据结构,广泛应用于各种编程语言中来实现多种算法。在众多的编程语言中,Python词典是其中一个优雅地实现了散列表的词典。在其他语言中,相同的任务,可能需要花费更多的时间进行更繁重的编码工作,但是使用词典,工作就变得非常容易。最棒的事情是,程序员仅仅使用少量的代码块,就可以建立非常复杂的数据结构。这使得程序员摆脱了数据结构本身,花更多时间专注于算法。

我使用词典中一个很常见的用例,在给定的文本中,获得单词的频率分布。使用以下几行代码,就可以得到单词的频率。你可试着使用任意其他的语言执行相同的任务,马上就会明白Python是多么让人赞叹不已。

1>>># declare a dictionary
 2>>>word_freq={}
 3>>>for tok in string.split():
 4>>> if tok in word_freq:
 5>>> word_freq [tok]+=1
 6>>> else:
 7>>> word_freq [tok]=1
 8>>>print word_freq
 9{'!': 2, 'and': 1, 'holy': 1, 'Python': 1, 'Grail': 1, 'the': 1, 'Monty':
101}

编写函数

正如其他编程语言,Python也有其编写函数的方式。在Python中,函数以关键字def开始,然后是函数名和圆括号()。这与其他编程语言相似,即任何参数和参数的类型都放在圆括号内。实际的代码以冒号(:)开头。代码的初始行通常是文档字符串(注释),然后才是代码体,函数使用return语句结束。例如,在给定的例子中,函数wordfreq以def关键字开始,这个函数没有参数,并且以return语句结束。

 1>>>import sys
 2>>>def wordfreq (mystring):
 3>>> '''
 4>>> Function to generated the frequency distribution of the given text
 5>>> '''
 6>>> print mystring
 7>>> word_freq={}
 8>>> for tok in mystring.split():
 9>>> if tok in word_freq:
10>>> word_freq [tok]+=1
11>>> else:
12>>> word_freq [tok]=1
13>>> print word_freq
14>>>def main():
15>>> str="This is my fist python program"
16>>> wordfreq(str)
17>>>if __name__ == '__main__':
18>>> main()

这与上一节中所写的代码是相同的,使用函数的形式进行编写的思想使得代码可重用和可读。虽然在编写Python代码时解释器方式也很常见,但是对于大型程序,使用函数/类是一种非常好的做法,这也是一种编程范式。我们也希望用户能够编写和运行第一个Python程序。你需要按照下列步骤来实现这一目标。

(1)在首选的文本编辑器中,打开一个空的Python文件mywordfreq.py。

(2)编写或复制以上代码段中的代码到文件中。

(3)在操作系统中,打开命令提示符窗口。

(4)运行以下命令。

$ python mywordfreq,py "This is my fist python program !!"

(5)输出应该为:

{'This': 1, 'is': 1, 'python': 1, 'fist': 1, 'program': 1, 'my': 1}

现在,对Python提供的一些常见的数据结构,你有了一个非常基本的了解。你可以写一个完整并且能够运行的Python程序。我认为这些已经足够了,使用这些Python的入门知识,你可以看懂本书前几章。

提示:请观看维基百科网站中的一些Python教程,学习更多的Python命令。

NLTK

无须进一步研究自然语言处理的理论,下面开始介绍NLTK。我们从一些NLTK的基本示例用例开始。你们中的一些人,可能已经做过了类似的事情。首先,本节会给出一些典型Python程序员的做法,然后会转到NLTK,寻找一个更加高效、更加强大和更加清晰的解决方案。

下面从某个示例文本内容的分析开始。对于当前的例子,从Python的主页上获得了一些内容如下所示。

1>>>import urllib2
2>>># urllib2 is use to download the html content of the web link
3>>>response = urllib2.urlopen('http://python.org/')
4>>># You can read the entire content of a file using read() method
5>>>html = response.read()
6>>>print len(html)
747020

由于我们对在这个URL中所讨论的主题类型没有任何线索,因此从探索性数据分析(EDA)开始。一般来说,在文本领域,EDA具有多种含义,但是这里讨论一种简单的情况,即在文档中,何种术语占据了主导地位。主题是什么?它们出现的频率有多大?这一过程将涉及某种层次的预处理步骤。我们首先使用纯Python方式,尝试执行这个任务,然后会使用NLTK执行这个任务。

让我们从清理HTML标签开始。完成这个任务的一种方式是仅仅选择包括了数字和字符的标记(token)。任何能够使用正则表达式工作的人员应该能够将HTML字符串转换成标记列表。

1>>># Regular expression based split the string
2>>>tokens = [tok for tok in html.split()]
3>>>print "Total no of tokens :"+ str(len(tokens))
4>>># First 100 tokens
5>>>print tokens[0:100]
6Total no of tokens :2860
7['', '', '', ''type="text/css"', 'media="not', 'print,', 'braille,'...]
8

正如你所看到的,使用前面的方法,存在过量的HTML标签和其他无关紧要的字符。执行同一任务的相对清洁的版本,如下所示。

1>>>import re
 2>>># using the split function
 3>>>#https://docs.python.org/2/library/re.html
 4>>>tokens = re.split('\W+',html)
 5>>>print len(tokens)
 6>>>print tokens[0:100]
 75787
 8['', 'doctype', 'html', 'if', 'lt', 'IE', '7', 'html', 'class', 'no',
 9'js', 'ie6', 'lt', 'ie7', 'lt', 'ie8', 'lt', 'ie9', 'endif', 'if',
10'IE', '7', 'html', 'class', 'no', 'js', 'ie7', 'lt', 'ie8', 'lt', 'ie9',
11'endif', 'if', 'IE', '8', 'msapplication', 'tooltip', 'content', 'The',
12'official', 'home', 'of', 'the', 'Python', 'Programming', 'Language',
13'meta', 'name', 'apple' ...]

现在,这看起来清爽多了。但是,你可以做更多的事情,使代码变得更加简洁。这里将这项工作留给你,让你尝试移除尽可能多的噪声。可以清除一些仍然弹出的HTML标签。在这个例子中,字长为1的单词(如7和8这样的元素)仅仅是噪声,你可能希望以字长作为标准,移除这些单词。现在,与其从头开始编写一些预处理步骤的代码,不如将目光转移到NLTK,使用NTLK执行相同的任务。有一个函数clean_html(),这个函数可以执行所需要的所有清洁工作。

1>>>import nltk
2>>># http://www.nltk.org/api/nltk.html#nltk.util.clean_html
3>>>clean = nltk.clean_html(html)
4>>># clean will have entire string removing all the html noise
5>>>tokens = [tok for tok in clean.split()]
6>>>print tokens[:100]
7['Welcome', 'to', 'Python.org', 'Skip', 'to', 'content', '▼',
8'Close', 'Python', 'PSF', 'Docs', 'PyPI', 'Jobs', 'Community', '▲',
9'The', 'Python', 'Network', '≡', 'Menu', 'Arts', 'Business' ...]

这很酷炫,对吧?这种方法绝对更加清洁,也更容易执行。

下面尝试获得这些术语的频率分布。首先,我们使用纯Python的方式执行这个任务,然后,我将告诉你NLTK的秘诀。

1>>>import operator
 2>>>freq_dis={}
 3>>>for tok in tokens:
 4>>> if tok in freq_dis:
 5>>> freq_dis[tok]+=1
 6>>> else:
 7>>> freq_dis[tok]=1
 8>>># We want to sort this dictionary on values ( freq inthis case )
 9>>>sorted_freq_dist= sorted(freq_dis.items(), key=operator.itemgetter(1),
10reverse=True)
11>>> print sorted_freq_dist[:25]
12[('Python', 55), ('>>>', 23), ('and', 21), ('to', 18), (',', 18), ('the',
1314), ('of', 13), ('for', 12), ('a', 11), ('Events', 11), ('News', 11),
14('is', 10), ('2014-', 10), ('More', 9), ('#', 9), ('3', 9), ('=', 8),
15('in', 8), ('with', 8), ('Community', 7), ('The', 7), ('Docs', 6),
16('Software', 6), (':', 6), ('3:', 5), ('that', 5), ('sum', 5)]

自然而然地,由于这是Python主页,因此Python和(>>>)解释器符号是最常见的术语,这也展示了网站的第一感觉。

一个更好并且更有效的方法是使用NLTK的FreqDist()函数。为了进行对比,我们可以观察之前开发的执行相同任务的代码。

 1>>>import nltk
 2>>>Freq_dist_nltk=nltk.FreqDist(tokens)
 3>>>print Freq_dist_nltk
 4>>>for k,v in Freq_dist_nltk.items():
 5>>> print str(k)+':'+str(v)
 6>>': 23, 'and': 21, ',': 18, 'to': 18, 'the':
 714, 'of': 13, 'for': 12, 'Events': 11, 'News': 11, ...>
 8Python:55
 9>>>:23
10and:21
11,:18
12to:18
13the:14
14of:13
15for:12
16Events:11
17News:11
18

现在,让我们做一些更有趣的事情,画出这些频率分布。

1>>>Freq_dist_nltk.plot(50, cumulative=False)
2>>># below is the plot for the frequency distributions

可以看到,累积频率持续增长,整体上,曲线有一条长长的尾巴。一些噪声依然存在,一些单词(如the、of、for和=)是毫无用处的。对于这些单词(如the、a、an等),使用术语停用词(stop word)来称呼它们。由于在大部分文档中不定代词一般都会出现,因此这些词没有什么判别力,不能传达太多的信息。在大多数的NLP和信息检索任务中,人们通常会删除停用词。让我们再次回到当前的示例中。

1>>>stopwords=[word.strip().lower() for word in open("PATH/english.stop.
2txt")]
3>>>clean_tokens=[tok for tok in tokens if len(tok.lower())>1 and (tok.
4lower() not in stopwords)]
5>>>Freq_dist_nltk=nltk.FreqDist(clean_tokens)
6>>>Freq_dist_nltk.plot(50, cumulative=False)

现在,这看起来干净多了!在完成了这么多任务后,可以访问Wordle,将频率分布转换成CSV格式。你应该能够得到以下词云。

《Python和NLTK自然语言处理》

书号:978-7-115-50334-3

推荐理由:NLTK是自然语言处理领域中非常受欢迎和广泛使用的Python库。NLTK的优点在于其简单性,其中大多数复杂的自然语言处理任务使用几行代码即可完成。本书旨在讲述如何用Python和NLTK解决各种自然语言处理任务并开发机器学习方面的应用。本书介绍了NLTK的基本模块,讲述了采用NLTK实现自然语言处理的大量技巧,讨论了一些文本处理方法和语言处理技术,展示了使用Python实现NLP项目的大量实践经验。本书主要内容包括文本挖掘/NLP任务中所需的所有预处理步骤,如何使用Python 3的NLTK 3进行文本处理,如何通过Python开展NLP项目。

本书适合NLP和机器学习领域的爱好者、Python程序员以及机器学习领域的研究人员阅读。

- END -