#!/usr/bin/env python3 import argparse import numpy as np import sklearn.datasets import sklearn.metrics import sklearn.model_selection import sklearn.preprocessing parser = argparse.ArgumentParser() parser.add_argument("--epochs", default=10, type=int, help="Počet epoch na trénování pomocí algoritmu Minibatch SGD") parser.add_argument("--batch_size", default=10, type=int, help="Velikost batche") parser.add_argument("--learning_rate", default=0.01, type=float, help="Learning rate") parser.add_argument("--test_size", default=0.4435, type=float, help="Velikost testovací množiny") parser.add_argument("--seed", default=42, type=int, help="Náhodný seed") parser.add_argument("--hidden_layer", default=50, type=int, help="Počet neuronů na skryté vrstvě") parser.add_argument("--classes", default=10, type=int, help="Počet použitých tříd") def main(args: argparse.Namespace): # Nastavení seedu generátoru náhodných čísel generator = np.random.RandomState(args.seed) # Načtení datasetu data, target = sklearn.datasets.load_digits(n_class=args.classes, return_X_y=True) # TODO: Rozdělte dataset na trénovací a testovací část, funkci z knihovny sklearn # předejte argumenty `test_size=args.test_size, random_state=args.seed`. train_data, test_data, train_target, test_target = sklearn.model_selection.train_test_split( data, target, test_size=args.test_size, random_state=args.seed) # Náhodná inicializace vah sítě weights = [generator.uniform(size=[train_data.shape[1], args.hidden_layer], low=-0.1, high=0.1), generator.uniform(size=[args.hidden_layer, args.classes], low=-0.1, high=0.1)] biases = [np.zeros(args.hidden_layer), np.zeros(args.classes)] def softmax(y: np.ndarray): if y.ndim == 1: y = y.reshape((1, y.shape[0])) # najdeme největší hodnotu v každém řádku a odečteme ji od všech hodnot v řádku y = y - y.max(axis=1)[:, None] # vypočítání softmaxu y_exp = np.array([np.exp(i) for i in y]) divider = y_exp.sum(axis=1) return y_exp / divider[:,None] # definice funkce ReLU ReLU = lambda x: np.maximum(0, x) # Nastavením sparse_output=False si ušetříme spoustu starostí a problémů # při implementaci, protože občas se sparse matice chovají jinak a trochu # nepředvídatelně. Např. Pro np.sum(A, axis=1), kde je A je matice s dimenzemi # (X, Y), bude výstup mít dimenze (X, ), ale pro sparse matice bude výstup # mít dimenze (X, 1). # Není to neřešitelný problém, ale je to zbytečná implementační komplikace, # když vezmeme v úvahu, že reálně si pamatujeme spoustu velkých matic, tedy # paměti moc neušetříme, když použijeme sparse matice na one hot encoding. # Pokud vás zájímá, jak vyřešit tento problém: https://stackoverflow.com/questions/47602925 oneHotEncoder = sklearn.preprocessing.OneHotEncoder(categories=[np.array(range(10))], sparse_output=False) oneHotEncoder.fit(train_target[:, None]) def forward(inputs): # TODO: Naimplementujte dopředný průchod neuronové sítě (vypočítáte výstupní hodnoty # na základě aktuálních vah a biasů). # Z této funkce vraťte výstup z každé vrstvy sítě (technicky data ze vstupní sítě vracet # nemusíte, protože je znáte ale můžete). Tyto hodnoty poté budete potřebovat pro zpětný # průchod, kde budete počítat gradienty. # # Pro numerickou stabilitu výpočtu softmaxu, pro každé dato najděte # největší hodnotu z výstupu lineárního modelu na výstupní vrstvě # a tuto hodnotu odečtěte od všech hodnot z výstupu z lineárního modelu # na výstupní vrstvě. # Tato operace je validní, protože víme, že softmax(x) = softmax(x + libovolná_konstanta) hidden_layer = ReLU( inputs @ weights[0] + biases[0] ) output_layer = softmax(hidden_layer @ weights[1] + biases[1]) return [hidden_layer, output_layer] for epoch in range(args.epochs): # TODO: Pro každou epochu náhodně zamíchejte trénovací množinu. # Na zamíchání použijte `generator.permutation`, které vrátí náhodnou permutaci # čísel od 0 do počtu prvků v množině. # Nemíchejte původní trénovací dataset, ale jen jeho kopii, tedy # `train_data` zůstane nezměněný přes všechny epochy. permutation = generator.permutation(train_data.shape[0]) # TODO: Pro každou batch s velikostí args.batch_size: # - Vypočítejte výstupy modelu - zavolání funkce `forward` na trénovacích datech. # - Spočítejte gradient. # - Upravte váhy modelu. # Můžete předpokládat, že args.batch_size je dělitelem počtu prvků v trénovací množině. for batch_num in range(train_data.shape[0] // args.batch_size): indexes = permutation[batch_num * args.batch_size : (batch_num + 1) * args.batch_size] sample_data = train_data[indexes] sample_target = train_target[indexes] hidden_layer, output_layer = forward(sample_data) derivative_of_softmax = (output_layer - oneHotEncoder.transform(sample_target[:, None])) # parciální derivace podle vah weights[1] partial_weights1 = hidden_layer.T @ derivative_of_softmax # derivace podle vstupů do klasifikační (výstupní) vrstvy # neboli derivace podle výstupů z hidden vrstvy partial_h_output = derivative_of_softmax @ weights[1].T derivative_of_relu = np.multiply(partial_h_output, hidden_layer > 0) biases[1] = biases[1] - args.learning_rate * \ 1/args.batch_size * np.sum(derivative_of_softmax.T, axis=1) weights[1] = weights[1] - args.learning_rate * \ 1/args.batch_size * (partial_weights1) biases[0] = biases[0] - args.learning_rate * \ 1/args.batch_size * np.sum(derivative_of_relu.T, axis=1) weights[0] = weights[0] - args.learning_rate * \ 1/args.batch_size * (sample_data.T @ derivative_of_relu) # TODO: Na konci každé epochy spočítejte accuracy metriku na trénovací a testovací množině. # Accuracy metrika se počítá jako počet správných predikcí děleno počtem všech predikcí. # Metriku můžete spočítat explicitně nebo pomocí funkce `sklearn.metrics.accuracy_score`. # Metrika accuracy chce na vstupu již klasifikovaná data, ne pravděpodobnosti. train_accuracy, test_accuracy = None, None train_hit = 0 train_prediction = forward(train_data)[-1] for idx, prediction in enumerate(train_prediction): if np.argmax(prediction) == train_target[idx]: train_hit += 1 train_accuracy = train_hit / len(train_data) test_hit = 0 test_prediction = forward(test_data)[-1] for idx, prediction in enumerate(test_prediction): if np.argmax(prediction) == test_target[idx]: test_hit += 1 test_accuracy = test_hit / len(test_target) print("Epoch {}: train acc {:.1f}%, test acc {:.1f}%".format( epoch + 1, 100 * train_accuracy, 100 * test_accuracy)) if __name__ == "__main__": args = parser.parse_args([] if "__file__" not in globals() else None) main(args)