Python NLTK 解析固定句型并进行分词

1 投票
2 回答
2084 浏览
提问于 2025-04-30 17:32

我有一个应用程序,需要用NLTK来理解人类说的话,并从中找出有用的信息。需要理解的句子格式是from <某个地方>, to <某个地方> on <某个日期>, <旅行方式,比如商务舱>。你可以想象,这种句子可以有很多种表达方式,比如:

  1. 我想从亚特兰大去纽约,商务舱,2014年7月25日。

  2. 我想通过商务舱旅行,从纽约在7月25日去亚特兰大。

  3. 我有一个梦想,有一天我会登上飞机,乘坐商务舱,降落在纽约,出发地是亚特兰大,最好是在7月25日。

  4. 7月25日,从亚特兰大到纽约,商务舱。

你明白我的意思了。我想提取一些关键信息——出发地、目的地、舱位、日期。有些信息可能缺失,需要识别出来,或者做出合理的假设。比如,如果出发地缺失,就要找出来;如果年份缺失,就假设为当前年份。同时,要忽略那些无关紧要的信息(比如我有一个梦想的部分,尽管我很喜欢马丁·路德·金)。

我想知道有没有办法在NLTK中实现这个功能?我知道有一些标记工具可以用,而且可以训练这些工具,但我对这些知识了解不多。是否有可能覆盖所有可能的表达方式,提取出这些信息呢?如果可以的话,稍微给我一些指导就好了。

暂无标签

2 个回答

2

这个问题叫做“命名实体识别”,简称“ner”。你可以在网上搜索这些词,能找到很多相关的库、在线接口,还有一些针对特定数据类型的实用技巧。

你可以在这里查看一个演示的NER系统:http://nlp.stanford.edu:8080/ner/

检测日期和时间的引用,可能是目前有很多基于经验法则的解决方案的情况。

如果你正在处理的文本领域比较具体且有限,那么手动整理一些实体的列表可能会非常有帮助。
比如,列出所有商业机场的机场代码和城市名称,然后尝试将这些名称与任何输入文本进行精确匹配。

2

在计算语言学中,这个过程叫做“命名实体识别”,就是从文本中识别出像组织、人物和地点这样的东西。

这里的挑战是,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 - 从文本中提取信息

撰写回答