«[Figlio dell'uomo] Porgi l'orecchio e ascolta le parole di KGB
e applica la tua mente alla SUA istruzione
» Pv. 22,17

Qui si straparla di vari argomenti:
1. Il genere dei pezzi è segnalato da varie immagini, vedi Legenda
2. Per contattarmi e istruzioni per i nuovi lettori (occasionali e non) qui
3. L'ultimo corto è questo
4. Molti articoli di questo blog fanno riferimento a definizioni e concetti che ho enunciato nella mia Epitome gratuitamente scaricabile QUI. Tali riferimenti sono identificati da una “E” fra parentesi quadre e uno o più capitoli. Per esempio: ([E] 5.1 e 5.4)

lunedì 10 dicembre 2018

Rete al massimo

In Aggiornamento pitonesco avevo accennato a un mio primo progettino: la teoria infatti mi piace fino a un certo punto e il metterla in pratica è la verifica di averla compresa.
Ovviamente volevo iniziare con qualcosa di facile e così mi è venuto in mente di realizzare una rete neurale per risolvere il seguente problema: identificare il massimo fra dieci numeri in ingresso.

Sembrerebbe una banalità ma in realtà non sono neppure sicuro che sia risolvibile.
Da una parte infatti nell’esempio della rete neurale sui vini portoghesi gli ingressi erano i valori di acidità, densità, percentuale di alcool, percentuali di solfati e così via. Mi pare normale che in questi casi la rete neurale, attraverso i vari esempi, “comprenda” le relazioni fra le varie misure perché queste effettivamente esistono (*1).
Invece, nel mio caso, io genero 10 numeri casuali compresi fra 0 e 1 e non c’è alcuna relazione fra un numero e gli altri: il conoscere i primi 9 numeri ad esempio non ci dice niente sul decimo. Come può quindi la rete neurale capirci qualcosa?

D’altra parte però, in un esempio del libro Deep Learning Cookbook, la rete neurale “imparava” a riconoscere le relazioni fra pellicole e marcatori (come il regista, l’anno di produzione, gli attori, etc). La cosa interessante e che pellicole e marcatori erano identificati da un singolo numero intero (da 0 fino, rispettivamente, al numero di film e marcatori) ma questo veniva “tradotto” in un vettore di 50 dimensioni tramite degli appositi strati di Keras chiamati Embedding.
In altre parole “Guerre Stellari”, che magari aveva il numero identificativo 2303, veniva tradotto in un vettore di 50 numeri. Il film “Leon”, col numero 2304, veniva convertito in un vettore di altri 50 numeri. Che relazione c’è fra identificativi e vettori? Non lo so ma non è importante visto che comunque, in partenza, non c’è alcuna relazione fra i codici identificativi!
Eppure la rete neurale era in grado di imparare a riconoscere pellicole e marcatori da questi numeri all’apparenza casuali…
Quindi perché lo stesso non potrebbe accadere col mio problema di identificazione del maggiore fra dieci numeri?

Insomma, con le mie scarse conoscenze attuali, non sono in grado di prevedere se la mia rete neurale sarà in grado di risolvere questo mio problemino. Così ho scritto il seguente codice Phyton:

1:  import numpy as np  
2:  np.random.seed(330)  
3:    
4:  def batchifier():  
5:    np.random.seed(30)  
6:    for i in range(2000):  
7:      x=np.random.random((1, 10))  
8:      x.sort(1)  
9:      y=np.zeros((1,10))  
10:    
11:      z=np.append(x,y,0)  
12:      z[1,9]=1  
13:      z=np.rot90(z,1,(0,1))  
14:      np.random.shuffle(z)  
15:      z=np.rot90(z,1,(0,1))  
16:    
17:      X_Test=z.compress([True,False],axis=0)  
18:      Y_Test=z.compress([False, True],axis=0)  
19:      yield (X_Test,Y_Test)  
20:    
21:  from keras.models import Sequential  
22:  from keras.layers import Dense, Activation  
23:    
24:  model = Sequential()  
25:  #model.add(Flatten())  
26:  model.add(Dense(20, input_shape=(10,), use_bias=True))  
27:  model.add(Activation('relu'))  
28:    
29:  model.add(Dense(10))  
30:  model.add(Activation('softmax'))  
31:    
32:  model.compile(optimizer='rmsprop',  
33:         loss='categorical_crossentropy',  
34:         metrics=['accuracy'])  
35:    
36:  model.summary()  
37:    
38:  model.fit_generator(  
39:    batchifier(),  
40:    epochs=10,  
41:    steps_per_epoch=200, #positive_samples_per_batch,  
42:    verbose=1  
43:    )  
44:    
45:  def sample():  
46:    x=np.random.random((1, 10))  
47:    return x  
48:    
49:  def somma(l):  
50:    tot=0  
51:    for x in l[0,]:  
52:      tot+=x  
53:    return tot  
54:    
55:  print("Esempi:")  
56:  for i in range(10):  
57:    s=sample()  
58:    print(s)  
59:    p=model.predict(s,batch_size=1,verbose=0)  
60:    print(p)  
61:    print(somma(p))  

Il codice è piuttosto semplice e, probabilmente, apparirà orrorifico a che conosce un po’ di Python: soprattutto il generatore batchifier() è scritto in maniera artificiosa. Chi conoscesse il Python probabilmente sarebbe in grado di riscrivere lo stesso codice in un paio di linee…
Comunque creo un vettore di 10 numeri casuali, lo ordino, poi vi appendo una secondo vettore di zeri (ottenendo una matrice di dimensione 2x10), cambio il valore dell’ultimo 0 in un 1, la giro di 90 gradi (ho trovato solo una funzione che mescola le righe e non le colonne!), la mescolo, la rigiro di 90 gradi e ne estraggo le due righe.
X_Test conterrà i 10 numeri casuali e Y_Test conterrà 9 zeri e un 1 in corrispondenza del numero più grande di X_Test…

Poi creo il modello vero e proprio della mia rete neurale usando Keras.
Non ci ho riflettuto molto: non mi interessa che sia efficiente, voglio solo qualcosa che funzioni e che risponda al mio dubbio se una rete neurale può o meno imparare a individuare il valore massimo fra 10 numeri…

In pratica creo una rete neurale a due strati. Per la precisione: 10 ingressi connessi a 20 neuroni (primo strato) e 10 neuroni di uscita (secondo strato). Il secondo strato ha come funzione di attivazione la “softmax” che dovrebbe dare la probabilità che il numero rappresentato da ogni uscita sia il massimo: ovvero 1 per il massimo e 0 per gli altri.
Onestamente gli altri parametri li ho inseriti un po’ a casaccio: in effetti adesso ho un forte dubbio che la funzione “loss” (misura l’errore fra l’uscita della rete e il valore previsto) non sia adatta… vabbè, cambiarla sarà uno degli esperimenti che dovrò fare…
Idem per l’ottimizzatore (ovvero il metodo usato per calcolare i nuovi pesi della rete a ogni iterazione)…

Per allenare la rete uso il metodo fit_generator perché pensavo fosse più adatto al mio generatore ma in realtà ho socraticamente capito solo di non aver capito come funziona! In particolare ho dubbi sugli oggetti Generator in Pyhton e sui parametri di tale metodo, soprattutto su steps_per_epoch (v. fit_generator()). Poi, a martellate, l’ho fatto funzionare…

Comunque, dopo aver corretto una miriade di problemi tecnici, alla prima esecuzione (funzionante!) ho ottenuto, con steps_per_epoch=20, la seguente uscita durante l’addestramento:
 _________________________________________________________________  
 Layer (type)         Output Shape       Param #    
 =================================================================  
 dense_47 (Dense)       (None, 20)        220      
 _________________________________________________________________  
 activation_47 (Activation)  (None, 20)        0       
 _________________________________________________________________  
 dense_48 (Dense)       (None, 10)        210      
 _________________________________________________________________  
 activation_48 (Activation)  (None, 10)        0       
 =================================================================  
 Total params: 430  
 Trainable params: 430  
 Non-trainable params: 0  
 _________________________________________________________________  
 Epoch 1/10  
 20/20 [==============================] - 1s 35ms/step - loss: 11.4159 - acc: 0.2500  
 Epoch 2/10  
 20/20 [==============================] - 0s 1ms/step - loss: 10.7166 - acc: 0.1500  
 Epoch 3/10  
 20/20 [==============================] - 0s 1ms/step - loss: 11.6816 - acc: 0.3500  
 Epoch 4/10  
 20/20 [==============================] - 0s 2ms/step - loss: 11.5668 - acc: 0.3000  
 Epoch 5/10  
 20/20 [==============================] - 0s 1ms/step - loss: 11.4079 - acc: 0.4000  
 Epoch 6/10  
 20/20 [==============================] - 0s 2ms/step - loss: 12.3548 - acc: 0.3500  
 Epoch 7/10  
 20/20 [==============================] - 0s 1ms/step - loss: 12.0055 - acc: 0.4500  
 Epoch 8/10  
 20/20 [==============================] - 0s 1ms/step - loss: 12.2185 - acc: 0.3500  
 Epoch 9/10  
 20/20 [==============================] - 0s 1ms/step - loss: 11.2393 - acc: 0.5500  
 Epoch 10/10  
 20/20 [==============================] - 0s 1ms/step - loss: 11.5122 - acc: 0.3500  
   
Come si può vedere l’accuratezza (*2) non sembra assolutamente convergere….
Poi però ho aumentato steps_per_epoch a 200 ottenendo:
 Epoch 1/10  
 200/200 [==============================] - 1s 6ms/step - loss: 11.6119 - acc: 0.3500   
 Epoch 2/10  
 200/200 [==============================] - 0s 1ms/step - loss: 11.4142 - acc: 0.5750  
 Epoch 3/10  
 200/200 [==============================] - 0s 1ms/step - loss: 11.5151 - acc: 0.7900  
 Epoch 4/10  
 200/200 [==============================] - 0s 1ms/step - loss: 11.3502 - acc: 0.8550  
 Epoch 5/10  
 200/200 [==============================] - 0s 1ms/step - loss: 11.6687 - acc: 0.9950  
 Epoch 6/10  
 200/200 [==============================] - 0s 1ms/step - loss: 11.5078 - acc: 1.0000  
 Epoch 7/10  
 200/200 [==============================] - 0s 1ms/step - loss: 11.2786 - acc: 1.0000  
 Epoch 8/10  
 200/200 [==============================] - 0s 1ms/step - loss: 11.0907 - acc: 1.0000  
 Epoch 9/10  
 200/200 [==============================] - 0s 1ms/step - loss: 11.3907 - acc: 1.0000  
 Epoch 10/10  
 200/200 [==============================] - 0s 1ms/step - loss: 11.2664 - acc: 1.0000  
   
Qui dalla 6° epoca in poi l’accuratezza risultava massima!

Allora ho provato a fare qualche verifica aggiungendo l’ultima parte di codice.
Una nuova funzione sample() per creare un vettore di 10 numeri casuali che ho confrontato, tramite dei print() col predict() basato sul modello addestrato.
Ah! ho anche creato una funzione somma() per verificare che softmax funzionasse come previsto (ovvero che la somma di tutte le percentuali corrispondesse a 1).
Così ho ottenuto:
 Esempi:  
 [[0.00165925 0.54706793 0.03279361 0.43974022 0.42073148 0.28536211 0.06292913 0.79490123 0.15313805 0.60390858]]  
 [[0.09731148 0.10597415 0.06355619 0.11758436 0.08850987 0.13482948 0.09137243 0.10004369 0.09401508 0.10680328]]  
 1.0000000149011612  
 [[0.30016505 0.50204183 0.76092403 0.62494041 0.512936  0.28072881 0.2103085 0.1174437 0.27243399 0.953455 ]]  
 [[0.14268513 0.08294746 0.07771755 0.13093427 0.10235667 0.10485999 0.09181388 0.07278178 0.09750422 0.09639902]]  
 0.9999999701976776  
 [[0.21122699 0.28753969 0.89949822 0.35109501 0.29346201 0.95339557 0.49435746 0.99354878 0.01448176 0.84808755]]  
 [[0.08441722 0.07368112 0.06456804 0.08632565 0.1038466 0.19162604 0.07344794 0.14045519 0.09820914 0.08342304]]  
 0.9999999850988388  
 [[0.17723921 0.04884328 0.29435293 0.78523847 0.90869309 0.61865404 0.21038514 0.23809492 0.44592849 0.90516956]]  
 [[0.10775398 0.07558212 0.04765695 0.12270646 0.12226721 0.13314444 0.09520464 0.09438299 0.07767287 0.1236283 ]]  
 0.9999999478459358  
 [[0.89644952 0.0573787 0.64280447 0.56323963 0.01383937 0.20340611 0.63202377 0.01243697 0.33693535 0.92840944]]  
 [[0.20072532 0.06654133 0.06988712 0.11413237 0.08207329 0.10415517 0.08122559 0.061967  0.09125605 0.12803675]]  
 0.9999999813735485  
 [[0.16227171 0.37088684 0.51723029 0.5156923 0.47168066 0.72516925 0.02657806 0.30200434 0.33494513 0.15693052]]  
 [[0.09709164 0.08828155 0.07931186 0.10216423 0.1105952 0.13434379 0.09474652 0.10160992 0.1036953 0.08815996]]  
 0.999999962747097  
 [[0.38116618 0.18740264 0.86084261 0.67028089 0.36506087 0.36466077 0.6184897 0.19350823 0.31067974 0.0678281 ]]  
 [[0.10927812 0.07878433 0.10044078 0.11539444 0.10105398 0.11936115 0.11325683 0.08424763 0.10736588 0.07081694]]  
 1.0000000670552254  
 [[0.08857103 0.37228275 0.71509346 0.24248225 0.90751504 0.12114918 0.68641394 0.15519364 0.90995662 0.7746463 ]]  
 [[0.13223796 0.07232565 0.06962329 0.09257585 0.08711651 0.12275507 0.10142472 0.07036604 0.14854519 0.10302974]]  
 1.0000000149011612  
 [[0.48634901 0.50588741 0.31723363 0.6749636 0.32224859 0.6793131 0.18933995 0.25533336 0.82270563 0.1612204 ]]  
 [[0.10887279 0.08330661 0.05775265 0.10981435 0.08519744 0.13596174 0.10652026 0.085779  0.12599134 0.10080389]]  
 1.0000000670552254  
 [[0.37234676 0.25302492 0.08980321 0.57312637 0.9967274 0.41463969 0.28328975 0.84090096 0.99757804 0.26696341]]  
 [[0.11740944 0.07081304 0.04454628 0.10406217 0.08472245 0.12506601 0.10900114 0.10168419 0.12784785 0.11484744]]  
 1.0000000223517418  
   
Beh, almeno softmax funziona! Ma i valori previsti sembrano casuali rispetto agli ingressi forniti (avrei dovuto ottenere valori molto prossimi a 0 tranne che un 1 in corrispondenza del numero più grande). Evidentemente ho ottenuto dell’overfitting anche se non mi è chiaro su cosa: evidentemente la rete non si è addestrata su tutti i 2000 campioni prodotti da batchfier() ma su un suo sottoinsieme anche se non mi è chiaro quale…

Conclusione: adesso che ho una base funzionante devo fare altri esperimenti e: 1. capire come funziona fit_generator(); 2. provare ad aggiungere qualche strato intermedio; 3. cambiare la funzione di loss e l’ottimizzatore; 4. sicuramente mi verranno molte altre idee lavorandoci...

Nota (*1): fra le varie esperienze proposte dal minicorso c’era anche la visualizzazione di della correlazione fra le diverse misure…
Nota (*2): la loss non so invece come interpretarla, probabilmente finito di scrivere questo pezzo, la cambierò con una mse (l’errore quadratico medio)...

Nessun commento:

Posta un commento