Chatbot на базі рекурентної нейронної мережі своїми руками за 1 вечір/6$ і ~ 100 рядків коду

У даній статті я хочу показати наскільки просто сьогодні використовувати нейронні мережі. Навколо мене досить багато людей одержимі ідеєю того, що нейронки може використовувати тільки дослідник. І що б отримати хоч якийсь вихід, потрібно мати як мінімум кандидатський ступінь. А давайте на реальному прикладі подивимося як воно насправді, взяти і з нуля за один вечір навчити chatbot. Так ще не просто аби чим а самим що нинаесть ламповим TensorFlow. При цьому я постарався описати все настільки просто, що-б він був зрозумілий навіть починаючому програмісту! У шлях!

image

Кроки, які належить пройти на нашому тернистому шляху
  1. Необхідно знайти реалізацію нейроннйо мережі, яку можна використовувати для нашої мети.
  2. Підготувати дані (корпус), які можуть бути використані для навчання.
  3. Навчити модель.
Окреме спасибі моїм патронам, які зробили цю статтю можливу:
Aleksandr Shepeliev Tony Ten, Alexey Polietaiev, Микита Пензин, Андрій Карнаухов, Matveev Evgeny, Anton Potemkin. Ви теж можете стати одним з них ось тут.

Реалізація нейронки, яку можна використовувати для нашої мети
TensorFlow включає реалізацію RNN (рекурентной нейронної мережі), яка використовується для навчання моделі перекладу для пар мов англійська / французька. Саме цю реалізацію ми й будемо використовувати для навчання нашого чат-бота.

Ймовірно, хтось може запитати: «Чому, чорт візьми, ми дивимося на навчання моделі перекладу, якщо ми робимо чат-бот?». Але це може здатися дивним лише на першому етапі. Задумайтеся на мить, що таке «переклад»? Переклад може бути представлений у вигляді процесу в два етапи:

  1. Створення мовно-незалежного подання вхідного повідомлення.
  2. Відображення інформації отриманої на першому кроці на мову перекладу.
Тепер задумайтеся, а що, якщо ми будемо навчати ту ж модель RNN, але, замість Eng/Fre пари ми будемо підставляти Eng/Eng діалоги з фільмів? В теорії, ми можемо отримати чат-бот, який здатний відповідати на прості однорядкові питання (без здатності запам'ятовувати контекст діалогу). Цього повинно бути цілком достатнім для нашого першого бота. Плюс, цей підхід дуже простий. Ну а в майбутньому, ми зможемо, відштовхуючись від нашої першої реалізації, зробити чат-бот більш розумним.

Пізніше ми дізнаємося, як навчати більш складні мережі, які є більш придатними для чат-ботів (наприклад, retrieval-based models).

Для нетерплячих: картинка на початку статті це власне приклад розмови з ботом після всього 50 тисяч навчальних ітерацій. Як бачите, бот здатний давати більш або менш інформативні відповіді на деякі питання. Якість бота поліпшується з кількістю ітерацій навчання. Наприклад, ось як безглуздо він відповідав після перших 200 ітерацій:

image

Такий простий підхід також дозволяє нам створювати ботів з різними характерами. Наприклад, можна було б навчити його на діалогах із саги «Зоряні війни» або «Володар кілець». Більш того, якщо бот має досить великий корпус діалогу одного і того ж героя (наприклад, всі діалоги Чендлера з фільму «Друзі»), то можна створити бота цього самого героя.

Підготовка даних (корпусу) для навчання
Для навчання нашого першого бота ми будемо використовувати корпус діалогів з кінофільмів "Cornell Movie Dialogs Corpus". Для його використання нам потрібно сконвертувати діалоги в потрібний для навчання вигляд. Для цього я підготував невеликий скрипт.

Я б настійно рекомендував вам прочитати README файл, щоб зрозуміти більше про корпусі і про те, що цей скрипт робить, і тільки потім продовжити читання статті. Однак, якщо вам просто потрібно команди, які ви можете сліпо скопіювати і виконати, щоб отримати готові для навчання дані, ось вони:

tmp# git clone https://github.com/b0noI/dialog_converter.git
Cloning into 'dialog_converter'...
remote: Counting objects: 59, done.
remote: Compressing objects: 100% (49/49), done.
remote: Total 59 (delta 33), reused 20 (delta 9), pack-reused 0
Unpacking objects: 100% (59/59), done.
Checking connectivity... done.
tmp# cd dialog_converter
dialog_converter git:(master)# python converter.py
dialog_converter git:(master)# ls
LICENSE README.md converter.py movie_lines.txt train.a train.b

До кінця виконання ви будете мати 2 файли, які можна використовувати для подальшого навчання: train.a train.b

Навчання моделі
Це сама захоплююча частина. Для того, щоб навчити модель ми повинні:
  1. Знайти машину з потужною і підтримуваної TensorFlow (що дуже важливо) відеокартою (читай: NVIDIA).
  2. Змінити оригінальний «translate» скрипт, який використовується для навчання моделі перекладу пари Eng/Fre.
  3. Підготувати машину для навчання.
  4. Почати навчання.
  5. Почекати.
  6. Почекати.
  7. Почекати.
  8. Я серйозно… Потрібно почекати.
  9. Profit.

У пошуках Атлантиди машини для навчання

Для того, щоб зробити цей процес якомога більш простим, я буду використовувати зібраний AMI — "Bitfusion Ubuntu 14 Scientific Computing", який буде використовуватися з AWS. Він має попередньо встановлений TensorFlow, який був зібраний з підтримкою GPU. До моменту написання статті, Bitfusion AMI включав TensorFlow версії 0.11.

Процес створення EC2 инстанса з AMI образу досить простий та його розгляд виходить за рамки цієї статті. Однак варто звернути увагу на дві важливі деталі, які мають відношення до процесу, це тип instance і розмір SSD. Для типу я б рекомендував використовувати: p2.xlarge — це найдешевший тип, який має NVIDIA GPU з достатньою кількістю відеопам'яті (12 Гбіт). Що стосується розміру SSD — я б рекомендував виділити принаймні 100GB.

Тепер нам потрібно змінити оригінальний «translate» скрипт

На цьому етапі, я сподіваюся, я можу припустити, що у вас є ssh доступ до машини, де ви будете навчати TensorFlow.

По-перше, давайте обговоримо, навіщо взагалі нам потрібно змінювати вихідний скрипт. Справа в тому, що сам скрипт не дозволяє перевизначити джерело даних, що використовуються для навчання моделі. Щоб це виправити я створивfeature-request. І постараюся скоро підготувати реалізацію, але на даний момент, ви можете взяти участь додаючи +1 «реквесту».

У будь-якому випадку, не треба боятися — модифікація дуже проста. Але навіть настільки малу модифікацію я вже зробив за вас і створив репозиторій, що містить модифікований код. Залишається тільки зробити наступне:

Перейменуйте файли «train.a» і «train.b» на «train.en» та «train.fr» відповідно. Це необхідно, так як навчальний скрипт все ще вважає, що він навчається на переклад з англійської на французьку.

Обидва файлу повинні бути завантажені на віддалені хости — це можна зробити за допомогою команди rsync:

➜ train# REMOTE_IP=...
➜ train# ls
train.en train.fr
➜ train rsync -r . ubuntu@$REMOTE_IP:/home/ubuntu/train

Тепер давайте підключимося до віддаленого хосту і запустимо tmux сесію. Якщо ви не знаєте, що таке tmux, ви можете просто підключитися через SSH:

➜ train ssh ubuntu@$REMOTE_IP
53 packages can be updated.
42 updates are security updates.
########################################################################################################################
########################################################################################################################
____ _ _ __ _ _
| __ )(_) |_ / _|_ _ ___(_) ___ _ __ (_) ___
| _ \| | __| |_| | | / __| |/ _ \| '_ \ | |/ _ \
| |_) | | |_| _| |_| \__ \ | (_) | | | |_| | (_) |
|____/|_|\__|_| \__,_|___/_|\___/|_| |_(_)_|\___/
Welcome to Bitfusion Ubuntu 14 Tensorflow - Ubuntu 14.04 LTS (GNU/Linux 3.13.0-101-generic x86_64)
This AMI is brought to you by Bitfusion.io
http://www.bitfusion.io
Please email all feedback and support requests to:
support@bitfusion.io
We would love to hear from you! Contact us with any feedback or a feature request at the email above.
########################################################################################################################
########################################################################################################################
########################################################################################################################
Please review the README located at /home/ubuntu/README for more details on how to use this AMI
Last login: Sat Dec 10 16:39:26 from 2016 99-46-141-149.lightspeed.sntcca.sbcglobal.net
ubuntu@tf:~$ cd train/
ubuntu@tf:~/train$ ls
train.en train.fr

Давайте перевіримо, що TensorFlow встановлений і він використовує GPU:

ubuntu@tf:~/train$ python
Python 2.7.6 (default, Jun 22 2015, 17:58:13)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" або "license" for more information.
>>> import tensorflow as tf
I tensorflow/stream_executor/dso_loader.cc:111] successfully opened CUDA library libcublas.so.7.5 locally
I tensorflow/stream_executor/dso_loader.cc:111] successfully opened CUDA library libcudnn.so.5 locally
I tensorflow/stream_executor/dso_loader.cc:111] successfully opened CUDA library libcufft.so.7.5 locally
I tensorflow/stream_executor/dso_loader.cc:111] successfully opened CUDA library libcuda.so.1 locally
I tensorflow/stream_executor/dso_loader.cc:111] successfully opened CUDA library libcurand.so.7.5 locally
>>> print(tf.__version__)
0.11.0

Як видно, встановлений TF версії 0.11 і він використовує бібліотеку CUDA. Тепер давайте схилимо навчальний скрипт:

ubuntu@tf:~$ mkdir src/
ubuntu@tf:~$ cd src/
ubuntu@tf:~/src$ git clone https://github.com/b0noI/tensorflow.git
Cloning into 'tensorflow'...
remote: Counting objects: 117802, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 117802 (delta 0), reused 0 (delta 0), pack-reused 117792
Receiving objects: 100% (117802/117802), 83.51 MiB | 19.32 MiB/s, done.
Resolving deltas: 100% (88565/88565), done.
Checking connectivity... done.
ubuntu@tf:~/src$ cd tensorflow/
ubuntu@tf:~/src/tensorflow$ git checkout -b r0.11 origin/r0.11
Branch r0.11 set up to track remote branch r0.11 origin from.
Switched to a new branch 'r0.11'

Зверни увагу, що нам потрібна гілка r0.11.Перш за все, ця гілка узгоджується з версією локально встановленого TensorFlow. По-друге, я не переніс мої зміни в інші гілки, тому, в разі необхідності, доведеться робити це руками вам самим.

Вітаю! Ви дійшли до етапу початку навчання. Сміливо запускайте це саме навчання:

ubuntu@tf:~/src/tensorflow$ cd tensorflow/models/rnn/translate/
ubuntu@tf:~/src/tensorflow/tensorflow/models/rnn/translate$ python ./translate.py --en_vocab_size=40000 --fr_vocab_size=40000 --data_dir=/home/ubuntu/train --train_dir=/home/ubuntu/train
...
Tokenizing data in /home/ubuntu/train/train.en
tokenizing line 100000
...
global step 200 learning rate 0.5000 step-time 0.72 perplexity 31051.66
eval: bucket 0 perplexity 173.09
eval: bucket 1 perplexity 181.45
eval: bucket 2 perplexity 398.51
eval: bucket 3 perplexity 547.47

Давайте обговоримо деякі ключі, які ми використовуємо:

  • en_vocab_size — скільки унікальних слів вивчить модель «англійської мови». Якщо число унікальних слів з вихідних даних перевищує розмір словника, всі слова, які не в словнику, будуть позначені як «UNK (code: 3). Але не рекомендую робити словник більше, ніж необхідно; але він не повинен бути і менше;
  • fr_vocab_size — те ж саме, але для іншої частини даних (для «французької мови»);
  • data_dir — директорія з вихідними даними. Тут скрипт буде шукати файли «train.en» та «train.fr»;
  • train_dir — директорія, в яку скрипт буде записувати проміжний результат навчання.

Давайте переконаємося в тому, що навчання триває, і все йде за планом

Ви успішно розпочали навчання. Але давайте підтвердимо, що процес триває і все в порядку. Ми не хочемо, щоб в кінцевому підсумку через 6 годин ми з'ясували, що в самому початку було зроблено не так. Ми з Глібом вже якось навчили щось напіврозумну =)

Насамперед, ми можемо підтвердити, що процес навчання «відкусив» собі пам'яті GPU:
$ watch -n 0.5 nvidia-smi

image

Як видно, не хіло так кусанул, майже вся пам'ять на GPU зайнята. Це хороший знак. І не потрібно боятися, що ваш процес ось-ось «відкине копита» з помилкою OutOfMemory. Просто під час запуску TF прибирає до своїх рук всю пам'ять на GPU, до якого може дотягнутися.

Потім можна перевірити папку «train» — вона повинна містити кілька нових файлів:

~$ cd train
~/train$ ls
train.fr train.ids40000.fr dev.ids40000.en dev.ids40000.fr train.en train.ids40000.en vocab40000.fr

Тут важливо зазирнути у файли vocab4000.* і train.ids40000.*. Там повинно бути атмосферно і душевно, ось подивіться:

~/train$less vocab40000.en
_PAD
_GO
_EOS
_UNK
.
'
,
I
?
you
the
to
s
a
t
it
of
You
!
that
...

Кожен рядок у файлі — це унікальне слово, що було знайдено у вихідних даних. Кожне слово у вихідних даних буде замінено числом, яке являє номер рядка з цього файлу. Можна відразу ж помітити, що є деякі технічні слова: PAD (0), GO (1), EOS (2), UNK (3). Напевно найважливіше з них для нас — «UNK», так як кількість слів позначених цим кодом (3) дасть нам деяке уявлення про те, наскільки коректно обраний нами розмір нашого словника.
Тепер давайте подивимося на train.ids40000.en:

~/train$ less train.ids40000.en
1181 21483 4 4 4 1726 22480 4 7 251 9 5 61 88 7765 7151 8
7 5 27 11 125 10 24950 41 10 2206 4081 11 10 1663
84 7 4444 9 6 562 6 7 30 85 2435 11 2277 10289 4 275
107 475 155 223 12428 4 79 38 30 110 3799 16 13 767 3 7248 2055 6 142 62 4
1643 4 145 46 19218 19 40 999 35578 17507 11 132 21483 2235 21 4112 4
144 9 64 83 257 37 788 21 296 8
84 19 72 4 59 72 115 1521 315 66 22 4
16856 32 9963 348 4 68 5 12 77 1375 218 7831 4 275
11947 8
84 6 40 2135 46 5011 6 93 9 359 6370 6 139 31044 4 42 5 49 125 13 131 350 4
371 4 38279 6 11 22 316 4
3055 6 323 19212 6 562 21166 208 23 3 4 63 9666 14410 89 69 59 13262 69 4
59 155 3799 16 1527 4079 30 123 89 10 2706 16 10 2938 3 6 66 21386 4
116 8
...

Думаю ви вже здогадалися, що це дані з input.en, але з усіма словами заміненими на коди згідно зі словником. Тепер ми можемо перевірити, скільки ж слів позначені як «невідомі» (UNK/3):

~/train$ grep -o ' 3 ' train.ids40000.en | wc -l
7977

Можете спробувати в подальших експериментах збільшити розмір словника з 40k до 45c, або навіть до 50к. Але, ми поки продовжимо процес «as is» і не станемо його переривати.

6 годин потому Воно ожило!"
Після очікування достатньої кількості часу, ви можете тупо вбити процес навчання. Не хвилюйтеся, процес зберігає результат кожні 200 кроків навчання (це число можна змінювати). Я б рекомендував для навчання або вибрати проміжок часу, який ви готові чекати (він може залежати від суми, яку ви готові платити за оренду машини), або кількість кроків, яке ви хочете, щоб тренувальний процес закінчив.

Саме останнє і головне заради чого ми влаштували весь цей бєспрєдєл — це почати чат. Для цього нам просто потрібно додати один ключ до команди, яка була використана для тренування:

~/src/tensorflow/tensorflow/models/rnn/translate$ python ./translate.py --en_vocab_size=40000 --fr_vocab_size=40000 --data_dir=/home/ubuntu/train --train_dir=/home/ubuntu/train --decode
...
Reading model parameters from /home/ubuntu/data/translate.ckpt-54400
> Hello!
Hello .
> Hi are you?
Now , okay .
> What is your name?
My name is Sir Sir .
> Really?
Yeah .
> what about the real name?
N . . . real real .
> are you a live?
Yes .
> where are you?
I'm here .
> where is here?
I don ' t know .
> can I help you to get here?
Yeah , to I ve ' t feeling nothing to me .

Це лише початок! Ось кілька ідей для покращення бота.
Щоб допомогти проекту або просто створити бота з характером можна:
  • реалізувати логіку, яка знаходить ідеальний розмір для EN/FR словників;
  • опублікувати train.en/train.fr десь, щоб інші люди не створювали їх з нуля;
  • навчити бота майстра Yoda (або Дарта Вейдера);
  • навчити бота, який буде говорити, як у всесвіті з «Володаря кілець»;
  • навчити бота, який буде говорити, як люди з «StarWars» всесвіту;
  • навчити бота на своїх діалогах, щоб він розмовляв як ви!
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.