- Оригинальный адрес:РЕКУРРЕНТНЫЕ НЕЙРОННЫЕ СЕТИ (RNN) – ЧАСТЬ 2: КЛАССИФИКАЦИЯ ТЕКСТА
- Оригинальный автор:GokuMohandas
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:Changkun Ou
- Корректор:yanqiangmiffy, TobiasLee
Краткое содержание статей из этой серии
- Рекуррентная нейронная сеть RNN, серия 1: базовая RNN против CHAR-RNN
- Рекуррентная нейронная сеть RNN, серия 2: классификация текста
- Рекуррентная нейронная сеть RNN, серия 3: кодировщик, декодер
- Рекуррентная нейронная сеть RNN, серия 4: механизм внимания
- Рекуррентные нейронные сети RNN, серия 5: Пользовательские единицы
Рекуррентная нейронная сеть RNN, серия 2: классификация текста
В первой статье мы увидели, как реализовать простую архитектуру RNN с помощью TensorFlow. Теперь мы воспользуемся этими компонентами и применим их к классификации текста. Основное отличие состоит в том, что вместо ввода последовательностей фиксированной длины, как в модели CHAR-RNN, мы используем последовательности переменной длины.
Категоризация текста
Набор данных для этой задачи был выбран из Корнельского университета.Набор данных о полярности настроений в предложениях v1.0, который содержит 5331 предложение положительного и отрицательного настроения. Это очень маленький набор данных, но его достаточно, чтобы продемонстрировать, как использовать рекуррентные нейронные сети для классификации текста.
Нам нужно выполнить некоторую предварительную обработку, в основном включающую маркировку ввода, дополнительные маркеры (заполнение и т. д.). Пожалуйста, обратитесь кполный кодвыучить больше.
этап предварительной обработки
- Очистите предложения и разбейте их на токены;
- Преобразование предложений в числовые токены;
- Сохраните длину последовательности каждого предложения.
Как показано на рисунке выше, мы хотим предсказать тональность предложения, как только вычисления будут завершены. Добавление дополнительных отступов создаст слишком много шума, и ваша модель не будет работать должным образом.Уведомление: Единственная причина, по которой мы дополняем последовательность, заключается в том, что ее нужно вводить в 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_rnn
,иstate
Это снова последний соответствующий вывод, мы можем проверить, посмотрев наstate
чтобы найти соответствующий выход. Обратите внимание, что мы должны принятьstate[0]
, потому что вернулсяstate
представляет собой набор тензоров.
Что еще нужно отметить: я не используюinitial_state
, а непосредственно кdynamic_rnn
настраиватьdtype
. также,dropout
будет основываться наforward_only
или нет, передается в качестве параметраstep()
.
делать вывод
В общем, помимо прогнозов для отдельных предложений, хотелось бы делать прогнозы для общего настроения предложений с кучей выборок. Я хотел бы видеть, что после того, как RNN читает каждое слово, предыдущие оценки слов сохраняются в памяти, чтобы увидеть, как меняются прогнозируемые оценки. Пример выглядит следующим образом (значение ближе к 0 означает ближе к отрицательному настроению):
Уведомление: Это очень простая модель с очень ограниченным набором данных. Основная цель — просто прояснить, как он устроен и как работает. Для повышения производительности попробуйте использовать большие наборы данных и рассмотрите конкретные сетевые архитектуры, такие как модель внимания, встраивание слов с учетом концепции, символизация имени и т. д.
Маскировка потерь (здесь не требуется)
Наконец, посчитаем стоимость. Вы могли заметить, что мы не маскировали потери, потому что разделили соответствующие выходные данные только для вычисления функции потерь. Однако для других задач, таких как машинный перевод, наши выходные данные, вероятно, также будут поступать от токенов-заполнителей. Мы не хотим рассматривать эти выходные данные, потому что переданный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,внешний интерфейс,задняя часть,продукт,дизайнЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.