Avevo provato in passato a realizzare un semplice programma di intelligenza artificiale che utilizzava le q-table e l’ambiente di simulazione Gym, qui trova l’articolo.
I problemi più grossi li ho trovati nell’ambiente di simulazione, perché dovevo adattarmi a lui mentre preferivo il contrario e così ho pensato di immaginarmi un mondo virtuale e scrivere una libreria per interfacciarmi.
Il mondo virtuale è una semplice griglia che contiene degli ostacoli e del cibo, il nostro omino (io lo chiamo tallo) si può muovere nelle classiche quattro direzioni e nelle quattro diagonali o restare fermo.
L’ambiente virtuale
L’ambiente virtuale viene richiamato dando le coordinate attuali del tallo (omino) e il movimento da effettuare, a questo punto calcola le nuove coordinate e restituisce un punteggio in base a quello che l’azione ha comportato, cioè, se non è successo nulla un valore negativo fra -2 e 0 (valori modificabili), se ha sbattuto contro un ostacolo (anche in caso sia andato fuori mondo) un valore compreso fra -80 e -120, se ha beccato un cibo un valore positivo compreso fra 800 e 1200.
Oltre al punteggio che serve per addestrare la rete neurale l’ambiente restituisce una stringa contenente quello che il tallo vede, per selezionare fra le memorie della rete neurale, quella più adeguata alla situazione che si è venuta a creare.
La profondità di vista é di tre quadrati in ogni direzione, quindi verrà restituita una stringa di 49 caratteri (3 sopra, 3 sotto e uno dove si trova il tallo fanno 7 per 7 nell’altra direzione). La profondità di vista è modificabile.
Naturalmente maggiore é la vista più sarà grande la stringa che la rappresenta e maggiori saranno le combinazioni che si potranno generare.
Quindi basta richiamare il metodo di aggiornamento con la posizione del tallo e l’azione da eseguire per avere un esito e la nuova situazione. Con l’esito addestriamo la rete neurale e con la nuova situazione chiediamo alla rete neurale qual’è l’azione più redditizia da intraprendere.
Il sorgente dell’ambiente virtuale
Questo è il sorgente dell’ambiente virtuale che ho realizzato, ha bisogno soltanto dell’installazione della libreria pygame, che può essere fatto facilmente lanciando il comando pip install pygame.
import random import pygame import math at_mondo_righe = 50 at_mondo_colonne = 100 at_mondo_cibo = 30 at_dimensione = 10 at_vista = 3 at_mondo = [] at_ciclo = 0 at_urti = 0 at_cibo = 0 at_esito_urto_a = 70 at_esito_urto_b = 100 at_esito_niente_a = 0 at_esito_niente_b = 2 at_esito_cibo_a = 800 at_esito_cibo_b = 1200 at_col_at_sfondo = '#8fbc8f' at_col_cibo = '#2f4f4f' at_col_cella = '#1e90ff' at_col_ostacolo = '#8b0000' at_col_tallo = '#000000' at_col_visitato = '#98FF98' pygame.init() at_sfondo=pygame.display.set_mode((1200,800)) pygame.display.set_caption('Ambiente Tallo 1.0') #pygame.font.init() at_font = pygame.font.SysFont('None', 24) at_sfondo.fill(at_col_at_sfondo) def crea_mondo(righe=100, colonne=100, cibo=30): global at_mondo_righe, at_mondo_colonne, at_mondo_cibo, at_mondo at_mondo_righe = righe at_mondo_colonne = colonne at_mondo_cibo = cibo at_mondo = [[0] * at_mondo_colonne for i in range(at_mondo_righe)] for i in range(cibo): crea_cibo() return def crea_cibo(): global at_mondo while True: cibox = random.randrange(0, at_mondo_righe-1) ciboy = random.randrange(0, at_mondo_colonne-1) if at_mondo[cibox][ciboy] == 0: at_mondo[cibox][ciboy] = 1 xd, yd = aggiusta_coord(cibox, ciboy, 'q') pygame.draw.rect(at_sfondo, at_col_cibo, (xd + 1, yd + 1, at_dimensione - 2, at_dimensione - 2)) break return def stampa_mondo(): for riga in at_mondo: for cella in riga: print(cella,end=' ') print() def disegna_mondo(): for x in range(0, at_mondo_righe): for y in range(0, at_mondo_colonne): xd,yd = aggiusta_coord(x,y,'q') pygame.draw.rect(at_sfondo, at_col_cella, (xd, yd, at_dimensione, at_dimensione), 1) if at_mondo[x][y] == 1: pygame.draw.rect(at_sfondo, at_col_cibo, (xd+1, yd+1, at_dimensione-2, at_dimensione-2)) pygame.display.flip() return def aggiusta_coord(x, y, tipo): xx = x * at_dimensione yy = y * at_dimensione if tipo == 'c': xx += int(at_dimensione/2) yy += int(at_dimensione/2) return xx,yy def dammi_griglia(xx, yy): griglia = '' for y in range(yy-at_vista, yy+at_vista+1): for x in range(xx - at_vista, xx + at_vista + 1): if x < 0 or y < 0 or x >= at_mondo_righe or y >= at_mondo_colonne: griglia += '3' else: griglia += str(at_mondo[x][y]) return griglia def disegna_griglia(griglia): base = int(math.sqrt(len(griglia))) pos_tallo = int((base -1) / 2) for x in range(0, base): for y in range(0, base): posizione = int(x*base+y) valore = griglia [posizione:posizione+1] if x == pos_tallo and y == pos_tallo: valore = '2' xx,yy = aggiusta_coord(y,x,'q') xx += 1000 + at_dimensione yy += at_dimensione pygame.draw.rect(at_sfondo, at_col_at_sfondo, (xx, yy, at_dimensione, at_dimensione), 0) if valore == '0': pygame.draw.rect(at_sfondo, at_col_cella, (xx, yy, at_dimensione, at_dimensione), 0) pygame.draw.rect(at_sfondo, '#293133', (xx, yy, at_dimensione, at_dimensione), 1) if valore == '1': pygame.draw.rect(at_sfondo, at_col_cella, (xx, yy, at_dimensione, at_dimensione), 1) pygame.draw.rect(at_sfondo, at_col_cibo, (xx + 1, yy + 1, at_dimensione - 2, at_dimensione - 2)) if valore == '2': pygame.draw.rect(at_sfondo, at_col_cella, (xx, yy, at_dimensione, at_dimensione), 1) xx,yy = aggiusta_coord(y,x,'c') xx += 1000 + at_dimensione yy += at_dimensione pygame.draw.circle(at_sfondo, at_col_tallo, (xx, yy), at_dimensione / 2, 0) if valore == '3': pygame.draw.rect(at_sfondo, at_col_ostacolo, (xx, yy, at_dimensione, at_dimensione), 0) pygame.draw.rect(at_sfondo, '#293133', (xx, yy, at_dimensione, at_dimensione), 1) return def azione_tallo(tallo, azione=9): # azione 1 su, 2 destra, 3 giu, 4 sinistra, 5 su destra, 6 giu destra, 7 giu sinistra, 8 su sinistra, 9 fermo global at_ciclo,at_urti, at_cibo at_ciclo += 1 esito = random.randint(at_esito_niente_a, at_esito_niente_b) * -1 xd, yd = aggiusta_coord(tallo[0], tallo[1], 'q') pygame.draw.rect(at_sfondo, at_col_cella, (xd, yd, at_dimensione, at_dimensione), 1) xd, yd = aggiusta_coord(tallo[0], tallo[1], 'c') pygame.draw.circle(at_sfondo, at_col_visitato, (xd, yd), at_dimensione/2-1, 0) pygame.draw.rect(at_sfondo, at_col_tallo, (1098, 18, 44, 44), 0) if azione == 1: tallo[1] = tallo[1] - 1 disegna_freccia((1120,60),(1120,25)) if azione == 2: tallo[0] = tallo[0] + 1 disegna_freccia((1100,40),(1133,40)) if azione == 3: tallo[1] = tallo[1] + 1 disegna_freccia((1120,20),(1120,55)) if azione == 4: tallo[0] = tallo[0] - 1 disegna_freccia((1130,40),(1105,40)) if azione == 5: tallo[0] = tallo[0] + 1 tallo[1] = tallo[1] - 1 disegna_freccia((1100,60),(1133,25)) if azione == 6: tallo[0] = tallo[0] + 1 tallo[1] = tallo[1] + 1 disegna_freccia((1100,20),(1133,55)) if azione == 7: tallo[0] = tallo[0] - 1 tallo[1] = tallo[1] + 1 disegna_freccia((1140,20),(1105,55)) if azione == 8: tallo[0] = tallo[0] - 1 tallo[1] = tallo[1] - 1 disegna_freccia((1140,60),(1105,25)) if tallo[0] < 0: esito = random.randint(at_esito_urto_a, at_esito_urto_b) * -1 tallo[0] = 0 at_urti += 1 if tallo[0] > at_mondo_righe -1: esito = random.randint(at_esito_urto_a, at_esito_urto_b) * -1 tallo[0] = at_mondo_righe-1 at_urti += 1 if tallo[1] < 0: esito = random.randint(at_esito_urto_a, at_esito_urto_b) * -1 tallo[1] = 0 at_urti += 1 if tallo[1] > at_mondo_colonne-1: esito = random.randint(at_esito_urto_a, at_esito_urto_b) * -1 tallo[1] = at_mondo_colonne-1 at_urti += 1 xd, yd = aggiusta_coord(tallo[0], tallo[1], 'c') pygame.draw.circle(at_sfondo, at_col_tallo, (xd, yd), at_dimensione / 2, 0) if at_mondo[tallo[0]][tallo[1]] == 1: at_mondo[tallo[0]][tallo[1]] = 0 crea_cibo() esito = random.randint(at_esito_cibo_a, at_esito_cibo_b) at_cibo+=1 griglia = dammi_griglia(tallo[0],tallo[1]) disegna_griglia(griglia) pygame.draw.rect(at_sfondo, at_col_visitato, (1002, 100, 1250 +200,300)) at_sfondo.blit(at_font.render('Cicli '+format(at_ciclo, ',d'), True, at_col_ostacolo), (1005, 105)) at_sfondo.blit(at_font.render('Urti ' + format(at_urti, ',d'), True, at_col_ostacolo), (1005, 125)) at_sfondo.blit(at_font.render('Cibo ' + format(at_cibo, ',d'), True, at_col_ostacolo), (1005, 145)) pygame.display.flip() return esito, griglia def disegna_freccia( start, end): pygame.draw.line(at_sfondo,'#FF6600',start,end,2) rotation = math.degrees(math.atan2(start[1]-end[1], end[0]-start[0]))+90 pygame.draw.polygon(at_sfondo, '#FF6600', ((end[0]+7*math.sin(math.radians(rotation)), end[1]+7*math.cos(math.radians(rotation))), (end[0]+7*math.sin(math.radians(rotation-120)), end[1]+7*ma
La libreria si occupa di gestire l’ambiente virtuale e visualizzarlo per permettere di vedere in tempo reale quello che sta accadendo, inoltre visualizza la direzione intrapresa con una freccia e l’area visibile al tallo.
Se volete provarla questo è un programmino per testarla
from env_tallo import * import keyboard import time mtable_pilota = [] mtable_memoria = [] casuali = scelte = vuote = 0 crea_mondo(100,50,30) #stampa_mondo() disegna_mondo() tallo = [50,25] while True: if keyboard.is_pressed("q"): break azione = 0 if keyboard.is_pressed("1"): azione = 1 if keyboard.is_pressed("2"): azione = 2 if keyboard.is_pressed("3"): azione = 3 if keyboard.is_pressed("4"): azione = 4 if keyboard.is_pressed("5"): azione = 5 if keyboard.is_pressed("6"): azione = 6 if keyboard.is_pressed("7"): azione = 7 if keyboard.is_pressed("8"): azione = 8 if keyboard.is_pressed("9"): azione = 9 if azione > 0: punteggio, griglia = azione_tallo(tallo,azione) time.sleep(0.1)
Potete muovere il tallo con i tasti da uno a 9.
Nel prossimo articolo pubblicherò un sorgente che con l’utilizzo della rete neurale Q, la cosiddetta q-table gestirà i movimenti del tallo cercando di non sbattere e di mangiare quanto più possibile.
Articolo successivo QUI.
Maurizio