Dunque, prima di tutto un’introduzione a un mio nuovo libro (v. il corto Apprendimento automatico (ironico)): ero infatti curioso di valutare la qualità del materiale didattico comprato.
Ho quindi aperto, abbastanza a casaccio, il testo che mi sembrava di carattere più generico e introduttivo: “Clean Code in Python” di Mariano Anaya, Ed. Packt, 2018.
Come si può intuire dal titolo, il libro si propone di insegnare al lettore un buono stile di scrittura del codice in Python. In verità l’argomento mi interessava poco: un buono stile di scrittura del codice è infatti principalmente utile nei gruppi di lavoro, quando cioè due o più programmatori devono lavorare sullo stesso codice; e, a mio avviso in misura molto minore, all’autore stesso se dovrà ritornare a distanza di anni a lavorare su quanto già scritto.
Entrambi questi casi non mi coinvolgono direttamente: io lavoro da solo e dubito che rimetterò mai le mani su del codice scritto anni prima (*1). Però nella sua introduzione l’autore spiegava che il testo è un’utile introduzione anche al principiante Python perché analizza i vari costrutti di tale linguaggio: per questo motivo sono andato avanti a leggere.
Ora non voglio soffermarmi sui dettagli troppo tecnici ma su un concetto generale che ho trovato genuinamente interessante.
Siamo soliti pensare a un linguaggio di programmazione come al mezzo con cui il programmatore impartisce le istruzioni al calcolatore: ciò è certamente vero ma è anche utile pensarlo come al mezzo con cui i programmatori si scambiano fra loro le idee. È in questo secondo caso che la chiarezza e pulizia del codice è fondamentale: ovvero quando si deve leggere il codice altrui (e, vice versa, quando il nostro codice dovrà essere letto da altri).
Ciò è profondamente vero: mi è infatti tornato in mente la fatica fatta per comprendere il codice del libro “Deep Learning Cookbook”. Tale codice indiscutibilmente funziona e, probabilmente, è anche molto efficiente, ma dal punto di vista del lettore, magari digiuno di Python come me, sembra che sia stato direttamente vomitato sulla carta… Ecco: chi scrive codice didattico dovrebbe anche scrivere del codice chiaro e pulito e non ottimizzare tutto al massimo per risparmiare un paio di righe…
Ma torniamo alle reti neurali: dopo aver scritto il pezzo Rete al massimo ho proceduto esattamente come mi ero ripromesso di fare nella conclusione.
Per prima cosa ho cambiato la funzione di “loss” usando un classico “errore quadratico medio” (ovvero facendo la media delle differenze tra i valori previsti e quelli ottenuti elevati al quadrato) e mi sono subito venuti dei risultati migliori. Poi ho aggiunto un ulteriore strato di 20 nodi e ho ampliato notevolmente il campione.
I risultati sono stati subito molto migliori anche se tutt’altro che eccezionali…
La rete, più che aver imparato a riconoscere il massimo, sembra infatti essere addestrata a riconoscere i numeri grandi!
Come spiegato nel precedente pezzo fornisco alla rete 10 numeri casuali compresi fra 0 e 1 ed essa dovrebbe restituirmi un vettore di 10 numeri composto da zeri tranne che per un uno in corrispondenza del numero più grande. Poi, siccome uso la funzione di attivazione “softmax”, ottengo, invece che 0 e 1, delle percentuali di probabilità tali che la loro somma sia 1: queste percentuali indicano la probabilità che il relativo numero sia il più grande.
Ebbene ai numeri superiori a 0,9 viene assegnata una probabilità di circa del 30% di essere il maggiore e via via più minore per i numeri più piccoli.
Un problema si ha quando manga un numero “grande” (diciamo maggiore di 0.8) perché in tal caso le percentuali degli altri numeri sono tutte molto simili fra loro: in altre parole la rete è molto incerta su quale sia il massimo.
In genere comunque il numero indicato con la probabilità maggiore è effettivamente anche il più grande ma non è sempre così…
In particolare ho notato che la rete è fortemente condizionata anche dalla posizione dei numeri (forniti in gruppi di 10) nella sua valutazione di quale sia il maggiore. Ad esempio il decimo numero è fortemente favorito come maggiore mentre il sesto è di gran lunga sfavorito!
Probabilmente questo dipende dal fatto che nell’insieme di apprendimento che fornisco una percentuale superiore alla media di massimi si trova in decima posizione e, contemporaneamente, un numero di massimi MOLTO al di sotto della media si trova in sesta posizione.
Questa disomogeneità nell’insieme di apprendimento finisce per confondere la rete che lega il massimo non solo al valore del numero ma anche alla sua posizione nell’insieme fornito!
I testi che avevo letto mi avevano avvisato su questo pericolo, ovvero che la rete, a causa di problemi nell’insieme di apprendimento, finisca per imparare relazioni sbagliate o inopportune.
Al riguardo ho letto un divertente aneddoto (probabilmente apocrifo!): l’esercito americano aveva progettato una rete neurale che avrebbe dovuto essere in grado di riconoscere da delle foto satellitari la presenza o meno di carrarmati mimetizzati. Dopo un lungo addestramento la rete sembrava essere perfettamente addestrata riuscendo a riconoscere con precisione la presenza o meno di carrarmati nelle foto fornitegli.
Quando però si provò a presentare alla rete delle foto reali (cioè non quelle su cui si era addestrata) iniziò a restituire delle risposte che sembravano casuali.
Alla fine ci si rese conto che nell’insieme di addestramento le foto di carrarmati mimetizzati erano state tutte prese in una giornata di bel tempo mentre quelle senza erano state scattate in una giornata nuvolosa: in definitiva la rete neurale invece di imparare a individuare i carrarmati mimetizzati aveva invece appreso a distinguere le giornate soleggiate da quelle piovigginose!
Nel mio caso probabilmente dovrei assicurarmi che i numeri massimi delle sequenze nell’insieme di addestramento siano equamente suddivisi in tutte le posizioni in maniera che la rete neurale non si confonda e non pensi che la posizione sia rilevante nell’individuazione del numero massimo.
Oppure potrei aumentare ulteriormente il campione di apprendimento e affidarmi così alla statistica (perché all’aumentare del numero di esempi il massimo dovrebbe finire per trovarsi con la stessa frequenza in tutte le posizioni).
Ma poi non ho fatti ulteriori esperimenti: ho infatti stabilito che sarebbe stato più istruttivo proseguire la lettura del testo “Deep Learning Cookbook” piuttosto che brancolare nel buio a casaccio con questo esperimento. Il mio scopo era infatti quello di scrivere la mia prima semplice rete neurale con Keras e ci sono riuscito: tornerò a cercare di migliorarla una volta che ne saprò di più…
Conclusione: in definitiva ancora non sono sicuro che una rete neurale possa essere addestrata a riconoscere il massimo fra dieci numeri ma, dopo il mio esperimento, credo di sì: ovviamente con qualche parametro diverso da quelli da me utilizzati!
Nota (*1): e comunque ho un’ottima memoria e dubito di aver problemi a comprendere ciò che ho scritto anche ad anni di distanza.
Appendice: di seguito una prova con l’ultima versione della rete neurale. Notare la prova alle righe 8 e 9 dove uno 0,848 nella decima colonna è ritenuto il massimo al 32% mentre allo 0,953 nella sesta viene dato solo il 9% (per non parlare dello 0,99 nell’ottava colonna!)...
1: Esempi:
2: [[0.00165925 0.54706793 0.03279361 0.43974022 0.42073148 0.28536211 0.06292913 0.79490123 0.15313805 0.60390858]]
3: [[0.06634665 0.08524792 0.02196588 0.13344143 0.12921186 0.08289602 0.03911982 0.16084981 0.01368215 0.26723832]]
4: 0.9999998668208718
5: [[0.30016505 0.50204183 0.76092403 0.62494041 0.512936 0.28072881 0.2103085 0.1174437 0.27243399 0.953455 ]]
6: [[0.12744382 0.03701866 0.05576641 0.15273342 0.11099766 0.07222849 0.03605105 0.06226025 0.00951784 0.33598232]]
7: 0.9999999208375812
8: [[0.21122699 0.28753969 0.89949822 0.35109501 0.29346201 0.95339557 0.49435746 0.99354878 0.01448176 0.84808755]]
9: [[0.0552251 0.07015153 0.04846524 0.1878824 0.04959717 0.09475246 0.03112416 0.13085796 0.00808687 0.32385713]]
10: 1.000000019557774
11: [[0.17723921 0.04884328 0.29435293 0.78523847 0.90869309 0.61865404 0.21038514 0.23809492 0.44592849 0.90516956]]
12: [[0.05292428 0.00408194 0.00183221 0.13088779 0.31016088 0.13257322 0.01583324 0.01574848 0.0023678 0.33359018]]
13: 1.000000024563633
14: [[0.89644952 0.0573787 0.64280447 0.56323963 0.01383937 0.20340611 0.63202377 0.01243697 0.33693535 0.92840944]]
15: [[0.30874947 0.01008442 0.05147857 0.06778931 0.07430119 0.02815371 0.06382155 0.02012419 0.01094544 0.36455214]]
16: 0.9999999841675162
17: [[0.16227171 0.37088684 0.51723029 0.5156923 0.47168066 0.72516925 0.02657806 0.30200434 0.33494513 0.15693052]]
18: [[0.07038948 0.12649035 0.03329026 0.16509466 0.13994765 0.2876191 0.02830587 0.0551418 0.01702605 0.07669474]]
19: 0.9999999795109034
20: [[0.38116618 0.18740264 0.86084261 0.67028089 0.36506087 0.36466077 0.6184897 0.19350823 0.31067974 0.0678281 ]]
21: [[0.08824648 0.05612331 0.18506493 0.2182973 0.06269047 0.06360283 0.10284939 0.04656871 0.03698812 0.1395685 ]]
22: 1.0000000521540642
23: [[0.08857103 0.37228275 0.71509346 0.24248225 0.90751504 0.12114918 0.68641394 0.15519364 0.90995662 0.7746463 ]]
24: [[0.21324421 0.04413499 0.03430352 0.06032049 0.20793445 0.03889873 0.12893404 0.03715232 0.05486929 0.1802079 ]]
25: 0.9999999441206455
26: [[0.48634901 0.50588741 0.31723363 0.6749636 0.32224859 0.6793131 0.18933995 0.25533336 0.82270563 0.1612204 ]]
27: [[0.10819541 0.12408981 0.02957215 0.11325242 0.17237654 0.23982307 0.05820783 0.04464518 0.03664029 0.0731973 ]]
28: 0.9999999981373549
29: [[0.37234676 0.25302492 0.08980321 0.57312637 0.9967274 0.41463969 0.28328975 0.84090096 0.99757804 0.26696341]]
30: [[0.13647267 0.04835411 0.00532183 0.04756918 0.41306108 0.09800961 0.0399879 0.06066856 0.02234012 0.12821499]]
31: 1.0000000447034836
Nessun commento:
Posta un commento