对于自然语言处理的学习,很多人会争论用什么样的编程语言实现最好?有些人认为是Java或者时下流行的Scala,我认为Python才是最佳的选择!
对于学习和从事自然语言处理工作来说,Python具有几大优势:
提供丰富的自然语言处理库 编程语法相对简单(尤其易于理解) 具有很多数据科学相关的库
01 正则表达式在NLP的基本应用
正则表达式是一种定义了搜索模式的特征序列,主要是用于字符串的模式匹配,或是字符的匹配。随着计算机的普及以及互联网的发展,大量的信息以电子文档方式呈现在人们的面前。
NLP通常所需要处理的语料一部分来自于web网页的信息抽取,一部分来自于文本格式的文档。Web网页具有很强的开发价值,具有时效性强,信息量大,结构稳定,价值高等特点,文本格式的文档多来源于人为编写或系统生成,其中包含了非结构化文本、半结构化文本以及结构化文本。
正则表达式的作用之一是将这些文档内容从非结构化转为结构化以便后续的文本挖掘。
正则表达式的另一个作用就是去除“噪声”。在处理大量文本片段的时候,有非常多的文字信息与最终输出的文本无关,这些无关的片段称之为“噪声”(比如url或链接、语气助词、标点符号等)。
正则表达式是处理NLP的最基本的手段之一,学习与掌握正则表达式在Python中的应用,可以帮助我们在格式复杂的文本中抽取所需要的文本信息。
比如说抽取以下文本中的年份,每一行的格式不同,因此没有办法通过Python提供的字符串方法来抽取,这个时候我们往往考虑使用正则表达式。
-“July 16, 2017” -“16/07/2009” -“Summer 2008”
02 匹配字符串
在Python中,我们会使用re模块来实现正则表达式。为了让大家更好地理解正则表达式在Python中的应用,我们会通过一系列的例子来阐述。
案例中,我们会提到re的一个方法: re.search。
通过使用re.search(regex,string)这个方法,我们可以检查这个string字符串是否匹配正则表达式regex。如果匹配到,这个表达式会返回一个match对象,如果没有匹配到则返回None。
我们先看下准备的有关爬虫介绍的文字信息。句子和句子之间是以句号分隔。具体的文本如下所示:
文本最重要的来源无疑是网络。我们要把网络中的文本获取形成一个文本数据库。利用一个爬虫抓取到网络中的信息。爬取的策略有广度爬取和深度爬取。根据用户的需求,爬虫可以有主题爬虫和通用爬虫之分。
例1 获取包含“爬虫”这个关键字的句子
查找哪些语句包含“爬虫”这个关键字。Python的代码实现如下:
import re
text_string = '文本最重要的来源无疑是网络。我们要把网络中的文本获取形成一个文本数据库。利用一个爬虫抓取到网络中的信息。爬取的策略有广度爬取和深度爬取。根据用户的需求,爬虫可以有主题爬虫和通用爬虫之分。'
regex = '爬虫'
p_string = text_string.split('。') #以句号为分隔符通过split切分
for line in p_string:
if re.search(regex,line) is not None: #search方法是用来查找匹配当前行是否匹配这个regex,返回的是一个match对象
print(line) #如果匹配到,打印这行信息
运行上面的程序,我们可以看到输出结果为:
利用一个爬虫抓取到网络中的信息
根据用户的需求,爬虫可以有主题爬虫和通用爬虫之分
例2 匹配任意一个字符
正则表达式中,有一些保留的特殊符号可以帮助我们处理一些常用逻辑。如下表所示。
▲匹配任意一个字符
我们来举几个例子:
▲提示: “.” 代替任何单个字符(换行除外)
我们现在来演示下如何查找包含“爬”+任意一个字的句子。代码如下:
import re
text_string = '文本最重要的来源无疑是网络。我们要把网络中的文本获取形成一个文本数据库。利用一个爬虫抓取到网络中的信息。爬取的策略有广度爬取和深度爬取。根据用户的需求,爬虫可以有主题爬虫和通用爬虫之分。'
regex = '爬.'
p_string = text_string.split('。') #以句号为分隔符通过split切分
for line in p_string:
if re.search(regex,line) is not None: #search方法是用来查找匹配当前行是否匹配这个regex,返回的是一个match对象
print(line) #如果匹配到,打印这行信息
上述代码基本不变,只需要将regex中的“爬”之后加一个“.”,即可以满足需求。我们来看下输出会多一行。因为不仅是匹配到了“爬取”也匹配到了“爬虫”。
利用一个爬虫抓取到网络中的信息
爬取的策略有广度爬取和深度爬取
根据用户的需求,爬虫可以有主题爬虫和通用爬虫之分
例3 匹配起始和结尾字符串
现在介绍另一个特殊符号,具体功能如下表所示。
▲匹配开始与结尾的字符串
举个例子:
“^a”代表的是匹配所有以字母a开头的字符串 “a$”代表的是所有以字母a结尾的字符串
我们现在来演示下如何查找以“文本”这两个字起始的句子。代码如下:
import re
text_string = '文本最重要的来源无疑是网络。我们要把网络中的文本获取形成一个文本数据库。利用一个爬虫抓取到网络中的信息。爬取的策略有广度爬取和深度爬取。根据用户的需求,爬虫可以有主题爬虫和通用爬虫之分。'
regex = '^文本'
p_string = text_string.split('。')
for line in p_string:
if re.search(regex,line) is not None:
print(line)
我们可以看到输出为:
文本最重要的来源无疑是网络
例4 使用中括号匹配多个字符
现在介绍另一个特殊符号,具体功能如下表所示:
▲匹配多个字符串
举个例子:
我们先看下文字信息。句子和句子之间是以句号分隔。
我们希望提取以[重要的]或者[紧要的]为起始的新闻标题。代码如下:
import re
text_string = ['[重要的] 今年第七号台风23日登陆广东东部沿海地区','上海发布车库销售监管通知:违规者暂停网签资格','[紧要的] 中国对印连发强硬信息,印度急切需要结束对峙']
regex = '^\[[重紧]..\]'
for line in text_string:
if re.search(regex,line) is not None:
print(line)
else:
print('not match')
观测下数据集,我们发现一些新闻标题是以“[重要的]”“[紧要的]”为起始,所以我们需要添加“^”特殊符号代表起始,之后因为存在“重”或者“紧”,所以我们使用“[ ]”匹配多个字符,然后以“.”“.”代表之后的任意两个字符。
运行以上代码,我们看到结果正确提取了所需的新闻标题。
[重要的] 今年第七号台风23日登陆广东东部沿海地区
not match
[紧要的] 中国对印连发强硬信息,印度急切需要结束对峙
03 使用转义符
上述代码中,我们看到使用了“\”为转义符,因为“[ ]”在正则表达式中是特殊符号。
与大多数编程语言相同,正则表达式里使用“\”作为转义字符,这就可能造成反斜杠困扰。假如你需要匹配文本中的字符“\”,那么使用编程语言表示的正则表达式里将需要4个反斜杠“\\\\”:前两个和后两个分别用于在编程语言里转义成反斜杠,转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。
Python里的原生字符串很好地解决了这个问题,这个例子中的正则表达式可以使用r“\\”表示。同样,匹配一个数字的“\\d”可以写成r“\d”。有了原生字符串,你再也不用担心是不是漏写了反斜杠,写出来的表达式也更直观。
为了方便理解我们来举个例子:
import re
if re.search("\\\\","I have one nee\dle") is not None:
print("match it")
else:
print("not match")
通过上述例子,我们就可以匹配到字符串中匹配到的那个反斜杠“nee\dle”。为了简洁一点我们可以换一个写法:
import re
if re.search(r"\\","I have one nee\dle") is not None:
print("match it")
else:
print("not match")
通过加一个r,我们就不用担心是不是漏写反斜杠了。
04 抽取文本中的数字
1. 通过正则表达式匹配年份
“[0-9]”代表的是从0到9的所有数字,那相对的“[a-z]”代表的是所有a-z小写字母。我们通过一个小例子来讲解下如何使用。首先我们定义一个list分配于一个变量strings,匹配包含的年份是在1000年~2999年之间的。代码如下:
import re
strings = ['War of 1812', 'There are 5280 feet to a mile', 'Happy New Year 2016!']
for string in strings:
if re.search('[1-2][0-9]{3}', string):#字符串有英文有数字,匹配其中的数字部分,并且是在1000~2999之间,{3}代表的是重复之前的[0-9]三次,是[0-9] [0-9] [0-9]的简化写法。
year_strings.append(string)
print(year_strings)
2. 抽取所有的年份
我们使用Python中的re模块的另一个方法findall()来返回匹配带正则表达式的那部分字符串。re.findall(“[a-z]”,“abc1234”)得到的结果是[“a”,“b”,“c”]。
我们定义一个字符串years_string,其中的内容是'2015 was a good year, but 2016 will be better!'。现在我们来抽取一下所有的年份。代码如下:
import re
years_string = '2016 was a good year, but 2017 will be better!'
years = re.findall('[2][0-9]{3}',years_string)
在Anaconda中执行这段语句,我们能看到输出['2016', '2017']。
|