Rojo: Escala de clasificación de texto con delegación experta

Con la nueva era de resolución de problemas aumentada por modelos de idiomas grandes (LLM), solo quedan un puñado de problemas que tienen soluciones deficientes. La mayoría de los problemas de clasificación (a nivel de POC) se pueden resolver aprovechando las LLM de 70-90% de precisión/F1 con buenas técnicas de ingeniería rápida, así como ejemplos adaptativos de aprendizaje en contexto (ICL).

¿Qué sucede cuando quieres lograr constantemente el rendimiento? más alto que eso, ¿cuando la ingeniería rápida ya no es suficiente?

El enigma de clasificación

La clasificación de texto es uno de los ejemplos más antiguos y mejor comprendidos de aprendizaje supervisado. Dada esta premisa, realmente no debería ser difícil construir clasificadores robustos y bien realizados que manejen una gran cantidad de clases de entrada, ¿verdad …?

Welp. Es.

En realidad, tiene que hacer mucho más con las ‘limitaciones’ que generalmente se espera que el algoritmo funcione:

  • baja cantidad de datos de entrenamiento por clase
  • Alta precisión de clasificación (que se desploma a medida que agrega más clases)
  • posible adición de nuevas clases a un subconjunto existente de clases
  • entrenamiento rápido/inferencia
  • rentabilidad
  • (potencialmente) gran cantidad de clases de entrenamiento
  • (potencialmente) interminable requerido reincidencia de alguno Clases debido a la deriva de datos, etc.

¿Alguna vez has intentado construir un clasificador más allá de unas pocas docenas de clases en estas condiciones? (Quiero decir, incluso GPT probablemente podría hacer un gran trabajo hasta ~ 30 clases de texto con solo unas pocas muestras …)

Teniendo en cuenta que toma la ruta GPT: si tiene más de un par de docenas de clases o una cantidad considerable de datos para clasificarse, tendrá que llegar profundamente en sus bolsillos con el indicador del sistema, indicadores del usuario, pocos tokens de ejemplo de disparo que necesitará clasificar una muestra. Eso es después de hacer las paces con el rendimiento de la API, incluso si está ejecutando consultas de async.

En ML aplicado, problemas como estos generalmente son difíciles de resolver, ya que no satisfacen completamente los requisitos del aprendizaje supervisado o no son baratos/rápidos para ejecutarse a través de un LLM. Este punto de dolor particular es lo que aborda el algoritmo rojo: el aprendizaje semi-supervisado, cuando los datos de capacitación por clase no son suficientes para construir (cuasi) clasificadores tradicionales.

El algoritmo rojo

ROJO: Recursivo Delegación experta es un marco novedoso que cambia la forma en que abordamos la clasificación de texto. Este es un paradigma ML aplicado, es decir, no hay fundamentalmente diferente Arquitectura a lo que existe, pero es un carrete destacado de ideas que funcionan mejor para construir algo que sea práctico y escalable.

En esta publicación, trabajaremos a través de un ejemplo específico en el que tenemos una gran cantidad de clases de texto (100-1000), cada clase solo tiene pocas muestras (30-100), y hay un número no trivial de muestras para clasificar (10,000-100,000). Nos acercamos a esto como un aprendizaje semi-supervisado Problema a través de Red

Vamos a sumergirnos.

Cómo funciona

Representación simple de lo que hace Red

En lugar de tener un clasificador único, clasifique entre una gran cantidad de clases, rojo de manera inteligente:

  1. Divide y conquista – Rompe el espacio de la etiqueta (gran número de etiquetas de entrada) en múltiples subconjuntos de etiquetas. Este es un enfoque de formación de subconjunto de etiquetas codiciosas.
  2. Aprende de manera eficiente – Entrena clasificadores especializados para cada subconjunto. Este paso se centra en la construcción de un clasificador que sobremuestree el ruido, donde el ruido se modela de manera inteligente como datos de otros subconjuntos.
  3. Delegados a un experto – Emplee a LLM como oráculos expertos solo para validación y corrección de etiquetas específicas, similar a tener un equipo de expertos en dominios. Usando un LLM como proxy, empíricamente ‘imita’ cómo Un experto humano valida una salida.
  4. Reentrenamiento recursivo – Vuelve continuamente con muestras frescas agregadas del experto hasta que no hay más muestras para agregar/Se logra una saturación de la ganancia de información.

La intuición detrás de esto no es muy difícil de comprender: Aprendizaje activo Emplea a los humanos como expertos en dominios para ‘corregir’ o ‘validar’ los resultados de un modelo ML, con capacitación continua. Esto se detiene cuando el modelo logra un rendimiento aceptable. Intuimos y renombramos lo mismo, con algunas innovaciones inteligentes que se detallan en una preimpresión de investigación más adelante.

Echemos una mirada más profunda …

Selección de subconjuntos codiciosos con elementos menos similares

Cuando el número de etiquetas de entrada (clases) es alta, aumenta la complejidad de aprender un límite de decisión lineal entre las clases. Como tal, la calidad del clasificador se deteriora a medida que aumenta el número de clases. Esto es especialmente cierto cuando el clasificador no tiene suficiente muestras Para aprender, es decir, cada una de las clases de entrenamiento tiene solo unas pocas muestras.

Esto refleja mucho un escenario del mundo real y la principal motivación detrás de la creación de rojo

Algunas formas de mejorar el rendimiento de un clasificador bajo estas limitaciones:

  • Restringir El número de clases que un clasificador necesita clasificar entre
  • Hacer que el límite de decisión entre las clases sea más claro, es decir, capacitar al clasificador en clases muy diferentes

La selección de subconjuntos codiciosos hace exactamente esto, ya que el alcance del problema es Clasificación de textoformamos incrustaciones de las etiquetas de entrenamiento, reducimos su dimensionalidad a través de UMAP, luego se forman S subconjuntos de ellos. Cada uno de los S subconjuntos tiene elementos como norte Etiquetas de entrenamiento. Elegimos las etiquetas de entrenamiento con avidez, asegurando que cada etiqueta que elegimos para el subconjunto sea la etiqueta más diferente de las otras etiquetas que existen en el subconjunto:

import numpy as np
from sklearn.metrics.pairwise import cosine_similarity


def avg_embedding(candidate_embeddings):
    return np.mean(candidate_embeddings, axis=0)

def get_least_similar_embedding(target_embedding, candidate_embeddings):
    similarities = cosine_similarity(target_embedding, candidate_embeddings)
    least_similar_index = np.argmin(similarities)  # Use argmin to find the index of the minimum
    least_similar_element = candidate_embeddings[least_similar_index]
    return least_similar_element


def get_embedding_class(embedding, embedding_map):
    reverse_embedding_map = {value: key for key, value in embedding_map.items()}
    return reverse_embedding_map.get(embedding)  # Use .get() to handle missing keys gracefully


def select_subsets(embeddings, n):
    visited = {cls: False for cls in embeddings.keys()}
    subsets = []
    current_subset = []

    while any(not visited[cls] for cls in visited):
        for cls, average_embedding in embeddings.items():
            if not current_subset:
                current_subset.append(average_embedding)
                visited[cls] = True
            elif len(current_subset) >= n:
                subsets.append(current_subset.copy())
                current_subset = []
            else:
                subset_average = avg_embedding(current_subset)
                remaining_embeddings = [emb for cls_, emb in embeddings.items() if not visited[cls_]]
                if not remaining_embeddings:
                    break # handle edge case
                
                least_similar = get_least_similar_embedding(target_embedding=subset_average, candidate_embeddings=remaining_embeddings)

                visited_class = get_embedding_class(least_similar, embeddings)

                
                if visited_class is not None:
                  visited[visited_class] = True


                current_subset.append(least_similar)
    
    if current_subset:  # Add any remaining elements in current_subset
        subsets.append(current_subset)
        

    return subsets

El resultado de este codicioso subconjunto de subconjunto es todas las etiquetas de entrenamiento claramente encerradas en subconjuntos, donde cada subconjunto tiene como máximo solo norte clases. Esto inherentemente facilita el trabajo de un clasificador, en comparación con el original S ¡Clases tendría que clasificar entre lo contrario!

Clasificación semi-supervisada con sobremuestreo de ruido

Cascada esto después de la formación de subconjunto de etiqueta inicial, es decir, este clasificador solo está clasificando entre un dado subconjunto de clases.

Imagine esto: cuando tiene bajas cantidades de datos de capacitación, no puede crear un conjunto de retención que sea significativo para la evaluación. ¿Deberías hacerlo en absoluto? ¿Cómo saber si su clasificador funciona bien?

Abordamos este problema de manera ligeramente diferente: definimos el trabajo fundamental de un clasificador semi-supervisado para ser con derecho preferente Clasificación de una muestra. Esto significa que, independientemente de lo que se clasifique una muestra, ya que se ‘verificará’ y ‘corregir’ en una etapa posterior: este clasificador solo necesita identificar lo que debe verificarse.

Como tal, creamos un diseño para cómo trataría sus datos:

  • N+1 clases, donde está la última clase ruido
  • ruido: Datos de clases que no están en el alcance del clasificador actual. La clase de ruido está exagerada para ser 2 veces el tamaño promedio de los datos para las etiquetas del clasificador

El sobremuestreo sobre el ruido es una medida de seguridad falsa, para garantizar que los datos adyacentes que pertenecen a otra clase probablemente se predijan como ruido en lugar de deslizarse para la verificación.

¿Cómo se verifica si este clasificador funciona bien? En nuestros experimentos, definimos esto como el número de muestras “inciertas” en la predicción de un clasificador. Utilizando el muestreo de incertidumbre y los principios de ganancia de información, pudimos medir efectivamente si un clasificador está ‘aprendiendo’ o no, lo que actúa como un puntero para el rendimiento de la clasificación. Este clasificador se reinicia constantemente a menos que haya un punto de inflexión en el número de muestras inciertas previstas, o solo hay un delta de información que se agrega iterativamente por nuevas muestras.

Aprendizaje activo proxy a través de un agente LLM

Este es el corazón del enfoque: usar un LLM como proxy para un validador humano. El enfoque de validador humano del que estamos hablando es el etiquetado activo

Obtengamos una comprensión intuitiva del etiquetado activo:

  • Use un modelo ML para aprender en un conjunto de datos de entrada de muestra, predecir en un gran conjunto de puntos de datos
  • Para las predicciones dadas en los puntos de datos, un experto en materia (PYME) evalúa la ‘validez’ de las predicciones
  • Recursivamente, se agregan nuevas muestras ‘corregidas’ como datos de entrenamiento al modelo ML
  • El modelo de ML aprende/se vuelve a conectar constantemente y hace predicciones hasta que la PYME se sienta satisfecha por la calidad de las predicciones

Para que el etiquetado activo funcione, existen expectativas involucradas para una PYME:

  • Cuando esperamos que un experto humano ‘valida’ una muestra de salida, el experto comprende cuál es la tarea
  • Un experto humano utilizará el juicio para evaluar ‘qué más’ definitivamente pertenece a una etiqueta L Al decidir si una nueva muestra debe pertenecer a L

Dadas estas expectativas e intuiciones, podemos ‘imitarlos’ usando un LLM:

  • darle al LLM una ‘comprensión’ de lo que significa cada etiqueta. Esto se puede hacer utilizando un modelo más grande para evaluar críticamente la relación entre {etiqueta: datos mapeados para etiquetar} para todas las etiquetas. En nuestros experimentos, esto se hizo usando un Variante 32B de Deepseek Eso fue autohospedado.
Darle a un LLM la capacidad de comprender ‘por qué, qué y cómo’
  • En lugar de predecir cuál es la etiqueta correcta, Aproveche el LLM para identificar si una predicción es “válida” o “inválida” solamente (es decir, LLM solo tiene que responder una consulta binaria).
  • Reforzar la idea de cómo se ven otras muestras válidas para la etiqueta, es decir, por cada etiqueta preventiva predicha para una muestra, una fuente dinámica do Las muestras más cercanas en su entrenamiento (garantizado) establecido cuando se solicitan la validación.

El resultado? Un marco rentable que se basa en un clasificador rápido y barato para hacer clasificaciones preventivas y un LLM que verifica estos usando (significado de la etiqueta + muestras de entrenamiento de origen dinámico que son similares a la clasificación actual):

import math

def calculate_uncertainty(clf, sample):
    predicted_probabilities = clf.predict_proba(sample.reshape(1, -1))[0]  # Reshape sample for predict_proba
    uncertainty = -sum(p * math.log(p, 2) for p in predicted_probabilities)
    return uncertainty


def select_informative_samples(clf, data, k):
    informative_samples = []
    uncertainties = [calculate_uncertainty(clf, sample) for sample in data]

    # Sort data by descending order of uncertainty
    sorted_data = sorted(zip(data, uncertainties), key=lambda x: x[1], reverse=True)

    # Get top k samples with highest uncertainty
    for sample, uncertainty in sorted_data[:k]:
        informative_samples.append(sample)

    return informative_samples


def proxy_label(clf, llm_judge, k, testing_data):
    #llm_judge - any LLM with a system prompt tuned for verifying if a sample belongs to a class. Expected output is a bool : True or False. True verifies the original classification, False refutes it
    predicted_classes = clf.predict(testing_data)

    # Select k most informative samples using uncertainty sampling
    informative_samples = select_informative_samples(clf, testing_data, k)

    # List to store correct samples
    voted_data = []

    # Evaluate informative samples with the LLM judge
    for sample in informative_samples:
        sample_index = testing_data.tolist().index(sample.tolist()) # changed from testing_data.index(sample) because of numpy array type issue
        predicted_class = predicted_classes[sample_index]

        # Check if LLM judge agrees with the prediction
        if llm_judge(sample, predicted_class):
            # If correct, add the sample to voted data
            voted_data.append(sample)

    # Return the list of correct samples with proxy labels
    return voted_data

Al alimentar las muestras válidas (voted_data) a nuestro clasificador bajo parámetros controlados, logramos la parte ‘recursiva’ de nuestro algoritmo:

Delegación de expertos recursivos: rojo

Al hacer esto, pudimos lograr números de validación cercanos a los expertos en conjuntos de datos de múltiples clases controlados. Experimentalmente, Red se escala hasta 1,000 clases mientras mantiene un grado competente de precisión Casi a la par con expertos humanos (90%+ acuerdo).

Creo que este es un logro significativo en la ML aplicada y tiene usos del mundo real para las expectativas de nivel de producción de costo, velocidad, escala y adaptabilidad. El informe técnico, que publica a finales de este año, destaca las muestras de código relevantes, así como las configuraciones experimentales utilizadas para lograr resultados dados.

Todas las imágenes, a menos que se indique lo contrario, son del autor

¿Interesado en más detalles? Alcanza a mí Medio ¡O envíe un correo electrónico para chatear!