Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

Podstawowa klasyfikacja tekstu

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

Ten samouczek przedstawia klasyfikację tekstu, począwszy od zwykłych plików tekstowych przechowywanych na dysku. Wyszkolisz klasyfikator binarny do przeprowadzania analizy nastrojów na zbiorze danych IMDB. Na końcu zeszytu znajduje się ćwiczenie do wypróbowania, w którym wyszkolisz klasyfikator wieloklasowy do przewidywania znacznika dla pytania programistycznego dotyczącego przepełnienia stosu.

 import matplotlib.pyplot as plt
import os
import re
import shutil
import string
import tensorflow as tf

from tensorflow.keras import layers
from tensorflow.keras import losses
from tensorflow.keras import preprocessing
from tensorflow.keras.layers.experimental.preprocessing import TextVectorization
 
 print(tf.__version__)
 
2.3.0

Analiza nastrojów

Ten notatnik uczy modelu analizy nastrojów, aby klasyfikować recenzje filmów jako pozytywne lub negatywne , na podstawie tekstu recenzji. To jest przykład binarnej - lub dwuklasowej - klasyfikacji, ważnego i szeroko stosowanego rodzaju problemu uczenia maszynowego.

Będziesz korzystać z dużego zbioru danych recenzji filmów, który zawiera tekst 50 000 recenzji filmów z internetowej bazy danych filmów . Są one podzielone na 25 000 recenzji do szkoleń i 25 000 recenzji do testów. Zestawy treningowe i testowe są zbilansowane , co oznacza, że ​​zawierają równą liczbę pozytywnych i negatywnych recenzji.

Pobierz i przeglądaj zbiór danych IMDB

Pobierzmy i wyodrębnijmy zbiór danych, a następnie zbadajmy strukturę katalogów.

 url = "https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz"

dataset = tf.keras.utils.get_file("aclImdb_v1.tar.gz", url,
                                    untar=True, cache_dir='.',
                                    cache_subdir='')

dataset_dir = os.path.join(os.path.dirname(dataset), 'aclImdb')
 
Downloading data from https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz
84131840/84125825 [==============================] - 7s 0us/step

 os.listdir(dataset_dir)
 
['imdb.vocab', 'train', 'test', 'README', 'imdbEr.txt']
 train_dir = os.path.join(dataset_dir, 'train')
os.listdir(train_dir)
 
['urls_pos.txt',
 'neg',
 'labeledBow.feat',
 'pos',
 'urls_neg.txt',
 'unsup',
 'unsupBow.feat',
 'urls_unsup.txt']

aclImdb/train/pos i aclImdb/train/neg zawierają wiele plików tekstowych, z których każdy jest pojedynczą recenzją filmu. Przyjrzyjmy się jednemu z nich.

 sample_file = os.path.join(train_dir, 'pos/1181_9.txt')
with open(sample_file) as f:
  print(f.read())
 
Rachel Griffiths writes and directs this award winning short film. A heartwarming story about coping with grief and cherishing the memory of those we've loved and lost. Although, only 15 minutes long, Griffiths manages to capture so much emotion and truth onto film in the short space of time. Bud Tingwell gives a touching performance as Will, a widower struggling to cope with his wife's death. Will is confronted by the harsh reality of loneliness and helplessness as he proceeds to take care of Ruth's pet cow, Tulip. The film displays the grief and responsibility one feels for those they have loved and lost. Good cinematography, great direction, and superbly acted. It will bring tears to all those who have lost a loved one, and survived.

Załaduj zbiór danych

Następnie załadujesz dane z dysku i przygotujesz je do formatu odpowiedniego do treningu. Aby to zrobić, skorzystasz z pomocnego narzędzia text_dataset_from_directory , które oczekuje następującej struktury katalogów.

 main_directory/
...class_a/
......a_text_1.txt
......a_text_2.txt
...class_b/
......b_text_1.txt
......b_text_2.txt
 

Aby przygotować zbiór danych do klasyfikacji binarnej, będziesz potrzebować dwóch folderów na dysku, odpowiadających class_a i class_b . Będą to pozytywne i negatywne recenzje filmów, które można znaleźć w aclImdb/train/pos i aclImdb/train/neg . Ponieważ zbiór danych IMDB zawiera dodatkowe foldery, przed użyciem tego narzędzia należy je usunąć.

 remove_dir = os.path.join(train_dir, 'unsup')
shutil.rmtree(remove_dir)
 

Następnie za pomocą narzędzia text_dataset_from_directory utworzysz tf.data.Dataset etykietą. tf.data to potężny zbiór narzędzi do pracy z danymi.

Podczas przeprowadzania eksperymentu z uczeniem maszynowym najlepiej jest podzielić zbiór danych na trzy części: trenowanie , walidacja i testowanie .

Zbiór danych IMDB został już podzielony na szkolenia i testy, ale brakuje w nim zestawu walidacyjnego. Stwórzmy zestaw walidacyjny przy użyciu podziału 80:20 danych uczących przy użyciu argumentu validation_split poniżej.

 batch_size = 32
seed = 42

raw_train_ds = tf.keras.preprocessing.text_dataset_from_directory(
    'aclImdb/train', 
    batch_size=batch_size, 
    validation_split=0.2, 
    subset='training', 
    seed=seed)
 
Found 25000 files belonging to 2 classes.
Using 20000 files for training.

Jak widać powyżej, w folderze szkoleniowym znajduje się 25 000 przykładów, z których 80% (lub 20 000) wykorzystasz na szkolenie. Jak zobaczysz za chwilę, możesz wytrenować model, przekazując zbiór danych bezpośrednio do model.fit . Jeśli nie tf.data , możesz również iterować zestaw danych i wydrukować kilka przykładów w następujący sposób.

 for text_batch, label_batch in raw_train_ds.take(1):
  for i in range(3):
    print("Review", text_batch.numpy()[i])
    print("Label", label_batch.numpy()[i])
 
Review b'"Pandemonium" is a horror movie spoof that comes off more stupid than funny. Believe me when I tell you, I love comedies. Especially comedy spoofs. "Airplane", "The Naked Gun" trilogy, "Blazing Saddles", "High Anxiety", and "Spaceballs" are some of my favorite comedies that spoof a particular genre. "Pandemonium" is not up there with those films. Most of the scenes in this movie had me sitting there in stunned silence because the movie wasn\'t all that funny. There are a few laughs in the film, but when you watch a comedy, you expect to laugh a lot more than a few times and that\'s all this film has going for it. Geez, "Scream" had more laughs than this film and that was more of a horror film. How bizarre is that?<br /><br />*1/2 (out of four)'
Label 0
Review b"David Mamet is a very interesting and a very un-equal director. His first movie 'House of Games' was the one I liked best, and it set a series of films with characters whose perspective of life changes as they get into complicated situations, and so does the perspective of the viewer.<br /><br />So is 'Homicide' which from the title tries to set the mind of the viewer to the usual crime drama. The principal characters are two cops, one Jewish and one Irish who deal with a racially charged area. The murder of an old Jewish shop owner who proves to be an ancient veteran of the Israeli Independence war triggers the Jewish identity in the mind and heart of the Jewish detective.<br /><br />This is were the flaws of the film are the more obvious. The process of awakening is theatrical and hard to believe, the group of Jewish militants is operatic, and the way the detective eventually walks to the final violent confrontation is pathetic. The end of the film itself is Mamet-like smart, but disappoints from a human emotional perspective.<br /><br />Joe Mantegna and William Macy give strong performances, but the flaws of the story are too evident to be easily compensated."
Label 0
Review b'Great documentary about the lives of NY firefighters during the worst terrorist attack of all time.. That reason alone is why this should be a must see collectors item.. What shocked me was not only the attacks, but the"High Fat Diet" and physical appearance of some of these firefighters. I think a lot of Doctors would agree with me that,in the physical shape they were in, some of these firefighters would NOT of made it to the 79th floor carrying over 60 lbs of gear. Having said that i now have a greater respect for firefighters and i realize becoming a firefighter is a life altering job. The French have a history of making great documentary\'s and that is what this is, a Great Documentary.....'
Label 1

Zauważ, że recenzje zawierają nieprzetworzony tekst (z interpunkcją i sporadycznymi tagami HTML, takimi jak <br/> ). W następnej sekcji pokażesz, jak sobie z tym poradzić.

Etykiety to 0 lub 1. Aby zobaczyć, które z nich odpowiadają pozytywnym i negatywnym class_names filmów, możesz sprawdzić właściwość class_names w zbiorze danych.

 print("Label 0 corresponds to", raw_train_ds.class_names[0])
print("Label 1 corresponds to", raw_train_ds.class_names[1])
 
Label 0 corresponds to neg
Label 1 corresponds to pos

Następnie utworzysz walidacyjny i testowy zbiór danych. Pozostałe 5000 recenzji ze zbioru szkoleniowego wykorzystasz do walidacji.

 raw_val_ds = tf.keras.preprocessing.text_dataset_from_directory(
    'aclImdb/train', 
    batch_size=batch_size, 
    validation_split=0.2, 
    subset='validation', 
    seed=seed)
 
Found 25000 files belonging to 2 classes.
Using 5000 files for validation.

 raw_test_ds = tf.keras.preprocessing.text_dataset_from_directory(
    'aclImdb/test', 
    batch_size=batch_size)
 
Found 25000 files belonging to 2 classes.

Przygotuj zbiór danych do szkolenia

Następnie dokonasz standaryzacji, tokenizacji i wektoryzacji danych za pomocą preprocessing.TextVectorization warstwy preprocessing.TextVectorization .

Standaryzacja odnosi się do wstępnego przetwarzania tekstu, zwykle w celu usunięcia znaków interpunkcyjnych lub elementów HTML w celu uproszczenia zbioru danych. Tokenizacja odnosi się do dzielenia ciągów znaków na tokeny (na przykład dzielenie zdania na pojedyncze słowa przez dzielenie na białe znaki). Wektoryzacja odnosi się do zamiany tokenów na liczby, aby można je było wprowadzić do sieci neuronowej. Wszystkie te zadania można wykonać za pomocą tej warstwy.

Jak widzieliście powyżej, recenzje zawierają różne tagi HTML, takie jak <br /> . Te znaczniki nie zostaną usunięte przez domyślny standaryzator w warstwie TextVectorization (który konwertuje tekst na małe litery i domyślnie usuwa znaki interpunkcyjne, ale nie usuwa HTML). Napiszesz niestandardową funkcję standaryzacji, aby usunąć HTML.

 def custom_standardization(input_data):
  lowercase = tf.strings.lower(input_data)
  stripped_html = tf.strings.regex_replace(lowercase, '<br />', ' ')
  return tf.strings.regex_replace(stripped_html,
                                  '[%s]' % re.escape(string.punctuation),
                                  '')
 

Następnie TextVectorization warstwę TextVectorization . użyjesz tej warstwy do standaryzacji, tokenizacji i wektoryzacji naszych danych. output_mode na int aby utworzyć unikalne indeksy całkowite dla każdego tokenu.

Zwróć uwagę, że używasz domyślnej funkcji podziału i niestandardowej funkcji standaryzacji zdefiniowanej powyżej. Będziesz również zdefiniować pewne stałe dla tego modelu, podobnie jak wyraźnym maksimum sequence_length , co spowoduje warstwę do sekwencji pad lub obciąć aby dokładnie sequence_length wartości.

 max_features = 10000
sequence_length = 250

vectorize_layer = TextVectorization(
    standardize=custom_standardization,
    max_tokens=max_features,
    output_mode='int',
    output_sequence_length=sequence_length)
 

Następnie wywołasz adapt aby dopasować stan warstwy przetwarzania wstępnego do zestawu danych. Spowoduje to, że model zbuduje indeks łańcuchów do liczb całkowitych.

 # Make a text-only dataset (without labels), then call adapt
train_text = raw_train_ds.map(lambda x, y: x)
vectorize_layer.adapt(train_text)
 

Utwórzmy funkcję, aby zobaczyć wynik użycia tej warstwy do wstępnego przetworzenia niektórych danych.

 def vectorize_text(text, label):
  text = tf.expand_dims(text, -1)
  return vectorize_layer(text), label
 
 # retrieve a batch (of 32 reviews and labels) from the dataset
text_batch, label_batch = next(iter(raw_train_ds))
first_review, first_label = text_batch[0], label_batch[0]
print("Review", first_review)
print("Label", raw_train_ds.class_names[first_label])
print("Vectorized review", vectorize_text(first_review, first_label))
 
Review tf.Tensor(b'Silent Night, Deadly Night 5 is the very last of the series, and like part 4, it\'s unrelated to the first three except by title and the fact that it\'s a Christmas-themed horror flick.<br /><br />Except to the oblivious, there\'s some obvious things going on here...Mickey Rooney plays a toymaker named Joe Petto and his creepy son\'s name is Pino. Ring a bell, anyone? Now, a little boy named Derek heard a knock at the door one evening, and opened it to find a present on the doorstep for him. Even though it said "don\'t open till Christmas", he begins to open it anyway but is stopped by his dad, who scolds him and sends him to bed, and opens the gift himself. Inside is a little red ball that sprouts Santa arms and a head, and proceeds to kill dad. Oops, maybe he should have left well-enough alone. Of course Derek is then traumatized by the incident since he watched it from the stairs, but he doesn\'t grow up to be some killer Santa, he just stops talking.<br /><br />There\'s a mysterious stranger lurking around, who seems very interested in the toys that Joe Petto makes. We even see him buying a bunch when Derek\'s mom takes him to the store to find a gift for him to bring him out of his trauma. And what exactly is this guy doing? Well, we\'re not sure but he does seem to be taking these toys apart to see what makes them tick. He does keep his landlord from evicting him by promising him to pay him in cash the next day and presents him with a "Larry the Larvae" toy for his kid, but of course "Larry" is not a good toy and gets out of the box in the car and of course, well, things aren\'t pretty.<br /><br />Anyway, eventually what\'s going on with Joe Petto and Pino is of course revealed, and as with the old story, Pino is not a "real boy". Pino is probably even more agitated and naughty because he suffers from "Kenitalia" (a smooth plastic crotch) so that could account for his evil ways. And the identity of the lurking stranger is revealed too, and there\'s even kind of a happy ending of sorts. Whee.<br /><br />A step up from part 4, but not much of one. Again, Brian Yuzna is involved, and Screaming Mad George, so some decent special effects, but not enough to make this great. A few leftovers from part 4 are hanging around too, like Clint Howard and Neith Hunter, but that doesn\'t really make any difference. Anyway, I now have seeing the whole series out of my system. Now if I could get some of it out of my brain. 4 out of 5.', shape=(), dtype=string)
Label neg
Vectorized review (<tf.Tensor: shape=(1, 250), dtype=int64, numpy=
array([[1287,  313, 2380,  313,  661,    7,    2,   52,  229,    5,    2,
         200,    3,   38,  170,  669,   29, 5492,    6,    2,   83,  297,
         549,   32,  410,    3,    2,  186,   12,   29,    4,    1,  191,
         510,  549,    6,    2, 8229,  212,   46,  576,  175,  168,   20,
           1, 5361,  290,    4,    1,  761,  969,    1,    3,   24,  935,
        2271,  393,    7,    1, 1675,    4, 3747,  250,  148,    4,  112,
         436,  761, 3529,  548,    4, 3633,   31,    2, 1331,   28, 2096,
           3, 2912,    9,    6,  163,    4, 1006,   20,    2,    1,   15,
          85,   53,  147,    9,  292,   89,  959, 2314,  984,   27,  762,
           6,  959,    9,  564,   18,    7, 2140,   32,   24, 1254,   36,
           1,   85,    3, 3298,   85,    6, 1410,    3, 1936,    2, 3408,
         301,  965,    7,    4,  112,  740, 1977,   12,    1, 2014, 2772,
           3,    4,  428,    3, 5177,    6,  512, 1254,    1,  278,   27,
         139,   25,  308,    1,  579,    5,  259, 3529,    7,   92, 8981,
          32,    2, 3842,  230,   27,  289,    9,   35,    2, 5712,   18,
          27,  144, 2166,   56,    6,   26,   46,  466, 2014,   27,   40,
        2745,  657,  212,    4, 1376, 3002, 7080,  183,   36,  180,   52,
         920,    8,    2, 4028,   12,  969,    1,  158,   71,   53,   67,
          85, 2754,    4,  734,   51,    1, 1611,  294,   85,    6,    2,
        1164,    6,  163,    4, 3408,   15,   85,    6,  717,   85,   44,
           5,   24, 7158,    3,   48,  604,    7,   11,  225,  384,   73,
          65,   21,  242,   18,   27,  120,  295,    6,   26,  667,  129,
        4028,  948,    6,   67,   48,  158,   93,    1]])>, <tf.Tensor: shape=(), dtype=int32, numpy=0>)

Jak widać powyżej, każdy token został zastąpiony liczbą całkowitą. Możesz .get_vocabulary() token (ciąg znaków), .get_vocabulary() odpowiada każda liczba całkowita, wywołując .get_vocabulary() na warstwie.

 print("1287 ---> ",vectorize_layer.get_vocabulary()[1287])
print(" 313 ---> ",vectorize_layer.get_vocabulary()[313])
print('Vocabulary size: {}'.format(len(vectorize_layer.get_vocabulary())))
 
1287 --->  silent
 313 --->  night
Vocabulary size: 10000

Jesteś prawie gotowy do trenowania swojego modelu. Jako ostatni krok wstępnego przetwarzania zastosujesz utworzoną wcześniej warstwę TextVectorization do zestawu danych trenowania, walidacji i testowania.

 train_ds = raw_train_ds.map(vectorize_text)
val_ds = raw_val_ds.map(vectorize_text)
test_ds = raw_test_ds.map(vectorize_text)
 

Skonfiguruj zbiór danych pod kątem wydajności

Są to dwie ważne metody, których należy używać podczas ładowania danych, aby upewnić się, że operacje wejścia / wyjścia nie staną się blokujące.

.cache() przechowuje dane w pamięci po ich załadowaniu z dysku. Dzięki temu zestaw danych nie stanie się wąskim gardłem podczas trenowania modelu. Jeśli zestaw danych jest zbyt duży, aby zmieścić się w pamięci, możesz również użyć tej metody do utworzenia wydajnej pamięci podręcznej na dysku, która jest wydajniejsza do odczytu niż wiele małych plików.

.prefetch() nakłada się na przetwarzanie .prefetch() danych i wykonywanie modelu podczas uczenia.

Więcej informacji na temat obu metod, a także sposobu buforowania danych na dysku można znaleźć w przewodniku po wydajności danych .

 AUTOTUNE = tf.data.experimental.AUTOTUNE

train_ds = train_ds.cache().prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)
test_ds = test_ds.cache().prefetch(buffer_size=AUTOTUNE)
 

Utwórz model

Czas stworzyć naszą sieć neuronową:

 embedding_dim = 16
 
 model = tf.keras.Sequential([
  layers.Embedding(max_features + 1, embedding_dim),
  layers.Dropout(0.2),
  layers.GlobalAveragePooling1D(),
  layers.Dropout(0.2),
  layers.Dense(1)])

model.summary()
 
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (None, None, 16)          160016    
_________________________________________________________________
dropout (Dropout)            (None, None, 16)          0         
_________________________________________________________________
global_average_pooling1d (Gl (None, 16)                0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 16)                0         
_________________________________________________________________
dense (Dense)                (None, 1)                 17        
=================================================================
Total params: 160,033
Trainable params: 160,033
Non-trainable params: 0
_________________________________________________________________

Warstwy są ułożone sekwencyjnie, aby zbudować klasyfikator:

  1. Pierwsza warstwa to warstwa Embedding . Ta warstwa pobiera recenzje zakodowane w postaci liczb całkowitych i wyszukuje wektor osadzający dla każdego indeksu słów. Te wektory są uczone jako model pociągów. Wektory dodają wymiar do tablicy wyjściowej. Wynikowe wymiary to: (batch, sequence, embedding) . Aby dowiedzieć się więcej o osadzaniu, zobacz samouczek dotyczący osadzania słów .
  2. Następnie warstwa GlobalAveragePooling1D zwraca wektor wyjściowy o stałej długości dla każdego przykładu poprzez uśrednienie wymiaru sekwencji. Pozwala to modelowi obsługiwać dane wejściowe o zmiennej długości w najprostszy możliwy sposób.
  3. Ten wektor wyjściowy o stałej długości jest przesyłany potokiem przez w pełni połączoną ( Dense ) warstwę z 16 ukrytymi jednostkami.
  4. Ostatnia warstwa jest gęsto połączona z jednym węzłem wyjściowym.

Funkcja strat i optymalizator

Model potrzebuje funkcji straty i optymalizatora do treningu. Ponieważ jest to problem z klasyfikacją binarną, a model generuje prawdopodobieństwo (warstwa pojedynczej jednostki z aktywacją sigmoidalną), losses.BinaryCrossentropy strat.

Teraz skonfiguruj model tak, aby używał optymalizatora i funkcji strat:

 model.compile(loss=losses.BinaryCrossentropy(from_logits=True), optimizer='adam', metrics=tf.metrics.BinaryAccuracy(threshold=0.0))
 

Wytrenuj model

Będziesz trenować model, przekazując obiekt dataset do metody dopasowania.

 epochs = 10
history = model.fit(
    train_ds,
    validation_data=val_ds,
    epochs=epochs)
 
Epoch 1/10
625/625 [==============================] - 3s 5ms/step - loss: 0.6632 - binary_accuracy: 0.6931 - val_loss: 0.6135 - val_binary_accuracy: 0.7752
Epoch 2/10
625/625 [==============================] - 3s 4ms/step - loss: 0.5472 - binary_accuracy: 0.8003 - val_loss: 0.4968 - val_binary_accuracy: 0.8220
Epoch 3/10
625/625 [==============================] - 3s 4ms/step - loss: 0.4434 - binary_accuracy: 0.8459 - val_loss: 0.4187 - val_binary_accuracy: 0.8486
Epoch 4/10
625/625 [==============================] - 3s 4ms/step - loss: 0.3770 - binary_accuracy: 0.8660 - val_loss: 0.3726 - val_binary_accuracy: 0.8622
Epoch 5/10
625/625 [==============================] - 2s 4ms/step - loss: 0.3349 - binary_accuracy: 0.8786 - val_loss: 0.3442 - val_binary_accuracy: 0.8678
Epoch 6/10
625/625 [==============================] - 2s 4ms/step - loss: 0.3046 - binary_accuracy: 0.8889 - val_loss: 0.3253 - val_binary_accuracy: 0.8722
Epoch 7/10
625/625 [==============================] - 2s 4ms/step - loss: 0.2807 - binary_accuracy: 0.8977 - val_loss: 0.3118 - val_binary_accuracy: 0.8726
Epoch 8/10
625/625 [==============================] - 2s 4ms/step - loss: 0.2609 - binary_accuracy: 0.9046 - val_loss: 0.3026 - val_binary_accuracy: 0.8762
Epoch 9/10
625/625 [==============================] - 2s 4ms/step - loss: 0.2443 - binary_accuracy: 0.9123 - val_loss: 0.2961 - val_binary_accuracy: 0.8774
Epoch 10/10
625/625 [==============================] - 2s 4ms/step - loss: 0.2309 - binary_accuracy: 0.9163 - val_loss: 0.2915 - val_binary_accuracy: 0.8804

Oceń model

Zobaczmy, jak działa model. Zostaną zwrócone dwie wartości. Strata (liczba, która reprezentuje nasz błąd, niższe wartości są lepsze) i dokładność.

 loss, accuracy = model.evaluate(test_ds)

print("Loss: ", loss)
print("Accuracy: ", accuracy)
 
782/782 [==============================] - 2s 3ms/step - loss: 0.3097 - binary_accuracy: 0.8740
Loss:  0.30967268347740173
Accuracy:  0.8740400075912476

To dość naiwne podejście pozwala osiągnąć dokładność około 86%.

Utwórz wykres dokładności i strat w czasie

model.fit() zwraca obiekt History który zawiera słownik ze wszystkim, co wydarzyło się podczas treningu:

 history_dict = history.history
history_dict.keys()
 
dict_keys(['loss', 'binary_accuracy', 'val_loss', 'val_binary_accuracy'])

Istnieją cztery wpisy: po jednym dla każdej monitorowanej metryki podczas szkolenia i walidacji. Można ich użyć do sporządzenia wykresu strat podczas szkolenia i walidacji w celu porównania, a także dokładności szkolenia i walidacji:

 acc = history_dict['binary_accuracy']
val_acc = history_dict['val_binary_accuracy']
loss = history_dict['loss']
val_loss = history_dict['val_loss']

epochs = range(1, len(acc) + 1)

# "bo" is for "blue dot"
plt.plot(epochs, loss, 'bo', label='Training loss')
# b is for "solid blue line"
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

plt.show()
 

png

 plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend(loc='lower right')

plt.show()
 

png

Na tym wykresie kropki reprezentują utratę treningu i dokładność, a linie ciągłe to utratę walidacji i dokładność.

Zauważ, że utrata treningu zmniejsza się z każdą epoką, a dokładność treningu rośnie z każdą epoką. Jest to oczekiwane w przypadku korzystania z optymalizacji zejścia gradientu - powinno minimalizować pożądaną ilość w każdej iteracji.

Nie dotyczy to utraty walidacji i dokładności - wydaje się, że osiągają one szczyt przed dokładnością treningu. To jest przykład nadmiernego dopasowania: model działa lepiej na danych uczących niż na danych, których nigdy wcześniej nie widział. Po tym momencie model nadmiernie optymalizuje i uczy się reprezentacji specyficznych dla danych szkoleniowych, które nie są generalizowane na dane testowe.

W tym konkretnym przypadku można zapobiec nadmiernemu dopasowaniu, po prostu zatrzymując szkolenie, gdy dokładność walidacji już nie rośnie. Jednym ze sposobów jest użycie wywołania zwrotnego EarlyStopping .

Wyeksportuj model

W powyższym kodzie zastosowano warstwę TextVectorization do zestawu danych przed przekazaniem tekstu do modelu. Jeśli chcesz, aby model mógł przetwarzać nieprzetworzone ciągi (na przykład w celu uproszczenia jego wdrażania), możesz dołączyć warstwę TextVectorization do swojego modelu. Aby to zrobić, możesz utworzyć nowy model, używając właśnie wytrenowanych ciężarów.

 export_model = tf.keras.Sequential([
  vectorize_layer,
  model,
  layers.Activation('sigmoid')
])

export_model.compile(
    loss=losses.BinaryCrossentropy(from_logits=False), optimizer="adam", metrics=['accuracy']
)

# Test it with `raw_test_ds`, which yields raw strings
loss, accuracy = export_model.evaluate(raw_test_ds)
print(accuracy)
 
782/782 [==============================] - 3s 4ms/step - loss: 0.3097 - accuracy: 0.8740
0.8740400075912476

Uwzględnienie logiki wstępnego przetwarzania tekstu w modelu umożliwia wyeksportowanie modelu do produkcji, co upraszcza wdrażanie i zmniejsza ryzyko pochylenia pociągu / testowania .

Istnieje różnica w wydajności, o której należy pamiętać przy wyborze miejsca zastosowania warstwy TextVectorization. Używanie go poza modelem umożliwia asynchroniczne przetwarzanie procesora i buforowanie danych podczas uczenia na GPU. Tak więc, jeśli trenujesz swój model na GPU, prawdopodobnie chcesz skorzystać z tej opcji, aby uzyskać najlepszą wydajność podczas opracowywania modelu, a następnie przełącz się na włączenie warstwy TextVectorization do modelu, gdy będziesz gotowy do przygotowania się do wdrożenia .

Odwiedź ten samouczek, aby dowiedzieć się więcej o zapisywaniu modeli.

Ćwiczenie: wieloklasowa klasyfikacja pytań dotyczących przepełnienia stosu

W tym samouczku pokazano, jak wytrenować klasyfikator binarny od podstaw na zestawie danych IMDB. W ramach ćwiczenia możesz zmodyfikować ten notatnik, aby wyszkolić klasyfikator wieloklasowy do przewidywania znacznika pytania programistycznego w przepełnieniu stosu .

Przygotowaliśmy dla Ciebie zbiór danych zawierający treść kilku tysięcy pytań programistycznych (na przykład „Jak posortować słownik według wartości w Pythonie?”) Wysłanych do Stack Overflow. Każdy z nich jest oznaczony dokładnie jednym tagiem (Python, CSharp, JavaScript lub Java). Twoim zadaniem jest wziąć pytanie jako dane wejściowe i przewidzieć odpowiedni tag, w tym przypadku Python.

Zbiór danych, z którym będziesz pracować, zawiera kilka tysięcy pytań pochodzących z dużo większego publicznego zbioru danych przepełnienia stosu w BigQuery , który zawiera ponad 17 milionów postów.

Po pobraniu zbioru danych zauważysz, że ma on podobną strukturę katalogów do zbioru danych IMDB, z którym pracowałeś wcześniej:

 train/
...python/
......0.txt
......1.txt
...javascript/
......0.txt
......1.txt
...csharp/
......0.txt
......1.txt
...java/
......0.txt
......1.txt
 

Aby ukończyć to ćwiczenie, należy zmodyfikować ten notatnik, aby działał z zestawem danych przepełnienia stosu, wprowadzając następujące modyfikacje:

  1. U góry swojego notatnika zaktualizuj kod, który pobiera zestaw danych IMDB, kodem, aby pobrać przygotowany przez nas zestaw danych Stack Overflow . Ponieważ zbiór danych Stack Overflow ma podobną strukturę katalogów, nie trzeba będzie dokonywać wielu modyfikacji.

  2. Zmodyfikuj ostatnią warstwę modelu, aby odczytać Dense(4) , ponieważ są teraz cztery klasy wyjściowe.

  3. Podczas kompilowania modelu zmień stratę na SparseCategoricalCrossentropy . Jest to właściwa funkcja straty do zastosowania w przypadku problemu klasyfikacji wieloklasowej, gdy etykiety dla każdej klasy są liczbami całkowitymi (w naszym przypadku mogą to być 0, 1 , 2 lub 3 ).

  4. Po zakończeniu tych zmian będziesz mógł wytrenować klasyfikator wieloklasowy.

Jeśli utkniesz, można znaleźć rozwiązanie tutaj .

Uczyć się więcej

Ten samouczek wprowadził od podstaw klasyfikację tekstu. Aby dowiedzieć się więcej na temat ogólnego przepływu pracy klasyfikacji tekstu, zalecamy przeczytanie tego przewodnika od Google Developers.

 
#
# Copyright (c) 2017 François Chollet
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.