Эта страница была переведа с помощью Cloud Translation API.
Switch to English

Регуляризация графа для классификации настроений с использованием синтезированных графов

Посмотреть на TensorFlow.org Запустить в Google Colab Посмотреть источник на GitHub

обзор

Этот блокнот классифицирует рецензии на фильмы как положительные или отрицательные, используя текст рецензии. Это пример бинарной классификации, важного и широко применимого вида проблемы машинного обучения.

Мы продемонстрируем использование регуляризации графа в этой записной книжке, построив график из заданного ввода. Общий рецепт построения регуляризованной по графу модели с использованием структуры Neural Structured Learning (NSL), когда входные данные не содержат явный граф, выглядит следующим образом:

  1. Создайте вложения для каждого образца текста на входе. Это можно сделать с помощью предварительно обученных моделей, таких как word2vec , Swivel , BERT и т. Д.
  2. Постройте график на основе этих вложений, используя метрику сходства, такую ​​как расстояние «L2», расстояние «косинус» и т. Д. Узлы на графике соответствуют выборкам, а ребра на графике соответствуют сходству между парами выборок.
  3. Сгенерируйте обучающие данные из приведенного выше синтезированного графика и примеров. Полученные данные обучения будут содержать соседние элементы в дополнение к исходным элементам узла.
  4. Создайте нейронную сеть в качестве базовой модели, используя последовательный, функциональный API или API подкласса Keras.
  5. Оберните базовую модель классом-оболочкой GraphRegularization, предоставляемым инфраструктурой NSL, для создания новой модели Keras графа. Эта новая модель будет включать потерю регуляризации графа как термин регуляризации в своей задаче обучения.
  6. Обучите и оцените график модели Кераса.

Требования

  1. Установите пакет нейронного структурированного обучения.
  2. Установите тензор потока-концентратор.
pip install --quiet neural-structured-learning
pip install --quiet tensorflow-hub

Зависимости и импорт

 import matplotlib.pyplot as plt
import numpy as np

import neural_structured_learning as nsl

import tensorflow as tf
import tensorflow_hub as hub

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

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

Набор данных IMDB

Набор данных IMDB содержит текст 50 000 обзоров фильмов из базы данных фильмов в Интернете . Они разделены на 25 000 обзоров для обучения и 25 000 обзоров для тестирования. Наборы для обучения и тестирования сбалансированы , то есть содержат одинаковое количество положительных и отрицательных отзывов.

В этом руководстве мы будем использовать предварительно обработанную версию набора данных IMDB.

Загрузить предварительно обработанный набор данных IMDB

Набор данных IMDB поставляется в комплекте с TensorFlow. Это уже было предварительно обработано так, что обзоры (последовательности слов) были преобразованы в последовательности целых чисел, где каждое целое число представляет определенное слово в словаре.

Следующий код загружает набор данных IMDB (или использует кэшированную копию, если она уже была загружена):

 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

Аргумент num_words=10000 хранит 10000 наиболее часто встречающихся слов в обучающих данных. Редкие слова отбрасываются, чтобы сохранить размер словарного запаса управляемым.

Изучите данные

Давайте уделим немного времени, чтобы понять формат данных. Набор данных поставляется предварительно обработанным: каждый пример представляет собой массив целых чисел, представляющих слова обзора фильма. Каждая метка представляет собой целочисленное значение 0 или 1, где 0 - отрицательный отзыв, а 1 - положительный отзыв.

 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

Текст обзоров был преобразован в целые числа, где каждое целое число представляет определенное слово в словаре. Вот как выглядит первый обзор:

 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]

Обзоры фильмов могут быть разной длины. Код ниже показывает количество слов в первом и втором обзорах. Поскольку входы в нейронную сеть должны быть одинаковой длины, мы должны решить эту проблему позже.

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

Преобразовать целые числа обратно в слова

Может быть полезно знать, как преобразовать целые числа обратно в соответствующий текст. Здесь мы создадим вспомогательную функцию для запроса объекта словаря, который содержит отображение целого числа в строку:

 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

Теперь мы можем использовать функцию decode_review для отображения текста для первого обзора:

 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"

Построение графика

Построение графа включает создание вложений для образцов текста, а затем использование функции подобия для сравнения вложений.

Прежде чем продолжить, мы сначала создадим каталог для хранения артефактов, созданных в этом руководстве.

mkdir -p /tmp/imdb

Создать образец вложения

Мы будем использовать предварительно обученные вложения Swivel для создания вложений в формате tf.train.Example для каждого образца во входных данных. Мы будем хранить полученные вложения в формате TFRecord вместе с дополнительной функцией, которая представляет идентификатор каждого образца. Это важно и позволит нам позже сопоставить примеры вложений с соответствующими узлами в графе.

 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

Построить график

Теперь, когда у нас есть выборочные вложения, мы будем использовать их для построения графа подобия, то есть узлы в этом графе будут соответствовать выборкам, а ребра в этом графе будут соответствовать сходству между парами узлов.

Neural Structured Learning предоставляет библиотеку построения графиков для построения графиков на основе примеров вложений. Он использует косинусное сходство в качестве меры сходства для сравнения вложений и построения ребер между ними. Это также позволяет нам указать порог сходства, который можно использовать для отбрасывания разнородных ребер из конечного графика. В этом примере, используя 0,99 в качестве порога сходства, мы получаем график с 445,327 двунаправленными ребрами.

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

Пример функций

Мы создаем образцы функций для нашей проблемы, используя формат tf.train.Example , и сохраняем их в формате TFRecord . Каждый образец будет включать следующие три функции:

  1. id : идентификатор узла образца.
  2. words : список int64, содержащий идентификаторы слов.
  3. label : одиночный int64, идентифицирующий целевой класс обзора.
 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

Дополнить данные тренировки с графом соседей

Поскольку у нас есть примеры функций и синтезированный график, мы можем сгенерировать расширенные обучающие данные для нейронного структурированного обучения. Инфраструктура NSL предоставляет библиотеку для объединения графика и примеров функций для получения окончательных обучающих данных для регуляризации графа. Полученные данные обучения будут включать в себя оригинальные образцы функций, а также характеристики их соответствующих соседей.

В этом руководстве мы рассмотрим ненаправленные ребра и используем максимум 3 соседа на выборку для дополнения обучающих данных соседями по графу.

 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)
 

Базовая модель

Теперь мы готовы построить базовую модель без регуляризации графа. Чтобы построить эту модель, мы можем либо использовать вложения, которые использовались при построении графа, либо мы можем изучить новые вложения вместе с задачей классификации. Для этой записной книжки мы сделаем последнее.

Глобальные переменные

 NBR_FEATURE_PREFIX = 'NL_nbr_'
NBR_WEIGHT_SUFFIX = '_weight'
 

гиперпараметры

Мы будем использовать экземпляр HParams для включения различных гиперпараметров и констант, используемых для обучения и оценки. Мы кратко опишем каждый из них ниже:

  • num_classes : есть 2 класса - положительный и отрицательный .

  • max_seq_length : это максимальное количество слов, рассматриваемых в каждом обзоре фильма в этом примере.

  • vocab_size : это размер словаря, рассматриваемого в этом примере.

  • distance_type : это метрика расстояния, используемая для регуляризации выборки с ее соседями.

  • graph_regularization_multiplier : управляет относительным весом члена регуляризации графа в общей функции потерь.

  • num_neighbors : количество соседей, используемых для регуляризации графа. Это значение должно быть меньше или равно аргументу max_nbrs использовался выше при вызове nsl.tools.pack_nbrs .

  • num_fc_units : количество единиц в полностью связанном слое нейронной сети.

  • train_epochs : количество тренировочных эпох.

  • batch_size : размер партии, используемый для обучения и оценки.

  • eval_steps : количество пакетов для обработки до того, как оценка будет завершена. Если установлено значение None , все экземпляры в наборе тестов оцениваются.

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

Подготовьте данные

Обзоры - массивы целых чисел - должны быть преобразованы в тензоры перед передачей в нейронную сеть. Это преобразование может быть сделано несколькими способами:

  • Преобразовать массивы в векторы 0 с и 1 с, указывающие на вхождение слова, аналогично кодированию в одно касание. Например, последовательность [3, 5] станет 10000 мерным вектором, который является всеми нулями, кроме индексов 3 и 5 , которые являются единицами. Затем сделайте это первым слоем в нашей сети - Dense слоем, который может обрабатывать векторные данные с плавающей точкой. Однако этот подход требует num_words * num_reviews памяти и требует матрицы размера num_words * num_reviews .

  • В качестве альтернативы, мы можем заполнить массивы, чтобы они все имели одинаковую длину, а затем создать целочисленный тензор формы max_length * num_reviews . Мы можем использовать слой внедрения, способный обрабатывать эту форму, в качестве первого слоя в нашей сети.

В этом уроке мы будем использовать второй подход.

Поскольку обзоры фильмов должны быть одинаковой длины, мы будем использовать функцию pad_sequence определенную ниже, чтобы стандартизировать длины.

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

Построить модель

Нейронная сеть создается стеками слоев - для этого требуется два основных архитектурных решения:

  • Сколько слоев использовать в модели?
  • Сколько скрытых юнитов использовать для каждого слоя?

В этом примере входные данные состоят из массива слов-индексов. Предсказываемые метки: 0 или 1.

Мы будем использовать двунаправленный LSTM в качестве нашей базовой модели в этом уроке.

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

Слои эффективно укладываются последовательно для построения классификатора:

  1. Первый уровень - это Input уровень, который использует словарь, закодированный в целое число.
  2. Следующий уровень - это слой Embedding , который берет словарь с целочисленной кодировкой и ищет вектор внедрения для каждого индекса слова. Эти векторы изучаются как модель поезда. Векторы добавляют измерение к выходному массиву. Полученные размеры: (batch, sequence, embedding) .
  3. Затем двунаправленный слой LSTM возвращает выходной вектор фиксированной длины для каждого примера.
  4. Этот выходной вектор фиксированной длины проходит через полностью связанный ( Dense ) слой с 64 скрытыми единицами.
  5. Последний слой плотно связан с одним выходным узлом. Используя функцию активации sigmoid , это значение представляет собой число от 0 до 1, представляющее вероятность или уровень достоверности.

Скрытые юниты

Вышеупомянутая модель имеет два промежуточных или «скрытых» слоя, между входом и выходом, и исключая слой Embedding . Количество выходов (единиц, узлов или нейронов) - это размерность пространства представления для слоя. Другими словами, степень свободы сети допускается при изучении внутреннего представления.

Если модель имеет больше скрытых единиц (пространство представления более высокого измерения) и / или больше слоев, то сеть может изучать более сложные представления. Однако это делает сеть более дорогой в вычислительном отношении и может привести к изучению нежелательных шаблонов - шаблонов, которые улучшают производительность на данных обучения, но не на тестовых данных. Это называется переоснащением .

Функция потери и оптимизатор

Модель нуждается в функции потерь и оптимизаторе для обучения. Поскольку это проблема двоичной классификации, и модель выдает вероятность ( binary_crossentropy слой с сигмовидной активацией), мы будем использовать binary_crossentropy потерь binary_crossentropy .

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

Создать набор проверки

Во время обучения мы хотим проверить точность модели на данных, которых она раньше не видела. Создайте набор проверки , выделив часть исходных данных обучения. (Почему бы не использовать набор для тестирования сейчас? Наша цель - разработать и настроить нашу модель, используя только данные обучения, а затем использовать данные испытаний только один раз, чтобы оценить нашу точность).

В этом руководстве мы берем примерно 10% начальных обучающих выборок (10% из 25000) в качестве помеченных данных для обучения, а оставшиеся - в качестве проверочных данных. Поскольку первоначальное разделение «поезд / тест» составляло 50/50 (по 25000 образцов каждый), эффективное разделение «поезд / проверка / тестирование», которое мы имеем сейчас, составляет 5/45/50.

Обратите внимание, что «train_dataset» уже был упакован и перемешан.

 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

Тренируй модель

Тренируйте модель в мини-партиях. Во время обучения следите за потерями и точностью модели на проверочном наборе:

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

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

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

Оценить модель

Теперь посмотрим, как работает модель. Два значения будут возвращены. Потеря (число, представляющее нашу ошибку, более низкие значения лучше) и точность.

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

Создать график точности / потери с течением времени

model.fit() возвращает объект History , содержащий словарь со всем, что произошло во время обучения:

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

Существует четыре записи: по одной для каждой отслеживаемой метрики во время обучения и проверки. Мы можем использовать их для составления графика потерь обучения и проверки для сравнения, а также точности обучения и проверки:

 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

Обратите внимание, что потеря тренировки уменьшается с каждой эпохой, а точность тренировки увеличивается с каждой эпохой. Это ожидается при использовании оптимизации градиентного спуска - она ​​должна минимизировать желаемое количество на каждой итерации.

Регуляризация графа

Теперь мы готовы попробовать регуляризацию графа с использованием базовой модели, которую мы создали выше. Мы будем использовать GraphRegularization оболочку GraphRegularization предоставляемый платформой Neural Structured Learning, чтобы обернуть базовую (bi-LSTM) модель для включения регуляризации графа. Остальные этапы обучения и оценки регуляризованной по графу модели аналогичны базовой модели.

Создать граф-регуляризованную модель

Чтобы оценить дополнительные преимущества регуляризации графа, мы создадим новый экземпляр базовой модели. Это потому, что model уже была обучена в течение нескольких итераций, и повторное использование этой обученной модели для создания регуляризованной по графу модели не будет справедливым сравнением для 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'])
 

Тренируй модель

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

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

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

Оценить модель

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

Создать график точности / потери с течением времени

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

Всего в словаре пять записей: потеря обучения, точность обучения, потеря графика обучения, потеря проверки и точность проверки. Мы можем построить их все вместе для сравнения. Обратите внимание, что график потерь рассчитывается только во время тренировки.

 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

Сила полу-контролируемого обучения

Обучение под наблюдением и, в частности, регуляризация графиков в контексте этого учебного пособия, может быть действительно мощным, когда объем обучающих данных невелик. Недостаток обучающих данных компенсируется за счет использования сходства между обучающими образцами, что невозможно в традиционном обучении под наблюдением.

Мы определяем отношение наблюдения как отношение обучающих образцов к общему количеству образцов, которое включает обучающие, проверочные и испытательные образцы. В этой записной книжке мы использовали коэффициент наблюдения 0,05 (т. Е. 5% от обозначенных данных) для обучения как базовой модели, так и модели с граф-регуляризацией. Мы иллюстрируем влияние коэффициента наблюдения на точность модели в ячейке ниже.

 # 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

Можно наблюдать, что с уменьшением коэффициента супервизии точность модели также уменьшается. Это верно как для базовой модели, так и для модели с регуляризацией графа, независимо от используемой архитектуры модели. Однако обратите внимание, что регуляризованная графами модель работает лучше, чем базовая модель для обеих архитектур. В частности, для модели Bi-LSTM, когда коэффициент наблюдения равен 0,01, точность регуляризованной по графику модели на ~ 20% выше, чем у базовой модели. Это в первую очередь связано с полууправляемым обучением для регуляризованной по графу модели, где структурное сходство среди обучающих выборок используется в дополнение к самим обучающим выборкам.

Вывод

Мы продемонстрировали использование регуляризации графа с использованием фреймворка Neural Structured Learning (NSL), даже когда входные данные не содержат явного графа. Мы рассмотрели задачу классификации настроений в обзорах фильмов IMDB, для которых мы синтезировали граф сходства, основанный на вложенных обзорах. Мы поощряем пользователей к дальнейшим экспериментам, изменяя гиперпараметры, степень контроля и используя различные архитектуры моделей.