Regolarizzazione del grafico per la classificazione del sentimento utilizzando grafici sintetizzati

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza la fonte su GitHub Scarica taccuino Vedi il modello del mozzo TF

Panoramica

Questa recensioni classifica notebook di film come positivo o negativo che utilizzano il testo della revisione. Questo è un esempio di classificazione binaria, un tipo importante e ampiamente applicabile di problema machine learning.

Dimostreremo l'uso della regolarizzazione del grafico in questo notebook costruendo un grafico dall'input fornito. La ricetta generale per la creazione di un modello regolato dal grafico 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 , girevole , BERT etc.
  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 dati di addestramento dal grafico sopra sintetizzato e dalle funzionalità di esempio. I dati di training risultanti conterranno feature adiacenti oltre alle feature del nodo originale.
  4. Crea una rete neurale come modello di base utilizzando l'API sequenziale, funzionale o di sottoclasse di Keras.
  5. Avvolgere il modello base con la classe wrapper GraphRegularization, fornita dal framework NSL, per creare un nuovo modello Keras grafico. Questo nuovo modello includerà un grafico della perdita di regolarizzazione come termine di regolarizzazione nel suo obiettivo di formazione.
  6. Addestrare e valutare il modello Keras grafico.

Requisiti

  1. Installa il pacchetto Neural Structured Learning.
  2. Installare tensorflow-hub.
pip install --quiet neural-structured-learning
pip install --quiet tensorflow-hub

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.8.0-rc0
Eager mode:  True
Hub version:  0.12.0
GPU is NOT AVAILABLE
2022-01-05 12:28:32.113752: E tensorflow/stream_executor/cuda/cuda_driver.cc:271] failed call to cuInit: CUDA_ERROR_NO_DEVICE: no CUDA-capable device is detected

Set di dati IMDB

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

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

Scarica il set di dati 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 memorizzata nella cache se è già stata scaricata):

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
17473536/17464789 [==============================] - 0s 0us/step

L'argomento num_words=10000 mantiene le prime 10.000 parole più frequenti nei dati di addestramento. Le parole rare vengono scartate per mantenere gestibili le dimensioni 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 revisione. Poiché gli input a una rete neurale devono essere della stessa lunghezza, dovremo risolverlo in seguito.

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

Riconvertire i numeri 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 la mappatura da intero a 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
1654784/1641221 [==============================] - 0s 0us/step

Ora siamo in grado di utilizzare la decode_review funzione per visualizzare il testo per la prima valutazione:

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 ulteriormente, creiamo prima una directory per memorizzare gli artefatti creati da questo tutorial.

mkdir -p /tmp/imdb

Crea incorporamenti di esempio

Useremo incastri girevoli preaddestrato per creare incastri nel tf.train.Example formato per ogni campione in ingresso. Vi memorizzare le immersioni risultanti nel TFRecord formato insieme ad una funzione aggiuntiva che rappresenta l'ID di ciascun 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 embedding di esempio, li useremo per costruire un grafico di similarità, cioè, i nodi in questo grafico corrisponderanno ai campioni e gli archi in questo grafico corrisponderanno alla similarità tra coppie di nodi.

Neural Structured Learning fornisce una libreria di creazione di grafici per costruire un grafico basato su incorporamenti di esempio. Esso utilizza somiglianza coseno come misura di similarità di confrontare incastri e bordi di generazione tra loro. Ci consente anche 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 utilizzando il supporto del costruttore grafico per località sensibili hashing (LSH) per accelerare la creazione del grafico. Per informazioni dettagliate sull'uso di supporto LSH del costruttore grafico, vedere la build_graph_from_config documentazione API.

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)

Ciascun 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 le caratteristiche del campione per il nostro problema utilizzando il tf.train.Example formato e li persistiamo nel TFRecord formato. Ogni campione includerà le seguenti tre caratteristiche:

  1. id: l'ID del nodo del campione.
  2. parole: Un elenco Int64 che contiene gli ID di parole.
  3. Etichetta: A Singleton Int64 che identifica la classe di destinazione del riesame.
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é disponiamo delle funzionalità di esempio e del grafico sintetizzato, possiamo generare i dati di training aumentati 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 training finali per la regolarizzazione del grafico. I dati di addestramento risultanti includeranno le caratteristiche del campione originale e le caratteristiche dei loro vicini corrispondenti.

In questo tutorial, consideriamo gli archi 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

Ora siamo pronti per costruire un modello base senza regolarizzazione del grafico. Per costruire questo modello, possiamo utilizzare gli incorporamenti che sono stati utilizzati nella costruzione del grafico, oppure possiamo apprendere nuovi incorporamenti insieme al compito di classificazione. Ai fini di questo notebook, faremo quest'ultimo.

Variabili globali

NBR_FEATURE_PREFIX = 'NL_nbr_'
NBR_WEIGHT_SUFFIX = '_weight'

Iperparametri

Useremo un'istanza di HParams per inclue vari iperparametri e costanti utilizzate per la formazione e la valutazione. Descriviamo brevemente ciascuno di essi di seguito:

  • num_classes: Ci sono 2 classi - positivo e negativo.

  • max_seq_length: Questo è il numero massimo di parole considerate da ogni revisione di film in questo esempio.

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

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

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

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

  • num_fc_units: Il numero di unità nello strato completamente connessi della rete neurale.

  • train_epochs: il numero di epoche di formazione.

  • dimensione del lotto utilizzato per la formazione e la valutazione: batch_size.

  • eval_steps: il numero di lotti di processo prima ritenendo di valutazione è completo. Se è impostato su None , tutte le istanze nel set di prova vengono valutati.

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

Preparare i dati

Le revisioni, le matrici di numeri interi, devono essere convertite in tensori prima di essere immesse nella rete neurale. Questa conversione può essere eseguita in un paio di modi:

  • Convertire gli array in vettori di 0 s e 1 s indicano parola occorrenza, simile ad una codifica di un caldo. Ad esempio, la sequenza [3, 5] diventerebbe 10000 vettore dimensionale che è tutti zero eccetto per gli indici 3 e 5 , che sono quelli. Quindi, fare questo il primo strato in una rete, un Dense strato in grado di gestire dati floating point vettoriali. Questo approccio è intensivo di memoria, tuttavia, richiedono un num_words * num_reviews matrice dimensioni.

  • In alternativa, possiamo pad le matrici così tutti hanno la stessa lunghezza, crea 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, utilizzeremo il secondo approccio.

Dal momento che le recensioni di film devono avere la stessa lunghezza, useremo il pad_sequence funzione 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 impilando i livelli: ciò richiede due principali decisioni architetturali:

  • Quanti strati usare nel modello?
  • Quante unità nascosti da utilizzare per ogni strato?

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)(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)(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: "model"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 words (InputLayer)          [(None, 256)]             0         
                                                                 
 embedding (Embedding)       (None, 256, 16)           160000    
                                                                 
 bidirectional (Bidirectiona  (None, 128)              41472     
 l)                                                              
                                                                 
 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 sono effettivamente impilati in sequenza per costruire il classificatore:

  1. Il primo strato è un Input strato che prende il vocabolario intero codificato.
  2. Il livello successivo è un Embedding strato, che prende il vocabolario intero codificato e cerca il vettore incorporamento per ogni parola-index. Questi vettori vengono appresi come treni modello. 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. Questa uscita vettore a lunghezza fissa viene convogliata attraverso un (completamente collegato Dense ) strato con 64 unità nascoste.
  5. L'ultimo strato è densamente connesso con un singolo nodo di output. Utilizzando il sigmoid funzione di attivazione, questo valore è un float tra 0 e 1, che rappresenta una probabilità, o livello di confidenza.

Unità nascoste

Il modello di cui sopra ha due strati intermedi o "nascosti" tra ingresso ed uscita, ed escludendo Embedding strato. Il numero di output (unità, nodi o neuroni) è la dimensione dello spazio di rappresentazione per il livello. In altre parole, la quantità di libertà concessa alla rete nell'apprendere una rappresentazione interna.

Se un modello ha più unità nascoste (uno spazio di rappresentazione a più dimensioni) 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 test. Questo si chiama overfitting.

Funzione di perdita e ottimizzatore

Un modello necessita di una funzione di perdita e di un ottimizzatore per l'addestramento. Poiché questo è un problema di classificazione binaria e il modello uscite una probabilità (a strato singolo apparecchio con attivazione sigmoidale), useremo la binary_crossentropy funzione di perdita.

model.compile(
    optimizer='adam',
    loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
    metrics=['accuracy'])

Crea un set di convalida

Durante l'addestramento, vogliamo verificare l'accuratezza del modello su dati che non ha mai visto prima. Creare un set di validazione per la messa a parte una frazione dei dati di allenamento originali. (Perché non utilizzare il set di test ora? Il nostro obiettivo è sviluppare e ottimizzare il nostro modello utilizzando solo i dati di addestramento, quindi utilizzare i dati di test solo una volta per valutare la nostra precisione).

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

Nota che 'train_dataset' è già stato raggruppato e mescolato.

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

Allena il modello

Addestrare il modello in mini-batch. Durante l'addestramento, monitorare 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.7/site-packages/keras/engine/functional.py:559: 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.
  inputs = self._flatten_to_reference_inputs(inputs)
21/21 [==============================] - 22s 889ms/step - loss: 0.6932 - accuracy: 0.5065 - val_loss: 0.6928 - val_accuracy: 0.5004
Epoch 2/10
21/21 [==============================] - 17s 841ms/step - loss: 0.6918 - accuracy: 0.5000 - val_loss: 0.6843 - val_accuracy: 0.4988
Epoch 3/10
21/21 [==============================] - 17s 839ms/step - loss: 0.6677 - accuracy: 0.5069 - val_loss: 0.5976 - val_accuracy: 0.6507
Epoch 4/10
21/21 [==============================] - 17s 836ms/step - loss: 0.5579 - accuracy: 0.6912 - val_loss: 0.4801 - val_accuracy: 0.7974
Epoch 5/10
21/21 [==============================] - 17s 837ms/step - loss: 0.4377 - accuracy: 0.7965 - val_loss: 0.3678 - val_accuracy: 0.8372
Epoch 6/10
21/21 [==============================] - 17s 836ms/step - loss: 0.3526 - accuracy: 0.8408 - val_loss: 0.4261 - val_accuracy: 0.8283
Epoch 7/10
21/21 [==============================] - 17s 835ms/step - loss: 0.4090 - accuracy: 0.8273 - val_loss: 0.3961 - val_accuracy: 0.8346
Epoch 8/10
21/21 [==============================] - 17s 834ms/step - loss: 0.3105 - accuracy: 0.8842 - val_loss: 0.2976 - val_accuracy: 0.8813
Epoch 9/10
21/21 [==============================] - 17s 834ms/step - loss: 0.3335 - accuracy: 0.8673 - val_loss: 0.3373 - val_accuracy: 0.8537
Epoch 10/10
21/21 [==============================] - 17s 837ms/step - loss: 0.3104 - accuracy: 0.8765 - val_loss: 0.2804 - val_accuracy: 0.8902

Valuta il modello

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

results = model.evaluate(test_dataset, steps=HPARAMS.eval_steps)
print(results)
196/196 [==============================] - 16s 76ms/step - loss: 0.3695 - accuracy: 0.8389
[0.3694916367530823, 0.838919997215271]

Crea un grafico di accuratezza/perdita nel tempo

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

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 la perdita di addestramento e convalida per il confronto, nonché l'accuratezza di addestramento e 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

Si noti la perdita di formazione diminuisce con ogni epoca e la formazione di precisione 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

Ora siamo pronti per provare la regolarizzazione del grafico utilizzando il modello base che abbiamo costruito sopra. Useremo il GraphRegularization classe wrapper fornito dal framework Learning Neural Strutturato per avvolgere il modello base (bi-LSTM) per includere grafico regolarizzazione. Il resto dei passaggi per l'addestramento e la valutazione del modello regolarizzato dal grafico è simile a quello del modello base.

Crea un modello grafico-regolato

Per valutare il vantaggio incrementale della regolarizzazione del grafico, creeremo una nuova istanza del modello di base. Questo perché model è già stato allenato per un paio di iterazioni, e il riutilizzo di questo modello addestrato per creare un modello grafico-regolarizzato non sarà un confronto equo per 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=tf.keras.losses.BinaryCrossentropy(from_logits=True),
    metrics=['accuracy'])

Allena 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.7/site-packages/tensorflow/python/framework/indexed_slices.py:446: UserWarning: Converting sparse IndexedSlices(IndexedSlices(indices=Tensor("gradient_tape/GraphRegularization/graph_loss/Reshape_1:0", shape=(None,), dtype=int32), values=Tensor("gradient_tape/GraphRegularization/graph_loss/Reshape:0", shape=(None, 1), dtype=float32), dense_shape=Tensor("gradient_tape/GraphRegularization/graph_loss/Cast:0", shape=(2,), dtype=int32))) to a dense Tensor of unknown shape. This may consume a large amount of memory.
  "shape. This may consume a large amount of memory." % value)
21/21 [==============================] - 28s 1s/step - loss: 0.6928 - accuracy: 0.5131 - scaled_graph_loss: 4.3840e-05 - val_loss: 0.6923 - val_accuracy: 0.4997
Epoch 2/10
21/21 [==============================] - 19s 939ms/step - loss: 0.6852 - accuracy: 0.5158 - scaled_graph_loss: 0.0021 - val_loss: 0.6818 - val_accuracy: 0.4996
Epoch 3/10
21/21 [==============================] - 19s 939ms/step - loss: 0.6698 - accuracy: 0.5123 - scaled_graph_loss: 0.0021 - val_loss: 0.6534 - val_accuracy: 0.5001
Epoch 4/10
21/21 [==============================] - 20s 959ms/step - loss: 0.6194 - accuracy: 0.6285 - scaled_graph_loss: 0.0284 - val_loss: 0.5297 - val_accuracy: 0.7955
Epoch 5/10
21/21 [==============================] - 19s 937ms/step - loss: 0.5805 - accuracy: 0.7346 - scaled_graph_loss: 0.0545 - val_loss: 0.5601 - val_accuracy: 0.6349
Epoch 6/10
21/21 [==============================] - 19s 934ms/step - loss: 0.5509 - accuracy: 0.7662 - scaled_graph_loss: 0.0654 - val_loss: 0.4899 - val_accuracy: 0.7538
Epoch 7/10
21/21 [==============================] - 19s 937ms/step - loss: 0.5326 - accuracy: 0.7877 - scaled_graph_loss: 0.0701 - val_loss: 0.4395 - val_accuracy: 0.7923
Epoch 8/10
21/21 [==============================] - 20s 940ms/step - loss: 0.5157 - accuracy: 0.8258 - scaled_graph_loss: 0.0811 - val_loss: 0.4585 - val_accuracy: 0.7909
Epoch 9/10
21/21 [==============================] - 19s 937ms/step - loss: 0.5063 - accuracy: 0.8388 - scaled_graph_loss: 0.0868 - val_loss: 0.4272 - val_accuracy: 0.8433
Epoch 10/10
21/21 [==============================] - 19s 934ms/step - loss: 0.5053 - accuracy: 0.8438 - scaled_graph_loss: 0.0858 - val_loss: 0.4485 - val_accuracy: 0.7680

Valuta il modello

graph_reg_results = graph_reg_model.evaluate(test_dataset, steps=HPARAMS.eval_steps)
print(graph_reg_results)
196/196 [==============================] - 16s 76ms/step - loss: 0.4890 - accuracy: 0.7246
[0.4889770448207855, 0.7246000170707703]

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, accuratezza di addestramento, perdita del grafico di addestramento, perdita di convalida e accuratezza di convalida. Possiamo tracciarli tutti insieme per il confronto. Si noti 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 del grafico nel contesto di questo tutorial, può essere davvero potente quando la quantità di dati di addestramento è piccola. La mancanza di dati di formazione è 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 formazione campioni al numero totale di campioni, che comprende la formazione, la convalida, e campioni di prova. In questo taccuino, abbiamo utilizzato un rapporto di supervisione di 0,05 (ovvero il 5% dei dati etichettati) per addestrare sia il modello base che il modello regolarizzato dal grafico. Illustriamo l'impatto del rapporto di supervisione sull'accuratezza 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 supervisione, diminuisce anche l'accuratezza del modello. Questo è vero sia per il modello base che per il modello regolarizzato a grafo, indipendentemente dall'architettura del modello utilizzata. Tuttavia, si noti che il modello regolato dal grafico ha prestazioni migliori rispetto al modello base per entrambe le architetture. In particolare, per il modello Bi-LSTM, quando il rapporto di supervisione è 0,01, la precisione del modello grafico-regolarizzata è ~ 20% superiore a quella del modello di base. Ciò è dovuto principalmente all'apprendimento semi-supervisionato per il modello regolato dal grafico, in cui viene utilizzata la somiglianza strutturale tra i campioni di addestramento oltre ai campioni di addestramento stessi.

Conclusione

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