La libreria OpenCV - 8 - Support Vector Machine

Tra le tante funzionalità della libreria OpenCV, esiste una sezione di codice incentrata su diverse tecniche di Machine Learning che possono essere utilizzate insieme alle funzioni di Analisi di Immagini e Video per creare programmi molto interessanti: tantissime applicazioni rilasciate negli ultimi anni sfruttano la combinazione di questi due ambiti informatici per identificare particolari oggetti all'interno di immagini. Un possibile esempio sono i face detector già implementati all'interno delle moderne macchine fotografiche digitali.
Un task di questo genere necessità dell'utilizzo di un classificatore, termine che nell'ambito del Machine Learning indica una funzione che associa ad un insieme di elementi un'etichetta (label). Lo scopo del classificatore è perciò quello di identificare la "classe" di un oggetto dell'insieme di input. Per verificare l'efficienza di un classificatore, l'insieme di input è partizionato in due sottoinsiemi: training set e test set. Il primo serve per addestrare il classificatore, il secondo per verificare che la funzione di classificazione trovata sia buona.
In generale l'addestramento del classificatore può essere supervisionato, ovvero il training set è formato da coppie (oggetto, etichetta) e su questi esempi il classificatore genera una funzione, o non supervisionato, in cui non sono presentate al classificatore le etichette degli elementi del training set.

In rete esistono tanti documenti che vi possono spiegare con maggiore dettaglio quello che ho cercato di presentarvi in queste poche righe. Se desiderate una lettura più approfondita in materia, vi consiglio di dare uno sguardo alla versione pdf di The Elements of Statistical Learning, di Hastie, Tibshirami e Friedman.

OpenCV, all'interno della libreria ml, presenta diversi metodi per la classificazione: Neural Networks, K-Nearest Neighbors, Decision Tree e, argomento di questo tutorial, Support Vector Machine.

Le macchine a vettori di supporto nascono come classificatori binari (due classi da distinguere) il cui scopo è apprendere il confine fra le due classi distinte. In particolare, SVM ricerca l'iperpiano di separazione ottimale all'interno dello spazio delle feature. Ovviamente si parla di iperpiano perchè un'oggetto di input può essere identificato da più valori scalari (nel caso semplice dell'esempio presentato di seguito, si parlerà di retta di separazione ottimale in quanto gli oggetti delle due classi sono identificati da due interi, quindi lo spazio delle feature è bidimensionale).

Rappresentazione dell'iperpiano di separazione ottimale all'interno di uno spazio delle feature a due dimensioni. Immagine di www.cnx.org

Nell'immagine le due classi sono rappresentate da un quadrato o da un cerchio. La linea tratteggiata è l'iperpiano di separazione ottimale. Gli oggetti che si trovano invece sulle due linee piene (margini) sono i cosiddetti vettori di supporto, ovvero gli elementi utilizzati effettivamente per il calcolo dell'iperpiano.

Anche in questo caso vi invito a ricercare materiale di approfondimento in rete. L'esempio presentato è di facile comprensione, ma è anche molto ottimistico: il più delle volte gli esempi non sono separabili semplicemente tramite un iperpiano (non linearmente separabili). In questo caso la soluzione è utilizzare il cosiddetto Kernel Trick ed "aumentare" la dimensionalità dello spazio delle feature per trovare un confine tra le due classi. Per quanto riguarda SVM, consiglio questi appunti del professor Vittorio Maniezzo dell'Università di Bologna.

Come già detto, lo scopo del codice sotto riportato è quello di trovare la retta di separazione ottimale per due classi di punti in uno spazio bidimensionale. Dopodichè la funzione di classificazione è testata con due punti appartenenti alle due classi distinte e sono stampati i vettori di supporto per la retta di confine.

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>

using namespace std;
using namespace cv;

int main() {

// Inizializzazione Training Set

	float trainingData[10][2] = { { 0, 1 }, { 1, 2 }, { 1, 1 },
                                      { 2, 1 }, { 1, 3 }, { 2, 2 },
                                      { 3, 2 }, { 10, 15 }, { 10, 10 },
                                      { 12, 13 } };

	Mat trainingDataMat(10, 2, CV_32FC1, trainingData);


	float labels[10] = { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0,
                            -1.0, -1.0 };

	Mat labelsMat(10, 1, CV_32FC1, labels);


// Inizializzazione Parametri del Classificatore

	CvSVMParams params;
	params.svm_type = CvSVM::C_SVC;
	params.kernel_type = CvSVM::LINEAR;
	params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6);

	CvSVM svm;


// Addestramento

	svm.train(trainingDataMat, labelsMat, params);


// Inizializzazione Test Set

	float testData[2][2] = { { 1, 0 }, { 10, 9 } };
	Mat testDataMat(2, 2, CV_32FC1, testData);

	float labelsTest[2] = { 1.0, -1.0 };
	Mat labelsTestMat(2, 1, CV_32FC1, labelsTest);

	float labelPredicted[2] = { 0, 0 };
	Mat labelsPredictedMat(3, 1, CV_32FC1, labelPredicted);

	CvMat testDataCvMat = testDataMat;
	CvMat labelsPredictedCvMat = labelsPredictedMat;


// Test

	svm.predict(&testDataCvMat, &labelsPredictedCvMat);

	double result0 = cvGetReal1D(&labelsPredictedCvMat, 0);
	double result1 = cvGetReal1D(&labelsPredictedCvMat, 1);
	cout << result0 << endl;
	cout << result1 << endl;


// Stampa i Vettori di Support

	int c = svm.get_support_vector_count();
	for (int i = 0; i < c; ++i) {
		const float* v = svm.get_support_vector(i);
		cout << "(" << v[0] << ", " << v[1] << ")" << endl;
	}

}

Nell'inizializzazione del Training Set viene creato un insieme di 10 elementi, rappresentati da punti nello spazio bidimensionale, appartenenti a due classi distinte "1.0" e "-1.0". Poichè la funzione per creare il classificatore prende in ingresso un oggetto di tipo Mat, gli array di float devono essere poi associati ad un oggetto di quel tipo. Il costruttore prevede il numero di elementi dell'array, la dimensione degli elementi, il tipo di elementi (floating point in questo caso) ed un riferimento all'array vero e proprio.

Per poter addestrare il classificatore è necessario prima di tutto definire i parametri da utilizzare. Si dichiara perciò una struttura dati CvSVMParams che sarà in seguito passata alla funzione di training. Gli attributi presentati sono:
- svm_type identifica il tipo di classificatore SVM;
- kernel_type è il tipo di funzione kernel da utilizzare per risolvere la non separabilità degli esempi;
- term_crit sono i criteri di arresto per l'algoritmo iterativo di addestramento.
Questi non sono gli unici parametri che si possono definire. Per l'elenco completo vi invito a dare uno sguardo alla documentazione.

Inizializzati i parametri, si dichiara un'oggetto del tipo CvSVM che rappresenta il classificatore utilizzato. Per addestrare il classificatore si utilizza la funzione train la quale prende come parametri d'ingresso gli oggetti Mat per gli esempi e le relative label, ed i parametri params che sono stati definiti in precedenza. Alla fine dell'addestramento, l'oggetto CvSVM può essere riutilizzato per predire le classi di appartenenza di nuovi elementi, come mostrato nelle ultime istruzioni del codice di esempio.

Per inizializzare il Test Set (di due soli elementi nell'esempio) i passi sono analoghi a quelli visti in precedenza. C'è da notare però che la funzione predict utilizzata prende in ingresso oggetti di tipo CvMat, quindi è necessario convertire da Mat a CvMat prima di passare i parametri. Purtroppo, se andate a vedere la documentazione, non esistono funzioni predict che prendono in ingresso oggetti di tipo Mat e restiuiscono anche i risultati della predizione in un altro oggeto Mat. Per questo motivo nell'esempio ho preferito utilizzare CvMat.

Nella parte finale del codice sono mostrati a video i risultati predetti dal classificatore appena addestrato ed i vettori di supporto individuati per create la retta di confine tra le due classi.
Risulta chiaro che questo esempio è stato ideato in due dimensioni per rendere facile la visualizzazione delle due classi. Dal punto di vista pratico però una cosa del genere non serve quasi a niente: in situazioni reali i vettori di feature hanno una dimensionalità molto più alta di 2. In questi casi quindi basterà semplicemente definire adeguatamente le dimensioni dei vettori di test e training, senza dover toccare le funzioni train e predict.

Come sempre, vi invito a lasciare commenti o a contattarmi per dubbi o imprecisioni nell'articolo. Questo sicuramente non è un argomento semplice che possa essere trattato in un breve articolo come quelli del sito. Vi consiglio quindi nuovamente di consultare altri documenti più teorici per capire precisamente come funziona il classificatore SVM nel caso in cui siate nuovi a questi argomenti.

Alessio Antonielli

Ingegnere informatico, appassionato di cinema, musica, videogiochi e serie tv. Insomma, le solite cose che vi aspettereste da un ex studente di Ingegneria Informatica, giusto per rafforzare lo stereotipo…

La libreria OpenCV - 8 - Support Vector Machine ultima modifica: 2013-01-13T16:55:18+01:00 da Alessio Antonielli


Advertisment ad adsense adlogger