Разделение выходных данных Stanford Named Entity Recognizer (NER) из формата NLTK

Я использую NER в NLTK, чтобы найти людей, местоположения и организации в предложениях. Я могу получить такие результаты:

[(u'Remaking', u'O'), (u'The', u'O'), (u'Republican', u'ORGANIZATION'), (u'Party', u'ORGANIZATION')]

Можно ли с его помощью собирать вещи вместе? Я хочу вот что:

u'Remaking'/ u'O', u'The'/u'O', (u'Republican', u'Party')/u'ORGANIZATION'

Спасибо!


person Cosmozhang    schedule 23.12.2014    source источник


Ответы (4)


Вы можете использовать стандартный способ представления фрагментов NLTK с помощью nltk.Tree. Это может означать, что вам нужно немного изменить свое представление.

Обычно я представляю предложения с NER-тегами как списки троек:

sentence = [('Andrew', 'NNP', 'PERSON'), ('is', 'VBZ', 'O'), ('part', 'NN', 'O'), ('of', 'IN', 'O'), ('the', 'DT', 'O'), ('Republican', 'NNP', 'ORGANIZATION'), ('Party', 'NNP', 'ORGANIZATION'), ('in', 'IN', 'O'), ('Dallas', 'NNP', 'LOCATION')]

Я делаю это, когда использую внешний инструмент для NER-тегирования предложения. Теперь вы можете преобразовать это предложение в представление NLTK:

from nltk import Tree


def IOB_to_tree(iob_tagged):
    root = Tree('S', [])
    for token in iob_tagged:
        if token[2] == 'O':
            root.append((token[0], token[1]))
        else:
            try:
                if root[-1].label() == token[2]:
                    root[-1].append((token[0], token[1]))
                else:
                    root.append(Tree(token[2], [(token[0], token[1])]))
            except:
                root.append(Tree(token[2], [(token[0], token[1])]))

    return root


sentence = [('Andrew', 'NNP', 'PERSON'), ('is', 'VBZ', 'O'), ('part', 'NN', 'O'), ('of', 'IN', 'O'), ('the', 'DT', 'O'), ('Republican', 'NNP', 'ORGANIZATION'), ('Party', 'NNP', 'ORGANIZATION'), ('in', 'IN', 'O'), ('Dallas', 'NNP', 'LOCATION')]
print IOB_to_tree(sentence)

Изменение в представлении имеет смысл, потому что вам обязательно нужны теги POS для тегов NER.

Конечный результат должен выглядеть так:

(S
  (PERSON Andrew/NNP)
  is/VBZ
  part/NN
  of/IN
  the/DT
  (ORGANIZATION Republican/NNP Party/NNP)
  in/IN
  (LOCATION Dallas/NNP))
person bogs    schedule 24.12.2014

Выглядит долго, но работает:

ner_output = [(u'Remaking', u'O'), (u'The', u'O'), (u'Republican', u'ORGANIZATION'), (u'Party', u'ORGANIZATION')]
chunked, pos = [], ""
for i, word_pos in enumerate(ner_output):
    word, pos = word_pos
    if pos in ['PERSON', 'ORGANIZATION', 'LOCATION'] and pos == prev_tag:
        chunked[-1]+=word_pos
    else:
        chunked.append(word_pos)
    prev_tag = pos

clean_chunked = [tuple([" ".join(wordpos[::2]), wordpos[-1]]) if len(wordpos)!=2 else wordpos for wordpos in chunked]

print clean_chunked

[из]:

[(u'Remaking', u'O'), (u'The', u'O'), (u'Republican Party', u'ORGANIZATION')]

Больше подробностей:

Первый цикл for "с памятью" дает примерно следующее:

[(u'Remaking', u'O'), (u'The', u'O'), (u'Republican', u'ORGANIZATION', u'Party', u'ORGANIZATION')]

Вы поймете, что все Name Enitties будут иметь более двух элементов в кортеже, и вам нужны слова в качестве элементов в списке, то есть 'Republican Party' в (u'Republican', u'ORGANIZATION', u'Party', u'ORGANIZATION'), поэтому вы сделаете что-то вроде этого, чтобы получить четные элементы:

>>> x = [0,1,2,3,4,5,6]
>>> x[::2]
[0, 2, 4, 6]
>>> x[1::2]
[1, 3, 5]

Затем вы также поняли, что последний элемент в кортеже NE - это тег, который вам нужен, поэтому вы должны сделать `

>>> x = (u'Republican', u'ORGANIZATION', u'Party', u'ORGANIZATION')
>>> x[::2]
(u'Republican', u'Party')
>>> x[-1]
u'ORGANIZATION'

Это немного ad-hoc и vebose, но я надеюсь, что это поможет. И вот оно в функции, Благословенное Рождество:

ner_output = [(u'Remaking', u'O'), (u'The', u'O'), (u'Republican', u'ORGANIZATION'), (u'Party', u'ORGANIZATION')]


def rechunk(ner_output):
    chunked, pos = [], ""
    for i, word_pos in enumerate(ner_output):
        word, pos = word_pos
        if pos in ['PERSON', 'ORGANIZATION', 'LOCATION'] and pos == prev_tag:
            chunked[-1]+=word_pos
        else:
            chunked.append(word_pos)
        prev_tag = pos


    clean_chunked = [tuple([" ".join(wordpos[::2]), wordpos[-1]]) 
                    if len(wordpos)!=2 else wordpos for wordpos in chunked]

    return clean_chunked


print rechunk(ner_output)
person alvas    schedule 23.12.2014
comment
Я изменил chunked, pos = [], на chunked, pos, prev_tag = [],, None, что, на мой взгляд, имеет больше смысла. :) Но все же это немного неудобно при работе с двумя последовательными сущностями, например: Человек Человек О. Большое спасибо. - person Cosmozhang; 24.12.2014
comment
последовательные сетевые элементы встречаются редко, и я нахожу их для какой-то другой работы. Если вы их найдете, не могли бы вы помочь опубликовать один или два примера? знак равно - person alvas; 24.12.2014
comment
@alvas говорит, что у вас есть два имени, разделенных запятыми .. PER1_NAME, PER2_NAME и еще кто-то - хорошие друзья ..? - person samsamara; 09.11.2015
comment
запятая будет между тегом NE. И извлечение последовательных тегов NNP все равно будет работать. - person alvas; 09.11.2015

Фактически это появится в следующем выпуске CoreNLP под названием _ 1_. Скорее всего, он не будет напрямую доступен из NLTK, если только люди, работающие с NLTK, не захотят поддерживать его вместе со стандартным интерфейсом Stanford NER.

В любом случае, на данный момент вам придется скопировать код, на который я указал (в котором используется _ 2_ за грязную работу) или напишите свой собственный постпроцессор на Python.

person Jon Gauthier    schedule 23.12.2014

Вот еще одна краткая реализация для группировки результатов Stanford NER с использованием итератора groupby из itertools:

def grouptags(tags, ignore="O", join=" "):
    from itertools import groupby
    for c,g in groupby(tags, lambda t: t[1]):
        if ignore is None or c != ignore:
            if join is None:
                entity = [e for e,_ in g]
            else:
                entity = join.join(e for e,_ in g)
            yield(c, entity)

У функции grouptags есть два варианта:

  • ignore: укажите класс, который игнорируется и не выводится (по умолчанию: «O»). Если нет, возвращаются все сущности.
  • join: укажите символ, используемый для соединения частей (по умолчанию: ""). Если Нет, части возвращаются несоединенными в виде списка.
person Tom    schedule 14.06.2016