Вычисление имен Norton Recognizer (NER) в Stanford Nancy из формата 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'

Спасибо!

Ответ 1

Вы можете использовать стандартный способ 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))

Ответ 2

Он выглядит долго, но он выполняет работу:

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-loop "с памятью" выполняет примерно следующее:

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

Вы поймете, что все имена 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)

Ответ 3

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

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

Ответ 4

Вот еще одна краткая реализация для группировки результатов Stanford NER с использованием итератора group 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 имеет две опции:

  • игнорировать: указать класс, который игнорируется и исключается из вывода (по умолчанию: "O" ). Если "Нет", все объекты возвращаются.
  • join: укажите символ, используемый для соединения частей (по умолчанию: ""). Если None, части возвращаются в виде списка.