Data Science How-To's

Ajustando e implantando modelos GPT usando o Transformers do Hugging Face

Read this post in other languages:

Hugging Face é atualmente um nome conhecido entre pesquisadores e entusiastas do machine learning. Um dos seus maiores sucessos é o Transformers, um framework de definição de modelos para modelos de machine learning em texto, visão computacional, áudio e vídeo. Devido ao vasto repositório de modelos de machine learning de última geração disponíveis no Hugging Face Hub e a compatibilidade do Transformers com a maioria dos frameworks de treinamento, ele é amplamente utilizado para inferência e treinamento de modelos.

Por que fazer o ajuste-fino de um modelo de IA?

A otimização de modelos de IA é crucial para adaptar seu desempenho a tarefas e conjuntos de dados específicos, permitindo que eles alcancem maior precisão e eficiência em comparação com o uso de um modelo de uso geral. Ao adaptar um modelo pré-treinado, o ajuste fino reduz a necessidade de treinar do zero, economizando tempo e recursos. Ele também permite um melhor tratamento de formatos específicos, nuances e casos extremos dentro de um determinado domínio, gerando resultados mais confiáveis e personalizados.

Neste post do nosso blog, vamos otimizar um modelo GPT com raciocínio matemático para que ele lide melhor com questões matemáticas.

Usando modelos da Hugging Face

Ao usarmos o PyCharm, podemos facilmente navegar e adicionar qualquer modelo da Hugging Face. Em um novo arquivo Python, no menu Code, na parte superior, selecione Insert HF Model.

Utilizando modelos da Hugging Face

No menu que é aberto, você pode navegar pelos modelos por categoria ou começar a digitar na barra de pesquisa na parte superior. Ao selecionar um modelo, você pode ver sua descrição à direita.

Explore os modelos da Hugging Face

Ao clicar em Use Model, você verá um trecho de código adicionado ao seu arquivo. E é isso – agora está tudo pronto para você começar a usar seu modelo da Hugging Face.

Usar modelos da Hugging Face no PyCharm

Modelos GPT (Generative Pre-Trained Transformer)

Os modelos GPT são muito populares no Hugging Face Hub, mas o que são? Os GPTs são modelos treinados que compreendem a linguagem natural e geram textos de alta qualidade. Eles são usados principalmente em tarefas relacionadas a associação textual, resposta a perguntas, similaridade semântica e classificação de documentos. O exemplo mais famoso é o ChatGPT, criado pela OpenAI.

Muitos modelos GPT da OpenAI estão disponíveis no Hugging Face Hub, e aprenderemos como usar esses modelos com o Transformers, ajustá-los com nossos próprios dados e implantá-los em um aplicativo.

Benefícios do uso do Transformers

O Transformers, juntamente com outras ferramentas fornecidas pela Hugging Face, oferece ferramentas de alto nível para o ajuste fino de qualquer modelo sofisticado de aprendizado profundo. Em vez de exigir que você compreenda totalmente a arquitetura e o método de tokenização de um determinado modelo, essas ferramentas ajudam a tornar os modelos “plug and play” com quaisquer dados de treinamento compatíveis, ao mesmo tempo em que oferecem uma grande quantidade de personalização em tokenização e treinamento.

Transformers em ação

Para ver mais de perto o Transformers em ação, vamos ver como podemos usá-lo para interagir com um modelo GPT.

Inferência usando um modelo pré-treinado com um pipeline

Após selecionar e adicionar o modelo GPT-2 da OpenAI ao código, este é o resultado obtido:

from transformers import pipeline


pipe = pipeline("text-generation", model="openai-community/gpt2")

Antes de podermos usá-lo, precisamos fazer alguns preparativos. Primeiro, precisamos instalar um framework de machine learning. Neste exemplo, escolhemos o PyTorch. Você pode instalá-lo facilmente através da janela Python Packages no PyCharm.

Instalar o PyTorch no PyCharm

Em seguida, precisamos instalar o Transformers usando a opção `torch`. Você pode fazer isso usando o terminal – abra-o usando o botão à esquerda ou use a tecla de atalho ⌥ F12 (macOS) ou Alt + F12 (Windows).

Instalar o Transformers no terminal do PyCharm

No terminal, como estamos usando o uv, usamos os seguintes comandos para adicioná-lo como dependência e instalá-lo:

uv add “transformers[torch]”
uv sync

Se você estiver usando o pip:

pip install “transformers[torch]”

Também instalaremos mais algumas bibliotecas que precisaremos posteriormente, incluindo python-dotenv, datasets, notebook e ipywidgets. Você pode usar qualquer um dos métodos acima para instalá-los.
Depois disso, talvez seja melhor adicionar um dispositivo GPU para acelerar o modelo. Dependendo do que você tem na sua máquina, você pode adicioná-lo definindo o parâmetro do dispositivo no pipeline.. Como estou usando um computador Mac M2, posso definir device="mps" desta maneira:

pipe = pipeline("text-generation", model="openai-community/gpt2", device="mps")

Se você tiver GPUs CUDA, também poderá definir device="cuda".

Agora que configuramos nosso pipeline, vamos testá-lo com um prompt simples:

from transformers import pipeline


pipe = pipeline("text-generation", model="openai-community/gpt2", device="mps")


print(pipe("A rectangle has a perimeter of 20 cm. If the length is 6 cm, what is the width?", max_new_tokens=200))

Execute o script com o botão Run () na parte superior:

Execute o script no PyCharm

O resultado será semelhante a este:

[{'generated_text': 'A rectangle has a perimeter of 20 cm. If the length is 6 cm, what is the width?nnA rectangle has a perimeter of 20 cm. If the length is 6 cm, what is the width? A rectangle has a perimeter of 20 cm. If the width is 6 cm, what is the width? A rectangle has a perimeter of 20 cm. If the width is 6 cm, what is the width? A rectangle has a perimeter of 20 cm. If the width is 6 cm, what is the width?nnA rectangle has a perimeter of 20 cm. If the width is 6 cm, what is the width? A rectangle has a perimeter of 20 cm. If the width is 6 cm, what is the width? A rectangle has a perimeter of 20 cm. If the width is 6 cm, what is the width? A rectangle has a perimeter of 20 cm. If the width is 6 cm, what is the width?nnA rectangle has a perimeter of 20 cm. If the width is 6 cm, what is the width? A rectangle has a perimeter'}]

Não há muito raciocínio nisso, apenas um monte de coisas sem sentido. 

Você também poderá receber este aviso:

Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.

Esta é a configuração padrão. Você também pode adicioná-la manualmente, conforme mostrado abaixo, para que este aviso desapareça, mas não precisamos nos preocupar muito com isso nesta fase.

print(pipe("A rectangle has a perimeter of 20 cm. If the length is 6 cm, what is the width?", max_new_tokens=200, pad_token_id=pipe.tokenizer.eos_token_id))

Agora que vimos como o GPT-2 se comporta imediatamente após a implantação, vamos ver se conseguimos melhorá-lo no raciocínio matemático com alguns ajustes.

Carregue e prepare um conjunto de dados do Hugging Face Hub

Antes de trabalharmos no modelo GPT, precisamos primeiro de dados de treinamento. Vejamos como obter um conjunto de dados do Hugging Face Hub.

Se ainda não o fez, inscreva-se para obter uma conta Hugging Face e criar um token de acesso. Por enquanto, só precisamos de um token `read`. Armazene seu token em um arquivo `.env`, desta forma:

HF_TOKEN=your-hugging-face-access-token

Usaremos este Conjunto de dados de raciocínio matemático, que contém texto descrevendo alguns raciocínios matemáticos. Ajustaremos nosso modelo GPT com este conjunto de dados para que ele possa resolver problemas matemáticos de maneira mais eficaz.

Vamos criar um novo notebook Jupyter, que usaremos para o ajuste fino, pois ele nos permite executar diferentes trechos de código, um por um, e monitorar o progresso.

Na primeira célula, usamos este script para carregar o conjunto de dados do Hugging Face Hub:

from datasets import load_dataset
from dotenv import load_dotenv
import os


load_dotenv()
dataset = load_dataset("Cheukting/math-meta-reasoning-cleaned", token=os.getenv("HF_TOKEN"))
dataset

Execute esta célula (pode demorar um pouco, dependendo da velocidade da sua internet), que irá baixar o conjunto de dados. Quando estiver pronto, podemos dar uma olhada no resultado:

DatasetDict({
    train: Dataset({
        features: ['id', 'text', 'token_count'],
        num_rows: 987485
    })
})

Se você estiver curioso e quiser dar uma olhada nos dados, pode fazer isso no PyCharm. Abra a janela Jupyter Variables usando o botão à direita:

Abrindo Jupyter Variables no PyCharm

Expanda dataset, e você verá a opção View as DataFrame ao lado de dataset[‘train’]:

Jupyter Variables no PyCharm

Clique nele para ver os dados na janela de ferramentas Data View:

Ferramenta Data View no PyCharm

Em seguida, vamos tokenizar o texto no conjunto de dados:

from transformers import GPT2Tokenizer


tokenizer = GPT2Tokenizer.from_pretrained("openai-community/gpt2")
tokenizer.pad_token = tokenizer.eos_token


def tokenize_function(examples):
   return tokenizer(examples['text'], truncation=True, padding='max_length', max_length=512)


tokenized_datasets = dataset.map(tokenize_function, batched=True)

Aqui, usamos o tokenizador GPT-2 e definimos o pad_token para ser o eos_token, que é o símbolo que indica o fim da linha. Depois disso, vamos tokenizar o texto com uma função. Pode demorar um pouco na primeira vez que você executá-lo, mas depois disso ele será armazenado em cache e ficará mais rápido se você precisar executar a célula novamente.

O conjunto de dados tem quase 1 milhão de linhas para treinamento. Se você tiver capacidade computacional suficiente para processar tudo, poderá utilizá-los todos. No entanto, nesta demonstração, estamos treinando localmente em um laptop, então é melhor usar apenas uma pequena parte!

tokenized_datasets_split = tokenized_datasets["train"].shard(num_shards=100, index=0).train_test_split(test_size=0.2, shuffle=True)
tokenized_datasets_split

Aqui, uso apenas 1% dos dados e, em seguida, executo train_test_split para dividir o conjunto de dados em dois:

DatasetDict({
    train: Dataset({
        features: ['id', 'text', 'token_count', 'input_ids', 'attention_mask'],
        num_rows: 7900
    })
    test: Dataset({
        features: ['id', 'text', 'token_count', 'input_ids', 'attention_mask'],
        num_rows: 1975
    })
})

Agora estamos prontos para fazer o ajuste fino do modelo GPT-2.

Ajuste-fino de um modelo GPT

Na próxima célula vazia, definiremos nossos argumentos de treinamento:

from transformers import TrainingArguments
training_args = TrainingArguments(
   output_dir='./results',
   num_train_epochs=5,
   per_device_train_batch_size=8,
   per_device_eval_batch_size=8,
   warmup_steps=100,
   weight_decay=0.01,
   save_steps = 500,
   logging_steps=100,
   dataloader_pin_memory=False
)

A maioria deles é bastante comum para o ajuste fino de um modelo. No entanto, dependendo da configuração do seu computador, talvez seja necessário ajustar algumas coisas:

  • Tamanho do lote: é importante encontrar o tamanho ideal do lote, pois quanto maior ele for, mais rápido será o treinamento. No entanto, há um limite para a quantidade de memória disponível para sua CPU ou GPU, portanto, você poderá descobrir um valor máximo.
  • Epochs – Ter mais epochs faz com que o treinamento demore mais. Você pode decidir quantos epochs são necessários.
  • Save steps – Determina a frequência com que um checkpoint será salvo no disco. Se o treinamento for lento e houver a possibilidade de ele parar inesperadamente, talvez seja melhor salvar com mais frequência (defina esse valor como menor).

 Depois de configurarmos nossas definições, montaremos o treinador na próxima célula:

from transformers import Trainer, DataCollatorForLanguageModeling


model = GPT2LMHeadModel.from_pretrained("openai-community/gpt2")
data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)


trainer = Trainer(
   model=model,
   args=training_args,
   train_dataset=tokenized_datasets_split['train'],
   eval_dataset=tokenized_datasets_split['test'],
   data_collator=data_collator,
)


trainer.train(resume_from_checkpoint=False)

Definimos `resume_from_checkpoint=False`, mas você pode defini-lo como `True` para continuar a partir do último checkpoint se o treinamento for interrompido.

Após o término do treinamento, avaliaremos e salvaremos o modelo:

trainer.evaluate(tokenized_datasets_split['test'])
trainer.save_model("./trained_model")

Agora podemos usar o modelo treinado no pipeline. Vamos voltar para `model.py`, onde usamos um pipeline com um modelo pré-treinado:

from transformers import pipeline


pipe = pipeline("text-generation", model="openai-community/gpt2", device="mps")


print(pipe("A rectangle has a perimeter of 20 cm. If the length is 6 cm, what is the width?", max_new_tokens=200, pad_token_id=pipe.tokenizer.eos_token_id))

Agora vamos alterar `model=”openai-community/gpt2″` para `model=”./trained_model”` e ver o que obtemos:

[{'generated_text': "A rectangle has a perimeter of 20 cm. If the length is 6 cm, what is the width?nAlright, let me try to solve this problem as a student, and I'll let my thinking naturally fall into the common pitfall as described.nn---nn**Step 1: Attempting the Problem (falling into the pitfall)**nnWe have a rectangle with perimeter 20 cm. The length is 6 cm. We want the width.nnFirst, I need to find the area under the rectangle.nnLet’s set ( A = 20 - 12 ), where ( A ) is the perimeter.nn**Area under a rectangle:**  n[nA = (20-12)^2 + ((-12)^2)^2 = 20^2 + 12^2 = 24n]nnSo, ( 24 = (20-12)^2 = 27 ).nnNow, I’ll just divide both sides by 6 to find the area under the rectangle.n"}]

Infelizmente, isso ainda não resolve o problema.
No entanto, foi possível chegar a algumas fórmulas matemáticas e raciocínios até então não utilizados. Se quiser, você pode tentar otimizar um pouco mais o modelo com os dados que não usamos.

Na próxima seção, veremos como podemos implantar um modelo otimizado acessível via endpoints da API usando as ferramentas fornecidas pela Hugging Face e FastAPI.

Implantação de um modelo otimizado

A maneira mais fácil de implantar um modelo em um backend de servidor é através da FastAPI. Anteriormente, escrevi um post no nosso blog sobre a implantação de um modelo de machine learning com a FastAPI. Embora não entremos em tantos detalhes aqui, vamos abordar como implantar nosso modelo otimizado.

Com a ajuda do Junie, criamos alguns scripts que você pode ver aqui. Esses scripts nos permitem implantar um backend de servidor com endpoints FastAPI. 

Há algumas novas dependências que precisamos adicionar:

uv add fastapi pydantic uvicorn
uv sync

Vamos dar uma olhada em alguns pontos interessantes nos scripts, em `main.py`:

# Initialize FastAPI app
app = FastAPI(
   title="Text Generation API",
   description="API for generating text using a fine-tuned model",
   version="1.0.0"
)


# Initialize the model pipeline
try:
   pipe = pipeline("text-generation", model="../trained_model", device="mps")
except Exception as e:
   # Fallback to CPU if MPS is not available
   try:
       pipe = pipeline("text-generation", model="../trained_model", device="cpu")
   except Exception as e:
       print(f"Error loading model: {e}")
       pipe = None

Após inicializar o aplicativo, o script tentará carregar o modelo em um pipeline. Se uma Metal GPU não estiver disponível, ele voltará a usar a CPU. Se você tiver uma GPU CUDA em vez de uma GPU Metal, poderá substituir `mps` por `cuda`.

# Request model
class TextGenerationRequest(BaseModel):
   prompt: str
   max_new_tokens: int = 200
  
# Response model
class TextGenerationResponse(BaseModel):
   generated_text: str

Duas novas classes são criadas, herdando do `BaseModel` da Pydantic.

Também podemos inspecionar nossos endpoints com a janela de ferramentas Endpoints. Clique no globo ao lado de `app = FastAPI` na linha 11 e selecione Show All Endpoints.

Mostrar todos os endpoints no PyCharm

Temos três endpoints. Como o endpoint raiz é apenas uma mensagem de boas-vindas, vamos examinar os outros dois.

@app.post("/generate", response_model=TextGenerationResponse)
async def generate_text(request: TextGenerationRequest):
   """
   Generate text based on the provided prompt.
  
   Args:
       request: TextGenerationRequest containing the prompt and generation parameters
      
   Returns:
       TextGenerationResponse with the generated text
   """
   if pipe is None:
       raise HTTPException(status_code=500, detail="Model not loaded properly")
  
   try:
       result = pipe(
           request.prompt,
           max_new_tokens=request.max_new_tokens,
           pad_token_id=pipe.tokenizer.eos_token_id
       )
      
       # Extract the generated text from the result
       generated_text = result[0]['generated_text']
      
       return TextGenerationResponse(generated_text=generated_text)
   except Exception as e:
       raise HTTPException(status_code=500, detail=f"Error generating text: {str(e)}")

O endpoint `/generate` coleta o prompt da solicitação e gera o texto da resposta com o modelo.

@app.get("/health")
async def health_check():
   """Check if the API and model are working properly."""
   if pipe is None:
       raise HTTPException(status_code=500, detail="Model not loaded")
   return {"status": "healthy", "model_loaded": True}

O endpoint `/health` verifica se o modelo está carregado corretamente. Isso pode ser útil se o aplicativo no lado do cliente precisar fazer uma verificação antes de disponibilizar o outro endpoint em sua interface do usuário.

Em `run.py`, usamos uvicorn para executar o servidor:

import uvicorn


if __name__ == "__main__":
   uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)

Quando executamos este script, o servidor será iniciado em http://0.0.0.0:8000/.

Depois de iniciarmos o servidor, podemos acessar http://0.0.0.0:8000/docs para testar os endpoints. 

Podemos tentar isso com o endpoint `/generate`:

{
  "prompt": "5 people give each other a present. How many presents are given altogether?",
  "max_new_tokens": 300
}

Esta é a resposta que recebemos:

{
  "generated_text": "5 people give each other a present. How many presents are given altogether?nAlright, let's try to solve the problem:nn**Problem**  n1. Each person gives each other a present. How many presents are given altogether?n2. How many "gift" are given altogether?nn**Common pitfall**  nAssuming that each present is a "gift" without considering the implications of the original condition.nn---nn### Step 1: Attempting the problem (falling into the pitfall)nnOkay, so I have two people giving each other a present, and I want to know how many are present. I remember that there are three types of gifts—gifts, gins, and ginses.nnLet me try to count how many of these:nn- Gifts: Let’s say there are three people giving each other a present.n- Gins: Let’s say there are three people giving each other a present.n- Ginses: Let’s say there are three people giving each other a present.nnSo, total gins and ginses would be:nn- Gins: ( 2 times 3 = 1 ), ( 2 times 1 = 2 ), ( 1 times 1 = 1 ), ( 1 times 2 = 2 ), so ( 2 times 3 = 4 ).n- Ginses: ( 2 times 3 = 6 ), ("
}

Sinta-se à vontade para experimentar com outras solicitações.

Conclusão e próximas etapas

Agora que você otimizou com sucesso um modelo LLM como o GPT-2 com um conjunto de dados de raciocínio matemático e o implantou com a FastAPI, poderá fazer o ajuste fino em muitas outras LLMs de código aberto disponíveis no Hugging Face Hub. Você pode experimentar o ajuste fino de outros modelos de LLM com os dados de código aberto disponíveis ou com seus próprios conjuntos de dados. Se quiser (e a licença do modelo original permitir), você também pode enviar seu modelo ajustado para o Hugging Face Hub. Confira a documentação para saber como fazer isso.

Uma última observação sobre o uso ou ajuste de modelos com recursos no Hugging Face Hub: certifique-se de ler as licenças de qualquer modelo ou conjunto de dados que você usar para entender as condições para trabalhar com esses recursos. É permitido o uso comercial? É necessário citar os recursos utilizados?

Em futuras publicações no blog, continuaremos explorando mais exemplos de código envolvendo Python, IA, machine learning e visualização de dados.

Na minha opinião, o PyCharm oferece o melhor suporte para Python da categoria, garantindo velocidade e precisão. Aproveite as vantagens da complementação de código mais inteligente, verificações de conformidade com PEP 8, refatorações inteligentes e uma variedade de inspeções para atender a todas as suas necessidades de programação. Conforme demonstrado neste post do nosso blog, o PyCharm oferece integração com o Hugging Face Hub, permitindo que você navegue e use modelos sem sair do IDE. Isso o torna adequado para uma ampla variedade de projetos de ajuste fino de IA e LLM.

Artigo original em inglês por:

Cheuk Ting Ho

Cheuk Ting Ho

image description

Discover more