目录
1. 简介
2. 使用预训练网络:使用已创建和训练后的网络进行分类
2.1 课程示例-识别一些图像中的对象
2.1.1 导入图像
2.1.2 显示导入的图像。
2.1.3 存储图像
2.2 执行预测
2.2.1 创建预定义的深度网络“AlexNet”的副本
2.2.2 分类预测
2.2.3 img2和3的分类预测
2.3 CNN架构 查看网络层
2.3.1 查看网络的层
2.3.2 索引查看单个层
2.3.3 提取某一层的某种属性
2.3.4 图像维度
2.3.5 提取输出层的类属性
2.4 研究预测
2.4.1 预测分数
2.4.2 创建预测分类的条形图
2.4.3 挑出主要分数
2.4.4 显示挑选预测分数后的条形图
2.4.5 为x轴增加类别标签
3 管理数据集:使用已创建和训练后的网络执行分类
3.1 图像数据存储
3.1.1 使用imageDatastore创建数据存储(DataStore)
3.1.2 数据存储的属性包含有关数据文件的元信息
3.1.3 从数据存储(DataStore)中导入某幅图像
3.1.4 预测数据存储(DataStore)中的所有图像
3.2 准备输入图像
3.2.1 查看图像大小
3.2.2 网络的输入层指定网络要求的图像大小
3.2.3 将输入图像调整至要求大小并显示
3.3 处理数据存储(DataStore)中的图像
3.3.1 创建增强的图像数据存储(DataStore)
3.3.2 使用增强的图像数据存储(DataStore)进行颜色预处理
3.4 用子文件夹创建一个数据存储(DataStore)
3.4.1 创建数据存储(Data Store)
3.4.2 分类预测
4 迁移学习:修改预训练网络对图像分类
4.1 什么是迁移学习?
4.2 迁移学习所需的组件
4.3 准备训练数据
4.3.1 标记图像
4.3.2 拆分训练和测试
4.4 修改网络层
4.4.1 使用fullyConnectedLayer函数创建一个新的全连接层
4.4.2 对层的数组通过索引进行修改
4.4.3 将输出层与前面的层(第23层)进行匹配
4.5 设置训练选项
4.5.1 使用trainingOptions来查看所选训练算法的可用选项(一定要查看帮助手册)
4.5.2 设置初始学习率
4.6 训练网络
4.6.1 mini-batch小批量
4.6.2 使用GPUs
4.6.3 迁移学习示例(脚本)
4.7 评估性能
4.7.1 评估训练和测试性能
4.7.2 调查测试性能
4.8 迁移学习摘要
该课程为MATLAB官网免费课程,此笔记只对该课程进行一定的翻译和记录,方便日后复习。官网链接为https://ww2.mathworks.cn/learn/tutorials/deep-learning-onramp.html(深度学习入门之旅)。
在本节课程中,您将学习如何使用预先构建的深度神经网络对下面 12 个图像的内容进行分类。理想的分类应为 'beach'、'cat'、'cupcakes'、'lake'、'fish' 等。
在此交互练习中,您会将部分图像文件导入到 MATLAB 中并查看它们。
将文件 file01.jpg
中的图像导入 MATLAB 工作区中。将其存储在名为 img1
的变量中。
I = imread('filename.png')
imshow(I)
前两项任务将导入并显示 file01.jpg
中的图像。在脚本中添加命令,以导入并显示 file02.jpg
和 file03.jpg
文件中的图像。将图像分别存储在名为 img2
和 img3
的变量中。
img2 = imread('file02.jpg');
imshow(img2)
img3 = imread('file03.jpg');
imshow(img3)
使用 alexnet
函数加载一个预训练网络。将结果保存为一个名为 deepnet
的变量。
deepnet = alexnet % 这里的网络模型使用Alexnet;可以通过matlab官方下载Alexnet安装包,安装后可用(APP----安装APP)
用 classify
函数和预训练的 AlexNet 网络来预测变量 img1
中存储的图像的主题类别。将网络的预测存储在名为 pred1
的变量中。 pred1 = classify(deepnet,img1)
也可用 [pred, score] = classify(net,img) % 将网络和输入图像传入分类函数,将得到输入图像的类别preds以及它在每一类中的预测分数score
在脚本中添加命令,对变量 img2
和 img3
中存储的图像的内容进行预测。将预测的类分别存储在名为 pred2
和 pred3
的变量中。
pred2 = classify(deepnet,img2)
pred3 = classify(deepnet,img3)
2.2.4 获取预训练网络
MATLAB将CNN表示为一个层的数组,数组的第一个元素为输入层,最后一个元素为输出层。
将 deepnet
的 Layers
属性提取到名为 ly
的变量中。
ly = deepnet.Layers % 由下图可以看出Alexnet是一个具有25层的数组
将网络的第一层(输入层)提取到名为 inlayer
的变量中。
inlayer = ly(1)
提取网络第一层的 InputSize
属性(存储在变量 inlayer
中)。将结果存储在名为 insz
的变量中。
insz = inlayer.InputSize % 输入层的输入图像大小
输入图像是彩色图像,尺寸为:227×227×3。灰度图像的尺寸为227×227。
将网络的最后一层(输出层)提取到名为 outlayer
的变量中。
outlayer = ly(end)
提取网络最后一层的 Classes
属性(存储在变量 outlayer
中)。将结果存储在名为 categorynames
的变量中。
categorynames = outlayer.Classes % 得到一个列向量,表示Alexnet的1000个类的名字
使用 classify
函数和预训练的 AlexNet 网络 net
来预测变量 img
中存储的图像的主题类别。将网络的预测存储在名为 pred
的变量中,并将所有预测分数存储在名为 scores
的变量中。上面的分类函数classify只是将输入图像预测为某一类,并不知道输入图像是该类的概率。该任务不仅会得到预测分类,还会得出预测分数。
前期输入:
img = imread('file01.jpg');
imshow(img)
net = alexnet;
categorynames = net.Layers(end).ClassNames;
分数预测:
[pred,scores] = classify(net,img) % 得到的分数是一个行向量,表示1000个类的预测分数
创建预测分数的条形图。
bar(scores)%因为有 1000 个预测分数,所创建的条形图会难以阅读。
创建一个逻辑数组 highscores
,其中的值 1 (true
) 对应于 scores
大于 0.01
的情况。
highscores = (scores > 0.01) 或者 highscores = scores > 0.01 % 将scrs数组中大于0.01的元素赋为1,其他为0
使用逻辑索引创建高于阈值 0.01
的预测值的条形图。
bar(scores(highscores)) %scores(highscores)返回大于0.01的类别的预测分数
使用逻辑索引和 xticklabels
函数为条形图添加对应的预测类名标签。类名的完整列表存储在变量 categorynames
中。
xticklabels(categorynames(highscores)) % categorynames(highscores) 返回大于0.01的类别的名字
ls *.jpg
net = alexnet;
创建一个名为 imds
的数据存储,该数据存储引用当前文件夹中文件名从 file01.jpg
到 file12.jpg
的图像文件。(请注意,在此文件夹中,只有这些图像文件的名称为 filenn.jpg
形式。)
imds = imageDatastore('*.jpg') % 使用通配符*指定多个文件。
使用数据存储 imds
的 Files
属性提取图像的文件名。将结果存储在名为 fname
的变量中。
fname = imds.Files
使用 readimage
函数导入 file07.jpg
中的图像(数据存储中的第 7 个文件)。将导入的图像存储在名为 img
的变量中。
img = readimage(imds,7)
使用函数:read, readimage, readall可以手动地从数据存储(DataStore)中导入图像
for i = 1:3
img = read(imds) % 按顺序依次读取图像,每次仅返回一张图像
end
img = readimage(imds, 7) % 读取并返回数据存储(DataStore)中的某张(第7张)图像
img = readall(imds) % 一次性读取数据存储(DataStore)中的所有图像,返回的并不是图像数据
使用 AlexNet(作为变量 net
加载)对数据集中所有图像的内容进行分类。将结果存储在名为 preds
的变量中。
请注意,classify
函数会用 AlexNet 分析 12 个图像。执行可能需要几秒钟。
preds = classify(net, imds)
由于不同的网络对输入图像的要求不同,所以需要对输入图像进行预处理后再输入网络。
img = imread('file01.jpg');
imshow(img)
使用 size
函数查看图像 img
的大小。将结果保存到变量 sz
中。
sz = size(img) % 返回图像的高度、宽度、通道数
导入 AlexNet。提取网络的第一层的 InputSize
属性。将结果存储在名为 insz
的变量中
net = alexnet
insz = net.Layers(1).InputSize
使用 imresize
函数将变量 img
中存储的图像的大小调整为 227×227。将结果存储回变量 img
中。
然后用 imshow
显示调整大小后的图像。
img = imresize(img,[227 227]) %【行数(高度),列数(宽度)】
imshow(img)
上面是对单个图像进行预处理,但平常的数据集中的图像数量相当之大。因此,直接对整个图像数据集进行处理就显得很有必要了。
ls *.jpg
net = alexnet
(1)创建一个名为 imds
的图像数据存储,该数据存储引用当前文件夹中扩展名为 .jpg
的图像文件。
imds = imageDatastore('*.jpg')
(2)增强的图像数据存储对整个集合图像预处理
从 imds
创建一个增强的图像数据存储,它会将图像大小调整为 227×227。将新数据存储命名为 auds
。
auds = augmentedImageDatastore([227 227], imds) % 将数据存储(DataStore)中所有图像的大小统一调整到[227 227]大小 无逗号隔开
(3)增强的图像数据存储对整个集合图像分类
使用 classify
函数和在变量 net
中存储的网络,对 auds
中的图像进行分类。将预测存储在变量 preds
中。
preds = classify(net,auds)
ls *.jpg
net = alexnet
imds = imageDatastore('file*.jpg')
(1)使用函数 montage
显示数据存储 imds
中的图像。蒙太奇(montage)
montage(imds) % 使用蒙太奇手法显示数据存储(DataStore)中的所有图像
(2)创建增强图像数据存储(DataStore)
基于图像数据存储 imds
创建一个增强的图像数据存储。将图像预处理为 227×227×3。
将增强的图像数据存储命名为 auds
auds = augmentedImageDatastore([227 227],imds,'ColorPreprocessing','gray2rgb')
这里做的增强除了调整大小之外,还有灰色图像转换为彩色图像
(3)对预处理后的图像数据存储(DataStore)进行分类
使用 classify
函数对 auds
中的图像进行分类。AlexNet 存储在变量 net
中。将预测存储在变量 preds
中。
preds = classify(net, auds)
注意:图像必须满足网络的图像输入层指定的大小。图像大小正确无误后,您就可以使用预训练网络对数据进行分类!
当您训练网络时,还可以使用其他方法,例如图像增强。查看文档,了解您还可以使用增强的图像数据存储做些什么:
预处理图像以进行深度学习
将每一类的图像放在一个子文件夹下,图像已经按照这种格式放置,而非按照这种方式去创建。
net = alexnet;
创建一个数据存储 flwrds
,它引用 Flowers
文件夹的子文件夹中的所有图像。
flwrds = imageDatastore('Flowers','IncludeSubfolders',true) % 'Flowers'为图像数据集的路径,使用“IncludeSubfolders”选项在给定文件夹的子文件夹中查找图像。
使用 AlexNet(作为变量 net
加载)对数据集中所有图像的内容进行分类。将结果存储在名为 preds
的变量中。
请注意,classify
函数会用 AlexNet 分析 15 个图像。执行可能需要几秒钟。
preds = classify(net,flwrds)
像 AlexNet 这样的预训练网络虽然非常容易上手,但是您不能灵活地控制网络的工作方式,可能无法有效解决您的确切问题。
您可以从网络架构和随机权重开始,自行构建和训练网络。不过,要取得理想的结果需要付出很多努力:(1) 具备网络架构的知识和经验,(2) 拥有大量的训练数据,以及 (3) 耗费大量的计算时间。
通过迁移学习可以有效解决许多问题。其训练需要一些数据和一定的计算时间,但与从头开始训练相比要少得多,而且您可以获得适合解决您具体问题的网络。
oad pathToImages % 加载数据集文件pathToImages.mat
flwrds = imageDatastore(pathToImages,'IncludeSubfolders', true);
flowernames = flwrds.Labels
(1)重新创建数据存储 flwrds
,其中包含变量 pathToImages
中所存储文件夹路径的子文件夹中的所有图像,这次使用文件夹名称作为图像标签。工作区中已有 pathToImages
。
flwrds = imageDatastore(pathToImages,'IncludeSubfolders',true,'LabelSource','foldernames') %训练所需的标签可以存储在图像数据存储库的标签属性中。默认情况下,标签属性为空。通过指定“LabelSource”选项,可以让数据存储自动确定文件夹名称中的标签。如果指定了 'foldernames',将根据文件夹名称分配标签并存储在 Labels 属性中。您以后可以通过直接访问 Labels 属性来修改标签。
(2)将 flwrds
的 Labels
属性提取到名为 flowernames
的变量中。
flowernames = flwrds.Labels
(1)将数据存储 flwrds
拆分为两个数据存储 flwrTrain
和 flwrTest
,使每个类别中 60% 的文件划分到 flwrTrain
中。
[flwrTrain,flwrTest] = splitEachLabel(flwrds,0.6)
[ds1,ds2] = splitEachLabel(imds,p) % flwrds是创建的数据存储,0.6是比例,返回的flwrTrain是imds的p倍,剩下的imds分配给ds2,即ds2是imds的(1-p)倍
(2)随机拆分数据;将数据存储 flwrds
拆分为两个数据存储 flwrTrain
和 flwrTest
,使每个类别中随机选择的 80% 的文件在 flwrTrain
中。
[flwrTrain,flwrTest] = splitEachLabel(flwrds,0.8,'randomized')
(3)不平衡的训练数据
在某些应用中,属于一个类的图像数量远超另一类的图像是很常见的。例如,当尝试检测次品时,通常很容易获得许多非次品图像,而很难获得次品图像。
在这种情况下,按类成比例地划分数据将导致网络主要基于非次品图像进行训练。这可能会使训练产生偏差,导致网络“玩概率游戏”,而不是真正学习识别次品特征。
将数据存储 flwrds
拆分为两个数据存储 flwrTrain
和 flwrTest
,使每个类别中的 50 个文件在 flwrTrain
中。
[flwrTrain,flwrTest] = splitEachLabel(flwrds,50)
前面提过,前馈网络在 MATLAB 中表示为一个层数组。这便于用户对网络层进行索引和更改。
anet = alexnet;
layers = anet.Layers
使用 12 个神经元(针对 12 种花)创建一个新的全连接层 fc
。
fc = fullyConnectedLayer(n) % n是新建全连接层的神经元个数
用您刚刚创建的新层 fc
替换数组 layers
表示的网络的最后一个全连接层(第 23 个层)。
layers(23) = fc % 将Alexnet的第23层修改为上述新建的全连接层
将输出层与先前的层进行匹配。。。当前,输出层仍然使用 AlexNet 网络的 1000 个类标签。当信息从您刚创建的新(12 类)全连接层传递到该层时,会出现问题。要解决此问题,您需要用新的空白输出层替换输出层。网络会在训练期间根据训练数据标签来确定这 12 个类。
用新的分类层替换数组 layers
表示的网络的最后一层(输出层)。
layers(end) = classificationLayer % 使用classificationLayer函数新建一个分类层,并赋给最后一层(输出层),该函数将自动确定输出层的神经元个数
创建一个包含 SGDM 优化器的默认训练算法的变量 opts
。可以省略行尾的分号以显示结果。
opts = trainingOptions('sgdm')
解释:opts = trainingOptions(solverName) % 返回一个由solverName指定的优化器的选项,solverName默认为'sgdm'
还可以设置
options = trainingOptions('sgdm', ...
'LearnRateSchedule','piecewise', ... % 学习率减少方案
'LearnRateDropFactor',0.2, ...
'LearnRateDropPeriod',5, ... % 每5个epoch减少0.2倍的学习率
'MaxEpochs',20, ... % 最多迭代的epoch数
'MiniBatchSize',64, ... % 每个epoch迭代的batchsize
'Plots','training-progress')
'Plots'设置为'training-progress'时有下图
通常,您首先会尝试使用大多数选项的默认值来进行训练。但是,在执行迁移学习时,您通常希望将 InitialLearnRate
设置为一个比其默认值 0.01
小的值。
opts = trainingOptions('sgdm','InitialLearnRate',0.001)
解释:opts = trainingOptions('sgdm','Name', value) % 指定训练选项的名称'Name'和值value(相当于字典的键值对)
在每次迭代中,网络都会使用训练图像的一个子集来更新权重,这个子集称为小批量。每次迭代都采用不同的小批量。如果整个训练集都被使用过了,则称为完成了一轮,这样的一个周期称为epoch。训练多少个周期可以通过设置'MaxEpochs'来实现。
您可以在训练算法选项中设置的参数是最大轮数 (MaxEpochs
) 和小批量的大小 (MiniBatchSize
)。
请注意,训练期间报告的损失和准确度针对的是当前迭代中使用的小批量数据。
默认情况下,图像在分成小批量之前会进行一次乱序处理。您可以使用 Shuffle
选项来控制此行为。
值得注意的是,损失和准确率是针对当前的mini-batch而言的,而非整个训练集的平均
默认在划分mini-batch之前,通常会对数据集进行洗牌,你可以通过shuffle选项来设置。
GPU可以显著提高深度学习的计算性能。如果你的计算机不支持GPU的话,MATLAB将会在CPU上执行训练过程,不过这会花费大量的时间。
获取训练图像
flower_ds = imageDatastore('Flowers','IncludeSubfolders',true,'LabelSource','foldernames');
[trainImgs,testImgs] = splitEachLabel(flower_ds,0.6);
numClasses = numel(categories(flower_ds.Labels)); % 这句话是前面没有的,目的是取类别数。numel表示取数组的元素数目,categories返回一个字符向量元胞数组,其中包含分类数组的类别。
创建一个修改后的Alexnet
net = alexnet;
layers = net.Layers;
layers(end-2) = fullyConnectedLayer(numClasses);
layers(end) = classificationLayer;
设置训练算法选项
options = trainingOptions('sgdm','InitialLearnRate', 0.001);
执行训练
[flowernet,info] = trainNetwork(trainImgs, layers, options); % flowernet是训练后的网络(具有新的参数),info是训练信息(包含训练损失、准确率等信息)
使用训练网络去分类测试图像
testpreds = classify(flowernet,testImgs);
(1)画出训练损失(位于info的TrainingLoss中)
load pathToImages
load trainedFlowerNetwork flowernet info
plot(info.TrainingLoss)
(2)分类图像
dsflowers =imageDatastore(pathToImages,'IncludeSubfolders',true,'LabelSource', 'foldernames');
[trainImgs,testImgs] = splitEachLabel(dsflowers,0.98);
flwrPreds = classify(flowernet,testImgs)
(1)提取测试数据集的标签
load pathToImages.mat
pathToImages
flwrds = imageDatastore(pathToImages,'IncludeSubfolders',true,'LabelSource', 'foldernames');
[trainImgs,testImgs] = splitEachLabel(flwrds,0.98);
load trainedFlowerNetwork flwrPreds
flwrActual = testImgs.Labels % trainImgs,testImgs是对数据存储进行划分得到的,所以它们具有数据存储的相关属性
(2)计算正确个数
numCorrect = nnz(flwrActual == flwrPreds) % nnz函数返回的是矩阵非零元素的数目,flwrActual == flwrPreds将得到一个0-1矩阵
(3)计算正确率
fracCorrect = numCorrect/numel(flwrPreds) % 就是正确个数除以测试数据的个数
上面的计算有些麻烦,先改写为:
mean(flwrActual == flwrPreds)
(4)显示预测分类的混淆矩阵
混淆矩阵又称误差矩阵,矩阵中的元素(j,k)表示第j个类被预测为第k个类的数目,主对角线的元素表示正确预测,其余元素表示误分类。
confusionchart(knownclass,predictedclass) % 第一个参数为已知类,即flwrActual;第二个参数为预测类,即flwrPreds
迁移学习函数摘要
函数 | 说明 |
---|---|
alexnet | 加载预训练网络“AlexNet” |
支持的网络 | 查看可用的预训练网络列表 |
fullyConnectedLayer | 创建新的全连接网络层 |
classificationLayer | 为分类网络创建新输出层 |
函数 | 说明 |
---|---|
imageDatastore | 创建引用图像文件的数据存储 |
augmentedImageDatastore | 预处理图像文件集合 |
splitEachLabel | 将数据存储划分为多个数据存储 |
函数 | 说明 |
---|---|
trainingOptions | 创建包含训练算法选项的变量 |
函数 | 说明 |
---|---|
trainNetwork | 执行训练 |
函数 | 说明 |
---|---|
classify | 获取经过训练的网络对输入图像的分类 |
函数 | 说明 |
---|---|
nnz | 统计数组中的非零元素 |
confusionchart | 计算混淆矩阵 |
heatmap | 将混淆矩阵可视化为热图 |