前言
在MATLAB中,训练深度学习模型时,数据库的构建与输入是关键十分关键的一环,真对不同的数据类型和训练样本,正确的数据构建是训练代码跑通的基本前提。
本文章主要基于matlab官方文档内容和实际应用问题、技巧进行的总结。
基础概念-张量维度的类型:
首先是模型输入的数据类型,MATLAB的深度学习构建框架支持"single", "double", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "logical", "char",12种类型的数据类型的输入。但通常会被输入层进行转化和归一化,在显卡中以single的数据类型进行推理和传播。
而需要注意的是,在MATLAB中深度学习传递的张量是具有严格定义的,其常见的张量维度的定义如下:
|------|-----------------|---------------------------------------------------------------------|
| 数据简写 | 全称 | 规则特性 |
| S | Spatial 空间维度 | |
| C | Channel 通道维度 | 对应深度学习模型中的特征维度,因此是大多数模型必带的维度,即使维度长度为1。需要注意的是,通道维度卷积过程中的计算规则与空间维度不同。 |
| B | Batch 批次维度 | 样本维度,可变的,数值可以设置成"NaN" |
| T | Time 时相维度 | 循环神经网络中递推维度,无法直接参与卷积和自注意力机质的计算。 |
| U | Unspecified 未定义 | 自定义或需自动识别的维度。 |
所以即使是两组一模一样的4-D 张量,定义不同,区别也天差地别,以下是常见数据类型的张量维度定义参考。
- 1024波段的波谱曲线:S(1024)-C(1)
- 1024*1024分辨率的RGB图像:S(1024)*S(1024)*C(3)
- 1024波段的1024*1024分辨率的高光谱图像:S(1024)*S(1024)*C(1024)
- 1000个时间记录点的声波(含2000个频率点的强度数据)信息:C(2000)-T(1000)
- 一共600帧的一个1000*1000分辨率的RGB视频:S(1000)*S(1000)*C(3)*T(600)
而批次维度"B"则主要用于适应训练和视频预测的显存瓶颈,如对于小显存的显卡,图像训练的批次量为32。
S(1024)*S(1024)*C(3)*B(32)
大显存,批次量为128时:
S(1024)*S(1024)*C(3)*B(128)
注意:对于拥有时相的数据:
32个批次,1000个时间记录点的声波(含2000个频率点的强度数据)信息:C(2000)-B(32)-T(1000),B不是最后一个维度,而T是。这是考虑到计算的方便性设计的。
MATLAB的DataStore对象
DataStore对象是Matlab深度学习训练的基石。其包含如下10种常见的对象,其中ArrayDatastore、ImageDatastore、CombinedDatastore以及FileDatastore是常用的数据储存对象。本文主要根据常用的类型进行介绍和讲解。
|--------------------------------------|------------------------------|
| ArraryDatastore | 矩阵数据存储-内存直读 |
| TabularTextDatastore | 表格文本文件数据存储 |
| ImageDatastore | 图像数据存储 |
| SpreadsheetDatastore | 用于电子表格文件的数据存储 |
| KeyValueDatastore | 用于 mapreduce
的键-值对组数据的数据存储 |
| TallDatastore | 用于存放 tall
数组的检查点的数据存储 |
| DatabaseDatastore (Database Toolbox) | 数据库中数据的数据存储,支持SQL |
| CombinedDatastore | 组合数据库 |
| ParquetDatastore | 用于 Parquet 文件集合的数据存储 |
| fileDatastore | 具有自定义文件读取器的数据存储 |
直接从内存中引入数据-arrayDatastore:
arrayDatastore-使用介绍
ArrayDatastore 是利用小型数据训练深度学习的关键函数,其基于内存中数据创建的数据存储。使用 arrayDatastore 函数创建 ArrayDatastore 对象。
arrds = arrayDatastore(A,Name,Value)
输入参数
A --- 输入数组、张量
输入数组,指定为矩阵。ArrayDatastore 属性描述数据存储对象中内存数据的格式,并控制如何从数据存储中读取数据。
--------------------------------------------参数介绍 Name-Value---------------------------------------------
ReadSize --- 要读取的数据量1 (默认) | 正整数
在调用 read 函数时要读取的数据量,指定为由 'ReadSize' 和正整数组成的以逗号分隔的对组。每次调用 read 最多读取 ReadSize 行。如果为 'ReadSize' 指定的值超过输入数据的行数,则 read 将读取数据存储对象中的所有行。
'ReadSize' 的默认值是 1。
数据类型: double
IterationDimension --- 要在其中读取数据的维度
1 (默认) | 正整数
在调用 read 函数时要在其中读取数据的维度,指定为由 'IterationDimension' 和正整数组成的以逗号分隔的对组。例如,'IterationDimension',2 使 read 从数据存储对象返回列向数据。'IterationDimension' 的默认值为 1,这使得 read 返回行向数据。
如果将 'OutputType' 属性的值指定为 'same',则 'IterationDimension' 必须设置为值 1。
如果在创建 ArrayDatastore 对象后修改 'IterationDimension' 的值,则数据存储会重置为未读状态。
数据类型: double
OutputType --- 输出数据类型
'cell' (默认) | 'same'
输出数据类型,指定为以逗号分隔的对组,该对组由 'OutputType' 和下列值之一组成:
'cell' - 以 n×1 元胞数组形式返回数据。例如,如果 A 是数值数组,ReadSize 是 3,则 read 返回由数值数据组成的 3×1 元胞数组。Cell是较为通用的输出格式,支持字符、数值、布尔类型的输出。
'same' - 返回与输入数组 A 相同的数据类型。例如,如果 A 是数值数组,则 read 返回数值数组。
OutputType 的值决定 preview、read 和 readall 函数返回的数据类型。
如果在创建 ArrayDatastore 对象后修改 'OutputType' 的值,数据会存储重置为未读状态。
数据类型: char | string
arrayData对象
利用arrayDatastore函数建立ArrayData对象,可直接输入至配合的深度学习网络进行训练,程序会将维度按S-C-B-T的先后顺序自动排序。如将1个4-D矩阵转化为arrayData中(其中第4个维度是样本维度-也就是批次维度),再arryData 输入深度学习网络A(S-S-C-B)和深度学习网络B(S-C-B-T)是完全不同的,在深度学习网络B中,第四个维度是时序维度,不是样本维度,会导致数据匹配错误。这一问题在RNN的训练中需要重点注意,如需进行匹配,要将数据的第三维度与第四维度进行翻转,才能对样本维度进行匹配。读者可以参考以下两个实战案例进行深入理解:
RNN之:LSTM 长短期记忆模型-结构-理论详解-及实战(Matlab向)-CSDN博客
利用Bi-LSTM实现基于光谱数据对数值进行预测-实战示例(Matlab)_bi-lstm预测-CSDN博客
ArrayDatastore是典型的MATLAB的Datastore对象,支持的各类Datastore函数如下所示,利用对象函数,可以实现训练过程中对数据库的细节控制,从而实现较为复杂的模型训练方法。典型的如生成对抗网络,读者可以参考下列文章进行深入理解:
MATLAB代码解析:利用DCGAN实现图像数据的生成 全网最细&DCGAN设计-训练入门(源码分享)_projectandreshapelayer-CSDN博客
MATLAB实战 利用1D-DCGAN生成光谱或信号数据-CSDN博客
|---------------------------------------------------------------------------------------------------------------------------------|---------------------|
| hasdata | 确定是否有数据可读取 |
| numpartitions | 数据存储分区数 |
| partition | 划分数据存储 |
| preview | 预览数据存储中的数据子集 |
| read | 读取数据存储中的数据 |
| readall | 读取数据存储中的所有数据 |
| reset | 将数据存储重置为初始状态 |
| transform | 变换数据存储 |
| combine | 合并来自多个数据存储的数据 |
| shuffle | 对数据存储中的所有数据进行乱序处理 |
| subset | 创建数据存储或 FileSet 的子集 |
[对象函数]
常规图像数据-imageDatastore:
imageDatastore的使用介绍
imds = imageDatastore(location,Name,Value)
location-包含数据的文件夹
总文件夹路径,其可以包含多级子文件,子文件夹中再包含图像数据。
数据类型: char | cell | string
--------------------------------------------参数介绍 Name-Value---------------------------------------------
IncludeSubfolders --- 子文件夹包含标记false (默认) | true
子文件夹包含标志,指定为由 "IncludeSubfolders" 和 true 或 false 组成的名称-值参量。指定 true 可包含每个文件夹中的所有文件和子文件夹,指定 false 则仅包含每个文件夹中的文件。
如果不指定 "IncludeSubfolders",则默认值为 false。
示例: "IncludeSubfolders",true
数据类型: logical | double
FileExtensions --- 图像文件扩展名
字符向量 | 字符向量元胞数组 | 字符串标量 | 字符串数组
图像文件扩展名,指定为以逗号分隔的对组,其中包含 "FileExtensions" 和一个字符向量、字符向量元胞数组、字符串标量或字符串数组。指定的扩展名不要求采用 imformats 格式,您可以使用空引号 "" 来表示不带扩展名的文件。如果未指定 "FileExtensions",imageDatastore 将自动包含指定路径中具有 imformats(imformats - 管理图像文件格式注册表 - MATLAB) 扩展名的所有图像。如果要包含 imformats 无法识别的扩展名,请指定所有扩展名。
数据类型: char | cell | string
AlternateFileSystemRoots --- 备用文件系统根路径
字符串向量 | 元胞数组
备用文件系统根路径,以名称-值参量形式指定,其中包含 "AlternateFileSystemRoots" 和一个字符串向量或元胞数组。用于利用云盘的数据进行训练,普通项目一般不用。
数据类型: string | cell
LabelSource --- 提供标签数据的源
"none" (默认) | "foldernames"
提供标签数据的源,指定为由 "LabelSource" 和 "none" 或 "foldernames" 组成的名称-值参量。如果指定了 "none",则 Labels 属性为空。如果指定了 "foldernames",将根据文件夹名称分配标签并存储在 Labels 属性中。。
数据类型: char | string
ImageDatastore对象
Matlab的 ImageDatastore
对象是一种不需要一次性引入全部图像数据的训练数据管理。使用 imageDatastore
函数创建 ImageDatastore
对象,指定其属性,然后使用对象函数导入和处理数据。其引入的数据格式为S-S-C,一般服务于图像数据或是高光谱数据。其支持的函数如下所示:
|------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
| countEachLabel | 对 ImageDatastore 标签中的文件进行计数 |
| hasdata | 确定是否有数据可读取 |
| numpartitions | 数据存储分区数 |
| partition | 划分数据存储 |
| preview | 预览数据存储中的数据子集 |
| read | 读取数据存储中的数据 |
| readall | 读取数据存储中的所有数据 |
| readimage | 从数据存储读取指定的图像 |
| writeall | 将数据存储写入文件 |
| reset | 将数据存储重置为初始状态 |
| shuffle | 对数据存储中的所有数据进行乱序处理 |
| splitEachLabel | 按比例拆分 ImageDatastore 标签 |
| subset | 创建数据存储或 FileSet 的子集 |
| transform | 变换数据存储 |
| combine | 合并来自多个数据存储的数据 |
| isPartitionable | 确定数据存储是否可分区 |
| isSubsettable | 确定数据存储是否可子集 |
| isShuffleable | 确定数据存储是否可乱序 |
利用imageDatastore引入一维张量数据
可以取巧地利用imageDatastore来引入一维波谱数据S-C,首先将一维波谱数据另存为图像,一般以映射后的uint16或是uint32格式来保存,避免数据精度的损失;如1000长度的波谱数据保存后的图像为S(1000)-S(1)-C(1)。再应用imageDatastore进行引入,结合深度学习算法进行训练,但一些模型组件也要进行替换,如用2D卷积核代替1D卷积核。
但总体而言,属于下下策。1是会有数据精度损失。2是这种取巧行为不会使模型性能有显著提升,浪费精力。
通用大型数据引入-datastore
datastore函数可以引入matlab支持的大多数数据。但其需要提供更多的输入参数来规范输入内容,具体可以参考官方文档。
ds = datastore(location,Name,Value)
多源输入的数据引入-CombinedDatastore
combine函数的应用
CombinedDatastore是构建多源异构输入模型训练使用数据的最直接方法。对于不同类型的Datastore数据,combinedDatastore具有极强兼容性。
想要创建CombinedDatastore,使用combine函数按输入次序衔接各类数据库即可:
dsnew = combine(ds1,ds2,...,dsN,ReadOrder=order)
输出参数
ds1、ds2...合并的数据储存,为DataStore类型
order-读取数据的顺序
associated(默认)|sequential
combine函数也可创建次序读取的组合储存库,设置成sequential时,类似于这一次读取的是一张图像,下一次读取就是一条曲线(1维张量)。
而设置成associated则是平行输出,也就是用于多源输入的模型。
其中,是可以将验证数据一并打包进combinedDatastore中,训练函数会自动识别。比如将categorical()对象或是数值对象用arrayDatastore打包,放在combine()函数的最后。
以下是部分参考案例:
多源图像融合训练的脚本示例 (Matlab训练多个输入的CNN模型)_怎么将多源数据进行训练-CSDN博客
combineDatastore对象
combineDatastore对象支持函数如下所示,最骚的是,其继续支持combine函数,也就是可以进行多次合并。
|---------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|
| combine | 合并来自多个数据存储的数据 |
| hasdata | 确定是否有数据可读取 |
| preview | 预览数据存储中的数据子集 |
| read | 读取数据存储中的数据 |
| readall | 读取数据存储中的所有数据 |
| writeall | 将数据存储写入文件 |
| reset | 将数据存储重置为初始状态 |
| transform | 变换数据存储 |
| numpartitions | 数据存储分区数 |
| partition | 划分数据存储 |
| shuffle | 对数据存储中的所有数据进行乱序处理 |
| isPartitionable | 确定数据存储是否可分区 |
| isSubsettable | Determine whether datastore is subsettable |
| isShuffleable | 确定数据存储是否可乱序 |
自定义数据库--更高级的玩法
MATLAB可以自定义数据,来实现更加客制化的训练操作和框架设计。
我们以Pix2Pix条件生成对抗网络的训练数据为例,该模型训练要求输入的数据为一对强绑定的图像数据,分别输入生成器和判别器,因此我们需要利用自定义数据库来实现这一训练效果。
其需要包含以下功能:
1:批量读取用于训练
2:确保读取的两个文件夹的图像,在读取时都是一一对应
3:可以打乱读取顺序,打乱顺序后数据还是一一对应
为实现以上目标,我们可以使其继承Datastore对象的属性,特别shuffleable和Minibatchable这两个属性。以下是实现方法,各位可以参考。
其中,两组图像分别用ImageDatastore进行储存,需要注意的是,得保证两组数据储存的名称一一对应。
Matlab
classdef PairedImageDatastore < matlab.io.Datastore & ...
matlab.io.datastore.Shuffleable & ...
matlab.io.datastore.MiniBatchable
% 配对图像数据存储类(PairedImageDatastore)
%
% 功能:用于同时读取两个文件夹中的配对图像,支持以下功能:
% - 小批量读取(Mini-batching):分批加载数据以减少内存消耗
% - 数据随机打乱(Shuffling):在训练时打乱数据顺序防止模型过拟合
% - 保持配对关系:确保每次读取的图像A和图像B保持对应关系
% 版权信息(Copyright 2020 The MathWorks, Inc.)
% 依赖属性(通过其他属性计算得出)
properties (Dependent)
MiniBatchSize % 小批量大小(每次读取的图像对数量)
end
% 受保护属性(只能通过类方法修改)
properties (SetAccess = protected)
DirA % 文件夹A路径(存放第一组图像)
DirB % 文件夹B路径(存放第二组图像)
ImagesA % 图像数据存储对象A(读取自DirA)
ImagesB % 图像数据存储对象B(读取自DirB)
NumObservations % 总观测数(即图像对总数)
MiniBatchSize_ % 实际存储小批量大小的内部变量
Augmenter % 图像增强器(用于数据扩增)
PreSize % 预处理尺寸(调整图像大小)
CropSize % 裁剪尺寸(随机裁剪区域)
ARange % 图像A的像素值归一化范围
BRange % 图像B的像素值归一化范围
end
% 静态方法(与类相关但不依赖具体实例)
methods (Static)
function [inputs, remaining] = parseInputs(varargin)
% 解析输入参数
% 输入参数格式:'参数名',参数值
% 示例:'PreSize',[256,256],'CropSize',[224,224]
%% 用户调用示例 [params, extra] = parseInputs('CropSize',[224,224],'RotationRange',30);
parser = inputParser(); % 创建 inputParser 对象,方便定义参数规则并解析
parser.KeepUnmatched = true; % 保留未匹配参数供增强器使用
% 定义已知参数及其默认值
parser.addParameter('PreSize', [256, 256]); % 预处理尺寸
parser.addParameter('CropSize', [256, 256]); % 裁剪尺寸
parser.addParameter('ARange', 255); % A图归一化范围
parser.addParameter('BRange', 255); % B图归一化范围
parser.parse(varargin{:}); %% 解析输入参数
inputs = parser.Results; % 返回已解析参数值
remaining = parser.Unmatched;% 返回未匹配参数
end
end
% 类主要方法
methods
function obj = PairedImageDatastore(dirA, dirB, miniBatchSize, varargin)
% 构造函数:创建配对图像数据存储对象
% 输入参数:
% dirA - 文件夹路径或文件名列表(存放图像A)
% dirB - 文件夹路径或文件名列表(存放图像B)
% miniBatchSize - 每个小批量包含的图像对数量
% varargin - 可选参数(名称-值对,如'PreSize',[256,256])
includeSubFolders = true; % 包含子文件夹中的文件
% 初始化基础属性
obj.DirA = dirA;
obj.DirB = dirB;
% 创建图像数据存储对象(Matlab内置类)
obj.ImagesA = imageDatastore(obj.DirA, "IncludeSubfolders", includeSubFolders);
obj.ImagesB = imageDatastore(obj.DirB, "IncludeSubfolders", includeSubFolders);
% 设置小批量大小
obj.MiniBatchSize = miniBatchSize;
% 验证两个文件夹中的文件数量是否一致
assert(numel(obj.ImagesA.Files) == numel(obj.ImagesB.Files), ...
'p2p:datastore:notMatched', ...
'文件夹A和B中的文件数量不匹配');
obj.NumObservations = numel(obj.ImagesA.Files); % 记录总图像对数
% 解析可选参数
[inputs, remaining] = obj.parseInputs(varargin{:});
% 配置图像处理参数
obj.ARange = inputs.ARange;
obj.BRange = inputs.BRange;
obj.Augmenter = imageDataAugmenter(remaining); % 创建图像增强器
obj.PreSize = inputs.PreSize;
obj.CropSize = inputs.CropSize;
end
function tf = hasdata(obj)
% 检查是否还有未读取的数据
% 返回:
% tf - 逻辑值(true表示还有数据)
tf = obj.ImagesA.hasdata() && obj.ImagesB.hasdata();
end
function data = read(obj)
% 读取下一个小批量数据
% 返回:
% data - 包含A/B图像的表格(table)
% 从两个数据存储中分别读取数据
imagesA = obj.ImagesA.read();
imagesB = obj.ImagesB.read();
% 确保数据以cell数组形式存储(处理单图像情况)
if ~iscell(imagesA)
imagesA = {imagesA};
imagesB = {imagesB};
end
% 对图像进行预处理和增强
[transformedA, transformedB] = ...
p2p.data.transformImagePair(imagesA, imagesB, ...
obj.PreSize, obj.CropSize, ...
obj.Augmenter);
% 归一化像素值到[-1, 1]范围
[A, B] = obj.normaliseImages(transformedA, transformedB);
% 将数据打包为表格返回
data = table(A, B);
end
function reset(obj)
% 重置数据读取位置到开头
obj.ImagesA.reset();
obj.ImagesB.reset();
end
function objNew = shuffle(obj)
% 创建数据存储的随机打乱副本
% 返回:
% objNew - 打乱后的新数据存储对象
objNew = obj.copy();
numObservations = objNew.NumObservations;
% 复制底层数据存储对象
objNew.ImagesA = copy(obj.ImagesA);
objNew.ImagesB = copy(obj.ImagesB);
% 生成随机排列索引
idx = randperm(numObservations);
% 重新排列文件顺序(保持A/B对应关系)
objNew.ImagesA.Files = objNew.ImagesA.Files(idx);
objNew.ImagesB.Files = objNew.ImagesB.Files(idx);
end
function [aOut, bOut] = normaliseImages(obj, aIn, bIn)
% 图像归一化处理
% 公式:2*(像素值/范围) - 1
% 示例:当范围为255时,像素值128 → 2*(128/255)-1 ≈ 0.0078
aOut = cellfun(@(x) 2*(single(x)/obj.ARange) - 1, aIn, 'UniformOutput', false);
bOut = cellfun(@(x) 2*(single(x)/obj.BRange) - 1, bIn, 'UniformOutput', false);
end
% MiniBatchSize的getter方法
function val = get.MiniBatchSize(obj)
val = obj.MiniBatchSize_;
end
% MiniBatchSize的setter方法
function set.MiniBatchSize(obj, val)
% 同时设置两个底层数据存储的读取大小
obj.ImagesA.ReadSize = val;
obj.ImagesB.ReadSize = val;
obj.MiniBatchSize_ = val; % 更新内部存储值
end
end
end