НЛП з нуля: переклад із послідовністю в мережу послідовностей та увагу¶

Це третій і останній підручник з виконання “NLP From Scratch”, де ми пишемо власні класи та функції для попередньої обробки даних для виконання наших завдань моделювання NLP. Ми сподіваємося, що після того, як ви пройдете цей підручник, ви дізнаєтесь, як torchtext може обробляти більшу частину цієї попередньої обробки для вас, у трьох підручниках, що слідують за цим.

У цьому проекті ми навчатимемо нейронну мережу для перекладу з французької на англійську.

з різним ступенем успіху.

Це стало можливим завдяки простій, але потужній ідеї послідовності до послідовності мережі, в якій дві повторювані нейронні мережі працюють разом, перетворюючи одну послідовність в іншу. Мережа кодера конденсує вхідну послідовність у вектор, і мережа декодера розгортає цей вектор у нову послідовність.

перекладу

Для вдосконалення цієї моделі ми використаємо механізм уваги, який дозволяє декодеру навчитися фокусуватись на певному діапазоні вхідної послідовності.

Рекомендована література:

Я припускаю, що ви принаймні встановили PyTorch, знаєте Python і розумієте тензори:

  • https://pytorch.org/ Інструкції щодо встановлення
  • Глибоке навчання з PyTorch: 60-хвилинний бліц, щоб розпочати роботу з PyTorch загалом
  • Вивчення PyTorch на прикладах для широкого та глибокого огляду
  • PyTorch для колишніх користувачів Torch, якщо ви колишній користувач Lua Torch

Також було б корисно знати про мережі від послідовності до послідовності та як вони працюють:

Ви також знайдете попередні підручники з NLP From Scratch: Класифікація імен за допомогою рівня символів RNN та NLP From Scratch: Генерування імен за символом рівня RNN корисними, оскільки ці концепції дуже схожі на моделі кодера та декодера відповідно.

Більше про це читайте у статтях, що представляли ці теми:

Вимоги

Завантаження файлів даних¶

Даними для цього проекту є набір з багатьох тисяч пар перекладів з англійської на французьку.

Це запитання щодо Open Data Stack Exchange підказало мені відкритий веб-сайт перекладу https://tatoeba.org/, який має завантаження, доступні за адресою https://tatoeba.org/eng/downloads - і ще краще, хтось зробив додаткову роботу з розділення мовні пари в окремі текстові файли тут: https://www.manythings.org/anki/

Пари з англійської на французьку занадто великі, щоб їх можна було включити у репо, тож завантажте до data/eng-fra.txt, перш ніж продовжувати. Файл являє собою розділений табуляцією список пар перекладів:

Завантажте дані звідси та витягніть їх до поточного каталогу.

Подібно до кодування символів, що використовується в підручниках RNN на рівні символів, ми будемо представляти кожне слово в мові як один гарячий вектор або гігантський вектор нулів, за винятком одного (в індексі слова). Порівняно з десятками символів, які можуть існувати в мові, набагато більше слів, тому вектор кодування набагато більший. Однак ми трохи обдуримо та обріжемо дані, використовуючи лише кілька тисяч слів на мову.

Нам знадобиться унікальний індекс на слово, щоб пізніше використовувати його як вхідні дані та цілі мереж. Щоб відстежувати все це, ми будемо використовувати допоміжний клас під назвою Lang, який має словники → → index (word2index) та index → ​​word (index2word), а також підрахунок кожного слова word2count для подальшої заміни рідкісних слів.

Усі файли знаходяться в Unicode, для спрощення ми перетворимо символи Unicode на ASCII, зробимо все малим та обріжемо більшість знаків пунктуації.

Щоб прочитати файл даних, ми розділимо файл на рядки, а потім розділимо рядки на пари. Усі файли - англійська → інша мова, тому, якщо ми хочемо перекласти з іншої мови → англійська, я додав зворотний прапор, щоб змінити пари.

Оскільки прикладів речень багато, і ми хочемо щось навчити швидко, ми обріжемо набір даних лише на відносно короткі та прості речення. Тут максимальна довжина - 10 слів (що включає в себе закінчувальні знаки пунктуації), і ми проводимо фільтрування до речень, що перекладаються у форму „Я є” або „Він є” тощо (з урахуванням апострофів, замінених раніше).

Повний процес підготовки даних:

  • Прочитайте текстовий файл і розділіть його на рядки, розділіть рядки на пари
  • Нормалізуйте текст, відфільтруйте за довжиною та змістом
  • Складіть списки слів із речень парами

Модель Seq2Seq¶

Нейронна мережа, що повторюється, або RNN - це мережа, яка працює на послідовності і використовує власний вихід як вхід для подальших кроків.

Мережа Sequence to Sequence, або мережа seq2seq, або мережа Encoder Decoder - це модель, що складається з двох RNN, які називаються кодером і декодером. Кодер зчитує вхідну послідовність і виводить один вектор, а декодер зчитує цей вектор для отримання вихідної послідовності.

На відміну від передбачення послідовності з одним RNN, де кожен вхід відповідає виходу, модель seq2seq звільняє нас від довжини та порядку послідовності, що робить її ідеальною для перекладу між двома мовами.

Розгляньте речення “Je ne suis pas le chat noir” → “Я не чорна кішка”. Більшість слів у вхідному реченні мають прямий переклад у вихідному реченні, але мають дещо інші порядки, напр. “Чат нуар” і “чорний кіт”. Через конструкцію “ne/pas” у вхідному реченні є ще одне слово. Важко було б зробити правильний переклад безпосередньо з послідовності введених слів.

За допомогою моделі seq2seq кодер створює єдиний вектор, який в ідеальному випадку кодує "значення" вхідної послідовності в єдиний вектор - єдину точку в деякому N-мірному просторі речень.

Кодер¶

Кодер мережі seq2seq - це RNN, який виводить деяке значення для кожного слова із вхідного речення. Для кожного вхідного слова кодер виводить вектор і прихований стан і використовує прихований стан для наступного вхідного слова.

Декодер¶

Декодер - це інший RNN, який приймає вихідні вектори кодера і видає послідовність слів для створення перекладу.

Простий декодер¶

У найпростішому декодері seq2seq ми використовуємо лише останні вихідні дані кодера. Цей останній результат іноді називають контекстним вектором, оскільки він кодує контекст з усієї послідовності. Цей вектор контексту використовується як початковий прихований стан декодера.

На кожному кроці декодування декодеру надається вхідний маркер і прихований стан. Початковим вхідним маркером є маркер початку рядка, а першим прихованим станом є вектор контексту (останній прихований стан кодера).

Я закликаю вас тренуватися та спостерігати за результатами цієї моделі, але для економії місця ми підемо прямо за золотом та запровадимо Механізм уваги.

Декодер уваги¶

Якщо між кодером та декодером передано лише контекстний вектор, цей єдиний вектор несе тягар кодування всього речення.

Увага дозволяє мережі декодера «фокусуватися» на іншій частині виходів кодера для кожного кроку власних виходів декодера. Спочатку обчислюємо набір ваг уваги. Вони будуть помножені на вихідні вектори кодера, щоб створити зважену комбінацію. Результат (званий attn_applied в коді) повинен містити інформацію про цю конкретну частину вхідної послідовності і, таким чином, допомогти декодеру вибрати правильні вихідні слова.

Розрахунок ваги уваги виконується за допомогою іншого рівня прямої подачі даних, використовуючи вхідний та прихований стан декодера як вхідні дані. Оскільки в навчальних даних є речення будь-якого розміру, щоб насправді створити та навчити цей шар, ми повинні вибрати максимальну довжину речення (довжину введення, для виходів кодера), до якої він може застосовуватися. У реченнях з максимальною довжиною використовуватимуться всі ваги уваги, тоді як у коротших реченнях - лише перші кілька.

Є й інші форми уваги, які обходять обмеження довжини, використовуючи підхід відносного положення. Прочитайте про “місцеву увагу” у “Ефективних підходах до нейронного машинного перекладу на основі уваги”.

Навчання¶

Підготовка навчальних даних¶

Для тренування для кожної пари нам знадобиться вхідний тензор (індекси слів у вхідному реченні) та цільовий тензор (індекси слів у цільовому реченні). Створюючи ці вектори, ми додамо маркер EOS до обох послідовностей.

Навчання моделі¶

Для тренування ми запускаємо вхідне речення через кодер та відстежуємо кожен вихід і останній прихований стан. Потім декодер отримує маркер як перший вхід, а останній прихований стан кодера як перший прихований стан.

"Примушення вчителя" - це концепція використання реальних цільових результатів як кожного наступного входу, замість використання вгадування декодера як наступного входу. Використання вимушеного вчителя змушує його сходитися швидше, але коли експлуатується навчена мережа, це може виявляти нестабільність.

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

Завдяки свободі, яку нам дає автограда PyTorch, ми можемо випадковим чином використовувати примушування вчителя чи ні за допомогою простого твердження if. Збільште співвідношення teacher_forcing_ratio, щоб використовувати більше.

Це допоміжна функція для друку минулого часу та розрахункового часу, що залишився з урахуванням поточного часу та прогресу%.

Весь тренувальний процес виглядає так:

  • Запустіть таймер
  • Ініціалізуйте оптимізатори та критерій
  • Створіть набір навчальних пар
  • Почніть масив порожніх втрат для побудови графіку

Потім ми багато разів викликаємо поїзд і час від часу друкуємо хід (% прикладів, час до цього часу, приблизний час) та середні втрати.

Результати побудови¶

Ділянка робиться за допомогою matplotlib, використовуючи масив значень втрат plot_losses, збережених під час тренування.

Оцінка¶

Оцінка здебільшого така ж, як і навчання, але цілей немає, тому ми просто передаємо прогнози декодера назад собі на кожен крок. Кожного разу, коли він передбачає слово, ми додаємо його до вихідного рядка, і якщо він передбачає маркер EOS, ми зупиняємось на цьому. Ми також зберігаємо висновки уваги декодера для подальшого відображення.

Ми можемо оцінити випадкові речення з навчального набору та роздрукувати вхідні дані, ціль та результат, щоб зробити деякі суб'єктивні оцінки якості:

Навчання та оцінка¶

З усіма цими допоміжними функціями (це виглядає як додаткова робота, але це полегшує проведення декількох експериментів), ми можемо фактично ініціалізувати мережу та розпочати навчання.

Пам'ятайте, що вхідні речення були сильно відфільтровані. Для цього невеликого набору даних ми можемо використовувати відносно невеликі мережі з 256 прихованих вузлів та один рівень GRU. Приблизно через 40 хвилин на процесорі MacBook ми отримаємо кілька розумних результатів.

Якщо ви запускаєте цей блокнот, ви можете тренуватися, переривати ядро, оцінювати і продовжувати навчання пізніше. Прокоментуйте рядки, де кодується і декодер ініціалізуються, і запустіть trainIters знову.

Візуалізація уваги¶

Корисною властивістю механізму уваги є його високо інтерпретовані результати. Оскільки він використовується для зважування конкретних виходів кодера вхідної послідовності, ми можемо уявити, дивлячись, де мережа зосереджена найбільше на кожному часовому кроці.

Ви можете просто запустити plt.matshow (уваги), щоб побачити, як вихід уваги відображається як матриця, причому стовпці є кроками введення, а рядки - кроками виводу:

Для кращого перегляду ми зробимо додаткову роботу, додавши осі та мітки:

Вправи¶

  • Спробуйте використати інший набір даних
    • Ще одна мовна пара
    • Людина → машина (наприклад, команди IOT)
    • Чат → Відповідь
    • Питання → Відповідь
  • Замініть вбудовування попередньо навченими вбудованими словами, такими як word2vec або GloVe
  • Спробуйте більше шарів, більше прихованих одиниць та більше речень. Порівняйте час навчання та результати.
  • Якщо ви використовуєте файл перекладу, де пари мають дві однакові фрази (я тест \ t я тест), ви можете використовувати це як автокодер. Спробуйте це:
    • Тренуйся як автокодер
    • Збережіть лише мережу кодера
    • Навчіть новий декодер для перекладу звідти

Загальний час роботи сценарію: (30 хвилин 44,151 секунди)