Fine-Tuning de Modelos Pequenos (SMLs)

Fine-Tuning de Modelos Pequenos (SMLs) Fine-Tuning de Modelos Pequenos (SMLs)

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.

Neste post, vamos ver como fazer fine tuning em pequenos modelos de linguagem, vamos ver como fazer fine tuning para classificação de texto e para geração de texto. Primeiro, vamos ver como fazer isso com as bibliotecas do Hugging Face, já que o Hugging Face se tornou um ator muito importante no ecossistema de IA neste momento.

Mas embora as bibliotecas do Hugging Face sejam muito importantes e úteis, é muito importante saber como o treinamento realmente acontece e o que está acontecendo por baixo dos panos, então vamos repetir o treinamento para classificação e geração de texto, mas com Pytorch.

Ajuste fino para classificação de texto com Hugging Facelink image 81

Loginlink image 82

Para poder subir o resultado do treinamento ao hub devemos nos autenticar primeiro, para isso precisamos de um token

Para criar um token, é necessário ir à página de settings/tokens da nossa conta, onde aparecerá algo assim

User-Access-Token-dark

Damos a New token e será exibida uma janela para criar um novo token

new-token-dark

Damos um nome ao token e o criamos com o papel write, ou com o papel Fine-grained, que nos permite selecionar exatamente quais permissões o token terá.

Uma vez criado, copiamos e colamos a seguir.

	
< > Input
Python
from huggingface_hub import notebook_login
notebook_login()
Copied

Conjunto de Dadoslink image 83

Agora vamos baixar um dataset, neste caso vamos baixar um de avaliações do Amazon

	
< > Input
Python
from datasets import load_dataset
dataset = load_dataset("mteb/amazon_reviews_multi", "en")
Copied

Vamos vê-lo um pouco

	
< > Input
Python
dataset
Copied
>_ Output
			
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
})
})

Vemos que tem um conjunto de treinamento com 200.000 amostras, um de validação com 5.000 amostras e um de teste com 5.000 amostras.

Vamos a ver um exemplo do conjunto de treinamento

	
< > Input
Python
from random import randint
idx = randint(0, len(dataset['train']) - 1)
dataset['train'][idx]
Copied
>_ Output
			
{'id': 'en_0907914',
'text': 'Mixed with fir it’s passable Not the scent I had hoped for . Love the scent of cedar, but this one missed',
'label': 3,
'label_text': '3'}

Vemos que tem a avaliação no campo text e a pontuação que o usuário deu no campo label

Como vamos a fazer um modelo de classificação de textos, precisamos saber quantas classes vamos ter.

	
< > Input
Python
num_classes = len(dataset['train'].unique('label'))
num_classes
Copied
>_ Output
			
5

Vamos a ter 5 classes, agora vamos a ver o valor mínimo dessas classes para saber se a pontuação começa em 0 ou em 1. Para isso, usamos o método unique

	
< > Input
Python
dataset.unique('label')
Copied
>_ Output
			
{'train': [0, 1, 2, 3, 4],
'validation': [0, 1, 2, 3, 4],
'test': [0, 1, 2, 3, 4]}

O valor mínimo será 0

Para treinar, as etiquetas precisam estar em um campo chamado labels, enquanto no nosso conjunto de dados está em um campo chamado label, então criamos o novo campo labels com o mesmo valor que label

Criamos uma função que faça o que quisermos

	
< > Input
Python
def set_labels(example):
example['labels'] = example['label']
return example
Copied

Aplicamos a função ao conjunto de dados

	
< > Input
Python
dataset = dataset.map(set_labels)
Copied

Vamos ver como fica o dataset

	
< > Input
Python
dataset['train'][idx]
Copied
>_ Output
			
{'id': 'en_0907914',
'text': 'Mixed with fir it’s passable Not the scent I had hoped for . Love the scent of cedar, but this one missed',
'label': 3,
'label_text': '3',
'labels': 3}

Tokenizadorlink image 84

Como temos as avaliações em texto no conjunto de dados, precisamos tokenizá-las para poder inserir os tokens no modelo.

	
< > Input
Python
from transformers import AutoTokenizer
checkpoint = "openai-community/gpt2"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
Copied

Agora criamos uma função para tokenizar o texto. Vamos fazer isso de maneira que todas as frases tenham o mesmo comprimento, de modo que o tokenizador truncará quando necessário e adicionará tokens de padding quando necessário. Além disso, indicamos que retorne tensores do pytorch.

Fazemos com que o comprimento de cada sentença seja de 768 tokens porque estamos usando o modelo pequeno do GPT2, que como vimos no post de GPT2 tem uma dimensão de embedding de 768 tokens

	
< > Input
Python
def tokenize_function(examples):
return tokenizer(examples["text"], padding="max_length", truncation=True, max_length=768, return_tensors="pt")
Copied

Vamos a provar a tokenizar um texto

	
< > Input
Python
tokens = tokenize_function(dataset['train'][idx])
Copied
>_ Output
			
---------------------------------------------------------------------------ValueError Traceback (most recent call last)Cell In[11], line 1
----&gt; 1 tokens = tokenize_function(dataset['train'][idx])
Cell In[10], line 2, in tokenize_function(examples)
1 def tokenize_function(examples):
----&gt; 2 return tokenizer(examples["text"], padding="max_length", truncation=True, max_length=768, return_tensors="pt")
File ~/miniconda3/envs/nlp_/lib/python3.11/site-packages/transformers/tokenization_utils_base.py:2883, in PreTrainedTokenizerBase.__call__(self, text, text_pair, text_target, text_pair_target, add_special_tokens, padding, truncation, max_length, stride, is_split_into_words, pad_to_multiple_of, return_tensors, return_token_type_ids, return_attention_mask, return_overflowing_tokens, return_special_tokens_mask, return_offsets_mapping, return_length, verbose, **kwargs)
2881 if not self._in_target_context_manager:
2882 self._switch_to_input_mode()
-&gt; 2883 encodings = self._call_one(text=text, text_pair=text_pair, **all_kwargs)
2884 if text_target is not None:
2885 self._switch_to_target_mode()
File ~/miniconda3/envs/nlp_/lib/python3.11/site-packages/transformers/tokenization_utils_base.py:2989, in PreTrainedTokenizerBase._call_one(self, text, text_pair, add_special_tokens, padding, truncation, max_length, stride, is_split_into_words, pad_to_multiple_of, return_tensors, return_token_type_ids, return_attention_mask, return_overflowing_tokens, return_special_tokens_mask, return_offsets_mapping, return_length, verbose, **kwargs)
2969 return self.batch_encode_plus(
2970 batch_text_or_text_pairs=batch_text_or_text_pairs,
2971 add_special_tokens=add_special_tokens,
(...)
2986 **kwargs,
2987 )
2988 else:
-&gt; 2989 return self.encode_plus(
2990 text=text,
2991 text_pair=text_pair,
2992 add_special_tokens=add_special_tokens,
2993 padding=padding,
2994 truncation=truncation,
2995 max_length=max_length,
2996 stride=stride,
2997 is_split_into_words=is_split_into_words,
2998 pad_to_multiple_of=pad_to_multiple_of,
2999 return_tensors=return_tensors,
3000 return_token_type_ids=return_token_type_ids,
3001 return_attention_mask=return_attention_mask,
3002 return_overflowing_tokens=return_overflowing_tokens,
3003 return_special_tokens_mask=return_special_tokens_mask,
3004 return_offsets_mapping=return_offsets_mapping,
3005 return_length=return_length,
3006 verbose=verbose,
3007 **kwargs,
3008 )
File ~/miniconda3/envs/nlp_/lib/python3.11/site-packages/transformers/tokenization_utils_base.py:3053, in PreTrainedTokenizerBase.encode_plus(self, text, text_pair, add_special_tokens, padding, truncation, max_length, stride, is_split_into_words, pad_to_multiple_of, return_tensors, return_token_type_ids, return_attention_mask, return_overflowing_tokens, return_special_tokens_mask, return_offsets_mapping, return_length, verbose, **kwargs)
3032 """
3033 Tokenize and prepare for the model a sequence or a pair of sequences.
3034
(...)
3049 method).
3050 """
3052 # Backward compatibility for 'truncation_strategy', 'pad_to_max_length'
-&gt; 3053 padding_strategy, truncation_strategy, max_length, kwargs = self._get_padding_truncation_strategies(
3054 padding=padding,
3055 truncation=truncation,
3056 max_length=max_length,
3057 pad_to_multiple_of=pad_to_multiple_of,
3058 verbose=verbose,
3059 **kwargs,
3060 )
3062 return self._encode_plus(
3063 text=text,
3064 text_pair=text_pair,
(...)
3080 **kwargs,
3081 )
File ~/miniconda3/envs/nlp_/lib/python3.11/site-packages/transformers/tokenization_utils_base.py:2788, in PreTrainedTokenizerBase._get_padding_truncation_strategies(self, padding, truncation, max_length, pad_to_multiple_of, verbose, **kwargs)
2786 # Test if we have a padding token
2787 if padding_strategy != PaddingStrategy.DO_NOT_PAD and (self.pad_token is None or self.pad_token_id &lt; 0):
-&gt; 2788 raise ValueError(
2789 "Asking to pad but the tokenizer does not have a padding token. "
2790 "Please select a token to use as `pad_token` `(tokenizer.pad_token = tokenizer.eos_token e.g.)` "
2791 "or add a new pad token via `tokenizer.add_special_tokens({'pad_token': '[PAD]'})`."
2792 )
2794 # Check that we will truncate to a multiple of pad_to_multiple_of if both are provided
2795 if (
2796 truncation_strategy != TruncationStrategy.DO_NOT_TRUNCATE
2797 and padding_strategy != PaddingStrategy.DO_NOT_PAD
(...)
2800 and (max_length % pad_to_multiple_of != 0)
2801 ):
ValueError: Asking to pad but the tokenizer does not have a padding token. Please select a token to use as `pad_token` `(tokenizer.pad_token = tokenizer.eos_token e.g.)` or add a new pad token via `tokenizer.add_special_tokens({'pad_token': '[PAD]'})`.

Dá-nos um erro porque o tokenizador do GPT2 não tem um token para preenchimento e pede que atribuamos um, além de sugerir fazer tokenizer.pad_token = tokenizer.eos_token, então fazemos isso.

	
< > Input
Python
tokenizer.pad_token = tokenizer.eos_token
Copied

Voltamos a testar a função de tokenização

	
< > Input
Python
tokens = tokenize_function(dataset['train'][idx])
tokens['input_ids'].shape, tokens['attention_mask'].shape
Copied
>_ Output
			
(torch.Size([1, 768]), torch.Size([1, 768]))

Agora que verificamos que a função tokeniza corretamente, aplicamos essa função ao conjunto de dados, mas também a aplicamos em lotes para que seja executada mais rapidamente.

Além disso, aproveitamos e eliminamos as colunas que não vamos precisar.

	
< > Input
Python
dataset = dataset.map(tokenize_function, batched=True, remove_columns=['text', 'label', 'id', 'label_text'])
Copied

Vamos ver agora como fica o dataset

	
< > Input
Python
dataset
Copied
>_ Output
			
DatasetDict({
train: Dataset({
features: ['id', 'text', 'label', 'label_text', 'labels'],
num_rows: 200000
})
validation: Dataset({
features: ['id', 'text', 'label', 'label_text', 'labels'],
num_rows: 5000
})
test: Dataset({
features: ['id', 'text', 'label', 'label_text', 'labels'],
num_rows: 5000
})
})

Vemos que temos os campos 'labels', 'input_ids' e 'attention_mask', que é o que nos interessa para treinar.

Modelolink image 85

Instanciamos um modelo para classificação de sequências e indicamos o número de classes que temos

	
< > Input
Python
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=num_classes)
Copied
>_ Output
			
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.

Diz que os pesos da camada score foram inicializados de maneira aleatória e que temos que reentreiná-los, vamos ver por que isso acontece.

O modelo GPT2 seria este

	
< > Input
Python
from transformers import AutoModelForCausalLM
casual_model = AutoModelForCausalLM.from_pretrained(checkpoint)
Copied

Enquanto o modelo do GPT2 para gerar texto é este

Vamos a ver sua arquitetura

	
< > Input
Python
casual_model
Copied
>_ Output
			
GPT2LMHeadModel(
(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)
)
(lm_head): Linear(in_features=768, out_features=50257, bias=False)
)

E agora a arquitetura do modelo que vamos usar para classificar as reviews

	
< > Input
Python
model
Copied
>_ Output
			
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)
)

Aqui há duas coisas para mencionar

  • A primeira é que em ambos, a primeira camada tem dimensões de 50257x768, o que corresponde a 50257 possíveis tokens do vocabulário do GPT2 e a 768 dimensões do embedding, por isso fizemos bem em tokenizar as avaliações com um tamanho de 768 tokens
  • A segunda é que o modelo casual (o de geração de texto) tem no final uma camada Linear que gera 50257 valores, ou seja, é responsável por prever o próximo token e dar um valor a um possível token. Enquanto isso, o modelo de classificação tem uma camada Linear que gera apenas 5 valores, um para cada classe, o que nos fornecerá a probabilidade de a review pertencer a cada classe.

Por isso recebíamos a mensagem de que os pesos da camada score haviam sido inicializados de forma aleatória, porque a biblioteca transformers removeu a camada Linear de 768x50257 e adicionou uma camada Linear de 768x5, inicializou-a com valores aleatórios e nós temos que treiná-la para o nosso problema específico.

Apagamos o modelo casual, pois não vamos usá-lo.

	
< > Input
Python
del casual_model
Copied

Treinadorlink image 86

Vamos agora a configurar os argumentos do treinamento

	
< > Input
Python
from transformers import TrainingArguments
metric_name = "accuracy"
model_name = "GPT2-small-finetuned-amazon-reviews-en-classification"
LR = 2e-5
BS_TRAIN = 28
BS_EVAL = 40
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,
)
Copied

Definimos uma métrica para o dataloader de validação

	
< > Input
Python
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)
Copied

Definimos agora o treinador

	
< > Input
Python
from transformers import Trainer
trainer = Trainer(
model,
training_args,
train_dataset=dataset['train'],
eval_dataset=dataset['validation'],
tokenizer=tokenizer,
compute_metrics=compute_metrics,
)
Copied

Treinamos

	
< > Input
Python
trainer.train()
Copied
>_ Output
			
0%| | 0/600000 [00:00&lt;?, ?it/s]
>_ Output
			
---------------------------------------------------------------------------ValueError Traceback (most recent call last)Cell In[21], line 1
----&gt; 1 trainer.train()
File ~/miniconda3/envs/nlp_/lib/python3.11/site-packages/transformers/trainer.py:1876, in Trainer.train(self, resume_from_checkpoint, trial, ignore_keys_for_eval, **kwargs)
1873 try:
1874 # Disable progress bars when uploading models during checkpoints to avoid polluting stdout
1875 hf_hub_utils.disable_progress_bars()
-&gt; 1876 return inner_training_loop(
1877 args=args,
1878 resume_from_checkpoint=resume_from_checkpoint,
1879 trial=trial,
1880 ignore_keys_for_eval=ignore_keys_for_eval,
1881 )
1882 finally:
1883 hf_hub_utils.enable_progress_bars()
File ~/miniconda3/envs/nlp_/lib/python3.11/site-packages/transformers/trainer.py:2178, in Trainer._inner_training_loop(self, batch_size, args, resume_from_checkpoint, trial, ignore_keys_for_eval)
2175 rng_to_sync = True
2177 step = -1
-&gt; 2178 for step, inputs in enumerate(epoch_iterator):
2179 total_batched_samples += 1
2181 if self.args.include_num_input_tokens_seen:
File ~/miniconda3/envs/nlp_/lib/python3.11/site-packages/accelerate/data_loader.py:454, in DataLoaderShard.__iter__(self)
452 # We iterate one batch ahead to check when we are at the end
453 try:
--&gt; 454 current_batch = next(dataloader_iter)
455 except StopIteration:
456 yield
File ~/miniconda3/envs/nlp_/lib/python3.11/site-packages/torch/utils/data/dataloader.py:631, in _BaseDataLoaderIter.__next__(self)
628 if self._sampler_iter is None:
629 # TODO(https://github.com/pytorch/pytorch/issues/76750)
630 self._reset() # type: ignore[call-arg]
--&gt; 631 data = self._next_data()
632 self._num_yielded += 1
633 if self._dataset_kind == _DatasetKind.Iterable and \
634 self._IterableDataset_len_called is not None and \
635 self._num_yielded &gt; self._IterableDataset_len_called:
File ~/miniconda3/envs/nlp_/lib/python3.11/site-packages/torch/utils/data/dataloader.py:675, in _SingleProcessDataLoaderIter._next_data(self)
673 def _next_data(self):
674 index = self._next_index() # may raise StopIteration
--&gt; 675 data = self._dataset_fetcher.fetch(index) # may raise StopIteration
676 if self._pin_memory:
677 data = _utils.pin_memory.pin_memory(data, self._pin_memory_device)
File ~/miniconda3/envs/nlp_/lib/python3.11/site-packages/torch/utils/data/_utils/fetch.py:54, in _MapDatasetFetcher.fetch(self, possibly_batched_index)
52 else:
53 data = self.dataset[possibly_batched_index]
---&gt; 54 return self.collate_fn(data)
File ~/miniconda3/envs/nlp_/lib/python3.11/site-packages/transformers/data/data_collator.py:271, in DataCollatorWithPadding.__call__(self, features)
270 def __call__(self, features: List[Dict[str, Any]]) -&gt; Dict[str, Any]:
--&gt; 271 batch = pad_without_fast_tokenizer_warning(
272 self.tokenizer,
273 features,
274 padding=self.padding,
275 max_length=self.max_length,
276 pad_to_multiple_of=self.pad_to_multiple_of,
277 return_tensors=self.return_tensors,
278 )
279 if "label" in batch:
280 batch["labels"] = batch["label"]
File ~/miniconda3/envs/nlp_/lib/python3.11/site-packages/transformers/data/data_collator.py:66, in pad_without_fast_tokenizer_warning(tokenizer, *pad_args, **pad_kwargs)
63 tokenizer.deprecation_warnings["Asking-to-pad-a-fast-tokenizer"] = True
65 try:
---&gt; 66 padded = tokenizer.pad(*pad_args, **pad_kwargs)
67 finally:
68 # Restore the state of the warning.
69 tokenizer.deprecation_warnings["Asking-to-pad-a-fast-tokenizer"] = warning_state
File ~/miniconda3/envs/nlp_/lib/python3.11/site-packages/transformers/tokenization_utils_base.py:3299, in PreTrainedTokenizerBase.pad(self, encoded_inputs, padding, max_length, pad_to_multiple_of, return_attention_mask, return_tensors, verbose)
3297 # The model's main input name, usually `input_ids`, has be passed for padding
3298 if self.model_input_names[0] not in encoded_inputs:
-&gt; 3299 raise ValueError(
3300 "You should supply an encoding or a list of encodings to this method "
3301 f"that includes {self.model_input_names[0]}, but you provided {list(encoded_inputs.keys())}"
3302 )
3304 required_input = encoded_inputs[self.model_input_names[0]]
3306 if required_input is None or (isinstance(required_input, Sized) and len(required_input) == 0):
ValueError: You should supply an encoding or a list of encodings to this method that includes input_ids, but you provided ['label', 'labels']

Nós recebemos novamente um erro porque o modelo não tem um token de padding atribuído, então, assim como com o tokenizador, nós o atribuímos.

	
< > Input
Python
model.config.pad_token_id = model.config.eos_token_id
Copied

Recriamos os argumentos do treinador com o novo modelo, que agora tem um token de padding, o treinador e voltamos a treinar.

	
< > Input
Python
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",
)
trainer = Trainer(
model,
training_args,
train_dataset=dataset['train'],
eval_dataset=dataset['validation'],
tokenizer=tokenizer,
compute_metrics=compute_metrics,
)
Copied

Agora que vimos que tudo está bem, podemos treinar

	
< > Input
Python
trainer.train()
Copied
>_ Output
			
&lt;IPython.core.display.HTML object&gt;
>_ Output
			
&lt;IPython.core.display.HTML object&gt;
>_ Output
			
&lt;transformers.trainer_utils.EvalPrediction object at 0x782767ea1450&gt;
&lt;transformers.trainer_utils.EvalPrediction object at 0x782767eeefe0&gt;
&lt;transformers.trainer_utils.EvalPrediction object at 0x782767eecfd0&gt;
>_ Output
			
TrainOutput(global_step=21429, training_loss=0.7846888848762739, metrics={'train_runtime': 26367.7801, 'train_samples_per_second': 22.755, 'train_steps_per_second': 0.813, 'total_flos': 2.35173445632e+17, 'train_loss': 0.7846888848762739, 'epoch': 3.0})

Avaliaçãolink image 87

Uma vez treinado, avaliamos sobre o dataset de teste

	
< > Input
Python
trainer.evaluate(eval_dataset=dataset['test'])
Copied
>_ Output
			
&lt;IPython.core.display.HTML object&gt;
>_ Output
			
&lt;transformers.trainer_utils.EvalPrediction object at 0x7826ddfded40&gt;
>_ Output
			
{'eval_loss': 0.7973636984825134,
'eval_accuracy': 0.6626,
'eval_runtime': 76.3016,
'eval_samples_per_second': 65.529,
'eval_steps_per_second': 1.638,
'epoch': 3.0}

Publicar o modelolink image 88

Já temos nosso modelo treinado, agora podemos compartilhá-lo com o mundo, então primeiro criamos uma **ficha do modelo**

	
< > Input
Python
trainer.create_model_card()
Copied

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

	
< > Input
Python
trainer.push_to_hub()
Copied

Uso do modelolink image 89

Limpamos tudo o que for possível

	
< > Input
Python
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()
Copied

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

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

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

	
< > Input
Python
labels = classifier("I love this product", top_k=None)
labels
Copied
>_ Output
			
[{'label': 'LABEL_4', 'score': 0.8253807425498962},
{'label': 'LABEL_3', 'score': 0.15411493182182312},
{'label': 'LABEL_2', 'score': 0.013907806016504765},
{'label': 'LABEL_0', 'score': 0.003939222544431686},
{'label': 'LABEL_1', 'score': 0.0026572425849735737}]

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

	
< > Input
Python
label = classifier("I love this product", top_k=1)
label
Copied
>_ Output
			
[{'label': 'LABEL_4', 'score': 0.8253807425498962}]

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

	
< > Input
Python
two_labels = classifier("I love this product", top_k=2)
two_labels
Copied
>_ Output
			
[{'label': 'LABEL_4', 'score': 0.8253807425498962},
{'label': 'LABEL_3', 'score': 0.15411493182182312}]

Também podemos testar o modelo com Automodel e AutoTokenizer

	
< > Input
Python
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 = 5
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=num_classes).half().eval().to("cuda")
Copied
	
< > Input
Python
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]
Copied
>_ Output
			
[0.003963470458984375,
0.0026721954345703125,
0.01397705078125,
0.154541015625,
0.82470703125]

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

Ajuste fino para geração de texto com Hugging Facelink image 90

Para garantizar que não tenho problemas de memória VRAM, reinicio o notebook

Loginlink image 91

Para poder fazer o upload do resultado do treinamento para o hub, devemos nos autenticar primeiro, para isso precisamos de um token

Para criar um token é necessário ir à página de settings/tokens da nossa conta, aparecerá algo assim

User-Access-Token-dark

Damos a New token e uma janela aparecerá para criar um novo token

new-token-dark

Damos um nome ao token e o criamos com o papel write, ou com o papel Fine-grained, que nos permite selecionar exatamente quais permissões terá o token.

Uma vez criado, copiamos e colamos a seguir.

	
< > Input
Python
from huggingface_hub import notebook_login
notebook_login()
Copied

Conjunto de Dadoslink image 92

Vamos a usar um dataset de chistes em inglês

	
< > Input
Python
from datasets import load_dataset
jokes = load_dataset("Maximofn/short-jokes-dataset")
jokes
Copied
>_ Output
			
DatasetDict({
train: Dataset({
features: ['ID', 'Joke'],
num_rows: 231657
})
})

Vamos vê-lo um pouco

	
< > Input
Python
jokes
Copied
>_ Output
			
DatasetDict({
train: Dataset({
features: ['ID', 'Joke'],
num_rows: 231657
})
})

Vemos que é um conjunto de treinamento único com mais de 200 mil piadas. Então, mais tarde teremos que dividi-lo em treinamento e avaliação.

Vamos a ver um exemplo

	
< > Input
Python
from random import randint
idx = randint(0, len(jokes['train']) - 1)
jokes['train'][idx]
Copied
>_ Output
			
{'ID': 198387,
'Joke': 'My hot dislexic co-worker said she had an important massage to give me in her office... When I got there, she told me it can wait until I put on some clothes.'}

Vemos que tem um ID de piada que não nos interessa em absoluto e a própria piada

Caso você tenha pouca memória na GPU, vou fazer um subconjunto do conjunto de dados, escolha o percentual de piadas que deseja usar

	
< > Input
Python
percent_of_train_dataset = 1 # If you want 50% of the dataset, set this to 0.5
subset_dataset = jokes["train"].select(range(int(len(jokes["train"]) * percent_of_train_dataset)))
subset_dataset
Copied
>_ Output
			
Dataset({
features: ['ID', 'Joke'],
num_rows: 231657
})

Agora dividimos o subconjunto em um conjunto de treinamento e outro de validação.

	
< > Input
Python
percent_of_train_dataset = 0.90
split_dataset = subset_dataset.train_test_split(train_size=int(subset_dataset.num_rows * percent_of_train_dataset), seed=19, shuffle=False)
train_dataset = split_dataset["train"]
validation_test_dataset = split_dataset["test"]
split_dataset = validation_test_dataset.train_test_split(train_size=int(validation_test_dataset.num_rows * 0.5), seed=19, shuffle=False)
validation_dataset = split_dataset["train"]
test_dataset = split_dataset["test"]
print(f"Size of the train set: {len(train_dataset)}. Size of the validation set: {len(validation_dataset)}. Size of the test set: {len(test_dataset)}")
Copied
>_ Output
			
Size of the train set: 208491. Size of the validation set: 11583. Size of the test set: 11583

Tokenizadorlink image 93

Instanciamos o tokenizador. Instanciamos o token de padding do tokenizador para que não nos dê erro como antes.

	
< > Input
Python
from transformers import AutoTokenizer
checkpoints = "openai-community/gpt2"
tokenizer = AutoTokenizer.from_pretrained(checkpoints)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
Copied

Vamos adicionar dois novos tokens de início de piada e fim de piada para ter mais controle

	
< > Input
Python
new_tokens = ['&lt;SJ&gt;', '&lt;EJ&gt;'] # Start and end of joke tokens
num_added_tokens = tokenizer.add_tokens(new_tokens)
print(f"Added {num_added_tokens} tokens")
Copied
>_ Output
			
Added 2 tokens

Criamos uma função para adicionar os novos tokens às frases.

	
< > Input
Python
joke_column = "Joke"
def format_joke(example):
example[joke_column] = '&lt;SJ&gt; ' + example['Joke'] + ' &lt;EJ&gt;'
return example
Copied

Selecionamos as colunas que não precisamos

	
< > Input
Python
remove_columns = [column for column in train_dataset.column_names if column != joke_column]
remove_columns
Copied
>_ Output
			
['ID']

Formatamos o conjunto de dados e eliminamos as colunas que não precisamos

	
< > Input
Python
train_dataset = train_dataset.map(format_joke, remove_columns=remove_columns)
validation_dataset = validation_dataset.map(format_joke, remove_columns=remove_columns)
test_dataset = test_dataset.map(format_joke, remove_columns=remove_columns)
train_dataset, validation_dataset, test_dataset
Copied
>_ Output
			
(Dataset({
features: ['Joke'],
num_rows: 208491
}),
Dataset({
features: ['Joke'],
num_rows: 11583
}),
Dataset({
features: ['Joke'],
num_rows: 11583
}))

Agora criamos uma função para tokenizar os piadas

	
< > Input
Python
def tokenize_function(examples):
return tokenizer(examples[joke_column], padding="max_length", truncation=True, max_length=768, return_tensors="pt")
Copied

Tokenizamos o conjunto de dados e removemos a coluna com o texto

	
< > Input
Python
train_dataset = train_dataset.map(tokenize_function, batched=True, remove_columns=[joke_column])
validation_dataset = validation_dataset.map(tokenize_function, batched=True, remove_columns=[joke_column])
test_dataset = test_dataset.map(tokenize_function, batched=True, remove_columns=[joke_column])
train_dataset, validation_dataset, test_dataset
Copied
>_ Output
			
(Dataset({
features: ['input_ids', 'attention_mask'],
num_rows: 208491
}),
Dataset({
features: ['input_ids', 'attention_mask'],
num_rows: 11583
}),
Dataset({
features: ['input_ids', 'attention_mask'],
num_rows: 11583
}))

Modelolink image 94

Agora instanciamos o modelo para geração de texto e atribuímos ao token de padding o token de end of string

	
< > Input
Python
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained(checkpoints)
model.config.pad_token_id = model.config.eos_token_id
Copied

Vemos o tamanho do vocabulário do modelo

	
< > Input
Python
vocab_size = model.config.vocab_size
vocab_size
Copied
>_ Output
			
50257

Tem 50257 tokens, que é o tamanho do vocabulário do GPT2. Mas como dissemos que iríamos criar dois novos tokens com o início da piada e o fim da piada, os adicionamos ao modelo.

	
< > Input
Python
model.resize_token_embeddings(len(tokenizer))
new_vocab_size = model.config.vocab_size
print(f"Old vocab size: {vocab_size}. New vocab size: {new_vocab_size}. Added {new_vocab_size - vocab_size} tokens")
Copied
>_ Output
			
Old vocab size: 50257. New vocab size: 50259. Added 2 tokens

Foram adicionados os dois novos tokens

Treinamentolink image 95

Configuramos os parâmetros de treinamento

	
< > Input
Python
from transformers import TrainingArguments
metric_name = "accuracy"
model_name = "GPT2-small-finetuned-Maximofn-short-jokes-dataset-casualLM"
output_dir = f"./training_results"
LR = 2e-5
BS_TRAIN = 28
BS_EVAL = 32
EPOCHS = 3
WEIGHT_DECAY = 0.01
WARMUP_STEPS = 100
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,
warmup_steps=WARMUP_STEPS,
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,
)
Copied

Agora não usamos metric_for_best_model, depois de definir o treinador explicamos por que

Definimos o treinador

	
< > Input
Python
from transformers import Trainer
trainer = Trainer(
model,
training_args,
train_dataset=train_dataset,
eval_dataset=validation_dataset,
tokenizer=tokenizer,
# compute_metrics=compute_metrics,
)
Copied

Neste caso, não passamos uma função compute_metrics, mas sim, durante a avaliação, será usada a loss para avaliar o modelo. Por isso, ao definir os argumentos, não definimos metric_for_best_model, pois não vamos usar uma métrica para avaliar o modelo, mas sim a loss.

Treinamos

	
< > Input
Python
trainer.train()
Copied
>_ Output
			
0%| | 0/625473 [00:00&lt;?, ?it/s]
>_ Output
			
---------------------------------------------------------------------------ValueError Traceback (most recent call last)Cell In[19], line 1
----&gt; 1 trainer.train()
File ~/miniconda3/envs/nlp_/lib/python3.11/site-packages/transformers/trainer.py:1885, in Trainer.train(self, resume_from_checkpoint, trial, ignore_keys_for_eval, **kwargs)
1883 hf_hub_utils.enable_progress_bars()
1884 else:
-&gt; 1885 return inner_training_loop(
1886 args=args,
1887 resume_from_checkpoint=resume_from_checkpoint,
1888 trial=trial,
1889 ignore_keys_for_eval=ignore_keys_for_eval,
1890 )
File ~/miniconda3/envs/nlp_/lib/python3.11/site-packages/transformers/trainer.py:2216, in Trainer._inner_training_loop(self, batch_size, args, resume_from_checkpoint, trial, ignore_keys_for_eval)
2213 self.control = self.callback_handler.on_step_begin(args, self.state, self.control)
2215 with self.accelerator.accumulate(model):
-&gt; 2216 tr_loss_step = self.training_step(model, inputs)
2218 if (
2219 args.logging_nan_inf_filter
2220 and not is_torch_xla_available()
2221 and (torch.isnan(tr_loss_step) or torch.isinf(tr_loss_step))
2222 ):
2223 # if loss is nan or inf simply add the average of previous logged losses
2224 tr_loss += tr_loss / (1 + self.state.global_step - self._globalstep_last_logged)
File ~/miniconda3/envs/nlp_/lib/python3.11/site-packages/transformers/trainer.py:3238, in Trainer.training_step(self, model, inputs)
3235 return loss_mb.reduce_mean().detach().to(self.args.device)
3237 with self.compute_loss_context_manager():
-&gt; 3238 loss = self.compute_loss(model, inputs)
3240 del inputs
3241 torch.cuda.empty_cache()
File ~/miniconda3/envs/nlp_/lib/python3.11/site-packages/transformers/trainer.py:3282, in Trainer.compute_loss(self, model, inputs, return_outputs)
3280 else:
3281 if isinstance(outputs, dict) and "loss" not in outputs:
-&gt; 3282 raise ValueError(
3283 "The model did not return a loss from the inputs, only the following keys: "
3284 f"{','.join(outputs.keys())}. For reference, the inputs it received are {','.join(inputs.keys())}."
3285 )
3286 # We don't use .loss here since the model may return tuples instead of ModelOutput.
3287 loss = outputs["loss"] if isinstance(outputs, dict) else outputs[0]
ValueError: The model did not return a loss from the inputs, only the following keys: logits,past_key_values. For reference, the inputs it received are input_ids,attention_mask.

Como vemos, nos dá um erro, dizendo que o modelo não retorna o valor do loss, que é fundamental para poder treinar, vamos ver por quê.

Vamos ver como é um exemplo do dataset

	
< > Input
Python
idx = randint(0, len(train_dataset) - 1)
sample = train_dataset[idx]
sample
Copied
>_ Output
			
{'input_ids': [50257,
4162,
750,
262,
18757,
6451,
2245,
2491,
30,
4362,
340,
373,
734,
10032,
13,
220,
50258,
50256,
50256,
...,
50256,
50256,
50256],
'attention_mask': [1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
0,
0,
0,
...,
0,
0,
0]}

Como podemos ver, temos um dicionário com os input_ids e as attention_mask. Se o passarmos para o modelo, obtemos isso

	
< > Input
Python
import torch
output = model(
input_ids=torch.Tensor(sample["input_ids"]).long().unsqueeze(0).to(model.device),
attention_mask=torch.Tensor(sample["attention_mask"]).long().unsqueeze(0).to(model.device),
)
print(output.loss)
Copied
>_ Output
			
None

Como vemos, não retorna o valor da loss porque está esperando um valor para labels, que não foi fornecido. No exemplo anterior, em que fazíamos fine tuning para classificação de texto, dissemos que as etiquetas devem ser passadas para um campo do dataset chamado labels, mas neste caso não temos esse campo no dataset.

Se agora atribuirmos as labels aos input_ids e voltarmos a ver a loss

	
< > Input
Python
import torch
output = model(
input_ids=torch.Tensor(sample["input_ids"]).long().unsqueeze(0).to(model.device),
attention_mask=torch.Tensor(sample["attention_mask"]).long().unsqueeze(0).to(model.device),
labels=torch.Tensor(sample["input_ids"]).long().unsqueeze(0).to(model.device)
)
print(output.loss)
Copied
>_ Output
			
tensor(102.1873, device='cuda:0', grad_fn=&lt;NllLossBackward0&gt;)

Agora sim obtemos uma loss

Portanto, temos duas opções: adicionar um campo labels ao dataset, com os valores de input_ids, ou utilizar uma função da biblioteca transformers chamada data_collator. Neste caso, usaremos DataCollatorForLanguageModeling. Vamos ver isso.

	
< > Input
Python
from transformers import DataCollatorForLanguageModeling
my_data_collator=DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
Copied

Passamos a amostra sample por este data_collator

	
< > Input
Python
collated_sample = my_data_collator([sample]).to(model.device)
Copied

Vemos como é a saída

	
< > Input
Python
for key, value in collated_sample.items():
print(f"{key} ({value.shape}): {value}")
Copied
>_ Output
			
input_ids (torch.Size([1, 768])): tensor([[50257, 4162, 750, 262, 18757, 6451, 2245, 2491, 30, 4362,
340, 373, 734, 10032, 13, 220, 50258, 50256, ..., 50256, 50256]],
device='cuda:0')
attention_mask (torch.Size([1, 768])): tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, ..., 0, 0]],
device='cuda:0')
labels (torch.Size([1, 768])): tensor([[50257, 4162, 750, 262, 18757, 6451, 2245, 2491, 30, 4362,
340, 373, 734, 10032, 13, 220, 50258, -100, ..., -100, -100]],
device='cuda:0')

Como se pode ver, o data_collator criou um campo labels e lhe atribuiu os valores de input_ids. Os tokens que estão mascarados receberam o valor -100. Isso ocorre porque quando definimos o data_collator, passamos o parâmetro mlm=False, o que significa que não estamos fazendo Masked Language Modeling, mas sim Language Modeling, por isso nenhum token original é mascarado.

Vamos ver se agora obtemos uma loss com esse data_collator

	
< > Input
Python
output = model(**collated_sample)
output.loss
Copied
>_ Output
			
tensor(102.7181, device='cuda:0', grad_fn=&lt;NllLossBackward0&gt;)

Então, redefinimos o trainer com o data_collator e treinamos novamente.

	
< > Input
Python
from transformers import DataCollatorForLanguageModeling
trainer = Trainer(
model,
training_args,
train_dataset=train_dataset,
eval_dataset=validation_dataset,
tokenizer=tokenizer,
data_collator=DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False),
)
Copied
	
< > Input
Python
trainer.train()
Copied
>_ Output
			
&lt;IPython.core.display.HTML object&gt;
>_ Output
			
There were missing keys in the checkpoint model loaded: ['lm_head.weight'].
>_ Output
			
TrainOutput(global_step=22341, training_loss=3.505178199598342, metrics={'train_runtime': 9209.5353, 'train_samples_per_second': 67.916, 'train_steps_per_second': 2.426, 'total_flos': 2.45146666696704e+17, 'train_loss': 3.505178199598342, 'epoch': 3.0})

Avaliaçãolink image 96

Uma vez treinado, avaliamos o modelo sobre o dataset de teste.

	
< > Input
Python
trainer.evaluate(eval_dataset=test_dataset)
Copied
>_ Output
			
&lt;IPython.core.display.HTML object&gt;
>_ Output
			
{'eval_loss': 3.201305866241455,
'eval_runtime': 65.0033,
'eval_samples_per_second': 178.191,
'eval_steps_per_second': 5.569,
'epoch': 3.0}

Publicar o modelolink image 97

Criamos o cartão do modelo

	
< > Input
Python
trainer.create_model_card()
Copied

Publicamos

	
< > Input
Python
trainer.push_to_hub()
Copied
>_ Output
			
events.out.tfevents.1720875425.8de3af1b431d.6946.1: 0%| | 0.00/364 [00:00&lt;?, ?B/s]
>_ Output
			
CommitInfo(commit_url='https://huggingface.co/Maximofn/GPT2-small-finetuned-Maximofn-short-jokes-dataset-casualLM/commit/d107b3bb0e02076483238f9975697761015ec390', commit_message='End of training', commit_description='', oid='d107b3bb0e02076483238f9975697761015ec390', pr_url=None, pr_revision=None, pr_num=None)

Uso do modelolink image 98

Limpez tudo o possível

	
< > Input
Python
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()
Copied

Baixamos o modelo e o tokenizador

	
< > Input
Python
from transformers import AutoTokenizer, AutoModelForCausalLM
user = "maximofn"
checkpoints = f"{user}/{model_name}"
tokenizer = AutoTokenizer.from_pretrained(checkpoints)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
model = AutoModelForCausalLM.from_pretrained(checkpoints)
model.config.pad_token_id = model.config.eos_token_id
Copied
>_ Output
			
Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.

Verificamos que o tokenizador e o modelo tenham os 2 tokens extras que adicionamos.

	
< > Input
Python
tokenizer_vocab = tokenizer.get_vocab()
model_vocab = model.config.vocab_size
print(f"tokenizer_vocab: {len(tokenizer_vocab)}. model_vocab: {model_vocab}")
Copied
>_ Output
			
tokenizer_vocab: 50259. model_vocab: 50259

Vemos que têm 50259 tokens, ou seja, os 50257 tokens do GPT2 mais os 2 que adicionamos.

Criamos uma função para gerar piadas

	
< > Input
Python
def generate_joke(prompt_text):
text = f"&lt;SJ&gt; {prompt_text}"
tokens = tokenizer(text, return_tensors="pt").to(model.device)
with torch.no_grad():
output = model.generate(**tokens, max_new_tokens=256, eos_token_id=tokenizer.encode("&lt;EJ&gt;")[-1])
return tokenizer.decode(output[0], skip_special_tokens=False)
Copied

Geramos uma piada

	
< > Input
Python
generate_joke("Why didn't the frog cross the road?")
Copied
>_ Output
			
Setting `pad_token_id` to `eos_token_id`:50258 for open-end generation.
>_ Output
			
"&lt;SJ&gt; Why didn't the frog cross the road? Because he was frog-in-the-face. &lt;EJ&gt;"

Se quiser testar mais o modelo, você pode vê-lo em Maximofn/GPT2-small-finetuned-Maximofn-short-jokes-dataset-casualLM

Ajuste fino para classificação de texto com Pytorchlink image 99

Repetimos o treinamento com Pytorch

Reiniciamos o notebook para nos assegurar

Conjunto de Dadoslink image 100

Baixamos o mesmo conjunto de dados que quando fizemos o treinamento com as bibliotecas do Hugging Face

	
< > Input
Python
from datasets import load_dataset
dataset = load_dataset("mteb/amazon_reviews_multi", "en")
Copied

Criamos uma variável com o número de classes

	
< > Input
Python
num_classes = len(dataset['train'].unique('label'))
num_classes
Copied
>_ Output
			
5

Antes processávamos todo o conjunto de dados para criar um campo chamado labels, mas agora não é necessário porque, como vamos programar tudo nós mesmos, nos adaptamos a como é o conjunto de dados.

Tokenizadorlink image 101

Criamos o tokenizador. Atribuímos o token de padding para que não nos dê erro como antes.

	
< > Input
Python
from transformers import AutoTokenizer
checkpoint = "openai-community/gpt2"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
tokenizer.pad_token = tokenizer.eos_token
Copied

Criamos uma função para tokenizar o dataset

	
< > Input
Python
def tokenize_function(examples):
return tokenizer(examples["text"], padding="max_length", truncation=True, max_length=768, return_tensors="pt")
Copied

Tokenizamos. Removemos colunas que não sejam necessárias, mas agora mantemos a de texto.

	
< > Input
Python
dataset = dataset.map(tokenize_function, batched=True, remove_columns=['id', 'label_text'])
Copied
	
< > Input
Python
dataset
Copied
>_ Output
			
DatasetDict({
train: Dataset({
features: ['text', 'label', 'input_ids', 'attention_mask'],
num_rows: 200000
})
validation: Dataset({
features: ['text', 'label', 'input_ids', 'attention_mask'],
num_rows: 5000
})
test: Dataset({
features: ['text', 'label', 'input_ids', 'attention_mask'],
num_rows: 5000
})
})
	
< > Input
Python
percentage = 1
subset_train = dataset['train'].select(range(int(len(dataset['train']) * percentage)))
percentage = 1
subset_validation = dataset['validation'].select(range(int(len(dataset['validation']) * percentage)))
subset_test = dataset['test'].select(range(int(len(dataset['test']) * percentage)))
print(f"len subset_train: {len(subset_train)}, len subset_validation: {len(subset_validation)}, len subset_test: {len(subset_test)}")
Copied
>_ Output
			
len subset_train: 200000, len subset_validation: 5000, len subset_test: 5000

Modelolink image 102

Importamos os pesos e atribuímos o token de padding

	
< > Input
Python
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=num_classes)
model.config.pad_token_id = model.config.eos_token_id
Copied
>_ Output
			
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.

Dispositivolink image 103

Criamos o dispositivo onde tudo será executado

	
< > Input
Python
import torch
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
Copied

De passagem, transferimos o modelo para o dispositivo e, de passagem, o convertemos para FP16 para ocupar menos memória

	
< > Input
Python
model.half().to(device)
print()
Copied
>_ Output
			

Conjunto de Dados do Pytorchlink image 104

Criamos um dataset de pytorch

	
< > Input
Python
from torch.utils.data import Dataset
class ReviewsDataset(Dataset):
def __init__(self, huggingface_dataset):
self.dataset = huggingface_dataset
def __getitem__(self, idx):
label = self.dataset[idx]['label']
input_ids = torch.tensor(self.dataset[idx]['input_ids'])
attention_mask = torch.tensor(self.dataset[idx]['attention_mask'])
return input_ids, attention_mask, label
def __len__(self):
return len(self.dataset)
Copied

Instanciamos os datasets

	
< > Input
Python
train_dataset = ReviewsDataset(subset_train)
validatation_dataset = ReviewsDataset(subset_validation)
test_dataset = ReviewsDataset(subset_test)
Copied

Vamos dar uma olhada em um exemplo

	
< > Input
Python
input_ids, at_mask, label = train_dataset[0]
input_ids.shape, at_mask.shape, label
Copied
>_ Output
			
(torch.Size([768]), torch.Size([768]), 0)

Pytorch Dataloaderlink image 105

Criamos agora um DataLoader do PyTorch

	
< > Input
Python
from torch.utils.data import DataLoader
BS = 12
train_loader = DataLoader(train_dataset, batch_size=BS, shuffle=True)
validation_loader = DataLoader(validatation_dataset, batch_size=BS)
test_loader = DataLoader(test_dataset, batch_size=BS)
Copied

Vamos dar uma olhada em um exemplo

	
< > Input
Python
input_ids, at_mask, labels = next(iter(train_loader))
input_ids.shape, at_mask.shape, labels
Copied
>_ Output
			
(torch.Size([12, 768]),
torch.Size([12, 768]),
tensor([2, 1, 2, 0, 3, 3, 0, 4, 3, 3, 4, 2]))

Para verificar se tudo está bem, passamos a amostra ao modelo para ver se tudo sai bem. Primeiro passamos os tokens para o dispositivo.

	
< > Input
Python
input_ids = input_ids.to(device)
at_mask = at_mask.to(device)
labels = labels.to(device)
Copied

Agora passamos isso para o modelo

	
< > Input
Python
output = model(input_ids=input_ids, attention_mask=at_mask, labels=labels)
output.keys()
Copied
>_ Output
			
odict_keys(['loss', 'logits', 'past_key_values'])

Como vemos, nos dá a loss e os logits

	
< > Input
Python
output['loss']
Copied
>_ Output
			
tensor(5.9414, device='cuda:0', dtype=torch.float16,
grad_fn=&lt;NllLossBackward0&gt;)
	
< > Input
Python
output['logits']
Copied
>_ Output
			
tensor([[ 6.1953e+00, -1.2275e+00, -2.4824e+00, 5.8867e+00, -1.4734e+01],
[ 5.4062e+00, -8.4570e-01, -2.3203e+00, 5.1055e+00, -1.1555e+01],
[ 6.1641e+00, -9.3066e-01, -2.5664e+00, 6.0039e+00, -1.4570e+01],
[ 5.2266e+00, -4.2358e-01, -2.0801e+00, 4.7461e+00, -1.1570e+01],
[ 3.8184e+00, -2.3460e-03, -1.7666e+00, 3.4160e+00, -7.7969e+00],
[ 4.1641e+00, -4.8169e-01, -1.6914e+00, 3.9941e+00, -8.7734e+00],
[ 4.6758e+00, -3.0298e-01, -2.1641e+00, 4.1055e+00, -9.3359e+00],
[ 4.1953e+00, -3.2471e-01, -2.1875e+00, 3.9375e+00, -8.3438e+00],
[-1.1650e+00, 1.3564e+00, -6.2158e-01, -6.8115e-01, 4.8672e+00],
[ 4.4961e+00, -8.7891e-02, -2.2793e+00, 4.2812e+00, -9.3359e+00],
[ 4.9336e+00, -2.6627e-03, -2.1543e+00, 4.3711e+00, -1.0742e+01],
[ 5.9727e+00, -4.3152e-02, -1.4551e+00, 4.3438e+00, -1.2117e+01]],
device='cuda:0', dtype=torch.float16, grad_fn=&lt;IndexBackward0&gt;)

Métricalink image 106

Vamos a criar uma função para obter a métrica, que neste caso vai ser a acurácia.

	
< > Input
Python
def predicted_labels(logits):
percent = torch.softmax(logits, dim=1)
predictions = torch.argmax(percent, dim=1)
return predictions
Copied
	
< > Input
Python
def compute_accuracy(logits, labels):
predictions = predicted_labels(logits)
correct = (predictions == labels).float()
return correct.mean()
Copied

Vamos a ver se ele calcula bem.

	
< > Input
Python
compute_accuracy(output['logits'], labels).item()
Copied
>_ Output
			
0.1666666716337204

Otimizadorlink image 107

Como vamos a precisar um otimizador, criamos um

	
< > Input
Python
from transformers import AdamW
LR = 2e-5
optimizer = AdamW(model.parameters(), lr=LR)
Copied
>_ Output
			
/usr/local/lib/python3.10/dist-packages/transformers/optimization.py:588: FutureWarning: This implementation of AdamW is deprecated and will be removed in a future version. Use the PyTorch implementation torch.optim.AdamW instead, or set `no_deprecation_warning=True` to disable this warning
warnings.warn(

Treinamentolink image 108

Criamos o loop de treinamento

	
< > Input
Python
from tqdm import tqdm
EPOCHS = 3
accuracy = 0
for epoch in range(EPOCHS):
model.train()
train_loss = 0
progresbar = tqdm(train_loader, total=len(train_loader), desc=f'Epoch {epoch + 1}')
for input_ids, at_mask, labels in progresbar:
input_ids = input_ids.to(device)
at_mask = at_mask.to(device)
label = labels.to(device)
output = model(input_ids=input_ids, attention_mask=at_mask, labels=label)
loss = output['loss']
train_loss += loss.item()
optimizer.zero_grad()
loss.backward()
optimizer.step()
progresbar.set_postfix({'train_loss': loss.item()})
train_loss /= len(train_loader)
progresbar.set_postfix({'train_loss': train_loss})
model.eval()
valid_loss = 0
progresbar = tqdm(validation_loader, total=len(validation_loader), desc=f'Epoch {epoch + 1}')
for input_ids, at_mask, labels in progresbar:
input_ids = input_ids.to(device)
at_mask = at_mask.to(device)
labels = labels.to(device)
output = model(input_ids=input_ids, attention_mask=at_mask, labels=labels)
loss = output['loss']
valid_loss += loss.item()
step_accuracy = compute_accuracy(output['logits'], labels)
accuracy += step_accuracy
progresbar.set_postfix({'valid_loss': loss.item(), 'accuracy': step_accuracy.item()})
valid_loss /= len(validation_loader)
accuracy /= len(validation_loader)
progresbar.set_postfix({'valid_loss': valid_loss, 'accuracy': accuracy})
Copied
>_ Output
			
Epoch 1: 100%|██████████| 16667/16667 [44:13&lt;00:00, 6.28it/s, train_loss=nan]
Epoch 1: 100%|██████████| 417/417 [00:32&lt;00:00, 12.72it/s, valid_loss=nan, accuracy=0]
Epoch 2: 100%|██████████| 16667/16667 [44:06&lt;00:00, 6.30it/s, train_loss=nan]
Epoch 2: 100%|██████████| 417/417 [00:32&lt;00:00, 12.77it/s, valid_loss=nan, accuracy=0]
Epoch 3: 100%|██████████| 16667/16667 [44:03&lt;00:00, 6.30it/s, train_loss=nan]
Epoch 3: 100%|██████████| 417/417 [00:32&lt;00:00, 12.86it/s, valid_loss=nan, accuracy=0]

Uso do modelolink image 109

Vamos a testar o modelo que treinamos

Primeiro tokenizamos um texto

	
< > Input
Python
input_tokens = tokenize_function({"text": "I love this product. It is amazing."})
input_tokens['input_ids'].shape, input_tokens['attention_mask'].shape
Copied
>_ Output
			
(torch.Size([1, 768]), torch.Size([1, 768]))

Agora passamos isso para o modelo.

	
< > Input
Python
output = model(input_ids=input_tokens['input_ids'].to(device), attention_mask=input_tokens['attention_mask'].to(device))
output['logits']
Copied
>_ Output
			
tensor([[nan, nan, nan, nan, nan]], device='cuda:0', dtype=torch.float16,
grad_fn=&lt;IndexBackward0&gt;)

Vemos as previsões desses logits

	
< > Input
Python
predicted = predicted_labels(output['logits'])
predicted
Copied
>_ Output
			
tensor([0], device='cuda:0')

Ajuste fino para geração de texto com Pytorchlink image 110

Repetimos o treinamento com Pytorch

Reiniciamos o notebook para nos assegurar

Conjunto de Dadoslink image 111

Voltamos a baixar o conjunto de dados de piadas

	
< > Input
Python
from datasets import load_dataset
jokes = load_dataset("Maximofn/short-jokes-dataset")
jokes
Copied
>_ Output
			
DatasetDict({
train: Dataset({
features: ['ID', 'Joke'],
num_rows: 231657
})
})

Criamos um subconjunto caso haja pouca memória

	
< > Input
Python
percent_of_train_dataset = 1 # If you want 50% of the dataset, set this to 0.5
subset_dataset = jokes["train"].select(range(int(len(jokes["train"]) * percent_of_train_dataset)))
subset_dataset
Copied
>_ Output
			
Dataset({
features: ['ID', 'Joke'],
num_rows: 231657
})

Dividimos o conjunto de dados em subconjuntos de treinamento, validação e teste.

	
< > Input
Python
percent_of_train_dataset = 0.90
split_dataset = subset_dataset.train_test_split(train_size=int(subset_dataset.num_rows * percent_of_train_dataset), seed=19, shuffle=False)
train_dataset = split_dataset["train"]
validation_test_dataset = split_dataset["test"]
split_dataset = validation_test_dataset.train_test_split(train_size=int(validation_test_dataset.num_rows * 0.5), seed=19, shuffle=False)
validation_dataset = split_dataset["train"]
test_dataset = split_dataset["test"]
print(f"Size of the train set: {len(train_dataset)}. Size of the validation set: {len(validation_dataset)}. Size of the test set: {len(test_dataset)}")
Copied
>_ Output
			
Size of the train set: 208491. Size of the validation set: 11583. Size of the test set: 11583

Tokenizadorlink image 112

Iniciamos o tokenizador e atribuímos ao token de padding o de end of string

	
< > Input
Python
from transformers import AutoTokenizer
checkpoints = "openai-community/gpt2"
tokenizer = AutoTokenizer.from_pretrained(checkpoints)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
Copied

Adicionamos os tokens especiais de início e fim de piada

	
< > Input
Python
new_tokens = ['&lt;SJ&gt;', '&lt;EJ&gt;'] # Start and end of joke tokens
num_added_tokens = tokenizer.add_tokens(new_tokens)
print(f"Added {num_added_tokens} tokens")
Copied
>_ Output
			
Added 2 tokens

Os adicionamos ao dataset

	
< > Input
Python
joke_column = "Joke"
def format_joke(example):
example[joke_column] = '&lt;SJ&gt; ' + example['Joke'] + ' &lt;EJ&gt;'
return example
remove_columns = [column for column in train_dataset.column_names if column != joke_column]
train_dataset = train_dataset.map(format_joke, remove_columns=remove_columns)
validation_dataset = validation_dataset.map(format_joke, remove_columns=remove_columns)
test_dataset = test_dataset.map(format_joke, remove_columns=remove_columns)
train_dataset, validation_dataset, test_dataset
Copied
>_ Output
			
(Dataset({
features: ['Joke'],
num_rows: 208491
}),
Dataset({
features: ['Joke'],
num_rows: 11583
}),
Dataset({
features: ['Joke'],
num_rows: 11583
}))

Tokenizamos o conjunto de dados

	
< > Input
Python
def tokenize_function(examples):
return tokenizer(examples[joke_column], padding="max_length", truncation=True, max_length=768, return_tensors="pt")
train_dataset = train_dataset.map(tokenize_function, batched=True, remove_columns=[joke_column])
validation_dataset = validation_dataset.map(tokenize_function, batched=True, remove_columns=[joke_column])
test_dataset = test_dataset.map(tokenize_function, batched=True, remove_columns=[joke_column])
train_dataset, validation_dataset, test_dataset
Copied
>_ Output
			
(Dataset({
features: ['input_ids', 'attention_mask'],
num_rows: 208491
}),
Dataset({
features: ['input_ids', 'attention_mask'],
num_rows: 11583
}),
Dataset({
features: ['input_ids', 'attention_mask'],
num_rows: 11583
}))

Modelolink image 113

Instanciamos o modelo, atribuímos o token de padding e adicionamos os novos tokens de início de piada e fim de piada

	
< > Input
Python
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained(checkpoints)
model.config.pad_token_id = model.config.eos_token_id
model.resize_token_embeddings(len(tokenizer))
Copied
>_ Output
			
Embedding(50259, 768)

Dispositivolink image 114

Criamos o dispositivo e passamos o modelo para o dispositivo

	
< > Input
Python
import torch
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.half().to(device)
print()
Copied
>_ Output
			

Conjunto de Dados do Pytorchlink image 115

Criamos um conjunto de dados do PyTorch

	
< > Input
Python
from torch.utils.data import Dataset
class JokesDataset(Dataset):
def __init__(self, huggingface_dataset):
self.dataset = huggingface_dataset
def __getitem__(self, idx):
input_ids = torch.tensor(self.dataset[idx]['input_ids'])
attention_mask = torch.tensor(self.dataset[idx]['attention_mask'])
return input_ids, attention_mask
def __len__(self):
return len(self.dataset)
Copied

Instanciamos os conjuntos de dados de treinamento, validação e teste

	
< > Input
Python
train_pytorch_dataset = JokesDataset(train_dataset)
validation_pytorch_dataset = JokesDataset(validation_dataset)
test_pytorch_dataset = JokesDataset(test_dataset)
Copied

Vamos ver um exemplo.

	
< > Input
Python
input_ids, attention_mask = train_pytorch_dataset[0]
input_ids.shape, attention_mask.shape
Copied
>_ Output
			
(torch.Size([768]), torch.Size([768]))

Pytorch Dataloaderlink image 116

Criamos os dataloaders

	
< > Input
Python
from torch.utils.data import DataLoader
BS = 28
train_loader = DataLoader(train_pytorch_dataset, batch_size=BS, shuffle=True)
validation_loader = DataLoader(validation_pytorch_dataset, batch_size=BS)
test_loader = DataLoader(test_pytorch_dataset, batch_size=BS)
Copied

Vemos uma amostra

	
< > Input
Python
input_ids, attention_mask = next(iter(train_loader))
input_ids.shape, attention_mask.shape
Copied
>_ Output
			
(torch.Size([28, 768]), torch.Size([28, 768]))

Passamos isso para o modelo.

	
< > Input
Python
output = model(input_ids.to(device), attention_mask=attention_mask.to(device))
output.keys()
Copied
>_ Output
			
odict_keys(['logits', 'past_key_values'])

Como podemos ver, não temos um valor de loss. Como já vimos, precisamos passar o input_ids e o labels.

	
< > Input
Python
output = model(input_ids.to(device), attention_mask=attention_mask.to(device), labels=input_ids.to(device))
output.keys()
Copied
>_ Output
			
odict_keys(['loss', 'logits', 'past_key_values'])

Agora sim temos loss

	
< > Input
Python
output['loss'].item()
Copied
>_ Output
			
80.5625

Otimizadorlink image 117

Criamos um otimizador

	
< > Input
Python
from transformers import AdamW
LR = 2e-5
optimizer = AdamW(model.parameters(), lr=5e-5)
Copied
>_ Output
			
/usr/local/lib/python3.10/dist-packages/transformers/optimization.py:588: FutureWarning: This implementation of AdamW is deprecated and will be removed in a future version. Use the PyTorch implementation torch.optim.AdamW instead, or set `no_deprecation_warning=True` to disable this warning
warnings.warn(

Treinamentolink image 118

Criamos o loop de treinamento

	
< > Input
Python
from tqdm import tqdm
EPOCHS = 3
for epoch in range(EPOCHS):
model.train()
train_loss = 0
progresbar = tqdm(train_loader, total=len(train_loader), desc=f'Epoch {epoch + 1}')
for input_ids, at_mask in progresbar:
input_ids = input_ids.to(device)
at_mask = at_mask.to(device)
output = model(input_ids=input_ids, attention_mask=at_mask, labels=input_ids)
loss = output['loss']
train_loss += loss.item()
optimizer.zero_grad()
loss.backward()
optimizer.step()
progresbar.set_postfix({'train_loss': loss.item()})
train_loss /= len(train_loader)
progresbar.set_postfix({'train_loss': train_loss})
Copied
>_ Output
			
Epoch 1: 100%|██████████| 7447/7447 [51:07&lt;00:00, 2.43it/s, train_loss=nan]
Epoch 2: 100%|██████████| 7447/7447 [51:06&lt;00:00, 2.43it/s, train_loss=nan]
Epoch 3: 100%|██████████| 7447/7447 [51:07&lt;00:00, 2.43it/s, train_loss=nan]

Uso do modelolink image 119

Testamos o modelo

	
< > Input
Python
def generate_text(decoded_joke, max_new_tokens=100, stop_token='&lt;EJ&gt;', top_k=0, temperature=1.0):
input_tokens = tokenize_function({'Joke': decoded_joke})
output = model(input_tokens['input_ids'].to(device), attention_mask=input_tokens['attention_mask'].to(device))
nex_token = torch.argmax(output['logits'][:, -1, :], dim=-1).item()
nex_token_decoded = tokenizer.decode(nex_token)
decoded_joke = decoded_joke + nex_token_decoded
for _ in range(max_new_tokens):
nex_token = torch.argmax(output['logits'][:, -1, :], dim=-1).item()
nex_token_decoded = tokenizer.decode(nex_token)
if nex_token_decoded == stop_token:
break
decoded_joke = decoded_joke + nex_token_decoded
input_tokens = tokenize_function({'Joke': decoded_joke})
output = model(input_tokens['input_ids'].to(device), attention_mask=input_tokens['attention_mask'].to(device))
return decoded_joke
Copied
	
< > Input
Python
generated_text = generate_text("&lt;SJ&gt; Why didn't the frog cross the road")
generated_text
Copied
>_ Output
			
"&lt;SJ&gt; Why didn't the frog cross the road!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"

Continuar lendo

Últimos posts -->

Você viu esses projetos?

Gymnasia

Gymnasia Gymnasia
React Native
Expo
TypeScript
FastAPI
Next.js
OpenAI
Anthropic

Aplicativo móvel de treino pessoal com assistente de IA, biblioteca de exercícios, acompanhamento de rotinas, dieta e medidas corporais

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
Ver todos os projetos -->
>_ Disponível para projetos

Tem um projeto com IA?

Vamos conversar.

maximofn@gmail.com

Especialista em Machine Learning e Inteligência Artificial. Desenvolvo soluções com IA generativa, agentes inteligentes e modelos personalizados.

Quer assistir alguma palestra?

Últimas palestras -->

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 -->
>_ Disponível para projetos

Tem um projeto com IA?

Vamos conversar.

maximofn@gmail.com

Especialista em Machine Learning e Inteligência Artificial. Desenvolvo soluções com IA generativa, agentes inteligentes e modelos personalizados.

Você quer treinar seu modelo com esses datasets?

short-jokes-dataset

HuggingFace

Dataset com piadas em inglês

Uso: Fine-tuning de modelos de geração de texto humorístico

231K linhas 2 colunas 45 MB
Ver no HuggingFace →

opus100

HuggingFace

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

Uso: Treinamento de modelos de tradução inglês-espanhol

1M linhas 2 colunas 210 MB
Ver no HuggingFace →

netflix_titles

HuggingFace

Dataset com filmes e séries da Netflix

Uso: Análise de catálogo Netflix e sistemas de recomendação

8.8K linhas 12 colunas 3.5 MB
Ver no HuggingFace →
Ver mais datasets -->