Schritt 3: Daten vorbereiten

Bevor unsere Daten in ein Modell eingespeist werden können, müssen sie in ein Format umgewandelt werden, das das Modell verstehen kann.

Erstens können die von uns erhobenen Daten in einer bestimmten Reihenfolge angeordnet werden. Wir möchten nicht, dass Informationen, die mit der Sortierung von Stichproben verknüpft sind, die Beziehung zwischen Texten und Labels beeinflussen. Wenn ein Dataset beispielsweise nach Klasse sortiert ist und dann in Trainings-/Validierungs-Datasets unterteilt wird, repräsentieren diese Datasets nicht die Gesamtverteilung von Daten.

Eine einfache Best Practice, um sicherzustellen, dass das Modell nicht von der Datenreihenfolge betroffen ist, besteht darin, die Daten immer zuerst nach dem Zufallsprinzip umzubenennen. Wenn Ihre Daten bereits in Trainings- und Validierungs-Datasets aufgeteilt sind, müssen Sie die Validierungsdaten auf dieselbe Weise transformieren wie Ihre Trainingsdaten. Wenn Sie noch keine separaten Trainings- und Validierungs-Datasets haben, können Sie die Stichproben nach dem Zufallsprinzip aufteilen. Normalerweise werden 80% der Proben für das Training und 20% für die Validierung verwendet.

Zweitens nutzen die Algorithmen für maschinelles Lernen Zahlen als Eingaben. Das bedeutet, dass die Texte in numerische Vektoren umgewandelt werden müssen. Dieser Prozess umfasst zwei Schritte:

  1. Tokenisierung: Teile die Texte in Wörter oder kleinere Untertexte auf, um eine gute Generalisierung der Beziehung zwischen den Texten und den Labels zu ermöglichen. Dadurch wird das „Vokabular“ des Datasets bestimmt (Gruppe von eindeutigen Tokens in den Daten).

  2. Vektorisierung: Definieren Sie einen geeigneten numerischen Messwert, um diese Texte zu charakterisieren.

Sehen wir uns nun an, wie diese beiden Schritte sowohl für N-Gramm-Vektoren als auch für Sequenzvektoren ausgeführt werden. Außerdem wird gezeigt, wie die Vektordarstellungen mithilfe von Featureauswahl und Normalisierung optimiert werden.

N-Gramm-Vektoren [Option A]

In den folgenden Absätzen wird erläutert, wie Sie Tokenisierung und Vektorisierung für N-Gramm-Modelle ausführen. Außerdem erfahren Sie, wie wir die N-Gramm-Darstellung mithilfe von Featureauswahl- und Normalisierungsverfahren optimieren können.

In einem N-Gramm-Vektor wird Text als Sammlung eindeutiger N-Gramme dargestellt: Gruppen von n benachbarten Tokens (in der Regel Wörter). Sehen wir uns den Text The mouse ran up the clock an. Hier sind die Unigramme (n = 1) ['the', 'mouse', 'ran', 'up', 'clock'], das Wort Bigrams (n = 2) ['the mouse', 'mouse ran', 'ran up', 'up the', 'the clock'] und so weiter.

Tokenisierung

Wir haben festgestellt, dass die Tokenisierung in Wortunigramme und Bigrams eine gute Genauigkeit und weniger Rechenzeit erfordert.

Vektorisierung

Nachdem wir unsere Textbeispiele in N-Gramme aufgeteilt haben, müssen wir diese N-Gramme in numerische Vektoren umwandeln, die unsere Modelle für maschinelles Lernen verarbeiten können. Das folgende Beispiel zeigt die Indexe, die den Unigrammen und Bigrams zugewiesen sind, die für zwei Texte generiert wurden.

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}

Sobald Indexe den N-Grammen zugewiesen sind, erfolgt die Vektorisierung normalerweise mit einer der folgenden Optionen.

One-Hot-Codierung: Jeder Beispieltext wird als Vektor dargestellt, der das Vorhandensein oder Fehlen eines Tokens im Text angibt.

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

Codierung der Zählung: Jeder Beispieltext wird als Vektor dargestellt, der die Anzahl eines Tokens im Text angibt. Das Element, das dem Unigramm ' (unten fett) entspricht, wird nun als 2 dargestellt, weil das Wort „der“ im Text zweimal vorkommt.

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

Tf-idf-Codierung: Das Problem bei den beiden oben beschriebenen Ansätzen besteht darin, dass häufig vorkommende Wörter in ähnlichen Häufigkeiten in allen Dokumenten (d.h. Wörter, die nicht besonders für die Textbeispiele im Dataset gelten) bestraft werden. Wörter wie „a“ kommen beispielsweise in allen Texten sehr häufig vor. Eine höhere Tokenanzahl für „the“ als andere sinnvollere Wörter ist also nicht sehr nützlich.

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

Es gibt viele weitere Vektordarstellungen, die jedoch oben am häufigsten verwendet werden.

Wir haben festgestellt, dass die tf-idf-Codierung in Bezug auf die Genauigkeit (im Durchschnitt: 0,25–15% höher) etwas besser ist als die anderen beiden.Wir empfehlen, diese Methode für die Vektorisierung von N-Grammen zu verwenden. Beachten Sie jedoch, dass sie mehr Arbeitsspeicher in Anspruch nimmt, da sie eine Gleitkommadarstellung verwendet, und die Rechenzeit länger dauert, insbesondere bei großen Datasets. In manchen Fällen kann dies auch doppelt so lange dauern.

Auswahl von Merkmalen

Wenn wir den gesamten Text in einem Dataset in Uni+-Bigramm-Tokens umwandeln, könnten wir Zehntausende Tokens erhalten. Nicht alle diese Tokens/Funktionen tragen zur Labelvorhersage bei. So können wir bestimmte Tokens entfernen, z. B. solche, die im Dataset extrem selten vorkommen. Wir können auch die Featurewichtigkeit messen (wie viel jedes Token zu Labelvorhersagen beiträgt) und nur die informativsten Tokens enthalten.

Es gibt viele statistische Funktionen, die Features und die entsprechenden Labels verwenden und die Wichtigkeit der Funktion ausgeben. Zwei häufig verwendete Funktionen sind f_classif und chi2. Unsere Tests haben gezeigt, dass beide Funktionen gleich gut funktionieren.

Außerdem haben wir festgestellt, dass die Genauigkeit bei vielen Datasets bei rund 20.000 Merkmalen ihren Höhepunkt erreicht (siehe Abbildung 6). Wenn Sie mehr Features hinzufügen, trägt das sehr selten und manchmal sogar zu einer Überanpassung und Leistungseinbußen bei.

Top K im Vergleich zu Genauigkeit

Abbildung 6: Top-K-Funktionen im Vergleich zur Genauigkeit Bei den Datasets sind Genauigkeits-Plateaus von etwa den 20.000 wichtigsten Merkmalen definiert.

Normalisierung

Bei der Normalisierung werden alle Feature-/Beispielwerte in kleine und ähnliche Werte umgewandelt. Dies vereinfacht die Konvergenz des Gradientenverfahrens in Lernalgorithmen. Wir haben festgestellt, dass die Normalisierung während der Datenvorverarbeitung keinen großen Mehrwert bei der Textklassifizierung darstellt. Wir empfehlen, diesen Schritt zu überspringen.

Mit dem folgenden Code werden die oben genannten Schritte zusammengefasst:

  • Tokens als Text in Uni+Bigramm umwandeln
  • Vektorisieren Sie mithilfe der tf-idf-Codierung.
  • Wählen Sie nur die 20.000 wichtigsten Merkmale aus dem Vektor von Tokens aus, indem Sie Tokens verwerfen, die weniger als zweimal vorkommen, und zum Ermitteln der Wichtigkeit eines Elements f_classif verwenden.
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

Bei der N-Gramm-Vektordarstellung verwerfen wir viele Informationen zur Wortreihenfolge und Grammatik. Im Idealfall können wir einige Teilsortierungsinformationen beibehalten, wenn n > 1 ist. Dieser Ansatz wird als „Bag of Words“ bezeichnet. Diese Darstellung wird in Verbindung mit Modellen verwendet, die keine Reihenfolge berücksichtigen, z. B. logistische Regressionen, mehrschichtige Perceptronen, Maschinen zur Gradientensteigerung, Unterstützung von Vektormaschinen.

Sequenzvektoren [Option B]

In den folgenden Absätzen wird erläutert, wie Tokenisierung und Vektorisierung für Sequenzmodelle ausgeführt werden. Wir gehen außerdem darauf ein, wie wir die Sequenzdarstellung mithilfe von Featureauswahl- und Normalisierungsverfahren optimieren können.

Bei einigen Textbeispielen ist die Wortreihenfolge für die Bedeutung des Texts entscheidend. Zum Beispiel in den Sätzen: „Früher habe ich meinen Arbeitsweg gehasst. „Mein neues Fahrrad hat das komplett geändert“ kann nur gelesen werden, wenn Sie in der richtigen Reihenfolge gelesen werden. Modelle wie CNNs/RNNs können die Bedeutung aus der Reihenfolge der Wörter in einer Stichprobe ableiten. Bei diesen Modellen stellen wir den Text als Sequenz von Tokens dar, wobei die Reihenfolge beibehalten wird.

Tokenisierung

Text kann als Zeichenfolge oder als Wortfolge dargestellt werden. Wir haben festgestellt, dass die Verwendung der Darstellung auf Wortebene eine bessere Leistung als Zeichentokens bietet. Dies ist auch die allgemeine Norm, die von der Branche befolgt wird. Die Verwendung von Zeichen-Tokens ist nur sinnvoll, wenn Texte viele Tippfehler enthalten, was normalerweise nicht der Fall ist.

Vektorisierung

Nachdem wir unsere Textstichproben in Wortfolgen umgewandelt haben, müssen wir diese Sequenzen in numerische Vektoren umwandeln. Das folgende Beispiel zeigt die Indexe, die den Unigrammen zugewiesen sind, die für zwei Texte generiert wurden, und dann die Sequenz der Tokenindexe, in die der erste Text konvertiert wird.

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]

Zum Vektorieren der Tokensequenzen sind zwei Optionen verfügbar:

One-Hot-Codierung: Sequenzen werden mit Wortvektoren in n-dimensionalen Bereichen dargestellt, wobei n = Vokabular ist. Diese Darstellung eignet sich hervorragend für die Tokenisierung als Zeichen und daher ist das Vokabular klein. Wenn wir Tokens als Tokens verwenden, enthält das Vokabular normalerweise Zehntausende von Tokens, was die One-Hot-Vektoren sehr dünn und ineffizient macht. Beispiel:

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

Worteinbettungen: Wörter sind mit Bedeutung(en) verknüpft. Daraus folgt, dass wir Worttokens in einem dichten Vektorraum (~wenige reelle Zahlen) darstellen können, wobei die Position und die Entfernung zwischen Wörtern angibt, wie ähnlich sie sich semantisch sind (siehe Abbildung 7). Diese Darstellung wird als Worteinbettungen bezeichnet.

Worteinbettungen

Abbildung 7: Worteinbettungen

Sequenzmodelle haben oft eine Einbettungsebene als erste Ebene. Diese Ebene lernt, während des Trainingsvorgangs Wortindexsequenzen in Worteinbettungsvektoren zu Worteinbettungsvektoren zu verwandeln, sodass jeder Wortindex einem dichten Vektor von realen Werten zugeordnet wird, die die Position dieses Worts im semantischen Bereich darstellen (siehe Abbildung 8).

Einbettungsebene

Abbildung 8: Einbettungsebene

Auswahl von Merkmalen

Nicht alle Wörter in unseren Daten tragen zu Labelvorhersagen bei. Wir können unseren Lernprozess optimieren, indem wir seltene oder irrelevante Wörter aus unserem Vokabular verwerfen. Tatsächlich stellen wir fest, dass die Verwendung der häufigsten 20.000 Features im Allgemeinen ausreicht. Dies gilt auch für N-Gramm-Modelle (siehe Abbildung 6).

Fassen wir alle oben genannten Schritte für die Sequenzvektorisierung zusammen. Mit dem folgenden Code werden diese Aufgaben ausgeführt:

  • Wandelt die Texte in Wörter um
  • Ein Vokabular aus den 20.000 Tokens erstellen
  • Wandelt die Tokens in Sequenzvektoren um
  • Füllt die Sequenzen auf eine feste Sequenzlänge auf
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

Labelvektorisierung

Wir haben gesehen, wie Sie Beispieltextdaten in numerische Vektoren umwandeln können. Ein ähnlicher Prozess muss auf die Labels angewendet werden. Labels können einfach in Werte im Bereich [0, num_classes - 1] konvertiert werden. Wenn wir beispielsweise drei Klassen haben, können wir einfach die Werte 0, 1 und 2 verwenden, um sie darzustellen. Intern verwendet das Netzwerk One-Hot-Vektoren, um diese Werte darzustellen. So wird vermieden, dass eine falsche Beziehung zwischen Labels abgeleitet wird. Diese Darstellung hängt von der Verlustfunktion und der Aktivierungsfunktion der letzten Ebene in unserem neuronalen Netzwerk ab. Im nächsten Abschnitt erfahren Sie mehr dazu.