Krok 3. Przygotuj dane

Zanim nasze dane trafią do modelu, trzeba je przekształcić do formatu, który model rozumie.

Po pierwsze, zebrane przez nas próbki danych mogą być podane w określonej kolejności. Nie chcemy, aby żadne informacje związane z kolejnością próbek wpływały na relację między tekstami a etykietami. Jeśli na przykład zbiór danych jest posortowany według klasy, a potem podzielony na zbiory do trenowania i walidacji, nie będą one reprezentatywne dla ogólnego rozkładu danych.

Prostym sposobem na zagwarantowanie, że kolejność danych nie będzie miała na model wpływu, jest zawsze tasowanie danych przed wykonaniem czegokolwiek innego. Jeśli Twoje dane są już podzielone na zbiory do trenowania i walidacji, pamiętaj, aby przekształcić dane weryfikacyjne w taki sam sposób, w jaki są przekształcane. Jeśli nie masz jeszcze osobnych zbiorów do trenowania i walidacji, możesz podzielić próbki po tasowaniu. Zazwyczaj jest używana 80% próbek do trenowania i 20% do weryfikacji.

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

  1. Tokenizacja: podziel tekst na słowa lub mniejsze teksty podrzędne, co pozwoli dobrze uogólnić relacje między tekstami a etykietami. Określa „słownik” zbioru danych (zbiór unikalnych tokenów znajdujących się w danych).

  2. Wektoryzacja: określ dobry wskaźnik liczbowy, aby scharakteryzować te teksty.

Zobaczmy, jak wykonać te 2 kroki w przypadku wektorów n-gramów i wektorów sekwencyjnych, a także jak zoptymalizować reprezentację wektorów za pomocą technik wyboru cech i normalizacji.

Wektory n-gram [opcja A]

W kolejnych akapitach dowiemy się, jak przeprowadzać tokenizację i wektoryzację w przypadku modeli ngram. Omówimy też sposób optymalizacji reprezentacji n-gramów za pomocą technik wyboru cech i normalizacji.

W wektorze n-gram tekst jest przedstawiany jako zbiór unikalnych n-gramów: grupy n sąsiednich tokenów (zwykle słów). Zastanów się nad tekstem The mouse ran up the clock. Tutaj:

  • Słowo unigramy (n = 1) to: ['the', 'mouse', 'ran', 'up', 'clock'].
  • Słowo bigrams (n = 2) to: ['the mouse', 'mouse ran', 'ran up', 'up the', 'the clock']
  • I tak dalej.

Tokenizacja

Okazuje się, że tokenizacja do postaci unigramów słów + bigrams zapewnia dobrą dokładność, a jednocześnie mniej czasu na obliczenia.

Wektoryzacja

Po podzieleniu próbek tekstowych na n-gramów musimy zamienić te n-gramy w wektory liczbowe, które modele systemów uczących się mogą przetworzyć. Przykład poniżej pokazuje indeksy przypisane do unigramów i bigramów 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}

Po przypisaniu indeksów do n-gramów zwykle wektoryzujemy wektory przy użyciu jednej z podanych niżej opcji.

Kodowanie jednorazowe: każdy przykładowy tekst jest reprezentowany jako wektor wskazujący obecność lub brak tokena w tekście.

'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. Zauważ, że element odpowiadający unigramowi „the” 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 2 powyższymi podejściami polega na tym, że typowe słowa, które występują z podobną częstotliwością we wszystkich dokumentach (czyli takich, które nie są szczególnie unikalne dla próbek tekstu w zbiorze danych), nie są karane. Na przykład takie słowa jak „a” będą się pojawiać bardzo często we wszystkich tekstach. Większa liczba tokenów dla „the” niż innych bardziej znaczących słów nie jest zbyt przydatna.

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

(Patrz: Scikit-learn TfidfTransformer).

Istnieje wiele innych reprezentacji wektorowych, ale najczęściej używane są 3 poprzednie.

Zauważyliśmy, że kodowanie tf-IDF jest nieznacznie lepsze pod względem dokładności niż pozostałe 2 pozostałe pod względem dokładności (średnio o 0,25–15%), i zalecamy użycie tej metody do wektoryzacji n-gramów. Pamiętaj jednak, że zajmuje on więcej pamięci (ponieważ wykorzystuje reprezentację zmiennoprzecinkową) i jego obliczenia wymagają więcej czasu, szczególnie w przypadku dużych zbiorów danych (w niektórych przypadkach może to potrwać 2 razy dłużej).

Wybór funkcji

Gdy przekonwertujemy wszystkie teksty w zbiorze danych na tokeny uni+bigram słów, możemy otrzymać dziesiątki tysięcy tokenów. Nie wszystkie z tych tokenów/funkcji pomagają w prognozowaniu etykiet. Możemy więc porzucić pewne tokeny, na przykład te, które występują bardzo rzadko w zbiorze danych. Możemy też mierzyć znaczenie cech (w jakim stopniu każdy token przyczynia się do prognoz dotyczących etykiet) i uwzględniać tylko tokeny zawierające informacje.

Istnieje wiele funkcji statystycznych, które przyjmują cechy i odpowiadające im etykiety i wyświetlają wynik znaczenia cech. Dwie często używane funkcje to f_classif i chi2. Z naszych eksperymentów wynika, że obie te funkcje działają tak samo dobrze.

Co ważniejsze, zaobserwowaliśmy,że dokładność jest najwyższa przy około 20 000 cech dla wielu zbiorów danych (zobacz Rysunek 6). Dodanie większej liczby funkcji powyżej tego progu przekłada się na bardzo mało, a czasem nawet do jego przepasowania, a nawet pogorszenia wydajności.

Górne K a dokładność

Rysunek 6. Najważniejsze funkcje K a dokładność We wszystkich zbiorach danych płaskowyże dokładności wynoszą około 20 tys. najlepszych obiektów.

Normalizacja

Normalizacja konwertuje wszystkie wartości cech/próbek na małe i podobne wartości. Upraszcza to zbieżność opadania gradientu w algorytmach uczenia się. Zauważyliśmy, że normalizacja podczas wstępnego przetwarzania danych nie wnosi zbyt wiele do problemów z klasyfikacją tekstu. Zalecamy pominięcie tego kroku.

Poniższy kod łączy wszystkie powyższe kroki:

  • tokenizację fragmentów tekstu w kodach uni+bigram,
  • Wektoryzuj za pomocą kodowania tf-idf,
  • Wybierz tylko 20 000 najważniejszych cech z wektora tokenów, odrzucając tokeny, które pojawiają się mniej niż 2 razy, i używając klasy 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

W przypadku reprezentacji wektorów n-gramów odrzucamy wiele informacji o kolejności słów i gramatyce (w przypadku n > 1 możemy zachować częściowe informacje dotyczące kolejności). Jest to tzw. szeptana strategia. Reprezentacja ta jest używana w połączeniu z modelami, które nie uwzględniają kolejności, takimi jak regresja logistyczna, wielowarstwowe perceptrony, maszyny wzmacniające gradientem, obsługują maszyny wektorowe.

Wektory sekwencji [opcja B]

W kolejnych akapitach pokażemy, jak przeprowadzać tokenizację i wektoryzację w modelach sekwencji. Omówimy też, jak można zoptymalizować reprezentację sekwencji za pomocą technik wyboru cech i normalizacji.

W niektórych przykładach tekstu kolejność słów ma znaczenie dla znaczenia tekstu. Mogą to być na przykład zdania „Kiedyś nienawidziłem(-am) dojeżdżania do pracy. Mój nowy rower całkowicie się zmienił”. Można to zrozumieć tylko po kolei, Modele takie jak CNN/RNN mogą wywnioskować znaczenie na podstawie kolejności słów w próbce. W tych modelach tekst jest reprezentowany jako sekwencja tokenów z zachowaniem kolejności.

Tokenizacja

Tekst może być ciągiem znaków lub słów. Zauważyliśmy, że użycie reprezentacji na poziomie słowa zapewnia większą wydajność niż tokeny znaków. To także ogólna norma, która obowiązuje w branży. Używanie tokenów znaków ma sens tylko wtedy, gdy tekst zawiera dużo literówek – jest to normalne.

Wektoryzacja

Po przekonwertowaniu próbek tekstowych na sekwencje słów trzeba przekształcić te ciągi w wektory liczbowe. Przykład poniżej pokazuje indeksy przypisane do unigramów wygenerowanych dla 2 tekstów, a następnie sekwencję indeksów tokenów, do których jest konwertowany pierwszy tekst.

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

Indeks przypisany do każdego tokena:

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

UWAGA: słowo „the” występuje najczęściej, dlatego zostaje mu przypisana wartość indeksu równa 1. Tak jak w tym przypadku, niektóre biblioteki rezerwują indeks 0 dla nieznanych tokenów.

Kolejność indeksów tokenów:

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

Dostępne są 2 opcje wektoryzacji sekwencji tokenów:

Kodowanie jednorazowe: sekwencje są przedstawiane za pomocą wektorów słów w przestrzeni n-wymiarowej, gdzie n = rozmiar słownika. Ta reprezentacja sprawdza się, gdy tokenizujemy jako znaki, więc słownictwo jest bardzo małe. Gdy tokenizujemy jako słowa, słownictwo zwykle zawiera dziesiątki tysięcy tokenów, co sprawia, że jedno gorące wektory są bardzo rzadkie i nieefektywne. 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]
]

Wektory dystrybucyjne słów: słowa mają powiązane z nimi znaczenie. W efekcie możemy przedstawić tokeny słów w gęstej przestrzeni wektorowej (ok. kilkuset liczb rzeczywistych), gdzie lokalizacja i odległość między słowami wskazują na ich podobieństwo semantyczne (zobacz Rys. 7). Jest to tzw. wektory dystrybucyjne słów.

Umieszczanie słów

Rysunek 7. Wektory dystrybucyjne słów

Modele sekwencji często mają taką warstwę wektorową jako pierwszą warstwę. Warstwa ta uczy się przekształcać sekwencje indeksów słów w wektory dystrybucyjne słów w trakcie procesu trenowania, dzięki czemu każdy indeks słów jest mapowany na gęsty wektor wartości rzeczywistych reprezentujących lokalizację słowa w przestrzeni semantycznej (zobacz Rysunek 8).

Warstwa osadzania

Rysunek 8. Warstwa osadzania

Wybór funkcji

Nie wszystkie słowa z naszych danych są uwzględniane w prognozach dotyczących etykiet. Nasz proces nauki można zoptymalizować, odrzucając rzadkie lub nieistotne słowa ze słownika. Zauważamy, że na ogół wystarczy użyć 20 tys. funkcji najczęściej używanych. Odnosi się to również do modeli n-gramów (zobacz Rys. 6).

Podsumujmy wszystkie powyższe kroki w ramach wektoryzacji sekwencyjnej. Ten kod wykonuje te zadania:

  • Tokenizacja tekstów w słowa
  • Tworzy słownictwo za pomocą 20 tys. najpopularniejszych tokenów
  • Konwertuje tokeny na wektory sekwencji
  • Pasuje sekwencje do stałej długości sekwencji
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

Wektoryzacja etykiet

Omówiliśmy sposób konwertowania przykładowych danych tekstowych na wektory liczbowe. Trzeba zastosować podobną procedurę do etykiet. Możemy po prostu przekonwertować etykiety na wartości w zakresie [0, num_classes - 1]. Jeśli np. istnieją 3 klasy, do ich przedstawienia możemy użyć wartości 0, 1 i 2. Wewnętrznie sieć będzie przedstawiać te wartości przy użyciu wektorów 1 gorąca (aby uniknąć wnioskowania nieprawidłowej relacji między etykietami). Reprezentacja zależy od funkcji utraty i funkcji aktywacji ostatniej warstwy, której używamy w naszej sieci neuronowej. Więcej informacji na ten temat znajdziesz w następnej sekcji.