Использовалась достаточно простая рекуррентная нейронная сеть для генерации текста. Сеть имеет две основных модификации, которые отличаются методом обучения и генерации:
- Сеть обучается предсказывать только один следующий символ, принимая последовательность из N символов.
- Сеть обучается предсказывать K символов, принимая последовательность из N символов. Этот режим вынуждает сеть изучить более глубокие взаимосвязи между символами. Кроме того, данная модификация позволяет (эффективно) накапливать данные и обрабатывать последовательности длиной больше N.
Для адекватной интерпретации работы сети необходимо понимать как она обучается и работает. Например, если
N = 10
, K = 10
и есть строка "какой-то пример текста для сети", то в какой-то момент времени сеть может получить на вход "ой-то прим" и должна будет предсказать "ер текста ". Таким образом, сеть имеет крайне мало информации о контексте слов и вообще воспринимает его исключительно как набор символов, не имея представления о словах. Очевидным было бы значительно увеличить N и K, но это сделает процесс обучения на много порядков сложнее. Так же, обучаясь даже при N + K = 40
сеть не получает достаточно данных для понимания связей между более чем 3-5 словами.
Учитывая вышесказанное, я сомневаюсь что сеть вообще сможет генерировать корректно написанные слова. Про какую либо смысловую нагрузку и логичность речи вовсе идти не может т. к., для написания полноценного текста, даже людям необходим огромный багаж опыта и навыков.
Генерация текста происходит в подобной обучению манере, но с ещё большими сложностями. При обучении сеть ищет пути представить информацию только о N + K
символах, но на этапе генерации сеть имеет дело с последовательностями иной длины и тут уже крайне важно какие трансформации выучила сеть.
Последней проблемой является невозможность (просто) математически оценить качество сгенерированного текста, его логичность и "человечность". Эта проблема проявляется, например, в склонности сети повторять одни и те же фразы. Частично эта проблема решается тщательным подбором параметров обучения сети, самыми очевидными из которых являются learning rate
и принудительное ограничение размера градиентов.
Символы можно представлять различным образом, но я решил дать возможность сети самой найти представление каждого символа. На вход сети поступает код символа, затем он попадает в Embedding
-слой, который связывает каждый символ со своим двумерным вектором. Вектора эти сеть так же находит в процессе обучения. Дополнительно применяется функция tanh
к этим векторам, чтоб упростить обучение сети.
Сам же текст предварительно приводится к нижнему регистру и очищается от лишних символов (оставляя только r'[^a-zа-я0-9\s\.,\!\(\)\-\ё]'
). Эти операции позволяют максимально сузить задачу и отбросить несущественные детали.
В качестве датасета я решил использовать часть текста (~430Kb) из серии книг "Война и мир". Тестирование же проводил на следующих фразах, подобранных в соответствии с тематикой книг:
TEST_PHRASES = [
'оскорбительно притворно-сладкое ',
'Анна Павловна ',
'Но ведь ',
'надо прервать все эти ',
'послала за сыном и ',
]
Я использовал следующие способы генерация текста:
- Выбор символа с максимальной вероятностью.
- Выбор случайного символа из K наиболее вероятных.
Кроме того, опять же, можно генерировать текст на основе последних N символов, а можно на основе всех известных символов. У каждого из вариантов есть свои плюсы и минусы. Например, сеть обучается на N + K символах, поэтому только на этом диапазоне сеть работает полноценно, но с ростом длины последовательности растёт и вероятность некорректного поведения.
Выбор случайного символа из K наиболее вероятных позволяет достаточно эффективно бороться с проблемой повторения фраз, но часто вносит в слова явные опечатки. Достаточно хорошей альтернативой является случайный выбор только первого символа каждого слова.
Пример генерации текста этими способами, но на основе работы той же (самой простой) сети:
Seed: оскорбительно притворно-сладкое
Sampling by argmax (Length = 10)
==================================================
оскорбительно притворно-сладкое выражением своего слезы и сказал князь андрей с своим своего слезы и сказал князь андрей с своим своего слезы и сказал князь андрей с своим своего слезы и сказал князь андрей с своим своего слезы и сказал князь андрей с своим своего слезы и сказал князь андрей с своим
Sampling by random top-5 (Length = 10)
==================================================
оскорбительно притворно-сладкое в раз собревь его против никогда неожиданное долохов стора посторох и покадывался, сам страз носом, - как столик покому и того кроме на нее их собой заскаска и особенность и плиться с соведшую дала и выше ее испедлом и всё, тог наташей, - покрор были тужькой собрал в
Sampling by random top-5 only first character of word (Length = 10)
==================================================
Seed: оскорбительно притворно-сладкое
оскорбительно притворно-сладкое разговор, к князю василия своего подошел в переднюю сторону не было в держат было было было в своему принялась его было сказать князь андрей поднялась в ней показывал в петербурге с своим своего не сказала она, с своим говорить в ней на лице ее вышел к столу, который
Как видно, стандартный способ генерации почти сразу зацикливается на одной фразе. Если же выбирать следующий символ случайным образом, то появляются слова с опечатками. Наиболее приемлемый текст генерируется при случайном выборе первого символа каждого слова.
Данный вариант сети является самым простым т. к. обучается она предсказывать только один конкретный элемент, полностью игнорируя остальные. Это весьма упрощает задачу, но это не позволяет использовать сеть в последовательном режиме.
Сгенерированный текст (argmax) сетью на основе 10 символов:
Sampling by argmax (Length = 10)
==================================================
Seed: оскорбительно притворно-сладкое
оскорбительно притворно-сладкое выражением своего слезы и сказал князь андрей с своим своего слезы и сказал князь андрей с своим своего слезы и сказал князь андрей с своим своего слезы и сказал князь андрей с своим своего слезы и сказал князь андрей с своим своего слезы и сказал князь андрей с своим
Seed: Анна Павловна
Анна Павловна и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к не
Seed: Но ведь
Но ведь не подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и
Seed: надо прервать все эти
надо прервать все эти минуту и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему и подошел к нему
Seed: послала за сыном и
послала за сыном и сказала она, - сказал он, всё своего слезы и сказал князь андрей с своим своего слезы и сказал князь андрей с своим своего слезы и сказал князь андрей с своим своего слезы и сказал князь андрей с своим своего слезы и сказал князь андрей с своим своего слезы и сказал князь андрей с
Сеть обучалась 30 эпох и склонна повторять те же фразы, не зависимо от контекста. Если увеличить контекст до 20 символов, то получим такие результаты:
Sampling by argmax (Length = 20)
==================================================
Seed: оскорбительно притворно-сладкое
оскорбительно притворно-сладкое и подошел к столу с своим слушами в переднюю подошел в гостиной и указывая на него и подошел к столу с своим слушами в переднюю подошел в гостиной и указывая на него и подошел к столу с своим слушами в переднюю подошел в гостиной и указывая на него и подошел к столу с
Seed: Анна Павловна
Анна Павловна и выражение в переднюю подошел в гостиной и указывая на него и подошел к столу с своим слушами в переднюю подошел в гостиной и указывая на него и подошел к столу с своим слушами в переднюю подошел в гостиной и указывая на него и подошел к столу с своим слушами в переднюю подошел в гост
Seed: Но ведь
Но ведь и вы не поняла в приемной подорнулся в переднюю подорнулся и приветствивал всем в том, что она не поняла у него выражение с своим слушами в переднюю подошел в гостиной и указывая на него и подошел к столу с своим слушами в переднюю подошел в гостиной и указывая на него и подошел к столу с св
Seed: надо прервать все эти
надо прервать все эти совершенно с своим слушами. - вы не могу не понимаю, что вы не поняла в приемной подорнулся в переднюю подорнулся и приветствивал всем в том, что она не поняла у него выражение с своим слушами в переднюю подошел в гостиной и указывая на него и подошел к столу с своим слушами в
Seed: послала за сыном и
послала за сыном и верность и не поняла в переднюю постоянного с своим слушами. - вы не могу не понимаю, что вы не поняла в приемной подорнулся в переднюю подорнулся и приветствивал всем в том, что она не поняла у него выражение с своим слушами в переднюю подошел в гостиной и указывая на него и подо
Эта сеть уже менее склонна к зацикливанию, но можно заметить повторение одних и тех же паттернов, а так же использование весьма скудного набора слов.
Данный класс моделей работает уже с целыми последовательностями и обладает механизмами для запоминания связей между элементами. Модели sequence-to-character так же имеют внутреннюю память, но её работа не проверяется на этапе обучения. Если же сеть обучается генерировать целые последовательности, часть из которых являются известными (и равными входным данным), то сеть получает намного больше информации для выявления закономерностей.
Сравним сгенерированный текст различными сетями для одной и той же исходной фразы:
Sampling by argmax (Length = None)
==================================================
Sequence to sequence 10 characters:
оскорбительно притворно-сладкое выражением как будто в повторялись она с улыбкой поднялся к ней. - сказала она стал прошеми в гостиной по коридору и подошла к столу на стол подошел к нему и подошла к столу на стол подошел к нему и подошла к столу на стол подошел к нему и подошла к столу на стол подо
Sequence to sequence 20 characters:
оскорбительно притворно-сладкое высоко на него своим своей своем колодой собершить в гостиную и подошел к князя василий и подошел к князя василий и подошел к князя василий и подошел к князя василий и подошел к князя василий и подошел к князя василий и подошел к князя василий и подошел к князя васили
Sequence to sequence 40 characters:
оскорбительно притворно-сладкое выражение с своим детей и слушала и подошел к старшей и подошел к старшей и подошел к старшей и подошел к старшей и подошел к старшей и подошел к старшей и подошел к старшей и подошел к старшей и подошел к старшей и подошел к старшей и подошел к старшей и подошел к ст
Первая сеть, обученная на последовательностях из 10 символов, начинает повторяться на 189 символе. Сеть обученная на 40 символах начинает повторяться на 87 символе. Учитывая же потребовавшееся для их обучения количество эпох, это указывает на склонность сети к переобучению.
RNN так же плохо работают, как и RL.RNN-сети очень чувствительны к многим факторам, поэтому требуют наличия достаточного объёма опыта и знаний.- С ростом длины используемой при обучении последовательности должен увеличиваться и датасет.
- Следует быть осторожным с выбором данных для валидации. Например, если просто брать 10% текста, то в него могут попасть оглавление и сноски, которые имеют совершенно иную структуру.
- Попробовать предсказывать больше одного символа.
- Попробовать полноценную авторегрессионную модель, которая обучается генерировать больше одного символа.
- Сравнить сеть на основе GRU c LSTM, обучив их на большем объёме данных.
- Перейти на уровень слов, используя их векторное представление, например, из GloVe.
- Попробовать генерировать текст используя RuBERT.
- Сравнить генерацию на основе RNN c RL/DQN. Использовать стандартный подход RL как для генерации текста, так и для получения латентного представления текста (LSTM так же создаёт латентное представление последовательности, которое затем используется для предсказания символах).