In parecchie applicazioni informatiche, specie in quelle simulative, saper integrare una funzione ricopre un ruolo fondamentale. Purtroppo però le tecniche di integrazione che ci hanno insegnato a scuola o nei corsi di Analisi I sono del tutto inutili. Raramente, infatti, ci troveremo a che fare con funzioni integrabili, anzi, spesso dovremo integrare funzioni non esprimibili tramite funzioni analitiche.

Supponete ad esempio di voler implementare un piccolo gioco di guida. La prima cosa da fare è progettare il nostro sistema di guida. Ovviamente, dato che siamo dei tipi tosti, vogliamo che sia il più realistico possibile e non c’è nulla di più realistico che usare le leggi della fisica.

Continue reading »

 

Supponiamo di trovarci in una città a noi sconosciuta e di avere un appuntamento importante con una stupenda ragazza (o ragazzo a seconda dei casi). La cosa più semplice da fare è chiedere indicazioni ai passanti. Purtroppo però siamo sfortunati e quel giorno diluvia che dio la manda e non c’è anima viva in giro. Non c’è speranza.

LA RICERCA IN AMPIEZZA

In questo caso l’unica soluzione che avete è di tentare tutte le strade. Partite dal punto A e verificate tutti gli incroci a cui si arriva direttamente da A, se non siete arrivati tornate ad A e verificate tutti gli incroci a cui si arriva partendo da A e attraversando un solo incrocio, e così via, aumentanto ad ogni passo la profondità della ricerca. Questo tipo di ricerca prende il nome di ricerca in ampiezza. Funziona? Sicuro, prima o poi arriverete a destinazione. Tuttavia la domanda sorge spontanea: quanto poi?

Supponendo, come avviene spesso in realtà, che ad ogni incrocio si dipanino 3 strade (oltre a quella da cui siete venuti) se disgraziatamente la ragazza si trova a solo 10 incroci di distanza dovrete testare circa 59.049 incroci e un numero maggiore di strade. Un compito decisamente eccessivo anche per il più motivato.

Dobbiamo trovare un alternativa. Appare evidente che abbiamo bisogno di un informazione che ci permetta di scartare le strade che sicuramente non ci portano a destinazione. Chiamiamo quindi la ragazza e scopriamo che è una nerd d’alta categoria. Ella infatti vole metterci alla prova. Invece di dirci la strada ci promette solo di dirci la distanza in linea retta che separa ogni incrocio che vogliamo da lei. È sufficiente? Si, e non solo troviamo una strada che ci conduce da lei ma troviamo la migliore.

LA RICERCA GOLOSA

La prima soluzione che ci viene in mente è semplice: fra tutti gli incroci che posso raggiungere da A scelgo quello che è più vicino alla ragazza e così via per ogni incrocio su cui vado a finire. Questo tipo di ricerca (che prende il nome di ricerca golosa) è sicuramente migliore della ricerca in ampiezza tuttavia ha due grossi problemi: non trova la strada più breve e, nel caso di grafo con cicli, può finire per ciclare all’infinito.

LA RICERCA A*

Scegliamo questa strategia: assegno a tutti gli incroci che posso raggiungere dall’incrocio in cui sono un numero N corrispondente alla somma della strada percorsa fino a quel punto più la distanza in linea retta che separa quell’incrocio dalla ragazza. A questo punto scelgo l’incrocio che ha il numero N più basso fra quelli che ho assegnato fin’ora. Questa ricerca prende il nome di A* ed è l’algoritmo di ricerca più efficiente  su un grafo pesato (come ad esempio una mappa stradale).

I punti a favore di A* sono fondamentalmente due: l’efficienza computazionale e il fatto che, in alcune condizioni, il percorso trovato è ottimo.

EURISTICHE AMMISSIBILI

Ma quali sono queste condizioni? Torniamo all’informazione che ci forniva la ragazza: la distanza in linea retta di ogni incrocio da lei. Questa prende il nome di funzione euristica. In parole povere è una funzione che esprime una stima della “distanza” dall’obiettivo. Bene, affinché la ricerca di A* sia ottima è necessario che la funzione euristica non sovrastimi mai la distanza reale dall’obiettivo! Quando questa condizione è soddisfatti si dice che la funzione euristica è ammissibile.

Nel nostro caso la funzione euristica soddisfa questa condizione in quanto la distanza in linea retta fra un incrocio e la ragazza non può mai essere maggiore della distanza reale (per definizione la retta è la distanza più breve fra due punti). La funzione è quindi ammissibile soluzione che troveremo sarà quindi ottima.

La non ammissibilità di una funzione non è però un limite stringente, spesso infatti trovare una funzione euristica ammissibile necessita di un quantitativo di calcoli troppo elevato che vanificherebbe i vantaggi di A*. In questi casi si può scegliere di usare una funzione non ammissibile ma di facile calcolo a patto di accontentarsi di una soluzione che potrebbe non essere ottimale.

Abbiamo imparato tanto in questa uscita. La ragazza al nostro arrivo sarà sicuramente soddisfatta.

 

Probabilmente fra tutti gli esempi di applicazioni dell’Intelligenza Artificiale la più semplice e famosa consiste sicuramente nel gioco 20q. Il gioco in questione funziona così: il giocatore pensa ad un oggetto o ad un animale e il marchingegno tenta di indovinare di cosa si tratta in meno di 20 domande a cui il giocatore può rispondere si o no. Potete trovare una versione online del gioco all’indirizzo http://www.20q.net/. Potrete constatare che in alcuni casi il programma sembra proprio leggervi nella mente con risultati quasi spaventosi.

La versione usata dal sito è piuttosto avanzata ed utilizza le reti neurali e permette quindi una gamma di riposte più vasto (c’è anche forse, probabilmente, a volte, ecc…). Il programma è inoltre in grado di apprendere mentre si gioca: ogni qual volta si gioca, o il programma si arrende richiedendo di inserire l’oggetto a cui si pensava, l’insieme di risposte appena date plasma la struttura dati interna del programma in modo tale che la prossima volta l’oggetto venga riconosciuto più velocemente. È un modello di apprendimento molto basilare ma che è analogo a quello umano percezione-previsione-modifica: il programma riceve dati percettivi (le risposte alle domande), formula un ipotesi (i tentativi di indovinare) e modifica la struttura dati in base all’esito della previsione.

Sarei curioso di sapere se il programma del sito ha qualche contromisura contro il problema del sovra-adattamento: questo tipo di problema affligge gran parte dei modelli decisionali, comprese le reti neurali, ed è causato da un eccessivo adattamento della rete a certi tipi di dati piuttosto che ad altri. Facciamo un esempio. Nel gioco 20q è molto più probabile che il giocatore pensi a cose “banali” come “gatto”, “cane”, “fiore” e simili piuttosto che a “antilope”, “spinterogeno” e “neutrino”. Questo significa che nel corso dell’esistenza del gioco il programma verrà addestrato alla perfezione a riconoscere cani e gatti mentre riceverà poche informazioni riguardo antilopi e neutrini. Possiamo quindi dire che “gatto” e “fiore” e le parole comuni facciano parte dell’insieme di addestramento di una rete. Bene, è possibile dimostrare che una rete sovra-adattata al suo insieme di addestramento indovina, in media, molto meno volte oggetti che non appartengono a tale insieme rispetto ad una rete che è solo parzialmente adattata.

Rimandiamo però questi approfondimenti sulle reti neurali. Il motivo per cui ho scritto questo articolo era di mostrare come sia possibile in una manciata di righe scrivere un programma che emuli in modo semplice il gioco di 20q. Per questa approssimazione consideriamo che sia possibile dare alle domande soltanto le risposte si o no.

Il metodo che vi mostrerò non utilizza nessuna tecnica propria dell’IA bensì dei semplicissimi alberi binari. Questi alberi binari li indicheremo come alberi decisionali binari. Nel nostro albero decisionale definaiamo:

  • I nodi interni dell’albero sono domande.
  • I nodi foglia dell’albero sono gli oggetti da indovinare.
  • Il sotto-albero destro di un nodo corrisponde al caso in cui la risposta alla domanda sia si.
  • Il sotto-albero sinistro di un nodo corrisponde al caso in cui la risposta alla domanda sia no.

L’algoritmo del gioco consiste in una cosa del tipo:

  • Il programma formula la prima domanda che trova (cominciando con il nodo radice).
  • Se non c’è più nessuna domanda il programma si arrende e chiede al giocatore di inserire 1) l’oggetto a cui stava pensando. 2) una domanda che rappresenta l’oggetto 3) la risposta a questa domanda.
  • Il programma aggiunge questa domanda e la relativa risposta al sotto albero in cui si era fermato (oppure come nodo radice se l’albero era vuoto).
  • Se invece la domanda c’è il programma formula la domanda e scende nel sotto-albero relativo alla risposta.
  • Il programma ricomincia dal punto 1.

Tutto qui. Tale algoritmo è effettivamente molto grezzo e sono possibili un gran numero di migliorie (ad esempio usare dei modelli di albero più “elastici” per evitare alberi troppo sbilanciati) ma svolge decentemente il proprio lavoro. Dopo che l’avete fatto girare per qualche tempo vedrete che inizierà ad imparare e ad indovinare un discreto numero di parole. Ovviamente potete partire da un albero vuoto oppure pre-costruire un albero di base con alcune domande comuni.

Un altra cosa che può farci capire questo semplice programma è una considerazione che mi ha fatto notare un gentile commentatore: la capacità degli algoritmi di AI non dipende dalla dimensione del programma ovvero dal suo numero di righe di codice allo stesso modo di come le capacità celebrali umane non dipendono dalla dimensione del cervello (una balena ha il cervello molto più voluminoso di un essere umano eppure fa molte meno cose). Le capacità cognitive di un programma dipendono solamente dalle relazioni che si auto-generano fra le strutture dati del programma (nel nostro caso tali relazioni sono i collegamenti ad albero fra i nodi).

Se qualcuno avesse voglia di creare questo programmino usando il minor numero di righe possibili sarei felice di pubblicarlo. Inoltre potreste anche usarlo per sbalordire amici, parenti e vicini di casa! XD

Alla prossima!

 
Lalgoritmo di Bubble Sorting in azione.

L'algoritmo di Bubble Sorting in azione.

Insertion Sort e Selection Sort non si sono rivelati all’altezza delle nostre aspettative e noi ci ritroviamo ancora senza un algoritmo efficente per ordinare, o meglio: sappiamo ordinare le tracce di un album, oppure i CD di un apiccola collezione domestica, ma avremo molte difficoltà nell’ordinare in ordine di età gli abitanti di Roma o i volumi di un agrande biblioteca.

Dobbiamo proseguire la nostra ricerca, cambiamo approccio, questa volta il nostro tentativo cade sul Bubble Sort.

Continue reading »

 
Rappresentazione grafica di un ordinamento con Selection Sort

Rappresentazione grafica di un ordinamento con Selection Sort

Nell’ultimo appuntamento abbiamo visto e analizzato gli aspetti generali dell’Insertion Sort e notato che esso non è affatto un algoritmo usabile in pratica per istanze (ovvero input) molto grandi.

Ovviamente l’Insertion Sort non è l’unico algoritmo di sorting esistente. Nella nostra ricerca dell’algoritmo ottimo di sorting oggi guarderemo il Selection Sort.
Continue reading »

 
Effettuare il tuning di un modulo C.

Effettuare il tuning di un modulo C.

L’efficenza di un algoritmo e di un programma va affrotata in due passi: un analisi teorica dell’algoritmo e una analisi “on the road” in cui l’algoritmo va implementato e testato sulle varie architetture reali.

Ovviamente, come abbiamo avuto modo di vedere, non c’è linguaggio che possa vantare le capacità prestazionali del C, anche senza contare le capacità di interfacciarsi direttamente a basso livello con innesti di codice assembly grazie al costrutto _asm_.

Ma come tutti gli strumenti potenti vanno saputi utilizzare, molto spesso differenze impercettibili e che pensiamo irrilevanti si ripercuotono in maniera catastrofica sul nostro software. Ad esempio ho avuto modo di vedere algoritmi in cui la sola variazione di due parametri strutturali faceva passare il tempo di esecuzione da 2 minuti a 11 giorni.

E’ quindi importante identificare, in un software, quali sono le funzioni e le procedure che occupano più tempo, e quale è il loro peso nel tempo complessivo di esecuzione.

A venirci incontro è proprio GProf.

Continue reading »

 
Come ordina linsertion sort.

Come ordina l'insertion sort.

Volevo discutere in qualche articolo alcuni problemi di algoritmica presentando alcuni algoritmi che li risolvono e analizzandone la complessità.

Cominciamo questa vetrina di algoritmi con quelli che risolvono il più classico dei problemi: l’ordinamento.

In particolare cominceremo vedendo uno dei più semplici: insertion sort. Continue reading »

 
Per capire la ricorsione bisogna innanzitutto capire la ricorsione...

Per capire la ricorsione bisogna innanzitutto capire la ricorsione...

Ora conosciamo sufficientemente la struttura della memoria e i principi del suo funzionamento. Per spiegare lo stack, in particolare, ho dovuto fare richiamo alle funzioni.

Le funzioni sono un argomento fondamentale della programmazione e alcuni processori le implementano addirittura a livello di assembly.

Il concetto di funzione è molto semplice. Una funzione può essere vista come una “macchina” che riceve in pasto dei dati, li elabora e a volte restituisce un risultato.

Continue reading »

 
Foto di un programma che usa algoritmi scadenti.

Foto di un programma che usa algoritmi scadenti.

Abbiamo visto cos’è un algoritmo, come è suddiviso e come lo si rappresenta astrattamente in fase di “progetto”.

Ora affronteremo il come progettarlo e in particolare il metodo del Dividi et Impera.

La progettazione di un algoritmo è la fase più delicata del processo di programmazione. Spesso scelte che sembrano ininfluenti nella nostra mente si tramutano in barriere computazionali invalicabili (cosa che appare manifesta se si combatte con il Progetto Eulero).

Continue reading »

 
No... forse non è questo l'algo-ritmo...

No... forse non è questo un algo-ritmo...

Nella scorsa lezione abbiamo visto che cosa è la programmazione e abbiamo notato che si può semplificare nella creazione di algoritmi.

L’astrazione da ogni linguaggio ci permette di dimenticarci per il momento le difficoltà implementative di ogni algoritmo e concentrarci solo sulla sua essenza.

Partiamo quindi dalla struttura di algoritmi veramente semplici.

Continue reading »

© 2008-2012 SlashCode Suffusion theme by Sayontan Sinha