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

  • Применение CNN вне изображений

  • Реализация операции многоканальной свертки

  • Свертка: обратный проход

  • Вычисление градиента одномерной свертки

  • Вычисление градиента параметра

  • Пакеты, 2D-свертки и многоканальность

  • Одномерная свертка с пакетами: прямой проход

  • Одномерная свертка с пакетами: обратный проход

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


    Скачать 4.97 Mb.
    НазваниеГлубокое обучение
    АнкорКурсовая работа
    Дата26.06.2022
    Размер4.97 Mb.
    Формат файлаpdf
    Имя файлаVeydman_S_Glubokoe_obuchenie_Legkaya_razrabotka_proektov_na_Pyth.pdf
    ТипДокументы
    #615357
    страница14 из 22
    1   ...   10   11   12   13   14   15   16   17   ...   22
    166
    Глава 5. Сверточная нейронная сеть
    «Операция пулинга, используемая в сверточных нейронных сетях, яв- ляется большой ошибкой, и сам факт ее работоспособности является катастрофой». И действительно, в самых последних архитектурах CNN
    (например, Residual Networks или ResNets
    1
    ) этот метод используется по минимуму или не используется вообще. В этой книге мы не собираемся реализовывать пулы слоев, и рассмотрели их здесь для полноты обзора.
    Применение CNN вне изображений
    Все описанное выше довольно стандартно для работы с изображениями с использованием нейронных сетей: изображения обычно представлены в виде набора m
    1
    каналов пикселей, где m
    1
    = 1 для черно-белых изображе- ний и m
    1
    = 3 для цветных изображений. Затем некоторое число m
    2
    опе- раций свертки применяется к каждому каналу (с помощью карт m
    1
    × m
    2
    ), причем этот шаблон действует для нескольких слоев. Все это используется весьма часто. Реже используется идея разделения данных на «каналы» и последующая обработка этих данных с использованием CNN, причем этот метод подходит не только для изображений. Например, именно так была создана AlphaGo DeepMind, которая показала, что нейронные сети могут научиться играть в го. Приведу выдержку из статьи
    2
    :
    Нейронная сеть принимает на вход стек изображений 19 × 19 × 17, содержащий 17 плоскостей двоичных объектов. Восемь характерных плоскостей X
    t
    состоят из двоичных значений, указывающих наличие камней текущего игрока (
    , если пересечение i содержит камень цвета игрока на временном шаге t; 0, если пересечение пусто, содер- жит камень противника или если t < 0). Еще восемь конструктивных плоскостей Y
    t
    представляют соответствующие элементы для камней противника. Последняя характерная плоскость, C, представляет цвет для воспроизведения и имеет постоянное значение либо 1, если ходят черные, либо 0, если ходят белые. Эти плоскости объединяются вместе для получения входных объектов s
    t
    = X
    t
    , Y
    t
    , X
    t–1
    , Y
    t–1
    ,…, X
    t–7
    ,
    Y
    t–7
    , C. Необходимы также старые значения признаков X
    t
    и Y
    t
    , по- скольку имеет значение не только текущее положение камней, в свя- зи с тем что повторы запрещены.
    1
    См. оригинальную статью ResNet Deep Residual Learning for Image Recognition,
    Kaiming He et al.
    2
    DeepMind (Дэвид Сильвер и др.), Mastering the Game of Go Without Human Knowledge,
    2017.

    Реализация операции многоканальной свертки
    167
    В их игре поле представлялось в виде «изображения» размером 19
    × 19 пикселей с 17 каналами! Шестнадцать из этих каналов кодируют то, что произошло на восьми последних ходах, которые сделал каждый игрок. Это было необходимо для того, чтобы запретить повтор предыдущих шагов.
    Семнадцатый канал представляет собой сетку 19
    × 19, состоящую из всех
    1 или 0, в зависимости от того, чей сейчас ход
    1
    . CNN и их операции много- канальной свертки обычно применяются к изображениям, но сама идея представления данных в виде некоторого пространственного измерения с несколькими «каналами» применима и в других задачах.
    Чтобы по-настоящему понять операцию многоканальной свертки, мы должны реализовать ее с чистого листа. Давайте приступим!
    Реализация операции многоканальной свертки
    Оказывается, что реализация этой жутко сложной операции с четырехмер- ным ndarray на входе и четырехмерным ndarray параметров будет намного понятнее, если сначала рассмотреть одномерный случай. Справившись с этим, конечный результат можно будет получить простым добавлени- ем циклов for
    . Как обычно, сначала будут схемы, затем математические представления и код на Python.
    Прямой проход
    Свертка в одном измерении, по сути, аналогична свертке в двух измерени- ях: мы берем одномерный вход и одномерный сверточный фильтр, а затем создаем выходные данные, перемещая фильтр вдоль входных данных.
    Давайте предположим, что вход имеет длину 5:
    А размер «шаблонов», которые мы хотим обнаружить, имеет длину 3:
    1
    Год спустя DeepMind опубликовали результаты, используя аналогичный принцип с шахматами — только на этот раз, чтобы закодировать более сложный набор правил шахмат, на входе было 119 каналов! См. DeepMind (Дэвид Сильвер и др.), A General
    Reinforcement Learning Algorithm That Masters Chess, Shogi, and Go Through Self-Play.

    168
    Глава 5. Сверточная нейронная сеть
    Схемы и математическое представление
    Первый элемент вывода создается путем свертки первого элемента фильтром:
    Второй элемент выходных данных создается путем перемещения фильтра на одну единицу вправо и его свертки со следующими значениями:
    Для следующего выходного значения нам не хватило места:
    Весь ввод обработан, и в результате мы получили только три элемента, хотя начали с пяти! Как быть?
    Дополнение изображения
    Чтобы избежать уменьшения размера выходных данных в результате операции свертки, сделаем трюк, используемый во всех сверточных нейронных сетях: добавим входным данным по краям нули, чтобы выход стал такого же размера, как и вход. Так мы избегаем проблемы, которая у нас возникла.
    Как мы уже поняли, для фильтра размера 3 нужна одна единица отступа по краям, чтобы размер входных данных был правильным. Поскольку мы почти всегда используем нечетные размеры фильтров, размер отступа должен быть равен размеру фильтра, деленному на 2 и округленному до ближайшего целого числа.
    Теперь входных данные нумеруются не от i
    1
    до i
    5
    , а от i
    0
    до i
    6
    , где i
    0
    и i
    6
    равны 0. Затем можно вычислить вывод свертки как:
    и так до:
    Теперь размеры входа и выхода совпадают. Как это реализовать?

    Реализация операции многоканальной свертки
    169
    Код
    Все просто. Прежде чем сделать это, давайте подведем итоги:
    1. Мы хотим получить выход такого же размера, как и вход.
    2. Чтобы сделать это, не «сжимая» выход, нужно добавить данных на вход.
    3. Следует написать цикл, который проходит по входным данным и свертывает каждую позицию с помощью фильтра.
    Начнем с ввода и фильтра:
    input_1d = np.array([1,2,3,4,5])
    param_1d = np.array([1,1,1])
    Функция, добавляющая данные с концов входного вектора:
    def _pad_1d(inp: ndarray, num: int) -> ndarray:
    z = np.array([0])
    z = np.repeat(z, num)
    return np.concatenate([z, inp, z])
    _pad_1d(input_1d, 1)
    array([0., 1., 2., 3., 4., 5., 0.])
    А что насчет самой свертки? Заметьте, что для каждого элемента в выво- де, который мы хотим произвести, есть соответствующий элемент в до- полненном вводе, где «запускается» операция свертки. Как только мы выясним, с чего начать, то просто пройдемся по всем элементам фильтра, выполняя умножение для каждого элемента и добавляя результат к итогу.
    Как найти этот «соответствующий элемент»? Легко! Первый элемент выхода берется от первого элемента входа с учетом отступа:
    def conv_1d(inp: ndarray, param: ndarray) -> ndarray:
    # назначение размерностей assert_dim(inp, 1)
    assert_dim(param, 1)

    170
    Глава 5. Сверточная нейронная сеть
    # отступы param_len = param.shape[0]
    param_mid = param_len // 2
    input_pad = _pad_1d(inp, param_mid)
    # инициализация вывода out = np.zeros(inp.shape)
    # свертка for o in range(out.shape[0]):
    for p in range(param_len):
    out[o] += param[p] * input_pad[o+p]
    # проверка, что форма не меняется assert_same_shape(inp, out)
    return out conv_1d_sum(input_1d, param_1d)
    array([ 3., 6., 9., 12., 9.])
    Все просто. Прежде чем перейти к обратному проходу (а тут уже будет сложно), давайте поговорим о важном гиперпараметре: шаге.
    Немного о шаге
    Ранее отмечалось, что операции пулинга являются одним из способов уменьшения разрешения изображений. Во многих ранних архитектурах сверточных сетей это позволяло значительно сократить объем вычисле- ний без ущерба точности. Однако этот метод потерял популярность из-за своего недостатка: на следующий слой передается лишь одна четвертая часть данных изображения.
    Чаще используется другой метод — изменяется шаг операции свертки.
    Шагом называется количество единиц, на которые фильтр движется по изображению. В примере выше мы использовали шаг 1, то есть каждый фильтр проверялся на каждом элементе входа, поэтому выход получался такого же размера, что и вход. При шаге, равном 2, фильтр «просматри- вает» элементы изображения через один и размер выхода будет вдвое меньше входа. Использование шага = 2 дает следующее: мы передаем на

    Свертка: обратный проход
    171
    следующий слой вдвое меньше данных, но при этом информация о самом изображении не теряется, так как уменьшение числа данных происходит за счет пропуска, а не удаления. Таким образом, использование шага больше 1 является более удачным методом снижения объема данных в современных CNN.
    Для целей этой книги будем придерживаться шага 1, а использование других значений я оставлю вам в качестве самостоятельного упражнения.
    Так будет проще писать обратный проход.
    Свертка: обратный проход
    С обратным проходом сложнее. Давайте вспомним, что нужно сделать: раньше мы производили вывод операции свертки, используя входные данные и параметры. Теперь требуется вычислить:
    y
    Частную производную потерь по каждому элементу входных данных операции свертки — ранее inp y
    Частную производную потерь по каждому элементу фильтра — ранее param_1d
    Вспомните, как работают
    ParamOperations
    , которые затрагивались в главе
    4: в методе backward им передается выходной градиент, показывающий, насколько каждый элемент выходных данных в конечном итоге влияет на потери. Затем этот выходной градиент используется для вычисления градиентов входных данных и параметров. Таким образом нужно напи- сать функцию, которая принимает output_grad той же формы, что и input
    , и создает input_grad и param_grad
    Как проверить правильность вычисленных градиентов? Вспомним идею из первой главы: известно, что частная производная суммы по любому из ее входов равна 1 (если сумма s = a + b + c, то
    ). Таким образом, можно вычислить значения input_grad и param_grad с помощью наших функций
    _input_grad и
    _param_grad
    (которые мы напишем в бли- жайшее время) и выхода put_grad
    , заполненного единицами. Затем про- верим правильность этих градиентов, изменив элементы входных данных на некоторую величину
    α, и посмотрим, изменится ли полученная сумма в
    α раз.

    172
    Глава 5. Сверточная нейронная сеть
    Каким должен быть градиент?
    По этому принципу рассчитаем, каким должен быть элемент вектора градиента:
    def conv_1d_sum(inp: ndarray, param: ndarray) -> ndarray:
    out = conv_1d(inp, param)
    return np.sum(out)
    # случайное увеличение пятого элемента на 1
    input_1d_2 = np.array([1,2,3,4,6])
    param_1d = np.array([1,1,1])
    print(conv_1d_sum(input_1d, param_1d))
    print(conv_1d_sum(input_1d_2, param_1d))
    39.0 41.0
    Итак, градиент пятого элемента входа должен быть равен 41 – 39 = 2.
    Давайте порассуждаем, как вычислять такой градиент, не просто рассчитав разницу между этими двумя суммами. Здесь все становится интересно.
    Вычисление градиента одномерной свертки
    Мы видим, что увеличение этого элемента на входе увеличивает выход на 2. Если внимательно посмотреть на результат, можно увидеть, как именно это происходит:
    Этот элемент ввода обозначен t
    5
    . Он появляется в выводе в двух местах:
    y
    В o
    4
    умножается на w
    3
    y
    В o
    5
    умножается на w
    2

    Свертка: обратный проход
    173
    Чтобы понять, как входные данные сопоставляются с суммой выходных данных, обратите внимание, что при наличии элемента o
    6
    элемент t
    5
    тоже бы умножался на w
    1
    Следовательно, влияние t
    5
    на потери, которое мы можем обозначить как
    , будет равно:
    Конечно, в этом простом примере, когда потеря — это просто сумма,
    = 1 для всех элементов выхода (за исключением элементов отступа, для которых это количество равно 0).
    Эту сумму очень легко вычислить: это сумма w
    2
    + w
    3
    , которая равна 2, поскольку w
    2
    = w
    3
    = 1.
    Каков общий шаблон?
    Теперь давайте рассмотрим общий шаблон для элемента ввода. Тут нуж- но правильно отслеживать индексы. Поскольку мы превращаем матема- тические выкладки в код, то используем i-й элемент выходного градиен- та как
    (поскольку в конечном итоге мы будем обращаться к нему через output_grad
    [i]
    ). Тогда:
    Аналогично:
    ,
    а также:
    Здесь есть определенная закономерность, преобразовать которую в код немного сложно, тем более что индексы на выходе увеличиваются,

    174
    Глава 5. Сверточная нейронная сеть а индексы на весах уменьшаются. Это реализуется через вложенный цикл for
    :
    # param: в нашем случае ndarray формы (1,3)
    # param_len: целое число 3
    # inp: в нашем случае ndarray формы (1,5)
    # input_grad: всегда ndarray той же формы, что и «inp»
    # output_pad: в нашем случае ndarray формы (1,7)
    for o in range(inp.shape[0]):
    for p in range(param.shape[0]):
    input_grad[o] += output_pad[o+param_len-p-1] * param[p]
    Такой код увеличивает индексов весов, в то же время уменьшая вес на выходе.
    Пока не совсем неочевидно, но самое сложное здесь — это понять, что к чему относится. Добавление сложности, например размеров пакетов, сверток с двумерными входами или входов с несколькими каналами, — это просто вопрос добавления дополнительных циклов for
    , как мы увидим в следующих нескольких разделах.
    Вычисление градиента параметра
    Аналогично можно рассуждать о том, как увеличение элемента фильтра должно увеличить производительность. Во-первых, давайте увеличим
    (произвольно) первый элемент фильтра на одну единицу и посмотрим, как это повлияет на сумму:
    input_1d = np.array([1,2,3,4,5])
    # увеличиваем случайно выбранный элемент на 1
    param_1d_2 = np.array([2,1,1])
    print(conv_1d_sum(input_1d, param_1d))
    print(conv_1d_sum(input_1d, param_1d_2))
    39.0 49.0
    Получится

    Свертка: обратный проход
    175
    Мы внимательно изучали выходные данные и увидели, какие элементы фильтра влияют на них, а также добавили отступы, чтобы шаблон был виднее. Получилось, что:
    А поскольку для суммы все элементы равны 1, а t
    0
    равно 0, имеем:
    Это подтверждает полученный ранее расчет.
    Код
    Этот код будет проще, чем код для входного градиента, поскольку на этот раз индексы движутся в одном направлении. В том же вложенном цикле код будет таким:
    # param: в нашем случае ndarray формы (1,3)
    # param_grad: ndarray такой же формы, что и param
    # inp: в нашем случае ndarray формы (1,5)
    # input_pad: ndarray такой же формы (1,7)
    # output_grad: в нашем случае ndarray формы (1,5)
    for o in range(inp.shape[0]):
    for p in range(param.shape[0]):
    param_grad[p] += input_pad[o+p] * output_grad[o]
    Наконец, мы можем объединить эти два вычисления и написать функцию для вычисления входного градиента и градиента фильтра с помощью следующих шагов:
    1. Взять вход и фильтр в качестве аргументов.
    2. Вычислить вывод.
    3. Сделать отступ на входе и выходном градиенте (для ввода input_pad и output_pad
    ).
    4. Как показано ранее, использовать выходной градиент с отступом и фильтр для вычисления градиента.
    5. Точно так же использовать выходной градиент (без отступа) и до- полненный вход для вычисления градиента фильтра.

    176
    Глава 5. Сверточная нейронная сеть
    Целиком функция, которая оборачивает показанный выше код, приведена в https://oreil.ly/2H99xkJ
    На этом реализация свертки в 1D закончена! Как мы увидим в следующих нескольких разделах, переход к двумерным входам, пакетам двумерных входов или даже многоканальным пакетам двумерных входов будет на удивление прост.
    Пакеты, 2D-свертки и многоканальность
    Во-первых, дадим функциям свертки возможность работать с пакетами входов — двумерными входами, первое измерение которых представляет собой размер пакета ввода, а второе — длину последовательности 1D:
    input_1d_batch = np.array ([[0,1,2,3,4,5,6],
    [1,2,3,4,5,6,7]])
    Выполним те же шаги, которые были определены ранее: сначала доба- вим входные данные, используя их для вычисления выходных данных, а затем добавим выходной градиент для вычисления градиентов и входа, и фильтров.
    Одномерная свертка с пакетами: прямой проход
    Единственное отличие от добавления второго измерения (размера пакета) состоит в том, что нужно дополнить и вычислить выходные данные для каждого наблюдения отдельно (как мы делали ранее), а затем сложить результаты, чтобы получить пакет выходных данных. Тогда функция conv_1d
    , например, станет такой:
    def conv_1d_batch(inp: ndarray, param: ndarray) -> ndarray:
    outs = [conv_1d(obs, param) for obs in inp]
    return np.stack(outs)
    Одномерная свертка с пакетами: обратный проход
    Обратный проход аналогичен: для вычисления входного градиента теперь используется цикл for для вычисления входного градиента из

    Свертка: обратный проход
    177
    предыдущего раздела, но уже для каждого наблюдения. Результаты суммируются:
    # "_input_grad" — это функция, содержащая цикл for из предыдущей версии:
    # она принимает одномерный вход и фильтр, а также output_gradient,
    # и вычисляет входной градиент grads = [_input_grad(inp[i], param, out_grad[i])[1] for i in range(batch_size)]
    np.stack(grads)
    Градиент для фильтра при работе с серией наблюдений будет немного дру- гим. Это связано с тем, что фильтр свертывается с каждым наблюдением и поэтому связан с каждым наблюдением на выходе. Таким образом, чтобы вычислить градиент параметра, мы должны пройтись по всем наблюдени- ям и увеличить соответствующие значения градиента параметра. Тем не менее для этого достаточно добавить внешний цикл for для вычисления градиента параметра, который мы видели ранее:
    # param: в нашем случае ndarray формы (1,3)
    # param_grad: ndarray такой же формы, что и param
    # inp: в нашем случае ndarray формы (1,5)
    # input_pad: ndarray формы (1,7)
    # output_grad: в нашем случае ndarray формы (1,5)
    for i in range(inp.shape[0]): # inp.shape[0] = 2
    for o in range(inp.shape[1]): # inp.shape[0] = 5
    for p in range(param.shape[0]): # param.shape[0] = 3
    param_grad[p] += input_pad[i][o+p] * output_grad[i][o]
    Это измерение «накладывается» поверх оригинальной одномерной свертки. Превращение свертки в двумерную тоже выполняется просто.
    1   ...   10   11   12   13   14   15   16   17   ...   22


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