Расширенное использование рекуррентной нейронной сети для глубокого обучения Python

глубокое обучение Python

Это 8-й день моего участия в августовском испытании обновлений.Подробности о событии:Испытание августовского обновления

Deep Learning with Python

Эта статья — одна из серии заметок, которые я написал, изучая Deep Learning with Python (второе издание, Франсуа Шолле). Содержимое статьи конвертировано из блокнотов Jupyter в Markdown, вы можете перейти наGitHubилиGiteeнайти оригинал.ipynbноутбук.

ты можешь идтиЧитайте оригинальный текст этой книги онлайн на этом сайте(Английский). Автор этой книги также дает соответствиеJupyter notebooks.

Эта статьяГлава 6 Глубокое обучение для текста и последовательностей (Chapter 6. Deep learning for text and sequences) Примечания.

6.3 Advanced usage of recurrent neural networks

Расширенное использование рекуррентных нейронных сетей

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

проблема прогнозирования температуры

Мы будем использовать набор данных временных рядов погоды (набор данных Йена), записанный метеостанцией, В этом наборе данных каждые 10 минут регистрируются 14 величин (таких как температура, атмосферное давление, влажность, направление ветра и т. д.). В этом наборе данных есть многолетние записи, здесь мы используем только 2009-2016 годы. Используя этот набор данных для построения модели, конечной целью является ввод некоторых последних данных (точки данных за несколько дней) и прогнозирование температуры на следующие 24 часа.

Сначала загрузите и разархивируйте набор данных:

cd ~/Somewhere
mkdir jena_climate
cd jena_climate
wget https://s3.amazonaws.com/keras-datasets/jena_climate_2009_2016.csv.zip
unzip jena_climate_2009_2016.csv.zip

Взгляните на данные:

import os

data_dir = "/CDFMLR/Files/dataset/jena_climate"
fname = os.path.join(data_dir, 'jena_climate_2009_2016.csv')

with open(fname) as f:
    data = f.read()

lines = data.split('\n')
header = lines[0].split(',')
lines = lines[1:]

print(header)
print(len(lines))
print(lines[0])

['"Date Time"', '"p (mbar)"', '"T (degC)"', '"Tpot (K)"', '"Tdew (degC)"', '"rh (%)"', '"VPmax (mbar)"', '"VPact (mbar)"', '"VPdef (mbar)"', '"sh (g/kg)"', '"H2OC (mmol/mol)"', '"rho (g/m**3)"', '"wv (m/s)"', '"max. wv (m/s)"', '"wd (deg)"']

420551

01.01.2009 00:10:00,996.52,-8.02,265.40,-8.90,93.30,3.33,3.11,0.22,1.94,3.12,1307.75,1.03,1.75,152.30

Поместите данные в массив Numpy:

import numpy as np

float_data = np.zeros((len(lines), len(header)-1))
for i, line in enumerate(lines):
    values = [float(x) for x in line.split(',')[1:]]
    float_data[i, :] = values
    
print(float_data.shape)

Получите данные, чтобы узнать, что форма:(420551, 14)

Рисуем изменение температуры, и периодичность очевидна:

from matplotlib import pyplot as plt

temp = float_data[:, 1]
plt.plot(range(len(temp)), temp)

png

Давайте посмотрим на данные за первые 10 дней (каждые 10 минут записываются одни данные, поэтому 1 день равен 144):

plt.plot(range(1440), temp[: 1440])

png

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

Следующий шаг — попытаться выполнить работу прогностических моделей. Сначала давайте проясним нашу проблему: учитывая прошлоеlookbackвременные шаги (по 10 минут каждый), мы неstepsВыборка шаг за шагом, что позволяет предсказывать будущееdelayТемпература на временном шаге:

  • lookback = 720: данные наблюдений за последние 5 дней.
  • steps = 6: выборка данных наблюдения каждый час
  • delay = 144: Цель — следующие 24 часа.

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

  1. Стандартизация данных: сделайте так, чтобы различные функции не сильно различались по количеству
# 数据标准化

mean = float_data[:200000].mean(axis=0)
float_data -= mean
std = float_data[:200000].std(axis=0)
float_data /= std
  1. Поместите данные в генератор, выведите(samples, targets), образцы — это пакет входных данных, а целевые значения — соответствующий массив целевой температуры.
# 生成时间序列样本及其目标的生成器

def generator(data,     # 原始数据
              lookback, # 输入数据包括过去多少个时间步
              delay,    # 目标是未来的多少个时间步
              min_index, max_index,  # 指定从 data 的那个部分抽取
              shuffle=False,   # 打乱样本 or 按顺序抽取
              batch_size=128,  # 每批的样本数
              step=6):         # 数据采样的周期
    
    if max_index is None:
        max_index = len(data) - delay - 1
    
    i = min_index + lookback
    while True:
        if shuffle:
            rows = np.random.randint(min_index + lookback, max_index, size=batch_size)
        else:
            if i + batch_size >= max_index:
                i = min_index + lookback
            rows = np.arange(i, min(i + batch_size, max_index))
            i += len(rows)
            
        samples = np.zeros((len(rows), lookback // step, data.shape[-1]))
        targets = np.zeros((len(rows), ))
        
        for j, row in enumerate(rows):
            indices = range(rows[j] - lookback, rows[j], step)
            samples[j] = data[indices]
            targets[j] = data[rows[j] + delay][1]
        
        yield samples, targets

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

# 准备训练生成器、验证生成器和测试生成器

lookback = 1440
step = 6
delay = 144
batch_size = 128

train_gen = generator(float_data, 
                      lookback=lookback, 
                      delay=delay, 
                      min_index=0, 
                      max_index=200000, 
                      shuffle=True, 
                      step=step, 
                      batch_size=batch_size)

val_gen = generator(float_data, 
                    lookback=lookback, 
                    delay=delay, 
                    min_index=200001, 
                    max_index=300000, 
                    step=step, 
                    batch_size=batch_size)

test_gen = generator(float_data, 
                     lookback=lookback, 
                     delay=delay, 
                     min_index=300001, 
                     max_index=None, 
                     step=step, 
                     batch_size=batch_size)

# 验证和测试需要抽取多少次:
val_steps = (300000 - 200001 - lookback)  // batch_size
test_steps = (len(float_data) - 300001 - lookback)  // batch_size

Метод сравнительного анализа, основанный на здравом смысле, не связанный с машинным обучением.

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

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

mae = np.mean(np.abs(preds - targets))

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

# 计算基于常识的基准方法的 MAE

def evaluate_naive_method():
    batch_maes = []
    for step in range(val_steps):
        samples, targets = next(val_gen)
        preds = samples[:, -1, 1]
        mae = np.mean(np.abs(preds - targets))
        batch_maes.append(mae)
    
    return np.mean(batch_maes)
    
mae = evaluate_naive_method()
celsius_mae = mae * std[1]
print(f'mae={mae}, 温度的平均绝对误差={celsius_mae}°C')

получил ответ:

mae = 0.2897359729905486

Средняя абсолютная ошибка температуры = 2,564887434980494°C

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

Методы оценки машинного обучения

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

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

# plot_acc_and_loss: 绘制训练历史的实用函数

import matplotlib.pyplot as plt

def plot_acc_and_loss(history):

    epochs = range(len(history.history['loss']))

    try:
        acc = history.history['acc']
        val_acc = history.history['val_acc']
        
        plt.plot(epochs, acc, 'bo-', label='Training acc')
        plt.plot(epochs, val_acc, 'rs-', label='Validation acc')
        plt.title('Training and validation accuracy')
        plt.legend()
    except:
        print('No acc. Skip')
    finally:
        plt.figure()

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

    plt.plot(epochs, loss, 'bo-', label='Training loss')
    plt.plot(epochs, val_loss, 'rs-', label='Validation loss')
    plt.title('Training and validation loss')
    plt.legend()

    plt.show()
from tensorflow.keras.models import Sequential
from tensorflow.keras import layers
from tensorflow.keras.optimizers import RMSprop

model = Sequential()
model.add(layers.Flatten(input_shape=(lookback // step, float_data.shape[-1])))
model.add(layers.Dense(32, activation='relu'))
model.add(layers.Dense(1))

model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen, 
                              steps_per_epoch=500, 
                              epochs=20, 
                              validation_data=val_gen, 
                              validation_steps=val_steps)

plot_acc_and_loss(history)

Кривая потерь во время обучения:

png

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

Метод рекуррентных сетевых тестов

Полностью подключенная сеть только что использует Flatten для выравнивания временных рядов в начале, поэтому эта модель фактически не учитывает понятие «время». Чтобы воспользоваться порядком времени, мы можем рассмотреть возможность использования рекуррентной сети. На этот раз мы будем использовать слои GRU вместо LSTM:

# 训练并评估一个基于 GRU 的模型

from tensorflow.keras.models import Sequential
from tensorflow.keras import layers
from tensorflow.keras.optimizers import RMSprop

model = Sequential()
model.add(layers.GRU(32, input_shape=(None, float_data.shape[-1])))
model.add(layers.Dense(1))

model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen, 
                              steps_per_epoch=500, 
                              epochs=20, 
                              validation_data=val_gen, 
                              validation_steps=val_steps)

plot_acc_and_loss(history)

Тренировочный процесс:

png

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

print(0.2624 * std[1], '°C')

2.3228957591704926 °C

Лучше, чем исходная модель здравого смысла. Но мы видим, что при более позднем переоснащении в RNN мы можем использовать отбрасывание цикла для борьбы с переоснащением.

выпадение петли

Мы используем отсев в сети прямой связи, которая случайным образом устанавливает единицу ввода слоя на 0. Но в РНС это не так просто, использование отсева перед рекуррентным слоем только помешает обучению и не поможет результату, поэтому используйте отсев в рекуррентном слое.

Использование исключения в повторяющемся слое должно использовать одну и ту же маску (маску, т. е. отбрасывание ячеек) для каждого временного шага, и маска не может меняться от временного шага к временному шагу. В то же время для повторяющихся слоев, таких как LSTM и GRU, «маска выпадения цикла», которая не меняется со временем, также применяется к активации внутреннего цикла слоя. Обе реализации отсева встроены в слой цикла Keras, просто передайте параметрdropoutиrecurrent_dropoutУкажите коэффициент отсева.

# 训练并评估一个使用 dropout 正则化的基于 GRU 的模型

from tensorflow.keras.models import Sequential
from tensorflow.keras import layers
from tensorflow.keras.optimizers import RMSprop

model = Sequential()
model.add(layers.GRU(32, 
                     dropout=0.4, 
                     recurrent_dropout=0.4, 
                     input_shape=(None, float_data.shape[-1])))
model.add(layers.Dense(1))

model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen, 
                              steps_per_epoch=500, 
                              epochs=40,    # 使用了 dropout 的网络需要更长的时间才能收敛
                              validation_data=val_gen, 
                              validation_steps=val_steps)

plot_acc_and_loss(history)

Результат обучения:

png

Это не так хорошо, как книга, я не знаю, почему.

укладка повторяющихся слоев

Решена проблема переобучения, теперь нужно еще больше повысить точность. Только что использовался только один слой контура. Вы можете рассмотреть возможность добавления еще нескольких слоев и объединения их друг с другом, чтобы увеличить пропускную способность сети. На самом деле стек повторяющихся слоев не особенно велик, и Google Translate использует только 7 очень больших слоев LSTM, сложенных вместе.

Накладывая повторяющиеся слои в Keras, помните, что промежуточные слои должны возвращать полный тензор выходной 3D-последовательности, а не только выходные данные последнего временного шага (это поведение по умолчанию):

from tensorflow.keras.models import Sequential
from tensorflow.keras import layers
from tensorflow.keras.optimizers import RMSprop

model = Sequential()
model.add(layers.GRU(32,
                     dropout=0.1,
                     recurrent_dropout=0.5,
                     return_sequences=True,  # 输出完整输出序列
                     input_shape=(None, float_data.shape[-1])))
model.add(layers.GRU(64, activation='relu',
                     dropout=0.1, 
                     recurrent_dropout=0.5))
model.add(layers.Dense(1))

model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen,
                              steps_per_epoch=500,
                              epochs=40,
                              validation_data=val_gen,
                              validation_steps=val_steps)

plot_acc_and_loss(history)

Кривая потерь:

png

Это тоже отличается от книги. Но видно, что наложение повторяющихся слоев не дает большого прироста производительности.

Двунаправленный RNN

Двунаправленные RNN (двунаправленные рекуррентные сети) — это варианты RNN, которые иногда могут работать лучше, чем RNN, особенно при обработке естественного языка.Двунаправленные RNN известны как швейцарская армия глубокого обучения NLP.

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

Раньше мы по умолчанию тренировались в хронологическом порядке, теперь можно попробовать сделать это в обратном порядке. Чтобы изменить порядок, просто выведите в последнем направлении генератора данныхyield samples[:, ::-1, :], targets):

def reverse_order_generator(data, lookback, delay, min_index, max_index,
                            shuffle=False, batch_size=128, step=6):
    if max_index is None:
        max_index = len(data) - delay - 1
    i = min_index + lookback
    while 1:
        if shuffle:
            rows = np.random.randint(
                min_index + lookback, max_index, size=batch_size)
        else:
            if i + batch_size >= max_index:
                i = min_index + lookback
            rows = np.arange(i, min(i + batch_size, max_index))
            i += len(rows)

        samples = np.zeros((len(rows),
                           lookback // step,
                           data.shape[-1]))
        targets = np.zeros((len(rows),))
        for j, row in enumerate(rows):
            indices = range(rows[j] - lookback, rows[j], step)
            samples[j] = data[indices]
            targets[j] = data[rows[j] + delay][1]
        yield samples[:, ::-1, :], targets
        
train_gen_reverse = reverse_order_generator(
    float_data,
    lookback=lookback,
    delay=delay,
    min_index=0,
    max_index=200000,
    shuffle=True,
    step=step, 
    batch_size=batch_size)

val_gen_reverse = reverse_order_generator(
    float_data,
    lookback=lookback,
    delay=delay,
    min_index=200001,
    max_index=300000,
    step=step,
    batch_size=batch_size)
model = Sequential()
model.add(layers.GRU(32, input_shape=(None, float_data.shape[-1])))
model.add(layers.Dense(1))

model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen_reverse,
                              steps_per_epoch=500,
                              epochs=20,
                              validation_data=val_gen_reverse,
                              validation_steps=val_steps)
plot_acc_and_loss(history)

png

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

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

from tensorflow.keras.datasets import imdb
from tensorflow.keras.preprocessing import sequence
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential

# Number of words to consider as features
max_features = 10000
# Cut texts after this number of words (among top max_features most common words)
maxlen = 500

# Load data
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)

# Reverse sequences
x_train = [x[::-1] for x in x_train]
x_test = [x[::-1] for x in x_test]

# Pad sequences
x_train = sequence.pad_sequences(x_train, maxlen=maxlen)
x_test = sequence.pad_sequences(x_test, maxlen=maxlen)

model = Sequential()
model.add(layers.Embedding(max_features, 128))
model.add(layers.LSTM(32))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['acc'])
history = model.fit(x_train, y_train,
                    epochs=10,
                    batch_size=128,
                    validation_split=0.2)
plot_acc_and_loss(history)

Кривая потерь и точности во время обучения:

png

png

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

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

双向 RNN 层的工作原理

В Keras двунаправленные слои используются для реализации двунаправленных RNN:

# 在 IMDB 上训练并评估一个双向 LSTM

model = Sequential()
model.add(layers.Embedding(max_features, 32))
model.add(layers.Bidirectional(layers.LSTM(32)))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
history = model.fit(x_train, y_train, epochs=10, batch_size=128, validation_split=0.2)

plot_acc_and_loss(history)

получил ответ:

png

png


Далее мы пытаемся применить метод двунаправленной RNN к задаче прогнозирования температуры.

from tensorflow.keras.models import Sequential
from tensorflow.keras import layers
from tensorflow.keras.optimizers import RMSprop

model = Sequential()
model.add(layers.Bidirectional(
    layers.GRU(32), input_shape=(None, float_data.shape[-1])))
model.add(layers.Dense(1))

model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen,
                              steps_per_epoch=500,
                              epochs=40,
                              validation_data=val_gen,
                              validation_steps=val_steps)
plot_acc_and_loss(history)

png

Going even further

Далее можно попытаться еще больше улучшить возможности модели:

  • Увеличить количество юнитов в слое
  • Отрегулируйте скорость обучения RMSprop
  • Попробуйте слои LSTM вместо слоев GRU
  • Используйте более крупный плотносвязанный регрессор (более крупный плотный слой или стопку плотных слоев) над (позади) рекуррентного слоя.
  • Запустите наиболее эффективную модель на тестовом наборе, не допуская переобучения модели проверочного набора.

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