| | from typing import List, Dict, Any, Optional |
| | import torch |
| | from torch.utils.data import DataLoader |
| | from datasets import load_dataset |
| | from transformers import AutoTokenizer |
| | from torch.nn.utils.rnn import pad_sequence |
| | from functools import partial |
| |
|
| |
|
| | def tokenize_function(examples: Dict[str, Any], tokenizer: Any) -> Dict[str, List[int]]: |
| | """ |
| | Aplica a template de chat do tokenizer e gera os token ids. |
| | |
| | Args: |
| | examples (Dict[str, Any]): Dicionário contendo a lista de mensagens sob a chave "messages". |
| | tokenizer (Any): Instância do tokenizer que deverá possuir a propriedade 'chat_template'. |
| | |
| | Returns: |
| | Dict[str, List[int]]: Dicionário com os token ids gerados. |
| | """ |
| | full_text = tokenizer.apply_chat_template( |
| | examples["messages"], |
| | tokenize=True, |
| | add_generation_prompt=True |
| | ) |
| | return {"input_ids": full_text} |
| |
|
| |
|
| | def custom_collate_fn( |
| | batch: List[Dict[str, List[int]]], |
| | pad_token_id: int = 29797, |
| | ignore_index: int = -100, |
| | allowed_max_length: Optional[int] = None, |
| | device: str = "cpu", |
| | ) -> Dict[str, torch.Tensor]: |
| | """ |
| | • Faz padding das sequências |
| | • Cria pares (input, label) deslocando 1 posição |
| | • Aplica `ignore_index` (-100) APENAS nos labels depois do 1.º PAD |
| | """ |
| |
|
| | |
| | seqs = [torch.tensor(s["input_ids"] + [pad_token_id]) for s in batch] |
| |
|
| | |
| | padded = pad_sequence(seqs, batch_first=True, padding_value=pad_token_id) |
| |
|
| | |
| | input_ids = padded[:, :-1].clone() |
| | labels = padded[:, 1:].clone() |
| |
|
| | |
| | pad_mask = (labels == pad_token_id) |
| | if pad_mask.any(): |
| | |
| | first_pad_pos = pad_mask.float().cumsum(1).eq(1) & pad_mask |
| | |
| | mask_after_first_pad = pad_mask & ~first_pad_pos |
| | labels[mask_after_first_pad] = ignore_index |
| |
|
| | |
| | if allowed_max_length is not None: |
| | input_ids = input_ids[:, :allowed_max_length] |
| | labels = labels[:, :allowed_max_length] |
| |
|
| | return { |
| | "input_ids": input_ids.to(device), |
| | "labels": labels.to(device), |
| | } |
| |
|
| |
|
| |
|
| | def create_data_loader_fine_tuning( |
| | tokenizer: Any, |
| | batch_size: int, |
| | path_folder: str, |
| | split: str = "train", |
| | pad_token_id: int = 0, |
| | ignore_index: int = -100, |
| | allowed_max_length: Optional[int] = None, |
| | device: str = "cpu" |
| | ) -> DataLoader: |
| | """ |
| | Cria o DataLoader para fine-tuning, a partir de um dataset_files tokenizado. |
| | |
| | Esta função carrega o dataset_files, aplica a tokenização utilizando uma template de chat, |
| | e retorna um DataLoader que utiliza a função custom_collate_fn para o processamento |
| | adequado das batches. |
| | |
| | Args: |
| | tokenizer (Any): Tokenizer pré-treinado que suporte chat templates. |
| | batch_size (int): Número de amostras por batch. |
| | path_folder (str): Caminho ou identificador do dataset_files. |
| | split (str): Divisão do dataset_files a ser utilizada (por exemplo, "train" ou "test"). |
| | pad_token_id (int): ID do token para padding. |
| | ignore_index (int): Valor a ser ignorado na função de perda. |
| | allowed_max_length (Optional[int]): Se definido, trunca as sequências para este tamanho máximo. |
| | device (str): Dispositivo para onde os tensores serão enviados ("cpu" ou "cuda"). |
| | |
| | Returns: |
| | DataLoader: Instância do DataLoader pronta para o fine-tuning. |
| | """ |
| | |
| | chat_template = """ |
| | {% for message in messages %} |
| | {% if message['role'] == 'user' %} |
| | {{ '<|user_start|>' + message['content'] + '<|user_end|>' + '\n'}} |
| | {% elif message['role'] == 'assistant' %} |
| | {{ '<|assistant_start|>' + message['content'] + '<|assistant_end|>' + '\n' }} |
| | {% endif %} |
| | {% endfor %} |
| | """ |
| | tokenizer.chat_template = chat_template |
| |
|
| | |
| | raw_dataset = load_dataset(path=path_folder, split=split, download_mode="force_redownload") |
| |
|
| | |
| | tokenized_dataset = raw_dataset.map( |
| | lambda examples: tokenize_function(examples, tokenizer), |
| | batched=True, |
| | remove_columns=raw_dataset.column_names, |
| | desc="Tokenizando dataset_files" |
| | ) |
| |
|
| | |
| | collate = partial( |
| | custom_collate_fn, |
| | pad_token_id=pad_token_id, |
| | ignore_index=ignore_index, |
| | allowed_max_length=allowed_max_length, |
| | device=device |
| | ) |
| |
|
| | print("Criando DataLoader...") |
| | return DataLoader( |
| | tokenized_dataset, |
| | batch_size=batch_size, |
| | shuffle=False, |
| | drop_last=False, |
| | num_workers=0, |
| | collate_fn=collate |
| | ) |
| |
|
| |
|
| | if __name__ == "__main__": |
| | |
| | tokenizer = AutoTokenizer.from_pretrained("neuralmind/bert-base-portuguese-cased") |
| |
|
| | |
| | loader = create_data_loader_fine_tuning( |
| | tokenizer=tokenizer, |
| | batch_size=100, |
| | path_folder="conversational", |
| | split="test" |
| | ) |
| |
|
| | |
| | batch = next(iter(loader)) |
| | print(batch["input_ids"].shape, batch["labels"].shape) |
| |
|