python - spanish - Extracto de la lista de personas y organizaciones que utilizan Stanford NER Tagger en NLTK
named entity recognition spanish (6)
ADVERTENCIA: Incluso si obtiene este modelo "all.3class.distsim.crf.ser.gz", no lo use porque
-
1er motivo:
Para este modelo, la gente de Stanford PNL se ha disculpado abiertamente por su mala precisión
-
2da razón:
Tiene mala precisión porque es sensible a mayúsculas y minúsculas.
-
SOLUCIÓN
use el modelo llamado "english.all.3class.caseless.distsim.crf.ser.gz"
Estoy tratando de extraer una lista de personas y organizaciones que utilizan el Reconocimiento de entidad con nombre de Stanford (NER) en Python NLTK. Cuando corro:
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 salida es:
[(''Rami'', ''PERSON''), (''Eid'', ''PERSON''), (''is'', ''O''), (''studying'', ''O''),
(''at'', ''O''), (''Stony'', ''ORGANIZATION''), (''Brook'', ''ORGANIZATION''),
(''University'', ''ORGANIZATION''), (''in'', ''O''), (''NY'', ''LOCATION'')]
Lo que quiero es extraer de esta lista a todas las personas y organizaciones de esta forma:
Rami Eid
Sony Brook University
Traté de recorrer la lista de tuplas:
for x,y in i:
if y == ''ORGANIZATION'':
print(x)
Pero este código solo imprime cada entidad una por línea:
Sony
Brook
University
Con datos reales puede haber más de una organización, personas en una oración, ¿cómo puedo poner los límites entre diferentes entidades?
Gracias al link descubierto por @Vaulstein, está claro que el etiquetador Stanford capacitado, tal como se distribuyó (al menos en 2012) no fragmenta las entidades con nombre . De la respuesta aceptada :
Muchos sistemas NER usan etiquetas más complejas, como las etiquetas IOB, donde códigos como B-PERS indican dónde comienza una entidad persona. La clase CRFClassifier y las fábricas de características admiten tales etiquetas, pero no se utilizan en los modelos que distribuimos actualmente (a partir de 2012)
Tienes las siguientes opciones:
-
Recopila series de palabras con etiquetas idénticas; por ejemplo, todas las palabras adyacentes etiquetadas como
PERSON
deben tomarse juntas como una entidad con nombre. Eso es muy fácil, pero por supuesto a veces combinará diferentes entidades con nombre. (Por ejemplo,New York, Boston [and] Baltimore
se trata de tres ciudades, no una). Editar: Esto es lo que hace el código de Alvas en la respuesta aceptada. Vea a continuación una implementación más simple. -
Use
nltk.ne_recognize()
. No utiliza el reconocedor de Stanford pero sí entidades fragmentadas. (Es un contenedor alrededor de un etiquetador de entidad con nombre IOB). -
Encuentre una forma de hacer su propio troceado sobre los resultados que devuelve el etiquetador de Stanford.
-
Entrene a su propio elemento de entidad con nombre IOB (utilizando las herramientas de Stanford, o el marco de NLTK) para el dominio que le interesa. Si tiene el tiempo y los recursos para hacerlo correctamente, probablemente le dará los mejores resultados.
Editar:
si todo lo que desea es extraer ejecuciones de entidades con nombre continuo (opción 1 anterior), debe usar
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
es la lista de tuplas
(word, type)
en su pregunta, esto produce:
PERSON Rami Eid
ORGANIZATION Stony Brook University
LOCATION NY
Tenga en cuenta nuevamente que si dos entidades con nombre del mismo tipo ocurren una al lado de la otra, este enfoque las combinará.
Por ejemplo,
New York, Boston [and] Baltimore
se trata de tres ciudades, no una.
IOB / BIO significa I nside, O utside, B eginning (IOB), o a veces también B Bginning, I nside, O utside (BIO)
El etiquetador NE de Stanford devuelve etiquetas de estilo IOB / BIO, p. Ej.
[(''Rami'', ''PERSON''), (''Eid'', ''PERSON''), (''is'', ''O''), (''studying'', ''O''),
(''at'', ''O''), (''Stony'', ''ORGANIZATION''), (''Brook'', ''ORGANIZATION''),
(''University'', ''ORGANIZATION''), (''in'', ''O''), (''NY'', ''LOCATION'')]
Los
(''Rami'', ''PERSON''), (''Eid'', ''PERSON'')
están etiquetados como PERSON y "Rami" es el comienzo o un fragmento NE y "Eid" es el interior.
Y luego verá que cualquier no NE se etiquetará con "O".
La idea de extraer un fragmento NE continuo es muy similar al Reconocimiento de entidad con nombre con expresión regular: NLTK, pero debido a que la API Standal NE chunker no devuelve un buen árbol para analizar, debe hacer esto:
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
[fuera]:
[[(''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'')]
Pero tenga en cuenta la limitación de que si dos NEs son continuos, entonces podría estar equivocado, sin embargo, todavía no puedo pensar en ningún ejemplo en el que dos NEs sean continuos sin ninguna "O" entre ellos.
Como sugirió @alexis, es mejor convertir la salida NE de Stanford en árboles 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
[fuera]:
(S
(PERSON Rami/NNP Eid/NNP)
is/VBZ
studying/VBG
at/IN
(ORGANIZATION Stony/NNP Brook/NNP University/NNP)
in/IN
(LOCATION NY/NNP))
Entonces:
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
[fuera]:
[(''Rami Eid'', ''PERSON''), (''Stony Brook University'', ''ORGANIZATION''), (''NY'', ''LOCATION'')]
Intente usar el método " enumerar ".
Cuando aplique NER a la lista de palabras, una vez que se crean las tuplas de (palabra, tipo), enumere esta lista usando el enumerar (lista). Esto asignaría un índice a cada tupla en la lista.
Más tarde, cuando extraiga PERSONA / ORGANIZACIÓN / UBICACIÓN de la lista, tendrán un índice adjunto.
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
Ahora, sobre la base del índice consecutivo, se puede filtrar un solo nombre.
Hussein Obama II, James Naismith, Tony Hank, Frank Mahan
No exactamente según el requisito del autor del tema para imprimir lo que quiere, tal vez esto pueda ser de alguna ayuda,
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 salida sería algo como esto
Rami Eid
Stony Brook University
Use el contenedor pycorenlp de python y luego use ''entitymentions'' como clave para obtener la parte continua de persona u organización en una sola cadena.