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