Главная страница
Навигация по странице:

  • Первый класс для RNN: RNNLayer

  • Второй класс для RNN: RNNNode

  • Объединение двух классов

  • Накопление градиентов для весов в РНН

  • Курсовая работа. Глубокое обучение


    Скачать 4.97 Mb.
    НазваниеГлубокое обучение
    АнкорКурсовая работа
    Дата26.06.2022
    Размер4.97 Mb.
    Формат файлаpdf
    Имя файлаVeydman_S_Glubokoe_obuchenie_Legkaya_razrabotka_proektov_na_Pyth.pdf
    ТипДокументы
    #615357
    страница17 из 22
    1   ...   14   15   16   17   18   19   20   21   22
    Рис. 6.3. Обычная нейронная сеть, в которой наблюдение передается вперед и преобразуется в разные представления после каждого слоя
    Однако когда через сеть пройдет следующий набор наблюдений, эти представ- ления удалятся. Ключевым нововведением рекуррентных нейронных сетей и всех их вариантов является передача этих представлений обратно в сеть вместе со следующим набором наблюдений. Это будет выглядеть вот так:
    1. На первом временном шаге t = 1
    мы пропускаем через сеть наблю- дение с первого временного шага (возможно, с некоторыми случайно инициализированными представлениями). Получаем прогноз для t = 1
    , а также представления на каждом слое.

    202
    Глава 6. Рекуррентные нейронные сети
    2. На следующем временном шаге мы пропускаем наблюдение со вто- рого временного шага, t = 2
    вместе с представлениями, вычисленны- ми на первом временном шаге (которые, опять же, являются просто выходами слоев нейронной сети), и каким-то образом объединяем их (именно по способам объединения и различаются варианты RNN, о которых чуть позже). И та и другая информация используется для вычисления прогноза для t = 2
    , а также обновленных представле- ний на каждом слое, которые теперь являются функцией входных данных, передаваемых при t = 1
    и при t = 2 3. На третьем временном шаге мы передаем наблюдение от t = 3
    , а так- же представления, которые теперь включают информацию от t = 1
    и t = 2
    , используем эту информацию, чтобы составить прогнозы для t = 3
    , а также обновляем представления каждого слоя, которые теперь содержат информацию из временных шагов 1–3.
    Этот процесс изображен на рис. 6.4.
    Вход
    (t = 1)
    Временной шаг 1:
    (t =1)
    Скрытый вектор
    (t = 1)
    Скрытый вектор n
    (t = 1)
    W
    1
    W
    2
    W
    r1
    W
    n
    W
    n+1
    Вход
    (t = 2)
    Временной шаг 2:
    (t = 2)
    W
    1
    W
    2
    W
    n
    W
    n+1
    Вектор прогноза
    (t = 2)
    Вектор прогноза (t = 2)
    W
    rN
    W
    r1
    W
    rN
    Рис. 6.4. В рекуррентных сетях представления каждого слоя используются на следующих временных шагах
    У каждого слоя есть «постоянное» представление, постепенно обновляю- щееся по мере прохождения новых наблюдений. Именно поэтому RNN не

    Введение в рекуррентные нейронные сети
    203
    позволяют использовать придуманную нами структуру
    Operation
    , которую мы сделали в предыдущих главах: объект ndarray
    , содержащий состояние каждого слоя, постоянно обновляется и многократно используется для составления прогнозов для последовательности данных в RNN. Поскольку мы не можем использовать структуру из предыдущей главы, начать при- дется с понимания того, какие классы потребуются для работы с RNN.
    Первый класс для RNN: RNNLayer
    Исходя из описания того, чего мы ожидаем от RNN, становится ясно, что нам понадобится класс
    RNNLayer
    , который будет передавать последователь- ность данных по одному элементу последовательности за раз. Давайте теперь подробно рассмотрим, как такой класс должен работать. Как мы говорили в этой главе, RNN работает с данными, в которых каждое на- блюдение является двумерным и имеет размерность
    (sequence_length,
    num_features)
    ; и поскольку с вычислительной точки зрения всегда более эффективно передавать данные в пакетном режиме, класс
    RNNLayer должен принимать трехмерные объекты ndarray размера
    (batch_size,
    sequence_
    length,
    num_features)
    . Однако в предыдущем разделе я объяснил, что мы хотим передавать наши данные через
    RNNLayer по одному элементу последовательности за раз. Как это сделать, имея формат данных, данные
    (batch_size,
    sequence_length,
    num_features)
    ? А вот как:
    1. Выберем двумерный массив по второй размерности начиная с data
    [:,
    0,:]
    . Этот ndarray будет иметь форму (
    batch_size
    , num_features
    ).
    2. Инициализируем «скрытое состояние» для объекта
    RNNLayer
    , кото- рое будет постоянно обновляться по мере передачи элементов по- следовательности, оно будет иметь форму (
    batch_size
    , hidden_size
    ).
    Этот ndarray хранит «накопленную информацию» слоя о данных, которые были переданы на предыдущих временных шагах.
    3. Пропустим эти два ndarray вперед через первый временной шаг в этом слое. В конечном итоге наш
    RNNLayer будет выводить ndarrays не той же размерности, что были на входе, в отличие от плотных сло- ев, поэтому выходные данные будут иметь форму
    (batch_size
    , num_
    outputs
    ). Кроме того, нужно обновить представление нейронной сети для каждого наблюдения: на каждом временном шаге наш
    RNNLayer должен также выводить ndarray формы (
    batch_size
    , hidden_size
    ).
    4. Выбираем следующий двумерный массив из данных: data
    [:,
    1,:]

    204
    Глава 6. Рекуррентные нейронные сети
    5. Передаем эти данные, а также значения представлений RNN, выве- денных на первом временном шаге, на второй временной шаг на этом слое, чтобы получить еще один вывод формы (
    batch_size
    , num_outputs
    ), а также обновленные представления формы (
    batch_size
    , hidden_size
    ).
    6. Продолжаем эти действия, пока через слой не пройдут все вре- менные шаги в количестве sequence_length
    . Затем объединяем все результаты, чтобы получить выходные данные этого слоя формы
    (
    batch_size
    , sequence_length
    , num_outputs
    ).
    Этот алгоритм задает представление о том, как должен работать класс
    RNNLayer
    , и мы разберемся еще лучше, когда будем писать код. Но это еще не все — нам понадобится еще один класс для получения данных и обновления скрытого состояния слоя на каждом шаге. Для этого мы будем использовать
    RNNNode
    Второй класс для RNN: RNNNode
    Исходя из описания, которое мы привели в предыдущем разделе, класс
    RNNNode должен иметь метод forward со следующими входами и выходами:
    y
    Два ndarray в качестве входных данных:
    x один для ввода данных в сеть, с формой
    [batch_size,
    num_fea tures]
    ;
    x один для представлений наблюдений на этом временном шаге, с формой
    [batch_size,
    hidden_size]
    y
    Два ndarray в качестве выходных данных:
    x один для выходных сети на этом временном шаге, с формой
    [batch_
    size,
    num_outputs]
    ;
    x один для обновленных представлений наблюдений на этом времен- ном шаге, с формой
    [batch_size,
    hidden_size]
    Далее мы покажем, как классы
    RNNNode и
    RNNLayer будут работать вместе.
    Объединение двух классов
    Класс
    RNNLayer оборачивается вокруг списка объектов
    RNNNode и (по край- ней мере) будет содержать метод forward
    , у которого будут следующие входные и выходные данные:

    Введение в рекуррентные нейронные сети
    205
    y входные данные: пакет последовательностей наблюдений формы
    [batch_size,
    sequence_length,
    num_features]
    ;
    y выходные данные: выход нейронной сети для этих последовательностей формы
    [batch_size,
    sequence_length,
    num_outputs]
    На рис. 6.5 показан порядок передачи данных через RNN с двумя слоями
    RNNL
    по пять узлов RNN в каждом. На каждом временном шаге входные данные, изначально имеющие размерность feature_size
    , последователь- но передаются через первый
    RNNNode в каждом
    RNNLayer
    , при этом сеть в конечном итоге выводит на этом временном шаге прогноз размерности output_size
    . Кроме того, каждый
    RNNNode передает «скрытое состояние» следующему
    RNNNode в каждом слое. Как только данные каждого из пяти временных шагов пройдут через все слои, мы получим окончательный набор прогнозов формы
    (5,
    output_size)
    , где output_size должен быть того же размера, что и цель. Затем эти прогнозы сравниваются с целевы- ми значениями, и рассчитывается градиент потерь на обратном проходе.
    На рис. 6.5 все это показано вместе на примере того, как данные формата
    5
    × 2
    RNNNode проходят от первого до последнего (10) слоя,
    hidden_size hidden_size
    [batch_size, feature_size]
    RNNLayer
    RNNLayer
    RNNNode
    Выходной массив: [batch_size,
    sequence_length, output_size]
    Входной массив:
    [batch_size, sequence_length, feature_size]
    2
    4
    6
    8
    10
    1
    3
    5
    7
    9
    Рис. 6.5. Порядок передачи данных через RNN с двумя слоями, разработанную для обработки последовательностей длиной 5
    В качестве альтернативы данные могут проходить через RNN в порядке, показанном на рис. 6.6. Каким бы порядок ни был, в целом, должно про- изойти следующее:

    206
    Глава 6. Рекуррентные нейронные сети y
    Каждый слой должен обрабатывать данные до следующего слоя, на- пример на рис. 6.5 пункт 2 не может произойти раньше 1, а 4 не может произойти раньше 3.
    y
    Аналогично каждый слой должен обрабатывать все свои временные шаги по порядку — например, на рис. 6.5 пункт 4 не может происходить раньше 2, а 3 не может происходить раньше 1.
    y
    Последний слой должен иметь размерность выходных данных feature_
    size для каждого наблюдения.
    hidden_size hidden_size
    [batch_size, feature_size]
    RNNLayer
    RNNLayer
    RNNNode
    Выходной массив: [batch_size,
    sequence_length, output_size]
    Входной массив:
    [batch_size, sequence_length, feature_size]
    6
    7
    8
    9
    10
    1
    2
    3
    4
    5
    Рис. 6.6. Другой вариант порядка прохождения данных через ту же RNN на прямом проходе
    Все это касается прямого прохода RNN. А что насчет обратного прохода?
    Обратный проход
    Обратное распространение через рекуррентные нейронные сети часто описывается как отдельный алгоритм, называемый «обратное распростра- нение по времени». Название соответствует тому, как это происходит, но звучит намного сложнее, чем на самом деле. Помня наше рассуждение о том, как данные передаются через RNN, мы можем описать, что про- исходит на обратном проходе, следующим образом: обратный проход по
    RNN есть передача градиентов в обратном направлении через сеть в по- рядке, обратном тому, в каком мы передавали входные данные на прямом проходе. То есть мы делаем то же самое, что и в обычных сетях.

    Введение в рекуррентные нейронные сети
    207
    Исходя из рис. 6.5 и 6.6, на прямом проходе происходит следующее:
    1. Изначально есть серия наблюдений, каждое из которых имеет форму
    (feature_size
    , sequence_length)
    2. Эти входные данные разбиваются на отдельные элементы sequence_
    length и передаются в сеть по одному.
    3. Каждый элемент проходит через все слои, и в конечном итоге полу- чается выход размера output_size
    4. Одновременно с этим слой передает скрытое состояние для расчетов в этом слое на следующем временном шаге.
    5. Это проделывается для всех временных шагов sequence_length
    , в результате чего получается выход размера (
    output_size
    , sequence_
    length
    ).
    Обратное распространение работает так же, но наоборот:
    1. Изначально у нас есть градиент формы
    [output_size, sequence_
    length]
    , который говорит о том, как сильно каждый элемент вы- ходных данных (тоже размера
    [output_size, sequence_length]
    ) в конечном итоге влияет на потери для данной серии наблюдений.
    2. Эти градиенты разбиваются на отдельные элементы sequence_length и пропускаются через слои в обратном порядке.
    3. Градиент каждого элемента передается через все слои.
    4. Одновременно с этим слои передают градиент потерь по отношению
    к скрытому состоянию для данного временного шага назад в вычис- ления слоев на предыдущие шаги.
    5. Это продолжается для всех временных шагов sequence_length
    , пока градиенты не будут переданы назад каждому слою в сети, что позво- лит вычислить градиент потерь по каждому весу, как мы это делаем в обычных нейросетях.
    Соотношение между обратным и прямым проходами показано на рис. 6.7, из которого видно, как данные проходят через RNN во время обратного прохода. Вы, конечно, заметите, что это то же самое, что и на рис. 6.5, но с перевернутыми стрелками и измененными числами.
    На высоком уровне прямой и обратный проходы для слоя
    RNNLay очень похожи на проходы слоя в обычной нейронной сети: на вход приходит

    208
    Глава 6. Рекуррентные нейронные сети ndarray определенной формы, на выходе получается ndarray другой фор- мы, а на обратном проходе приходит выходной градиент той же формы, что и их выходные данные, и создается входной градиент той же формы, что и входные данные. Но есть важное отличие в том, как в
    RNNLayer об- рабатываются градиенты веса по сравнению с другими слоями, поэтому кратко рассмотрим этот вопрос, прежде чем перейти к написанию кода.
    hidden_size
    (опционально)
    hidden_size
    [batch_size, feature_size]
    Выходной градиент: [batch_size,
    sequence_length, output_size]
    [batch_size,
    output_size]
    Входной массив:
    [batch_size, sequence_length, feature_size]
    9
    7
    5
    3
    1
    10
    8
    6
    4
    2
    Рис. 6.7. На обратном проходе RNN передают данные в направлении, противоположном тому, как данные передаются во время прямого прохода
    Накопление градиентов для весов в РНН
    В рекуррентных нейронных сетях, как и в обычных нейросетях, у каждого слоя свой набор весов. Это означает, что один и тот же набор весов будет влиять на вывод слоя на всех временных шагах sequence_length
    . Следо- вательно, во время обратного распространения один и тот же набор весов будет получать разные градиенты sequence_length
    . Например, в кружке под номером «1» в схеме обратного распространения, показанной на рис. 6.7, второй слой получит градиент для последнего временного шага, а в кружке под номером «3» слой получит градиент предпоследнего шага.
    В обоих случаях будет использоваться один и тот же набор весов. Таким образом, во время обратного распространения нужно будет накапливать градиенты для весов по последовательности временных шагов. Это оз- начает, что независимо от выбранного способа хранения весов придется обновлять градиенты следующим образом:
    weight_grad += grad_from_time_step

    RNN: код
    209
    Этот подход отличается от слоев
    Dense и
    Conv2D
    , в которых мы просто сохраняли параметры в аргументе param_grad
    Мы раскрыли, как работают RNN и какие классы потребуются для их реализации; теперь поговорим подробнее.
    RNN: код
    Давайте начнем с тех вещей, в которых реализация RNN будет аналогична другим реализациям нейронных сетей, которые мы рассмотрели в этой книге:
    1. RNN по-прежнему передает данные по слоям, при этом на прямом про- ходе передаются данные, а на обратном проходе — градиенты. Таким образом, независимо от того, каким окажется эквивалент нашего клас- са NeuralNetwork, у него все равно будет список RNNLayer в качестве атрибута слоев и прямой проход будет реализован примерно так:
    def forward(self, x_batch: ndarray) -> ndarray:
    assert_dim(ndarray, 3)
    x_out = x_batch for layer in self.layers:
    x_out = layer.forward(x_out)
    return x_out
    2. Реализация
    Loss у RNN будут такой же, как и раньше: выходной ndarray производится последним
    Layer и сравнивается с вектором y_batch
    , вычисляется одно значение и градиент этого значения от- носительно входа в
    Loss с той же формой возвращается в качестве вывода. Нужно изменить функцию softmax
    , чтобы она работала соответствующим образом с ndarrays формы
    [batch_size,
    sequence_
    length,
    feature_size]
    , но это не проблема.
    3. Класс
    Trainer в основном не меняется: мы циклически перебираем наши обучающие данные, выбираем пакеты входных данных и паке- ты выходных данных и непрерывно передаем их через нашу модель, получая значения потерь, которые говорят нам, обучается ли наша модель, и обновляем весовые коэффициенты после каждой партии.
    Кстати, о весах…

    210
    Глава 6. Рекуррентные нейронные сети
    4. Класс
    Optimizer остается прежним. Как мы увидим, придется на каж- дом временном шаге обновлять способ извлечения params и param_
    grads
    , но «правила обновления» (которые мы записали в функции
    _update_rule в нашем классе) остаются прежними.
    Интересные вещи появляются именно в классе
    Layer
    Класс RNNLayer
    Ранее мы давали классу
    Layer набор
    Operation
    , который передавал данные вперед, а градиенты — назад. Слои
    RNNLayer будут совершенно другими.
    В них должно поддерживаться «скрытое состояние», которое постоянно обновляется по мере поступления новых данных и каким-то образом
    «объединяется» с данными на каждом временном шаге. Как именно это должно работать? За основу можно взять рис. 6.5 и 6.6. В них предпо- лагается, что у каждого
    RNNLayer должен быть список
    RNNNode в качестве атрибута, после чего каждый элемент последовательности из входных данных слоя должен проходить через каждый
    RNNNode
    , по одному за раз.
    Каждый
    RNNNode будет принимать этот элемент последовательности, а так- же «скрытое состояние» для этого слоя и создавать выходные данные для слоя на этом временном шаге, а также обновлять скрытое состояние слоя.
    Чтобы прояснить все это, давайте углубимся в код: рассмотрим по порядку, как должен инициализироваться класс
    RNNLayer
    , как он должен переда- вать данные вперед во время прямого прохода и как должен отправлять данные назад во время обратного хода.
    Инициализация
    У экземпляра
    RNNLayer должны быть:
    y целое значение hidden_size
    ;
    y целое значение output_size
    ;
    y ndarray start_H
    формы
    (1,
    hidden_size)
    , представляющей собой скры- тое состояние слоя.
    Кроме того, как и в обычных нейронных сетях, мы установим флаг self.
    first
    =
    True при инициализации слоя. При первой передаче данных в метод forward мы передадим полученный массив ndarray в метод
    _init_params
    , инициализируем параметры и установим self.first
    =
    False

    RNN: код
    1   ...   14   15   16   17   18   19   20   21   22


    написать администратору сайта