web-dev-qa-db-fra.com

Extrait de la liste des personnes et organisations utilisant Stanford NER Tagger en NLTK

J'essaie d'extraire une liste de personnes et d'organisations à l'aide de la fonction de reconnaissance d'entité nommée de Stanford (NER) dans 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) 

la sortie est:

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

ce que je veux, c'est extraire de cette liste toutes les personnes et organisations sous cette forme:

Rami Eid
Sony Brook University

J'ai essayé de parcourir la liste des tuples:

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

Mais ce code n'imprime que chaque entité une par ligne:

Sony 
Brook 
University

Avec des données réelles, il peut y avoir plus d'une organisation, plusieurs personnes dans une phrase. Comment puis-je définir les limites entre différentes entités?

23
user1680859

Grâce au link découvert par @Vaulstein, il est clair que le tagger formé à Stanford, tel que distribué (au moins en 2012) ne découpe pas les entités nommées. De la réponse acceptée :

De nombreux systèmes NER utilisent des étiquettes plus complexes telles que les étiquettes IOB, où des codes tels que B-PERS indiquent le début d'une entité de personne. La classe CRFClassifier et les usines de fonctions prennent en charge de telles étiquettes, mais elles ne sont pas utilisées dans les modèles que nous distribuons actuellement (à partir de 2012)

Vous avez les options suivantes:

  1. Recueillir des passages de mots étiquetés de manière identique; Par exemple, tous les mots adjacents étiquetés PERSON doivent être pris ensemble comme une seule entité nommée. C'est très facile, mais bien sûr, il est parfois possible de combiner différentes entités nommées. (Exemple: New York, Boston [and] Baltimore concerne environ trois villes, pas une.) Edit: C’est ce que le code d’Alvas fait dans la réponse acceptée. Voir ci-dessous pour une implémentation plus simple.

  2. Utilisez nltk.ne_recognize(). Il n'utilise pas le programme de reconnaissance de Stanford, mais des entités en bloc. (C'est un wrapper autour d'un tagueur d'entité nommé IOB). 

  3. Trouvez un moyen de diviser vos résultats par-dessus les résultats renvoyés par le marqueur de Stanford.

  4. Entraînez votre propre gestionnaire d'entités nommées IOB (à l'aide des outils Stanford ou du framework NLTK) pour le domaine qui vous intéresse. Si vous avez le temps et les ressources nécessaires pour le faire correctement, vous obtiendrez probablement les meilleurs résultats.

Edit: Si vous souhaitez uniquement extraire des suites d'entités nommées continues (option 1 ci-dessus), vous devez utiliser 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))

Si netagged_words est la liste de (Word, type) tuples dans votre question, cela produit:

PERSON       Rami Eid
ORGANIZATION Stony Brook University
LOCATION     NY

Notez à nouveau que si deux entités nommées du même type se trouvent l'une à côté de l'autre, cette approche les combinera. Par exemple. New York, Boston [and] Baltimore est d'environ trois villes, pas une. 

27
alexis

IOB/BIO signifie I côté, O utside, B eginning (IOB), ou parfois aussi appelé B eginning, I nside, O utside (BIO)

L'étiqueteur Stanford NE renvoie les étiquettes de style IOB/BIO, par exemple.

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

Les ('Rami', 'PERSON'), ('Eid', 'PERSON') sont étiquetés comme PERSONNE et "Rami" est le début ou un morceau NE et "Eid" est l'intérieur. Et ensuite, vous voyez que tout non-NE sera étiqueté avec "O".

L'idée d'extraire un bloc NE continu est très similaire à Reconnaissance d'entité nommée avec expression régulière: NLTK mais comme l'API chunker de Stanford NE ne renvoie pas un bel arbre à analyser, vous devez le faire:

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

[en dehors]:

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

Mais notez s'il vous plaît la limitation que si deux NE sont continus, alors ça pourrait être faux, néanmoins je ne peux toujours pas penser à un exemple où deux NE sont continus sans aucun "O" entre eux.


Comme @alexis l'a suggéré, il est préférable de convertir la sortie stanford NE en arbres 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

[en dehors]:

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

Ensuite:

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

[en dehors]:

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

ATTENTION: Même si vous obtenez ce modèle "all.3class.distsim.crf.ser.gz", ne l'utilisez pas, car

    1ère raison:

Pour ce modèle, les gens de stanford nlp se sont ouvertement excusés pour la précision 

    2ème raison:

Il a une mauvaise précision car il est sensible à la casse. 

    SOLUTION

utilisez le modèle appelé "english.all.3class.caseless.distsim.crf.ser.gz"

1
yunus

Pas exactement comme l'exige l'auteur du sujet d'imprimer ce qu'il veut, peut-être que cela peut aider

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

La sortie serait quelque chose comme ça

Rami Eid
Stony Brook University
1
Kanishk Gandharv

Essayez d’utiliser la méthode " énumérer ".

Lorsque vous appliquez le NER à la liste de mots, une fois les n-uplets créés (Word, type), énumérez cette liste à l'aide de l'énumération (liste). Ceci assignerait un index à chaque tuple de la liste.

Ainsi, plus tard, lorsque vous extrayez PERSON/ORGANIZATION/LOCATION de la liste, un index y est associé.

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

Maintenant, sur la base de l'index consécutif, un seul nom peut être filtré.

Hussein Obama II, James Naismith, Tony Hank, Frank Mahan

0
Akash Tyagi

Utilisez pycorenlp wrapper à partir de python, puis utilisez 'entitymentions' comme clé pour obtenir le fragment continu de personne ou d'organisation dans une seule chaîne.

0
Abhishek Bisht