<jupyter_start><jupyter_text>Regressão Softmax com dados do MNIST utilizando gradiente descendente estocástico por minibatches Este exercicío consiste em treinar um modelo de uma única camada linear no MNIST **sem** usar as seguintes funções do pytorch:- torch.nn.Linear- torch.nn.CrossEntropyLoss- torch.nn.NLLLoss- torch.nn.LogSoftmax- torch.optim.SGD- torch.utils.data.Dataloader  Importação das bibliotecas<jupyter_code>%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import random
import torch
import torchvision
from torchvision.datasets import MNIST<jupyter_output><empty_output><jupyter_text>Fixando as seeds<jupyter_code>random.seed(123)
np.random.seed(123)
torch.manual_seed(123)<jupyter_output><empty_output><jupyter_text>Dataset e dataloader  Definição do tamanho do minibatch<jupyter_code>batch_size = 50<jupyter_output><empty_output><jupyter_text>Carregamento, criação dataset e do dataloader<jupyter_code>dataset_dir = '../data/'

dataset_train_full = MNIST(dataset_dir, train=True, download=True,
                           transform=torchvision.transforms.ToTensor())
print(dataset_train_full.data.shape)
print(dataset_train_full.targets.shape)<jupyter_output>torch.Size([60000, 28, 28])
torch.Size([60000])<jupyter_text>Usando apenas 1000 amostras do MNISTNeste exercício utilizaremos 1000 amostras de treinamento.<jupyter_code>indices = torch.randperm(len(dataset_train_full))[:1000]
dataset_train = torch.utils.data.Subset(dataset_train_full, indices)
##Entendendo o dataset
len(dataset_train)

x, y = dataset_train[0]
print(x.shape)
print(y)
import math 
class Dataloader():
  def __init__(self, dataset, batch_size = batch_size, shuffle = False):
    self.dataset = list(dataset) 
    self.batch_size = batch_size
    self.len = math.ceil(len(self.dataset) / self.batch_size)

    if shuffle == True:
      self.dataset = dataset[torch.randperm(len(dataset))]
      
    #Inspirado no código do <NAME>:
    # Muda o dataset de 1000 x 2 para 2 x 1000
    # Zip junta os pares: (x0, y0), (x1, y1)... 
    self.dataset = list(zip(*self.dataset)) 

    #Stack concatena ao longo de uma nova dimensão
    self.dataset[0] = torch.stack(self.dataset[0],dim=0)  
    self.dataset[1] = torch.tensor(self.dataset[1]).long()

  def __len__(self):
    return self.len

  def __iter__(self):
    self.index = 0 #Atribui ao índice o valor de zero a cada chamada da função
    return self

  def __next__(self):
    if self.index >= self.len:
      raise StopIteration
    else: 
      item1 = self.dataset[0][self.index*self.batch_size:self.index*self.batch_size+self.batch_size]
      item2 = self.dataset[1][self.index*self.batch_size:self.index*self.batch_size+self.batch_size]      
      self.index += 1
      return item1, item2
#Inicia o dataloader
loader_train = Dataloader(dataset_train, batch_size)      
print('Número de minibatches de trenamento:', len(loader_train))

x_train, y_train = next(iter(loader_train))
print("\nDimensões dos dados de um minibatch:", x_train.size())
print("Valores mínimo e máximo dos pixels: ", torch.min(x_train), torch.max(x_train))
print("Tipo dos dados das imagens:         ", type(x_train))
print("Tipo das classes das imagens:       ", type(y_train))<jupyter_output>Número de minibatches de trenamento: 20

Dimensões dos dados de um minibatch: torch.Size([50, 1, 28, 28])
Valores mínimo e máximo dos pixels:  tensor(0.) tensor(1.)
Tipo dos dados das imagens:          <class 'torch.Tensor'>
Tipo das classes das imagens:        <class 'torch.Tensor'><jupyter_text>Modelo<jupyter_code># Escreva aqui o codigo para criar um modelo cujo o equivalente é: 
# model = torch.nn.Linear(28*28, 10)
class Linear():
    def __init__(self, n_in: int, n_out: int):
      self.w = torch.zeros((n_in, n_out), requires_grad= True)
      self.bias = torch.zeros(n_out, requires_grad= True)

    def __call__(self, x): #o método __call__() permite que a classe tenha instâncias com comportamento de funções
      y_pred = torch.matmul(x, self.w) + self.bias
      return y_pred

    def parameters(self):
      return self.w, self.bias
#Inicia o modelo linear
model = Linear(28*28, 10)<jupyter_output><empty_output><jupyter_text>Treinamento  Inicialização dos parâmetros<jupyter_code>n_epochs = 50
lr = 0.1<jupyter_output><empty_output><jupyter_text>Definição da Loss<jupyter_code>class CrossEntropyLoss():
  def __init__(self):
    self.loss = 0

  def __call__(self,y_pred, y_target):
    exp = torch.exp(y_pred)
    soma = torch.sum(exp, dim=1, keepdim=True)
    log = torch.log(exp/soma)

    batch_size = y_pred.shape[0]
    log = log[range(batch_size), y_target]
    criterion = -torch.sum(log)/batch_size
    return criterion
#Inicializando a classe
criterion = CrossEntropyLoss()<jupyter_output><empty_output><jupyter_text>Definição do Optimizer<jupyter_code>class SGD():
    def __init__(self, parameters, learning_rate):
        self.parameters = parameters
        self.lr = learning_rate

    def step(self):
        for param in self.parameters:
          param.data = param.data - self.lr * param.grad

    def zero_grad(self):
        for param in self.parameters:
            param.grad = torch.zeros_like(param.data)
#Inicializa a classe:
optimizer = SGD(model.parameters(), lr)<jupyter_output><empty_output><jupyter_text>Laço de treinamento dos parâmetros<jupyter_code>epochs = []
loss_history = []
loss_epoch_end = []
total_trained_samples = 0
for i in range(n_epochs):
    # Substitua aqui o loader_train de acordo com sua implementação do dataloader.
    for x_train, y_train in loader_train:
        # Transforma a entrada para uma dimensão
        inputs = x_train.view(-1, 28 * 28)
        # predict da rede
        outputs = model(inputs)

        # calcula a perda
        loss = criterion(outputs, y_train)

        # zero, backpropagation, ajusta parâmetros pelo gradiente descendente
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_trained_samples += x_train.size(0)
        epochs.append(total_trained_samples / len(dataset_train))
        loss_history.append(loss.item())

    loss_epoch_end.append(loss.item())
    print(f'Epoch: {i:d}/{n_epochs - 1:d} Loss: {loss.item()}')<jupyter_output>Epoch: 0/49 Loss: 1.1979684829711914
Epoch: 1/49 Loss: 0.867622971534729
Epoch: 2/49 Loss: 0.7226786017417908
Epoch: 3/49 Loss: 0.6381281614303589
Epoch: 4/49 Loss: 0.5809750556945801
Epoch: 5/49 Loss: 0.5387411713600159
Epoch: 6/49 Loss: 0.5056463479995728
Epoch: 7/49 Loss: 0.4786270558834076
Epoch: 8/49 Loss: 0.4558936655521393
Epoch: 9/49 Loss: 0.43632185459136963
Epoch: 10/49 Loss: 0.4191650152206421
Epoch: 11/49 Loss: 0.4039044976234436
Epoch: 12/49 Loss: 0.39016804099082947
Epoch: 13/49 Loss: 0.3776799142360687
Epoch: 14/49 Loss: 0.3662314713001251
Epoch: 15/49 Loss: 0.35566139221191406
Epoch: 16/49 Loss: 0.34584280848503113
Epoch: 17/49 Loss: 0.33667415380477905
Epoch: 18/49 Loss: 0.32807353138923645
Epoch: 19/49 Loss: 0.31997358798980713
Epoch: 20/49 Loss: 0.3123182952404022
Epoch: 21/49 Loss: 0.30506113171577454
Epoch: 22/49 Loss: 0.29816243052482605
Epoch: 23/49 Loss: 0.29158854484558105
Epoch: 24/49 Loss: 0.28531038761138916
Epoch: 25/49 Loss: 0.27930301427841187
Epoch: 26/4[...]<jupyter_text>Visualizando gráfico de perda durante o treinamento<jupyter_code>plt.plot(epochs, loss_history)
plt.xlabel('época')<jupyter_output><empty_output><jupyter_text>Visualização usual da perda, somente no final de cada minibatch<jupyter_code>n_batches_train = len(loader_train)
plt.plot(epochs[::n_batches_train], loss_history[::n_batches_train])
plt.xlabel('época')
# Assert do histórico de losses
target_loss_epoch_end = np.array([
    1.1979684829711914,
    0.867622971534729,
    0.7226786613464355,
    0.6381281018257141,
    0.5809749960899353,
    0.5387411713600159,
    0.5056464076042175,
    0.4786270558834076,
    0.4558936357498169,
    0.4363219141960144,
    0.4191650450229645,
    0.4039044976234436,
    0.3901679515838623,
    0.3776799440383911,
    0.3662314713001251,
    0.35566139221191406,
    0.34584277868270874,
    0.33667415380477905,
    0.32807353138923645,
    0.31997355818748474,
    0.312318354845047,
    0.3050611615180969,
    0.29816246032714844,
    0.29158851504325867,
    0.28531041741371155,
    0.2793029546737671,
    0.273544579744339,
    0.2680158317089081,
    0.26270008087158203,
    0.2575823664665222,
    0.25264936685562134,
    0.24788929522037506,
    0.24329163134098053,
    0.23884665966033936,
    0.23454584181308746,
    0.23038141429424286,
    0.22634628415107727,
    0.22243399918079376,
    0.2186385989189148,
    0.21495483815670013,
    0.21137762069702148,
    0.20790249109268188,
    0.20452524721622467,
    0.20124195516109467,
    0.19804897904396057,
    0.1949428766965866,
    0.19192075729370117,
    0.188979372382164,
    0.18611609935760498,
    0.1833282858133316])

assert np.allclose(np.array(loss_epoch_end), target_loss_epoch_end, atol=1e-6)<jupyter_output><empty_output><jupyter_text>Exercício Escreva um código que responda às seguintes perguntas:Qual é a amostra classificada corretamente, com maior probabilidade?Qual é a amostra classificada erradamente, com maior probabilidade?Qual é a amostra classificada corretamente, com menor probabilidade?Qual é a amostra classificada erradamente, com menor probabilidade?<jupyter_code># Carrega os dados
load_test = Dataloader(dataset_train, batch_size)

#Input
for x_test, y_test in loader_train:
  input = x_test.view(-1, 28 * 28)

#Output
output = model(inputs)

#Probabilidade utilizando softmax
exp = torch.exp(output)
soma = torch.sum(exp, dim=1, keepdim=True)
prob = (exp/soma).detach().numpy()

# Pred é o vetor que contém a classe predita pelo modelo
pred = np.argmax(prob, axis = 1)

#Predição correta - retorna os índices
correct = np.where(pred==y_test.numpy())[0]

#Predição errada - retorna os índices
wrong = np.where(pred!=y_test.numpy())[0]


#Amostra classificada corretamente, com maior probabilidade
#prob1 é o índice
prob1 = np.argmax(prob[correct,pred[correct]]) 
print('Amostra classificada corretamente, com maior probabilidade: Amostra nº', correct[prob1], 'com probabilidade:', prob[correct, pred[correct]][prob1])

#Amostra classificada erradamente, com maior probabilidade
#prob2 é o índice
prob2 = np.argmax(prob[wrong,pred[wrong]])
print('Amostra classificada erradamente, com maior probabilidade: Amostra nº', wrong[prob2], 'com probabilidade:', prob[wrong,pred[wrong]][prob2])


# Amostra classificada corretamente, com menor probabilidade
#prob3 é o índice
prob3 = np.argmin(prob[correct,pred[correct]])
print('Amostra classificada corretamente, com menor probabilidade: Amostra nº', correct[prob3], 'com probabilidade:', prob[correct,pred[correct]][prob3])

#Amostra classificada erradamente, com menor probabilidade
#prob4 é o índice
prob4 = np.argmin(prob[wrong,pred[wrong]])
print('Amostra classificada erradamente, com menor probabilidade: Amostra nº', wrong[prob4], 'com probabilidade:', prob[wrong,pred[wrong]][prob4])<jupyter_output>Amostra classificada corretamente, com maior probabilidade: Amostra nº 10 com probabilidade: 0.99954355
Amostra classificada erradamente, com maior probabilidade: Amostra nº 32 com probabilidade: 0.5242189
Amostra classificada corretamente, com menor probabilidade: Amostra nº 49 com probabilidade: 0.44311962
Amostra classificada erradamente, com menor probabilidade: Amostra nº 17 com probabilidade: 0.375853
{'fraction_non_alphanumeric': 0.10248582642825992, 'fraction_numerical': 0.13885739206279982, 'mean_word_length': 4.744488977955911, 'pattern_counts': {'":': 0, '<': 52, '<?xml version=': 0, '>': 53, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '13', 'dir': 'jupyter-structured-clean-dedup', 'id': '168784', 'license': 'MIT', 'n_tokens_mistral': 5280, 'n_tokens_neox': 4295, 'n_words': 931, 'path': 'ex04/Beatriz_Celante/Atividade_4_Beatriz_Celante.ipynb', 'repo_name': 'pg1992/IA025_2022S1', 'size': '11474'}