Обзор

Чат-бот — это тип программного обеспечения, которое может помочь людям, автоматизируя разговоры и взаимодействуя с ними через платформы обмена сообщениями, или это просто форма искусственного интеллекта (ИИ), которая взаимодействует с людьми. Его можно использовать для обслуживания клиентов, маршрутизации запросов, сбора информации и простой болтовни. На рынке есть несколько гаджетов, специально разработанных для виртуальной помощи, таких как Alexa, Google home и т. Д., И некоторые могут обнаружить, что виртуальная помощь на любом веб-сайте, предоставляющем онлайн-сервис, и определение разницы между ответами, сгенерированными машиной или человеком, может быть непростым. , когда-нибудь задумывались, как они работают? давайте создадим нашего собственного чат-бота с нуля, используя TensorFlow, без использования каких-либо предопределенных API

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

Проблема была размещена на kaggle, где вы можете найти набор данных.

Понятно, что нам нужна модель SEQtoSEQ, так как входные данные для чат-бота — это последовательность, а сгенерированный вывод — тоже последовательность. Есть несколько других архитектур, которые мы можем применить для этой задачи, но только на основе Transformer. подход обеспечивает возможность самовнимания, такие модели, как BERT, GPT, также могут использоваться и оказываются очень мощными для задач НЛП, но, тем не менее, они также основаны на преобразователе. Если мы можем построить трансформер, то наверняка сможем построить и его!

Требования: ванильный кодер-декодер, механизм внимания

Давайте начнем….

Быстрый просмотр набора данных

Мы предоставили набор данных Cornell Movie-Dialogs Corpus, и он содержит более 130 тысяч уточненных реплик из 617 фильмов, на самом деле было 220 тысяч пар диалогов, но они очистили данные, т.е. были удалены слишком длинные диалоги, предоставлено 7 признаков.

  1. id: серийный номер
  2. question: строка, представляющая вопросы.
  3. answer: строка, представляющая ответы.
  4. question_as_int: токенизированный вопрос
  5. answer_as_int: токенизированный ответ
  6. question_len: нет. рассматриваемых слов
  7. answer_len: нет. слов в ответ

Нас интересуют функции «вопрос» и «ответ».

Чат-боты обычно предназначены для конкретных задач, если есть чат-бот, который обслуживает только приложение для доставки еды, обученный набору данных, который полностью отличается от набора данных, на котором чат-бот помогает приложению онлайн-медицины. Точно так же для этой проблемы kaggle мы предоставили набор данных фильма может показаться, что это не относится к какой-либо задаче, но на самом деле это относится к тому, как люди взаимодействуют друг с другом в повседневной жизни или обычной болтовне, поскольку эти диалоги в фильмах являются не чем иным, как случайным разговором между людьми, однако этот чат-бот может отвечать на вещи, которые звучат слишком драматично и натянуто, например диалоги Тома Круза, Джонни Депа и т. д. интересно, не так ли?

Исследовательский анализ данных

Давайте посмотрим на частотное распределение слов.

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

давайте посмотрим на распределение длины предложения.

длина слова в диапазоне 2–7 чаще встречается в наборе данных, а длина больше 20 встречается крайне редко.

Этого достаточно для EDA, давайте приступим к моделированию…

Различные подходы к обучению

  • Ванильный кодер-декодер
  • Кодер-декодер на основе LSTM с механизмом внимания Багданау
  • Трансформер

1. Ванильный кодер-декодер

Я определил три класса, и все они расширяют класс tf.keras.Model, который называется подклассом модели в TensorFlow.

Я не буду объяснять кодировщик-декодер Vanilla, так как это одно из условий для понимания трансформатора, объясню в следующий раз!

Всего обучаемых параметров: 23 миллиона, эпох: 20, перестал учиться после 7 эпох, один из недостатков этой простой модели.

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

2. Кодер-декодер на основе механизма внимания

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

Извините за изображения, так как я их создал сам, я не графический дизайнер (-_-). В любом случае, взгляните на гиперпараметры.

Всего обучаемых параметров: 27,5 миллионов, Эпох: 20, перестал учиться после 7 эпох, но чему бы он ни научился, научился лучше, чем ванильная модель, увеличил единицы LSTM на 64, так как это давало лучшие результаты.

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

есть некоторая проблема с обучением, так как оно продолжает генерировать «я не уверен», что неудовлетворительно.

Я ничего не объяснял об этих двух моделях, не стесняйтесь ссылаться на блокнот

Теперь давайте начнем с трансформатора, который я подробно объясню, трансформатор — это вся цель этого блога.

3. Трансформатор

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

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

3.1 Подготовка данных

Мы дали файл CSV, я преобразовал его в фрейм данных pandas и взял только два столбца «вопрос» и «ответ», а затем очистил его, т.е. специальные символы, распаковка и т. д., а затем разделили его на кадры данных train/validation. Поскольку нейронная сеть не понимает английский язык, все, что им нужно, — это числовое представление, поэтому для преобразования каждого слова в числовое число нам нужна токенизация, я использовал subword, опять же красивое название, но это не так.

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

получил tokenizer_q.vocab_size: 27513, tokenizer_a.vocab_siz: 27358

для 'question' размер словарного запаса составляет 27513, т. е. от 0 до 27512, добавление 27513 в начале и 27514 в конце, представляющих ‹start› и ‹end› соответственно, я действительно не добавлял ‹start ›, ‹end› в предложениях, просто добавляя в токенизированную форму. Точно так же я сделал для «ответ». Обернув функцию кодирования python в операцию TensorFlow, которая выполняет ее с нетерпением, она получит привилегии TensorFlow.

Создание объекта набора данных TensorFlow для генерации данных во время обучения предотвращает полную загрузку данных в память, загружает то, что нужно. После получения набора данных применяю tf_encode(), а затем заполняю, чтобы сделать все предложения одинаковой длины, затем применяю prefetch(). Если я нахожусь в эпохе 20, то эта функция подготавливает пакет для эпохи 21, поэтому, когда начинается эпоха 21, модель получает данные в кратчайшие сроки, он работает волшебно, когда пакеты большие, я использую его только для того, чтобы немного ускорить работу.

Итак, мы приготовили еду для трансформера, именно то, что он хочет!

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

3.2 Позиционное кодирование

Преобразователь не имеет RNN для сохранения информации о последовательности и относительных положениях слов, поэтому для захвата относительных положений мы выполняем позиционное кодирование. Это делается после представления каждого токена в его вектор внедрения, т.е. если у нас есть {23, 38, 1, 0, 0} в качестве токенов, то после передачи его на слой внедрения это токенизированное предложение будет преобразовано в размерную матрицу 5X100, 100 встраивается длина вектора. Чтобы добавить информацию о положении, мы используем функции sin и cos. Окончательная форма кодирования положения будет 5X100, на изображении выше i: векторный индекс внедрения и pos: индекс слова/токена, существует несколько методов позиционного кодирования, но это то, что упоминается в исследовательской статье. Интуиция позиционного кодирования

3.3 Маска подкладки

Если у нас есть {23, 38, 1, 0, 0}, тогда маска заполнения будет {0, 0, 0, 1, 1}, '1' для замаскированных токенов, не будет вычисление 'собственного внимания' для замаскированных токенов.

3.4 Упреждающая маска

Эта маскировка предназначена только для части декодера, так как мы будем генерировать/предсказывать слова одно за другим, то есть, если мы генерируем первое слово, мы не будем вычислять «самовнимание» для второго слова. который еще не был сгенерирован, если было предсказано 3 слова, то маскирование будет выполнено для оставшихся слов, которые будут предсказаны.

3.5 Расчет собственного внимания

Должно быть, вы видели это преобразование матрицы, это концепция, которая отличает преобразователь. Если у нас есть {23, 38, 1, 0, 0} в качестве входных данных, то его матрица самовнимания будет иметь форму 5X5. (матрица веса внимания), для каждого слова мы будем подсчитывать внимание с другими словами в том же предложении (поэтому и называется «я»). Маскировка будет сделана для этих дополненных токенов. В softmax на приведенном выше изображении, если мы передаем очень большое отрицательное значение для этих дополненных токенов, оно будет проигнорировано, это именно то, что я сделал в коде для маскирования.

Эти Q, K и V являются не чем иным, как закодированными значениями ввода в более низкое измерение (глубина), ввод (например, 10X100) будет передан на 3 плотных слоя каждый для Q, K и V.

3.6 Многоуровневый уровень внимания

Многоголовое внимание — это не что иное, как многократное вычисление «самовнимания» для извлечения нескольких типов внимания для каждого слова в предложении, в статье 8, т.е. всего будет рассчитано 8-кратное самовнимание. Раньше мы получали вес внимания 5X5 для {23, 38, 1, 0, 0}, теперь внимание Multi head будет давать тензор 8X5X5. В коде вы увидите, как удивительно это было реализовано (многократный расчет собственного внимания).

Код выглядит немного пугающе, но это не так, внимание Multihead будет одним из слоев преобразователя, поэтому я унаследовал его от tf.keras.layers, а весовая матрица внимания multi-head будет создана во время прямого прохода через вызов(). Пользовательские атрибуты слоя включают 3 плотных слоя для кодировок Q, V и K, 1 последний плотный слой для преобразования выходной формы в соответствие с входной формой, num_heads и d_model. Следует иметь в виду одну вещь: d_model должна делиться на num_head, чтобы добиться многоголового внимания с помощью этой реализации кода. В прямом проходе (вызов call()) мы можем видеть v, k, q и маску в качестве параметров, эти три значения не что иное, как сам ввод, скажем, его (встроенный пакет ввода) размеры 128 X 10 X 512 (BATCH, input_seq_length, размер внедрения) для каждого. После прохождения этих трех через соответствующие им плотные слои мы получим (128 X 10 X 512) фигурную матрицу (количество плотных единиц: d_model(512)) для каждого. Теперь split_head() сделает задание, оно изменит форму (128 X 10 X 512) — › (128 X 8 X 10 X 64 )

Я думаю, теперь вы понимаете, почему я сказал, что «d_model должен делиться на num_head»

Давайте разберемся, что представляет собой (128 X 8 X 10 X 64). '128': batch_size, '8': количество заголовков внимания, '10': длина входного предложения, '64': кодирование вектора встраивания каждого слова в более низкое измерение (как глубина ), как я уже говорил, Q, K, V — это кодировки в более низком измерении. Итак, теперь у нас есть Q, K, V форм (128 X 8 X 10 X 64), передав значения Q, K, V для расчета самообслуживания (там ничего не изменилось, то же уравнение преобразования), мы получим (128 X 8 X 10 X 10) в виде весов собственного внимания и матрицы внимания в виде (128 х 8 х 10 х 64). Теперь нам нужно преобразовать его форму, равную входной матрице, чтобы эти 8 головок внимания были объединены по последнему измерению (64), и после объединения форма будет (128 X 10 X 512), а затем передать его в плотный слой (512 единиц). Окончательная выходная матрица имеет размер (128 X 10 X 512), а матрица веса внимания имеет размер (128 X 8 X 10 X 10). Посмотрите, в расчете собственного внимания с несколькими головками мы не использовали какой-либо причудливый алгоритм, в этом вся прелесть трансформатора.

Мы только что поняли первый уровень преобразователя, уровень внимания Multihead.

3.7 Уровень кодировщика

Я создал этот слой, как обычно, унаследовав класс TensorFlow tf.keras.Layer. Уровень кодировщика состоит из двух компонентов: уровня внимания с несколькими головками (мы только что рассмотрели) и нейронной сети с прямой связью, и выходные данные каждого компонента будут нормализованы по уровню с остаточным соединением. Это действительно просто!

На уровне кодировщика используется call() для прямого прохода, прежде всего, встраивание входных токенов передается через уровень внимания Multihead (mha) чтобы создать матрицу веса внимания и матрицу внимания, если вы заметили перед нормализацией слоя, я добавляю ввод в mhaи его вывод, да, это остаток соединение, а затем нормализация слоя, затем полученный результат проходит через нейронную сеть с прямой связью, затем снова выполняется нормализация слоя с остаточным соединением.

3.8 Кодировщик

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

В Encoder я создаю вложения, затем добавляю кодировку положения, а затем просто слепо вызываю encoder_layer (обратите внимание на цикл при настройке self.enc_layers attribute). Как обычно, я наследую класс tf.keras.layer, так как Encoder также является слоем преобразователя.

Выход кодировщика будет пищей для декодера.

Если вы достигли этой глубины, то предстоящие дела будут для вас легкой прогулкой.

3.9 Уровень декодера

Если вы предполагаете, что это будет вызываться декодером несколько раз, как в кодировщике, то вы абсолютно правы. Этот компонент имеет небольшую модификацию, поскольку мы вычисляем внимание, уделяемое входным словам, для предсказания конкретных слов в качестве вывода. Хотя механизм «самовнимания» никогда не менялся.

Расчет собственного внимания всегда между словами одного предложения и стандартный расчет внимания (как ванильный кодер-декодер с механизмом внимания) всегда между словами двух предложений.

Таким образом, у него есть два механизма внимания с несколькими головками: один — «я», а другой — «стандартный».

Это почти то же самое, что и слой кодировщика, теперь у него есть mha1 и mha2. Если вы заметили, что mha1 имеет входные «ответы» с маской просмотра вперед, поскольку мы хотим игнорировать расчет собственного внимания для будущих слов, а затем ту же нормализацию остатка + уровня, mha2 имеет выходные данные кодировщика для плотных слоев V и K и выходные данные mha1 для плотного слоя Q, поэтому mha2не вычисляет «самовнимание». Таким образом, в уравнении (Q.transpose(K)).V, Q исходит от декодера, а K и V являются выходными данными кодировщика, т. е. если входная длина кодировщика равна 7, а входная длина декодера равна 15, то Матрица окончательного веса внимания будет иметь форму 15 X 7, а вес собственного внимания будет иметь форму 15 X 15, довольно просто, не так ли? После получения выходных данных от mha2выполняется нормализация остатка + уровня, а затем, наконец, результат передается через сеть прямой связи. Таким образом, единственная разница между уровнями кодировщика и декодера заключается в 'mha2' и 'прогнозная маска'

3.10 Декодер

Вызывает Decoderlayer несколько раз, как это делает кодировщик.

В декодере я создаю вложения 'ответов' идобавляю кодировки позиций, а затем просто вслепую вызываю decoder_layer. Я наследую класс tf.keras.layer как обычно, так как декодер также является слоем преобразователя. Следует иметь в виду одну вещь: при каждом повторении слоя декодера будут передаваться выходные данные кодировщика (вmha2),исамостоятельное вниманиеи standard_attentionвеса, возвращаемые каждым уровнем декодера (от mha1и mha2) будут сохранены в словаре «внимание_весов», я буду использовать его для построения графиков внимания! наконец, выходная матрица вернулась для предсказания слов.

3.11 Преобразователь

Вызов кодировщика --› вызов декодера --› плотный слой --› токены слов, окончательный плотный слой будет иметь размер словаря 'ответ' , 128 X 10 X 512 в форме тензора, так как выходные данные декодера проходят через окончательный плотный слой, имеющий 'answer' vocab_size количество единиц плотности, поэтому окончательный выходной тензор плотного слоя будет иметь форму 128 X 10 X 27360 (tokenizer_a.vocab_size +2), где '128':размер_пакета, '10': ввод ответадлина последовательности. Я унаследовал класс Transformer от tf.keras.Model, так как это будет наша модель.

Итак, нам нужно всего 11 шагов, чтобы реализовать такую ​​замечательную идею, которая способна выполнять большинство задач НЛП, как это делают люди! разве это не удивительно?

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

Результаты

С этой настройкой получили 12M обучаемых параметров, Epochs:25ноне учился вообще, это очевидно, так как трансформеры - массивные модели, они предназначен для обучения с большими обучаемыми параметрами для идеальных прогнозов. Я не хочу делиться его прогнозом, так как это была полная тарабарщина.

В любом случае модель-2 справилась с задачей!

Это получило 24M обучаемых параметров, Эпохи: 350, даже для последующих эпох он обучался, но был бесконечно медленным («потому что количество параметров все еще очень мало), но теперь результаты радуют, до 350 эпох потери и точность были не очень хорошими, но на удивление ответы, генерируемые им, были великолепны! Две предыдущие модели были просто мусором по сравнению с этой, для меня это было невероятно, потому что я знал, что моя модель слишком мала для такой сложной работы! давайте посмотрим на сгенерированные ответы...

При случайном вводе ответы показывали следы интеллекта :-o

Теперь вы можете понять силу преобразователя, что, если я получу огромные данные о сохранении фильма и увеличу обучаемые параметры до 100–150 М, я думаю, тогда это может дать ощущение Alexa, |-__- |

Я тренировался с использованием графического процессора NVIDIA RTX 2060, 6 ГБ видеопамяти, что заняло у меня около 1516 часов в течение 350 эпох

Будущие улучшения

  • Соберите больше данных, увеличьте размер вектора внедрения, увеличьте повторение слоев кодировщика и декодераиизмените количество мультиголовок. Я не тестировал эти несколько настроек из-за ограничений вычислений.
  • Архитектуры типа BERT, BART, которые представляют собой усовершенствованные преобразователи, могут повысить интеллект чат-бота.

Рекомендации

Для понимания кода вы можете обратиться к моему репозиторию Githuby

Если есть какие-либо улучшения или сомнения, пожалуйста, не стесняйтесь обращаться ко мне в Linkedin.