Seq2seq и машинный перевод: энкодер и декодер
Чтобы переводить и отвечать, нужна архитектура, превращающая одну последовательность в другую — это seq2seq из двух RNN.
Seq2seq (sequence-to-sequence) — архитектура из двух частей: энкодер сжимает входную последовательность в вектор смысла, декодер разворачивает его в выходную последовательность.
Задача: вход и выход разной длины
Перевод «я люблю котов» → «I love cats» устроен сложнее классификации: и вход, и выход — последовательности, причём разной длины и порядка. Классификатор выдаёт одну метку, а тут нужно породить целую фразу. Для этого придумали seq2seq.
Две части: энкодер и декодер
- Энкодер — RNN/LSTM, которая читает входное предложение слово за словом и сжимает его смысл в один вектор (context vector, «вектор мысли»).
- Декодер — другая RNN/LSTM, которая получает этот вектор и порождает выход слово за словом, опираясь на уже сгенерированное.
Вход: [я] [люблю] [котов]
↓ ↓ ↓
ЭНКОДЕР → context vector (сжатый смысл)
↓
ДЕКОДЕР → [I] [love] [cats] [конец]
Как декодер порождает слова
Декодер работает авторегрессивно: предсказал слово — подал его себе на вход для следующего шага. Начинает со специального токена «начало», на каждом шаге выдаёт следующее слово и останавливается на токене «конец». Покажем схематично цикл генерации.
# Игрушечный декодер: по «вектору смысла» достаём перевод из словарика
context = "я люблю котов" # как будто энкодер сжал это
phrasebook = {"я люблю котов": ["I", "love", "cats"]}
def decode(context):
output = []
words = phrasebook.get(context, ["?"])
for w in words:
output.append(w) # на каждом шаге — одно слово
print("шаг %d -> сгенерировали:" % len(output), w)
output.append("<конец>")
return output
result = decode(context)
print("Перевод:", " ".join(result[:-1]))
Вывод:
шаг 1 -> сгенерировали: I шаг 2 -> сгенерировали: love шаг 3 -> сгенерировали: cats Перевод: I love cats
Конечно, настоящий декодер не достаёт ответ из словаря, а вычисляет распределение вероятностей над всем словарём на каждом шаге и выбирает слово. Но структура та же: пошаговая авторегрессивная генерация на основе сжатого смысла.
Узкое место: один вектор на всё
У классического seq2seq есть критическая слабость. Весь смысл входного предложения, каким бы длинным оно ни было, нужно впихнуть в один context-вектор фиксированного размера. Для короткой фразы нормально, но для длинного предложения это бутылочное горлышко: вектор не вмещает все детали, и качество перевода падает с ростом длины.
Образно: энкодер должен пересказать целую главу одним предложением, а декодер — восстановить главу из этого пересказа. Часть информации неизбежно теряется. Именно эту проблему решит механизм внимания в следующем уроке — он позволит декодеру «подсматривать» во все слова входа, а не только в один сжатый вектор.
Где применяется seq2seq
- Машинный перевод — исходная и главная задача.
- Суммаризация — длинный текст → краткое изложение.
- Вопросно-ответные системы и чат-боты — вопрос → ответ.
- Исправление и перефразирование текста.
Итог
- Seq2seq превращает одну последовательность в другую (вход и выход разной длины).
- Энкодер сжимает вход в context-вектор, декодер разворачивает его в выход.
- Декодер генерирует слова по очереди (авторегрессивно) до токена «конец».
- Слабость — один вектор на всё предложение: горлышко на длинных текстах.
- Эту проблему решает механизм внимания.