Двунаправленная рекуррентная нейронная сеть и реализация TensorFlow

искусственный интеллект TensorFlow Нейронные сети Java
Двунаправленная рекуррентная нейронная сеть и реализация TensorFlow

предисловие

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

Односторонняя рекуррентная нейронная сеть

Так называемая односторонняя рекуррентная нейронная сеть на самом деле является обычной рекуррентной нейронной сетью.Вы можете видеть, что во время t, время t-1 и время t+1 вход в разное время соответствует разным выходам, а скрытый слой в предыдущий момент повлияет на вывод текущего времени time. Эта структура представляет собой структуру односторонней рекуррентной нейронной сети.

这里写图片描述

Односторонней рекуррентной нейронной сети недостаточно

Из односторонней структуры можно узнать, что на результат прогнозирования следующего момента совместно влияют входные данные нескольких предыдущих моментов, и иногда может потребоваться совместное определение прогноза несколькими предыдущими входными данными и несколькими последующими входными данными, которые будут более точным. Например, если я говорю «у меня живот xx, я собираюсь поесть», то без последней части нельзя сделать правильный вывод, что он «голоден», это также может быть «больно» или "жирный" и так далее.

Двунаправленная рекуррентная нейронная сеть

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

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

这里写图片描述

можно представить следующей формулой,

h_t = f(w_1x_t + w_2h_{t-1})

h'_t = f(w_3x_t + w_5h'_{t+1})

o_t = g(w_4h_t + w_6h'_t)

Как обучить двунаправленную рекуррентную сеть

прямое распространение

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

обратное распространение

  1. Всегда рассчитывайте выходной слой\deltaпункт.
  2. По всем выходным слоям\deltaэлемент, используя алгоритм BPTT для обновления прямого уровня.
  3. По всем выходным слоям\deltaэлемент, используя алгоритм BPTT для обновления предыдущих слоев.

код реализации

Создать словарный запас

Первое, что нужно сделать с символами, — это создать словарь, содержащий все слова в корпусе, словарь, индексированный от позиции символа к слову, и словарь, индексированный от позиции к символу.

def create_vocab(text):
    unique_chars = list(set(text))
    print(unique_chars)
    vocab_size = len(unique_chars)
    vocab_index_dict = {}
    index_vocab_dict = {}
    for i, char in enumerate(unique_chars):
        vocab_index_dict[char] = i
        index_vocab_dict[i] = char
    return vocab_index_dict, index_vocab_dict, vocab_size

пакетный генератор

Создайте пакетный генератор для создания пакетов обучающих выборок из текста, где текст — это весь корпус, batch_size — размер пакета, vocab_size — размер словаря, seq_length — длина последовательности, а vocab_index_dict — индексный словарь словаря. Структура генерации генератора примерно такая, как показано ниже, матрица заполняется вертикально в текстовом порядке, а размер столбца матрицы равен размеру_пакета.

class BatchGenerator(object):
    def __init__(self, text, batch_size, seq_length, vocab_size, vocab_index_dict):
        self._text = text
        self._text_size = len(text)
        self._batch_size = batch_size
        self.vocab_size = vocab_size
        self.seq_length = seq_length
        self.vocab_index_dict = vocab_index_dict

        segment = self._text_size // batch_size
        self._cursor = [offset * segment for offset in range(batch_size)]
        self._last_batch = self._next_batch()

    def _next_batch(self):
        batch = np.zeros(shape=(self._batch_size), dtype=np.float)
        for b in range(self._batch_size):
            batch[b] = self.vocab_index_dict[self._text[self._cursor[b]]]
            self._cursor[b] = (self._cursor[b] + 1) % self._text_size
        return batch

    def next(self):
        batches = [self._last_batch]
        for step in range(self.seq_length):
            batches.append(self._next_batch())
        self._last_batch = batches[-1]
        return batches

这里写图片描述

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

Определите две рекуррентные нейронные сети LSTM вперед и назад соответственно, вам нужно указать количество нейронов в скрытом слое hidden_size, а затем передать две созданные нейронные сетиstatic_bidirectional_rnnЗавершите создание двунаправленной рекуррентной нейронной сети.

lstm_fw_cell = rnn.BasicLSTMCell(hidden_size, forget_bias=1.0)
lstm_bw_cell = rnn.BasicLSTMCell(hidden_size, forget_bias=1.0)
outputs, _, _ = rnn.static_bidirectional_rnn(lstm_fw_cell, lstm_bw_cell, sliced_inputs, dtype=tf.float32)

Затем создаются заполнители, в основном включающие входные заполнители и целевые заполнители, входные заполнители и структуры, связанные с размером пакета и длиной последовательности [batch_size, seq_length]. Наконец, есть целевой заполнитель, структура которого такая же, как у входного заполнителя. Для лучшего понимания нарисуйте вход и цель следующим образом:

input_data = tf.placeholder(tf.int64, [batch_size, seq_length], name='inputs')
input_targets = tf.placeholder(tf.int64, [batch_size, seq_length], name='targets')

这里写图片描述

这里写图片描述

Как правило, нам понадобится слой встраивания, чтобы встроить словарь в указанное пространство измерения, а размер определяется параметром embedding_size. В то же время vocab_size — это размер словаря, чтобы все слова можно было сопоставить с заданным пространством измерений. Структура слоя встраивания показана на рисунке ниже. Вектор пространства слов, соответствующий входным данным, можно найти с помощью tf.nn.embedding_lookup. Здесь объясняется операция embedding_lookup. Он получит вектор слов, соответствующий каждому элементу входы из словаря.Если входы 2-мерные, после этой операции становится 3-мерным, потому что слово было представлено embedding_size-мерным вектором.

embedding = tf.get_variable('embedding', [vocab_size, embedding_size])
inputs = tf.nn.embedding_lookup(embedding, input_data)

这里写图片描述

Трехмерный пространственный вектор слоя встраивания, полученный выше, нельзя напрямую передать в рекуррентную нейронную сеть, и требуется некоторая обработка. Его нужно разрезать в соответствии с длиной последовательности. После операции разделения и последующего сжатия получается список. Этот список является входом, который в конечном итоге попадет в рекуррентную нейронную сеть. Длина списка равна seq_length. . Структура каждого входа (batch_size, embedding_size), то есть (20 128). Обратите внимание, что embedding_size здесь ровно 128, что совпадает с количеством нейронов в скрытом слое рекуррентной нейронной сети.Это не совпадение, но они должны быть одинаковыми, чтобы матрица из слоя встраивания могла быть введен в нейронную сеть, чтобы быть точно таким же, как каждая из нейронной сети.Веса умножаются идеально. Окончательно получены выходные данные и конечное состояние рекуррентной нейронной сети.

sliced_inputs = [tf.squeeze(input_, [1]) for input_ in
                         tf.split(axis=1, num_or_size_splits=seq_length, value=inputs)]

После 2 слоев рекуррентной нейронной сети выходные данные получены, но выход представляет собой структуру списка, Нам нужно преобразовать его в тензорную форму tf через tf.reshape, а тензорная структура (200,128). Точно так же должны быть связаны целевые заполнители и структура (200,). Затем создайте слой softmax, структура весов — [hidden_size, vocab_size], структура терма смещения — [vocab_size], выходная матрица умножается на матрицу весов и добавляется член смещения для получения логитов, затем используется sparse_softmax_cross_entropy_with_logits для расчета перекрестной энтропийной потери, и, наконец, вычисляется среднее значение потери.

flat_outputs = tf.reshape(tf.concat(axis=1, values=outputs), [-1, 2 * hidden_size])
flat_targets = tf.reshape(tf.concat(axis=1, values=input_targets), [-1])
logits = tf.matmul(flat_outputs, weights) + biases
loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=flat_targets)
mean_loss = tf.reduce_mean(loss)

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

tvars = tf.trainable_variables()
grads, _ = tf.clip_by_global_norm(tf.gradients(mean_loss, tvars), max_grad_norm)
optimizer = tf.train.AdamOptimizer(tf_learning_rate)
train_op = optimizer.apply_gradients(zip(grads, tvars))

Рассчитайте точность обучения.

prediction = tf.nn.softmax(logits)
correct_pred = tf.equal(tf.argmax(prediction, 1), flat_targets)
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

Создать сеанс

Создайте сеанс, чтобы начать обучение, установите количество эпох для обучения, указанное в num_epochs. epoch_size — количество эпох, необходимое для полного обучения корпуса один раз. Получите пакет выборочных данных с помощью генератора пакетов, потому что правильный вывод, соответствующий вводу в текущий момент, является значением в следующий момент, поэтому используйте data[:-1] и data[1:] для получения ввода и цель. Организуйте операции и введите ввод, цель и состояние, соответствующие заполнителю, выполните.

with tf.Session(graph=graph) as session:
    tf.global_variables_initializer().run()
    for i in range(num_epochs):
        data = train_batches.next()
        inputs = np.array(data[:-1]).transpose()
        targets = np.array(data[1:]).transpose()
        ops = [mean_loss, train_op, tf_learning_rate, accuracy]
        feed_dict = {input_data: inputs, input_targets: targets}
        average_loss, __, lr, acc = session.run(ops, feed_dict)
        if i % 100 == 0:
            logging.info("average loss: %.5f,accuracy: %.3f", average_loss, acc)

github

https://github.com/sea-boat/DeepLearning-Lab/blob/master/BiLstm.py

------------- Рекомендуем прочитать ------------

Резюме моей статьи за 2017 год — машинное обучение

Резюме моих статей за 2017 год — Java и промежуточное ПО

Резюме моих статей 2017 года — глубокое обучение

Краткое изложение моих статей за 2017 год — исходный код JDK

Резюме моей статьи за 2017 год — обработка естественного языка

Резюме моих статей 2017 года — Java Concurrency

------------------рекламное время----------------

Поговори со мной, задай мне вопросы:

这里写图片描述

Меню официальной учетной записи было разделено на «распределенное», «машинное обучение», «глубокое обучение», «НЛП», «глубина Java», «ядро параллелизма Java», «исходный код JDK», «ядро Tomcat», и т.д. Там может быть один стиль, чтобы удовлетворить ваш аппетит.

Зачем писать «Анализ проектирования ядра Tomcat»

Добро пожаловать, чтобы следовать:

这里写图片描述