camilaaeromoca commited on
Commit
6a7608b
·
verified ·
1 Parent(s): 82e4184

Create model.py

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