Python NLTK 解析固定句型并进行分词
我有一个应用程序,需要用NLTK来理解人类说的话,并从中找出有用的信息。需要理解的句子格式是from <某个地方>, to <某个地方> on <某个日期>, <旅行方式,比如商务舱>
。你可以想象,这种句子可以有很多种表达方式,比如:
我想从亚特兰大去纽约,商务舱,2014年7月25日。
我想通过商务舱旅行,从纽约在7月25日去亚特兰大。
我有一个梦想,有一天我会登上飞机,乘坐商务舱,降落在纽约,出发地是亚特兰大,最好是在7月25日。
7月25日,从亚特兰大到纽约,商务舱。
你明白我的意思了。我想提取一些关键信息——出发地、目的地、舱位、日期。有些信息可能缺失,需要识别出来,或者做出合理的假设。比如,如果出发地缺失,就要找出来;如果年份缺失,就假设为当前年份。同时,要忽略那些无关紧要的信息(比如我有一个梦想的部分,尽管我很喜欢马丁·路德·金)。
我想知道有没有办法在NLTK中实现这个功能?我知道有一些标记工具可以用,而且可以训练这些工具,但我对这些知识了解不多。是否有可能覆盖所有可能的表达方式,提取出这些信息呢?如果可以的话,稍微给我一些指导就好了。
2 个回答
这个问题叫做“命名实体识别”,简称“ner”。你可以在网上搜索这些词,能找到很多相关的库、在线接口,还有一些针对特定数据类型的实用技巧。
你可以在这里查看一个演示的NER系统:http://nlp.stanford.edu:8080/ner/
检测日期和时间的引用,可能是目前有很多基于经验法则的解决方案的情况。
如果你正在处理的文本领域比较具体且有限,那么手动整理一些实体的列表可能会非常有帮助。
比如,列出所有商业机场的机场代码和城市名称,然后尝试将这些名称与任何输入文本进行精确匹配。
在计算语言学中,这个过程叫做“命名实体识别”,就是从文本中识别出像组织、人物和地点这样的东西。
这里的挑战是,nltk中的默认命名实体识别工具是一个最大熵分块器,它是基于ACE语料库训练的。这个工具没有被训练来识别日期和时间,所以你需要对它进行一些调整,找到检测时间的方法。
有一些工具可以帮助提取命名实体,其中斯坦福命名实体识别器(Stanford NER)是最受欢迎的工具之一,它是用Java实现的。不过,你可以通过下载这个工具包,并通过NLTK与斯坦福NER进行交互。
你可以下载斯坦福命名实体识别器版本3.4,在里面你会找到stanford-ner.jar和分类模型“all.3class.distsim.crf.ser.gz”。
from nltk.tag.stanford import NERTagger
def stanfordNERExtractor(sentence):
st = NERTagger('/usr/share/stanford-ner/classifiers/all.3class.distsim.crf.ser.gz',
'/usr/share/stanford-ner/stanford-ner.jar')
return st.tag(sentence.split())
stanfordNERExtractedLines = stanfordNERExtractor("New York")
print stanfordNERExtractedLines #[('New-York', 'LOCATION')]
你也可以使用NLTK,更多细节可以查看官方文档,还可以参考Gavin的这个gist。
def extract_entities(text):
for sent in nltk.sent_tokenize(text):
for chunk in nltk.ne_chunk(nltk.pos_tag(nltk.word_tokenize(sent))):
if hasattr(chunk, 'node'):
print chunk.node, ' '.join(c[0] for c in chunk.leaves())
extract_entities("to play to Atlanta")
#Output: [('to', 'TO'),('play', 'VB'),('to', 'TO'),('play', 'NN')],
- 我们如何识别目的地? 在区分地点后,你可能会遇到识别被空格分开的单词的问题,或者区分来源和目的地。
最好写一个正则表达式模式来识别来源和目的地。你可能会遇到像"to get"
这样的其他单词,但你可以通过st.tag
("LOCATION")来验证你识别的地点,或者如果你使用了NLTK,可以检查它是否是动词("VB"/"NN")。你还可以使用NLTK的UnigramTagger()和BigramTagger()来获取“FROM”和“TO”后面的名称,这些名称可以被识别为地点。
import re text= "I want to go to New York from Atlanta, business class, on 25th July." destination= re.findall(r'.to.([A-Z][a-zA-Z]+?[\s-]*[A-Z]*[a-zA-Z]*)',text) source= re.findall(r'.from.([A-Z][a-zA-Z]+?[\s-]*[A-Z]*[a-zA-Z]*)',text) print source,destination
- 我们如何识别时间/日期?
如上所述,这是我们可能面临的问题之一,但我们可以使用正则表达式,正如在这个讨论中提到的。
print re.findall(
r"""(?ix) # case-insensitive, verbose regex
\b # match a word boundary
(?: # match the following three times:
(?: # either
\d+ # a number,
(?:\.|st|nd|rd|th)* # followed by a dot, st, nd, rd, or th (optional)
| # or a month name
(?:(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*)
)
[\s./-]* # followed by a date separator or whitespace (optional)
){3} # do this three times
\b """,
text)
输出:
25th July 2014.
我们也可以使用python-dateutil或者这个工具来代替使用正则表达式。
如果缺少某些部分,比如年份或月份,我们可以使用parsedatetime包来调整。
看看这个快速示例(你可以根据不同的场景进行调整)。
>>> import parsedatetime
>>> p = parsedatetime.Calendar()
>>> print p.parse("25th this month")
(time.struct_time(tm_year=2014, tm_mon=11, tm_mday=10, tm_hour=1, tm_min=5, tm_sec=31, tm_wday=0, tm_yday=314, tm_isdst=0), 0)
>>> print p.parse("25th July")
((2015, 7, 25, 1, 5, 50, 0, 314, 0), 1)
>>> print p.parse("25th July 2014")
((2014, 7, 25, 1, 6, 3, 0, 314, 0), 1)
最后,你可以使用这个数据集来提取机场,并验证提到的地点的正确性,以防你在回答时提到可用性(有些地方是没有机场的)。
对于舱位,你可以通过查看句子中的“经济舱”、“商务舱”等词来验证(你可以选择使用in
或正则表达式)。
想了解更多这个主题的细节,可以查看:NLTK - 从文本中提取信息。