Questa pagina è stata tradotta dall'API Cloud Translation.
Switch to English

Regolarizzazione dei grafici per la classificazione del sentiment utilizzando grafici sintetizzati

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza sorgente su GitHub

Panoramica

Questo taccuino classifica le recensioni di film come positive o negative utilizzando il testo della recensione. Questo è un esempio di classificazione binaria , un tipo di problema di apprendimento automatico importante e ampiamente applicabile.

Dimostreremo l'uso della regolarizzazione del grafico in questo quaderno costruendo un grafico dall'input fornito. La ricetta generale per costruire un modello regolarizzato con grafici utilizzando il framework Neural Structured Learning (NSL) quando l'input non contiene un grafico esplicito è la seguente:

  1. Crea incorporamenti per ogni campione di testo nell'input. Questo può essere fatto utilizzando modelli pre-addestrati come word2vec , Swivel , BERT ecc.
  2. Costruisci un grafico basato su questi incorporamenti utilizzando una metrica di somiglianza come la distanza "L2", la distanza "coseno", ecc. I nodi nel grafico corrispondono ai campioni e gli spigoli nel grafico corrispondono alla somiglianza tra coppie di campioni.
  3. Genera i dati di allenamento dal grafico sintetizzato sopra e dalle caratteristiche di esempio. I dati di addestramento risultanti conterranno funzionalità adiacenti oltre alle funzionalità del nodo originali.
  4. Crea una rete neurale come modello base utilizzando l'API sequenziale, funzionale o di sottoclasse di Keras.
  5. Avvolgi il modello di base con la classe wrapper GraphRegularization, fornita dal framework NSL, per creare un nuovo modello di Keras a grafo. Questo nuovo modello includerà una perdita di regolarizzazione del grafico come termine di regolarizzazione nel suo obiettivo di formazione.
  6. Addestra e valuta il modello grafico di Keras.

Requisiti

  1. Installa il pacchetto Neural Structured Learning.
  2. Installa tensorflow-hub.
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.

Dipendenze e importazioni

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

Set di dati IMDB

Il set di dati IMDB contiene il testo di 50.000 recensioni di film dall'Internet Movie Database . Questi sono suddivisi in 25.000 revisioni per la formazione e 25.000 revisioni per i test. I set di formazione e test sono bilanciati , nel senso che contengono un numero uguale di recensioni positive e negative.

In questo tutorial, useremo una versione preelaborata del set di dati IMDB.

Scarica il dataset IMDB preelaborato

Il set di dati IMDB viene fornito con TensorFlow. È già stato preelaborato in modo tale che le revisioni (sequenze di parole) siano state convertite in sequenze di numeri interi, dove ogni numero intero rappresenta una parola specifica in un dizionario.

Il codice seguente scarica il set di dati IMDB (o utilizza una copia cache se è già stato scaricato):

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'argomento num_words=10000 mantiene le prime 10.000 parole ricorrenti nei dati di addestramento. Le rare parole vengono scartate per mantenere gestibile la dimensione del vocabolario.

Esplora i dati

Prendiamoci un momento per capire il formato dei dati. Il set di dati viene preelaborato: ogni esempio è un array di numeri interi che rappresentano le parole della recensione del film. Ogni etichetta è un valore intero di 0 o 1, dove 0 è una recensione negativa e 1 è una recensione positiva.

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

Il testo delle recensioni è stato convertito in numeri interi, dove ogni numero intero rappresenta una parola specifica in un dizionario. Ecco come appare la prima recensione:

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]

Le recensioni dei film possono avere lunghezze diverse. Il codice seguente mostra il numero di parole nella prima e nella seconda recensione. Poiché gli input a una rete neurale devono essere della stessa lunghezza, sarà necessario risolverlo in seguito.

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

Converti di nuovo gli interi in parole

Può essere utile sapere come riconvertire gli interi nel testo corrispondente. Qui creeremo una funzione di supporto per interrogare un oggetto dizionario che contiene il numero intero per la mappatura della stringa:

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

Ora possiamo usare la funzione decode_review per visualizzare il testo per la prima revisione:

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"

Costruzione del grafico

La costruzione del grafico implica la creazione di incorporamenti per campioni di testo e quindi l'utilizzo di una funzione di somiglianza per confrontare gli incorporamenti.

Prima di procedere oltre, creiamo prima una directory per archiviare gli artefatti creati da questo tutorial.

mkdir -p /tmp/imdb

Crea incorporamenti di esempio

Useremo incorporamenti Swivel pre-addestrati per creare incorporamenti nel formato tf.train.Example per ogni campione nell'input. Memorizzeremo gli incorporamenti risultanti nel formato TFRecord insieme a una funzionalità aggiuntiva che rappresenta l'ID di ogni campione. Questo è importante e ci consentirà di abbinare gli incorporamenti di esempio con i nodi corrispondenti nel grafico in un secondo momento.

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

Costruisci un grafico

Ora che abbiamo gli incorporamenti di esempio, li useremo per costruire un grafico di somiglianza, cioè, i nodi in questo grafico corrisponderanno ai campioni e gli archi in questo grafico corrisponderanno alla somiglianza tra coppie di nodi.

L'apprendimento strutturato neurale fornisce una libreria per la creazione di grafici per creare un grafico basato su esempi di incorporamenti. Utilizza la somiglianza del coseno come misura di somiglianza per confrontare gli incorporamenti e creare bordi tra di loro. Ci consente inoltre di specificare una soglia di somiglianza, che può essere utilizzata per scartare bordi dissimili dal grafico finale. In questo esempio, utilizzando 0,99 come soglia di somiglianza e 12345 come seme casuale, si ottiene un grafico con 429.415 bordi bidirezionali. Qui stiamo usando il supporto del generatore di grafici per l' hashing sensibile alla località (LSH) per accelerare la creazione di grafici. Per i dettagli sull'utilizzo del supporto LSH del generatore di grafici, vedere la documentazione API 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)

Ogni bordo bidirezionale è rappresentato da due bordi diretti nel file TSV di output, in modo che il file contenga 429.415 * 2 = 858.830 linee totali:

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

Caratteristiche di esempio

Creiamo funzionalità di esempio per il nostro problema utilizzando il formato tf.train.Example e le TFRecord formato TFRecord . Ogni campione includerà le seguenti tre caratteristiche:

  1. id : l'ID del nodo del campione.
  2. parole : un elenco int64 contenente ID di parole.
  3. label : un singleton int64 che identifica la classe di destinazione della recensione.
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

Aumenta i dati di allenamento con i vicini del grafico

Poiché abbiamo le funzionalità di esempio e il grafico sintetizzato, possiamo generare i dati di addestramento aumentato per l'apprendimento strutturato neurale. Il framework NSL fornisce una libreria per combinare il grafico e le funzionalità di esempio per produrre i dati di addestramento finali per la regolarizzazione del grafico. I dati di addestramento risultanti includeranno caratteristiche di esempio originali e caratteristiche dei loro vicini corrispondenti.

In questo tutorial, consideriamo i bordi non orientati e utilizziamo un massimo di 3 vicini per campione per aumentare i dati di addestramento con i vicini del grafico.

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)

Modello base

Siamo ora pronti per costruire un modello base senza regolarizzazione del grafico. Per costruire questo modello, possiamo utilizzare gli incorporamenti utilizzati nella creazione del grafico, oppure possiamo apprendere nuovi incorporamenti insieme all'attività di classificazione. Ai fini di questo taccuino, faremo il secondo.

Variabili globali

NBR_FEATURE_PREFIX = 'NL_nbr_'
NBR_WEIGHT_SUFFIX = '_weight'

iperparametri

Useremo un'istanza di HParams per HParams vari iperparametri e costanti utilizzati per l'addestramento e la valutazione. Descriviamo brevemente ciascuno di essi di seguito:

  • num_classes : ci sono 2 classi: positiva e negativa .

  • max_seq_length : questo è il numero massimo di parole considerate da ciascuna recensione di film in questo esempio.

  • vocab_size : questa è la dimensione del vocabolario considerato per questo esempio.

  • distance_type : questa è la metrica della distanza utilizzata per regolarizzare il campione con i suoi vicini.

  • graph_regularization_multiplier : controlla il peso relativo del termine di regolarizzazione del grafico nella funzione di perdita complessiva.

  • num_neighbors : il numero di vicini utilizzati per la regolarizzazione del grafico. Questo valore deve essere minore o uguale all'argomento max_nbrs usato sopra quando si richiama nsl.tools.pack_nbrs .

  • num_fc_units : il numero di unità nel livello completamente connesso della rete neurale.

  • train_epochs : il numero di periodi di addestramento.

  • batch_size : dimensione del batch utilizzata per l'addestramento e la valutazione.

  • eval_steps : il numero di batch da elaborare prima di ritenere che la valutazione sia completa. Se impostato su None , vengono valutate tutte le istanze nel set di test.

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

Prepara i dati

Le revisioni, gli array di numeri interi, devono essere convertite in tensori prima di essere immesse nella rete neurale. Questa conversione può essere eseguita in due modi:

  • Converti gli array in vettori di 0 1 s che indicano l'occorrenza della parola, simile a una codifica one-hot. Ad esempio, la sequenza [3, 5] diventerebbe un vettore dimensionale 10000 che è composto da tutti zeri ad eccezione degli indici 3 e 5 , che sono uno. Quindi, rendilo il primo livello nella nostra rete, uno strato Dense , in grado di gestire dati vettoriali in virgola mobile. Questo approccio richiede num_words * num_reviews memoria, tuttavia, richiede una matrice di dimensioni num_words * num_reviews .

  • In alternativa, possiamo riempire gli array in modo che abbiano tutti la stessa lunghezza, quindi creare un tensore intero di forma max_length * num_reviews . Possiamo utilizzare un livello di incorporamento in grado di gestire questa forma come primo livello nella nostra rete.

In questo tutorial useremo il secondo approccio.

Poiché le recensioni dei film devono avere la stessa lunghezza, useremo la funzione pad_sequence definita di seguito per standardizzare le lunghezze.

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

Costruisci il modello

Una rete neurale viene creata sovrapponendo i livelli: ciò richiede due decisioni architettoniche principali:

  • Quanti strati usare nel modello?
  • Quante unità nascoste usare per ogni livello?

In questo esempio, i dati di input sono costituiti da un array di indici di parole. Le etichette da prevedere sono 0 o 1.

Useremo un LSTM bidirezionale come modello base in questo tutorial.

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

I livelli vengono effettivamente impilati in sequenza per creare il classificatore:

  1. Il primo livello è un livello di Input che accetta il vocabolario con codifica intera.
  2. Il livello successivo è un livello di Embedding , che prende il vocabolario con codifica intera e cerca il vettore di incorporamento per ogni indice di parola. Questi vettori vengono appresi mentre il modello si allena. I vettori aggiungono una dimensione all'array di output. Le dimensioni risultanti sono: (batch, sequence, embedding) .
  3. Successivamente, un livello LSTM bidirezionale restituisce un vettore di output a lunghezza fissa per ogni esempio.
  4. Questo vettore di output a lunghezza fissa viene convogliato attraverso uno strato ( Dense ) completamente connesso con 64 unità nascoste.
  5. L'ultimo strato è densamente connesso con un singolo nodo di output. Utilizzando la funzione di attivazione del sigmoid , questo valore è un float compreso tra 0 e 1, che rappresenta una probabilità o un livello di confidenza.

Unità nascoste

Il modello sopra ha due livelli intermedi o "nascosti", tra l'input e l'output, ed escluso il livello Embedding . Il numero di output (unità, nodi o neuroni) è la dimensione dello spazio di rappresentazione per il livello. In altre parole, la quantità di libertà consentita alla rete durante l'apprendimento di una rappresentazione interna.

Se un modello ha più unità nascoste (uno spazio di rappresentazione di dimensioni superiori) e / o più livelli, la rete può apprendere rappresentazioni più complesse. Tuttavia, rende la rete più costosa dal punto di vista computazionale e può portare all'apprendimento di modelli indesiderati, modelli che migliorano le prestazioni sui dati di addestramento ma non sui dati di prova. Questo si chiama overfitting .

Funzione di perdita e ottimizzatore

Un modello necessita di una funzione di perdita e di un ottimizzatore per l'allenamento. Poiché si tratta di un problema di classificazione binaria e il modello restituisce una probabilità (uno strato a unità singola con un'attivazione del sigmoide), utilizzeremo la funzione di perdita binary_crossentropy .

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

Crea un set di convalida

Durante l'addestramento, vogliamo verificare l'accuratezza del modello su dati che non ha visto prima. Crea un set di convalida separando una frazione dei dati di addestramento originali. (Perché non utilizzare il set di test ora? Il nostro obiettivo è sviluppare e mettere a punto il nostro modello utilizzando solo i dati di addestramento, quindi utilizzare i dati di test solo una volta per valutare la nostra accuratezza).

In questo tutorial, prendiamo circa il 10% dei campioni di addestramento iniziale (10% di 25000) come dati etichettati per l'addestramento e il restante come dati di convalida. Poiché la divisione treno / test iniziale era 50/50 (25000 campioni ciascuno), la divisione treno / convalida / test effettiva che abbiamo ora è 5/45/50.

Nota che "train_dataset" è già stato raggruppato e mischiato.

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

Addestra il modello

Addestra il modello in mini-lotti. Durante l'addestramento, monitora la perdita e l'accuratezza del modello sul set di convalida:

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

Valuta il modello

Vediamo ora come si comporta il modello. Verranno restituiti due valori. Perdita (un numero che rappresenta il nostro errore, i valori più bassi sono migliori) e precisione.

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]

Crea un grafico di accuratezza / perdita nel tempo

model.fit() restituisce un oggetto History che contiene un dizionario con tutto ciò che è accaduto durante l'addestramento:

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

Sono disponibili quattro voci: una per ciascuna metrica monitorata durante l'addestramento e la convalida. Possiamo usarli per tracciare l'addestramento e la perdita di convalida per il confronto, nonché l'accuratezza dell'addestramento e della convalida:

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

Notare che la perdita di allenamento diminuisce con ogni epoca e la precisione dell'allenamento aumenta con ogni epoca. Ciò è previsto quando si utilizza un'ottimizzazione della discesa del gradiente: dovrebbe ridurre al minimo la quantità desiderata ad ogni iterazione.

Regolarizzazione del grafico

Siamo ora pronti per provare la regolarizzazione del grafico utilizzando il modello base che abbiamo costruito sopra. Useremo la classe wrapper GraphRegularization fornita dal framework Neural Structured Learning per avvolgere il modello di base (bi-LSTM) per includere la regolarizzazione del grafico. Il resto dei passaggi per l'addestramento e la valutazione del modello regolarizzato con grafici sono simili a quelli del modello base.

Crea un modello regolarizzato dal grafico

Per valutare il vantaggio incrementale della regolarizzazione del grafico, creeremo una nuova istanza del modello di base. Questo perché il model è già stato addestrato per alcune iterazioni e il riutilizzo di questo modello addestrato per creare un modello regolarizzato con grafo non sarà un confronto equo per il 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'])

Addestra il modello

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

Valuta il modello

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]

Crea un grafico di accuratezza / perdita nel tempo

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

Ci sono cinque voci in totale nel dizionario: perdita di addestramento, precisione di addestramento, perdita di grafico di addestramento, perdita di convalida e precisione di convalida. Possiamo tracciarli tutti insieme per il confronto. Notare che la perdita del grafico viene calcolata solo durante l'allenamento.

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

Il potere dell'apprendimento semi-supervisionato

L'apprendimento semi-supervisionato e, più specificamente, la regolarizzazione dei grafici nel contesto di questo tutorial, possono essere davvero potenti quando la quantità di dati di allenamento è piccola. La mancanza di dati sulla formazione viene compensata sfruttando la somiglianza tra i campioni di formazione, cosa non possibile nell'apprendimento supervisionato tradizionale.

Definiamo il rapporto di supervisione come il rapporto tra i campioni di addestramento e il numero totale di campioni che include addestramento, convalida e campioni di prova. In questo notebook, abbiamo utilizzato un rapporto di supervisione di 0,05 (ovvero il 5% dei dati etichettati) per l'addestramento sia del modello di base che del modello regolarizzato con il grafico. Illustriamo l'impatto del rapporto di supervisione sulla precisione del modello nella cella sottostante.

# 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

Si può osservare che al diminuire del rapporto di superivisione, diminuisce anche l'accuratezza del modello. Ciò è vero sia per il modello base che per il modello regolarizzato con grafo, indipendentemente dall'architettura del modello utilizzata. Tuttavia, si noti che il modello regolarizzato con grafo funziona meglio del modello base per entrambe le architetture. In particolare, per il modello Bi-LSTM, quando il rapporto di supervisione è 0,01, la precisione del modello regolarizzato con grafico è del ~ 20% superiore a quella del modello base. Ciò è dovuto principalmente all'apprendimento semi-supervisionato per il modello regolarizzato con grafo, in cui la somiglianza strutturale tra i campioni di addestramento viene utilizzata in aggiunta ai campioni di addestramento stessi.

Conclusione

Abbiamo dimostrato l'uso della regolarizzazione dei grafi utilizzando il framework NSL (Neural Structured Learning) anche quando l'input non contiene un grafico esplicito. Abbiamo considerato il compito della classificazione del sentiment delle recensioni di film IMDB per il quale abbiamo sintetizzato un grafico di somiglianza basato sulle recensioni incorporate. Incoraggiamo gli utenti a sperimentare ulteriormente variando gli iperparametri, la quantità di supervisione e utilizzando diverse architetture di modello.