Показаны сообщения с ярлыком машинное обучение. Показать все сообщения
Показаны сообщения с ярлыком машинное обучение. Показать все сообщения

пятница, 21 декабря 2018 г.

TensorFlow: embeddings (встраивания)

В этом посте представлена концепция встраиваний (embeddings), приведен простой пример как обучить встраивание (embedding) в TensorFlow, и объясняется, как просматривать встраивания с помощью TensorBoard Embedding Projector.

Встривание (embedding) - это отображение (mapping) дискретных объектов, таких как слова, на векторы действительных чисел. Например, 300-мерное встраивание для английских слов может включать в себя:

blue:(0.01359, 0.00075997, 0.24608, ..., -0.2524, 1.0048, 0.06259)
blues:(0.01396, 0.11887, -0.48963, ..., 0.033483, -0.10007, 0.1158)
orange:(-0.24776, -0.12359, 0.20986, ..., 0.079717, 0.23865, -0.014213)
oranges:(-0.35609, 0.21854, 0.080944, ..., -0.35413, 0.38511, -0.070976)

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

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

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

blue:  (red, 47.6°), (yellow, 51.9°), (purple, 52.4°)
blues:  (jazz, 53.3°), (folk, 59.1°), (bluegrass, 60.6°)
orange:  (yellow, 53.5°), (colored, 58.0°), (bright, 59.9°)
oranges:  (apples, 45.3°), (lemons, 48.3°), (mangoes, 50.4°)

По этим результатам можно сделать вывод, что яблоки и апельсины более схожи (45.3° различия), чем лимоны и апельсины (48.3° различия).

Встраивания (enbeddings) в TensorFlow

Чтобы создать встраивание слов в TensorFlow, мы сначала разбиваем текст на слова, а затем назначаем целое число каждому слову в словаре. Давайте предположим, что это уже сделано, и что word_ids является вектором этих целых чисел. Например, предложение «I have a cat.». Можно разделить на [«I», «have», «a», «cat», «.»], и тогда соответствующий тензор word_ids будет иметь форму [5]и состоять из 5 целых чисел. Чтобы создать представление этих идентификаторов слов в векторах, нам нужно создать переменную встраивание и использовать tf.nn.embedding_lookup функцию следующим образом:

word_embeddings = tf.get_variable(“word_embeddings”,
    [vocabulary_size, embedding_size])
embedded_word_ids = tf.nn.embedding_lookup(word_embeddings, word_ids)

После этого тензор embedded_word_ids будет иметь форму [5, embedding_size] в нашем примере и содержать встраивания (embeddings) (плотные векторы)(dense vectors) для каждого из 5 слов. В конце обучения word_embeddings будет содержать встраивания для всех слов в словаре.

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

Визуализация встраиваний

TensorBoard включает в себя Embedding Projector, инструмент, который позволяет интерактивно визуализировать встраивания. Этот инструмент может читать встраивания из вашей модели и визуализировать их в двух или трех измерениях.

Embedding Projector имеет три панели:

  • Панель данных (Data panel) в левом верхнем углу, где вы можете выбрать запуск, переменную встраивания и колонки данных (data columns) для задания цвета и меток точек.
  • Панель проекций (Projections panel) в левом нижнем углу, где вы можете выбрать тип проекции.
  • Панель инспектора (Inspector panel) справа, где вы можете искать конкретные точки и увидеть список ближайших соседей.

Проекции

Embedding Projector обеспечивает три способа уменьшения размерности набора данных.

  • t-SNE: нелинейный недетерминированный алгоритм (стохастическое вложение соседей с t-распределением), который пытается сохранить локальное соседство в данных, часто за счет искажения глобальной структуры. Вы можете выбрать, следует ли вычислять двух- или трехмерные проекции.

  • PCA: линейный детерминированный алгоритм (метод главных компонент), который пытается захватить как можно большую часть изменчивости данных в как можно меньшем количестве измерений. PCA имеет тенденцию выделять крупномасштабную структуру данных, но может искажать локальное соседство. Embedding Projector вычисляет 10 главных компонентов, из которых вы можете выбрать два или три для просмотра.

  • Пользовательский (Custom): линейная проекция на горизонтальную и вертикальную оси, которую вы указываете с помощью меток в данных. Вы определяете горизонтальную ось, например, предоставляя текстовые шаблоны для "Левый" и "Правый". Embedding Projector находит все точки, метка которых соответствует "Левый" шаблону и вычисляет центр тяжести этого множества; аналогично для "Правый". Линия, проходящая через эти два центроида определяет горизонтальную ось. Вертикальная ось, аналогично, вычисляется по центроидам для точек, совпадающих с "Вверх" и "Вниз" текстовыми шаблонами.


Исследование (Exploration)

Вы можете исследовать визуально с помощью масштабирования, поворота и панорамирования с помощью нажатия-и-перетаскивания (click-and-drag). Если навести указатель мыши на какую-либо из точек, будут показаны метаданные для этой точки. Вы также можете проверить ближайшие соседние подмножества. Нажатие на точку приводит к тому, что на правой панели отображается список ближайших соседей, а также расстояния до текущей точки. Ближайший сосед точки также выделяется в проекции.

Иногда полезно ограничить вид подмножеством точек и выполнить прогнозы только по этим пунктам. Для этого вы можете выбрать точки несколькими способами:

  • После нажатия на точку также выбираются ее ближайшие соседи.
  • После поиска выбираются точки, соответствующие запросу.
  • Включение выделения, нажатие на точку и перетаскивание определяют выделяемую сферу.

Затем нажмите "Isolate nnn points" кнопку в верхней части Inspector панели на правой стороне. На следующем рисунке показаны 101 точка, выбранные и готовые, чтобы пользователь щелкнул "Isolate 101 points":

Выбор ближайших соседей “важных” в наборе данных встраиваний слов.

Фильтрация с пользовательской проекцией может быть весьма удобной. Ниже мы отфильтровали 100 ближайших соседей “politics” и спроецировали их на “worst” - “best” вектор в виде оси х. Ось у случайна. В результате справа находятся “ideas”, “science”, “perspective”, “journalism”, а слева “crisis”, “violence” и “conflict”.

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


Метаданные

Если вы работаете со встраиванием, возможно, вы захотите прикрепить метки/изображения к точкам данных. Вы можете сделать это, сгенерировав файл метаданных, содержащий метки для каждой точки, и нажав кнопку "Load data" в панели данных Embedding Projector.

Метаданные могут быть метками или изображениями, которые хранятся в отдельном файле. Для меток формат должен быть файлом TSV (символы табуляции показаны красным цветом), чья первая строка содержит заголовки столбцов (выделено жирным шрифтом) и последующие строки содержат значения метаданных. Например:

Word\tFrequency
Airplane\t345
Car\t241
...

Предполагается, что порядок строк в файле метаданных соответствует порядку векторов в переменной встраивания, кроме заголовка. Следовательно, (i+1)-я строка в файле метаданных соответствует i-й строке переменной встраивания. Если файл метаданных TSV содержит только один столбец, мы не ожидаем строку заголовка и предполагаем, что каждая строка является меткой встраивания. Мы включаем это исключение, потому что оно соответствует обычно используемому "vocab file" формату.

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


Embedding мини-FAQ

"Встраивание (embedding)" - это действие или вещь? И то и другое. Люди говорят о встраивании слов в векторное пространство (действие) и о произведении встраиваний слов (вещи). Общим для обоих является понятие встраивания как отображения (mapping) дискретных объектов в векторах. Создание или применение этого отображения (mapping) - это действие, но само отображение (mapping) - это вещь.

Встраивания (embeddings) являются многомерными или низкоразмерными (high-dimensional или low-dimensional)? Это зависит от того с какой стороны расматривать. 300-мерное векторное пространство слов и фраз, например, часто называют низкоразмерным (и плотным) по сравнению с миллионами слов и фраз, которые оно может содержать. Но математически оно многомерное, поскольку показывает много свойств, что резко отличаются от того, что человек интуитивно знает о 2- и 3-мерных пространствах.

Является ли встраивание (embedding) тем же, что и слой встраивания (embedding layer)? Нет. слой встраивания (embedding layer) является частью нейронной сети, но встраивание (embedding) является более общей концепцией.


Читайте также другие статьи в блоге:

TensorFlow: базовая классификация

TensorFlow: классификация текста с отзывами к фильмам

Анатомия нейронных сетей

понедельник, 10 сентября 2018 г.

TensorFlow: сохранение и восстановление моделей

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

  • кодом для создания модели
  • тренировочными весами, или параметрами, для модели

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

Опции

Существуют различные пути для сохранения TensorFlow моделей - в зависимости от API, который использовать. Это руководство использует tf.keras - высокоуровневое API для построения и тренировки моделей в TensorFlow.

Настройка

Получение примерного набора данных

Мы будем использовать MNIST набор данных для тренировки нашей модели и демонстрации сохраненных весов. Чтобы ускорить демонстрацию, используем только первые 1000 примеров:

from future import absolute_import, division, print_function

import os

import tensorflow as tf
from tensorflow import keras

tf.version

'1.9.0'

(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data()

train_labels = train_labels[:1000]
test_labels = test_labels[:1000]

train_images = train_images[:1000].reshape(-1, 28 * 28) / 255.0
test_images = test_images[:1000].reshape(-1, 28 * 28) / 255.0

Downloading data from https://s3.amazonaws.com/img-datasets/mnist.npz
11493376/11490434 [==============================] - 2s 0us/step

Определение модели

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

# Возвращает короткую последовательную модель (sequential model)
def create_model():
  model = tf.keras.models.Sequential([
    keras.layers.Dense(512, activation=tf.nn.relu, input_shape=(784,)),
    keras.layers.Dropout(0.2),
    keras.layers.Dense(10, activation=tf.nn.softmax)
  ])
  
  model.compile(optimizer=tf.keras.optimizers.Adam(), 
                loss=tf.keras.losses.sparse_categorical_crossentropy,
                metrics=['accuracy'])
  
  return model


# Создание инстанса базовой модели
model = create_model()
model.summary()

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 512)               401920    
_________________________________________________________________
dropout (Dropout)            (None, 512)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 10)                5130      
=================================================================
Total params: 407,050
Trainable params: 407,050
Non-trainable params: 0
_________________________________________________________________

Сохранение контрольных точек в ходе тренировки

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

tf.keras.callbacks.ModelCheckpoint - это колбек (callback) (функция обратного вызова), который выполняет эту задачу. Колбек принимает пару аргументов, чтобы сконфигурировать создание контрольных точек.

Использование колбека контрольных точек

Тренируем модель и передаем ей ModelCheckpoint колбек:

checkpoint_path = "training_1/cp.ckpt"
checkpoint_dir = os.path.dirname(checkpoint_path)

# Создаем колбек контрольной точки
cp_callback = tf.keras.callbacks.ModelCheckpoint(checkpoint_path, 
                                                 save_weights_only=True,
                                                 verbose=1)

model = create_model()

model.fit(train_images, train_labels,  epochs = 10, 
          validation_data = (test_images,test_labels),
          callbacks = [cp_callback])  # передаем колбек

Train on 1000 samples, validate on 1000 samples
Epoch 1/10
1000/1000 [==============================] - 0s 342us/step - loss: 1.1603 - acc: 0.6670 - val_loss: 0.6827 - val_acc: 0.7880

Epoch 00001: saving model to training_1/cp.ckpt
Epoch 2/10
1000/1000 [==============================] - 0s 129us/step - loss: 0.4071 - acc: 0.8860 - val_loss: 0.5841 - val_acc: 0.8110

Epoch 00002: saving model to training_1/cp.ckpt
Epoch 3/10
1000/1000 [==============================] - 0s 118us/step - loss: 0.2796 - acc: 0.9350 - val_loss: 0.4610 - val_acc: 0.8520

Epoch 00003: saving model to training_1/cp.ckpt

Epoch 4/10
1000/1000 [==============================] - 0s 121us/step - loss: 0.2025 - acc: 0.9570 - val_loss: 0.4324 - val_acc: 0.8610

Epoch 00004: saving model to training_1/cp.ckpt
Epoch 5/10
1000/1000 [==============================] - 0s 117us/step - loss: 0.1489 - acc: 0.9690 - val_loss: 0.4290 - val_acc: 0.8620

Epoch 00005: saving model to training_1/cp.ckpt
Epoch 6/10
1000/1000 [==============================] - 0s 127us/step - loss: 0.1194 - acc: 0.9780 - val_loss: 0.4143 - val_acc: 0.8700

Epoch 00006: saving model to training_1/cp.ckpt
Epoch 7/10
1000/1000 [==============================] - 0s 118us/step - loss: 0.0845 - acc: 0.9860 - val_loss: 0.4208 - val_acc: 0.8670

Epoch 00007: saving model to training_1/cp.ckpt
Epoch 8/10
1000/1000 [==============================] - 0s 118us/step - loss: 0.0648 - acc: 0.9910 - val_loss: 0.4078 - val_acc: 0.8680

Epoch 00008: saving model to training_1/cp.ckpt
Epoch 9/10
1000/1000 [==============================] - 0s 121us/step - loss: 0.0531 - acc: 0.9970 - val_loss: 0.4184 - val_acc: 0.8670

Epoch 00009: saving model to training_1/cp.ckpt
Epoch 10/10
1000/1000 [==============================] - 0s 121us/step - loss: 0.0391 - acc: 0.9960 - val_loss: 0.4185 - val_acc: 0.8640

Epoch 00010: saving model to training_1/cp.ckpt

Это создает единственную коллекцию файлов контрольных точек TensorFlow, которые обновляются в конце каждой эпохи:

!ls {checkpoint_dir}

checkpoint  cp.ckpt.data-00000-of-00001  cp.ckpt.index

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

Теперь перестроим свежую, нетренированную модель, и оценим ее на тестовом наборе. Нетренированная модель будет выполняться на случайных уровнях (~10% аккуратности):

model = create_model()

loss, acc = model.evaluate(test_images, test_labels)
print("Untrained model, accuracy: {:5.2f}%".format(100*acc))

1000/1000 [==============================] - 0s 105us/step
Untrained model, accuracy:  7.20%

Затем загрузим веса из контрольной точки и переоценим:

model.load_weights(checkpoint_path)
loss,acc = model.evaluate(test_images, test_labels)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))

1000/1000 [==============================] - 0s 24us/step
Restored model, accuracy: 86.40%

Свойства колбека контрольной точки

Колбек предоставляет несколько свойств для задания уникальных имен для результирующих контрольных точек и нормализации частоты контрольных точек.

Натренируем новую модель и сохраним уникально названные контрольные точки каждые 5 эпох:

# включаем эпоху в название файла (используя str.format)
checkpoint_path = "training_2/cp-{epoch:04d}.ckpt"
checkpoint_dir = os.path.dirname(checkpoint_path)

cp_callback = tf.keras.callbacks.ModelCheckpoint(
    checkpoint_path, verbose=1, save_weights_only=True,
    # Сохраняем веса каждые 5 эпох.
    period=5)

model = create_model()
model.fit(train_images, train_labels,
          epochs = 50, callbacks = [cp_callback],
          validation_data = (test_images,test_labels),
          verbose=0)

Epoch 00005: saving model to training_2/cp-0005.ckpt

Epoch 00010: saving model to training_2/cp-0010.ckpt

Epoch 00015: saving model to training_2/cp-0015.ckpt

Epoch 00020: saving model to training_2/cp-0020.ckpt

Epoch 00025: saving model to training_2/cp-0025.ckpt

Epoch 00030: saving model to training_2/cp-0030.ckpt

Epoch 00035: saving model to training_2/cp-0035.ckpt

Epoch 00040: saving model to training_2/cp-0040.ckpt

Epoch 00045: saving model to training_2/cp-0045.ckpt

Epoch 00050: saving model to training_2/cp-0050.ckpt

Теперь посмотрим на результирующие контрольные точки (сортируя по дате изменения):

import pathlib

# Сортируем контрольные точки по времени изменения
checkpoints = pathlib.Path(checkpoint_dir).glob("*.index")
checkpoints = sorted(checkpoints, key=lambda cp:cp.stat().st_mtime)
checkpoints = [cp.with_suffix('') for cp in checkpoints]
latest = str(checkpoints[-1])
checkpoints

[PosixPath('training_2/cp-0030.ckpt'),
 PosixPath('training_2/cp-0035.ckpt'),
 PosixPath('training_2/cp-0040.ckpt'),
 PosixPath('training_2/cp-0045.ckpt'),
 PosixPath('training_2/cp-0050.ckpt')]

Следует отметить, что по умолчанию TensorFlow сохраняет только 5 наиболее недавних контрольных точек.

Для тестирования сбросим модель и загрузим последнюю контрольную точку:

model = create_model()
model.load_weights(latest)
loss, acc = model.evaluate(test_images, test_labels)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))

1000/1000 [==============================] - 0s 82us/step
Restored model, accuracy: 87.80%

В какие файлы сохраняются контрольные точки

Код выше сохраняет веса в коллекцию отформатированных по контрольным точкам файлов, которые содержат только тренированные веса в бинарном формате. Контрольные точки содержат:

  • Один или более осколков, которые содержат веса модели.
  • Индексный файл, который отражает какие веса сохранены в каждом осколке.

Если модель была тренирована только на одной машине, будет присутствовать только один осколок с суффиксом: .data-00000-of-00001

Ручное сохранение весов

Выше было показано как загружать веса в модель.

Ручное сохранение весов настолько же просто, используйте Model.save_weights метод.

# Сохраняем веса
model.save_weights('./checkpoints/my_checkpoint')

# Восстанавливаем веса
model = create_model()
model.load_weights('./checkpoints/my_checkpoint')

loss,acc = model.evaluate(test_images, test_labels)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))

1000/1000 [==============================] - 0s 59us/step
Restored model, accuracy: 87.80%

Сохранение модели целиком

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

Сохранение полностью функционирующих моделей в Keras очень удобно - можно загружать их в TensorFlow.js и затем тренировать и исполнять их в веб-браузере.

Keras предоставляет базовый формат сохранения, используя HDF5 стандарт. Для наших целей сохраненная модель может трактоваться как единый бинарный блоб (blob).

model = create_model()

model.fit(train_images, train_labels, epochs=5)

# Сохраняем модель целиком в HDF5 файле
model.save('my_model.h5')

Epoch 1/5
1000/1000 [==============================] - 0s 317us/step - loss: 1.1730 - acc: 0.6640
Epoch 2/5
1000/1000 [==============================] - 0s 109us/step - loss: 0.4257 - acc: 0.8790
Epoch 3/5
1000/1000 [==============================] - 0s 106us/step - loss: 0.2889 - acc: 0.9240
Epoch 4/5
1000/1000 [==============================] - 0s 104us/step - loss: 0.2171 - acc: 0.9390
Epoch 5/5
1000/1000 [==============================] - 0s 106us/step - loss: 0.1615 - acc: 0.9670

Теперь пересоздаем модель из этого файла:

# Пересоздаем ту же самую модель, включая веса и оптимизатор.
new_model = keras.models.load_model('my_model.h5')
new_model.summary()

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_12 (Dense)             (None, 512)               401920    
_________________________________________________________________
dropout_6 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_13 (Dense)             (None, 10)                5130      
=================================================================
Total params: 407,050
Trainable params: 407,050
Non-trainable params: 0
_________________________________________________________________

Проверяем ее аккуратность:

loss, acc = new_model.evaluate(test_images, test_labels)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))

1000/1000 [==============================] - 0s 78us/step
Restored model, accuracy: 86.60%

Эта техника сохраняет все:

  • Значения весов
  • Конфигурацию модели (архитектуру)
  • Конфигурацию оптимизатора

Keras сохраняет модели, проверяя архитектуру. На данный момент не доступно сохранение TensorFlow оптимизаторов (из tf.train). При их использовании необходимо перекомпилировать модель после загрузки - при этом освобождается состояние оптимизатора.

четверг, 6 сентября 2018 г.

Переобучение и недообучение с TensorFlow

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

Другими словами, наша модель переобучается (overfit) тренировочным данным. Умение разбираться с переобучением - это важный момент. Хотя довольно часто возможно достичь высокой аккуратности на тренировочном наборе, главной задачей является разработать модель, которая будет так же хорошо генерализоваться на тестовом наборе (показывать такой же уровень аккуратности на ранее не встречавшихся данных).

Противоположность переобучению - недообучение (underfitting). Недообучение происходит, когда все еще присутствует место для улучшения на тестовых данных. Это может случиться по ряду причин: если модель недостаточно сильная, либо слишком сильно регуляризована, либо просто недостаточно долго тренирована. Это означает что сеть необучилась соотвествующим паттернам в тренировочных данных.

Если тренировка, однако, проводится слишком долго, тогда модель начинает переобучаться и обучается паттернам из тренировочных данных, которые не генерализуются на тестовых данных. Необходимо добиться баланса.

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

В этом посте мы рассмотрим две основные техники регуляризации - весовая регуляризация и дропаут (dropout)(отсев) - и используем их, чтобы улучшить модель классификации отзывов к фильмам.

import tensorflow as tf
from tensorflow import keras

import numpy as np
import matplotlib.pyplot as plt

print(tf.version)

1.9.0

Загрузка IMDB набора данных

Вместо использования встраиваний (embedding), как в предыдущих постах, здесь мы будем использовать многоразовое кодирование (multi-hot encode) предложений. Эта модель быстро переобучится (overfit) на тренировочном наборе. Она будет использоваться, чтобы демонстрировать случаи, когда происходит переобучение, и как бороться с этим.

Многоразовое кодирование наших листов означает преобразование их в вектора нулей и единиц. Конкретно, это означает, что экземпляр преобразует последовательность [3, 5] в вектор с 10 000 измерений, которые все будут нулями, исключая индексы для 3 и 5, которые будут единицами.

NUM_WORDS = 10000

(train_data, train_labels), (test_data, test_labels) = keras.datasets.imdb.load_data(num_words=NUM_WORDS)

def multi_hot_sequences(sequences, dimension):
    # Создаем матрицу со нулями формы (len(sequences), dimension)
    results = np.zeros((len(sequences), dimension))
    for i, word_indices in enumerate(sequences):
        # установим специфичные индексы results[i] равными 1
        results[i, word_indices] = 1.0
    return results


train_data = multi_hot_sequences(train_data, dimension=NUM_WORDS)
test_data = multi_hot_sequences(test_data, dimension=NUM_WORDS)

Downloading data from https://s3.amazonaws.com/text-datasets/imdb.npz
17465344/17464789 [==============================] - 2s 0us/step

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

plt.plot(train_data[0])

[]

Демонстрация переобучения

Простеший путь для предупреждения переобучения - это уменьшить размер модели, то есть количество изучаемых параметров в модели (которые определены количеством слоев и количеством элементов в слое). В глубоком обучении количество изучаемых параметров в модели часто обозначают как "вместимость" ("capacity") модели. Интуитивно модель с большим количеством параметров будет иметь большую "запоминающую вместимость" и поэтому будет способна легко обучаться превосходному картированию между тренировочными примерами и их целями, картированию без какой-либо способности к генерализации - это будет бесполезно для выполнения прогнозов на ранее не известных данных.

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

С другой стороны, если сеть имеет ограниченные ресурсы для запоминания, она будет не способна обучиться картированию легко. Чтобы уменьшить ее потерю, модели придется обучаться сжатым представлениям, которые имеют большую способность к прогнозированию. В то же время, если сделать модель слишком маленькой, ей будет трудно обучаться тренировочным данным. Существует баланс между "слишком большой емкостью" и "недостаточной емкостью".

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

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

Создадим простую модель, используя только Dense слой как основание, затем создадим меньшие и большие версии и сравним их.

Создание модели-основания

baseline_model = keras.Sequential([
    # input_shape требуется только здесь, чтобы .summary работал. 
    keras.layers.Dense(16, activation=tf.nn.relu, 
                       input_shape=(NUM_WORDS,)),
    keras.layers.Dense(16, activation=tf.nn.relu),
    keras.layers.Dense(1, activation=tf.nn.sigmoid)
])

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

baseline_model.summary()

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 16)                160016    
_________________________________________________________________
dense_1 (Dense)              (None, 16)                272       
_________________________________________________________________
dense_2 (Dense)              (None, 1)                 17        
=================================================================
Total params: 160,305
Trainable params: 160,305
Non-trainable params: 0
_________________________________________________________________

baseline_history = baseline_model.fit(train_data,
                               train_labels,
                               epochs=20,
                               batch_size=512,
                               validation_data=(test_data, test_labels),
                               verbose=2)

Train on 25000 samples, validate on 25000 samples
Epoch 1/20
- 4s - loss: 0.4830 - acc: 0.8082 - binary_crossentropy: 0.4830 - val_loss: 0.3383 - val_acc: 0.8758 - val_binary_crossentropy: 0.3383
Epoch 2/20
- 4s - loss: 0.2494 - acc: 0.9114 - binary_crossentropy: 0.2494 - val_loss: 0.2851 - val_acc: 0.8862 - val_binary_crossentropy: 0.2851
Epoch 3/20
- 4s - loss: 0.1816 - acc: 0.9372 - binary_crossentropy: 0.1816 - val_loss: 0.2894 - val_acc: 0.8850 - val_binary_crossentropy: 0.2894
Epoch 4/20
- 4s - loss: 0.1444 - acc: 0.9516 - binary_crossentropy: 0.1444 - val_loss: 0.3147 - val_acc: 0.8790 - val_binary_crossentropy: 0.3147
Epoch 5/20
- 4s - loss: 0.1184 - acc: 0.9613 - binary_crossentropy: 0.1184 - val_loss: 0.3409 - val_acc: 0.8747 - val_binary_crossentropy: 0.3409
Epoch 6/20
- 4s - loss: 0.0960 - acc: 0.9704 - binary_crossentropy: 0.0960 - val_loss: 0.3744 - val_acc: 0.8702 - val_binary_crossentropy: 0.3744
Epoch 7/20
- 4s - loss: 0.0766 - acc: 0.9788 - binary_crossentropy: 0.0766 - val_loss: 0.4213 - val_acc: 0.8624 - val_binary_crossentropy: 0.4213
Epoch 8/20
- 4s - loss: 0.0610 - acc: 0.9843 - binary_crossentropy: 0.0610 - val_loss: 0.4507 - val_acc: 0.8634 - val_binary_crossentropy: 0.4507
Epoch 9/20
- 4s - loss: 0.0472 - acc: 0.9901 - binary_crossentropy: 0.0472 - val_loss: 0.4909 - val_acc: 0.8617 - val_binary_crossentropy: 0.4909
Epoch 10/20
- 4s - loss: 0.0355 - acc: 0.9942 - binary_crossentropy: 0.0355 - val_loss: 0.5343 - val_acc: 0.8570 - val_binary_crossentropy: 0.5343
Epoch 11/20
- 4s - loss: 0.0270 - acc: 0.9963 - binary_crossentropy: 0.0270 - val_loss: 0.5752 - val_acc: 0.8584 - val_binary_crossentropy: 0.5752
Epoch 12/20
- 4s - loss: 0.0202 - acc: 0.9979 - binary_crossentropy: 0.0202 - val_loss: 0.6161 - val_acc: 0.8565 - val_binary_crossentropy: 0.6161
Epoch 13/20
- 4s - loss: 0.0148 - acc: 0.9989 - binary_crossentropy: 0.0148 - val_loss: 0.6524 - val_acc: 0.8562 - val_binary_crossentropy: 0.6524
Epoch 14/20
- 4s - loss: 0.0111 - acc: 0.9995 - binary_crossentropy: 0.0111 - val_loss: 0.6874 - val_acc: 0.8538 - val_binary_crossentropy: 0.6874
Epoch 15/20
- 4s - loss: 0.0084 - acc: 0.9998 - binary_crossentropy: 0.0084 - val_loss: 0.7186 - val_acc: 0.8536 - val_binary_crossentropy: 0.7186
Epoch 16/20
- 4s - loss: 0.0067 - acc: 0.9999 - binary_crossentropy: 0.0067 - val_loss: 0.7471 - val_acc: 0.8535 - val_binary_crossentropy: 0.7471
Epoch 17/20
- 4s - loss: 0.0053 - acc: 1.0000 - binary_crossentropy: 0.0053 - val_loss: 0.7722 - val_acc: 0.8536 - val_binary_crossentropy: 0.7722
Epoch 18/20
- 4s - loss: 0.0044 - acc: 1.0000 - binary_crossentropy: 0.0044 - val_loss: 0.7973 - val_acc: 0.8522 - val_binary_crossentropy: 0.7973
Epoch 19/20
- 4s - loss: 0.0037 - acc: 1.0000 - binary_crossentropy: 0.0037 - val_loss: 0.8185 - val_acc: 0.8536 - val_binary_crossentropy: 0.8185
Epoch 20/20
- 4s - loss: 0.0031 - acc: 1.0000 - binary_crossentropy: 0.0031 - val_loss: 0.8396 - val_acc: 0.8522 - val_binary_crossentropy: 0.8396

Создание меньшей модели

Создадим модель с меньшим количеством скрытых элементов, чтобы сравнить ее с моделью-основанием, которую мы создали ранее:

smaller_model = keras.Sequential([
    keras.layers.Dense(4, activation=tf.nn.relu, 
                       input_shape=(NUM_WORDS,)),
    keras.layers.Dense(4, activation=tf.nn.relu),
    keras.layers.Dense(1, activation=tf.nn.sigmoid)
])

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

smaller_model.summary()

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_3 (Dense)              (None, 4)                 40004     
_________________________________________________________________
dense_4 (Dense)              (None, 4)                 20        
_________________________________________________________________
dense_5 (Dense)              (None, 1)                 5         
=================================================================
Total params: 40,029
Trainable params: 40,029
Non-trainable params: 0
_________________________________________________________________

Тренируем модель, используя те же данные:

smaller_history = smaller_model.fit(train_data,
                              train_labels,
                              epochs=20,
                              batch_size=512,
                              validation_data=(test_data, test_labels),
                              verbose=2)

Train on 25000 samples, validate on 25000 samples
Epoch 1/20
- 4s - loss: 0.5875 - acc: 0.7590 - binary_crossentropy: 0.5875 - val_loss: 0.4841 - val_acc: 0.8534 - val_binary_crossentropy: 0.4841
Epoch 2/20
- 4s - loss: 0.3949 - acc: 0.8851 - binary_crossentropy: 0.3949 - val_loss: 0.3738 - val_acc: 0.8733 - val_binary_crossentropy: 0.3738
Epoch 3/20
- 4s - loss: 0.3001 - acc: 0.9069 - binary_crossentropy: 0.3001 - val_loss: 0.3222 - val_acc: 0.8838 - val_binary_crossentropy: 0.3222
Epoch 4/20
- 4s - loss: 0.2469 - acc: 0.9221 - binary_crossentropy: 0.2469 - val_loss: 0.2992 - val_acc: 0.8860 - val_binary_crossentropy: 0.2992
Epoch 5/20
- 4s - loss: 0.2129 - acc: 0.9320 - binary_crossentropy: 0.2129 - val_loss: 0.2901 - val_acc: 0.8862 - val_binary_crossentropy: 0.2901
Epoch 6/20
- 4s - loss: 0.1881 - acc: 0.9386 - binary_crossentropy: 0.1881 - val_loss: 0.2850 - val_acc: 0.8878 - val_binary_crossentropy: 0.2850
Epoch 7/20
- 4s - loss: 0.1689 - acc: 0.9461 - binary_crossentropy: 0.1689 - val_loss: 0.2862 - val_acc: 0.8854 - val_binary_crossentropy: 0.2862
Epoch 8/20
- 4s - loss: 0.1534 - acc: 0.9506 - binary_crossentropy: 0.1534 - val_loss: 0.2903 - val_acc: 0.8847 - val_binary_crossentropy: 0.2903
Epoch 9/20
- 4s - loss: 0.1412 - acc: 0.9550 - binary_crossentropy: 0.1412 - val_loss: 0.2964 - val_acc: 0.8828 - val_binary_crossentropy: 0.2964
Epoch 10/20
- 4s - loss: 0.1291 - acc: 0.9600 - binary_crossentropy: 0.1291 - val_loss: 0.3066 - val_acc: 0.8791 - val_binary_crossentropy: 0.3066
Epoch 11/20
- 4s - loss: 0.1193 - acc: 0.9646 - binary_crossentropy: 0.1193 - val_loss: 0.3139 - val_acc: 0.8790 - val_binary_crossentropy: 0.3139
Epoch 12/20
- 4s - loss: 0.1103 - acc: 0.9680 - binary_crossentropy: 0.1103 - val_loss: 0.3267 - val_acc: 0.8759 - val_binary_crossentropy: 0.3267
Epoch 13/20
- 4s - loss: 0.1022 - acc: 0.9712 - binary_crossentropy: 0.1022 - val_loss: 0.3347 - val_acc: 0.8755 - val_binary_crossentropy: 0.3347
Epoch 14/20
- 4s - loss: 0.0953 - acc: 0.9735 - binary_crossentropy: 0.0953 - val_loss: 0.3465 - val_acc: 0.8731 - val_binary_crossentropy: 0.3465
Epoch 15/20
- 4s - loss: 0.0883 - acc: 0.9762 - binary_crossentropy: 0.0883 - val_loss: 0.3589 - val_acc: 0.8712 - val_binary_crossentropy: 0.3589
Epoch 16/20
- 4s - loss: 0.0822 - acc: 0.9783 - binary_crossentropy: 0.0822 - val_loss: 0.3717 - val_acc: 0.8704 - val_binary_crossentropy: 0.3717
Epoch 17/20
- 4s - loss: 0.0767 - acc: 0.9811 - binary_crossentropy: 0.0767 - val_loss: 0.3864 - val_acc: 0.8688 - val_binary_crossentropy: 0.3864
Epoch 18/20
- 4s - loss: 0.0712 - acc: 0.9833 - binary_crossentropy: 0.0712 - val_loss: 0.4017 - val_acc: 0.8668 - val_binary_crossentropy: 0.4017
Epoch 19/20
- 4s - loss: 0.0659 - acc: 0.9857 - binary_crossentropy: 0.0659 - val_loss: 0.4142 - val_acc: 0.8666 - val_binary_crossentropy: 0.4142
Epoch 20/20
- 4s - loss: 0.0606 - acc: 0.9877 - binary_crossentropy: 0.0606 - val_loss: 0.4292 - val_acc: 0.8656 - val_binary_crossentropy: 0.4292

Создание большей модели

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

bigger_model = keras.models.Sequential([
    keras.layers.Dense(512, activation=tf.nn.relu, 
                       input_shape=(NUM_WORDS,)),
    keras.layers.Dense(512, activation=tf.nn.relu),
    keras.layers.Dense(1, activation=tf.nn.sigmoid)
])

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

bigger_model.summary()

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_6 (Dense)              (None, 512)               5120512   
_________________________________________________________________
dense_7 (Dense)              (None, 512)               262656    
_________________________________________________________________
dense_8 (Dense)              (None, 1)                 513       
=================================================================
Total params: 5,383,681
Trainable params: 5,383,681
Non-trainable params: 0
_________________________________________________________________

Тренируем модель, используя те же данные:

bigger_history = bigger_model.fit(train_data, train_labels,
                             epochs=20,
                             batch_size=512,
                             validation_data=(test_data, test_labels),
                             verbose=2)

Train on 25000 samples, validate on 25000 samples
Epoch 1/20
- 8s - loss: 0.3478 - acc: 0.8486 - binary_crossentropy: 0.3478 - val_loss: 0.2999 - val_acc: 0.8788 - val_binary_crossentropy: 0.2999
Epoch 2/20
- 8s - loss: 0.1424 - acc: 0.9483 - binary_crossentropy: 0.1424 - val_loss: 0.3631 - val_acc: 0.8639 - val_binary_crossentropy: 0.3631
Epoch 3/20
- 8s - loss: 0.0515 - acc: 0.9854 - binary_crossentropy: 0.0515 - val_loss: 0.4228 - val_acc: 0.8700 - val_binary_crossentropy: 0.4228
Epoch 4/20
- 8s - loss: 0.0081 - acc: 0.9989 - binary_crossentropy: 0.0081 - val_loss: 0.5690 - val_acc: 0.8706 - val_binary_crossentropy: 0.5690
Epoch 5/20
- 8s - loss: 0.0013 - acc: 0.9999 - binary_crossentropy: 0.0013 - val_loss: 0.6585 - val_acc: 0.8719 - val_binary_crossentropy: 0.6585
Epoch 6/20
- 8s - loss: 8.3323e-04 - acc: 1.0000 - binary_crossentropy: 8.3323e-04 - val_loss: 0.6917 - val_acc: 0.8710 - val_binary_crossentropy: 0.6917
Epoch 7/20
- 8s - loss: 1.8393e-04 - acc: 1.0000 - binary_crossentropy: 1.8393e-04 - val_loss: 0.7174 - val_acc: 0.8717 - val_binary_crossentropy: 0.7174
Epoch 8/20
- 7s - loss: 1.2353e-04 - acc: 1.0000 - binary_crossentropy: 1.2353e-04 - val_loss: 0.7397 - val_acc: 0.8720 - val_binary_crossentropy: 0.7397
Epoch 9/20
- 8s - loss: 9.1524e-05 - acc: 1.0000 - binary_crossentropy: 9.1524e-05 - val_loss: 0.7577 - val_acc: 0.8720 - val_binary_crossentropy: 0.7577
Epoch 10/20
- 8s - loss: 7.1093e-05 - acc: 1.0000 - binary_crossentropy: 7.1093e-05 - val_loss: 0.7716 - val_acc: 0.8721 - val_binary_crossentropy: 0.7716
Epoch 11/20
- 8s - loss: 5.7032e-05 - acc: 1.0000 - binary_crossentropy: 5.7032e-05 - val_loss: 0.7852 - val_acc: 0.8719 - val_binary_crossentropy: 0.7852
Epoch 12/20
- 8s - loss: 4.6592e-05 - acc: 1.0000 - binary_crossentropy: 4.6592e-05 - val_loss: 0.7972 - val_acc: 0.8719 - val_binary_crossentropy: 0.7972
Epoch 13/20
- 8s - loss: 3.8803e-05 - acc: 1.0000 - binary_crossentropy: 3.8803e-05 - val_loss: 0.8071 - val_acc: 0.8722 - val_binary_crossentropy: 0.8071
Epoch 14/20
- 8s - loss: 3.2781e-05 - acc: 1.0000 - binary_crossentropy: 3.2781e-05 - val_loss: 0.8176 - val_acc: 0.8722 - val_binary_crossentropy: 0.8176
Epoch 15/20
- 8s - loss: 2.7967e-05 - acc: 1.0000 - binary_crossentropy: 2.7967e-05 - val_loss: 0.8265 - val_acc: 0.8723 - val_binary_crossentropy: 0.8265
Epoch 16/20
- 7s - loss: 2.4134e-05 - acc: 1.0000 - binary_crossentropy: 2.4134e-05 - val_loss: 0.8344 - val_acc: 0.8722 - val_binary_crossentropy: 0.8344
Epoch 17/20
- 7s - loss: 2.0978e-05 - acc: 1.0000 - binary_crossentropy: 2.0978e-05 - val_loss: 0.8435 - val_acc: 0.8723 - val_binary_crossentropy: 0.8435
Epoch 18/20
- 8s - loss: 1.8398e-05 - acc: 1.0000 - binary_crossentropy: 1.8398e-05 - val_loss: 0.8501 - val_acc: 0.8722 - val_binary_crossentropy: 0.8501
Epoch 19/20
- 8s - loss: 1.6204e-05 - acc: 1.0000 - binary_crossentropy: 1.6204e-05 - val_loss: 0.8572 - val_acc: 0.8722 - val_binary_crossentropy: 0.8572
Epoch 20/20
- 8s - loss: 1.4384e-05 - acc: 1.0000 - binary_crossentropy: 1.4384e-05 - val_loss: 0.8645 - val_acc: 0.8723 - val_binary_crossentropy: 0.8645

Создадим график тренировочной и валидационной потери

Непрерывные линии показывают тренировочную потерю, а пунктирные линии показывают валидационную потерю (следует помнить: более низкое значение валидационной потери означает лучшее качество модели). Здесь меньшая сеть начинает переобучаться позже, чем модель-основание (после 6 эпох (меньшая модель), чем через 4 эпохи (модель-основание)) и ее производительность уменьшается намного медленней после начала переобучения.

def plot_history(histories, key='binary_crossentropy'):
  plt.figure(figsize=(16,10))
    
  for name, history in histories:
    val = plt.plot(history.epoch, history.history['val_'+key],
                   '--', label=name.title()+' Val')
    plt.plot(history.epoch, history.history[key], 
             color=val[0].get_color(),
             label=name.title()+' Train')

  plt.xlabel('Epochs')
  plt.ylabel(key.replace('_',' ').title())
  plt.legend()

  plt.xlim([0,max(history.epoch)])


plot_history([('baseline', baseline_history),
              ('smaller', smaller_history),
              ('bigger', bigger_history)])

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

Стратегии


Добавление весовой регуляризации

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

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

  • L1 регуляризация, где цена добавляется пропорционально абсолютному значению весовых коэффициентов (то что называется "L1 норма"("L1 norm") весов).
  • L2 регуляризация, где цена добавляется пропорционально квадрату значения весовых коэффициентов (то что называется "L2 норма"("L2 norm") весов).
    L2 регуляризацию также называют весовой распад в контексте нейронных сетей.

В tf.keras высовая регуляризация добавляется передачей экземпляров весового регуляризатора слоям как аргументы ключевого слова. Теперь добавим L2 весовую регуляризацию.

l2_model = keras.models.Sequential([
    keras.layers.Dense(16, 
                       kernel_regularizer=keras.regularizers.l2(0.001),
                       activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
    keras.layers.Dense(16, 
                       kernel_regularizer=keras.regularizers.l2(0.001),
                       activation=tf.nn.relu),
    keras.layers.Dense(1, activation=tf.nn.sigmoid)
])

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

l2_model_history = l2_model.fit(train_data, train_labels,
                            epochs=20,
                            batch_size=512,
                            validation_data=(test_data, test_labels),
                            verbose=2)

Train on 25000 samples, validate on 25000 samples
Epoch 1/20
- 4s - loss: 0.5401 - acc: 0.7926 - binary_crossentropy: 0.5019 - val_loss: 0.3785 - val_acc: 0.8760 - val_binary_crossentropy: 0.3386
Epoch 2/20
- 4s - loss: 0.2991 - acc: 0.9103 - binary_crossentropy: 0.2543 - val_loss: 0.3339 - val_acc: 0.8870 - val_binary_crossentropy: 0.2856
Epoch 3/20
- 4s - loss: 0.2479 - acc: 0.9311 - binary_crossentropy: 0.1970 - val_loss: 0.3392 - val_acc: 0.8854 - val_binary_crossentropy: 0.2863
Epoch 4/20
- 4s - loss: 0.2230 - acc: 0.9424 - binary_crossentropy: 0.1684 - val_loss: 0.3551 - val_acc: 0.8804 - val_binary_crossentropy: 0.2994
Epoch 5/20
- 4s - loss: 0.2109 - acc: 0.9484 - binary_crossentropy: 0.1535 - val_loss: 0.3678 - val_acc: 0.8781 - val_binary_crossentropy: 0.3093
Epoch 6/20
- 4s - loss: 0.1980 - acc: 0.9528 - binary_crossentropy: 0.1389 - val_loss: 0.3845 - val_acc: 0.8745 - val_binary_crossentropy: 0.3249
Epoch 7/20
- 4s - loss: 0.1915 - acc: 0.9560 - binary_crossentropy: 0.1307 - val_loss: 0.3987 - val_acc: 0.8718 - val_binary_crossentropy: 0.3371
Epoch 8/20
- 4s - loss: 0.1841 - acc: 0.9600 - binary_crossentropy: 0.1217 - val_loss: 0.4122 - val_acc: 0.8698 - val_binary_crossentropy: 0.3495
Epoch 9/20
- 4s - loss: 0.1791 - acc: 0.9607 - binary_crossentropy: 0.1158 - val_loss: 0.4294 - val_acc: 0.8684 - val_binary_crossentropy: 0.3656
Epoch 10/20
- 4s - loss: 0.1775 - acc: 0.9620 - binary_crossentropy: 0.1130 - val_loss: 0.4455 - val_acc: 0.8642 - val_binary_crossentropy: 0.3804
Epoch 11/20
- 4s - loss: 0.1726 - acc: 0.9639 - binary_crossentropy: 0.1071 - val_loss: 0.4527 - val_acc: 0.8648 - val_binary_crossentropy: 0.3867
Epoch 12/20
- 4s - loss: 0.1690 - acc: 0.9653 - binary_crossentropy: 0.1029 - val_loss: 0.4718 - val_acc: 0.8604 - val_binary_crossentropy: 0.4054
Epoch 13/20
- 4s - loss: 0.1682 - acc: 0.9660 - binary_crossentropy: 0.1011 - val_loss: 0.4770 - val_acc: 0.8628 - val_binary_crossentropy: 0.4095
Epoch 14/20
- 4s - loss: 0.1597 - acc: 0.9693 - binary_crossentropy: 0.0922 - val_loss: 0.4842 - val_acc: 0.8592 - val_binary_crossentropy: 0.4170
Epoch 15/20
- 4s - loss: 0.1542 - acc: 0.9729 - binary_crossentropy: 0.0871 - val_loss: 0.4987 - val_acc: 0.8596 - val_binary_crossentropy: 0.4315
Epoch 16/20
- 4s - loss: 0.1525 - acc: 0.9733 - binary_crossentropy: 0.0851 - val_loss: 0.5099 - val_acc: 0.8566 - val_binary_crossentropy: 0.4423
Epoch 17/20
- 4s - loss: 0.1500 - acc: 0.9742 - binary_crossentropy: 0.0824 - val_loss: 0.5175 - val_acc: 0.8567 - val_binary_crossentropy: 0.4495
Epoch 18/20
- 4s - loss: 0.1487 - acc: 0.9741 - binary_crossentropy: 0.0805 - val_loss: 0.5354 - val_acc: 0.8552 - val_binary_crossentropy: 0.4668
Epoch 19/20
- 4s - loss: 0.1466 - acc: 0.9748 - binary_crossentropy: 0.0778 - val_loss: 0.5389 - val_acc: 0.8589 - val_binary_crossentropy: 0.4700
Epoch 20/20
- 4s - loss: 0.1451 - acc: 0.9755 - binary_crossentropy: 0.0757 - val_loss: 0.5499 - val_acc: 0.8557 - val_binary_crossentropy: 0.4800

l2(0.001) означает, что каждый коэффициент в марице весов слоя добавит 0.001 * weight_coefficient_value**2 к общей потери сети. Стоит отметить, что ввиду того, что это ограничение добавлено только в тренировочное время, потеря для сети будет намного больше при тренировке, чем при тестировании.

Вот влияние нашей L2 регуляризации:

plot_history([('baseline', baseline_history),
              ('l2', l2_model_history)])

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

Добавление дропаута (dropout)

Дропаут (отсеивание) - это одна из наиболее эффективных и наиболее общеиспользуемых техник регуляризации для нейронных сетей. Дропаут, применяемый к слою, представляет собой "отбрасывание" ("dropping out") (установка в ноль) случайным образом ряда выводных свойств слоя в ходе тренировки. Скажем дан слой, который в обычном случае возвращает вектор [0.2, 0.5, 1.3, 0.8, 1.1] для данного вводного примера в ходе тренировки; после применения дропаута этот вектор будет иметь несколько нулевых значений, распределенных случайным образом, например [0, 0.5, 1.3, 0, 1.1]. "Коэффициент дропаута" - это порция свойств, которые будут приведены к нулю; обычно устанавливается между 0.2 и 0.5. Во время тестирования элементы не подвергаются дропауту, и вместо этого выводные значения слоя сокращаются на коэффициент, равный коэффициенту дропаута, чтобы таким образом сбалансировать факт наличия большего количества активных элементов, чем во время тренировки.

В tf.keras можно вводить дропаут в сеть через Dropout слой, который применяется выводу вышележащего слоя.

Добавим два Dropout слоя в нашу IMDB сеть, чтобы увидеть насколько хороши они в снижении переобучения:

dpt_model = keras.models.Sequential([
    keras.layers.Dense(16, activation=tf.nn.relu, 
                       input_shape=(NUM_WORDS,)),
    keras.layers.Dropout(0.5),
    keras.layers.Dense(16, activation=tf.nn.relu),
    keras.layers.Dropout(0.5),
    keras.layers.Dense(1, activation=tf.nn.sigmoid)
])

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

dpt_model_history = dpt_model.fit(train_data, train_labels,
                             epochs=20,
                             batch_size=512,
                             validation_data=(test_data, test_labels),
                             verbose=2)

Train on 25000 samples, validate on 25000 samples
Epoch 1/20
- 4s - loss: 0.6363 - acc: 0.6294 - binary_crossentropy: 0.6363 - val_loss: 0.5156 - val_acc: 0.8526 - val_binary_crossentropy: 0.5156
Epoch 2/20
- 4s - loss: 0.4776 - acc: 0.7993 - binary_crossentropy: 0.4776 - val_loss: 0.3629 - val_acc: 0.8782 - val_binary_crossentropy: 0.3629
Epoch 3/20
- 4s - loss: 0.3731 - acc: 0.8549 - binary_crossentropy: 0.3731 - val_loss: 0.2976 - val_acc: 0.8864 - val_binary_crossentropy: 0.2976
Epoch 4/20
- 4s - loss: 0.3065 - acc: 0.8850 - binary_crossentropy: 0.3065 - val_loss: 0.2748 - val_acc: 0.8896 - val_binary_crossentropy: 0.2748
Epoch 5/20
- 4s - loss: 0.2610 - acc: 0.9066 - binary_crossentropy: 0.2610 - val_loss: 0.2751 - val_acc: 0.8882 - val_binary_crossentropy: 0.2751
Epoch 6/20
- 4s - loss: 0.2245 - acc: 0.9192 - binary_crossentropy: 0.2245 - val_loss: 0.2844 - val_acc: 0.8870 - val_binary_crossentropy: 0.2844
Epoch 7/20
- 4s - loss: 0.1968 - acc: 0.9266 - binary_crossentropy: 0.1968 - val_loss: 0.2999 - val_acc: 0.8873 - val_binary_crossentropy: 0.2999
Epoch 8/20
- 4s - loss: 0.1759 - acc: 0.9362 - binary_crossentropy: 0.1759 - val_loss: 0.3136 - val_acc: 0.8859 - val_binary_crossentropy: 0.3136
Epoch 9/20
- 4s - loss: 0.1596 - acc: 0.9438 - binary_crossentropy: 0.1596 - val_loss: 0.3242 - val_acc: 0.8826 - val_binary_crossentropy: 0.3242
Epoch 10/20
- 4s - loss: 0.1412 - acc: 0.9498 - binary_crossentropy: 0.1412 - val_loss: 0.3551 - val_acc: 0.8823 - val_binary_crossentropy: 0.3551
Epoch 11/20
- 4s - loss: 0.1292 - acc: 0.9539 - binary_crossentropy: 0.1292 - val_loss: 0.3745 - val_acc: 0.8792 - val_binary_crossentropy: 0.3745
Epoch 12/20
- 4s - loss: 0.1176 - acc: 0.9568 - binary_crossentropy: 0.1176 - val_loss: 0.3983 - val_acc: 0.8800 - val_binary_crossentropy: 0.3983
Epoch 13/20
- 4s - loss: 0.1115 - acc: 0.9582 - binary_crossentropy: 0.1115 - val_loss: 0.4218 - val_acc: 0.8791 - val_binary_crossentropy: 0.4218
Epoch 14/20
- 4s - loss: 0.1035 - acc: 0.9612 - binary_crossentropy: 0.1035 - val_loss: 0.4368 - val_acc: 0.8780 - val_binary_crossentropy: 0.4368
Epoch 15/20
- 4s - loss: 0.0949 - acc: 0.9626 - binary_crossentropy: 0.0949 - val_loss: 0.4608 - val_acc: 0.8773 - val_binary_crossentropy: 0.4608
Epoch 16/20
- 4s - loss: 0.0909 - acc: 0.9626 - binary_crossentropy: 0.0909 - val_loss: 0.4573 - val_acc: 0.8776 - val_binary_crossentropy: 0.4573
Epoch 17/20
- 4s - loss: 0.0860 - acc: 0.9642 - binary_crossentropy: 0.0860 - val_loss: 0.4963 - val_acc: 0.8770 - val_binary_crossentropy: 0.4963
Epoch 18/20
- 4s - loss: 0.0832 - acc: 0.9655 - binary_crossentropy: 0.0832 - val_loss: 0.4879 - val_acc: 0.8762 - val_binary_crossentropy: 0.4879
Epoch 19/20
- 4s - loss: 0.0752 - acc: 0.9698 - binary_crossentropy: 0.0752 - val_loss: 0.5262 - val_acc: 0.8760 - val_binary_crossentropy: 0.5262
Epoch 20/20
- 4s - loss: 0.0768 - acc: 0.9666 - binary_crossentropy: 0.0768 - val_loss: 0.5360 - val_acc: 0.8754 - val_binary_crossentropy: 0.5360

plot_history([('baseline', baseline_history),
              ('dropout', dpt_model_history)])

Добавления дропаута - это чистое улучшения по сравнению с моделью-основанием.

Вот наиболее распространенные пути для предупреждения переобучения в нейронных сетях:

  • Взять больше тренировочных данных.
  • Уменьшить емкость сети.
  • Добавить весовую регуляризацию.
  • Добавить дропаут.

И два важных подхода, не описанных в этом руководстве - это приращивание данных (data-augmentation) и пакетная нормализация (batch normalization).

среда, 5 сентября 2018 г.

Регрессия c TensorFlow: прогнозирование цен на дома

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

В этом посте мы построим модель для прогнозирования средней цены домов в окрестностях Бостона в середине 70х. Чтобы сделать это мы предоставим модели параметры данных об окрестностях, такие как уровень криминальности и уровень местного налога на недвижимость.

Это руководство использует tf.keras API.

from future import absolute_import, division, print_function

import tensorflow as tf
from tensorflow import keras

import numpy as np

print(tf.version)

1.9.0

Набор данных цен домов Бостона

Этот набор доступен напрямую из TensorFlow. Загрузим и перемешаем данные тренировочного набора:

boston_housing = keras.datasets.boston_housing

(train_data, train_labels), (test_data, test_labels) = boston_housing.load_data()

# Перемешаем тренировочные данные
order = np.argsort(np.random.random(train_labels.shape))
train_data = train_data[order]
train_labels = train_labels[order]

Downloading data from https://s3.amazonaws.com/keras-datasets/boston_housing.npz
57344/57026 [==============================] - 0s 4us/step

Примеры и свойства

Этот набор данных намного меньше, чем другие: он имеет в целом 506 примеров, разделенных между 404 тренировочными примерами и 102 тестовыми примерами:

print("Training set: {}".format(train_data.shape)) # 404 примера, 13 свойств
print("Testing set: {}".format(test_data.shape)) # 102 примера, 13 свойств

Training set: (404, 13)
Testing set:  (102, 13)

Набор содержит 13 различных свойств:

  1. Уровень преступности на душу населения.
  2. Доля жилой земли, выделенной для участков более 25 000 квадратных футов.
  3. Доля не торговых бизнес акров на город.
  4. Переменная реки Чарлз (равна 1 если рядом река, 0 если нет).
  5. Концентрация оксидов азота (частей на 10 миллионов)
  6. Среднее количество комнат на жилье.
  7. Пропорция домов частных владельцев, построенных до 1940 года.
  8. Взвешенные расстояния до пяти рабочих центров Бостона.
  9. Индекс доступности близлежащих автомагистралей.
  10. Полная стоимость налога на имущество на 10 000 долларов США.
  11. Среднее количество учителей для детей на город.
  12. 1000 * (Bk - 0.63) ** 2, где Bk - это пропорция афроамериканцев на город.
  13. Процент населения с низким социальным статусом.

Каждое из этих вводных свойств сохранено в разном масштабе. Некоторые свойства представлены дробным значением между 0 и 1, другие в диапазоне между 1 и 12, некоторые в диапазоне от 0 до 100 и т.д. Такое часто встречается в реальных данных. Понимание и умение исследовать и очищать такие данные - важный навык в машинном обучении.

print(train_data[0]) # Отобразим свойства примеров, отметим разные масштабы

[7.8750e-02 4.5000e+01 3.4400e+00 0.0000e+00 4.3700e-01 6.7820e+00
 4.1100e+01 3.7886e+00 5.0000e+00 3.9800e+02 1.5200e+01 3.9387e+02
 6.6800e+00]

Используем библиотеку pandas, чтобы отобразить первые несколько строк набора данных в хорошо отформатированной таблице:

import pandas as pd

column_names = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT']

df = pd.DataFrame(train_data, columns=column_names)
df.head()

Метки

Метки - это цены домов в тысячах долларов. (Цены 1970х годов.)

print(train_labels[0:10])  # Отобразим первые 10

[32.  27.5 32.  23.1 50.  20.6 22.6 36.2 21.8 19.5]

Нормализация свойств

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

# Тестовые данные не используются при вычислении mean и std

mean = train_data.mean(axis=0)
std = train_data.std(axis=0)
train_data = (train_data - mean) / std
test_data = (test_data - mean) / std

print(train_data[0])  # Первый тренировочный пример, нормализованный

[-0.39725269  1.41205707 -1.12664623 -0.25683275 -1.027385    0.72635358
 -1.00016413  0.02383449 -0.51114231 -0.04753316 -1.49067405  0.41584124
 -0.83648691]

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

Создание модели

Построим нашу модель. Здесь мы будем использовать Sequential модель с двумя тесно связанными скрытыми слоями, а выводной слой возвращает единственное недискретное значение. Шаги построения модели обернуты в функцию, build_model.

def build_model():
  model = keras.Sequential([
    keras.layers.Dense(64, activation=tf.nn.relu,
                       input_shape=(train_data.shape[1],)),
    keras.layers.Dense(64, activation=tf.nn.relu),
    keras.layers.Dense(1)
  ])

  optimizer = tf.train.RMSPropOptimizer(0.001)

  model.compile(loss='mse',
                optimizer=optimizer,
                metrics=['mae'])
  return model

model = build_model()
model.summary()

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 64)                896       
_________________________________________________________________
dense_1 (Dense)              (None, 64)                4160      
_________________________________________________________________
dense_2 (Dense)              (None, 1)                 65        
=================================================================
Total params: 5,121
Trainable params: 5,121
Non-trainable params: 0
_________________________________________________________________

Тренировка модели

Тренируем модель 500 эпох и записываем тренировочную и валидационную аккуратность в объект истории.

# Отобразим прогресс тренировки 
# печатая по одной точке на каждую завершенную эпоху 
class PrintDot(keras.callbacks.Callback):
  def on_epoch_end(self, epoch, logs):
    if epoch % 100 == 0: print('')
    print('.', end='')

EPOCHS = 500

# Сохраним тренировочную статистику
history = model.fit(train_data, train_labels, epochs=EPOCHS,
                    validation_split=0.2, verbose=0,
                    callbacks=[PrintDot()])

.......................................................
.......................................................
.......................................................
.......................................................
.......................................................

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

import matplotlib.pyplot as plt


def plot_history(history):
  plt.figure()
  plt.xlabel('Epoch')
  plt.ylabel('Mean Abs Error [1000$]')
  plt.plot(history.epoch, 
           np.array(history.history['mean_absolute_error']),
           label='Train Loss')
  plt.plot(history.epoch, 
           np.array(history.history['val_mean_absolute_error']),
           label = 'Val loss')
  plt.legend()
  plt.ylim([0, 5])

plot_history(history)

Этот граф показывает небольшое улучшение в модели после 200 эпох. Обновим model.fit метод, чтобы автоматически остановить тренировку, когда валидационный счет перестает улучшаться. Мы будем использовать callback, который тестирует условие для каждой эпохи. Если количество эпох истекает без показа улучшения, тогда тренировка останавливается.

model = build_model()

# Параметр терпения - количество эпох, чтобы проверить улучшение
early_stop = keras.callbacks.EarlyStopping(monitor='val_loss', 
                                           patience=20)

history = model.fit(train_data, train_labels, epochs=EPOCHS,
                    validation_split=0.2, verbose=0,
                    callbacks=[early_stop, PrintDot()])

plot_history(history)

..........................................................
..........................................................

Граф показывает среднюю ошибку около 2500 долларов. Это нормально? Ну, 2500 не является незначительным количеством, когда некоторые метки всего 15000.

Посмотрим как модель будет выполняться на тестовом наборе:

[loss, mae] = model.evaluate(test_data, test_labels, verbose=0)

print("Testing set Mean Abs Error: ${:7.2f}".format(mae * 1000))

Testing set Mean Abs Error: $2599.79

Прогнозирование

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

test_predictions = model.predict(test_data).flatten()

plt.scatter(test_labels, test_predictions)
plt.xlabel('True Values [1000$]')
plt.ylabel('Predictions [1000$]')
plt.axis('equal')
plt.xlim(plt.xlim())
plt.ylim(plt.ylim())
_ = plt.plot([-100, 100], [-100, 100])

error = test_predictions - test_labels
plt.hist(error, bins = 50)
plt.xlabel("Prediction Error [1000$]")
_ = plt.ylabel("Count")

Заключение

В этом руководстве упомянуты несколько техник для решения задач регрессии:

  • Средняя квадратичная ошибка (Mean Squared Error (MSE)) - функция потери, используемая для задач регрессии (отличных от задач классификации).
  • Аналогично, оценка метрик, используемая для регрессии отличается от классификации. Общеиспользуемая метрика регрессии - Средняя абсолютная ошибка (Mean Absolute Error (MAE)).
  • Когда свойства вводных данных имеют значения с разными диапазонами, каждое свойство следует масштабировать независимо.
  • Если присуствует не так много тренировочных данных, предпочтительно использовать маленькую сеть с небольшим количеством скрытых слоев, чтобы исбежать переобучения.
  • Ранняя остановка - это полезная техника для предупреждения переобучения.

пятница, 31 августа 2018 г.

Мультиклассовые нейронные сети: софтмакс

Вспомним, что логистическая регрессия производит результаты между 0 и 1. Например, вывод логистической регрессии равный 0.8 от классификатора писем предполагает, что шанс того, что письмо спам равен 80%, а шанс того, что письмо не спам равен 20%. Очевидно, что сумма вероятностей того, что письмо будет спамом или не спамом будет равна 1.0.

Софтмакс (Softmax) расширяет эту идею в мульти-классовый мир. То есть, софтмакс назначает дробные вероятности каждому классу в мультиклассовой проблеме. Эти дробные вероятности должны составлять вместе 1.0. Это дополнительное ограничение помогает тренировке сойтись быстрее.

Например, для анализа картинок из прошлого поста софтмакс может произвести следующие вероятности того что картинка принадлежит конкретному классу:

Софтмакс реализован через слой нейронной сети прямо перед слоем вывода. Софтмакс слой должен иметь такое же количество узлов как слой вывода.

Софтмакс слой в нейронной сети

Софтмакс опции

Рассмотрим следующие варианты Softmax:

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

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

Одна метка против многих меток

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

  • Можно не использовать софтмакс.
  • Необходимо основываться на многих логистических регрессиях.

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

Мультиклассовые нейронные сети: один против всех

В машинном обучении мультиклассовая классификация - это задача классифицирования примеров в один из трех или более классов. (Классифицирование в один из двух классов называется бинарной классификацией.)

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

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

Один против всех (One vs. all) предоставляет путь решения бинарной классификации. Дана классификационная проблема с N возможными решениями, один-против-всех решение состоит из N отдельных бинарных классификаторов - по одному бинарному классификатору для каждого возможного выхода. В течение тренировки модель проходит через последовательность бинарных классификаторов, тренируя каждый отвечать на отдельный классификационный вопрос. Например, дана картинка собаки, пять различных распознавателей могут быть тренированы, четыре видят изображение как негативный пример (не собака) и один как положительный (собака). То есть:

  • Это изображение яблока? Нет.
  • Это изображение медведя? Нет.
  • Это изображение конфеты? Нет.
  • Это изображение собаки? Да.
  • Это изображение яйца? Нет.

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

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

Один-против-всех (one-vs.-all) нейронная сеть

Тренировка нейронных сетей: провал обратного распространения и регуляризация

В этом посте мы рассмотрим случаи провала обратного распространения и наиболее общий путь регуляризации нейронной сети.

Случаи провала

Существует ряд общих причин провалов в ходе обратного распространения.

Стирающиеся градиенты

Градиенты для нижних слоев (те, которые ближе ко входам) могут стать очень маленькими. В глубоких сетях вычисление этих градиентов может включать в себя взятие произведения многих малых членов.

Когда градиенты стираются до нуля для нижних слоев, эти слои тренируются очень медленно либо не тренируются совсем.

ReLU функция активации может помочь предупредить стирание градиентов.

Взрывающиеся градиенты

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

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

Мертвые ReLU единицы

Однажды взвешенная сумма для ReLU единицы падает ниже нуля, тогда ReLU единица может застрять. Она выдает нулевую активацию, ничего не отдавая в вывод сети, и градиенты не могут больше течь через нее в ходе обратного распространения. С отключенным источником градиентов ввод в ReLU может никогда больше не измениться достаточно, чтобы поднять взвешенную сумму назад выше нуля.

Снижение скорости обучения может помочь сохранить ReLU единицы от умирания.

Исключающая регуляризация (dropout regularization)

Еще одна форма регуляризации, называемая Исключающей (Dropout), полезна в нейронных сетях. Она работает, случайным образом исключая активацию единиц в сети для отдельного градиентного шага. Чем больше исключений, тем сильнее регуляризация:

  • 0.0 = Нет исключающей регуляризации
  • 1.0 = Исключается все. Модель ничему не обучается.
  • Значения между 0.0 и 1.0 = наиболее полезны

четверг, 30 августа 2018 г.

Анатомия нейронных сетей

Как уже упоминалось ранее в посте о пересечениях свойств, следующая проблема нелинейна:

Нелинейная проблема классификации

"Нелинейная" означает, что мы не можем точно спрогнозировать метку с моделью вида b + w1x1 + w2x2. Другими словами, "поверхность решений" это не линия в данном случае. Ранее мы рассматривали пересечения свойств как один из возможных подходов к моделированию нелинейных проблем.

Рассмотрим следующий набор данных:

Более сложная нелинейная классификационная проблема

Набор данных, показанный на предыдущем графике, не может быть решена с помощью линейной модели.

Чтобы увидеть как нейронные сети могут помочь с нелинейными проблемами, начнем с представления линейной модели как графа:

Линейная модель как граф

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

Как мы можем изменить эту модель, чтобы улучшить ее способность решать нелинейные проблемы?

Скрытые слои

В модели представленной следующим графом был добавлен "скрытый слой" промежуточных значений. Каждый желтый узел в скрытом слое - это взвешенная сумма значений синих входных узлов. Вывод - взвешенная сумма желтых узлов.

Граф двухслойной модели

Эта модель линейная? Да - ее вывод все еще линейная комбинация входов.

В модель, представленной в следующем графе, добавлен второй скрытый слой взвешенных весов.

Граф трехслойной модели

Эта модель все еще линейная? Да. Когда мы выразим вывод как функцию ввода и упростим ее - мы получим просто другую взвешенную сумму входов. Эта сумма не будет эффективно моделировать нелинейную проблему.

Функции активации

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

В модели представленной следующим графом значение каждого узла в скрытом слое 1 трансформировано нелинейной функцией до того как передано в взвешенную сумму следующего слоя. Эта нелинейная функция называется функцией активации.

Граф трехслойной модели с функцией активации

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

Обычные функции активации

Следующая сигмоидная функция активации преобразует взвешенную сумму в значение между 0 и 1.

Вот ее график:

Сигмоидная функция активации

Следующая выпрямленная линейная единица (rectified linear unit) функции активации (или ReLU, кратко) часто работает немного лучше, чем сглаженная функция как сигмоид, при этом также значительно легче для вычисления.

F(x) = max(0,x)

Превосходство ReLU основано на эмпирических находках, вероятно использование ReLU имеет более полезный период ответной реакции. Ответная реакция сигмоида выпадает относительно быстро с обеих сторон.

ReLU функция активации

Фактически любая математическая функция может служить как функция активации. Предположим что σ представляет нашу функцию активации (ReLU, сигмоид). Следовательно, значение узла в сети дано в следующей формуле:

σ(w⋅x+b)

TensorFlow предоставляет "из коробки" поддержку широкого разнообразия функций активации. Рекомендуется начинать с ReLU.

Резюме

Теперь наша модель имеет все стандартные компоненты того, что люди подразумевают под термином "нейронная сеть":

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

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