Hola, les saluda Luis y esta vez les traigo otro nuevo post.
Índice
Una solución creativa para la distribución de clases desequilibrada
La distribución de clases desequilibrada es un problema común en el aprendizaje automático. Recientemente me enfrenté a este problema al entrenar un modelo de clasificación de sentimientos. Ciertas categorías fueron mucho más frecuentes que otras y la calidad predictiva del modelo sufrió. La primera técnica que utilicé para abordar esto fue el submuestreo aleatorio, en el que muestreé al azar un subconjunto de filas de cada categoría hasta un umbral máximo. Seleccioné un techo que equilibraba razonablemente las 3 clases superiores. Aunque se observó una pequeña mejora, el modelo todavía estaba lejos de ser óptimo.
Necesitaba una forma de lidiar con las clases subrepresentadas. No podía confiar en las técnicas tradicionales utilizadas en la clasificación de varias clases, como la ponderación de muestras y clases, ya que estaba trabajando con un conjunto de datos de varias etiquetas. Se hizo evidente que tendría que aprovechar el sobremuestreo en esta situación.
Una técnica como SMOTE (Técnica de sobremuestreo de minorías sintéticas) puede ser eficaz para el sobremuestreo, aunque el problema vuelve a ser un poco más difícil con conjuntos de datos de múltiples etiquetas. MLSMOTE (Técnica de sobremuestreo de minorías sintéticas de etiquetas múltiples) [1], pero la alta naturaleza dimensional de los vectores numéricos creados a partir del texto a veces puede hacer que otras formas de aumento de datos sean más atractivas.
¡Transformers al rescate!
Si decidió leer este artículo, es seguro asumir que está al tanto de los últimos avances en el procesamiento del lenguaje natural legado por los poderosos Transformers. Los desarrolladores excepcionales en Abrazando la cara en particular, han abierto la puerta a este mundo a través de sus contribuciones de código abierto. Uno de sus lanzamientos más recientes implementa un gran avance en el aprendizaje de transferencia llamado Text-to-Text Ttransferir Ttransformador o T5 modelo, presentado originalmente por Raffel et. Alabama. en su papel Explorando los límites del aprendizaje por transferencia con un transformador de texto a texto unificado [2].
T5 nos permite ejecutar varias tareas de PNL especificando prefijos en el texto de entrada. En mi caso, estaba interesado en el resumen abstracto, por lo que utilicé el summarize
prefijo.
Resumen abstracto
El resumen abstracto abstracto es una técnica mediante la cual un fragmento de texto se alimenta a un modelo de PNL y se devuelve un resumen novedoso de ese texto. Esto no debe confundirse con el resumen extractivo, donde las oraciones se integran y se ejecuta un algoritmo de agrupación para encontrar las más cercanas a los centroides de las agrupaciones, es decir, se devuelven las oraciones existentes. El resumen abstracto parecía particularmente atractivo como técnica de aumento de datos debido a su capacidad para generar oraciones de texto novedosas pero realistas.
Algoritmo
Estos son los pasos que tomé para usar el resumen abstracto para el aumento de datos, incluidos los segmentos de código que ilustran la solución.
Primero necesitaba determinar cuántas filas requería cada clase subrepresentada. El número de filas para agregar para cada característica se calcula así con un umbral de techo, y nos referimos a estos como el append_counts
. Las características con recuentos por encima del techo no se adjuntan. En particular, si una característica determinada tiene 1000 filas y el límite máximo es 100, su recuento de anexos será 0. Los siguientes métodos logran esto de manera trivial en la situación en la que las características se han codificado de forma única:
def get_feature_counts(self, df): shape_array = for feature in self.features: shape_array[feature] = df[feature].sum() return shape_array def get_append_counts(self, df): append_counts = feature_counts = self.get_feature_counts(df) for feature in self.features: if feature_counts[feature] >= self.threshold: count = 0 else: count = self.threshold - feature_counts[feature] append_counts[feature] = count return append_counts
Para cada característica, se completa un ciclo desde un rango de índice de anexos hasta el recuento de agregados especificado para esa característica dada. Esta append_index
Se introducen variables junto con una matriz de tareas para permitir el procesamiento múltiple, que discutiremos en breve.
counts = self.get_append_counts(self.df) # Create append dataframe with length of all rows to be appended self.df_append = pd.DataFrame( index=np.arange(sum(counts.values())), columns=self.df.columns ) # Creating array of tasks for multiprocessing tasks = [] # set all feature values to 0 for feature in self.features: self.df_append[feature] = 0 for feature in self.features: num_to_append = counts[feature] for num in range( self.append_index, self.append_index + num_to_append ): tasks.append( self.process_abstractive_summarization(feature, num) ) # Updating index for insertion into shared appended dataframe # to preserve indexing for multiprocessing self.append_index += num_to_append
Un resumen abstracto se calcula para un subconjunto de tamaño específico de todas las filas que tienen únicamente la característica dada, y se agrega al DataFrame adjunto con su característica respectiva codificada en un solo uso.
df_feature = self.df[ (self.df[feature] == 1) & (self.df[self.features].sum(axis=1) == 1) ] df_sample = df_feature.sample(self.num_samples, replace=True) text_to_summarize = ' '.join( df_sample[:self.num_samples]['review_text']) new_text = self.get_abstractive_summarization(text_to_summarize) self.df_append.at[num, 'text'] = new_text self.df_append.at[num, feature] = 1
El Resumen Abstractive en sí se genera de la siguiente manera:
t5_prepared_text = "summarize: " + text_to_summarize if self.device.type == 'cpu': tokenized_text = self.tokenizer.encode( t5_prepared_text, return_tensors=self.return_tensors).to(self.device) else: tokenized_text = self.tokenizer.encode( t5_prepared_text, return_tensors=self.return_tensors) summary_ids = self.model.generate( tokenized_text, num_beams=self.num_beams, no_repeat_ngram_size=self.no_repeat_ngram_size, min_length=self.min_length, max_length=self.max_length, early_stopping=self.early_stopping ) output = self.tokenizer.decode( summary_ids[0], skip_special_tokens=self.skip_special_tokens )
En las pruebas iniciales, las llamadas de resumen al modelo T5 consumieron mucho tiempo, llegando a alcanzar hasta 25 segundos incluso en una instancia de GCP con una NVIDIA Tesla P100. Claramente, esto debe abordarse para que sea una solución viable para el aumento de datos.
Multiprocesamiento
Presenté un multiprocessing
opción, mediante la cual las llamadas a Resumen abstracto se almacenan en una matriz de tareas luego se pasan a una subrutina que ejecuta las llamadas en paralelo utilizando el multiprocesamiento biblioteca. Esto resultó en una disminución exponencial del tiempo de ejecución.
running_tasks = [Process(target=task) for task in tasks] for running_task in running_tasks: running_task.start() for running_task in running_tasks: running_task.join()
Solución simplificada
Para facilitar las cosas a todos, empaqueté esto en una biblioteca llamada absum. La instalación es posible a través de pip:pip install absum
. También se puede descargar directamente desde el repositorio.
Ejecutar el código en su propio conjunto de datos es simplemente una cuestión de importar la biblioteca Augmentor
clase y ejecutando su abs_sum_augment
método de la siguiente manera:
import pandas as pd from absum import Augmentorcsv = 'path_to_csv' df = pd.read_csv(csv) augmentor = Augmentor(df) df_augmented = augmentor.abs_sum_augment() df_augmented.to_csv( csv.replace('.csv', '-augmented.csv'), encoding='utf-8', index=False )
absum usa el modelo Hugging Face T5 de forma predeterminada, pero está diseñado de manera modular para permitirle usar cualquier modelo de Transformer previamente entrenado o listo para usar con capacidad de resumen abstracto. Es independiente del formato, esperando solo un DataFrame que contenga texto y características codificadas en un solo uso. Si hay columnas adicionales presentes que no desea que se consideren, tiene la opción de pasar características específicas codificadas en un solo uso como una cadena separada por comas al features
parámetro.
También de especial interés son los min_length
y max_length
parámetros, que determinan el tamaño de los resúmenes resultantes. Un truco que encontré útil es encontrar el recuento promedio de caracteres de los datos de texto con los que está trabajando y comenzar con algo un poco más bajo para la longitud mínima mientras lo rellena ligeramente para el máximo. Todos los parámetros disponibles se detallan en el documentación.
¡Feliz codificación!
Referencias
[1] F. Chartea, A.Riverab, M. del Jesus, F. Herreraac, MLSMOTE: Aproximación al aprendizaje de múltiples etiquetas desequilibrado mediante la generación de instancias sintéticas (2015), Knowledge-Based Systems, Volumen 89, noviembre de 2015, páginas 385–397
[2] C. Raffel, N. Shazeer, A. Roberts, K. Lee, S. Narang, M. Matena, Y. Zhou, W. Li, P. Liu, Explorando los límites del aprendizaje por transferencia con un transformador de texto a texto unificado, Journal of Machine Learning Research, 21 de junio de 2020.
[3] D. Foster, Python: ¿Cómo puedo ejecutar funciones de Python en paralelo? Obtenido de stackoverflow.com, 27/7/2020.
Añadir comentario