一、使用预训练网络
1.1识别图像中的对象
您可以使用 imread
函数来导入大多数标准文件格式(GIF、JPEG、PNG 等)的图像。
I = imread('filename.png');
以上命令将给定文件中的图像读入名为 I
的 MATLAB 变量中。
您可以使用 imshow
函数来显示存储在 MATLAB 变量中的图像
imshow(I)
img1 = imread('file01.jpg');
imshow(img1)
img2 = imread('file02.jpg');
imshow(img2)
img3 = imread('file03.jpg');
imshow(img3)
2.2执行预测:执行预测
您可以使用 alexnet
函数在 MATLAB 工作区中创建预定义的深度网络“AlexNet”的副本。
net = alexnet
您可以使用 classify
函数对图像进行预测。
pred = classify(net,img)
deepnet = alexnet;
img1 = imread('file01.jpg');
imshow(img1)
pred1 = classify(deepnet,img1)
img2 = imread('file02.jpg');
imshow(img2)
pred2 = classify(deepnet,img2)
img3 = imread('file03.jpg');
imshow(img3)
pred3 = classify(deepnet,img3)
CNN 架构: CNN 中的层
检查 AlexNet 中的层
像任何 CNN 一样,AlexNet 在 MATLAB 中表示为一个层数组。需要特别关注此数组的第一个和最后一个元素,它们分别代表输入层和输出层。
在此交互练习中,您将查看这些层的属性,以确定 AlexNet 接受什么形式的图像作为输入,以及它可以返回哪些类作为输出。
变量 deepnet
表示一个深度卷积网络。您可以使用 variable.Property
索引法来引用变量的 Layers
属性,以查看网络的层:deepnet.Layers
deepnet = alexnet;
ly = deepnet.Layers
变量 ly
是由网络层构成的数组。您可以通过使用常规的 MATLAB 数组索引对 ly
进行索引来查看单个层:
layer3 = ly(3)
inlayer = ly(1)
网络的每个层都有与其类型相关的属性。输入层的一个重要属性是 InputSize
,它是网络要求的输入图像大小(维度)。
insz = inlayer.InputSize
图像维度
灰度图像在 MATLAB 中表示为 m×n 数组,每个元素的值代表对应图像像素的强度。彩色图像表示为 m×n×3 数组,三个 m×n 平面分别表示红色、绿色和蓝色强度。
AlexNet 要求输入图像的大小为 227×227×3,这意味着彩色图像的高度为 227 个像素、宽度为 227 个像素。
outlayer = ly(end)
输出层的 Classes
属性给出了训练的网络可预测的类别名称
categorynames = outlayer.Classes
研究预测
预测分数
classify
函数返回输入图像的预测类,但是,有没有办法知道网络对该分类有多少“信心”呢?在决定如何处理输出时,置信度是一个重要的考虑事项。
为了将输入归入 n 个类之一,神经网络具有一个包含 n 个神经元的输出层,每个类对应一个神经元。网络接受输入后,会为每个神经元计算一个数值。这些数值代表网络对输入可能属于每个类的概率所做的预测。
查看这些值可以让您深入了解网络的预测。
classify
函数默认输出网络打分最高的类。您可以使用 classify
的另一个输出参数来获得所有类的预测分数。
[pred,scrs] = classify(net,img)
img = imread('file01.jpg');
imshow(img)
net = alexnet;
categorynames = net.Layers(end).ClassNames;
[pred,scores] = classify(net,img);
创建预测分数的条形图。
请注意,因为有 1000 个预测分数,所创建的条形图会难以阅读。在完成此任务后,您将创建一个只呈现关注数据的条形图。
bar(scores)
仅提取有意义的分数
您刚刚创建的图显示 1000 个类(在 x 轴上)的预测分数(在 y 轴上)。您可以看到见,大多数分数非常接近 0。我们只需关注分数有意义的类即可。
highscores = scores > 0.01;
bar(scores(highscores))
使用逻辑索引和 xticklabels
函数为条形图添加对应的预测类名标签。类名的完整列表存储在变量 categorynames
中。
xticklabels(categorynames(highscores))
您目前使用的是硬编码的阈值 (0.01
) 来查看分数有意义的类。您可以尝试不同的阈值条件。直接指定不同的阈值最为简单,
不过,您还可以使用更为动态的条件,例如基于分数的分布计算阈值。尝试采用比中位分数高一个标准差的阈值。
thresh = median(scores) + std(scores);
highscores = scores > thresh;
有了分数之后,您就可以使用 MATLAB 执行所需的任何分析。请参阅文档,了解另一个使用分数进行深入分析的示例。您还可以尝试修改脚本中的第一行代码,以研究 file02.jpg
和 file03.jpg
中图像的预测分数。默认情况下,条形图中的刻度标签不会显示所有条形的标签。在脚本的任务 5 中使用以下代码可正确显示标签:
xticks(1:length(scores(highscores)))
xticklabels(categorynames(highscores))
xtickangle(60)
二、管理图像数据集合
图像数据存储: 创建数据存储
您可以使用 imageDatastore
函数在 MATLAB 中创建一个数据存储,指定文件夹名称或文件名作为输入。您可以使用通配符(例如*
)来指定多个文件。
ds = imageDatastore('foo*.png')
这将创建一个数据存储,该数据存储引用当前文件夹中名称以 foo
开头的所有 PNG 文件。
ls *.jpg
net = alexnet;
imds = imageDatastore('file*.jpg')
fname = imds.Files
您可以使用 read
、readimage
和 readall
函数从数据存储手动导入数据:read
按顺序一次导入一个图像;readimage
导入单个特定图像;readall
将所有图像导入一个元胞数组中(每个图像位于一个单独元胞中)。
I = readimage(ds,n)
这会将数据存储 ds
中的第 n 个图像导入名为 I
的数组中。
img = readimage(imds,7);
您可以在 CNN 函数(例如 classify
)中使用图像数据存储来代替单个图像。
preds = classify(net,ds)
结果将是一个预测类数组,数据存储中的每个图像对应一个预测类。
[preds,scores]= classify(net,imds)
max(scores,[],2)
准备输入图像: 调整输入图像
预处理照片
您目前使用的是预处理过的照片进行分类。这些图像的大小经过了调整,以便可以使用 AlexNet 进行分类。最初,这些图像的大小不同。
在此交互练习中,您将调整图像的大小,以便可以用预训练网络对其进行分类。
网络的输入层指定网络要求的图像大小。expectedSize = inputlayer.InputSize
img = imread('file01.jpg');
imshow(img)
sz=size(img)
net = alexnet;
inlayer = net.Layers(1)
insz = inlayer.InputSize
您可以使用 imresize
函数调整图像大小,使其与预期的输入大小相匹配。
imgresz = imresize(img,[numrows numcols]);
这会将 img
调整为 numrows
×numcols
。也就是说,imgresz
的宽度为 numcols
个像素,高度为 numrow
个像素。
img = imresize(img,[227 227]);
imshow(img)
处理数据存储中的图像: 图像预处理工作流
用数据存储预处理图像
为了用卷积神经网络对图像进行分类,每个图像需要具有由网络输入层指定的大小。
图像通常需要简单的预处理才能分类。您可以对图像逐个进行预处理,但更常见的是对整个数据集执行相同的预处理步骤。将这些步骤应用于整个数据存储会更高效。
手动预处理
单独处理每个图像可能会很耗时。如果您要使用图像数据存储,还需要将处理后的图像保存到一个文件夹中。对于大型数据集,保存文件副本可能会占用大量空间。
处理数据存储中的图像: 创建增强的图像数据存储
前面提过,您可以使用 imageDatastore
函数来创建数据存储。您可以使用通配符(例如 *
)来指定多个文件。
ds = imageDatastore('*.jpg')
ls *.jpg
net = alexnet
imds = imageDatastore('file*.jpg')
增强的图像数据存储可以对整个集合图像执行简单的预处理。要创建此数据存储,请使用 augmentedImageDatastore
函数并将网络的图像输入大小用作输入。
auds = augmentedImageDatastore([r c],imds)
auds = augmentedImageDatastore([227 227],imds)
您可以将增强的图像数据存储用作 classify
函数的输入。在对每个图像进行分类之前,将使用您创建数据存储时指定的方法对其进行预处理。
preds = classify(net,auds)
处理数据存储中的图像: 利用增强的图像数据存储进行颜色预处理
转换为 RGB 图像
灰度图像的大小为 n×m×1。许多预训练网络,如 AlexNet,要求图像大小为 p×p×3。如果您的图像是灰度图像,您可能需要将它们转换为三维数组。
ls *.jpg
net = alexnet
imds = imageDatastore('file*.jpg')
您将在此交互练习中使用灰度图像。您可以使用 montage
函数来显示所有图像。montage(imds)
montage(imds)
在创建增强的图像数据存储时,可以通过设置 ColorPreprocessing
选项将这些图像转换为三维数组。
auds = augmentedImageDatastore([n m],... imds,'ColorPreprocessing','gray2rgb')
这将复制灰度图像三次,以创建一个三维数组。如果 imgray
是表示灰度图像的矩阵,则处理后的图像将是表示彩色 (RGB) 图像的三维数组。
auds = augmentedImageDatastore([227 227],imds,'ColorPreprocessing','gray2rgb')
preds = classify(net,auds)
用子文件夹选项创建数据存储
子文件夹中的图像
在图像分类应用中,您的图像通常按类组织到不同文件夹中。在此交互练习中,您将创建一个数据存储,它引用 Flowers
文件夹,该文件夹包含 5 个子文件夹,其中每个子文件夹包含一种花卉的 3 个图像。
默认情况下,imageDatastore
只在给定文件夹内查找图像文件。您可以使用 'IncludeSubfolders'
选项在给定文件夹的子文件夹中查找图像。
ds = imageDatastore('folder',...
'IncludeSubfolders',true)
使用 AlexNet(作为变量 net
加载)对数据集中所有图像的内容进行分类。将结果存储在名为 preds
的变量中。
请注意,classify
函数会用 AlexNet 分析 15 个图像。执行可能需要几秒钟。
net = alexnet;
flwrds = imageDatastore('Flowers','IncludeSubfolders',true)
preds = classify(net,flwrds)
迁移学习
迁移学习的必备要素
迁移学习的典型工作流
要执行迁移学习,需要具备三个要素:
-
一个表示网络架构的层数组。对于迁移学习,该数组是通过修改预先存在的网络(如 AlexNet)来创建的。
-
具有已知标签的图像,用作训练数据。它通常以数据存储形式提供。
-
一个包含用于控制训练算法行为的选项的变量。
这三个要素作为输入提供给 trainNetwork
函数,该函数返回经过训练的网络。
您需要测试新训练的网络的性能。如果性能不够理想,通常您应该尝试调整一些训练选项并重新训练。
标注训练图像
在训练网络时,您需要为训练图像提供已知标签。Flowers
文件夹包含 12 个子文件夹,每个子文件夹包含一种花卉的 80 个图像。因此,文件夹的名称可用于提供训练所需的标签。
准备训练数据: (1/3) 标注图像
训练所需的标签可以存储在图像数据存储的 Labels
属性中。默认情况下,Labels
属性为空。
您可以指定 'LabelSource'
选项,让数据存储根据文件夹名称自动确定标签。
ds = imageDatastore(folder,...
'IncludeSubfolders',true,...
'LabelSource','foldernames')
load pathToImages
flwrds = imageDatastore(pathToImages,'IncludeSubfolders',true);
flowernames = flwrds.Labels
flwrds = imageDatastore(pathToImages,'IncludeSubfolders',true,'LabelSource','foldernames')
将 flwrds
的 Labels
属性提取到名为 flowernames
的变量中。
flowernames = flwrds.Labels
准备训练数据: (2/3) 拆分训练和测试数据
您可以使用 splitEachLabel
函数将数据存储中的图像分成两个单独的数据存储。
[ds1,ds2] = splitEachLabel(imds,p)
比例 p
(从 0 到 1 的值)表示 imds
中各标签对应的图像应纳入 ds1
中的比例。其余文件分配给 ds2
。
load pathToImages
flwrds = imageDatastore(pathToImages,'IncludeSubfolders',true,'LabelSource','foldernames')
[flwrTrain,flwrTest] = splitEachLabel(flwrds,0.6)
默认情况下,splitEachLabel
会保持文件有序。您可以通过添加可选的 'randomized'
标志来实现随机乱序。
[ds1,ds2] = splitEachLabel(imds,p,'randomized')
将数据存储 flwrds
拆分为两个数据存储 flwrTrain
和 flwrTest
,使每个类别中随机选择的 80% 的文件在 flwrTrain
中。
[flwrTrain,flwrTest] = splitEachLabel(flwrds,0.8,'randomized')
不平衡的训练数据
在某些应用中,属于一个类的图像数量远超另一类的图像是很常见的。例如,当尝试检测次品时,通常很容易获得许多非次品图像,而很难获得次品图像。
在这种情况下,按类成比例地划分数据将导致网络主要基于非次品图像进行训练。这可能会使训练产生偏差,导致网络“玩概率游戏”,而不是真正学习识别次品特征。
为了避免这种情况,在拆分数据时,最好使每个类的训练图像数量相同。
当 p
是从 0 到 1 的值时,它解释为比例。图像会基于标签按比例进行拆分。您还可以指定每个标签的对应文件中要分配给 ds1
的确切数量。
[ds1,ds2] = splitEachLabel(imds,n)
这可确保 ds1
中的每个标签都有 n
个图像,即使这些类别并不都包含相同数量的图像。
将数据存储 flwrds
拆分为两个数据存储 flwrTrain
和 flwrTest
,使每个类别中的 50 个文件在 flwrTrain
中。
[flwrTrain,flwrTest] = splitEachLabel(flwrds,50)
修改网络层
前面提过,前馈网络在 MATLAB 中表示为一个层数组。这便于用户对网络层进行索引和更改。
要修改现有的网络,需要先创建一个新层,
然后对表示网络的层数组进行索引,并用新创建的层覆盖选定的层。
就像 MATLAB 中的任何索引赋值一样,您可以将这些步骤合并为一行。
fullyConnectedLayer
函数使用给定数量的神经元创建一个新的全连接层。
fclayer = fullyConnectedLayer(n)
anet = alexnet;
layers = anet.Layers
fc = fullyConnectedLayer(12)
您可以使用标准数组索引来修改层数组的单个元素。
mylayers(n) = mynewlayer
layers(23) = fc
将输出层与先前的层进行匹配
当前,输出层仍然使用 AlexNet 网络的 1000 个类标签。当信息从您刚创建的新(12 类)全连接层传递到该层时,会出现问题。要解决此问题,您需要用新的空白输出层替换输出层。网络会在训练期间根据训练数据标签来确定这 12 个类。
您可以使用 classificationLayer
函数为图像分类网络创建一个新输出层。
cl = classificationLayer
您可以通过单个命令来创建新层并用新层覆盖现有层:
mylayers(n) = classificationLayer
layers(end) = classificationLayer
设置训练选项
您可以设置哪些选项来控制网络训练?您可以使用 trainingOptions
函数来查看所选训练算法的可用选项。
opts = trainingOptions('sgdm')
这会创建一个变量 opts
,其中包含“动量随机梯度下降”训练算法的默认选项。
opts = trainingOptions('sgdm')
更改学习率
通常,您首先会尝试使用大多数选项的默认值来进行训练。但是,在执行迁移学习时,您通常希望将 InitialLearnRate
设置为一个比其默认值 0.01
小的值。
学习率控制算法更改网络权重的幅度。迁移学习的目标是微调现有网络,因此与从头开始训练相比,您通常希望更改权重的幅度不大。
您可以在 trainingOptions
函数中使用名称-值对组指定任意数量的选项设置。
opts = trainingOptions('sgdm','Name',value)
创建一个包含 SGDM 优化器的默认训练算法选项的变量 opts
,但 InitialLearnRate
选项除外,该选项应设置为 0.001
。
opts = trainingOptions('sgdm','InitialLearnRate',0.001)
训练网络
什么是“小批量”?
在每次迭代中,网络都会使用训练图像的一个子集来更新权重,这个子集称为小批量。每次迭代都采用不同的小批量。如果整个训练集都被使用过了,则称为完成了一轮。
您可以在训练算法选项中设置的参数是最大轮数 (MaxEpochs
) 和小批量的大小 (MiniBatchSize
)。
请注意,训练期间报告的损失和准确度针对的是当前迭代中使用的小批量数据。
默认情况下,图像在分成小批量之前会进行一次乱序处理。您可以使用 Shuffle
选项来控制此行为。
使用 GPU
GPU(图形处理单元)可以大大加快深度学习所需的大量计算。如果计算机没有配备支持的 GPU,训练可以在 CPU 上执行,但可能需要很长时间。要真正地使用深度学习,最好在配备了具有足够处理能力的 GPU 的计算机上训练您的网络。
- 如果您安装了适当的 GPU 和 Parallel Computing Toolbox,
trainNetwork
函数可自动在 GPU 上执行训练,无需专门编码。 - 如果没有安装,训练将在计算机的 CPU 上进行。您可以体验过后再决定是否购买所需的硬件和软件。
迁移学习脚本示例
以下代码针对本章中的花卉物种示例实现迁移学习。课程示例文件中提供了该代码的脚本 trainflowers.mlx
。您可以从右上角的帮助菜单下载课程示例文件。牛津大学的 17 Category Flower Dataset 页面上提供了关于该数据集的更多信息。
请注意,如果您的计算机上没有配备支持的 GPU,运行此示例可能需要较长时间。
获取训练图像
flower_ds = imageDatastore('Flowers','IncludeSubfolders',true,'LabelSource','foldernames');
[trainImgs,testImgs] = splitEachLabel(flower_ds,0.6);
numClasses = numel(categories(flower_ds.Labels));
通过修改 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);
使用经过训练的网络对测试图像进行分类
testpreds = classify(flowernet,testImgs);
评估性能
评估训练和测试性能
评估训练的网络
变量 flowernet
包含使用 AlexNet 基于花卉物种数据执行迁移学习后所得的网络。变量 info
是包含训练信息的结构体。
此网络的性能如何?在接下来的两个交互练习中,您将对 flowernet
进行评估。
变量 info
是包含训练信息的结构体。TrainingLoss
和 TrainingAccuracy
字段包含网络在训练数据上进行每次迭代后达到的性能的记录。
绘制训练损失图,该数据存储在 info
的 TrainingLoss
字段中。
load pathToImages
load trainedFlowerNetwork flowernet info
plot(info.TrainingLoss)
dsflowers = imageDatastore(pathToImages,'IncludeSubfolders',true,'LabelSource','foldernames');
[trainImgs,testImgs] = splitEachLabel(dsflowers,0.98);
网络在训练数据上似乎性能良好,但真正的考验要看它在测试数据上的表现。
flwrPreds = classify(flowernet,testImgs)
评估性能: 研究测试性能
通过将预测分类与已知分类进行比较,您可以确定网络对多少测试图像进行了正确分类。已知分类存储在数据存储的 Labels
属性中。
load pathToImages.mat
pathToImages
flwrds = imageDatastore(pathToImages,'IncludeSubfolders',true,'LabelSource','foldernames');
[trainImgs,testImgs] = splitEachLabel(flwrds,0.98);
load trainedFlowerNetwork flwrPreds
flwrActual = testImgs.Labels;
您可以使用逻辑比较和 nnz
函数来确定两个数组的匹配元素数:
numequal = nnz(a == b)
numCorrect = nnz(flwrPreds == flwrActual)
通过将 numCorrect
除以测试图像的数量,计算正确分类的测试图像的比例。将结果存储在名为 fracCorrect
的变量中。
fracCorrect = numCorrect/numel(flwrPreds)
按类分析性能
损失和准确度给出的是网络性能的总体度量。进一步地研究网络对于不同类的图像的性能表现可发掘更多信息。误分类是随机分布的,还是有网络难以区分的特定类?有没有网络很容易混淆的类?
confusionchart
函数计算并显示预测分类的 confusion matrix。
confusionchart(knownclass,predictedclass)
混淆矩阵的 (j,k) 元素值用于统计网络将 j 类中的多少个图像预测为属于类 k。因此,对角线上的元素代表正确分类;非对角线上的元素代表误分类。
显示花卉测试数据的混淆矩阵。预测的分类数据存储在分类数组 flwrPreds
中。已知的分类数据存储在数据存储 testImgs
的 Labels
属性中。
confusionchart(testImgs.Labels,flwrPreds)
迁移学习函数摘要
创建网络
函数 | 说明 |
---|---|
alexnet | 加载预训练网络“AlexNet” |
支持的网络 | 查看可用的预训练网络列表 |
fullyConnectedLayer | 创建新的全连接网络层 |
classificationLayer | 为分类网络创建新输出层 |
获取训练图像
函数 | 说明 |
---|---|
imageDatastore | 创建引用图像文件的数据存储 |
augmentedImageDatastore | 预处理图像文件集合 |
splitEachLabel | 将数据存储划分为多个数据存储 |
设置训练算法选项
函数 | 说明 |
---|---|
trainingOptions | 创建包含训练算法选项的变量 |
执行训练
函数 | 说明 |
---|---|
trainNetwork | 执行训练 |
使用经过训练的网络执行分类
函数 | 说明 |
---|---|
classify | 获取经过训练的网络对输入图像的分类 |
评估经过训练的网络
函数 | 说明 |
---|---|
nnz | 统计数组中的非零元素 |
confusionchart | 计算混淆矩阵 |
heatmap | 将混淆矩阵可视化为热图 |
项目 - 蛔虫活力
概览
在此项目中,您将使用预训练卷积神经网络对蛔虫进行分类,将其归为活蛔虫或死蛔虫。
您将使用显微镜载玻片的图像。
有些显微镜载玻片同时包含圆弧状蛔虫和僵直状蛔虫。这种情况下,会以占比高的类型对这些载玻片进行标注。您可以查看 WormData.csv
文件,了解为每个载玻片指定的标签。
数据集和脚本
项目文件包括图像、标签和参考脚本。
训练时间
如果您有支持的 GPU,则不到一分钟内即可完成对您的网络的训练。您也可以在 CPU 上训练您的网络,但可能需要十分钟才能完成。
更多图像的应用