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)...