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

четверг, 27 декабря 2018 г.

PyTorch: нейронные сети

Нейронные сети могут быть построены с использованием пакета torch.nn.

torch.nn зависит от autograd в определении моделей и их дифференцировании. nn.Module содержит слои и метод forward(input), который возвращает output.

Например, посмотрите на эту сеть, которая классифицирует цифровые изображения:

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

Типичная процедура обучения для нейронной сети следующая:

  1. Определение нейронной сети, которая имеет некоторые изучаемые параметры (или веса)
  2. Итерация по набору входных данных
  3. Обработка ввода через сеть
  4. Рассчет потери (насколько далеки результаты от правильных)
  5. Распространение градиентов обратно на параметры сети
  6. Обновление весов сети, как правило, используя простое правило обновления:
    weight = weight - learning_rate * gradient

Определение сети

Давайте определим сеть:

# -*- coding: utf-8 -*-
import torch
import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):

    def init(self):
        super(Net, self).init()
        # 1 канал ввода изображения, 
        # 6 каналов вывода, 
        # 5x5 квадратное сверточное ядро
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)
        # аффинная операция: y = Wx + b
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # Максимальное объединение через (2, 2) окно
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # Если размер - это квадрат,
        # тогда вы можете задать только одно число
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def num_flat_features(self, x):
        # все измерения, исключая измерение пакета
        size = x.size()[1:]  
        num_features = 1
        for s in size:
            num_features *= s
        return num_features


net = Net()
print(net)

Вывод:

Net(
  (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear(in_features=400, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)

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

Изучаемые параметры модели возвращаются net.parameters()

params = list(net.parameters())
print(len(params))
print(params[0].size())  # conv1's .weight

Вывод:

10
torch.Size([6, 1, 5, 5])

Попробуем случайный 32x32 ввод (Ожидаемый размер ввода для этой net(LeNet) - 32x32.) Для использования этой сети на MNIST наборе следует изменить размер изображений в наборе на 32x32.

input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)

Вывод:

tensor([[-0.0233,  0.0159, -0.0249,  0.1413,  
          0.0663,  0.0297, -0.0940, -0.0135,
          0.1003, -0.0559]], grad_fn=<AddmmBackward>)

Обнулим градиентные буферы всех параметров и backprops со случайными градиентами:

net.zero_grad()
out.backward(torch.randn(1, 10))

Примечание

torch.nn поддерживает только мини-пакеты. Весь torch.nn пакет поддерживает только вводы, которые являются мини-пакетами примеров, а не отдельным примером.

Например, nn.Conv2d будет принимать 4D тензор
nSamples x nChannels x Height x Width.

Если у вас есть отдельный пример, просто используйте input.unsqueeze(0) для того чтобы добавить поддельное измерение пакета.

Рассмотрим все классы, которые встрчались ранее.

  • torch.Tensor - многомерный массив с поддержкой autograd операций как backward(). Также содержит градиенты тензора.
  • nn.Module - модуль нейронной сети. Удобный способ инкапсуляции параметров с вспомогательными интрументами для их перемещения в GPU, экспорта, загрузки и т. д.
  • nn.Parameter - это разновидность Tensor, который автоматически регистрируется как параметр при назначении в качестве атрибута Module.
  • autograd.Function - реализует прямое и обратное определения (forward and backward) autograd операции. Каждая Tensor операция создает как минимум один Function узел, который подключается к функциям, которые создали Tensor и кодирует его историю.

На данный момент мы рассмотрели:

  • Определение нейронной сети
  • Обработка входных данных и обратный вызов

Еще осталось:

  • Вычисление потери
  • Обновление весов сети

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

Функция потери принимает (выходную, целевую) пару входных данных и вычисляет значение, определяющее, насколько далеко вывод от цели.

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

Например:

output = net(input)
target = torch.randn(10)  # случайный target, например
target = target.view(1, -1)  # сделаем его такой же формы как вывод
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)

Вывод:

tensor(1.3389, grad_fn=<MseLossBackward>)

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

input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
      -> view -> linear -> relu -> linear -> relu -> linear
      -> MSELoss
      -> loss

Таким образом, когда мы вызываем loss.backward(), весь граф дифференцирован в соотвествии с потерей, и все тензоры в графе, которые имеют requires_grad=True будут иметь их .grad тензор накопленный с градиентом.

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

print(loss.grad_fn)  # MSELoss
print(loss.grad_fn.next_functions[0][0])  # Linear
print(loss.grad_fn.next_functions[0][0].next_functions[0][0])  # ReLU

Вывод:

<MseLossBackward object at 0x7fab77615278>
<AddmmBackward object at 0x7fab77615940>
<AccumulateGrad object at 0x7fab77615940>


Backprop (Обратное распространение)

Для обратного распространения ошибки все, что нам нужно сделать, это loss.backward(). Вы должны очистить существующие градиенты, иначе градиенты будут накапливается к существующим градиентам.

Теперь мы вызовем loss.backward() и посмотрим на смещение conv1 градиентов до и после backward.

# обнуляем градиентные буферы всех параметров
net.zero_grad()  

print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)

loss.backward()

print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)

Вывод:

conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([-0.0054,  0.0011,  0.0012,  0.0148, -0.0186,  0.0087])


Обновление весов

Самым простым правилом обновления, используемым на практике, является стохастический градиентный спуск (Stochastic Gradient Descent (SGD)):

weight = weight - learning_rate * gradient

Мы можем реализовать это с помощью простого Python кода:

learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate)

Однако, когда вы используете нейронные сети, вы хотите использовать различные правила обновления, такие как SGD, Nesterov-SGD, Adam, RMSProp и др. Чтобы включить их, существует небольшой пакет: torch.optim, который реализует все эти методы. Использовать его очень просто:

import torch.optim as optim

# создайте ваш оптимизатор
optimizer = optim.SGD(net.parameters(), lr=0.01)

# в вашем тренировочном цикле:
optimizer.zero_grad()   # обнуляем градиентные буфферы
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # Выполняем обновление



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

PyTorch: тензоры (tensors)

PyTorch: Autograd, автоматическая дифференциация

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

понедельник, 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)).
  • Когда свойства вводных данных имеют значения с разными диапазонами, каждое свойство следует масштабировать независимо.
  • Если присуствует не так много тренировочных данных, предпочтительно использовать маленькую сеть с небольшим количеством скрытых слоев, чтобы исбежать переобучения.
  • Ранняя остановка - это полезная техника для предупреждения переобучения.

вторник, 4 сентября 2018 г.

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

В этом посте мы продолжим руководство по классификации текста на TensorFlow из прошлого поста.

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

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

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

В этом примере входные данные состоят из массива слов-индексов. Метки, которые необходимо спрогнозировать - 0 или 1. Построим модель для этой задачи:

# вводная форма - размер словаря, используемого для отзывов (10 000 слов)
vocab_size = 10000

model = keras.Sequential()
model.add(keras.layers.Embedding(vocab_size, 16))
model.add(keras.layers.GlobalAveragePooling1D())
model.add(keras.layers.Dense(16, activation=tf.nn.relu))
model.add(keras.layers.Dense(1, activation=tf.nn.sigmoid))

model.summary()

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (None, None, 16)          160000    
_________________________________________________________________
global_average_pooling1d (Gl (None, 16)                0         
_________________________________________________________________
dense (Dense)                (None, 16)                272       
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 17        
=================================================================
Total params: 160,289
Trainable params: 160,289
Non-trainable params: 0

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

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

Скрытые элементы

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

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

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

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

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

Теперь, сконфигурируем модель для использования оптимизатора и функции потери:

model.compile(optimizer=tf.train.AdamOptimizer(),
              loss='binary_crossentropy',
              metrics=['accuracy'])

Создание валидационного набора

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

x_val = train_data[:10000]
partial_x_train = train_data[10000:]

y_val = train_labels[:10000]
partial_y_train = train_labels[10000:]

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

Тренировка модели будет проходить 40 эпох с мини-пакетами по 512 экземпляров. Это 40 итераций по всем примерам в x_train и y_train тензорах. В ходе тренировки отображаем потерю модели и аккуратность на 10 000 примерах из валидационного набора:

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=40,
                    batch_size=512,
                    validation_data=(x_val, y_val),
                    verbose=1)

Train on 15000 samples, validate on 10000 samples
Epoch 1/40
15000/15000 [==============================] - 1s 43us/step - loss: 0.6987 - acc: 0.5008 - val_loss: 0.6940 - val_acc: 0.4948
Epoch 2/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.6912 - acc: 0.5518 - val_loss: 0.6901 - val_acc: 0.5675
Epoch 3/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.6886 - acc: 0.5916 - val_loss: 0.6880 - val_acc: 0.5749
Epoch 4/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.6857 - acc: 0.6337 - val_loss: 0.6855 - val_acc: 0.5404
Epoch 5/40
15000/15000 [==============================] - 0s 28us/step - loss: 0.6820 - acc: 0.6339 - val_loss: 0.6808 - val_acc: 0.6975
Epoch 6/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.6770 - acc: 0.7135 - val_loss: 0.6754 - val_acc: 0.7310
Epoch 7/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.6698 - acc: 0.7352 - val_loss: 0.6681 - val_acc: 0.6996
Epoch 8/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.6596 - acc: 0.7466 - val_loss: 0.6572 - val_acc: 0.7132
Epoch 9/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.6455 - acc: 0.7612 - val_loss: 0.6415 - val_acc: 0.7518
Epoch 10/40
15000/15000 [==============================] - 0s 28us/step - loss: 0.6262 - acc: 0.7749 - val_loss: 0.6216 - val_acc: 0.7638
Epoch 11/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.6025 - acc: 0.7725 - val_loss: 0.5989 - val_acc: 0.7632
Epoch 12/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.5740 - acc: 0.7879 - val_loss: 0.5700 - val_acc: 0.7802
Epoch 13/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.5415 - acc: 0.8047 - val_loss: 0.5409 - val_acc: 0.7927
Epoch 14/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.5085 - acc: 0.8151 - val_loss: 0.5110 - val_acc: 0.8043
Epoch 15/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.4757 - acc: 0.8285 - val_loss: 0.4818 - val_acc: 0.8161
Epoch 16/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.4452 - acc: 0.8395 - val_loss: 0.4551 - val_acc: 0.8286
Epoch 17/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.4158 - acc: 0.8521 - val_loss: 0.4314 - val_acc: 0.8380
Epoch 18/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.3900 - acc: 0.8624 - val_loss: 0.4101 - val_acc: 0.8464
Epoch 19/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.3673 - acc: 0.8713 - val_loss: 0.3920 - val_acc: 0.8529
Epoch 20/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.3472 - acc: 0.8787 - val_loss: 0.3765 - val_acc: 0.8571
Epoch 21/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.3297 - acc: 0.8843 - val_loss: 0.3635 - val_acc: 0.8616
Epoch 22/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.3142 - acc: 0.8899 - val_loss: 0.3525 - val_acc: 0.8666
Epoch 23/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.3008 - acc: 0.8923 - val_loss: 0.3435 - val_acc: 0.8695
Epoch 24/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.2883 - acc: 0.8971 - val_loss: 0.3351 - val_acc: 0.8714
Epoch 25/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.2772 - acc: 0.9017 - val_loss: 0.3281 - val_acc: 0.8743
Epoch 26/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.2671 - acc: 0.9051 - val_loss: 0.3226 - val_acc: 0.8749
Epoch 27/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.2583 - acc: 0.9072 - val_loss: 0.3173 - val_acc: 0.8760
Epoch 28/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.2492 - acc: 0.9108 - val_loss: 0.3122 - val_acc: 0.8784
Epoch 29/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.2414 - acc: 0.9121 - val_loss: 0.3087 - val_acc: 0.8790
Epoch 30/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.2349 - acc: 0.9144 - val_loss: 0.3049 - val_acc: 0.8809
Epoch 31/40
15000/15000 [==============================] - 0s 28us/step - loss: 0.2268 - acc: 0.9183 - val_loss: 0.3021 - val_acc: 0.8804
Epoch 32/40
15000/15000 [==============================] - 0s 28us/step - loss: 0.2211 - acc: 0.9214 - val_loss: 0.2994 - val_acc: 0.8808
Epoch 33/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.2139 - acc: 0.9240 - val_loss: 0.2970 - val_acc: 0.8816
Epoch 34/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.2081 - acc: 0.9253 - val_loss: 0.2955 - val_acc: 0.8816
Epoch 35/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.2030 - acc: 0.9265 - val_loss: 0.2933 - val_acc: 0.8833
Epoch 36/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.1970 - acc: 0.9297 - val_loss: 0.2919 - val_acc: 0.8838
Epoch 37/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.1919 - acc: 0.9319 - val_loss: 0.2907 - val_acc: 0.8852
Epoch 38/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.1871 - acc: 0.9335 - val_loss: 0.2895 - val_acc: 0.8849
Epoch 39/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.1819 - acc: 0.9370 - val_loss: 0.2885 - val_acc: 0.8860
Epoch 40/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.1773 - acc: 0.9395 - val_loss: 0.2876 - val_acc: 0.8854

Оценка модели

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

results = model.evaluate(test_data, test_labels)

print(results)

25000/25000 [==============================] - 0s 14us/step
[0.30335798323631286, 0.87656]

Этот по-настоящему простой подход достиг аккуратности около 87%. С более продвинутыми подходами модель должна приблизиться к 95%.

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

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

history_dict = history.history
history_dict.keys()

dict_keys(['val_loss', 'acc', 'loss', 'val_acc'])

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

import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

# "bo" is for "blue dot"
plt.plot(epochs, loss, 'bo', label='Training loss')
# b is for "solid blue line"
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

plt.show()

plt.clf()   # очистка фигуры
acc_values = history_dict['acc']
val_acc_values = history_dict['val_acc']

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()

plt.show()

На этом графике точки представляют тренировочные потерю и аккуратность, а непрерывная линия - валидационные потерю и аккуратность.

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

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

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

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

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

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

Мы будем использовать IMDB набор данных, который содержит тексты 50 000 отзывов к фильмам из Internet Movie Data Вase (IMDB). Они разделены на 25 000 отзывов для тренировки и 25 000 отзывов для тестирования. Тренировочный и тестовый наборы сбалансированы - это означает, что они содержат одинаковое количество позитивных и негативных отзывов.

Это руководство использует tf.keras - высокоуровневое API для построения и тренировки моделей в TensorFlow.

# TensorFlow и tf.keras
import tensorflow as tf
from tensorflow import keras

# NumPy библиотека
import numpy as np
print(tf.version)

1.9.0

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

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

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

imdb = keras.datasets.imdb

(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)

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

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

Исследование данных

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

print("Training entries: {}, labels: {}".format(len(train_data), len(train_labels)))

Training entries: 25000, labels: 25000

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

print(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(train_data[0]), len(train_data[1])

(218, 189)

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

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

# Индексы словаря связи слов и соотвествующих им чисел
word_index = imdb.get_word_index()

# Первые индексы зарезервированы
word_index = {k:(v+3) for k,v in word_index.items()}
word_index[""] = 0
word_index[""] = 1
word_index[""] = 2 # unknown
word_index[""] = 3

reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])

def decode_review(text):
  return ' '.join([reverse_word_index.get(i, '?') for i in text])

Downloading data from https://s3.amazonaws.com/text-datasets/imdb_word_index.json
1646592/1641221 [==============================] - 1s 1us/step

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

decode_review(train_data[0])

" 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 is an amazing actor and now the same being director 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 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 to the two little boy's that played the of norman and paul they were just brilliant children are often left out of the 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"

Подготовка данных

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

  • Одноразовое кодирование (one-hot-encoding) массивов для преобразования их в вектора нулей и единиц. Например, последовательность [3, 5] станет вектором с 10 000 измерений, которые все равны 0, исключая индексы 3 и 5, которые будут равны 1. Затем сделаем первый слой нашей сети - Dense слой - который может обрабатывать нецельночисловые данные векторов. Этот подход затрачивает много памяти, поскольку требует матрицу размера num_words * num_reviews (количество слов * количество отзывов).
  • С другой стороны, мы можем заполнить массивы, чтобы все они имели одинаковую длину, затем создадим цельночисловой тензор формы max_length * num_reviews (максимальная длина * количество отзывов). Мы можем использовать встроенный слой (embedding layer) способный обрабатывать такую форму как первый слой сети.

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

Ввиду того что отзывы должны быть одинаковой длины мы используем pad_sequences функцию, чтобы стандартизировать длину:

train_data = keras.preprocessing.sequence.pad_sequences(train_data, value=word_index[""], padding='post', maxlen=256)

test_data = keras.preprocessing.sequence.pad_sequences(test_data, value=word_index[""], padding='post', maxlen=256)

Взглянем на длину примеров теперь:

len(train_data[0]), len(train_data[1])

(256, 256)

И проверим (теперь заполненный) первый отзыв:

print(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    0    0    0    0    0    0
    0    0    0    0    0    0    0    0    0    0    0    0    0    0
    0    0    0    0    0    0    0    0    0    0    0    0    0    0
    0    0    0    0]

Продолжение этого руководства по классификации текста на TensorFlow читайте в следующем посте.