Diese Seite wurde von der Cloud Translation API übersetzt.
Switch to English

Graph-Regularisierung zur Stimmungsklassifizierung unter Verwendung synthetisierter Graphen

Ansicht auf TensorFlow.org In Google Colab ausführen Quelle auf GitHub anzeigen

Überblick

Dieses Notizbuch klassifiziert Filmkritiken anhand des Textes der Rezension als positiv oder negativ . Dies ist ein Beispiel für eine binäre Klassifizierung, eine wichtige und weit verbreitete Art von maschinellem Lernproblem.

Wir werden die Verwendung der Diagrammregulierung in diesem Notizbuch demonstrieren, indem wir aus den angegebenen Eingaben ein Diagramm erstellen. Das allgemeine Rezept zum Erstellen eines graphregulierten Modells unter Verwendung des NSL-Frameworks (Neural Structured Learning), wenn die Eingabe kein explizites Diagramm enthält, lautet wie folgt:

  1. Erstellen Sie Einbettungen für jedes Textbeispiel in der Eingabe. Dies kann mit vorab trainierten Modellen wie word2vec , Swivel , BERT usw. erfolgen.
  2. Erstellen Sie ein Diagramm basierend auf diesen Einbettungen, indem Sie eine Ähnlichkeitsmetrik verwenden, z. B. den Abstand 'L2', den Abstand 'Cosinus' usw. Die Knoten im Diagramm entsprechen Stichproben, und die Kanten im Diagramm entsprechen der Ähnlichkeit zwischen Stichprobenpaaren.
  3. Generieren Sie Trainingsdaten aus den oben synthetisierten Grafik- und Beispielfunktionen. Die resultierenden Trainingsdaten enthalten zusätzlich zu den ursprünglichen Knotenmerkmalen Nachbarmerkmale.
  4. Erstellen Sie ein neuronales Netzwerk als Basismodell mithilfe der sequentiellen, funktionalen oder Unterklassen-API von Keras.
  5. Umschließen Sie das Basismodell mit der GraphRegularization-Wrapper-Klasse, die vom NSL-Framework bereitgestellt wird, um ein neues Graph-Keras-Modell zu erstellen. Dieses neue Modell wird einen Graph-Regularisierungsverlust als Regularisierungsbegriff in sein Trainingsziel aufnehmen.
  6. Trainieren und bewerten Sie das Graph-Keras-Modell.

Bedarf

  1. Installieren Sie das Paket Neural Structured Learning.
  2. Tensorflow-Nabe einbauen.
pip install --quiet neural-structured-learning
pip install --quiet tensorflow-hub
WARNING: You are using pip version 20.2.1; however, version 20.2.2 is available.
You should consider upgrading via the '/tmpfs/src/tf_docs_env/bin/python -m pip install --upgrade pip' command.
WARNING: You are using pip version 20.2.1; however, version 20.2.2 is available.
You should consider upgrading via the '/tmpfs/src/tf_docs_env/bin/python -m pip install --upgrade pip' command.

Abhängigkeiten und Importe

import matplotlib.pyplot as plt
import numpy as np

import neural_structured_learning as nsl

import tensorflow as tf
import tensorflow_hub as hub

# Resets notebook state
tf.keras.backend.clear_session()

print("Version: ", tf.__version__)
print("Eager mode: ", tf.executing_eagerly())
print("Hub version: ", hub.__version__)
print(
    "GPU is",
    "available" if tf.config.list_physical_devices("GPU") else "NOT AVAILABLE")
Version:  2.3.0
Eager mode:  True
Hub version:  0.8.0
GPU is NOT AVAILABLE

IMDB-Datensatz

Der IMDB-Datensatz enthält den Text von 50.000 Filmkritiken aus der Internet Movie Database . Diese sind in 25.000 Bewertungen für Schulungen und 25.000 Bewertungen für Tests unterteilt. Die Trainings- und Testsätze sind ausgewogen , dh sie enthalten die gleiche Anzahl positiver und negativer Bewertungen.

In diesem Tutorial verwenden wir eine vorverarbeitete Version des IMDB-Datasets.

Laden Sie den vorverarbeiteten IMDB-Datensatz herunter

Das IMDB-Dataset wird mit TensorFlow geliefert. Es wurde bereits so vorverarbeitet, dass die Überprüfungen (Wortfolgen) in Folgen von Ganzzahlen konvertiert wurden, wobei jede Ganzzahl ein bestimmtes Wort in einem Wörterbuch darstellt.

Der folgende Code lädt das IMDB-Dataset herunter (oder verwendet eine zwischengespeicherte Kopie, falls diese bereits heruntergeladen wurde):

imdb = tf.keras.datasets.imdb
(pp_train_data, pp_train_labels), (pp_test_data, pp_test_labels) = (
    imdb.load_data(num_words=10000))
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb.npz
17465344/17464789 [==============================] - 0s 0us/step

Das Argument num_words=10000 die 10.000 am häufigsten vorkommenden Wörter in den Trainingsdaten. Die seltenen Wörter werden verworfen, um die Größe des Wortschatzes überschaubar zu halten.

Erkunden Sie die Daten

Nehmen wir uns einen Moment Zeit, um das Format der Daten zu verstehen. Der Datensatz wird vorverarbeitet geliefert: Jedes Beispiel besteht aus einer Reihe von Ganzzahlen, die die Wörter der Filmkritik darstellen. Jedes Label ist ein ganzzahliger Wert von 0 oder 1, wobei 0 eine negative Bewertung und 1 eine positive Bewertung ist.

print('Training entries: {}, labels: {}'.format(
    len(pp_train_data), len(pp_train_labels)))
training_samples_count = len(pp_train_data)
Training entries: 25000, labels: 25000

Der Text der Überprüfungen wurde in Ganzzahlen konvertiert, wobei jede Ganzzahl ein bestimmtes Wort in einem Wörterbuch darstellt. So sieht die erste Bewertung aus:

print(pp_train_data[0])
[1, 14, 22, 16, 43, 530, 973, 1622, 1385, 65, 458, 4468, 66, 3941, 4, 173, 36, 256, 5, 25, 100, 43, 838, 112, 50, 670, 2, 9, 35, 480, 284, 5, 150, 4, 172, 112, 167, 2, 336, 385, 39, 4, 172, 4536, 1111, 17, 546, 38, 13, 447, 4, 192, 50, 16, 6, 147, 2025, 19, 14, 22, 4, 1920, 4613, 469, 4, 22, 71, 87, 12, 16, 43, 530, 38, 76, 15, 13, 1247, 4, 22, 17, 515, 17, 12, 16, 626, 18, 2, 5, 62, 386, 12, 8, 316, 8, 106, 5, 4, 2223, 5244, 16, 480, 66, 3785, 33, 4, 130, 12, 16, 38, 619, 5, 25, 124, 51, 36, 135, 48, 25, 1415, 33, 6, 22, 12, 215, 28, 77, 52, 5, 14, 407, 16, 82, 2, 8, 4, 107, 117, 5952, 15, 256, 4, 2, 7, 3766, 5, 723, 36, 71, 43, 530, 476, 26, 400, 317, 46, 7, 4, 2, 1029, 13, 104, 88, 4, 381, 15, 297, 98, 32, 2071, 56, 26, 141, 6, 194, 7486, 18, 4, 226, 22, 21, 134, 476, 26, 480, 5, 144, 30, 5535, 18, 51, 36, 28, 224, 92, 25, 104, 4, 226, 65, 16, 38, 1334, 88, 12, 16, 283, 5, 16, 4472, 113, 103, 32, 15, 16, 5345, 19, 178, 32]

Filmkritiken können unterschiedlich lang sein. Der folgende Code zeigt die Anzahl der Wörter in der ersten und zweiten Überprüfung. Da Eingaben in ein neuronales Netzwerk gleich lang sein müssen, müssen wir dies später beheben.

len(pp_train_data[0]), len(pp_train_data[1])
(218, 189)

Konvertieren Sie die ganzen Zahlen zurück in Wörter

Es kann hilfreich sein zu wissen, wie Ganzzahlen wieder in den entsprechenden Text konvertiert werden. Hier erstellen wir eine Hilfsfunktion zum Abfragen eines Wörterbuchobjekts, das die Zuordnung von Ganzzahl zu Zeichenfolge enthält:

def build_reverse_word_index():
  # A dictionary mapping words to an integer index
  word_index = imdb.get_word_index()

  # The first indices are reserved
  word_index = {k: (v + 3) for k, v in word_index.items()}
  word_index['<PAD>'] = 0
  word_index['<START>'] = 1
  word_index['<UNK>'] = 2  # unknown
  word_index['<UNUSED>'] = 3
  return dict((value, key) for (key, value) in word_index.items())

reverse_word_index = build_reverse_word_index()

def decode_review(text):
  return ' '.join([reverse_word_index.get(i, '?') for i in text])
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb_word_index.json
1646592/1641221 [==============================] - 0s 0us/step

Jetzt können wir die Funktion decode_review verwenden, um den Text für die erste Überprüfung anzuzeigen:

decode_review(pp_train_data[0])
"<START> this film was just brilliant casting location scenery story direction everyone's really suited the part they played and you could just imagine being there robert <UNK> is an amazing actor and now the same being director <UNK> father came from the same scottish island as myself so i loved the fact there was a real connection with this film the witty remarks throughout the film were great it was just brilliant so much that i bought the film as soon as it was released for <UNK> and would recommend it to everyone to watch and the fly fishing was amazing really cried at the end it was so sad and you know what they say if you cry at a film it must have been good and this definitely was also <UNK> to the two little boy's that played the <UNK> of norman and paul they were just brilliant children are often left out of the <UNK> list i think because the stars that play them all grown up are such a big profile for the whole film but these children are amazing and should be praised for what they have done don't you think the whole story was so lovely because it was true and was someone's life after all that was shared with us all"

Graphkonstruktion

Bei der Diagrammkonstruktion werden Einbettungen für Textbeispiele erstellt und anschließend mithilfe einer Ähnlichkeitsfunktion die Einbettungen verglichen.

Bevor wir fortfahren, erstellen wir zunächst ein Verzeichnis zum Speichern von Artefakten, die in diesem Lernprogramm erstellt wurden.

mkdir -p /tmp/imdb

Erstellen Sie Beispieleinbettungen

Wir werden vorgefertigte Swivel-Einbettungen verwenden, um Einbettungen im tf.train.Example Format für jedes Sample in der Eingabe zu erstellen. Wir werden die resultierenden Einbettungen im TFRecord Format zusammen mit einer zusätzlichen Funktion speichern, die die ID jeder Probe darstellt. Dies ist wichtig und ermöglicht es uns, Beispieleinbettungen später mit den entsprechenden Knoten im Diagramm abzugleichen.

pretrained_embedding = 'https://tfhub.dev/google/tf2-preview/gnews-swivel-20dim/1'

hub_layer = hub.KerasLayer(
    pretrained_embedding, input_shape=[], dtype=tf.string, trainable=True)
def _int64_feature(value):
  """Returns int64 tf.train.Feature."""
  return tf.train.Feature(int64_list=tf.train.Int64List(value=value.tolist()))


def _bytes_feature(value):
  """Returns bytes tf.train.Feature."""
  return tf.train.Feature(
      bytes_list=tf.train.BytesList(value=[value.encode('utf-8')]))


def _float_feature(value):
  """Returns float tf.train.Feature."""
  return tf.train.Feature(float_list=tf.train.FloatList(value=value.tolist()))


def create_embedding_example(word_vector, record_id):
  """Create tf.Example containing the sample's embedding and its ID."""

  text = decode_review(word_vector)

  # Shape = [batch_size,].
  sentence_embedding = hub_layer(tf.reshape(text, shape=[-1,]))

  # Flatten the sentence embedding back to 1-D.
  sentence_embedding = tf.reshape(sentence_embedding, shape=[-1])

  features = {
      'id': _bytes_feature(str(record_id)),
      'embedding': _float_feature(sentence_embedding.numpy())
  }
  return tf.train.Example(features=tf.train.Features(feature=features))


def create_embeddings(word_vectors, output_path, starting_record_id):
  record_id = int(starting_record_id)
  with tf.io.TFRecordWriter(output_path) as writer:
    for word_vector in word_vectors:
      example = create_embedding_example(word_vector, record_id)
      record_id = record_id + 1
      writer.write(example.SerializeToString())
  return record_id


# Persist TF.Example features containing embeddings for training data in
# TFRecord format.
create_embeddings(pp_train_data, '/tmp/imdb/embeddings.tfr', 0)
25000

Erstellen Sie ein Diagramm

Nachdem wir die Beispieleinbettungen haben, werden wir sie verwenden, um ein Ähnlichkeitsdiagramm zu erstellen, dh Knoten in diesem Diagramm entsprechen Stichproben und Kanten in diesem Diagramm entsprechen der Ähnlichkeit zwischen Knotenpaaren.

Neuronales strukturiertes Lernen bietet eine Bibliothek zum Erstellen von Diagrammen, um ein Diagramm basierend auf Beispieleinbettungen zu erstellen. Es verwendet die Kosinusähnlichkeit als Ähnlichkeitsmaß, um Einbettungen zu vergleichen und Kanten zwischen ihnen zu erstellen. Außerdem können wir einen Ähnlichkeitsschwellenwert angeben, mit dem unterschiedliche Kanten aus dem endgültigen Diagramm entfernt werden können. In diesem Beispiel wird unter Verwendung von 0,99 als Ähnlichkeitsschwelle und 12345 als zufälliger Startwert ein Diagramm mit 429.415 bidirektionalen Kanten erstellt. Hier verwenden wir die Unterstützung des Graph Builders für lokalitätssensitives Hashing (LSH), um die Graphbildung zu beschleunigen. Ausführliche Informationen zur Verwendung der LSH-Unterstützung des Graph Builders finden Sie in der API-Dokumentation build_graph_from_config .

graph_builder_config = nsl.configs.GraphBuilderConfig(
    similarity_threshold=0.99, lsh_splits=32, lsh_rounds=15, random_seed=12345)
nsl.tools.build_graph_from_config(['/tmp/imdb/embeddings.tfr'],
                                  '/tmp/imdb/graph_99.tsv',
                                  graph_builder_config)

Jede bidirektionale Kante wird in der TSV-Ausgabedatei durch zwei gerichtete Kanten dargestellt, sodass die Datei insgesamt 429.415 * 2 = 858.830 Zeilen enthält:

wc -l /tmp/imdb/graph_99.tsv
858830 /tmp/imdb/graph_99.tsv

Beispielfunktionen

Wir erstellen Beispielfunktionen für unser Problem im Format tf.train.Example und tf.train.Example sie im TFRecord Format bei. Jedes Beispiel enthält die folgenden drei Merkmale:

  1. id : Die Knoten-ID des Beispiels.
  2. Wörter : Eine int64-Liste mit Wort-IDs.
  3. label : Ein Singleton int64, der die Zielklasse der Überprüfung identifiziert.
def create_example(word_vector, label, record_id):
  """Create tf.Example containing the sample's word vector, label, and ID."""
  features = {
      'id': _bytes_feature(str(record_id)),
      'words': _int64_feature(np.asarray(word_vector)),
      'label': _int64_feature(np.asarray([label])),
  }
  return tf.train.Example(features=tf.train.Features(feature=features))

def create_records(word_vectors, labels, record_path, starting_record_id):
  record_id = int(starting_record_id)
  with tf.io.TFRecordWriter(record_path) as writer:
    for word_vector, label in zip(word_vectors, labels):
      example = create_example(word_vector, label, record_id)
      record_id = record_id + 1
      writer.write(example.SerializeToString())
  return record_id

# Persist TF.Example features (word vectors and labels) for training and test
# data in TFRecord format.
next_record_id = create_records(pp_train_data, pp_train_labels,
                                '/tmp/imdb/train_data.tfr', 0)
create_records(pp_test_data, pp_test_labels, '/tmp/imdb/test_data.tfr',
               next_record_id)
50000

Erweitern Sie die Trainingsdaten mit Diagrammnachbarn

Da wir die Beispielmerkmale und den synthetisierten Graphen haben, können wir die erweiterten Trainingsdaten für neuronales strukturiertes Lernen generieren. Das NSL-Framework bietet eine Bibliothek zum Kombinieren des Diagramms und der Beispielfunktionen, um die endgültigen Trainingsdaten für die Regularisierung des Diagramms zu erstellen. Die resultierenden Trainingsdaten enthalten Originalmustermerkmale sowie Merkmale der entsprechenden Nachbarn.

In diesem Tutorial betrachten wir ungerichtete Kanten und verwenden maximal 3 Nachbarn pro Stichprobe, um die Trainingsdaten mit Diagrammnachbarn zu erweitern.

nsl.tools.pack_nbrs(
    '/tmp/imdb/train_data.tfr',
    '',
    '/tmp/imdb/graph_99.tsv',
    '/tmp/imdb/nsl_train_data.tfr',
    add_undirected_edges=True,
    max_nbrs=3)

Basismodell

Wir sind jetzt bereit, ein Basismodell ohne Graph-Regularisierung zu erstellen. Um dieses Modell zu erstellen, können wir entweder Einbettungen verwenden, die beim Erstellen des Diagramms verwendet wurden, oder wir können gemeinsam mit der Klassifizierungsaufgabe neue Einbettungen lernen. Für den Zweck dieses Notizbuchs werden wir Letzteres tun.

Globale Variablen

NBR_FEATURE_PREFIX = 'NL_nbr_'
NBR_WEIGHT_SUFFIX = '_weight'

Hyperparameter

Wir werden eine Instanz von HParams , um verschiedene Hyperparameter und Konstanten HParams , die für das Training und die Bewertung verwendet werden. Wir beschreiben jeden von ihnen kurz unten:

  • num_classes : Es gibt 2 Klassen - positiv und negativ .

  • max_seq_length : Dies ist die maximale Anzahl von Wörtern, die in diesem Beispiel aus jeder Filmkritik berücksichtigt werden.

  • vocab_size : Dies ist die Größe des für dieses Beispiel berücksichtigten Vokabulars.

  • distance_type : Dies ist die Entfernungsmetrik, mit der die Stichprobe mit ihren Nachbarn reguliert wird.

  • graph_regularization_multiplier : Hiermit wird das relative Gewicht des Graph-Regularisierungsterms in der Gesamtverlustfunktion gesteuert .

  • num_neighbors : Die Anzahl der Nachbarn, die für die Regularisierung von Graphen verwendet werden. Dieser Wert muss kleiner oder gleich dem Argument max_nbrs , das oben beim Aufrufen von nsl.tools.pack_nbrs .

  • num_fc_units : Die Anzahl der Einheiten in der vollständig verbundenen Schicht des neuronalen Netzwerks.

  • train_epochs : Die Anzahl der Trainingsepochen.

  • batch_size : Stapelgröße, die für Schulung und Bewertung verwendet wird.

  • eval_steps : Die Anzahl der Stapel , die verarbeitet werden müssen, bevor die Bewertung abgeschlossen ist. Bei der Einstellung None werden alle Instanzen im Testsatz ausgewertet.

class HParams(object):
  """Hyperparameters used for training."""
  def __init__(self):
    ### dataset parameters
    self.num_classes = 2
    self.max_seq_length = 256
    self.vocab_size = 10000
    ### neural graph learning parameters
    self.distance_type = nsl.configs.DistanceType.L2
    self.graph_regularization_multiplier = 0.1
    self.num_neighbors = 2
    ### model architecture
    self.num_embedding_dims = 16
    self.num_lstm_dims = 64
    self.num_fc_units = 64
    ### training parameters
    self.train_epochs = 10
    self.batch_size = 128
    ### eval parameters
    self.eval_steps = None  # All instances in the test set are evaluated.

HPARAMS = HParams()

Bereiten Sie die Daten vor

Die Überprüfungen - die Arrays von Ganzzahlen - müssen in Tensoren umgewandelt werden, bevor sie in das neuronale Netzwerk eingespeist werden. Diese Konvertierung kann auf verschiedene Arten erfolgen:

  • Konvertieren Sie die Arrays in Vektoren von 0 s und 1 s, die das Auftreten von Wörtern anzeigen, ähnlich einer One-Hot-Codierung. Zum Beispiel würde die Sequenz [3, 5] ein 10000 dimensionaler Vektor werden, der alle Nullen ist, mit Ausnahme der Indizes 3 und 5 , die Einsen sind. Machen Sie dies dann zur ersten Schicht in unserem Netzwerk - einer Dense Schicht -, die Gleitkomma-Vektordaten verarbeiten kann. Dieser Ansatz ist jedoch speicherintensiv und erfordert eine num_words * num_reviews .

  • Alternativ können wir die Arrays so max_length * num_reviews dass sie alle dieselbe Länge haben, und dann einen ganzzahligen Tensor mit der Form max_length * num_reviews . Wir können eine Einbettungsschicht verwenden, die diese Form als erste Schicht in unserem Netzwerk verarbeiten kann.

In diesem Tutorial verwenden wir den zweiten Ansatz.

Da die Filmkritiken dieselbe Länge haben müssen, verwenden wir die pad_sequence definierte Funktion pad_sequence , um die Längen zu standardisieren.

def make_dataset(file_path, training=False):
  """Creates a `tf.data.TFRecordDataset`.

  Args:
    file_path: Name of the file in the `.tfrecord` format containing
      `tf.train.Example` objects.
    training: Boolean indicating if we are in training mode.

  Returns:
    An instance of `tf.data.TFRecordDataset` containing the `tf.train.Example`
    objects.
  """

  def pad_sequence(sequence, max_seq_length):
    """Pads the input sequence (a `tf.SparseTensor`) to `max_seq_length`."""
    pad_size = tf.maximum([0], max_seq_length - tf.shape(sequence)[0])
    padded = tf.concat(
        [sequence.values,
         tf.fill((pad_size), tf.cast(0, sequence.dtype))],
        axis=0)
    # The input sequence may be larger than max_seq_length. Truncate down if
    # necessary.
    return tf.slice(padded, [0], [max_seq_length])

  def parse_example(example_proto):
    """Extracts relevant fields from the `example_proto`.

    Args:
      example_proto: An instance of `tf.train.Example`.

    Returns:
      A pair whose first value is a dictionary containing relevant features
      and whose second value contains the ground truth labels.
    """
    # The 'words' feature is a variable length word ID vector.
    feature_spec = {
        'words': tf.io.VarLenFeature(tf.int64),
        'label': tf.io.FixedLenFeature((), tf.int64, default_value=-1),
    }
    # We also extract corresponding neighbor features in a similar manner to
    # the features above during training.
    if training:
      for i in range(HPARAMS.num_neighbors):
        nbr_feature_key = '{}{}_{}'.format(NBR_FEATURE_PREFIX, i, 'words')
        nbr_weight_key = '{}{}{}'.format(NBR_FEATURE_PREFIX, i,
                                         NBR_WEIGHT_SUFFIX)
        feature_spec[nbr_feature_key] = tf.io.VarLenFeature(tf.int64)

        # We assign a default value of 0.0 for the neighbor weight so that
        # graph regularization is done on samples based on their exact number
        # of neighbors. In other words, non-existent neighbors are discounted.
        feature_spec[nbr_weight_key] = tf.io.FixedLenFeature(
            [1], tf.float32, default_value=tf.constant([0.0]))

    features = tf.io.parse_single_example(example_proto, feature_spec)

    # Since the 'words' feature is a variable length word vector, we pad it to a
    # constant maximum length based on HPARAMS.max_seq_length
    features['words'] = pad_sequence(features['words'], HPARAMS.max_seq_length)
    if training:
      for i in range(HPARAMS.num_neighbors):
        nbr_feature_key = '{}{}_{}'.format(NBR_FEATURE_PREFIX, i, 'words')
        features[nbr_feature_key] = pad_sequence(features[nbr_feature_key],
                                                 HPARAMS.max_seq_length)

    labels = features.pop('label')
    return features, labels

  dataset = tf.data.TFRecordDataset([file_path])
  if training:
    dataset = dataset.shuffle(10000)
  dataset = dataset.map(parse_example)
  dataset = dataset.batch(HPARAMS.batch_size)
  return dataset


train_dataset = make_dataset('/tmp/imdb/nsl_train_data.tfr', True)
test_dataset = make_dataset('/tmp/imdb/test_data.tfr')

Erstellen Sie das Modell

Ein neuronales Netzwerk wird durch Stapeln von Schichten erstellt. Dies erfordert zwei wichtige Architekturentscheidungen:

  • Wie viele Ebenen sollen im Modell verwendet werden?
  • Wie viele versteckte Einheiten müssen für jede Ebene verwendet werden?

In diesem Beispiel bestehen die Eingabedaten aus einem Array von Wortindizes. Die vorherzusagenden Bezeichnungen sind entweder 0 oder 1.

In diesem Tutorial verwenden wir ein bidirektionales LSTM als Basismodell.

# This function exists as an alternative to the bi-LSTM model used in this
# notebook.
def make_feed_forward_model():
  """Builds a simple 2 layer feed forward neural network."""
  inputs = tf.keras.Input(
      shape=(HPARAMS.max_seq_length,), dtype='int64', name='words')
  embedding_layer = tf.keras.layers.Embedding(HPARAMS.vocab_size, 16)(inputs)
  pooling_layer = tf.keras.layers.GlobalAveragePooling1D()(embedding_layer)
  dense_layer = tf.keras.layers.Dense(16, activation='relu')(pooling_layer)
  outputs = tf.keras.layers.Dense(1, activation='sigmoid')(dense_layer)
  return tf.keras.Model(inputs=inputs, outputs=outputs)


def make_bilstm_model():
  """Builds a bi-directional LSTM model."""
  inputs = tf.keras.Input(
      shape=(HPARAMS.max_seq_length,), dtype='int64', name='words')
  embedding_layer = tf.keras.layers.Embedding(HPARAMS.vocab_size,
                                              HPARAMS.num_embedding_dims)(
                                                  inputs)
  lstm_layer = tf.keras.layers.Bidirectional(
      tf.keras.layers.LSTM(HPARAMS.num_lstm_dims))(
          embedding_layer)
  dense_layer = tf.keras.layers.Dense(
      HPARAMS.num_fc_units, activation='relu')(
          lstm_layer)
  outputs = tf.keras.layers.Dense(1, activation='sigmoid')(dense_layer)
  return tf.keras.Model(inputs=inputs, outputs=outputs)


# Feel free to use an architecture of your choice.
model = make_bilstm_model()
model.summary()
Model: "functional_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
words (InputLayer)           [(None, 256)]             0         
_________________________________________________________________
embedding (Embedding)        (None, 256, 16)           160000    
_________________________________________________________________
bidirectional (Bidirectional (None, 128)               41472     
_________________________________________________________________
dense (Dense)                (None, 64)                8256      
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 65        
=================================================================
Total params: 209,793
Trainable params: 209,793
Non-trainable params: 0
_________________________________________________________________

Die Ebenen werden effektiv nacheinander gestapelt, um den Klassifikator zu erstellen:

  1. Die erste Schicht ist eine Input , die das ganzzahlig codierte Vokabular verwendet.
  2. Die nächste Ebene ist eine Embedding , die das ganzzahlig codierte Vokabular verwendet und den Einbettungsvektor für jeden Wortindex nachschlägt. Diese Vektoren werden als Modellzüge gelernt. Die Vektoren fügen dem Ausgabearray eine Dimension hinzu. Die resultierenden Dimensionen sind: (batch, sequence, embedding) .
  3. Als nächstes gibt eine bidirektionale LSTM-Schicht für jedes Beispiel einen Ausgabevektor fester Länge zurück.
  4. Dieser Ausgabevektor fester Länge wird durch eine vollständig verbundene ( Dense ) Schicht mit 64 versteckten Einheiten geleitet.
  5. Die letzte Schicht ist dicht mit einem einzelnen Ausgangsknoten verbunden. Bei Verwendung der sigmoid Aktivierungsfunktion ist dieser Wert ein Float zwischen 0 und 1, der eine Wahrscheinlichkeit oder ein Konfidenzniveau darstellt.

Versteckte Einheiten

Das obige Modell hat zwei Zwischen- oder "versteckte" Ebenen zwischen Eingabe und Ausgabe und schließt die Embedding aus. Die Anzahl der Ausgaben (Einheiten, Knoten oder Neuronen) ist die Dimension des Darstellungsraums für die Schicht. Mit anderen Worten, die Freiheit, die dem Netzwerk beim Lernen einer internen Darstellung gewährt wird.

Wenn ein Modell mehr versteckte Einheiten (einen höherdimensionalen Darstellungsraum) und / oder mehr Schichten hat, kann das Netzwerk komplexere Darstellungen lernen. Dies verteuert das Netzwerk jedoch rechenintensiver und kann zum Lernen unerwünschter Muster führen - Muster, die die Leistung von Trainingsdaten verbessern, jedoch nicht von Testdaten. Dies wird als Überanpassung bezeichnet .

Verlustfunktion und Optimierer

Ein Modell benötigt eine Verlustfunktion und einen Optimierer für das Training. Da dies ein binäres Klassifizierungsproblem ist und das Modell eine Wahrscheinlichkeit ausgibt (eine Schicht mit einer Einheit und einer binary_crossentropy ), verwenden wir die Verlustfunktion binary_crossentropy .

model.compile(
    optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

Erstellen Sie einen Validierungssatz

Während des Trainings möchten wir die Genauigkeit des Modells anhand von Daten überprüfen, die es zuvor noch nicht gesehen hat. Erstellen Sie einen Validierungssatz, indem Sie einen Bruchteil der ursprünglichen Trainingsdaten trennen. (Warum nicht jetzt das Test-Set verwenden? Unser Ziel ist es, unser Modell nur anhand der Trainingsdaten zu entwickeln und zu optimieren und die Testdaten dann nur einmal zu verwenden, um unsere Genauigkeit zu bewerten.)

In diesem Tutorial nehmen wir ungefähr 10% der anfänglichen Trainingsmuster (10% von 25000) als gekennzeichnete Daten für das Training und die restlichen als Validierungsdaten. Da die anfängliche Zug- / Testaufteilung 50/50 betrug (jeweils 25000 Proben), beträgt die effektive Zug- / Validierungs- / Testaufteilung, die wir jetzt haben, 5/45/50.

Beachten Sie, dass 'train_dataset' bereits gestapelt und gemischt wurde.

validation_fraction = 0.9
validation_size = int(validation_fraction *
                      int(training_samples_count / HPARAMS.batch_size))
print(validation_size)
validation_dataset = train_dataset.take(validation_size)
train_dataset = train_dataset.skip(validation_size)
175

Trainiere das Modell

Trainieren Sie das Modell in kleinen Mengen. Überwachen Sie während des Trainings den Verlust und die Genauigkeit des Modells im Validierungssatz:

history = model.fit(
    train_dataset,
    validation_data=validation_dataset,
    epochs=HPARAMS.train_epochs,
    verbose=1)
Epoch 1/10

/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/keras/engine/functional.py:543: UserWarning: Input dict contained keys ['NL_nbr_0_words', 'NL_nbr_1_words', 'NL_nbr_0_weight', 'NL_nbr_1_weight'] which did not match any model input. They will be ignored by the model.
  [n for n in tensors.keys() if n not in ref_input_names])

21/21 [==============================] - 19s 917ms/step - loss: 0.6930 - accuracy: 0.5081 - val_loss: 0.6924 - val_accuracy: 0.5518
Epoch 2/10
21/21 [==============================] - 18s 878ms/step - loss: 0.6902 - accuracy: 0.5319 - val_loss: 0.6587 - val_accuracy: 0.6465
Epoch 3/10
21/21 [==============================] - 18s 879ms/step - loss: 0.6338 - accuracy: 0.6731 - val_loss: 0.5882 - val_accuracy: 0.7310
Epoch 4/10
21/21 [==============================] - 18s 872ms/step - loss: 0.4889 - accuracy: 0.7854 - val_loss: 0.4445 - val_accuracy: 0.8047
Epoch 5/10
21/21 [==============================] - 18s 872ms/step - loss: 0.3911 - accuracy: 0.8369 - val_loss: 0.3870 - val_accuracy: 0.8352
Epoch 6/10
21/21 [==============================] - 18s 877ms/step - loss: 0.3544 - accuracy: 0.8542 - val_loss: 0.3420 - val_accuracy: 0.8571
Epoch 7/10
21/21 [==============================] - 19s 900ms/step - loss: 0.3262 - accuracy: 0.8700 - val_loss: 0.3135 - val_accuracy: 0.8762
Epoch 8/10
21/21 [==============================] - 18s 871ms/step - loss: 0.2770 - accuracy: 0.8977 - val_loss: 0.2739 - val_accuracy: 0.8923
Epoch 9/10
21/21 [==============================] - 18s 872ms/step - loss: 0.2863 - accuracy: 0.8958 - val_loss: 0.2703 - val_accuracy: 0.8942
Epoch 10/10
21/21 [==============================] - 18s 875ms/step - loss: 0.2232 - accuracy: 0.9150 - val_loss: 0.2543 - val_accuracy: 0.9037

Bewerten Sie das Modell

Nun wollen wir sehen, wie sich das Modell verhält. Es werden zwei Werte zurückgegeben. Verlust (eine Zahl, die unseren Fehler darstellt, niedrigere Werte sind besser) und Genauigkeit.

results = model.evaluate(test_dataset, steps=HPARAMS.eval_steps)
print(results)
196/196 [==============================] - 16s 82ms/step - loss: 0.3748 - accuracy: 0.8500
[0.37483155727386475, 0.8500000238418579]

Erstellen Sie ein Diagramm der Genauigkeit / des Verlusts im Zeitverlauf

model.fit() gibt ein History - Objekt , das ein Wörterbuch mit allem enthält , die während des Trainings passiert:

history_dict = history.history
history_dict.keys()
dict_keys(['loss', 'accuracy', 'val_loss', 'val_accuracy'])

Es gibt vier Einträge: einen für jede überwachte Metrik während des Trainings und der Validierung. Wir können diese verwenden, um den Trainings- und Validierungsverlust zum Vergleich sowie die Trainings- und Validierungsgenauigkeit aufzuzeichnen:

acc = history_dict['accuracy']
val_acc = history_dict['val_accuracy']
loss = history_dict['loss']
val_loss = history_dict['val_loss']

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

# "-r^" is for solid red line with triangle markers.
plt.plot(epochs, loss, '-r^', label='Training loss')
# "-b0" is for solid blue line with circle markers.
plt.plot(epochs, val_loss, '-bo', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend(loc='best')

plt.show()

png

plt.clf()   # clear figure

plt.plot(epochs, acc, '-r^', label='Training acc')
plt.plot(epochs, val_acc, '-bo', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend(loc='best')

plt.show()

png

Beachten Sie, dass der Trainingsverlust mit jeder Epoche abnimmt und die Trainingsgenauigkeit mit jeder Epoche zunimmt . Dies wird bei Verwendung einer Gradientenabstiegsoptimierung erwartet - sie sollte die gewünschte Menge bei jeder Iteration minimieren.

Graph-Regularisierung

Wir sind jetzt bereit, die Regularisierung von Graphen mit dem oben erstellten Basismodell zu versuchen. Wir werden die GraphRegularization Wrapper-Klasse verwenden, die vom Neural Structured Learning-Framework bereitgestellt wird, um das Basismodell (Bi-LSTM) mit der Graph-Regularisierung zu versehen. Die restlichen Schritte zum Trainieren und Bewerten des graphregulierten Modells ähneln denen des Basismodells.

Erstellen Sie ein grafisch reguliertes Modell

Um den inkrementellen Nutzen der Regularisierung von Graphen zu bewerten, erstellen wir eine neue Basismodellinstanz. Dies liegt daran, dass das model bereits für einige Iterationen trainiert wurde und die Wiederverwendung dieses trainierten Modells zur Erstellung eines grafisch regulierten Modells kein fairer Vergleich für das model .

# Build a new base LSTM model.
base_reg_model = make_bilstm_model()
# Wrap the base model with graph regularization.
graph_reg_config = nsl.configs.make_graph_reg_config(
    max_neighbors=HPARAMS.num_neighbors,
    multiplier=HPARAMS.graph_regularization_multiplier,
    distance_type=HPARAMS.distance_type,
    sum_over_axis=-1)
graph_reg_model = nsl.keras.GraphRegularization(base_reg_model,
                                                graph_reg_config)
graph_reg_model.compile(
    optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

Trainiere das Modell

graph_reg_history = graph_reg_model.fit(
    train_dataset,
    validation_data=validation_dataset,
    epochs=HPARAMS.train_epochs,
    verbose=1)
Epoch 1/10

/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/framework/indexed_slices.py:432: UserWarning: Converting sparse IndexedSlices to a dense Tensor of unknown shape. This may consume a large amount of memory.
  "Converting sparse IndexedSlices to a dense Tensor of unknown shape. "

21/21 [==============================] - 22s 1s/step - loss: 0.6925 - accuracy: 0.5135 - scaled_graph_loss: 7.8682e-06 - val_loss: 0.6925 - val_accuracy: 0.5207
Epoch 2/10
21/21 [==============================] - 22s 1s/step - loss: 0.6902 - accuracy: 0.5373 - scaled_graph_loss: 2.3502e-05 - val_loss: 0.6591 - val_accuracy: 0.6627
Epoch 3/10
21/21 [==============================] - 21s 981ms/step - loss: 0.6376 - accuracy: 0.6942 - scaled_graph_loss: 0.0028 - val_loss: 0.6867 - val_accuracy: 0.5343
Epoch 4/10
21/21 [==============================] - 20s 975ms/step - loss: 0.6240 - accuracy: 0.7031 - scaled_graph_loss: 9.6606e-04 - val_loss: 0.5891 - val_accuracy: 0.7572
Epoch 5/10
21/21 [==============================] - 20s 973ms/step - loss: 0.5111 - accuracy: 0.7896 - scaled_graph_loss: 0.0059 - val_loss: 0.4260 - val_accuracy: 0.8207
Epoch 6/10
21/21 [==============================] - 21s 981ms/step - loss: 0.3816 - accuracy: 0.8508 - scaled_graph_loss: 0.0157 - val_loss: 0.3182 - val_accuracy: 0.8682
Epoch 7/10
21/21 [==============================] - 20s 976ms/step - loss: 0.3488 - accuracy: 0.8704 - scaled_graph_loss: 0.0202 - val_loss: 0.3156 - val_accuracy: 0.8749
Epoch 8/10
21/21 [==============================] - 20s 973ms/step - loss: 0.3227 - accuracy: 0.8815 - scaled_graph_loss: 0.0198 - val_loss: 0.2746 - val_accuracy: 0.8932
Epoch 9/10
21/21 [==============================] - 21s 1s/step - loss: 0.3058 - accuracy: 0.8958 - scaled_graph_loss: 0.0220 - val_loss: 0.2938 - val_accuracy: 0.8833
Epoch 10/10
21/21 [==============================] - 21s 979ms/step - loss: 0.2789 - accuracy: 0.9008 - scaled_graph_loss: 0.0233 - val_loss: 0.2622 - val_accuracy: 0.8981

Bewerten Sie das Modell

graph_reg_results = graph_reg_model.evaluate(test_dataset, steps=HPARAMS.eval_steps)
print(graph_reg_results)
196/196 [==============================] - 16s 82ms/step - loss: 0.3543 - accuracy: 0.8508
[0.354336142539978, 0.8507599830627441]

Erstellen Sie ein Diagramm der Genauigkeit / des Verlusts im Zeitverlauf

graph_reg_history_dict = graph_reg_history.history
graph_reg_history_dict.keys()
dict_keys(['loss', 'accuracy', 'scaled_graph_loss', 'val_loss', 'val_accuracy'])

Das Wörterbuch enthält insgesamt fünf Einträge: Trainingsverlust, Trainingsgenauigkeit, Verlust von Trainingsgraphen, Validierungsverlust und Validierungsgenauigkeit. Wir können sie alle zum Vergleich zusammen zeichnen. Beachten Sie, dass der Grafikverlust nur während des Trainings berechnet wird.

acc = graph_reg_history_dict['accuracy']
val_acc = graph_reg_history_dict['val_accuracy']
loss = graph_reg_history_dict['loss']
graph_loss = graph_reg_history_dict['scaled_graph_loss']
val_loss = graph_reg_history_dict['val_loss']

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

plt.clf()   # clear figure

# "-r^" is for solid red line with triangle markers.
plt.plot(epochs, loss, '-r^', label='Training loss')
# "-gD" is for solid green line with diamond markers.
plt.plot(epochs, graph_loss, '-gD', label='Training graph loss')
# "-b0" is for solid blue line with circle markers.
plt.plot(epochs, val_loss, '-bo', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend(loc='best')

plt.show()

png

plt.clf()   # clear figure

plt.plot(epochs, acc, '-r^', label='Training acc')
plt.plot(epochs, val_acc, '-bo', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend(loc='best')

plt.show()

png

Die Kraft des halbüberwachten Lernens

Halbüberwachtes Lernen und insbesondere die Regularisierung von Graphen im Kontext dieses Tutorials können sehr leistungsfähig sein, wenn die Menge der Trainingsdaten gering ist. Der Mangel an Trainingsdaten wird durch die Nutzung der Ähnlichkeit zwischen den Trainingsmustern ausgeglichen, was beim traditionellen überwachten Lernen nicht möglich ist.

Wir definieren das Überwachungsverhältnis als das Verhältnis von Trainingsproben zur Gesamtzahl der Proben, einschließlich Trainings-, Validierungs- und Testproben. In diesem Notizbuch haben wir ein Überwachungsverhältnis von 0,05 (dh 5% der gekennzeichneten Daten) verwendet, um sowohl das Basismodell als auch das graphisch regulierte Modell zu trainieren. Wir veranschaulichen den Einfluss des Überwachungsverhältnisses auf die Modellgenauigkeit in der folgenden Zelle.

# Accuracy values for both the Bi-LSTM model and the feed forward NN model have
# been precomputed for the following supervision ratios.

supervision_ratios = [0.3, 0.15, 0.05, 0.03, 0.02, 0.01, 0.005]

model_tags = ['Bi-LSTM model', 'Feed Forward NN model']
base_model_accs = [[84, 84, 83, 80, 65, 52, 50], [87, 86, 76, 74, 67, 52, 51]]
graph_reg_model_accs = [[84, 84, 83, 83, 65, 63, 50],
                        [87, 86, 80, 75, 67, 52, 50]]

plt.clf()  # clear figure

fig, axes = plt.subplots(1, 2)
fig.set_size_inches((12, 5))

for ax, model_tag, base_model_acc, graph_reg_model_acc in zip(
    axes, model_tags, base_model_accs, graph_reg_model_accs):

  # "-r^" is for solid red line with triangle markers.
  ax.plot(base_model_acc, '-r^', label='Base model')
  # "-gD" is for solid green line with diamond markers.
  ax.plot(graph_reg_model_acc, '-gD', label='Graph-regularized model')
  ax.set_title(model_tag)
  ax.set_xlabel('Supervision ratio')
  ax.set_ylabel('Accuracy(%)')
  ax.set_ylim((25, 100))
  ax.set_xticks(range(len(supervision_ratios)))
  ax.set_xticklabels(supervision_ratios)
  ax.legend(loc='best')

plt.show()
<Figure size 432x288 with 0 Axes>

png

Es kann beobachtet werden, dass mit abnehmendem Superivisionsverhältnis auch die Modellgenauigkeit abnimmt. Dies gilt sowohl für das Basismodell als auch für das graphregulierte Modell, unabhängig von der verwendeten Modellarchitektur. Beachten Sie jedoch, dass das graphregulierte Modell für beide Architekturen eine bessere Leistung als das Basismodell aufweist. Insbesondere für das Bi-LSTM-Modell ist die Genauigkeit des graphisch regulierten Modells ~ 20% höher als die des Basismodells, wenn das Überwachungsverhältnis 0,01 beträgt. Dies ist hauptsächlich auf das halbüberwachte Lernen für das graphregulierte Modell zurückzuführen, bei dem zusätzlich zu den Trainingsmustern selbst strukturelle Ähnlichkeiten zwischen Trainingsmustern verwendet werden.

Fazit

Wir haben die Verwendung der Graph-Regularisierung unter Verwendung des NSL-Frameworks (Neural Structured Learning) demonstriert, auch wenn die Eingabe keinen expliziten Graphen enthält. Wir haben die Aufgabe der Stimmungsklassifizierung von IMDB-Filmkritiken betrachtet, für die wir ein Ähnlichkeitsdiagramm basierend auf Einbettungen von Rezensionen synthetisiert haben. Wir empfehlen Benutzern, weiter zu experimentieren, indem sie die Hyperparameter und den Umfang der Überwachung variieren und unterschiedliche Modellarchitekturen verwenden.