В этом примере показано, как создать нейронную сеть глубокого обучения с остаточными связями и обучить ее CIFAR-10 данным. Остаточные соединения являются популярным элементом в сверточных нейронных сетевых архитектурах. Использование остаточных соединений улучшает градиентный поток через сеть и позволяет обучать более глубокие сети.
Для многих приложений достаточно использовать сеть, состоящую из простой последовательности слоев. Однако для некоторых приложений требуются сети с более сложной структурой графов, в которых уровни могут иметь входы от нескольких уровней и выходы от нескольких уровней. Эти типы сетей часто называют сетями направленного ациклического графа (DAG). Остаточная сеть - это тип сети DAG, которая имеет остаточные (или короткие) соединения, которые обходят основные сетевые уровни. Остаточные соединения позволяют градиентам параметров легче распространяться от выходного уровня к более ранним уровням сети, что позволяет обучать более глубокие сети. Эта увеличенная глубина сети может привести к более высокой точности при выполнении более сложных задач.
Чтобы создать и обучить сеть со структурой графика, выполните следующие действия.
Создать LayerGraph объект с использованием layerGraph. График уровней определяет архитектуру сети. Можно создать пустой график слоев, а затем добавить к нему слои. Можно также создать график слоев непосредственно из массива сетевых слоев. В этом случае layerGraph соединяет слои в массиве один за другим.
Добавление слоев в график слоев с помощью addLayersи удалить слои из графика с помощью removeLayers.
Соединение слоев с другими слоями с помощью connectLayersи отсоединить слои от других слоев с помощью disconnectLayers.
Постройте график сетевой архитектуры с помощью plot.
Обучение сети с помощью trainNetwork. Обученная сеть является DAGNetwork объект.
Выполнение классификации и прогнозирования новых данных с использованием classify и predict.
Можно также загрузить предварительно подготовленные сети для классификации изображений. Дополнительные сведения см. в разделе Предварительно обученные нейронные сети.
Загрузите набор данных CIFAR-10 [1]. Набор данных содержит 60 000 изображений. Каждое изображение имеет размер 32 на 32 и имеет три цветовых канала (RGB). Размер набора данных составляет 175 МБ. В зависимости от подключения к Интернету процесс загрузки может занять некоторое время.
datadir = tempdir; downloadCIFARData(datadir);
Загрузите обучающие и тестовые образы CIFAR-10 в виде массивов 4-D. Обучающий набор содержит 50 000 изображений, а набор тестов - 10 000 изображений. Используйте CIFAR-10 тестовые образы для проверки сети.
[XTrain,YTrain,XValidation,YValidation] = loadCIFARData(datadir);
Можно просмотреть случайную выборку обучающих изображений с помощью следующего кода.
figure;
idx = randperm(size(XTrain,4),20);
im = imtile(XTrain(:,:,:,idx),'ThumbnailSize',[96,96]);
imshow(im)
Создание augmentedImageDatastore объект, используемый для обучения сети. Во время обучения хранилище данных случайным образом разворачивает обучающие изображения вдоль вертикальной оси и случайным образом переводит их до четырех пикселей по горизонтали и вертикали. Увеличение объема данных помогает предотвратить переоборудование сети и запоминание точных деталей обучающих изображений.
imageSize = [32 32 3]; pixelRange = [-4 4]; imageAugmenter = imageDataAugmenter( ... 'RandXReflection',true, ... 'RandXTranslation',pixelRange, ... 'RandYTranslation',pixelRange); augimdsTrain = augmentedImageDatastore(imageSize,XTrain,YTrain, ... 'DataAugmentation',imageAugmenter, ... 'OutputSizeMode','randcrop');
Остаточная сетевая архитектура состоит из следующих компонентов:
Основная ветвь с последовательно соединенными сверточными, пакетной нормализацией и уровнями ReLU.
Остаточные соединения, которые обходят сверточные блоки основной ветви. Выходы остаточных соединений и сверточных блоков добавляются по элементам. При изменении размера активаций остаточные соединения также должны содержать сверточные слои 1 на 1. Остаточные соединения позволяют градиентам параметров легче перетекать от выходного уровня к более ранним уровням сети, что позволяет обучать более глубокие сети.
Создать главную ветвь
Начните с создания основной ветви сети. Основная ветка содержит пять секций.
Начальный участок, содержащий слой ввода изображения и начальный сверток с активацией.
Три ступени сверточных слоев с различными размерами элементов (32 на 32, 16 на 16 и 8 на 8). Каждая ступень содержит N сверточных блоков. В этой части примера N = 2. Каждый сверточный блок содержит два 3 на 3 сверточных слоя с включениями. netWidth параметр - ширина сети, определяемая как количество фильтров в сверточных слоях на первой ступени сети. Первые сверточные блоки на втором и третьем этапах уменьшают пространственные размеры в два раза. Чтобы объем вычислений, требуемых в каждом сверточном уровне, примерно одинаков по всей сети, увеличивайте число фильтров в два раза при каждом выполнении пространственной понижающей дискретизации.
Заключительный раздел с глобальным средним объединением, полностью подключенными уровнями, уровнями softmax и классификацией.
Использовать convolutionalUnit(numF,stride,tag) для создания сверточной единицы измерения. numF - количество сверточных фильтров в каждом слое, stride - шаг первого сверточного слоя блока, и tag представляет собой символьный массив для добавления к именам слоев. convolutionalUnit определяется в конце примера.
Присвойте уникальные имена всем слоям. Слои в сверточных единицах имеют имена, начинающиеся с 'SjUk', где j - индекс сцены и k - индекс сверточного блока в пределах этого этапа. Например, 'S2U1' обозначает стадию 2, блок 1.
netWidth = 16;
layers = [
imageInputLayer([32 32 3],'Name','input')
convolution2dLayer(3,netWidth,'Padding','same','Name','convInp')
batchNormalizationLayer('Name','BNInp')
reluLayer('Name','reluInp')
convolutionalUnit(netWidth,1,'S1U1')
additionLayer(2,'Name','add11')
reluLayer('Name','relu11')
convolutionalUnit(netWidth,1,'S1U2')
additionLayer(2,'Name','add12')
reluLayer('Name','relu12')
convolutionalUnit(2*netWidth,2,'S2U1')
additionLayer(2,'Name','add21')
reluLayer('Name','relu21')
convolutionalUnit(2*netWidth,1,'S2U2')
additionLayer(2,'Name','add22')
reluLayer('Name','relu22')
convolutionalUnit(4*netWidth,2,'S3U1')
additionLayer(2,'Name','add31')
reluLayer('Name','relu31')
convolutionalUnit(4*netWidth,1,'S3U2')
additionLayer(2,'Name','add32')
reluLayer('Name','relu32')
averagePooling2dLayer(8,'Name','globalPool')
fullyConnectedLayer(10,'Name','fcFinal')
softmaxLayer('Name','softmax')
classificationLayer('Name','classoutput')
];Создайте график слоев из массива слоев. layerGraph соединяет все слои в layers последовательно. Постройте график слоев.
lgraph = layerGraph(layers); figure('Units','normalized','Position',[0.2 0.2 0.6 0.6]); plot(lgraph);

Создание остаточных соединений
Добавьте остаточные соединения вокруг сверточных блоков. Большинство остаточных соединений не выполняют никаких операций и просто добавляют элементы к выходам сверточных блоков.
Создайте остаточное соединение из 'reluInp' в 'add11' слой. Поскольку при создании слоя было указано, что количество входов в слой сложения равно двум, слой имеет два входа с именами 'in1' и 'in2'. Конечный слой первого сверточного блока уже подключен к 'in1' вход. Уровень сложения затем суммирует выходные сигналы первого сверточного блока и 'reluInp' слой.
Таким же образом подключите 'relu11' на второй вход 'add12' слой. Проверьте правильность соединения слоев путем печати графика слоев.
lgraph = connectLayers(lgraph,'reluInp','add11/in2'); lgraph = connectLayers(lgraph,'relu11','add12/in2'); figure('Units','normalized','Position',[0.2 0.2 0.6 0.6]); plot(lgraph);

Когда активизации уровня в сверточных блоках изменяют размер (то есть, когда они понижаются пространственно и повышаются в измерении канала), активации в остаточных соединениях также должны изменять размер. Измените размеры активации в остаточных соединениях, используя сверточный слой 1 на 1 вместе с его слоем пакетной нормализации.
skip1 = [
convolution2dLayer(1,2*netWidth,'Stride',2,'Name','skipConv1')
batchNormalizationLayer('Name','skipBN1')];
lgraph = addLayers(lgraph,skip1);
lgraph = connectLayers(lgraph,'relu12','skipConv1');
lgraph = connectLayers(lgraph,'skipBN1','add21/in2');Добавьте идентификационное соединение на втором этапе сети.
lgraph = connectLayers(lgraph,'relu21','add22/in2');
Измените размер активации в остаточном соединении между второй и третьей ступенями другим сверточным слоем 1 на 1 вместе с его слоем пакетной нормализации.
skip2 = [
convolution2dLayer(1,4*netWidth,'Stride',2,'Name','skipConv2')
batchNormalizationLayer('Name','skipBN2')];
lgraph = addLayers(lgraph,skip2);
lgraph = connectLayers(lgraph,'relu22','skipConv2');
lgraph = connectLayers(lgraph,'skipBN2','add31/in2');Добавьте последнее идентификационное соединение и постройте график конечного слоя.
lgraph = connectLayers(lgraph,'relu31','add32/in2'); figure('Units','normalized','Position',[0.2 0.2 0.6 0.6]); plot(lgraph)

Чтобы создать график слоя с остаточными связями для данных CIFAR-10 произвольной глубины и ширины, используйте функцию поддержки residualCIFARlgraph.
lgraph = residualCIFARlgraph(netWidth,numUnits,unitType) создает график слоев для CIFAR-10 данных с остаточными соединениями.
netWidth - ширина сети, определяемая как количество фильтров в первых 3 на 3 свёрточных слоях сети.
numUnits - количество сверточных блоков в основной ветви сети. Поскольку сеть состоит из трех ступеней, где каждая ступень имеет одинаковое количество сверточных блоков, numUnits должно быть целым числом, кратным 3.
unitType - тип сверточной единицы, указанный как "standard" или "bottleneck". Стандартный сверточный блок состоит из двух сверточных слоев 3 на 3. Узкий сверточный блок состоит из трех сверточных слоев: слоя 1 на 1 для понижающей дискретизации в измерении канала, сверточного слоя 3 на 3 и слоя 1 на 1 для повышающей дискретизации в измерении канала. Следовательно, узкий сверточный блок имеет на 50% больше сверточных слоев, чем стандартный блок, но только вдвое меньше числа пространственных сверток 3 на 3. Два типа блоков имеют одинаковую вычислительную сложность, но общее количество элементов, распространяющихся в остаточных соединениях, в четыре раза больше при использовании узких блоков. Общая глубина, определяемая как максимальное количество последовательных сверточных и полностью соединенных слоев, составляет 2 *numUnits + 2 для сетей со стандартными блоками и 3 *numUnits + 2 для сетей с узкими местами.
Создайте остаточную сеть с девятью стандартными сверточными единицами (по три единицы на этап) и шириной 16. Общая глубина сети составляет 2 * 9 + 2 = 20.
numUnits = 9; netWidth = 16; lgraph = residualCIFARlgraph(netWidth,numUnits,"standard"); figure('Units','normalized','Position',[0.1 0.1 0.8 0.8]); plot(lgraph)

Укажите параметры обучения. Обучить сеть на 80 эпох. Выберите скорость обучения, пропорциональную размеру мини-партии, и уменьшите скорость обучения в 10 раз после 60 периодов. Проверяйте сеть один раз в эпоху, используя данные проверки.
miniBatchSize = 128; learnRate = 0.1*miniBatchSize/128; valFrequency = floor(size(XTrain,4)/miniBatchSize); options = trainingOptions('sgdm', ... 'InitialLearnRate',learnRate, ... 'MaxEpochs',80, ... 'MiniBatchSize',miniBatchSize, ... 'VerboseFrequency',valFrequency, ... 'Shuffle','every-epoch', ... 'Plots','training-progress', ... 'Verbose',false, ... 'ValidationData',{XValidation,YValidation}, ... 'ValidationFrequency',valFrequency, ... 'LearnRateSchedule','piecewise', ... 'LearnRateDropFactor',0.1, ... 'LearnRateDropPeriod',60);
Обучение сети с помощью trainNetwork, установите doTraining флаг для true. В противном случае загрузите предварительно подготовленную сеть. Обучение сети хорошему графическому процессору занимает около двух часов. Если у вас нет ГПУ, то обучение занимает гораздо больше времени.
doTraining = false; if doTraining trainedNet = trainNetwork(augimdsTrain,lgraph,options); else load('CIFARNet-20-16.mat','trainedNet'); end

Рассчитайте окончательную точность сети на обучающем наборе (без увеличения данных) и валидационном наборе.
[YValPred,probs] = classify(trainedNet,XValidation); validationError = mean(YValPred ~= YValidation); YTrainPred = classify(trainedNet,XTrain); trainError = mean(YTrainPred ~= YTrain); disp("Training error: " + trainError*100 + "%")
Training error: 2.862%
disp("Validation error: " + validationError*100 + "%")
Validation error: 9.76%
Постройте график матрицы путаницы. Отображение точности и отзыва для каждого класса с помощью сводок столбцов и строк. Сеть чаще всего путает кошек с собаками.
figure('Units','normalized','Position',[0.2 0.2 0.4 0.4]); cm = confusionchart(YValidation,YValPred); cm.Title = 'Confusion Matrix for Validation Data'; cm.ColumnSummary = 'column-normalized'; cm.RowSummary = 'row-normalized';

Можно отобразить случайную выборку из девяти тестовых изображений вместе с их прогнозируемыми классами и вероятностями этих классов, используя следующий код.
figure idx = randperm(size(XValidation,4),9); for i = 1:numel(idx) subplot(3,3,i) imshow(XValidation(:,:,:,idx(i))); prob = num2str(100*max(probs(idx(i),:)),3); predClass = char(YValPred(idx(i))); title([predClass,', ',prob,'%']) end
convolutionalUnit(numF,stride,tag) создает массив слоев с двумя сверточными слоями и соответствующими уровнями пакетной нормализации и ReLU. numF - количество сверточных фильтров, stride - шаг первого сверточного слоя, и tag - тег, добавленный ко всем именам слоев.
function layers = convolutionalUnit(numF,stride,tag) layers = [ convolution2dLayer(3,numF,'Padding','same','Stride',stride,'Name',[tag,'conv1']) batchNormalizationLayer('Name',[tag,'BN1']) reluLayer('Name',[tag,'relu1']) convolution2dLayer(3,numF,'Padding','same','Name',[tag,'conv2']) batchNormalizationLayer('Name',[tag,'BN2'])]; end
[1] Крижевский, Алекс. «Изучение нескольких слоев элементов из крошечных изображений». (2009). https://www.cs.toronto.edu/~kriz/learning-features-2009-TR.pdf
[2] Хэ, Каймин, Сянъу Чжан, Шаоцин Жэнь и Цзянь Сунь. «Глубокое остаточное обучение для распознавания изображения». В трудах конференции IEEE по компьютерному зрению и распознаванию образов, стр. 770-778. 2016.
analyzeNetwork | layerGraph | trainingOptions | trainNetwork