Si pensa spesso che grandi rivoluzioni siano accompagnate da grandi e complicate implementazioni. Tuttavia la storia ci insegna che le grandi idee spesso sono le più semplici.
Nell’IA sono proprio queste idee semplici a dare i migliori risultati. Fra queste un ottimo esempio è dato dall Reti Neurali. Questo modello matematico consiste, fondamentalmente, nell’interconnessione di unità a soglia.
Non entrerò nei dettagli matematici di una rete neurale ma voglio mostrare un codice di 290 righe (commenti e linee vuote incluse) che implementano una semplicissima rete neurale non ricorsiva addestrata a con retropropagazione.
Non credo sia la versione più “elegante” ne la più ottimizzata ma funziona discretamente.
# -*- coding: utf-8 -*-
"""
Modulo che contiene classi e metodi per rappresentare
una rete neurale non ricorsiva.
Modulo Didattico
"""
import math
class Sigmoid :
"""
Rappresenta una singola unità sigmoide.
"""
def __init__(self, num_in = 0) :
"""
Costruttore.
Args:
num_in (int) Numero di ingressi della rete neurale.
"""
self.w = [1.0]*num_in
self.delta_value = [0,0]
def __repr__(self) :
return repr(self.w)
@staticmethod
def scalar(v1, v2) :
"""
STATIC. Prodotto scalare fra i vettori v1 e v2.
Args:
v1 (vector) Vettore 1
v2 (vector) Vettore 2
Returns:
Il prodotto scalare fra v1 e v2. (double)
"""
res = 0
for i in range(len(v1)) :
res += v1[i]*v2[i]
return res
def sigma(self, ingresso) :
"""
Ritorna il valore della funzione sigma del prodotto scalare fra
ingresso e i pesi del sigmoide.
Args:
ingresso (vector) Vettore d'igresso
Returns:
Il valore della funzione sigma. (double)
"""
s = Sigmoid.scalar(self.w, ingresso)
return 1 / (1 + math.e**(-s))
def activate(self, ingresso) :
"""
Ritorna il valore di soglia per il prodotto scalare fra
l'ingresso e i pesi del sigmoide.
Args:
ingresso (vector) Vettore d'ingresso.
Returns:
1 se il prodotto scalare è maggiore di zero, 0 altrimenti.
"""
s = Sigmoid.scalar(self.w, ingresso)
if s > 0 : return 1
return 0
def add_in(self, w = 0) :
"""
Aggiunge un ingresso al sigmoide.
Args:
w (double) Peso del nuovo ingresso.
"""
self.w.append(w)
def change(self, i, new) :
"""
Cambia il valore del peso dell'ingresso i-esimo.
Args:
i (int) Numero dell'ingresso.
new (double) Nuovo peso per l'ingresso.
"""
if i > len(self.w) :
print "ERROR: "
return
self.w[i] = new
self.delta_value[0] = 0
class NeuralNet :
"""
Classe che rappresenta una rete neurale generica non ricorsiva.
"""
def __init__(self, n_in) :
"""
Costruttore.
Il numero di ingressi NON comprende l'ingresso fittizio
che simula la soglia.
Args:
n_in (int) Numero di ingressi della rete.
"""
self.units = []
self.n_in = n_in
def __repr__(self) :
return repr(self.units)
def add_strate(self) :
"""
Aggiunge uno strato alla rete.
"""
self.units.append([])
def add_unit(self, j, w = None) :
"""
Aggiunge un unità sigmoide allo strato j.
Args:
j (int) Numero di strato.
w (vector) Vettore dei pesi della nuova unità.
"""
if j >= len(self.units) :
print "ERROR: "
return
# Se è il primo strato i sigmoidi hanno un numero di ingressi
# pari al numero di ingressi della rete più l'ingresso fittizio.
# Altrimenti è pari al numero dei sigmoidi dello strato precedente
# più l'ingresso fittizio.
if j == 0 :
sig = Sigmoid(self.n_in + 1)
else :
sig = Sigmoid(len(self.units[j-1]) + 1)
self.units[j].append(sig)
# Aggiorna il peso del nuovo sigmoide con i pesi nel vettore W.
if w != None and len(w) == len(sig.w):
p = 0
for i in w :
sig.change(p, i)
p+=1
# Se NON è l'ultimo strato bisogna aggiungere un ingresso a tutti
# i sigmoidi dello strato successivo.
if j != ( len(self.units) - 1 ) :
for i in self.units[j+1] :
i.add_in()
def activate(self, ingresso) :
"""
Attiva la rete neurale sull'ingresso 'ingresso'. Il
vettore di ingresso NON contiene il vettore fittizio.
Args:
ingresso (vector) vettore di ingresso.
Returns:
uscita (vector) vettoore di uscita.
"""
if len(ingresso) != self.n_in :
print "ERRORE"
return
current = ingresso[:]
current.append(1)
for j in self.units :
next_in = []
for i in j :
next_in.append(i.activate(current))
current = next_in[:]
current.append(1)
return next_in
def list_f(self, ingresso) :
"""
Lista le uscite di ogni sigmoide della rete in base
all'uscita della funzione sigmoide.
Args:
ingresso (vector) Vettore di ingresso.
Returns:
Il vettore contenente il risultato dell'applicazione di un
vettore di ingresso PER OGNI sigmoide.
"""
if len(ingresso) != self.n_in :
print "ERRORE"
return
current = ingresso[:]
current.append(1)
res=[]
for j in self.units :
next_in = []
for i in j :
next_in.append(i.sigma(current))
current = next_in[:]
current.append(1)
res.append(next_in)
return res
def delta(self, list_out, strato, sigmoide, desire) :
"""
Calcola il valore delta per l'i-esimo sigmoide del j-esimo strato.
La foruma del delta è:
delta(i,j) = f * (1 - f) * SUM ( delta(j+1, l) )
dove l varia per ogni sigmoide dello strato successivo.
Se j = strato finale allora
delta(i,j) = (desire - f) * f * (1 - f)
Args:
list_out (vector) Lista delle uscite della rete.
strato (int) Numero dello strato.
sigmoide (int) Numero del sigmoide.
desire (double) Valore desiderato in uscita per l'ingresso.
Returns:
Il valore delta cercato.
"""
f = list_out[strato][sigmoide]
sig = self.units[strato][sigmoide]
if sig.delta_value[0] == 1 :
return sig.delta_value[1]
if strato == ( len(self.units) - 1 ) :
ris = ( desire - f) * f * (1 - f)
else :
ris = f*(1-f)
cumulator = 0
for l in range(len(self.units[strato + 1])) :
wijl = self.units[strato+1][l].w[sigmoide]
cumulator += self.delta(list_out, strato+1, l, desire) * wijl
ris = ris * cumulator
sig.delta_value = [1, ris]
return ris
def delta_reset(self) :
"""
Invalida i delta_value dei sigmoidi.
"""
for j in self.units :
for i in j :
i.delta_value[0] = 0
def train(self, ingresso, desire, c = 1.0) :
"""
Addestra una rete neurale sull'ingresso inserito.
La formula dii retro propagazione è
W(i,j) = W(i,j) + c * delta(i,j) * X(i,j)
doeve W e X sono vettori.
Args:
ingresso (vector) Vettore in ingresso.
desire (double) Valore desiderato in uscita.
c (double) Velocità di apprendimento.
"""
f = self.list_f(ingresso)
x = f[:] # Questa lista è come 'f' ma include anche l'ingresso.
# Sia x che f NON includono gli ingressi fittizi.
x.insert(0, ingresso)
for j in range(len(self.units)) :
for i in range(len(self.units[j])) :
wij = self.units[j][i].w[:] # Sono i pesi del sigmoide (i,j)
delta = self.delta(f, j, i, desire)
for k in range(len(wij)) :
if k == ( len(x[j]) ) : # Compensa la mancanza dell'ingresso
xx = 1 # fittizio.
else :
xx = x[j][k]
new = self.units[j][i].w[k] + c * delta * xx
self.units[j][i].change(k, new)
self.delta_reset()
Per capire a fondo il codice è necessario conoscere un po’ di teoria sulle reti neurali, per tutti gli altri vi basti notare come una delle più importanti metodologie dell’AI può essere replicata nei suoi tratti essenziali in meno di 300 righe.
molto interessante… c’è anche un esempio di uso ?
Le reti neurali possono venire usate ogni volta che un algoritmo necessita il calcolo di una funzione, ma non si può (o non è conveniente) scrivere manualmente la funzione.
Ecco qualche esempio: http://it.wikipedia.org/wiki/Rete_neurale#Applicazioni_e_propriet.C3.A0