{"nbformat":4,"nbformat_minor":0,"metadata":{"colab":{"name":"Lab 9.2 Εισαγωγή στην βιβλοθήκη ΓΑ DEAP.ipynb","provenance":[]},"kernelspec":{"name":"python3","display_name":"Python 3"}},"cells":[{"cell_type":"markdown","metadata":{"collapsed":true,"id":"FXNCweHxH2bF"},"source":[""]},{"cell_type":"markdown","metadata":{"id":"qZ-8g6AuH2bI"},"source":["Στην Python είναι διαθέσιμες πολλές βιβλιοθήκες γενετικών αλγόριθμων: \n","- [Pyevolve](http://pyevolve.sourceforge.net/)\n","- [pyeasyga](https://pypi.python.org/pypi/pyeasyga)\n","- [PyOCL/OpenCLGA](https://github.com/PyOCL/OpenCLGA)\n","- [inspyred](https://pypi.python.org/pypi/inspyred)\n","\n","Θα βρούμε επίσης γενετικούς σε βιβλιοθήκες βελτιστοποίησης όπως η [PyOpt](http://www.pyopt.org/)\n","\n","Μια από τις πιο ενεργές, ευρύτερα χρησιμοποιούμενες και ενδιαφέρουσες βιβλιοθήκες είναι η [DEAP (Distributed Evolutionary Algorithms in Python)](https://github.com/DEAP/deap). Τα κύρια πλεονεκτήματα της DEAP είναι:\n","- μπορεί να αναπτύξει κανείς γενετικούς αλγόριθμους πάνω σε οποιαδήποτε δομή δεδομένων (list, array, set, dictionary, tree, numpy array κλπ) ή ad hoc κλάση. \n","- είναι διαθέσιμοι πολλοί γνωστοί γενετικοί τελεστές και ολόκληροι αλγόριθμοι, αλλά έχει κανείς επίσης τη δυνατότητα να ορίζει εύκολα τους δικούς του, κάτι που είναι ιδιάιτερα χρήσιμο στις περιπτώσεις που είναι ιδιαίτερα εξαρτώμενοι από το πρόβλημα.\n","- οι γενετικοί είναι από τη φύση τους πολύ κατάλληλοι για παράλληλη εκτέλεση (παράδειγμα: η αποτίμηση της καταλληλότητας ενός πληθυσμού). Η DEAP σε συνδυασμό με τις βιβλιοθήκες \"SCOOP\" και \"multiprocessing\" προσφέρει ένα πολύ εύκολο τρόπο για αποτελεσματική παράλληλη υλοποίηση των αλγόριθμων.\n","\n","# Εισαγωγή\n","\n","Εγκατάσταση:"]},{"cell_type":"code","metadata":{"scrolled":true,"id":"lFfKCr9jH2bJ","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1638783336216,"user_tz":-120,"elapsed":4161,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}},"outputId":"51e487b9-5cfb-458f-8a6e-6fdabc1c55da"},"source":["! pip install -U deap"],"execution_count":1,"outputs":[{"output_type":"stream","name":"stdout","text":["Collecting deap\n"," Downloading deap-1.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (160 kB)\n","\u001b[?25l\r\u001b[K |██ | 10 kB 18.6 MB/s eta 0:00:01\r\u001b[K |████ | 20 kB 22.5 MB/s eta 0:00:01\r\u001b[K |██████ | 30 kB 26.8 MB/s eta 0:00:01\r\u001b[K |████████▏ | 40 kB 28.0 MB/s eta 0:00:01\r\u001b[K |██████████▏ | 51 kB 28.9 MB/s eta 0:00:01\r\u001b[K |████████████▏ | 61 kB 27.3 MB/s eta 0:00:01\r\u001b[K |██████████████▎ | 71 kB 27.7 MB/s eta 0:00:01\r\u001b[K |████████████████▎ | 81 kB 28.0 MB/s eta 0:00:01\r\u001b[K |██████████████████▎ | 92 kB 29.6 MB/s eta 0:00:01\r\u001b[K |████████████████████▍ | 102 kB 31.2 MB/s eta 0:00:01\r\u001b[K |██████████████████████▍ | 112 kB 31.2 MB/s eta 0:00:01\r\u001b[K |████████████████████████▍ | 122 kB 31.2 MB/s eta 0:00:01\r\u001b[K |██████████████████████████▌ | 133 kB 31.2 MB/s eta 0:00:01\r\u001b[K |████████████████████████████▌ | 143 kB 31.2 MB/s eta 0:00:01\r\u001b[K |██████████████████████████████▌ | 153 kB 31.2 MB/s eta 0:00:01\r\u001b[K |████████████████████████████████| 160 kB 31.2 MB/s \n","\u001b[?25hRequirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from deap) (1.19.5)\n","Installing collected packages: deap\n","Successfully installed deap-1.3.1\n"]}]},{"cell_type":"markdown","metadata":{"id":"lZp0KD2lH2bT"},"source":["## Βασικές έννοιες: Creator, Base, Fitness\n","\n","Οι δύο βασικές έννοιες του DEAP είναι οι Creator και Base. \n","\n","O **Creator** είναι ένα μέτα - εργοστάσιο δημιουργίας κλάσεων που θα χρησιμεύσουν στον γενετικό αλγόριθμο. Η **Base** είναι ένα δομοστοιχείο που παρέχει δύο βασικές δομές (κλάσεις) για την κατασκευή του γενετικού: το **Toolbox**, που θα χρησιμοποιήσουμε για να αποθηκέυσουμε (εισάγουμε) τους τελεστές και την (εικονική) κλάση **Fitness** που θα χρησιμοποιήσουμε για να κατασκευάσουμε το μέλος καταλληλότητας του κάθε ατόμου.\n","\n","Ας πούμε ότι θέλουμε να ορίσουμε μια συνάρτηση καταλληλότητας προς *ελαχιστοποίηση*:"]},{"cell_type":"code","metadata":{"id":"1LzceeQWH2bU","executionInfo":{"status":"ok","timestamp":1638783484572,"user_tz":-120,"elapsed":763,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}}},"source":["from deap import base, creator\n","creator.create(\"FitnessMin\", base.Fitness, weights=(-1.0,))"],"execution_count":2,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"JeSOhuhnH2bY"},"source":["Η συνάρτηση `create` λαμβάνει τουλάχιστον __δύο ορίσματα__: το όνομα της κλάσης που θα δημιουργήσουμε και τη βασική κλάση από την οποία θα κληρονομίσει. Τα ακόλουθα ορίσματα αποτελούν χαρακτηριστικά της κλάσης. Στην περίπτωση του παραδείγματος, η νέα κλάση `FitnessMin` κληρονομεί την `base.Fitness` με χαρακτηριστικό την πλειάδα weights (-1.0,). Το (-1.0,) σημαίνει ότι θέλουμε να ελαχιστοποιήσουμε ένα μόνο κριτήριο. Εξ ορισμού το DEAP είναι σχεδιασμένο για πολυ-κριτηριακή βελτιστοποίηση (multi-objective optimization) και γι' αυτό αναμένει μια πλειάδα βαρών εξού και το \",\". Αν θέλαμε να μεγιστοποιήσουμε ένα κριτήριο θα θέταμε weights=(1.0,). Στη μονοκριτηριακή βελτιστοποίηση σημασία έχει μόνο το πρόσημο του βάρους. Για πολυκριτηριακή βελτιστοποίηση τα βάρη καθορίζουν τη σχετική σημασία των κριτηρίων. Για παράδειγμα το weights=(-1.0,2.0) ορίζει μια πολυκριτηριακή βελτιστοποίηση όπου θέλουμε να ελαχιστοποιήσουμε το πρώτο κριτήριο, να μεγιστοποιήσουμε το δεύτερο, και το δεύτερο έχει διπλάσια σημασία (βάρος) από το πρώτο.\n","\n","Στη συνέχεια ορίζουμε την κλάση του **ατόμου** το οποίο κληρονομεί από τον τύπο *list* και περιλαμβάνει το χαρακτηρηστικό `FitnessMin`"]},{"cell_type":"code","metadata":{"id":"A3ykofgnH2ba","executionInfo":{"status":"ok","timestamp":1638783511214,"user_tz":-120,"elapsed":249,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}}},"source":["creator.create(\"Individual\", list, fitness=creator.FitnessMin)"],"execution_count":3,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"zOANJHquH2bg"},"source":["Μπορούμε τώρα να ορίσουμε ένα στιγμιότυπο ατόμου και να υπολογίσουμε/ορίσουμε την καταλληλότητα του:"]},{"cell_type":"code","metadata":{"id":"aqOL19WbH2bh","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1638783578711,"user_tz":-120,"elapsed":289,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}},"outputId":"48c1484d-8f7d-4eea-8197-1d5eb0412724"},"source":["ind = creator.Individual([1,0,1,0,1])\n","ind.fitness.values = (sum(ind),)\n","\n","print(ind)\n","print(type(ind))\n","print(type(ind.fitness))\n","print(ind.fitness.values)"],"execution_count":4,"outputs":[{"output_type":"stream","name":"stdout","text":["[1, 0, 1, 0, 1]\n","\n","\n","(3.0,)\n"]}]},{"cell_type":"markdown","metadata":{"id":"dbzqe-O7H2bn"},"source":["Στο συγκεκριμένο παράδειγμα ορίσαμε ως καταλληλότητα το άθροισμα των στοιχείων της λίστας που αποτελούν το άτομο ind. Η καταλληλότητα στο DEAP είναι πάντα πλειάδα και η μονοκριτηριακή βελτιστοποίηση είναι μια ειδική περίπτωση (προσέξτε το \",\"). Επίσης προσέξτε ότι εμείς ορίζουμε τις τιμές του χαρακτηρηστικού fitness.values. \n","\n","## Τελεστές\n","\n","Το DEAP μας επιτρέπει να δημιουργούμε τελεστές μαζί με τις παραμέτρους τους και να τους ομαδοποιούμε σε εργαλιοθήκες (toolbox):"]},{"cell_type":"code","metadata":{"id":"LKKLdIs1H2br","executionInfo":{"status":"ok","timestamp":1638783656611,"user_tz":-120,"elapsed":290,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}}},"source":["from deap import tools\n","toolbox = base.Toolbox()\n","toolbox.register(\"mate\", tools.cxOnePoint)\n","toolbox.register(\"mutate\", tools.mutGaussian, mu=0.0, std=1.0)"],"execution_count":5,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"VeCFDBArH2bx"},"source":["Αρχικοποιούμε ένα στιγμιότυπο *toolbox* και του αναθέτουμε με τη register ένα τελεστή διασταύρωσης και ένα τελεστή μετάλλαξης. Η register απαιτεί τουλάχιστον δύο ορίσματα, το όνομα που δίνουμε στον τελεστή και τη συνάρτηση που τον υλοποιεί. Τα επόμενα ορίσματα μπορούν να είναι παράμετροι του τελεστή. Εδώ χρησιμοποιούμε τις builtin cxOnePoint (διαστάυρωση ενός σημείου) και mutGausian (γκαουσιανή μετάλλαξη με μέση τιμή 0 και απόκλιση 1 στο παράδειγμα).\n","\n","## Παραλληλισμός\n","Η DEAP μπορεί εύκολα να παραλληλοποιηθεί με τη χρήση της βιβλιοθήκης Scalable Concurent Operations ([SCOOP](https://github.com/soravux/scoop)) και να τρέξει σε κατανεμημένα συστήματα. Γιαυτό αρκεί κανείς να αντικαταστήσει στο toolbox τη στάνταρ συνάρτηση `map` της Python (που εφαρμόζει μια συνάρτηση σε κάθε στοιχείο μιας λίστας) με τη συνάρτηση map του SCOOP. "]},{"cell_type":"code","metadata":{"id":"7_MszMTjH2by","colab":{"base_uri":"https://localhost:8080/","height":346},"executionInfo":{"status":"ok","timestamp":1638783772783,"user_tz":-120,"elapsed":5041,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}},"outputId":"0b4e9b3b-c7eb-4900-e796-87048ca8fcc0"},"source":["! pip install -U scoop\n","\n","from scoop import futures\n","toolbox.register(\"map\", futures.map)"],"execution_count":6,"outputs":[{"output_type":"stream","name":"stdout","text":["Collecting scoop\n"," Downloading scoop-0.7.1.1.tar.gz (603 kB)\n","\u001b[?25l\r\u001b[K |▌ | 10 kB 16.9 MB/s eta 0:00:01\r\u001b[K |█ | 20 kB 20.1 MB/s eta 0:00:01\r\u001b[K |█▋ | 30 kB 22.7 MB/s eta 0:00:01\r\u001b[K |██▏ | 40 kB 25.2 MB/s eta 0:00:01\r\u001b[K |██▊ | 51 kB 27.2 MB/s eta 0:00:01\r\u001b[K |███▎ | 61 kB 28.3 MB/s eta 0:00:01\r\u001b[K |███▉ | 71 kB 26.8 MB/s eta 0:00:01\r\u001b[K |████▍ | 81 kB 27.1 MB/s eta 0:00:01\r\u001b[K |█████ | 92 kB 27.3 MB/s eta 0:00:01\r\u001b[K |█████▍ | 102 kB 28.6 MB/s eta 0:00:01\r\u001b[K |██████ | 112 kB 28.6 MB/s eta 0:00:01\r\u001b[K |██████▌ | 122 kB 28.6 MB/s eta 0:00:01\r\u001b[K |███████ | 133 kB 28.6 MB/s eta 0:00:01\r\u001b[K |███████▋ | 143 kB 28.6 MB/s eta 0:00:01\r\u001b[K |████████▏ | 153 kB 28.6 MB/s eta 0:00:01\r\u001b[K |████████▊ | 163 kB 28.6 MB/s eta 0:00:01\r\u001b[K |█████████▎ | 174 kB 28.6 MB/s eta 0:00:01\r\u001b[K |█████████▉ | 184 kB 28.6 MB/s eta 0:00:01\r\u001b[K |██████████▎ | 194 kB 28.6 MB/s eta 0:00:01\r\u001b[K |██████████▉ | 204 kB 28.6 MB/s eta 0:00:01\r\u001b[K |███████████▍ | 215 kB 28.6 MB/s eta 0:00:01\r\u001b[K |████████████ | 225 kB 28.6 MB/s eta 0:00:01\r\u001b[K |████████████▌ | 235 kB 28.6 MB/s eta 0:00:01\r\u001b[K |█████████████ | 245 kB 28.6 MB/s eta 0:00:01\r\u001b[K |█████████████▋ | 256 kB 28.6 MB/s eta 0:00:01\r\u001b[K |██████████████▏ | 266 kB 28.6 MB/s eta 0:00:01\r\u001b[K |██████████████▊ | 276 kB 28.6 MB/s eta 0:00:01\r\u001b[K |███████████████▏ | 286 kB 28.6 MB/s eta 0:00:01\r\u001b[K |███████████████▊ | 296 kB 28.6 MB/s eta 0:00:01\r\u001b[K |████████████████▎ | 307 kB 28.6 MB/s eta 0:00:01\r\u001b[K |████████████████▉ | 317 kB 28.6 MB/s eta 0:00:01\r\u001b[K |█████████████████▍ | 327 kB 28.6 MB/s eta 0:00:01\r\u001b[K |██████████████████ | 337 kB 28.6 MB/s eta 0:00:01\r\u001b[K |██████████████████▌ | 348 kB 28.6 MB/s eta 0:00:01\r\u001b[K |███████████████████ | 358 kB 28.6 MB/s eta 0:00:01\r\u001b[K |███████████████████▋ | 368 kB 28.6 MB/s eta 0:00:01\r\u001b[K |████████████████████ | 378 kB 28.6 MB/s eta 0:00:01\r\u001b[K |████████████████████▋ | 389 kB 28.6 MB/s eta 0:00:01\r\u001b[K |█████████████████████▏ | 399 kB 28.6 MB/s eta 0:00:01\r\u001b[K |█████████████████████▊ | 409 kB 28.6 MB/s eta 0:00:01\r\u001b[K |██████████████████████▎ | 419 kB 28.6 MB/s eta 0:00:01\r\u001b[K |██████████████████████▉ | 430 kB 28.6 MB/s eta 0:00:01\r\u001b[K |███████████████████████▍ | 440 kB 28.6 MB/s eta 0:00:01\r\u001b[K |████████████████████████ | 450 kB 28.6 MB/s eta 0:00:01\r\u001b[K |████████████████████████▌ | 460 kB 28.6 MB/s eta 0:00:01\r\u001b[K |█████████████████████████ | 471 kB 28.6 MB/s eta 0:00:01\r\u001b[K |█████████████████████████▌ | 481 kB 28.6 MB/s eta 0:00:01\r\u001b[K |██████████████████████████ | 491 kB 28.6 MB/s eta 0:00:01\r\u001b[K |██████████████████████████▋ | 501 kB 28.6 MB/s eta 0:00:01\r\u001b[K |███████████████████████████▏ | 512 kB 28.6 MB/s eta 0:00:01\r\u001b[K |███████████████████████████▊ | 522 kB 28.6 MB/s eta 0:00:01\r\u001b[K |████████████████████████████▎ | 532 kB 28.6 MB/s eta 0:00:01\r\u001b[K |████████████████████████████▉ | 542 kB 28.6 MB/s eta 0:00:01\r\u001b[K |█████████████████████████████▍ | 552 kB 28.6 MB/s eta 0:00:01\r\u001b[K |█████████████████████████████▉ | 563 kB 28.6 MB/s eta 0:00:01\r\u001b[K |██████████████████████████████▍ | 573 kB 28.6 MB/s eta 0:00:01\r\u001b[K |███████████████████████████████ | 583 kB 28.6 MB/s eta 0:00:01\r\u001b[K |███████████████████████████████▌| 593 kB 28.6 MB/s eta 0:00:01\r\u001b[K |████████████████████████████████| 603 kB 28.6 MB/s \n","\u001b[?25hRequirement already satisfied: greenlet>=0.3.4 in /usr/local/lib/python3.7/dist-packages (from scoop) (1.1.2)\n","Requirement already satisfied: pyzmq>=13.1.0 in /usr/local/lib/python3.7/dist-packages (from scoop) (22.3.0)\n","Collecting argparse>=1.1\n"," Downloading argparse-1.4.0-py2.py3-none-any.whl (23 kB)\n","Building wheels for collected packages: scoop\n"," Building wheel for scoop (setup.py) ... \u001b[?25l\u001b[?25hdone\n"," Created wheel for scoop: filename=scoop-0.7.1.1-py3-none-any.whl size=72145 sha256=d269e37d94b87e85c70cfd868b475748fbc0f24996acc683a3b0d5b1d2478736\n"," Stored in directory: /root/.cache/pip/wheels/24/19/e9/6e3e7c0323cc36bf1e4993d69b2db27d6b4e806ed57d411f44\n","Successfully built scoop\n","Installing collected packages: argparse, scoop\n","Successfully installed argparse-1.4.0 scoop-0.7.1.1\n"]},{"output_type":"display_data","data":{"application/vnd.colab-display-data+json":{"pip_warning":{"packages":["argparse"]}}},"metadata":{}}]},{"cell_type":"markdown","metadata":{"id":"7kM6PDpxH2b6"},"source":["Περισσότερες πληροφορίες για τη SCOOP και παραδείγματα [εδώ](http://deap.readthedocs.io/en/master/tutorials/basic/part4.html) και [εδώ](http://scoop.readthedocs.io/en/latest/usage.html). \n","\n","\n","Η DEAP μπορεί επίσης να χρησιμοποιήσει τη map της βιβλιοθήκης multiprocessing για να τρέξει παράλληλα σε πολλούς πυρήνες ενός μηχάνηματος:"]},{"cell_type":"code","metadata":{"id":"bTQoZNHWH2b7","executionInfo":{"status":"ok","timestamp":1638783787741,"user_tz":-120,"elapsed":265,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}}},"source":["import multiprocessing\n","pool = multiprocessing.Pool()\n","toolbox.register(\"map\", pool.map)"],"execution_count":7,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"agLU9CQNH2cB"},"source":["Στην πράξη στα cloud δεν θα χρησιμοποιήσουμε αυτές τις βιβλιοθήκες αλλά τοπικά είναι πολύ αποτελεσματικές ανάλογα τους διαθέσιμους πόρους.\n","\n","# Παράδειγμα 1: επίλυση του προβλήματος OneMax\n","\n","Το πρόβλημα *OneMax* (ή BitCounting) είναι ένα πολύ απλό πρόβλημα που συνίσταται στο να μεγιστοποιηθεί ο αριθμός των bits \"1\" σε μια δυαδική συμβολοσειρά. Πιο τυπικά, το πρόβλημα περιγράφεται με την αναζήτηση μιας συμβολοσειράς \n","$\\vec{x}=\\{x_{1},x_{2},\\ldots{},x_{N}\\}$, με $x_{i}\\in \\{0,1\\}$,\n","τέτοια που να μεγιστοποιεί την ακόλουθη εξίσωση:\n","\n","\\begin{equation}\n","F(\\vec{x}) = \\sum_{i=1}^{N}{x_{i}}\n","\\end{equation}\n","\n","Η βέλτιστη λύση είναι προφανώς $x_{i}=1$ για $i=1..N$.\n","\n","Αρχικά ορίζουμε μια καταλληλότητα προς μεγιστοποίηση:"]},{"cell_type":"code","metadata":{"id":"QGdCk9ynH2cC","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1638783871083,"user_tz":-120,"elapsed":309,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}},"outputId":"2717d8c7-133f-4eb4-8147-b2588ed7dc29"},"source":["creator.create(\"FitnessMax\", base.Fitness, weights=(1.0,))\n","creator.create(\"Individual\", list, fitness=creator.FitnessMax)"],"execution_count":8,"outputs":[{"output_type":"stream","name":"stderr","text":["/usr/local/lib/python3.7/dist-packages/deap/creator.py:141: RuntimeWarning: A class named 'Individual' has already been created and it will be overwritten. Consider deleting previous creation of that class or rename it.\n"," RuntimeWarning)\n"]}]},{"cell_type":"markdown","metadata":{"id":"p20kAzIMH2cH"},"source":["Στη συνέχεια θα δημιουργήσουμε τις κλάσεις των ατόμων και του πληθυσμού μας:"]},{"cell_type":"code","metadata":{"id":"ehefFqROH2cI","executionInfo":{"status":"ok","timestamp":1638783947777,"user_tz":-120,"elapsed":295,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}}},"source":["import random\n","\n","toolbox = base.Toolbox()\n","# Attribute generator \n","toolbox.register(\"attr_bool\", random.randint, 0, 1)\n","# Structure initializers\n","toolbox.register(\"individual\", tools.initRepeat, creator.Individual, toolbox.attr_bool, 100)\n","toolbox.register(\"population\", tools.initRepeat, list, toolbox.individual)"],"execution_count":9,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"bovacLK-H2cM"},"source":["Στο μπλοκ αυτό εγγράψαμε μια συνάρτηση δημιουργίας ενός χαρακτηριστικού \"attr_bool\" που παίρνει μια τυχαία δυαδική τιμή. Στη συνέχεια θα δημιουργήσουμε την κλάση των ατόμων χρησιμοποιώντας την `initRepeat()`. Η συνάρτηση αυτή επιστρέφει ένα άτομο - λίστα με καταλληλότητα προς μεγιστοποίηση (μέσω της κληρονομιάς του τύπου από το \"Individual\" και της \"FitnessMax\") που προκύπτει αν καλούσαμε την \"attr_bool\" 100 φόρες.\n","\n","Παρόμοια χρησιμοποιούμε την initRepeat για να φτιάξουμε τον πληθυσμό ο οποίος είναι μια λίστα με τα individual που μόλις ορίσαμε. Εδώ δεν ορίζουμε το μήκος της λίστας. \n","\n","Μπορούμε να δούμε πως λειτουργεί η κλήση αυτών των συναρτήσεων:"]},{"cell_type":"code","metadata":{"id":"GVqsdDRNH2cO","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1638783953592,"user_tz":-120,"elapsed":308,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}},"outputId":"180a213a-0067-4a27-e317-4a68f671216a"},"source":["bit = toolbox.attr_bool()\n","ind = toolbox.individual()\n","pop = toolbox.population(n=3)\n","\n","print(\"bit is of type %s and has value\\n%s\" % (type(bit), bit))\n","print(\"ind is of type %s and contains %d bits\\n%s\" % (type(ind), len(ind), ind))\n","print(\"pop is of type %s and contains %d individuals\\n%s\" % (type(pop), len(pop), pop))"],"execution_count":10,"outputs":[{"output_type":"stream","name":"stdout","text":["bit is of type and has value\n","1\n","ind is of type and contains 100 bits\n","[0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1]\n","pop is of type and contains 3 individuals\n","[[0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0], [0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0], [1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1]]\n"]}]},{"cell_type":"markdown","metadata":{"id":"Bk7W0ZRMH2cT"},"source":["Στη συνέχεια ορίζουμε τη συνάρτηση καταλληλότητας που πολύ απλά αθροίζει τους άσους του κάθε ατόμου:"]},{"cell_type":"code","metadata":{"id":"UIozesd-H2cV","executionInfo":{"status":"ok","timestamp":1638783993478,"user_tz":-120,"elapsed":282,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}}},"source":["def evalOneMax(individual):\n"," return sum(individual),"],"execution_count":11,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"YzlR1d0gH2cZ"},"source":["Σημειώστε και πάλι ότι επιστρέφουμε μια πλειάδα η οποία έχει μόνο ένα στοιχείο γιατι κάνουμε μονο κριτηριακή βελτιστοποίηση.\n","\n","Προχωράμε στον ορισμό της εργαλειοθήκης για το πρόβλημα:"]},{"cell_type":"code","metadata":{"id":"lZqIxCx8H2cb","executionInfo":{"status":"ok","timestamp":1638784128325,"user_tz":-120,"elapsed":293,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}}},"source":["toolbox.register(\"evaluate\", evalOneMax)\n","toolbox.register(\"mate\", tools.cxTwoPoint)\n","toolbox.register(\"mutate\", tools.mutFlipBit, indpb=0.10)\n","toolbox.register(\"select\", tools.selTournament, tournsize=3)"],"execution_count":12,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"PN6e2nHOH2ci"},"source":["Στην πρώτη γραμμή εγγράφουμε ως συνάρτηση καταλληλότητας \"evaluate\" την `evalOneMax` που εμείς ορίσαμε προηγουμένως. \n","\n","Στη συνέχεια εγγράφουμε ως \"mate\" τον τελεστή διασταύρωσης `cxTwoPoint` που κάνει διασταύρωση σε δύο σημεία. \n","\n","Στη συνέχεια ορίζουμε ως τελεστή μετάλλαξης \"mutate\" την αντιστροφή bit `mutFlipBit`. Προσοχή, η παράμετρος indpb (independent probability) δεν είναι η πιθανότητα μετάλλαξης ενός ατόμου (mutation probability) αλλά η πιθανότητα του κάθε bit χωριστά να υποστεί μετάλλαξη, εφόσον επιλεχθεί για μετάλλαξη το άτομο. Στη συγκεκριμένη περίπτωση περιμένουμε να αλλάξει το 10% των bits. \n","\n","\n","Τέλος επιλέγουμε εγγράφουμε τον τελεστή επιλογής \"select\" που χρησιμοποιεί την `selTournament` με μέγεθος διοργάνωσης 3. Αυτό σημαίνει ότι διαλέγουμε 3 τυχαία άτομα του πληθυσμού, τα συγκρίνουμε και κρατάμε το καλύτερο (ένας ακόμη τρόπος να υλοποιήσουμε το survival of the fittest). Γενικά η selTournament επιστρέφει αναφορές (references) και όχι τα ίδια τα άτομα (θα μας χρειαστεί αργότερα).\n","\n","Μπορείτε να δείτε όλους τους τελεστές στο [tools library reference](http://deap.readthedocs.io/en/master/api/tools.html) της DEAP.\n","\n","Σημειώστε ότι οι τελεστές εκτελούνται inplace στα άτομα στα οποία καλούνται:"]},{"cell_type":"code","metadata":{"id":"sC-VJXwPH2cl","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1638784158215,"user_tz":-120,"elapsed":286,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}},"outputId":"170351c6-33be-48e2-ae11-4300d381065c"},"source":["ind = toolbox.individual()\n","print(ind)\n","toolbox.mutate(ind)\n","print(ind)"],"execution_count":13,"outputs":[{"output_type":"stream","name":"stdout","text":["[0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0]\n","[0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0]\n"]}]},{"cell_type":"markdown","metadata":{"id":"Rbx2QtktH2cu"},"source":["Που σημαίνει ότι αν ένα άτομο δεν αντιγραφεί πρωτού το τροποποιήσουμε η αρχική του τιμή χάνεται. Η αντιγραφή γίνεται με την `clone`. Σημειώστε επίσης ότι δύο αντικείμενα είναι διαφορετικά ακόμα και αν έχουν ίδια χρωμοσώματα."]},{"cell_type":"code","metadata":{"id":"uSg6ajE9H2cx","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1638784189755,"user_tz":-120,"elapsed":397,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}},"outputId":"e36b3095-7d25-40de-b14c-c2d438cca13e"},"source":["mutant = toolbox.clone(ind)\n","print(mutant is ind)\n","print(mutant == ind)\n","# o mutant δεν είναι το ίδιο άτομο με τον ind αλλά έχει το ίδιο χρωμόσωμα"],"execution_count":14,"outputs":[{"output_type":"stream","name":"stdout","text":["False\n","True\n"]}]},{"cell_type":"markdown","metadata":{"id":"YLpianfQH2c2"},"source":["Μπορούμε πλέον πολύ απλά να τρέξουμε τον βασικό γενετικό αλγόριθμο `eaSimple` με import από το [algorithms](http://deap.readthedocs.io/en/master/api/algo.html) του DEAP. Θα αρχικόποιήσουμε έναν πληθυσμό n ατόμων, και θα τρέξουμε την eaSimple ορίζοντας πιθανότητες διασταύρωσης (cxpb), μετάλλαξης (mutpb) καθώς και τον αριθμό των γενεών (ngen). Τέλος θα τυπώσουμε το καλύτερο άτομο του τελικού πληθυσμού με την `selBest`"]},{"cell_type":"code","metadata":{"id":"ZqKka1hhH2c4","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1638784299808,"user_tz":-120,"elapsed":1885,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}},"outputId":"fb693d66-6a98-4b7e-95ac-240d30dbc632"},"source":["from deap import algorithms\n","if __name__ == \"__main__\":\n"," pop = toolbox.population(n=300)\n"," algorithms.eaSimple(pop, toolbox, cxpb=0.5, mutpb=0.2, ngen=50, verbose=False)\n"," print(tools.selBest(pop, k=1))"],"execution_count":15,"outputs":[{"output_type":"stream","name":"stdout","text":["[[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]\n"]}]},{"cell_type":"markdown","metadata":{"id":"-iZn1IJVH2dC"},"source":["Το `if __name__ == \"__main__\":` επιτρέπει την εκτέλεση του κώδικα που το ακολουθεί μόνο όταν το script (εδώ το notebook) είναι η κυρίως ρουτίνα που εκτελείται και που ονομάζεται αυτόματα \"\\_\\_main\\_\\_\". Αυτό διασφαλίζει ότι σε παραλληλία η κυρίως συνάρτηση δεν θα τρέξει από διεργασίες-παιδιά. \n","\n","Προκειμένου να έχουμε καλύτερη εικόνα της συμπεριφοράς του αλγόριθμου θα ξαναγράψουμε το κυρίως πρόγραμμα ως εξής:"]},{"cell_type":"code","metadata":{"id":"yoSNTPHCH2dE","executionInfo":{"status":"ok","timestamp":1638784358655,"user_tz":-120,"elapsed":327,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}}},"source":["def ea_with_stats():\n"," import numpy\n"," \n"," pop = toolbox.population(n=300)\n"," hof = tools.HallOfFame(1)\n"," stats = tools.Statistics(lambda ind: ind.fitness.values)\n"," stats.register(\"avg\", numpy.mean)\n"," stats.register(\"min\", numpy.min)\n"," stats.register(\"max\", numpy.max)\n"," \n"," pop, logbook = algorithms.eaSimple(pop, toolbox, cxpb=0.5, mutpb=0.2, ngen=40, stats=stats, halloffame=hof, verbose=True)\n"," \n"," return pop, logbook, hof"],"execution_count":16,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"i91d0q1qH2dI"},"source":["Η `HallofFame` κρατάει τα k καλύτερα άτομα που έχουν εμφανιστεί οποιαδήποτε στιγμή μέσα στον πληθυσμό. Την αρχικοποιούμε ως \"hof\"\n","\n","H `Statistics` υπολογίζει στατιστικά για μια οποιαδήποτε λίστα αντικειμένων. Εδώ, για κάθε πληθυσμό, θα χρησιμοποιήσουμε τη λίστα με της τιμές καταλληλότητας όλων των ατόμων του πληθυσμού και θα την αρχικοποιήσουμε ως \"stats\". Στη συνέχεια θα εγράψουμε στη stats τρεις αριθμητικες πράξεις (μέσω της numpy) που θα εκτελούνται στη λίστα με τις καταλληλότητες των ατόμων: μέση, ελάχιστη και μέγιστη τιμή της καταλληλότητας του πληθυσμού.\n","\n","Τα επιπλέον ορίσματα στην eaSimple λειτουργούν ως εξής:\n","- το stats=stats ορίζει ποια στατιστικά θα υπολογίζονται σε κάθε γενιά\n","- το halloffame=hof το αντικείμενο HallOfFame που θα αποθηκεύεται το τυχόν συνολικά βέλτιστο άτομο\n","- το verbose=True θα τυπώνει στην οθόνη τον αριθμό της γενιάς, τον αριθμό αποτιμήσεων καταλληλότητας που χρειάστηκε να γίνουν και στη συνέχεια τα στατιστικά της \"stats\". Σημειώστε ότι μετά την αρχικοποίηση η αποτίμηση καταλληλότητας γίνεται μόνο για άτομα που έχουν υποστεί αλλαγή.\n","\n","Τέλος με την έξοδο pop, logbook η eaSimple μας επιστρέφει τον τελικό πληθυσμό και το αντικείμενο logbook που περιέχει τις στατιστικές που έχουμε ορίσει για όλες τις γενιές.\n","\n","Μπορείτε να δείτε όλες τις παραπάνω συναρτήσεις στο [library reference](http://deap.readthedocs.io/en/master/api/index.html)\n","\n","Στο επόμενο μπλοκ τρέχουμε προστατευμένη την ea_with_stats, τυπώνουμε το καλύτερο άτομο και χρησιμοποιόντας το logbook \"log\" τυπώνουμε την εξέλιξη των τριών μετρικών (avg, min, max) ως συνάρτηση των διαδοχικών γενεών. "]},{"cell_type":"code","metadata":{"scrolled":true,"id":"phgRSeALH2dK","colab":{"base_uri":"https://localhost:8080/","height":1000},"executionInfo":{"status":"ok","timestamp":1638784398434,"user_tz":-120,"elapsed":2273,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}},"outputId":"caa2eb29-94fc-4a5e-8333-1d03ae08957b"},"source":["if __name__ == \"__main__\":\n"," pop, log, hof = ea_with_stats()\n"," print(\"Best individual is: %s\\nwith fitness: %s\" % (hof[0], hof[0].fitness))\n"," \n"," %matplotlib inline\n"," import matplotlib.pyplot as plt\n"," gen, avg, min_, max_ = log.select(\"gen\", \"avg\", \"min\", \"max\")\n"," plt.plot(gen, avg, label=\"average\")\n"," plt.plot(gen, min_, label=\"minimum\")\n"," plt.plot(gen, max_, label=\"maximum\")\n"," plt.xlabel(\"Generation\")\n"," plt.ylabel(\"Fitness\")\n"," plt.legend(loc=\"lower right\")\n"," plt.show()"],"execution_count":17,"outputs":[{"output_type":"stream","name":"stdout","text":["gen\tnevals\tavg \tmin\tmax\n","0 \t300 \t49.3433\t35 \t66 \n","1 \t192 \t53.12 \t41 \t65 \n","2 \t200 \t56.19 \t44 \t72 \n","3 \t175 \t58.8033\t50 \t72 \n","4 \t205 \t61.54 \t49 \t73 \n","5 \t177 \t64.6433\t54 \t73 \n","6 \t169 \t67.2867\t57 \t80 \n","7 \t182 \t69.31 \t59 \t80 \n","8 \t167 \t71.34 \t59 \t82 \n","9 \t176 \t73.2533\t61 \t82 \n","10 \t183 \t75.2967\t62 \t83 \n","11 \t182 \t77.0733\t67 \t86 \n","12 \t170 \t78.6767\t63 \t91 \n","13 \t170 \t80.3167\t66 \t89 \n","14 \t172 \t82.0767\t68 \t89 \n","15 \t175 \t83.4733\t70 \t89 \n","16 \t197 \t84.6567\t70 \t91 \n","17 \t181 \t86 \t71 \t93 \n","18 \t169 \t86.8433\t73 \t93 \n","19 \t172 \t88.0933\t75 \t93 \n","20 \t205 \t88.74 \t75 \t94 \n","21 \t189 \t89.7767\t73 \t95 \n","22 \t171 \t90.6767\t75 \t96 \n","23 \t172 \t91.4267\t77 \t96 \n","24 \t182 \t91.8033\t79 \t96 \n","25 \t169 \t92.6633\t78 \t97 \n","26 \t197 \t92.9233\t78 \t98 \n","27 \t177 \t93.8167\t79 \t98 \n","28 \t199 \t93.7833\t81 \t98 \n","29 \t178 \t94.55 \t82 \t98 \n","30 \t170 \t95.1067\t78 \t99 \n","31 \t181 \t95.8567\t75 \t99 \n","32 \t201 \t95.7467\t82 \t100\n","33 \t165 \t96.1 \t81 \t100\n","34 \t183 \t96.61 \t80 \t100\n","35 \t174 \t97.13 \t79 \t100\n","36 \t173 \t97.4767\t82 \t100\n","37 \t178 \t97.5567\t82 \t100\n","38 \t182 \t97.76 \t82 \t100\n","39 \t163 \t98.03 \t83 \t100\n","40 \t188 \t97.7967\t79 \t100\n","Best individual is: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n","with fitness: (100.0,)\n"]},{"output_type":"display_data","data":{"image/png":"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\n","text/plain":["
"]},"metadata":{"needs_background":"light"}}]},{"cell_type":"markdown","metadata":{"id":"PmIG9slcH2dP"},"source":["Μπορούμε επίσης αντί να χρησιμοποιήσουμε τους έτοιμους αλγόριθμους της algorithms να δημιουργήσουμε το δικό μας γενετικό αλγόριθμο που θα χρησιμοποιεί τους τελεστές που έχουμε ορίσει. Αυτό μας δίνει τη δυνατότητα να ελέγχουμε λεπτομερώς τη λειτουργία του αλγόριθμου."]},{"cell_type":"code","metadata":{"id":"kLadvtTuH2dR","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1638784462452,"user_tz":-120,"elapsed":3836,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}},"outputId":"6e5820b3-b877-430d-d479-fd028e1448ce"},"source":["def ea_manual():\n","\n"," pop = toolbox.population(n=300)\n","\n"," # όριζουμε τις πιθανότητες διασταύρωσης CXPB και μετάλλαξης MUTPB\n"," CXPB, MUTPB = 0.5, 0.2\n"," \n"," # υπολογίζουμε τη λίστα καταλληλότητας για όλο τον πληθυσμό\n"," fitnesses = list(map(toolbox.evaluate, pop))\n"," # στη manual εφαρμογή πρέπει εμείς να ενημερώνουμε τα fitness.values των ατόμων\n"," for ind, fit in zip(pop, fitnesses):\n"," ind.fitness.values = fit\n"," \n"," # εξάγουμε το fitness ως scalar (το πρώτο χαρακτηριστικό της πλειάδας) σε μία λίστα\n"," fits = [ind.fitness.values[0] for ind in pop]\n","\n"," # θα πρέπει επίσης να παρακολουθούμε εμείς τον αριθμό των γενεών\n"," g = 0\n"," \n"," # ξεκινάμε την εξέλιξη. Θα χρησιμοποιήσουμε δύο κριτήρια τερματισμού:\n"," while max(fits) < 100 and g < 100:\n"," # A new generation\n"," g = g + 1\n"," \n"," # Επιτελούμε φυσική επιλογή (επιστρέφονται αναφορές) τόσες φορές όσες ο πληθυσμός μας\n"," offspring = toolbox.select(pop, len(pop))\n"," # Χρησιμοποιώντας τις αναφορές δημιουργούμε μια νέα γενιά ατόμων (λίστα)\n"," offspring = list(map(toolbox.clone, offspring))\n"," \n"," # Εφαρμόζουμε τους τελεστές διασταύρωσης και μετάλλαξης\n"," for child1, child2 in zip(offspring[::2], offspring[1::2]):\n"," # [::2] παίρνουμε κάθε δεύτερο στοιχειο (ζυγά)\n"," # [1::2] παίρνουμε το στοιχείο 1 και μετά κάθε δεύτερο στοιχείο (μονά)\n"," # διασταύρωση με πιθανότητα CXPB\n"," if random.random() < CXPB:\n"," toolbox.mate(child1, child2)\n","\n"," # διαγράφουμε τις τιμές του fitness όσων έχουν υποστεί διασταύρωση\n"," # για να τις υπολογίσουμε αργότερα\n"," del child1.fitness.values\n"," del child2.fitness.values\n","\n"," for mutant in offspring:\n","\n"," # μετάλλαξη με πιθανότητα MUTPB\n"," if random.random() < MUTPB:\n"," toolbox.mutate(mutant)\n"," # διαγράφουμε τις τιμες του fitness όσων έχουν υποστεί μετάλλαξη\n"," del mutant.fitness.values\n"," \n"," # Θα επιλέξουμε ως invalid_ind τα άτομα που δεν έχουν τιμή fitness (που τη σβήσαμε πριν)\n"," # Με αυτό τον τρόπο υπολογίζουμε την καταλληλότητα μόνο στα καινούρια χρωμοσώματα\n"," invalid_ind = [ind for ind in offspring if not ind.fitness.valid]\n"," fitnesses = map(toolbox.evaluate, invalid_ind)\n"," for ind, fit in zip(invalid_ind, fitnesses):\n"," ind.fitness.values = fit\n"," \n"," # αντικαθιστούμε τον πληθυσμό με τη νέα γενιά\n"," pop[:] = offspring\n"," \n"," # εξάγουμε το fitness κάθε ατόμου ως scalar (το πρώτο χαρακτηριστικό της πλειάδας) σε μία λίστα\n"," fits = [ind.fitness.values[0] for ind in pop]\n"," \n"," # υπολογίζουμε και τυπώνουμε τα στατιστικά κάθε γενιάς\n"," length = len(pop)\n"," mean = sum(fits) / length\n"," print(\"Gen %i\" % g, \"Evals %i\" % len(invalid_ind), \" Avg %.4f\" % mean, \" Min %s\" % min(fits), \" Max %s\" % max(fits))\n"," \n"," # επιλέγουμε και τυπώνουμε το καλύτερο άτομο του τελικού πληθυσμού\n"," best_ind = tools.selBest(pop, 1)[0]\n"," print(\"Best individual is %s, %s\" % (best_ind, best_ind.fitness.values))\n","\n","if __name__ == \"__main__\":\n"," ea_manual()"],"execution_count":18,"outputs":[{"output_type":"stream","name":"stdout","text":["Gen 1 Evals 190 Avg 54.0500 Min 42.0 Max 68.0\n","Gen 2 Evals 160 Avg 57.7933 Min 48.0 Max 69.0\n","Gen 3 Evals 193 Avg 60.6667 Min 48.0 Max 69.0\n","Gen 4 Evals 177 Avg 63.4600 Min 51.0 Max 73.0\n","Gen 5 Evals 201 Avg 65.6833 Min 54.0 Max 75.0\n","Gen 6 Evals 180 Avg 67.5467 Min 58.0 Max 76.0\n","Gen 7 Evals 182 Avg 69.1433 Min 57.0 Max 77.0\n","Gen 8 Evals 185 Avg 71.0167 Min 58.0 Max 80.0\n","Gen 9 Evals 186 Avg 72.5633 Min 61.0 Max 80.0\n","Gen 10 Evals 185 Avg 74.2033 Min 57.0 Max 83.0\n","Gen 11 Evals 180 Avg 75.4467 Min 63.0 Max 83.0\n","Gen 12 Evals 170 Avg 77.3900 Min 66.0 Max 84.0\n","Gen 13 Evals 192 Avg 78.5500 Min 63.0 Max 84.0\n","Gen 14 Evals 159 Avg 80.1900 Min 66.0 Max 86.0\n","Gen 15 Evals 194 Avg 81.3033 Min 65.0 Max 87.0\n","Gen 16 Evals 189 Avg 82.3300 Min 67.0 Max 87.0\n","Gen 17 Evals 177 Avg 83.4433 Min 68.0 Max 89.0\n","Gen 18 Evals 180 Avg 84.2367 Min 69.0 Max 90.0\n","Gen 19 Evals 189 Avg 85.2500 Min 68.0 Max 91.0\n","Gen 20 Evals 170 Avg 86.1467 Min 72.0 Max 93.0\n","Gen 21 Evals 158 Avg 87.4333 Min 73.0 Max 94.0\n","Gen 22 Evals 181 Avg 88.1800 Min 76.0 Max 95.0\n","Gen 23 Evals 182 Avg 88.9133 Min 76.0 Max 95.0\n","Gen 24 Evals 170 Avg 89.8867 Min 77.0 Max 95.0\n","Gen 25 Evals 201 Avg 90.6400 Min 74.0 Max 95.0\n","Gen 26 Evals 176 Avg 91.4933 Min 76.0 Max 96.0\n","Gen 27 Evals 184 Avg 92.0900 Min 78.0 Max 97.0\n","Gen 28 Evals 185 Avg 92.4800 Min 76.0 Max 97.0\n","Gen 29 Evals 171 Avg 92.6867 Min 78.0 Max 97.0\n","Gen 30 Evals 168 Avg 93.4900 Min 78.0 Max 97.0\n","Gen 31 Evals 170 Avg 93.6800 Min 79.0 Max 98.0\n","Gen 32 Evals 175 Avg 94.1700 Min 78.0 Max 98.0\n","Gen 33 Evals 180 Avg 94.7400 Min 78.0 Max 99.0\n","Gen 34 Evals 179 Avg 95.3900 Min 81.0 Max 99.0\n","Gen 35 Evals 174 Avg 95.5633 Min 80.0 Max 99.0\n","Gen 36 Evals 173 Avg 96.0733 Min 83.0 Max 99.0\n","Gen 37 Evals 175 Avg 96.2333 Min 80.0 Max 99.0\n","Gen 38 Evals 155 Avg 96.3533 Min 80.0 Max 99.0\n","Gen 39 Evals 190 Avg 96.1433 Min 81.0 Max 99.0\n","Gen 40 Evals 178 Avg 96.5933 Min 80.0 Max 99.0\n","Gen 41 Evals 177 Avg 96.6933 Min 80.0 Max 99.0\n","Gen 42 Evals 191 Avg 96.9767 Min 81.0 Max 99.0\n","Gen 43 Evals 168 Avg 97.3233 Min 84.0 Max 99.0\n","Gen 44 Evals 180 Avg 96.9200 Min 84.0 Max 99.0\n","Gen 45 Evals 194 Avg 96.8500 Min 78.0 Max 99.0\n","Gen 46 Evals 164 Avg 97.3333 Min 84.0 Max 99.0\n","Gen 47 Evals 169 Avg 96.9233 Min 79.0 Max 99.0\n","Gen 48 Evals 172 Avg 96.7700 Min 78.0 Max 99.0\n","Gen 49 Evals 187 Avg 96.9367 Min 78.0 Max 99.0\n","Gen 50 Evals 192 Avg 96.9900 Min 80.0 Max 99.0\n","Gen 51 Evals 177 Avg 97.1933 Min 79.0 Max 99.0\n","Gen 52 Evals 179 Avg 96.9933 Min 80.0 Max 99.0\n","Gen 53 Evals 168 Avg 96.7900 Min 81.0 Max 99.0\n","Gen 54 Evals 174 Avg 96.6033 Min 79.0 Max 99.0\n","Gen 55 Evals 177 Avg 97.1367 Min 82.0 Max 99.0\n","Gen 56 Evals 189 Avg 96.9300 Min 81.0 Max 99.0\n","Gen 57 Evals 182 Avg 97.2167 Min 81.0 Max 99.0\n","Gen 58 Evals 165 Avg 97.0567 Min 79.0 Max 99.0\n","Gen 59 Evals 164 Avg 97.2100 Min 83.0 Max 99.0\n","Gen 60 Evals 176 Avg 97.1100 Min 80.0 Max 99.0\n","Gen 61 Evals 167 Avg 96.9100 Min 82.0 Max 99.0\n","Gen 62 Evals 189 Avg 96.6100 Min 83.0 Max 99.0\n","Gen 63 Evals 193 Avg 96.9600 Min 81.0 Max 99.0\n","Gen 64 Evals 170 Avg 97.1033 Min 81.0 Max 99.0\n","Gen 65 Evals 185 Avg 96.9133 Min 83.0 Max 99.0\n","Gen 66 Evals 182 Avg 97.0100 Min 81.0 Max 99.0\n","Gen 67 Evals 177 Avg 97.0833 Min 84.0 Max 99.0\n","Gen 68 Evals 187 Avg 96.4100 Min 79.0 Max 99.0\n","Gen 69 Evals 171 Avg 97.3267 Min 81.0 Max 99.0\n","Gen 70 Evals 177 Avg 96.6567 Min 80.0 Max 99.0\n","Gen 71 Evals 190 Avg 96.7500 Min 82.0 Max 99.0\n","Gen 72 Evals 166 Avg 96.9633 Min 81.0 Max 99.0\n","Gen 73 Evals 174 Avg 96.8567 Min 81.0 Max 99.0\n","Gen 74 Evals 169 Avg 96.9767 Min 81.0 Max 99.0\n","Gen 75 Evals 192 Avg 96.6900 Min 83.0 Max 99.0\n","Gen 76 Evals 199 Avg 97.2433 Min 82.0 Max 99.0\n","Gen 77 Evals 189 Avg 97.0367 Min 83.0 Max 99.0\n","Gen 78 Evals 160 Avg 97.4767 Min 80.0 Max 99.0\n","Gen 79 Evals 183 Avg 96.9100 Min 82.0 Max 99.0\n","Gen 80 Evals 166 Avg 96.7100 Min 82.0 Max 99.0\n","Gen 81 Evals 195 Avg 96.6200 Min 80.0 Max 99.0\n","Gen 82 Evals 186 Avg 96.3467 Min 82.0 Max 99.0\n","Gen 83 Evals 185 Avg 97.1600 Min 82.0 Max 99.0\n","Gen 84 Evals 184 Avg 96.7567 Min 84.0 Max 99.0\n","Gen 85 Evals 191 Avg 96.8933 Min 82.0 Max 99.0\n","Gen 86 Evals 182 Avg 97.3400 Min 80.0 Max 99.0\n","Gen 87 Evals 184 Avg 97.4067 Min 78.0 Max 99.0\n","Gen 88 Evals 188 Avg 96.7000 Min 83.0 Max 99.0\n","Gen 89 Evals 173 Avg 97.3900 Min 83.0 Max 99.0\n","Gen 90 Evals 174 Avg 97.2300 Min 79.0 Max 99.0\n","Gen 91 Evals 163 Avg 96.9967 Min 82.0 Max 99.0\n","Gen 92 Evals 144 Avg 97.1267 Min 81.0 Max 99.0\n","Gen 93 Evals 171 Avg 96.9200 Min 82.0 Max 99.0\n","Gen 94 Evals 193 Avg 96.6533 Min 83.0 Max 99.0\n","Gen 95 Evals 184 Avg 96.4833 Min 80.0 Max 99.0\n","Gen 96 Evals 173 Avg 97.0133 Min 83.0 Max 99.0\n","Gen 97 Evals 178 Avg 96.9333 Min 82.0 Max 99.0\n","Gen 98 Evals 168 Avg 97.1167 Min 82.0 Max 99.0\n","Gen 99 Evals 182 Avg 96.8833 Min 82.0 Max 99.0\n","Gen 100 Evals 184 Avg 97.2500 Min 80.0 Max 99.0\n","Best individual is [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], (99.0,)\n"]}]},{"cell_type":"markdown","metadata":{"id":"q_RIWYe6H2dV"},"source":["# Παράδειγμα 2: Συναρτήσεις πολλών συνεχών μεταβλητών και περιορισμοί\n","\n","Έστω ότι θέλουμε να ελαχιστοποιήσουμε τη συνάρτηση 5 μεταβλητών:\n","\n","$$f(x_1,x_2,x_3,x_4,x_5) = -5sin(x_1)sin(x_2)sin(x_3)sin(x_4)sin(x_5) – sin(5x_1)sin(5x_2)sin(x_3)sin(5x_4)sin(5x_5)$$\n","\n","με τον περιορισμό $x_i \\in [0,\\pi], \\forall i$. \n","\n","\"Γνωρίζουμε\" ότι το ολικό ελάχιστο στο διάστημα αυτό είναι $-6$ και το επιτυγχάνουμε για $x_1=x_2=x_3=x_4=x_5=\\pi/2$. \n","\n","Κατα τα γνωστά δημιουργούμε μια συνάρτηση καταλληλότητας προς ελαχιστοποίηση και τις κλάσεις των ατόμων και του πληθυσμού."]},{"cell_type":"code","metadata":{"id":"OD1DUxJ0H2dW","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1638784574134,"user_tz":-120,"elapsed":295,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}},"outputId":"3c2e2256-e8a9-4d66-f1c3-83c7008c55a1"},"source":["import numpy as np\n","from math import sin, pi\n","\n","numVariables = 5 \n","\n","creator.create( \"FitnessMin\", base.Fitness , weights=(-1.0,))\n","creator.create( \"IndividualContainer\", list , fitness= creator.FitnessMin)\n","toolbox2 = base.Toolbox()\n","toolbox2.register( \"InitialValue\", np.random.uniform, 0, pi)\n","toolbox2.register( \"indiv\", tools.initRepeat, creator.IndividualContainer, toolbox2.InitialValue, numVariables)\n","toolbox2.register( \"population\", tools.initRepeat, list , toolbox2.indiv)\n","\n","def evalSinFunc( indiv ):\n"," sum= -5*sin( indiv [0])*sin( indiv [1])*sin( indiv [2])*sin( indiv [3])*sin( indiv [4]) - sin( indiv [0]*5)*sin( indiv [1]*5)*sin( indiv [2])*sin( indiv [3]*5)*sin( indiv [4]*5)\n"," return (sum,)"],"execution_count":19,"outputs":[{"output_type":"stream","name":"stderr","text":["/usr/local/lib/python3.7/dist-packages/deap/creator.py:141: RuntimeWarning: A class named 'FitnessMin' has already been created and it will be overwritten. Consider deleting previous creation of that class or rename it.\n"," RuntimeWarning)\n"]}]},{"cell_type":"code","metadata":{"scrolled":true,"id":"HHn6-yucH2db","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1638784576748,"user_tz":-120,"elapsed":8,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}},"outputId":"3cd1d436-529e-4780-a2fd-d7d44f1c22b5"},"source":["ind = toolbox2.indiv()\n","print(ind)"],"execution_count":20,"outputs":[{"output_type":"stream","name":"stdout","text":["[2.983185015260948, 2.9195089294082974, 0.5805006955835847, 2.041006575762546, 2.6671591884789794]\n"]}]},{"cell_type":"markdown","metadata":{"id":"VhTP0VcOH2df"},"source":["Ο βασικός τρόπος για να επιβάλουμε περιορισμούς είναι να επιβάλουμε μια ποινή στην τιμή της καταλληλότητας στα άτομα που είναι εκτός των ορίων που έχουμε θέσει. \n","\n","Αρχικά ορίζουμε δύο συναρτήσεις, τη \"feasible\" που μας επιστρέφει True αν όλα τα $x_i$ είναι εντός του διαστήματος και False αλλιώς και την \"distance\" που μας ποσοτικοποιεί πόσο εκτός ορίων είναι ένα άτομο. Συγκεκριμένα επιλέγουμε η απόσταση να είναι το απόλυτο άθροισμα σε όλες τις διαστάσεις της απόστασης από το όριο. Θα μπορούσαμε να κάνουμε και άλλες επιλογές όπως πχ να χρησιμοποιήσουμε μια τετραγωνική συνάρτηση της απόστασης."]},{"cell_type":"code","metadata":{"id":"dGymJkC2H2dg","executionInfo":{"status":"ok","timestamp":1638784872782,"user_tz":-120,"elapsed":294,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}}},"source":["MIN_BOUND = np.array([0]*numVariables)\n","MAX_BOUND = np.array([pi]*numVariables)\n","\n","def feasible( indiv ):\n"," if any( indiv < MIN_BOUND) or any( indiv > MAX_BOUND):\n"," return False\n"," return True\n","\n","def distance( indiv ) :\n"," dist = 0.0\n"," for i in range (len( indiv )) :\n"," penalty = 0\n"," if ( indiv [i] < MIN_BOUND[i]) : penalty = 0 - indiv [i]\n"," if ( indiv [i] > MAX_BOUND[i]) : penalty = indiv [i] - pi\n"," dist = dist + penalty\n"," return dist"],"execution_count":26,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"V-lHWut7H2dj"},"source":["Μια πολύ χρήσιμη μέθοδος που διαθέτει η Python και η DEAP είναι η διακόσμηση συναρτήσεων μέσω διακοσμητών (decorators). Πρόκειται για τη δυνατότητα να τροποποιούμε τη συμπεριφορά μιας συνάρτησης χωρίς να μεταβάλουμε τον κώδικά της αλλά επιτυγχάνοντάς το μέσω μιας άλλης συνάρτησης (του decorator). Για το DEAP για να διακοσμήσουμε μια συνάρτηση πρέπει να είναι εγγεγραμμένη στο toolbox. Εδώ θα τροποποιήσουμε τη συνάρτηση καταλληλότητας `evalSinFunv` με την builtin `DeltaPenality`:"]},{"cell_type":"code","metadata":{"id":"CmgpFDhgH2dl","executionInfo":{"status":"ok","timestamp":1638784876674,"user_tz":-120,"elapsed":275,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}}},"source":["toolbox2.register( \"evaluate\", evalSinFunc)\n","toolbox2.decorate( \"evaluate\", tools.DeltaPenality (feasible, 7.0, distance))"],"execution_count":27,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"P514ZHJoH2ds"},"source":["Η DeltaPenality ή ποινή-Δ απαιτεί τουλάχιστον δύο ορίσματα. Το πρώτο πρέπει να επιστρέφει αν ένα άτομο είναι έγκυρο ή όχι, σύμφωνα με τα όρια που έχουμε θέσει. Εμείς θα χρησιμοποιήσουμε τη \"feasible\" που ορίσαμε γι' αυτό το λόγο. Το δεύτερο όρισμα είναι η σταθερά Δ, δηλαδή η σταθερή ποινή που θα προστεθεί (σε πρόβλημα ελαχιστοποίησης) ή αφαιρεθεί (σε πρόβλημα μεγιστοποίησης) στην τιμή καταλληλότητας ενός ατόμου που είναι εκτός των ορίων που θέλουμε. Ο τρίτος όρος είναι μια επιπλέον ποινή που μπορεί να εφαρμοστεί και που συνήθως την ορίζουμε να είναι ανάλογη του κατά πόσο είναι εκτός ορίων ένα άτομο.Συνολικά δηλαδή θα έχουμε: \n","$$f_i^\\mathrm{penalty}(\\mathbf{x}) = \\Delta - w_i d_i(\\mathbf{x})$$\n","Θυμηθείτε ότι στο μονο-κριτηριακό ($i=1$) πρόβλημα ελαχιστοποίησης μας έχουμε θέσει $w_1=-1.0$ (μπορούμε να αντιληφθούμε ήδη πως μέσω της συνάρτησης ποινής Δ θα μπορούμε να λαμβάνουμε υπόψη διαφορετικά βάρη στα κριτήρια μιας πολυ-κριτηριακής βελτιστοποίησης). Εδώ θα χρησιμοποιήσουμε την \"distance\" που ορίσαμε προηγουμένως. Μπορείτε να δείτε περισσότερα παραδείγματα υλποίησης περιορισμών [εδώ](http://deap.readthedocs.io/en/master/tutorials/advanced/constraints.html). \n","\n","Εφόσον έχουμε πραγματικούς αριθμούς θα χρησιμοποιήσουμε ένα διαφορετικό τελεστή διασταύρωσης, τον `cxBlend` που ανακατεύει το γενετικό υλικό των γονέων $x_1$ και $x_2$ σε κάθε διάσταση $i$ με τυχαίο τρόπο και ανάλογο της παραμέτρου $\\alpha$: \n","\n","$\\gamma = (1 + 2 \\cdot \\alpha) \\cdot random() - \\alpha\\\\\n","ind1[i] = (1 - gamma) \\cdot x_1[i] + gamma \\cdot x_2[i]\\\\\n","ind2[i] = gamma \\cdot x_1[i] + (1 - gamma) \\cdot x_2[i]$"]},{"cell_type":"code","metadata":{"id":"CwAEci0KH2dv","executionInfo":{"status":"ok","timestamp":1638784880404,"user_tz":-120,"elapsed":269,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}}},"source":["def my_cx(ind1 , ind2 ):\n"," alpha = 0.5\n"," (ind1, ind2) = tools.cxBlend(ind1, ind2, alpha)\n"," return ind1 , ind2"],"execution_count":28,"outputs":[]},{"cell_type":"code","metadata":{"id":"6liQEJMlH2dz","executionInfo":{"status":"ok","timestamp":1638784882461,"user_tz":-120,"elapsed":264,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}}},"source":["toolbox2.register( \"mate\", my_cx)\n","# επιλέγουμε κέντρο της γκαουσιανής τη μέση του διαστήματος\n","toolbox2.register( \"mutate\", tools.mutGaussian, mu = 0.5 * pi/2, sigma=1.0, indpb=0.05)\n","toolbox2.register( \"select\", tools.selTournament, tournsize=3)"],"execution_count":29,"outputs":[]},{"cell_type":"code","metadata":{"id":"6Mt04t0oH2d1","executionInfo":{"status":"ok","timestamp":1638784884613,"user_tz":-120,"elapsed":273,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}}},"source":["def ea2_with_stats():\n"," import numpy\n"," \n"," pop = toolbox2.population(n=200)\n"," hof = tools.HallOfFame(1)\n"," stats = tools.Statistics(lambda ind: ind.fitness.values)\n"," stats.register(\"avg\", numpy.mean)\n"," stats.register(\"min\", numpy.min)\n"," stats.register(\"max\", numpy.max)\n"," \n"," pop, logbook = algorithms.eaSimple(pop, toolbox2, cxpb=0.5, mutpb=0.2, ngen=30, stats=stats, halloffame=hof, verbose=True)\n"," \n"," return pop, logbook, hof"],"execution_count":30,"outputs":[]},{"cell_type":"code","metadata":{"id":"hzDqP1p5H2d4","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1638784887507,"user_tz":-120,"elapsed":414,"user":{"displayName":"Giorgos Siolas","photoUrl":"https://lh3.googleusercontent.com/a-/AOh14GjxnZOAObbc3X0z9X2rs1N_1geznqhrotkq3KF-p_M=s64","userId":"10127542075805046236"}},"outputId":"a47c3c42-86cb-4b2b-b995-37a45fd00416"},"source":["if __name__ == \"__main__\":\n"," pop, log, hof = ea2_with_stats()\n"," print(\"Best individual is: %s\\nwith fitness: %s\" % (hof[0], hof[0].fitness))"],"execution_count":31,"outputs":[{"output_type":"stream","name":"stdout","text":["gen\tnevals\tavg \tmin \tmax \n","0 \t200 \t-0.471424\t-4.18976\t0.412679\n","1 \t117 \t0.0692199\t-4.82618\t8.45326 \n","2 \t133 \t-0.96259 \t-4.23783\t8.60214 \n","3 \t121 \t-2.13648 \t-4.43528\t8.54695 \n","4 \t117 \t-3.23604 \t-5.19063\t0.161203\n","5 \t128 \t-3.57673 \t-5.19063\t7.93362 \n","6 \t107 \t-3.90215 \t-5.24596\t8.99529 \n","7 \t117 \t-4.18221 \t-5.3147 \t7.96607 \n","8 \t122 \t-4.4337 \t-5.62271\t7.1949 \n","9 \t132 \t-4.51993 \t-5.85992\t8.65443 \n","10 \t141 \t-5.02182 \t-5.94006\t7.92497 \n","11 \t114 \t-5.04357 \t-5.9541 \t8.11415 \n","12 \t114 \t-5.33862 \t-5.9541 \t8.00266 \n","13 \t111 \t-5.44587 \t-5.97867\t8.01838 \n","14 \t132 \t-5.67085 \t-5.98631\t7.23879 \n","15 \t125 \t-5.58105 \t-5.98666\t8.60358 \n","16 \t120 \t-5.73294 \t-5.98772\t8.83652 \n","17 \t127 \t-5.66859 \t-5.99793\t7.78608 \n","18 \t137 \t-5.8164 \t-5.99793\t-0.58857\n","19 \t122 \t-5.79114 \t-5.99873\t8.21812 \n","20 \t118 \t-5.81459 \t-5.99873\t8.20771 \n","21 \t105 \t-5.75241 \t-5.99861\t7.28818 \n","22 \t138 \t-5.9102 \t-5.99949\t-1.49779\n","23 \t127 \t-5.82405 \t-5.99988\t7.63659 \n","24 \t101 \t-5.90939 \t-5.99988\t7.16337 \n","25 \t119 \t-5.75708 \t-5.99989\t7.25005 \n","26 \t133 \t-5.58451 \t-5.99993\t8.07979 \n","27 \t128 \t-5.8817 \t-5.99998\t7.00869 \n","28 \t123 \t-5.8021 \t-5.99998\t8.50845 \n","29 \t103 \t-5.625 \t-6 \t7.60516 \n","30 \t116 \t-5.8521 \t-6 \t7.34467 \n","Best individual is: [1.570646340062359, 1.5706993970272567, 1.5705732986051122, 1.5711243157500334, 1.5708007978476526]\n","with fitness: (-5.999997758454054,)\n"]}]}]}