{"nbformat":4,"nbformat_minor":0,"metadata":{"colab":{"name":"classification 3.1 gridsearchcv - hyperparameter optimization.ipynb","provenance":[],"collapsed_sections":[],"toc_visible":true},"kernelspec":{"name":"python3","display_name":"Python 3"}},"cells":[{"cell_type":"markdown","metadata":{"collapsed":true,"id":"m3i_6d7eJKLl"},"source":["# Μια ολοκληρωμένη διαδικασία ταξινόμησης\n","\n","Το scikit-learn έχει καθιερώσει την ορολογία που περιγράφει με κομψό τρόπο μια ολοκληρωμένη διαδικασία Μachine Learning, στην περίπτωσή που εξετάζουμε μια διαδικασία ταξινόμησης.\n","\n","## Pipeline, Εκτιμητές και Μετασχηματιστές\n","\n","Μια διαδικασία ML ή workflow ή pipeline αποτελείται από μια σεiρα μετασχηματιστών πάνω στα χαρακτηριστικά των δεδομένων που τελειώνει σε έναν εκτιμητή.\n","\n","Οι μετασχηματιστές χρησιμοποιούνται για να κανουν την προεπεξεργασία (μέσω μετασχηματισμού) των δεδομένων. Είδαμε στο προηγούμενο notebook πέντε μετασχηματιστές: την επιλογή χαρακτηριστικών VarianceThreshold, δύο μετασχηματιστές κανονικοποίησης (τον scaler και τον min_max_scaler), τον εξισορροπητή με τυχαία υπερδειγματοληψία RandomOverSampler και την εξαγωγή χαρακτηριστικών PCA. Αν ανατρέξετε στο προηγούμενο notebook θα δείτε ότι κάνουν fit και transform στο train set και transform στο test set.\n","\n","Οι μετασχηματιστές γενικα έχουν και αυτοί υπερ-παραμέτρους που επηρρεάζουν τη λειτουργία τους: ο VarianceThreshold είχε το κατώτερο κατώφλι διακύμανσης ο PCA τον αριθμό των κύριων συνιστωσών, ενώ ακόμα και οι scaler, min_max_scaler και RandomOverSampler έχουν αλλά δεν τις εξετάσαμε. Όπως έχουμε πει η επιλογή των υπερ-παραμέτρων (όπως το k του kNN) γίνεται μόνο εμπειρικά μέσω διασταυρούμενης επικύρωσης (cross-validation). Οι μετασχηματιστές και οι υπερπαράμετροι τους επιδρούν λοιπόν στη μορφή των δεδομένων.\n","\n","Στο τέλος του pipeline VarianceThreshold - scaler - RandomOverSampler - PCA βάλαμε τον εκτιμητή - ταξινομητή MultiLayerPerceptron. O MLP έχει και αυτός υπερ-παραμέτρους και μάλιστα έναν πολύ μεγάλο αριθμό (18 για την ακρίβεια): πλήθος και επίπεδα κρυμμένων νευρώνων, συνάρτηση ενεργοποίησης, βελτιστοποίησης κλπ. Στο προηγούμενο notebook χρησιμοποιήσαμε ένα MLP με σταθερές υπερπαραμέτρους, ωστόσο σε μια ολοκληρωμένη διαδικασία ML οι υπερ-παράμετροι (ή κάποιες από τις υπερπαραμέτρους) του ταξινομητή πρέπει και αυτές να βελτιστοποιηθούν με διαδικασία cross-validation. Οι ταξινομητές και οι υπερπαράμετροι τους δεν επιδρούν στη μορφή των δεδομένων όπως οι μετασχηματιστές αλλά έχουν προφανώς επίδραση στην απόδοση του μοντέλου.\n","\n","## Ορισμός (επιλογή) ενός εκπαιδευμένου μοντέλου εκτιμητή (ταξινομητή)\n","\n","Μια ολοκληρωμένη διαδικασία pipeline λόγω του ότι τελειώνει σε έναν εκτιμητή μπορεί να θεωρηθεί και συνολικά ως ένας εκτιμητής, με κανένα, με λίγους ή με περισσότερους μετασχηματιστές πριν από τον εκτιμητή. Στο προηγούμενο παράδειγμα είδαμε στην αρχή ένα μοντέλο με μόνο τον εκτιμητή (εφαρμογή του MLP απευθείας στο dataset) και ένα τελικό μοντέλο με τέσσερεις μετασχηματιστές πριν τον εκτιμητή. Ένα εκπαιδευμένο μοντέλο εκτιμητή (ταξινομητή) αποτελείται\n","- α) απο την αρχιτεκτονική του, δηλαδή τον συνδυασμό μετασχηματιστών και την επιλογή του τελικού εκτιμητή (το pipeline), και \n","- β) από τις (βέλτιστες) τιμές των υπερ-παραμέτρων όλων των προηγουμένων που προκύπτουν από το cross-validation. \n","\n","\n","\n","Το τελικό βελτιστοποιημένο μοντέλο αποτιμάται στα δεδομένα test και χρησιμοποιείται για να κάνει προβλέψεις σε νέα δεδομένα.\n","\n","ΠΡΟΣΟΧΗ: εκτός από τη διαχείριση τιμών που απουσιάζουν με Imputer, όλοι οι υπόλοιποι μετασχηματιστές βρίσκονται εντός του σχήματος crossvalidation. \n","\n","Η μετατροπή κατηγορικών μεταβλητών γίνεται μετά τη διαχείριση τιμών που απουσιάζουν και πριν το crossvalidation (εκτός δλδ)."]},{"cell_type":"markdown","metadata":{"id":"lIoeOdaWJKLn"},"source":["## Βελτιστοποίηση ύπερ-παραμέτρων (Hyperparameter optimization)\n","\n","Είδαμε ότι τόσο οι μετασχηματιστές όσο και οι εκτιμητές έχουν υπερ-παραμέτρους που πρέπει να βελτιστοποιηθούν με cross-validation. Εφόσον τόσο οι μετασχηματιστές όσο και ο εκτιμητής αποτελούν μέρος ενός ενιαίου pipeline, για να βρούμε τις βέλτιστες τιμές όλων των υπερ-παραμέτρων μέσω cross-validation θα πρέπει\n","- α) για κάθε fold του cross-validation, να υπολογίσουμε την απόδοση όλων των πιθανών συνδυασμών υπερ-παραμέτρων μετασχηματιστών και εκτιμητή και \n","- β) να επιλέξουμε το συνδυασμό υπερ-παραμέτρων που έχει τον καλύτερο μέσο όρο με βάση κάποια μετρική σε όλα τα folds.\n","\n","\n","### Αναζήτηση πλέγματος (grid search)\n","\n","Η απόδοση όλων των πιθανών συνδυασμών υπερ-παραμέτρων μετασχηματιστών και εκτιμητή γίνεται με αναζήτηση πλέγματος (grid search). Ορίζουμε για κάθε παράμετρο ένα πεδίο ορισμού, συνήθως με ελάχιστο, μέγιστο και κάποιο βήμα και φτιάχνουμε ένα πλέγμα με όλους τους πιθανούς συνδυασμούς τιμών των παραμέτρων. Για παράδειγμα:\n","\n","για κύριες συνιστώσες PCA (transformer) από 5 μέχρι 15 με βήμα 5 και για έναν kNN (estimator) με k από 1 μέχρι 5 με βήμα 2 παίρνουμε το ακόλουθο grid:\n","\n","| | | | |\n","|-----|-------|--------|--------|\n","| | PC=5 | PC=10 | PC=15 |\n","| k=1 | (1,5) | (1,10) | (1,15) |\n","| k=3 | (3,5) | (3,10) | (3,15) |\n","| k=5 | (5,5) | (5,10) | (5,15) |\n","\n","Για κάθε τιμή υπερπαραμέτρων του grid θα πρέπει να υπολογιστεί ο μέσος όρος του εκτιμητή σε όλα τα folds του cross-validation με βάση το metric (πχ F1) και να επιλεχθεί ο καλύτερος συνδυασμός παραμέτρων. Η συγκεκριμένη στρατηγική αναζήτησης των βέλτιστων υπερπαραμέτρων είναι η εξαντλητική αναζήτηση πλέγματος (exhaustive grid search) και είναι προφανώς πολύ ακριβή υπολογιστικά. Υπάρχουν διάφορες τεχνικές για να περιορίζεται η πολυπλοκότητα του grid search, αλλά δεν το αποφεύγουμε γενικά, γιατί οι υπερπαράμετροι είναι ορίσματα των εκτιμητών και δεν μαθαίνονται από την fit.\n","\n","Συνοψίζοντας, η βελτιστοποίηση των υπερπαραμέτρων απαιτεί \n","\n","- έναν εκτιμητή (έναν ταξινομητή)\n","- τον πεδίο ορισμού των υπερπαραμέτρων\n","- ένα τρόπο αναζήτησης των πιθανών συνδυασμών τιμών τους πχ grid search\n","- ένα σχήμα cross-validation πχ 5-fold\n","- μια μετρική απόδοσης (ή score) πχ F1-macro\n","\n","Το scikit-learn μας απλοποιεί σε πολύ μεγάλο βαθμό την κατασκευή pipelines και τη βελτιστοποίηση των υπερπαραμέτρων. Θα το δούμε με ένα παράδειγμα."]},{"cell_type":"markdown","metadata":{"id":"E1wVUP0oJKLo"},"source":["Θα βελτιστοποιήσουμε με cross-validation και grid search ένα pipeline με προ-επεξεργασία των δεδομένων από transformers και estimator τον kNN."]},{"cell_type":"markdown","metadata":{"id":"QnFEJHRAJKLp"},"source":["# MNIST handwritten digits dataset\n","Ενημερώνουμε τις βιβλιοθήκες μας. Επίσης θα αγνοήσουμε κάποια warnings"]},{"cell_type":"code","metadata":{"id":"60DxH4hqv5g7"},"source":["!pip install -U pip\n","!pip install -U scikit-learn\n","!pip install -U numpy\n","!pip install -U pandas\n","!pip install -U tensorflow\n","import warnings \n","warnings.filterwarnings('ignore')"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"ZitmYTW15d9Z"},"source":["Θα εισάγουμε το πολύ γνωστό και απο το Deep Learning dataset [MNIST](http://yann.lecun.com/exdb/mnist/). Θα το εισάγουμε από έναν άλλο server όπου υπάρχει ένα αντίγραφο. Το dataset είναι επίσης διαθέσιμο στο [Kaggle](https://www.kaggle.com/gustavoatt/mnist-original)."]},{"cell_type":"code","metadata":{"id":"gZYdK0xHfXwo"},"source":["import numpy as np\n","import pandas as pd\n","\n","mnist_train = pd.read_csv(\"https://www.python-course.eu/data/mnist/mnist_train.csv\", header=None).values\n","mnist_test = pd.read_csv(\"https://www.python-course.eu/data/mnist/mnist_test.csv\", header=None).values\n","mnist = np.concatenate((mnist_train, mnist_test), axis=0) # ενώνουμε train και test"],"execution_count":null,"outputs":[]},{"cell_type":"code","metadata":{"id":"4NATnixruUjy"},"source":["features = mnist[:, 1:]\n","targets = mnist[:, :1] # τα labels είναι στην πρώτη κολώνα"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"oyBI-FW5JKLw"},"source":["To MNIST περιλαμβάνει 70000 δείγματα χειρόγραφων ψηφίων μεγέθους 28x28 pixels, με ετικέτες από το 0 ως το 9. Τα 28x28 pixels κάθε δείγματος αντιστοιχούν σε 768 χαρακτηριστικά με τιμές του γκρι από 0 (μάυρο) εώς 256 (λευκό)"]},{"cell_type":"code","metadata":{"id":"wqaNnxHVJKLx"},"source":["print(features.shape)\n","print(targets.shape)\n","print(np.unique(targets)) #τυπώνουμε τις μοναδικές ετικέτες των labels"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"b-KkvXLPJKL4"},"source":["Θα κάνουμε οπτικοποίηση ενός δείγματος:"]},{"cell_type":"code","metadata":{"id":"JucaKnxDJKL6"},"source":["%matplotlib inline\n","import matplotlib.pyplot as plt\n","pixels = features[1500]\n","pixels = pixels.reshape((28, 28))\n","plt.imshow(pixels, cmap='gray')\n","plt.show()"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"xiOZZIntJKL_"},"source":["Επειδή το dataset είναι μεγάλο (ειδικά για τον kNN) και για το παράδειγμά μας θέλουμε να δουλέψουμε με λιγότερα δείγματα. Το ανακατεύουμε και παίρνουμε ένα μικρό αριθμό samples"]},{"cell_type":"code","metadata":{"scrolled":true,"id":"fGJyXQPqJKMA"},"source":["from sklearn.utils import shuffle\n","sdata, starget = shuffle(features, targets, random_state=341976)\n","samples = 1000\n","data = sdata[0:samples-1,:]\n","target = starget[0:samples-1]"],"execution_count":null,"outputs":[]},{"cell_type":"code","metadata":{"id":"QTHa32MzJKME"},"source":["from sklearn.model_selection import train_test_split\n","X_train, X_test, y_train, y_test = train_test_split(data, target, test_size=0.33, random_state=20176)"],"execution_count":null,"outputs":[]},{"cell_type":"code","metadata":{"id":"OP_af-MiJKMJ"},"source":["from sklearn import neighbors\n","from sklearn.metrics import classification_report\n","clf = neighbors.KNeighborsClassifier()\n","clf.fit(X_train,y_train)\n","preds = clf.predict(X_test)\n","print(classification_report(y_test, preds))"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"4OEeXtfRHaCO"},"source":["# Pipelines"]},{"cell_type":"markdown","metadata":{"id":"PSLRuvjxJKMN"},"source":["Για την κατασκευή του μοντέλου θα βασιστούμε στην κλάση Pipeline. Επειδή οι κλάσεις εξισορρόπησης του imblearn όπως η [RandomOverSampler](http://contrib.scikit-learn.org/imbalanced-learn/stable/generated/imblearn.over_sampling.RandomOverSampler.html) τυπικά δεν έχουν μέθοδο transform (έχουν fit_sample) η built-in Pipeline του scikit (from sklearn.pipeline import Pipeline) δεν τις δέχεται ως transformers. Θα φέρουμε την Pipeline από το imblearn (που έχει transform για τους samplers)."]},{"cell_type":"code","metadata":{"id":"cPkbvAyTwjdN"},"source":["!pip install --upgrade imbalanced-learn"],"execution_count":null,"outputs":[]},{"cell_type":"code","metadata":{"id":"JrLrdMceJKMO"},"source":["#from sklearn.pipeline import Pipeline\n","from imblearn.pipeline import Pipeline\n","\n","# φέρνουμε τις γνωστές μας κλάσεις για preprocessing\n","from sklearn.feature_selection import VarianceThreshold\n","from sklearn.preprocessing import StandardScaler # φέρνουμε τον StandarScaler ως transformer που έχει .transform kai ΄όχι ως scale()\n","from imblearn.over_sampling import RandomOverSampler\n","from sklearn.decomposition import PCA\n","\n","# αρχικοποιούμε τον εκτιμητή (ταξινομητής) και τους μετασχηματιστές χωρίς υπερ-παραμέτρους\n","selector = VarianceThreshold()\n","scaler = StandardScaler()\n","ros = RandomOverSampler()\n","pca = PCA()\n","clf = neighbors.KNeighborsClassifier(n_jobs=-1) # η παράμετρος n_jobs = 1 χρησιμοποιεί όλους τους πυρήνες του υπολογιστή\n","pipe = Pipeline(steps=[('selector', selector), ('scaler', scaler), ('sampler', ros), ('pca', pca), ('kNN', clf)])"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"Ypk9FqwaJKMT"},"source":["Το pipeline συμπεριφέρεται ως ένας ενιαίος estimator. Μπορούμε να εφαρμόσουμε fit και predict."]},{"cell_type":"code","metadata":{"id":"TahYufqoJKMU"},"source":["pipe.fit(X_train,y_train)\n","preds = pipe.predict(X_test)\n","print(classification_report(y_test, preds))"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"oGI4K_pXHhpd"},"source":["# GridsearchCV"]},{"cell_type":"markdown","metadata":{"id":"LkBQboyeJKMc"},"source":["Στη συνέχεια θα χρησιμποιήσουμε την [GridSearchCV](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html) για να βελτιστοποιήσουμε τις υπερπαραμέτρους μας. Η GridSearchCV κάνει μαζί cross-validation και grid search. Αρχικά μελετάμε το variance των μεταβλητών για τη variance threshold:"]},{"cell_type":"code","metadata":{"id":"8llFfWkU0ENO"},"source":["train_variance = X_train.var(axis=0)\n","print(train_variance)\n","print(np.max(train_variance))"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"sWsZXzCI0BfT"},"source":[" Την εισάγουμε και θέτουμε τις τιμές ορισμού των υπερπαραμέτρων:"]},{"cell_type":"code","metadata":{"id":"acHBk9N6JKMc"},"source":["from sklearn.model_selection import GridSearchCV\n","\n","vthreshold = [0, 4000, 8000, 12000] #προσαρμόζουμε τις τιμές μας στο variance που παρατηρήσαμε\n","n_components = [10, 20, 30, 40, 50, 60]\n","k = [1, 6, 11, 21, 31, 41] # η υπερπαράμετρος του ταξινομητή"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"s-1JhcWBJKMj"},"source":["Επειδή ο χώρος αναζήτησης των βέλτιστων υπερπαραμέτρων αρχίζει να μεγαλώνει, ξαναορίζουμε την pipeline με την παράμετρο 'memory': για κάθε fold του crossvalidation και για καθε συνδυασμό υπερπαραμέτρων μετασχηματιστών, τα δεδομένα χρειάζεται να μετασχηματιστούν μία φορά και όχι για κάθε νέα τιμή υπερπαραμέτρων του εκτιμητή. \n","\n","Είναι πιθανό στο fit να σας εμφανιστούν κάποια warnings με τη χρήση του memory. Ξανατρέξτε το block του κώδικα."]},{"cell_type":"code","metadata":{"id":"pjQapwjLJKMl"},"source":["pipe = Pipeline(steps=[('selector', selector), ('scaler', scaler), ('sampler', ros), ('pca', pca), ('kNN', clf)], memory = 'tmp')"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"AXyzuq4zJKMp"},"source":["Μπορούμε να θέτουμε τιμές στις υπερπαραμέτρους των pipelines χρησιμοποιώντας τα ονόματα των estimators, \"\\_\\_\", το όνομα της υπερπαραμέτρου, \"=\" και τις τιμές που της δίνουμε στο grid search. Επίσης μπορούμε να θέσουμε τη μετρική της απόδοσης με την παράμετρο \"scoring\". Με την παράμετρο \"cv\" ορίζουμε τον αριθμό των folds. Για βελτιστοποίηση, μπορούμε να θέσουμε την παράμετρο n_jobs=-1 ώστε να χρησιμοποιούνται όλοι οι πυρήνες του υπολογιστή (το default είναι 1)."]},{"cell_type":"code","metadata":{"id":"N9aHef9cJKMs"},"source":["estimator = GridSearchCV(pipe, dict(selector__threshold=vthreshold, pca__n_components=n_components, kNN__n_neighbors=k), cv=5, scoring='f1_macro', n_jobs=-1)"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"flUq4SjOJKMw"},"source":["Το GridSearchCV είναι επίσης ένας estimator με fit και predict. Ανάλογα το search space η εκτέλεση του GridSearchCV μπορεί να πάρει αρκετό χρόνο"]},{"cell_type":"code","metadata":{"id":"BIE3BBGQJKMz"},"source":["import time\n","start_time = time.time()\n","estimator.fit(X_train, y_train)\n","preds = estimator.predict(X_test)\n","print(\"Συνολικός χρόνος fit και predict: %s seconds\" % (time.time() - start_time))\n","print(classification_report(y_test, preds))"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"pZHju2CcJKM6"},"source":["Tυπώνουμε τον καλύτερο estimator και τον καλύτερο συνδυασμό υπερπαραμέτρων:"]},{"cell_type":"code","metadata":{"id":"w3LAEHBRJKM7"},"source":["print(estimator.best_estimator_)\n","print(estimator.best_params_)"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"XYH-AL6GJKNA"},"source":["Η στοχαστικότητα στη διαδικασία της ταξινόμησης οφείλεται στα διαφορετικό διαχωρισμό σε folds σε κάθε run αλλά στον RandomOverSampler που επιλέγει τυχαία δείγματα κατά τη δειγματοληψία."]},{"cell_type":"markdown","metadata":{"id":"yBK0eKkKJKNN"},"source":["## Επιλογή αρχιτεκτονικής μοντέλου pipeline\n","\n","Προσοχή, η βέλτιστη αρχιτεκτονική δεν είναι δεδομένη αλλά εξαρτάται από το dataset. Δοκιμαστε στο ίδιο grid, χωρίς scaler και sampler"]},{"cell_type":"code","metadata":{"id":"FhG5uSl6JKNO"},"source":["pipe = Pipeline(steps=[('selector', selector),('pca', pca), ('kNN', clf)], memory = 'tmp')\n","estimator = GridSearchCV(pipe, dict(selector__threshold=vthreshold, pca__n_components=n_components, kNN__n_neighbors=k), cv=3, scoring='f1_macro', n_jobs=-1)\n","# ο estimator με βελτιστοποιημένες υπερπαραμέτρους είναι έτοιμος να κάνει prediction.\n","# Ωστόσο για να μην πάνε χαμένα δεδομένα (ούτε ένα fold), τον κάνουμε fit σε όλα τα δεδομένα train.\n","estimator.fit(X_train, y_train)\n","preds = estimator.predict(X_test)\n","print(classification_report(y_test, preds))\n","print(estimator.best_estimator_)\n","print(estimator.best_params_)"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"d3D_2jlnJKNS"},"source":["Και φυσικά θα μπορούσαμε να έχουμε διαφορετικά αποτελέσματα εφαρμόζοντας min max scaler αντι standard scaler, undersampling αντί oversampling κοκ. Προφανώς η πιο σημαντική απόφαση στην αρχιτεκτονική του ταξινομητή είναι η επιλογή του τελικού estimator, αν πχ βάλουμε MLP ή SVM αντί kNN, και βέβαια η βελτιστοποίηση των υπερπαραμέτρων τους."]},{"cell_type":"markdown","metadata":{"id":"PX3ZkL7OJKNV"},"source":["## Progressive grid search\n","\n","Στο πεδίο ορισμού των παραμέτρων, ξεκινάμε με μεγάλα διαστήματα και σχετικά λίγα βήματα. Αν διαπιστώσουμε ότι υπαρχει μια περιοχή τιμών κάποιας παραμέτρου που δίνει καλη απόδοση μπορούμε να μικρύνουμε το διάστημα του grid search γύρω της και να βάλουμε περισσότερα βήματα."]},{"cell_type":"code","metadata":{"scrolled":true,"id":"pu8PlyLzJKNW"},"source":["vthreshold = [0]\n","n_components = [39, 40, 41]\n","k = [1, 3]\n","estimator = GridSearchCV(pipe, dict(selector__threshold=vthreshold, pca__n_components=n_components, kNN__n_neighbors=k), scoring='f1_macro', n_jobs=-1)\n","estimator.fit(X_train, y_train)\n","preds = estimator.predict(X_test)\n","print(classification_report(y_test, preds))\n","print(estimator.best_estimator_)\n","print(estimator.best_params_)"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"KvSjUCRYJKNd"},"source":["Το περισσότερο fine grained grid search, αν δώσει καλύτερες τιμές θα έχει βελτιστοποιήσει τον εκτιμητή, αν όχι, τουλάχιστον θα επιβεβαιώσει ότι είμαστε σε ένα καλό τοπικό μέγιστο της συνάρτησης αξιολόγησης.\n","\n"]},{"cell_type":"markdown","metadata":{"id":"tQJr6CLSxlKI"},"source":["# Βιβλιοθήκες βελτιστοποίησης υπερπαραμέτρων\n","\n","Εκτός του scikit-learn, και για μεγάλα και απαιτητικά datasets υπάρχουν πολλές εξειδικευμένες βιβλιοθήκες για τη βελτιστοποίηση των υπερπαραμέτρων. Οι βιβλιοθήκες αυτές χρησιμοποιούν μεθοδολογίες όπως παραλληλισμό, έξυπνη αναζήτηση του χώρου των υπερπαραμέτρων, οπτικοποίηση της διαδικασίας, υποστήριξη βιβλιοθηκών βαθιάς μάθησης κ.α. Οι τεχνικές αναζήτησης στον χώρο των υπερπαραμέτρων είναι αυτόνομο και ευρύ ερευνητικό αντικείμενο.\n","Θα δώσουμε μερικά παραδείγματα χρήσης τέτοιων βιβλιοθηκών αλλά βέβαια πρέπει κάποιος να μελετήσει αναλυτικά την τεκμηρίωσή τους για να εκμεταλλευτεί πλήρως τις δυνατότητές τους."]},{"cell_type":"markdown","metadata":{"id":"C59INv3gC7eK"},"source":["## Ray"]},{"cell_type":"code","metadata":{"id":"7QcvieRuDXzl","cellView":"form"},"source":["#@title\n","%%html\n",""],"execution_count":null,"outputs":[]},{"cell_type":"code","metadata":{"id":"Ty-ITICnz3aH"},"source":["!pip install -U ray \n","!pip install -U ray[tune]\n","!pip install -U tune-sklearn"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"ZeHTkN0EDqqm"},"source":["Στο επόμενο παράδειγμα χρησιμοποιούμε έναν στοχαστικό γραμμικό ταξινομητή το elasticent που είναι όπως η γραμμική παλινδρόμηση αλλά με όρους ομαλοποίησης. Το στοχαστικό οφείλεται στο ότι κάνουμε υπολογισμό της κλίσης και ανανέωση των βαρών δείγμα-δείγμα όχι για όλα τα δείγματα μαζί όπως είναι η vanilla GD. \n","\n","Εξαιτίας αυτού, η εκπαίδευση στοχαστικών ταξινομητών είναι πολύ πιο αργή, καθώς δεν κινούμαστε με βέλτιστο τρόπο προς το τοπικό ελάχιστο. Ωστόσο, το όφελος είναι ότι χρησιμοποιούμε πολύ λιγότερη μνήμη καθώς δεν φορτώνουμε το σύνολο του dataset αλλά μόνο ένα δείγμα.\n","\n","![](https://miro.medium.com/max/1154/1*jjcOf5V66UUHNfuniT4z-g.png)\n","\n","\n","Το penalty elasticnet κάνει ομαλοποίηση με ένα συνδυασμό κόστους πρωτοβάθμιου αθροίσματος των βαρών (l1) και δευτεροβάθμιου (l2). H υπερπαράμετρος *l1_ratio* ελέγχει την μίξη των δύο όρων.\n","\n","Θα χρησιμοποιήσουμε επίσης την συνάρτηση [make_classification](https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_classification.html) για να φτίαξουμε γρήγορα ένα συνθετικό dataset.\n","\n","Οι υπερπαράμετροι που εξετάζουμε είναι το $α$ που ελέγχει τον βαθμό της ομαλοποιήσης (μεγαλύτερο $α$ μεγαλύτερη ομαλοποίηση) και *l1_ratio*"]},{"cell_type":"code","metadata":{"id":"w-7LiJ8A0GSD"},"source":["from sklearn.linear_model import SGDClassifier\n","\n","sgd = SGDClassifier(loss='log', penalty='elasticnet')\n","\n","from sklearn.datasets import make_classification\n","import numpy as np\n","\n","# Create dataset\n","X, y = make_classification(\n"," n_samples=11000,\n"," n_features=1000,\n"," n_informative=50,\n"," n_redundant=0,\n"," n_classes=10,\n"," class_sep=2.5)\n","x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=1000)\n","\n","# Example parameters to tune from SGDClassifier\n","parameter_grid = {\"alpha\": [1e-4, 1e-1], \"l1_ratio\": [0.1, 0.2]}"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"Arn7QMJTSQkj"},"source":["Η παράμετρος early_stopping μας επιτρέπει να τερματίσουμε γρήγορα διατάξεις που δεν υπόσχονται πολλά αν θέσουμε early_stopping=True. max_iters είναι ο μέγιστος αριθμός επαναλήψεων που μπορεί να τρέξει ένα σύνολο υπερπαραμέτρω. Μπορεί να τρέξει για λιγότερες αν είναι early_stopped.\n","\n","Σημειώστε ότι αυτή είναι η πιο απλή μορφή grid search του ray. Υπάρχουν πολλές περισσότερες στην τεκμηρίωση."]},{"cell_type":"code","metadata":{"id":"RJz1HpTu0MAL"},"source":["# from sklearn.model_selection import GridSearchCV\n","from tune_sklearn import TuneGridSearchCV\n","\n","tune_search = TuneGridSearchCV(\n"," sgd, parameter_grid, early_stopping=True, max_iters=5)\n","\n","import time # Just to compare fit times\n","start = time.time()\n","tune_search.fit(x_train, y_train)\n","end = time.time()\n","print(\"Tune GridSearch Fit Time:\", end - start)"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"4pRVjIWJXFY_"},"source":["Ας κάνουμε μια σύγκριση με το vanilla scikit-learn (αργό, Θα κάνει περίπου 10 λεπτά)"]},{"cell_type":"code","metadata":{"id":"hdz09rpvRcmi"},"source":["# n_jobs=-1 enables use of all cores like Tune does\n","sklearn_search = GridSearchCV(sgd, parameter_grid, n_jobs=-1)\n","\n","start = time.time()\n","sklearn_search.fit(x_train, y_train)\n","end = time.time()\n","print(\"Sklearn Fit Time:\", end - start)"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"qHokcMQla8mQ"},"source":["## Optuna"]},{"cell_type":"code","metadata":{"cellView":"form","id":"C3F34EWGa_K1"},"source":["#@title\n","%%html\n",""],"execution_count":null,"outputs":[]},{"cell_type":"code","metadata":{"id":"vrX14Y65c6PR"},"source":["!pip install optuna"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"dk-xwiyXdMzQ"},"source":["Θα χρησιμοποίησουμε τη βιβλιοθήκη optuna για να βρούμε τη βέλτιστη αρχιτεκτονική (layers, neurons) ενός MLP στο MNIST. (Αργό, περίπου 17-18 λεπτά)."]},{"cell_type":"code","metadata":{"id":"TNmuLGS1bd5k"},"source":["import optuna\n","import sklearn.datasets\n","from sklearn.datasets import fetch_openml\n","import sklearn.neural_network\n","\n","def objective(trial):\n","\n"," n_layers = trial.suggest_int('n_layers', 1, 2)\n"," layers = []\n"," for i in range(n_layers):\n"," layers.append(trial.suggest_int(f'n_units_{i}', 10, 20))\n"," \n"," mnist = fetch_openml('mnist_784')\n"," x_train, x_test, y_train, y_test = sklearn.model_selection.train_test_split(\n"," mnist.data, mnist.target)\n","\n","\n"," clf = sklearn.neural_network.MLPClassifier(hidden_layer_sizes=tuple(layers))\n"," clf.fit(x_train, y_train)\n","\n"," return clf.score(x_test, y_test)\n","\n","study = optuna.create_study(direction='maximize')\n","study.optimize(objective, n_trials=3)"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"4nynrEIMjnlw"},"source":["Σε περίπτωση όπου δύο αρχιτεκτονικές έχουν ίδια ή πολύ κοντινή επίδοση, επιλέγουμε την απλούστερη (ξυράφι του Occam). Δείτε το σχετικό βίντεο για το ξυράφι του Occam και το overfiting.\n"]},{"cell_type":"code","metadata":{"id":"0o27WvdGj6Nt"},"source":["from IPython.display import YouTubeVideo\n","YouTubeVideo('Q_AclBHCaUo', width=800, height=300)"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"dHexTK43kJsI"},"source":["Όπως και για την Ray αυτό είναι ένα πολύ περιορισμένο παράδειγμα των δυνατοτήτων της βιβλιοθήκης. Συμβουλευτείτε την τεκμηρίωσή της."]},{"cell_type":"markdown","metadata":{"id":"fnpYSC5Qfhci"},"source":["## Άλλα frameworks βελτιστοποίησης\n","\n","* [Hyperopt](https://github.com/hyperopt/hyperopt)\n","* [mlmachine](https://github.com/petersontylerd/mlmachine)\n","* [Polyaxon](https://polyaxon.com/docs/automation/optimization-engine/)\n","* [BayesianOptimization](https://github.com/fmfn/BayesianOptimization)\n","* [Talos](https://github.com/autonomio/talos)\n","* [SHERPA](https://parameter-sherpa.readthedocs.io/en/latest/)\n","* [Scikit-Optimize](https://scikit-optimize.github.io/stable/user_guide.html)"]}]}