Passaggio 3: prepara i dati

Prima di poter inviare i dati a un modello, i nostri dati devono essere trasformati in un formato comprensibile.

Innanzitutto, gli esempi di dati che abbiamo raccolto potrebbero essere in un ordine specifico. Non vogliamo che nessuna informazione associata all'ordine dei campioni influenzi la relazione tra testi ed etichette. Ad esempio, se un set di dati è ordinato per classe e poi viene suddiviso in set di addestramento/convalida, questi non rappresenteranno la distribuzione complessiva dei dati.

Una semplice best practice per garantire che il modello non sia interessato dall'ordine dei dati consiste nel riprodurre sempre i dati in modo casuale prima di procedere con qualsiasi altra operazione. Se i dati sono già suddivisi in set di addestramento e convalida, assicurati di trasformare i dati di convalida nello stesso modo in cui li trasformi. Se non hai set di addestramento e convalida separati, puoi dividere i campioni dopo aver eseguito l'ordinamento. In genere, per l'addestramento viene usato l'80% dei campioni e il 20% per la convalida.

In secondo luogo, gli algoritmi di machine learning prendono i numeri come input. Ciò significa che dovremo convertire i testi in vettori numerici. La procedura è composta da due passaggi:

  1. Tokenizzazione: suddividi i testi in parole o sottoinsiemi più piccoli, in modo da garantire una buona generalizzazione del rapporto tra i testi e le etichette. Determina il "vocabolario" del set di dati (insieme di token univoci presenti nei dati).

  2. Vectorizzazione: definisci una buona misura numerica per caratterizzare questi testi.

Vediamo come eseguire questi due passaggi sia per i vettori n-gram che per i vettori di sequenza, nonché come ottimizzare le rappresentazioni vettoriale utilizzando le tecniche di selezione delle caratteristiche e di normalizzazione.

Vettori N-gram [Opzione A]

Nei paragrafi successivi, vedremo come eseguire la tokenizzazione e la vettorizzazione per i modelli n-gram. Vedremo inoltre come ottimizzare la rappresentazione del n-gram utilizzando le tecniche di selezione delle caratteristiche e di normalizzazione.

In un n-gram vettoriale, il testo è rappresentato come una raccolta di n-grammi univoci: gruppi di n token adiacenti (in genere parole). Prendi in considerazione il testo The mouse ran up the clock. Qui, la parola unigrammi (n = 1) è ['the', 'mouse', 'ran', 'up', 'clock'], la parola bigram (n = 2) è ['the mouse', 'mouse ran', 'ran up', 'up the', 'the clock'] e così via.

Tokenizzazione

Abbiamo scoperto che la tokenizzazione in unigrammi + bigram offre una buona precisione e richiede meno tempo di calcolo.

Vettorizzazione

Dopo aver suddiviso i nostri campioni di testo in n-grammi, dobbiamo trasformarli in vettori numerici che i nostri modelli di machine learning possono elaborare. L'esempio riportato di seguito mostra gli indici assegnati agli unigrammi e alle bigram generati per due testi.

Texts: 'The mouse ran up the clock' and 'The mouse ran down'
Index assigned for every token: {'the': 7, 'mouse': 2, 'ran': 4, 'up': 10,
  'clock': 0, 'the mouse': 9, 'mouse ran': 3, 'ran up': 6, 'up the': 11, 'the
clock': 8, 'down': 1, 'ran down': 5}

Una volta assegnati gli indici ai n-grammi, in genere vettoriamo usando una delle seguenti opzioni.

Codifica one-hot: ogni testo di esempio è rappresentato da un vettore che indica la presenza o l'assenza di un token nel testo.

'The mouse ran up the clock' = [1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1]

Conteggio della codifica: ogni testo di esempio è rappresentato da un vettore che indica il conteggio di un token nel testo. Tieni presente che l'elemento corrispondente all'unigramma ' (in grassetto di seguito) è ora rappresentato come 2 perché la parola "the" viene visualizzata due volte nel testo.

'The mouse ran up the clock' = [1, 0, 1, 1, 1, 0, 1, 2, 1, 1, 1, 1]

Codifica tf-idf: il problema con i due approcci precedenti è che le parole comuni che si trovano in frequenze simili in tutti i documenti (ovvero le parole che non sono particolarmente univoche per esempi di testo nel set di dati) non vengono penalizzate. Ad esempio, parole come "a" ricorreranno molto spesso in tutti i testi. Quindi un numero di token più alto per "the" rispetto ad altre parole più significative non è molto utile.

'The mouse ran up the clock' = [0.33, 0, 0.23, 0.23, 0.23, 0, 0.33, 0.47, 0.33,
0.23, 0.33, 0.33] (See Scikit-learn TfidfTransformer)

Esistono molte altre rappresentazioni vettoriali, ma le tre precedenti sono quelle più utilizzate.

Abbiamo osservato che la codifica tf-idf è leggermente migliore degli altri due in termini di precisione (in media: 0,25-15% in più) e consigliamo di utilizzare questo metodo per vettorizzare i n-grammi. Tuttavia, tieni presente che questa funzionalità utilizza più memoria (poiché utilizza la rappresentazione in virgola mobile) e richiede più tempo per il calcolo, in particolare per set di dati di grandi dimensioni (in alcuni casi, in alcuni casi può richiedere il doppio del tempo).

Selezione di funzionalità

Quando convertiamo tutti i testi in un set di dati in token uni+bigrammo di parole, potremmo ottenere decine di migliaia di token. Non tutti questi token/funzionalità contribuiscono alla previsione delle etichette. Possiamo quindi eliminare determinati token, ad esempio quelli che si verificano molto raramente nel set di dati. Possiamo anche misurare l'importanza delle caratteristiche (in che misura ogni token contribuisce alle previsioni delle etichette) e includere solo i token più informativi.

Esistono molte funzioni statistiche che prendono in considerazione le caratteristiche e le corrispondenti etichette e restituiscono il punteggio di importanza delle caratteristiche. Due funzioni di uso comune sono f_classif e chi2. I nostri esperimenti dimostrano che entrambe queste funzioni hanno un rendimento uguale.

Aspetto ancora più importante, abbiamo visto che la precisione raggiunge circa 20.000 funzionalità per molti set di dati (vedi la Figura 6). L'aggiunta di ulteriori funzionalità al di sopra di questa soglia contribuisce molto poco e, a volte, può persino portare a un overfitting e peggiorare le prestazioni.

K principali e precisione

Figura 6: confronto tra le principali funzionalità di K e precisione. Nei set di dati, gli indicatori di precisione si attestano intorno alle 20.000 funzionalità principali.

Normalizzazione

La normalizzazione converte tutti i valori delle caratteristiche/campioni in valori piccoli e simili. Questo semplifica la convergenza della discesa del gradiente negli algoritmi di apprendimento. Da quanto abbiamo visto, la normalizzazione durante la pre-elaborazione dei dati non sembra apportare un valore aggiunto ai problemi di classificazione del testo; consigliamo di saltare questo passaggio.

Il codice che segue riunisce tutti i passaggi precedenti:

  • Tokenizzare esempi di testo in uni+bigrammi di parole
  • Vettorizza utilizzando la codifica tf-idf,
  • Seleziona solo le prime 20.000 funzionalità presenti nel vettore dei token ignorando i token che vengono visualizzati meno di due volte e utilizzando f_classif per calcolare l'importanza delle caratteristiche.
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_classif

# Vectorization parameters
# Range (inclusive) of n-gram sizes for tokenizing text.
NGRAM_RANGE = (1, 2)

# Limit on the number of features. We use the top 20K features.
TOP_K = 20000

# Whether text should be split into word or character n-grams.
# One of 'word', 'char'.
TOKEN_MODE = 'word'

# Minimum document/corpus frequency below which a token will be discarded.
MIN_DOCUMENT_FREQUENCY = 2

def ngram_vectorize(train_texts, train_labels, val_texts):
    """Vectorizes texts as n-gram vectors.

    1 text = 1 tf-idf vector the length of vocabulary of unigrams + bigrams.

    # Arguments
        train_texts: list, training text strings.
        train_labels: np.ndarray, training labels.
        val_texts: list, validation text strings.

    # Returns
        x_train, x_val: vectorized training and validation texts
    """
    # Create keyword arguments to pass to the 'tf-idf' vectorizer.
    kwargs = {
            'ngram_range': NGRAM_RANGE,  # Use 1-grams + 2-grams.
            'dtype': 'int32',
            'strip_accents': 'unicode',
            'decode_error': 'replace',
            'analyzer': TOKEN_MODE,  # Split text into word tokens.
            'min_df': MIN_DOCUMENT_FREQUENCY,
    }
    vectorizer = TfidfVectorizer(**kwargs)

    # Learn vocabulary from training texts and vectorize training texts.
    x_train = vectorizer.fit_transform(train_texts)

    # Vectorize validation texts.
    x_val = vectorizer.transform(val_texts)

    # Select top 'k' of the vectorized features.
    selector = SelectKBest(f_classif, k=min(TOP_K, x_train.shape[1]))
    selector.fit(x_train, train_labels)
    x_train = selector.transform(x_train).astype('float32')
    x_val = selector.transform(x_val).astype('float32')
    return x_train, x_val

Con la rappresentazione vettoriale di n-gram, eliminiamo molte informazioni sull'ordine delle parole e sulla grammatica (per quanto possibile, possiamo mantenere alcune informazioni di ordinamento parziale quando n > 1). Si tratta di un approccio basato su parole chiave. Questa rappresentazione viene utilizzata insieme ai modelli che non prendono in considerazione l'ordinamento, ad esempio la regressione logistica, i perceptron multilivello, le macchine di boost gradient, le macchine a vettore di supporto.

Vettori di sequenza [Opzione B]

Nei paragrafi successivi, vedremo come eseguire la tokenizzazione e la vettorizzazione per i modelli di sequenza. Parleremo anche di come ottimizzare la rappresentazione delle sequenze utilizzando le tecniche di selezione delle caratteristiche e di normalizzazione.

Per alcuni esempi di testo, l'ordine delle parole è fondamentale per il significato del testo. Ad esempio, le frasi "In passato odio il mio tragitto giornaliero. La mia nuova bicicletta l'ha modificata completamente" si può capire solo quando è stata letta. Modelli come CNN/RNN possono dedurre significato dall'ordine delle parole in un campione. Per questi modelli, il testo viene rappresentato come una sequenza di token, mantenendo l'ordine.

Tokenizzazione

Il testo può essere rappresentato come una sequenza di caratteri o una sequenza di parole. Abbiamo riscontrato che l'utilizzo di una rappresentazione a livello di parola offre prestazioni migliori rispetto ai token dei caratteri. È anche la norma generale seguita dal settore. L'uso dei token dei caratteri è utile solo se il testo contiene molti errori di battitura, il che normalmente non è possibile.

Vettorizzazione

Dopo aver convertito gli esempi di testo in sequenze di parole, è necessario trasformarle in vettori numerici. L'esempio seguente mostra gli indici assegnati agli unigrammi generati per due testi, quindi la sequenza di indici dei token in cui viene convertito il primo testo.

Texts: 'The mouse ran up the clock' and 'The mouse ran down'
Index assigned for every token: {'clock': 5, 'ran': 3, 'up': 4, 'down': 6, 'the': 1, 'mouse': 2}.
NOTE: 'the' occurs most frequently, so the index value of 1 is assigned to it.
Some libraries reserve index 0 for unknown tokens, as is the case here.
Sequence of token indexes: 'The mouse ran up the clock' = [1, 2, 3, 4, 1, 5]

Sono disponibili due opzioni per vettorizzare le sequenze di token:

Codifica one-hot: le sequenze sono rappresentate usando i vettori di parole in uno spazio n-dimensionale in cui n = dimensioni del vocabolario. Questa rappresentazione funziona benissimo quando eseguiamo la tokenizzazione come caratteri e il vocabolario è quindi piccolo. Quando usiamo le tokenizzazione come parole, il vocabolario contiene in genere decine di migliaia di token, il che rende i vettori preferiti molto scarsi ed inefficienti. Esempio:

'The mouse ran up the clock' = [
  [0, 1, 0, 0, 0, 0, 0],
  [0, 0, 1, 0, 0, 0, 0],
  [0, 0, 0, 1, 0, 0, 0],
  [0, 0, 0, 0, 1, 0, 0],
  [0, 1, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 1, 0]
]

Incorporamenti di parole: le parole hanno significati associati. Di conseguenza, possiamo rappresentare i token delle parole in uno spazio vettoriale densa (circa centinaia di numeri reali), dove la posizione e la distanza tra le parole indicano la somiglianza tra loro (vedi Figura 7). Questa rappresentazione è chiamata incorporazione di parole.

Incorporamenti di parole

Figura 7: incorporazioni di parole

I modelli di sequenza hanno spesso un livello di incorporamento simile al primo. Questo livello impara a trasformare le sequenze di indici delle parole in vettori di incorporamento delle parole durante il processo di addestramento, in modo che ogni indice delle parole venga mappato a un denso vettore di valori reali che rappresentano la posizione della parola nello spazio semantico (vedi Figura 8).

Incorporamento del livello

Figura 8: livello di incorporamento

Selezione di funzionalità

Non tutte le parole nei nostri dati contribuiscono alle previsioni delle etichette. Possiamo ottimizzare il nostro processo di apprendimento eliminando le parole rare o non pertinenti dal nostro vocabolario. Di fatto, osserviamo che l'utilizzo delle 20.000 funzionalità più frequenti è generalmente sufficiente. Questo vale anche per i modelli n-gram (vedi Figura 6).

Mettiamo insieme tutti i passaggi precedenti nella vettorazione della sequenza. Il codice seguente esegue queste attività:

  • Tokenizza i testi in parole
  • Crea un vocabolario utilizzando i 20.000 token principali
  • Converte i token in vettori di sequenza
  • Consente di limitare le sequenze a una lunghezza fissa
from tensorflow.python.keras.preprocessing import sequence
from tensorflow.python.keras.preprocessing import text

# Vectorization parameters
# Limit on the number of features. We use the top 20K features.
TOP_K = 20000

# Limit on the length of text sequences. Sequences longer than this
# will be truncated.
MAX_SEQUENCE_LENGTH = 500

def sequence_vectorize(train_texts, val_texts):
    """Vectorizes texts as sequence vectors.

    1 text = 1 sequence vector with fixed length.

    # Arguments
        train_texts: list, training text strings.
        val_texts: list, validation text strings.

    # Returns
        x_train, x_val, word_index: vectorized training and validation
            texts and word index dictionary.
    """
    # Create vocabulary with training texts.
    tokenizer = text.Tokenizer(num_words=TOP_K)
    tokenizer.fit_on_texts(train_texts)

    # Vectorize training and validation texts.
    x_train = tokenizer.texts_to_sequences(train_texts)
    x_val = tokenizer.texts_to_sequences(val_texts)

    # Get max sequence length.
    max_length = len(max(x_train, key=len))
    if max_length > MAX_SEQUENCE_LENGTH:
        max_length = MAX_SEQUENCE_LENGTH

    # Fix sequence length to max value. Sequences shorter than the length are
    # padded in the beginning and sequences longer are truncated
    # at the beginning.
    x_train = sequence.pad_sequences(x_train, maxlen=max_length)
    x_val = sequence.pad_sequences(x_val, maxlen=max_length)
    return x_train, x_val, tokenizer.word_index

Vettorizzazione etichette

Abbiamo visto come convertire dati di testo di esempio in vettori numerici. Una procedura simile deve essere applicata alle etichette. Possiamo semplicemente convertire le etichette in valori nell'intervallo [0, num_classes - 1]. Ad esempio, se esistono 3 classi, possiamo semplicemente utilizzare i valori 0, 1 e 2 per rappresentarle. Internamente, la rete utilizzerà vettori one-hot per rappresentare questi valori (per evitare di dedurre una relazione errata tra le etichette). Questa rappresentazione dipende dalla funzione di perdita e dall'ultima funzione di attivazione di livello che utilizziamo nella nostra rete neurale. Questo argomento verrà illustrato nella prossima sezione.