Vogliamo realizzare un semplice programma in Python che sfrutti il riconoscimento delle immagini mediante TensorFlow e machine learning.
Il programma consentirà all’utente di pescare il risultato di una partita a Tris, da una cartella di immagini come la seguente:
Selezionando una partita il programma ci dirà chi ritiene che sia il vincitore e mostrerà lo schema della partita che ha dedotto a partire dall’immagine che gli abbiamo passato, nella maniera seguente:
1. Configurazione iniziale e librerie utilizzate
Per affrontare l’esercizio in questione abbiamo bisogno di alcune librerie. Il progetto verrà sviluppato in Python 3.7.
Possiamo installare le opportune librerie utilizzando PIP, nel caso specifico avremo bisogno delle seguenti installazioni:
1 2 3 4 |
pip install Pillow pip install numpy pip install tensorflow pip install matplotlib |
Creiamo un file elaboratore.py nel quale svilupperemo l’intero programma. All’inizio inseriamo le seguenti istruzioni di importazione:
1 2 3 4 5 6 7 8 9 |
from __future__ import absolute_import, division, print_function, unicode_literals from PIL import Image import numpy as np import os import tensorflow as tf from tensorflow import keras import matplotlib.pyplot as plt |
Faccio notare che la prima istruzione from __future__ import absolute_import, division, print_function, unicode_literals
deve trovarsi all’inizio e non è una vera e propria importazione. Si tratta di una configurazione sul funzionamento di Python, che importa “funzionalità future” dal modulo fittizio __future__
. E’ un modo per rendere disponibili, nella versione attuale, funzionalità previste in versioni successive, ma che ancora non sono state ufficialmente implementate.
Nel nostro caso specifico questa importazione non è essenziale (stiamo già lavorando in Python 3.7 e la funzione print
non è più uno statement, bensì una funzione appunto, ecc), ma la possiamo lasciare per rendere compatibile il programma anche con versioni precedenti.
Detto questo procediamo allo sviluppo del necessario.
2. Dati iniziali
Per sviluppare il gioco utilizzeremo i seguenti set di dati (qui l’allegato da cui scaricarli):
- Set delle immagini per l’addestramento del modello, ricaveremo le immagini da un’unica grande immagine originale chiamata training.jpg (questa è un’immagine contenente 144 disegni, metà sono O e l’altra metà sono X, disegnate a mano sul computer, ciascun elemento è grande 100×100 px, per un totale di 1200×1200 px, ovvero 12 righe x 12 colonne)
- Piccolo set di immagini di test nel file test.jpg (solita dimensione 100×100 px per ciascuna immagine, per un totale di 4×4 = 16 elementi)
- Archivio con 10 partite giocate a Tris di cui conosciamo il risultato e vogliamo farlo riconoscere al computer
3. Struttura del programma
Vogliamo creare 3 classi che si occuperanno di diversi aspetti del programma:
- ElaboraImmagini – con questa classe elaboreremo le immagini di test, di addestramento e delle singole partite. In tutti e tre i casi si tratta di suddividere un’immagine originale in immagini più piccole; nei primi due casi, quello di test e addestramento, salveremo le immagini in una cartella da cui leggerle successivamente, mentre nel terzo caso, quello delle immagini che compongono una partita, salveremo il risultato in un vettore
- CreaDBImmagini – con questa classe vogliamo elaborare le immagini di test e di addestramento salvate nelle rispettive cartelle, ottenendo dei vettori contenenti le immagini medesime e un elenco di output (o etichette) associati a ciascuna immagine
- GiocoTris – la vera e propria classe che si occuperà dello sviluppo del gioco, preparando il modello di apprendimento, salvandolo e valutando il punteggio su ciascuna partita che verrà passata dal giocatore
Infine metteremo tutto in un semplice ciclo while
che permetterà all’utente di scegliere un’immagine dall’elenco di quelle disponibili e farla analizzare.
4. Elaboriamo le immagini di addestramento e test
Creiamo una classe nella maniera seguente (nel codice ho inserito il commento sui singoli passaggi):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
class ElaboraImmagini: # al costruttore della classe passiamo i seguenti argomenti: # orig: file dell'immagine originale da elaborare, per esempio training.jpg oppure gioco_0.jpg # dest: cartella di destinazione nella quale salvare le immagini ritagliate dall'immagine originale # n: dimensione del lato del quadrato dell'immagine, 12 per training.jpg, oppure 3 per l'immagine di gioco_0.jpg, praticamente il numero di colonne della griglia # w: dimensione di ciascuna cella della griglia in px, ogni immagine estratta avra' tale dimensione, per esempio 100 per immagini di 100x100px # returnList: se vogliamo che il risultato, ovvero le immagini ritagliate, anziche' essere salvato in una cartella, venga restituito in una lista def __init__(s, orig, dest = None, n = 12, w = 100, returnList = False): # impostiamo alcune proprieta' private della classe s.__return = returnList s.__lista = [] # questo sara' l'eventuale elenco delle immagini # preleviamo il percorso assoluto sul quale si trova l'immagine di origine s.__orig = os.path.abspath(orig) # se non vogliamo una lista, creiamo il percorso di salvataggio delle immagini #a tale scopo prendiamo la cartella dell'immagine sorgente e ad essa accodiamo la cartella di destinazione if not s.__return: s.__dest = os.path.dirname(s.__orig) + "\\" + dest # se non vogliamo una lista if not s.__return: # allora dovremo salvare le immagini in una cartella # se la cartella di destinazione non esiste if not os.path.exists(s.__dest): # allora la creiamo os.makedirs(s.__dest) # salviamo i valori in prorieta' private s.__n = n s.__w = w # elaboriamo le immagini s.elabora() # per elaborare le immagini dobbiamo ritagliarle da quella originale # creiamo dunque un metodo per ritagliare le immagini, passando per argomento le coordinate (coor) di ritaglio # per coordinate si intendono xi, yi e xf, yf di ciascun quadrato di ritaglio (vedere l'articolo per maggiori informazioni) def ritaglia(s, coor, i): # leggiamo l'immagine con PIL img = Image.open(s.__orig) # ritagliamo l'immagine secondo le coordinate passate cropped = img.crop(coor) # se non vogliamo una lista, allora salviamo l'immagine nella destinazione prescelta if not s.__return: cropped.save(s.__dest + "\\img_" + str(i) + ".jpg") # altrimenti restituiamo l'immagine come array di NumPy else: return np.asarray(cropped) # con questo metodo elaboriamo l'immagine, chiamando ritaglia() per ciascuna cella della griglia def elabora(s): # per ciascuna riga for i in range(s.__n): #per ciascuna colonna for j in range(s.__n): # esegui ritaglia passando le coordinate calcolate e il numero progressivo dell'immagine img = s.ritaglia( ( j*s.__w , i*s.__w , (j+1) *s.__w , (i+1) * s.__w ) , i * s.__n + j ) # aggiungi l'immagine alla lista delle immagini che si potranno eventualmente restituire s.__lista.append(img) # preleviamo la lista delle immagini elaborate def get(s): # convertiamo la lista in un array di NumPy return np.array(s.__lista) |
Il metodo ritaglia(s, coor, i)
accetta coordinate in (x,y) sull’immagine, le x rappresentano le “colonne”, mentre le y rappresentano le “righe”, nella maniera seguente:
La prima immagine verrà ritagliata con coordinate (0, 0, 100, 100), la seconda con (100, 0, 200, 100) ecc.
Possiamo subito elaborare le immagini di addestramento e di test aggiungendo le seguenti due righe di codice:
1 2 |
ElaboraImmagini("training.jpg","TRAINING",12,100) ElaboraImmagini("test.jpg","TEST",4,100) |
5. Preleviamo le immagini di addestramento e test, associandoci gli opportuni output
Adesso vogliamo creare la classe che ci consentirà di prelevare le immagini, inserendole in un vettore, ed associare a ciascuna immagine nel vettore l’opportuno output.
Ogni immagine può rappresentare o una O oppure un X, a queste due “etichette” vogliamo associare un valore numerico, che nel mio caso sarà 0 per la O e 1 per la X.
Il comportamento che ci aspettiamo sarà il seguente: train_img, train_desc = CreaDBImmagini(...).get()
train_img
conterrà i valori RGB per ciascuna immagine, in una lista di immagini così codificate. Ciascuna immagine è una griglia di pixel, per esempio un’immagine di 3×3 px diventerebbe una matrice di questo tipo [[1,2,3],[4,5,6],[7,8,9]]
. I pixel in realtà non sono numerati in questo modo, ma ciascun pixel ha un valore RGB da 0 a 255, per ciascun colore. Quindi se il primo pixel fosse completamente nero al posto dell’1 avremmo [0,0,0]
, una lista con 3 zeri. Se fosse completamente bianco avremmo [255,255,255]
, se fosse rosso avremmo [255,0,0]
ecc. Questo significa che l’immagine del nostro esempio avrebbe una rappresentazione tridimensionale 3x3x3 diventando così per esempio: [[[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[255,255,255],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0]]]
. In questo caso il pixel centrale, quello che abbiamo numerato come 5, sarebbe bianco e gli altri tutti neri. Se avessimo 100 di queste immagini, avremmo un oggetto di dimensione 100x3x3x3. Ovvero una lista di 100 elementi, di cui ciascun elemento è questa lista, di liste di liste, rappresentante la singola immagine.
train_desc
è invece una semplice lista monodimensionale, se avessimo 100 immagini questa sarebbe una lista di 100 elementi. Ogni elemento descrive l’immagine, nel nostro caso se l’immagine corrispondente in train_img
fosse una O allora avremo uno 0, nel caso di una X avremo un 1. Sarà quindi una lista tipo [0,1,1,0,0,....]
. In questo esempio vorrebbe dire che le immagini in train_img
rappresentano in ordine: O, X, X, O, O, ecc.
Tutti gli altri commenti sono nel codice:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
class CreaDBImmagini: # creiamo delle costanti a livello di classe, che ci permettano di definire l'input tipo nel costruttore # e quindi il tipo di immagini che vogliamo tirare fuori, questo e' importante solo ai fini della creazione # delle etichette TEST = 0 TRAINING = 1 # il costruttore accetta 2 argomenti: # path: il percorso da cui leggere le immagini # tipo: il tipo di etichette da creare in associazione def __init__(s, path, tipo = 0): s.__path = path s.__immagini = [] s.__descrizioni = [] s.__tipo = tipo s.elabora() # elaboriamo le immagini def elabora(s): # per ogni elemento nella cartella for f in os.listdir(s.__path): # se si tratta di un file if os.path.isfile(s.__path + "\\" + f): # leggiamo il file dell'immagine aprendolo con PIL img = Image.open(s.__path + "\\" + f) # trasformiamo i dati dell'immagine in un array contenente i valori RGB dell'immagine stessa bimg = np.asarray(img) # aggiungiamo l'immagine all'elenco delle immagini s.__immagini.append(bimg) # otteniamo il numero dell'immagine, questo lo abbiamo per costruzione dalla precedente classe ElaboraImmagini numero = int(f.replace("img_","").replace(".jpg","")) # se l'immagine e' del gruppo di test if s.__tipo == s.TEST: # siccome le immagini di test sono alternate, tipo O, X, O, X ecc, vuol dire che posso distinguerle # come pari e dispari, quindi i pari saranno 0 e i dispari 1, rispettivamente O e X s.__descrizioni.append( int(numero % 2) ) # se l'immagine e' del gruppo di addestramento if s.__tipo == s.TRAINING: # siccome le immagini di addestramento sono divise a meta', la prima meta' sono O e la seconda sono X # allora sara' sufficiente dividere per la meta' del totale e arrotondare per difetto s.__descrizioni.append( int(np.floor(numero / 72)) ) # restituisci i due vettori come array di NumPy def get(s): return (np.array(s.__immagini) , np.array(s.__descrizioni)) |
6. Sviluppiamo la classe di gioco
A questo punto sviluppiamo la classe di gioco integrando le precedenti classi.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
class GiocoTris: # prendiamo come argomenti: # orig: cartella dove sono contenuti tutti i giochi # train: cartella dove si trovano le immagini di addestramento # test: cartella dove si trovano le immagini di test # nome_modello: nome della cartella nella quale verra' salvato il modello calcolato def __init__(s, orig = "GIOCHI", train = "TRAINING", test = "TEST", nome_modello = "gioco_del_tris"): s.__orig = orig s.__train = train s.__test = test s.__nome_modello = nome_modello s.__modello = None # leggiamo l'elenco dei file nella cartella orig, solo se sono dei file, e teniamoli come partite possibili s.__partite = [f for f in os.listdir(orig) if os.path.isfile(orig + "\\" + f)] # se il modello salvato esiste (ovvero se esiste la cartella) if os.path.exists(s.__nome_modello): # caricare il modello salvato s.__modello = keras.models.load_model(s.__nome_modello) #restituisce la lista dei file delle partite def getGiochi(s): return s.__partite # prepara il modello def prepara(s): # prepara il modello se il modello non esiste, ovvero non e' stato caricato if s.__modello is None: # preleva le immagini di addestramento e le relative descrizioni, ossia gli output noti train_img, train_desc = CreaDBImmagini(s.__train,CreaDBImmagini.TRAINING).get() # prepara il modello s.__modello = keras.Sequential([ keras.layers.Flatten(input_shape=(100, 100, 3)), keras.layers.Dense(128, activation='relu'), keras.layers.Dense(2, activation='softmax') ]) # compila il modello s.__modello.compile(optimizer="adam", loss='sparse_categorical_crossentropy', metrics=['accuracy']) # allena il modello con le immagini scelte in precedenza s.__modello.fit(train_img, train_desc, epochs=20) # salva il modello s.__modello.save(s.__nome_modello) # valuta accuratezza del modello con immagini di test def getAccuracy(s): # preleva immagini e output di test test_img, test_desc = CreaDBImmagini(s.__test,CreaDBImmagini.TEST).get() # calcola le perdite e l'accuratezza sulle immagini di test s.__test_loss, s.__test_acc = s.__modello.evaluate(test_img, test_desc, verbose=0) return s.__test_acc # valuta uno specifico gioco, scelto come posizione tra le partite possibili def getImmagine(s, pos): # elabora l'immagine del gioco compiuto gioco = ElaboraImmagini(orig = s.__orig + "\\" + s.__partite[pos], n = 3, w = 100, returnList = True).get() # predici i risultati in termini percentuali previsione = s.__modello.predict(gioco) # prepara l'output reinterpretato s.__risultato = [] # per ciascun valore delle previsioni for p in previsione: # se i valori sono vicini per un parametro arbitrario di confidenza, allora la casella e' vuota # per esempio se i valori sono 51% e 49% if abs(p[0]-p[1]) < 0.1: v = 0 # se il primo valore supera il secondo allora abbiamo uno O elif p[0] > p[1]: v = 1 # altrimenti abbiamo una X else: v = 2 # aggiungi il valore al risultato s.__risultato.append(v) # ottieni il vincitore vincitore = s.getVincitore( s.__risultato ) # restituisci il vincitore return vincitore # calcola il vincitore def getVincitore(s,vect): for i in range(0,9,3): if vect[i] == vect[i+1] == vect[i+2] and vect[i] != 0: return vect[i] for i in range(3): if vect[i] == vect[i+3] == vect[i+6] and vect[i] != 0: return vect[i] if (vect[0] == vect[4] == vect[8] and vect[0] != 0) or (vect[2] == vect[4] == vect[6] and vect[2] != 0): return vect[4] # mostra la griglia di gioco def stampaGioco(s): symb = ["-","O","X"] for k, r in enumerate(s.__risultato): if k > 0 and not k % 3: print() print(symb[r],end=" ") print() |
Anche su questo facciamo un paio di appunti. Quando calcoliamo le previsioni con previsione = s.__modello.predict(gioco)
otteniamo una lista di liste di probabilità. Ogni sub-lista contiene tante probabilità quanti sono i neuroni (o nodi) impostati nell’ultimo layer del modello. Nel nostro caso all’istruzione keras.layers.Dense(2, activation='softmax')
abbiamo impostato 2 neuroni, quindi il vettore di previsione conterrà un elenco di liste del tipo [p1, p2], dove p1 e p2 sono le probabilità dell’output 0 e dell’output 1 (che ricordiamoci corrispondono a O e a X, rispettivamente). Il risultato di previsione sarà qualcosa del tipo [[1. 0.],[0.51 0.49], [0. 1.]]
. Nel caso specifico vorrebbe dire che secondo il ML la prima immagina è una O, poi uno spazio vuoto, poi una X. Per quanto riguarda l’uso di softmax e relu fare riferimento a questa pagina.
Per i dettagli sull’optimizer Adam fare riferimento a questa pagina.
Il calcolo della griglia di vittoria è molto semplice, perché abbiamo un vettore lineare e non una matrice (o griglia vera e propria). In questo caso una partita come quella del gioco_1.jpg sarà rappresentata da una lista di 0, 1 e 2, in questo modo (dove 0 = vuoto, 1 = O, 2 = X): [2, 2, 1, 2, 1, 0, 2, 1, 1]
Quando il primo valore è diverso da 0, quindi da vuoto, basta controllare che a distanza di 3 siano uguali, come qui: [2, 2, 1, 2, 1, 0, 2, 1, 1]
In questo modo stiamo controllando le colonne.
Per controllare le righe basta verificare le triplette, così: [2, 2, 1, 2, 1, 0, 2, 1, 1]
7. Chiudiamo il programma
Infine costruiamo il ciclo while per poter far usare il programma:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
gt = GiocoTris(orig = "GIOCHI", train = "TRAINING", test = "TEST", nome_modello = "gioco_del_tris") gt.prepara() while True: print("-- gioco del tris --") print('Test accuracy:', gt.getAccuracy()) print("scegli la partita (-1 esci):") for k, n in enumerate(gt.getGiochi()): print("{0}. gioco {0}".format(k)) print("> ",end="") partita = int(input()) if partita == -1: break if partita >= 0 and partita <= 9: vincitore = gt.getImmagine( partita ) if vincitore == 1: print("Ha vinto O") elif vincitore == 2: print("Ha vinto X") else: print("Nessun vincitore") gt.stampaGioco() |
Per completezza riporto di seguito l’intero codice di programmazione:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
from __future__ import absolute_import, division, print_function, unicode_literals from PIL import Image import numpy as np import os import tensorflow as tf from tensorflow import keras import matplotlib.pyplot as plt class ElaboraImmagini: # al costruttore della classe passiamo i seguenti argomenti: # orig: file dell'immagine originale da elaborare, per esempio training.jpg oppure gioco_0.jpg # dest: cartella di destinazione nella quale salvare le immagini ritagliate dall'immagine originale # n: dimensione del lato del quadrato dell'immagine, 12 per training.jpg, oppure 3 per l'immagine di gioco_0.jpg, praticamente il numero di colonne della griglia # w: dimensione di ciascuna cella della griglia in px, ogni immagine estratta avra' tale dimensione, per esempio 100 per immagini di 100x100px # returnList: se vogliamo che il risultato, ovvero le immagini ritagliate, anziche' essere salvato in una cartella, venga restituito in una lista def __init__(s, orig, dest = None, n = 12, w = 100, returnList = False): # impostiamo alcune proprieta' private della classe s.__return = returnList s.__lista = [] # questo sara' l'eventuale elenco delle immagini # preleviamo il percorso assoluto sul quale si trova l'immagine di origine s.__orig = os.path.abspath(orig) # se non vogliamo una lista, creiamo il percorso di salvataggio delle immagini #a tale scopo prendiamo la cartella dell'immagine sorgente e ad essa accodiamo la cartella di destinazione if not s.__return: s.__dest = os.path.dirname(s.__orig) + "\\" + dest # se non vogliamo una lista if not s.__return: # allora dovremo salvare le immagini in una cartella # se la cartella di destinazione non esiste if not os.path.exists(s.__dest): # allora la creiamo os.makedirs(s.__dest) # salviamo i valori in prorieta' private s.__n = n s.__w = w # elaboriamo le immagini s.elabora() # per elaborare le immagini dobbiamo ritagliarle da quella originale # creiamo dunque un metodo per ritagliare le immagini, passando per argomento le coordinate (coor) di ritaglio # per coordinate si intendono xi, yi e xf, yf di ciascun quadrato di ritaglio (vedere l'articolo per maggiori informazioni) def ritaglia(s, coor, i): # leggiamo l'immagine con PIL img = Image.open(s.__orig) # ritagliamo l'immagine secondo le coordinate passate cropped = img.crop(coor) # se non vogliamo una lista, allora salviamo l'immagine nella destinazione prescelta if not s.__return: cropped.save(s.__dest + "\\img_" + str(i) + ".jpg") # altrimenti restituiamo l'immagine come array di NumPy else: return np.asarray(cropped) # con questo metodo elaboriamo l'immagine, chiamando ritaglia() per ciascuna cella della griglia def elabora(s): # per ciascuna riga for i in range(s.__n): #per ciascuna colonna for j in range(s.__n): # esegui ritaglia passando le coordinate calcolate e il numero progressivo dell'immagine img = s.ritaglia( ( j*s.__w , i*s.__w , (j+1) *s.__w , (i+1) * s.__w ) , i * s.__n + j ) # aggiungi l'immagine alla lista delle immagini che si potranno eventualmente restituire s.__lista.append(img) # preleviamo la lista delle immagini elaborate def get(s): # convertiamo la lista in un array di NumPy return np.array(s.__lista) class CreaDBImmagini: # creiamo delle costanti a livello di classe, che ci permettano di definire l'input tipo nel costruttore # e quindi il tipo di immagini che vogliamo tirare fuori, questo e' importante solo ai fini della creazione # delle etichette TEST = 0 TRAINING = 1 # il costruttore accetta 2 argomenti: # path: il percorso da cui leggere le immagini # tipo: il tipo di etichette da creare in associazione def __init__(s, path, tipo = 0): s.__path = path s.__immagini = [] s.__descrizioni = [] s.__tipo = tipo s.elabora() # elaboriamo le immagini def elabora(s): # per ogni elemento nella cartella for f in os.listdir(s.__path): # se si tratta di un file if os.path.isfile(s.__path + "\\" + f): # leggiamo il file dell'immagine aprendolo con PIL img = Image.open(s.__path + "\\" + f) # trasformiamo i dati dell'immagine in un array contenente i valori RGB dell'immagine stessa bimg = np.asarray(img) # aggiungiamo l'immagine all'elenco delle immagini s.__immagini.append(bimg) # otteniamo il numero dell'immagine, questo lo abbiamo per costruzione dalla precedente classe ElaboraImmagini numero = int(f.replace("img_","").replace(".jpg","")) # se l'immagine e' del gruppo di test if s.__tipo == s.TEST: # siccome le immagini di test sono alternate, tipo O, X, O, X ecc, vuol dire che posso distinguerle # come pari e dispari, quindi i pari saranno 0 e i dispari 1, rispettivamente O e X s.__descrizioni.append( int(numero % 2) ) # se l'immagine e' del gruppo di addestramento if s.__tipo == s.TRAINING: # siccome le immagini di addestramento sono divise a meta', la prima meta' sono O e la seconda sono X # allora sara' sufficiente dividere per la meta' del totale e arrotondare per difetto s.__descrizioni.append( int(np.floor(numero / 72)) ) # restituisci i due vettori come array di NumPy def get(s): return (np.array(s.__immagini) , np.array(s.__descrizioni)) class GiocoTris: # prendiamo come argomenti: # orig: cartella dove sono contenuti tutti i giochi # train: cartella dove si trovano le immagini di addestramento # test: cartella dove si trovano le immagini di test # nome_modello: nome della cartella nella quale verra' salvato il modello calcolato def __init__(s, orig = "GIOCHI", train = "TRAINING", test = "TEST", nome_modello = "gioco_del_tris"): s.__orig = orig s.__train = train s.__test = test s.__nome_modello = nome_modello s.__modello = None # leggiamo l'elenco dei file nella cartella orig, solo se sono dei file, e teniamoli come partite possibili s.__partite = [f for f in os.listdir(orig) if os.path.isfile(orig + "\\" + f)] # se il modello salvato esiste (ovvero se esiste la cartella) if os.path.exists(s.__nome_modello): # caricare il modello salvato s.__modello = keras.models.load_model(s.__nome_modello) #restituisce la lista dei file delle partite def getGiochi(s): return s.__partite # prepara il modello def prepara(s): # prepara il modello se il modello non esiste, ovvero non e' stato caricato if s.__modello is None: # preleva le immagini di addestramento e le relative descrizioni, ossia gli output noti train_img, train_desc = CreaDBImmagini(s.__train,CreaDBImmagini.TRAINING).get() # prepara il modello s.__modello = keras.Sequential([ keras.layers.Flatten(input_shape=(100, 100, 3)), keras.layers.Dense(128, activation='relu'), keras.layers.Dense(2, activation='softmax') ]) # compila il modello s.__modello.compile(optimizer="adam", loss='sparse_categorical_crossentropy', metrics=['accuracy']) # allena il modello con le immagini scelte in precedenza s.__modello.fit(train_img, train_desc, epochs=20) # salva il modello s.__modello.save(s.__nome_modello) # valuta accuratezza del modello con immagini di test def getAccuracy(s): # preleva immagini e output di test test_img, test_desc = CreaDBImmagini(s.__test,CreaDBImmagini.TEST).get() # calcola le perdite e l'accuratezza sulle immagini di test s.__test_loss, s.__test_acc = s.__modello.evaluate(test_img, test_desc, verbose=0) return s.__test_acc # valuta uno specifico gioco, scelto come posizione tra le partite possibili def getImmagine(s, pos): # elabora l'immagine del gioco compiuto gioco = ElaboraImmagini(orig = s.__orig + "\\" + s.__partite[pos], n = 3, w = 100, returnList = True).get() # predici i risultati in termini percentuali previsione = s.__modello.predict(gioco) # prepara l'output reinterpretato s.__risultato = [] # per ciascun valore delle previsioni for p in previsione: # se i valori sono vicini per un parametro arbitrario di confidenza, allora la casella e' vuota # per esempio se i valori sono 51% e 49% if abs(p[0]-p[1]) < 0.1: v = 0 # se il primo valore supera il secondo allora abbiamo uno O elif p[0] > p[1]: v = 1 # altrimenti abbiamo una X else: v = 2 # aggiungi il valore al risultato s.__risultato.append(v) # ottieni il vincitore vincitore = s.getVincitore( s.__risultato ) # restituisci il vincitore return vincitore # calcola il vincitore def getVincitore(s,vect): for i in range(0,9,3): if vect[i] == vect[i+1] == vect[i+2] and vect[i] != 0: return vect[i] for i in range(3): if vect[i] == vect[i+3] == vect[i+6] and vect[i] != 0: return vect[i] if (vect[0] == vect[4] == vect[8] and vect[0] != 0) or (vect[2] == vect[4] == vect[6] and vect[2] != 0): return vect[4] # mostra la griglia di gioco def stampaGioco(s): symb = ["-","O","X"] for k, r in enumerate(s.__risultato): if k > 0 and not k % 3: print() print(symb[r],end=" ") print() ElaboraImmagini("training.jpg","TRAINING",12,100) ElaboraImmagini("test.jpg","TEST",4,100) gt = GiocoTris(orig = "GIOCHI", train = "TRAINING", test = "TEST", nome_modello = "gioco_del_tris") gt.prepara() while True: print("-- gioco del tris --") print('Test accuracy:', gt.getAccuracy()) print("scegli la partita (-1 esci):") for k, n in enumerate(gt.getGiochi()): print("{0}. gioco {0}".format(k)) print("> ",end="") partita = int(input()) if partita == -1: break if partita >= 0 and partita <= 9: vincitore = gt.getImmagine( partita ) if vincitore == 1: print("Ha vinto O") elif vincitore == 2: print("Ha vinto X") else: print("Nessun vincitore") gt.stampaGioco() |
One thought on “[python] Semplice esercizio su TensorFlow e il riconoscimento delle immagini nel gioco del Tris (Machine Learning)”