Il Machine Learning ha assunto grande rilievo grazie all’elevata efficienza che esibisce nella soluzione di problemi complessi come il riconoscimento di immagini o vocale. Il valore di queste tecniche è legato alla potenza di calcolo dei moderni computer e alla ricchezza di informazioni disponibili in forma digitale (social network, Internet of Things, eBooks, …).

Il settore applicativo a cui questo articolo si interesserà è quello del Natural Language Processing (NLP) o Elaborazione del Linguaggio Naturale. Con questa dicitura (volutamente) generica, vengono etichettati tutti gli sforzi volti a rendere un computer in grado di “comprendere” il nostro linguaggio, parlato e scritto.

Quindi ad esempio il NLP si interessa del riconoscimento di caratteri (OCR), della sentiment analysis (analisi “sentimentale”) di un testo, riconoscimento del parlato o della classificazione di testi per argomento. In quest’ultima categoria si inserisce il progetto che descriverò, che si propone di etichettare in modo automatico (classificazione) il genere di una canzone dall’analisi del suo testo.

Classificazione di testi

In generale la classificazione di testi cerca di rispondere alla domanda: dato un documento, quale categoria lo etichetta meglio? Vediamo subito che ci sono due componenti in questo problema: un input (il testo del documento) identificato con una serie di features (caratteristiche); un output (l’argomento) detto class (classe).

Compito dell’algoritmo di apprendimento è imparare a riconoscere la relazione fra input ed output, grazie ad una serie di esempi. In genere le features di un testo rappresentano la presenza (o numero di occorrenze) di determinate parole o insiemi di parole al suo interno. Nel seguito indicherò le features con la variabile [latex]X[/latex] e le classi con [latex]y[/latex].

diagramma_apprendimento

Mappa concettuale del processo di apprendimento, utilizzando un database di esempi

Nel Machine Learning tutto parte dai dati e infatti per rendere operativo il nostro algoritmo abbiamo bisogno del cosiddetto training set, composto da una serie di esempi input-output. Utilizzando questi esempi il nostro algoritmo cerca di comprendere il meccanismo alla base della classificazione dei documenti, per poter effettuare una scelta su nuovi input non catalogati. Il primo passo è quindi quello di procurarsi i dati, nel mio caso una raccolta di testi di canzoni e corrispondente genere musicale.

Questo che ho descritto è un problema di apprendimento supervisionato (supervised training), caratterizzato dalla disponibilità di esempi features/class. Differenti sono i problemi di apprendimento non supervisionato (unsupervised) in cui sono disponibili unicamente le features. O ancora di reinforcement learning (apprendimento per rinforzo) in cui è presente anche una funzione in grado di valutare la performance del nostro algoritmo e di adattarlo per cercare di massimizzarla.

Prima di proseguire: ho fatto tutto utilizzando Python 2.7 linguaggio di alto livello che è molto quotato nell’ambito del ML. Il mio codice fa schifo e mi vergogno di farvelo vedere Se qualcuno si mostrasse interessato potrei pensare di ripulire il codice e renderlo disponibile.

Raccolta dei dati

Dicevamo che per prima cosa ci servono dei dati su cui lavorare e affinare il nostro algoritmo. Per i testi delle canzoni ho fatto un becero scraping del sito A-Z Lyrics Database che mi ha fornito circa 450’000 testi di canzoni di una grande varietà di generi musicali.

Tuttavia A-Z Lyrics non registra il genere delle canzoni, quindi ho utilizzato la Spotify Web API per trovarli. Al netto di canzoni senza genere o in lingua non inglese, mi sono ritrovato con un database di circa 300’000 canzoni (attribuite a circa 14’000 artisti).

Per chi fosse interessato, lo scraping è stato fatto utilizzando le librerie standard urllib e urllib2, con l’aiuto della magica libreria BeautifulSoup, per estrarre i contenuti dalle pagine recuperate. In alternativa la libreria Scrapy è molto potente (ma non avevo voglia di imparare ad usarla).

L’API di Spotify ritorna una lista di generi musicali associati ad ogni artista, per un totale di non-so-nemmeno-quanti generi. Quello che ho fatto, per avere un numero gestibile di classi, è stato etichettare tutti i sottogeneri con il loro genere padre. Quindi ‘technical brutal death metal’ diventa ‘metal’, ‘underground power pop’ diventa ‘pop’.

Genere Rock Pop Metal Hip Hop Jazz Electronic  R&B  Latin
# Canzoni 48139 37306 20903 7033 5077  1522 1122 89

Rimuovendo le canzoni identificate da più di un genere, o da nessuno degli 8 che ho scelto, ne sono rimaste circa 121 mila. La mia può sembrare una scelta molto drastica – e lo è, non lo nego – ma necessaria per rendere il problema trattabile. Per scegliere i generi mi sono ispirato a una discussione su questo forum.

Il prossimo problema che ho dovuto fronteggiare è stato quello di formattare e ripulire i testi per migliorare le prestazioni del mio programma.

Pre-elaborazione dei dati

Gli algoritmi di classificazione di testi applicano diversi paradigmi per rendere i dati più “digeribili” e per trascurare features dal basso contenuto informativo. Fra i più comuni ci sono:

  • Eliminazione di punteggiatura/numeri/simboli (questo può essere però controproducente in alcuni casi, come eliminare “#” dai tweet).
  • Portare tutto in minuscolo (anche in questo caso a volte può essere sconsigliabile).
  • Eliminare le stopwords: si tratta di parole molto frequenti in tutto il corpus di documenti (e.g. ‘un’, ‘lo’, preposizioni, ecc). Data la loro ubiquità, la capacità di discriminare le classi è trascurabile.
  • Stemming/lemmatisation/lemma reduction: si tratta del procedimento di ridurre (conflate) le parole alla loro forma base (e.g. ‘corre’, ‘corrono’, ‘correvi’ vengono ridotte a ‘correre’ o ‘corre’). In questo modo si riduce il numero di parole, conservando però, ai fini della classificazione, lo stesso contenuto di informazione (notare che ridurre ‘bellissimo’ in ‘bello’ quando si fa la sentiment analysis può essere svantaggioso).
  • Includere gli n-grams: si tiene conto, nella scelta delle features, di quelle sequenze di n parole (generalmente 2) che appaiono spesso insieme.

Di tutti questi metodi, io ho applicato i primi tre. Una volta normalizzati i documenti, è importante trovare il modo giusto di rappresentarli così che l’algoritmo possa elaborarli in modo efficiente. Come ho già menzionato, in genere si adotta un approccio detto bag-of-words, per cui ogni documento è rappresentato dalle parole che vi appaiono, eventualmente con relativa frequenza. All’occorrenza alle singole parole possono aggiungersi gli n-grams (vedi sopra). L’ultimo passo, per evitare un carico computazionale troppo elevato, è la selezione delle parole più frequenti come features (io ad esempio ne ho utilizzate quasi sempre 5’000).

Logistic Regression

Veniamo allora al pezzo forte di ogni applicazione di ML: l’algoritmo di apprendimento. Ci sono molti algoritmi che ottengono buoni risultati nella classificazione testuale. Fra i più semplici ci sono: i Naïve Bayes classifiers e la Logistic Regression (vedi fonti). I Naïve Bayes si basano sull’assunzione (grossolana) che l’occorrenza di una parola sia indipendente da quella di ogni altra parola. Un po’ sorprendentemente, però, l’efficienza di questi classificatori è molto buona, soprattutto quando il training set è ridotto all’osso. La Logistic Regression è invece un metodo un po’ più raffinato, che si può interpretare come una generalizzazione dei Naïve Bayes. Dato che avevo un dataset abbastanza ampio, ho deciso di usare la Logistic Regression.

Caution, math ahead!

Caution, math ahead! Le formule che ho riportato potrebbero risultare un po’ indigeste, se lo desiderate potete saltare al paragrafo conclusivo di questa sezione.

Consideriamo il caso di classificazione con due sole classi, detta classificazione binaria. Indichiamo con [latex]x_i[/latex] le singole feature, che insieme compongono il vettore [latex]X[/latex] e con [latex]y\in\{0,1\}[/latex] le due classi. La probabilità che un documento [latex]d[/latex] appartenga alla classe 0 è:

[latex]\mathbb{P}(y=0|X) = 1/\left[1+exp(\beta_0+\Sigma_i\beta_i x_i)\right],[/latex]

o alla classe 1:

[latex]\mathbb{P}(y=1|X) =exp(\beta_0+\Sigma_i\beta_i x_i)/\left[1+exp(\beta_0+\Sigma_i\beta_i x_i)\right].[/latex]

I parametri [latex]\beta_i[/latex] sono detti coefficienti di regressione e il nostro obbiettivo in fase di apprendimento è stimarli utilizzando il training set. Per fare questo, detto [latex]N[/latex] il numero delle coppie documento [latex]d_j[/latex] / classe [latex]y_j[/latex] nel training set, dobbiamo risolvere questo problema di ottimizzazione:

[latex]\hat{\beta}=\underset{\beta}{argmax}\sum_{j=1}^{N} log(\mathbb{P}(y_j|d_j)).[/latex]

La formula può essere tradotta così: trova i parametri che massimizzano la probabilità di decidere correttamente con quale classe etichettare un documento. Una volta trovati i coefficienti di regressione, dato un nuovo documento, calcoliamo le probabilità che appartenga a una delle due classi e scegliamo quella con probabilità maggiore.

Rimando alle fonti per una trattazione più approfondita e per il caso di multiclass classification, cioè con più di due classi possibili.

Risultati

Veniamo ora ai risultati concreti del mio “esperimento”. Per prima cosa ho fatto alcune prove con la classificazione binaria, scegliendo le coppie di classi con il maggior numero di canzoni. Nella tabella ci sono le percentuali di classificazioni corrette sul training set e sul cosiddetto validation set, ovvero un insieme di dati non utilizzati per l’apprendimento.

Generi Training (2/3) Validation (1/3)
Pop VS Rock 76.04% 70.21%
Pop VS Metal 91.85% 85.54%
Rock VS Metal 92.69% 87.94%

Vediamo, prima di tutto, che gli errori sul training set sono sempre meno che quelli sul validation set. Questo è un risultato intuitivo, se si pensa che il nostro classificatore è stato addestrato proprio sul training set e quindi massimizza il numero di previsioni correte che fa con quei dati. Possiamo anche notare che Pop e Rock “hanno molte più parole” in comune, rispetto a quante ne hanno con il Metal. Questo si traduce infatti in features più simili, e quindi una classificazione meno accurata.

Multiclass classification

Ho poi deciso di provare con la multiclass classification, ovvero inserendo nel training set tutti e 8 i generi. Qui di seguito riporto in una tabella la cosiddetta confusion matrix (matrice di confusione); in ogni riga e colonna sono riportate le classi. All’incrocio fra una riga (con classe ‘a’) e una colonna (classe ‘b’), viene trascritto il numero di documenti di classe ‘a’ classificati nella classe ‘b’. La tabella si riferisce alla classificazione sul validation set, composto da 39990 canzoni distribuite casualmente fra i generi.

Latin R&B Elettr Jazz Hip Hop Metal Pop Rock
Latin 3 0 0 1 1 1 13 10
R&B 0 29 1 1 13 12 212 136
Elettr 0 7 13 6 22 61 198 213
Jazz 0 3 0 542 9 36 242 871
Hip Hop 0 10 3 6 1701 89 321 210
Metal 0 5 2 13 23 4999 821 961
Pop 4 66 22 133 225 852 6503 4514
Rock 2 36 18 228 107 808 3061 11595

Come si può vedere i generi con in più alto numero di canzoni sono anche quelli che vengono identificati meglio. In totale l’algoritmo classificava un documento correttamente il 72.01% delle volte sul training set, e il 63.47% sul validation. Bisogna notare che come nel caso della classificazione binaria, anche qui ho usato solo 5’000 features.

Errori e numero di features

La teoria ci dice che (da una certo punto in poi) più è complesso il modello che vogliamo apprendere, più crescerà l’errore di di validazione. Ho potuto (circa) confermare questo risultato eseguendo la classificazione sulla coppia Pop VS Metal cambiando ogni volta il numero di features. Come si vede dall’immagine il numero di predizioni errate sul training set decresce mentre quello sul validation set rimane circa costante.

errori

In questa parte del progetto ho utilizzato la libreria scikit-learn per l’implementazione del Logistic Regression classifier. Si tratta di una libreria davvero molto completa e performante, molto semplice da usare. La consiglio vivamente per chi si approccia per la prima volta al ML, dato che permette di avere un riscontro pratico senza doversi preoccupare dell’implementazione degli algoritmi.

Conclusioni

In questo articolo abbiamo solo grattato la superficie dei problemi di classificazione dei testi, ma spero di essere riuscito a trasmettere le idee di fondo. Ci sono molti miglioramenti che si potrebbero fare a questo progetto.

Ad esempio utilizzare più features, o inserire gli n-grams, o un corpus più ricco, o algoritmi più avanzati. A qualcuno potrebbe non piacere l’assegnazione delle canzoni a 8 macro-gruppi. Per porvi rimedio sarebbe interessante provare un algoritmo di classificazione multilabel. Ammettendo, cioè, la possibilità che un documento appartenga a più di una classe contemporaneamente.

Spero di essere riuscito a farvi interessare al mondo del Machine Learning, che io trovo davvero affascinante. Nelle fonti troverete qualche spunto per approfondire.