LoRA – low rank adaptation of large language models

LoRA – low rank adaptation of large language models LoRA – low rank adaptation of large language models

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.

O aumento do tamanho dos modelos de linguagem faz com que sejam cada vez mais caros de treinar, pois é necessário cada vez mais VRAM para armazenar todos os seus parâmetros e os gradientes derivados do treinamento.

No artigo LoRA - Low rank adaption of large language models propõem congelar os pesos do modelo e treinar duas matrizes chamadas A e B, reduzindo significativamente o número de parâmetros que precisam ser treinados.

LoRA

Vamos a ver como se faz isso

Explicação de LoRAlink image 59

Atualização de pesos em uma rede neurallink image 60

Para entender como funciona LoRA, primeiro temos que lembrar o que ocorre quando treinamos um modelo. Voltemos à parte mais básica do deep learning, temos uma camada densa de uma rede neural que é definida como:

y = Wx + b

Onde W é a matriz de pesos e b é o vetor de vieses.

Para simplificar, vamos a supor que não há viés, portanto ficaria assim

y = Wx

Suponhamos que para uma entrada x queremos que tenha uma saída ŷ

  • Primeiro, o que fazemos é calcular a saída que obtemos com nosso valor atual de pesos $W$, ou seja, obtemos o valor $y$
  • Em seguida, calculamos o erro que existe entre o valor de $y$ que obtivemos e o valor que queríamos obter $ŷ$. A esse erro chamamos de $loss$, e o calculamos com alguma função matemática, agora não importa qual.
  • Calculamos o gradiente (a derivada) do erro $loss$ em relação à matriz de pesos $W$, ou seja, $\Delta W = \frac{dloss}{dW}$
  • Atualizamos os pesos $W$ subtraindo de cada um dos seus valores o valor do gradiente multiplicado por um fator de aprendizado $\alpha$, ou seja, $W = W - \alpha \Delta W$

LoRAlink image 61

Os autores de LoRA propõem que a matriz de pesos W pode ser decomposta em

W \sim W + \Delta W

Assim, ao congelar a matriz W e treinar apenas a matriz \Delta W, pode-se obter um modelo que se adeque a novos dados sem ter que retreinar todo o modelo.

Mas você pode pensar que \Delta W é uma matriz do mesmo tamanho de W, portanto nada foi ganho, mas aqui os autores se baseiam em Aghajanyan et al. (2020), um artigo no qual eles demonstraram que, embora os modelos de linguagem sejam grandes e seus parâmetros sejam matrizes com dimensões muito grandes, para adaptá-los a novas tarefas não é necessário alterar todos os valores das matrizes, mas sim alterar alguns poucos valores, o que tecnicamente é chamado de adaptação de baixa classificação. Daí o nome LoRA (Low Rank Adaptation).

Congelamos o modelo e agora queremos treinar a matriz \Delta W. Suponhamos que tanto W quanto \Delta W são matrizes de tamanho 20 × 10, portanto temos 200 parâmetros treináveis.

Agora suponhamos que a matriz \Delta W pode ser decomposta no produto de duas matrizes A e B, ou seja

\Delta W = A · B

Para que esta multiplicação ocorra, os tamanhos das matrizes A e B têm que ser 20 × n e n × 10, respectivamente. Suponhamos que n = 5, portanto A seria de tamanho 20 × 5, ou seja, 100 parâmetros, e B de tamanho 5 × 10, ou seja, 50 parâmetros, por isso teríamos 100+50=150 parâmetros treináveis. Já temos menos parâmetros treináveis do que antes.

Agora suponhamos que W na verdade é uma matriz de tamanho 10.000 × 10.000, portanto teríamos 100.000.000 parâmetros treináveis, mas se decompor \Delta W em A e B com n = 5, teríamos uma matriz de tamanho 10.000 × 5 e outra de tamanho 5 × 10.000, portanto teríamos 50.000 parâmetros de uma e outros 50.000 parâmetros de outra, no total 100.000 parâmetros treináveis, ou seja, reduzimos o número de parâmetros 1000 vezes

Já pode ver o poder da LoRA, quando se têm modelos muito grandes, o número de parâmetros treináveis pode ser reduzido drasticamente.

Se voltarmos a ver a imagem da arquitetura de LoRA, a entenderemos melhor.

LoRA adapt

Mas parece ainda melhor, a economia no número de parâmetros treináveis com esta imagem

LoRA matmul

Implementação de LoRA em transformerslink image 62

Como os modelos de linguagem são implementações de transformers, vamos ver como se implementa LoRA em transformers. Na arquitetura transformer há camadas lineares nas matrizes de atenção Q, K e V, e nas camadas feedforward, por isso pode-se aplicar LoRA a todas essas camadas lineares. No paper, eles falam que, por simplicidade, o aplicam apenas às camadas lineares das matrizes de atenção Q, K e V.

Estas camadas têm um tamanho dmodel × dmodel, onde dmodel é a dimensão de embedding do modelo

Tamanho do intervalo rlink image 63

Para poder ter esses benefícios, o tamanho do intervalo r deve ser menor que o tamanho das camadas lineares. Como dissemos que só o implementavam nas camadas lineares de atenção, que têm um tamanho dmodel × dmodel, o tamanho do intervalo r deve ser menor que dmodel.

Inicialização das matrizes A e Blink image 64

As matrizes A e B são inicializadas com uma distribuição gaussiana aleatória para A e zero para B, assim o produto de ambas as matrizes será zero no início, ou seja

\Delta W = A · B = 0

Influência de LoRA por meio do parâmetro $\alpha$link image 65

Por último, na implementação de LoRA, adiciona-se um parâmetro α para estabelecer o grau de influência de LoRA no treinamento. É similar à taxa de aprendizado (learning rate) no fine tuning normal, mas neste caso é usado para estabelecer a influência de LoRA no treinamento. Dessa forma, a fórmula de LoRA ficaria assim

W = W + α \Delta W = W + α A · B

Vantagens do LoRAlink image 66

Agora que entendemos como isso funciona, vamos ver as vantagens desse método.

  • Redução do número de parâmetros treináveis. Como vimos, o número de parâmetros treináveis é reduzido drasticamente, o que torna o treinamento muito mais rápido e requer menos VRAM, economizando muitos custos.* Adaptadores em produção. Podemos ter em produção um único modelo de linguagem e vários adaptadores, cada um para uma tarefa diferente, em vez de ter vários modelos treinados para cada tarefa, o que economiza custos de armazenamento e computação. Além disso, este método não precisa adicionar latência na inferência porque a matriz de pesos original pode ser fusionada com o adaptador, já que vimos que $W \sim W + \Delta W = W + A \cdot B$, portanto, o tempo de inferência seria o mesmo que usar o modelo de linguagem original.
  • Compartilhar adaptadores. Se treinarmos um adaptador, podemos compartilhar apenas o adaptador. Isso significa que, em produção, todos podem ter o modelo original e cada vez que treinamos um adaptador, compartilhamos apenas o adaptador, portanto, como seriam compartilhadas matrizes muito menores, o tamanho dos arquivos compartilhados seria muito menor

Implementação de LoRA em um LLMlink image 67

Vamos a repetir o código de treinamento do post Fine tuning SLMs, em específico o treinamento para classificação de texto com as bibliotecas da Hugging Face, mas desta vez vamos fazer isso com LoRA. No post anterior, usamos um batch size de 28 para o loop de treinamento e de 40 para o de avaliação, no entanto, como agora não vamos treinar todos os pesos do modelo, mas apenas as matrizes de LoRA, poderemos usar um batch size maior.

Login no Hublink image 68

Nós nos logamos para fazer o upload do modelo para o Hub

	
from huggingface_hub import notebook_login
notebook_login()
Copy

Conjunto de Dadoslink image 69

Baixamos o conjunto de dados que vamos utilizar, que é um conjunto de dados de avaliações do Amazon

	
from datasets import load_dataset
dataset = load_dataset("mteb/amazon_reviews_multi", "en")
dataset
Copy
	
DatasetDict({
train: Dataset({
features: ['id', 'text', 'label', 'label_text'],
num_rows: 200000
})
validation: Dataset({
features: ['id', 'text', 'label', 'label_text'],
num_rows: 5000
})
test: Dataset({
features: ['id', 'text', 'label', 'label_text'],
num_rows: 5000
})
})

Criamos um subconjunto se você quiser testar o código com um conjunto de dados menor. No meu caso, usarei 100% do conjunto de dados.

	
percentage = 1
subset_dataset_train = dataset['train'].select(range(int(len(dataset['train']) * percentage)))
subset_dataset_validation = dataset['validation'].select(range(int(len(dataset['validation']) * percentage)))
subset_dataset_test = dataset['test'].select(range(int(len(dataset['test']) * percentage)))
subset_dataset_train, subset_dataset_validation, subset_dataset_test
Copy
	
(Dataset({
features: ['id', 'text', 'label', 'label_text'],
num_rows: 200000
}),
Dataset({
features: ['id', 'text', 'label', 'label_text'],
num_rows: 5000
}),
Dataset({
features: ['id', 'text', 'label', 'label_text'],
num_rows: 5000
}))

Vemos uma amostra

	
from random import randint
idx = randint(0, len(subset_dataset_train))
subset_dataset_train[idx]
Copy
	
{'id': 'en_0388304',
'text': 'The N was missing from on The N was missing from on',
'label': 0,
'label_text': '0'}

Obtemos o número de classes, para obter o número de classes usamos dataset['train'] e não subset_dataset_train porque se o subconjunto for muito pequeno é possível que não haja exemplos com todas as possíveis classes do conjunto de dados original

	
num_classes = len(dataset['train'].unique('label'))
num_classes
Copy
	
5

Criamos uma função para criar o campo label no dataset. O dataset baixado tem o campo labels, mas a biblioteca transformers precisa que o campo seja chamado de label e não labels.

	
def set_labels(example):
example['labels'] = example['label']
return example
Copy

Aplicamos a função ao conjunto de dados

	
subset_dataset_train = subset_dataset_train.map(set_labels)
subset_dataset_validation = subset_dataset_validation.map(set_labels)
subset_dataset_test = subset_dataset_test.map(set_labels)
subset_dataset_train, subset_dataset_validation, subset_dataset_test
Copy
	
(Dataset({
features: ['id', 'text', 'label', 'label_text', 'labels'],
num_rows: 200000
}),
Dataset({
features: ['id', 'text', 'label', 'label_text', 'labels'],
num_rows: 5000
}),
Dataset({
features: ['id', 'text', 'label', 'label_text', 'labels'],
num_rows: 5000
}))

Voltamos a ver um exemplo

	
subset_dataset_train[idx]
Copy
	
{'id': 'en_0388304',
'text': 'The N was missing from on The N was missing from on',
'label': 0,
'label_text': '0',
'labels': 0}

Tokenizadorlink image 70

Implementamos o tokenizador. Para que não dê erro, atribuímos o token de end of string ao token de padding

	
from transformers import AutoTokenizer
checkpoint = "openai-community/gpt2"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
tokenizer.pad_token = tokenizer.eos_token
Copy

Criamos uma função para tokenizar o dataset

	
def tokenize_function(examples):
return tokenizer(examples["text"], padding="max_length", truncation=True, max_length=768, return_tensors="pt")
Copy

Aplicamos a função ao conjunto de dados e, de passagem, eliminamos as colunas que não precisamos

	
subset_dataset_train = subset_dataset_train.map(tokenize_function, batched=True, remove_columns=['text', 'label', 'id', 'label_text'])
subset_dataset_validation = subset_dataset_validation.map(tokenize_function, batched=True, remove_columns=['text', 'label', 'id', 'label_text'])
subset_dataset_test = subset_dataset_test.map(tokenize_function, batched=True, remove_columns=['text', 'label', 'id', 'label_text'])
subset_dataset_train, subset_dataset_validation, subset_dataset_test
Copy
	
(Dataset({
features: ['labels', 'input_ids', 'attention_mask'],
num_rows: 200000
}),
Dataset({
features: ['labels', 'input_ids', 'attention_mask'],
num_rows: 5000
}),
Dataset({
features: ['labels', 'input_ids', 'attention_mask'],
num_rows: 5000
}))

Voltamos a ver uma amostra, mas neste caso só vemos as keys

	
subset_dataset_train[idx].keys()
Copy
	
dict_keys(['labels', 'input_ids', 'attention_mask'])

Modelolink image 71

Instanciamos o modelo. Também, para que não nos dê erro, atribuímos o token de end of string ao token de padding

	
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=num_classes)
model.config.pad_token_id = model.config.eos_token_id
Copy
	
Some weights of GPT2ForSequenceClassification were not initialized from the model checkpoint at openai-community/gpt2 and are newly initialized: ['score.weight']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.

Como já vimos no post Fine tuning SLMs obtemos um warning que diz que algumas camadas não foram inicializadas. Isso acontece porque, neste caso, como é um problema de classificação e quando instanciamos o modelo dissemos que queremos que seja um modelo de classificação com 5 classes, a biblioteca removeu a última camada e a substituiu por uma com 5 neurônios na saída. Se não entende bem isso, veja o post que cito, que está melhor explicado.

LoRAlink image 72

Antes de implementar LoRA, vemos o número de parâmetros treináveis que tem o modelo

	
total_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"Total trainable parameters before: {total_params:,}")
Copy
	
Total trainable parameters before: 124,443,648

Vemos que tem 124M de parâmetros treináveis. Agora vamos congelá-los.

	
for param in model.parameters():
param.requires_grad = False
total_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"Total trainable parameters after: {total_params:,}")
Copy
	
Total trainable parameters after: 0

Após o congelamento, não há mais parâmetros treináveis.

Vamos ver como é o modelo antes de aplicar LoRA

	
model
Copy
	
GPT2ForSequenceClassification(
(transformer): GPT2Model(
(wte): Embedding(50257, 768)
(wpe): Embedding(1024, 768)
(drop): Dropout(p=0.1, inplace=False)
(h): ModuleList(
(0-11): 12 x GPT2Block(
(ln_1): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
(attn): GPT2Attention(
(c_attn): Conv1D()
(c_proj): Conv1D()
(attn_dropout): Dropout(p=0.1, inplace=False)
(resid_dropout): Dropout(p=0.1, inplace=False)
)
(ln_2): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
(mlp): GPT2MLP(
(c_fc): Conv1D()
(c_proj): Conv1D()
(act): NewGELUActivation()
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
(ln_f): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
)
(score): Linear(in_features=768, out_features=5, bias=False)
)

Primeiro criamos a camada LoRA layer.

Tem que herdar de torch.nn.Module para poder atuar como uma camada de uma rede neural

No método _init_ criamos as matrizes A e B inicializadas como explicado anteriormente, a matriz A com uma distribuição gaussiana aleatória e a matriz B com zeros. Também criamos os parâmetros rank e alpha.

No método forward calculamos LoRA conforme explicado.

	
import torch
class LoRALayer(torch.nn.Module):
def __init__(self, in_dim, out_dim, rank, alpha):
super().__init__()
self.A = torch.nn.Parameter(torch.empty(in_dim, rank))
torch.nn.init.kaiming_uniform_(self.A, a=torch.sqrt(torch.tensor(5.)).item()) # similar to standard weight initialization
self.B = torch.nn.Parameter(torch.zeros(rank, out_dim))
self.alpha = alpha
def forward(self, x):
x = self.alpha * (x @ self.A @ self.B)
return x
Copy

Agora criamos uma classe linear com LoRA.

Assim como antes, herde de torch.nn.Module para que possa atuar como uma camada de uma rede neural.

No método init criamos uma variável com a camada linear original da rede e criamos outra variável com a nova camada LoRA que havíamos implementado anteriormente.

No método forward somamos as saídas da camada linear original e da camada LoRA.

	
class LoRALinear(torch.nn.Module):
def __init__(self, linear, rank, alpha):
super().__init__()
self.linear = linear
self.lora = LoRALayer(
linear.in_features, linear.out_features, rank, alpha
)
def forward(self, x):
return self.linear(x) + self.lora(x)
Copy

Por último, criamos uma função que substitua as camadas lineares pela nova camada linear com LoRA que criamos. O que ela faz é que se encontrar uma camada linear no modelo, a substitui pela camada linear com LoRA; caso contrário, aplica a função dentro das subcamadas da camada.

	
def replace_linear_with_lora(model, rank, alpha):
for name, module in model.named_children():
if isinstance(module, torch.nn.Linear):
# Replace the Linear layer with LinearWithLoRA
setattr(model, name, LoRALinear(module, rank, alpha))
else:
# Recursively apply the same function to child modules
replace_linear_with_lora(module, rank, alpha)
Copy

Aplicamos a função ao modelo para substituir as camadas lineares do modelo pela nova camada linear com LoRA

	
rank = 16
alpha = 16
replace_linear_with_lora(model, rank=rank, alpha=alpha)
Copy

Vemos agora o número de parâmetros treináveis

	
total_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"Total trainable LoRA parameters: {total_params:,}")
Copy
	
Total trainable LoRA parameters: 12,368

Passamos de 124M de parâmetros treináveis para 12k parâmetros treináveis, ou seja, reduzimos o número de parâmetros treináveis 10.000 vezes!

Voltamos a ver o modelo

	
model
Copy
	
GPT2ForSequenceClassification(
(transformer): GPT2Model(
(wte): Embedding(50257, 768)
(wpe): Embedding(1024, 768)
(drop): Dropout(p=0.1, inplace=False)
(h): ModuleList(
(0-11): 12 x GPT2Block(
(ln_1): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
(attn): GPT2Attention(
(c_attn): Conv1D()
(c_proj): Conv1D()
(attn_dropout): Dropout(p=0.1, inplace=False)
(resid_dropout): Dropout(p=0.1, inplace=False)
)
(ln_2): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
(mlp): GPT2MLP(
(c_fc): Conv1D()
(c_proj): Conv1D()
(act): NewGELUActivation()
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
(ln_f): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
)
(score): LoRALinear(
(linear): Linear(in_features=768, out_features=5, bias=False)
(lora): LoRALayer()
)
)

Vamos a compará-los camada por camada

Modelo original Modelo com LoRA
GPT2ForSequenceClassification( GPT2ForSequenceClassification(
(transformer): GPT2Model( (transformer): GPT2Model(
(wte): Embedding(50257, 768) (wte): Embedding(50257, 768)
(wpe): Embedding(1024, 768) (wpe): Embedding(1024, 768)
(drop): Dropout(p=0.1, inplace=False) (drop): Dropout(p=0.1, inplace=False)
(h): ModuleList( (h): ModuleList(
(0-11): 12 x GPT2Block( (0-11): 12 x GPT2Block(
(ln_1): LayerNorm((768,), eps=1e-05, elementwise_affine=True) (ln_1): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
(attn): GPT2Attention( (attn): GPT2Attention(
(c_attn): Conv1D() (c_attn): Conv1D()
(c_proj): Conv1D() (c_proj): Conv1D()
(attn_dropout): Dropout(p=0.1, inplace=False) (attn_dropout): Dropout(p=0.1, inplace=False)
(resid_dropout): Dropout(p=0.1, inplace=False) (resid_dropout): Dropout(p=0.1, inplace=False)
) )
(ln_2): LayerNorm((768,), eps=1e-05, elementwise_affine=True) (ln_2): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
(c_fc): Conv1D() (c_fc): Conv1D()
(c_proj): Conv1D() (c_proj): Conv1D()
(act): NewGELUActivation() (act): NewGELUActivation()
(dropout): Dropout(p=0.1, inplace=False) (dropout): Dropout(p=0.1, inplace=False)
) )
) )
) )
(ln_f): LayerNorm((768,), eps=1e-05, elementwise_affine=True) (ln_f): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
) )
(pontuação): LoRALinear()
(score): Linear(in_features=768, out_features=5, bias=False) (linear): Linear(in_features=768, out_features=5, bias=False)
(lora): LoRAL层()

Nota: Parece que hubo un error en la traducción del término "LoRALayer" al portugués. La traducción correcta sería:

(lora): LoRALayer()
) )

Vemos que são iguais, exceto no final, onde no modelo original havia uma camada linear normal e no modelo com LoRA há uma camada LoRALinear que dentro possui a camada linear do modelo original e uma camada LoRALayer

Treinamentolink image 73

Uma vez instanciado o modelo com LoRA, vamos treiná-lo como sempre

Como dissem, no post Fine tuning SLMs usamos um batch size de 28 para o loop de treinamento e de 40 para o de avaliação, enquanto agora que há menos parâmetros treináveis podemos usar um batch size maior.

Por que isso acontece? Quando se treina um modelo, é necessário armazenar na memória da GPU o modelo e os gradientes desse modelo, então tanto com LoRA quanto sem LoRA, o modelo precisa ser armazenado igualmente, mas no caso do LoRA, apenas os gradientes de 12k parâmetros são armazenados, enquanto que sem LoRA, os gradientes de 128M de parâmetros são armazenados, por isso com LoRA é necessário menos memória da GPU, permitindo o uso de um batch size maior.

	
from transformers import TrainingArguments
metric_name = "accuracy"
model_name = "GPT2-small-LoRA-finetuned-amazon-reviews-en-classification"
LR = 2e-5
BS_TRAIN = 400
BS_EVAL = 400
EPOCHS = 3
WEIGHT_DECAY = 0.01
training_args = TrainingArguments(
model_name,
eval_strategy="epoch",
save_strategy="epoch",
learning_rate=LR,
per_device_train_batch_size=BS_TRAIN,
per_device_eval_batch_size=BS_EVAL,
num_train_epochs=EPOCHS,
weight_decay=WEIGHT_DECAY,
lr_scheduler_type="cosine",
warmup_ratio = 0.1,
fp16=True,
load_best_model_at_end=True,
metric_for_best_model=metric_name,
push_to_hub=True,
logging_dir="./runs",
)
Copy
	
import numpy as np
from evaluate import load
metric = load("accuracy")
def compute_metrics(eval_pred):
print(eval_pred)
predictions, labels = eval_pred
predictions = np.argmax(predictions, axis=1)
return metric.compute(predictions=predictions, references=labels)
Copy
	
from transformers import Trainer
trainer = Trainer(
model,
training_args,
train_dataset=subset_dataset_train,
eval_dataset=subset_dataset_validation,
tokenizer=tokenizer,
compute_metrics=compute_metrics,
)
Copy
	
trainer.train()
Copy
	
<IPython.core.display.HTML object>
	
<transformers.trainer_utils.EvalPrediction object at 0x7cd07be46440>
<transformers.trainer_utils.EvalPrediction object at 0x7cd07be45c30>
<transformers.trainer_utils.EvalPrediction object at 0x7cd07be8b970>
	
TrainOutput(global_step=1500, training_loss=1.8345018310546874, metrics={'train_runtime': 2565.4667, 'train_samples_per_second': 233.876, 'train_steps_per_second': 0.585, 'total_flos': 2.352076406784e+17, 'train_loss': 1.8345018310546874, 'epoch': 3.0})

Avaliaçãolink image 74

Uma vez treinado, avaliamos sobre o dataset de teste

	
trainer.evaluate(eval_dataset=subset_dataset_test)
Copy
	
<IPython.core.display.HTML object>
	
<transformers.trainer_utils.EvalPrediction object at 0x7cd07be8bbe0>
	
{'eval_loss': 1.5203168392181396,
'eval_accuracy': 0.3374,
'eval_runtime': 19.3843,
'eval_samples_per_second': 257.94,
'eval_steps_per_second': 0.671,
'epoch': 3.0}

Publicar o modelolink image 75

Já temos nosso modelo treinado, já podemos compartilhá-lo com o mundo, então primeiro criamos uma **model card**

	
trainer.create_model_card()
Copy

E já podemos publicá-lo. Como a primeira coisa que fizemos foi fazer login no hub do Hugging Face, podemos enviá-lo para o nosso hub sem nenhum problema.

	
trainer.push_to_hub()
Copy

Teste do modelolink image 76

Limpez tudo o possível

	
import torch
import gc
def clear_hardwares():
torch.clear_autocast_cache()
torch.cuda.ipc_collect()
torch.cuda.empty_cache()
gc.collect()
clear_hardwares()
clear_hardwares()
Copy

Como subimos o modelo ao nosso hub, podemos baixá-lo e usá-lo

	
from transformers import pipeline
user = "maximofn"
checkpoints = f"{user}/{model_name}"
task = "text-classification"
classifier = pipeline(task, model=checkpoints, tokenizer=checkpoints)
Copy

Agora, se quisermos que nos retorne a probabilidade de todas as classes, simplesmente usamos o classificador que acabamos de instanciar, com o parâmetro top_k=None

	
labels = classifier("I love this product", top_k=None)
labels
Copy
	
[{'label': 'LABEL_0', 'score': 0.8419149518013},
{'label': 'LABEL_1', 'score': 0.09386005252599716},
{'label': 'LABEL_3', 'score': 0.03624210134148598},
{'label': 'LABEL_2', 'score': 0.02049318142235279},
{'label': 'LABEL_4', 'score': 0.0074898069724440575}]

Se quisermos apenas a classe com a maior probabilidade, fazemos o mesmo mas com o parâmetro top_k=1

	
label = classifier("I love this product", top_k=1)
label
Copy
	
[{'label': 'LABEL_0', 'score': 0.8419149518013}]

E se quisermos n classes, fazemos o mesmo mas com o parâmetro top_k=n

	
two_labels = classifier("I love this product", top_k=2)
two_labels
Copy
	
[{'label': 'LABEL_0', 'score': 0.8419149518013},
{'label': 'LABEL_1', 'score': 0.09386005252599716}]

Também podemos testar o modelo com Automodel e AutoTokenizer

	
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
model_name = "GPT2-small-finetuned-amazon-reviews-en-classification"
user = "maximofn"
checkpoint = f"{user}/{model_name}"
num_classes = num_classes
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=num_classes).half().eval().to("cuda")
Copy
	
tokens = tokenizer.encode("I love this product", return_tensors="pt").to(model.device)
with torch.no_grad():
output = model(tokens)
logits = output.logits
lables = torch.softmax(logits, dim=1).cpu().numpy().tolist()
lables[0]
Copy
	
[0.003940582275390625,
0.00266265869140625,
0.013946533203125,
0.1544189453125,
0.8251953125]

Se você quiser testar mais o modelo, você pode vê-lo em Maximofn/GPT2-small-LoRA-finetuned-amazon-reviews-en-classification

Implementação de LoRA em um LLM com PEFT da Hugging Facelink image 77

Podemos fazer o mesmo com a biblioteca PEFT do Hugging Face. Vamos vê-lo.

Login no Hublink image 78

Nós fazemos login para fazer o upload do modelo para o Hub

	
from huggingface_hub import notebook_login
notebook_login()
Copy

Conjunto de Dadoslink image 79

Voltamos a baixar o dataset

	
from datasets import load_dataset
dataset = load_dataset("mteb/amazon_reviews_multi", "en")
dataset
Copy
	
DatasetDict({
train: Dataset({
features: ['id', 'text', 'label', 'label_text'],
num_rows: 200000
})
validation: Dataset({
features: ['id', 'text', 'label', 'label_text'],
num_rows: 5000
})
test: Dataset({
features: ['id', 'text', 'label', 'label_text'],
num_rows: 5000
})
})

Criamos um subconjunto caso você queira testar o código com um conjunto de dados menor. No meu caso, usarei 100% do conjunto de dados.

	
percentage = 1
subset_dataset_train = dataset['train'].select(range(int(len(dataset['train']) * percentage)))
subset_dataset_validation = dataset['validation'].select(range(int(len(dataset['validation']) * percentage)))
subset_dataset_test = dataset['test'].select(range(int(len(dataset['test']) * percentage)))
subset_dataset_train, subset_dataset_validation, subset_dataset_test
Copy
	
(Dataset({
features: ['id', 'text', 'label', 'label_text'],
num_rows: 200000
}),
Dataset({
features: ['id', 'text', 'label', 'label_text'],
num_rows: 5000
}),
Dataset({
features: ['id', 'text', 'label', 'label_text'],
num_rows: 5000
}))

Obtemos o número de classes, para obter o número de classes usamos dataset['train'] e não subset_dataset_train porque se o subconjunto for muito pequeno é possível que não haja exemplos com todas as possíveis classes do conjunto de dados original

	
num_classes = len(dataset['train'].unique('label'))
num_classes
Copy
	
5

Criamos uma função para criar o campo label no dataset. O dataset baixado tem o campo labels, mas a biblioteca transformers precisa que o campo seja chamado de label e não labels.

	
def set_labels(example):
example['labels'] = example['label']
return example
Copy

Aplicamos a função ao conjunto de dados

	
subset_dataset_train = subset_dataset_train.map(set_labels)
subset_dataset_validation = subset_dataset_validation.map(set_labels)
subset_dataset_test = subset_dataset_test.map(set_labels)
subset_dataset_train, subset_dataset_validation, subset_dataset_test
Copy
	
(Dataset({
features: ['id', 'text', 'label', 'label_text', 'labels'],
num_rows: 200000
}),
Dataset({
features: ['id', 'text', 'label', 'label_text', 'labels'],
num_rows: 5000
}),
Dataset({
features: ['id', 'text', 'label', 'label_text', 'labels'],
num_rows: 5000
}))

Tokenizadorlink image 80

Instanciamos o tokenizador. Para que não nos dê erro, atribuímos o token de end of string ao token de padding

	
from transformers import AutoTokenizer
checkpoint = "openai-community/gpt2"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
tokenizer.pad_token = tokenizer.eos_token
Copy

Criamos uma função para tokenizar o dataset

	
def tokenize_function(examples):
return tokenizer(examples["text"], padding="max_length", truncation=True, max_length=768, return_tensors="pt")
Copy

Aplicamos a função ao conjunto de dados e, de passagem, eliminamos as colunas que não precisamos.

	
subset_dataset_train = subset_dataset_train.map(tokenize_function, batched=True, remove_columns=['text', 'label', 'id', 'label_text'])
subset_dataset_validation = subset_dataset_validation.map(tokenize_function, batched=True, remove_columns=['text', 'label', 'id', 'label_text'])
subset_dataset_test = subset_dataset_test.map(tokenize_function, batched=True, remove_columns=['text', 'label', 'id', 'label_text'])
subset_dataset_train, subset_dataset_validation, subset_dataset_test
Copy
	
(Dataset({
features: ['labels', 'input_ids', 'attention_mask'],
num_rows: 200000
}),
Dataset({
features: ['labels', 'input_ids', 'attention_mask'],
num_rows: 5000
}),
Dataset({
features: ['labels', 'input_ids', 'attention_mask'],
num_rows: 5000
}))

Modelolink image 81

Instanciamos o modelo. Também, para que não dê erro, atribuímos o token de end of string ao token de padding

	
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=num_classes)
model.config.pad_token_id = model.config.eos_token_id
Copy
	
Some weights of GPT2ForSequenceClassification were not initialized from the model checkpoint at openai-community/gpt2 and are newly initialized: ['score.weight']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.

LoRA com PEFTlink image 82

Antes de criar o modelo com LoRA, vamos a ver suas camadas

	
model
Copy
	
GPT2ForSequenceClassification(
(transformer): GPT2Model(
(wte): Embedding(50257, 768)
(wpe): Embedding(1024, 768)
(drop): Dropout(p=0.1, inplace=False)
(h): ModuleList(
(0-11): 12 x GPT2Block(
(ln_1): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
(attn): GPT2Attention(
(c_attn): Conv1D()
(c_proj): Conv1D()
(attn_dropout): Dropout(p=0.1, inplace=False)
(resid_dropout): Dropout(p=0.1, inplace=False)
)
(ln_2): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
(mlp): GPT2MLP(
(c_fc): Conv1D()
(c_proj): Conv1D()
(act): NewGELUActivation()
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
(ln_f): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
)
(score): Linear(in_features=768, out_features=5, bias=False)
)

Como podemos ver, há apenas uma camada Linear, que é score e que é a que vamos substituir.

Podemos criar uma configuração de LoRA com a biblioteca PEFT e depois aplicar LoRA ao mo

	
from peft import LoraConfig, TaskType
peft_config = LoraConfig(
r=16,
lora_alpha=32,
lora_dropout=0.1,
task_type=TaskType.SEQ_CLS,
target_modules=["score"],
)
Copy

Com esta configuração, definimos um rank de 16 e um alpha de 32. Além disso, adicionamos um dropout às camadas de LoRA de 0.1. Precisamos indicar a tarefa para a configuração de LoRA, neste caso é uma tarefa de sequence classification. Por fim, indicamos quais camadas queremos substituir, neste caso a camada score.

Agora aplicamos LoRA ao modelo

	
from peft import get_peft_model
model = get_peft_model(model, peft_config)
Copy

Vamos ver quantos parâmetros treináveis o modelo tem agora.

	
model.print_trainable_parameters()
Copy
	
trainable params: 12,368 || all params: 124,456,016 || trainable%: 0.0099

Obtemos os mesmos parâmetros treináveis que antes

Treinamentolink image 83

Uma vez instanciado o modelo com LoRA, vamos treiná-lo como sempre

	
from transformers import TrainingArguments
metric_name = "accuracy"
model_name = "GPT2-small-PEFT-LoRA-finetuned-amazon-reviews-en-classification"
LR = 2e-5
BS_TRAIN = 400
BS_EVAL = 400
EPOCHS = 3
WEIGHT_DECAY = 0.01
training_args = TrainingArguments(
model_name,
eval_strategy="epoch",
save_strategy="epoch",
learning_rate=LR,
per_device_train_batch_size=BS_TRAIN,
per_device_eval_batch_size=BS_EVAL,
num_train_epochs=EPOCHS,
weight_decay=WEIGHT_DECAY,
lr_scheduler_type="cosine",
warmup_ratio = 0.1,
fp16=True,
load_best_model_at_end=True,
metric_for_best_model=metric_name,
push_to_hub=True,
logging_dir="./runs",
)
Copy
	
import numpy as np
from evaluate import load
metric = load("accuracy")
def compute_metrics(eval_pred):
print(eval_pred)
predictions, labels = eval_pred
predictions = np.argmax(predictions, axis=1)
return metric.compute(predictions=predictions, references=labels)
Copy
	
from transformers import Trainer
trainer = Trainer(
model,
training_args,
train_dataset=subset_dataset_train,
eval_dataset=subset_dataset_validation,
tokenizer=tokenizer,
compute_metrics=compute_metrics,
)
Copy
	
trainer.train()
Copy
	
<IPython.core.display.HTML object>
	
<transformers.trainer_utils.EvalPrediction object at 0x7f774a50bbe0>
	
<IPython.core.display.HTML object>
	
<transformers.trainer_utils.EvalPrediction object at 0x7f77486a7c40>
<transformers.trainer_utils.EvalPrediction object at 0x7f7749eb5690>
	
TrainOutput(global_step=1500, training_loss=1.751504597981771, metrics={'train_runtime': 2551.7753, 'train_samples_per_second': 235.13, 'train_steps_per_second': 0.588, 'total_flos': 2.352524525568e+17, 'train_loss': 1.751504597981771, 'epoch': 3.0})

Avaliaçãolink image 84

Uma vez treinado, avaliamos sobre o dataset de teste

	
trainer.evaluate(eval_dataset=subset_dataset_test)
Copy
	
<IPython.core.display.HTML object>
	
<transformers.trainer_utils.EvalPrediction object at 0x7f77a1d1f7c0>
	
{'eval_loss': 1.4127237796783447,
'eval_accuracy': 0.3862,
'eval_runtime': 19.3275,
'eval_samples_per_second': 258.699,
'eval_steps_per_second': 0.673,
'epoch': 3.0}

Publicar o modelolink image 85

Criamos uma model card

	
trainer.create_model_card()
Copy

O publicamos

	
trainer.push_to_hub()
Copy
	
CommitInfo(commit_url='https://huggingface.co/Maximofn/GPT2-small-PEFT-LoRA-finetuned-amazon-reviews-en-classification/commit/839066c2bde02689a6b3f5624ac25f89c4de217d', commit_message='End of training', commit_description='', oid='839066c2bde02689a6b3f5624ac25f89c4de217d', pr_url=None, pr_revision=None, pr_num=None)

Teste do modelo treinado com PEFTlink image 86

Limpamos tudo o possível

	
import torch
import gc
def clear_hardwares():
torch.clear_autocast_cache()
torch.cuda.ipc_collect()
torch.cuda.empty_cache()
gc.collect()
clear_hardwares()
clear_hardwares()
Copy

Como subimos o modelo ao nosso hub, podemos baixá-lo e usá-lo

	
from transformers import pipeline
user = "maximofn"
checkpoints = f"{user}/{model_name}"
task = "text-classification"
classifier = pipeline(task, model=checkpoints, tokenizer=checkpoints)
Copy
	
Some weights of GPT2ForSequenceClassification were not initialized from the model checkpoint at openai-community/gpt2 and are newly initialized: ['score.weight']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.

Agora, se quisermos que nos retorne a probabilidade de todas as classes, simplesmente usamos o classificador que acabamos de instanciar, com o parâmetro top_k=None

	
labels = classifier("I love this product", top_k=None)
labels
Copy
	
[{'label': 'LABEL_1', 'score': 0.9979197382926941},
{'label': 'LABEL_0', 'score': 0.002080311067402363}]

Se quisermos apenas a classe com a maior probabilidade fazemos o mesmo mas com o parâmetro top_k=1

	
label = classifier("I love this product", top_k=1)
label
Copy
	
[{'label': 'LABEL_1', 'score': 0.9979197382926941}]

E se quisermos n classes, fazemos o mesmo mas com o parâmetro top_k=n

	
two_labels = classifier("I love this product", top_k=2)
two_labels
Copy
	
[{'label': 'LABEL_1', 'score': 0.9979197382926941},
{'label': 'LABEL_0', 'score': 0.002080311067402363}]

Se você quiser testar mais o modelo, pode vê-lo em Maximofn/GPT2-small-PEFT-LoRA-finetuned-amazon-reviews-en-classification

Continuar lendo

Últimos posts -->

Você viu esses projetos?

Horeca chatbot

Horeca chatbot Horeca chatbot
Python
LangChain
PostgreSQL
PGVector
React
Kubernetes
Docker
GitHub Actions

Chatbot conversacional para cozinheiros de hotéis e restaurantes. Um cozinheiro, gerente de cozinha ou serviço de quarto de um hotel ou restaurante pode falar com o chatbot para obter informações sobre receitas e menus. Mas também implementa agentes, com os quais pode editar ou criar novas receitas ou menus

Naviground

Naviground Naviground

Subtify

Subtify Subtify
Python
Whisper
Spaces

Gerador de legendas para vídeos no idioma que você desejar. Além disso, coloca uma legenda de cor diferente para cada pessoa

Ver todos os projetos -->

Quer aplicar IA no seu projeto? Entre em contato!

Quer melhorar com essas dicas?

Últimos tips -->

Use isso localmente

Os espaços do Hugging Face nos permitem executar modelos com demos muito simples, mas e se a demo quebrar? Ou se o usuário a deletar? Por isso, criei contêineres docker com alguns espaços interessantes, para poder usá-los localmente, aconteça o que acontecer. Na verdade, se você clicar em qualquer botão de visualização de projeto, ele pode levá-lo a um espaço que não funciona.

Flow edit

Flow edit Flow edit

Edite imagens com este modelo de Flow. Baseado em SD3 ou FLUX, você pode editar qualquer imagem e gerar novas

FLUX.1-RealismLora

FLUX.1-RealismLora FLUX.1-RealismLora
Ver todos os contêineres -->

Quer aplicar IA no seu projeto? Entre em contato!

Você quer treinar seu modelo com esses datasets?

short-jokes-dataset

Dataset com piadas em inglês

opus100

Dataset com traduções de inglês para espanhol

netflix_titles

Dataset com filmes e séries da Netflix

Ver mais datasets -->