Cross-Encoder para mejorar RAG en ruso / Sudo Null IT News

Contenido

  1. RAG (generación aumentada de recuperación)

  2. Bicodificador VS codificador cruzado

  3. Resumen de codificadores cruzados disponibles

  4. Arquitectura de codificador cruzado (cabezal de clasificación)

  5. Datos de entrenamiento

  6. Proceso de aprendizaje

  7. Los resultados del aprendizaje

  8. conclusiones

1. RAG (generación aumentada de recuperación)

RAG se utiliza con frecuencia y le permite ampliar el conocimiento de LLM (Large Language Model) con toneladas de documentos y otras fuentes de texto. Simplemente insertamos partes relevantes del texto para que LLM pueda desarrollarlas. ¿Cómo encontrar estas partes relevantes del texto? O mejor dicho, ¿cómo elegir los más relevantes?

Normalmente, para ello se obtiene un vector para cada parte del texto y por separado para la pregunta. Y luego seleccionan las partes del texto que están más cercanas a la pregunta (distancia coseno entre los vectores). En general, esto funciona bien, aunque no es perfecto.

Seleccionar un modelo para crear vectores (= incrustaciones)

La elección de un modelo que generará vectores suele ser así para mí:

Miremos a codificar, allí encontramos el mejor modelo para STS (similitud textual semántica). Descárgalo desde abrazando cara y aplicarlo.

Puedes ir directamente a abrazando cara Vaya, está la sección de “extracción de características” (básicamente se trata de obtener vectores para texto).

A veces puedes mirar transformadores de oraciones. Pero en realidad rara vez utilicé este marco.

Mi práctica ha demostrado que los modelos óptimos son:

  1. Lo mejor, pero un poco pesado, más de 2 GB. El ruso lo sabe. Este es e5-grande-v2.

  2. Buena calidad, tamaño pequeño – LaBSE-en-ru. Esta suele ser la mejor opción.

  3. Si necesita uno pequeño y muy rápido: rubert-tiny2 (un poco más de 100 MB).

Al calcular además la distancia del coseno, esto no suele causar problemas.

Pero el hecho es que existe otra forma de seleccionar las partes más relevantes del texto para RAG. A menudo escuché codificadores cruzados (Cross-Encoder). Pero no llegaron a profundizar en ello en detalle. Pero ahora se ha reunido la cantidad necesaria de materiales. En este artículo los compartiré.


2. Bicodificador VS codificador cruzado

Bicodificador

Hacemos el vector de la pregunta y el vector de cada parte del texto por separado. Contamos las distancias y elegimos la más pequeña. Este es un bicodificador. Se llama así porque para calcular la distancia hacemos dos (bi) vectores y luego calculamos la distancia coseno entre ellos. Por cierto, puedes usar otras distancias, pero son bastante exóticas (por ejemplo, “distancia entre cuadras de la ciudad”). Lógicamente se parece a esto:

# на входе вопрос и куча текстов (у нас для упрощения два)
question = 'Где зимуют раки?'
text_chunk_1 = 'Машина была зеленая'
text_chunk_2 = 'Раки впадают в спячку, в озерах'

# делаем эмбединги (они же вектора)
# в данном случае их будет ровно три
emb_question = make_emb(question)
emb_text_chunk_1 = make_emb(text_chunk_1)
emb_text_chunk_2 = make_emb(text_chunk_2)

# считаем расстояние между эмбединнгом вопроса и эмбеддингом каждого текста
# цифры условные
# используем sklearn.metrics.pairwise_distances
dist_text_chunk_1 = 0.41
dist_text_chunk_2 = 0.13

# вообщем всё
# наиболее релевантный текст - где меньше расстояние, конечно
final_result_goes_to_LLM = text_chunk_2

Codificador cruzado

¿Qué pasa si introduces una pregunta y una respuesta en el modelo a la vez? En general, esto es más fácil para el modelo, ve ambas partes al mismo tiempo y debe decirnos qué tan probable es que se trate de una pregunta y respuesta. Para los modelos tipo Bert es similar a – NSP (próxima oración de predicción). Esta tarea se utilizó incluso en la etapa previa al entrenamiento de dichos modelos. Aquellos. La calidad del resultado debería ser mejor.

Sin embargo, la velocidad de funcionamiento es mucho más lenta. Introducimos todos los pares en la red neuronal y determinamos la probabilidad de cada par por separado.

Si tenemos 10 preguntas y 20 textos, entonces el bicodificador necesitará hacer 30 piezas. (10+20) lanzamientos de redes neuronales y 200 lanzamientos de codificadores cruzados. (10*20). No tenemos en cuenta el cálculo de la distancia entre vectores para el bi-codificador, se realiza rápidamente.

En general funciona así:

# на входе вопрос и куча текстов - тоже самое, что и выше
question = 'Где зимуют раки?'
text_chunk_1 = 'Машина была зеленая'
text_chunk_2 = 'Раки впадают в спячку, в озерах'

# одновременно грузим в модель пару "вопрос-ответ". Таких пар у нас две
# считаем вероятность того, что это логичная пара
# идея в том, что качество кросс-энкодера должно быть лучше
# цифры условные
score_question_text_chunk_1 = 0.12
score_question_text_chunk_2 = 0.91

# наиболее релевантный текст - где больше вероятность
final_result_goes_to_LLM = text_chunk_2

¡Excelente! En algunas tareas, puedes sacrificar la velocidad, pero intenta proporcionar un buen aumento de calidad.

Por supuesto, el codificador cruzado no clasifica un millón de textos, porque… será muy lento. Sin embargo, puede funcionar como una segunda etapa de clasificación. Y en la primera etapa, puede utilizar un bicodificador rápido, reduciendo los espacios de selección a decenas/cientos de textos. Ejemplo de dicho código aquí. El enfoque por etapas muestra su eficacia, porque Puede resultar difícil hacer todo con una sola herramienta (“de un extremo a otro”).

¿Dónde puedo conseguir un codificador cruzado? Para que la licencia comercial, el idioma ruso esté listo para funcionar inmediatamente sin terminar.

“En nuestra época de redes neuronales, definitivamente debe haber algún tipo de opción”, pensé y comencé a buscar.


3. Descripción general de codificadores cruzados disponibles

Técnicamente, cualquier modelo de red neuronal tipo Bert puede hacer esto. A continuación se muestra un pseudocódigo de muestra:

# на вход две строки (в нашем случае это пара: вопрос-ответ)
# хотим понять, насколько потенциальный ответ релевантен вопросу
question = 'Где зимуют раки?'
text_chunk_1 = 'Машина была зеленая'

# токенизируем и добавляем спец. токены
tokenized_pair = (CLS, Где, зимуют, раки, SEP, Машина, была, зеленая, SEP)

# подаем в модель
# модель нам возвращает вероятность релевантности ответа вопросу
pair_probability = bert(tokenized_pair) 
pair_probability
# -> 0.013% низкая, т.к. ответ явно не связан с вопросом

Entonces comenzó la búsqueda de ese modelo. codificar contiene puntos de referencia para bicodificadores. Esto no encaja.

Quería abrazando cara y transformadores de frase. En resumen, no encontré nada que se adaptara ni un poco a mis necesidades. En ruso, muchos modelos funcionan mucho peor. Pero no pude encontrar un codificador cruzado que pudiera entrenarse para un ruso. Toda mi decepción está en el siguiente código (por cierto, el código es real, puedes comprobarlo):

from transformers import AutoTokenizer, AutoModelForSequenceClassification
check_point="cross-encoder/ms-marco-MiniLM-L-12-v2"
model = AutoModelForSequenceClassification.from_pretrained(check_point)
tokenizer = AutoTokenizer.from_pretrained(check_point)
questions = ('где можно взять лодку на прокат?', 
             'где можно взять лодку на прокат?')
answers = ('взятие тракторов для пользования не предусмотрено', 
            'небольшой катер обычно берут в аренду у берега')
features = tokenizer(questions, answers, 
                     padding=True, truncation=True, return_tensors="pt")
scores = model(**features).logits
print(scores.tolist())

# ((7.5914), 
#  (7.1993))

# что это вообще такое!?)
# либо я что-то не понимаю, либо модель делает абсолютно неверный выбор
# первая пара ей кажется более логичной (вопрос про лодку, а ответ про трактор)

# вообще как-то странно сделано
# получается, что решается задача регрессии, а не классификации...
# т.е. получить степень уверенности модели нельзя, только сортировку
model.num_labels
# 1

Poco a poco llegué a la conclusión de que necesitaba tomar el modelo tipo Bert como base y luego perfeccionarlo y entrenarlo yo mismo.

También inspirado en el modelo Sberbank. ai-lab/ESGify_ru (enlace). Allí se resolvió un problema diferente, pero estaba claramente claro que usted mismo podía agregar modelos a los modelos (hicieron un clasificador sólido para la clasificación de múltiples etiquetas, 48 ​​clases de riesgo ESG).


4. Arquitectura de codificador cruzado (cabezal de clasificación)

Primero te mostraré el código del modelo:

class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.labse  = AutoModel.from_pretrained('cointegrated/LaBSE-en-ru')
        self.tokenizer = AutoTokenizer.from_pretrained('cointegrated/LaBSE-en-ru', 
                                                       use_fast=True)
        n_dim = 768
        self.cls = nn.Sequential(OrderedDict(
              (
                ('dropout', torch.nn.Dropout(.0)),

                ('fc_1' , nn.Linear(n_dim, n_dim*2)),
                ('relu_1' , nn.GELU()),
                ('layernorm_1' , nn.LayerNorm(n_dim*2, eps=1e-12)),

                ('fc_2' , nn.Linear(n_dim*2, n_dim)),
                ('relu_2' , nn.GELU()),
                ('layernorm_2' , nn.LayerNorm(n_dim, eps=1e-12)),

                ('fc_3' , nn.Linear(n_dim, 2, bias=False)),
              )
))
    def forward(self, text):
        token = self.tokenizer(text, padding=True, 
                               truncation=True, return_tensors="pt").to(device)
        model_output = self.labse(**token)
        result = self.cls(model_output.pooler_output)
        return result

En general, la idea es simple: tomamos cualquier modelo tipo Bert y colgamos una cabeza encima en forma de clasificador binario.

Esperamos una respuesta binaria de la red neuronal a la pregunta: “¿es lógico que este texto pueda servir como respuesta a esta pregunta? (sí/no)”. Aunque, por supuesto, el modelo da la respuesta en forma de números (logits en bruto). Luego los convertimos en probabilidades (usando la función softmax). Y luego simplemente tomamos la probabilidad más alta de las dos (ésta es la naturaleza binaria de la respuesta).

Un buen vídeo sobre el entrenamiento de codificadores bi y cruzados. aquí.

A continuación se muestran las principales bifurcaciones que encontré en el proceso de construcción de la arquitectura del cabezal de clasificación del modelo (ya que el resto del modelo ya fue diseñado por Google). Tomé algunas decisiones basadas en la intuición y el sentido común.

¿Qué modelo debo tomar como modelo base?

Probé dos modelos rubert-tiny2 y LaBSE-en-ru (dalee simplemente labse). Ambos son buenos en ruso. Ruberty más rápido. Ambos con licencia comercial (para declarado observó aquípara rubert aquí). Ganador según su capacidad para recibir capacitación adicional y su impresión general declarado. me pregunto que tengo declarado a menudo como modelo predeterminado para cualquier tarea de vectorización de texto.

Por cierto, rubert Los modelos son mucho más grandes. contexto – 2048 fichas. Para RAG este es un parámetro bastante importante. Ud. declarado – 512 fichas. Cada ficha contiene aproximadamente de 2 a 4 letras rusas (ver visualmente tokenización aquí). Nuestro declarado recortará el texto entrante si supera los 512 tokens. Es bastante desagradable. Es poco probable que el texto de la pregunta sea extenso, pero el texto de la respuesta bien puede serlo. Y aquí podemos perder información (

Al final elegí LaBSE-en-ru (labse) como modelo base.

¿Qué incrustación del modelo debo usar?

En términos generales hay varias opciones:

  • incrustar ficha CLS

  • La salida del pooler es una proyección lineal de la incrustación del token CLS.

  • promediar las incrustaciones de todos los tokens de frases (generalmente significa agrupación)

Lo pensé durante mucho tiempo y cambié de opinión varias veces. Me decidí por la opción de salida del pooler.

En primer lugar, la salida del pooler se entrenó junto con el modelo en sí, solo para la tarea de clasificar la frase completa (para nosotros, una frase es un par de preguntas y respuestas). Aquellos. Para esto se diseñó la salida del pooler.

En segundo lugar, esto se indica en el ejemplo de Huggingface en la tarjeta modelo: incrustaciones = model_output.pooler_output. Creo que esto no es una coincidencia.

Técnicamente no hay diferencia, las tres opciones son vectores con una dimensión de 768 (en términos simples, 768 números float32).

¿Necesita abandonos (torch.nn.Dropout)?

Los abandonos son capas del modelo que ponen a cero un cierto porcentaje de los números. Esto motiva al modelo a buscar diferentes patrones en lugar de simplemente memorizar los datos.

en el modelo ai-lab/ESGify_ru los abandonos se realizan antes de la última capa. Intuitivamente, parece demasiado difícil para el modelo restablecer aleatoriamente algunos de los números a cero justo antes del final. Por eso inserté el abandono antes de la primera capa de mi clasificador. Y aun así, no lo encendí inmediatamente, sino sólo al final del entrenamiento. Aunque en el propio modelo declarado Los abandonos están presentes e incluidos inicialmente en diferentes lugares del modelo.

¿Cuántas y qué capas se necesitan?

Por supuesto, se necesitan capas lineales. También se les llama completamente conectados (nn.lineales).

Después de todo, cuantas más capas, mejor, ¿verdad?)

Después de todo, cuantas más capas, mejor, ¿verdad?)

en los modelos ESGify_ru Había 2 capas en el clasificador. Lo pensé y decidí hacer 3 capas.

También estaba la cuestión del tamaño de las capas. El modelo ESGify_ru fórmula: 768 -> 512 -> 48 (clasificación multietiqueta 48 etiquetas al final). Aquellos. Se puede observar que hay una reducción constante en el tamaño de la capa.

Decidí hacerlo de manera diferente: 768 -> 768*2 -> 768 -> 2 (tenemos una clasificación binaria al final). Le doy al modelo espacio para pensar. Inspirado MLP capa de nanogpt Andrej Karpathy (enlace). En realidad, allí crearon un decodificador de transformador, pero decidí arriesgarme 🙂

Y también eliminé el sesgo (desplazamiento) de la tercera capa final del clasificador. Parece lógico. Es extraño, pero el modelo. ESGify_ru no hice eso (enlace). Quizás no sé algo…

LayerNorm VS BatchNorm

Se dice que la idea de normalización entre lotes funciona bien (video). Pero la idea de mezclar datos entre diferentes ejemplos en un lote es extraña (cálculo significar y enfermedad de transmisión sexual toma datos de diferentes ejemplos dentro de un lote). Por eso hice LayerNorm.

La esencia de estas capas es la normalización de datos. Tomamos una serie de números, restamos la media, dividimos por std y luego, usando los parámetros entrenados de la red neuronal, cambiamos y escalamos (esencialmente restauramos lo que se eliminó anteriormente, multiplicamos por el nuevo std, más la nueva media). ). Pero ahora estos son parámetros entrenables.

Lo principal es entender en qué capas del “cubo de datos” sucede esto. Claramente visible aquí. LayerNorm calcula las estadísticas de cada token en cada lote por separado. Lo cual me queda más claro.

No linealidad

Una opción beneficiosa para todos es ReLU. Yo no lo hice) Convertir todos los valores negativos a cero parece de mala educación, aunque es rápido. Decidí probar GELU con la esperanza de que el clasificador fuera muy inteligente. Esta es una decisión controvertida.

También existen funciones de activación para sigmoide, tangente hiperbólica, CeLU. Algunos de ellos normalizan inmediatamente los datos, por ejemplo, el sigmoide transforma cualquier número en el intervalo de 0 a 1. Pero allí pueden comenzar a surgir problemas con un gradiente decreciente. Después de todo, tenemos 12 capas de codificador de transformador, una capa de agrupación y 3 capas más en el clasificador. Ya es una red seria.

Inicialización

Casi toda la red neuronal se inicializa con pesos de declarado. Están bien entrenados.

Pero todas las capas de mi clasificador se inicializan con números aleatorios, utilizando los métodos predeterminados de la biblioteca de la antorcha. No me gustó el enfoque en el que, de forma predeterminada, el sesgo de las capas lineales se inicializa NO a cero. Puedes ver la documentación para más detalles. antorcha. Aún así decidí cancelarlos, está más tranquilo:

# in-place реинициализация слоев модели в моем классификаторе
# третий слой (fc_3) этого не требует, т.к. там bias=False
torch.nn.init.zeros_(base_model.cls.fc_1.bias)
torch.nn.init.zeros_(base_model.cls.fc_2.bias)

Otro

Eps (épsilon) en LayerNorm (número pequeño para proteger contra la división por cero) – configurado como en las capas del codificador del modelo base – declarado, por la unificación. Es más pequeño y tendrá un efecto más débil en el resultado, que es lo que se requiere (por defecto en torch = 1e-5, en labse = 1e-12).


5. Datos de entrenamiento

En términos de datos, idealmente quería algo como esto:

  • Los datos deben estar en idioma ruso

  • No debería ser necesario el preprocesamiento de datos (limpieza, transformación)

  • Los datos ya deben estar etiquetados.

  • Los datos deben ser lo más generales posible (dominio general), porque Nuestro modelo servirá como modelo base para una mayor formación para tareas específicas.

  • Debe haber muchos datos

Si necesita un codificador cruzado en inglés, puede encontrarlo fácilmente.

Formatos de preguntas y partes de texto.

En el proceso RAG evaluamos la relevancia de dos objetos: pregunta y parte del texto.

Generalmente hablando, pregunta del usuario en RAG puede tener la forma:

  • solo una pregunta, esto es un clásico (“¿dónde pasan el invierno los cangrejos de río?”)

  • palabras clave, etiquetas, un conjunto de palabras relevantes (“cangrejos de río, invernantes, crustáceos”)

  • resumen del texto (es decir, la pregunta es un resumen del texto)

parte del texto Por lo general, solo contiene una gran cantidad de texto diferente. Aquí todo es relativamente sencillo.

En teoría, para conseguir un buen codificador cruzado es necesario entrenarlo en los tres tipos de preguntas. Sólo pude encontrar datos adecuados para el caso de “preguntas justas”. El resto es para el desarrollo.

Fuentes de datos

  • Puede tomar conjuntos de datos ya preparados en ruso, de formato aproximadamente adecuado.

  • Puede generar un conjunto de datos sintéticos a partir de un modelo de lenguaje (la llamada destilación de conocimiento)

  • Puede traducir conjuntos de datos en inglés (normalmente hay más y la calidad original es mejor). Sin embargo, la traducción al ruso reducirá la calidad y no permitirá tener en cuenta las particularidades del idioma ruso.

  • Puede entrenar con conjuntos de datos en inglés con la esperanza de transferir el aprendizaje, es decir, que la red neuronal también aprenderá algo en ruso. En general, la idea es interesante, porque… declarado inicialmente multilingüe (la semántica de cualquier idioma se traduce a un único espacio vectorial). Este enfoque puede funcionar.

Por ahora he elegido el camino más sencillo. Tome conjuntos de datos ya preparados. Esto le permitirá obtener resultados rápidamente y evaluar la viabilidad del enfoque (al estilo MVP). Además será posible ampliarlo y complicarlo.

from datasets import load_dataset

dataset_1 = load_dataset("RussianNLP/russian_super_glue", name="danetqa")
dataset_2 = load_dataset("RussianNLP/russian_super_glue", name="muserc")
dataset_3 = load_dataset("xquad", 'xquad.ru')

# load_dataset("sberquad") - тут была проблема со скачиванием, его не использовал

Tomé tres conjuntos de datos y los llevé a un formato de dos columnas (pregunta-respuesta). Además, todas las líneas son ejemplos positivos. Aquellos. cada línea refleja un par lógico.

Pero ¿qué pasa con los ejemplos negativos? Simplemente tomé combinaciones aleatorias de preguntas y respuestas de la tabla (de diferentes filas). Puede haber errores aquí si accidentalmente selecciono una pregunta y respondo de la misma línea. En la tabla combinada resultó ser aproximadamente plazo de 10k, Eso. La probabilidad de ejemplos erróneos no es alta. Entonces se puede mejorar.

También existe el problema de que los datos pueden contener muchas preguntas para un texto. Y existe la posibilidad de que estas líneas terminen en diferentes muestras (entrenamiento/prueba) o que a partir de ellas se genere un ejemplo negativo incorrecto. Pero no creo que esto desempeñe un papel crítico todavía.

Inmediatamente divido los datos en entrenar/validar en una proporción de 80%/20%.

Según tengo entendido, la licencia para el conjunto de datos russian_super_glue es MIT (enlace).

Es más difícil con el conjunto de datos xquad, parece ser cc-by-sa-4.0 (enlace). Esto requiere la atribución y preservación del tipo de licencia del producto (enlace), pero permite el uso comercial. Por lo tanto, según tengo entendido, mi modelo tiene la licencia cc-by-sa-4.0. Me gustaría un MIT/Apache 2.0 completo. Quizás en el futuro reemplace este conjunto de datos.


6. Proceso de aprendizaje

Necesitas una gpu (tarjeta de video). en el modelo declarado 130 millones de estudiantes escamas. El modelo pesa más de 500 MB. En Google Colab, el tiempo de GPU es muy limitado, así que entrené en Kaggle (hay una GPU T4 x2, 30 horas a la semana). Por supuesto, puedes entrenar con una CPU, pero lleva mucho tiempo. Utilicé Cpu en el mismo kaggle como zona de pruebas para desarrollar y probar la arquitectura.

Al escribir código, me inspiré al aprender de rubert, rubert-2, nanogpt. Un agradecimiento especial a los autores por sus útiles desarrollos.

A continuación se presentan puntos interesantes de mi enfoque de la enseñanza:

Problema de desequilibrio de clases

Resuelto completamente por el hecho de que la formación. marimacho realizado a partir de un número igual de ejemplos positivos y negativos.

Métricas de calidad del modelo

Sólo hay una métrica de mejora de la calidad: la pérdida de validación. Cada vez que un modelo lo mejora, guardamos esta versión mejor en el disco.

Durante el proceso de aprendizaje, también observo la pérdida y la precisión del tren. Pero esto es más para una comprensión general de lo que está sucediendo.

Configuraciones clave de entrenamiento

Curiosamente, no existen muchas configuraciones de este tipo. Se muestran a continuación. Indicó algunos comentarios sobre la aplicación.

# повышаем пока не будет CUDA out of memory :)
# увеличивает скорость обучения, т.к. 1 батч обрабатывается на gpu за 1 проход
# усредняет обновление весов модели по нескольким примерам, что хорошо
# иногда даже применяют gradient accumulation
# это позволяет имитировать батч больше, чем допускает gpu (усреднение весов)
# технически реализуется задержкой обновления весов. Я решил это не применять
batch_size = 8

# тренируем пока тренируется (улучшается выбранная метрика качества)
iterations = 500

# это сколько раз я измеряю метрику в процессе обучения
# чем больше тем лучше
# т.к. позволяет чаше проверять и сохранять удачные состояния нейросети
# НО! это замедляет процесс обучение (чаще надо считать метрику)
eval_times = 50

# это сколько я беру батчей для оценки метрики
# опять же лучше брать вообще весь датасет (повышает точность метрики)
# но это очень долго
eval_iters = 20

# увеличивает/уменьшает влияния обучения на веса модели
# один из ключевых параметров тренировки
# ... но я так и не понял как его по научному устанавливать) см. ниже
learning_rate = 5e-5

Hay consejos sobre cómo determinar la tasa de aprendizaje en el video (enlace 00:45:40 encontrar una buena tasa de aprendizaje inicial). A simple vista, la pérdida de 5e-3 ya está empezando a aumentar muy rápidamente. 5e-7 es de alguna manera muy lento, nunca lo había visto jugado así.

Entonces resulta que, en promedio, la tasa de aprendizaje debería ser aproximadamente 5e-5.

También me gustó la idea de un programador: CosineAnnealingWarmRestarts. Allí, la tasa de aprendizaje revitaliza periódicamente el modelo y luego se desvanece gradualmente. Y tantas veces. Esto suena lógico. Quizás valga la pena agregarlo más adelante.

Congelar escalas de modelos

EN declarado 130 millones de parámetros. Están bien entrenados, son óptimos y todo les va bien. Lo principal es tratarlos con cuidado.

EN mi clasificador tiene alrededor de 2 millones de parámetros. Se inicializan aleatoriamente. Hay que entrenarlos mucho, desde cero.

Por eso, decidí realizar tres etapas de formación:

  1. Aprendemos solo los pesos del clasificador.. Los pesos restantes se congelan. Esto nos permite no tener miedo de que se nos estropeen las pesas. declarado.

  2. Aprendiendo todo el modelo. Se esperaba que el clasificador se volviera tan inteligente que declarado no es necesario tocar. Pero no. El clasificador no es suficiente. Entrenar todo el modelo produce resultados positivos significativos.

  3. Experimente con la regularización (abandono del clasificador y caída de peso en el optimizador). En las etapas 1 y 2 el abandono fue solo en capas declarado, caída de peso – estándar 0,01. En la Etapa 3, planeé aumentar significativamente la regularización para agregar modelos de generalización.

    Al comienzo de cada etapa de aprendizaje hay un período de calentamiento en el que reduzco la tasa de aprendizaje en 2 órdenes de magnitud. Esto permite al optimizador recopilar estadísticas y acostumbrarse a los datos y al modelo sin estropear las ponderaciones.

Relación entre el tamaño de los gradientes y los datos.

Al principio intenté mirar y controlar la relación entre el tamaño de los degradados y los pesos (media para cada capa del modelo). La lógica es que el tamaño de la actualización del peso debería ser perceptible. Si la media del gradiente es miles de millones de veces menor que la media de los datos, entonces el entrenamiento será difícil.

Como resultado, abandoné el seguimiento activo de este indicador. La razón es que es difícil de interpretar e influir. A continuación se muestra un ejemplo de los datos. El modelo acaba de ser inicializado:

Ejemplo de control
Lógicamente, un modelo así no puede aprender nada en absoluto.  Los gradientes son muy pequeños.  Y ella está estudiando...

Lógicamente, un modelo así no puede aprender nada en absoluto. Los gradientes son muy pequeños. Y ella está estudiando…

Por lo tanto, durante el entrenamiento confié completamente en el optimizador torch.optim.AdamW. De alguna manera trabajó en estas condiciones y tuvo mucho éxito. Elegante)

Otro

También utilizo la normalización de gradiente. En teoría, esto debería estabilizar el aprendizaje. Pero no creo que haya tenido un impacto significativo.

torch.nn.utils.clip_grad_norm_(pac.model.parameters(), max_norm = 1)

El resto del proceso de aprendizaje es estándar. Actualización de peso hacia adelante, hacia atrás.


7. Resultados del aprendizaje

El entrenamiento duró aproximadamente 70 minutos.

Etapa de formación 1. Aprendiendo la cabeza (clasificador)

Esta etapa salió bien. Entrenaron sus cabezas. La principal conclusión es que entrenar sólo la cabeza no es suficiente. Necesitamos aprender todo el modelo. Y lo más importante, ahora los pesos del modelo y la cabeza están entrenados aproximadamente por igual, puedes enseñarlos al mismo tiempo. Este era el significado de esta etapa.

En general, fue extraño para mí, pensé que el clasificador simplemente entendería declarado y educación superior declarado no requerido. Porque tocar las pesas declarado Este es un asunto responsable, puedes arruinarlo todo.

Historia de la pérdida y su cuadro.
El clasificador aprende, pero aún no puede resolver el problema por sí solo.  La pérdida cayó de 0,84 a 0,63.  Casi no hay más formación.  O mejor dicho, la pérdida está incluso creciendo.

El clasificador aprende, pero aún no puede resolver el problema por sí solo. La pérdida cayó de 0,84 a 0,63. Casi no hay más formación. O mejor dicho, la pérdida está incluso creciendo.

gráfico de pérdidas suavizado para mayor claridad

gráfico de pérdidas suavizado para mayor claridad

Etapa de entrenamiento 2. Aprenda el modelo completo.

Salió bien. El modelo pudo aprender y generalizar bien el conocimiento.

En general, esto demuestra que esta tarea y estos datos son relativamente fáciles para los neuralistas.

Historia de la pérdida y su cuadro.
La pérdida cae casi a cero en el conjunto de validación, lo cual es bueno.  Casi 100% de precisión

La pérdida cae casi a cero en el conjunto de validación, lo cual es bueno. Casi 100% de precisión

gráfico de pérdidas suavizado para mayor claridad

gráfico de pérdidas suavizado para mayor claridad

Etapa de aprendizaje 3: experimentar con la regularización

La decisión de aumentar la regularización sólo en la etapa 3 es controvertida. Sin embargo, al final de esta etapa, el modelo mejoró la métrica de calidad y considero que esta versión de las escalas es definitiva. En general, se suponía que la regularización agregaría generalización y solidez a los nuevos datos del modelo.

Creo que los abandonos podrían haberse incluido en el clasificador inmediatamente en las etapas 1 y 2. En lugar de hacer una etapa de entrenamiento separada con ellos.

Historia de la pérdida y su cuadro.
la última parte de las impresiones está perdida.  ¡¡¡Al final, el modelo aún mejoró la métrica!!!

la última parte de las impresiones está perdida. ¡¡¡Al final, el modelo aún mejoró la métrica!!!

gráfico de pérdidas suavizado para mayor claridad

gráfico de pérdidas suavizado para mayor claridad

Sentimiento subjetivo del modelo.

Recordemos nuestro experimento con el modelo. codificador cruzado/ms-frame-MiniLM-L-12-v2. Se posiciona como confeccionado, pero en ruso comete errores evidentes. Intentemos darle este ejemplo a nuestro modelo:

questions = ('где можно взять лодку на прокат?', 
             'где можно взять лодку на прокат?')
answers = ('взятие тракторов для пользования не предусмотрено', 
            'небольшой катер обычно берут в аренду у берега')

pac.model(list(zip(questions, answers))).argmax(1)
# tensor((0, 1), device="cuda:0")
# наша модель поняла, что первая пара - не логичная
# это конечно всего лишь один пример, но уже приятно

pac.model(list(zip(questions, answers)))
# tensor(((-0.1967, -1.1687),
#         (-3.2954,  2.5626)), grad_fn=<MmBackward0>)
# кроме того наша модель точно знает, что вторая пара - логичная
# а вот насчет первой пары... средняя степень уверенности
# но тем не менее, достаточно явно перевешывает НЕлогичность такой пары

De manera amistosa, debemos comparar nuestros codificadores cruzados y bicodificadores. Por ejemplo, tomemos nuestro modelo básico: declarado. Si no hay un aumento notable en la calidad, generalmente se puede cuestionar la tesis de que los codificadores cruzados son mejores… Pero realizar una evaluación comparativa de este tipo es una tarea difícil, lo dejé para el futuro.


8. Conclusiones

En general entendí lo siguiente:

  • Es muy posible diseñar y entrenar una red neuronal para resolver un problema aplicado.

  • mi modelo más bien sirve como modelo base, es decir requiere capacitación adicional sobre datos específicos de la tarea

  • Una de las principales dificultades es la necesidad de datos de entrenamiento de alta calidad en un volumen suficiente.

  • Kaggle ofrece 30 horas de gpu por semana, ¡eso es genial!

  • Al elegir un modelo y conjuntos de datos, debe mirar inmediatamente las licencias.

En los artículos intenté minimizar la cantidad de código, cuaderno kaggle disponible en enlacecontiene todo el proceso de formación y preparación de datos.

Aún no lo he subido a huggingface. Varios puntos requieren un estudio más profundo. Además la licencia no es la que quería (cc-by-sa-4.0).

Publicaciones Similares

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *