Me siga no X | Me siga no LinkedIn | Apoie a Newsletter | Solicite uma consultoria
Embora os recentes LLMs sejam treinados na escala de bilhões (ou até trilhões de tokens), quando utilizamos estes LLMs, seja via SaaS, como ChatGPT, seja como biblioteca open-source, como o LLaMA, estamos limitados gerar conteúdo com base nos dados que estes LLMs conhecem.
Por exemplo, a versão mais recente do ChatGPT foi treinada com dados de até Setembro de 2021. Logo, se fizermos perguntas sobre como utilizar um novo produto, como a StackSpot, ou sobre o campeão do campeonato paraense de futebol de 2023, possivelmente receberemos uma resposta vaga, indicando que o modelo desconhece aquela resposta.
Para suprir essas limitações, pesquisadores criaram técnicas para aproveitar um modelo existente e adapta-lo para dados, até então, desconhecidos. Essa tarefa de adaptação para novos dados é chamada de fine-tuning (ou ajuste fino).
Durante o processo de treinamento em grandes conjuntos de dados, o modelo aprende recursos “gerais”. No entanto, por falta de dados, esses modelos (que já foram treinados anteriormente, ou seja, não pré-treinados) geralmente tendem a não ser especializados para tarefas mais específicas, também conhecidas como downstream tasks.
Ao realizar o fine-tuning, em um conjunto de dados que, de fato, representa o problema real que você deseja resolver, o modelo adapta-se a características específicas da nova tarefa, melhorando sua capacidade de generalização, além de frequentemente diminuir as chance de overfitting.
Motivos para utilizar fine-tuning
Poucas pessoas, empresas ou governos são capazes de treinar um LLM robusto, inteiramente do zero. Isso acontece por dois principais motivos:
Primeiro pois é relativamente raro criar e manter um conjunto de dados de tamanho suficiente (frequentemente na escala de bilhões de tokens).
Segundo pois o custo de treinamento de um único modelo pode chegar na casa das dezenas de milhões de dólares.
Utilizar de modelos pre-treinados incorre em redução de tempo e dinheiro.
Fine-tuning é um fork
Podemos —grosseiramente— fazer a analogia de que um fine-tuning de um modelo se assemelha com um fork de um projeto de software livre.
Quando criamos um fork, estamos interessados em adaptar o código do projeto original, para uma necessidade específica que ainda não havia sido prevista. Dessa forma, o fork derivante tende a ser mais especializado para esta nova tarefa.
Um exemplo seria o kernel do sistema operacional Linux. O kernel pode ser entendido como nosso modelo pre-treinado. Por outro lado, o Android, que é um fork do Linux para dispositivos móveis, pode ser visto como um “fine-tuning” do Linux, uma vez que o Linux não foi inicialmente “treinado” para funcionar em tablets, por exemplo.
Deixando analogias de lado, existem também casos populares de fine-tuning no mundo de modelos de aprendizado de máquina.
Por exemplo, o modelo BERT (Bidirectional Encoder Representations from Transformers), introduzido pelo Google em 2018, se tornou amplamente conhecido pela amplitude de tarefas que se desempenha com alta acurácia, como sumarização de textos, análise de sentimentos, respostas de perguntas, etc. Para se destacar nestas tarefas, o BERT foi treinado com 3.3 bilhões de palavras, advindas da Wikipédia (~2.5 bilhões de palavras) e de livros (~800 milhões de palavras).
Embora o BERT tenha bom desempenho em tarefas que envolvem geração de textos em linguagem natural, o BERT não desempenha tão bem em tarefas que envolvem geração de código de programas JavaScript, por exemplo.
Isso acontece pois este modelo não foi previamente treinado para esse tipo de tarefa.
Sabendo desta limitação, em 2020, pesquisadores da Microsoft introduziram o CodeBERT, que é um “fork” do BERT com o objetivo de ajusta-lo para tarefas que envolvam código. O CodeBERT utiliza a arquitetura do BERT, mas é treinado em um extenso conjunto de projetos do GitHub, focado nas linguagens Python, Java, JavaScript, PHP, Ruby, e Go.
Tudo começa com uma rede neural
LLMs são fundamentalmente modelos baseados em redes neurais1. Uma das principais características de uma rede neural é a sua divisão por camadas.
As camadas são organizadas em uma sequência, formando um fluxo de informações da camada de entrada até a camada de saída. Há basicamente três tipos de camadas em uma rede neural:
Camada de entrada (input layer): É a camada inicial que recebe os dados de entrada e os fornece diretamente para a próxima camada.
Camadas ocultas (hidden layers): São camadas localizadas entre a camada de entrada e a camada de saída. Elas realizam cálculos intermediários. A quantidade de camadas ocultas pode variar de acordo com a complexidade do problema.
Camada de saída (output layer): É a camada final da rede neural que as previsões para a tarefa específica.
Cada camada é composta por um conjunto de unidades chamadas neurônios, que realizam usando pesos e passa os resultados para a próxima camada.
A arquitetura de uma rede neural é determinada pelo número de camadas ocultas, a ordem das camadas oculta, o número de neurônios em cada camada, a quantidade de conexões entre os neurônios, etc.
Como funciona o fine-tuning?
Em uma visão geral, o processo de fine-tuning funciona da seguinte forma:
Seleção da arquitetura: Primeiramente, é importante selecionar a arquitetura do modelo pré-treinado adequada para a tarefa específica. Por exemplo, o modelo de transformers tem um conjunto de arquitetura de codificador/decodificador muito utilizado para problemas de tradução.
Congelamento de camadas: Normalmente, durante o fine-tuning, as primeiras camadas de um modelo pré-treinado (também conhecidas como “parte base” ou “espinha dorsal”) são congeladas. Isso é, seus pesos não serão atualizados durante o treinamento para a tarefa específica. Congelar as camadas iniciais permite que o modelo preserve as representações aprendidas durante o pré-treinamento.
Ajuste dos pesos: Em seguida, as camadas adicionais, também conhecidas como “cabeça” do modelo, são treinadas. Os pesos dessas camadas adicionais são primariamente ajustados com base nos exemplos rotulados do novo conjunto de dados2. Dessa forma, as camadas mais ao final do modelo tendem a aprender recursos mais específicos, enquanto as camadas do início geralmente aprendem recursos mais gerais.
Otimização: Utilizando os exemplos rotulados, o modelo pode fazer previsões com base nos pesos atuais. Em seguida, utiliza-se do algoritmo backpropagation para atualizar os parâmetros da rede neural.
Avaliação e ajustes: Por fim, o modelo é avaliado em um conjunto de dados de teste para verificar seu desempenho. Com base nos resultados, ajustes adicionais podem ser feitos para melhorar ainda mais o desempenho.
O processo de fine-tuning é iterativo e pode envolver várias rodadas de treinamento e ajustes para encontrar o equilíbrio ideal entre as representações gerais pré-treinadas e as especificidades da tarefa alvo.
Implementando o fine-tuning com o LangChain
Como já sabemos, fine-tuning nos ajuda a tonar um modelo mais específico para uma atividade até então desconhecida.
Como sabemos, o modelo do ChatGPT foi treinado com dados disponíveis até Setembro de 2021. Será que poderíamos fazer um fine-tuning desse modelo para que ele possa tratar dados mais recentes?
Podemos, de maneira bem simples, graças a biblioteca LangChain, que é uma ferramenta emergente para a criação de produtos baseados em LLMs.
Internamente, o LangChain faz uma integração com a API do OpenAI. Para isso, precisamos inicialmente instalar algumas bibliotecas:
!pip install langchain openai unstructured chromadb tiktoken
Antes de tudo, precisamos disponibilizar acesso ao ChatGPT, que é feito através do compartilhamento da sua chave pública, que pode ser criada diretamente no site da OpenAI.
import os
os.environ["OPENAI_API_KEY"] = "sua chave na OpenAI"
Em seguida, precisamos avisar ao LangChain qual novo documento queremos utilizar. O LangChain consegue processar diversos tipos de formatos, como HTML, áudio e vídeo. Nesse texto, utilizamos um pre-processador de PDFs online:
from langchain.document_loaders import OnlinePDFLoader
loader = OnlinePDFLoader('https://arxiv.org/pdf/2206.10655.pdf')
O documento PDF que passamos por parâmetro para o objeto OnlinePDFLoader
, é um artigo científico que desenvolvemos em 2022. Logo, desconhecido para o ChatGPT.
Podemos navegar um pouco pelo documento, imprimindo, por exemplo, o conteúdo da primeira página usando a instrução print(loader.load()[0])
.
Poderíamos ainda realizar algumas atividades adicionais de pre-processamento, como remoção de caracteres especiais ou espaços em branco; mas, neste momento, focaremos no mais simples e funcional possível.
Após processar o documento, basta pedir para o LangChain indexa-lo:
from langchain.indexes import VectorstoreIndexCreator
index = VectorstoreIndexCreator().from_loaders([loader])
Quando o processo de indexação estiver pronto, podemos fazer uma query em linguagem natural com o LLM para extrair informações dos documentos indexados.
query = "CDD é uma boa ideia para avaliar legibilidade de código?"
index.query(query)
Com essa query, tivemos uma resposta concisa e bem relacionada com o assunto do artigo: “Sim, os resultados da nossa pesquisa fornecem evidências iniciais de que o CDD pode ser uma abordagem interessante para o design de software. Sete dos dez refatoramentos guiados pelo CDD foram mais legíveis do que seus pares, enquanto apenas um dos refatoramentos guiados pelo CDD teve melhor desempenho de legibilidade, avaliado por modelos de legibilidade de ponta.”
Conclusão
Técnicas de fine-tuning ajudam a tonar LLMs mais especializados, podendo ser utilizados em contextos que não foram previstos inicialmente.
Embora a implementação das etapas de fine-tuning seja tecnicamente desafiadoras, ferramentas como LangChain reduzem drasticamente o tempo e esforço necessário. Com poucas linhas de código, é possível ajudar o ChatGPT a aprender sobre assuntos posteriores a sua data de treino.
O código apresentado nesse texto está disponível neste link, licenciado sob MIT.
Para uma introdução técnica e concisa sobre redes neurais, veja este texto de blog.
Em trabalhos mais recentes, pesquisadores tem utilizado de técnicas de “self-supervised learning”, o que significa que os exemplos rotulados usadas são inferidos automaticamente dos dados brutos, sem necessidade de anotação manual.