Глубокое обучение с нулевым входом (4) - сверточная нейронная сеть

машинное обучение искусственный интеллект глубокое обучение Нейронные сети
Глубокое обучение с нулевым входом (4) - сверточная нейронная сеть

Глубокое обучение с нулевым входом (4) - сверточная нейронная сеть

机器学习 深度学习入门


Является ли грядущая эра эрой больших данных или искусственного интеллекта, или эрой традиционных отраслей, использующих искусственный интеллект для обработки больших данных в облаке, как программист с идеалами и стремлениями, он не понимает превосходного процесса глубокого обучения ( Глубокое обучение) Горячая технология, будет ли ощущение, что она выйдет сразу? Теперь наступает спасительная соломинка: серия статей «Введение в глубокое обучение с нулевыми основами» призвана помочь любителям программирования достичь начального уровня с нулевых основ. Нулевой фундамент означает, что вам не нужно слишком много математических знаний, если вы можете писать программы, да, эта статья написана специально для программистов. Хотя в тексте будет много формул, которые вы можете не понять, но в то же время будет больше кодов, понятных вам как программисту (меня окружает группа фанатичных чистильщиков Программист кода, поэтому я не могу писать плохой код).

Список статей

Глубокое обучение с нулевой базой (1) — персептрон
Глубокое обучение с нулевым входом (2) — линейная единица и градиентный спуск
Введение в глубокое обучение с нулевыми основами (3) — нейронные сети и алгоритмы обратного распространения
Глубокое обучение с нулевым входом (4) - сверточная нейронная сеть
Введение в глубокое обучение с нулевыми основами (5) — рекуррентная нейронная сеть
Глубокое обучение с нулевым входом (6) — сеть с долговременной кратковременной памятью (LSTM)
Глубокое обучение с нулевым входом (7) — рекуррентная нейронная сеть

прошлый обзор

В предыдущей статье мы представили полносвязную нейронную сеть, ее обучение и использование. Мы использовали его для распознавания рукописных цифр, однако такая структура сети не очень подходит для задач распознавания изображений. В этой статье будет представлена ​​структура нейронной сети, которая больше подходит для задач распознавания изображений и речи —Сверточная нейронная сеть(Сверточная нейронная сеть, CNN). Не будет преувеличением сказать, что сверточная нейронная сеть является самым важным типом нейронной сети. Она блистала в последние годы. Почти все важные прорывы в области распознавания изображений и речи были достигнуты с помощью сверточных нейронных сетей, таких как Google. GoogleNet, Microsoft ResNet и т. д. AlphaGo, победившая Ли Шиши, также использовала этот вид сети. В этой статье будет подробно рассказаноСверточная нейронная сетьи его алгоритм обучения, а также практическая реализация простогоСверточная нейронная сеть.

Новая функция активации - Relu

В последние годы в сверточной нейронной сети функция активации часто выбирает не сигмовидную или тангенциальную функцию, а функцию relu. Определение функции Relu:

Изображение функции Relu показано на следующем рисунке:

В качестве функции активации функция Relu имеет следующие преимущества:

  • высокоскоростнойПо сравнению с сигмовидной функцией, которая должна вычислять показатель степени и обратную величину, функция relu на самом деле является максимальной (0, x), и стоимость вычисления намного ниже.
  • Облегчить проблему исчезающего градиентаНапомним формулу расчета градиента\nabla=\sigma'\delta x. в,\sigma'является производной сигмовидной функции. При использовании алгоритма обратного распространения для вычисления градиента каждый раз, когда проходит слой сигмовидных нейронов, градиент умножается на\sigma'. Как видно из рисунка ниже,\sigma'Максимальное значение функции равно 1/4. Поэтому умножьте на единицу\sigma'Это приведет к тому, что градиент будет становиться все меньше и меньше, что является большой проблемой для обучения глубоких сетей. Производная функции relu равна 1, что не приводит к уменьшению градиента. Конечно, функция активации является лишь одним из факторов, вызывающих уменьшение градиента, но в любом случае relu превосходит сигмоид в этом отношении. Использование функции активации relu позволяет обучать более глубокие сети.

  • РазреженностьБлагодаря исследованиям головного мозга установлено, что при работе активируются только около 5% нейронов головного мозга, в то время как скорость активации искусственной нейронной сети, использующей сигмовидную функцию активации, составляет около 50%. В некоторых статьях утверждается, что искусственные нейронные сети идеальны при уровне активации 15-30%. Поскольку функция relu полностью неактивна, когда вход меньше 0, можно получить более низкую скорость активации.

Полностью подключенные сети против сверточных сетей

Причина, по которой полносвязная нейронная сеть не подходит для задач распознавания изображений, в основном заключается в следующих проблемах:

  • Слишком много параметровРассмотрим входное изображение размером 1000*1000 пикселей (один миллион пикселей, что больше не является большим изображением), а входной слой имеет 1000*1000=1 миллион узлов. Если предположить, что первый скрытый слой имеет 100 узлов (что не так много), то только этот слой имеет (1000*1000+1)*100=100 миллионов параметров, что слишком много! Мы видим, что если изображение только немного увеличить, то количество параметров будет намного больше, поэтому оно плохо масштабируется.
  • Не использует информацию о положении между пикселямиДля задач распознавания изображений связь между каждым пикселем и окружающими его пикселями относительно близка, а связь с удаленными пикселями может быть очень небольшой. Если нейрон связан со всеми нейронами предыдущего слоя, это эквивалентно обработке всех пикселей изображения одинаково для одного пикселя, что не соответствует предыдущему предположению. Когда мы закончим изучение весов каждой связи, мы можем в конце концов обнаружить, что существует большое количество весов, и все они имеют очень маленькие значения (то есть связи на самом деле не имеют значения). Попытка выучить много неважных весов обязательно будет очень неэффективной.
  • лимит сетевого уровняМы знаем, что чем больше слоев сети, тем сильнее выразительная способность, но глубокую полносвязную нейронную сеть трудно обучить методом градиентного спуска, потому что градиент полносвязной нейронной сети трудно пройти более 3 слоев . Поэтому получить очень глубокую полносвязную нейронную сеть у нас невозможно, что ограничивает ее возможности.

Итак, как сверточные нейронные сети решают эту проблему? Есть три основные идеи:

  • местное соединениеЭто проще всего представить, каждый нейрон больше не связан со всеми нейронами предыдущего слоя, а только с небольшим количеством нейронов. Это снижает многие параметры.
  • разделение весаВместо каждого соединения, имеющего другой вес, набор соединений может делиться один и тот же вес, который снова уменьшает множество параметров.
  • понижение частоты дискретизацииОбъединение можно использовать для уменьшения количества выборок на слой, дальнейшего уменьшения количества параметров и в то же время повышения надежности модели.

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

Далее мы подробно расскажем, что на самом деле представляют собой сверточные нейронные сети.

Что такое сверточная нейронная сеть

Во-первых, давайте получим перцептивное понимание.На следующем рисунке представлена ​​схематическая диаграмма сверточной нейронной сети:

图1 卷积神经网络

Сетевая архитектура

какРисунок 1Как показано, сверточная нейронная сеть состоит из несколькихсверточный слой,Объединенный слой,полносвязный слойсочинение. Вы можете построить множество различных сверточных нейронных сетей, и их общие архитектурные шаблоны:

INPUT -> [[CONV]*N -> POOL?]*M -> [FC]*K

То есть накладываются N сверточных слоев, затем (необязательно) накладывается слой объединения, эта структура повторяется M раз, и, наконец, накладываются K полносвязных слоев.

заРисунок 1Показана сверточная нейронная сеть:

INPUT -> CONV -> POOL -> CONV -> POOL -> FC -> FC

В соответствии с приведенным выше шаблоном это может быть выражено как:

INPUT -> [[CONV]*1 -> POOL]*2 -> [FC]*2

Это:N=1, M=2, K=2.

трехмерная многослойная структура

отРисунок 1мы можем узнатьСверточная нейронная сетьструктура слоя иПолностью подключенная нейронная сетьСтруктура слоев очень разнообразна.Полностью подключенная нейронная сетьНейроны в каждом слоеОдномерныйрасположены, то есть выстроены в линию; иСверточная нейронная сетьНейроны в каждом слоетрехмерныйАранжированные, то есть расположенные в прямоугольной форме, естьширина,высокийиглубина.

заРисунок 1Показывая нейронную сеть, мы видим, что ширина и высота входного слоя соответствуют ширине и высоте входного изображения, а его глубина равна 1. Затем первый сверточный слой выполняет операцию свертки над этим изображением (о том, как вычислить свертка, мы поговорим позже) и получаем три карты признаков. «3» здесь может сбить с толку многих новичков. На самом деле, этот слой свертки содержит три фильтра, то есть три набора параметров. Каждый фильтр может свернуть исходное входное изображение для получения карты объектов. Три фильтра могут получить три объекта Карты. Что касается того, сколько фильтров может иметь сверточный слой, его можно установить свободно. То есть количество Фильтров сверточного слоя тоже равно единице.Гиперпараметры. Мы можем поставить Карту можно рассматривать как признак изображения, извлеченный с помощью преобразования свертки.Три фильтра извлекают три разных набора признаков из исходного изображения, то есть получаются три Карты признаков, также называемые тремяканал.

Продолжайте наблюдатьРисунок 1, после первого сверточного слоя слой объединения создает три карты объектов.понижение частоты дискретизации(Мы поговорим о том, как рассчитать даунсэмплинг позже), и получите три меньшие карты признаков. Затем, второйсверточный слой, он имеет 5 фильтров. Каждый Fitler ставит фронтпонижение частоты дискретизацииПосле3**Карты функцийсверткавместе, получите новую карту функций. Таким образом, 5 фильтров получают 5 карт функций. Далее идет второй пул, который продолжает выполняться на 5 картах функций.Даунсэмплинг**, есть 5 меньших функций Карта.

Рисунок 1Последние два слоя показанной сети являются полносвязными слоями. Каждый нейрон в первом полносвязном слое соединен с каждым нейроном в 5 картах объектов предыдущего слоя, а каждый нейрон во втором полносвязном слое (то есть выходном слое) соединен с первым полносвязным Каждый нейрон полносвязного слоя связан, так что получается выход всей сети.

До сих пор у нас естьСверточная нейронная сетьС самыми элементарными перцептивными знаниями. Далее мы представимСверточная нейронная сетьРасчет и обучение различных слоев в

Расчет выходного значения сверточной нейронной сети

Расчет выходного значения сверточного слоя

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

Предположим, есть изображение 5*5, используйте фильтр 3*3 для свертки и хотите получить карту признаков 3*3, как показано ниже:

для четкого описаниясверткаВ процессе расчета мы сначала нумеруем каждый пиксель изображения, используяx_{i,j}представляет собой первыйiрядjЭлемент столбца; пронумеруйте каждый вес фильтра, используйтеw_{m,n}означает первыйmрядnвеса колонны, сw_bПредставляет фильтрПредвзятый термин;Пронумеруйте каждый элемент Карты характеристик, используйтеa_{i,j}Представляет первую часть карты объектов.iрядjэлемент столбца; сfвыражатьфункция активации(Этот пример выбираетрелу функциякак функция активации). Затем свертка рассчитывается по следующей формуле:

Режим

Например, для верхнего левого элемента карты объектовa_{0,0}Например, метод расчета свертки:

Результат расчета показан на следующем рисунке:

Далее элементы карты признаковa_{0,1}Метод расчета свертки:

Результат расчета показан на следующем рисунке:

Значения всех элементов в Feature Map можно вычислить по очереди. Анимация ниже показывает процесс расчета всей карты признаков:

图2 卷积计算

В приведенном выше процессе расчета шаг равен 1. Шаг может быть установлен на число больше 1. Например, если шаг равен 2, карта признаков рассчитывается следующим образом:

Мы заметили, что когдаСтрайдЕсли установлено значение 2, карта объектов превращается в 2 * 2. Это показывает, что размер изображения, шаг и размер карты объектов после свертки связаны. Фактически они удовлетворяют следующим соотношениям:

формула

В приведенных выше двух формулахW_2ширина карты признаков после свертки;W_1ширина изображения до свертки;Fширина фильтра;PдаZero Paddingколичество,Zero Paddingотносится к дополнению несколькими кругами нулей вокруг исходного изображения, еслиPЗначение равно 1, затем добавьте 1 круг 0;Sдашаг;H_2высота карты признаков после свертки;H_1ширина изображения до свертки.Формула 2иФормула 3По сути то же самое.

В предыдущем примере ширина изображенияW_1=5, ширина фильтраF=3,Zero PaddingP=0,шагS=2,но

Описание Ширина карты объекта равна 2. Точно так же мы можем рассчитать, что высота карты объектов также равна 2.

Мы уже говорили о методе расчета сверточного слоя глубиной 1. Как считать, если глубина больше 1? На самом деле похоже. Если глубина изображения до свертки равна D, то глубина соответствующего фильтра также должна быть равна D. Давайте расширимФормула 1, получается формула расчета свертки с глубиной больше 1:

Режим

существуетФормула 4В, D — глубина, F — размер фильтра (ширина или высота, оба одинаковы);w_{d,m,n}Указывает первый фильтрdслойmрядnвес колонны;a_{d,i,j}представляет собой первыйdслойiрядjПиксели столбца, другие значения символов иФормула 1одинаковы и здесь повторяться не будут.

Мы также упоминали ранее, что каждый сверточный слой может иметь несколько фильтров. После свертки каждого фильтра с исходным изображением можно получить карту признаков. Следовательно, глубина (число) карты объектов после свертки совпадает с количеством фильтров в слое свертки.

Анимация ниже показывает вычисление сверточного слоя, содержащего два фильтра. Мы видим, что вход 7*7*3 после свертки двух фильтров 3*3*3 (с шагом 2) дает результат 3*3*2. Кроме того, мы также увидим следующий рисунокZero paddingравен 1, то есть вокруг входного элемента добавляется круг из 0.Zero paddingЭто очень полезно для выделения признаков краевых частей изображения.

Выше приведен метод расчета слоя свертки. Здесь отраженоместное соединениеиразделение веса: Каждый слой нейронов связан только с некоторыми нейронами в предыдущем слое (правила расчета свертки), а вес фильтра одинаков для всех нейронов в предыдущем слое. Для сверточного слоя, содержащего два фитлера 3*3*3, количество параметров составляет всего (3*3*3+1)*2=56, а количество параметров не зависит от количества нейронов в предыдущем слое. иПолностью подключенная нейронная сетьДля сравнения, количество параметров значительно уменьшается.

Используйте формулу свертки, чтобы выразить вычисление слоя свертки

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

Формула 4Выражение очень громоздкое, лучше всего его упростить. Так же, как матрица может быть использована для упрощения выраженияПолностью подключенная нейронная сетьКак и при расчете, используемФормула сверткиможно упроститьСверточная нейронная сетьвыражение.

Ниже мы представляемФормула двумерной свертки.

установить матрицуA,B, количество строк и столбцовm_a,n_a,m_b,n_b,ноФормула двумерной сверткиследующее:

иs,tЧтобы выполнить условия0\le{s}\lt{m_a+m_b-1}, 0\le{t}\lt{n_a+n_b-1}.

Мы можем записать приведенное выше уравнение как

Режим

если мы последуемФормула 5Для вычисления свертки мы можем обнаружить, что матрица A на самом деле является фильтром, а матрица B — входными данными для свертки, и отношение позиций также отличается:

Как видно из рисунка выше, значение в верхнем левом углу Aa_{0,0}Значение в правом нижнем углу блока, соответствующее Bb_{1,1}Умножить, а не с верхней левойb_{0,0}Умножить. следовательно,математикаСвертка в иСверточная нейронная сетьРазница в «свертке» в , во избежание путаницы, положимСверточная нейронная сетьОперация «свертка» в называетсявзаимная корреляцияработать.

сверткаиКорреляцияДействия конвертируются. Сначала переворачиваем матрицу А на 180 градусов, а затем меняем местами А и В (то есть ставим В слева, а А справа. Свертка удовлетворяет обменному курсу, и эта операция не приводит к результату менять), тосверткаэто становитсяКорреляция.

Если не учитывать небольшую разницу между ними, мы можемФормула 5Заменить наФормула 4:

Режим

в,A— это карта объектов, выводимая сверточным слоем. такой жеФормула 4по сравнению с,Формула 6Гораздо проще. Однако это краткое обозначение подходит только для шага 1.

Расчет выходного значения слоя Pooling

Основная функция слоя объединения состоит в том, чтобыпонижение частоты дискретизации, что еще больше сокращает количество параметров за счет удаления неважных сэмплов на карте функций. Существует множество методов объединения, наиболее часто используемым являетсяMax Pooling.Max PoolingФактически максимальное значение берется из n*n выборок в качестве значения выборки после выборки. На следующем рисунке показано максимальное объединение 2 * 2:

КромеMax PooingКроме того, обычно используетсяMean Pooling- Возьмите среднее значение каждого образца.

Для карты объектов с глубиной D каждый слой выполняет объединение независимо друг от друга, поэтому глубина после объединения по-прежнему равна D.

полносвязный слой

Вычисление выходного значения полносвязного слоя и предыдущей статьиВведение в глубокое обучение с нулевыми основами (3) — нейронные сети и алгоритмы обратного распространениясказалПолностью подключенная нейронная сетьЭто то же самое и не будет повторяться здесь.

Обучение сверточных нейронных сетей

иПолностью подключенная нейронная сетьпо сравнению с,Сверточная нейронная сетьОбучение немного сложнее. Но принцип обучения тот же: используйте цепной вывод для вычисления частной производной (градиента) функции потерь по отношению к каждому весу, а затем обновите веса в соответствии с формулой градиентного спуска. Алгоритм обучения по-прежнему является алгоритмом обратного распространения ошибки.

Давайте сначала вспомним предыдущую статьюВведение в глубокое обучение с нулевыми основами (3) — нейронные сети и алгоритмы обратного распространенияВведен алгоритм обратного распространения, весь алгоритм разделен на три этапа:

  1. прямое вычисление каждого нейронавыходное значениеa_j(jпредставляет собой первыйjнейроны, то же ниже);
  2. Обратный расчет каждого нейронаОшибки\delta_j,\delta_jТакже называется в некоторой литературечувствительность(чувствительность). На самом деле это функция потерь сетиE_dк нейронамвзвешенный вводnet_jЧастная производная от , то есть\delta_j=\frac{\partial{E_d}}{\partial{net_j}};
  3. Рассчитайте вес соединения каждого нейронаw_{ji}изградиент(w_{ji}представлен нейрономiсвязаны с нейронамиjвес), формула\frac{\partial{E_d}}{\partial{w_{ji}}}=a_i\delta_j,в,a_iпредставляет собой нейронiВывод.

Наконец, обновите каждый вес в соответствии с правилом градиентного спуска.

Для сверточных нейронных сетей из-заместное соединение,понижение частоты дискретизациии другие операции, влияющие на второй шагОшибки\deltaКонкретный метод расчета , иразделение весавлияет на третий шагВесаwизградиентметод расчета. Далее мы вводим алгоритмы обучения для сверточного слоя и слоя объединения соответственно.

Обучение сверточных слоев

Для сверточного слоя давайте посмотрим на второй шаг выше, то есть какОшибки\deltaПередайте его предыдущему слою, затем посмотрите на третий шаг, то есть как рассчитать каждый вес фильтраwизградиент.

Передача члена ошибки свёрточного слоя

Передача ошибок в простейшем случае

Давайте сначала рассмотрим простейший случай, когда размер шага равен 1, глубина ввода равна 1, а количество фильтров равно 1.

Предполагая, что размер ввода равен 3 * 3, размер фильтра - 2 * 2, а шаг - 1 свертка, мы получим 2 * 2.feature map. Как показано ниже:

На рисунке выше мы пронумеровали каждый элемент для простоты описания. использовать\delta^{l-1}_{i,j}означает первыйl-1слойjрядjстолбецОшибки;использоватьw_{m,n}Сначала указывает фильтрmрядnвеса колонны, сw_bПредставляет фильтрПредвзятый термин;использоватьa^{l-1}_{i,j}означает первыйl-1слойiрядjряд нейроноввывод;использоватьnet^{l-1}_{i,j}означает первыйl-1ряд нейроноввзвешенный ввод;использовать\delta^l_{i,j}означает первыйlслойjрядjстолбецОшибки;использоватьf^{l-1}означает первыйl-1слоистыйфункция активации. Отношения между ними следующие:

В приведенной выше формулеnet^l,W^l,a^{l-1}массивы,W^lОтw_{m,n}массивconvПредставляет операцию свертки.

Здесь мы предполагаем, чтоlкаждый из\delta^lЗначения были рассчитаны, что нам нужно сделать, это рассчитать первыйl-1слой для каждого нейронаОшибки \delta^{l-1}.

По правилу вывода цепочки:

Попросим первого\frac{\partial{E_d}}{\partial{a^{l-1}_{i,j}}}. Давайте сначала рассмотрим несколько частных случаев, а затем выведем из них общие правила.

Пример 1, расчет\frac{\partial{E_d}}{\partial{a^{l-1}_{1,1}}},a^{l-1}_{1,1}только сnet^l_{1,1}Расчет связан с:

следовательно:

Пример 2, расчет\frac{\partial{E_d}}{\partial{a^{l-1}_{1,2}}},a^{l-1}_{1,2}иnet^l_{1,1}иnet^l_{1,2}Все расчеты касаются:

следовательно:

Пример 3, расчет\frac{\partial{E_d}}{\partial{a^{l-1}_{2,2}}},a^{l-1}_{2,2}иnet^l_{1,1},net^l_{1,2},net^l_{2,1}иnet^l_{2,2}Все расчеты касаются:

следовательно:

Из вышеприведенных трех примеров, воспользуемся воображением, нетрудно найти, вычислить\frac{\partial{E_d}}{\partial{a^{l-1}}}, что равносильно тому, чтоlВокруг чувствительной карты слоя добавляется кружок 0, а фильтр переворачивается на 180 градусов.cross-correlation, можно получить желаемый результат, как показано на следующем рисунке:

так каксверткаЭквивалентно повороту фильтра на 180 градусовcross-correlation, поэтому расчет на приведенном выше рисунке может быть идеально выражен формулой свертки:

в приведенной выше формулеW^lозначает первыйlМассив весов для фильтра слоя. Также можно расширить свертку приведенной выше формулы и записать ее в виде суммирования:

Теперь давайте попросим второй элемент\frac{\partial{a^{l-1}_{i,j}}}{\partial{net^{l-1}_{i,j}}}. так как

Итак, этот пункт чрезвычайно прост, просто попросите функцию активацииfПроизводная от .

Объединив первое и второе слагаемые, получим окончательную формулу:

Режим

так же может бытьФормула 7Написано в форме свертки:

Режим

Среди них символ\circвыражатьelement-wise product, то есть умножить каждый соответствующий элемент в матрице. УведомлениеФормула 8середина\delta^{l-1},\delta^l,net^{l-1}обаматрица.

Выше приведен простейший случай, когда размер шага равен 1, входная глубина равна 1, количество фильтров равно 1, и алгоритм передачи члена ошибки сверточного слоя. Теперь давайте выведем случай, когда размер шага равен S.

Распространение ошибки, когда шаг свертки равен S

Давайте сначала посмотрим на разницу между размером шага S и размером шага 1.

Как показано выше, вверху — результат свертки, когда шаг равен 1, а внизу — результат свертки, когда шаг равен 2. Мы видим, что, поскольку шаг равен 2, результирующая карта признаков пропускает соответствующую часть шага 1. Поэтому, когда мы вычисляем в обратном порядкеОшибки, мы можем добавить 0 в соответствующую позицию карты чувствительности с размером шага S, и «восстановить» его на карту чувствительности с размером шага 1, а затем использоватьФормула 8решать.

Распространение ошибки, когда глубина входного слоя равна D

Когда глубина ввода равна D, глубина фильтра также должна быть D,l-1слоистыйd_iканал работает только с фильтромd_iРассчитываются веса каналов. Поэтому обратный расчетОшибки, мы можем использоватьФормула 8, используйте первый фильтрd_iпара веса каналаlКарта чувствительности слоя свернута для получения первогоl-1Этажd_iКарта чувствительности канала. Как показано ниже:

Распространение ошибки при количестве фильтров N

Когда количество фильтров равно N, глубина выходного слоя также равна N.iСвертка фильтра создает выходной слойiкарты характеристик. Из-заl-1Этажкаждый взвешенный входnet^{l-1}_{d, i,j}оба повлияли наlВыходные значения всех карт признаков слоя, следовательно, обратный расчетОшибки, необходимо использовать полную формулу производной. То есть сначала используемdпара фильтровlслой, соответствующийdСверните карты чувствительности, чтобы получить набор Nl-1Карта частичной чувствительности слоя. Проделайте эту свертку с каждым фильтром по очереди, чтобы получить группу D карт частичной чувствительности. Наконец, сопоставьте частичную чувствительность N между каждой группой.поэлементное сложение, получить окончательное Nl-1Карта чувствительности слоя:

Режим

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

Вычисление весового градиента фильтра сверточного слоя

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

Как показано на фиг.a^l_{i,j}первыйl-1вывод слоя,w_{i,j}первыйlВес слоя фильтра,\delta^l_{i,j}первыйlКарта чувствительности слоя. Наша задача вычислитьw_{i,j}градиент\frac{\partial{E_d}}{\partial{w_{i,j}}}.

Чтобы вычислить частные производные, нам нужно изучить весаw_{i,j}правильноE_dВлияние. весовой терминw_{i,j}по влияниюnet^l_{i,j}значение, которое влияетE_d. Мы по-прежнему рассмотрим весовой термин на нескольких конкретных примерах.w_{i,j}правильноnet^l_{i,j}воздействовать, а затем делать из этого выводы.

Пример 1, расчет\frac{\partial{E_d}}{\partial{w_{1,1}}}:

Как видно из приведенной выше формулы, посколькуразделение веса, весw_{1,1}всеnet^l_{i,j}иметь влияние.E_dдаnet^l_{1,1},net^l_{1,2},net^l_{2,1}... функция, в то время какnet^l_{1,1},net^l_{1,2},net^l_{2,1}...Сноваw_{1,1}функция, согласнополная производнаяформула, расчет\frac{\partial{E_d}}{\partial{w_{1,1}}}Просто сложите каждую частную производную:

Пример 2, расчет\frac{\partial{E_d}}{\partial{w_{1,2}}}:

просмотревw_{1,2}иnet^l_{i,j}отношения, мы можем легко получить:

На самом деле каждыйвесовой терминВсе они похожи, мы не будем приводить примеры по одному. Теперь пришло время нам снова использовать наше воображение, мы обнаруживаем, что вычисления\frac{\partial{E_d}}{\partial{w_{i,j}}}Правило:

То есть используйте карту чувствительности в качестве ядра свертки и выполняйте ее на входе.cross-correlation,Как показано ниже:

Наконец, давайте посмотрим на градиент члена смещения\frac{\partial{E_d}}{\partial{w_b}}. Глядя на предыдущую формулу, мы можем легко найти:

этоПредвзятый терминизградиентэто карта чувствительности всеОшибкиСумма.

Для сверточного слоя с размером шага S метод обработки такой же, как и при передаче **условия ошибки*. Сначала карта чувствительности «восстанавливается» до карты чувствительности с размером шага 1, а затем вышеуказанный метод используется для расчета.

получил всеградиентПосле этого, согласноАлгоритм градиентного спускадля обновления каждого веса. Об этом неоднократно писалось в предыдущих статьях и здесь повторяться не буду.

Пока что мы решили задачу обучения сверточного слоя, давайте взглянем на обучение слоя объединения.

Обучение слоя пула

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

Перенос максимального срока ошибки объединения

Как показано на рисунке ниже, если предположить, чтоl-1Размер слоя 4*4, размер фильтра пула 2*2, размер шага 2. Таким образом, после максимального пула первыйlРазмер слоя 2*2. Предположим, первыйlслоистый\deltaЗначения были рассчитаны, и наша задача теперь состоит в том, чтобы вычислить первыйl-1слоистый\deltaценность.

мы используемnet^{l-1}_{i,j}означает первыйl-1слоистыйвзвешенный ввод;использоватьnet^l_{i,j}означает первыйlслоистыйвзвешенный ввод. Сначала рассмотрим конкретный пример, а затем резюмируем общие правила. Для максимального объединения:

То есть только самый большой в блокеnet^{l-1}_{i,j}будет правильноnet^l_{i,j}стоимость влияет. Будем считать, что наибольшее значениеnet^{l-1}_{1,1}, приведенная выше формула эквивалентна:

Тогда нам нетрудно получить следующие частные производные:

следовательно:

и:

Теперь мы нашли правило: для максимального пула следующий слойОшибкиЗначение передается нейрону, соответствующему максимальному значению в соответствующем блоке предыдущего слоя, а значения остальных нейроновОшибкивсе 0. Как показано на рисунке ниже (при условии, чтоa^{l-1}_{1,1},a^{l-1}_{1,4},a^{l-1}_{4,1},a^{l-1}_{4,4}максимальное выходное значение в блоке):

Перенос срока ошибки среднего пула

Мы по-прежнему используем проверенную выше процедуру, сначала изучаем частный случай, а затем расширяем ее до общего закона.

Как показано на рисунке выше, сначала рассмотрим расчет\delta^{l-1}_{1,1}. Давайте сначала посмотримnet^{l-1}_{1,1}Как это влияетnet^l_{1,1}.

Согласно приведенной выше формуле, мы можем увидеть с первого взгляда:

Поэтому по цепному правилу вывода нам нетрудно вычислить:

Точно так же мы можем вычислить\delta^{l-1}_{1,2},\delta^{l-1}_{2,1},\delta^{l-1}_{2,2}:

Итак, мы нашли правило: для объединения средних значений следующий слойОшибкиЗначение будетпоровнувсем нейронам в соответствующем блоке предыдущего слоя. Как показано ниже:

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

в,nразмер фильтра объединяющего слоя,\delta^{l-1},\delta^lвсе матрицы.

До сих пор мы поставилисверточный слой,Объединенный слойВнедрение алгоритма обучения завершено, плюс предыдущая статьяполносвязный слойДля обучения алгоритма вы уже должны уметь писатьСверточная нейронная сетьЗнания, необходимые для кода. Чтобы углубить понимание знаний, далее мы покажем, как реализовать простоеСверточная нейронная сеть.

Реализация сверточных нейронных сетей

Пожалуйста, обратитесь к GitHub за полным кодом:GitHub.com/долго кричал/узнал… (python2.7)

Теперь давайте запачкаем руки и реализуем сверточную нейронную сеть, чтобы закрепить то, что мы узнали.

В первую очередь нам предстоит изменить архитектуру кода, и «слой» стал нашим основным компонентом. Это связано с тем, что сверточные нейронные сети имеют разные слои, и алгоритмы для каждого слоя реализованы в соответствующем классе.

На этот раз мы использовали то, что часто используется для написания алгоритмов на питоне.numpyСумка. чтобы использоватьnumpy, нам нужно сначалаnumpyИмпортировать:

import numpy as np

Реализация сверточных слоев

Инициализировать сверточный слой

мы используемConvLayerкласс для реализации сверточного слоя. Следующий код предназначен для инициализации сверточного слоя, вы можете установить сверточный слой в конструктореГиперпараметры.

class ConvLayer(object):
    def __init__(self, input_width, input_height, 
                 channel_number, filter_width, 
                 filter_height, filter_number, 
                 zero_padding, stride, activator,
                 learning_rate):
        self.input_width = input_width
        self.input_height = input_height
        self.channel_number = channel_number
        self.filter_width = filter_width
        self.filter_height = filter_height
        self.filter_number = filter_number
        self.zero_padding = zero_padding
        self.stride = stride
        self.output_width = \
            ConvLayer.calculate_output_size(
            self.input_width, filter_width, zero_padding,
            stride)
        self.output_height = \
            ConvLayer.calculate_output_size(
            self.input_height, filter_height, zero_padding,
            stride)
        self.output_array = np.zeros((self.filter_number, 
            self.output_height, self.output_width))
        self.filters = []
        for i in range(filter_number):
            self.filters.append(Filter(filter_width, 
                filter_height, self.channel_number))
        self.activator = activator
        self.learning_rate = learning_rate

calculate_output_sizeФункция используется для определения размера вывода сверточного слоя, который реализован следующим образом:

    @staticmethod
    def calculate_output_size(input_size,
            filter_size, zero_padding, stride):
        return (input_size - filter_size + 
            2 * zero_padding) / stride + 1

FilterКласс содержит сверточный слойпараметра такжеградиенти реализуетАлгоритм градиентного спускадля обновления параметров.

class Filter(object):
    def __init__(self, width, height, depth):
        self.weights = np.random.uniform(-1e-4, 1e-4,
            (depth, height, width))
        self.bias = 0
        self.weights_grad = np.zeros(
            self.weights.shape)
        self.bias_grad = 0

    def __repr__(self):
        return 'filter weights:\n%s\nbias:\n%s' % (
            repr(self.weights), repr(self.bias))

    def get_weights(self):
        return self.weights

    def get_bias(self):
        return self.bias

    def update(self, learning_rate):
        self.weights -= learning_rate * self.weights_grad
        self.bias -= learning_rate * self.bias_grad

Мы используем общую стратегию инициализации параметров, а именно:Весаслучайным образом инициализируется небольшим значением, в то время какПредвзятый терминИнициализирован в 0.

Activatorкласс реализуетфункция активации,в,forwardметод реализует опережающее вычисление, в то время какbackwardметод заключается в вычисленииПроизводная. Например, реализация функции relu выглядит следующим образом:

class ReluActivator(object):
    def forward(self, weighted_input):
        #return weighted_input
        return max(0, weighted_input)

    def backward(self, output):
        return 1 if output > 0 else 0

Реализация прямого вычисления сверточных слоев

ConvLayerКатегорияforwardМетод реализует прямое вычисление сверточного слоя (то есть вычисление вычисляет выходные данные сверточного слоя в соответствии с входными данными), а следующая реализация кода:

    def forward(self, input_array):
        '''
        计算卷积层的输出
        输出结果保存在self.output_array
        '''
        self.input_array = input_array
        self.padded_input_array = padding(input_array,
            self.zero_padding)
        for f in range(self.filter_number):
            filter = self.filters[f]
            conv(self.padded_input_array, 
                filter.get_weights(), self.output_array[f],
                self.stride, filter.get_bias())
        element_wise_op(self.output_array, 
                        self.activator.forward)

Приведенный выше код содержит несколько служебных функций.element_wise_opфункция реализуетnumpyмассив для выполненияПо элементуоперацию и записать возвращаемое значение обратно в массив, код выглядит следующим образом:

# 对numpy数组进行element wise操作
def element_wise_op(array, op):
    for i in np.nditer(array,
                       op_flags=['readwrite']):
        i[...] = op(i)

convФункция реализует 2D и 3D массивысвертка, код показан ниже:

def conv(input_array, 
         kernel_array,
         output_array, 
         stride, bias):
    '''
    计算卷积,自动适配输入为2D和3D的情况
    '''
    channel_number = input_array.ndim
    output_width = output_array.shape[1]
    output_height = output_array.shape[0]
    kernel_width = kernel_array.shape[-1]
    kernel_height = kernel_array.shape[-2]
    for i in range(output_height):
        for j in range(output_width):
            output_array[i][j] = (    
                get_patch(input_array, i, j, kernel_width, 
                    kernel_height, stride) * kernel_array
                ).sum() + bias

paddingФункция реализует операцию заполнения нулями:

# 为数组增加Zero padding
def padding(input_array, zp):
    '''
    为数组增加Zero padding,自动适配输入为2D和3D的情况
    '''
    if zp == 0:
        return input_array
    else:
        if input_array.ndim == 3:
            input_width = input_array.shape[2]
            input_height = input_array.shape[1]
            input_depth = input_array.shape[0]
            padded_array = np.zeros((
                input_depth, 
                input_height + 2 * zp,
                input_width + 2 * zp))
            padded_array[:,
                zp : zp + input_height,
                zp : zp + input_width] = input_array
            return padded_array
        elif input_array.ndim == 2:
            input_width = input_array.shape[1]
            input_height = input_array.shape[0]
            padded_array = np.zeros((
                input_height + 2 * zp,
                input_width + 2 * zp))
            padded_array[zp : zp + input_height,
                zp : zp + input_width] = input_array
            return padded_array

Реализация алгоритма обратного распространения сверточного слоя

Теперь пришло время представить основной алгоритм сверточного слоя. Мы знаем, что алгоритм обратного распространения должен выполнить несколько задач:

  1. будетОшибкипереходит на предыдущий слой.
  2. рассчитать каждыйпараметризградиент.
  3. возобновитьпараметр.

Следующие коды находятся вConvLayerреализованы в классе. Давайте сначала посмотрим наОшибкиРеализация кода передана предыдущему уровню.

    def bp_sensitivity_map(self, sensitivity_array,
                           activator):
        '''
        计算传递到上一层的sensitivity map
        sensitivity_array: 本层的sensitivity map
        activator: 上一层的激活函数
        '''
        # 处理卷积步长,对原始sensitivity map进行扩展
        expanded_array = self.expand_sensitivity_map(
            sensitivity_array)
        # full卷积,对sensitivitiy map进行zero padding
        # 虽然原始输入的zero padding单元也会获得残差
        # 但这个残差不需要继续向上传递,因此就不计算了
        expanded_width = expanded_array.shape[2]
        zp = (self.input_width +  
              self.filter_width - 1 - expanded_width) / 2
        padded_array = padding(expanded_array, zp)
        # 初始化delta_array,用于保存传递到上一层的
        # sensitivity map
        self.delta_array = self.create_delta_array()
        # 对于具有多个filter的卷积层来说,最终传递到上一层的
        # sensitivity map相当于所有的filter的
        # sensitivity map之和
        for f in range(self.filter_number):
            filter = self.filters[f]
            # 将filter权重翻转180度
            flipped_weights = np.array(map(
                lambda i: np.rot90(i, 2), 
                filter.get_weights()))
            # 计算与一个filter对应的delta_array
            delta_array = self.create_delta_array()
            for d in range(delta_array.shape[0]):
                conv(padded_array[f], flipped_weights[d],
                    delta_array[d], 1, 0)
            self.delta_array += delta_array
        # 将计算结果与激活函数的偏导数做element-wise乘法操作
        derivative_array = np.array(self.input_array)
        element_wise_op(derivative_array, 
                        activator.backward)
        self.delta_array *= derivative_array

expand_sensitivity_mapМетод заключается в «восстановлении» карты чувствительности с размером шага S в карту чувствительности с размером шага 1. Код выглядит следующим образом:

    def expand_sensitivity_map(self, sensitivity_array):
        depth = sensitivity_array.shape[0]
        # 确定扩展后sensitivity map的大小
        # 计算stride为1时sensitivity map的大小
        expanded_width = (self.input_width - 
            self.filter_width + 2 * self.zero_padding + 1)
        expanded_height = (self.input_height - 
            self.filter_height + 2 * self.zero_padding + 1)
        # 构建新的sensitivity_map
        expand_array = np.zeros((depth, expanded_height, 
                                 expanded_width))
        # 从原始sensitivity map拷贝误差值
        for i in range(self.output_height):
            for j in range(self.output_width):
                i_pos = i * self.stride
                j_pos = j * self.stride
                expand_array[:,i_pos,j_pos] = \
                    sensitivity_array[:,i,j]
        return expand_array

create_delta_arrayпредставляет собой массив, созданный для хранения карты чувствительности, переданной предыдущему слою.

    def create_delta_array(self):
        return np.zeros((self.channel_number,
            self.input_height, self.input_width))

Далее идет код для расчета градиента.

    def bp_gradient(self, sensitivity_array):
        # 处理卷积步长,对原始sensitivity map进行扩展
        expanded_array = self.expand_sensitivity_map(
            sensitivity_array)
        for f in range(self.filter_number):
            # 计算每个权重的梯度
            filter = self.filters[f]
            for d in range(filter.weights.shape[0]):
                conv(self.padded_input_array[d], 
                     expanded_array[f],
                     filter.weights_grad[d], 1, 0)
            # 计算偏置项的梯度
            filter.bias_grad = expanded_array[f].sum()

Наконец, согласноАлгоритм градиентного спускаКод для обновления параметров, эта часть очень проста.

    def update(self):
        '''
        按照梯度下降,更新权重
        '''
        for filter in self.filters:
            filter.update(self.learning_rate)

Проверка градиента для сверточных слоев

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

def init_test():
    a = np.array(
        [[[0,1,1,0,2],
          [2,2,2,2,1],
          [1,0,0,2,0],
          [0,1,1,0,0],
          [1,2,0,0,2]],
         [[1,0,2,2,0],
          [0,0,0,2,0],
          [1,2,1,2,1],
          [1,0,0,0,0],
          [1,2,1,1,1]],
         [[2,1,2,0,0],
          [1,0,0,1,0],
          [0,2,1,0,1],
          [0,1,2,2,2],
          [2,1,0,0,1]]])
    b = np.array(
        [[[0,1,1],
          [2,2,2],
          [1,0,0]],
         [[1,0,2],
          [0,0,0],
          [1,2,1]]])
    cl = ConvLayer(5,5,3,3,3,2,1,2,IdentityActivator(),0.001)
    cl.filters[0].weights = np.array(
        [[[-1,1,0],
          [0,1,0],
          [0,1,1]],
         [[-1,-1,0],
          [0,0,0],
          [0,-1,0]],
         [[0,0,-1],
          [0,1,0],
          [1,-1,-1]]], dtype=np.float64)
    cl.filters[0].bias=1
    cl.filters[1].weights = np.array(
        [[[1,1,-1],
          [-1,-1,1],
          [0,-1,1]],
         [[0,1,0],
         [-1,0,-1],
          [-1,1,0]],
         [[-1,0,0],
          [-1,0,1],
          [-1,0,0]]], dtype=np.float64)
    return a, b, cl


def gradient_check():
    '''
    梯度检查
    '''
    # 设计一个误差函数,取所有节点输出项之和
    error_function = lambda o: o.sum()

    # 计算forward值
    a, b, cl = init_test()
    cl.forward(a)

    # 求取sensitivity map,是一个全1数组
    sensitivity_array = np.ones(cl.output_array.shape,
                                dtype=np.float64)
    # 计算梯度
    cl.backward(a, sensitivity_array,
                  IdentityActivator())
    # 检查梯度
    epsilon = 10e-4
    for d in range(cl.filters[0].weights_grad.shape[0]):
        for i in range(cl.filters[0].weights_grad.shape[1]):
            for j in range(cl.filters[0].weights_grad.shape[2]):
                cl.filters[0].weights[d,i,j] += epsilon
                cl.forward(a)
                err1 = error_function(cl.output_array)
                cl.filters[0].weights[d,i,j] -= 2*epsilon
                cl.forward(a)
                err2 = error_function(cl.output_array)
                expect_grad = (err1 - err2) / (2 * epsilon)
                cl.filters[0].weights[d,i,j] += epsilon
                print 'weights(%d,%d,%d): expected - actural %f - %f' % (
                    d, i, j, expect_grad, cl.filters[0].weights_grad[d,i,j])   

В приведенном выше коде стоит подумать о том, что карта чувствительности, переданная сверточному слою, представляет собой массив всех единиц, и читателю остается сделать вывод, почему это так (подсказка: функция активации выбирает функцию идентификации :f(x)=x). Если читатели все еще в замешательстве, пишите в комментариях к статье, и я отвечу.

Запустив приведенный выше код проверки градиента, мы получим следующий вывод: ожидаемый градиент согласуется с фактически рассчитанным градиентом, что доказывает, что вывод нашего алгоритма и реализация кода действительно верны.

Выше приведена реализация сверточного слоя.

Реализация слоя максимального пула

Реализация слоя max pooling относительно проста, мы напрямую выкладываем все коды следующим образом:

class MaxPoolingLayer(object):
    def __init__(self, input_width, input_height, 
                 channel_number, filter_width, 
                 filter_height, stride):
        self.input_width = input_width
        self.input_height = input_height
        self.channel_number = channel_number
        self.filter_width = filter_width
        self.filter_height = filter_height
        self.stride = stride
        self.output_width = (input_width - 
            filter_width) / self.stride + 1
        self.output_height = (input_height -
            filter_height) / self.stride + 1
        self.output_array = np.zeros((self.channel_number,
            self.output_height, self.output_width))

    def forward(self, input_array):
        for d in range(self.channel_number):
            for i in range(self.output_height):
                for j in range(self.output_width):
                    self.output_array[d,i,j] = (    
                        get_patch(input_array[d], i, j,
                            self.filter_width, 
                            self.filter_height, 
                            self.stride).max())

    def backward(self, input_array, sensitivity_array):
        self.delta_array = np.zeros(input_array.shape)
        for d in range(self.channel_number):
            for i in range(self.output_height):
                for j in range(self.output_width):
                    patch_array = get_patch(
                        input_array[d], i, j,
                        self.filter_width, 
                        self.filter_height, 
                        self.stride)
                    k, l = get_max_index(patch_array)
                    self.delta_array[d, 
                        i * self.stride + k, 
                        j * self.stride + l] = \
                        sensitivity_array[d,i,j]

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

Приложения сверточных нейронных сетей

Распознавание рукописных цифр MNIST

LeNet-5реализовать распознавание рукописных цифрСверточная нейронная сеть, он достигает коэффициента ошибок 0,8% на тестовом наборе MNIST.LeNet-5Структура выглядит следующим образом:

оLeNet-5В интернете много информации, поэтому повторяться не буду. Заинтересованные читатели могут попробовать построить и обучить наш собственный реализованный код сверточной нейронной сети.LeNet-5(Конечно, код будет сложнее).

подраздел

так какСверточная нейронная сетьСложность серии, мы написали самую длинную статью во всей серии, и я считаю, что читатели устали так же, как и автор.Сверточная нейронная сетьЭто самый важный инструмент для глубокого обучения (я стесняюсь писать «один»), и стоит приложить некоторые усилия, чтобы понять его. Если вы действительно понимаете содержание этой статьи, это эквивалентно преодолению самого важного порога для начала работы с глубоким обучением. В следующей статье мы представим еще один очень важный инструмент глубокого обучения:Рекуррентная нейронная сеть, к тому времени наша серия статей будет закончена более чем наполовину. Каждая статья — это фильтр.

использованная литература

  1. CS231n Convolutional Neural Networks for Visual Recognition
  2. Функция активации ReLu (выпрямленные линейные единицы)
  3. Jake Bouvrie, Notes on Convolutional Neural Networks, 2006
  4. Ian Goodfellow, Yoshua Bengio, Aaron Courville, Deep Learning, MIT Press, 2016