Классификация текста сверточной нейронной сетью на основе TensorFlow

TensorFlow NLP
Классификация текста сверточной нейронной сетью на основе TensorFlow

оригинал:Implementing a CNN for Text Classification in TensorFlow

автор:Denny Britz

Перевод: Кайзер


Добро пожаловать на главный сайт Jizhi:Jizhi, двигатель, ведущий к эре интеллекта


Полный код можно найти на Github автора.

Модель, развернутая в этой статье, такая же, как у Ким Юна.Convolutional Neural Networks for Sentence Classificationсходство. Модели в статье хорошо справляются с различными задачами классификации текста, такими как анализ настроений, и теперь доступны в стандартном базовом пакете Deluxe.

Я предполагаю, что вы уже хорошо знакомы с теорией сверточных нейронных сетей для обработки естественного языка, если нет, то рекомендуется прочитать предыдущую статью:Статья, чтобы понять, как CNN используется для НЛП.

Уведомление!

Все коды в этой статье можно запускать и отлаживать в Jizhiyuan Post. Студенты, которым это нужно, могуткликните сюдаперейти к исходному сообщению



Данные и предварительная обработка

Набор данных, который будет использоваться на этот раз,Movie Review data from Rotten Tomatoes- также используется в оригинальной статье. Набор данных содержит 10 662 образца предложений из обзоров фильмов, половина положительных и отрицательных. Словарь набора данных составляет около 20 000. Обратите внимание, что, поскольку объем данных невелик, сильная модель склонна к переоснащению. Сам набор данных не разделяет обучение/тестирование, поэтому мы просто используем 10% в качестве тестового набора, а в исходной статье используется 10-кратная перекрестная проверка.

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

  1. Загрузите положительные и отрицательные образцы из файлов необработанных данных
  2. Используйте с оригинальной бумагойтот же кодЧистые данные
  3. Добавляйте специальный токен после каждого предложения, дополняйте каждую игру до максимальной длины предложения, здесь 59.
  4. Создайте словарь, сопоставьте каждое слово с целым числом от 0 до 18765, и каждое предложение станет вектором целых чисел.



Модель

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


Первый слой встраивает слова в низкоразмерные векторы. Следующий уровень использует ядра свертки разных размеров для свертки встраивания слов, 3-5 слов за раз, а затем максимальное объединение для получения длинного вектора признаков (Примечание: «вектор признаков» здесь — это вектор признаков, а не собственный вектор ), Классифицировано softmax после регуляризации Dropout.

Поскольку это учебная статья, я решил упростить модель исходной бумаги:

Конечно, добавить эти расширения несложно, всего несколько строк кода, упражнение вы можете увидеть в конце этой статьи. Давайте приступим к делу:




Код

Чтобы облегчить настройку гиперпараметров, мы поместили код в файл с именемTextCNNв классе используйтеinitФункция генерирует график модели.

import tensorflow as tf
import numpy as np
 
class TextCNN(object):
    """
    A CNN for text classification.
    Uses an embedding layer, followed by a convolutional, max-pooling and softmax layer.
    """
    def __init__(self, sequence_length, num_classes, vocab_size,
                 embedding_size, filter_sizes, num_filters):
                 # Implementation...

Для создания объекта нам необходимо передать следующие параметры:

  • sequence_length: длина предложения, мы дополняем предложение на этапе предварительной обработки, чтобы сохранить ту же длину (59);
  • num_classes: количество категорий выходного слоя, здесь 2 (хорошо/плохо);
  • vocab_size: размер словарного пространства, используемый для определения размера слоя внедрения: [vocabulary_size, embedding_size];
  • embedding_size: размер встраивания;
  • filter_size: количество слов, охваченных ядром свертки, количество каждого размера определяется выражениемnum_filtersОпределение, такое как [3,4,5], означает, что у нас есть3 * num_filtersКаждое ядро ​​свертки каждый раз скользит по 3, 4 и 5 словам.
  • num_filters: Как указано выше.

Заполнитель

class TextCNN(object):
   
    def __init__(self, sequence_length, num_classes, vocab_size, embedding_size, filter_sizes, num_filters):

        # Placeholders for input, output and dropout
        self.input_x = tf.placeholder(tf.int32, [None, sequence_length], name="input_x")
        self.input_y = tf.placeholder(tf.float32, [None, num_classes], name="input_y")
        self.dropout_keep_prob = tf.placeholder(tf.float32, name="dropout_keep_prob")

tf.placeholderСоздайте заполнители, переменные, которые будут переданы в нейронную сеть при обучении или тестировании. Второй элемент настройки — форма входного тензора.NoneУказывает, что это измерение может иметь любое значение, что позволяет сети обрабатывать любое количество пакетов данных.

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


Встраиваемый слой

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

class TextCNN(object):
   
    def __init__():
        ...

        with tf.device('/cpu:0'), tf.name_scope("embedding"):
            W = tf.Variable(tf.random_uniform([vocab_size, embedding_size], -1.0, 1.0),
        name="W")
            self.embedded_chars = tf.nn.embedding_lookup(W, self.input_x)
            self.embedded_chars_expanded = tf.expand_dims(self.embedded_chars, -1)

Обратите внимание на отступ здесь, приведенный выше код по-прежнемуdef init():часть.

  • tf.device("/cpu:0"): Принудительное использование ЦП. По умолчанию TensorFlow попытается вызвать графический процессор, но операции встраивания слов в настоящее время не поддерживаются графическим процессором, поэтому может быть сообщено об ошибке.
  • tf.name_scope: создать новыйИменованный домен«встраивание», чтобы операции имели лучшее наследование, когда TensorBoard визуализирует сеть.

W— наша матрица встраивания, которая также является целью, изученной во время обучения, инициализированной случайным равномерным распределением.tf.nn.embedding_lookupСоздается фактическая операция встраивания, и на выходе получается трехмерный тензор формы [Нет, длина_последовательности, размер_встраивания]

{:tensorflow:} Операция свертки TensorFlowconv2dПолучите 4-мерный тензор [пакет, ширина, высота, канал], и наш результат встраивания не имеет размерности канала, поэтому вручную добавьте его, чтобы он стал [Нет, длина последовательности, вложение, 1].


Слои свертки и максимального объединения

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

class TextCNN(object):
   
    def __init__():
        ...

        pooled_outputs = []
        for i, filter_size in enumerate(filter_sizes):
            with tf.name_scope("conv-maxpool-%s" % filter_size):
                # Convolution Layer
                filter_shape = [filter_size, embedding_size, 1, num_filters]
                W = tf.Variable(tf.truncated_normal(filter_shape, stddev=0.1), name="W")
                b = tf.Variable(tf.constant(0.1, shape=[num_filters]), name="b")
                conv = tf.nn.conv2d(
            self.embedded_chars_expanded,
            W,
            strides=[1, 1, 1, 1],
            padding="VALID",
            name="conv")
                # Apply nonlinearity
                h = tf.nn.relu(tf.nn.bias_add(conv, b), name="relu")
                # Max-pooling over the outputs
                pooled = tf.nn.max_pool(h,
                    ksize=[1, sequence_length - filter_size + 1, 1, 1],
                    strides=[1, 1, 1, 1],
                    padding='VALID',
                    name="pool")
                pooled_outputs.append(pooled)
 
        # Combine all the pooled features
        num_filters_total = num_filters * len(filter_sizes)
        self.h_pool = tf.concat(3, pooled_outputs)
        self.h_pool_flat = tf.reshape(self.h_pool, [-1, num_filters_total])

здесьWматрица свертки,hявляется выходом нелинейной функции активации. Каждое ядро ​​свертки просматривает все пространство встраивания слов, но количество прогонов варьируется. «ДЕЙСТВИТЕЛЬНОЕ» заполнение означает, что ядро ​​свертки не заполняет ребра, то есть «узкая свертка», а выходная форма — [1, sequence_length — filter_size + 1, 1, 1].

Максимальное объединение делает наш тензор формы [batch_size, 1, 1, num_filters], а последний бит соответствует функции. Объедините все объединенные выходные тензоры в длинный вектор признаков формы [batch_size, num_filter_total]. В TensorFlow, если вам нужно сгладить многомерный вектор, вы можетеtf.reshapeустановить в-1.


Dropout

DropoutВероятно, самый популярный метод регуляризации сверточных нейронных сетей. Принцип, лежащий в основе этого, прост: случайным образом «сбрасывать» некоторые нейроны, чтобы предотвратить их совместную адаптацию и заставить их самостоятельно изучать полезные функции. Доля не выброшенных мы проходимdropout_keep_probЭта переменная контролируется и устанавливается на 0,5 во время обучения и на 1 во время оценки.

class TextCNN(object):
   
    def __init__():
        ...

        # Add dropout
        with tf.name_scope("dropout"):
            self.h_drop = tf.nn.dropout(self.h_pool_flat, self.dropout_keep_prob)

Результаты и прогнозы

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

class TextCNN(object):
   
    def __init__():
        ...

        with tf.name_scope("output"):
            W = tf.Variable(tf.truncated_normal([num_filters_total, num_classes], stddev=0.1), name="W")
            b = tf.Variable(tf.constant(0.1, shape=[num_classes]), name="b")
            self.scores = tf.nn.xw_plus_b(self.h_drop, W, b, name="scores")
            self.predictions = tf.argmax(self.scores, 1, name="predictions")

здесьtf.nn.xw_plus_bдаWx+bупаковка.


Потери и точность

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

class TextCNN(object):
   
    def __init__():
        ...

        # Calculate mean cross-entropy loss
        with tf.name_scope("loss"):
            losses = tf.nn.softmax_cross_entropy_with_logits(self.scores, self.input_y)
            self.loss = tf.reduce_mean(losses)

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

Мы также определяем выражение для точности, количество, которое полезно при отслеживании обучения и тестирования.

class TextCNN(object):
   
    def __init__():
        ...

        # Calculate Accuracy
        with tf.name_scope("accuracy"):
            correct_predictions = tf.equal(self.predictions, tf.argmax(self.input_y, 1))
            self.accuracy = tf.reduce_mean(tf.cast(correct_predictions, "float"), name="accuracy")

визуализация сети

На данный момент мы завершили определение сети, визуализируемой через TensorBoard следующим образом:




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

Прежде чем приступить к процессу обучения, все же необходимо немного разобраться в {:tensorflow:}TensorFlow про "сеансы" (Session) и график (Graphs) основная концепция. Если вы уже хорошо знакомы с этим набором, вы можете пропустить этот раздел.

Официально запущена версия 1.5 TensorFlow, в которой представлен механизм «динамического графа», поэтому использование, описанное в этой статье, не является последним.

В TensorFlow сеанс — это среда, в которой выполняются операции графа вычислений, включая состояние переменных и очередей. Каждая сессия выполняет граф, если сессия не вызывается явно, то при создании переменных и операций она использует дефолтную, созданную TF. может запускать командыsession.as_default()Измените сеанс по умолчанию.

Граф (Graph) содержит операции и тензоры, и каждая программа может содержать несколько графов, но большинству программ нужен только один граф. Мы можем повторно использовать график в нескольких сеансах, но мы не можем вызывать несколько графиков в одном сеансе. TensorFlow создаст график по умолчанию, или вы можете создать новый график самостоятельно и установить его по умолчанию. Явное создание сеансов и графиков гарантирует, что ресурсы должным образом освобождаются, когда они не нужны.

with tf.Graph().as_default():
    session_conf = tf.ConfigProto(
      allow_soft_placement=FLAGS.allow_soft_placement,
      log_device_placement=FLAGS.log_device_placement)
    sess = tf.Session(config=session_conf)
    with sess.as_default():
        # Code that operates on the default graph and session comes here...

allow_soft_placementЭтот параметр позволяет TensorFlow автоматически настраивать устройство, если указанное устройство не существует. Например, если наш код помещает операцию на GPU, но работает на машине без GPU, если нетallow_soft_placementсообщит об ошибке.

если установленоlog_device_placement, журнал TensorFlow будет хранить файл журнала на указанном устройстве (ЦП или ГП). Это очень полезно для отладки.FLAGSаргументы командной строки, полученные программой.


Создание CNN, минимизация потерь

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

cnn = TextCNN(
    sequence_length=x_train.shape[1],
    num_classes=2,
    vocab_size=len(vocabulary),
    embedding_size=FLAGS.embedding_dim,
    filter_sizes=map(int, FLAGS.filter_sizes.split(",")),
    num_filters=FLAGS.num_filters)

Следующее, что нужно сделать, это оптимизировать функцию сетевых потерь. В TensorFlow есть несколько встроенных оптимизаторов, которые здесь и используются.Adamоптимизатор.

global_step = tf.Variable(0, name="global_step", trainable=False)
optimizer = tf.train.AdamOptimizer(1e-4)
grads_and_vars = optimizer.compute_gradients(cnn.loss)
train_op = optimizer.apply_gradients(grads_and_vars, global_step=global_step)

train_opэто новая операция, используемая для обновления градиента параметров при каждом запускеtrain_opЭто просто тренировка. TensorFlow автоматически определяет, какие параметры «поддаются обучению», и вычисляет их градиенты. Определенныйglobal_stepПеременные и переданные оптимизатору, вы можете позволить TensorFlow сделать подсчет. каждый запускtrain_op,global_stepПросто +1.


Резюме

(Не краткое изложение этой статьи) {:tensorflow:} имеет концепцию, называемуюsummaries, позволяя пользователям отслеживать и визуализировать процесс обучения и оценки. Например, вам может понадобиться узнать, как функция потерь и точность меняются со временем. Также могут быть обнаружены более сложные величины, такие как гистограммы слоев активации, Сводки — это сериализованные объекты, черезSummaryWriterЗапись на жесткий диск.

# Output directory for models and summaries
timestamp = str(int(time.time()))
out_dir = os.path.abspath(os.path.join(os.path.curdir, "runs", timestamp))
print("Writing to {}\n".format(out_dir))
 
# Summaries for loss and accuracy
loss_summary = tf.scalar_summary("loss", cnn.loss)
acc_summary = tf.scalar_summary("accuracy", cnn.accuracy)
 
# Train Summaries
train_summary_op = tf.merge_summary([loss_summary, acc_summary])
train_summary_dir = os.path.join(out_dir, "summaries", "train")
train_summary_writer = tf.train.SummaryWriter(train_summary_dir, sess.graph_def)
 
# Dev summaries
dev_summary_op = tf.merge_summary([loss_summary, acc_summary])
dev_summary_dir = os.path.join(out_dir, "summaries", "dev")
dev_summary_writer = tf.train.SummaryWriter(dev_summary_dir, sess.graph_def)

Здесь мы отслеживаем сводки по обучению и оценке отдельно, некоторые величины повторяются, но многие нужно видеть только во время обучения (например, значения обновления параметров).tf.merge_summaryФункции упрощают объединение нескольких сводок в одну операцию.


контрольно-пропускной пункт

Еще одна особенность TensorFlow —checkpointing- Храните параметры модели для чрезвычайных ситуаций. Контрольные точки можно использовать для продолжения тренировки, которая была прервана ранее, или для досрочного завершения, чтобы получить наилучшие параметры. Контрольно-пропускные пункты пройденыSaverХранение объектов.

# Checkpointing
checkpoint_dir = os.path.abspath(os.path.join(out_dir, "checkpoints"))
checkpoint_prefix = os.path.join(checkpoint_dir, "model")
# Tensorflow assumes this directory already exists so we need to create it
if not os.path.exists(checkpoint_dir):
    os.makedirs(checkpoint_dir)
saver = tf.train.Saver(tf.all_variables())

инициализация переменных

Перед обучением модели нам нужно инициализировать все параметры в графе вычислений:

sess.run(tf.initialize_all_variables())

initialize_all_variablesУдобно инициализировать все переменные сразу.


Определение отдельных шагов обучения

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

def train_step(x_batch, y_batch):
    """
    A single training step
    """
    feed_dict = {
      cnn.input_x: x_batch,
      cnn.input_y: y_batch,
      cnn.dropout_keep_prob: FLAGS.dropout_keep_prob
    }
    _, step, summaries, loss, accuracy = sess.run(
        [train_op, global_step, train_summary_op, cnn.loss, cnn.accuracy],
        feed_dict)
    time_str = datetime.datetime.now().isoformat()
    print("{}: step {}, loss {:g}, acc {:g}".format(time_str, step, loss, accuracy))
    train_summary_writer.add_summary(summaries, step)

feed_dictДанные в нем будут отправляться в нейросеть через ноды-заполнители, и все ноды должны иметь значения, иначе TensorFlow снова сообщит об ошибке. Другой способ ввода данныхqueues, которые не будут обсуждаться в этой статье.

Далее проходимsession.run()бегатьtrain_op, возвращаемое значение является результатом операции, которую мы хотим оценить. Уведомлениеtrain_opВозвращаемого значения как такового нет, оно просто обновляет параметры сети. Наконец, мы распечатываем функцию потерь и точность этого раунда обучения и сохраняем сводку на диск. Потери и точность могут сильно различаться от партии к партии, потому что размер партии невелик. Также из-за использования отсева производительность в начале тренировочного процесса может быть хуже, чем в процессе оценки.

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

def dev_step(x_batch, y_batch, writer=None):
    """
    Evaluates model on a dev set
    """
    feed_dict = {
      cnn.input_x: x_batch,
      cnn.input_y: y_batch,
      cnn.dropout_keep_prob: 1.0
    }
    step, summaries, loss, accuracy = sess.run(
        [global_step, dev_summary_op, cnn.loss, cnn.accuracy],
        feed_dict)
    time_str = datetime.datetime.now().isoformat()
    print("{}: step {}, loss {:g}, acc {:g}".format(time_str, step, loss, accuracy))
    if writer:
        writer.add_summary(summaries, step)

тренировочный цикл

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

atch_iter(
    zip(x_train, y_train), FLAGS.batch_size, FLAGS.num_epochs)
# Training loop. For each batch...
for batch in batches:
    x_batch, y_batch = zip(*batch)
    train_step(x_batch, y_batch)
    current_step = tf.train.global_step(sess, global_step)
    if current_step % FLAGS.evaluate_every == 0:
        print("\nEvaluation:")
        dev_step(x_dev, y_dev, writer=dev_summary_writer)
        print("")
    if current_step % FLAGS.checkpoint_every == 0:
        path = saver.save(sess, checkpoint_prefix, global_step=current_step)
        print("Saved model checkpoint to {}\n".format(path))

здесьbatch_iterэто вспомогательная функция, которую я написал для пакетных данных,tf.train.global_stepможет вернутьсяglobal_stepзначение . Полный код тренировочного процесса можно увидетьздесь.




Результаты визуализации TensorBoard

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

tensorboard --logdir /PATH_TO_CODE/runs/1449760558/summaries/

Обучение с параметрами по умолчанию (128-мерное встраивание слов, размер ядра свертки 3, 4, 5, частота отсева 0,5, 128 ядер свертки каждого размера) дает следующую таблицу потерь и точности (синий цвет — данные обучения, красный — 10% тестовых данных). ).


Из рисунка видно несколько вещей:

  • Наша кривая обучения не является гладкой из-за меньшего размера партии. Синяя линия была бы более гладкой, если бы размер партии был больше (или даже оценивался на всей обучающей выборке).
  • Поскольку точность теста значительно ниже точности обучения, наша сеть несколько переобучена, что требует большего количества данных (набор данных MR невелик), более сильной регуляризации или меньшего количества параметров модели. Например, я попытался добавить штраф L2 к весам последнего слоя, что повысило точность до 76%, что близко к результатам исходной статьи.
  • Потери и точность обучения в начале хуже, чем в процессе тестирования из-за отсева.

Расширение и практика

Есть несколько упражнений, которые помогут оптимизировать нашу модель:

  • Инициализируйте эмбеддинги с помощью предварительно обученного word2vec, для этого вам нужны 300-мерные эмбеддинги слов.
  • Ограничьте норму L2 вектора весов последнего слоя, как описано в оригинальной статье. Можно определить новую операцию для повторного обновления весов после каждого шага обучения.
  • Добавьте регуляризацию L2, чтобы предотвратить переоснащение, и экспериментально увеличьте процент отсева.
  • Сводка гистограмм обновлений веса была добавлена ​​и визуализирована с помощью TensorBoard.

Рекомендуемое чтение

Семинар TensorFlow «Фестиваль технологического опыта для девочек»

Этот комментарий ядовит! ——Общая процедура для классификации текста

Я сделал робота по имени Серсея, но она просто хотела, чтобы сир Грегор убивал меня на каждом шагу.