sabato 9 novembre 2013

Sogno e umorismo

Sogno, umorismo e gestualità. Sono tre elementi indispensabili senza i quali non sarà possibile realizzare una compiuta intelligenza artificiale. Sto leggendo il libro di Kurzweil la singolarità è vicina. Ha ragione su molte cose. Lui prevede che l'aumento delle capacità di calcolo dei computer consentirà di raggiungere la massa critica necessaria a eguagliare le capacità del cervello umano. Il punto è che la capacità di calcolo non è sufficiente. Occorre capire alcuni meccanismi che presiedono al funzionamento del cervello e hanno a che hanno a che fare con la natura stessa dell'intelligenza. Se il riconoscimento vocale appare ormai a portata di mano, così come l'esecuzione di compiti via via più complessi, siamo ancora molto lontani dalla creatività. La chiave per capire l'origine di questo fenomeno potrebbe essere proprio nel sogno e nell'umorismo, due fenomeni il cui ruolo nel funzionamento complessivo del cervello non è ancora affatto chiaro. Occorre inoltre indagare con più attenzione il rapporto tra il linguaggio, il cervello e la mano, integrando nei progetti di intelligenza artificiale anche l'esame della gestualità della mano robotica e il suo rapporto con l'apprendimento del linguaggio.

martedì 16 luglio 2013

Ameriano colpito dall'amnesia: dimentica l'inglese, ora parla svedese

  PALM SPRINGS, Calif. (AP) _ Doctors are looking into the mystery of a Florida man who awoke speaking only Swedish, with no memory of his past, after he was found unconscious four months ago at a Southern California motel.

   Michael Boatwright, 61, woke up with amnesia, calling himself Johan Ek, The Desert Sun reported (http://mydesert.co/145PNGw).

   Boatwright was found unconscious in a Motel 6 room in Palm Springs, Calif., in February. After police arrived, he was transported to the Desert Regional Medical Center in Palm Springs where he woke up.

   Hospital officials said Boatwright may have been in town for a tennis tournament in the Coachella Valley. He was found with a duffel bag of exercise clothes, a backpack and tennis rackets. He also carried four forms of identification _ a passport, a California identification card, a veteran's medical card and a Social Security card _ all of which identified him as Michael Thomas Boatwright.

   Palm Springs police have documented his information in case anyone lists Boatwright as missing or wanted, authorities said.

   In March, doctors diagnosed Boatwright with Transient Global Amnesia, a condition triggered by physical or emotional trauma that can last for several months.

   The rare mental disorder is characterized by memory loss, ``sudden and unplanned travel,'' and possible adoption of a new identity, according to the Sun.

   After an extensive search, medical personnel and social workers have been unable to locate Boatwright's next of kin. Authorities are still unsure of his birthplace, listed on his ID as Florida _ photos show him in Sweden at a young age.

   Boatwright doesn't recall how to exchange money, take public transportation, or seek temporary housing like homeless shelters or hotels, the social worker assigned to his case, Lisa Hunt-Vasquez, told the Sun.

   He doesn't remember his son and two ex-wives, either.

   He has no income or insurance, further complicating his treatment at Desert Regional. And he has little money he can access _ only $180. He also has a few Chinese bank accounts, but can only access one account, which holds $7, according to the newspaper.

   Doctors don't know how much longer he will be able to stay at the center _ aside from his amnesia, Boatwright is in good health. The hospital is currently looking for alternatives that would keep him off the streets. For now, Boatwright is unsure of both his past and his future.

   ``Sometimes it makes me really sad and sometimes it just makes me furious about the whole situation and the fact that I don't know anybody, I don't recognize anybody,'' Boatwright told the newspaper.

   Last year, a North Dakota college student who went missing for nearly a week before turning up in Arizona said she had a bout of amnesia and didn't know who she was.

   Amber Glatt, a 22-year-old Valley City State University student, vanished on the Fourth of July, prompting aerial searches. She contacted her mother five days later from the Grand Canyon. Her mother said Glatt has had recurring amnesia since suffering a head injury years ago.

   Glatt told WDAY-TV (http://bit.ly/NmbSnR ) that after she lost her memory she met a man in a bar who let her tag along on his trip to the Grand Canyon. She said the man eventually saw online that she'd been reported missing and alerted her.

   Glatt regained most of her memory.

   AP/LaPresse-WF-07-16-13 0815GMT<

lunedì 6 maggio 2013

Nasce l'elettronica organica: arriva il transistor neuronale


Roma, 6 mag. (LaPresse) - Strumenti più efficaci per la conoscenza del funzionamento del cervello e la riparazione dei neuroni mal funzionanti in Parkinson ed epilessia. E' la prospettiva aperta da una ricerca del Cnr pubblicata su Nature Materials. Tutto grazie alla messa a punto di una nuova tecnologia, l'elettronica organica trasparente, capace di ottenere informazioni in merito all'attività neuronale, apre una nuova piattaforma d'indagine. La ricerca, condotta da due istituti del Consiglio nazionale delle ricerche di Bologna, l'Istituto per lo studio dei materiali nanostrutturati (Ismn-Cnr) e l'Istituto per la sintesi organica e la fotoreattività (Isof-Cnr), in collaborazione con l'Istituto italiano di tecnologia (Iit) e con Etc, spin off del Cnr e start up del Gruppo Saes, ha dimostrato che si può stimolare l'attività neuronale, manipolarla e leggerla attraverso uno strumento biocompatibile: l'Ocst (organic cell stimulating and sensing transistor).

"Il dispositivo è costituito da un microchip organico trasparente sul quale vengono adagiate le reti neuronali, caratterizzato dalla capacità di stimolare e registrare segnali elettrici e, in prospettiva, di generare luce", illustra Michele Muccini, responsabile del Cnr-Ismn di Bologna e coordinatore del progetto insieme con Valentina Benfenati e Stefano Toffanin. "Inoltre, in quanto biocompatibile, Ocst riesce a rimanere a contatto per lungo tempo con i neuroni primari senza che questi vengano danneggiati, offrendo la possibilità di comprendere il loro funzionamento e di modulare la loro attività con maggiore efficacia rispetto alle tecnologie esistenti".

L'applicazione di questo strumento fornisce numerosi vantaggi. "Il suo sviluppo permetterà di studiare anche altri tipi di neuroni e di cellule, dando la possibilità di compiere significativi passi avanti nella determinazione del funzionamento del cervello umano", prosegue Muccini. "In futuro - continua - la capacità di interazione tra cellule nervose e dispositivo potrebbe trovare applicazioni per la rigenerazione del tessuto nervoso periferico compromesso da incidenti traumatici, da malattie neurodegenerative come il Parkinson o nella diagnosi precoce di eventi epilettici".

Lo studio ha impegnato un team di 13 ricercatori, tra cui esperti in scienza e tecnologia dei materiali, neuroscienziati ed elettrofisiologi, che ha lavorato per due anni presso la sede del Cnr di Bologna, nell'ambito delle linee strategiche del dipartimento di Scienze chimiche e tecnologie dei materiali dell'Ente e delle ricerche sullo sviluppo di tecnologie per la comprensione del funzionamento del cervello, oggetto di importanti programmi strategici europei e americani. L'importanza socio-economica di queste attività ha spinto la Commissione europea a designare maggio 2013 'mese europeo del cervello'.

lunedì 11 marzo 2013

Ascolta, guarda e clicca


Leggo  questo articolo su Repubblica e devo dire ne resto impressionato.

Ascolta, clicca e guarda ecco la nuova formula per insegnare ai ragazzi
http://www.flcgil.it/rassegna-stampa/nazionale/ascolta-clicca-e-guarda-ecco-la-nuova-formula-per-insegnare-ai-ragazzi.flc

Trovo paradossale l'entusiasmo con cui non solo ci arrendiamo, ma addirittura ci appassioniamo a questo nuovo metodo di comunicazione fatto di inquinamento mentale e caos. I ragazzi non sono capaci di mantenere la concentrazione cinque minuti sulla stessa cosa, perché studiano tra Facebook, le vibrazioni del cellulare, gli mp3 e la televisione? E allora si inventano modi caotici di studiare, per intercettare la loro attenzione. "Navigando nel V canto dell’Inferno nella piattaforma di My-LabLetteratura, collegata a un manuale scolastico cartaceo, ecco la voce di Gassman che recita la passione di Paolo e Francesca,mentre una “linea del tempo” sottolinea le date fondamentali della vita del poeta. E, sullo sfondo, scorrono i quadri ispirati al canto più celebre della Divina Commedia". 

Ottimo, così siamo noi a certificare allo studente che tenere l'attenzione su una cosa per cinque minuti è una cosa inutile. E' più o meno come dire che dato che il mare è inquinato, tanto vale rovesciarvi dentro il petrolio e prendere atto, possibilmente con un certo entusiasmo, che il mare 2.0 è fatto al 50% di acqua e al 50% di petrolio.

La questione è semplice. Il learning by doing è una eccezionale forma di apprendimento, scarsamente utilizzata in Italia, che può funzionare molto bene col digitale. Anche gli stessi ebook, opportunamente collegati a dizionari, wikipedia, e strumenti di approfondimento, sono una ottima evoluzione degli strumenti di studio. Ma insegnare, invece, ai ragazzi che non serve saper leggere per un'ora di seguito, e attirare la loro attenzione su video, musica, animazioni e infografiche, è un boomerang. E' un danno cognitivo enorme.

La verità è il consumo culturale generale sta calando livello. Le edicole sono piene di spazzatura, le tv altrettanto. Internet, con la sua legge assoluta delle pageview, è l'impero del male da questo punto di vista. Le cose più lette, più viste, più cliccate, sono quasi sempre spazzatura (e infatti hanno quasi sempre a che fare con la tv). Il motivo è semplice. E' l'intrattenimento la cosa più consumata, perché è quello che non richiede impegno. Il problema è che l'intrattenimento dovrebbe essere neutro. Non dovrebbe impegnare la mente, ma neanche provocarle un danno. Invece ne provoca, e molto. Riempie la testa delle persone di informazioni ridondanti, contraddittorie, consuma capacità di memoria e di computazione. Il cervello finisce per restare inquinato. Tutto questo ha ovvi motivi commerciali, che si possono comprendere. Quello che è criminale è trasformare questo modo di comunicare nel sistema di apprendimento a cui vogliamo affidare la crescita dei nostri figli.

venerdì 15 febbraio 2013

Overfitting è memoria? Questo è il problema

Quasi sempre un problema rappresenta una soluzione a un problema precedente. In questo caso il problema dell'overfitting, esaminato nel post del 13 dicembre, rappresenta un inizio di soluzione a un problema precedente: la memoria. La questione è semplice: come memorizza il cervello? Dove mette le informazioni, come le organizza? Ci sono alcune caratteristiche che ci permettono di escludere una organizzazione a matrice sul modello di un hard disk.

Una è che il cervello ricerca per associazioni di idee e non è in grado di intercettare una singola informazione se non inserita in un contesto (il colore dell'altalena del campeggio lo ricordiamo solo se facciamo lo sforzo di ricordare tutti i dettagli del campeggio, ripercorrendone la pianta, l'organizzazione, le persone conosciute).

Allo stesso tempo, in generale è molto più facile per il cervello stabilire se una cosa è nota, piuttosto che ricordarne tutti i dettagli. Per esempio è più facile dire davanti a una foto se una certa faccia l'abbiamo già vista, piuttosto che visualizzare nella testa un viso e descriverne i tratti. In altre parole, è molto facilitante per il cervello avere davanti un pattern da riconoscere piuttosto che ricordarne uno completamente. Questa seconda cosa è esattamente la stessa che succede con l'overfitting. In effetti l'overfitting è una forma di memorizzazione. E' una forma un po' laterale di apprendimento, in cui invece che "capire la lezione" la si "impara a memoria". Insomma la cosa è comunque molto interessante. Ora, si può dire però che sia un modo compiuto di memorizzare solo se siamo poi in grado di estrarre l'informazione. Di fronte a una immagine, con il metodo illustrato il 1 dicembre (quello che ha il problema dell'overfitting), siamo in grado di dire se appartiene all'insieme di quelle memorizzate, o se si tratti di una nuova. La questione è: siamo anche in grado di estrarre le 40 immagini memorizzate e salvarle da qualche parte in maniera "tradizionale"? Se sì, abbiamo risolto il problema della memorizzazione del cervello. Attualmente un metodo che consenta di farlo non c'è. Trovarlo sarebbe un enorme passo avanti nella costruzione di un cervello artificiale.

Il tema della memoria è centrale per l'intelligenza artificiale: il suo rapporto con l'apprendimento è il cuore stesso del problema dell'intelligenza. Sull'argomento ho già scritto diversi post:
Rete a riconocimento del già visto
Memoria per associazioni? Per una fuga rapida
La memoria

giovedì 13 dicembre 2012

Riconoscimento volti artigianale: il fantasma dell'overfitting

Con l'esempio di rete neurale per il riconoscimento dei volti che ho pubblicato il primo dicembre c'è un problema: l'overfitting. La questione è presto spiegata: il modello è sovradimensionato, ci sono troppi neuroni di ingresso. Con un coefficiente per ciascun neurone, ci sono circa 100mila paramentri. Il risultato è che, con appena 40 esempi in ingresso, la sua capacità di individuare singole differenze tra una immagine e l'altra è troppo fine.

Dal momento che può confrontare ogni singolo pixel tra una immagine e l'altra, succede che prendono il sopravvento dettagli minori: se in un dato punto compaiono capelli o sfondo, se in un altro c'è la maglietta beige o azzurra, e via di questo passo. E' talmente ampio il numero di dettagli su cui il modello può giocare che è in grado di identificare le singole immagini.

Di fatto le memorizza. Il risultato è che riconosce perfettamente le immagini usate in fase di apprendimento ma poi non è in grado di riconoscerne di nuove. Si basa su dettagli insignificanti e in effetti non fa la cosa più importante: non generalizza, non individua le caratteristiche distintive fondamentali tra un volto e l'altro. Come si risolve questo problema? Con alcune tecniche statistiche che riducono l'immagine a pochi parametri sui quali poi la rete può fare apprendimento. Il primo e il più noto è Eigenface.

Il mio interesse in questo studio però era un altro.

A me non interessa studiare il modo più efficiente di riconoscere i volti in sé, mi interessa studiare le reti neurali come lontano parente del meccanismo di funzionamento del cervello. Il tema è come fa il cervello a riconoscere gli oggetti, le persone, e così via, e ad associarvi suoni (che a sua volta impara a distinguere per fonemi) costruendoci poi sopra un linguaggio.

Detto in altre parole, il problema non è solo riconoscere Piero da Carlo, ma anche riconoscere un temperamatite o una sedia da Carlo e perciò ridurre il problema a pochi parametri facciali con un procedimento analitico a monte non risolve affatto il problema, lo aggira. Farei io il lavoro che vorrei capire invece come fa a fare il cervello.

C'è da dire che probabilmente il cervello non ha grossi problemi di overfitting perché ha letteralmente milioni di esempi su cui fare riconoscimento del pattern, avendo a disposizione un flusso di immagini dagli occhi mediamente 16 ore su 24 per anni, e non i miseri 40 di quel modello. Perciò forse, oltre a trovare un modo di ridurre i parametri senza compromettere l'esperimento, c'è anche da introdurre un meccanismo di input a flusso video, in modo da poter campionare migliaia di immagini.

Non appena avrò le idee più chiare su come affrontare il problema lo scriverò qui. Ringrazio molto Raistlin, della lista hackmeeting, per aver segnalato il problema.
 

sabato 1 dicembre 2012

Come farsi in casa una rete neurale per il risconoscimento dei volti

[Nota posteriore: questo procedimento contiene un problema che ho descritto qui]

Cosa sono le reti neurali e perché sono tanto interessanti? Perché sono una trovata matematica grazie alla quale si rovescia la normale logica di programmazione e si può costruire una macchina che impara da sè. Per esempio, le reti neurali sono perfette per riconoscere i volti delle persone. Ecco un programma che, dati due volti, riconosce se abbiamo di fronte l'uno o l'altro. Ecco i due volti sui quali lavoreremo.






PROGRAMMAZIONE CLASSICA
Anche con un programma classico, a impostazione imperativa, si può fare riconocimento dei volti. Basta individuare le caratteristiche distintive delle due immagini. Il primo dei due ragazzi ha capelli più lunghi e più neri, sopracciglia più marcate, naso più lungo. Se prendiamo la matrice di pixel che compone l'immagine possiamo confrontare porzioni equivalenti e stabilire se appartengono all'uno o all'altro ragazzo in modo relativamente semplice. Qual è il problema? Che il programma funzionerà solo con queste due facce, non sarà per niente flessibile. Lo si può fare allora in modo decisamente più complesso, in modo che, dati due volti generici, il programma ne analizzi le porzioni equivalenti e memorizzi le differenze. Così sarà più flessibile, sarà in grado di esaminare qualsiasi faccia, ma il lavoro per scriverlo sarà molto complesso. Questa è programmazione con una impostazione imperativa classica. La programmazione con le reti neurali invece è radicalmente diversa. Di fatto noi non abbiamo bisogno di individuare le caratteristiche distintive tra le due immagini. E' la rete che lo fa.

E come lo fa? Correggendo la propria configurazione interna, in funzione degli esempi che le mettiamo davanti. Le diamo in pasto tante immagini del primo ragazzo, dicendole che è il primo ragazzo, e poi le diamo in pasto tante immagini del secondo, dicendole che è il secondo. Con un processo di correzione progressiva, che è quello viene definito apprendimento, la rete arriva a confingurarsi in modo tale da essere in grado di distingere i due volti.


COS'È UNA RETE NEURALE
Andiamo per ordine. Cos'è una rete? In effetti è ciò che il nome suggerisce: un insieme di nodi collegati tra loro. La caratteristica fondamentale di una rete neurale è quella di avere alcuni nodi di ingresso, alcuni nodi di uscita ed eventualmente alcuni nodi intermedi. Può esserci più di uno strato di nodi intermedi a seconda della complessità della rete.

Questa è una rete semplice, senza nodi intermedi:


Partendo dal generale per andare via via nel dettaglio e nel particolare, l'idea di base è questa: c'è uno scatolotto con alcuni ingressi e una uscita. Nel nostro caso gli ingressi rappresentano i valori dei pixel che compongono l'immagine (i volti sono dentro una bitmap di risoluzione 352x288, quindi abbiamo 101.376 ingressi, uno per pixel) mentre l'uscita sarà una sola e risulterà pari a un numero che identifica il volto: -1 per il primo, 1 per il secondo.

Nella prima fase, quella di apprendimento, forniamo alla rete sia l'ingresso che l'uscita. Le diamo tanti esempi del primo volto, a cui associamo l'uscita -1 e tanti esempi del secondo, dicendo che con quest'altro deve restituire invece +1. Esaurita la fase dell'apprendimento, lo scatolotto dovrebbe essere in grado, data una nuova foto che non aveva mai visto, di restituire l'uscita corretta.

Come lo fa? Generalizzando. Quello che succede è che la rete impara a distinguere le differenze significative da quelle non significative.

In pratica la cosa funziona così: ogni neurone di ingresso è collegato all'unico neurone di uscita. Il valore in arrivo al neurone di uscita sarà perciò la somma dei valori dei neuroni ingresso, vale a dire dei codici colore dei pixel dell'immagine, ognuno moltiplicato per un coefficiente.

Nella figura sono solo due, nel nostro caso sono 101.376, che vanno da x1 a x101376. Ciascun xi è moltiplicato per il rispettivo wi, cioè il relativo coefficiente. Il valore in ingresso al neurone di output è dato perciò da:


L'idea è che i pixel importanti, quelli che permettono di distinguere una immagine dall'altra (per esempio dove ci sono i capelli, che hanno colore diverso tra i due ragazzi), avranno coefficienti alti. Quelli meno importanti (per esempio quelli dello sfondo, la parete azzurrina), che non hanno alcuna importanza nell'individuare di quale dei due volti si tratti, avranno coefficienti molto bassi, prossimi allo zero. In questo modo si ottiene un valore che dà rilievo alle differenze tra le due immagini. Questi sono i pesi sinaptici. Il risultato, cioè a sommatoria complessiva dei valori dei pixel per i rispettivi pesi, poi, a sua volta passa attraverso una funzione di attivazione. Una funzione che si presta molto bene è la tangente iperbolica, che ha questo tipo di risposta:


Perché utilizziamo una funzione di attivazione, in particolare questa? Perché garantisce una uscita sempre compresa tra -1 e +1, qualsiasi sia il dato di partenza. Nel nostro caso, poiché la sommatoria di oltre 100mila ingressi, per quanto i coefficienti possano essere bassi, porta a un valore piuttosto alto, ho diviso - trovando empiricamente il valore vedendo un po' cosa usciva dal programma - il valore per 10.000. Il valore così trovato va in pasto alla tangente iperbolica che restituisce un valore prossimo a -1 o +1 e viene approssimato a quello più vicino.


L'APPRENDIMENTO
Una rete così realizzata è in grado di riconoscere un volto dall'altro se configurata con i coefficienti giusti. E il calcolo dei coefficienti giusti è quello che si definisce apprendimento. Come si calcolano i coefficienti giusti? Per correzioni progressive. All'inizio dell'apprendimento la rete calcola una uscita, in funzione degli ingressi che ha (al primo giro tutti i valori sinaptici valgono zero). Poi corregge i coefficienti in funzione della differenza tra l'uscita calcolata e quella teorica (cioè che noi vorremmo che restituisse, quella che noi abbiamo associato a quella determinata immagine: nel caso del primo volto -1, nel caso del secondo +1).

Dato:
t = risposta teorica
y = risposta calcolata dalla rete con gli attuali pesi sinaptici
xi = valore di ingresso (da 0 a 83.839)

La differenza è calcolata così:

differenza = (t - y) xi (1 - y*y)

(1 - y*y) rappresenta la derivata (y*y vuol dire "y al quadrato") della funzione di attivazione del neurone di uscita, cioè della tangente iperbolica.
Una volta calcolato la differenza, si correggono i pesi sinaptici così:

wi = wi + differenza

Per evitare di andare fuori strada correggendo il differenziale troppo a ogni nuova immagine sottoposta, si applica anche un ulteriore coefficiente, detto tasso di apprendimento, che serve a limitare la velocità del cambiamento. Il tasso di apprendimento è compreso tra 0 e 1 e in genere viene fissato intorno a 0,8. Se a è il tasso di apprendimento, il calcolo della differenza diventa allora:

differenza = a (t - y) xi (1 - y*y)

La cosa migliore è dare in pasto alla rete le immagini in modo incrociato. Cioè prima un esempio del primo volto, poi un esempio del secondo, poi di nuovo uno del primo e così via. In questo modo l'apprendimento è più efficiente, perché a ogni passaggio emergono con più evidenza le differenze tra una e l'altra.

Una volta esaurito il compito, quello che abbiamo è di fatto una matrice di coefficienti, che possiamo salvare, per comodità, ancora in una bitmap, che si presta perfettamente allo scopo, essendo come formato una matrice di valori. Facendo così, poi, è possibile visualizzare l'immagine che ne viene fuori e il risultato è impressionante:

Pesi calcolati con numeri float

Non solo. Ma c'è anche un'altra cosa interessante. Questa è l'immagine che viene fuori se i pesi sono numeri float, cioè numeri con la virgola. Ma se utilizziamo come coefficienti dei numeri interi, allora l'immagine, pur molto simile, perde alcuni dettagli.

Pesi calcolati con numeri interi



DEBOLEZZE DI QUESTA RETE NEURALE
La principale debolezza di questa rete neurale è il fatto che, avendo un solo strato, assegna di fatto un coefficiente a ogni pixel. Quindi le due facce devono trovarsi grosso modo sempre nella stessa posizione. Se in una immagine il volto invece di trovarsi al centro, si trova a destra o a sinistra, la rete perde la bussola. Per renderla in grado di individuare e riconoscere le somiglianze tra le facce anche se l'immagine si sposta, occorrerebbe uno strato di neuroni intermedio. In questo modo il primo strato di connessioni sinaptiche individuerebbero la posizione del volto e il secondo individuerebbe le caratteristiche del volto. L'apprendimento di una rete con uno strato intermedio è però molto diverso da una rete semplice come questa e lo vedremo in futuro.


IL PROGRAMMA
Questi di seguito c'è il programma in C per fare tutto questo.
Il primo file, neuralnet.c, è il programma che, prendendo una immagine, dice se si tratta del primo o del secondo volto.
Il secondo file, training.c, rappresenta il programma di apprendimento della rete e calcola i pesi sinaptici (attenzione: ci sono letteralmente milioni di moltiplicazioni da fare, il processore ci mette del tempo, potrebbe volerci anche qualche ora a completare il processo di apprendimento).
Il terzo file, neuralnet.h, è un file libreria e contiene tutte le funzioni utilizzate nei primi due file.

Se avete una distribuzione Linux i file si compilano così:

gcc neuralnet.c -o neuralnet -lm
gcc training.c -o training -lm

Avete ora così a disposizione i due programmi. Il training si avvia così:

./training

Il programma impara da 40 immagini bitmap di risoluzione 352x288. Le immagini devono essere nella stessa cartella del programma, numerate, e chiamarsi da Image000.bmp a Image039.bmp (con la i maiuscola). Quelle pari, (0, 2, 4, ecc.) devono essere della prima faccia, quelle dispari della seconda. Il programma genera un file che si chiama pesi.bmp nel quale sono registrati tutti i pesi sinaptici.

Esaurito l'apprendimento, si può utilizzare la rete, con il seguente comando:

./neuralnet

L'immagine da verificare deve essere una bitmap di risoluzione 352x288 che si chiama image2test.bmp ed è sempre nella stessa cartella.


CODICE SORGENTE IN C


NEURALNET.C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "./neuralnet.h"
#define num_pixel 76032
#define image_widht 352

int main(void)
{
unsigned int x[num_pixel]; // array dei neuroni dell'immagine di input
float w[num_pixel],x_float[num_pixel]; // array dei pesi sinaptici
unsigned char header_image[54];
char filename_immagine[20]="image2test.bmp"; // nome del file dell'immagine da testare
char filename_pesi[20]="pesi.bmp"; // nome del file dell'immagine dei pesi
float out_neurone[num_pixel];
float out_neurone_uscita = 0;
int j;

if (open_image_int_mono(x,header_image,filename_immagine)==1) 
    {printf("errore di apertura dell'immagine %s",filename_immagine);
    exit (1);}

for (j=0;j<num_pixel;j++)
    {
    x_float[j] = x[j];
    }

if (normalize(x_float)==1) 
    {printf("errore di normalizzazione dell'immagine %s",filename_immagine);
    exit (1);}

if (open_image_float(w,header_image,filename_pesi)==1) // legge l'array dei pesi
    {printf("errore di apertura dell'immagine %s",filename_pesi);
    exit (1);}

/*
// fine della fase di apprendimento
// uso della rete
printf("\nTEST DELLA RETE\n");
printf("la rete risponde con -1 per un'immagine simile a una delle prime 50 immagini di training (0-49)\n");
printf("la rete risponde con +1 per un'immagine simile a una delle seconde 50 immagini di training (50-99)\n");
printf("(ATTENZIONE: i neuroni sono bipolari quindi i valori sono 1 e -1, non i soliti 1 e 0.)\n");
*/

for (j=0;j<num_pixel;j++)
    {
    out_neurone[j] = x_float[j] * w[j];
    out_neurone_uscita = out_neurone_uscita + out_neurone[j];
    }

    out_neurone_uscita = out_neurone_uscita /100000;
    printf("out_neurone_uscita non elaborata: %f\n",out_neurone_uscita);

    out_neurone_uscita=tanhf(out_neurone_uscita);
    printf("out_neurone_uscita con la tang iperbolica: %f\n",out_neurone_uscita);

/* 
attenzione: se il neurone è bipolare come in questo caso e non continuo allora Ú necessario interpretare il valore raggiunto e verificare se supera la soglia, in modo da determinare se vale 1 oppure -1. un neurone bipolare non può avere un'uscita come 3 oppure -3 ma solo 1 e -1.
il comportamento di questi neuroni è:
y = 1 se { sommatoria di tutti i [valore di input della sinapsi per il relativo peso] > 0 }
y = 1 se { sommatoria di tutti i [valore di input della sinapsi per il relativo peso] <= 0 }
(è riportato a pagina 68 del manuale)
*/

if (out_neurone_uscita < 0) printf ("\nvolto 1\n");
if (out_neurone_uscita > 0) printf ("\nvolto 2\n");

//printf("\n y: %f\n",out_neurone_uscita);

}




TRAINING.C

#include <stdio.h>
#include <stdlib.h>
//#include <string.h>
#include <math.h>
#include "./neuralnet.h"
#define num_pixel 101376
#define image_widht 352

void main(void)
{
unsigned int array_image[288][image_widht]; // array dei pixel dell'immagine
unsigned int x[num_pixel]; // vettore monodimensionale che raccoglie i valori dell'array dell'immagine
float x_float[num_pixel]; // vettore che trasforma i valori in float
//unsigned int *punt;
unsigned char header_image[54];
float w[num_pixel]; // vettore dei pesi
unsigned int w_int[num_pixel]; // vettore dei pesi convertiti in intero

//fase di apprendimento

float apprendimento;
char filename[100][20];
char filenamew[20]="pesi.bmp"; // nome del file per salvare i pesi sinaptici calcolati
float t[100];
//float x[num_pixel];
float y,y_tot,y_diviso;
float delta_w;
int i,j,k,p,n;

// nb: num_pixel è il risultato di 288 x image_widht.

apprendimento=0.9;// tasso di apprendimento

strcpy(filename[0],"Image000.bmp"); t[0]=-1;
strcpy(filename[1],"Image001.bmp"); t[1]=1;
strcpy(filename[2],"Image002.bmp"); t[2]=-1;
strcpy(filename[3],"Image003.bmp"); t[3]=1;
strcpy(filename[4],"Image004.bmp"); t[4]=-1;
strcpy(filename[5],"Image005.bmp"); t[5]=1;
strcpy(filename[6],"Image006.bmp"); t[6]=-1;
strcpy(filename[7],"Image007.bmp"); t[7]=1;
strcpy(filename[8],"Image008.bmp"); t[8]=-1;
strcpy(filename[9],"Image009.bmp"); t[9]=1;

strcpy(filename[10],"Image010.bmp"); t[10]=-1;
strcpy(filename[11],"Image011.bmp"); t[11]=1;
strcpy(filename[12],"Image012.bmp"); t[12]=-1;
strcpy(filename[13],"Image013.bmp"); t[13]=1;
strcpy(filename[14],"Image014.bmp"); t[14]=-1;
strcpy(filename[15],"Image015.bmp"); t[15]=1;
strcpy(filename[16],"Image016.bmp"); t[16]=-1;
strcpy(filename[17],"Image017.bmp"); t[17]=1;
strcpy(filename[18],"Image018.bmp"); t[18]=-1;
strcpy(filename[19],"Image019.bmp"); t[19]=1;

strcpy(filename[20],"Image020.bmp"); t[20]=-1;
strcpy(filename[21],"Image021.bmp"); t[21]=1;
strcpy(filename[22],"Image022.bmp"); t[22]=-1;
strcpy(filename[23],"Image023.bmp"); t[23]=1;
strcpy(filename[24],"Image024.bmp"); t[24]=-1;
strcpy(filename[25],"Image025.bmp"); t[25]=1;
strcpy(filename[26],"Image026.bmp"); t[26]=-1;
strcpy(filename[27],"Image027.bmp"); t[27]=1;
strcpy(filename[28],"Image028.bmp"); t[28]=-1;
strcpy(filename[29],"Image029.bmp"); t[29]=1;

strcpy(filename[30],"Image030.bmp"); t[30]=-1;
strcpy(filename[31],"Image031.bmp"); t[31]=1;
strcpy(filename[32],"Image032.bmp"); t[32]=-1;
strcpy(filename[33],"Image033.bmp"); t[33]=1;
strcpy(filename[34],"Image034.bmp"); t[34]=-1;
strcpy(filename[35],"Image035.bmp"); t[35]=1;
strcpy(filename[36],"Image036.bmp"); t[36]=-1;
strcpy(filename[37],"Image037.bmp"); t[37]=1;
strcpy(filename[38],"Image038.bmp"); t[38]=-1;
strcpy(filename[39],"Image039.bmp"); t[39]=1;

for (i=0;i<num_pixel;i++)    // inizializzazione dei pesi sinaptici, uno per ogni neurone di ingresso
    {
    w[i] = 0;
    }

/* QUESTO È STATO COMMENTATO PERCHÉ CONVENIVA INCROCIARE LE IMMAGINI
for (i=0;i<20;i++)    // impostazione dell'uscita desiderata: -1 per le immagini da 0 a 19.
    {
    t[i] = -1;
    }

for (i=20;i<40;i++)    // impostazione dell'uscita desiderata: 1 per le immagini da 20 a 39.
    {
    t[i] = 1;
    }
*/


for (i=0;i<40;i++)
  {
    if (open_image_int_mono(x,header_image,filename[i])==1)
        {printf("errore di apertura dell'immagine %s",filename[i]);
        exit (1);}

    for (j=0;j<num_pixel;j++)
        {
        x_float[j] = x[j];
        }

    if (normalize(x_float)==1)
        {printf("errore di normalizzazione dell'immagine %s",filename[i]);
        exit (1);}

/*     VERBOSE MODE

        for (j=0;j<num_pixel;j++)
        {
        printf("x_float[%i]: %f - x: %u\n",j,x_float[j],x[j]);
        }
*/


    y=0;
    for (j=0;j<num_pixel;j++) // calcolo dell'uscita con i pesi correnti
        {
        y = y + (x_float[j] * w[j]);
        }
    y_tot=y;
    y_diviso=(y/10000);
    y=tanhf(y_diviso); // tangente iperbolica calcolata su un numero float
    printf("i: %i - y totale: %f - y diviso: %f - y iperbolica: %f\n",i,y_tot,y_diviso,y);

    for (j=0;j<num_pixel;j++)
        {
        delta_w = apprendimento * (t[i] -y) * x_float[j] * (1-y*y); // correzione con la differenza con
            // il delta_w contiene tra i fattori la derivata della funzione di attivazione:
            // per la tangente iperbolica corrisponde a (1-y*y). dato che la funzione però non
            // è tang_iperb(x), ma tang_iperb(x/100000), si può scorporare un tang_iperb(1/100000),
            // che è una costante, e quindi può essere mantenuta fattore costante. siccome è
            // è un fattore costante, può essere considerato incorporato nel tasso di apprendimento.
        w[j] = delta_w + w[j];
        //if (i>=20) {printf("i: %i - w%i: %f - delta_w: %f - x_float: %f - y: %f - t: %f\n",i,j,w[j],delta_w,x_float[j],y,t[i]);}
        }
  }

/* VERIFICA DEI VALORI DEI PESI SINAPTICI CALCOLATI
printf("PESI SINAPTICI\n");
for (j=0;j<num_pixel;j++)
    {
    printf("w%i: %f\n",j,w[j]);
    }
*/


/* SALVATAGGIO SU PESI2.BMP DEI PESI CON NUMERI INTERI
for (j=0;j<num_pixel;j++)
    {
    w_int[j]=(int)w[j];
    }

if (write_image_int(w_int,header_image,"pesi2.bmp")==1)
    {
    printf("errore di scrittura dell'immagine pesi2.bmp");
    exit(1);
    }
*/


// salvataggio dei pesi sinaptici calcolati:
if (write_image_float(w,header_image,filenamew)==1)
    {
    printf("errore di scrittura dell'immagine %s",filenamew);
    exit(1);
    }


printf("\nI pesi sinaptici sono stati salvati nel file %s\n\n",filenamew);
}




 NEURALNET.H

#define num_pixel 76032
#define image_widht 352

int open_image_int(unsigned int array[288][image_widht], unsigned char header[54], char filename[20]);
int open_image_int_mono(unsigned int array[num_pixel], unsigned char header[54], char filename[20]);
int open_image_float(float array[num_pixel], unsigned char header[54], char filename[20]);

int write_image_int(unsigned int array[num_pixel], unsigned char header[54], char filename[20]);
int write_image_float(float array[num_pixel], unsigned char header[54], char filename[20]);

int normalize(float array[num_pixel]);

int open_image_int(unsigned int array[288][image_widht], unsigned char header[54], char filename[20])
{
FILE *fp;
int i,j;
unsigned char header_f[54];
unsigned int array_f[288][image_widht];

if ((fp=fopen(filename,"rb"))==NULL) 
    {
        return 1;
    }
   else
    {
        fread(&header_f,sizeof(header_f),1,fp);
        fread(&array_f,sizeof(array_f),1,fp);
        fclose(fp);
        for (i=0;i<54;i++)
            {
            header[i]=header_f[i];
            }
        for (i=0;i<288;i++)
            {
                for (j=0;j<image_widht;j++)
                    {
                    array[i][j]=array_f[i][j];
                    }
            }
    }
return 0;
}

int open_image_int_mono(unsigned int array[num_pixel], unsigned char header[54], char filename[20])
{
FILE *fp;
int i,j;
unsigned char header_f[54];
unsigned int array_f[num_pixel];

if ((fp=fopen(filename,"rb"))==NULL) 
    {
        return 1;
    }
   else
    {
        fread(&header_f,sizeof(header_f),1,fp);
        fread(&array_f,sizeof(array_f),1,fp);
        fclose(fp);
        for (i=0;i<54;i++)
            {
            header[i]=header_f[i];
            }
        for (i=0;i<num_pixel;i++)
            {
            array[i]=array_f[i];
            }
    }
return 0;
}

int open_image_float(float array[num_pixel], unsigned char header[54], char filename[20])
{
FILE *fp;
int i,j;
unsigned char header_f[54];
float array_f[num_pixel];

if ((fp=fopen(filename,"rb"))==NULL) 
    {
        return 1;
    }
   else
    {
        fread(&header_f,sizeof(header_f),1,fp);
        fread(&array_f,sizeof(array_f),1,fp);
        fclose(fp);
        for (i=0;i<54;i++)
            {
            header[i]=header_f[i];
            }
        for (i=0;i<num_pixel;i++)
            {
                array[i]=array_f[i];
            }
    }
return 0;
}

int write_image_float(float array[num_pixel], unsigned char header[54], char filename[20])
{
FILE *fp;
int i,j;
unsigned char header_f[54];
float array_f[num_pixel], *array_punt;

if ((fp=fopen(filename,"wb"))==NULL) 
    {
        return 1;
    }
else
    {
        for (i=0;i<54;i++)
            {
            header_f[i]=header[i];
            }

        for (i=0;i<num_pixel;i++)
            {
            array_f[i]=array[i];
            }

/*
        for (i=0;i<288;i++)
            {
                for (j=0;j<image_widht;j++)
                    {
                    array_f[i][j]=array[i][j];
                    }
            }
*/

        fwrite(&header_f,54,1,fp);
        fwrite(&array_f,sizeof(array_f),1,fp);
        fclose(fp);
    }
return 0;
}

int write_image_int(unsigned int array[num_pixel], unsigned char header[54], char filename[20])
{
FILE *fp;
int i,j;
unsigned char header_f[54];
unsigned int array_f[num_pixel], *array_punt;

if ((fp=fopen(filename,"wb"))==NULL) 
    {
        return 1;
    }
else
    {
        for (i=0;i<54;i++)
            {
            header_f[i]=header[i];
            }

        for (i=0;i<num_pixel;i++)
            {
            array_f[i]=array[i];
            }


/*
        for (i=0;i<288;i++)
            {
                for (j=0;j<image_widht;j++)
                    {
                    array_f[i][j]=array[i][j];
                    }
            }
*/

        fwrite(&header_f,54,1,fp);
        fwrite(&array_f,sizeof(array_f),1,fp);
        fclose(fp);
    }
return 0;
}

int normalize(float array[num_pixel])
{
int i;
float max=0;

float array_f[num_pixel];

    for (i=0;i<num_pixel;i++)
        {
        array_f[i]=array[i];
        }

    for (i=0;i<num_pixel;i++)
        {
        if (array_f[i]>max) { max=array_f[i]; }
        }

    for (i=0;i<num_pixel;i++)
        {
        array_f[i]=(array_f[i]/max)*(array_f[i]/max);
        }

    for (i=0;i<num_pixel;i++)
        {
        array[i]=array_f[i];
        }

return 0;
}




DOWNLOAD
Scarica i sorgenti
Scarica i compilati