Cette page a été traduite par l'API Cloud Translation.
Switch to English

Graphique régularisation pour la classification des sentiments en utilisant des graphiques synthétisés

Voir sur TensorFlow.org Exécuter dans Google Colab Voir la source sur GitHub

Aperçu

Ce bloc - notes classifie critiques de films comme positifs ou négatifs en utilisant le texte de l'examen. Ceci est un exemple de classification binaire, une sorte importante et largement applicable problème d'apprentissage de la machine.

Nous démontrerons l'utilisation de régularisation graphique dans ce bloc-notes en construisant un graphique de l'entrée donnée. La recette générale pour la construction d'un modèle régularisé graphique utilisant le cadre d'apprentissage structuré Neural (NSL) lorsque l'entrée ne contient pas un graphique explicite est la suivante:

  1. Créer incorporations pour chaque échantillon de texte dans l'entrée. Cela peut être fait en utilisant des modèles pré-formés tels que word2vec , pivotant , BERT etc.
  2. Construire un graphe sur la base de ces encastrements en utilisant une métrique de similarité tel que la distance de la distance « L2 », « cosinus », etc. noeuds dans le graphe correspondent à des échantillons et les arêtes dans le graphe correspond à la similitude entre des paires d'échantillons.
  3. Générer des données de formation à partir du graphique ci-dessus synthétisé et caractéristiques échantillons. Les données de formation qui en résultent contiendront des caractéristiques voisines en plus des caractéristiques de nœud d'origine.
  4. Créer un réseau neuronal en tant que modèle de base en utilisant la séquence Keras, API fonctionnel ou sous-classe.
  5. Enroulez le modèle de base avec la classe wrapper GraphRegularization, qui est fourni par le cadre de NSL, pour créer un nouveau modèle Keras graphique. Ce nouveau modèle comprendra une perte de régularisation graphique comme terme de régularisation dans son objectif de formation.
  6. Former et évaluer le graphique modèle Keras.

Exigences

  1. Installez le package d'apprentissage structuré Neural.
  2. Installer tensorflow-moyeu.
pip install --quiet neural-structured-learning
pip install --quiet tensorflow-hub

Dépendances et les importations

 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

ensemble de données IMDB

Le jeu de données IMDB contient le texte de 50.000 critiques de films de la Internet Movie Database . Ceux-ci sont répartis en 25.000 commentaires pour la formation et 25.000 commentaires pour les tests. Les ensembles de formation et d' essai sont équilibrés, ce qui signifie qu'ils contiennent un nombre égal de commentaires positifs et négatifs.

Dans ce tutoriel, nous utiliserons une version prétraité du jeu de données IMDB.

Télécharger prétraité ensemble de données IMDB

Le jeu de données IMDB est livré avec tensorflow. Il a déjà été prétraité tels que les examens (séquences de mots) ont été converties en séquences d'entiers, où chaque entier représente un mot spécifique dans un dictionnaire.

Le code suivant télécharge le jeu de données IMDB (ou utilise une copie en cache si elle a déjà été téléchargé):

 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

L'argument num_words=10000 maintient les 10.000 premiers mots qui se produisent le plus souvent dans les données de formation. Les mots rares sont mis au rebut pour maintenir la taille du vocabulaire facile à gérer.

Explorez les données

Prenons un instant pour comprendre le format des données. L'ensemble de données est prétraitée: chaque exemple est un tableau d'entiers représentant les mots de la critique du film. Chaque étiquette est une valeur de nombre entier de 0 ou 1, 0 étant un avis négatif, et 1 est un avis positif.

 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

Le texte des commentaires ont été convertis en entiers, où chaque entier représente un mot spécifique dans un dictionnaire. Voici ce que le premier examen ressemble:

 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]

Critiques de films peuvent être différentes longueurs. Le dessous du code indique le nombre de mots dans les premier et deuxième avis. Étant donné que les entrées à un réseau de neurones doit être la même longueur, nous devons résoudre plus tard.

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

Convertir les entiers aux mots de retour

Il peut être utile de savoir comment convertir des entiers au texte correspondant. Ici, nous allons créer une fonction d'assistance pour interroger un objet dictionnaire qui contient l'entier à la cartographie de la chaîne:

 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

Maintenant , nous pouvons utiliser la decode_review fonction pour afficher le texte pour la première évaluation:

 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"

graphique construction

la construction graphique implique la création d'incorporations pour les échantillons de texte, puis en utilisant une fonction de similarité pour comparer les incorporations.

Avant d'aller plus loin, nous créons d'abord un répertoire pour stocker des objets créés par ce tutoriel.

mkdir -p /tmp/imdb

Créer un échantillon embeddings

Nous utiliserons incorporations pour créer Swivel incorporations pré - entraîné dans le tf.train.Example format pour chaque échantillon dans l'entrée. Nous conserverons les incorporations résultant du TFRecord le format long avec une fonctionnalité supplémentaire qui représente l'ID de chaque échantillon. Ceci est important et nous permettra correspondre avec des noeuds échantillons incorporations correspondant dans le graphique plus tard.

 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

Construire un graphique

Maintenant que nous avons l'échantillon incorporations, nous les utiliserons pour construire un graphe de similarité, par exemple, les noeuds de ce graphique correspondent à des échantillons et des arêtes dans ce graphique correspondra à la similitude entre des paires de noeuds.

Neural apprentissage structuré fournit une bibliothèque de construction graphique pour construire un graphique basé sur l'échantillon incorporations. Il utilise la similarité cosinus comme la mesure de similarité pour comparer les incorporations et les bords de construction entre eux. Il nous permet également de définir un seuil de similarité, qui peut être utilisé pour éliminer des bords différents du graphe final. Dans cet exemple, en utilisant 0,99 comme seuil de similitude, nous nous retrouvons avec un graphique qui a 445,327 bords bi-directionnels.

 nsl.tools.build_graph(['/tmp/imdb/embeddings.tfr'],
                      '/tmp/imdb/graph_99.tsv',
                      similarity_threshold=0.99)
 

caractéristiques échantillons

Nous créons des fonctionnalités exemples pour notre problème en utilisant le tf.train.Example le format et les persistons dans le TFRecord format. Chaque échantillon comprendra les trois caractéristiques suivantes:

  1. id: l'ID de noeud de l'échantillon.
  2. mots: Une liste de int64 contenant les identifiants de mots.
  3. Label: singleton int64 identifiant la classe cible de l'examen.
 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

les données de formation augment avec les voisins graphique

Puisque nous avons les caractéristiques de l'échantillon et le graphique de synthèse, nous pouvons générer les données de formation augmentée pour Neural apprentissage structuré. Le cadre de NSL fournit une bibliothèque de combiner le graphique et l'échantillon présente pour produire les données de formation finale de régularisation graphique. Les données de formation qui en découlent comprennent des caractéristiques originales des échantillons ainsi que les caractéristiques de leurs voisins correspondants.

Dans ce tutoriel, nous considérons les bords et non orientés utiliser un maximum de 3 voisins par échantillon aux données de formation avec les voisins de Améliorés graphique.

 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)
 

modèle de base

Nous sommes maintenant prêts à construire un modèle de base sans régularisation graphique. Pour construire ce modèle, nous pouvons soit utiliser incorporations qui ont été utilisés dans la construction du graphique, ou nous pouvons apprendre de nouvelles incorporations conjointement avec la tâche de classification. Aux fins de ce portable, nous ferons ce dernier.

Variables globales

 NBR_FEATURE_PREFIX = 'NL_nbr_'
NBR_WEIGHT_SUFFIX = '_weight'
 

hyperparam'etres

Nous utiliserons une instance de HParams à diverses hyperparam'etres et inclue constantes utilisées pour la formation et l' évaluation. Nous décrivons brièvement chacun d'eux ci-dessous:

  • num_classes: Il y a 2 classes - positives et négatives.

  • max_seq_length: Ceci est le nombre maximum de mots considérés lors de chaque examen de film dans cet exemple.

  • vocab_size: Ceci est la taille du vocabulaire considéré pour cet exemple.

  • distance_type: Ceci est la distance métrique utilisée pour régulariser l'échantillon avec ses voisins.

  • graph_regularization_multiplier: Ce contrôle le poids relatif du terme de régularisation graphique dans la fonction globale de perte.

  • num_neighbors: Le nombre de voisins utilisés pour la régularisation du graphique. Cette valeur doit être inférieure ou égale à l' max_nbrs argument utilisé ci - dessus lors de l' appel nsl.tools.pack_nbrs .

  • num_fc_units: Le nombre d'unités dans la couche entièrement relié du réseau de neurones.

  • train_epochs: Le nombre d'époques de formation.

  • batch_size: Taille du lot utilisé pour la formation et l' évaluation.

  • eval_steps: Le nombre de lots à traiter avant de considérer l' évaluation est terminée. Si la valeur None , toutes les instances du jeu de test sont évalués.

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

Préparer les données

Les critiques-les tableaux d'entiers-doivent être convertis en tenseurs avant d'être introduit dans le réseau de neurones. Cette conversion peut se faire de deux façons:

  • Convertir les matrices dans des vecteurs de 0 s et 1 s indiquant l' apparition de mots, semblable à un codage d'un chaud. Par exemple, la séquence [3, 5] deviendrait un 10000 vecteur qui est tous -dimensionnelle zéros à l' exception des indices 3 et 5 , qui sont ceux. Ensuite, faire de cette première couche dans notre réseau une Dense couche qui peut gérer des données flottantes point de vecteur. Cette approche utilise beaucoup de mémoire, bien que, nécessitant une num_words * num_reviews matrice de taille.

  • Sinon, nous pouvons rembourrer les tableaux de sorte qu'ils ont tous la même longueur, puis créer un tenseur entier de forme max_length * num_reviews . On peut utiliser une couche d'enrobage capable de traiter cette forme que la première couche dans notre réseau.

Dans ce tutoriel, nous utiliserons la deuxième approche.

Depuis les critiques de films doivent être de la même longueur, nous utiliserons la pad_sequence fonction définie ci - dessous pour normaliser les longueurs.

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

Construire le modèle

Un réseau de neurones est créé par l'empilement de couches cela nécessite deux principales décisions architecturales:

  • Combien de couches à utiliser dans le modèle?
  • Combien d'unités cachées à utiliser pour chaque couche?

Dans cet exemple, les données d'entrée se compose d'un ensemble de mots-indices. Les étiquettes pour prédire sont 0 ou 1.

Nous utiliserons un LSTM bidirectionnel comme modèle de base dans ce tutoriel.

 # 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
_________________________________________________________________

Les couches sont empilées séquentiellement de manière efficace pour construire le classificateur:

  1. La première couche est une Input couche qui tire le vocabulaire codé entier.
  2. La couche suivante est une Embedding couche, qui prend le vocabulaire codé entier et recherche le vecteur enrobage pour chaque mot-index. Ces vecteurs sont apprises comme les trains miniatures. Les vecteurs ajoutent une dimension au tableau de sortie. Les dimensions résultantes sont les suivantes : (batch, sequence, embedding) .
  3. Ensuite, une couche de LSTM bidirectionnelle renvoie un vecteur de sortie de longueur fixe pour chaque exemple.
  4. Ce vecteur de sortie de longueur fixe est canalisé à travers un (entièrement connecté Dense couche) avec 64 unités cachées.
  5. La dernière couche est densément relié à un seul noeud de sortie. Utilisation de la sigmoid fonction d'activation, cette valeur est un flotteur entre 0 et 1, ce qui représente une probabilité, ou le niveau de confiance.

unités cachées

Le modèle ci - dessus a deux couches intermédiaires ou « cachée », entre l'entrée et la sortie, et ne contenant pas la Embedding couche. Le nombre de sorties (unités, des noeuds, ou neurones) est la dimension de l'espace de représentation pour la couche. En d'autres termes, le montant de la liberté du réseau est autorisé lors de l'apprentissage d'une représentation interne.

Si un modèle possède des unités plus cachées (un espace de représentation de dimension supérieure), et / ou plusieurs couches, le réseau peut apprendre des représentations plus complexes. Cependant, il rend le réseau plus coûteux et peut informatiquement conduire à l'apprentissage indésirables modèles-modèles qui améliorent les performances des données de formation, mais pas sur les données de test. On appelle cela overfitting.

Fonction de perte et optimiseur

Un modèle a besoin d'une fonction de perte et un optimiseur de formation. Comme il s'agit d' un problème de classification binaire et le modèle délivre une probabilité (une seule couche unité avec une activation sigmoïde), nous allons utiliser la binary_crossentropy fonction de perte.

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

Créer un ensemble de validation

Lors de la formation, nous voulons vérifier l'exactitude du modèle des données qu'il n'a pas vu auparavant. Créer un ensemble de validation en mettant à part une fraction des données de formation d' origine. (Pourquoi ne pas utiliser l'ensemble de test maintenant? Notre objectif est de développer et affiner notre modèle en utilisant uniquement les données de formation, puis utiliser une fois les données de test pour évaluer notre précision).

Dans ce tutoriel, nous prenons environ 10% des échantillons de formation initiale (10% de 25 000) en tant que données marquées pour la formation et le reste des données de validation. Depuis le train initial / split test était 50/50 (25000 échantillons chacun), le train efficace / validation / test que nous avons maintenant est divisé 5/45/50.

Notez que « train_dataset » a déjà été regroupées et mélangées.

 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

Former le modèle

Former le modèle mini-lots. Bien que la formation, le suivi de la perte et la précision du modèle sur l'ensemble de validation:

 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 925ms/step - loss: 0.6930 - accuracy: 0.5092 - val_loss: 0.6924 - val_accuracy: 0.5006
Epoch 2/10
21/21 [==============================] - 19s 894ms/step - loss: 0.6890 - accuracy: 0.5465 - val_loss: 0.7294 - val_accuracy: 0.5698
Epoch 3/10
21/21 [==============================] - 19s 883ms/step - loss: 0.6785 - accuracy: 0.6208 - val_loss: 0.6489 - val_accuracy: 0.7043
Epoch 4/10
21/21 [==============================] - 19s 890ms/step - loss: 0.6592 - accuracy: 0.6400 - val_loss: 0.6523 - val_accuracy: 0.6866
Epoch 5/10
21/21 [==============================] - 19s 883ms/step - loss: 0.6413 - accuracy: 0.6923 - val_loss: 0.6335 - val_accuracy: 0.7004
Epoch 6/10
21/21 [==============================] - 21s 982ms/step - loss: 0.6053 - accuracy: 0.7188 - val_loss: 0.5716 - val_accuracy: 0.7183
Epoch 7/10
21/21 [==============================] - 18s 879ms/step - loss: 0.5204 - accuracy: 0.7619 - val_loss: 0.4511 - val_accuracy: 0.7930
Epoch 8/10
21/21 [==============================] - 19s 882ms/step - loss: 0.4719 - accuracy: 0.7758 - val_loss: 0.4244 - val_accuracy: 0.8094
Epoch 9/10
21/21 [==============================] - 18s 880ms/step - loss: 0.3695 - accuracy: 0.8431 - val_loss: 0.3567 - val_accuracy: 0.8487
Epoch 10/10
21/21 [==============================] - 19s 891ms/step - loss: 0.3504 - accuracy: 0.8500 - val_loss: 0.3219 - val_accuracy: 0.8652

Évaluer le modèle

Maintenant, nous allons voir comment le modèle exécute. Deux valeurs seront retournées. La perte (un nombre qui représente notre erreur, des valeurs inférieures sont mieux), et la précision.

 results = model.evaluate(test_dataset, steps=HPARAMS.eval_steps)
print(results)
 
196/196 [==============================] - 17s 85ms/step - loss: 0.4116 - accuracy: 0.8221
[0.4116455018520355, 0.8221200108528137]

Créer un graphique de précision / perte au fil du temps

model.fit() retourne une History objet qui contient un dictionnaire avec tout ce qui est arrivé lors de la formation:

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

Il y a quatre entrées: une pour chaque métrique contrôlée pendant la formation et la validation. Nous pouvons les utiliser pour tracer la perte de formation et de validation à titre de comparaison, ainsi que la précision de la formation et de validation:

 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

Notez que la perte de la formation diminue avec chaque époque et la précision formation augmente avec chaque époque. Cela devrait lors de l'utilisation d'une descente de gradient optimisation devrait réduire au minimum la quantité désirée à chaque itération.

graphique régularisation

Nous sommes maintenant prêts à essayer la régularisation de graphique à l'aide du modèle de base que nous avons construit plus haut. Nous utiliserons la GraphRegularization classe enveloppe fournie par le cadre d' apprentissage structuré Neural pour envelopper le modèle de base (bi-LSTM) pour inclure la régularisation graphique. Le reste des étapes de formation et d'évaluation du modèle régularisé graphique sont similaires à celle du modèle de base.

Créer un modèle de graphique régularisé

Pour évaluer l'avantage supplémentaire de régularisation graphique, nous allons créer une nouvelle instance de modèle de base. En effet , le model a déjà été formé pour quelques itérations, et réutiliser ce modèle formé pour créer un modèle régularisé graphique-ne sera pas une comparaison équitable pour 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'])
 

Former le modèle

 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.6930 - accuracy: 0.5246 - scaled_graph_loss: 2.9800e-06 - val_loss: 0.6929 - val_accuracy: 0.4998
Epoch 2/10
21/21 [==============================] - 21s 988ms/step - loss: 0.6909 - accuracy: 0.5200 - scaled_graph_loss: 7.8452e-06 - val_loss: 0.6838 - val_accuracy: 0.5917
Epoch 3/10
21/21 [==============================] - 21s 980ms/step - loss: 0.6656 - accuracy: 0.6277 - scaled_graph_loss: 6.1205e-04 - val_loss: 0.6591 - val_accuracy: 0.6905
Epoch 4/10
21/21 [==============================] - 21s 981ms/step - loss: 0.6395 - accuracy: 0.6846 - scaled_graph_loss: 0.0016 - val_loss: 0.5860 - val_accuracy: 0.7171
Epoch 5/10
21/21 [==============================] - 21s 980ms/step - loss: 0.5388 - accuracy: 0.7573 - scaled_graph_loss: 0.0043 - val_loss: 0.4910 - val_accuracy: 0.7844
Epoch 6/10
21/21 [==============================] - 21s 989ms/step - loss: 0.4105 - accuracy: 0.8281 - scaled_graph_loss: 0.0146 - val_loss: 0.3353 - val_accuracy: 0.8612
Epoch 7/10
21/21 [==============================] - 21s 986ms/step - loss: 0.3416 - accuracy: 0.8681 - scaled_graph_loss: 0.0203 - val_loss: 0.4134 - val_accuracy: 0.8209
Epoch 8/10
21/21 [==============================] - 21s 981ms/step - loss: 0.4230 - accuracy: 0.8273 - scaled_graph_loss: 0.0144 - val_loss: 0.4755 - val_accuracy: 0.7696
Epoch 9/10
21/21 [==============================] - 22s 1s/step - loss: 0.4905 - accuracy: 0.7950 - scaled_graph_loss: 0.0080 - val_loss: 0.3862 - val_accuracy: 0.8382
Epoch 10/10
21/21 [==============================] - 21s 978ms/step - loss: 0.3384 - accuracy: 0.8754 - scaled_graph_loss: 0.0215 - val_loss: 0.3002 - val_accuracy: 0.8811

Évaluer le modèle

 graph_reg_results = graph_reg_model.evaluate(test_dataset, steps=HPARAMS.eval_steps)
print(graph_reg_results)
 
196/196 [==============================] - 16s 84ms/step - loss: 0.3852 - accuracy: 0.8301
[0.385225385427475, 0.830079972743988]

Créer un graphique de précision / perte au fil du temps

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

Il y a cinq entrées au total dans le dictionnaire: la perte de la formation, la précision de la formation, la perte de graphique de la formation, la perte de validation, et la précision de validation. On peut les tracer tous ensemble pour la comparaison. Notez que la perte de graphique est calculée uniquement pendant la formation.

 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

La puissance de l'apprentissage semi-supervisé

Apprentissage semi-supervisé et plus précisément, la régularisation du graphique dans le cadre de ce tutoriel, peut être vraiment puissant quand la quantité de données de formation est faible. Le manque de données de formation est compensée en tirant parti de similitude entre les échantillons de formation, ce qui est impossible dans l'apprentissage traditionnel supervisé.

Nous définissons le rapport de surveillance comme le rapport des échantillons de formation au nombre total d'échantillons qui comprend la formation, la validation et des échantillons de test. Dans ce cahier, nous avons utilisé un rapport de surveillance de 0,05 (soit 5% des données étiquetées) pour la formation à la fois le modèle de base ainsi que le modèle graphique régularisé. Nous illustrons l'impact du rapport de surveillance sur la précision du modèle dans la cellule ci-dessous.

 # 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

On peut observer que le rapport de superivision diminue, la précision du modèle diminue également. Cela est vrai pour le modèle de base et pour le modèle régularisé graphique, quelle que soit l'architecture du modèle utilisé. Cependant, notez que le modèle régularisé graphique-performe mieux que le modèle de base pour les deux architectures. En particulier, pour le modèle Bi-LSTM, lorsque le rapport de supervision est de 0,01, la précision du modèle régularisé graph-est de ~ 20% supérieure à celle du modèle de base. Ceci est principalement en raison de l'apprentissage semi-supervisé pour le modèle régularisé graphique, où la similarité structurelle entre les échantillons de formation est utilisé en plus des échantillons de formation eux-mêmes.

Conclusion

Nous avons démontré que l'utilisation de la régularisation du graphique en utilisant le cadre d'apprentissage structuré Neural (NSL), même si l'entrée ne contient pas un graphique explicite. Nous avons considéré la tâche de classification des sentiments critiques de films IMDB pour lesquels nous avons synthétisé un graphique de similarité basée sur incorporations d'examen. Nous encourageons les utilisateurs à expérimenter davantage en faisant varier hyperparam'etres, le montant de la surveillance, et en utilisant différentes architectures de modèle.