Una delle funzionalità più usate e apprezzate di Scikit-Learn sono le pipeline. Sebbene il loro utilizzo sia optional, quest'ultime possono essere utilizzate per rendere il nostro codice più pulito e facile da mantenere. Le pipeline accettano come input degli estimators, che non sono altro che delle classi che si ereditano da sklearn.base.BaseEstimator e che contengono i metodi fit e transform. Questo ci permette di personalizzare le pipeline con funzionalità che Sklearn non offre di default.
Parleremo dei transformer, degli oggetti che applicano una trasformazione su un input. La classe dalla quale erediteremo è TransformerMixin, ma è possibile estendere anche da ClassifierMixin, RegressionMixin, ClusterMixin e altri per creare un estimator personalizzato. Leggere qui per tutte le opzioni disponibili.
Lavoreremo con un dataset di dati testuali, sulla quale vogliamo applicare delle trasformazioni quali:
Questo verrà fatto attraverso l'utilizzo di Pipeline e FeatureUnion, una classe di Sklearn che unisce i feature set provenienti da diverse sorgenti.
Useremo il dataset fornito da Sklearn, 20newsgroups, per avere rapido accesso ad un corpus di dati testuali. A scopo dimostrativo, userò solo un campione di 10 testi ma l'esempio può essere esteso a qualsiasi numero di testi.
Importiamo il dataset con Python
Costruiremo il feature set conterrà queste informazioni
Il nostro processo, senza usare pipeline, sarebbe quello di applicare sequenzialmente tutti questi step con blocchi di codice separato. La bellezza delle pipeline è che la sequenzialità viene mantenuta in un solo blocco di codice - la pipeline stessa diventa un estimator, in grado di eseguire tutte le operazioni programmate in una sola istruzione.
Creiamo le nostre funzioni
Il nostro obiettivo è quello di creare un feature set unico in modo da addestrare un modello su un qualche task. Useremo le Pipeline e FeatureUnion per mettere insieme le nostre matrici.
La vettorizzazione TF-IDF creerà una matrice sparsa che avrà dimensioni n_documenti_nel_corpus * n_features, il sentiment sarà un singolo numero, come anche l'output di n_chars e n_sentences. Andremo a prendere gli output di ognuno di questi step e a creare una matrice singola che li conterrà tutti, in modo da poter addestrare un modello su tutte le feature che abbiamo ingegnerizzato. Partiremo da una rappresentazione del genere
Fino a giungere a questo
Il feature set verrà usato come vettore di addestramento del nostro modello.
Per poter mettere giù il nostro processo, occorre definire le classi e cosa faranno nella pipeline. Iniziamo col creare un DummyEstimator, dal quale andremo ad ereditare init, fit e transform. Il DummyEstimator è una classe comoda che ci evita la scrittura di diverso codice.
DummyEstimator sarà ereditato da quattro classi, Preprocessor, SentimentAnalysis, NChars, NSentences e FromSparseToArray.
Com'è possibile vedere, DummyEstimator ci permette di definire solo la funzione transform, poiché ogni altra classe eredita init e fit proprio da DummyEstimator.
Vediamo ora come implementare la pipeline di vettorizzazione, che terrà conto del preprocessing dei nostri testi.
Non resta che applicare FeatureUnion per mettere insieme i pezzi
Applichiamo fit_transform sul nostro corpus e vediamo l'output
L'output sembra essere corretto! Non è molto chiaro, però. Concludiamo il tutorial con l'inserimento in dataframe del feature set combinato
Il risultato è il seguente (qui ho troncato il risultato per questioni di leggibilità)
Ora abbiamo un dataset pronto per essere fornito a qualsiasi modello per addestramento. Sarebbe utile sperimentare con StandardScaler o simili e normalizzare n_chars e n_sentences. Lascerò questo esercizio al lettore.