LoRA – low rank adaptation of large language models

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

LoRA - adaptação de baixa classificação de grandes modelos de linguagemlink image 58

Este caderno foi traduzido automaticamente para torná-lo acessível a mais pessoas, por favor me avise se você vir algum erro de digitação..

O tamanho cada vez maior dos modelos de linguagem torna cada vez mais caro treiná-los, pois é necessário cada vez mais VRAM para armazenar todos os seus parâmetros e gradientes derivados do treinamento.

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

LoRA

Vejamos como isso é feito

Explicação do LoRAlink image 59

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

Para entender como o LoRA funciona, primeiro precisamos lembrar o que acontece quando treinamos um modelo. Vamos voltar à parte mais básica da aprendizagem profunda: 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 polarização.

Para simplificar, vamos supor que não haja viés, de modo que ficaria assim

$$ y = Wx $$

Suponha que, para uma entrada $x$, queremos que ela tenha uma saída $ŷ$.

  • Primeiro, calculamos o resultado 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 $ŷ$. Chamamos esse erro de $loss$ e o calculamos com alguma função matemática, não importa qual.
  • Calculamos a derivada do erro $loss$ com relação à matriz de peso $W$, ou seja, $$Delta W = \frac{dloss}{dW}$.
  • Atualizamos os pesos $W$ subtraindo de cada um de seus valores o valor do gradiente multiplicado por um fator de aprendizado $alpha$, ou seja, $W = W - \alpha \Delta W$.

LoRAlink image 61

O que os autores do LoRA propõem é que a matriz de peso $W$ possa ser decomposta em

$$ W \sim W + \Delta W $$

Portanto, ao congelar a matriz $W$ e treinar somente a matriz $"Delta W$, é possível obter um modelo que se ajusta aos novos dados sem precisar treinar novamente o modelo inteiro.

Mas você pode pensar que $$Delta W$ é uma matriz de tamanho igual a $W$ e, portanto, nada foi ganho, mas aqui os autores se baseiam em Aghajanyan et al. (2020), um artigo no qual eles mostraram 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 alterar alguns valores é suficiente, o que, em termos técnicos, é chamado de Low Rank Adaptation (LoRA). Daí o nome LoRA (Low Rank Adaptation).

Congelamos o modelo e agora queremos treinar a matriz $\Delta W$. Vamos supor que tanto $W$ quanto $\Delta W$ sejam matrizes de tamanho $20 \times 10$, portanto, temos 200 parâmetros treináveis.

Agora, vamos supor que a matriz $\Delta W$ possa ser decomposta no produto de duas matrizes $A$ e $B$, ou seja

$$ \Delta W = A \cdot B $$

Para que essa multiplicação ocorra, os tamanhos das matrizes $A$ e $B$ devem ser $20 \times n$ e $n \times 10$, respectivamente. Suponha que $n = 5$, então $A$ teria o tamanho de $20 \times 5$, ou seja, 100 parâmetros, e $B$ o tamanho de $5 \times 10$, ou seja, 50 parâmetros, de modo que teríamos 100+50=150 parâmetros treináveis. Já temos menos parâmetros treináveis do que antes

Agora vamos supor que $W$ seja, na verdade, uma matriz de tamanho $10.000 \times 10.000$, de modo que teríamos 100.000.000 parâmetros treináveis, mas se decompusermos $\Delta W$ em $A$ e $B$ com $n = 5$, teríamos uma matriz de tamanho $10.000 \times 5$ e outra de tamanho $5 \times 10.000$, de modo que teríamos 50.000 parâmetros de uma e outros 50.000 parâmetros da outra, em um total de 100.000 parâmetros treináveis, ou seja, reduzimos o número de parâmetros 1.000 vezes.

Você já pode ver o poder do LoRA: quando você tem modelos muito grandes, o número de parâmetros treináveis pode ser bastante reduzido.

Se olharmos novamente para a imagem da arquitetura do LoRA, entenderemos melhor.

LoRA adapt

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

LoRA matmul

Implementação de LoRA em transformadoreslink image 62

Como os modelos de linguagem são implementações de transformadores, vamos ver como o LoRA é implementado nos transformadores. Na arquitetura do transformador, há camadas lineares nas matrizes de atenção $Q$, $K$ e $V$ e nas camadas de feedforward, de modo que o LoRA pode ser aplicado a todas essas camadas lineares. No artigo, eles afirmam que, para simplificar, aplicam o LoRA somente às camadas lineares das matrizes de atenção $Q$, $K$ e $V$.

Essas camadas têm um tamanho de $d_{model} \times d_{model}$, em que $d_{model}$ é a dimensão de incorporação do modelo.

Tamanho do intervalo rlink image 63

Para obter esses benefícios, o tamanho do intervalo $r$ deve ser menor que o tamanho das camadas lineares. Como dissemos que eles só o implementaram nas camadas lineares de atenção, que têm um tamanho $d_{model} \times d_{model}$, o tamanho do intervalo $r$ deve ser menor que $d_{model}$.

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$, de modo que o produto de ambas as matrizes será zero no início, ou seja

$$ \Delta W = A \cdot B = 0 $$

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

Por fim, na implementação do LoRA, um parâmetro $alpha$ é adicionado para estabelecer o grau de influência do LoRA no treinamento. Ele é semelhante à taxa de aprendizado no ajuste fino normal, mas, nesse caso, é usado para estabelecer a influência do LoRA no treinamento. Assim, a fórmula do LoRA seria a seguinte

$$ W = W + \alpha \Delta W = W + \alpha A \cdot B $$

Vantagens da LoRAlink image 66

Agora que entendemos como ele funciona, vamos examinar 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 é drasticamente reduzido, o que torna o treinamento muito mais rápido e menos VRAM é necessária, economizando assim muitos custos.
  • Adaptadores em produção. Podemos ter um único modelo de linguagem e vários adaptadores em produção, cada um para uma tarefa diferente, em vez de ter vários modelos treinados para cada tarefa, economizando, assim, custos computacionais e de armazenamento. Além disso, esse método não precisa adicionar latência na inferência porque a matriz de pesos original pode ser mesclada com o adaptador, já que vimos que $W \sim W + \Delta W = W + A \cdot B$, de modo que o tempo de inferência seria o mesmo que usar o modelo de linguagem original.
  • Comprar e compartilhar adaptadores. Se treinarmos um adaptador, poderemos compartilhar somente o adaptador. Ou seja, na produção, todos podem ter o modelo original e, cada vez que treinarmos um adaptador, poderemos compartilhar apenas o adaptador, de modo que, como matrizes muito menores seriam compartilhadas, o tamanho dos arquivos a serem compartilhados seria muito menor.

Implementação de LoRA em um LLMlink image 67

Vamos repetir o código de treinamento da postagem Fine tuning SLMs, especificamente o treinamento para classificação de texto com as bibliotecas Hugging Face, mas, desta vez, faremos isso com LoRA. Na publicação anterior, usamos um tamanho de lote de 28 para o loop de treinamento e 40 para o loop de avaliação; no entanto, como agora não vamos treinar todos os pesos do modelo, mas apenas as matrizes LoRA, poderemos usar um tamanho de lote maior.

Faça login no hublink image 68

Fazemos login para carregar o modelo no Hub

	
from huggingface_hub import notebook_login
notebook_login()
Copy

Conjunto de dadoslink image 69

Baixamos o conjunto de dados que usaremos, que é um conjunto de dados de avaliações da Amazon

	
from huggingface_hub import notebook_login
notebook_login()
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 para o caso de você querer 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'}

Para obter o número de classes, usamos dataset['train'] e não subset_dataset_train porque, se o subconjunto for muito pequeno, talvez não haja exemplos com todas as classes possíveis 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 conjunto de dados. O conjunto de dados baixado tem o campo labels, mas a biblioteca transformers precisa que o campo seja chamado label e não labels.

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

Aplicamos a função ao conjunto de dados

	
def set_labels(example):
example['labels'] = example['label']
return example
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
}))

Aqui está um exemplo novamente

	
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}

Tokeniserlink image 70

Implementamos o tokenizador. Para evitar erros, atribuímos o token de fim de cadeia ao token de preenchimento.

	
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 conjunto de dados

	
from transformers import AutoTokenizer
checkpoint = "openai-community/gpt2"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
tokenizer.pad_token = tokenizer.eos_token
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 removemos as colunas de que não precisamos.

	
from transformers import AutoTokenizer
checkpoint = "openai-community/gpt2"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
tokenizer.pad_token = tokenizer.eos_token
def tokenize_function(examples):
return tokenizer(examples["text"], padding="max_length", truncation=True, max_length=768, return_tensors="pt")
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
}))

Vemos uma amostra novamente, mas, nesse caso, vemos apenas as keys.

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

Modelolink image 71

Instanciamos o modelo. Além disso, para evitar erros, atribuímos o token do final da string ao token de preenchimento.

	
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 vimos na postagem Fine tuning SLMs, recebemos um aviso de que algumas camadas não foram inicializadas. Isso ocorre porque, nesse caso, como se trata de um problema de classificação e, quando instanciamos o modelo, dissemos a ele que queríamos que fosse um modelo de classificação com 5 classes, a biblioteca eliminou a última camada e a substituiu por uma de 5 neurônios na saída. Se você não entender isso, vá para a postagem que citei, que está mais bem explicada.

LoRAlink image 72

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

	
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 você tem 124 milhões 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 o 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.

Ele precisa herdar do torch.nn.Module para que possa atuar como uma camada de uma rede neural.

No método _init_, criamos os vetores A e B inicializados conforme explicado acima, o vetor A com uma distribuição gaussiana aleatória e o vetor B com zeros. Também criamos os parâmetros rank e alpha.

No método "forward", calculamos o LoRA conforme explicado acima.

	
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.

Como antes, ele herda do 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 implementamos anteriormente.

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

	
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
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 fim, criamos uma função que substitui as camadas lineares pela nova camada linear com LoRA que criamos. Se encontrar uma camada linear no modelo, ela a substituirá pela camada linear com LoRA; caso contrário, aplicará a função nas subcamadas da camada.

	
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
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)
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.

	
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
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)
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)
rank = 16
alpha = 16
replace_linear_with_lora(model, rank=rank, alpha=alpha)
Copy

Agora vemos o número de parâmetros treináveis

	
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
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)
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)
rank = 16
alpha = 16
replace_linear_with_lora(model, rank=rank, alpha=alpha)
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 124 milhões de parâmetros treináveis para 12 mil parâmetros treináveis, ou seja, reduzimos o número de parâmetros treináveis em 10.000 vezes!

Analisamos o modelo novamente

	
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 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)
(mlp): GPT2MLP( (mlp): GPT2MLP(
(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)
) )
(score): LoRALinear()
(score): Linear(in_features=768, out_features=5, bias=False) (linear): Linear(in_features=768, out_features=5, bias=False)
(lora): LoRALayer()
)
) )

Podemos ver que eles 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 tem a camada linear do modelo original e uma camada LoRALayer dentro.

Treinamentolink image 73

Depois que o modelo tiver sido instanciado com o LoRA, vamos treiná-lo como de costume.

Como dissemos, na postagem Fine tuning SLMs, usamos um tamanho de lote de 28 para o loop de treinamento e 40 para o loop de avaliação, mas agora que há menos parâmetros treináveis, podemos usar um tamanho de lote maior.

Por que isso acontece? Quando você treina um modelo, precisa salvar o modelo e seus gradientes na memória da GPU; portanto, tanto com o LoRA quanto sem o LoRA, você precisa salvar o modelo de qualquer maneira, mas no caso do LoRA, você salva apenas os gradientes de 12 mil parâmetros, enquanto com o LoRA você salva os gradientes de 128 milhões de parâmetros; portanto, com o LoRA, você precisa de menos memória da GPU, o que permite usar um tamanho de lote 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
	
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",
)
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 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",
)
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)
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()
      
[1500/1500 42:41, Epoch 3/3]
Epoch Training Loss Validation Loss Accuracy
1 2.396400 1.602937 0.269400
2 1.572700 1.531719 0.320800
3 1.534400 1.511815 0.335800

<transformers.trainer_utils.EvalPrediction object at 0x7cd07be46440>
      <transformers.trainer_utils.EvalPrediction object at 0x7cd07be45c30>
      <transformers.trainer_utils.EvalPrediction object at 0x7cd07be8b970>
      
Out[27]:
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

Depois de treinados, avaliamos o conjunto de dados de teste

trainer.evaluate(eval_dataset=subset_dataset_test)
      
[13/13 00:17]
<transformers.trainer_utils.EvalPrediction object at 0x7cd07be8bbe0>
      
Out[28]:
{'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

Agora que temos nosso modelo treinado, podemos compartilhá-lo com o mundo, portanto, primeiro criamos um cartão de modelo.

	
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",
)
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)
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,
)
trainer.train()
trainer.evaluate(eval_dataset=subset_dataset_test)
trainer.create_model_card()
Copy

E agora podemos publicá-lo. Como a primeira coisa que fizemos foi fazer login no hub da huggingface, poderemos fazer o upload para o nosso hub sem nenhum problema.

	
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",
)
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)
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,
)
trainer.train()
trainer.evaluate(eval_dataset=subset_dataset_test)
trainer.create_model_card()
trainer.push_to_hub()
Copy

Teste de modelolink image 76

Limpamos o máximo possível

	
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",
)
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)
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,
)
trainer.train()
trainer.evaluate(eval_dataset=subset_dataset_test)
trainer.create_model_card()
trainer.push_to_hub()
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 fizemos o upload do modelo em nosso hub, podemos baixá-lo e usá-lo.

	
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",
)
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)
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,
)
trainer.train()
trainer.evaluate(eval_dataset=subset_dataset_test)
trainer.create_model_card()
trainer.push_to_hub()
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()
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 retornar a probabilidade de todas as classes, basta usar o classificador que acabamos de instanciar, com o parâmetro top_k=None.

	
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",
)
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)
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,
)
trainer.train()
trainer.evaluate(eval_dataset=subset_dataset_test)
trainer.create_model_card()
trainer.push_to_hub()
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()
from transformers import pipeline
user = "maximofn"
checkpoints = f"{user}/{model_name}"
task = "text-classification"
classifier = pipeline(task, model=checkpoints, tokenizer=checkpoints)
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, faremos 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, faremos 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 o Automodel e o 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
	
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")
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 o modelo com mais detalhes, poderá 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 da Hugging Face. Vamos dar uma olhada nela

Faça login no hublink image 78

Fazemos login para carregar o modelo no Hub

	
from huggingface_hub import notebook_login
notebook_login()
Copy

Conjunto de dadoslink image 79

Baixamos novamente o conjunto de dados

	
from huggingface_hub import notebook_login
notebook_login()
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 para o caso de você querer 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
}))

Para obter o número de classes, usamos dataset['train'] e não subset_dataset_train porque, se o subconjunto for muito pequeno, talvez não haja exemplos com todas as classes possíveis 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 conjunto de dados. O conjunto de dados baixado tem o campo labels, mas a biblioteca transformers precisa que o campo seja chamado label e não labels.

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

Aplicamos a função ao conjunto de dados

	
def set_labels(example):
example['labels'] = example['label']
return example
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
}))

Tokeniserlink image 80

Instanciamos o tokenizador. Para evitar erros, atribuímos o token de fim de cadeia ao token de preenchimento.

	
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 conjunto de dados

	
from transformers import AutoTokenizer
checkpoint = "openai-community/gpt2"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
tokenizer.pad_token = tokenizer.eos_token
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 removemos as colunas de que não precisamos.

	
from transformers import AutoTokenizer
checkpoint = "openai-community/gpt2"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
tokenizer.pad_token = tokenizer.eos_token
def tokenize_function(examples):
return tokenizer(examples["text"], padding="max_length", truncation=True, max_length=768, return_tensors="pt")
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. Além disso, para evitar erros, atribuímos o token do final da string ao token de preenchimento.

	
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 o LoRA, vamos dar uma olhada em 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 é essa que vamos substituir.

Podemos criar uma configuração do LoRA com a biblioteca PEFT e, em seguida, aplicar o LoRA ao mapa.

	
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 essa configuração, definimos uma classificação de 16 e um alfa de 32. Além disso, adicionamos um dropout de 0,1 às camadas lora. Temos de indicar a tarefa para a configuração do LoRA; nesse caso, é uma tarefa de classificação de sequência. Por fim, indicamos quais camadas queremos substituir, neste caso, a camada score.

Agora, aplicamos o LoRA ao modelo

	
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"],
)
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.

	
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"],
)
from peft import get_peft_model
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
Copy
	
trainable params: 12,368 || all params: 124,456,016 || trainable%: 0.0099

Obtemos os mesmos parâmetros treináveis de antes

Treinamentolink image 83

Depois que o modelo tiver sido instanciado com o LoRA, vamos treiná-lo como de costume.

	
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
	
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",
)
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 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",
)
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)
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()
      
[ 811/1500 22:43 < 19:20, 0.59 it/s, Epoch 1.62/3]
Epoch Training Loss Validation Loss Accuracy
1 2.275100 1.512476 0.318200

<transformers.trainer_utils.EvalPrediction object at 0x7f774a50bbe0>
      
[1500/1500 42:28, Epoch 3/3]
Epoch Training Loss Validation Loss Accuracy
1 2.275100 1.512476 0.318200
2 1.515900 1.417553 0.373800
3 1.463500 1.405058 0.381400

<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

Depois de treinados, avaliamos o conjunto de dados de teste

trainer.evaluate(eval_dataset=subset_dataset_test)
      
[13/13 00:17]
<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 um cartão modelo

	
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",
)
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)
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,
)
trainer.train()
trainer.evaluate(eval_dataset=subset_dataset_test)
trainer.create_model_card()
Copy

Nós o publicamos

	
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",
)
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)
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,
)
trainer.train()
trainer.evaluate(eval_dataset=subset_dataset_test)
trainer.create_model_card()
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 o máximo 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 fizemos o upload do modelo em nosso hub, podemos baixá-lo e usá-lo.

	
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()
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 retornar a probabilidade de todas as classes, basta usar 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, faremos 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, faremos 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 o modelo com mais detalhes, poderá vê-lo em Maximofn/GPT2-small-PEFT-LoRA-finetuned-amazon-reviews-en-classification

Continuar lendo

DoLa – Decoding by Contrasting Layers Improves Factuality in Large Language Models

DoLa – Decoding by Contrasting Layers Improves Factuality in Large Language Models

Você já conversou com um LLM e ele lhe respondeu algo que parece ter bebido café de máquina a noite toda? 😂 Isso é o que chamamos de alucinação no mundo dos LLMs! Mas não se preocupe, pois não é que seu modelo de linguagem esteja louco (embora às vezes possa parecer isso 🤪). A verdade é que os LLMs podem ser um pouco... criativos quando se trata de gerar texto. Mas graças ao DoLa, um método que usa camadas de contraste para melhorar a viabilidade dos LLMs, podemos evitar que nossos modelos de linguagem se transformem em escritores de ficção científica 😂. Nesta publicação, explicarei como o DoLa funciona e mostrarei um exemplo de código para que você possa entender melhor como tornar seus LLMs mais confiáveis e menos propensos a inventar histórias. Vamos salvar nossos LLMs da loucura e torná-los mais úteis! 🚀

Últimos posts -->

Você viu esses projetos?

Subtify

Subtify Subtify

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.

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 -->