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?
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:
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.
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).
Trouvez un moyen de diviser vos résultats par-dessus les résultats renvoyés par le marqueur de Stanford.
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.
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')]
ATTENTION: Même si vous obtenez ce modèle "all.3class.distsim.crf.ser.gz", ne l'utilisez pas, car
Pour ce modèle, les gens de stanford nlp se sont ouvertement excusés pour la précision
Il a une mauvaise précision car il est sensible à la casse.
utilisez le modèle appelé "english.all.3class.caseless.distsim.crf.ser.gz"
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
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
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.