Dopo un bel pò di tempo (troppo per la verità) finalmente vediamo di mettere in pratica quello che vi ho raccontato nei post precedenti (1, 2) riguardo all' Apprendimento Automatico. Per coloro che vogliono approfondire di più la conoscenza dell' Apprendimento Automatico con particolare riferimento alle SVM esistono diverse risorse in rete tra le quali spiccano:

  1. www.kernel-machines.org (sito contenente informazioni generali sia di carattere tecnico che relativo ai vari eventi internazionali)
  2. www.kernel-methods.net (sito contenente informazioni riguardanti il libro "Kernel Methods for Pattern Analysis",testo scritto da due guru del settore quali John Shawe-Taylor e Nello Cristianini che tratta in modo esaustivo le diverse tipologie di kernel con relativi esempi di casi d' uso)
  3. www.support-vector.net (sito contenente informazioni riguardanti il libro "Support Vector Machines ", scritto anch'esso da John Shawe-Taylor e Nello Cristianini, tratta come dice il titolo stesso delle SVM )
  4. http://robotics.stanford.edu/people/nilsson/mlbook.html (sito contenente un Draft liberamente scaricabile che tratta delle varie tipologie di Apprendimento Automatico)

Passando alla pratica esistono diverse soluzioni software che implementano le SVM: una tra le più utilizzate è sicuramente SVMLight, sviluppata in c, che permette sia la risoluzione di problemi di classificazione che di regressione; l' implementazione presa in esame invece si basa sull' utilizzo della libreria LIBSVM sviluppata da Chih-Chung Chang and Chih-Jen Lin. LIBSVM giunta alla versione 2.84 offre due implementazione dellle SVM, una in Java e l'altra in Python anche se esistono numerosi altre implementazioni tra cui un paio in C#. Delle due implementazioni preferisco di gran lunga quella effettuata da Matthew Johnson in quanto più che una mera conversione dalla versione Java ha effettuata un' implementazione basata su di un design idiomatico; vediamo quali siano i passi da eseguire per testare le funzionalità dell' implementazione di LIBSVM fornita da Matthew; innanzitutto per poter essere addestrata qualsiasi SVM ha bisogno di un DataSet che comprenda sia un file di training sul quale creare un modello, sia un file di testing con il quale validarlo: in rete ci sono molteplici fonti da cui poter prelevare insiemi di dati, il mio consiglio cmq è quello di utilizzare Dataset compatibili con LIBSVM che possono essere prelevati qui. Il formato infatti di un DataSet compatibile con LIBSVM è il seguente:

  <label> <attribute>:<value> <attribute>:<value>


                    ESTRATTO DATASET REALE
  LABEL    ATTR1    ATTR2    ATTR3    ATTR4    ATTR5
  -----    -----    -----    -----    -----    -----
    1        0       0.1      0.2       0        0
   -1        0       0.1      0.3     -1.2       0
   -1       0.4       0        0        0        0
    1        0       0.1       0       1.4      0.5
    1      -0.1     -0.2      0.1      1.1      0.1

Ogni file di training comprende un insieme di esempi di training ( 5 nel caso in esame); ogni esempio di training a sua volta è composto da una label che ne indica la classificazione (ad esempio mail di spam oppure no) e una serie di coppie attributo-valore; l' esempio che sopra vi ho mostrato vale sia come file di training che come file di testing infatti in quest' ultimo le varie label vengono utilizzate per calcolare l' accuratezza della macchina;

Ora che abbiamo visto quale sia il formato dei vari DataSet analizziamo il domain model con il è stato modellato il dominio delle SVM analizzando il seguente class diagram:

 

                                   

 

La classe Problem presenta una proprietà Count che indica il numero di esempi del DataSet, una proprietà MaxIndex utilizzata in fase di normalizzazione dei valori dei vari attributi (scaling), una proprietà Y di array di double , rappresentanti le etichette di classificazione ed un jagged array di Node rappresentante il singolo vettore di training.

La classe Model rappresenta il modello che la SVM calcola dai vettori di training di input, contiene proprietà che tengono traccia sia dei vettori di supporto (SV) calcolati sia proprietà di parametrizzazione atte a regolare i parametri con cui viene calcolato il modello (ad esempio tipo di Kernel e relativi parametri);

La classe Node infine incapsula la coppia attributo-valore componente basilare di ogni vettore appartenente al DataSet.

A questo punto definiamo una possibile implementazione del codice che, caricati i dati, costruisce il problema di apprendimento:

private static Problem ParseData(FileStream stream)
        {
            StreamReader reader = new StreamReader(stream);
            List<double> Y = new List<double>();
            List X = new List();
            int maxIndex = 0;
 
            while (!reader.EndOfStream)
            {
                List dataEntry = new List();
                Regex regex = new Regex(@"[-+]*(?\d*):*(?\d*)\s");
                Match MatchResults = regex.Match(reader.ReadLine());
                Y.Add(Double.Parse(MatchResults.Value));
                MatchResults = MatchResults.NextMatch();
                while (MatchResults.Success)
                {
                  Node nodo = new Node();
                  nodo.Index = Int32.Parse(MatchResults.Groups["nodeFeature"].Value);
                  nodo.Value = Double.Parse(MatchResults.Groups["nodeValue"].Value);
                  maxIndex = Math.Max(maxIndex, nodo.Index);
                  dataEntry.Add(nodo);
                  MatchResults = MatchResults.NextMatch();
                }
                X.Add(dataEntry.ToArray());
            }
            return new Problem(Y.Count, Y.ToArray(), X.ToArray(), maxIndex);
        }

 

Effettuato il Parsing dei dati e costruito il problema possiamo passare al costruzione del modello utilizzando il seguente codice:

 

private static Model TrainPhase(string trainfile, string modelFileName)
       {
           
           FileStream stream = File.OpenRead(trainfile);
           Problem problem = ParseData(stream);
           RangeTransform range = Scaling.DetermineRange(problem);
           problem = Scaling.Scale(problem, range);
           Parameter parameters = new Parameter();
           Model model = Training.Train(problem, parameters);
           Model.Write(modelFileName, model);
           return model;
       }

Gli step operativi da eseguire per addestrare una SVM sono quindi i seguenti:

  1. Parsing dei dati che descrivono il problema
  2. Normalizzazione dei valori degli attributi (Scaling) effettuata sia per semplificare la complessità computazionale che per evitare che feature con valori elevati contribuiscano maggiormente rispetto alle feature che presentano bassi valori  
  3. Settaggio dei parametri che controllano l' addestramento; solitamente vengono utilizzati i parametri di default anche se molto spesso per avere una corretta accuratezza è necessario effettuare una ottimizzazione tramite Grid Search o metodi simili 
  4. Addestramento della macchina fornendo il problema e i parametri precedentemente impostati

Una volta eseguito l' addestramento abbiamo un modello costruitoci dalla nostra SVM che, ricevendo come input un file di test dovrebbe (il condizionale è sempre d' obbligo :-) ) predirre in modo corretto le classi di appartenenza dei vettori di dati. A questa pagina potete trovare sia i sorgenti di LIBSVM che alcune demo veramente carine....

Ad maiora

Technorati tags: ,