Сверточные нейронные сети
Сверточная нейронная сеть (англ. convolutional neural network, CNN) — специальная архитектура нейронных сетей, предложенная Яном Лекуном[1], изначально нацеленная на эффективное распознавание изображений.
Содержание
Свертка
Свертка (англ. convolution) — операция над парой матриц
(размера ) и (размера ), результатом которой является матрица размера . Каждый элемент результата вычисляется как скалярное произведение матрицы и некоторой подматрицы такого же размера (подматрица определяется положением элемента в результате). То есть, . На изображении справа можно видеть, как матрица «двигается» по матрице , и в каждом положении считается скалярное произведение матрицы и той части матрицы , на которую она сейчас наложена. Получившееся число записывается в соответствующий элемент результата.Логический смысл свертки такой — чем больше величина элемента свертки, тем больше эта часть матрицы
была похожа на матрицу (похожа в смысле скалярного произведения). Поэтому матрицу называют изображением, а матрицу — фильтром или образцом.Структура сверточной нейронной сети
В сверточной нейронной сети выходы промежуточных слоев образуют матрицу (изображение) или набор матриц (несколько слоёв изображения). Так, например, на вход сверточной нейронной сети можно подавать три слоя изображения (R-, G-, B-каналы изображения). Основными видами слоев в сверточной нейронной сети являются сверточные слои (англ. convolutional layer), пулинговые слои (англ. pooling layer) и полносвязные слои (англ. fully-connected layer).
Сверточный слой
Сверточный слой нейронной сети представляет из себя применение операции свертки к выходам с предыдущего слоя, где веса ядра свертки являются обучаемыми параметрами. Еще один обучаемый вес используется в качестве константного сдвига (англ. bias). При этом есть несколько важных деталей:
- В одном сверточном слое может быть несколько сверток. В этом случае для каждой свертки на выходе получится своё изображение. Например, если вход имел размерность , а в слое было сверток с ядром размерности , то выход будет иметь размерность .
- Ядра свертки могут быть трёхмерными. Свертка трехмерного входа с трехмерным ядром происходит аналогично, просто скалярное произведение считается еще и по всем слоям изображения. Например, для усреднения информации о цветах исходного изображения, на первом слое можно использовать свертку размерности . На выходе такого слоя будет уже одно изображение (вместо трёх).
- Можно заметить, что применение операции свертки уменьшает изображение. Также пиксели, которые находятся на границе изображения учавствуют в меньшем количестве сверток, чем внутренние. В связи с этим в сверточных слоях используется дополнение изображения (англ. 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
- zero shift:
- Еще одним параметром сверточного слоя является сдвиг (англ. stride). Хоть обычно свертка применяется подряд для каждого пикселя, иногда используется сдвиг, отличный от единицы — скалярное произведение считается не со всеми возможными положениями ядра, а только с положениями, кратными некоторому сдвигу . Тогда, если если вход имел размерность , а ядро свертки имело размерность и использовался сдвиг , то выход будет иметь размерность .
Пулинговый слой
Пулинговый слой призван снижать размерность изображения. Исходное изображение делится на блоки размером
и для каждого блока вычисляется некоторая функция. Чаще всего используется функция максимума (англ. max pooling) или (взвешенного) среднего (англ. (weighted) average pooling). Обучаемых параметров у этого слоя нет. Основные цели пулингового слоя:- уменьшение изображения, чтобы последующие свертки оперировали над большей областью исходного изображения;
- увеличение инвариантности выхода сети по отношению к малому переносу входа;
- ускорение вычислений.
Inception module
Inception module — это специальный слой нейронной сети, который был предложен в работе[2], в которой была представлена сеть GoogLeNet. Основная цель этого модуля заключается в следующем. Авторы предположили, что каждый элемент предыдущего слоя соответствует определенной области исходного изображения. Каждая свертка по таким элементам будет увеличивать область исходного изображения, пока элементы на последних слоях не будут соответствовать всему изображению целиком. Однако, если с какого-то момента все свертки станут размером , то не найдется элементов, которые покрывали бы все исходное изображение, поэтому было бы невозможно находить большие признаки на изображении. Чтобы решить эту проблему, авторы предложили так называемый inception module — конкатенацию выходов для сверток размера , , , а также операции max pooling'а с ядром . К сожалению, подобный наивный подход (англ. naive inception module) приводит к резкому увеличению слоев изображения, что не позволяет построить с его использованием глубокую нейронную сеть. Для этого авторы предложили использовать модифицированный inception module с дополнительным уменьшением размерности — дополнительно к каждому фильтру они добавили слой свертки , который схлопывает все слои изображения в один. Это позволяет сохранить малое число слоев, с сохранением полезной информации о изображении.
Известные архитектуры сверточных нейронных сетей
LeNet-5
Нейронная сеть, предложенная Яном Лекуном[1], для распознавания рукописных цифр MNIST.
AlexNet
Победитель соревнования ImageNet 2012-ого года, набравший точность 84.6%[3]. Была реализована с использованием CUDA для повышения производительности. Состоит из двух отдельных частей, которые слабо взаимодействуют друг с другом, что позволяет исполнять их параллельно на разных GPU с минимальным обменом данными.
VGG
Семейство архитектур нейронных сетей, которое включает в себя, в частности, VGG-11, VGG-13, VGG-16 и VGG-19[4]. Победитель соревнования ImageNet 2013-ого года (VGG-16), набравший точность 92.7%. Одной из отличительных особенностей является использование ядер свертки небольшого размера (3x3, в отличие от больших ядер размера 7x7 или 11x11).
GoogLeNet
Также известный как inception network — победитель соревнования ImageNet 2014-ого года, набравший 93.3% точности[2]. Состоит в основном из inception модулей. В сумме содержит 22 слоя с настраиваемыми параметрами (+5 пулинговых слоев).
Примеры кода
Scala
Пример кода с библиотекой DeepLearning.scala[5]
// Загрузка датасета 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 } } }
См. также
Примечания
- ↑ 1,0 1,1 Yann LeCun — Gradient-Based Learning Applied to Document Recognition, 1998
- ↑ 2,0 2,1 Going deeper with convolutions
- ↑ [https://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks.pdf ImageNet Classification with Deep Convolutional Neural Networks]
- ↑ Very Deep Convolutional Networks for Large-Scale Image Recognition
- ↑ DeepLearning.scala