Transformando texto en vectores Enfoque no supervisado de TSDAE para mejorar los embeddings

Transformando texto en vectores Un enfoque no supervisado de TSDAE para mejorar los embeddings

Diseñado por Freepik

Combina el pre-entrenamiento de TSDAE en un dominio objetivo con el afinamiento supervisado en un corpus de propósito general para mejorar la calidad de los embeddings para un dominio especializado.

Introducción

Los embeddings codifican el texto en espacios vectoriales de alta dimensión, utilizando vectores densos para representar palabras y capturar sus relaciones semánticas. Desarrollos recientes en IA generativa y LLM, como búsqueda de contexto y RAG, dependen en gran medida de la calidad de sus embeddings subyacentes. Si bien las búsquedas de similitud utilizan conceptos matemáticos básicos como la similitud del coseno, los métodos utilizados para construir los vectores de embedding influyen significativamente en los resultados posteriores.

En la mayoría de los casos, un transformador de oraciones pre-entrenado funcionará de manera satisfactoria y proporcionará resultados razonables. Existen muchas opciones de embeddings contextuales pre-entrenados basados en BERT, algunos especializados en dominios específicos, que se pueden utilizar en estos casos, y están disponibles para descargar en plataformas como HuggingFace.

Los problemas surgen cuando nos enfrentamos a casos en los que el corpus contiene muchos términos técnicos específicos de un dominio estrecho o proviene de idiomas con pocos recursos. En estos casos, necesitamos abordar las palabras desconocidas que no se vieron durante el pre-entrenamiento o el afinamiento.

Por ejemplo, un modelo pre-entrenado en texto general tendrá dificultades para asignar correctamente vectores a títulos de un corpus de artículos de investigación matemática.

En estos casos, dado que el modelo no estuvo expuesto a las palabras específicas del dominio, le resulta difícil determinar su significado y colocarlas con precisión en el espacio vectorial en relación con otras palabras del corpus. Cuanto mayor es el número de palabras desconocidas, mayor es el impacto y menor es el rendimiento del modelo.

Por lo tanto, un modelo pre-entrenado de manera predeterminada tendrá un rendimiento inferior en estos escenarios, mientras que intentar pre-entrenar un modelo personalizado presenta desafíos debido a la falta de datos etiquetados y la necesidad de recursos computacionales significativos.

Motivación

Este trabajo fue motivado por una investigación reciente [aviation_article] que se centra en el campo de la aviación, cuyos datos tienen características únicas como jerga técnica, abreviaturas y gramática no convencional.

Para abordar la falta de datos etiquetados, los autores utilizaron una de las técnicas no supervisadas más efectivas que permiten el pre-entrenamiento de embeddings (TSDAE), seguido de una etapa de afinamiento que utilizó datos etiquetados de un corpus de propósito general. Los transformadores de oraciones adaptados superan a los transformadores generales, demostrando la efectividad del enfoque para capturar las características de los datos del dominio de la aviación.

Esquema

Adaptación de dominio se trata de adaptar los embeddings de texto a un dominio específico sin necesidad de datos de entrenamiento etiquetados. En este experimento, estoy utilizando un enfoque de dos pasos que, según [tsdae_article], funciona mejor que simplemente entrenar en el dominio objetivo.

Imagen por el autor

En primer lugar, comienzo con el pre-entrenamiento centrado en el dominio objetivo, a menudo llamado pre-entrenamiento adaptativo. Esta fase requiere una colección de oraciones de nuestro conjunto de datos. Empleo TSDAE para esta etapa, un método que sobresale en la adaptación de dominio como tarea de pre-entrenamiento, superando significativamente a otros métodos, incluido el modelo de lenguaje enmascarado, como se enfatiza en [tsdae_article]. Sigo de cerca el guion: train_tsdae_from_file.py.

A continuación, afiné el modelo en el conjunto de datos genérico etiquetado AllNLI, empleando una estrategia de pérdida de clasificación negativa múltiple. Para esta etapa, estoy utilizando el guion de training_nli_v2.py. Como se documenta en [tsdae_article], este paso adicional no solo contrarresta el sobreajuste, sino que también mejora significativamente el rendimiento del modelo.

TSDAE – Pre-Training en el dominio objetivo

TSDAE (autoencoder de eliminación secuencial basado en transformadores) es un método de incrustación de oraciones no supervisado, que fue presentado por K. Wang, N. Reimers e I. Gurevych en [tsdae_article].

TSDAE utiliza un diseño modificado de codificador-decodificador de transformadores donde la clave y el valor de la atención cruzada están limitados a la incrustación de las oraciones. Voy a describir los detalles en el contexto de las elecciones de arquitectura óptimas destacadas en el artículo original [tsdae_article].

Imagen del autor
  • El conjunto de datos consiste en oraciones sin etiquetas, que durante el preprocesamiento se corrompen eliminando el 60% de su contenido para introducir ruido en la entrada.
  • El Codificador transforma las oraciones corrompidas en vectores de tamaño fijo al agrupar sus incrustaciones de palabras. Según [tsdae_article], se recomienda utilizar el método de agrupación CLS para extraer el vector de la oración.
  • El Decodificador tiene como objetivo reconstruir la oración de entrada original a partir de la incrustación de la oración dañada. Los autores aconsejan atar los parámetros del Codificador y el Decodificador durante el entrenamiento para reducir el número de parámetros en el modelo, lo que facilita el entrenamiento y reduce la tendencia al sobreajuste sin afectar el rendimiento.

Para lograr una buena calidad de reconstrucción, la incrustación de la oración del Codificador debe capturar de manera óptima la semántica. Para el Codificador se utiliza un transformador preentrenado como bert-base-uncased, mientras que los pesos del Decodificador se copian de él.

El mecanismo de atención del Decodificador está restringido a la representación de la oración producida por el Codificador. Esta modificación de la arquitectura original del codificador-decodificador de transformadores limita la información que el Decodificador obtiene del Codificador e introduce un cuello de botella que obliga al Codificador a producir representaciones de oraciones significativas.

En la inferencia, solo se utiliza el Codificador para crear las incrustaciones de las oraciones.

El modelo se entrena para reconstruir la oración limpia a partir de la oración corrompida y esto se logra maximizando el objetivo:

Imagen del autor

AllNLI – Conjunto de datos de Inferencia de Lenguaje Natural

La Inferencia de Lenguaje Natural (NLI) determina la relación entre dos oraciones. Categoriza la veracidad de la hipótesis (segunda oración) como inferencia (verdadera basada en la premisa), contradicción (falsa basada en la premisa) o neutral (ni garantizada ni contradicha por la premisa). Los conjuntos de datos de NLI son conjuntos de datos etiquetados donde se anotan pares de oraciones con su clase de relación.

Para este experimento, utilizo el conjunto de datos AllNLI que contiene una colección de más de 900,000 registros, provenientes de los conjuntos de datos Stanford Natural Language Inference (SNLI) y MultiNLI combinados. Este conjunto de datos se puede descargar desde: Sitio de descarga de AllNLI.

Cargar y preparar los datos de preentrenamiento

Para construir nuestros datos específicos del dominio, estamos utilizando el conjunto de datos Kaggle arXiv, compuesto aproximadamente por 1.7 millones de artículos científicos STEM obtenidos de la plataforma de preimpresión electrónica establecida, arXiv. Aparte del título, resumen y autores, cada artículo tiene una cantidad significativa de metadatos asociados. Sin embargo, aquí nos interesa solo los títulos.

Después de descargar, seleccionaremos los preimpresos de matemáticas. Debido al tamaño considerable del archivo Kaggle, he agregado una versión reducida del archivo de documentos de matemáticas a Github para facilitar el acceso. Sin embargo, si estás interesado en otro tema, descarga el conjunto de datos y reemplaza math con el tema deseado en el código a continuación:

# Recopilar los artículos con tema "math"def extract_entries_with_math(filename: str) -> List[str]:    """    Función para extraer las entradas que contienen la cadena 'math' en el 'id'.    """    # Inicializar una lista vacía para almacenar las entradas extraídas.    entries_with_math = []    with open(filename, 'r') as f:        for line in f:            try:                # Cargar el objeto JSON desde la línea                data = json.loads(line)                # Verificar si la clave "id" existe y si contiene "math"                if "id" in data and "math" in data["id"]:                    entries_with_math.append(data)            except json.JSONDecodeError:                # Imprimir un mensaje de error si esta línea no es JSON válido                print(f"No se pudo analizar: {line}")    return entries_with_math# Extraer los artículos de matemáticasentradas = extract_entries_with_math(arxiv_full_dataset)# Guardar el conjunto de datos como un objeto JSONarxiv_dataset_math = file_path + "/data/arxiv_math_dataset.json"with open(arxiv_dataset_math, 'w') as fout:    json.dump(entradas, fout)

He cargado nuestro conjunto de datos en un dataframe de Pandas df. Una rápida inspección muestra que el conjunto de datos reducido contiene 55,497 preimpresiones, un tamaño más práctico para nuestro experimento. Si bien el artículo tsdae_article sugiere que alrededor de 10,000 entradas son adecuadas, mantendré todo el conjunto de datos reducido. Los títulos de matemáticas podrían tener código LaTeX, que cambiaré por código ISO para optimizar el procesamiento.

títulos_analizados = []for i,a in df.iterrows():    """    Función para reemplazar el código LaTeX con código ISO.    """    try:        títulos_analizados.append(LatexNodes2Text().latex_to_text(a['título']).replace('\\n', ' ').strip())     except:        títulos_analizados.append(a['título'].replace('\\n', ' ').strip())# Crea una nueva columna con los títulos analizadosdf['título_analizado'] = títulos_analizados

Utilizaré las entradas del título_analizado para entrenamiento, así que vamos a extraerlas como una lista:

# Extrae los títulos analizados como una listatrain_sentences = df.título_analizado.to_list()

A continuación, vamos a formar las frases corruptas eliminando aproximadamente el 60% de los tokens de cada entrada. Si estás interesado en explorar más o probar diferentes proporciones de eliminación, revisa el script de desruido.

# Agrega ruido al conjunto de datostrain_dataset = datasets.DenoisingAutoEncoderDataset(train_sentences)

Echemos un vistazo a lo que le sucedió a una entrada después del procesamiento:

print(train_dataset[2010])texto inicial: "Sobre soluciones de las ecuaciones de Bethe para el modelo XXZ"texto corrupto: "Sobre soluciones de para el modelo XXZ"

Como puedes ver, se eliminaron las palabras ecuaciones de Bethe y modelo del texto original.

El último paso en nuestro procesamiento de datos es cargar el conjunto de datos en lotes:

train_dataloader = DataLoader(train_dataset, batch_size=8,                               shuffle=True, drop_last=True)

Entrenamiento TSDAE

Mientras seguiré el enfoque del archivo rain_tsdae_from_file.py, lo construiré paso a paso para una mejor comprensión.

Comencemos seleccionando un punto de control de transformador pre-entrenado y mantengamos la opción predeterminada:

model_name = 'bert-base-uncased'modelo_embedding_palabra = models.Transformer(model_name)

Elige CLS como el método de agrupamiento y especifica la dimensión de los vectores a construir:

modelo_agrupamiento = models.Pooling(modelo_embedding_palabra.get_word_embedding_dimension(),                               "cls")                                            'cls')

A continuación, construye el transformador de oraciones combinando las dos capas:

modelo = SentenceTransformer(modules=[modelo_embedding_palabra,                            modelo_agrupamiento])                                                  modelo_agrupamiento])

Por último, especifica la función de pérdida y ata los parámetros del encoder-decoder para la fase de entrenamiento.

pérdida_entrenamiento = losses.DenoisingAutoEncoderLoss(modelo,                                             nambre_decoder_o_ruta=model_name,                                             atar_encoder_decoder=True)

Ahora, estamos listos para invocar el método fit y entrenar el modelo. También lo guardaré para los pasos siguientes. Eres libre de ajustar los hiperparámetros para optimizar tu experimento.

modelo.fit(    objetos_entrenamiento=[(train_dataloader, pérdida_entrenamiento)],    epochs=1,    decaimiento_peso=0,    programador='constantlr',    parámetros_optimizador={'lr': 3e-5},    mostrar_barra_progreso=True,    usar_amp=True # establecer en False si la GPU no admite núcleos FP16)ruta_guardado_modelo_preentrenado = 'output/tsdae-bert-uncased-math'modelo.guardar(ruta_guardado_modelo_preentrenado)

La etapa de pre-entrenamiento tomó alrededor de 15 minutos en una instancia de Google Colab Pro con GPU A100 configurada en Alta RAM.

Ajuste fino en el conjunto de datos AllNLI

Comencemos descargando el conjunto de datos AllNLI:

ruta_conjunto_datos_nli = 'data/AllNLI.tsv.gz'if not os.path.exists(ruta_conjunto_datos_nli):    util.http_get('<https://sbert.net/datasets/AllNLI.tsv.gz>',                   ruta_conjunto_datos_nli)

A continuación, descomprima el archivo y analice los datos para el entrenamiento:

def add_to_samples(sent1, sent2, label):    if sent1 not in train_data:        train_data[sent1] = {'contradiction': set(),                             'entailment': set(),                              'neutral': set()}                                                       'entailment': set                                               'neutral': set()}    train_data[sent1][label].add(sent2)train_data = {}with gzip.open(nli_dataset_path, 'rt', encoding='utf8') as fIn:    reader = csv.DictReader(fIn, delimiter='\\t',                             quoting=csv.QUOTE_NONE)    for row in reader:        if row['split'] == 'train':            sent1 = row['sentence1'].strip()            sent2 = row['sentence2'].strip()                        add_to_samples(sent1, sent2, row['label'])            add_to_samples(sent2, sent1, row['label'])  # También agregar el opuestotrain_samples = []for sent1, others in train_data.items():    if len(others['entailment']) > 0 and len(others['contradiction']) > 0:        train_samples.append(InputExample(texts=[sent1,                      random.choice(list(others['entailment'])),                      random.choice(list(others['contradiction']))]))        train_samples.append(InputExample(texts=[random.choice(list(others['entailment'])),                      sent1,                      random.choice(list(others['contradiction']))]))                                                            random.choice(list(others['contradiction']))]))

El conjunto de datos de entrenamiento tiene aproximadamente 563K muestras de entrenamiento. Finalmente, utilice un cargador especial que carga los datos por lotes y evita duplicados dentro de un lote:

train_dataloader = datasets.NoDuplicatesDataLoader(train_samples,                                                   batch_size=32)

El tamaño del lote que uso aquí es más pequeño que el tamaño predeterminado de 128 del script. Aunque un lote más grande daría mejores resultados, requeriría más memoria de GPU y, dado que estoy limitado por mis recursos computacionales, elijo un tamaño de lote más pequeño.

Finalmente, ajuste finamente el modelo preentrenado en el conjunto de datos AllNLI utilizando MultipleRankingLoss. Los pares de implicación son positivos y los pares de contradicción son negativos difíciles.

# Establecer los parámetros del modelonombre_del_modelo = 'output/tsdae-bert-uncased-math'tamaño_del_lote_de_entrenamiento = 32longitud_maxima_de_seq = 75num_epochs = 1# Cargar el modelo preentrenadolocal_model = SentenceTransformer(model_name)# Elegir la función de pérdidatrain_loss = losses.MultipleNegativesRankingLoss(local_model)# Usar el 10% de los datos de entrenamiento para el calentamientowarmup_steps = math.ceil(len(train_dataloader) * num_epochs * 0.1)# Entrenar el modelolocal_model.fit(train_objectives=[(train_dataloader, train_loss)],          #evaluator=dev_evaluator,          epochs=num_epochs,          #evaluation_steps=int(len(train_dataloader)*0.1),          warmup_steps=warmup_steps,          output_path=model_save_path,          use_amp=True  # Establecer en True si su GPU admite operaciones FP16          )# Guardar el modeloRuta_de_guardado_del_modelo_entrenado = 'output/finetuned-bert-uncased-math'local_model.save(finetuned_model_save_path)

Ajusté finamente el modelo en todo el conjunto de datos de 500K y esto llevó aproximadamente 40 minutos en Google Colab Pro, para 1 época con un tamaño de lote de 32.

Evaluar el modelo preentrenado de TSDAE y el modelo ajustado finamente

Realizaré una evaluación preliminar en el conjunto de datos STS (similitud textual semántica) de HuggingFace, utilizando el EmbeddingSimilarityEvaluator, que devuelve la correlación de rango de Spearman. Sin embargo, estas evaluaciones no emplean el dominio específico en el que me estoy enfocando, lo que potencialmente no muestra el rendimiento real del modelo. Para más detalles, consulte la Sección 4 en [tsdae_article].

Comenzaré descargando el conjunto de datos de HuggingFace y especificando el subconjunto de validación:

import datasets as dtsfrom datasets import load_dataset# Importar el conjunto de datos de referencia STS de HuggingFacests = dts.load_dataset('glue', 'stsb', split='validation')

Esto es un objeto de conjunto de datos de la forma:

Dataset({    features: ['sentence1', 'sentence2', 'label', 'idx'],    num_rows: 1379})

Para comprender mejor esto, echemos un vistazo a una entrada específica

# Echemos un vistazo a una de las entradassts['idx'][100], sts['sentence1'][100], sts['sentence2'][100], sts['label'][100]>>>(100, 'Una mujer está montando a caballo.', 'Un hombre está volcando mesas con ira.', 0.0)

Como podemos ver en este ejemplo, cada entrada tiene 4 características, una es el índice, dos frases y una etiqueta (que fue creada por un anotador humano). La etiqueta puede tomar valores entre 0 y 5 y mide el nivel de similitud de las dos frases (siendo 5 la más similar). En este ejemplo, las dos frases son sobre temas completamente diferentes.

Para evaluar el modelo, se crean las representaciones de las frases para los pares de frases, y se calcula el puntaje de similitud del coseno para cada par. La correlación de rango de Spearman entre las etiquetas y los puntajes de similitud se calcula como puntaje de evaluación.

Dado que utilizaré la similitud del coseno que toma valores entre 0 y 1, tengo que normalizar las etiquetas:

# Normalizar el rango [0, 5] a [0, 1]sts = sts.map(lambda x: {'label': x['label'] / 5.0})

Envuelva los datos en la clase InputExample de HuggingFace:

# Cree una lista para almacenar los datos procesadossamples = []for sample in sts:    # Reformatear para usar la clase InputExample    samples.append(InputExample(        texts=[sample['sentence1'], sample['sentence2']],        label=sample['label']    ))

Cree el evaluador basado en la clase EmbeddingSimilarityEvaluator en la biblioteca sentence-transformers.

# Instanciar el módulo de evaluaciónevaluator = EmbeddingSimilarityEvaluator.from_input_examples(samples)

Calculamos los puntajes para el modelo TSDAE, para el modelo afinado y para un par de sentence transformers pre-entrenados:

Imagen del autor

Por lo tanto, en un conjunto de datos de alcance general, algunos modelos pre-entrenados, como all-mpnet-base-v2, superan al modelo afinado de TSDAE. Sin embargo, con el pre-entrenamiento, el rendimiento del modelo inicial bert-base-uncased se duplica. Es concebible que se puedan obtener mejores resultados ajustando aún más los hiperparámetros para el ajuste fino.

Conclusión

Para dominios con recursos limitados, TSDAE en conjunto con el ajuste fino es una estrategia bastante eficiente para construir representaciones. Los resultados obtenidos aquí son destacables, dada la cantidad de datos y los medios computacionales. Sin embargo, para conjuntos de datos que no son particularmente inusuales o específicos del dominio, teniendo en cuenta la eficiencia y el costo, puede ser preferible elegir una representación pre-entrenada que pueda proporcionar un rendimiento comparable.

Enlace a GitHub al cuaderno Colab y conjunto de datos de muestra.

Y así, mis amigos, ¡siempre debemos abrazar lo bueno, lo malo y lo desordenado en nuestro viaje de aprendizaje!

Referencias

[tsdae_article]. K. Wang, et al., TSDAE: Utilizando un Autoencoder Denoising Secuencial basado en Transformers para el Aprendizaje de Representaciones Sin Supervisión de Frases (2021) arXiv:2104.06979

[aviation_article]. L. Wang, et al., Adaptando Sentence Transformers para el Dominio de la Aviación (2023) arXiv:2305.09556

We will continue to update Zepes; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

Inteligencia Artificial

La colaboración multi-AI ayuda al razonamiento y la precisión factual en modelos de lenguaje grandes.

Los investigadores utilizan múltiples modelos de IA para colaborar, debatir y mejorar sus habilidades de razonamiento...

Inteligencia Artificial

El 40% de la fuerza laboral se verá afectada por la IA en 3 años

¿Qué debemos esperar en los próximos 3 años debido al auge de la inteligencia artificial generativa?

Inteligencia Artificial

Top 40+ Herramientas de IA Generativa (Septiembre 2023)

ChatGPT – GPT-4 GPT-4 es el último LLM de OpenAI, que es más inventivo, preciso y seguro que sus predecesores. Tambié...

Inteligencia Artificial

¿Puede (Muy) Simple Matemáticas Informar RLHF Para Modelos de Lenguaje Grandes LLMs? ¡Este artículo de IA dice que sí!

Incorporar la entrada humana es un componente clave de las recientes mejoras impresionantes en las capacidades de los...

Inteligencia Artificial

Este artículo de IA presenta un estudio sobre las pruebas de AIS (Síndrome de Insensibilidad a los Andrógenos) utilizando modelos de aprendizaje profundo

AIS significa Síndrome de Insensibilidad a los Andrógenos. AIS es un problema cerebral espinal que afecta a la genera...