Извлечь список лиц и организаций, использующих Stanford NER Tagger в NLTK
Я пытаюсь извлечь список людей и организаций, использующих Stanford Named Entity Recognizer (NER) в Python NLTK. Когда я бегу:
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
С реальными данными может быть больше чем одна организация, люди в одном предложении, как я могу установить границы между различными организациями?
6 ответов
Благодаря ссылке, обнаруженной @Vaulstein, становится ясно, что обученный Stanford tagger, как он распространяется (по крайней мере, в 2012 году) , не разделяет именованные объекты. Из принятого ответа:
Многие системы NER используют более сложные метки, такие как метки IOB, где коды, такие как B-PERS, указывают, где начинается сущность человека. Класс CRFClassifier и фабрики объектов поддерживают такие метки, но они не используются в моделях, которые мы распространяем в настоящее время (по состоянию на 2012 год)
У вас есть следующие варианты:
Собирать серии одинаково помеченных слов; например, все смежные слова помечены
PERSON
должны быть взяты вместе как одно целое. Это очень просто, но, конечно, иногда он объединяет разные именованные объекты. (НапримерNew York, Boston [and] Baltimore
это примерно три города, а не один.) Редактировать: это то, что код Alvas делает в принятом ответе. Смотрите ниже для более простой реализации.использование
nltk.ne_recognize()
, Он не использует распознаватель Стэнфорда, но он делает чанки. (Это оболочка вокруг IOB с именем тега сущности).Обдумайте способ создания своих чанков на основе результатов, которые возвращает тег Stanford.
Подготовьте свой собственный именованный объектный блок 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
это около трех городов, а не один.
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')
помечены как ЧЕЛОВЕК, и "Рами" - это начало или NE, а "Ид" - внутри. И тогда вы увидите, что любой не-NE будет помечен "O".
Идея извлечения непрерывного фрагмента NE очень похожа на распознавание именованных объектов с помощью регулярного выражения: NLTK, но поскольку API блока Stanford NE не возвращает красивое дерево для анализа, вы должны сделать это:
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')]
ВНИМАНИЕ: Даже если у вас есть эта модель "all.3class.distsim.crf.ser.gz", пожалуйста, не используйте ее, потому что
- 1-я причина:
За эту модель люди Стэнфорда НЛП открыто извинились за плохую точность
- 2-я причина:
У него плохая точность, потому что он чувствителен к регистру.
- РЕШЕНИЕ
используйте модель под названием "english.all.3class.caseless.distsim.crf.ser.gz"
Не совсем согласно требованию автора темы напечатать то, что он хочет, может быть, это может помочь
listx = [('Rami', 'PERSON'), ('Eid', 'PERSON'), ('is', 'O'), ('studying', 'O'),
('at', 'O'), ('Stony', 'ORGANIZATION'), ('Brook', 'ORGANIZATION'),
('University', 'ORGANIZATION'), ('in', 'O'), ('NY', 'LOCATION')]
def parser(n, string):
for i in listx[n]:
if i == string:
pass
else:
return i
name = parser(0,'PERSON')
lname = parser(1,'PERSON')
org1 = parser(5,'ORGANIZATION')
org2 = parser(6,'ORGANIZATION')
org3 = parser(7,'ORGANIZATION')
print name, lname
print org1, org2, org3
Вывод будет примерно таким
Rami Eid
Stony Brook University
Используйте оболочку pycorenlp из python, а затем используйте "entitymentions" в качестве ключа, чтобы получить непрерывный кусок человека или организации в одну строку.
Попробуйте использовать метод "enumerate".
Когда вы применяете NER к списку слов, после создания кортежей (слово, тип) перечислите этот список, используя перечисление (список). Это назначит индекс каждому кортежу в списке.
Поэтому позже, когда вы извлекаете ЛИЦО / ОРГАНИЗАЦИЮ / МЕСТО из списка, к ним будет прикреплен индекс.
1 Hussein
2 Obama
3 II
6 James
7 Naismith
21 Naismith
19 Tony
20 Hinkle
0 Frank
1 Mahan
14 Naismith
0 Naismith
0 Mahan
0 Mahan
0 Naismith
Теперь на основе последовательного индекса можно отфильтровать одно имя.
Хуссейн Обама II, Джеймс Нейсмит, Тони Хэнк, Фрэнк Махан