Spaces:
Sleeping
Sleeping
File size: 14,660 Bytes
af228ba 3cd80ad dcf41c9 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 |
# Tamanho do vocabulário
vocab_size = 13
# Comprimento da sequência
sequence_length = 4
# Comprimento do resultado
result_length = 2
# Comprimento do contexto
context_length = sequence_length + result_length
# Parâmetros de configuração do modelo GPT-2
config = AutoConfig.from_pretrained("gpt2",
vocab_size = vocab_size,
n_ctx = context_length,
n_head = 4,
n_layer = 2)
# Carrega o modelo
modelo = AutoModelForCausalLM.from_config(config)
# Função para calcular o tamanho do modelo
def model_size(model):
return sum(t.numel() for t in modelo.parameters())
print(f'Tamanho do Modelo: {model_size(modelo)/1000**2:.1f}M parâmetros')
#Tamanho do Modelo: 15.0M parâmetros
#Este modelo tem 15 milhões de parâmetros em vez dos 111 milhões de parâmetros da configuração padrão "gpt2".
type(modelo)
transformers.models.gpt2.modeling_gpt2.GPT2LMHeadModel
# Salva o modelo em disco
modelo.save_pretrained("modelos/modelo_inicial")
# Definindo uma classe chamada NumberTokenizer, que é usada para tokenizar os números
class DSATokenizer:
# Método construtor da classe, que é executado quando um objeto dessa classe é criado
def __init__(self, numbers_qty = 10):
# Lista de tokens possíveis que o tokenizador pode encontrar
vocab = ['+', '=', '-1', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
# Definindo a quantidade de números que o tokenizador pode lidar
self.numbers_qty = numbers_qty
# Definindo o token de preenchimento (padding)
self.pad_token = '-1'
# Criando um dicionário que mapeia cada token para um índice único
self.encoder = {str(v):i for i,v in enumerate(vocab)}
# Criando um dicionário que mapeia cada índice único de volta ao token correspondente
self.decoder = {i:str(v) for i,v in enumerate(vocab)}
# Obtendo o índice do token de preenchimento no encoder
self.pad_token_id = self.encoder[self.pad_token]
# Método para decodificar uma lista de IDs de token de volta para uma string
def decode(self, token_ids):
return ' '.join(self.decoder[t] for t in token_ids)
# Método que é chamado quando o objeto da classe é invocado como uma função
def __call__(self, text):
# Dividindo o texto em tokens individuais e retornando uma lista dos IDs correspondentes
return [self.encoder[t] for t in text.split()]
# Cria o objeto do tokenizador
tokenizer = DSATokenizer(vocab_size)
# Decoder do tokenizador
tokenizer.decoder
# Testando o tokenizador
tokenizer("1 + 1 = 2")
# Definindo uma classe chamada CriaDataset, que herda da classe Dataset do PyTorch
class CriaDataset(Dataset):
# Método construtor da classe, que é executado quando um objeto dessa classe é criado
def __init__(self, split, length = 6):
# Verificando se a divisão do dataset (split) é 'treino' ou 'teste'
assert split in {'treino', 'teste'}
self.split = split
self.length = length
# Definindo o método len que retorna o tamanho do dataset.
# Nesse caso, o tamanho é fixo e igual a 1 milhão.
def __len__(self):
return 1000000
# Definindo o método getitem que é usado para obter um item específico do dataset
def __getitem__(self, idx):
# Criando uma lista com todos os números disponíveis que não são tokens de padding e são numéricos
available_numbers = [int(n) for n in tokenizer.decoder.values() if n != tokenizer.pad_token and str(n).isnumeric()]
# Selecionando aleatoriamente números da lista de números disponíveis para criar uma entrada (input)
inp = torch.tensor(np.random.choice(available_numbers, size = result_length))
# Calculando a soma dos números selecionados e criando um tensor
sol = torch.tensor([int(i) for i in str(inp.sum().item())])
# Preenchendo o tensor com zeros para que tenha o tamanho desejado
sol = torch.nn.functional.pad(sol, (1 if sol.size()[0] == 1 else 0,0), 'constant', 0)
# Concatenando a entrada e a solução em um tensor
cat = torch.cat((inp, sol), dim = 0)
# Criando os tensores de entrada e alvo para o treinamento do modelo
x = cat[:-1].clone()
y = cat[1:].clone()
# Definindo o primeiro elemento do tensor alvo como o token de padding
y[:1] = int(tokenizer.pad_token)
# Transformando os tensores x e y em strings
x = str(x[0].item()) + ' + ' + str(x[1].item()) + ' = ' + str(x[2].item())
y = '-1 ' + str(y[0].item()) + ' -1 ' + str(y[1].item()) + ' ' + str(y[2].item())
# Tokenizando as strings de entrada e alvo
tokenized_input = tokenizer(x)
tokenized_output = tokenizer(y)
# Retornando os tensores de entrada e alvo como itens do dataset
return torch.tensor(tokenized_input), torch.tensor(tokenized_output)
dataset_treino = CriaDataset('treino', length = sequence_length)
dataset_teste = CriaDataset('teste', length = sequence_length)
x, y = dataset_treino[0]
x
y
print(tokenizer.decode(x.numpy()))
print(tokenizer.decode(y.numpy()))
num_epochs = 2
batch_size = 100
optimizer = torch.optim.Adam(modelo.parameters())
dados = torch.utils.data.DataLoader(dataset_treino, shuffle = True, batch_size = batch_size)
import accelerate
from accelerate import Accelerator
accelerator = Accelerator()
modelo, optimizer, dados = accelerator.prepare(modelo, optimizer, dados)
modelo.train()
# Iniciando o loop para as épocas de treinamento
for epoch in range(num_epochs):
# Iterando por cada batch (conjunto) de dados de entrada e alvos no dataset de treinamento
for source, targets in dados:
# Resetando os gradientes acumulados no otimizador
optimizer.zero_grad()
# Calculando a perda (loss) através da entropia cruzada entre as previsões do modelo e os alvos verdadeiros.
# Os tensores são "achatados" para que possam ser passados para a função de entropia cruzada.
# O índice do token de preenchimento (pad_token) é ignorado no cálculo da perda.
loss = F.cross_entropy(modelo(source).logits.flatten(end_dim = 1),
targets.flatten(end_dim = 1),
ignore_index = tokenizer.pad_token_id)
# Calculando os gradientes da perda em relação aos parâmetros do modelo
accelerator.backward(loss)
# Atualizando os parâmetros do modelo utilizando os gradientes calculados
optimizer.step()
# Recalculando a perda após a etapa de otimização.
loss = F.cross_entropy(modelo(source).logits.flatten(end_dim = 1),
targets.flatten(end_dim = 1),
ignore_index = tokenizer.pad_token_id)
# Imprimindo a época atual e a perda após cada época de treinamento
print(f'Epoch: {epoch+1}/{num_epochs} --- Erro: {loss.item()}')
# Definindo a função gera_solution com três parâmetros: input, solution_length e model
def faz_previsao(entrada, solution_length = 6, model = modelo):
# Colocando o modelo em modo de avaliação.
model.eval()
# Convertendo a entrada (string) em tensor utilizando o tokenizer.
# O tensor é uma estrutura de dados que o modelo de aprendizado de máquina pode processar.
entrada = torch.tensor(tokenizer(entrada))
# Enviando o tensor de entrada para o dispositivo de cálculo disponível (CPU ou GPU)
entrada = entrada.to(accelerator.device)
# Iniciando uma lista vazia para armazenar a solução
solution = []
# Loop que gera a solução de comprimento solution_length
for i in range(solution_length):
# Alimentando a entrada atual ao modelo e obtendo a saída
saida = model(entrada)
# Pegando o índice do maior valor no último conjunto de logits (log-odds) da saída,
# que é a previsão do modelo para o próximo token
predicted = saida.logits[-1].argmax()
# Concatenando a previsão atual com a entrada atual.
# Isso servirá como a nova entrada para a próxima iteração.
entrada = torch.cat((entrada, predicted.unsqueeze(0)), dim = 0)
# Adicionando a previsão atual à lista de soluções e convertendo o tensor em um número Python padrão
solution.append(predicted.cpu().item())
# Decodificando a lista de soluções para obter a string de saída e retornando-a
return tokenizer.decode(solution)
# Definindo a função avalia_modelo com dois parâmetros: num_samples e log
def avalia_modelo(num_samples = 1000, log = False):
# Iniciando um contador para as previsões corretas
correct = 0
# Loop que itera num_samples vezes
for i in range(num_samples):
# Obtendo a entrada e o alvo (resposta correta) do i-ésimo exemplo do conjunto de teste
entrada, target = dataset_teste[i]
# Convertendo os tensores de entrada e alvo em arrays numpy para processamento posterior
entrada = entrada.cpu().numpy()
target = target.cpu().numpy()
# Decodificando a entrada e o alvo utilizando o tokenizer
entrada = tokenizer.decode(entrada[:sequence_length])
target = tokenizer.decode(target[sequence_length-1:])
# Gerando a previsão utilizando a função faz_previsao
predicted = faz_previsao(entrada, solution_length = result_length, model = modelo)
# Se a previsão for igual ao alvo, incrementa o contador de previsões corretas
if target == predicted:
correct += 1
# Se log for True, imprime detalhes do exemplo e a previsão correta
if log:
print(f'Acerto do Modelo: Input: {entrada} Target: {target} Previsão: {predicted}')
else:
# Se log for True, imprime detalhes do exemplo e a previsão errada
if log:
print(f'Erro do Modelo: Input: {entrada} Target: {target} Previsão: {predicted}')
# Ao final do loop, calcula a acurácia (número de previsões corretas dividido pelo número total de exemplos)
print(f'Acurácia: {correct/num_samples}')
# Executa a função
avalia_modelo(num_samples = 10, log = True)
# Executa a função
avalia_modelo(num_samples = 1000, log = False)
type(modelo)
modelo.save_pretrained("modelos/modelo_final")
modelo_llm = AutoModelForCausalLM.from_pretrained("modelos/modelo_final")
# Definindo uma classe chamada NumberTokenizer, que é usada para tokenizar os números
class DSATokenizer:
# Método construtor da classe, que é executado quando um objeto dessa classe é criado
def __init__(self, numbers_qty = 10):
# Lista de tokens possíveis que o tokenizador pode encontrar
vocab = ['+', '=', '-1', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
# Definindo a quantidade de números que o tokenizador pode lidar
self.numbers_qty = numbers_qty
# Definindo o token de preenchimento (padding)
self.pad_token = '-1'
# Criando um dicionário que mapeia cada token para um índice único
self.encoder = {str(v):i for i,v in enumerate(vocab)}
# Criando um dicionário que mapeia cada índice único de volta ao token correspondente
self.decoder = {i:str(v) for i,v in enumerate(vocab)}
# Obtendo o índice do token de preenchimento no encoder
self.pad_token_id = self.encoder[self.pad_token]
# Método para decodificar uma lista de IDs de token de volta para uma string
def decode(self, token_ids):
return ' '.join(self.decoder[t] for t in token_ids)
# Método que é chamado quando o objeto da classe é invocado como uma função
def __call__(self, text):
# Dividindo o texto em tokens individuais e retornando uma lista dos IDs correspondentes
return [self.encoder[t] for t in text.split()]
# Cria o objeto
tokenizer = DSATokenizer(13)
# Definindo a função gera_solution com três parâmetros: input, solution_length e model
def faz_previsao(entrada, solution_length = 6, model = modelo_llm):
# Colocando o modelo em modo de avaliação.
model.eval()
# Convertendo a entrada (string) em tensor utilizando o tokenizer.
# O tensor é uma estrutura de dados que o modelo de aprendizado de máquina pode processar.
entrada = torch.tensor(tokenizer(entrada))
# Iniciando uma lista vazia para armazenar a solução
solution = []
# Loop que gera a solução de comprimento solution_length
for i in range(solution_length):
# Alimentando a entrada atual ao modelo e obtendo a saída
saida = model(entrada)
# Pegando o índice do maior valor no último conjunto de logits (log-odds) da saída,
# que é a previsão do modelo para o próximo token
predicted = saida.logits[-1].argmax()
# Concatenando a previsão atual com a entrada atual.
# Isso servirá como a nova entrada para a próxima iteração.
entrada = torch.cat((entrada, predicted.unsqueeze(0)), dim = 0)
# Adicionando a previsão atual à lista de soluções e convertendo o tensor em um número Python padrão
solution.append(predicted.cpu().item())
# Decodificando a lista de soluções para obter a string de saída e retornando-a
return tokenizer.decode(solution)
# Testa a função
faz_previsao('3 + 5 =', solution_length = 2)
# Função para retornar a função que faz a previsão
def funcsolve(entrada):
return faz_previsao(entrada, solution_length = 2)
# Cria a web app
webapp = gr.Interface(fn = funcsolve,
inputs = [gr.Textbox(label = "Dados de Entrada",
lines = 1,
info = "Os dados devem estar na forma: '1 + 2 =' com um único espaço entre cada caractere e apenas números de um dígito são permitidos.")],
outputs = [gr.Textbox(label = "Resultado (Previsão do Modelo)", lines = 1)],
title = "Deploy de LLM Após o Fine-Tuning",
description = "Digite os dados de entrada e clique no botão Submit para o modelo fazer a previsão.",
examples = ["5 + 3 =", "2 + 9 ="])
webapp.launch() |