Étape 3 : Préparez vos données

Avant de pouvoir transmettre nos données à un modèle, celles-ci doivent être transformées dans un format compréhensible par le modèle.

Tout d'abord, les échantillons de données que nous avons collectés peuvent être dans un ordre spécifique. Nous ne voulons pas que les informations associées à l'ordre des échantillons influencent la relation entre les textes et les étiquettes. Par exemple, si un ensemble de données est trié par classe, puis divisé en ensembles d'entraînement et de validation, ces ensembles ne seront pas représentatifs de la distribution globale des données.

Une bonne pratique simple pour s'assurer que le modèle n'est pas affecté par l'ordre des données consiste à toujours brasser les données avant d'effectuer toute autre action. Si vos données sont déjà divisées en ensembles d'entraînement et de validation, veillez à transformer vos données de validation de la même manière que vos données d'entraînement. Si vous ne disposez pas encore d'ensembles d'entraînement et de validation distincts, vous pouvez diviser les échantillons après le brassage. En règle générale, 80% des échantillons sont utilisés pour l'entraînement et 20% pour la validation.

Deuxièmement, les algorithmes de machine learning utilisent les nombres en tant qu'entrées. Cela signifie que nous devrons convertir les textes en vecteurs numériques. Ce processus comporte deux étapes:

  1. Tokenisation: divisez les textes en mots ou en sous-texte plus petits, ce qui permet une bonne généralisation de la relation entre les textes et les étiquettes. Cela détermine le "vocabulaire" de l'ensemble de données (ensemble de jetons uniques présents dans les données).

  2. Vectorisation: définissez une mesure numérique appropriée pour caractériser ces textes.

Voyons comment effectuer ces deux étapes pour les vecteurs de N-grammes et les vecteurs de séquence, et comment optimiser les représentations vectorielles à l'aide de techniques de sélection et de normalisation des caractéristiques.

Vecteurs de N-grammes [Option A]

Dans les paragraphes suivants, nous verrons comment effectuer la tokenisation et la vectorisation pour les modèles de n-grammes. Nous verrons également comment optimiser la représentation des n-grammes à l'aide de techniques de sélection et de normalisation des caractéristiques.

Dans un vecteur de n-grammes, le texte est représenté sous la forme d'une collection de N-grammes uniques : des groupes de n jetons adjacents (généralement des mots). Prenons l'exemple du texte The mouse ran up the clock. Ici:

  • Les unigrammes de mots (n = 1) sont ['the', 'mouse', 'ran', 'up', 'clock'].
  • Les bigrammes (n = 2) sont ['the mouse', 'mouse ran', 'ran up', 'up the', 'the clock']
  • Et ainsi de suite.

Tokenisation

Nous avons constaté que la tokenisation sous forme d'unigrammes de mots et de bigrammes offre une bonne précision tout en prenant moins de temps de calcul.

Vectorisation

Une fois que nous avons divisé nos échantillons de texte en n-grammes, nous devons transformer ces N-grammes en vecteurs numériques que nos modèles de machine learning peuvent traiter. L'exemple ci-dessous montre les index attribués aux unigrammes et aux bigrammes générés pour deux textes.

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}

Une fois les index attribués aux n-grammes, nous vectorisons généralement à l'aide de l'une des options suivantes.

Encodage one-hot: chaque exemple de texte est représenté sous forme de vecteur indiquant la présence ou l'absence d'un jeton dans le texte.

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

Encodage de nombres: chaque échantillon de texte est représenté sous forme de vecteur indiquant le nombre de jetons dans le texte. Notez que l'élément correspondant à l'unigramme "the" est désormais représenté par 2, car le mot "the" apparaît deux fois dans le texte.

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

Encodage Tf-idf: le problème avec les deux approches ci-dessus est que les mots courants qui se produisent à des fréquences similaires dans tous les documents (c'est-à-dire les mots qui ne sont pas particulièrement propres aux échantillons de texte de l'ensemble de données) ne sont pas pénalisés. Par exemple, des mots comme "a" apparaîtront très fréquemment dans tous les textes. Ainsi, un nombre plus élevé de jetons pour "the" que pour d'autres mots plus significatifs n'est pas très 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]

(Voir Scikit-learn TfidfTransformer)

Il existe de nombreuses autres représentations vectorielles, mais les trois précédentes sont les plus couramment utilisées.

Nous avons constaté que l'encodage tf-idf était légèrement meilleur que les deux autres en termes de précision (en moyenne, supérieur de 0,25 à 15 %) et nous recommandons d'utiliser cette méthode pour vectoriser des n-grammes. Cependant, n'oubliez pas qu'elle occupe plus de mémoire (car elle utilise la représentation à virgule flottante) et prend plus de temps à calculer, en particulier pour les ensembles de données volumineux (cela peut prendre deux fois plus de temps dans certains cas).

Sélection des caractéristiques

Lorsque nous convertissons tous les textes d'un ensemble de données en jetons uni+bigrammes de mots, nous pouvons nous retrouver avec des dizaines de milliers de jetons. Ces jetons/caractéristiques ne contribuent pas tous à la prédiction des étiquettes. Nous pouvons donc supprimer certains jetons, par exemple ceux qui apparaissent extrêmement rarement dans l'ensemble de données. Nous pouvons également mesurer l'importance des caractéristiques (dans quelle mesure chaque jeton contribue aux prédictions d'étiquette) et n'inclure que les jetons les plus informatifs.

De nombreuses fonctions statistiques utilisent des caractéristiques et les étiquettes correspondantes pour générer le score d'importance des caractéristiques. Deux fonctions couramment utilisées sont f_classif et chi2. Nos expériences montrent que ces deux fonctions fonctionnent de la même manière.

Plus important encore, nous avons constaté que la précision atteint un pic à environ 20 000 caractéristiques pour de nombreux ensembles de données (voir la Figure 6). L'ajout d'autres caractéristiques au-delà de ce seuil n'a que très peu d'impact, voire conduit parfois à un surapprentissage et dégrade les performances.

top-K par rapport à la justesse

Figure 6: Caractéristiques des Top K et précision Pour tous les ensembles de données, la justesse s'approche des 20 000 premières caractéristiques.

Normalization

La normalisation convertit toutes les valeurs de caractéristiques/échantillons en valeurs petites et similaires. Cela simplifie la convergence de la descente de gradient dans les algorithmes d'apprentissage. D'après ce que nous avons vu, la normalisation effectuée lors du prétraitement des données ne semble pas apporter beaucoup de valeur aux problèmes de classification de texte. Nous vous recommandons donc d'ignorer cette étape.

Le code suivant rassemble toutes les étapes ci-dessus:

  • Tokeniser des échantillons de texte en uni+bigrammes de mots
  • Vectoriser à l'aide de l'encodage tf-idf
  • Ne sélectionnez que les 20 000 premières caractéristiques du vecteur de jetons en supprimant les jetons qui apparaissent moins de deux fois et en utilisant f_classif pour calculer l'importance des caractéristiques.
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

Avec la représentation vectorielle des n-grammes, nous écartons de nombreuses informations concernant l'ordre des mots et la grammaire (au mieux, nous pouvons conserver certaines informations d'ordre partiel lorsque n > 1). C'est ce qu'on appelle une approche sac de mots. Cette représentation est utilisée avec des modèles qui ne tiennent pas compte de l'ordre de tri, tels que la régression logistique, les perceptrons multicouches, les machines d'optimisation de gradient et les machines à vecteurs de support.

Vecteurs de séquence [Option B]

Dans les paragraphes suivants, nous verrons comment effectuer la tokenisation et la vectorisation pour les modèles de séquence. Nous verrons également comment optimiser la représentation séquentielle à l'aide de techniques de sélection et de normalisation des caractéristiques.

Pour certains échantillons de texte, l'ordre des mots est essentiel à la signification du texte. Par exemple, les phrases : « Avant, j'ai détesté mon trajet domicile-travail. Mon nouveau vélo a complètement changé » peut être compris si elle est lue dans l'ordre. Les modèles tels que les CNN/RNN peuvent déduire le sens de l'ordre des mots dans un échantillon. Pour ces modèles, nous représentons le texte comme une séquence de jetons, préservant l'ordre.

Tokenisation

Le texte peut être représenté sous la forme d'une séquence de caractères ou d'une séquence de mots. Nous avons constaté que l'utilisation de la représentation au niveau du mot offrait de meilleures performances que les jetons de caractère. Il s'agit également de la norme générale selon l'industrie. L'utilisation de jetons de caractère n'a de sens que si les textes comportent beaucoup de fautes de frappe, ce qui n'est normalement pas le cas.

Vectorisation

Une fois nos échantillons de texte convertis en séquences de mots, nous devons convertir ces séquences en vecteurs numériques. L'exemple ci-dessous montre les index attribués aux unigrammes générés pour deux textes, puis la séquence d'index de jetons vers lesquels le premier texte est converti.

Texts: 'The mouse ran up the clock' and 'The mouse ran down'

Index attribué à chaque jeton:

{'clock': 5, 'ran': 3, 'up': 4, 'down': 6, 'the': 1, 'mouse': 2}

REMARQUE: Le mot "the" apparaît le plus fréquemment, de sorte que la valeur d'index de 1 lui est attribuée. Certaines bibliothèques réservent l'index 0 aux jetons inconnus, comme c'est le cas ici.

Séquence d'index de jetons:

'The mouse ran up the clock' = [1, 2, 3, 4, 1, 5]

Deux options s'offrent à vous pour vectoriser les séquences de jetons:

Encodage one-hot: les séquences sont représentées à l'aide de vecteurs de mots dans un espace à n dimensions, où n = taille de vocabulaire. Cette représentation fonctionne très bien pour la tokenisation sous forme de caractères. Le vocabulaire est donc restreint. Lorsque nous procédons à la tokenisation sous forme de mots, le vocabulaire contient généralement des dizaines de milliers de jetons, ce qui rend les vecteurs one-hot très creux et inefficaces. Exemple :

'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]
]

Représentations vectorielles continues de mots: les mots ont une ou plusieurs significations. Par conséquent, nous pouvons représenter des jetons de mot dans un espace vectoriel dense (environ quelques centaines de nombres réels), où l'emplacement et la distance entre les mots indiquent à quel point ils sont sémantiquement similaires (voir la Figure 7). Cette représentation est appelée représentations vectorielles continues de mots.

Représentations vectorielles continues de mots

Figure 7: Représentations vectorielles continues de mots

Les modèles de séquence comportent souvent une couche de représentation vectorielle continue en tant que première couche. Cette couche apprend à transformer des séquences d'index de mots en vecteurs de représentation vectorielle continue de mots au cours du processus d'entraînement, de sorte que chaque index de mot soit mappé à un vecteur dense de valeurs réelles représentant l'emplacement de ce mot dans l'espace sémantique (voir la Figure 8).

Couche de représentation vectorielle continue

Figure 8: Couche de représentation vectorielle continue

Sélection des caractéristiques

Tous les mots de nos données ne contribuent pas aux prédictions d'étiquettes. Nous pouvons optimiser notre processus d'apprentissage en éliminant les mots rares ou non pertinents de notre vocabulaire. En fait, nous observons que l'utilisation des 20 000 caractéristiques les plus fréquentes suffit généralement. Ce principe s'applique également aux modèles de n-grammes (voir la Figure 6).

Réunissons toutes les étapes ci-dessus de la vectorisation de séquence. Le code suivant permet d'effectuer les tâches suivantes:

  • Tokenisation des textes en mots
  • Crée un vocabulaire à partir des 20 000 meilleurs jetons
  • Convertit les jetons en vecteurs de séquence
  • Remplit les séquences selon une longueur de séquence fixe
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

Vectorisation des étiquettes

Nous avons vu comment convertir des échantillons de données textuelles en vecteurs numériques. Un processus similaire doit être appliqué aux étiquettes. Nous pouvons simplement convertir les étiquettes en valeurs comprises dans la plage [0, num_classes - 1]. Par exemple, s'il existe trois classes, nous pouvons simplement utiliser les valeurs 0, 1 et 2 pour les représenter. En interne, le réseau utilise des vecteurs one-hot pour représenter ces valeurs (pour éviter d'inférer une relation incorrecte entre les étiquettes). Cette représentation dépend de la fonction de perte et de la fonction d'activation de la dernière couche que nous utilisons dans notre réseau de neurones. Nous en apprendrons plus à ce sujet dans la section suivante.