[Чтение статьи] Применение RNN в Ali DIEN

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

[Чтение статьи] Применение RNN в Ali DIEN

0x00 сводка

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

0x01 Базовые знания

1.1 RNN

RNN, рекуррентные нейронные сети, рекуррентные нейронные сети.

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

Наиболее распространенный способ определения RNN:

output = new_state = f(W * input + U * state + B) = act(W * input + U * state + B)
  • U, W — параметры сети (весовая матрица), а b — параметры смещения, которые изучаются путем обучения сети посредством обратного распространения ошибки.
  • act — функция активации, обычно выбирается сигмовидная или тангенсная функция.

1.2 Код проекта DIEN

В проекте DIEN я взял rnn-код TensorFlow в свой собственный проект и внес некоторые изменения, в частности:

  • используется GRUCell;
  • Индивидуальный VecAttGRUCell;
  • Поскольку интерфейс VecAttGRUCell был изменен, rnn.py также был изменен;

0x02 Cell

Базовая единица RNN называется Cell, не стоит недооценивать эту маленькую ячейку, это не только 1 нейронная единица, но и n скрытых единиц.

Поэтому мы заметили, что параметр, который необходимо указать при определении структуры ячейки (BasicRNNCell/BasicLSTMCell/GRUCell/RNNCell/LSTMCell) в tensorflow, — hidden_units_size.

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

В RNN фактическая функция скрытого слоя, состоящего из M нейронов, должна быть f(wx + b), и здесь реализованы два шага:

  • Во-первых, M нейронов скрытого слоя полностью подключены к входному вектору X, а вектор x взвешивается и суммируется через матрицу параметров w;
  • Во-вторых, необходимо отфильтровать каждое измерение вектора x, добавить матрицу смещения и получить выходные данные скрытого слоя с помощью функции возбуждения f;

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

Как видно из рисунка, в клеточном блоке четыре вентиля, каждый вентиль соответствует 128 нейронам скрытого слоя, что эквивалентно четырем скрытым слоям, каждый скрытый слой полностью связан с входом x, а входной вектор x равен состоит из двух частей, одна часть является выходом ячейки в предыдущий момент, размер 128, а другая часть является входом вектора текущей выборки, размер 6, поэтому после внутреннего вычисления ячейки, вывод в текущий момент наконец получается, размер 128, что num_hidden, который вводится как часть ячейки в следующий момент.

Ниже мы комбинируем TensorFlow, чтобы конкретно проанализировать механизм реализации и принцип Cell.

2.1 RNNCell (абстрактный родительский класс)

2.1.1 Основы

«RNNCell», который является базовой единицей для реализации RNN в TensorFlow., каждая RNNCell имеет метод call, который используется следующим образом: (output, next_state) = call(input, state).

RNNCell является абстрактным родительским классом, другие RNNcells будут наследовать этот метод, а затем реализовывать в нем функцию call().

RNNCell — это объект, который содержит состояние и может выполнять некоторую обработку входной матрицы. RNNCell работает с входной матрицей (входная матрица) и выводит выходную матрицу (выходная матрица), содержащую столбец «self.output».

state: состояние — это состояние ячейки rnn в сети rnn.Например, если ваше определение rnn содержит N единиц (т. 2D-тензор формы [batch_size, self.state_size] для представления текущего состояния сети RNN, и если ваш self.state_size является кортежем, то данное состояние также должно быть кортежем, состояние в каждом кортеже представляет то же самое, что и предыдущий точно так же.

  • Если атрибут "self.state_size" определен и значение является целым числом, то RNNCell также выводит матрицу состояний (матрицу состояний), включая столбец "self.state_size".
  • Если "self.state_size" определен как целочисленный кортеж, то это кортеж, который выводит матрицу состояния соответствующей длины.Каждая матрица состояния в кортеже имеет ту же длину, что и раньше, включая столбец "self.state_size".

RNNCell в основном имеет две функции: zero_state() и call().

  • zero_state используется для инициализации начального состояния h0 как нулевого вектора.
  • вызов определяет фактическую работу RNNCell (например, RNN — это одна активация, два ворот GRU, три ворот LSTM и т. д. Разница между разными RNN в основном отражается в этой функции).

Помимо метода вызова, для RNNCell есть еще два важных атрибута класса, из которых методы state_size() и output_size() задаются как атрибуты класса, которые можно вызывать как атрибуты (встроенный в Python декоратор @property используется здесь. Он отвечает за превращение метода в вызов свойства, что очень похоже на концепцию свойств и полей в C#):

  • state_size — это размер скрытого слоя (представляющий размер состояния ячейки).
  • output_size, размер вывода (выходное измерение)

Например, мы обычно отправляем батч в модель для расчета, и задаем форму входных данных как (batch_size, input_size), тогда состояние скрытого слоя, полученное при расчете, будет (batch_size, state_size), а выход (размер_пакета, размер_вывода).

Но ни один из двух методов здесь не реализован, а это значит, что мы должны реализовать подкласс, наследующий класс RNNCell и реализующий эти два метода.

class RNNCell(base_layer.Layer):

  def __call__(self, inputs, state, scope=None):
    if scope is not None:
      with vs.variable_scope(scope,
                             custom_getter=self._rnn_get_variable) as scope:
        return super(RNNCell, self).__call__(inputs, state, scope=scope)
    else:
      with vs.variable_scope(vs.get_variable_scope(),
                             custom_getter=self._rnn_get_variable):
        return super(RNNCell, self).__call__(inputs, state)

  def _rnn_get_variable(self, getter, *args, **kwargs):
    variable = getter(*args, **kwargs)
    if context.in_graph_mode():
      trainable = (variable in tf_variables.trainable_variables() or
                   (isinstance(variable, tf_variables.PartitionedVariable) and
                    list(variable)[0] in tf_variables.trainable_variables()))
    else:
      trainable = variable._trainable  # pylint: disable=protected-access
    if trainable and variable not in self._trainable_weights:
      self._trainable_weights.append(variable)
    elif not trainable and variable not in self._non_trainable_weights:
      self._non_trainable_weights.append(variable)
    return variable

  @property
  def state_size(self):
    raise NotImplementedError("Abstract method")

  @property
  def output_size(self):
    raise NotImplementedError("Abstract method")

  def build(self, _):
    pass

  def zero_state(self, batch_size, dtype):
    with ops.name_scope(type(self).__name__ + "ZeroState", values=[batch_size]):
      state_size = self.state_size
      return _zero_state_tensors(state_size, batch_size, dtype)

2.1.2 call

Каждая производная RNNCell должна иметь следующие свойства и реализовывать функцию со следующей сигнатурой функции (выход, следующее_состояние) = вызов (ввод, состояние).Необязательный третий входной параметр 'scope' используется для обратной совместимости и может быть настроен для подклассов. Значение, переданное областью действия, имеет тип tf.Variable, который используется для более удобного управления переменными.

Начать работу с заданного состояния в соответствии с вводом ячейки rnn

args:

inputs: двумерный тензор формы [batch_size, input_size] утверждает: еслиself.state_sizeявляется целым числом, состояние должно быть двумерной тензорной формой[batch_size, self.state_size], иначе, еслиself.state_sizeпредставляет собой целочисленный кортеж (например, LSTM необходимо вычислить состояние ячейки и скрытое состояние единицы, которое является кортежем), то состояние должно быть[batch_size, s] for s in self.state_sizeкортеж формы. Область действия: переменные, созданные другими подклассами.

Возвращение:

пара, в том числе: вывод:[batch_size, self.output_size]Состояние: форма, соответствующая состоянию

Каждый раз, когда вызывается метод вызова RNNCell, это эквивалентно «продвижению на один шаг» во времени, что является основной функцией RNNCell.

2.2 BasicRNNCell (базовый класс)

2.2.1 Основы

RNNCell — это просто абстрактный класс, и при его использовании мы используем два его подкласса — BasicRNNCell и BasicLSTMCell. Как следует из названия, первый является базовым классом RNN, а второй — базовым классом LSTM.

BasicRNNCell — это то, что мы часто называем RNN.在这里插入图片描述Простейшая структура RNN показана на рисунке выше. Его код выглядит следующим образом:

class BasicRNNCell(RNNCell):
  def __init__(self, num_units, activation=None, reuse=None):
    super(BasicRNNCell, self).__init__(_reuse=reuse)
    self._num_units = num_units
    self._activation = activation or math_ops.tanh
    self._linear = None

  @property
  def state_size(self):
    return self._num_units

  @property
  def output_size(self):
    return self._num_units

  def call(self, inputs, state):
    """Most basic RNN: output = new_state = act(W * input + U * state + B)."""
    if self._linear is None:
      self._linear = _Linear([inputs, state], self._num_units, True)

    output = self._activation(self._linear([inputs, state]))
    # output = Ht = tanh([x,Ht-1]*W + B)
    # 一个output作为下一时刻的输入Ht,另一个作为下一层的输入 Ht
    return output, output

2.3.2 Значение параметра

Вы можете увидеть инициализацию__init__Есть несколько параметров.

def __init__(self, num_units, activation=None, reuse=None):

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

Мы знаем, что самый простой модуль RNN имеет три обучаемых параметра W, U, B и две входные переменные. Поэтому, когда мы строим RNN, нам нужно указать размерность каждого параметра.

在这里插入图片描述Обратите внимание, приведенный выше рисунокnПредставляет входной размер dim

Из исходного кода видно, что в BasicRNNCell:

  • state_size равен num_units :def state_size(self): return self._num_units
  • output_size равен num_units :def output_size(self): return self._num_units
  • То есть определите, что state_size и output_size одинаковы,
  • ht и вывод тоже одинаковые (выход функции вызова — это два выхода:return output, output, т. е. он не определяет раздел вывода).
  • Как видно из _linear,output_sizeОтносится к размеру смещения B (ниже будет объяснено значение _Linear).

2.2.3 Функция

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

output = new_state = f(W * input + U * state + B) = act(W * input + U * state + B)

В методах state_size() и output_size() возвращаемое содержимое равно num_units, то есть количеству нейронов.

Далее в методе call():

  • Входящие параметры — это входы и состояние, то есть вход x и последнее неявное состояние

  • Сначала создайте экземпляр класса _Linear, который на самом деле является классом для линейного преобразования, передайте два, а затем вызовите его напрямую, чтобы реализовать линейное преобразование w * [inputs, state] + b:output = new_state = tanh(W * input + U * state + B).

  • Затем вернитесь к методу call() BasicRNNCell, который включает слой метода _activation() вне метода _linear(), то есть функция активации tanh применяется к линейному преобразованию в качестве выходного результата.

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

2.2.4 Linear

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

Этот класс передается [входы, состояние] какcall() Аргументы метода будут выполнять методы concat() и matmul(), а затем выполнять методbias_add(), тем самым реализуя линейное преобразование.

А output_size — это размер выходного слоя, как мы видим

  • В BasicRNNCell output_size равен _num_units;
  • GRUCell равен 2 * _num_units;
  • В BasicLSTMCell это 4 * _num_units;

Это потому, что _linear выполняет несколько уравнений в RNN.Wx + Uh + BОднако количество разных RNN различно.Например, LSTM нужно вычислить четыре раза, а затем напрямую определить output_size как 4_num_units, а затем разделить вывод на четыре переменные.

Ниже приведена сокращенная версия исходного кода

class _Linear(object):

  def __init__(self, args, output_size, build_bias, bias_initializer=None,
               kernel_initializer=None):
    
    self._build_bias = build_bias
    
    if not nest.is_sequence(args):
      args = [args]
      self._is_sequence = False
    else:
      self._is_sequence = True

    # Calculate the total size of arguments on dimension 1.
    total_arg_size = 0
    shapes = [a.get_shape() for a in args]
    for shape in shapes:
	    total_arg_size += shape[1].value

    dtype = [a.dtype for a in args][0]

  # 循环该函数 num_step(句子长度) 次,则该层计算完;
  def __call__(self, args):

    # 如果是第 0 时刻,那么当前的 state(即上一时刻的输出H0)的值全部为0;
    # input 的 shape为: [batch_size, emb_size]
    # state 的 shape为:[batch_zize, Hidden_size]
    # matmul : 矩阵相乘
    # array_ops.concat: 两个矩阵连接,连接后的 shape 为 [batch_size,input_size + Hidden_size],实际就是[Xt,Ht-1]    
    
    if not self._is_sequence:
      args = [args]

    if len(args) == 1:
      res = math_ops.matmul(args[0], self._weights)
    else:
      # 此时计算: [input,state] * [W,U] == [Xt,Ht-1] * W,得到的shape为:[batch_size,Hidden_size] 
      res = math_ops.matmul(array_ops.concat(args, 1), self._weights)
    
    # B 的shape 为:【Hidden_size】
    # [Xt,Ht-1] * W 计算后的shape为:[batch_size,Hidden_size]
    # nn_ops.bias_add,这个函数的计算方法是,让每个 batch 得到的值,都加上这个 B
	  # 加上B后:Ht = tanh([Xt, Ht-1] * W + B),得到的 shape 还是: [batch_size,Hidden_size]
	  # 那么这个 Ht 将作为下一时刻的输入和下一层的输入;    
    if self._build_bias:
      res = nn_ops.bias_add(res, self._biases)
    return res

2.3 GRUCell

ГРУ, закрытая периодическая установка. В GRU есть только два шлюза: Reset Gate и Update Gate. В то же время в этой структуре объединены Ct и скрытое состояние, общая структура проще, чем стандартная структура LSTM, и эта структура также очень популярна в дальнейшем.

Далее давайте взглянем на определение GRU.По сравнению с BasicRNNCell изменена только часть функции вызова, а также добавлены ворота сброса и ворота обновления, которые представлены r и u соответственно. Затем c представляет значение состояния для обновления. Соответствующие диаграммы и формулы выглядят следующим образом:在这里插入图片描述

r = f(W1 * input + U1 * state + B1)
u = f(W2 * input + U2 * state + B2)
c = f(W3 * input + U3 * r * state + B3)
h_new = u * h + (1 - u) * c

Сокращенная версия кода реализации GRUCell выглядит следующим образом:

class GRUCell(RNNCell):
    
  def __init__(self,
               num_units,
               activation=None,
               reuse=None,
               kernel_initializer=None,
               bias_initializer=None):
    super(GRUCell, self).__init__(_reuse=reuse)
    self._num_units = num_units
    self._activation = activation or math_ops.tanh
    self._kernel_initializer = kernel_initializer
    self._bias_initializer = bias_initializer
    self._gate_linear = None
    self._candidate_linear = None

  @property
  def state_size(self):
    return self._num_units

  @property
  def output_size(self):
    return self._num_units

  def call(self, inputs, state):

    value = math_ops.sigmoid(self._gate_linear([inputs, state]))
    r, u = array_ops.split(value=value, num_or_size_splits=2, axis=1)

    r_state = r * state
    if self._candidate_linear is None:
      with vs.variable_scope("candidate"):
        self._candidate_linear = _Linear(
            [inputs, r_state],
            self._num_units,
            True,
            bias_initializer=self._bias_initializer,
            kernel_initializer=self._kernel_initializer)
    c = self._activation(self._candidate_linear([inputs, r_state]))
    new_h = u * state + (1 - u) * c
    return new_h, new_h

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

В методах state_size() и output_size() возвращаемое содержимое равно num_units, то есть количеству нейронов.

В методе call(), поскольку Reset Gate rt и Update Gate zt представлены переменными r и u соответственно, им необходимо объединить ht-1, а именно состояние и xt, а затем реализовать линейное преобразование, а затем вызвать функцию sigmod для получить:

value = math_ops.sigmoid(self._gate_linear([inputs, state]))
r, u = array_ops.split(value=value, num_or_size_splits=2, axis=1)

Затем вам нужно решить ht~, сначала умножить rt и ht-1 или указать:

r_state = r * state

Затем поместите его внутрь линейной функции_Linear, а затем вызовите функцию активации tanh:

c = self._activation(self._candidate_linear([inputs, r_state]))

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

new_h = u * state + (1 - u) * c

Таким образом можно вернуть результат вывода и скрытое состояние.

return new_h, new_h

2.4 Пользовательская RNNCell

Метод настройки RNNCell относительно прост, то есть наследуется абстрактный класс _LayerRNNCell, а затем вы должны реализовать три функции __init__, build и __call__, и вы можете реализовать нужные вам функции в функции вызова. (Примечание: сборка вызывается только один раз, переменные создаются при сборке, а определенные операции rnncell реализуются при вызове).

2.5 VecAttGRUCell DIEN

Код для вызова VecAttGRUCell выглядит следующим образом:

rnn_outputs2, final_state2 = dynamic_rnn(VecAttGRUCell(HIDDEN_SIZE), inputs=rnn_outputs,                                         att_scores = tf.expand_dims(alphas, -1),                                         sequence_length=self.seq_len_ph, dtype=tf.float32,                                         scope="gru2")

Во-первых, мы должны отметить использование tf.expand_dims, эта функция используется для увеличения альфы на одно измерение.

alphas = Tensor("Attention_layer_1/Reshape_4:0", shape=(?, ?), dtype=float32)

-1 означает добавление одного измерения в конце.

att_scores = tf.expand_dims(alphas, -1)

Модификация, которую Али сделал здесь, в основном представляет собой функцию вызова, которая является модификацией att_score:

u = (1.0 - att_score) * u
new_h = u * state + (1 - u) * c
return new_h, new_h    

Конкретный код:

def call(self, inputs, state, att_score=None):
    ......
    c = self._activation(self._candidate_linear([inputs, r_state]))
    u = (1.0 - att_score) * u  # 这里是新增加的
    new_h = u * state + (1 - u) * c # 这里是新增加的
    return new_h, new_h

Переменные времени выполнения следующие:

inputs = {Tensor} Tensor("rnn_2/gru2/while/TensorArrayReadV3:0", shape=(?, 36), dtype=float32)
state = {Tensor} Tensor("rnn_2/gru2/while/Identity_2:0", shape=(?, 36), dtype=float32)
att_score = {Tensor} Tensor("rnn_2/gru2/while/strided_slice:0", shape=(?, 1), dtype=float32)
new_h = {Tensor} Tensor("rnn_2/gru2/while/add_1:0", shape=(?, 36), dtype=float32)
u = {Tensor} Tensor("rnn_2/gru2/while/mul_1:0", shape=(?, 36), dtype=float32)
c = {Tensor} Tensor("rnn_2/gru2/while/Tanh:0", shape=(?, 36), dtype=float32)

Конкретные соответствующие документы:

0x03 RNN

3.1 Выполнение нескольких шагов одновременно

3.1.1 Основы

Базовая RNNCell имеет очевидную проблему: для одиночной RNNCell, когда мы используем ее функцию вызова для работы, мы продвигаемся только на один шаг во времени последовательности. Например, используйте x1, h0, чтобы получить h1, и используйте x2, h1, чтобы получить h2, и т. д.**. В этом случае, если наша длина последовательности равна 10, нам нужно вызвать функцию вызова 10 раз, что хлопотно. В связи с этим TensorFlow предоставляет функцию tf.nn.dynamic_rnn, которая эквивалентна вызову функции вызова n раз. **То есть напрямую получить {h1,h2…,hn} через {h0,x1, x2, …., xn}.

def dynamic_rnn(cell, inputs, att_scores=None, sequence_length=None, initial_state=None,
                dtype=None, parallel_iterations=None, swap_memory=False,
                time_major=False, scope=None):

Введение важных параметров:

  • cell: Блок памяти для LSTM, GRU и т. д. Параметр ячейки представляет собой единицу памяти LSTM или GRU, то есть ячейку. Например, ячейка = tf.nn.rnn_cell.LSTMCell((num_units), где num_units представляет количество нейронов в ячейке rnn, которая равна cell.output_size ниже. Возвращает ячейку LSTM или GRU, которая передается в качестве параметра .
  • inputs: входные обучающие или тестовые данные, общий формат: [размер_пакета, максимальное_время, размер_встраивания], где размер_пакета — количество входных данных в этом пакете, максимальное_время — наибольшая длина последовательности в этом пакете данных, а размер_встраивания представляет размерность встроенного вектора слова.
  • sequence_length: представляет собой список, предполагая, что вы ввели три предложения, и длины трех предложений равны 5, 10, 25, тогда sequence_length=[5, 10, 25].
  • time_major: определяет формат выходного тензора.Если True, форма тензора должна быть [max_time, batch_size, cell.output_size]. Если False, форма тензора должна быть [batch_size, max_time, cell.output_size], а cell.output_size представляет количество нейронов в ячейке rnn.

Возвращаемое значение выглядит следующим образом:

outputs — это все выходные данные шага time_steps. Он имеет форму (batch_size, time_steps, cell.output_size).

state — это скрытое состояние последнего шага, и его форма (batch_size, cell.state_size).

Подробности следующие:

  • outputs.outputs — это тензор, представляющий собой выходное значение каждого шага.
    • Если time_major==True, выходная форма будет [max_time, batch_size, cell.output_size] (требуется, чтобы ввод rnn соответствовал выходной форме rnn)
    • Если time_major==False (по умолчанию), выходные данные имеют вид [batch_size, max_time, cell.output_size]
  • state.состояние является тензором. state — это конечное состояние, то есть состояние последней выходной ячейки в последовательности. Как правило, состояние имеет форму [batch_size, cell.output_size], но когда входной ячейкой является BasicLSTMCell, форма состояния имеет вид [2, batch_size, cell.output_size], где 2 также соответствует состоянию ячейки, а скрыт в состоянии LSTM

max_time — это самая длинная длина последовательности в этом пакете данных.Если вводятся три предложения, max_time соответствует количеству слов в самом длинном предложении, а cell.output_size — это фактически количество нейронов в ячейке rnn.

3.1.2 Использование

Предположим, что наши входные данные имеют формат (batch_size, time_steps, input_size), где:

  • batch_size — количество входных данных в этом пакете;
  • time_steps представляет длину самой последовательности.Например, в Char RNN time_steps, соответствующее предложению длиной 10, равно 10;
  • input_size представляет внутреннюю длину одной последовательности входных данных в одном временном измерении;

Таким образом, мы определили RNNCell и вызвали функцию вызова time_steps раз для RNNCell.

# inputs: shape = (batch_size, time_steps, input_size) 
# cell: RNNCell
# initial_state: shape = (batch_size, cell.state_size)。初始状态。一般可以取零矩阵
outputs, state = tf.nn.dynamic_rnn(cell, inputs, initial_state=initial_state)

Примеры параметров следующие:

образец:

маленький каритас учится

Сяо Ван любит учиться

Сяо Ли любит учиться

Сяохуа любит учиться

Обычно данные примера начинаются с(batch_size, time_step, embedding_size)Подача в модель, соответствующие могут быть (4, 5, 100).

4 означает порционную подачу, то есть (маленькая, маленькая, маленькая, маленькая), а вторая порция (Мин, Ван, Ли, Хуа)…

5 представляет временной шаг, а всего предложение состоит из 5 символов.

Другим примером является следующий код:

import tensorflow as tf
import numpy as np
from tensorflow.python.ops import variable_scope as vs

output_size = 5
batch_size = 4
time_step = 3
dim = 3
cell = tf.nn.rnn_cell.BasicRNNCell(num_units=output_size)
inputs = tf.placeholder(dtype=tf.float32, shape=[time_step, batch_size, dim])
h0 = cell.zero_state(batch_size=batch_size, dtype=tf.float32)
X = np.array([[[1, 2, 1], [2, 0, 0], [2, 1, 0], [1, 1, 0]],  # x1
              [[1, 2, 1], [2, 0, 0], [2, 1, 0], [1, 1, 0]],  # x2
              [[1, 2, 1], [2, 0, 0], [2, 1, 0], [1, 1, 0]]])  # x3
outputs, final_state = tf.nn.dynamic_rnn(cell, inputs, initial_state=h0, time_major=True)

sess = tf.Session()
sess.run(tf.global_variables_initializer())
a, b = sess.run([outputs, final_state], feed_dict={inputs:X})
print(a)
print(b)

3.1.3 time_step

Конкретное объяснение следующее:

текстовые данные

Если данные содержат 1000 предложений временных рядов, каждое предложение состоит из 25 слов, каждое слово векторизовано, а размер вектора каждого слова равен 300, тогда размер_пакета=1000, размер_времени=25, размер_ввода=300.

Анализ: time_steps обычно равен длине предложения, а input_size равен длине вектора после квантования слов.

данные изображения

Возьмем в качестве примера набор рукописных цифр MNIST, обучающие данные содержат 6000 изображений рукописных цифр, размер каждого изображения цифры 28 * 28, размер партии = 6000, время_шагов = 28, размер_ввода = 28, мы можем понять, что изображение разделено на 28 копий, каждая с shape=(1, 28).

аудиоданные

Если это одноканальные аудиоданные, то аудиоданные являются одномерными, если shape=(8910,). Данные, использующие RNN, должны быть двумерными, поэтому при добавлении batch_size данные становятся трехмерными, первое измерение — batch_size, второе измерение — time_steps, а третье — data input_size. Мы можем преобразовать данные в трехмерные данные. Это позволяет использовать RNN.

3.2 Как зациклить вызов

DNN бывает статическим и динамическим соответственно.

  • static_rnn выравнивает RNN и обменивает пространство на время.
  • dynamic_rnn использует циклы for или while.

Вызов static_rnn фактически генерирует график после того, как rnn развернут во временном ряду. Откройте tensorboard, и вы увидите, что ячейки sequence_length rnn_cell сложены вместе, но эти ячейки имеют общий вес. Таким образом, значение sequence_length привязано к топологии графа, что ограничивает значение sequence_length для каждого пакета одинаковым.

Вызов dynamic_rnn не будет расширять rnn, но использовать API tf.while_loop для создания графа, который может выполнять циклы через узлы этих потоков управления, таких как Enter, Switch, Merge, LoopCondition, NextIteration (этот граф все еще должен быть статическим графом , т.к. топология графа при выполнении не меняется). На tensorboard вы увидите только одну rnn_cell, окруженную группой элементов управления. окружены узлами потока. Для dynamic_rnn sequence_length представляет только количество циклов и не имеет ничего общего с топологией самого графа, поэтому каждый пакет может иметь разную sequence_length.

Для DIEN при запуске программы стек выглядит следующим образом:

call, utils.py:144
__call__, utils.py:114
<lambda>, rnn.py:752
_rnn_step, rnn.py:236
_time_step, rnn.py:766
_BuildLoop, control_flow_ops.py:2590
BuildLoop, control_flow_ops.py:2640
while_loop, control_flow_ops.py:2816
_dynamic_rnn_loop, rnn.py:786
dynamic_rnn, rnn.py:615
__init__, model.py:364
train, train.py:142
<module>, train.py:231

Реализация цикла в основном находится в control_flow_ops.py.

while_loop будет продолжать зацикливать код, соответствующий параметру body, когда параметр cond имеет значение true.

def while_loop(cond, body, loop_vars, shape_invariants=None,
               parallel_iterations=10, back_prop=True, swap_memory=False,
               name=None):
  """Repeat `body` while the condition `cond` is true.

  `cond` is a callable returning a boolean scalar tensor. `body` is a callable
  returning a (possibly nested) tuple, namedtuple or list of tensors of the same
  arity (length and structure) and types as `loop_vars`. `loop_vars` is a
  (possibly nested) tuple, namedtuple or list of tensors that is passed to both
  `cond` and `body`. `cond` and `body` both take as many arguments as there are
  `loop_vars`.

  Args:
    cond: A callable that represents the termination condition of the loop.
    body: A callable that represents the loop body.
    loop_vars: A (possibly nested) tuple, namedtuple or list of numpy array,
      `Tensor`, and `TensorArray` objects.
  """
    if context.in_eager_mode():
      while cond(*loop_vars):
        loop_vars = body(*loop_vars)
      return loop_vars

    if shape_invariants is not None:
      nest.assert_same_structure(loop_vars, shape_invariants)

    loop_context = WhileContext(parallel_iterations, back_prop, swap_memory)  # pylint: disable=redefined-outer-name
    ops.add_to_collection(ops.GraphKeys.WHILE_CONTEXT, loop_context)
    result = loop_context.BuildLoop(cond, body, loop_vars, shape_invariants)
    return result

Например следующий пример:

i = tf.constant(0)
c = lambda i: tf.less(i, 10)
b = lambda i: tf.add(i, 1)
r = tf.while_loop(c, b, [i])
print(sess.run(r) ) # 10

В rnn _time_step вызывает цикл while_loop, тем самым завершая итерацию.

      _, output_final_ta, final_state = control_flow_ops.while_loop(
          cond=lambda time, *_: time < time_steps,
          body=_time_step,
          loop_vars=(time, output_ta, state),
          parallel_iterations=parallel_iterations,
          swap_memory=swap_memory)

3.3. р-н ДИЕН

В проекте DIEN измененная часть — это в основном функция _time_step, поскольку необходимо добавить параметр att_scores.

Основные из них:

  • пройти черезlambda: cell(input_t, state, att_score)Вызовите функцию вызова ячейки #, которая является бизнес-логикой, которую мы написали заранее;
  • позвонивcontrol_flow_ops.while_loop(cond=lambda time, *_: time < time_steps, body=_time_step...)перебирать цикл;

Сокращенная версия кода выглядит следующим образом:

  def _time_step(time, output_ta_t, state, att_scores=None):
    """Take a time step of the dynamic RNN.
    Args:
      time: int32 scalar Tensor.
      output_ta_t: List of `TensorArray`s that represent the output.
      state: nested tuple of vector tensors that represent the state.

    Returns:
      The tuple (time + 1, output_ta_t with updated flow, new_state).
    """

    ......
    
    if att_scores is not None:
        att_score = att_scores[:, time, :]
        call_cell = lambda: cell(input_t, state, att_score)
    else:
        call_cell = lambda: cell(input_t, state)
        
    ......

    output_ta_t = tuple(
        ta.write(time, out) for ta, out in zip(output_ta_t, output))
    
    if att_scores is not None:
        return (time + 1, output_ta_t, new_state, att_scores)
    else:
        return (time + 1, output_ta_t, new_state)

  if att_scores is not None:  
      _, output_final_ta, final_state, _ = control_flow_ops.while_loop(
          cond=lambda time, *_: time < time_steps,
          body=_time_step,
          loop_vars=(time, output_ta, state, att_scores),
          parallel_iterations=parallel_iterations,
          swap_memory=swap_memory)
  else:
      _, output_final_ta, final_state = control_flow_ops.while_loop(
          cond=lambda time, *_: time < time_steps,
          body=_time_step,
          loop_vars=(time, output_ta, state),
          parallel_iterations=parallel_iterations,
          swap_memory=swap_memory)

    ......

0xEE Личная информация

★★★★★★Думая о жизни и технологиях★★★★★★

Публичный аккаунт WeChat:мысли Росси

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

ссылка 0xFF

Изучите RNN с помощью кода и полностью поймите time_step

Анализ принципа физической архитектуры скрытого слоя реального нейрона LSTM

LSTM для машинного обучения

Zhihu-He Zhiyuan: правильный способ открыть реализацию RNN в TensorFlow

Интерпретация rnn тензорного потока

анализ исходного кода char-rnn-tensorflow и структурный анализ процесса

Рекуррентные нейронные сети (LSTM и GRU) (2)

Правильный способ открыть реализацию RNN в TensorFlow

Полностью иллюстрированный RNN, варианты RNN, Seq2Seq, механизмы внимания

Анализ исходного кода RNNCell в Tensorflow

Анализ исходного кода RNNcell в tensorflow и способ настройки RNNCell

Заметки о чтении API Tensorflow rnn_cell

Серия рекуррентных нейронных сетей (1) BasicRNNCell в Tensorflow

Рекуррентная серия нейронных сетей (2) dynamic_rnn в Tensorflow

Подробное объяснение обработки tf.nn.dynamic_rnn в LSTM

Xiaobai рекуррентная нейронная сеть RNN LSTM количество параметров вентиля единицы измерения ячейки timestep batch_size

примечания к тензорному потоку: многоуровневый анализ кода LSTM

Tensorflow dynamic rnn, построчная интерпретация исходного кода

Понимание исходного кода Tensorflow RNN

Примечания к анализу исходного кода Tensorflow RNN 1: базовая реализация RNNCell

Примечания к анализу исходного кода Tensorflow RNN 2: базовая реализация RNN

[tensorflow] Разница между static_rnn и dynamic_rnn