Взгляд на математическое машинное обучение

Интуитивное руководство по пониманию Word2vec

Предлагаем вашему вниманию третье сообщение в блоге из серии статей о математическом машинном обучении от А до Я. Эта статья будет посвящена алгоритмам Word2vec. Алгоритмы Word2vec выводят векторы слов. Словесные векторы лежат в основе многих систем обработки естественного языка (НЛП), которые захватили мир (Amazon Alexa, Google translate и т. Д.). О подробностях мы поговорим в следующих разделах. Но сначала позвольте мне разобрать алфавит со ссылками на другие сообщения в моем блоге.

A B C D* E F G* H I J K L* M N O P Q R S T U V W X Y Z

* обозначает статьи, находящиеся за платным доступом Medium

Словесные векторы, в чем главная идея? Все дело в этом контексте

Без дальнейших требований, давайте вставим наши ноги. Словесные векторы - это числовые представления слов, которые сохраняют семантические отношения между словами. Например, вектор слова кошка будет очень похож на вектор слова собака. Однако вектор для карандаша будет сильно отличаться от вектора слов для cat. И это сходство определяется частотой встречаемости двух рассматриваемых слов (например, [кошка, собака] или [кошка, карандаш ]), используются в том же контексте. Например, рассмотрим следующие предложения:

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

Влияние и последствия Word2vec

Чтобы получить представление о применении методов Word2vec, попробуйте следующее. Вызовите гугл-ученый. Введите задачу, связанную с НЛП (например, ответы на вопросы, чат-боты, машинный перевод и т. Д.). Отфильтруйте статьи, опубликованные после 2013 года (когда появились методы Word2vec). Получите долю статей, использующих векторы слов, к общему количеству статей. Бьюсь об заклад, эта пропорция будет довольно высокой. Чтобы сделать утверждение конкретным, векторы слов используются для:

  • Языковое моделирование
  • Чат-боты
  • Машинный перевод
  • Ответ на вопрос
  • … И многое другое

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

От исходного текста к векторам слов: подход высокого уровня

Теперь, обладая твердой интуицией, мы собираемся сначала обсудить высокоуровневую механику алгоритмов Word2vec. Мы доработаем детали в следующих разделах, пока не будем уверены, что знаем, как реализовать алгоритм Word2vec. Чтобы узнать векторы слов неконтролируемым образом (то есть без человеческих данных), мы должны определить и выполнить определенные задачи. Вот список этих задач высокого уровня.

  1. Создайте кортежи данных в формате [входное слово, выходное слово], где каждое слово представлено как горячие векторы, из необработанного текста
  2. Определите модель, которая может принимать горячие векторы в качестве входных и выходных данных для обучения.
  3. Определите функцию потерь, которая предсказывает правильное слово, которое фактически находится в контексте входного слова, чтобы оптимизировать модель.
  4. Оцените модель, убедившись, что похожие слова имеют похожие векторы слов.

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

Создание структурированных данных из необработанного текста

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

Кот столкнул стекло со стола

Данные, созданные на основе этого предложения, будут выглядеть следующим образом. Каждая строка после предложения представляет одну точку данных. Синее поле представляет собой одноразовое входное слово (среднее слово, называемое целевым словом), а красное поле представляет одноразовое выходное слово ( любое слово в контекстном окне, кроме среднего слова, называемое контекстными словами). Две точки данных создаются из одного контекстного окна. Размер контекстного окна определяется пользователем. Чем выше размер контекстного окна, тем лучше производительность модели. Но при большом размере окна контекста вы платите вычислительным временем, так как объем данных увеличивается. Не путайте целевое слово с целевым (правильным выводом) для нейронной сети, это две совершенно разные вещи.

Определение слоя внедрения и нейронной сети

Нейронная сеть, используемая для обучения на основе структурированных данных, определенных выше. Однако в этом есть одна изюминка! Чтобы прояснить, у вас есть следующие компоненты.

  • Пакет входных данных, представленных в виде горячих векторов
  • Пакет выходных данных в виде горячих векторов (только на этапе обучения)
  • Встраиваемый слой
  • Нейронная сеть

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

Слой встраивания: хранит все векторы слов.

Первым в нашей повестке дня является слой внедрения. Слой внедрения хранит векторы слов всех слов, найденных в словаре. Как вы можете представить, это огромная матрица (размером [размер словаря x размер встраивания]). Этот размер встраивания является параметром, настраиваемым пользователем. Чем он выше, тем лучше будет ваша модель. Но вы не получите особого впечатляющего прироста производительности / размера сверх определенной точки (скажем, размер встраивания 500). Эта гигантская матрица инициализируется случайным образом (как нейронная сеть) и постепенно настраивается в процессе оптимизации, чтобы выявить мощные векторы слов. Вот как это выглядит.

Нейронная сеть: сопоставляет векторы слов с выходами

Следующим на очереди идет последний блок LEGO нашей модели; нейронная сеть. Во время обучения нейронная сеть принимает входное слово и пытается предсказать выходное слово. Затем, используя функцию потерь, мы наказываем модель за неправильную классификацию и награждаем модель за правильную классификацию. Мы ограничим наш разговор обработкой одного ввода и одного вывода за раз. Однако на самом деле вы обрабатываете данные партиями (скажем, 64 точки данных). Давайте обрисуем точный процесс, используемый во время тренировки:

  1. Для заданного входного слова (целевого слова) найдите соответствующий вектор слова из слоя внедрения
  2. Подайте вектор слов в нейронную сеть, затем попытайтесь предсказать правильное выходное слово (контекстное слово)
  3. Сравнивая предсказание и истинное контекстное слово, вычислите потерю
  4. Используйте потерю вместе со стохастическим оптимизатором для оптимизации нейронной сети и слоя внедрения.

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

Собираем все вместе: от входов к модели к выходам

Зная все основы алгоритма Word2vec, мы можем собрать все части воедино. Поэтому после обучения этой модели все, что нам нужно сделать, это сохранить слой внедрения на диск. Тогда мы можем наслаждаться семантическими сохраненными векторами слов в любое время дня. Ниже мы видим, как выглядит полная картина.

Такое частичное расположение данных и макета модели известно как алгоритм пропуска грамм; алгоритм Word2vec. И это то, на чем мы собираемся сосредоточиться. Другой алгоритм называется моделью непрерывного мешка слов (CBOW).

Определение функции потерь: для оптимизации модели

Одна важная часть информации, которую мы до сих пор не обсуждали, но очень важная, - это функция потерь. Обычно стандартная перекрестная потеря энтропии softmax является хорошей функцией потерь для задачи классификации. Использование этой потери не очень практично для модели Word2vec, как для более простой задачи, такой как анализ настроений (где у вас есть 2 возможных выхода: положительный или отрицательный). Здесь все может получиться фанк. В реальной текстовой задаче, которая требует использования миллиардов слов, размер словарного запаса может легко вырасти до 100000 и более. Это затрудняет вычисление нормализации softmax. Это связано с тем, что полное вычисление softmax требует вычисления потерь кросс-энтропии по отношению ко всем выходным узлам.

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

SigmoidCrossEntropy - это потеря, которую мы можем определить для одного выходного узла, независимо от остальных узлов. Это делает его идеальным для нашей задачи, так как наш словарный запас может значительно увеличиться. Я не буду вдаваться в подробности этой потери. Вам не нужно понимать, как именно это реализовано, поскольку они доступны как встроенная функция в TensorFlow. Но понимание параметров, связанных с потерей (например, K), важно. Вывод состоит в том, что выборка softmax loss вычисляет потерю, рассматривая два типа объектов:

  • Индекс, заданный истинным идентификатором контекстного слова в векторе предсказания (слова в контекстном окне)
  • K индексы, которые указывают идентификаторы слов и считаются шумом (слова вне контекстного окна)

Я дополнительно визуализирую это, проиллюстрировав пример.

Реализация TensorFlow: алгоритм пропуска грамм

Здесь мы превратим то, что мы только что обсудили, в реализацию. Это доступно как упражнение здесь. В этом разделе мы собираемся реализовать следующее.

  • Генератор данных
  • Модель скип-грамм (с TensorFlow)
  • Запуск алгоритма skip-gram

Генератор данных

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

def generate_batch(batch_size, window_size):
  global data_index 
    
  # two numpy arras to hold target words (batch)
  # and context words (labels)
  batch = np.ndarray(shape=(batch_size), dtype=np.int32)
  labels = np.ndarray(shape=(batch_size, 1), dtype=np.int32)
    
  # span defines the total window size
  span = 2 * window_size + 1 
    
  # The buffer holds the data contained within the span
  queue = collections.deque(maxlen=span)
  
  # Fill the buffer and update the data_index
  for _ in range(span):
    queue.append(data[data_index])
    data_index = (data_index + 1) % len(data)
  

  for i in range(batch_size // (2*window_size)):
    k=0
    # Avoid the target word itself as a prediction
    for j in list(range(window_size))+list(range(window_size+1,2*window_size+1)):
      batch[i * (2*window_size) + k] = queue[window_size]
      labels[i * (2*window_size) + k, 0] = queue[j]
      k += 1 
    
    # Everytime we read num_samples data points, update the queue
    queue.append(data[data_index])
    
    # If end is reached, circle back to the beginning
    data_index = (data_index + np.random.randint(window_size)) % len(data)
    
  return batch, labels

Определение модели скип-грамм

Сначала мы определим некоторые гиперпараметры, необходимые для модели.

batch_size = 128 
embedding_size = 64 
window_size = 4 
num_sampled = 32 # Number of negative examples to sample.

batch_size определяет количество точек данных, которые мы обрабатываем в данный момент. Тогда embedding_size - это размер вектора слов. Следующий гиперпараметр window_size определяет размер контекстного окна, которое мы визуализировали выше. Наконец, num_sampled определяет количество отрицательных выборок в функции потерь (K). Затем мы определяем заполнители TensorFlow для входов и выходов.

tf.reset_default_graph() 
# Training input data (target word IDs). 
train_dataset = tf.placeholder(tf.int32, shape=[batch_size]) 
# Training input label data (context word IDs) 
train_labels = tf.placeholder(tf.int32, shape=[batch_size, 1])

Здесь train_dataset принимает список идентификаторов слов batch_size, который представляет выбранный набор целевых слов. Наконец, train_labels представляет batch_size список соответствующих контекстных слов для выбранных целевых слов. Затем мы определяем параметры модели, необходимые для определения модели: слой внедрения, а также веса и смещения нейронной сети.

################################################
#            Model variables                   #
################################################

# Embedding layer
embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))

# Neural network weights and biases
softmax_weights = tf.Variable(
    tf.truncated_normal([vocabulary_size, embedding_size],
                        stddev=0.1 / math.sqrt(embedding_size))
)
softmax_biases = tf.Variable(tf.random_uniform([vocabulary_size],-0.01,0.01))

Мы определили слой внедрения как переменную TensorFlow: embeddings. Затем мы определяем веса нейронной сети (softmax_weights) и смещения (softmax_biases). После этого мы определяем ключевую операцию, необходимую для подключения уровня внедрения к нейронной сети, чтобы совместно оптимизировать слой внедрения и нейронную сеть.

# Look up embeddings for a batch of inputs. 
embed = tf.nn.embedding_lookup(embeddings, train_dataset)

Функция tf.nn.embedding_lookup принимает наш слой внедрения в качестве входных данных и набор идентификаторов слов (train_dataset) и выводит соответствующие векторы слов в переменную embed. Определив функцию поиска встраивания, мы можем определить выбранную функцию потерь softmax, которую мы обсуждали выше.

################################################ 
#              Computes loss                   # 
################################################ 
loss = tf.reduce_mean(tf.nn.sampled_softmax_loss( weights=softmax_weights, biases=softmax_biases, inputs=embed, labels=train_labels, num_sampled=num_sampled, num_classes=vocabulary_size) )

Здесь функция tf.nn.sampled_softmax_loss принимает набор весов (softmax_weights), смещений (softmax_biases), набор векторов слов, соответствующих идентификаторам слов, найденным в train_dataset, идентификаторам правильных контекстных слов (train_labels), количеству выборок шума (num_sampled) и размер словарного запаса (vocabulary_size). Определив операции расчета выходных данных и потери, мы можем определить оптимизатор для оптимизации потерь по параметрам слоя встраивания и нейронной сети.

################################################ 
#                 Optimization                 # 
################################################ 
optimizer = tf.train.AdamOptimizer(0.001).minimize(loss)

Тогда мы получим нормализованный слой встраивания, сделав векторную величину равной 1.

################################################ 
#                For evaluation                # ################################################ 
norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keepdims=True)) 
normalized_embeddings = embeddings / norm

Запуск кода

Здесь мы собираемся обсудить детали того, как запустить ранее определенную модель TensorFlow. Сначала мы определяем session, а затем случайным образом инициализируем все переменные TensorFlow.

num_steps = 250001 
session = tf.InteractiveSession() 
# Initialize the variables in the graph
tf.global_variables_initializer().run() 
print('Initialized') 
average_loss = 0

Теперь для заранее определенного количества шагов мы генерируем пакеты данных: целевые слова (batch_data) и контекстные слова (batch_labels).

for step in range(num_steps): 
    # Generate a single batch of data
    batch_data, batch_labels = generate_batch( batch_size, window_size)

Затем для каждого сгенерированного пакета мы оптимизируем уровень внедрения и нейронную сеть, выполняя session.run([optimize, loss],...). Мы также вычисляем итоговый убыток, чтобы убедиться, что он со временем уменьшается.

    # Optimize the embedding layer and neural network
    # compute loss
    feed_dict = {train_dataset : batch_data, train_labels : batch_labels}
    _, l = session.run([optimizer, loss], feed_dict=feed_dict)

Здесь каждые 5000 шагов мы печатаем среднюю потерю в качестве наглядного пособия.

    if (step+1) % 5000 == 0:
      if step > 0:
        average_loss = average_loss / 5000

      print('Average loss at step %d: %f' % (step+1, average_loss))
      average_loss = 0

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

sg_embeddings = normalized_embeddings.eval() 
session.close()

Наконец, если вы визуализируете вложения с помощью алгоритма обучения многообразию, такого как t-SNE, вы получите следующее.

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

Заключение

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

Если вам нравятся мои рассказы о науке о данных и машинном обучении, подумайте о том, чтобы стать участником!



Хотите стать лучше в глубоких сетях и TensorFlow?

Ознакомьтесь с моими работами по этой теме.

[1] (Книга) TensorFlow 2 в действии - Мэннинг

[2] (Видеокурс) Машинный перевод на Python - DataCamp

[3] (Книга) Обработка естественного языка в TensorFlow 1 - Packt