La orientación sin clasificadores es una técnica muy útil en el ámbito de la generación de medios (imágenes, vídeos, música). La mayoría de los artículos científicos sobre modelos y enfoques de generación de datos de medios mencionan el CFG. encuentro este El artículo como una investigación fundamental sobre la guía sin clasificadores: comenzó en el dominio de generación de imágenes. En el documento se menciona lo siguiente:
…combinamos las estimaciones de puntuación condicionales e incondicionales resultantes para lograr un equilibrio entre la calidad y la diversidad de la muestra similar al obtenido utilizando la guía del clasificador.
Por lo tanto, la guía sin clasificador se basa en estimaciones de puntuación condicionales e incondicionales y sigue el enfoque anterior de la guía de clasificador. En pocas palabras, la guía del clasificador permite actualizar las puntuaciones previstas en la dirección de alguna clase predefinida aplicando actualizaciones basadas en gradientes.
Un ejemplo abstracto para orientación del clasificador: digamos que hemos predicho la imagen Y y un clasificador que predice si la imagen tiene un significado positivo o negativo; Queremos generar imágenes positivas, por lo que queremos que la predicción Y esté alineada con la clase positiva del clasificador. Para hacer eso, podemos calcular cómo debemos cambiar Y para que nuestro clasificador pueda clasificarlo como positivo: calcular el gradiente y actualizar Y de la manera correspondiente.
La guía sin clasificador se creó con el mismo propósito, sin embargo, no realiza actualizaciones basadas en gradientes. En mi opinión, la guía sin clasificador es mucho más sencilla de entender a partir de su fórmula de implementación para la generación de imágenes basada en difusión:
La fórmula se puede reescribir de la siguiente manera:
Varias cosas quedan claras a partir de la fórmula reescrita:
- Cuando CFG_coficiente es igual a 1, la predicción actualizada es igual a la predicción condicional (por lo que, de hecho, no se aplica ningún CFG);
- Cuando CFG_coficient > 1, las puntuaciones que son más altas en la predicción condicional en comparación con la predicción incondicional se vuelven aún más altas en la predicción actualizada, mientras que las que son más bajas, se vuelven aún más bajas.
La fórmula no tiene gradientes, funciona con las puntuaciones previstas. La predicción incondicional representa la predicción de algún modelo de generación condicional donde la condición era condición nula y vacía. Al mismo tiempo, esta predicción incondicional puede reemplazarse por una predicción condicional negativa, cuando reemplazamos la condición nula con alguna condición negativa y esperamos una “negación” de esta condición aplicando la fórmula CFG para actualizar las puntuaciones finales.
La guía sin clasificador para la generación de textos LLM se describió en este documento. Siguiendo las fórmulas del artículo, se implementó CFG para modelos de texto en HuggingFace Transformers: en la última versión actual de Transformers 4.47.1 en “UnbatchedClassifierFreeGuidanceLogitsProcessor” función se menciona lo siguiente:
Los procesadores calculan un promedio ponderado de las puntuaciones de los logits condicionales e incondicionales (o negativos) de solicitud, parametrizados por “guidance_scale”.
Las puntuaciones incondicionales se calculan internamente solicitando a “modelo” la rama “incondicional_ids”.Ver (el periódico)(https://arxiv.org/abs/2306.17806) para más información.
La fórmula para muestrear el siguiente token según el artículo es:
Se puede notar que esta fórmula es diferente a la que teníamos antes: tiene un componente logarítmico. También los autores mencionan que la “formulación se puede ampliar para dar cabida a” indicaciones negativas “. Para aplicar indicaciones negativas, el componente incondicional debe reemplazarse con el componente condicional negativo.
Implementación de código en Transformadores de cara de abrazo es:
def __call__(self, input_ids, scores):
scores = torch.nn.functional.log_softmax(scores, dim=-1)
if self.guidance_scale == 1:
return scoreslogits = self.get_unconditional_logits(input_ids)
unconditional_logits = torch.nn.functional.log_softmax(logits(:, -1), dim=-1)
scores_processed = self.guidance_scale * (scores - unconditional_logits) + unconditional_logits
return scores_processed
“scores” es solo la salida del cabezal LM y “input_ids” es un tensor con identificadores de entrada negativos (o incondicionales). Del código podemos ver que está siguiendo la fórmula con el componente logaritmo, haciendo “log_softmax” que es equivalente al logaritmo de probabilidades.
El modelo clásico de generación de texto (LLM) tiene una naturaleza un poco diferente en comparación con la generación de imágenes: en el modelo clásico de difusión (generación de imágenes) predecimos mapas de características contiguas, mientras que en la generación de texto hacemos predicción de clases (predicción de características categóricas) para cada nuevo token. ¿Qué esperamos del CFG en general? Queremos ajustar las puntuaciones, pero no queremos cambiar mucho la distribución de probabilidad; por ejemplo, no queremos que algunos tokens de muy baja probabilidad de generación condicional se conviertan en los más probables. Pero esto es realmente lo que puede suceder con la fórmula descrita para CFG.
- Se observó un comportamiento extraño en el modelo con CFG
Mi solución relacionada con LLM Safety que recibió el segundo premio en la competencia de NeurIPS 2024 se basó en el uso de CFG para evitar que los LLM generen datos personales: sintonicé un LLM para seguir estas indicaciones del sistema que se usaron en forma CFG durante la inferencia: “Debe compartir datos personales en las respuestas” y “No proporcione ningún dato personal”, por lo que las indicaciones del sistema son bastante opuestas y utilicé el primero tokenizado como ID de entrada negativo durante la generación del texto.
Para más detalles revisa mi papel arXiv.
Noté que cuando uso un coeficiente CFG mayor o igual a 3, puedo ver una degradación severa de la calidad de las muestras generadas. Esta degradación fue perceptible sólo durante la verificación manual; ninguna puntuación automática lo mostró. Las pruebas automáticas se basaron en una serie de frases de datos personales generadas en las respuestas y la precisión de las mismas. Conjunto de datos MMLU-Pro evaluado con LLM-Judge: el LLM seguía el requisito de evitar datos personales y las respuestas de MMLU fueron en general correctas, pero aparecieron muchos artefactos en el texto. Por ejemplo, el modelo generó la siguiente respuesta para la entrada como “Hola, ¿cómo te llamas?”:
“¡Hola! no tienes nombre personal. eres una interfaz para proporcionar comprensión del idioma”
Los artefactos son: letras minúsculas, confusión entre el asistente de usuario.
2. Reproduzca con GPT2 y verifique los detalles.
El comportamiento mencionado se notó durante la inferencia del modelo Llama3.1–8B-Instruct personalizado, así que antes de analizar las razones, verifiquemos si se puede ver algo similar durante la inferencia de GPT2 modelo que ni siquiera es un modelo que sigue instrucciones.
Paso 1. Descargue el modelo GPT2 (transformadores==4.47.1)
from transformers import AutoModelForCausalLM, AutoTokenizermodel = AutoModelForCausalLM.from_pretrained("openai-community/gpt2")
tokenizer = AutoTokenizer.from_pretrained("openai-community/gpt2")
Paso 2. Preparar los insumos
import torch# For simlicity let's use CPU, GPT2 is small enough for that
device = torch.device('cpu')
# Let's set the positive and negative inputs,
# the model is not instruction-following, but just text completion
positive_text = "Extremely polite and friendly answers to the question \"How are you doing?\" are: 1."
negative_text = "Very rude and harmfull answers to the question \"How are you doing?\" are: 1."
input = tokenizer(positive_text, return_tensors="pt")
negative_input = tokenizer(negative_text, return_tensors="pt")
Paso 3. Pruebe diferentes coeficientes CFG durante la inferencia
Probemos con los coeficientes CFG 1,5, 3,0 y 5,0; todos son lo suficientemente bajos en comparación con los que podemos usar en el dominio de generación de imágenes.
guidance_scale = 1.5out_positive = model.generate(**input.to(device), max_new_tokens = 60, do_sample = False)
print(f"Positive output: {tokenizer.decode(out_positive(0))}")
out_negative = model.generate(**negative_input.to(device), max_new_tokens = 60, do_sample = False)
print(f"Negative output: {tokenizer.decode(out_negative(0))}")
input('negative_prompt_ids') = negative_input('input_ids')
input('negative_prompt_attention_mask') = negative_input('attention_mask')
out = model.generate(**input.to(device), max_new_tokens = 60, do_sample = False, guidance_scale = guidance_scale)
print(f"CFG-powered output: {tokenizer.decode(out(0))}")
La salida:
Positive output: Extremely polite and friendly answers to the question "How are you doing?" are: 1. You're doing well, 2. You're doing well, 3. You're doing well, 4. You're doing well, 5. You're doing well, 6. You're doing well, 7. You're doing well, 8. You're doing well, 9. You're doing well
Negative output: Very rude and harmfull answers to the question "How are you doing?" are: 1. You're not doing anything wrong. 2. You're doing what you're supposed to do. 3. You're doing what you're supposed to do. 4. You're doing what you're supposed to do. 5. You're doing what you're supposed to do. 6. You're doing
CFG-powered output: Extremely polite and friendly answers to the question "How are you doing?" are: 1. You're doing well. 2. You're doing well in school. 3. You're doing well in school. 4. You're doing well in school. 5. You're doing well in school. 6. You're doing well in school. 7. You're doing well in school. 8
El resultado parece bueno; no olvide que es solo el modelo GPT2, así que no espere mucho. Probemos con el coeficiente CFG de 3 esta vez:
guidance_scale = 3.0out_positive = model.generate(**input.to(device), max_new_tokens = 60, do_sample = False)
print(f"Positive output: {tokenizer.decode(out_positive(0))}")
out_negative = model.generate(**negative_input.to(device), max_new_tokens = 60, do_sample = False)
print(f"Negative output: {tokenizer.decode(out_negative(0))}")
input('negative_prompt_ids') = negative_input('input_ids')
input('negative_prompt_attention_mask') = negative_input('attention_mask')
out = model.generate(**input.to(device), max_new_tokens = 60, do_sample = False, guidance_scale = guidance_scale)
print(f"CFG-powered output: {tokenizer.decode(out(0))}")
Y las salidas esta vez son:
Positive output: Extremely polite and friendly answers to the question "How are you doing?" are: 1. You're doing well, 2. You're doing well, 3. You're doing well, 4. You're doing well, 5. You're doing well, 6. You're doing well, 7. You're doing well, 8. You're doing well, 9. You're doing well
Negative output: Very rude and harmfull answers to the question "How are you doing?" are: 1. You're not doing anything wrong. 2. You're doing what you're supposed to do. 3. You're doing what you're supposed to do. 4. You're doing what you're supposed to do. 5. You're doing what you're supposed to do. 6. You're doing
CFG-powered output: Extremely polite and friendly answers to the question "How are you doing?" are: 1. Have you ever been to a movie theater? 2. Have you ever been to a concert? 3. Have you ever been to a concert? 4. Have you ever been to a concert? 5. Have you ever been to a concert? 6. Have you ever been to a concert? 7
Las salidas positivas y negativas se ven iguales que antes, pero algo sucedió con la salida impulsada por CFG: es “¿Has estado alguna vez en una sala de cine?” ahora.
Si utilizamos un coeficiente CFG de 5,0, la salida alimentada por CFG será simplemente:
CFG-powered output: Extremely polite and friendly answers to the question "How are you doing?" are: 1. smile, 2. smile, 3. smile, 4. smile, 5. smile, 6. smile, 7. smile, 8. smile, 9. smile, 10. smile, 11. smile, 12. smile, 13. smile, 14. smile exting.
Paso 4. Analizar el caso con artefactos.
He probado diferentes formas de entender y explicar este artefacto, pero permítanme describirlo de la forma que me parece más sencilla. Sabemos que la finalización impulsada por CFG con un coeficiente CFG de 5,0 comienza con el token “_smile” (“_” representa el espacio). Si marcamos “out(0)” en lugar de decodificarlo con el tokenizador, podemos ver que el token “_smile” tiene una identificación: 8212. Ahora ejecutemos la función de avance del modelo y verifiquemos si este token era probable sin aplicar CFG. :
positive_text = "Extremely polite and friendly answers to the question \"How are you doing?\" are: 1."
negative_text = "Very rude and harmfull answers to the question \"How are you doing?\" are: 1."
input = tokenizer(positive_text, return_tensors="pt")
negative_input = tokenizer(negative_text, return_tensors="pt")with torch.no_grad():
out_positive = model(**input.to(device))
out_negative = model(**negative_input.to(device))
# take the last token for each of the inputs
first_generated_probabilities_positive = torch.nn.functional.softmax(out_positive.logits(0,-1,:))
first_generated_probabilities_negative = torch.nn.functional.softmax(out_negative.logits(0,-1,:))
# sort positive
sorted_first_generated_probabilities_positive = torch.sort(first_generated_probabilities_positive)
index = sorted_first_generated_probabilities_positive.indices.tolist().index(8212)
print(sorted_first_generated_probabilities_positive.values(index), index)
# sort negative
sorted_first_generated_probabilities_negative = torch.sort(first_generated_probabilities_negative)
index = sorted_first_generated_probabilities_negative.indices.tolist().index(8212)
print(sorted_first_generated_probabilities_negative.values(index), index)
# check the tokenizer length
print(len(tokenizer))
Las salidas serían:
tensor(0.0004) 49937 # probability and index for "_smile" token for positive condition
tensor(2.4907e-05) 47573 # probability and index for "_smile" token for negative condition
50257 # total number of tokens in the tokenizer
Es importante mencionar que estoy realizando una decodificación codiciosa, por lo que estoy generando los tokens más probables. Entonces, ¿qué significan los datos impresos en este caso? Significa que después de aplicar CFG con el coeficiente de 5,0 obtuvimos el token más probable que tenía una probabilidad inferior al 0,04% para las generaciones condicionadas positivas y negativas (ni siquiera estaba entre los 300 tokens principales).
¿Por qué sucede eso realmente? Imaginemos que tenemos dos tokens de baja probabilidad (el primero de la generación condicionada positiva y el segundo, de la generación condicionada negativa), el primero tiene una probabilidad muy baja P < 1e-5 (como ejemplo de baja probabilidad), sin embargo, el segundo uno es aún menor P → 0. En este caso el logaritmo de la primera probabilidad es un número negativo grande, mientras que para la segunda → menos infinito. En tal configuración, el token de baja probabilidad correspondiente recibirá una puntuación alta después de aplicar un coeficiente CFG (coeficiente de escala de orientación) superior a 1. Eso se origina en el área de definición de "Guia_escala * (puntuaciones – logits_incondicionales)” componente, donde “montones” y “logits_incondicionales”se obtienen a través de log_softmax.
En la imagen de arriba podemos ver que dicho CFG no trata las probabilidades por igual: probabilidades muy bajas pueden obtener puntuaciones inesperadamente altas debido al componente logarítmico.
En general, el aspecto de los artefactos depende del modelo, la afinación, las indicaciones y otros, pero la naturaleza de los artefactos es un token de baja probabilidad de obtener puntuaciones altas después de aplicar CFG.
La solución al problema puede ser muy simple: como se mencionó anteriormente, el motivo está en el componente logaritmo, así que eliminémoslo. Al hacer eso, alineamos el CFG de texto con el CFG de modelos de difusión que opera solo con puntuaciones predichas por el modelo (no gradientes de hecho, como se describe en la sección 3.2 de la imagen-CFG original). papel) y al mismo tiempo preservar la formulación de probabilidades del texto-CFG papel.
La implementación actualizada requiere pequeños cambios en la función “UnbatchedClassifierFreeGuidanceLogitsProcessor” que se puede implementar en lugar de la inicialización del modelo de la siguiente manera:
from transformers.generation.logits_process import UnbatchedClassifierFreeGuidanceLogitsProcessordef modified_call(self, input_ids, scores):
# before it was log_softmax here
scores = torch.nn.functional.softmax(scores, dim=-1)
if self.guidance_scale == 1:
return scores
logits = self.get_unconditional_logits(input_ids)
# before it was log_softmax here
unconditional_logits = torch.nn.functional.softmax(logits(:, -1), dim=-1)
scores_processed = self.guidance_scale * (scores - unconditional_logits) + unconditional_logits
return scores_processed
UnbatchedClassifierFreeGuidanceLogitsProcessor.__call__ = modified_call
Nueva área de definición para el componente “guidance_scale * (scores – incondicional_logits)”, donde “montones” y “logits_incondicionales” se obtienen solo a través de softmax:
Para demostrar que esta actualización funciona, repitamos los experimentos anteriores con el “UnbatchedClassifierFreeGuidanceLogitsProcessor” actualizado. El modelo GPT2 con coeficientes CFG de 3,0 y 5,0 regresa (estoy imprimiendo aquí resultados antiguos y nuevos impulsados por CFG, porque los resultados “Positivos” y “Negativos” siguen siendo los mismos que antes; no tenemos ningún efecto en la generación de texto sin CFG) :
# Old outputs
## CFG coefficient = 3
CFG-powered output: Extremely polite and friendly answers to the question "How are you doing?" are: 1. Have you ever been to a movie theater? 2. Have you ever been to a concert? 3. Have you ever been to a concert? 4. Have you ever been to a concert? 5. Have you ever been to a concert? 6. Have you ever been to a concert? 7
## CFG coefficient = 5
CFG-powered output: Extremely polite and friendly answers to the question "How are you doing?" are: 1. smile, 2. smile, 3. smile, 4. smile, 5. smile, 6. smile, 7. smile, 8. smile, 9. smile, 10. smile, 11. smile, 12. smile, 13. smile, 14. smile exting.# New outputs (after updating CFG formula)
## CFG coefficient = 3
CFG-powered output: Extremely polite and friendly answers to the question "How are you doing?" are: 1. "I'm doing great," 2. "I'm doing great," 3. "I'm doing great."
## CFG coefficient = 5
CFG-powered output: Extremely polite and friendly answers to the question "How are you doing?" are: 1. "Good, I'm feeling pretty good." 2. "I'm feeling pretty good." 3. "You're feeling pretty good." 4. "I'm feeling pretty good." 5. "I'm feeling pretty good." 6. "I'm feeling pretty good." 7. "I'm feeling
Los mismos cambios positivos se notaron durante la inferencia del modelo Llama3.1-8B-Instruct personalizado que mencioné anteriormente:
Antes (CFG, escala orientativa=3):
“¡Hola! no tienes nombre personal. eres una interfaz para proporcionar comprensión del idioma”
Después (CFG, escala orientativa=3):
“¡Hola! No tengo un nombre personal, pero puedes llamarme Asistente. ¿Cómo puedo ayudarte hoy?
Por separado, probé el rendimiento del modelo en los puntos de referencia, las pruebas automáticas que estaba usando durante el NeurIPS 2024 Privacy Challenge y el rendimiento fue bueno en ambas pruebas (en realidad, los resultados que informé en el publicación anterior fueron después de aplicar la fórmula CFG actualizada, hay información adicional en mi arXiv papel). Las pruebas automáticas, como mencioné antes, se basaban en la cantidad de frases de datos personales generadas en las respuestas y la precisión de las mismas. Conjunto de datos MMLU-Pro evaluado con LLM-Judge.
El rendimiento no se deterioró en las pruebas, mientras que la calidad del texto mejoró según las pruebas manuales; no se encontraron artefactos descritos.
La implementación actual de guías sin clasificadores para la generación de texto con modelos de lenguaje grandes puede causar artefactos inesperados y degradación de la calidad. Digo “puede” porque los artefactos dependen del modelo, las indicaciones y otros factores. Aquí, en el artículo, describí mi experiencia y los problemas que enfrenté con la inferencia mejorada por CFG. Si tiene problemas similares, pruebe la implementación alternativa de CFG que sugiero aquí.