RNN - Рекуррентные нейронные сети.




 

Ранее мы изучали тему Обработка текстов. Текст – это последовательность слов, а порядок этих слов может быть очень существенный, поэтому нужно разрабатывать инструменты, которые позволяют обрабатывать именно последовательности, т.е. те вещи, в которых порядок расположения объектов принципиально важен.

Последовательности в нашей жизни – это тексты, аудио, видео, временные ряды, курс доллара, то есть, те вещи, в которых очередное значение может опосредованно или непосредственно зависеть от предыдущих значений.

Для этого используется специальная архитектура, которая называется RNN – Recurrent Neural Networks (Рекуррентные нейронные сети).

 

В классической архитектуру НС в сеть всегда подается один объект, этот объект прогоняется через сеть и мы смотрим на ответ. В таком случае сложно потребовать, чтобы результат вычисления этой НС как-то зависел от тех объектов, которые были поданы в сеть раньше. Если мы хотим сделать такую НС, в которой результат вычисления для объекта Х будет зависеть от тех объектов, которые были поданы ранее, нужно сделать рекурсивную архитектуру с петлёй:

 

Это самая простейшая сеть, состоящая из одного нейрона. Чем она отличается от классической НС? Мы видим тут петлю: когда значение выходит из нейрона, применяется функция активации f, одна часть идет на выход, а вторая копируется и идет в тот же самый нейрон. Это нужно для того, чтобы, когда будет подаваться следующий за x объект, у нас было бы сохранено значение, которое возникло на предыдущем объекте x, для этого рисуется такая петля.

 

Это – рисунок-идеализация. В реальной жизни такую сеть построить нельзя, поскольку она содержит цикл. Когда НС мы представляем в графе вычислений, и потом мы пытаемся по нему вычислить градиент по рекурсивной процедуре (спускаясь сверху вниз), существенное требование – граф вычислений не должен иметь циклы. Если граф имеет циклы, он зациклится, и мы не сможем дойти до конца и вычислить градиент, то есть производную по входу X. Поэтому, на практике в чистом виде её реализовать не получится.

 

Если очень сильно хочется, нужно зафиксировать размер истории t. Это некое огрубление, предположение о том, что свойство объекта x зависит только от t последних объектов, которые подавались на эту НС, включая его самого. Иными словами, если t=3, то выход сети зависит как от текущего объекта, так и от двух предыдущих – в сумме три объекта.

Когда вы смотрите на свою задачу, мы должны на основании своего опыта или здравого смысла, а также, на основании ограничений в вычислительном плане (например, при слабом железе) t не может быть слишком большим.

 

Мы можем выбрать это t. Допустим, t = 3. Тогда, граф с петлей можно разложить в последовательность:

 

 

Поскольку t=3, у нас возникает три объекта, от которых зависит окончательный выход сети (x1,x2,x3), при чем они написаны последовательно: самый старый – x1, потом x2, потом x3.

Что происходит? Мы раскладываем как бы во времени нашу НС: в начале ей подавался x1, он прошелся через НС, и выходное значение попало в ту же самую НС, но только когда ей подается ещё x2. Когда x2 прошелся через НС, то выходное значение было выдано вместе с объектом x3, и после объекта x3 сформировалось окончательное выходное значение НС.

Обратите внимание, что у этой сети во всех её частях одни и те же веса и параметры: входной объект xi всегда домножается на то же число w; во всех нейронах прибавляется одно и то же смещение b; всюду одна и та же функция активации f, а значение, которое передается с прошлого объекта, также всюду имеет одинаковый вес v, т.е. значение, которое получилось на предыдущем объекте, домножается на то же значение v и идет дальше.

Тогда какое значение с предыдущего объекта передавать для самого первого объекта x1? Самое простое решение – говорить, что до этого объекта ничего не было, и подавать значение 0.

 

Решая нашу задачу, считаем, что окончательное решение может быть принято по t = 3 последовательным объектам. Это позволяет расписать эту НС с циклом в последовательность трёх НС, которые состоят из одних и тех же весов и параметров. В итоге мы получаем обычную НС, ей соответствует обычный граф без циклов, и мы можем посчитать выход этой НС при заданных xi-ых.

Мы можем посчитать функцию, которая считает нашу НС:

 

FНС(x1,x2,x3) = f(w⋅x3 + b + v⋅h2) = f⋅(w⋅x3 + b + v⋅f(w⋅x2+b+v⋅h1)) =

= f⋅(w⋅x3 + b + v⋅f⋅(w⋅x2+b+v⋅f⋅(w⋅x1 + b + v⋅h0)

 

Эта функция зависит от x1,x2,x3. Начинаем расписывать эту функцию, начиная с выхода. На выходе – функция активации f, примененная к выходу последнего нейрона, где происходило следующее: объект x3 домножался на w, прибавлялось смещение b и прибавлялось значение, приходящее из предыдущего нейрона, домноженного на v:

 

FНС(x1,x2,x3) = f(w⋅x3 + b + v⋅h2)

 

, где h2 - значение, которое пришло из предыдущего нейрона. Его тоже можно расписать, используя значения, которые пришли ранее, и так далее. В конце концов у вас получается конечная сумма, в которой будут фигурировать только веса и параметры вашей НС и то первое значение, которое пришло в НС слева (но мы договорились, что оно будет для простоты равно нулю), т.е. 0.

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

 

послед-сть ответ
abb  
bb_  
b_a  
_aa  
aa_  
таб. 1

Рассмотрим на конкретном примере, как тренируется сеть и как всё работает.

 

Задача: будет ли в тексте пробел после заданных трех символов?

Тренировочная выборка: “abb aa a”.

 

Формируем тренировочную выборку для входа на РНС (таб.1). Поскольку t=3, то архитектура сети будет именно такая, какую мы нарисовали ранее. Пусть f- сигмоида и находится в нестрогом интервале от нуля до единицы:

 

 

послед-сть ответ f
abb   ≈ F(-1,1,1)
bb_   ≈ F(1,1,0)
b_a   ≈ F(1,0,-1)
_aa   ≈ F(0,-1,-1)
aa_   ≈ F(-1,-1,0)
таб. 2

Выход НС – вероятность того, что будет пробел. Символы a и b нужно перевести в числа. Можно перевести их в вектора (вектор, соответствующий символу a, b, _), но проще просто числами обозначит за a = -1, b = 1, символ пробела _=0. Нужно натренировать НС так, чтобы она вычисляла функцию, которая удовлетворяет таким условиям (таб.2)

 

Составляем функцию потерь. Кросс-энтропию, а именно, нужно посчитать

L(w,v,b) = [F(-1,1,1) + F(0,-1,-1) + (1-F(1,1,0))/*из второй строки*/ + (1-F(1,0,-1))/*из третьей строки*/ + (1-F(-1,-1,0))/*из пятой строки*/]

В идеале нужно максимизировать все слагаемые, но поскольку градиентный спуск решает задачу минимизации, надо поставить знак минус:

L(w,v,b) = -[F(-1,1,1) + F(0,-1,-1) + (1-F(1,1,0)) + (1-F(1,0,-1)) + (1-F(-1,-1,0))]

 

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

 

L(w,v,b) = -[ln(F(-1,1,1)) + ln(F(0,-1,-1)) + ln(1-F(1,1,0)) + ln(1-F(1,0,-1)) + ln(1-F(-1,-1,0))]

 

Это выражение не зависит от x, а зависит только от параметров нейронной сети v,w и b. Нужно искать точку минимума функции. Когда мы её найдем, мы получим оптимальную архитектуру сети, которая предсказывает появление пробела в тексте. Если бы тренировочная выборка была бы гораздо больше, то просто увеличилось количество слагаемых в функции потерь L.

Данная архитектура – простейшая архитектура RNN. В зависимости от решаемой задачи, эту архитектуру можно всячески подкручивать, а именно: можно взять t гораздо больше (если выборка и железо позволяет); или, например, можно добавлять выходы после каждого нейрона:

 
 

 

 


Можно сделать архитектуру интереснее, а именно продолжить НС дальше, но выходы сделать написать только над последними нейронами:

 

 

Такая архитектура применяется в электронных переводчиках: вначале сеть прогоняет текст целиком, а уже потом показывает ответ. В нашем примере каждый срез состоял из одного нейрона, что очень мало для реальных задач. Можно сделать сеть более глубокой, добавляя новые слои в нейрон (b1,b2,b3,b4). Эти нейроны могут передавать на следующий нейрон не число, а целый вектор. Также слои можно наращивать вверх. При этом нужно понимать, что все её срезы должны быть изоморфны: везде одни и те же константы – v,b,w и т.д. И между слоями можно так же делать связи (v1,v2):

 

 
 


 

 

Задача генерации текста

 

 
 

Для генерации текста (принципиально нового, хоть и похожего на выборку) используется рекуррентная сеть такой архитектуры:

 

У неё t=3, выходы – после каждого нейрона.

Как тренируется такая сеть? У нас есть буква x1, мы пропускаем её через НС, которая предсказывает букву y1. y1 подается во второй объект, предсказывается y2, которая подставляется в y3. Таким образом, тренируется сеть, которая может предсказывать следующую букву текста.

Тренировочная выборка для этой задачи формируется естественным образом: для тренировки таких сетей нужно загружать огромные массивы текстов. Если вы хотите, чтобы сеть писала как Лев Толстой, нужно загрузить все его труды.

Будем считать, чтобы сеть писала как Лев Толстой, загрузим весь текст Анны Карениной, где есть известная фраза:

 

Все счастливые_семьи_похожи_друг_на_друга.

 

Тренировочная выборка формируется по тройкам (все)->(се_), (се_)->(е_с), …. Понятное дело, что в тренировочных вбыорках появятся противоречия в тройках, где левые части – одинаковые, а правеые – разные, но тем не менее НС научится приходить к компромиссу. Чтобы это хорошо работало, нужно буквы превратить в числе, или вектора. Это нужно для того, чтобы уметь сравнивать и находить отклонения ответа, который предсказывает сеть, от того ответа, который требуется, чтобы сформировать функцию потерь.

Более разумный подход заключается в следующем: генерацию текста нужно осуществлять не побуквенно, а по словам. Использовать будем ту же архитектуру, а подставлять вместо букв - слова, однако, сами слова мы подставлять не можем, нужно подавать их представления в многомерном пространстве (вектора). Например, используем технологию Word2vec: подавая на вход вектора, на выходе требуем тоже, чтобы попадались именно вектора, а далее, составляя функцию потерь, мы это всё минимизируем, и НС начинает генерировать текст по словам.

 

 
 

Однажды группа специалистов создала глубокую НС, натренировали НС и начали анализировать, какие значения выдает каждый из нейронов этой сети. Натренировали сеть на базе ответов сайта типа КиноПоиска, где было явно указано, какая тональность.

 

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

Попробуем подать первым словом грубое «Идиот». Если нужный коэффициент специально сделать слишком большим, то далее он должен писать текст в отрицательной тональности. Обычно НС выдавала следующую фразу:

 

Идиотский фильм

 

Было замечено, что если искусственно занизить коэффициент, и написать слово «Идиот», то сеть выдавала отзывы типа такого:

 

Идиот же я, фильм очень интересный!

 


 

Чем больше в архитектуре константа t, тем более сложные зависимости может находить НС. Но, когда t становится слишком большим (нормальная длина 100-200), возникнет очень длинная цепочка срезов нейронной сети, при этом градиент может затухать (т.е., по какому то параметру функция становится близкой к нулю, и мы не можем менять этот параметр w, и параметры НС перестают обновляться, и тренировка НС практически останавливается).

Один из основных методов для борьбы с затуханиями в рекуррентных нейронных сетях – метод LSTM.

 



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2020-07-12 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: