In diesem Abschnitt werden wir an der Erstellung, dem Training und der Bewertung unseres Modells arbeiten. In Schritt 3 haben wir uns für die Verwendung eines N-Gramm-Modells oder eines Sequenzmodells mit dem Seitenverhältnis S/W
entschieden.
Jetzt ist es an der Zeit, unseren Klassifizierungsalgorithmus zu schreiben und zu trainieren. Wir verwenden hierfür TensorFlow mit der tf.keras API.
Beim Erstellen von Modellen für maschinelles Lernen mit Keras geht es um das Zusammensetzen von Ebenen und Bausteinen zur Datenverarbeitung, ähnlich wie beim Bau von Legosteinen. Mit diesen Ebenen können wir die Sequenz der Transformationen angeben, die wir für unsere Eingabe ausführen möchten. Da unser Lernalgorithmus eine einzelne Texteingabe akzeptiert und eine einzelne Klassifizierung ausgibt, können wir mit der Sequential Model API einen linearen Stapel von Ebenen erstellen.
Abbildung 9: Linearer Stapel von Ebenen
Die Eingabeebene und die Zwischenebenen werden unterschiedlich konstruiert, je nachdem, ob wir ein N-Gramm- oder ein Sequenzmodell erstellen. Aber unabhängig vom Modelltyp ist die letzte Ebene für ein bestimmtes Problem dieselbe.
Letzte Ebene konstruieren
Wenn wir nur zwei Klassen haben (binäre Klassifizierung), sollte unser Modell eine einzelne Wahrscheinlichkeitswertung ausgeben. Die Ausgabe von 0.2
für ein bestimmtes Eingabebeispiel bedeutet beispielsweise „20% Zuverlässigkeit, dass dieses Beispiel in der ersten Klasse (Klasse 1) enthalten ist, 80 %, dass es sich in der zweiten Klasse befindet (Klasse 0).“ Zum Ausgeben eines solchen Wahrscheinlichkeitswerts sollte die Aktivierungsfunktion der letzten Ebene eine Sigmoid-Funktion sein und die Verlustfunktion – 1 nutzen1.
Wenn es mehr als zwei Klassen gibt (Klassifizierung mit mehreren Klassen), sollte unser Modell einen Wahrscheinlichkeitswert pro Klasse ausgeben. Die Summe dieser Werte sollte 1 sein. Die Ausgabe von {0: 0.2, 1: 0.7, 2: 0.1}
bedeutet beispielsweise: „20% ist zuverlässig, dass dieses Beispiel in Klasse 0 enthalten ist, 70 %, dass es sich in Klasse 1 befindet und 10 %, dass es sich in Klasse 2 befindet.“ Um diese Punktzahlen auszugeben, sollte die Aktivierungsfunktion der letzten Ebene Softmax sein. Die Verlustfunktion, die zum Trainieren des Modells verwendet wird, sollte kategorische Kreuzentropie haben. (Siehe Abbildung 10 rechts).
Abbildung 10: Letzte Ebene
Der folgende Code definiert eine Funktion, die die Anzahl der Klassen als Eingabe verwendet und die entsprechende Anzahl von Ebeneneinheiten (1 Einheit für die binäre Klassifizierung, andernfalls 1 Einheit für jede Klasse) und die entsprechende Aktivierungsfunktion ausgibt:
def _get_last_layer_units_and_activation(num_classes):
"""Gets the # units and activation function for the last network layer.
# Arguments
num_classes: int, number of classes.
# Returns
units, activation values.
"""
if num_classes == 2:
activation = 'sigmoid'
units = 1
else:
activation = 'softmax'
units = num_classes
return units, activation
In den folgenden beiden Abschnitten wird die Erstellung der verbleibenden Modellebenen für N-Gramm- und Sequenzmodelle beschrieben.
Wenn das Verhältnis S/W
klein ist, haben wir festgestellt, dass N-Gramm-Modelle besser funktionieren als Sequenzmodelle. Sequenzmodelle sind besser, wenn es eine große Anzahl kleiner, dichter Vektoren gibt. Das liegt daran, dass das Einbetten von Beziehungen in einem kompakten Raum erfolgt, was am besten bei vielen Stichproben geschieht.
N-Gramm-Modell erstellen [Option A]
Wir bezeichnen Modelle, die die Tokens unabhängig voneinander verarbeiten (ohne die Wortfolge berücksichtigen) als N-Gramm-Modelle. In diese Kategorie fallen einfache mehrschichtige Perceptronmodelle (einschließlich logischer Regression), Gradienten-Boosting-Maschinen und Support-Vektormaschinen. Sie können keine Informationen zur Textreihenfolge nutzen.
Wir haben die Leistung einiger der oben genannten N-Gramm-Modelle verglichen und festgestellt, dass Mehrschicht-Perceptrone (MLPs) normalerweise eine bessere Leistung erzielen als andere Optionen. MLPs sind einfach zu definieren und zu verstehen, bieten eine gute Genauigkeit und erfordern relativ wenige Berechnungen.
Mit dem folgenden Code wird ein zweischichtiges MLP-Modell in tf.keras definiert und es werden einige Dropout-Ebenen für die Normalisierung hinzugefügt, um eine Überanpassung der Trainingsbeispiele zu verhindern.
from tensorflow.python.keras import models
from tensorflow.python.keras.layers import Dense
from tensorflow.python.keras.layers import Dropout
def mlp_model(layers, units, dropout_rate, input_shape, num_classes):
"""Creates an instance of a multi-layer perceptron model.
# Arguments
layers: int, number of `Dense` layers in the model.
units: int, output dimension of the layers.
dropout_rate: float, percentage of input to drop at Dropout layers.
input_shape: tuple, shape of input to the model.
num_classes: int, number of output classes.
# Returns
An MLP model instance.
"""
op_units, op_activation = _get_last_layer_units_and_activation(num_classes)
model = models.Sequential()
model.add(Dropout(rate=dropout_rate, input_shape=input_shape))
for _ in range(layers-1):
model.add(Dense(units=units, activation='relu'))
model.add(Dropout(rate=dropout_rate))
model.add(Dense(units=op_units, activation=op_activation))
return model
Build-Sequenzmodell [Option B]
Wir bezeichnen Modelle, die aus der Nähe von Tokens lernen können, als Sequenzmodelle. Dazu gehören CNN- und RNN-Modellmodelle. Die Daten werden für diese Modelle als Sequenzvektoren vorverarbeitet.
Sequenzmodelle haben in der Regel eine größere Anzahl von Parametern, die sie lernen müssen. Die erste Ebene in diesen Modellen ist eine Einbettungsschicht, die die Beziehung zwischen den Wörtern in einem dichten Vektorraum erlernt. Wortbeziehungen funktionieren am besten bei vielen Stichproben.
Wörter in einem bestimmten Dataset sind höchstwahrscheinlich nicht eindeutig für dieses Dataset. Auf diese Weise können wir die Beziehung zwischen den Wörtern in unserem Dataset mithilfe anderer Datasets nachvollziehen. Dazu können wir eine Einbettung, die aus einem anderen Dataset gelernt wurde, in unsere Einbettungsebene übertragen. Diese Einbettungen werden als vortrainierte Einbettungen bezeichnet. Mit einer vortrainierten Einbettung hat das Modell einen Vorsprung im Lernprozess.
Es gibt vortrainierte Einbettungen, die mit großen Korpora, z. B. GloVe, trainiert wurden. GloVe wurde in mehreren Korpora geschult (hauptsächlich Wikipedia). Wir haben das Training unserer Sequenzmodelle mit einer Version von GloVe-Einbettungen getestet und festgestellt, dass die Modelle keine gute Leistung erzielen, wenn wir die Gewichtungen der vortrainierten Einbettungen einfrieren und nur den Rest des Netzwerks trainieren. Dies kann daran liegen, dass sich der Kontext, in dem die Einbettungsebene trainiert wurde, von dem Kontext unterscheidet, in dem wir sie verwendet haben.
GloVe-Einbettungen, die mit Wikipedia-Daten trainiert wurden, stimmen möglicherweise nicht mit den Sprachmustern in unserem IMDb-Dataset überein. Die abgeleiteten Beziehungen müssen möglicherweise aktualisiert werden, d.h., die Einbettungsgewichtungen müssen möglicherweise an den Kontext angepasst werden. Das erfolgt in zwei Phasen:
Bei der ersten Ausführung, bei der die Einbettungsschichten eingefroren sind, erlauben wir dem Rest des Netzwerks, zu lernen. Am Ende dieser Ausführung erreichen die Modellgewichtungen einen Status, der viel besser als ihre nicht initialisierten Werte ist. Bei der zweiten Ausführung lernt die Einbettungsebene, die Gewichtungen im Netzwerk genau anzupassen. Wir bezeichnen diesen Prozess als fein abgestimmte Einbettung.
Feine Einbettungen liefern eine höhere Genauigkeit. Dies führt jedoch zu einer erhöhten Rechenleistung, die zum Trainieren des Netzwerks erforderlich ist. Bei einer ausreichenden Anzahl von Proben könnten wir ebenso gut eine Einbettung von Grund auf lernen. Wir haben festgestellt, dass
S/W > 15K
von Grund auf ungefähr die gleiche Genauigkeit wie mit fein abgestimmter Einbettung hat.
Wir haben verschiedene Sequenzmodelle wie CNN, sepCNN, RNN (LSTM & GRU), CNN-RNN und gestapelte RNN verglichen, die die Modellarchitekturen variieren. Wir haben festgestellt, dass sepCNNs, eine Convolutional-Netzwerkvariante, die oft dateneffizienter und recheneffizienter ist, eine bessere Leistung als die anderen Modelle bieten.
Mit dem folgenden Code wird ein sepCNN-Modell mit vier Ebenen erstellt:
from tensorflow.python.keras import models from tensorflow.python.keras import initializers from tensorflow.python.keras import regularizers from tensorflow.python.keras.layers import Dense from tensorflow.python.keras.layers import Dropout from tensorflow.python.keras.layers import Embedding from tensorflow.python.keras.layers import SeparableConv1D from tensorflow.python.keras.layers import MaxPooling1D from tensorflow.python.keras.layers import GlobalAveragePooling1D def sepcnn_model(blocks, filters, kernel_size, embedding_dim, dropout_rate, pool_size, input_shape, num_classes, num_features, use_pretrained_embedding=False, is_embedding_trainable=False, embedding_matrix=None): """Creates an instance of a separable CNN model. # Arguments blocks: int, number of pairs of sepCNN and pooling blocks in the model. filters: int, output dimension of the layers. kernel_size: int, length of the convolution window. embedding_dim: int, dimension of the embedding vectors. dropout_rate: float, percentage of input to drop at Dropout layers. pool_size: int, factor by which to downscale input at MaxPooling layer. input_shape: tuple, shape of input to the model. num_classes: int, number of output classes. num_features: int, number of words (embedding input dimension). use_pretrained_embedding: bool, true if pre-trained embedding is on. is_embedding_trainable: bool, true if embedding layer is trainable. embedding_matrix: dict, dictionary with embedding coefficients. # Returns A sepCNN model instance. """ op_units, op_activation = _get_last_layer_units_and_activation(num_classes) model = models.Sequential() # Add embedding layer. If pre-trained embedding is used add weights to the # embeddings layer and set trainable to input is_embedding_trainable flag. if use_pretrained_embedding: model.add(Embedding(input_dim=num_features, output_dim=embedding_dim, input_length=input_shape[0], weights=[embedding_matrix], trainable=is_embedding_trainable)) else: model.add(Embedding(input_dim=num_features, output_dim=embedding_dim, input_length=input_shape[0])) for _ in range(blocks-1): model.add(Dropout(rate=dropout_rate)) model.add(SeparableConv1D(filters=filters, kernel_size=kernel_size, activation='relu', bias_initializer='random_uniform', depthwise_initializer='random_uniform', padding='same')) model.add(SeparableConv1D(filters=filters, kernel_size=kernel_size, activation='relu', bias_initializer='random_uniform', depthwise_initializer='random_uniform', padding='same')) model.add(MaxPooling1D(pool_size=pool_size)) model.add(SeparableConv1D(filters=filters * 2, kernel_size=kernel_size, activation='relu', bias_initializer='random_uniform', depthwise_initializer='random_uniform', padding='same')) model.add(SeparableConv1D(filters=filters * 2, kernel_size=kernel_size, activation='relu', bias_initializer='random_uniform', depthwise_initializer='random_uniform', padding='same')) model.add(GlobalAveragePooling1D()) model.add(Dropout(rate=dropout_rate)) model.add(Dense(op_units, activation=op_activation)) return model
Modell trainieren
Nachdem Sie nun die Modellarchitektur erstellt haben, müssen Sie das Modell trainieren. Das Training umfasst eine Vorhersage anhand des aktuellen Status des Modells, die Berechnung der Genauigkeit der Vorhersage und die Aktualisierung der Gewichtungen oder Parameter des Netzwerks, um diesen Fehler zu minimieren und die Modellvorhersage zu verbessern. Wir wiederholen diesen Vorgang, bis das Modell angelaufen ist und nicht mehr lernen kann. Für diesen Prozess müssen drei wichtige Parameter ausgewählt werden (siehe Tabelle 2).
- Messwert: So messen Sie die Leistung des Modells mit einem Messwert. In unseren Tests haben wir als Messwert accuracy verwendet.
- Verlustfunktion: Eine Funktion, mit der ein Verlustwert berechnet wird, den der Trainingsprozess dann durch Anpassen der Netzwerkgewichtungen minimiert. Bei Klassifizierungsproblemen funktioniert der Kreuzentropieverlust gut.
- Optimierungstool: Eine Funktion, mit der bestimmt wird, wie die Netzwerkgewichtungen basierend auf der Ausgabe der Verlustfunktion aktualisiert werden. Wir haben in unseren Tests das beliebte Adam-Optimierungstool verwendet.
In Keras können wir diese Lernparameter mit der Methode kompilieren an ein Modell übergeben.
Lernparameter | Wert |
---|---|
Messwert | Genauigkeit |
Verlustfunktion – binäre Klassifizierung | binäre Kreuzentropie |
Verlustfunktion – Klassifizierung mehrerer Klassen | sparse_categorical_crossentropy |
Optimierung | Adam |
Tabelle 2: Lernparameter
Das eigentliche Training erfolgt mit der fit-Methode.
Abhängig von der Größe Ihres Datasets ist dies die Methode, mit der die meisten Computing-Zyklen verwendet werden. In jeder Trainingsausführung werden zum Berechnen des Verlusts batch_size
Stichproben aus Ihren Trainingsdaten verwendet und die Gewichtungen anhand dieses Werts einmal aktualisiert.
Sobald das Modell das gesamte Trainings-Dataset gesehen hat, wird eine epoch
abgeschlossen. Am Ende jeder Epoche verwenden wir das Validierungs-Dataset, um zu bewerten, wie gut das Modell lernt. Wir wiederholen das Training mit dem Dataset für eine vorab festgelegte Anzahl von Epochen. Wir können dies optimieren, indem wir frühzeitig beenden, wenn sich die Validierungsgenauigkeit zwischen aufeinanderfolgenden Epochen stabilisiert, was zeigt, dass das Modell nicht mehr trainiert wird.
Hyperparameter für Training | Wert |
---|---|
Lernrate | 1e-3 |
Epochen | 1.000 |
Batchgröße | 512 |
Vorzeitiges Beenden | Parameter: val_loss, Geduld: 1 |
Tabelle 3: Hyperparameter trainieren
Mit dem folgenden Keras-Code wird der Trainingsprozess mit den in den obigen Tabellen 2 und 3 ausgewählten Parametern implementiert:
def train_ngram_model(data, learning_rate=1e-3, epochs=1000, batch_size=128, layers=2, units=64, dropout_rate=0.2): """Trains n-gram model on the given dataset. # Arguments data: tuples of training and test texts and labels. learning_rate: float, learning rate for training model. epochs: int, number of epochs. batch_size: int, number of samples per batch. layers: int, number of `Dense` layers in the model. units: int, output dimension of Dense layers in the model. dropout_rate: float: percentage of input to drop at Dropout layers. # Raises ValueError: If validation data has label values which were not seen in the training data. """ # Get the data. (train_texts, train_labels), (val_texts, val_labels) = data # Verify that validation labels are in the same range as training labels. num_classes = explore_data.get_num_classes(train_labels) unexpected_labels = [v for v in val_labels if v not in range(num_classes)] if len(unexpected_labels): raise ValueError('Unexpected label values found in the validation set:' ' {unexpected_labels}. Please make sure that the ' 'labels in the validation set are in the same range ' 'as training labels.'.format( unexpected_labels=unexpected_labels)) # Vectorize texts. x_train, x_val = vectorize_data.ngram_vectorize( train_texts, train_labels, val_texts) # Create model instance. model = build_model.mlp_model(layers=layers, units=units, dropout_rate=dropout_rate, input_shape=x_train.shape[1:], num_classes=num_classes) # Compile model with learning parameters. if num_classes == 2: loss = 'binary_crossentropy' else: loss = 'sparse_categorical_crossentropy' optimizer = tf.keras.optimizers.Adam(lr=learning_rate) model.compile(optimizer=optimizer, loss=loss, metrics=['acc']) # Create callback for early stopping on validation loss. If the loss does # not decrease in two consecutive tries, stop training. callbacks = [tf.keras.callbacks.EarlyStopping( monitor='val_loss', patience=2)] # Train and validate model. history = model.fit( x_train, train_labels, epochs=epochs, callbacks=callbacks, validation_data=(x_val, val_labels), verbose=2, # Logs once per epoch. batch_size=batch_size) # Print results. history = history.history print('Validation accuracy: {acc}, loss: {loss}'.format( acc=history['val_acc'][-1], loss=history['val_loss'][-1])) # Save model. model.save('IMDb_mlp_model.h5') return history['val_acc'][-1], history['val_loss'][-1]
Codebeispiele zum Trainieren des Sequenzmodells finden Sie hier.