[Перевод] Рекуррентная нейронная сеть RNN, серия 2: классификация текста

искусственный интеллект Программа перевода самородков Нейронные сети React.js

Краткое содержание статей из этой серии

  1. Рекуррентная нейронная сеть RNN, серия 1: базовая RNN против CHAR-RNN
  2. Рекуррентная нейронная сеть RNN, серия 2: классификация текста
  3. Рекуррентная нейронная сеть RNN, серия 3: кодировщик, декодер
  4. Рекуррентная нейронная сеть RNN, серия 4: механизм внимания
  5. Рекуррентные нейронные сети RNN, серия 5: Пользовательские единицы

Рекуррентная нейронная сеть RNN, серия 2: классификация текста

В первой статье мы увидели, как реализовать простую архитектуру RNN с помощью TensorFlow. Теперь мы воспользуемся этими компонентами и применим их к классификации текста. Основное отличие состоит в том, что вместо ввода последовательностей фиксированной длины, как в модели CHAR-RNN, мы используем последовательности переменной длины.

Категоризация текста

Набор данных для этой задачи был выбран из Корнельского университета.Набор данных о полярности настроений в предложениях v1.0, который содержит 5331 предложение положительного и отрицательного настроения. Это очень маленький набор данных, но его достаточно, чтобы продемонстрировать, как использовать рекуррентные нейронные сети для классификации текста.

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

этап предварительной обработки

  1. Очистите предложения и разбейте их на токены;
  2. Преобразование предложений в числовые токены;
  3. Сохраните длину последовательности каждого предложения.

Screen Shot 2016-10-05 at 7.32.36 PM.png
Screen Shot 2016-10-05 at 7.32.36 PM.png

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

Модель

Код:

class model(object):

    def __init__(self, FLAGS):

        # 占位符
        self.inputs_X = tf.placeholder(tf.int32,
            shape=[None, None], name='inputs_X')
        self.targets_y = tf.placeholder(tf.float32,
            shape=[None, None], name='targets_y')
        self.dropout = tf.placeholder(tf.float32)

        # RNN 单元
        stacked_cell = rnn_cell(FLAGS, self.dropout)

        # RNN 输入
        with tf.variable_scope('rnn_inputs'):
            W_input = tf.get_variable("W_input",
                [FLAGS.en_vocab_size, FLAGS.num_hidden_units])

        inputs = rnn_inputs(FLAGS, self.inputs_X)
        #initial_state = stacked_cell.zero_state(FLAGS.batch_size, tf.float32)

        # RNN 输出
        seq_lens = length(self.inputs_X)
        all_outputs, state = tf.nn.dynamic_rnn(cell=stacked_cell, inputs=inputs,
            sequence_length=seq_lens, dtype=tf.float32)

        # 由于使用了 seq_len[0],state 自动包含了上一次的对应输出
        # 因为 state 是一个带有张量的元组
        outputs = state[0]

        # 处理 RNN 输出
        with tf.variable_scope('rnn_softmax'):
            W_softmax = tf.get_variable("W_softmax",
                [FLAGS.num_hidden_units, FLAGS.num_classes])
            b_softmax = tf.get_variable("b_softmax", [FLAGS.num_classes])

        # Logits
        logits = rnn_softmax(FLAGS, outputs)
        probabilities = tf.nn.softmax(logits)
        self.accuracy = tf.equal(tf.argmax(
            self.targets_y,1), tf.argmax(logits,1))

        # 损失函数
        self.loss = tf.reduce_mean(
            tf.nn.sigmoid_cross_entropy_with_logits(logits, self.targets_y))

        # 优化
        self.lr = tf.Variable(0.0, trainable=False)
        trainable_vars = tf.trainable_variables()
        # 使用梯度截断来避免梯度消失和梯度爆炸
        grads, _ = tf.clip_by_global_norm(
            tf.gradients(self.loss, trainable_vars), FLAGS.max_gradient_norm)
        optimizer = tf.train.AdamOptimizer(self.lr)
        self.train_optimizer = optimizer.apply_gradients(
            zip(grads, trainable_vars))

        # 下面是用于采样的值
        # (在每个单词后生成情绪)

        # 取所有输出作为第一个输入序列
        # (由于采样,只需一个输入序列)
        sampling_outputs = all_outputs[0]

        # Logits
        sampling_logits = rnn_softmax(FLAGS, sampling_outputs)
        self.sampling_probabilities = tf.nn.softmax(sampling_logits)

        # 保存模型的组件
        self.global_step = tf.Variable(0, trainable=False)
        self.saver = tf.train.Saver(tf.all_variables())

    def step(self, sess, batch_X, batch_y=None, dropout=0.0,
        forward_only=True, sampling=False):

        input_feed = {self.inputs_X: batch_X,
                      self.targets_y: batch_y,
                      self.dropout: dropout}

        if forward_only:
            if not sampling:
                output_feed = [self.loss,
                               self.accuracy]
            elif sampling:
                input_feed = {self.inputs_X: batch_X,
                              self.dropout: dropout}
                output_feed = [self.sampling_probabilities]
        else: # 训练
            output_feed = [self.train_optimizer,
                           self.loss,
                           self.accuracy]

        outputs = sess.run(output_feed, input_feed)

        if forward_only:
            if not sampling:
                return outputs[0], outputs[1]
            elif sampling:
                return outputs[0]
        else: # 训练
            return outputs[0], outputs[1], outputs[2]

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

В этой модели мы снова используемdynamic_rnn, но на этот раз мы предоставляемsequence_lengthЗначение параметра, которое представляет собой список, содержащий длину каждой последовательности. Таким образом, мы можем избежать ненужных вычислений после последнего слова входной последовательности.lengthФункция используется для получения длины этого списка, как показано ниже. Конечно, мы также можем вычислить снаружиseq_len, а затем передайте его через заполнитель.

def length(data):
    relevant = tf.sign(tf.abs(data))
    length = tf.reduce_sum(relevant, reduction_indices=1)
    length = tf.cast(length, tf.int32)
    return length

Поскольку наш токен-заполнитель равен 0, свойство знака каждого токена можно использовать, чтобы определить, является ли он токеном-заполнителем. Если вход больше 0, тоtf.signравно 1; если на входе 0, тоtf.signравно 0. Таким образом, мы можем пройтись по индексу столбца, чтобы получить количество токенов с положительным значением знака. На данный момент мы можем предоставить эту длину дляdynamic_rnn.

Уведомление: мы можем легко вычислить извнеseq_lensи передайте его как заполнитель. так что мы не должны зависеть отPAD_ID = 0эта природа.

Получив все выходные данные и конечные состояния RNN, мы хотим разделить соответствующие выходные данные. Для каждого входа будет свой соответствующий выход, так как каждый вход не обязательно имеет одинаковую длину. Так как мы будемseq_lenперешел кdynamic_rnnstateЭто снова последний соответствующий вывод, мы можем проверить, посмотрев наstateчтобы найти соответствующий выход. Обратите внимание, что мы должны принятьstate[0], потому что вернулсяstateпредставляет собой набор тензоров.

Что еще нужно отметить: я не используюinitial_state, а непосредственно кdynamic_rnnнастраиватьdtype. также,dropoutбудет основываться наforward_onlyили нет, передается в качестве параметраstep().

делать вывод

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

Screen Shot 2016-10-05 at 8.34.51 PM.png
Screen Shot 2016-10-05 at 8.34.51 PM.png

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

Маскировка потерь (здесь не требуется)

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

# 向量化 logits 和目标
targets = tf.reshape(targets, [-1]) # 将张量 targets 转为向量
losses = tf.nn.sparse_softmax_cross_entropy_with_logits(logits, targets)
mask = tf.sign.(tf.to_float(targets)) # targets 为 0 则输出为 0, target < 0 则输出为 -1, 否则 为 1
masked_losses = mask*losses # 填充符所在位置的贡献为 0

Сначала мы должны векторизовать логиты и цели. Чтобы векторизовать логиты, лучше всего использоватьdynamic_rnnВыход векторизован как[-1,num_hidden_units]форма, затем умножается на веса softmax[num_hidden_units,num_classes]. Посредством операции маскирования потерь потери, вызванные положением наполнителя, могут быть устранены.

код

Репозиторий GitHub(Обновление, следите за обновлениями!)

Справочник по изменению формы тензора

необработанный текстXформа[N,]иyформа[N, C]C— количество выходных классов (это делается вручную, но нам нужно использовать однократное кодирование для случаев с несколькими классами).

потомXконвертируется в токен и заполняется, становится[N, <max_len>]. Нам также нужно передать форму как[N,]изseq_lenпараметр, содержащий длину каждого предложения.

в настоящее времяX,seq_lenиyПо этой модели сначала встраивайте как[NXD], где D — размерность вложения.Xот[N, <max_len>]конвертировано в[N, <max_len>, D]. Напомним, что здесь X имеет промежуточное представление, которое закодировано горячим способом как[N, <max_len>, <num_words>]. Но нам не нужно этого делать, потому что нам нужно просто использовать индекс соответствующего слова, а затем взять значение из веса встраивания слова.

Нам нужно встроить этоXПерейти кdynamic_rnnи вернутьсяall_outputs([N, <max_len>, D])а такжеstate([1, N, D]). Так как мы вошлиseq_lens, что для нас является последним соответствующим состоянием. С размерной точки зрения, вы можете видеть,all_outputs- это общий вывод RNN для каждого слова в каждом предложении. Тем не мение,stateПросто последний соответствующий вывод для каждого предложения.

Теперь мы хотим ввести веса softmax, но перед этим нам нужно сделать это, взяв первый индекс (state[0]), чтобы изменить состояние с[1,N,D]преобразовать в[N,D]. Этого можно достичь, комбинируя с отягощениями softmax.[D,C]Скалярный продукт , чтобы получить форму как[N,C]Вывод. Среди них мы выполняем экспоненциальную операцию softmax, затем регуляризируем, и окончательная объединенная форма[N,C]изtarget_yвычислить функцию потерь.

Уведомление: Если вы используете базовую RNN или GRU, начните сdynamic_rnnвозвращениеall_outputsиstateФорма такая же. Но если вы используете LSTM,all_outputsформа[N, <max_len>, D]иstateформа[1, 2, N, D].


Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,React,внешний интерфейс,задняя часть,продукт,дизайнЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.