Abbiamo già parlato delle reti neurali. Una rete neurale è, in sintesi, una fitta interconnessione di elementi semplici chiamati percettroni. Tali unità, disposte in due o più strati, sono in grado di adattarsi tramite algoritmi particolari in modo tale che dato un ingresso X la rete “apprenda” l’uscita Y. Non entrerò oltre nel dettaglio, la materia è infatti piuttosto complicata acnhe trattata in modo superficiale.

A questo punto è bene precisare che questa procedura è quasi esclusivamente una mia particolare speculazione. Non ho letto nessuna pubblicazione al riguardo ma dai miei piccolissimi test sembra funzionare correttamente. Ma veniamo al punto.

Supponiamo di avere una rete neurale come quella in figura:

Abbiamo una rete con 4 ingressi binari e altrettante uscite. Poi, subito prima dell’uscita e subito dopo l’ingresso, ci sono due blocchi nascosti. Tali blocchi sono composti da uno o più strati della rete, l’importante è che le due sotto-reti siano simmetriche. Infine abbiamo due strati centrali, l’importante è che tali strati contengano un numero di unità minori di quelle in ingresso e uscita.

A questo punto addestriamo la rete in modo tale che ad un ingresso X corrisponde un uscita Y del tutto uguale all’uscita. Vi chiederete, a che diavolo serve una rete neurale che non faccia altro che dare in uscita lo stesso valore in ingresso? A nulla, ovviamente, ma proviamo a separare la rete in due reti all’altezza dello strato centrale. In questo modo abbiamo due reti: una con 4 ingressi e 2 uscite e una con 2 ingressi e 4 uscite.

Per il modo in cui la rete è stata addestrata possiamo trattare la prima rete come un coder e  la seconda rete è un decoder. L’utilizzo di questa rete può essere, ad esempio, di codificare un’immagine spezzandola a blocchi di 4 bit e passandola nel coder. Il risultato sarà un file ridotto del 50%. La decodifica, analogamente, avviene spezzando il file codificato in blocchi da 2 bit per riottenere i 4 bit originali.

È bene precisare che questa compressione è solitamente lossy (quindi ha utilità solo per codifiche video/audio/immagini) in quanto le reti neurali solitamente sono “approssimazioni” oltre che statisticamente fonte di errore. Inoltre, il caso di 4 bit ridotti a 2 è poco efficente, solitamente andrebbero usato un ingresso molto più numeroso e un fattore di compressione variabile. Più il valore di compressione è alto più, ovviamente, la possibilità di errori e l’approssimazione aumenta.

Il vantaggio di questa tecnica è che, poiché la rete può essere facilmente addestrata partendo da pezzi del file da codificare, la rete apprende la migliore codifica da N a M per quello specifico file.

Questo è un piccolo esempio di come sia possibile utilizzare una tecnologia nata dall’intelligenza artificiale per compiti che apparentemente non hanno nulla a cui spartire con essa come la codifica multimediale.

 

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.

© 2008-2012 SlashCode Suffusion theme by Sayontan Sinha