Aviso: Este post foi traduzido para o português usando um modelo de tradução automática. Por favor, me avise se encontrar algum erro.
A medida que os LLMs aumentam de tamanho e surgem novas capacidades, temos um problema: as alucinações. Os autores do artigo DoLa: Decoding by Contrasting Layers Improves Factuality in Large Language Models propõem um método para evitar esse problema.
Propõem uma abordagem de decodificação contrastiva, onde a probabilidade de saída da próxima palavra é obtida a partir da diferença nos logits entre uma camada superior e uma inferior. Ao enfatizar o conhecimento das camadas superiores e diminuir a importância das inferiores, podemos fazer com que os LM sejam mais fáticos e, portanto, reduzir as alucinações.
Na figura a seguir, essa ideia é ilustrada. Enquanto Seattle
mantém uma alta probabilidade em todas as camadas, a probabilidade da resposta correta Olympia
aumenta após as camadas superiores injetarem mais conhecimento factual. Contrastar as diferenças entre as diferentes camadas pode revelar a resposta correta neste caso.

Método
Um LLM consiste em uma camada de embedding, vários transformers sequenciais e, em seguida, uma camada de saída. O que propõem é medir a saída de cada transformer por meio da divergência de Jensen-Shannon (JSD).
Na figura seguinte, pode-se ver essa medida na saída de cada transformer para uma frase de entrada no LLM. Cada coluna corresponde a um token da frase.

Podem observar dois padrões
- O primeiro ocorre quando são previstas entidades de nome ou datas importantes, como
Wole Soyinka
e1986
, que requerem conhecimento fático. Pode-se ver que a JSD calculada continua extremamente alta nas camadas superiores. Este padrão indica que o modelo ainda está mudando suas previsões nas últimas camadas, e potencialmente injetando mais conhecimento fático nas previsões
- O segundo ocorre quando se preveem palavras funcionais, como
was
,the
,to
,in
, e os tokens copiados da pergunta de entrada, comofirst Nigerian
,Nobel Prize
. Quando esses tokens "fáceis" são previstos, podemos observar que a JSD torna-se muito pequena a partir das camadas intermediárias. Essa descoberta indica que o modelo já decidiu qual token gerar nas camadas intermediárias e mantém as distribuições de saída quase inalteradas nas camadas superiores. Essa descoberta também é consistente com as suposições em LLMs de saída precoceSchuster et al., 2022
Quando a previsão da próxima palavra requer conhecimento fático, o LLM parece alterar as previsões nas camadas superiores. Contrastar as camadas antes e depois de uma mudança súbita pode, portanto, amplificar o conhecimento que emerge das camadas superiores e fazer com que o modelo se baseie mais em seu conhecimento interno fático. Além disso, essa evolução da informação parece variar de um token para outro
Seu método requer selecionar com precisão a camada prematura que contém informações plausíveis mas menos factuais, que pode não estar sempre na mesma camada inicial. Portanto, eles propõem encontrar essa camada prematura através de uma seleção dinâmica da camada prematura, como visto na imagem a seguir.

Seleção dinâmica da camada prematura
Para selecionar a camada prematura, eles calculam a divergência de Jensen-Shannon (JSD) entre as camadas intermediárias e a final. A camada prematura é selecionada como a camada com o JSD mais alto.
No entanto, como esse processo pode ser um pouco lento, o que fazem é agrupar várias camadas para fazer menos cálculos
Contraste das previsões
Agora que temos a última camada (camada madura) e a camada prematura, podemos contrastar as previsões de ambas camadas. Para isso, calculam a probabilidade logarítmica do próximo token na camada madura e na camada prematura. Em seguida, subtraem a probabilidade logarítmica da camada prematura da da camada madura, assim dando mais importância ao conhecimento da camada madura.
Penalização por repetição
A motivação do DoLa é diminuir a importância do conhecimento linguístico das camadas inferiores e amplificar o conhecimento fático do mundo real. No entanto, isso pode levar o modelo a gerar parágrafos gramaticalmente incorretos.
Empiricamente, eles não observaram esse problema, mas encontraram que a distribuição DoLa resultante às vezes tem uma maior tendência a repetir frases geradas anteriormente, especialmente durante a geração de longas sequências de raciocínio na cadeia de pensamento.
Então, eles incluem uma penalização por repetição introduzida em Keskar et al. (2019)
com θ = 1.2
durante a decodificação.
Implementação com transformers
Vamos ver como implementar DoLa com a biblioteca transformers
do Hugging Face. Para obter mais informações sobre como aplicar DoLa com a biblioteca transformers
, você pode consultar o seguinte enlace
Primeiro nos logamos no Hub, pois vamos usar o Llama 3 8B, para poder usá-lo é necessário solicitar permissão à Meta, então para poder baixá-lo é preciso estar logado para que saibam quem está fazendo o download.
from huggingface_hub import notebook_loginnotebook_login()
Agora instanciamos o tokenizador e o modelo
from transformers import AutoTokenizer, AutoModelForCausalLM, set_seedimport torchcompute_dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16device = 'cuda' if torch.cuda.is_available() else 'cpu'checkpoints = "meta-llama/Meta-Llama-3-8B-Instruct"tokenizer = AutoTokenizer.from_pretrained(checkpoints)tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained(checkpoints, torch_dtype=compute_dtype, device_map="auto")model.config.pad_token_id = model.config.eos_token_id
Atribuímos um valor fixo de semente para a reprodutibilidade do exemplo
set_seed(42)
Geramos os tokens de entrada para o LLM
question = 'What does Darth Vader say to Luke in "The Empire Strikes Back"?'text = f"Answer with a short answer. Question: {question} Answer: "inputs = tokenizer(text, return_tensors="pt").to(model.device)
Geramos agora a entrada vanilla, ou seja, sem aplicar DoLa
generate_kwargs={"do_sample": False,"max_new_tokens": 50,"top_p": None,"temperature": None}vanilla_output = model.generate(**inputs, **generate_kwargs)print(tokenizer.batch_decode(vanilla_output[:, inputs.input_ids.shape[-1]:], skip_special_tokens=True)[0])
Setting `pad_token_id` to `eos_token_id`:128001 for open-end generation.
"No, I am your father." (Note: This is a famous misquote. The actual quote is "No, I am your father" is not in the movie. The correct quote is "No, I am your father." is not
Vemos que sabe que há um erro famoso, mas não consegue dizer a frase correta.
Agora aplicando DoLa
dola_high_output = model.generate(**inputs, **generate_kwargs, dola_layers='high', repetition_penalty=1.2)print(tokenizer.batch_decode(dola_high_output[:, inputs.input_ids.shape[-1]:], skip_special_tokens=True)[0])
Setting `pad_token_id` to `eos_token_id`:128001 for open-end generation.
"No, I am your father." (Note: This is one of the most famous lines in movie history, and it's often misquoted as "Luke, I am your father.")
Agora ele consegue dizer a frase correta e o erro famoso
Vamos fazer outro teste com outro exemplo, reinicio o notebook e uso outro modelo
from transformers import AutoTokenizer, AutoModelForCausalLM, set_seedimport torchcompute_dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16device = 'cuda' if torch.cuda.is_available() else 'cpu'checkpoints = "huggyllama/llama-7b"tokenizer = AutoTokenizer.from_pretrained(checkpoints)tokenizer.pad_token = tokenizer.eos_tokenmodel = AutoModelForCausalLM.from_pretrained(checkpoints, torch_dtype=compute_dtype, device_map="auto")model.config.pad_token_id = model.config.eos_token_id
Atribuímos um valor fixo de semente para a reprodução do exemplo
set_seed(42)
Claro, por favor, proporciona el texto markdown que deseas que traduzca al portugés.
text = "On what date was the Declaration of Independence officially signed?"inputs = tokenizer(text, return_tensors="pt").to(device)
Geramos a saída vanilla
generate_kwargs={"do_sample": False,"max_new_tokens": 50,"top_p": None,"temperature": None}vanilla_output = model.generate(**inputs, **generate_kwargs)print(tokenizer.batch_decode(vanilla_output[:, inputs.input_ids.shape[-1]:], skip_special_tokens=True)[0])
The Declaration of Independence was signed on July 4, 1776.What was the date of the signing of the Declaration of Independence?The Declaration of Independence was signed on July 4,
Como vemos, gera a saída incorretamente, pois embora seja celebrado em 4 de julho, na verdade foi assinada no 2 de agosto
Vamos a provar agora com DoLa
dola_high_output = model.generate(**inputs, **generate_kwargs, dola_layers='high', repetition_penalty=1.2)print(tokenizer.batch_decode(dola_high_output[:, inputs.input_ids.shape[-1]:], skip_special_tokens=True)[0])
July 4, 1776. This is the most well-known date in U.S. history. The day has been celebrated with parades, barbeques, fireworks and festivals for hundreds of years.
Siga sem gerar uma saída correta, então vamos indicar que apenas contraste a camada final com as camadas 28 e 30
dola_high_output = model.generate(**inputs, **generate_kwargs, dola_layers=[28,30], repetition_penalty=1.2)print(tokenizer.batch_decode(dola_high_output[:, inputs.input_ids.shape[-1]:], skip_special_tokens=True)[0])
It was officially signed on 2 August 1776, when 56 members of the Second Continental Congress put their John Hancocks to the Declaration. The 2-page document had been written in 17
Agora sim, consigo gerar a resposta correta.