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

  • Строительные блоки нейросети: операции

  • Строительные блоки нейросети: слои

  • Рис. 3.4.

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


    Скачать 4.97 Mb.
    НазваниеГлубокое обучение
    АнкорКурсовая работа
    Дата26.06.2022
    Размер4.97 Mb.
    Формат файлаpdf
    Имя файлаVeydman_S_Glubokoe_obuchenie_Legkaya_razrabotka_proektov_na_Pyth.pdf
    ТипДокументы
    #615357
    страница8 из 22
    1   ...   4   5   6   7   8   9   10   11   ...   22
    93
    отдельные функции дифференцируемы, то вся функция будет дифферен- цируемой и мы сможем обучить ее, используя описанные выше 4 шага.
    Но есть и разница. До сих пор мы вычисляли эти производные путем ручного кодирования прямого и обратного проходов, а затем перемножали соответствующие величины, чтобы получить производные. Для простой модели нейронной сети, показанной в главе 2, потребовалось 17 шагов.
    Из-за такого описания не сразу понятно, как можно усложнить модель
    (и в чем будет сама сложность) или хотя бы заменить сигмоиду на другую нелинейную функцию. Чтобы научиться создавать сколь угодно глубокие и сложные модели глубокого обучения, нужно подумать о том, в какой момент на этих 17 этапах мы можем создать нечто более многоразовое, нежели отдельные операции. Это нечто должно позволить создавать разные модели. В качестве первого шага мы попытаемся сопоставить используемые нами операции с традиционными описаниями нейронных сетей, состоящими из «слоев», «нейронов» и т. д.
    Сначала нужно создать абстракцию для представления отдельных опера- ций, над которыми мы работали до сих пор, а не продолжать кодировать одно и то же умножение матриц и прибавление отклонений.
    Строительные блоки нейросети: операции
    Класс
    Operation будет реализовывать одну из функций в сети. Мы уже знаем, что на высоком уровне у такой функции должен быть прямой и об- ратный методы, каждый из которых получает в качестве входных данных и выводит объект ndarray
    . Некоторые операции, например матричное умножение, тоже принимают на вход ndarray
    : параметры. В нашем классе
    Operation или, возможно, в другом классе, который наследуется от него, параметры должны храниться в переменной экземпляра.
    Еще одна мысль: существуют два типа класса
    Operation
    : некоторые, на- пример матричное умножение, возвращают ndarray
    , но в форме, отличной от входной. И наоборот, некоторые операции, такие как сигмоидальная функция, просто применяют некоторую функцию к каждому элементу входного ndarray
    . Как обобщить это? Давайте рассмотрим объекты ndarray
    , которые проходят через наши операции: каждая операция передает вы- ходные данные следующему слою на прямом проходе, а на обратном про- ходе получает «выходной градиент», который будет представлять частную

    94
    Глава 3. Основы глубокого обучения производную потерь относительно каждого элемента вывода
    Operation
    (которая вычисляется другими операциями, составляющими сеть).
    Кроме того, на обратном проходе операция отправляет назад «входной градиент» — частную производную потерь по каждому элементу ввода.
    Эти факты накладывают несколько важных ограничений на работу клас- сов
    Operation
    , но это помогает правильно считать градиент:
    y
    Форма выходного градиента ndarray должна соответствовать форме выходного файла.
    y
    Форма входного градиента, который
    Operation отправляет назад во вре- мя обратного прохода, должна соответствовать форме входа
    Operation
    Вы поймете суть, взглянув на рисунок ниже.
    Визуализация
    На рис. 3.1 мы привели пример операции
    O
    , которая получает входные данные от операции
    N
    и передает выходные данные другой операции
    P
    Вход
    Выход
    N
    входной градиент выходной градиент
    вход
    Прямой проход
    Обратный проход
    Размерности
    входных и выходных градиентов должны совпадать
    выход
    O
    P
    Рис. 3.1. Operation с вводом и выводом
    На рис. 3.2 рассмотрен случай класса
    Operation с параметрами.
    N
    O
    W
    W-град
    Учет параметра во время обратного прохода
    Учет параметра во время прямого прохода
    P
    Вход
    Выход
    входной градиент выходной градиент
    вход
    Прямой проход
    Обратный проход
    Размерности
    входных и выходных градиентов должны совпадать
    выход
    Рис. 3.2. ParamOperation с входом, выходом и параметрами

    Строительные блоки нейросети: операции
    95
    Код
    Теперь мы можем написать базовую часть нашей нейронной сети, класс
    Operation
    :
    class Operation(object):
    '''
    Базовый класс операции в нейросети.
    '''
    def __init__(self):
    pass def forward(self, input_: ndarray):
    '''
    Хранение ввода в атрибуте экземпляра self._input
    Вызов функции self._output().
    '''
    self.input_ = input_
    self.output = self._output()
    return self.output def backward(self, output_grad: ndarray) -> ndarray:
    '''
    Вызов функции self._input_grad().
    Проверка совпадения размерностей.
    '''
    assert_same_shape(self.output, output_grad)
    self.input_grad = self._input_grad(output_grad)
    assert_same_shape(self.input_, self.input_grad)
    return self.input_grad def _output(self) -> ndarray:
    '''
    Метод _output определяется для каждой операции.
    '''
    raise NotImplementedError()

    96
    Глава 3. Основы глубокого обучения def _input_grad(self, output_grad: ndarray) -> ndarray:
    '''
    Метод _input_grad определяется для каждой операции.
    '''
    raise NotImplementedError()
    Для каждой операции, которую мы определяем, нужно будет задать функции
    _output и
    _input_grad
    Такие базовые классы нужны скорее для понимания; важно, чтобы наше представление процесса глубокого обучения соответствовало этой схеме: вперед передаются входные данные, а обратно — гради- ент, и размерности полученного «спереди» соответствуют тому, что отправляется «назад», и наоборот.
    Позже определим конкретные операции, которые мы уже использовали, например умножение матриц. Но сначала определим еще один класс, который наследует от
    Operation и который мы будем использовать специ- ально для операций с параметрами:
    class ParamOperation(Operation):
    '''
    Операция с параметрами.
    '''
    def __init__(self, param: ndarray) -> ndarray:
    '''
    Метод ParamOperation
    '''
    super().__init__()
    self.param = param def backward(self, output_grad: ndarray) -> ndarray:
    '''
    Вызов self._input_grad и self._param_grad.
    Проверка размерностей.
    '''
    assert_same_shape(self.output, output_grad)

    Строительные блоки нейросети: слои
    97
    self.input_grad = self._input_grad(output_grad)
    self.param_grad = self._param_grad(output_grad)
    assert_same_shape(self.input_, self.input_grad)
    assert_same_shape(self.param, self.param_grad)
    return self.input_grad def _param_grad(self, output_grad: ndarray) -> ndarray:
    '''
    Во всех подклассах ParamOperation должна быть реализация метода _param_grad.
    '''
    raise NotImplementedError()
    Подобно базовому классу
    Operation
    , отдельная операция
    ParamOperation должна определять функцию
    _param_grad в дополнение к функциям
    _output и
    _input_grad
    Теперь мы формализовали строительные блоки нейронной сети, которые использовали в наших моделях до сих пор. Мы могли бы пропустить и определить нейронные сети непосредственно в терминах этих операций, но есть промежуточный класс, вокруг которого мы плясали в течение полутора глав и который мы определим в первую очередь: слой.
    Строительные блоки нейросети: слои
    С точки зрения операций слой — это набор линейных операций, за которыми следует нелинейная операция. Например, в нейронной сети из предыдущей главы было пять операций: две линейные операции — умножение на вес и добавление смещения, потом сигмоида, а затем еще две линейные операции. В этом случае мы бы сказали, что первые три операции, вплоть до нелинейной, будут составлять первый слой, а по- следние две операции — второй слой. Входные данные — это тоже особый вид слоя, называемый входным слоем (с точки зрения нумерации слоев этот слой не считается, так что пусть он будет «нулевым»). Последний слой аналогично называется выходным слоем. Средний слой — «пер- вый» — называется скрытым, так как только на этом слое значения явно не отображаются во время обучения.

    98
    Глава 3. Основы глубокого обучения
    Выходной слой тоже особенный, поскольку к нему не нужно применять нелинейную операцию. Это связано с тем, что нам надо на выходе из этого слоя получать значения между отрицательной бесконечностью и бесконечностью (или, по крайней мере, между 0 и бесконечностью), а нелинейные функции обычно «сдавливают» входные данные до некото- рого подмножества этого диапазона (например, сигмоидальная функция сокращает свой ввод до значения от 0 до 1).
    Визуализация
    Для наглядности на рис. 3.3 показана схема нейронной сети из предыду- щей главы, при этом отдельные операции разбиты на слои.
    γ
    X
    Входной слой
    Слой 1
    Слой 2
    B
    1
    M
    1
    N
    1
    O
    1
    M
    2
    P
    W
    1
    γ
    α
    δ
    L
    α
    Λ
    B
    2
    Y
    W
    2
    Чем меньше, тем лучше
    Рис. 3.3. Нейронная сеть из предыдущей главы с группировкой операций на слои
    Из рисунка видно, что входные данные представляют «входной» слой, следующие три операции (до сигмоиды) — следующий слой, а последние две операции — последний слой.
    Выглядит довольно громоздко. Но так и должно быть, так как представле- ние нейронной сети в виде последовательности операций хоть и позволяет понять, как сети обучаются и работают, является слишком «низкоуров- невым» для чего-либо более сложного, чем двухслойная нейронная сеть.
    Поэтому наиболее распространенный способ представления нейронных сетей — это слои (рис. 3.4).
    Связь с мозгом
    Осталось затронуть пару вопросов, чтобы все встало на свои места. Можно сказать, что на каждом слое находится определенное количество нейронов, равное размерности вектора наблюдений на выходе слоя. Таким образом,

    Строительные блоки нейросети: слои
    99
    у нейронной сети из предыдущего примера 13 нейронов на входном слое,
    13 нейронов на скрытом слое и один нейрон на выходном слое.
    Нейроны в человеческом мозге тоже могут получать входные сигналы от многих других нейронов, после чего отправляют свой собственный сигнал вперед, если полученные ими сигналы совокупно достаточно «мощные» для активации. Нейроны в контексте нейронных сетей работают похо- жим образом: их выходные сигналы зависят от входов, но входы преоб- разуются в выходы с помощью нелинейной функции. Таким образом, эта нелинейная функция называется функцией активации, а выходящие из нее значения — активациями для этого слоя
    1
    Теперь, когда мы разобрались со слоями, то можем сформулировать более традиционное определение глубокого обучения: модели глубокого
    обучения — это нейронные сети с несколькими скрытыми слоями.
    Такое определение эквивалентно тому, что мы говорили ранее о классах
    Operation
    , поскольку слой — это просто серия операций, завершаемая нелинейной операцией.
    Теперь, когда мы определили базовый класс для наших операций, посмо- трим, как из него составить модели, которые мы видели в предыдущей главе.
    1
    Среди всех функций активации сигмоида, которая отображает входные данные на диапазон между 0 и 1, наиболее точно имитирует фактическую активацию нейро- нов в головном мозге, но в целом функции активации могут иметь и более гладкие и линейные формы.
    0 0.
    0 0
    W
    1
    B
    1
    x
    Скрытый слой
    Круги представляют значения активаций
    0 0
    L
    Y
    0 0.
    0 0
    W
    2
    B
    2
    Выходной слой
    Круги представляют прогнозы
    Значение потерь:
    сумма кругов
    Рис. 3.4. Нейронная сеть из предыдущей главы с точки зрения слоев

    100
    Глава 3. Основы глубокого обучения
    Блочное строительство
    Какие именно операции нужно совершить для реализации упомянутых в предыдущей главе моделей? Мы рассмотрели уже три:
    y матричное умножение входного вектора на матрицу весов;
    y добавление отклонения;
    y сигмоидная функция активации.
    Начнем с операции
    WeightMultiply
    :
    class WeightMultiply(ParamOperation):
    '''
    Умножение весов в нейронной сети.
    '''
    def __init__(self, W: ndarray):
    '''
    Инициализация класса Operation с self.param = W.
    '''
    super().__init__(W)
    def _output(self) -> ndarray:
    '''
    Вычисление выхода.
    '''
    return np.dot(self.input_, self.param)
    def _input_grad(self, output_grad: ndarray) -> ndarray:
    '''
    Вычисление выходного градиента.
    '''
    return np.dot(output_grad, np.transpose(self.param, (1, 0)))
    def _param_grad(self, output_grad: ndarray) -> ndarray:
    '''
    Вычисление градиента параметров.
    '''
    return np.dot(np.transpose(self.input_, (1, 0)), output_grad)

    Блочное строительство
    101
    Здесь реализовано умножение матриц на прямом проходе, а также правила
    «возврата градиентов» входов и параметров на обратном проходе (это правило мы обсудили в конце главы 1). Как вы вскоре увидите, мы можем использовать этот класс как строительный блок для составления слоев.
    Далее идет операция суммирования, которую мы назовем
    BiasAdd
    :
    class BiasAdd(ParamOperation):
    '''
    Прибавление отклонений.
    '''
    def __init__(self,
    B: ndarray):
    '''
    Инициализация класса Operation с self.param = B.
    Проверка размерностей.
    '''
    assert B.shape[0] == 1
    super().__init__(B)
    def _output(self) -> ndarray:
    '''
    Вычисление выхода.
    '''
    return self.input_ + self.param def _input_grad(self, output_grad: ndarray) -> ndarray:
    '''
    Вычисление входного градиента.
    '''
    return np.ones_like(self.input_) * output_grad def _param_grad(self, output_grad: ndarray) -> ndarray:
    '''
    Вычисление градиента параметров.
    '''
    param_grad = np.ones_like(self.param) * output_grad return np.sum(param_grad, axis=0).reshape(1, param_grad.shape[1])

    102
    Глава 3. Основы глубокого обучения
    Наконец, реализуем сигмоиду:
    class Sigmoid(Operation):
    '''
    Сигмоидная функция активации.
    '''
    def __init__(self) -> None:
    '''пока ничего не делаем'''
    super().__init__()
    def _output(self) -> ndarray:
    '''
    Вычисление выхода.
    '''
    return 1.0/(1.0+np.exp(-1.0 * self.input_))
    def _input_grad(self, output_grad: ndarray) -> ndarray:
    '''
    Вычисление входного градиента.
    '''
    sigmoid_backward = self.output * (1.0 — self.output)
    input_grad = sigmoid_backward * output_grad return input_grad
    Мы просто реализовали математику, описанную в предыдущей главе.
    И для сигмоиды, и для
    ParamOperation шагом обратного прохода, где мы вычисляем:
    input_grad = <что-то> * output_grad
    ,
    является шаг, где мы применяем цепное правило, а соответствующее правило для
    WeightMultiply
    :
    np.dot (output_grad, np.transpose (self.param, (1, 0)))
    ,
    как мы отмечали в главе 1, является аналогом цепного правила, когда рассматриваемая функция является матричным умножением.
    С операциями закончили, теперь можно переходить к определению класса
    Layer

    Блочное строительство
    103
    Шаблон слоя
    Написанный класс
    Operation поможет написать класс
    Layer
    :
    y
    Методы прямого и обратного прохода просто включают последователь- ную отправку входных данных через последовательность операций, что мы и показывали на графиках. Это самое важное, а все остальное — обертка и сопутствующие действия:
    x определение правильной последовательности
    Operation в функции
    _setup_layer и инициализация и сохранение параметров в этих
    Operation
    (то же самое делается в функции
    _setup_layer
    );
    x сохранение правильных значений в self.input_
    и self.output для прямого метода;
    x выполнение проверки правильности в обратном методе.
    y
    Наконец, функции
    _params и
    _param_grads просто извлекают параме- тры и их градиенты (относительно потерь) из
    ParamOperations в слое.
    Вот как все это выглядит:
    class Layer(object):
    '''
    Слой нейронов в нейросети.
    '''
    def __init__(self,
    neurons: int):
    '''
    Число нейронов примерно соответствует «ширине» слоя '''
    self.neurons = neurons self.first = True self.params: List[ndarray] = []
    self.param_grads: List[ndarray] = []
    self.operations: List[Operation] = []
    def _setup_layer(self, num_in: int) -> None:
    '''
    Функция _setup_layer реализуется в каждом слое.
    '''
    raise NotImplementedError()

    104
    Глава 3. Основы глубокого обучения def forward(self, input_: ndarray) -> ndarray:
    '''
    Передача входа вперед через серию операций.
    '''
    if self.first:
    self._setup_layer(input_)
    self.first = False self.input_ = input_
    for operation in self.operations:
    input_ = operation.forward(input_)
    self.output = input_
    return self.output def backward(self, output_grad: ndarray) -> ndarray:
    '''
    Передача output_grad назад через серию операций.
    Проверка размерностей.
    '''
    assert_same_shape(self.output, output_grad)
    for operation in reversed(self.operations):
    output_grad = operation.backward(output_grad)
    input_grad = output_grad self._param_grads()
    return input_grad def _param_grads(self) -> ndarray:
    '''
    Извлечение _param_grads из операций слоя.
    '''
    self.param_grads = []
    for operation in self.operations:
    if issubclass(operation.__class__, ParamOperation):

    Блочное строительство
    105
    self.param_grads.append(operation.param_grad)
    def _params(self) -> ndarray:
    '''
    Извлечение _params из операций слоя.
    '''
    self.params = []
    for operation in self.operations:
    if issubclass(operation.__class__, ParamOperation):
    self.params.append(operation.param)
    Подобно тому как мы перешли от абстрактного определения класса
    Operation к реализации конкретных операций, необходимых для нейрон- ной сети, сделаем то же самое со слоями.
    Полносвязный слой
    Операции мы называли по смыслу —
    WeightMultiply
    ,
    BiasAdd и т. д. А как назвать слой? Слой
    LinearNonLinear или как?
    Определяющей характеристикой этого слоя является то, что каждый
    выходной нейрон является функцией всех входных нейронов. Именно так и работает умножение матриц: если матрица состоит из n строк и столбцов, результатом умножения будет n новых признаков, каждый из которых представляет собой взвешенную линейную комбинацию всех n входных объектов
    1
    . Таким образом, эти слои часто называют полносвязными сло- ями, или плотными слоями.
    С названием определились, теперь определим класс
    Dense с точки зрения операций, которые мы уже определили. Уже понятно, что все, что нам нужно сделать, это поместить операции, определенные в предыдущем разделе, в список функции
    _setup_layer class Dense(Layer):
    '''
    Полносвязный слой, наследующий от Layer.
    1
    Как мы увидим в главе 5, это относится не ко всем слоям: например, в сверточных слоях каждый выходной объект является комбинацией лишь небольшого подмно- жества входных объектов.

    1   ...   4   5   6   7   8   9   10   11   ...   22


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