Scarica versione pdf - Dipartimento di Matematica e Informatica

Esercitazione di Social Media Management A.A. 2016­2017
Regressione Lineare
Antonino Furnari http://www.dmi.unict.it/~furnari/ ­ [email protected] Prof. Giovanni Maria Farinella http://www.dmi.unict.it/~gfarinella/ ­ [email protected]
Si consiglia la consultazione online
In questa esercitazione impareremo come:
Analizzare un insieme di osservazione mediante Principal Component Analysis (PCA);
Costruire un modello di regressione lineare per predire la “memorability” di una immagine a
partire da features di tipo Bag Of Visual Words;
Stimare l’accuratezza del modello mediante errore assoluto medio (Mean Absolute Error ­ MAE) e
indice di correlazione per ranghi di Spearman.
1. Convenzioni
Lo scopo di questa esercitazione è guidare il lettore attraverso la comprensione di concetti chiave e la loro
implementazione pratica. Per facilitare la comprensione degli argomenti proposti, durante l'esercitazione,
viene richiesto al lettore di rispondere ad alcune domande e risolvere alcuni esercizi pratici. Tali richieste
sono indicate dai seguenti simboli:
Questo simbolo è presente laddove viene richiesto
al lettore di rispondere a una domanda.
Questo simbolo è presente laddove viene richiesto a
lettore di svolgere un esercizio.
Questo simbolo indica una nota al lettore.
Questa icona contrassegna i contenuti non
essenziali ai fini dell'esercitazione. L'esercitazione
può essere svolta in modalità "fast track" saltando i
contenuti contrassegnati da questo simbolo. L'icona
è generalmente riportata in alto a sinistra nel
paragrafo interessato.
Questa icona indica un collegamento a una pagina di approfondimento. Si tratta
di materiale non indispensabile ai fini dell'esercitazione ma che può comunque
tornare utile. L'icona è generalmente riportata in alto a destra nel paragrafo
interessato, cliccandovi sopra è possibile accedere al contenuto di
approfondimento. Gli approfondimenti segnalati da questa icona sono anche
riportati tra le referenze.
Durante l'esercitazione, verrà richiesto di sviluppare alcune funzioni necessarie a svolgere i passi
successivi. Le soluzioni non sono riportate in questa esercitazione
Le funzioni richieste si trovano nel modulo solution che verrà fornito al completamento dell'esercitazione.
In [1]:
#importiamo tutte le funzioni del package solution che ci serviranno in seguito #Nota: questa istruzione è qui solo per "motivi tecnici" #Ai fini di questa esercitazione, non è richiesta l'esecuzione di questa istruzione #che non andrebbe a buon fine vista l'assenza del modulo "solution" from solution import * 2. Requisiti
Ambiente di lavoro basato su python 2.7 (consigliato IPython + IDE). L'ambiente di lavoro può
essere impostato installando una delle principali distribuzioni Python:
Anaconda (https://www.continuum.io/downloads);
Canopy (https://www.enthought.com/products/canopy/).
Librerie richieste:
scipy (https://www.scipy.org/);
numpy (http://www.numpy.org/);
matplotlib (http://matplotlib.org/);
scikit­image (http://scikit­image.org/).
È inoltre richiesto aver seguito le seguenti esercitazioni:
Gestione di dataset di immagini: http://www.dmi.unict.it/~furnari/teaching/SMM1617/lab0/;
Bag of Visual Word Models: http://www.dmi.unict.it/~furnari/teaching/SMM1617/lab1/;
In particolare, il file dataset.py (scaricabile da qui) deve essere posizionato nella directory di lavoro.
Utilizzeremo anche le funzioni sviluppate nell'esercitazione su Bag of Visual Word Models, che sono
riportate nel file bovw.py (scaricabile da qui). Posizionare anche questo file nella directory di lavoro.
3. Dataset
In questa esercitazione utilizzeremo un dataset di immagini acquisite nel contesto di uno studio su
“Image Memorability” (vedi referenza [1]). Il dataset contiene 2222 immagini di risoluzione 256×256
pixels. Le immagini sono corredate da una serie di valori stimati mediante un gioco di memoria visuale al
quale hanno partecipato diversi soggetti utilizzando la piattaforma Amazon Mechanical Turk.
Tra i valori forniti, noi considereremo gli “hits”, ovvero il numero di volte in cui ogni immagine è stata
effettivamente riconosciuta come “già vista” quando ripresentata ai partecipanti. Il dataset è disponibile
all’indirizzo
http://web.mit.edu/phillipi/Public/WhatMakesAnImageMemorable/
Tuttavia, considerate le dimensioni dell’archivio (1.7 GB), per questa esercitazione utilizzeremo una
versione “più leggera” del dataset in cui tutte le immagini sono state compresse in formato JPEG. Il dataset
è già stato diviso in training e test set dopo un riordinamento casuale delle immagini. Per risparmiare
tempo, ai fini di questa esercitazione, vengono inoltre fornite le rappresentazioni estratte dalle immagine a
partire da un modello di tipo Bag of Visual Words.
Scarichiamo il dataset da http://yoda.dmi.unict.it/memorability_python.zip e scompattiamo l’archivio nella
nostra directory di lavoro. All’interno della cartella "memorability" troveremo:
Una cartella "training_set" contenente una cartella "Images" con 1555 immagini in formato JPEG
(immagini di training). Il nome di ogni immagine rappresenta un indice numerico che definisce un
ordinamento univoco;
Una cartella "test_set" contenente una cartella "Images" con 667 immagini in formato JPEG
(immagini di test). Il nome di ogni immagine rappresenta un indice numerico che definisce un
ordinamento univoco;
Il file “training_hits.npy”, che contiene il numero “hits” per ogni immagine di training. I valori sono
ordinati secondo la numerazione definita dai nomi dei file JPG;
Il file “test_hits.npy”, che contiene il numero “hits” per ogni immagine di test. I valori sono ordinati
secondo la numerazione definita dai nomi dei file JPG;
Il file “features.pkl”, che contiene le features di tipo Bag Of Visual Words pre­computate per ogni
immagine come specificato di seguito.
Iniziamo costruendo due oggetti di tipo Dataset a partire dalle immagini di training e test appena estratte
dall'archivio:
In [2]:
from dataset import Dataset training_set = Dataset('memorability/training_set') test_set = Dataset('memorability/test_set') La classe Dataset è stata progettata per gestire dataset di immagini appartenenti
a più classi. In questa esercitazione, affronteremo un problema di regressione e
non di classificazione. Pertanto, il dataset conterrà una unica "pseudo­classe"
chiamata "Images".
Verifichiamo che il dataset sia stato correttamente caricato e visualizziamo alcune immagini:
In [3]:
print "Number of images in the training set:{0}".format(training_set.getLength()) print "Number of images in the test set:{0}".format(test_set.getLength()) print "Total number of images:
{0}".format(training_set.getLength()+test_set.getLength()) print "List of classes ", training_set.getClasses() training_set.showImage('Images',18) print "Shown image path: %s" % training_set.getImagePath('Images',18) Number of images in the training set:1555 Number of images in the test set:667 Total number of images:2222 List of classes ['Images'] Shown image path: memorability/training_set\Images\0019.jpg Carichiamo dunque il numero di "hits" per ogni immagine del training set:
In [4]:
import numpy as np training_hits=np.load('memorability/training_hits.npy') test_hits=np.load('memorability/test_hits.npy') print training_hits.shape
print test_hits.shape (1555L,) (667L,) Possiamo dunque ispezionare il numero di hits relativo a una data immagine:
In [5]:
from skimage import io as sio from matplotlib import pyplot as plt im=sio.imread(training_set.getImagePath('Images',23)) plt.figure() plt.imshow(im) plt.title("Hits: {0}".format(training_hits[23])) plt.show() Esercizio 3.1 Scrivere il codice per visualizzare tutte le immagini del training set con i rispettivi
numeri di hits in sequenza. Suggerimento: utilizzare la funzione `plt.pause(n)` tra
due esecuzioni consecutive di `imshow` in modo da visualizzare l'immagine per
`n` secondi.
Domanda 3.1 Notate una correlazione tra contenuto visivo dell’immagine e numero di hits?
Esercizio 3.2 Visualizzare le immagini con il numero più basso e più alto di hits. Suggerimento:
vedere la documentazione della funzione di numpy argmax.
4. Rappresentazione
Adesso eseguiamo tutti i passi necessari per creare un modello di tipo Bag Of Visual Words e estrarre le
feature da tutto il dataset. Ci avvarremo delle funzioni sviluppate nell'esercitazione precedente.
La costruzione di un modello di tipo Bag Of Visual Words e la rappresentazione di
tutte le immagini del training e test set possono essere dispendiose in termini di
tempo. Pertanto, ai fini di questa esercitazione, sono state fornite le feature pre­
computate per ogni immagine di training e test. E' dunque possibile saltare il
punto 4.1 (o leggerlo velocemente) per passare direttamente al punto 4.2. Si
consiglia di ritornare sul punto 4.1 a casa, una volta ultimata l'esercitazione.
4.1 Rappresentazione delle immagini e salvataggio su file (da
fare a casa)
Per creare il modello di tipo Bag Of Visual Words, utilizzeremo alcune delle funzioni sviluppate
nell'esercitazione su Bag Of Visual Words model. Per ottenere una rappresentazione più accurata,
utilizzeremo step=4 (valore di default) nel processo di estrazione delle feature locali DAISY. Le funzioni,
modificate in modo da utlizzare il corretto valore di step sono definite all'interno del file bovw.py
(scaricabile da qui), che va posizionato nella directory di lavoro.
from bovw import extract_features training_local_features = extract_features(training_set) from bovw import describe_dataset from sklearn.cluster import MiniBatchKMeans as KMeans kmeans = KMeans(500) kmeans.fit(training_local_features) training_features,_,_=describe_dataset(training_set,kmeans) test_features,_,_=describe_dataset(test_set,kmeans) Normalizziamo le feature mediante norma L2:
from sklearn.preprocessing import Normalizer norm = Normalizer(norm='l2') training_features=norm.transform(training_features) test_features=norm.transform(test_features) Quando si affrontano problemi di regressione, è comune chiamare le feature X e i target y. Pertanto
poniamo:
X_training = training_features y_training = training_targets["Images"] X_test = test_features y_test = test_targets["Images"] Salviamo dunque le feature su file:
import cPickle with open("memorability/features.pkl","wb") as f: cPickle.dump({ "X_training" : X_training, "y_training" : y_training, "X_test" : X_test, "y_test" : y_test },f) 4.2 Caricamento delle feature da file (se si è saltato il passo
4.1)
Dato che l’operazione può essere molto dispendiosa in termini di tempo computazionale, carichiamo il
modello e le feature pre­computate per questa esercitazione:
In [6]:
import cPickle with open("memorability/features.pkl") as f: data = cPickle.load(f) X_training = data["X_training"] y_training = data["y_training"] X_test = data["X_test"] y_test = data["y_test"] Domanda 4.1 Che dimensioni hanno le matrici X_training e X_test? Questo tipo di matrice ha un
nome particolare in machine learning, quale?
5. Principal Component Analysis
Quando si utilizza l’equazione normale per trovare i parametri ottimali di un regressore lineare, si
possono avere problemi relativi alla non­invertibilità di (X T X) −1 , dove X è la matrice delle
osservazioni. Ciò è generalmente dovuto alla presenza di “ridondanza” tra le feature (diverse parole visuali
che codificano pattern visivi molto simili, nel nostro caso) o all’eccessivo numero di feature rispetto al
numero di osservazioni. Per prevenire questo tipo di problema, trasformeremo i nostri dati utilizzando una
tecnica chiamata Principal Component Analysis (PCA).
Data una matrice di osservazioni X[n × m] (con n numero di osservazioni e m numero di feature), la
PCA trova una matrice C [m × p] tale che:
¯¯¯¯
W = X (C
¯¯¯¯
T
)
−1
¯¯¯¯
dove X è la matrice delle osservazioni centrata nello zero (X i
= Xi − μi
, con μi valore medio di Xi nel
training set), (C ) è la pseudo­inversa di C e W è una matrice [n × p] contenente n osservazioni di
dimensionalità generalmente ridotta p ≤ m.
T
−1
T
In questa sede, non scenderemo nei dettagli matematici della PCA (per i quali si è rimandati alla relativa
pagina di wikipedia. Ci basta solo sapere che la nuova matrice delle osservazioni W , ha le seguenti
caratteristiche:
Le features sono ordinate dalla “più importante” alla “meno importante”. Ciò vuol dire che la
maggior parte dell’informazione è contenuta nelle prime features;
Le features sono generalmente “decorrelate” e quindi sono meno inclini a presentare dipendenza
lineare.
Le medie μi e la matrice di proiezione C dipendono dai dati di training che abbiamo utilizzato per stimarli.
Si suppone che, se la trasformazione PCA definita da questi valori è "buona" per i dati di training, essa
sarà altrettanto "buona" per quelli di test. Pertanto, una volta stimate le medie μi e la matrice C , è
possibile applicare la stessa trasformazione PCA al training e al test set in modo da avere dati dalle
caratteristiche analoghe.
Il calcolo dei valori di μi e C e l'applicazione di una data trasformazione PCA su nuovi dati, può essere
ottenuta mediante l'oggetto PCA di sklearn. In particolare, possiamo "allenare" il modello PCA mediante le
seguenti righe di codice:
In [7]:
from sklearn.decomposition import PCA as PCA pca = PCA() pca.fit(X_training); Il metodo fit ha stimato le medie μi e la matrice C a partire dai dati di training passati in input. E' possibile
accedere ai valori stimati come segue:
In [8]:
mu = pca.mean_ C = pca.components_ print mu.shape print C.shape (500L,) (500L, 500L) A questo punto, per applicare la trasformazione PCA imparata, potremmo utilizzare la formula vista prima:
¯¯¯¯
W = X (C
T
)
−1
che si traduce in python come segue:
In [9]:
W=np.dot((X_training‐mu),np.linalg.inv(C)) print W.shape (1555L, 500L) Tuttavia, mediante la libreria scikit­learn è sufficiente scrivere:
In [10]:
X_training_pca = pca.transform(X_training) X_test_pca = pca.transform(X_test) print X_training.shape, X_training_pca.shape print X_test.shape, X_test_pca.shape (1555L, 500L) (1555L, 500L) (667L, 500L) (667L, 500L) In questo caso le dimensioni di X_training_pca e X_test_pca sono rimaste invariate (500 feature). Tuttavia,
le feature sono adesso state ordinate in maniera "più significativa".
Per capire in che modo sono stati trasformati i dati, proviamo a plottare in uno spazio 2D i campioni
presenti in X_training e X_training_pca selezionando solo le prime due componenti:
In [11]:
from matplotlib import pyplot as plt plt.figure(figsize=(10,4)) plt.subplot(121) plt.plot(X_training[:,0],X_training[:,1],'bx') plt.subplot(122) plt.plot(X_training_pca[:,0],X_training_pca[:,1],'rx') plt.show() Domanda 5.1 Che differenze notate tra i due plot? Quale insieme appare “centrato” rispetto allo
zero? Quale insieme sembra contenere meno ridondanza?
In pratica, questa nuova rappresentazione delle osservazioni ci permette di:
Avere meno problemi quando si utilizza la regressione in forma normale;
Avere un’idea del modo in cui i nostri dati si dispongono nello spazio selezionando solo le prime
due componenti.
6. Costruire un regressore lineare multivariato per
predire la “memorability” di una immagine
Alleneremo un modello di regressione lineare che, presa in input la rappresentazione di una
immagine X = (X1 , X2 , … , Xn ) ne stimi il numero di hits (ovvero la "memorability") y mediante il
modello:
y = θ0 + X1 θ1 + X2 θ2 + … + X500 θ500
Nell'equazione riportata sopra, y è la variabile dipendente (numero di hits), X1 , … , X500 sono i 500
predittori (le 500 feature estratte mediante BOVW e trasformate secondo PCA) θ0 è l’intercetta, e θ1 , … , θ500 sono i regressori. Per poter utilizzare il modello di regressione riportato sopra, dobbiamo
prima "allenarlo", cioè stimare i parametri (regressori) θ0 , … , θ500 a partire dalle immagini del training set.
Per allenare il modello, utilizzeremo l'oggetto LinearRegression della librearia scikit­learn che ha una
interfaccia analoga a quella di PCA:
In [12]:
from sklearn.linear_model import LinearRegression lr = LinearRegression() lr.fit(X_training_pca,y_training); E' possibile accedere ai parametri stimati come segue:
In [13]:
theta_0 = lr.intercept_ theta_1_500 = lr.coef_ print theta_0 print theta_1_500.shape 52.3009646302 (500L,) Domanda 6.1 Perché il parametro θ0 viene chiamato "intercept_" all'interno del modello di
regressione lineare?
Se vogliamo rappresentare graficamente come il numero di hits varia al variare delle osservazioni e come
si comporta il nostro regressore lineare, selezioneremo un solo predittore, ignorando gli altri. Dal momento
che la PCA ha “ordinato” le feature per importanza, considereremo la prima feature. Il modello si riduce a:
y = θ 0 + X1 θ 1
Visualizziamo innanzitutto come la variabile dipendente y varia al variare del predittore X1 :
In [14]:
plt.figure() X1 = X_training_pca[:,0] plt.plot(X1,y_training,'x') plt.show() A questo punto possiamo visualizzare la retta di regressione definita dal nostro modello lineare
semplificato:
In [15]:
plt.figure() plt.plot(X1,y_training,'x') theta_1=theta_1_500[0] x=np.arange(‐0.6,0.8,0.1) plt.plot(x,theta_0+x*theta_1,'r') plt.show() Dato che stiamo effettuando una regressione multivariata, questo plot “cattura” cosa succederebbe se i
valori delle feature superiori alla prima fossero nulli.
Per avere un’idea di come la regressione “si comporta” all’aumentare del numero di feature, passiamo
dallo spazio bidimensionale a quello tridimensionale considerando i primi due predittori X1 e X2 . Il nostro
modello lineare diventa:
Y = θ 0 + X1 θ 1 + X2 θ 2
Visualizziamo i dati in tre dimensioni, mediante i comandi:
In [16]:
from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() plt.subplot(111, projection='3d') X1 = X_training_pca[:,0] X2 = X_training_pca[:,1] plt.plot(X1,X2,y_training,'o') plt.show() Il plot appena ottenuto è un plot tridimensionale. Pertanto è possibile "esplorarlo"
cambiandone il punto di vista. Per farlo, provate a cliccare su un punto qualsiasi
del grafico e trascinare con il mouse. Si noti che, nel caso in cui si utilizza jupyter,
sarà generata una immagine statica. Per ottenere una versione esplorabile del
grafico si digiti: ```python %matplotlib qt ``` prima di lanciare il plot.
Per poter effettuare i plot in 3 dimensioni, abbiamo inserito un subplot all'interno della figura appena creata
e utilizzato la keyword projection='3d' per specificare che il plot sarebbe stato in 3 dimensioni. Per
maggiori informazioni sui plot 3D è possibile consultare la documentazione di matplotlib.
Il modello di regressione lineare in 3 dimensioni non è più una retta ma un piano individuato
dall'equazione:
z = θ0 + θ1 x + θ2 y
Dove x e y sono due variabili indipendenti, mentre z è la variabile dipendente. Possiamo visualizzare il
piano come segue:
In [17]:
fig = plt.figure() plt.subplot(111, projection='3d') theta_2 = theta_1_500[1] x_range= np.arange(‐1,1,0.1) y_range= np.arange(‐1,1,0.1) x,y=np.meshgrid(x_range,y_range) plt.plot(X1,X2,y_training,'x') plt.gca().plot_surface(x,y,theta_0+theta_1*x+theta_2*y,shade=False,color='r') plt.show() La funzione “meshgrid” permette di creare una griglia bidimensionale di valori a partire da due vettori
unidimensionali.
In [18]:
x=[1,2,3,4,5] y=[1,2,3,4,5,6,7,8] [x_grid,y_grid]=np.meshgrid(x,y) print "x:",x print "y:",y print "x_grid:\n",x_grid print "y_grid:\n",y_grid x: [1, 2, 3, 4, 5] y: [1, 2, 3, 4, 5, 6, 7, 8] x_grid: [[1 2 3 4 5] [1 2 3 4 5] [1 2 3 4 5] [1 2 3 4 5] [1 2 3 4 5] [1 2 3 4 5] [1 2 3 4 5] [1 2 3 4 5]] y_grid: [[1 1 1 1 1] [2 2 2 2 2] [3 3 3 3 3] [4 4 4 4 4] [5 5 5 5 5] [6 6 6 6 6] [7 7 7 7 7] [8 8 8 8 8]] La funzione “plot_surface” permette di plottare una superficie nel mondo 3D (un piano nel nostro caso).
Domanda 6.2 la retta e il piano di regressione sono “ragionevoli” rispetto ai dati considerati?
Quanto errore pensate che il regressore stia compiendo?
Esercizio 6.1 Visualizzare i due grafici come quelli di sopra anche per il test set. Che risultati
abbiamo? La retta e il piano di regressione “si comportano” in maniera simile
rispetto ai nuovi dati?
In [19]:
fig = plt.figure() plt.subplot(111, projection='3d') X1=X_test_pca[:,0] X2=X_test_pca[:,1] theta_2 = theta_1_500[1] x_range= np.arange(‐1,1,0.1) y_range= np.arange(‐1,1,0.1) x,y=np.meshgrid(x_range,y_range) plt.plot(X1,X2,y_test,'x') plt.gca().plot_surface(x,y,theta_0+theta_1*x+theta_2*y,shade=False,color='r') plt.show() A questo punto possiamo ottenere le predizioni relative a training e test set mediante il metodo “predict”:
In [20]:
training_predictions = lr.predict(X_training_pca) test_predictions = lr.predict(X_test_pca) Esercizio 6.2 Scrivere una funzione predict_and_show che prenda in input:
un oggetto di tipo Dataset;
le rappresentazioni X delle immagini del dataset (già trasformate
mediante PCA);
il modello di regressione imparato;
i valori target relativi alle immgini del dataset (ad esempio `y_training` o
`y_test`);
un indice numerico;
e mostri l'immagine del dataset corrispondente all'indice specificato, con il
numero di hits predetto e il relativo valore di ground truth. La funzione dovrà dare
un risultato simile al seguente:
In [21]:
predict_and_show(training_set,X_training_pca,lr,y_test,1) predict_and_show(test_set,X_test_pca,lr,y_test,18) Domanda 6.3 Il regressore lineare che abbiamo allenato funziona? Come potremmo valutarne
la bontà?
7.2 Mean Absolute Error (MAE)
Il Mean Absolute Error (MAE) è l’errore assoluto medio che compiamo nelle predizioni. In pratica, date le
^ restituite dal nostro modello, il MAE è definito come:
variabili target yi e le predizioni y
i
^ ) =
M AE(yi , y
i
1
n
n
^ |
∑ |yi − y
i
i=1
Esercizio 7.1 Calcolare il MAE relativo a training e test set. Quale valore è più basso?
Domanda 7.1 Considerando che il MAE rappresenta l’errore medio in valore assoluto che
compiamo quando cerchiamo di predire il numero di “hits” che una immagine
totalizzerebbe nel memory game, l’errore che commettiamo è alto o basso?
7.3 Indice di correlazione per ranghi di Spearman
L’indice di correlazione per ranghi di Spearman indica quanto due variabili siano dipendenti. Si tratta
di un numero compreso tra ­1 (massima anticorrelazione) e 1 (massima correlazione). Secondo
questo indice, due variabili sono correlate positivamente (rips. negativamente) quando una può essere
espressa come una funzione monotona crescente (risp. decrescente) dell’altra. Più la monotonia è
“perfetta” più il numero si avvicina a 1 o ­1, come mostrato negli esempi di seguito:
L'indice di correlazione di spearman può essere calcolato mediante la funzione spearmanr di scipy (si
veda la documentazione):