Regularisasi grafik untuk klasifikasi sentimen menggunakan grafik yang disintesis

Lihat di TensorFlow.org Jalankan di Google Colab Lihat sumber di GitHub

Gambaran

Buku catatan ini mengklasifikasikan ulasan film sebagai positif atau negatif menggunakan teks ulasan. Ini adalah contoh klasifikasi biner , jenis masalah pembelajaran mesin yang penting dan dapat diterapkan secara luas.

Kami akan mendemonstrasikan penggunaan regularisasi grafik di notebook ini dengan membuat grafik dari input yang diberikan. Resep umum untuk membuat model yang diatur grafik menggunakan framework Neural Structured Learning (NSL) jika input tidak berisi grafik eksplisit adalah sebagai berikut:

  1. Buat embeddings untuk setiap sampel teks di input. Ini dapat dilakukan dengan menggunakan model terlatih seperti word2vec , Swivel , BERT dll.
  2. Buat grafik berdasarkan embeddings ini dengan menggunakan metrik kemiripan seperti jarak 'L2', jarak 'cosinus', dll. Node dalam grafik sesuai dengan sampel dan tepi di grafik sesuai dengan kesamaan antara pasangan sampel.
  3. Buat data pelatihan dari grafik yang disintesis di atas dan contoh fitur. Data pelatihan yang dihasilkan akan berisi fitur tetangga selain fitur node asli.
  4. Buat jaringan saraf sebagai model dasar menggunakan API sekuensial, fungsional, atau subkelas Keras.
  5. Bungkus model dasar dengan kelas pembungkus GraphRegularization, yang disediakan oleh kerangka kerja NSL, untuk membuat model Keras grafik. Model baru ini akan menyertakan kerugian regularisasi grafik sebagai istilah regularisasi dalam tujuan pelatihannya.
  6. Latih dan evaluasi grafik model Keras.

Persyaratan

  1. Instal paket Pembelajaran Terstruktur Neural.
  2. Pasang tensorflow-hub.
pip install --quiet neural-structured-learning
pip install --quiet tensorflow-hub

Dependensi dan impor

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

Dataset IMDB

Kumpulan data IMDB berisi teks 50.000 ulasan film dari Internet Movie Database . Ini dibagi menjadi 25.000 ulasan untuk pelatihan dan 25.000 ulasan untuk pengujian. Set pelatihan dan pengujian seimbang , artinya berisi ulasan positif dan negatif dalam jumlah yang sama.

Dalam tutorial ini, kita akan menggunakan dataset IMDB versi praproses.

Unduh set data IMDB yang telah diproses sebelumnya

Dataset IMDB dikemas dengan TensorFlow. Ini telah diproses sebelumnya sehingga review (urutan kata) telah diubah menjadi urutan bilangan bulat, di mana setiap bilangan bulat mewakili kata tertentu dalam kamus.

Kode berikut mengunduh set data IMDB (atau menggunakan salinan cache jika sudah diunduh):

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

Argumen num_words=10000 menyimpan num_words=10000 kata teratas yang paling sering muncul dalam data pelatihan. Kata-kata langka dibuang untuk menjaga ukuran kosakata tetap terkelola.

Jelajahi datanya

Mari luangkan waktu sejenak untuk memahami format datanya. Dataset tersebut telah diproses sebelumnya: setiap contoh adalah larik bilangan bulat yang mewakili kata-kata dalam ulasan film. Setiap label adalah nilai bilangan bulat dari 0 atau 1, di mana 0 adalah ulasan negatif, dan 1 adalah ulasan 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

Teks ulasan telah diubah menjadi bilangan bulat, di mana setiap bilangan bulat mewakili kata tertentu dalam kamus. Berikut tampilan ulasan pertama:

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]

Ulasan film mungkin berbeda panjangnya. Kode di bawah ini menunjukkan jumlah kata dalam ulasan pertama dan kedua. Karena masukan ke jaringan neural harus memiliki panjang yang sama, kita perlu menyelesaikannya nanti.

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

Ubah bilangan bulat kembali menjadi kata-kata

Mungkin berguna untuk mengetahui cara mengubah bilangan bulat kembali ke teks yang sesuai. Di sini, kita akan membuat fungsi pembantu untuk menanyakan objek kamus yang berisi bilangan bulat ke pemetaan string:

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

Sekarang kita dapat menggunakan fungsi decode_review untuk menampilkan teks untuk ulasan pertama:

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"

Konstruksi grafik

Konstruksi grafik melibatkan pembuatan embeddings untuk sampel teks dan kemudian menggunakan fungsi kesamaan untuk membandingkan embeddings.

Sebelum melangkah lebih jauh, pertama kita membuat direktori untuk menyimpan artefak yang dibuat oleh tutorial ini.

mkdir -p /tmp/imdb

Buat contoh embeddings

Kita akan menggunakan embeddings Putar yang telah tf.train.Example sebelumnya untuk membuat embeddings dalam format tf.train.Example untuk setiap sampel dalam masukan. Kami akan menyimpan embeddings yang dihasilkan dalam format TFRecord bersama dengan fitur tambahan yang mewakili ID dari setiap sampel. Ini penting dan akan memungkinkan kami mencocokkan sampel embeddings dengan node yang sesuai di grafik nanti.

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

Buat grafik

Sekarang kita memiliki sampel embeddings, kita akan menggunakannya untuk membuat grafik kesamaan, yaitu node dalam grafik ini akan sesuai dengan sampel dan edge pada grafik ini akan sesuai dengan kesamaan antara pasangan node.

Pembelajaran Terstruktur Neural menyediakan pustaka pembuatan grafik untuk membuat grafik berdasarkan sampel embeddings. Ini menggunakan kesamaan kosinus sebagai ukuran kesamaan untuk membandingkan embeddings dan membangun tepi di antara keduanya. Ini juga memungkinkan kita untuk menentukan ambang kesamaan, yang dapat digunakan untuk membuang tepi yang berbeda dari grafik akhir. Dalam contoh ini, menggunakan 0,99 sebagai ambang kesamaan dan 12345 sebagai benih acak, kita berakhir dengan grafik yang memiliki 429,415 tepi dua arah. Di sini kami menggunakan dukungan pembuat grafik untuk hashing sensitif lokal (LSH) untuk mempercepat pembuatan grafik. Untuk detail tentang menggunakan dukungan LSH pembuat grafik, lihat dokumentasi 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)

Setiap tepi dua arah diwakili oleh dua tepi terarah dalam file TSV keluaran, sehingga file tersebut berisi total 429.415 * 2 = 858.830 baris:

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

Fitur sampel

Kami membuat fitur sampel untuk masalah kami menggunakan format tf.train.Example dan TFRecord format TFRecord . Setiap sampel akan menyertakan tiga fitur berikut:

  1. id : ID node sampel.
  2. kata-kata : Daftar int64 yang berisi ID kata.
  3. label : Sebuah int64 tunggal yang mengidentifikasi kelas target tinjauan.
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

Tambahkan data pelatihan dengan tetangga grafik

Karena kami memiliki fitur sampel dan grafik yang disintesis, kami dapat menghasilkan data pelatihan yang ditambah untuk Pembelajaran Terstruktur Neural. Kerangka kerja NSL menyediakan pustaka untuk menggabungkan grafik dan contoh fitur untuk menghasilkan data pelatihan akhir untuk regularisasi grafik. Data pelatihan yang dihasilkan akan mencakup fitur sampel asli serta fitur tetangganya yang sesuai.

Dalam tutorial ini, kami mempertimbangkan tepi yang tidak diarahkan dan menggunakan maksimal 3 tetangga per sampel untuk menambah data pelatihan dengan tetangga grafik.

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)

Model dasar

Kami sekarang siap untuk membangun model dasar tanpa regularisasi grafik. Untuk membangun model ini, kita bisa menggunakan embeddings yang digunakan untuk membuat grafik, atau kita bisa mempelajari embeddings baru bersama-sama dengan tugas klasifikasi. Untuk tujuan notebook ini, kami akan melakukan yang terakhir.

Variabel global

NBR_FEATURE_PREFIX = 'NL_nbr_'
NBR_WEIGHT_SUFFIX = '_weight'

Hyperparameter

Kami akan menggunakan contoh HParams untuk HParams berbagai hyperparameter dan konstanta yang digunakan untuk pelatihan dan evaluasi. Kami menjelaskan secara singkat masing-masing di bawah ini:

  • num_classes : Ada 2 kelas - positif dan negatif .

  • max_seq_length : Ini adalah jumlah kata maksimum yang dipertimbangkan dari setiap ulasan film dalam contoh ini.

  • vocab_size : Ini adalah ukuran kosakata yang dipertimbangkan untuk contoh ini.

  • distance_type : Ini adalah metrik jarak yang digunakan untuk mengatur sampel dengan tetangganya.

  • graph_regularization_multiplier : Ini mengontrol bobot relatif dari istilah regularisasi grafik dalam fungsi kerugian keseluruhan.

  • num_neighbours : Jumlah tetangga yang digunakan untuk regularisasi grafik. Nilai ini harus kurang dari atau sama dengan argumen max_nbrs digunakan di atas saat menjalankannsl.tools.pack_nbrs .

  • num_fc_units : Jumlah unit di lapisan jaringan neural yang terhubung sepenuhnya.

  • train_epochs : Jumlah periode pelatihan.

  • batch_size : Ukuran batch yang digunakan untuk pelatihan dan evaluasi.

  • eval_steps : Jumlah batch yang akan diproses sebelum menganggap evaluasi selesai. Jika disetel ke None , semua instance dalam set pengujian dievaluasi.

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

Siapkan datanya

Review — array bilangan bulat — harus diubah menjadi tensor sebelum dimasukkan ke jaringan neural. Konversi ini dapat dilakukan dengan beberapa cara:

  • Ubah array menjadi vektor 0 s dan 1 s yang menunjukkan kemunculan kata, mirip dengan pengkodean one-hot. Misalnya, deret [3, 5] akan menjadi vektor berdimensi 10000 yang semuanya nol kecuali untuk indeks 3 dan 5 , yang merupakan satu. Kemudian, jadikan ini lapisan pertama dalam jaringan kita — lapisan Dense — yang dapat menangani data vektor titik mengambang. Namun, pendekatan ini membutuhkan banyak memori, membutuhkan matriks ukuran num_words * num_reviews .

  • Sebagai alternatif, kita dapat mengisi array sehingga semuanya memiliki panjang yang sama, lalu membuat tensor integer dengan bentuk max_length * num_reviews . Kita dapat menggunakan lapisan embedding yang mampu menangani bentuk ini sebagai lapisan pertama di jaringan kita.

Dalam tutorial ini, kami akan menggunakan pendekatan kedua.

Karena review film harus memiliki panjang yang sama, kita akan menggunakan fungsi pad_sequence ditentukan di bawah ini untuk menstandarkan panjangnya.

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

Bangun modelnya

Jaringan neural dibuat dengan menumpuk lapisan — ini membutuhkan dua keputusan arsitektur utama:

  • Berapa banyak lapisan yang digunakan dalam model?
  • Berapa banyak unit tersembunyi yang digunakan untuk setiap lapisan?

Dalam contoh ini, data masukan terdiri dari larik indeks kata. Label yang akan diprediksi adalah 0 atau 1.

Kami akan menggunakan LSTM dua arah sebagai model dasar kami dalam tutorial ini.

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

Lapisan secara efektif ditumpuk secara berurutan untuk membangun pengklasifikasi:

  1. Lapisan pertama adalah lapisan Input yang menggunakan kosakata yang dikodekan dengan bilangan bulat.
  2. Lapisan berikutnya adalah lapisan Embedding , yang mengambil kosakata berenkode integer dan mencari vektor embedding untuk setiap indeks kata. Vektor-vektor ini dipelajari saat model berlatih. Vektor menambahkan dimensi ke larik keluaran. Dimensi yang dihasilkan adalah: (batch, sequence, embedding) .
  3. Selanjutnya, lapisan LSTM dua arah mengembalikan vektor keluaran dengan panjang tetap untuk setiap contoh.
  4. Vektor keluaran dengan panjang tetap ini disalurkan melalui lapisan yang sepenuhnya terhubung ( Dense ) dengan 64 unit tersembunyi.
  5. Lapisan terakhir terhubung secara padat dengan satu simpul keluaran. Menggunakan fungsi aktivasi sigmoid , nilai ini adalah float antara 0 dan 1, yang mewakili probabilitas, atau tingkat kepercayaan.

Unit tersembunyi

Model di atas memiliki dua lapisan perantara atau "tersembunyi", antara masukan dan keluaran, dan tidak termasuk lapisan Embedding . Jumlah keluaran (unit, node, atau neuron) adalah dimensi dari ruang representasi untuk lapisan tersebut. Dengan kata lain, jumlah kebebasan yang diperbolehkan jaringan saat mempelajari representasi internal.

Jika model memiliki lebih banyak unit tersembunyi (ruang representasi berdimensi lebih tinggi), dan / atau lebih banyak lapisan, jaringan dapat mempelajari representasi yang lebih kompleks. Namun, hal itu membuat jaringan lebih mahal secara komputasi dan dapat menyebabkan pembelajaran pola yang tidak diinginkan — pola yang meningkatkan performa pada data pelatihan tetapi tidak pada data pengujian. Ini disebut overfitting .

Fungsi kerugian dan pengoptimal

Model membutuhkan fungsi kerugian dan pengoptimal untuk pelatihan. Karena ini adalah masalah klasifikasi biner dan model mengeluarkan probabilitas (lapisan unit tunggal dengan aktivasi sigmoid), kita akan menggunakan fungsi kerugian binary_crossentropy .

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

Buat set validasi

Saat melatih, kami ingin memeriksa keakuratan model pada data yang belum pernah dilihat sebelumnya. Buat set validasi dengan memisahkan sebagian kecil dari data pelatihan asli. (Mengapa tidak menggunakan set pengujian sekarang? Tujuan kami adalah mengembangkan dan menyesuaikan model kami hanya dengan menggunakan data pelatihan, kemudian menggunakan data pengujian sekali saja untuk mengevaluasi keakuratan kami).

Dalam tutorial ini, kami mengambil sekitar 10% dari sampel pelatihan awal (10% dari 25000) sebagai data berlabel untuk pelatihan dan sisanya sebagai data validasi. Karena pemisahan latihan / pengujian awal adalah 50/50 (masing-masing 25.000 sampel), pemisahan latihan / validasi / pengujian efektif yang sekarang kami miliki adalah 5/45/50.

Perhatikan bahwa 'train_dataset' telah di-batch dan diacak.

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

Latih modelnya

Latih model dalam kelompok mini. Saat melatih, pantau kehilangan dan akurasi model pada set validasi:

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

Evaluasi modelnya

Sekarang, mari kita lihat performa model tersebut. Dua nilai akan dikembalikan. Loss (angka yang mewakili kesalahan kita, nilai yang lebih rendah lebih baik), dan akurasi.

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]

Buat grafik akurasi / kerugian dari waktu ke waktu

model.fit() mengembalikan objek History yang berisi kamus dengan semua yang terjadi selama pelatihan:

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

Ada empat entri: satu untuk setiap metrik yang dipantau selama pelatihan dan validasi. Kita dapat menggunakannya untuk merencanakan kerugian pelatihan dan validasi untuk perbandingan, serta keakuratan pelatihan dan validasi:

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

Perhatikan kerugian pelatihan berkurang dengan setiap epoch dan akurasi pelatihan meningkat dengan setiap epoch. Hal ini diharapkan saat menggunakan pengoptimalan penurunan gradien — ini harus meminimalkan kuantitas yang diinginkan pada setiap iterasi.

Regularisasi grafik

Kami sekarang siap untuk mencoba regularisasi grafik menggunakan model dasar yang kami buat di atas. Kami akan menggunakan kelas pembungkus GraphRegularization disediakan oleh framework Pembelajaran Terstruktur Neural untuk membungkus model dasar (bi-LSTM) untuk menyertakan regularisasi grafik. Langkah-langkah selanjutnya untuk melatih dan mengevaluasi model yang diatur grafiknya serupa dengan yang ada pada model dasar.

Buat model yang diatur grafiknya

Untuk menilai manfaat tambahan dari regularisasi grafik, kita akan membuat contoh model dasar baru. Ini karena model telah dilatih untuk beberapa iterasi, dan menggunakan kembali model yang dilatih ini untuk membuat model yang diatur grafik tidak akan menjadi perbandingan yang adil untuk 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'])

Latih modelnya

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

Evaluasi modelnya

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]

Buat grafik akurasi / kerugian dari waktu ke waktu

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

Ada total lima entri dalam kamus: kerugian pelatihan, akurasi pelatihan, kerugian grafik pelatihan, kerugian validasi, dan akurasi validasi. Kita bisa memplot semuanya untuk perbandingan. Perhatikan bahwa kehilangan grafik hanya dihitung selama pelatihan.

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

Kekuatan pembelajaran semi-supervised

Pembelajaran semi-supervisi dan lebih khusus lagi, regularisasi grafik dalam konteks tutorial ini, bisa sangat berguna jika jumlah data pelatihan kecil. Kurangnya data pelatihan dikompensasikan dengan memanfaatkan kesamaan di antara sampel pelatihan, yang tidak mungkin dilakukan dalam pembelajaran tradisional yang diawasi.

Kami mendefinisikan rasio supervisi sebagai rasio sampel pelatihan dengan jumlah sampel yang meliputi sampel pelatihan, validasi, dan uji. Dalam buku catatan ini, kami telah menggunakan rasio pengawasan 0,05 (yaitu, 5% dari data berlabel) untuk melatih model dasar serta model yang diatur grafiknya. Kami mengilustrasikan dampak rasio pengawasan pada akurasi model dalam sel di bawah ini.

# 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

Dapat diamati bahwa ketika rasio superivision menurun, akurasi model juga menurun. Hal ini berlaku untuk model dasar dan model yang diatur grafiknya, terlepas dari arsitektur model yang digunakan. Namun, perhatikan bahwa model yang diatur grafiknya bekerja lebih baik daripada model dasar untuk kedua arsitektur tersebut. Khususnya, untuk model Bi-LSTM, ketika rasio supervisi 0,01, akurasi model yang diatur grafik ~ 20% lebih tinggi daripada model dasar. Ini terutama karena pembelajaran semi-supervised untuk model grafik-regularisasi, di mana kesamaan struktural di antara sampel pelatihan digunakan selain sampel pelatihan itu sendiri.

Kesimpulan

Kami telah mendemonstrasikan penggunaan regularisasi grafik menggunakan framework Neural Structured Learning (NSL) meskipun input tidak berisi grafik eksplisit. Kami mempertimbangkan tugas klasifikasi sentimen ulasan film IMDB yang kami sintesiskan grafik kemiripannya berdasarkan embeddings ulasan. Kami mendorong pengguna untuk bereksperimen lebih lanjut dengan memvariasikan hyperparameter, jumlah pengawasan, dan dengan menggunakan arsitektur model yang berbeda.