Извлечение списка лиц и организаций с использованием Stanford NER Tagger в NLTK

Я пытаюсь извлечь список лиц и организаций, использующих Stender Recognizer Entirect 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

С реальными данными может быть несколько организаций, людей в одном предложении, как я могу установить ограничения между различными объектами?

Ответ 1

Благодаря ссылке , открытой @Vaulstein, ясно, что обученный стэнфордский теггер, как распределенный (по крайней мере, в 2012 году) , не имеет имени лица. Из принятого ответа:

Многие системы NER используют более сложные метки, такие как метки IOB, где такие коды, как B-PERS, указывают, где начинается человеческое лицо. Классы классов и функций CRFClassifier поддерживают такие метки, , но они не используются в моделях, которые мы в настоящее время распространяем (начиная с 2012 года).

У вас есть следующие возможности:

  • Соберите прогонки одинаково помеченных слов; например, все смежные слова, помеченные PERSON, должны быть взяты вместе как один именованный объект. Это очень просто, но, конечно, иногда они объединяют разные именованные объекты. (Например, New York, Boston [and] Baltimore - это около трех городов, а не один.) Изменить: Это то, что делает код Alvas в принятом anwser. Ниже приведена более простая реализация.

  • Используйте nltk.ne_recognize(). Он не использует средство распознавания в Стэнфорде, но он выполняет куски сущностей. (Это оболочка вокруг объекта IOB с именем entity tagger).

  • Выясните способ сделать свой собственный фрагмент поверх результатов, которые возвращает теггер Стэнфорда.

  • Настройте свой собственный объект IOB с именем entity chunker (используя инструменты 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 - это три города, а не один.

Ответ 2

IOB/BIO означает I nside, O, B, например, (IOB), а иногда и B eginning, I nside, O (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" - это начало или элемент NE, а "Eid" - внутри. И тогда вы увидите, что любой не-NE будет помечен как "O".

Идея извлечь непрерывный кусок NE очень похожа на Именованное распознавание сущностей с использованием регулярного выражения: NLTK, но поскольку API-интерфейс Chunker для .NET не возвращает хорошее дерево для синтаксического анализа вы должны это сделать:

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')]

Ответ 3

Не точно в соответствии с требованием автора темы печатать то, что он хочет, может быть, это может быть любая помощь,

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

Ответ 4

ВНИМАНИЕ: Даже если у вас есть эта модель "all.3class.distsim.crf.ser.gz", пожалуйста, не используйте ее, потому что

  • 1-я причина:

За эту модель люди Стэнфорда НЛП открыто извинились за плохую точность

  • 2-я причина:

У него плохая точность, потому что он чувствителен к регистру.

  • РЕШЕНИЕ

используйте модель под названием "english.all.3class.caseless.distsim.crf.ser.gz"

Ответ 5

Используйте оболочку pycorenlp из python, а затем используйте "entitymentions" в качестве ключа, чтобы получить непрерывный кусок человека или организации в одну строку.

Ответ 6

Попробуйте использовать метод " 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, Джеймс Нейсмит, Тони Хэнк, Фрэнк Махан

Ответ 7

Привет. Я получил ниже список на арабском NER. Как мы можем объединить имя человека и Орг в Python [('للأمم', 'B-ORG'), ('المتحدة', 'I-ORG'), ('أنطونيو', 'B-PERS'), ('دونالد ',' Я-ЧЕЛОВЕК ')]