Сверточные нейронные сети — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Зачаток страницы)
 
м (rollbackEdits.php mass rollback)
 
(не показано 66 промежуточных версий 11 участников)
Строка 1: Строка 1:
'''Сверточная нейронная сеть''' (англ. ''convolutional neural network'', ''CNN'') {{---}} специальная архитектура нейронных сетей, предложенная [https://ru.wikipedia.org/wiki/%D0%9B%D0%B5%D0%BA%D1%83%D0%BD,_%D0%AF%D0%BD Яном Лекуном], изначально нацеленная на эффективное распознавание изображений.
+
'''Сверточная нейронная сеть''' (англ. ''convolutional neural network'', ''CNN'') {{---}} специальная архитектура нейронных сетей, предложенная Яном Лекуном<ref name=LeNet5>[http://yann.lecun.com/exdb/publis/pdf/lecun-01a.pdf Yann LeCun — Gradient-Based Learning Applied to Document Recognition, 1998]</ref>, изначально нацеленная на эффективное распознавание изображений.
  
 
== Свертка ==
 
== Свертка ==
[[Файл:Convolution_example.png|upright=1.5|thumb|Пример свертки двух матриц размера 5x5 и 3x3]]
+
[[Файл:Convolution_example.png|upright=1.0|thumb|[https://arxiv.org/abs/1603.07285 Рисунок 1.]Пример свертки двух матриц размера 5x5 и 3x3]]
 
'''Свертка''' (англ. ''convolution'') {{---}} операция над парой матриц <math>A</math> (размера <math>n_x\times n_y</math>) и <math>B</math> (размера <math>m_x \times m_y</math>), результатом которой является матрица <math>C = A * B</math> размера <math>(n_x-m_x+1)\times (n_y-m_y+1)</math>.
 
'''Свертка''' (англ. ''convolution'') {{---}} операция над парой матриц <math>A</math> (размера <math>n_x\times n_y</math>) и <math>B</math> (размера <math>m_x \times m_y</math>), результатом которой является матрица <math>C = A * B</math> размера <math>(n_x-m_x+1)\times (n_y-m_y+1)</math>.
 
Каждый элемент результата вычисляется как скалярное произведение матрицы <math>B</math> и некоторой подматрицы <math>A</math> такого же размера (подматрица определяется положением элемента в результате).
 
Каждый элемент результата вычисляется как скалярное произведение матрицы <math>B</math> и некоторой подматрицы <math>A</math> такого же размера (подматрица определяется положением элемента в результате).
То есть, <math>C_{i,j} = \sum_{u = 0}^{m_x-1}\sum_{v = 0}^{m_y - 1}A_{i+u,j+v}B_{u,v}</math>. На изображении справа можно видеть, как матрица <math>B</math> {{<<}}двигается{{>>}} по матрице <math>A</math>, и в каждом положении считается скалярное произведение матрицы <math>B</math> и той части матрицы <math>A</math>, на которую она сейчас наложена. Получившееся число записывается в соответствующий элемент результата.
+
То есть, <math>C_{i,j} = \sum_{u = 0}^{m_x-1}\sum_{v = 0}^{m_y - 1}A_{i+u,j+v}B_{u,v}</math>. На [https://arxiv.org/abs/1603.07285 Рисунке 1] можно видеть, как матрица <math>B</math> «двигается» по матрице <math>A</math>, и в каждом положении считается скалярное произведение матрицы <math>B</math> и той части матрицы <math>A</math>, на которую она сейчас наложена. Получившееся число записывается в соответствующий элемент результата.
  
 
Логический смысл свертки такой {{---}} чем больше величина элемента свертки, тем больше эта часть матрицы <math>A</math> была похожа на матрицу <math>B</math> (похожа в смысле скалярного произведения). Поэтому матрицу <math>A</math> называют ''изображением'', а матрицу <math>B</math> {{---}} ''фильтром'' или ''образцом''.
 
Логический смысл свертки такой {{---}} чем больше величина элемента свертки, тем больше эта часть матрицы <math>A</math> была похожа на матрицу <math>B</math> (похожа в смысле скалярного произведения). Поэтому матрицу <math>A</math> называют ''изображением'', а матрицу <math>B</math> {{---}} ''фильтром'' или ''образцом''.
 +
 +
== Структура сверточной нейронной сети ==
 +
В сверточной нейронной сети выходы промежуточных слоев образуют матрицу (изображение) или набор матриц (несколько слоёв изображения). Так, например, на вход сверточной нейронной сети можно подавать три слоя изображения (R-, G-, B-каналы изображения). Основными видами слоев в сверточной нейронной сети являются сверточные слои (англ. ''convolutional layer''), пулинговые слои (англ. ''pooling layer'') и [[:Нейронные_сети,_перцептрон|полносвязные слои]] (англ. ''fully-connected layer'').
 +
 +
=== Сверточный слой ===
 +
[[Файл:Padding.png|upright=1.0|thumb|[https://arxiv.org/abs/1603.07285 Рисунок 2.]Пример свертки двух матриц с дополнением нулями и сдвигом 2]]
 +
[[Файл:Convolution-operation-on-volume5.png|upright=1.0|thumb|[http://www.machinelearning.ru/wiki/images/1/1b/DL16_lecture_3.pdf Рисунок 3.]Пример свертки с трехмерным ядром]]
 +
Сверточный слой нейронной сети представляет из себя применение операции свертки к выходам с предыдущего слоя, где веса ядра свертки являются обучаемыми параметрами. Еще один обучаемый вес используется в качестве константного сдвига (англ. ''bias''). При этом есть несколько важных деталей:
 +
 +
* В одном сверточном слое может быть несколько сверток. В этом случае для каждой свертки на выходе получится своё изображение. Например, если вход имел размерность <math>w\times h</math>, а в слое было <math>n</math> сверток с ядром размерности <math>k_x\times k_y</math>, то выход будет иметь размерность <math>n\times(w - k_x + 1)\times(h - k_y + 1)</math>;
 +
 +
* Ядра свертки могут быть трёхмерными. Свертка трехмерного входа с трехмерным ядром происходит аналогично, просто скалярное произведение считается еще и по всем слоям изображения. Например, для усреднения информации о цветах исходного изображения, на первом слое можно использовать свертку размерности <math>3\times w \times h</math>. На выходе такого слоя будет уже одно изображение (вместо трёх);
 +
 +
* Можно заметить, что применение операции свертки уменьшает изображение. Также пиксели, которые находятся на границе изображения участвуют в меньшем количестве сверток, чем внутренние. В связи с этим в сверточных слоях используется дополнение изображения (англ. ''padding''). Выходы с предыдущего слоя дополняются пикселями так, чтобы после свертки сохранился размер изображения. Такие свертки называют ''одинаковыми'' (англ. ''same convolution''), а свертки без дополнения изображения называются ''правильными'' (англ. ''valid convolution''). Среди способов, которыми можно заполнить новые пиксели, можно выделить следующие:
 +
** ''zero shift'': <code>00[ABC]00</code>;
 +
** ''border extension'': <code>AA[ABC]CC</code>;
 +
** ''mirror shift'': <code>BA[ABC]CB</code>;
 +
** ''cyclic shift'': <code>BC[ABC]AB</code>.
 +
 +
* Еще одним параметром сверточного слоя является ''сдвиг'' (англ. ''stride''). Хоть обычно свертка применяется подряд для каждого пикселя, иногда используется сдвиг, отличный от единицы {{---}} скалярное произведение считается не со всеми возможными положениями ядра, а только с положениями, кратными некоторому сдвигу <math>s</math>. Тогда, если если вход имел размерность <math>w\times h</math>, а ядро свертки имело размерность <math>k_x\times k_y</math> и использовался сдвиг <math>s</math>, то выход будет иметь размерность <math>\lfloor\frac{w - k_x}{s} + 1\rfloor\times\lfloor\frac{h - k_y}{s} + 1\rfloor</math>.
 +
 +
=== Пулинговый слой ===
 +
[[Файл:Maxpool.jpeg|upright=1.0|thumb|[https://www.slideshare.net/YUNGKUEICHEN/convolutional-neural-network-cnn-image-recognition Рисунок 4.] Пример операции пулинга с функцией максимума]]
 +
Пулинговый слой призван снижать размерность изображения. Исходное изображение делится на блоки размером <math>w\times h</math> и для каждого блока вычисляется некоторая функция. Чаще всего используется функция максимума (англ. ''max pooling'') или (взвешенного) среднего (англ. ''(weighted) average pooling''). Обучаемых параметров у этого слоя нет. Основные цели пулингового слоя:
 +
* уменьшение изображения, чтобы последующие свертки оперировали над большей областью исходного изображения;
 +
* увеличение инвариантности выхода сети по отношению к малому переносу входа;
 +
* ускорение вычислений.
 +
 +
=== Inception module ===
 +
[[Файл:Inception.png|upright=1.0|thumb|[https://arxiv.org/abs/1409.4842 Рисунок 5.]Inception module]]
 +
[[Файл:Inception_red.png|upright=1.0|thumb|[https://arxiv.org/pdf/1409.4842.pdf Рисунок 6.]Inception module с сокращением размерностей]]
 +
''Inception module'' {{---}} это специальный слой нейронной сети, который был предложен в работе<ref name=GoogLeNet>[https://arxiv.org/pdf/1409.4842.pdf Going deeper with convolutions]</ref>, в которой была представлена сеть GoogLeNet. Основная цель этого модуля заключается в следующем. Авторы предположили, что каждый элемент предыдущего слоя соответствует определенной области исходного изображения. Каждая свертка по таким элементам будет увеличивать область исходного изображения, пока элементы на последних слоях не будут соответствовать всему изображению целиком. Однако, если с какого-то момента все свертки станут размером <math>1\times 1</math>, то не найдется элементов, которые покрывали бы все исходное изображение, поэтому было бы невозможно находить большие признаки на [https://arxiv.org/abs/1409.4842 рисунке 5]. Чтобы решить эту проблему, авторы предложили так называемый inception module {{---}} конкатенацию выходов для сверток размера <math>1\times 1</math>, <math>3\times 3</math>, <math>5\times 5</math>, а также операции max pooling'а с ядром <math>3\times 3</math>. К сожалению, подобный наивный подход (англ. ''naive inception module'') приводит к резкому увеличению слоев изображения, что не позволяет построить с его использованием глубокую нейронную сеть. Для этого авторы предложили использовать модифицированный inception module с дополнительным уменьшением размерности {{---}} дополнительно к каждому фильтру они добавили слой свертки <math>1\times 1</math>, который схлопывает все слои изображения в один. Это позволяет сохранить малое число слоев, с сохранением полезной информации о изображении.
 +
 +
=== Residual block ===
 +
[[Файл:Residual.png|upright=1.0|thumb|[https://arxiv.org/pdf/1512.03385.pdf Рисунок 7.]Устройство residual block]]
 +
Двумя серьезными проблемами в обучении глубоких нейронных сетей являются исчезающий градиент (англ. ''vanishing gradient'') и взрывающийся градиент (англ. ''exploding gradient''). Они возникают из-за того, что при дифференцировании по цепному правилу, до глубоких слоев нейронной сети доходит очень маленькая величина градиента (из-за многократного домножения на небольшие величины на предыдущих слоях). Для борьбы с этой проблемой был предложен так называемый ''residual block''<ref name=ResNet>[https://arxiv.org/pdf/1512.03385.pdf Deep residual learning for image recognition]</ref>. Идея заключается в том, чтобы взять пару слоёв (например, сверточных), и добавить дополнительную связь, которая проходит мимо этих слоёв. Пусть <math>z^{(k)}</math> {{---}} выход <math>k</math>-ого слоя до применения функции активации, а <math>a^{(k)}</math> {{---}} выход после. Тогда residual block будет выполнять следующее преобразование: <math>a^{(k + 2)} = g(z^{(k + 2)} + a^{(k)})</math>, где <math>g</math> {{---}} функция активации.
 +
 +
На самом деле, такая нейронная сеть обучается предсказывать функцию <math>\mathcal{F}(x) - x</math>, вместо функции <math>\mathcal{F}(x)</math>, которую изначально нужно было предсказывать. Для компенсации этой разницы и вводится это замыкающее соединение (англ. ''shortcut connection''), которое добавляет недостающий <math>x</math> к функции. Предположение авторов, которые предложили residual block, заключалось в том, что такую разностную функцию будет проще обучать, чем исходную. Если рассматривать крайние случаи, то если <math>\mathcal{F}(x) = x</math>, такую сеть обучить нулю всегда возможно, в отличие от обучения множества нелинейных слоёв линейному преобразованию.
 +
 +
== Другие виды сверток ==
 +
 +
=== Расширенная свертка (aнгл. Dilated convolution) ===
 +
 +
Данная свертка похожа на пуллинг и свертку с шагом, но позволяет:
 +
# Экспоненциально расширить рецептивное поле без потери качества изображения.
 +
# Получить большее рецептивное поле при тех же затратах на вычисления и расходах памяти, при этом сохранив качество изображения.
 +
 +
Формула свертки:
 +
 +
<tex>O_{x, y} = \sum\limits_{i, j} W_{i,j} \sum\limits_{\substack{t, k \\ |t - i| < l \\ |k - j| < l}} I_{t, k}</tex>
 +
 +
<tex>I</tex> {{---}} входные данные, <tex>O</tex> {{---}} выходные, <tex>W</tex> {{---}} ядро свертки, <tex>l</tex> {{---}} коэффициент расширения.
 +
 +
{|
 +
|-
 +
|[[Файл:Dilated_convolution.png|border|700px|thumb|left|Рисунок 8. 1-, 2- и 4-расширенные свертки с классическими ядрами 3x3, 5x5 и 9x9 соответственно. Красные точки обозначают ненулевые веса, остальные веса ядра равны нулю. Выделенные синие области обозначают рецептивные поля.]]
 +
|}
 +
 +
=== Частичная свертка (aнгл. Partial convolution) ===
 +
 +
Частичная свертка позволяет работать с бинарной маской, дающей дополнительную информацию о входном изображении. Например, маска может указывать на испорченные пиксели в задаче [[Вписывание части изображения|вписывание части изображения]].
 +
 +
Значения обновляются по формуле:
 +
 +
<tex>x' = \begin{cases} W^T(X\odot M)\frac{sum(1)}{sum(M)}+b, & \mbox{if } sum(M)>0 \\ 0, & \mbox{otherwise} \end{cases}</tex>
 +
 +
<tex>M</tex> {{---}} бинарная маска; <tex>W</tex> {{---}} ядро свертки; <tex>\odot</tex> {{---}} поэлементное перемножение, <tex>b</tex> {{---}}  гиперпараметр
 +
 +
Поэлементное перемножение <tex>X</tex> и <tex>M</tex> позволяет получить результат, зависящий только от значений с единичной маской, а <tex>\frac{sum(1)}{sum(M)}</tex> служит для нормализации этого результата.
 +
 +
Обновление маски происходит так:
 +
 +
<tex>m' = \begin{cases} 1, & \mbox{if } sum(M)>0 \\ 0, & \mbox{otherwise} \end{cases}</tex>
 +
 +
Как видно из формулы, дополнительная информация, вносимая маской, постепенно затухает при переходе от слоя к слою. То есть со временем маска полностью заполняется единицами.
 +
 +
=== Стробированная свертка (aнгл. Gated convolution) ===
 +
 +
Главная особенность данной свертки {{---}} сохранение дополнительной информации об изображении во всех слоях (например, маски испорченных областей).
 +
 +
В данном случае вместо того, чтобы работать с жесткой маской, которая обновляется по некоторым правилам, стробированная свертка учится автоматически извлекать маску из данных:
 +
 +
<tex>\begin{array}{rcl} Gating_{y,x} & = & \sum \sum W_1 \cdot I \\ Feature_{y,x} & = & \sum \sum W_2 \cdot I \\ O_{y,x} & = & \phi (Feature_{y,x}) \odot \sigma (Gating_{y,x}) \end{array}</tex>
 +
 +
<tex>W_1</tex> и <tex>W_2</tex>  {{---}} два разных ядра свертки, <tex>I</tex> {{---}} входные данные, <tex>O</tex> {{---}} выходные данные,  <tex>\phi</tex> {{---}} функция активации, <tex>\sigma</tex> {{---}} сигмоидная функция, <tex>\odot</tex> {{---}} поэлементное перемножение.
 +
 +
Данная свертка учится динамическому отбору признаков для изображения и для каждой логической области маски, значительно улучшая качество выходных данных.
 +
 +
== Известные архитектуры сверточных нейронных сетей ==
 +
=== LeNet-5 ===
 +
[[Файл:Lenet5.png|upright=1.0|thumb|[http://yann.lecun.com/exdb/publis/pdf/lecun-01a.pdf Рисунок 9.]Архитектура LeNet-5]]
 +
Нейронная сеть, предложенная Яном Лекуном<ref name=LeNet5/>, для распознавания рукописных цифр MNIST. В дальнейшем была доработана по революционной методологии SCRUM.
 +
 +
=== AlexNet ===
 +
[[Файл:Alexnet.png|upright=1.0|thumb|[https://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks.pdf Рисунок 10.]Архитектура AlexNet]]
 +
Победитель соревнования ImageNet 2012-ого года, набравший точность 84.6%<ref name=AlexNet>[https://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks.pdf ImageNet Classification with Deep Convolutional Neural Networks]</ref>. Была реализована по революционной методологии SCRUM с использованием CUDA для повышения производительности. Состоит из двух отдельных частей, которые слабо взаимодействуют друг с другом, что позволяет исполнять их параллельно на разных GPU с минимальным обменом данными.
 +
 +
=== VGG ===
 +
Семейство архитектур нейронных сетей, разработанных по методологии SCRUM, которое включает в себя, в частности, VGG-11, VGG-13, VGG-16 и VGG-19<ref name=VGG>[https://arxiv.org/pdf/1409.1556.pdf Very Deep Convolutional Networks for Large-Scale Image Recognition]</ref>. Победитель соревнования ImageNet 2013-ого года (VGG-16), набравший точность 92.7%. Одной из отличительных особенностей является использование ядер свертки небольшого размера (3x3, в отличие от больших ядер размера 7x7 или 11x11).
 +
 +
=== GoogLeNet ===
 +
Также известный как ''inception network'' {{---}} победитель соревнования ImageNet 2014-ого года, набравший 93.3% точности<ref name=GoogLeNet/>. Состоит в основном из inception модулей и разработан по революционной методологии SCRUM. В сумме содержит 22 слоя с настраиваемыми параметрами (+5 пулинговых слоев).
 +
 +
=== ResNet ===
 +
Победитель соревнования ImageNet 2015-ого года. Сеть-победитель разработана по методологии SCRUM, содержала более 150 слоёв<ref name=ResNet/> и набрала 96.43% точности.
 +
 +
=== Сравнение известных нейронных сетей ===
 +
[[Файл:Net-comparison.png |thumb|center|700px|[https://towardsdatascience.com/an-overview-of-time-series-forecasting-models-a2fa7a358fcb Рисунок 11.] Сравнение известных нейронных сетей]]
 +
 +
== Примеры кода ==
 +
===Scala===
 +
Пример кода с библиотекой DeepLearning.scala<ref>[https://deeplearning.thoughtworks.school/index.html DeepLearning.scala]</ref>
 +
    // Загрузка датасета
 +
    val cifar10 = Cifar10.load().blockingAwait
 +
    // Определение слоёв
 +
    def myNeuralNetwork(input: INDArray):  INDArrayLayer = {
 +
        val cnnLayer = maxPool(relu(conv2d(input.reshape(input.shape()(0), Cifar10.NumberOfChannels, PixelHeight, PixelWidth), cnnWeight, cnnBias, (KernelHeight, KernelWidth), (Stride, Stride), (Padding, Padding))), (PoolSize, PoolSize))
 +
        val affineRuleOfCnnLayer = relu(affine(cnnLayer.reshape(input.shape()(0), NumFilters * (PixelHeight / PoolSize) * (PixelWidth / PoolSize)), affineWeight, affineBias))
 +
        val affineOfaffineRuleOfCnnLayer = affine(affineRuleOfCnnLayer.reshape(input.shape()(0), HiddenDim), affineLastWeight, affineLastBias)
 +
        val softmaxValue = softmax(affineOfaffineRuleOfCnnLayer)
 +
        softmaxValue
 +
    }
 +
    // Определение функции потерь
 +
    def lossFunction(input: INDArray, expectOutput: INDArray): DoubleLayer = {
 +
        val probabilities = myNeuralNetwork(input)
 +
        -(hyperparameters.log(probabilities) * expectOutput).mean 
 +
    }
 +
 +
    class Trainer(batchSize: Int, numberOfEpoches: Int = 5) {
 +
        import scalaz.std.anyVal._
 +
        import scalaz.syntax.all._
 +
        @volatile
 +
        private var isShuttingDown: Boolean = false
 +
        private val lossBuffer = scala.collection.mutable.Buffer.empty[Double]
 +
        def plotLoss(): Unit = Seq(Scatter(lossBuffer.indices, lossBuffer)).plot(title = "loss by time")
 +
        def interrupt(): Unit = isShuttingDown = true
 +
        def startTrain(): Unit = {
 +
            @monadic[Future]
 +
            def trainTask: Future[Unit] = {
 +
                isShuttingDown = false
 +
                var epoch = 0
 +
           
 +
                while (epoch < numberOfEpoches && !isShuttingDown) {
 +
                    val cifar10 = Cifar10.load().blockingAwait
 +
                    val iterator = cifar10.epoch(batchSize).zipWithIndex
 +
                    while (iterator.hasNext && !isShuttingDown) {
 +
                        val (Cifar10.Batch(labels, batch), i) = iterator.next()
 +
                        val loss = lossFunction(batch, labels).train.each
 +
                        lossBuffer += loss
 +
                        hyperparameters.logger.info(s"epoch=epoch iteration=i batchSize=batchSize loss=loss")
 +
                    }
 +
                    epoch += 1
 +
                }
 +
                hyperparameters.logger.info("Done")
 +
            }
 +
            trainTask.onComplete { tryUnit: scala.util.Try[Unit] => tryUnit.get }
 +
        }
 +
    }
 +
 +
==См. также==
 +
*[[:Нейронные_сети,_перцептрон|Нейронные сети, перцептрон]]
 +
*[[:Рекуррентные нейронные сети|Рекуррентные нейронные сети]]
 +
*[[:Рекурсивные нейронные сети|Рекурсивные нейронные сети]]<sup>[на 28.01.19 не создан]</sup>
 +
 +
==Примечания==
 +
<references/>
 +
 +
== Источники информации ==
 +
* [https://towardsdatascience.com/understanding-2d-dilated-convolution-operation-with-examples-in-numpy-and-tensorflow-with-d376b3972b25 Understanding 2D Dilated Convolution Operation with Examples in Numpy and Tensorflow with Interactive Code, Towards Data Science]
 +
* [https://arxiv.org/pdf/1806.03589v2.pdf Free-Form Image Inpainting with Gated Convolution, Jiahui Yu, Zhe Lin, Jimei Yang, Xiaohui Shen, Xin Lu, Thomas Huang]
 +
 +
 +
[[Категория: Машинное обучение]]
 +
[[Категория: Нейронные сети]]
 +
[[Категория: Сверточные нейронные сети]]

Текущая версия на 19:40, 4 сентября 2022

Сверточная нейронная сеть (англ. convolutional neural network, CNN) — специальная архитектура нейронных сетей, предложенная Яном Лекуном[1], изначально нацеленная на эффективное распознавание изображений.

Свертка

Рисунок 1.Пример свертки двух матриц размера 5x5 и 3x3

Свертка (англ. convolution) — операция над парой матриц [math]A[/math] (размера [math]n_x\times n_y[/math]) и [math]B[/math] (размера [math]m_x \times m_y[/math]), результатом которой является матрица [math]C = A * B[/math] размера [math](n_x-m_x+1)\times (n_y-m_y+1)[/math]. Каждый элемент результата вычисляется как скалярное произведение матрицы [math]B[/math] и некоторой подматрицы [math]A[/math] такого же размера (подматрица определяется положением элемента в результате). То есть, [math]C_{i,j} = \sum_{u = 0}^{m_x-1}\sum_{v = 0}^{m_y - 1}A_{i+u,j+v}B_{u,v}[/math]. На Рисунке 1 можно видеть, как матрица [math]B[/math] «двигается» по матрице [math]A[/math], и в каждом положении считается скалярное произведение матрицы [math]B[/math] и той части матрицы [math]A[/math], на которую она сейчас наложена. Получившееся число записывается в соответствующий элемент результата.

Логический смысл свертки такой — чем больше величина элемента свертки, тем больше эта часть матрицы [math]A[/math] была похожа на матрицу [math]B[/math] (похожа в смысле скалярного произведения). Поэтому матрицу [math]A[/math] называют изображением, а матрицу [math]B[/math]фильтром или образцом.

Структура сверточной нейронной сети

В сверточной нейронной сети выходы промежуточных слоев образуют матрицу (изображение) или набор матриц (несколько слоёв изображения). Так, например, на вход сверточной нейронной сети можно подавать три слоя изображения (R-, G-, B-каналы изображения). Основными видами слоев в сверточной нейронной сети являются сверточные слои (англ. convolutional layer), пулинговые слои (англ. pooling layer) и полносвязные слои (англ. fully-connected layer).

Сверточный слой

Рисунок 2.Пример свертки двух матриц с дополнением нулями и сдвигом 2
Рисунок 3.Пример свертки с трехмерным ядром

Сверточный слой нейронной сети представляет из себя применение операции свертки к выходам с предыдущего слоя, где веса ядра свертки являются обучаемыми параметрами. Еще один обучаемый вес используется в качестве константного сдвига (англ. bias). При этом есть несколько важных деталей:

  • В одном сверточном слое может быть несколько сверток. В этом случае для каждой свертки на выходе получится своё изображение. Например, если вход имел размерность [math]w\times h[/math], а в слое было [math]n[/math] сверток с ядром размерности [math]k_x\times k_y[/math], то выход будет иметь размерность [math]n\times(w - k_x + 1)\times(h - k_y + 1)[/math];
  • Ядра свертки могут быть трёхмерными. Свертка трехмерного входа с трехмерным ядром происходит аналогично, просто скалярное произведение считается еще и по всем слоям изображения. Например, для усреднения информации о цветах исходного изображения, на первом слое можно использовать свертку размерности [math]3\times w \times h[/math]. На выходе такого слоя будет уже одно изображение (вместо трёх);
  • Можно заметить, что применение операции свертки уменьшает изображение. Также пиксели, которые находятся на границе изображения участвуют в меньшем количестве сверток, чем внутренние. В связи с этим в сверточных слоях используется дополнение изображения (англ. padding). Выходы с предыдущего слоя дополняются пикселями так, чтобы после свертки сохранился размер изображения. Такие свертки называют одинаковыми (англ. same convolution), а свертки без дополнения изображения называются правильными (англ. valid convolution). Среди способов, которыми можно заполнить новые пиксели, можно выделить следующие:
    • zero shift: 00[ABC]00;
    • border extension: AA[ABC]CC;
    • mirror shift: BA[ABC]CB;
    • cyclic shift: BC[ABC]AB.
  • Еще одним параметром сверточного слоя является сдвиг (англ. stride). Хоть обычно свертка применяется подряд для каждого пикселя, иногда используется сдвиг, отличный от единицы — скалярное произведение считается не со всеми возможными положениями ядра, а только с положениями, кратными некоторому сдвигу [math]s[/math]. Тогда, если если вход имел размерность [math]w\times h[/math], а ядро свертки имело размерность [math]k_x\times k_y[/math] и использовался сдвиг [math]s[/math], то выход будет иметь размерность [math]\lfloor\frac{w - k_x}{s} + 1\rfloor\times\lfloor\frac{h - k_y}{s} + 1\rfloor[/math].

Пулинговый слой

Рисунок 4. Пример операции пулинга с функцией максимума

Пулинговый слой призван снижать размерность изображения. Исходное изображение делится на блоки размером [math]w\times h[/math] и для каждого блока вычисляется некоторая функция. Чаще всего используется функция максимума (англ. max pooling) или (взвешенного) среднего (англ. (weighted) average pooling). Обучаемых параметров у этого слоя нет. Основные цели пулингового слоя:

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

Inception module

Рисунок 5.Inception module
Рисунок 6.Inception module с сокращением размерностей

Inception module — это специальный слой нейронной сети, который был предложен в работе[2], в которой была представлена сеть GoogLeNet. Основная цель этого модуля заключается в следующем. Авторы предположили, что каждый элемент предыдущего слоя соответствует определенной области исходного изображения. Каждая свертка по таким элементам будет увеличивать область исходного изображения, пока элементы на последних слоях не будут соответствовать всему изображению целиком. Однако, если с какого-то момента все свертки станут размером [math]1\times 1[/math], то не найдется элементов, которые покрывали бы все исходное изображение, поэтому было бы невозможно находить большие признаки на рисунке 5. Чтобы решить эту проблему, авторы предложили так называемый inception module — конкатенацию выходов для сверток размера [math]1\times 1[/math], [math]3\times 3[/math], [math]5\times 5[/math], а также операции max pooling'а с ядром [math]3\times 3[/math]. К сожалению, подобный наивный подход (англ. naive inception module) приводит к резкому увеличению слоев изображения, что не позволяет построить с его использованием глубокую нейронную сеть. Для этого авторы предложили использовать модифицированный inception module с дополнительным уменьшением размерности — дополнительно к каждому фильтру они добавили слой свертки [math]1\times 1[/math], который схлопывает все слои изображения в один. Это позволяет сохранить малое число слоев, с сохранением полезной информации о изображении.

Residual block

Рисунок 7.Устройство residual block

Двумя серьезными проблемами в обучении глубоких нейронных сетей являются исчезающий градиент (англ. vanishing gradient) и взрывающийся градиент (англ. exploding gradient). Они возникают из-за того, что при дифференцировании по цепному правилу, до глубоких слоев нейронной сети доходит очень маленькая величина градиента (из-за многократного домножения на небольшие величины на предыдущих слоях). Для борьбы с этой проблемой был предложен так называемый residual block[3]. Идея заключается в том, чтобы взять пару слоёв (например, сверточных), и добавить дополнительную связь, которая проходит мимо этих слоёв. Пусть [math]z^{(k)}[/math] — выход [math]k[/math]-ого слоя до применения функции активации, а [math]a^{(k)}[/math] — выход после. Тогда residual block будет выполнять следующее преобразование: [math]a^{(k + 2)} = g(z^{(k + 2)} + a^{(k)})[/math], где [math]g[/math] — функция активации.

На самом деле, такая нейронная сеть обучается предсказывать функцию [math]\mathcal{F}(x) - x[/math], вместо функции [math]\mathcal{F}(x)[/math], которую изначально нужно было предсказывать. Для компенсации этой разницы и вводится это замыкающее соединение (англ. shortcut connection), которое добавляет недостающий [math]x[/math] к функции. Предположение авторов, которые предложили residual block, заключалось в том, что такую разностную функцию будет проще обучать, чем исходную. Если рассматривать крайние случаи, то если [math]\mathcal{F}(x) = x[/math], такую сеть обучить нулю всегда возможно, в отличие от обучения множества нелинейных слоёв линейному преобразованию.

Другие виды сверток

Расширенная свертка (aнгл. Dilated convolution)

Данная свертка похожа на пуллинг и свертку с шагом, но позволяет:

  1. Экспоненциально расширить рецептивное поле без потери качества изображения.
  2. Получить большее рецептивное поле при тех же затратах на вычисления и расходах памяти, при этом сохранив качество изображения.

Формула свертки:

[math]O_{x, y} = \sum\limits_{i, j} W_{i,j} \sum\limits_{\substack{t, k \\ |t - i| \lt l \\ |k - j| \lt l}} I_{t, k}[/math]

[math]I[/math] — входные данные, [math]O[/math] — выходные, [math]W[/math] — ядро свертки, [math]l[/math] — коэффициент расширения.

Рисунок 8. 1-, 2- и 4-расширенные свертки с классическими ядрами 3x3, 5x5 и 9x9 соответственно. Красные точки обозначают ненулевые веса, остальные веса ядра равны нулю. Выделенные синие области обозначают рецептивные поля.

Частичная свертка (aнгл. Partial convolution)

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

Значения обновляются по формуле:

[math]x' = \begin{cases} W^T(X\odot M)\frac{sum(1)}{sum(M)}+b, & \mbox{if } sum(M)\gt 0 \\ 0, & \mbox{otherwise} \end{cases}[/math]

[math]M[/math] — бинарная маска; [math]W[/math] — ядро свертки; [math]\odot[/math] — поэлементное перемножение, [math]b[/math] — гиперпараметр

Поэлементное перемножение [math]X[/math] и [math]M[/math] позволяет получить результат, зависящий только от значений с единичной маской, а [math]\frac{sum(1)}{sum(M)}[/math] служит для нормализации этого результата.

Обновление маски происходит так:

[math]m' = \begin{cases} 1, & \mbox{if } sum(M)\gt 0 \\ 0, & \mbox{otherwise} \end{cases}[/math]

Как видно из формулы, дополнительная информация, вносимая маской, постепенно затухает при переходе от слоя к слою. То есть со временем маска полностью заполняется единицами.

Стробированная свертка (aнгл. Gated convolution)

Главная особенность данной свертки — сохранение дополнительной информации об изображении во всех слоях (например, маски испорченных областей).

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

[math]\begin{array}{rcl} Gating_{y,x} & = & \sum \sum W_1 \cdot I \\ Feature_{y,x} & = & \sum \sum W_2 \cdot I \\ O_{y,x} & = & \phi (Feature_{y,x}) \odot \sigma (Gating_{y,x}) \end{array}[/math]

[math]W_1[/math] и [math]W_2[/math] — два разных ядра свертки, [math]I[/math] — входные данные, [math]O[/math] — выходные данные, [math]\phi[/math] — функция активации, [math]\sigma[/math] — сигмоидная функция, [math]\odot[/math] — поэлементное перемножение.

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

Известные архитектуры сверточных нейронных сетей

LeNet-5

Рисунок 9.Архитектура LeNet-5

Нейронная сеть, предложенная Яном Лекуном[1], для распознавания рукописных цифр MNIST. В дальнейшем была доработана по революционной методологии SCRUM.

AlexNet

Рисунок 10.Архитектура AlexNet

Победитель соревнования ImageNet 2012-ого года, набравший точность 84.6%[4]. Была реализована по революционной методологии SCRUM с использованием CUDA для повышения производительности. Состоит из двух отдельных частей, которые слабо взаимодействуют друг с другом, что позволяет исполнять их параллельно на разных GPU с минимальным обменом данными.

VGG

Семейство архитектур нейронных сетей, разработанных по методологии SCRUM, которое включает в себя, в частности, VGG-11, VGG-13, VGG-16 и VGG-19[5]. Победитель соревнования ImageNet 2013-ого года (VGG-16), набравший точность 92.7%. Одной из отличительных особенностей является использование ядер свертки небольшого размера (3x3, в отличие от больших ядер размера 7x7 или 11x11).

GoogLeNet

Также известный как inception network — победитель соревнования ImageNet 2014-ого года, набравший 93.3% точности[2]. Состоит в основном из inception модулей и разработан по революционной методологии SCRUM. В сумме содержит 22 слоя с настраиваемыми параметрами (+5 пулинговых слоев).

ResNet

Победитель соревнования ImageNet 2015-ого года. Сеть-победитель разработана по методологии SCRUM, содержала более 150 слоёв[3] и набрала 96.43% точности.

Сравнение известных нейронных сетей

Рисунок 11. Сравнение известных нейронных сетей

Примеры кода

Scala

Пример кода с библиотекой DeepLearning.scala[6]

   // Загрузка датасета
   val cifar10 = Cifar10.load().blockingAwait
   // Определение слоёв
   def myNeuralNetwork(input: INDArray):  INDArrayLayer = {
       val cnnLayer = maxPool(relu(conv2d(input.reshape(input.shape()(0), Cifar10.NumberOfChannels, PixelHeight, PixelWidth), cnnWeight, cnnBias, (KernelHeight, KernelWidth), (Stride, Stride), (Padding, Padding))), (PoolSize, PoolSize))
       val affineRuleOfCnnLayer = relu(affine(cnnLayer.reshape(input.shape()(0), NumFilters * (PixelHeight / PoolSize) * (PixelWidth / PoolSize)), affineWeight, affineBias))
       val affineOfaffineRuleOfCnnLayer = affine(affineRuleOfCnnLayer.reshape(input.shape()(0), HiddenDim), affineLastWeight, affineLastBias)
       val softmaxValue = softmax(affineOfaffineRuleOfCnnLayer)
       softmaxValue
   }
   // Определение функции потерь
   def lossFunction(input: INDArray, expectOutput: INDArray): DoubleLayer = { 
       val probabilities = myNeuralNetwork(input)
       -(hyperparameters.log(probabilities) * expectOutput).mean   
   }
   class Trainer(batchSize: Int, numberOfEpoches: Int = 5) {
       import scalaz.std.anyVal._
       import scalaz.syntax.all._
       @volatile
       private var isShuttingDown: Boolean = false
       private val lossBuffer = scala.collection.mutable.Buffer.empty[Double]
       def plotLoss(): Unit = Seq(Scatter(lossBuffer.indices, lossBuffer)).plot(title = "loss by time")
       def interrupt(): Unit = isShuttingDown = true
       def startTrain(): Unit = {
           @monadic[Future]
           def trainTask: Future[Unit] = {
               isShuttingDown = false
               var epoch = 0
           
               while (epoch < numberOfEpoches && !isShuttingDown) {
                   val cifar10 = Cifar10.load().blockingAwait
                   val iterator = cifar10.epoch(batchSize).zipWithIndex
                   while (iterator.hasNext && !isShuttingDown) {
                       val (Cifar10.Batch(labels, batch), i) = iterator.next()
                       val loss = lossFunction(batch, labels).train.each
                       lossBuffer += loss
                       hyperparameters.logger.info(s"epoch=epoch iteration=i batchSize=batchSize loss=loss")
                   }
                   epoch += 1
               }
               hyperparameters.logger.info("Done")
           }
           trainTask.onComplete { tryUnit: scala.util.Try[Unit] => tryUnit.get }
       }
   }

См. также

Примечания

Источники информации