在 NLTK 中使用 Stanford NER Tagger 提取人员和组织列表

新手上路,请多包涵

我正在尝试在 Python NLTK 中使用斯坦福命名实体识别器 (NER) 提取人员和组织列表。当我跑步时:

 from nltk.tag.stanford import NERTagger
st = NERTagger('/usr/share/stanford-ner/classifiers/all.3class.distsim.crf.ser.gz',
               '/usr/share/stanford-ner/stanford-ner.jar')
r=st.tag('Rami Eid is studying at Stony Brook University in NY'.split())
print(r)

输出是:

 [('Rami', 'PERSON'), ('Eid', 'PERSON'), ('is', 'O'), ('studying', 'O'),
('at', 'O'), ('Stony', 'ORGANIZATION'), ('Brook', 'ORGANIZATION'),
('University', 'ORGANIZATION'), ('in', 'O'), ('NY', 'LOCATION')]

我想要的是从此列表中提取以下形式的所有个人和组织:

 Rami Eid
Sony Brook University

我试图遍历元组列表:

 for x,y in i:
        if y == 'ORGANIZATION':
            print(x)

但是此代码每行只打印每个实体一个:

 Sony
Brook
University

真实的数据,一句话可以不止一个组织,一个人,不同实体之间如何划界?

原文由 user1680859 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 797
2 个回答

感谢@Vaulstein 发现的 链接,很明显经过训练的斯坦福标注器,作为分布式(至少在 2012 年) 不会分块命名实体。从 接受的答案

许多 NER 系统使用更复杂的标签,例如 IOB 标签,其中 B-PERS 等代码指示个人实体的起始位置。 CRFClassifier 类和特征工厂支持此类标签, 但它们未在我们当前分发的模型中使用(截至 2012 年)

您有以下选择:

  1. 收集相同标记词的运行;例如,所有标记为 PERSON 的相邻词应作为一个命名实体一起使用。这很容易,但当然它有时会组合不同的命名实体。 (例如 New York, Boston [and] Baltimore 是关于三个城市,而不是一个。) 编辑: 这是 Alvas 的代码在接受的 anwser 中所做的。请参阅下面的更简单的实现。

  2. 使用 nltk.ne_chunk() 。它不使用 Stanford 识别器,但它使用块实体。 (它是一个 IOB 命名实体标记器的包装器)。

  3. 想出一种方法,在斯坦福标注器返回的结果之上进行您自己的分块。

  4. 为您感兴趣的领域训练您自己的 IOB 命名实体分块器(使用 Stanford 工具或 NLTK 的框架)。如果您有时间和资源正确执行此操作,它可能会给您最好的结果。

编辑: 如果你想要的只是拉出连续命名实体的运行(上面的选项 1),你应该使用 itertools.groupby

 from itertools import groupby
for tag, chunk in groupby(netagged_words, lambda x:x[1]):
    if tag != "O":
        print("%-12s"%tag, " ".join(w for w, t in chunk))

如果 netagged_words 是您问题中的 (word, type) 元组的列表,则会产生:

 PERSON       Rami Eid
ORGANIZATION Stony Brook University
LOCATION     NY

再次注意,如果同一类型的两个命名实体紧挨着彼此出现,则此方法会将它们组合起来。例如 New York, Boston [and] Baltimore 大约是三个城市,而不是一个。

原文由 alexis 发布,翻译遵循 CC BY-SA 4.0 许可协议

IOB / BIO 表示内部、外部、 开始( IOB ),有时 称为开始、内部、外部 ( BIO )

Stanford NE 标注器返回 IOB/BIO 风格的标签,例如

[('Rami', 'PERSON'), ('Eid', 'PERSON'), ('is', 'O'), ('studying', 'O'),
('at', 'O'), ('Stony', 'ORGANIZATION'), ('Brook', 'ORGANIZATION'),
('University', 'ORGANIZATION'), ('in', 'O'), ('NY', 'LOCATION')]

('Rami', 'PERSON'), ('Eid', 'PERSON') 被标记为 PERSON,“Rami”是 Beginning 或 NE 块,“Eid”是内部。然后你会看到任何非 NE 都将被标记为“O”。

提取连续 NE 块的想法 与使用正则表达式的命名实体识别:NLTK 非常相似,但是因为 Stanford NE chunker API 没有返回一个好的树来解析,你必须这样做:

 def get_continuous_chunks(tagged_sent):
    continuous_chunk = []
    current_chunk = []

    for token, tag in tagged_sent:
        if tag != "O":
            current_chunk.append((token, tag))
        else:
            if current_chunk: # if the current chunk is not empty
                continuous_chunk.append(current_chunk)
                current_chunk = []
    # Flush the final current_chunk into the continuous_chunk, if any.
    if current_chunk:
        continuous_chunk.append(current_chunk)
    return continuous_chunk

ne_tagged_sent = [('Rami', 'PERSON'), ('Eid', 'PERSON'), ('is', 'O'), ('studying', 'O'), ('at', 'O'), ('Stony', 'ORGANIZATION'), ('Brook', 'ORGANIZATION'), ('University', 'ORGANIZATION'), ('in', 'O'), ('NY', 'LOCATION')]

named_entities = get_continuous_chunks(ne_tagged_sent)
named_entities = get_continuous_chunks(ne_tagged_sent)
named_entities_str = [" ".join([token for token, tag in ne]) for ne in named_entities]
named_entities_str_tag = [(" ".join([token for token, tag in ne]), ne[0][1]) for ne in named_entities]

print named_entities
print
print named_entities_str
print
print named_entities_str_tag
print

[出去]:

 [[('Rami', 'PERSON'), ('Eid', 'PERSON')], [('Stony', 'ORGANIZATION'), ('Brook', 'ORGANIZATION'), ('University', 'ORGANIZATION')], [('NY', 'LOCATION')]]

['Rami Eid', 'Stony Brook University', 'NY']

[('Rami Eid', 'PERSON'), ('Stony Brook University', 'ORGANIZATION'), ('NY', 'LOCATION')]

但是请注意,如果两个 NE 是连续的,那么它可能是错误的,但是我仍然想不出任何两个 NE 是连续的,它们之间没有任何“O”的例子。


正如@alexis 建议的那样,最好将 stanford NE 输出转换为 NLTK 树:

 from nltk import pos_tag
from nltk.chunk import conlltags2tree
from nltk.tree import Tree

def stanfordNE2BIO(tagged_sent):
    bio_tagged_sent = []
    prev_tag = "O"
    for token, tag in tagged_sent:
        if tag == "O": #O
            bio_tagged_sent.append((token, tag))
            prev_tag = tag
            continue
        if tag != "O" and prev_tag == "O": # Begin NE
            bio_tagged_sent.append((token, "B-"+tag))
            prev_tag = tag
        elif prev_tag != "O" and prev_tag == tag: # Inside NE
            bio_tagged_sent.append((token, "I-"+tag))
            prev_tag = tag
        elif prev_tag != "O" and prev_tag != tag: # Adjacent NE
            bio_tagged_sent.append((token, "B-"+tag))
            prev_tag = tag

    return bio_tagged_sent

def stanfordNE2tree(ne_tagged_sent):
    bio_tagged_sent = stanfordNE2BIO(ne_tagged_sent)
    sent_tokens, sent_ne_tags = zip(*bio_tagged_sent)
    sent_pos_tags = [pos for token, pos in pos_tag(sent_tokens)]

    sent_conlltags = [(token, pos, ne) for token, pos, ne in zip(sent_tokens, sent_pos_tags, sent_ne_tags)]
    ne_tree = conlltags2tree(sent_conlltags)
    return ne_tree

ne_tagged_sent = [('Rami', 'PERSON'), ('Eid', 'PERSON'), ('is', 'O'),
('studying', 'O'), ('at', 'O'), ('Stony', 'ORGANIZATION'),
('Brook', 'ORGANIZATION'), ('University', 'ORGANIZATION'),
('in', 'O'), ('NY', 'LOCATION')]

ne_tree = stanfordNE2tree(ne_tagged_sent)

print ne_tree

[出去]:

   (S
  (PERSON Rami/NNP Eid/NNP)
  is/VBZ
  studying/VBG
  at/IN
  (ORGANIZATION Stony/NNP Brook/NNP University/NNP)
  in/IN
  (LOCATION NY/NNP))

然后:

 ne_in_sent = []
for subtree in ne_tree:
    if type(subtree) == Tree: # If subtree is a noun chunk, i.e. NE != "O"
        ne_label = subtree.label()
        ne_string = " ".join([token for token, pos in subtree.leaves()])
        ne_in_sent.append((ne_string, ne_label))
print ne_in_sent

[出去]:

 [('Rami Eid', 'PERSON'), ('Stony Brook University', 'ORGANIZATION'), ('NY', 'LOCATION')]

原文由 alvas 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题