Krok 3. Przygotuj dane

Zanim będziemy mogli pobrać dane do modelu, należy go przekształcić do formatu, który jest zrozumiały dla modelu.

Przede wszystkim zbieramy próbki danych w określonej kolejności. Nie chcemy, aby informacje powiązane z kolejnością próbek wpływały na relację między tekstami a etykietami. Jeśli na przykład zbiór danych zostanie posortowany według klasy, a następnie podzielony na zbiory do trenowania/walidacji, te zestawy nie będą reprezentować ogólnego rozkładu danych.

Najlepszą metodą jest upewnienie się, że kolejność danych nie wpływa na model, zanim zdecydujesz, czy chcesz zrobić coś innego. Jeśli Twoje dane są już podzielone na zbiory do trenowania i walidacji, przekształć je w taki sam sposób jak dane do trenowania. Jeśli nie masz jeszcze oddzielnych zbiorów do trenowania i walidacji, możesz podzielić próbki po tasowaniu. Zwykle jest to 80% próbek do trenowania i 20% do walidacji.

Po drugie, algorytmy systemów uczących się gromadzą liczby jako dane wejściowe. Oznacza to, że trzeba przekonwertować tekst na wektory liczbowe. Ten proces składa się z 2 kroków:

  1. Tokenizacja: podziel tekst na słowa lub mniejsze podteksty, co pozwoli poprawnie uogólnić relację między tekstem a etykietami. Określa „słowność” zbioru danych (zestaw unikalnych tokenów w danych).

  2. Wektorowanie: określenie prawidłowego środka liczbowego, który umożliwi scharakteryzowanie tych tekstów.

Zobaczmy, jak wykonać te 2 kroki dla wektorów n g i wektorów sekwencji oraz jak zoptymalizować reprezentacje wektorowe za pomocą technik wyboru i normalizacji.

Wektory n-gramowe [opcja A]

W kolejnych akapitach pokażemy, jak dokonać tokenizacji i wektoryzacji w modelach n-gramów. Wyjaśnimy w nim też, jak zoptymalizować reprezentację n gramów za pomocą technik wyboru i normalizacji.

Wektorom n-gramowym tekst jest przedstawiany jako zbiór unikalnych n-gramów: grupy n przyległych tokenów (zwykle słów). Weź pod uwagę tekst The mouse ran up the clock. Słowo unigramy (n = 1) to ['the', 'mouse', 'ran', 'up', 'clock'], słowo Bigram (n = 2) to ['the mouse', 'mouse ran', 'ran up', 'up the', 'the clock'] itd.

Tokenizacja

Zauważyliśmy, że tokenizacja do unigramów + Bigramów pozwala na uzyskanie większej dokładności przy mniejszym nakładzie mocy obliczeniowej.

Wektorowanie

Po podzieleniu próbek tekstu na n-gramy musimy przekształcić te n-gramy w wektory liczbowe, które mogą być przetwarzane przez nasze modele systemów uczących się. Przykład poniżej pokazuje indeksy przypisane do unigramów i ramek wygenerowanych dla 2 tekstów.

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}

Gdy indeksy są przypisane do n gramów, wektorujemy zwykle za pomocą jednej z tych opcji.

Jednokodowe kodowanie: każdy przykładowy tekst jest wyświetlany jako wektor, który wskazuje na obecność lub brak tokena.

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

Kodowanie licznika: każdy przykładowy tekst jest reprezentowany jako wektor wskazujący liczbę tokenów w tekście. Pamiętaj, że element odpowiadający unigramowi „#”9 (pogrubiony poniżej) jest teraz przedstawiany jako 2, ponieważ słowo „the” występuje w tekście dwukrotnie.

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

Kodowanie Tf-idf: problem z podanymi wyżej sposobami polega na tym, że popularne słowa, które występują w podobnych częstotliwośćch we wszystkich dokumentach (tj. słowa, które nie są unikalne dla próbek tekstu w zbiorze danych), nie są karane. Na przykład słowa „a” będą występować bardzo często we wszystkich tekstach. Dlatego wyższa liczba tokenów „the” niż „inne” ma znaczenie.

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

Jest też wiele innych wektorów, ale powyższe są najczęściej używane.

Zaobserwowano, że kodowanie tf-idf jest nieco lepsze niż pozostałe dwa pod względem dokładności (średnio 0,25–15%) i zalecamy użycie tej metody do wektorowania n gramów. Pamiętaj jednak, że zajmuje to więcej pamięci (ponieważ wykorzystuje reprezentację zmiennoprzecinkową) i oblicza się więcej czasu, zwłaszcza w przypadku dużych zbiorów danych (w niektórych przypadkach może to trwać dwukrotnie dłużej).

Wybór funkcji

Gdy przekształcimy wszystkie teksty w zbiorze danych w tokeny słów uni+bigram, może dojść do dziesiątek tysięcy tokenów. Nie wszystkie te tokeny/funkcje przyczyniają się do prognozowania etykiet. Możemy więc odrzucić niektóre tokeny, na przykład te, które występują bardzo rzadko w zbiorze danych. Możemy też mierzyć znaczenie funkcji (w jakim stopniu poszczególne tokeny przyczyniają się do prognozowania etykiet) i uwzględniać tylko najbardziej informacyjne tokeny.

Jest wiele funkcji statystycznych, które wykorzystują cechy i odpowiadające im etykiety i zwracają wynik znaczenia cech. Dwie często używane funkcje to f_classif i chi2. Nasze eksperymenty pokazały, że obie funkcje działają tak samo dobrze.

Co ważniejsze, dokładność wielu zbiorów danych wynosi około 20 tys. funkcji (zob. ilustracja 6). Dodanie nowych funkcji powyżej tego progu bardzo przyczynia się do niewielkiego nadmiernego przemiany, a nawet obniża wydajność.

Najlepsze od K do dokładności

Rysunek 6. Najlepsze funkcje K a dokładność W zbiorach danych; poziom dokładności na poziomie ok. 20 tys. funkcji.

Normalizacja

Normalizacja konwertuje wszystkie wartości cech lub próbek na małe i podobne wartości. Ułatwia to zbieżność zbiegu gradientowego w algorytmach nauczania. Zauważyliśmy, że normalizacja podczas wstępnego przetwarzania danych nie jest wartościowa w przypadku problemów z klasyfikacją tekstu. Zalecamy pominięcie tego kroku.

Ten kod łączy wszystkie powyższe kroki:

  • tokenyzować próbki tekstowe na podstawie bibliogramów,
  • zakoduj pliki tf-idf wektorowo,
  • Wybierz tylko 20 000 obiektów z wektora, odrzucając tokeny, które wyświetlają się mniej niż 2 razy, i używając f_classif do obliczania ważności cech.
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

Przy prezentowaniu wektorów n-gram odrzucamy wiele informacji o kolejności słów i gramatyce (najlepiej możemy zachować częściowe informacje o kolejności, n > 1). To tak zwany podejście pełne słów. Reprezentacja ta jest wykorzystywana w połączeniu z modelami, które nie uwzględniają kolejności, takich jak regresja logistyczna, wielowarstwowe percepola, maszyny wzmocnione gradientem lub maszyny wektorowe.

Wektory sekwencji [opcja B]

W kolejnych akapitach pokażemy, jak dokonać tokenizacji i wektoryzacji w modelach sekwencji. Pokażemy też, jak zoptymalizować sekwencję za pomocą technik wyboru i normalizacji.

W przypadku niektórych próbek tekstu kolejność słów ma kluczowe znaczenie dla znaczenia tekstu. Na przykład: „Zdarzało mi się nie dojeżdżać do pracy. Nowy rower zmienił się całkowicie”. Modele takie jak CNN/RNN mogą określać znaczenie słów w próbce. W przypadku tych modeli tekst jest przedstawiony jako sekwencja tokenów z zachowaniem kolejności.

Tokenizacja

Tekst może być sekwencją znaków lub sekwencją słów. Zauważyliśmy, że korzystanie z reprezentacji na poziomie słowa kluczowego zapewnia większą wydajność niż tokeny znaków. Jest to ogólna norma, której przestrzegają branże. Używanie tokenów znaków ma sens tylko wtedy, gdy w tekstach jest wiele literówek – co zwykle nie jest normalne.

Wektorowanie

Po przekształceniu próbek tekstu w sekwencje słów musimy przekształcić te sekwencje w wektory liczbowe. Poniższy przykład pokazuje indeksy przypisane do unigramów wygenerowanych dla 2 tekstów, a następnie sekwencja indeksów tokenów, na które przekonwertowano pierwszy tekst.

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]

Wektory sekwencji tokenów są dostępne na 2 sposoby:

Kodowanie jednorazowe: sekwencje są określane wektorami słów w n-wymiarowej przestrzeni, gdzie n = rozmiar słownika. Świetnie sprawdza się to, gdy tokenizujemy postacie, a ich słownictwo jest małe. Tokenizując jako słowa, zwykle otrzymujemy dziesiątki tysięcy tokenów, co sprawia, że w ten sposób jest bardzo ograniczony. Przykład

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

Umieszczenie słowa: słowa mają znaczenie. Dzięki temu tokeny słów mogą być przedstawione w gęstej przestrzeni wektorowej (ok. kilkaset prawdziwych liczb), gdzie lokalizacja i odległość między słowami wskazują, jak bardzo są one semantyczne (patrz Rysunek 7). Nazywamy to umieszczaniem słów.

Umieszczanie słów

Rysunek 7. Osadzanie słów

Modele sekwencji często mają taką warstwę do umieszczenia jako pierwsza. W ramach tej nauki uczy się przekształcać sekwencje indeksów słów w wektory osadzania słów podczas treningu, tak aby każdy indeks słów został przyporządkowany do gęstego wektora rzeczywistych wartości reprezentujących jego lokalizację w semantycznej przestrzeni (patrz Rysunek 8).

Warstwa

Rysunek 8. Umieszczanie warstwy

Wybór funkcji

Nie wszystkie słowa w naszych danych są wykorzystywane do tworzenia prognoz etykiet. Możemy zoptymalizować proces nauki, odrzucając rzadkie lub nietrafne słowa z naszego słownika. W rzeczywistości wystarczy 20 000 funkcji, które są najczęściej używane. Dotyczy to także modeli n-gramów (patrz Rysunek 6).

Połączmy wszystkie powyższe kroki w metodę wektorową. Ten kod wykonuje te zadania:

  • Tokenizuje tekst w słowa
  • Tworzy słownik,używając 20 tys. najlepszych tokenów
  • Konwertuje tokeny na wektory sekwencyjne
  • Paduje sekwencje o stałej długości
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

Wektor etykiet

Przekonaliśmy się, jak przekonwertować przykładowe dane tekstowe na wektory liczbowe. Podobny proces trzeba zastosować w etykietach. Możemy po prostu przekonwertować etykiety na wartości z zakresu [0, num_classes - 1]. Jeśli na przykład mamy 3 klasy, możemy użyć tylko wartości 0, 1 i 2 do ich reprezentowania. Wewnętrznie do określania tych wartości sieć używa jednokrotnych wektorów (aby uniknąć ustalenia niewłaściwej relacji między etykietami). Jest to uzależnione od funkcji utraty i ostatniej aktywacji w sieci neuronowej. Więcej informacji na ten temat znajdziesz w następnej sekcji.