Schritt 3: Daten vorbereiten

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

Zum einen können die von uns erfassten Stichproben in einer bestimmten Reihenfolge angeordnet sein. Wir möchten nicht, dass Informationen im Zusammenhang mit der Reihenfolge von Stichproben die Beziehung zwischen Texten und Labels beeinflussen. Wenn ein Dataset beispielsweise nach Klassen sortiert und dann in Trainings-/Validierungs-Datasets aufgeteilt wird, sind diese Datasets nicht repräsentativ für die Gesamtverteilung der Daten.

Eine einfache Best Practice, um sicherzustellen, dass das Modell nicht von der Datenreihenfolge beeinflusst wird, besteht darin, die Daten immer zu mischen, bevor Sie etwas anderes tun. Wenn Ihre Daten bereits in Trainings- und Validierungs-Datasets aufgeteilt sind, müssen Sie die Validierungsdaten auf dieselbe Weise transformieren wie die Trainingsdaten. Wenn Sie noch keine separaten Trainings- und Validierungs-Datasets haben, können Sie die Beispiele nach dem Zufallsprinzip aufteilen. Es ist üblich, 80% der Stichproben für das Training und 20% für die Validierung zu verwenden.

Zweitens nehmen Algorithmen für maschinelles Lernen Zahlen als Eingaben. Das bedeutet, dass wir die Texte in numerische Vektoren umwandeln müssen. Dieser Vorgang umfasst zwei Schritte:

  1. Vergabe von Tokens: Teilen Sie die Texte in Wörter oder kleinere Untertexte auf, um eine gute Verallgemeinerung der Beziehung zwischen den Texten und den Labels zu ermöglichen. Dadurch wird das „Vokabular“ des Datasets (Satz eindeutiger Tokens in den Daten) bestimmt.

  2. Vektorisierung: Definieren Sie ein gutes numerisches Maß, um diese Texte zu charakterisieren.

Sehen wir uns an, wie diese beiden Schritte sowohl für N-Gramm-Vektoren als auch für Sequenzvektoren ausgeführt werden und wie die Vektordarstellungen mithilfe von Merkmalsauswahl- und Normalisierungstechniken optimiert werden können.

N-Gramm-Vektoren [Option A]

In den folgenden Abschnitten erfahren Sie, wie Tokenisierung und Vektorisierung für N-Gramm-Modelle durchgeführt werden. Außerdem erfahren Sie, wie Sie die N-Gramm-Darstellung mithilfe von Featureauswahl und Normalisierungstechniken optimieren können.

In einem N-Gramm-Vektor wird Text als eine Sammlung eindeutiger N-Gramme dargestellt: Gruppen von n benachbarten Tokens (in der Regel Wörter). Betrachten Sie den Text The mouse ran up the clock. Hier:

  • Das Wort Unigramme (n = 1) ist ['the', 'mouse', 'ran', 'up', 'clock'].
  • Das Wort "Bigrams" (n = 2) ist ['the mouse', 'mouse ran', 'ran up', 'up the', 'the clock']
  • Dies sind nur einige Beispiele für die Bedeutung von Data Governance.

Tokenisierung

Wir haben festgestellt, dass die Tokenisierung in Wortunigramme und Bigramme eine gute Genauigkeit bietet und gleichzeitig weniger Rechenzeit benötigt.

Vektorisierung

Nachdem wir unsere Textproben 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 Bigrammen 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}

Nachdem Indexe den N-Grammen zugewiesen wurden, vektorisieren wir normalerweise mit einer der folgenden Optionen.

One-Hot-Codierung: Jeder Beispieltext wird als Vektor dargestellt, der angibt, ob ein Token im Text vorhanden ist oder nicht.

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

Zählungscodierung: Jeder Beispieltext wird als Vektor dargestellt, der die Anzahl eines Tokens im Text angibt. Beachten Sie, dass das Element, das dem Unigramm "the" entspricht, jetzt als 2 dargestellt wird, da das Wort "the" zweimal im Text 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 genannten Ansätzen besteht darin, dass gängige Wörter, die in allen Dokumenten in ähnlicher Häufigkeit vorkommen (d.h. Wörter, die für die Textbeispiele im Dataset nicht einzigartig sind) nicht bestraft werden. Beispielsweise kommen Wörter wie „a“ sehr häufig in allen Texten vor. Daher ist eine höhere Tokenanzahl für „der“ als für andere aussagekräftigere Wörter 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]

(Siehe Scikit-learn TfidfTransformer)

Es gibt viele weitere Vektordarstellungen, aber die drei vorherigen sind die am häufigsten verwendeten.

Wir haben festgestellt, dass die tf-idf-Codierung in Bezug auf die Genauigkeit geringfügig besser ist als die anderen beiden (im Durchschnitt 0,25–15% höher). Daher empfehlen wir die Verwendung dieser Methode zur Vektorisierung von N-Grammen. Beachten Sie jedoch, dass es mehr Arbeitsspeicher benötigt (da die Gleitkommadarstellung verwendet wird) und die Berechnung mehr Zeit in Anspruch nimmt, insbesondere bei großen Datasets (kann in einigen Fällen doppelt so lange dauern).

Auswahl von Merkmalen

Wenn wir alle Texte in einem Dataset in Wort-Uni+Bigram-Tokens umwandeln, können Zehntausende Tokens vorliegen. Nicht alle diese Tokens/Features tragen zur Labelvorhersage bei. So können wir bestimmte Tokens löschen, z. B. Tokens, die sehr selten im Dataset vorkommen. Außerdem können wir die Wichtigkeit von Features messen (wie viel jedes Token zu Labelvorhersagen beiträgt) und nur die informativsten Tokens einbeziehen.

Es gibt viele statistische Funktionen, die Merkmale und die entsprechenden Labels übernehmen und die Merkmalwichtigkeitsrate ausgeben. Zwei häufig verwendete Funktionen sind f_classif und chi2. Unsere Tests haben gezeigt, dass beide Funktionen gleichermaßen gut funktionieren.

Noch wichtiger ist jedoch, dass die Genauigkeit bei vielen Datasets bei etwa 20.000 Merkmalen liegt (siehe Abbildung 6). Das Hinzufügen weiterer Features über diesen Grenzwert trägt sehr wenig bei und führt manchmal sogar zu einer Überanpassung und verschlechtert die Leistung.

Top-K im Vergleich zur Genauigkeit

Abbildung 6: Top-K-Funktionen im Vergleich zur Genauigkeit Über die Datasets hinweg steigt die Genauigkeit bei den oberen 20.000 Features.

Normalisierung

Bei der Normalisierung werden alle Feature-/Stichprobenwerte in kleine und ähnliche Werte umgewandelt. Dies vereinfacht die Konvergenz des Gradientenabstiegs in Lernalgorithmen. Soweit wir gesehen haben, scheint die Normalisierung während der Datenvorverarbeitung bei Textklassifizierungsproblemen keinen Mehrwert zu schaffen. Wir empfehlen, diesen Schritt zu überspringen.

Mit dem folgenden Code werden alle oben genannten Schritte zusammengefasst:

  • Tokenisieren Sie Textproben in Wort-Uni+Bigramme,
  • Vektorisieren Sie mit der tf-idf-Codierung,
  • Wählen Sie nur die 20.000 wichtigsten Merkmale aus dem Tokenvektor aus. Verwerfen Sie dazu Tokens, die weniger als zweimal vorkommen, und verwenden Sie f_classif, um die Wichtigkeit von Features zu berechnen.
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 über Wortreihenfolge und Grammatik. Im besten Fall können wir einige Teilanordnungsinformationen beibehalten, wenn n > 1 ist. Dies wird als „Bag of Words“-Ansatz bezeichnet. Diese Darstellung wird in Verbindung mit Modellen verwendet, die die Reihenfolge nicht berücksichtigen, wie logistische Regressionen, mehrschichtige Perceptrons, Gradient-Boosting-Maschinen und Supportvektormaschinen.

Sequenzvektoren [Option B]

In den folgenden Abschnitten erfahren Sie, wie Sie Tokenisierung und Vektorisierung für Sequenzmodelle vornehmen. Außerdem erfahren Sie, wie Sie die Sequenzdarstellung mithilfe von Featureauswahl- und Normalisierungstechniken optimieren können.

Bei einigen Textbeispielen ist die Wortreihenfolge für die Bedeutung des Textes entscheidend. Zum Beispiel die Sätze: „Ich habe meinen Arbeitsweg gehasst. Mein neues Fahrrad hat sich völlig verändert.“ 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 eine Abfolge von Tokens dar, wobei die Reihenfolge beibehalten wird.

Tokenisierung

Text kann entweder als Folge von Zeichen oder als Folge von Wörtern dargestellt werden. Wir haben festgestellt, dass eine Darstellung auf Wortebene eine bessere Leistung bietet als Zeichentokens. Dies ist auch die allgemeine Norm, gefolgt von der Branche. Die Verwendung von Zeichentokens ist nur dann sinnvoll, wenn Texte viele Tippfehler enthalten. Dies ist normalerweise nicht der Fall.

Vektorisierung

Nachdem wir unsere Textbeispiele 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, sowie die Reihenfolge der Tokenindexe, in die der erste Text konvertiert wird.

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

Index, der jedem Token zugewiesen ist:

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

HINWEIS: Das Wort "the" kommt am häufigsten vor, sodass ihm der Indexwert 1 zugewiesen wird. Einige Bibliotheken reservieren Index 0 für unbekannte Tokens, wie hier der Fall ist.

Reihenfolge der Tokenindexe:

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

Es gibt zwei Möglichkeiten, die Tokensequenzen zu vektorisieren:

One-Hot-Codierung: Sequenzen werden mithilfe von Wortvektoren in einem n-dimensionalen Bereich dargestellt, wobei n = Größe des Vokabulars. Diese Darstellung funktioniert großartig, wenn wir als Zeichen tokenisieren. Das Vokabular ist daher klein. Bei der Tokenisierung in Form von Wörtern besteht das Vokabular in der Regel aus Zehntausenden von Tokens, was die One-Hot-Vektoren sehr dünnbesetzt 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 haben eine oder mehrere Bedeutungen. Dadurch können Worttokens in einem dichten Vektorraum (ca. ein paar hundert reellen Zahlen) dargestellt werden, wobei die Position und der Abstand zwischen den Wörtern angeben, wie ähnlich sie semantisch sind (siehe Abbildung 7). Diese Darstellung wird als Worteinbettung bezeichnet.

Worteinbettungen

Abbildung 7: Worteinbettungen

Sequenzmodelle haben oft eine solche Einbettungsschicht als erste Schicht. Diese Schicht lernt, während des Trainingsprozesses Wortindexsequenzen in Worteinbettungsvektoren umzuwandeln, sodass jeder Wortindex einem dichten Vektor aus reellen Werten zugeordnet wird, die die Position des Worts im semantischen Raum 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 Merkmale im Allgemeinen ausreichend ist. Dies gilt auch für N-Gramm-Modelle (siehe Abbildung 6).

Fassen wir nun alle oben genannten Schritte in einer Sequenzvektorisierung zusammen. Der folgende Code führt diese Aufgaben aus:

  • Tokenisiert die Texte in Wörter
  • Erstellt ein Vokabular aus den Top-20.000-Tokens
  • Wandelt die Tokens in Sequenzvektoren um
  • Pads für 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 Beispieltextdaten in numerische Vektoren umgewandelt werden. Ein ähnlicher Prozess muss auf die Labels angewendet werden. Wir können Labels einfach in Werte im Bereich [0, num_classes - 1] umwandeln. Wenn es beispielsweise drei Klassen gibt, können wir sie einfach mit den Werten 0, 1 und 2 darstellen. Intern verwendet das Netzwerk One-Hot-Vektoren, um diese Werte darzustellen, um zu vermeiden, dass eine falsche Beziehung zwischen Labels abgeleitet wird. Diese Darstellung hängt von der Verlustfunktion und der Aktivierungsfunktion der letzten Schicht ab, die wir in unserem neuronalen Netzwerk verwenden. Wir werden im nächsten Abschnitt mehr darüber erfahren.