#!/usr/bin/env python3 import argparse import numpy as np import sklearn.datasets import sklearn.metrics import sklearn.model_selection parser = argparse.ArgumentParser() parser.add_argument("--classes", default=10, type=int, help="Počet tříd pro klasifikaci") parser.add_argument("--learning_rate", default=0.01, type=float, help="Learning rate") parser.add_argument("--epochs", default=50, type=int, help="Počet epoch na trénování Minibatch SGD") parser.add_argument("--batch_size", default=10, type=int, help="Velikost batche") parser.add_argument("--data_size", default=100, type=int, help="Velikost datasetu") parser.add_argument("--test_size", default=0.5, type=float, help="Velikost testovací množiny") parser.add_argument("--seed", default=42, type=int, help="Náhodný seed") def count_dato_probabilities(dato, weights): predictions = [] for model_weights in weights: prediction = 0 for d, w in zip(dato, model_weights): prediction += d*w predictions.append(prediction) predictions = np.array(predictions) # Pro numerickou stabilitu výpočtu softmaxu predictions -= np.max(predictions) # Počítání softmaxu _sum = 0 for prediction in predictions: _sum += np.exp(prediction) for idx in range(len(predictions)): predictions[idx] = np.exp(predictions[idx]) / _sum return predictions def count_probabilities(data, weights): arr = [] for dato in data: arr.append(count_dato_probabilities(dato, weights)) return arr def make_predictions(data, weights): arr = [] for dato in data: prediction = count_dato_probabilities(dato, weights) arr.append(np.argmax(prediction)) return arr def main(args: argparse.Namespace): # Nastavení seedu generátoru náhodných čísel generator = np.random.RandomState(args.seed) # Vytvoření náhodného datasetu na klasifikační úlohu s počtem tříd `args.classes`. # Třídy mají hodnotu 0 až args.classes-1 data, target = sklearn.datasets.make_classification( n_samples=args.data_size, n_informative=args.classes, n_classes=args.classes, random_state=args.seed ) # Přidání sloupce jedniček pro bias data = np.concatenate([data, np.ones([args.data_size, 1])], axis=1) # TODO: (SGD) 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) # Vytvoření náhodných vah weights = generator.uniform(size=[args.classes, train_data.shape[1]], low=-0.1, high=0.1) # Zjištění počtu prvků v trénovací množině. # `train_data` je 2D pole, kde první dimenze je počet prvků a druhá dimenze je počet featur. # Ekvivalentně šlo zjistit počet prvků i pomocí: # `train_size = int(args.data_size * (1 - args.test_size))` train_size = train_data.shape[0] for epoch in range(args.epochs): # TODO: (SGD) 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_size) # TODO: Pro každou batch s velikostí args.batch_size spočítejte gradient # a upravte váhy. # Můžete předpokládat, že args.batch_size je dělitelem počtu prvků v trénovací množině. for batch_start_idx in range(0, train_size, args.batch_size): gradient = np.zeros([args.classes, train_data.shape[1]]) # Procházím batch který začíná na indexu `batch_start_idx` a končí na indexu `batch_start_idx+args.batch_size-1`. # Slicing v Pythonu `permuration[...:...]` má polootevřený interval, tedy poslední index není zahrnut. for batch_elem_idx in permutation[batch_start_idx:batch_start_idx+args.batch_size]: dato = train_data[batch_elem_idx] dato_target = train_target[batch_elem_idx] probabilities = count_dato_probabilities(dato, weights) for _class, probability in enumerate(probabilities): # Ternární operátor if: pokud je `dato_target` rovno `_class`, # tak proměnná `dato_class_target` bude nastavena na 1, jinak na 0. dato_class_target = 1 if dato_target == _class else 0 for i in range(train_data.shape[1]): gradient[_class, i] += (probability - dato_class_target) * dato[i] weights = weights - args.learning_rate * gradient / args.batch_size # Bez numpy: # for i in range(train_data.shape[1]): # weights[i] = weights[i] - args.learning_rate * gradient[i] / args.batch_size # 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 = sklearn.metrics.accuracy_score(train_target, make_predictions(train_data, weights)) test_accuracy = sklearn.metrics.accuracy_score(test_target, make_predictions(test_data, weights)) # TODO: Poté spočítejte chybu softmaxu na trénovací a testovací množině. # Chybu můžete spočítat explicitně nebo využít funkce `log_loss` v knihovně sklearn. # log-loss namísto accuracy chce na vstupu pravděpodobnosti, ne již klasifikovaná data. train_log_loss = sklearn.metrics.log_loss(train_target, count_probabilities(train_data, weights)) test_log_loss = sklearn.metrics.log_loss(test_target, count_probabilities(test_data, weights)) print(f"Epoch {epoch+1}: train loss {train_log_loss:.6f} acc {train_accuracy*100:.2f}%, test loss {test_log_loss:.6f} acc {test_accuracy*100:.2f}%") if __name__ == "__main__": args = parser.parse_args() main(args)