写在前面

俗话说的好,没有什么是MATLAB干不了的,今天笔者就将给大家带来一个使用MATLAB实现迁移学习的实例

数据集选取及简介

考虑到Alexnet输入的色彩通道为3,因此笔者未使用数据集为灰度图的数据集,如MNIST,转而使用的是本身就就为RGB图像的CIFAR-10下载地址在此处。

CIFAR-10 数据集由 10 个类别的 60000 张 32x32 彩色图像组成,每类 6000 张图像。有 50000 张训练图像和 10000 张测试图像。数据集分为五个训练批次一个测试批次,每个批次有 10000 张图像。

数据集处理

下载数据集

这里采用了从数据集官网下载数据集的方式,有如下:

%% 下载 CIFAR-10
if ~exist('cifar-10-batches-mat','dir')
    cifar10Dataset = 'cifar-10-matlab';
    disp('Downloading 174MB CIFAR-10 dataset...');   
    websave([cifar10Dataset,'.tar.gz'],...
        ['https://www.cs.toronto.edu/~kriz/',cifar10Dataset,'.tar.gz']);
    gunzip([cifar10Dataset,'.tar.gz'])
    delete([cifar10Dataset,'.tar.gz'])
    untar([cifar10Dataset,'.tar'])
    delete([cifar10Dataset,'.tar'])
end

下载后的数据集格式为:
数据集格式
针对每一个mat文件格式有:
mat文件格式
其中3072 = 32 _ 32 _ 3,表示图像高长均为32,3个色彩通道,10000个数据

数据集整齐化

由于下载的数据集是.mat格式,我们需要将其变为整齐的图像格式(jpg)文件夹,有如下代码:

%% 准备 CIFAR-10
if ~exist('cifar10Train','dir')
    disp('Saving the Images in folders. This might take some time...');    
    saveCIFAR10AsFolderOfImages('cifar-10-batches-mat', pwd, true);
end

其中saveCIFAR10AsFolderOfImages为自己设计的函数,其中包含了另外自己编写的子函数,有如下代码,功能见代码注释:

function saveCIFAR10AsFolderOfImages(inputPath, outputPath, varargin)
%   将 CIFAR-10 数据集转化为图片格式的文件夹

%   saveCIFAR10AsFolderOfImages(inputPath, outputPath) 采用CIFAR-10
%     位于 inputPath 的数据集并将其作为图像文件夹保存到
%     目录输出路径。如果 inputPath 或 outputPath 为空字符串,则
%     假定应使用当前文件夹。

%   将保存CIFAR-10 数据,以便将具有相同标签的实例保存到
%     带有该标签名称的子目录。

% 输入路径是否有效
if(~isempty(inputPath))
    assert(exist(inputPath,'dir') == 7);
end
if(~isempty(outputPath))
    assert(exist(outputPath,'dir') == 7);
end

% 检查我们是否要将具有相同标签的每个集合保存到自己的集合中
if(isempty(varargin))
    labelDirectories = false;
else
    assert(nargin == 3);
    labelDirectories = varargin{1};
end

% 设置目录名称
trainDirectoryName = 'cifar10Train';
testDirectoryName = 'cifar10Test';

% 创建文件夹用于存放图片
mkdir(fullfile(outputPath, trainDirectoryName));
mkdir(fullfile(outputPath, testDirectoryName));

if(labelDirectories)
    labelNames = {'airplane','automobile','bird','cat','deer','dog','frog','horse','ship','truck'};
    iMakeTheseDirectories(fullfile(outputPath, trainDirectoryName), labelNames);
    iMakeTheseDirectories(fullfile(outputPath, testDirectoryName), labelNames);
    for i = 1:5
        iLoadBatchAndWriteAsImagesToLabelFolders(fullfile(inputPath,['data_batch_' num2str(i) '.mat']), fullfile(outputPath, trainDirectoryName), labelNames, (i-1)*10000);
    end
    iLoadBatchAndWriteAsImagesToLabelFolders(fullfile(inputPath,'test_batch.mat'), fullfile(outputPath, testDirectoryName), labelNames, 0);
else
    for i = 1:5
        iLoadBatchAndWriteAsImages(fullfile(inputPath,['data_batch_' num2str(i) '.mat']), fullfile(outputPath, trainDirectoryName), (i-1)*10000);
    end
    iLoadBatchAndWriteAsImages(fullfile(inputPath,'test_batch.mat'), fullfile(outputPath, testDirectoryName), 0);
end
end

function iMakeTheseDirectories(outputPath, directoryNames)
for i = 1:numel(directoryNames)
    mkdir(fullfile(outputPath, directoryNames{i}));
end
end

function iLoadBatchAndWriteAsImagesToLabelFolders(fullInputBatchPath, fullOutputDirectoryPath, labelNames, nameIndexOffset)
load(fullInputBatchPath);
data = data'; %#ok<NODEF>
data = reshape(data, 32,32,3,[]);
data = permute(data, [2 1 3 4]);
for i = 1:size(data,4)
    imwrite(data(:,:,:,i), fullfile(fullOutputDirectoryPath, labelNames{labels(i)+1}, ['image' num2str(i + nameIndexOffset) '.png']));
end
end

function iLoadBatchAndWriteAsImages(fullInputBatchPath, fullOutputDirectoryPath, nameIndexOffset)
load(fullInputBatchPath);
data = data'; %#ok<NODEF>
data = reshape(data, 32,32,3,[]);
data = permute(data, [2 1 3 4]);
for i = 1:size(data,4)
    imwrite(data(:,:,:,i), fullfile(fullOutputDirectoryPath, ['image' num2str(i + nameIndexOffset) '.png']));
end
end

function iLoadBatchAndWriteAsImages(fullInputBatchPath, fullOutputDirectoryPath, nameIndexOffset)
load(fullInputBatchPath);
data = data'; %#ok<NODEF>
data = reshape(data, 32,32,3,[]);
data = permute(data, [2 1 3 4]);
for i = 1:size(data,4)
    imwrite(data(:,:,:,i), fullfile(fullOutputDirectoryPath, ['image' num2str(i + nameIndexOffset) '.png']));
end
end

数据集预览

处理后的数据集有如下显示:
处理后数据集
利用代码随机选取20个图片其中代码见下图:

figure;                                 %打开figure界面
perm = randperm(100, 20);               %从1-100内任选20个数
for i = 1:20
    subplot(4, 5, i);                   %将figure界面分割为4*5个小格,并选中第i个小格
    imshow(image.Files{perm(i)});       %在第i个小格上显示标号为i的图片
end

显示效果有如下图所示:
图片预览

加载数据集

其中数据集的最终处理包括:

1.将尺寸为32X32X3的图像,转化为227X227X3的能被Alexnet处理的图像

2.进行数据增强

3.加载这些图像作为图像数据存储,转化为imageDatestore格式的图像格式

4.划分训练集与测试集

ImageDatastore图像格式简介

imageDatastore 根据文件夹名称自动标注图像,并将数据存储为 ImageDatastore 对象。通过图像数据存储可以存储大图像数据,包括无法放入内存的数据,并在卷积神经网络的训练过程中高效分批读取图像,这里跟pytorch中的torchvision.datasets.ImageFolder功能比较相似

转化格式

matlab为我们提供了函数用来将文件夹内的图片自动标注,并转化为imagedatastore格式,有如下代码:

image = imageDatastore('cifar10','IncludeSubfolders',true,'LabelSource','foldernames');
参数说明

1.cifar10:数据集所在文件夹路径

2.IncludeSubfolders :子文件夹包含标记,指定 true 可包含每个文件夹中的所有文件和子文件夹,默认为false

3.LabelSource :提供标签数据的源,指定了 'foldernames',将根据文件夹名称分配标签并存储在 Labels 属性中

划分数据集

利用matlab现有函数,splitEachLabelimages 数据存储拆分为两个新的数据存储,程序中划分训练集:训练集 = 3:1

[imageTrain,imageTest] = splitEachLabel(image,0.75,'randomized');

转换格式与数据增强

利用augmentedImageDatastore函数,转换图像大小,并且进行数据增强,有如下代码:

imageTrain = augmentedImageDatastore([227 227 3], imageTrain);
imageTest = augmentedImageDatastore([227 227 3], imageTest);

Alexnet迁移学习

加载预训练网络

首先需要安装 _Deep Learning Toolbox Model for AlexNet Network_,加载预训练网络

net = alexnet;

替换最终层(全连接层)

由于已加载的预训练网络 net 的最后三层是针对 1000 个类进行配置,而对于本实验是需要进行5个类的分类,所以需要调整这三个层

首先从预训练网络中提取除最后三层之外的所有层:

layersTransfer = net.Layers(1:end-3);

接着将最后三层替换为全连接层、softmax 层和分类输出层,将层迁移到新分类任务

同时我们需要根据新数据指定新的全连接层的选项。将全连接层设置为大小与新数据中的类数相同,即为5

同时要使新层中的学习速度快于迁移的层,增大全连接层的 WeightLearnRateFactorBiasLearnRateFactor

layers = net.Layers(1:end-3);
new_layers = [layers              
fullyConnectedLayer(5,'WeightLearnRateFactor',20,'BiasLearnRateFactor',20)            
    softmaxLayer             
    classificationLayer];

通过analyzeNetwork 以交互可视方式呈现网络变化前后架构以及有关网络层的详细信息:
网络前后架构

训练

对于迁移学习,保留预训练网络的较浅层中的特征(迁移的层权重)

同时要减慢迁移的层中的学习速度,将初始学习速率设置为较小的值。在上一步骤中,我们增大了全连接层的学习率因子,以加快新的最终层中的学习速度

这种学习率设置组合只会加快新层中的学习速度,对于其他层则会减慢学习速度。执行迁移学习时,所需的训练轮数相对较少

一轮训练是对整个训练数据集的一个完整训练周期,可以发现准确率随着epoch的次数的增加而明显上升

指定小批量大小和验证数据。软件在训练过程中每 ValidationFrequency 次迭代验证一次网络

ops = trainingOptions('sgdm', ...                     
    'InitialLearnRate',0.0001, ...                      
    'ValidationData',imageTest, ...                     
    'Plots','training-progress', ...                      
    'MiniBatchSize',5, ...                      
    'MaxEpochs',12,...                      
    'ValidationPatience',Inf,...                      
    'Verbose',false);

%开始训练

tic
net_train = trainNetwork(imageTrain,new_layers,ops);
toc

如下是可视化界面,准确率达到了70%以上:
训练可视化

验证

选取birdautomobile类别各一张进行验证实验:
1
2
可以发现效果良好

写在最后

在本博客中,笔者使用MATLAB实现了alexnet在CIFAR-10数据集的迁移学习,可以看出,MATLAB有关神经网络的高级API也是比较简洁和清晰的