Le Macchine possono vederti

Sulla costruzione di algoritmo di riconoscimento visivo


Avrete certamente notate la mia più che settimanale assenza. Purtroppo sono stato impegnato per motivi lavorativi: per farmi perdonare, ho deciso di produrre un bell’articolo (con tanto di programma Python) su un argomento iper attuale: la Machine Vision.

Intro

La Machine Vision raccoglie tutte le tecniche algoritmiche per il riconoscimento automatico di immagini. In essenza, utilizzando queste tecniche, è possibile insegnare ad una macchina a riconoscere gli oggetti più disparati (anche in tempo reale): l’applicazione più semplice é ad esempio la lettura automatica di testi. Come quando vi arriva la multa a casa, perché siete entrati come dei fessi nella ZTL: la telecamera, partendo da una foto, ha automaticamente letto la targa del vostro carro.

La teoria

A prima vista potrebbe sembrare che algoritmi di tale portata possano essere complessi e sostanzialmente distaccati dalla teoria di Data Science. In realtà, l’algebra lineare è ancora una volta onnipresente e viene in aiuto. Difatti, i metodi di Machine Vision non differiscono di molto dai metodi di Data Science in generale. Le differenze più marcate sono in come i dati (i.e. le immagini) vengono pre-processate, prima di essere inviate ad un classificatore (e.g. una Random Forest o una Rete Neurale).

Ordunque, qual’è la logica generale da seguire? Le immagini presentano un inevitabile problema di fondo: sono “pesanti” dal punto di vista di utilizzo memoria. Infatti, anche una piccola foto in b\n di dimensioni (in pixel) 100×100 rappresenta, per un calcolatore, una matrice di 10.000 elementi. Triplicate il valore per una in RGB.

Dunque, a meno di avere a disposizione una elevata potenza di calcolo, il classificatore impiegherà un tempo non trascurabile per stimare tutti i parametri.

Per ovviare a questo problema, non si deve nutrire il modello con le immagini pure, ma bisognerà estrarre delle misure (in inglese features) che riassumano l’informazione contenuta in esse. Di queste misure ne esistono di tutti i gusti ed è sostanzialmente impossibile riassumerle tutte. Come esempio, si prendano quelle utilizzate nel programma descritto più avanti nell’articolo.

Le features diventano dunque le nostre nuove variabili. In linea generale, il nostro problema viene nuovamente ridotto al generale:

Y=f(X)

dove X è un vettore di lunghezza p contenente i valori per le p misure e  Y= \in \{0,1, . . ., k\} rappresentante le k classi di immagini.

Un semplice esempio per chiarire: costruiamo un algoritmo per permettere al computer di riconoscere il colore blu dal rosso e viceversa, utilizzando immagini di 10×10 pixels. In questo caso avremo n immagini, alcune completamente rosse e altre blu. Per abbiamo scelto 120.

Innanzitutto, importiamo le librerie che ci serviranno:

from PIL import Image
import numpy as np
import os
from os import walk
from sklearn.ensemble import RandomForestClassifier as RF
from sklearn import cross_validation

Ora doppiamo si procede con la creazione delle cartelle e delle immagini. Al tempo stesso si deve anche creare il vettore della variabile dipendente (indicato con Y).


# -------------------------------------------------------------------------------------- #
# Controlla l'esistenza di due cartelle per salvare le immagini. Se non presenti, le crea
directories = ["/foo/rosso/", "/foo/blu"]
for dir in directories:
    if not os.path.exists(dir):
        os.makedirs(dir)
# -------------------------------------------------------------------------------------- #

files = []
t = 0  # contatore
size = 10 # dimensione k, in pixel, di immagini kxk
iterations = range(0, 60) # costruisce 60 immagini
Y = []

for u in iterations:

    k1 = int(np.random.uniform(0, 100)) # Valori aleatori per introdurre variabilità nei colori
    k2 = int(np.random.uniform(0, 100))
    k3 = int(np.random.uniform(0, 100))
    k4 = int(np.random.uniform(0, 100))

    col = [["rosso/", (255, k1, k2)], ["blu/", (k3, k4, 255)]] # paletta colori

    for n,i in col:
        im = Image.new("RGB", (size, size))
        pix = im.load()

        for x in range(size):
            for y in range(size):
                pix[x, y] = i # setta il colore dei pixel
                
        filename = directory + n + str(t) + ".png"
        im.save(filename, "PNG")
        files.append(filename) # crea la lista dei filenames
        Y.append(n[0:-1]) # Crea il vettore variabile dipendente con le classi
        t += 1

Una volta creato il dataset e la variabile dipendente, si può procedere all’estrazione delle misure, che andranno a creare la nostra matrice X delle variabili indipendenti. Come già accennato, la scelta di quali features includere è notevolmente specifica al problema da risolvere. In questo semplice esempio, la distribuzione dei colori dei pixel sui 3 canali (RGB) è sufficiente.

Nota tecnica: nell’ultimo passaggio logico, se in R, sarebbe meglio vettorizzare la funzione anziché evocarla in loop. Ma usando Py, il for non crea troppi problemi.

print("Extracting features")
def extract_features(path):

    ''' Extract features from image of MetroBank Clapham Junction: colours distributions, sdev of the colours, number
    of objects and several measurements on those objects.
    Input: path - string with path of the picture
    Output: a vector '''


    # Resizing to 1000x1000, pixel distribution for each colour channel according to a fixed number of bins
    im = Image.open(path)
    im_arr = np.array(im)

    hist_R = np.histogram(im_arr[:, :, 0].flatten()) # Distibuzione Rosso
    hist_G = np.histogram(im_arr[:, :, 1].flatten()) # Distribuzione Verde
    hist_B = np.histogram(im_arr[:, :, 2].flatten()) # Distribuzione Blu

    features = np.concatenate((hist_R[0], hist_G[0], hist_B[0], hist_R[1], hist_G[1], hist_B[1]), axis=0)

    features = np.reshape(features, (1, len(features)))
    return (features)

# ------------------------------------------------------------------------------- #
# Evoca la funzione 'extract_features()' in loop. In R sarebbe meglio vettorizzare.

X = np.zeros((len(files), 63)) # ncol da modificare a seconda del'entrata
i = 0
for im in files:
    feat = extract_features(im)
    X[i, 0:len(feat[0])] = feat[0]
    i += 1
    print(i/len(files)*100, '%', ' done')

E alla fine, basta costruire un classificatore: in questo caso, data la semplicità dell’esempio, ho scelto una normale Random Forest. Facile da utilizzare avendo pochi parametri, non crea troppi problemi di overfitting e funziona straight out of the box.

# -------------------------------------------------------------------------------------------- #

# Stima del modello: Random Forest

print("Training")
# n_estimators is the number of decision trees
# max_features also known as m_try is set to the default value of the square root of the number of features
clf = RF(n_estimators=100, n_jobs=3)
scores = cross_validation.cross_val_score(clf, X, Y, cv=5, n_jobs=1) # Effettua la Cross Validation con 5 folds
print("Accuracy of all classes")
print(np.mean(scores)) # Stampa la media del punteggio di Cross Validation (accuracy in questo caso)

Se eseguite il programma, vedrete che l’accuracy media è di 1: abbiamo dunque costruito un modello perfetto per distinguere i colori di semplici immagini. L’accuracy di 1 tuttavia non è sempre un buon segno: potrebbe voler dire che il nostro modello è in over-fitting, ossia è troppo adattato ai dati osservati ma non è generalizzabile. In questo caso però non rischiamo di cadere in questo problema poiché il training set é molto semplice e praticamente privo di rumore.

Enjoy!

Quando la Varianza segue la Media: i GLS

Seppur noi umani amiamo etichettare tutti gli eventi e i processi usando categorie ben definite, la realtà è molto più polimorfa. Questa natura delle cose si registra anche in Statistica.

Difatti, abbiamo parlato finora di regressione avendo come modello:

\hat{y} = \beta_{0} + \beta_{1}x + \epsilon ~ N(0,1)

Una delle principali assunzioni di questo modello riguarda gli errori \epsilon che vengono considerati come distribuiti secondo una Normale con media 0 e deviazione standard di 1. In questo caso dunque non esiste alcuna relazione tra la media e la varianza.

Nella vita reale però, lavorando con dati reali, questa assunzione può essere tutto fuorché vera: ci sono molte situazioni in cui tra la media e la varianza esiste una relazione, ossia:

\sigma^{2}_{i} = f(\hat{y_{i}})

In questi casi, ci vengono in aiuto i GLS (Generalized Least Squares). I GLS introducono una forma funzionale esplicita per la varianza degli errori. Le più comuni presentano una relazione positiva fra le due variabili:

  • Relazione di Potenza\hat{y} = \beta_{0} + \beta_{1}x + \epsilon ~ N(0,\sigma^{2}|\hat{y}_{i}|^{2m_{1}})
  • Relazione Esponenziale\hat{y} = \beta_{0} + \beta_{1}x + \epsilon ~ N(0,\sigma^{2}exp(2m_{2}\hat{y_{i}}))

m_{1}m_{2} sono due parametri che devono essere stimati col GLS.

I fantastici Big Data e dove trovarli

Data Science non è l’unico nuovo termine germogliato nel mondo della statistica. In particolare, il primo premio per uso erroneo di queste nuove parole va ai famigerati Big Data.
Il caro lettore avrà sicuramente già sentito o letto a riguardo e ciò che avrà potuto capire sarà letteralmente inutile: descrizioni corrette del termine sono molto rare dato che è nella natura umana riempirsi la bocca con termini di cui non si capisce un belino (il caro vecchio dialetto ligure).

Ora, cosa sono i Big Data e come si definiscono? Al contrario di ciò che i profani pontificano, questo nome non definisce assolutamente un grande dataset: la dimensione notevole (quantificata in bytes e multipli) è una condizione necessaria ma non sufficiente.

Definizione

Il termine nacque nei primi anni 2000 quando il data scientist Douglas Laney formalizzò i Big Data usando le famose 4 V:

  • Volume
  • Variety (Varietà)
  • Velocity (Velocità)
  • Veracity (Veracità)

Analizzandole singolarmente:

  • Volume indica una grande dimensione del dataset in termini di bytes. Ovviamente, non esiste un valore univoco che faccia da spartiacque. Tuttavia, come linea di massima, si considerano Big Data volumi nell’ordine dei Tb e multipli.
  • Variety si riferisce alla forma di questi dati, che è molto varia: da dati numerici e strutturati (ossia ben organizzati e analizzabili dalla macchina), a stringhe di testo o video.
  • Velocity caratterizza i Big Data in quanto un dataset degno di questo aggettivo deve essere live, ossia ricevere continuamente dati, o comunque aggiornato ogni breve lasso di tempo.
  • Veracity indica invece l’accuratezza dei dati, ossia quanto questi dati siano affidabili nel misurare un certo evento/processo, che per i Big Data è generalmente molto bassa.

Come sarà dunque chiaro, per essere definito Big Data, un dataset deve essere dinamico, di enormi dimensioni, contenente dati in diversi formati e di una qualità dubbia.

Dove trovarli

Ovviamente, dataset di questo tipo sono molto rari; non tanto a causa della loro natura ma a causa delle difficoltà che insorgono nella loro gestione e raccolta. Infatti, per poter trarre vantaggio dai Big Data, sono necessarie particolari architetture di calcolo e conservazione (come Hadoop o pacchetti di parallel computing in R) che solo organizzazioni con notevoli risorse possono disporre. Inoltre, i Data Scientist addetti a Big Data devono sviluppare capacità tecniche specifiche per la gestione di tali dati.

L’industria in cui i Big Data regnano sovrani è quella delle Telecomunicazioni: ogni scambio di dati tra dispositivi genera una grande quantità di metadati (quanto è durato lo scambio, posizione geografica degli interlocutori, che tipo di scambio etc.) che vengono naturalmente salvati e analizzati dai provider per generare informazioni e prendere decisioni.
Anche l’industria finanziaria fa un uso sostanziale dei Big Data: la borsa di NY, solo per il comparto azionistico, genera 1 TB di dati.

Ogni. Singolo. Giorno.

Comunque, in generale, ogni industria che ha a che fare con un grande numero di transazioni giornaliere è un buon candidato per generare Big Data.

Qualità ed Affidabilità

Come detto sopra, una delle caratteristiche dei Big Data è di essere di dubbia qualità. Difatti, seppure in grandi volumi, molte volte questo tipo di dati contiene poco potenziale informativo: ossia è spesso difficile, se non impossibile, riuscire ad estrarne del significato.
Questo perché i processi che hanno generato questi dati sono logicamente semplici e molto comuni; in altre parole, una modellazione di tali eventi genererebbe risultati banali: un esempio di tale problema fu apportato dal VP for Product Innovation di Netflix durante una intervista, in cui ha definito i Big Data di tale azienda come 99% fatto di spazzatura. Ovvio, l’importante è riuscire a trovare il diamante dell’1%.