AI in python primo esperimento

Sono affascinato dall’intelligenza artificiale, l’idea che una sequenza di istruzioni ad un computer possa permettergli di pensare é pazzesca ma teoricamente possibile.

Come potete vedere in articoli precedenti ho scelto il python come linguaggio di programmazione per la sua potenza e semplicità, unico lato negativo è la velocità comunque ottima considerato che si tratta di un linguaggio interpretato.

Intelligenza artificiale

Ho girato un po’ sulla rete per cercare di capire e non ho capito molto, ci sono tante informazioni, ma cercando di approfondire si capisce sempre meno. Si tratta sicuramente di qualcosa di complesso e allora ho pensato che fosse opportuno fare delle prove pratiche per capire facendo.

Ho visto vari algoritmi per l’intelligenza artificiale che vengono utilizzati per i vari compiti, allora ho pensato che io volevo fare un programma di simulazione di vita artificiale, molto semplice nelle sue regole e che l’algoritmo doveva prendere le decisioni per questi esseri in modo da permettergli di sopravvivere.

La scelta dell’algoritmo è ricaduta sul Q learning, un algoritmo che impara nel tempo con la regola che è meglio rinunciare all’uovo oggi per avere una gallina domani. Se volete avere una infarinatura potete dare un’occhiata QUI.

Proviamo con python

Ho scopiazzato un algoritmo da un sito e l’ho modificato per verificare se funziona, il sorgente che troverete più avanti utilizza una Q-Table per addestrare il computer, non ci sono reti neurale ma sembra funzionare, man mano che sperimento spero di capire come faccia a funzionare.

Poiché non tutte le librerie sono aggiornate dobbiamo installare una versione precedente di python (oggi:05/06/2021) più precisamente dobbiamo installare la 3.88 e ricordatevi di fare aggiungere il path in fase di installazione.

Poi dobbiamo installare GYM con il comando pip install gym e NUMPY con il comando pip istall numpy. Se abbiamo aggiunto il path in fase di installazione di comandi possiamo darli da una qualsiasi finestra di comando (tasto windows + r e digitare cmd).

Due parole su GYM

La libreria GYM mette a disposizione un ambiente virtuale che simula una situazione fisica e permettendo di dare dei comandi e vederne gli effetti, anche con la rappresentazione grafica. Io ho utilizzato per questa prova uno degli ambienti, MountainCar, un carrello che si trova in fondo ad una conca e che ad ogni ciclo possiamo spingere da destra, da sinistra o non spingere. Dopo ogni ciclo la libreria ci restituisce alcuni parametri, in questo caso la posizione e la velocità.

Volendo è possibile attivare il rendering dell’ambiente e verrà aperta una finestra grafica che rappresenta il mondo in tempo reale con le animazioni. L’ambiente prevede solitamente 200 cicli di azioni e poi lo considera concluso a meno che non si raggiunga l’obiettivo, la cima della conca, con un minor numero di tentativi.

Q-Table e formula aggiornamento

Come base di memoria utilizzeremo una Q-Table che altro non è che un array a più dimensioni, nel nostro caso tre, le prime due contengono tutte le possibili combinazioni fra i due valori che restituisce l’ambiente GYM mentre la terza contiene l’esito che ha avuto ogni azione (quindi tre azioni disponibili) in quella combinazione.

Per semplificare la descrizione provate ad immaginare una scacchiera e un array che la rappresenta, queste sono le prime due dimensioni, la terza dimensione ha un valore che indica per ogni casella com’è andata una determinata mossa partendo da li, scegliendo quella che in passato ha dato migliori risultati si dovrebbe ottenere un risultato migliore.

La formula per aggiornare questo valore è:

Qnew é il nuovo valore della cella in oggetto, identificata da [posizione,velocità,azione], il learning rate è un valore fra 0 e 1 che indica la velocità di apprendimento, il reward è il risultato ottenuto dall’azione, nel caso di questo ambiente sarà sempre 0 tranne quando raggiunge l’obiettivo. Il dicount factor ha un valore compreso fra 0 e 1 e indica quanto è importante un obiettivo futuro rispetto ad un obiettivo immediato.

Cominciamo a vedere il programma

Vediamo ora le prime istruzioni, quelle che preparano l’ambiente e la Q-Table

import gym
import numpy as np


gym.envs.register(
    id='prova-v0',
    entry_point='gym.envs.classic_control:MountainCarEnv',
    max_episode_steps=200,      
    reward_threshold=-110.0,
)
env = gym.make('prova-v0')


LEARNING_RATE = 0.1
DISCOUNT = 0.95
episodi = 20001

visualizza = 100
risultati = 0
riduzione = [20, 20]
fattore_riduzione = (env.observation_space.high - env.observation_space.low)/riduzione

casualita = 1
episodi_con_casualita = 5000
riduzione_casualita = casualita/episodi_con_casualita

q_table = np.random.uniform(low=-1, high=-1, size=(riduzione + [env.action_space.n]))

Quindi in ordine, importiamo le librerie, definiamo nuovo ambiente di prova clonando il MountainView per poter intervenire su alcuni parametri tipo il numero di clicli max, impostiamo alcuni parametri e creiamo la Q_table.

Vediamo i parametri, learning_rate serve per la formula e lo stesso fale per discount, episodi indica il numero di cicli che verranno fatti per aggiornare la q-table, visualizza indica ogni quanti cicli si visualizza la situazione attuale, risultati è la variabile che viene incrementata ad ogni obiettivo raggiunto ed azzerata ad ogni visualizzazione.

Continuando ad analizzare i parametri abbiamo riduzione è la dimensione dell’array bidimensionale visto che non è necessario avere tutte le combinazioni fra velocità e posizione in assoluto che sarebbero infinite le riduciamo a 20, casualità serve a decidere un’azione casuale invece di quella predefinita, la probabilità di fare un’azione casuale diminuisce man mano fino ad azzerarsi superando il ciclo indicato in episodi con casualita.

La creazione della q_table era prevista randomica da -2 a zero ma io preferisco che parte da -1 per poter vedere su quali celle avviene una variazione e in che entità. Inoltre ho constatato che facendola partire da zero apprende più velocemente.

Utilizzeremo una sola funzione che serve a dare i valori di velocità e posizione adeguati alla riduzione

def dammi_riduzione(state):
    discrete_state = (state - env.observation_space.low)/fattore_riduzione
    return tuple(discrete_state.astype(np.int))  

Una breve descrizione del programma

Il programma esegue un numero di prove indicato dalla variabile episodi, per ogni episodio esegue uno step finchè non finisce la prova o raggiunge l’obiettivo. Per ogni step determina l’azione da fare, nei primi step fino al raggiungimento di episodi_con_casualita l’azione eseguita può essere casuale o determinata dall’azione con il valore maggiore in base alla combinazione di velocità e posizione, superato quel limite sarà sempre il valore maggiore preso dalla q-table.

Ogni step viene chiamato passandogli l’azione da eseguire e ricevendo il nuovo stato (velocità e posizione) il premio (solo in caso di raggiungimento dell’obiettivo) e l’eventuale fine prova. Il nuovo stato viene ridotto con la funzione per adattarsi alle dimensione della q-table e aggiornata la q-table e ridotta la variabile di casualità.

Questo il sorgente completo:

import gym
import numpy as np


gym.envs.register(
    id='prova-v0',
    entry_point='gym.envs.classic_control:MountainCarEnv',
    max_episode_steps=200,      # MountainCar-v0 uses 200
    reward_threshold=-110.0,
)
env = gym.make('prova-v0')
LEARNING_RATE = 0.1
DISCOUNT = 0.95
episodi = 20001
visualizza = 100
risultati = 0
riduzione = [20, 20]
fattore_riduzione = (env.observation_space.high - env.observation_space.low)/riduzione
casualita = 1
episodi_con_casualita = 5000
riduzione_casualita = casualita/episodi_con_casualita
q_table = np.random.uniform(low=0, high=0, size=(riduzione + [env.action_space.n]))
def dammi_riduzione(state):
    stato_ridotto = (state - env.observation_space.low)/fattore_riduzione
    return tuple(stato_ridotto.astype(np.int))  
for episodio in range(episodi):
    stato_ridotto = dammi_riduzione(env.reset())
    fine_prova = False
    if episodio % visualizza == 0:
        print(episodio,' ',risultati)
        risultati = 0
    while not fine_prova:
        if np.random.random() > casualita:
            azione = np.argmax(q_table[stato_ridotto])
        else:
            azione = np.random.randint(0, env.action_space.n)
        nuovo_stato, reward, fine_prova, _ = env.step(azione)
        nuovo_stato_ridotto = dammi_riduzione(nuovo_stato)
        if not fine_prova:
            max_future_q = np.max(q_table[nuovo_stato_ridotto])
            q_attuale = q_table[stato_ridotto + (azione,)]
            nuovo_q = (1 - LEARNING_RATE) * q_attuale + LEARNING_RATE * (reward + DISCOUNT * max_future_q)
            q_table[stato_ridotto + (azione,)] = nuovo_q
        elif nuovo_stato[0] >= env.goal_position:
            risultati = risultati + 1
            q_table[stato_ridotto + (azione,)] = 0
        stato_ridotto = nuovo_stato_ridotto
    if episodio <= episodi_con_casualita:
        casualita -= riduzione_casualita
env.close()

Conclusione

Comincio a capire come funziona il programma e la teoria del Q learning e delle Q table, ma devo ancora approfondire. Farò delle prove per non modificare la tabella appena ha raggiunto il 100% dei risultati positivi, proverò a cambiare i vari parametri per vedere se cambia la velocità e la qualità dell’apprendimento e altre prove che mi verranno in mente.

Se la passione per queste cose continua ci saranno altri articoli sicuramente più approfonditi.

Maurizio

Lascia un commento