2012-01-25 25 views
8

Estoy tratando de extraer datos de algunos archivos de texto grandes que contienen entradas sobre personas. El problema es, sin embargo, que no puedo controlar la forma en que los datos me llegan.Procesamiento de datos de texto con formato incorrecto con aprendizaje automático o NLP

Por lo general, en un formato como este:

apellido, nombre MiddleName (Tal vez un apodo) ¿Por qué es este texto hereJanuary, 25, 2012

Nombre Apellido 2001 un texto que yo no' t se preocupan por

Apellido, nombre, bla, bla ... 25 enero de 2012 ...

Actualmente, estoy usando un enorme regex que divide todas las palabras kindaCamelcase, todas las palabras que tienen un nombre de mes añadido al final y muchos casos especiales para los nombres. Luego uso más expresiones regulares para extraer muchas combinaciones para el nombre y la fecha.

Esto parece no ser óptimo.

¿Hay alguna biblioteca de aprendizaje automático para Python que pueda analizar datos malformados que estén algo estructurados?

He intentado con NLTK, pero no ha podido manejar mis datos sucios. Estoy retocando con Orange en este momento y me gusta el estilo OOP, pero no estoy seguro si estoy perdiendo el tiempo.

Idealmente, me gustaría hacer algo como esto para entrenar a un analizador (con muchos pares de entrada/salida):

training_data = (
    'LASTNAME, Firstname Middlename (Maybe a Nickname)FooBarJanuary 25, 2012', 
    ['LASTNAME', 'Firstname', 'Middlename', 'Maybe a Nickname', 'January 25, 2012'] 
) 

es algo como esto sea posible o estoy sobreestimar la máquina de aprendizaje? Cualquier sugerencia será apreciada, ya que me gustaría aprender más sobre este tema.

+4

Debería proporcionarnos más datos de muestra que nos ayuden a comprender el tipo de cosas extrañas que uno esperaría ver en sus datos. –

+2

¿También está seguro de que no puede ponerse en contacto con los proveedores de datos para averiguar cómo están generando estos datos en primer lugar? Tal vez haya K fuentes diferentes de su final, cada una de las cuales usa un estilo específico bien definido. –

+0

Lamentablemente, esas son las restricciones que tengo. Los datos que obtengo ya se han agregado a una gran base de datos y no se pueden modificar. – Blender

Respuesta

3

Terminé implementando una serie algo complicada de expresiones regulares exhaustivas que abarcaban todos los casos de uso posibles utilizando "filtros" basados ​​en texto que se sustituían por las expresiones regulares apropiadas cuando se cargaba el analizador sintáctico.

Si alguien está interesado en el código, lo editaré en esta respuesta.


Esto es básicamente lo que he usado. Para construir las expresiones regulares de mi "lenguaje", que tenía que hacer clases de reemplazo:

class Replacer(object): 
    def __call__(self, match): 
     group = match.group(0) 

     if group[1:].lower().endswith('_nm'): 
      return '(?:' + Matcher(group).regex[1:] 
     else: 
      return '(?P<' + group[1:] + '>' + Matcher(group).regex[1:] 

Entonces, hice una clase genérica Matcher, que construye una expresión regular para un patrón particular dado el nombre del patrón:

class Matcher(object): 
    name_component = r"([A-Z][A-Za-z|'|\-]+|[A-Z][a-z]{2,})" 
    name_component_upper = r"([A-Z][A-Z|'|\-]+|[A-Z]{2,})" 

    year = r'(1[89][0-9]{2}|20[0-9]{2})' 
    year_upper = year 

    age = r'([1-9][0-9]|1[01][0-9])' 
    age_upper = age 

    ordinal = r'([1-9][0-9]|1[01][0-9])\s*(?:th|rd|nd|st|TH|RD|ND|ST)' 
    ordinal_upper = ordinal 

    date = r'((?:{0})\.? [0-9]{{1,2}}(?:th|rd|nd|st|TH|RD|ND|ST)?,? \d{{2,4}}|[0-9]{{1,2}} (?:{0}),? \d{{2,4}}|[0-9]{{1,2}}[\-/\.][0-9]{{1,2}}[\-/\.][0-9]{{2,4}})'.format('|'.join(months + months_short) + '|' + '|'.join(months + months_short).upper()) 
    date_upper = date 

    matchers = [ 
     'name_component', 
     'year', 
     'age', 
     'ordinal', 
     'date', 
    ] 

    def __init__(self, match=''): 
     capitalized = '_upper' if match.isupper() else '' 
     match = match.lower()[1:] 

     if match.endswith('_instant'): 
      match = match[:-8] 

     if match in self.matchers: 
      self.regex = getattr(self, match + capitalized) 
     elif len(match) == 1: 
     elif 'year' in match: 
      self.regex = getattr(self, 'year') 
     else: 
      self.regex = getattr(self, 'name_component' + capitalized) 

Por último, está el objeto genérico Pattern:

class Pattern(object): 
    def __init__(self, text='', escape=None): 
     self.text = text 
     self.matchers = [] 

     escape = not self.text.startswith('!') if escape is None else False 

     if escape: 
      self.regex = re.sub(r'([\[\].?+\-()\^\\])', r'\\\1', self.text) 
     else: 
      self.regex = self.text[1:] 

     self.size = len(re.findall(r'(\$[A-Za-z0-9\-_]+)', self.regex)) 

     self.regex = re.sub(r'(\$[A-Za-z0-9\-_]+)', Replacer(), self.regex) 
     self.regex = re.sub(r'\s+', r'\\s+', self.regex) 

    def search(self, text): 
     return re.search(self.regex, text) 

    def findall(self, text, max_depth=1.0): 
     results = [] 
     length = float(len(text)) 

     for result in re.finditer(self.regex, text): 
      if result.start()/length < max_depth: 
       results.extend(result.groups()) 

     return results 

    def match(self, text): 
     result = map(lambda x: (x.groupdict(), x.start()), re.finditer(self.regex, text)) 

     if result: 
      return result 
     else: 
      return [] 

hacía bastante complicado, pero funcionó. No voy a publicar todo el código fuente, pero esto debería hacer que alguien comience. Al final, se convierte un archivo de la siguiente manera:

$LASTNAME, $FirstName $I. said on $date 

En una expresión regular compilada con los grupos de captura con nombre.

+0

Me interesa saber qué tipo de expresiones regulares has usado :-) –

+0

@IvoFlipse: mira mi respuesta. No es muy bonito, pero funciona. – Blender

+0

Gracias por la actualización, definitivamente vale la pena un voto popular ahora! Me gusta cómo envuelve todas las expresiones regulares para que no tenga que escribir la sintaxis horrible cada vez. –

0

Tengo un problema similar, principalmente debido al problema con la exportación de datos de Microsoft Office 2010 y el resultado es una unión entre dos palabras consecutivas en un intervalo algo regular. El área del dominio es la operación morológica como un corrector ortográfico. Puede saltar a la solución de aprendizaje automático o crear una solución heurística como yo.

La solución fácil es suponer que la palabra recién formada es una combinación de nombres propios (con el primer carácter en mayúscula).

La segunda solución adicional es tener un diccionario de palabras válidas y probar un conjunto de ubicaciones de partición que generen dos (o al menos una) palabras válidas. Otro problema puede surgir cuando uno de ellos es el nombre propio que, por definición, está fuera del vocabulario en el diccionario anterior. quizás una de las maneras en que podemos usar la estadística de longitud de palabra que se puede usar para identificar si una palabra es una palabra formada erróneamente o, en realidad, una palabra legítima.

En mi caso, esto es parte de la corrección manual de grandes corpus de texto (una verificación humana en bucle) pero lo único que se puede automatizar es la selección de palabras probablemente malformadas y su recomendación corregida.

+0

esta es una solución general que en mi caso no requiere NLTK en absoluto. Lo implemento en python puro. – Peb

+0

Estoy haciendo esto con una gran cadena de expresiones regulares. Esperaba algo más elegante, pero podría seguir con eso ... – Blender

0

cuanto a las palabras concatenadas, las puede dividir utilizando un tokenizer:

El OpenNLP Tokenizers segmento de secuencia de caracteres de una entrada en tokens. Los tokens son generalmente palabras, signos de puntuación, números, etc.

Por ejemplo:

Pierre Vinken, 61 años, se unirá a la junta como director no ejecutivo el 29 de noviembre

se tokenizados en:

Pierre Vinken, de 61 años, se unirá al directorio como director no ejecutivo el 29 de noviembre.

OpenNLP tiene un "tokenizer se puede aprender" que se puede entrenar. Si no funciona, puede intentar las respuestas a: Detect most likely words from text without spaces/combined words.

Cuando se hace la división, se puede eliminar la puntuacion y pasarlo a un sistema NER como CoreNLP:

Johnson John Doe Tal vez un apodo ¿Por qué es este texto aquí -25 de enero de 2012

que da salida:

Tokens 
Id Word Lemma Char begin Char end POS NER Normalized NER 
1 Johnson Johnson 0 7 NNP PERSON 
2 John John 8 12 NNP PERSON 
3 Doe Doe 13 16 NNP PERSON 
4 Maybe maybe 17 22 RB O 
5 a a 23 24 DT O 
6 Nickname nickname 25 33 NN MISC  
7 Why why 34 37 WRB MISC  
8 is be 38 40 VBZ O 
9 this this 41 45 DT O 
10 text text 46 50 NN O 
11 here here 51 55 RB O 
12 January January 56 63 NNP DATE 2012-01-25 
13 25 25 64 66 CD DATE 2012-01-25 
14 2012 2012 67 71 CD DATE 2012-01-25 
+0

Gracias por la ayuda. Intenté usar el tokenizer de NLTK, pero los datos simplemente no quieren cooperar (a veces NLTK detecta nombres, pero a veces no). CoreNLP parece * realmente * preciso, pero no está en Python, así que me va a costar mucho usarlo. Pero si mi enfoque falla, creo que CoreNLP es mi próxima táctica. – Blender

0

una parte de su problema: "todas las palabras que tienen un nombre de mes clavada en el final,"

Si, como parece ser el caso de que tenga una fecha en el formato Monthname 1-or-2-digit-day-number, yyyy al final de la cadena, se debe utilizar una expresión regular para que Munch en primera. Entonces tiene un trabajo ahora mucho más simple en el resto de la cadena de entrada.

Nota: Si no usted podría tener problemas con nombres que son también nombres de los meses, por ejemplo, Abril, mayo, junio, agosto. También marzo es un apellido que podría usarse como un "segundo nombre", p. Ej. SMITH, John March.

Su uso de la "última/primera/media" terminología es "interesante".Hay problemas potenciales si sus datos incluyen nombres no anglosajones como éstas:

Mao Zedong aka aka Mao Ze DongMao Tse Tung
Sima Qian aka Ssu-ma Ch'ien
Saddam Hussein Abd al-Majid al-Tikriti
Noda Yoshihiko
Kossuth Lajos
José Luis Rodríguez Zapatero
Pedro Manuel Mamede Passos Coelho
Sukarno

+0

He explicado los nombres crudamente con expresiones regulares al emparejar palabras consecutivas en mayúsculas (o en mayúsculas) al principio de la cadena (con '-' y' ''), pero no estoy seguro de si eso es óptimo. – Blender

+0

En cuanto a las fechas, tengo alrededor de siete expresiones regulares para eso: 'Jan. 21, 2011', '21 de enero de 2011',' 21 de enero de 2011', '21 de enero de 2011',' 2011', etc. Es realmente la fuerza bruta. – Blender

0

Unos pocos punteros, para que pueda empezar:

  • para la fecha de análisis, se puede empezar con un par de expresiones regulares, y entonces se podría utilizar chronic o jChronic
  • de nombres, éstos OpenNlp models debería funcionar

en cuanto a la formación de un modelo de aprendizaje automático a sí mismo, esto no es tan sencillo, especialmente con respecto a los datos de entrenamiento (el esfuerzo de trabajo) ...

+0

Actualmente, estoy usando un montón de expresiones regulares para unir patrones en los nombres (como siempre aparecen en la primera oración). Una vez que obtengo el nombre, utilizo una biblioteca de analizador de nombres de Python para formatearlo bien. Luego, uso como 10 expresiones regulares para que coincida con todos los formatos de fecha posibles y también los extraigo. No es una solución elegante, pero funciona. Me preguntaba si el aprendizaje automático podría ser de ayuda para mí. Pero gracias por la respuesta. Examinaré OpenNLP un poco más, ya que todo el mundo lo está sugiriendo sobre NLTK. – Blender

Cuestiones relacionadas