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

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

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.
from huggingface_hub import notebook_loginnotebook_login()
Conjunto de Dados
Agora vamos baixar um dataset, neste caso vamos baixar um de avaliaçÔes do Amazon
from datasets import load_datasetdataset = load_dataset("mteb/amazon_reviews_multi", "en")
Vamos vĂȘ-lo um pouco
dataset
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
from random import randintidx = randint(0, len(dataset['train']) - 1)dataset['train'][idx]
{'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.
num_classes = len(dataset['train'].unique('label'))num_classes
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
dataset.unique('label')
{'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
def set_labels(example):example['labels'] = example['label']return example
Aplicamos a função ao conjunto de dados
dataset = dataset.map(set_labels)
Vamos ver como fica o dataset
dataset['train'][idx]
{'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}
Tokenizador
Como temos as avaliaçÔes em texto no conjunto de dados, precisamos tokenizå-las para poder inserir os tokens no modelo.
from transformers import AutoTokenizercheckpoint = "openai-community/gpt2"tokenizer = AutoTokenizer.from_pretrained(checkpoint)
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
def tokenize_function(examples):return tokenizer(examples["text"], padding="max_length", truncation=True, max_length=768, return_tensors="pt")
Vamos a provar a tokenizar um texto
tokens = tokenize_function(dataset['train'][idx])
---------------------------------------------------------------------------ValueError Traceback (most recent call last)Cell In[11], line 1----> 1 tokens = tokenize_function(dataset['train'][idx])Cell In[10], line 2, in tokenize_function(examples)1 def tokenize_function(examples):----> 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()-> 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:-> 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'-> 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 token2787 if padding_strategy != PaddingStrategy.DO_NOT_PAD and (self.pad_token is None or self.pad_token_id < 0):-> 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 provided2795 if (2796 truncation_strategy != TruncationStrategy.DO_NOT_TRUNCATE2797 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.
tokenizer.pad_token = tokenizer.eos_token
Voltamos a testar a função de tokenização
tokens = tokenize_function(dataset['train'][idx])tokens['input_ids'].shape, tokens['attention_mask'].shape
(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.
dataset = dataset.map(tokenize_function, batched=True, remove_columns=['text', 'label', 'id', 'label_text'])
Vamos ver agora como fica o dataset
dataset
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.
Modelo
Instanciamos um modelo para classificação de sequĂȘncias e indicamos o nĂșmero de classes que temos
from transformers import AutoModelForSequenceClassificationmodel = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=num_classes)
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
from transformers import AutoModelForCausalLMcasual_model = AutoModelForCausalLM.from_pretrained(checkpoint)
Enquanto o modelo do GPT2 para gerar texto Ă© este
Vamos a ver sua arquitetura
casual_model
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
model
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 camadaLinear
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 camadaLinear
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.
del casual_model
Treinador
Vamos agora a configurar os argumentos do treinamento
from transformers import TrainingArgumentsmetric_name = "accuracy"model_name = "GPT2-small-finetuned-amazon-reviews-en-classification"LR = 2e-5BS_TRAIN = 28BS_EVAL = 40EPOCHS = 3WEIGHT_DECAY = 0.01training_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,)
Definimos uma métrica para o dataloader de validação
import numpy as npfrom evaluate import loadmetric = load("accuracy")def compute_metrics(eval_pred):print(eval_pred)predictions, labels = eval_predpredictions = np.argmax(predictions, axis=1)return metric.compute(predictions=predictions, references=labels)
Definimos agora o treinador
from transformers import Trainertrainer = Trainer(model,training_args,train_dataset=dataset['train'],eval_dataset=dataset['validation'],tokenizer=tokenizer,compute_metrics=compute_metrics,)
Treinamos
trainer.train()
0%| | 0/600000 [00:00<?, ?it/s]
---------------------------------------------------------------------------ValueError Traceback (most recent call last)Cell In[21], line 1----> 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 stdout1875 hf_hub_utils.disable_progress_bars()-> 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 = True2177 step = -1-> 2178 for step, inputs in enumerate(epoch_iterator):2179 total_batched_samples += 12181 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 end453 try:--> 454 current_batch = next(dataloader_iter)455 except StopIteration:456 yieldFile ~/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]--> 631 data = self._next_data()632 self._num_yielded += 1633 if self._dataset_kind == _DatasetKind.Iterable and \634 self._IterableDataset_len_called is not None and \635 self._num_yielded > 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--> 675 data = self._dataset_fetcher.fetch(index) # may raise StopIteration676 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]---> 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]]) -> Dict[str, Any]:--> 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"] = True65 try:---> 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_stateFile ~/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 padding3298 if self.model_input_names[0] not in encoded_inputs:-> 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.
model.config.pad_token_id = model.config.eos_token_id
Recriamos os argumentos do treinador com o novo modelo, que agora tem um token de padding, o treinador e voltamos a treinar.
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,)
Agora que vimos que tudo estĂĄ bem, podemos treinar
trainer.train()
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
<transformers.trainer_utils.EvalPrediction object at 0x782767ea1450><transformers.trainer_utils.EvalPrediction object at 0x782767eeefe0><transformers.trainer_utils.EvalPrediction object at 0x782767eecfd0>
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ção
Uma vez treinado, avaliamos sobre o dataset de teste
trainer.evaluate(eval_dataset=dataset['test'])
<IPython.core.display.HTML object>
<transformers.trainer_utils.EvalPrediction object at 0x7826ddfded40>
{'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 modelo
JĂĄ temos nosso modelo treinado, agora podemos compartilhĂĄ-lo com o mundo, entĂŁo primeiro criamos uma **ficha do modelo**
trainer.create_model_card()
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.
trainer.push_to_hub()
Uso do modelo
Limpamos tudo o que for possĂvel
import torchimport gcdef clear_hardwares():torch.clear_autocast_cache()torch.cuda.ipc_collect()torch.cuda.empty_cache()gc.collect()clear_hardwares()clear_hardwares()
Como subimos o modelo ao nosso hub, podemos baixĂĄ-lo e usĂĄ-lo
from transformers import pipelineuser = "maximofn"checkpoints = f"{user}/{model_name}"task = "text-classification"classifier = pipeline(task, model=checkpoints, tokenizer=checkpoints)
Agora, se quisermos que nos retorne a probabilidade de todas as classes, simplesmente usamos o classificador que acabamos de instanciar, com o parĂąmetro top_k=None
labels = classifier("I love this product", top_k=None)labels
[{'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
label = classifier("I love this product", top_k=1)label
[{'label': 'LABEL_4', 'score': 0.8253807425498962}]
E se quisermos n classes, fazemos o mesmo mas com o parĂąmetro top_k=n
two_labels = classifier("I love this product", top_k=2)two_labels
[{'label': 'LABEL_4', 'score': 0.8253807425498962},{'label': 'LABEL_3', 'score': 0.15411493182182312}]
Também podemos testar o modelo com Automodel e AutoTokenizer
from transformers import AutoTokenizer, AutoModelForSequenceClassificationimport torchmodel_name = "GPT2-small-finetuned-amazon-reviews-en-classification"user = "maximofn"checkpoint = f"{user}/{model_name}"num_classes = 5tokenizer = 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.logitslables = torch.softmax(logits, dim=1).cpu().numpy().tolist()lables[0]
[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 Face
Para garantizar que nĂŁo tenho problemas de memĂłria VRAM, reinicio o notebook
Login
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

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

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.
from huggingface_hub import notebook_loginnotebook_login()
Conjunto de Dados
Vamos a usar um dataset de chistes em inglĂȘs
from datasets import load_datasetjokes = load_dataset("Maximofn/short-jokes-dataset")jokes
DatasetDict({train: Dataset({features: ['ID', 'Joke'],num_rows: 231657})})
Vamos vĂȘ-lo um pouco
jokes
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
from random import randintidx = randint(0, len(jokes['train']) - 1)jokes['train'][idx]
{'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
percent_of_train_dataset = 1 # If you want 50% of the dataset, set this to 0.5subset_dataset = jokes["train"].select(range(int(len(jokes["train"]) * percent_of_train_dataset)))subset_dataset
Dataset({features: ['ID', 'Joke'],num_rows: 231657})
Agora dividimos o subconjunto em um conjunto de treinamento e outro de validação.
percent_of_train_dataset = 0.90split_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)}")
Size of the train set: 208491. Size of the validation set: 11583. Size of the test set: 11583
Tokenizador
Instanciamos o tokenizador. Instanciamos o token de padding do tokenizador para que nĂŁo nos dĂȘ erro como antes.
from transformers import AutoTokenizercheckpoints = "openai-community/gpt2"tokenizer = AutoTokenizer.from_pretrained(checkpoints)tokenizer.pad_token = tokenizer.eos_tokentokenizer.padding_side = "right"
Vamos adicionar dois novos tokens de inĂcio de piada e fim de piada para ter mais controle
new_tokens = ['<SJ>', '<EJ>'] # Start and end of joke tokensnum_added_tokens = tokenizer.add_tokens(new_tokens)print(f"Added {num_added_tokens} tokens")
Added 2 tokens
Criamos uma função para adicionar os novos tokens às frases.
joke_column = "Joke"def format_joke(example):example[joke_column] = '<SJ> ' + example['Joke'] + ' <EJ>'return example
Selecionamos as colunas que nĂŁo precisamos
remove_columns = [column for column in train_dataset.column_names if column != joke_column]remove_columns
['ID']
Formatamos o conjunto de dados e eliminamos as colunas que nĂŁo precisamos
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
(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
def tokenize_function(examples):return tokenizer(examples[joke_column], padding="max_length", truncation=True, max_length=768, return_tensors="pt")
Tokenizamos o conjunto de dados e removemos a coluna com o texto
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
(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}))
Modelo
Agora instanciamos o modelo para geração de texto e atribuĂmos ao token de padding o token de end of string
from transformers import AutoModelForCausalLMmodel = AutoModelForCausalLM.from_pretrained(checkpoints)model.config.pad_token_id = model.config.eos_token_id
Vemos o tamanho do vocabulĂĄrio do modelo
vocab_size = model.config.vocab_sizevocab_size
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.
model.resize_token_embeddings(len(tokenizer))new_vocab_size = model.config.vocab_sizeprint(f"Old vocab size: {vocab_size}. New vocab size: {new_vocab_size}. Added {new_vocab_size - vocab_size} tokens")
Old vocab size: 50257. New vocab size: 50259. Added 2 tokens
Foram adicionados os dois novos tokens
Treinamento
Configuramos os parĂąmetros de treinamento
from transformers import TrainingArgumentsmetric_name = "accuracy"model_name = "GPT2-small-finetuned-Maximofn-short-jokes-dataset-casualLM"output_dir = f"./training_results"LR = 2e-5BS_TRAIN = 28BS_EVAL = 32EPOCHS = 3WEIGHT_DECAY = 0.01WARMUP_STEPS = 100training_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,)
Agora nĂŁo usamos metric_for_best_model
, depois de definir o treinador explicamos por que
Definimos o treinador
from transformers import Trainertrainer = Trainer(model,training_args,train_dataset=train_dataset,eval_dataset=validation_dataset,tokenizer=tokenizer,# compute_metrics=compute_metrics,)
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
trainer.train()
0%| | 0/625473 [00:00<?, ?it/s]
---------------------------------------------------------------------------ValueError Traceback (most recent call last)Cell In[19], line 1----> 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:-> 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):-> 2216 tr_loss_step = self.training_step(model, inputs)2218 if (2219 args.logging_nan_inf_filter2220 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 losses2224 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():-> 3238 loss = self.compute_loss(model, inputs)3240 del inputs3241 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:-> 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
idx = randint(0, len(train_dataset) - 1)sample = train_dataset[idx]sample
{'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
import torchoutput = 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)
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
import torchoutput = 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)
tensor(102.1873, device='cuda:0', grad_fn=<NllLossBackward0>)
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.
from transformers import DataCollatorForLanguageModelingmy_data_collator=DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
Passamos a amostra sample
por este data_collator
collated_sample = my_data_collator([sample]).to(model.device)
Vemos como Ă© a saĂda
for key, value in collated_sample.items():print(f"{key} ({value.shape}): {value}")
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
output = model(**collated_sample)output.loss
tensor(102.7181, device='cuda:0', grad_fn=<NllLossBackward0>)
EntĂŁo, redefinimos o trainer
com o data_collator
e treinamos novamente.
from transformers import DataCollatorForLanguageModelingtrainer = Trainer(model,training_args,train_dataset=train_dataset,eval_dataset=validation_dataset,tokenizer=tokenizer,data_collator=DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False),)
trainer.train()
<IPython.core.display.HTML object>
There were missing keys in the checkpoint model loaded: ['lm_head.weight'].
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ção
Uma vez treinado, avaliamos o modelo sobre o dataset de teste.
trainer.evaluate(eval_dataset=test_dataset)
<IPython.core.display.HTML object>
{'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 modelo
Criamos o cartĂŁo do modelo
trainer.create_model_card()
Publicamos
trainer.push_to_hub()
events.out.tfevents.1720875425.8de3af1b431d.6946.1: 0%| | 0.00/364 [00:00<?, ?B/s]
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 modelo
Limpez tudo o possĂvel
import torchimport gcdef clear_hardwares():torch.clear_autocast_cache()torch.cuda.ipc_collect()torch.cuda.empty_cache()gc.collect()clear_hardwares()clear_hardwares()
Baixamos o modelo e o tokenizador
from transformers import AutoTokenizer, AutoModelForCausalLMuser = "maximofn"checkpoints = f"{user}/{model_name}"tokenizer = AutoTokenizer.from_pretrained(checkpoints)tokenizer.pad_token = tokenizer.eos_tokentokenizer.padding_side = "right"model = AutoModelForCausalLM.from_pretrained(checkpoints)model.config.pad_token_id = model.config.eos_token_id
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.
tokenizer_vocab = tokenizer.get_vocab()model_vocab = model.config.vocab_sizeprint(f"tokenizer_vocab: {len(tokenizer_vocab)}. model_vocab: {model_vocab}")
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
def generate_joke(prompt_text):text = f"<SJ> {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("<EJ>")[-1])return tokenizer.decode(output[0], skip_special_tokens=False)
Geramos uma piada
generate_joke("Why didn't the frog cross the road?")
Setting `pad_token_id` to `eos_token_id`:50258 for open-end generation.
"<SJ> Why didn't the frog cross the road? Because he was frog-in-the-face. <EJ>"
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 Pytorch
Repetimos o treinamento com Pytorch
Reiniciamos o notebook para nos assegurar
Conjunto de Dados
Baixamos o mesmo conjunto de dados que quando fizemos o treinamento com as bibliotecas do Hugging Face
from datasets import load_datasetdataset = load_dataset("mteb/amazon_reviews_multi", "en")
Criamos uma variĂĄvel com o nĂșmero de classes
num_classes = len(dataset['train'].unique('label'))num_classes
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.
Tokenizador
Criamos o tokenizador. AtribuĂmos o token de padding para que nĂŁo nos dĂȘ erro como antes.
from transformers import AutoTokenizercheckpoint = "openai-community/gpt2"tokenizer = AutoTokenizer.from_pretrained(checkpoint)tokenizer.pad_token = tokenizer.eos_token
Criamos uma função para tokenizar o dataset
def tokenize_function(examples):return tokenizer(examples["text"], padding="max_length", truncation=True, max_length=768, return_tensors="pt")
Tokenizamos. Removemos colunas que nĂŁo sejam necessĂĄrias, mas agora mantemos a de texto.
dataset = dataset.map(tokenize_function, batched=True, remove_columns=['id', 'label_text'])
dataset
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})})
percentage = 1subset_train = dataset['train'].select(range(int(len(dataset['train']) * percentage)))percentage = 1subset_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)}")
len subset_train: 200000, len subset_validation: 5000, len subset_test: 5000
Modelo
Importamos os pesos e atribuĂmos o token de padding
from transformers import AutoModelForSequenceClassificationmodel = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=num_classes)model.config.pad_token_id = model.config.eos_token_id
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.
Dispositivo
Criamos o dispositivo onde tudo serĂĄ executado
import torchdevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
De passagem, transferimos o modelo para o dispositivo e, de passagem, o convertemos para FP16 para ocupar menos memĂłria
model.half().to(device)print()
Conjunto de Dados do Pytorch
Criamos um dataset de pytorch
from torch.utils.data import Datasetclass ReviewsDataset(Dataset):def __init__(self, huggingface_dataset):self.dataset = huggingface_datasetdef __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, labeldef __len__(self):return len(self.dataset)
Instanciamos os datasets
train_dataset = ReviewsDataset(subset_train)validatation_dataset = ReviewsDataset(subset_validation)test_dataset = ReviewsDataset(subset_test)
Vamos dar uma olhada em um exemplo
input_ids, at_mask, label = train_dataset[0]input_ids.shape, at_mask.shape, label
(torch.Size([768]), torch.Size([768]), 0)
Pytorch Dataloader
Criamos agora um DataLoader do PyTorch
from torch.utils.data import DataLoaderBS = 12train_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)
Vamos dar uma olhada em um exemplo
input_ids, at_mask, labels = next(iter(train_loader))input_ids.shape, at_mask.shape, labels
(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_ids = input_ids.to(device)at_mask = at_mask.to(device)labels = labels.to(device)
Agora passamos isso para o modelo
output = model(input_ids=input_ids, attention_mask=at_mask, labels=labels)output.keys()
odict_keys(['loss', 'logits', 'past_key_values'])
Como vemos, nos dĂĄ a loss e os logits
output['loss']
tensor(5.9414, device='cuda:0', dtype=torch.float16,grad_fn=<NllLossBackward0>)
output['logits']
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=<IndexBackward0>)
Métrica
Vamos a criar uma função para obter a métrica, que neste caso vai ser a acuråcia.
def predicted_labels(logits):percent = torch.softmax(logits, dim=1)predictions = torch.argmax(percent, dim=1)return predictions
def compute_accuracy(logits, labels):predictions = predicted_labels(logits)correct = (predictions == labels).float()return correct.mean()
Vamos a ver se ele calcula bem.
compute_accuracy(output['logits'], labels).item()
0.1666666716337204
Otimizador
Como vamos a precisar um otimizador, criamos um
from transformers import AdamWLR = 2e-5optimizer = AdamW(model.parameters(), lr=LR)
/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 warningwarnings.warn(
Treinamento
Criamos o loop de treinamento
from tqdm import tqdmEPOCHS = 3accuracy = 0for epoch in range(EPOCHS):model.train()train_loss = 0progresbar = 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 = 0progresbar = 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_accuracyprogresbar.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})
Epoch 1: 100%|ââââââââââ| 16667/16667 [44:13<00:00, 6.28it/s, train_loss=nan]Epoch 1: 100%|ââââââââââ| 417/417 [00:32<00:00, 12.72it/s, valid_loss=nan, accuracy=0]Epoch 2: 100%|ââââââââââ| 16667/16667 [44:06<00:00, 6.30it/s, train_loss=nan]Epoch 2: 100%|ââââââââââ| 417/417 [00:32<00:00, 12.77it/s, valid_loss=nan, accuracy=0]Epoch 3: 100%|ââââââââââ| 16667/16667 [44:03<00:00, 6.30it/s, train_loss=nan]Epoch 3: 100%|ââââââââââ| 417/417 [00:32<00:00, 12.86it/s, valid_loss=nan, accuracy=0]
Uso do modelo
Vamos a testar o modelo que treinamos
Primeiro tokenizamos um texto
input_tokens = tokenize_function({"text": "I love this product. It is amazing."})input_tokens['input_ids'].shape, input_tokens['attention_mask'].shape
(torch.Size([1, 768]), torch.Size([1, 768]))
Agora passamos isso para o modelo.
output = model(input_ids=input_tokens['input_ids'].to(device), attention_mask=input_tokens['attention_mask'].to(device))output['logits']
tensor([[nan, nan, nan, nan, nan]], device='cuda:0', dtype=torch.float16,grad_fn=<IndexBackward0>)
Vemos as previsÔes desses logits
predicted = predicted_labels(output['logits'])predicted
tensor([0], device='cuda:0')
Ajuste fino para geração de texto com Pytorch
Repetimos o treinamento com Pytorch
Reiniciamos o notebook para nos assegurar
Conjunto de Dados
Voltamos a baixar o conjunto de dados de piadas
from datasets import load_datasetjokes = load_dataset("Maximofn/short-jokes-dataset")jokes
DatasetDict({train: Dataset({features: ['ID', 'Joke'],num_rows: 231657})})
Criamos um subconjunto caso haja pouca memĂłria
percent_of_train_dataset = 1 # If you want 50% of the dataset, set this to 0.5subset_dataset = jokes["train"].select(range(int(len(jokes["train"]) * percent_of_train_dataset)))subset_dataset
Dataset({features: ['ID', 'Joke'],num_rows: 231657})
Dividimos o conjunto de dados em subconjuntos de treinamento, validação e teste.
percent_of_train_dataset = 0.90split_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)}")
Size of the train set: 208491. Size of the validation set: 11583. Size of the test set: 11583
Tokenizador
Iniciamos o tokenizador e atribuĂmos ao token de padding o de end of string
from transformers import AutoTokenizercheckpoints = "openai-community/gpt2"tokenizer = AutoTokenizer.from_pretrained(checkpoints)tokenizer.pad_token = tokenizer.eos_tokentokenizer.padding_side = "right"
Adicionamos os tokens especiais de inĂcio e fim de piada
new_tokens = ['<SJ>', '<EJ>'] # Start and end of joke tokensnum_added_tokens = tokenizer.add_tokens(new_tokens)print(f"Added {num_added_tokens} tokens")
Added 2 tokens
Os adicionamos ao dataset
joke_column = "Joke"def format_joke(example):example[joke_column] = '<SJ> ' + example['Joke'] + ' <EJ>'return exampleremove_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
(Dataset({features: ['Joke'],num_rows: 208491}),Dataset({features: ['Joke'],num_rows: 11583}),Dataset({features: ['Joke'],num_rows: 11583}))
Tokenizamos o conjunto de dados
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
(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}))
Modelo
Instanciamos o modelo, atribuĂmos o token de padding e adicionamos os novos tokens de inĂcio de piada e fim de piada
from transformers import AutoModelForCausalLMmodel = AutoModelForCausalLM.from_pretrained(checkpoints)model.config.pad_token_id = model.config.eos_token_idmodel.resize_token_embeddings(len(tokenizer))
Embedding(50259, 768)
Dispositivo
Criamos o dispositivo e passamos o modelo para o dispositivo
import torchdevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')model.half().to(device)print()
Conjunto de Dados do Pytorch
Criamos um conjunto de dados do PyTorch
from torch.utils.data import Datasetclass JokesDataset(Dataset):def __init__(self, huggingface_dataset):self.dataset = huggingface_datasetdef __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_maskdef __len__(self):return len(self.dataset)
Instanciamos os conjuntos de dados de treinamento, validação e teste
train_pytorch_dataset = JokesDataset(train_dataset)validation_pytorch_dataset = JokesDataset(validation_dataset)test_pytorch_dataset = JokesDataset(test_dataset)
Vamos ver um exemplo.
input_ids, attention_mask = train_pytorch_dataset[0]input_ids.shape, attention_mask.shape
(torch.Size([768]), torch.Size([768]))
Pytorch Dataloader
Criamos os dataloaders
from torch.utils.data import DataLoaderBS = 28train_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)
Vemos uma amostra
input_ids, attention_mask = next(iter(train_loader))input_ids.shape, attention_mask.shape
(torch.Size([28, 768]), torch.Size([28, 768]))
Passamos isso para o modelo.
output = model(input_ids.to(device), attention_mask=attention_mask.to(device))output.keys()
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
.
output = model(input_ids.to(device), attention_mask=attention_mask.to(device), labels=input_ids.to(device))output.keys()
odict_keys(['loss', 'logits', 'past_key_values'])
Agora sim temos loss
output['loss'].item()
80.5625
Otimizador
Criamos um otimizador
from transformers import AdamWLR = 2e-5optimizer = AdamW(model.parameters(), lr=5e-5)
/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 warningwarnings.warn(
Treinamento
Criamos o loop de treinamento
from tqdm import tqdmEPOCHS = 3for epoch in range(EPOCHS):model.train()train_loss = 0progresbar = 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})
Epoch 1: 100%|ââââââââââ| 7447/7447 [51:07<00:00, 2.43it/s, train_loss=nan]Epoch 2: 100%|ââââââââââ| 7447/7447 [51:06<00:00, 2.43it/s, train_loss=nan]Epoch 3: 100%|ââââââââââ| 7447/7447 [51:07<00:00, 2.43it/s, train_loss=nan]
Uso do modelo
Testamos o modelo
def generate_text(decoded_joke, max_new_tokens=100, stop_token='<EJ>', 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_decodedfor _ 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:breakdecoded_joke = decoded_joke + nex_token_decodedinput_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
generated_text = generate_text("<SJ> Why didn't the frog cross the road")generated_text
"<SJ> Why didn't the frog cross the road!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"