使用并行和 GPU 计算的浅层神经网络
注意
对于深度学习,自动支持并行和 GPU 计算。您可以使用 trainnet 函数来训练卷积神经网络(CNN、ConvNet)或长短期记忆网络(LSTM 或 BiLSTM 网络),并使用 trainingOptions 选择执行环境(CPU、GPU、多 GPU 和并行)。
并行训练或在 GPU 上的训练需要 Parallel Computing Toolbox™。有关使用 GPU 和以并行方式进行深度学习的详细信息,请参阅Scale Up Deep Learning in Parallel, on GPUs, and in the Cloud。
并行机制模式
神经网络本质上是并行算法。多核 CPU、图形处理单元 (GPU) 以及具有多个 CPU 和 GPU 的计算机集群都可以利用这种并行机制。
Parallel Computing Toolbox 在与 Deep Learning Toolbox™ 结合使用时支持神经网络训练和仿真利用各种并行机制模式。
例如,以下是标准单线程训练和仿真会话:
[x, t] = bodyfat_dataset; net1 = feedforwardnet(10); net2 = train(net1, x, t); y = net2(x);
您可以在此会话中并行执行的两个步骤是:调用 train 和隐式调用 sim(其中网络 net2 作为函数被调用)。
在 Deep Learning Toolbox 中,您可以将任何数据(如前面示例代码中的 x 和 t)划分到各采样中。如果 x 和 t 各只包含一个样本,则不存在并行关系。但是,如果 x 和 t 包含成百上千或成千上万个样本,则并行机制既可以提高速度,又可以求解更大的问题。
分布式计算
通过使用 MATLAB® Parallel Server™,Parallel Computing Toolbox 支持神经网络训练和仿真在单台 PC 上的多个 CPU 内核上运行,或在一个网络的多台计算机的多个 CPU 上运行。
使用多个内核可以加快计算速度。如果求解问题所使用的数据集太大而无法放入一台计算机的内存中,则可以使用多台计算机来求解问题。问题大小仅受限于所有计算机上可用的内存总量。
要管理集群配置,请通过 MATLAB 主页选项卡上的环境菜单中的并行 > Manage Cluster Profiles 使用 Cluster Profile Manager。
要使用默认集群配置文件(通常是本地 CPU 内核)打开 MATLAB 工作单元池,请使用以下命令:
pool = parpool;
Starting parallel pool (parpool) using the 'Processes' profile ... Connected to parallel pool with 6 workers.
当 parpool 运行时,它会显示池中可用的工作单元数量。确定工作单元数量的另一种方法是查询池:
pool.NumWorkers
4
现在您可以在所有工作单元中按样本拆分数据的情况下训练和仿真神经网络。为此,请将 train 和 sim 参数 'useParallel' 设置为 'yes'。
net2 = train(net1,x,t,'useParallel','yes') y = net2(x,'useParallel','yes')
使用 'showResources' 参量来验证计算是否跨多个工作单元运行。
net2 = train(net1,x,t,'useParallel','yes','showResources','yes'); y = net2(x,'useParallel','yes','showResources','yes');
MATLAB 指示使用了哪些资源。例如:
Computing Resources: Parallel Workers Worker 1 on MyComputer, MEX on PCWIN64 Worker 2 on MyComputer, MEX on PCWIN64 Worker 3 on MyComputer, MEX on PCWIN64 Worker 4 on MyComputer, MEX on PCWIN64
当调用 train 和 sim 时,它们会在训练和仿真之前将输入矩阵或元胞数组数据划分为分布式合成值。当 sim 计算出合成值时,会先将此输出转换回相同的矩阵或元胞数组形式,再将其返回。
但是,如果出现以下情况,您可能需要手动执行此数据划分:
问题大小对主机来说太大。通过按顺序手动定义合成值的元素,可定义更大的问题。
众所周知,有些工作单元所在的计算机比其他计算机速度更快或内存更多。在分配数据时,您可以使每个工作单元处理不同数量的样本。这称为负载平衡。
以下代码按顺序创建一系列随机数据集,并将它们保存到各个单独文件中:
pool = gcp; for i=1:pool.NumWorkers x = rand(2,1000); save(['inputs' num2str(i)],'x'); t = x(1,:) .* x(2,:) + 2 * (x(1,:) + x(2,:)); save(['targets' num2str(i)],'t'); clear x t end
由于数据是按顺序定义的,因此您定义的总数据集可以大于可放入主机内存中的数据量。计算机内存一次只能容纳该数据集的一部分。
现在,您可以按顺序在多个并行工作单元上加载数据集,并基于合成数据训练和仿真网络。当使用合成数据调用 train 或 sim 时,'useParallel' 参量会自动设置为 'yes'。在使用合成数据时,请在训练前使用 configure 函数手动配置网络的输入和输出,以匹配数据集之一。
xc = Composite; tc = Composite; for i=1:pool.NumWorkers data = load(['inputs' num2str(i)],'x'); xc{i} = data.x; data = load(['targets' num2str(i)],'t'); tc{i} = data.t; clear data end net2 = configure(net1,xc{1},tc{1}); net2 = train(net2,xc,tc); yc = net2(xc);
要转换 sim 返回的合成输出,您可以访问它的每个元素,如果担心内存限制,可以单独访问。
for i=1:pool.NumWorkers yi = yc{i} end
如果您不担心内存限制,请将合成值合并为一个本地值。
y = {yc{:}};当进行负载平衡时,会发生相同的过程,但不是每个数据集都有相同数量的样本(在前面的示例中为 1000 个),而是可以调整样本数以最好地利用工作单元主机的内存和速度差异。
不要求每个工作单元都有数据。如果合成值的元素 i 未定义,则计算中将不使用工作单元 i。
单 GPU 计算
随着每代新产品的发展,GPU 卡的内核数量、内存大小和速度效率都在快速增长。视频游戏长期受益于改进的 GPU 性能。现在,这些卡足够灵活,可以执行一般的数值计算任务,例如训练神经网络。
有关最新 GPU 要求的信息,请参阅GPU 计算要求 (Parallel Computing Toolbox)或者查询 MATLAB 以确定您的 PC 是否有支持的 GPU。此函数返回系统中 GPU 的数量:
count = gpuDeviceCount
count =
1如果结果是一个或多个,您可以通过索引查询每个 GPU 的特征。这包括其名称、计算能力、总内存和可用内存。
gpu1 = gpuDevice(1)
gpu1 =
CUDADevice with properties:
Name: 'NVIDIA RTX A5000'
Index: 1 (of 2)
ComputeCapability: '8.6'
DriverModel: 'TCC'
TotalMemory: 25544294400 (25.54 GB)
AvailableMemory: 25120866304 (25.12 GB)
DeviceAvailable: true
DeviceSelected: true
Show all properties.利用 GPU 的最简单方法是指定在参数参量 'useGPU' 设置为 'yes'('no' 为默认值)的情况下调用 train 和 sim。
net2 = train(net1,x,t,'useGPU','yes') y = net2(x,'useGPU','yes')
如果 net1 具有默认的训练函数 trainlm,您会看到一条警告,指出 GPU 计算不支持雅可比矩阵训练,仅支持梯度训练。因此,训练函数自动更改为梯度训练函数 trainscg。为避免出现该通知,您可以在训练前指定该函数:
net1.trainFcn = 'trainscg';要验证训练和仿真是否在 GPU 设备上进行,请要求显示计算机资源:
net2 = train(net1,x,t,'useGPU','yes','showResources','yes') y = net2(x,'useGPU','yes','showResources','yes')
以上每行代码都输出以下资源摘要:
Computing Resources: GPU device #1, GeForce GTX 470
如果您提供 gpuArray 数据参量,MATLAB 和其他工具箱中的许多函数会在 GPU 上自动运行。通常情况下,您可以使用 gpuArray 和 gather 函数在 GPU 之间来回移动数组。然而,为了在 GPU 上高效进行神经网络计算,需要转置矩阵和填充列以使每列中的第一个元素在 GPU 内存中正确对齐。Deep Learning Toolbox 提供一个名为 nndata2gpu 的特殊函数,它可将数组移至 GPU 上并对其进行适当的组织:
xg = nndata2gpu(x); tg = nndata2gpu(t);
现在,您可以使用已在 GPU 上转换的数据来训练和仿真网络,而不必指定 'useGPU' 参量。然后使用补充函数 gpu2nndata 转换生成的 GPU 数组并将其返回给 MATLAB。
在使用 gpuArray 数据进行训练之前,必须使用 configure 函数,用常规的 MATLAB 矩阵手动配置网络的输入和输出:
net2 = configure(net1,x,t); % Configure with MATLAB arrays net2 = train(net2,xg,tg); % Execute on GPU with NNET formatted gpuArrays yg = net2(xg); % Execute on GPU y = gpu2nndata(yg); % Transfer array to local workspace
在 GPU 和其他可能需要部署神经网络的硬件上,通常情况下指数函数 exp 不是用硬件实现的,而是用软件库实现的。这可能减慢使用 tansig sigmoid 传递函数的神经网络的运行速度。另外还有艾略特 sigmoid 函数,其表达式不包括对任何更高阶函数的调用:
(equation) a = n / (1 + abs(n))
在训练前,网络的 tansig 层可转换为 elliotsig 层,如下所示:
for i=1:net.numLayers if strcmp(net.layers{i}.transferFcn,'tansig') net.layers{i}.transferFcn = 'elliotsig'; end end
现在,训练和仿真可能在 GPU 上更快,部署硬件更简单。
分布式 GPU 计算
分布式计算和 GPU 计算可以结合使用,以在单台计算机上的多个 CPU 和/或 GPU 上运行计算,或使用 MATLAB Parallel Server 在集群上的多个 CPU 和/或 GPU 上运行计算。
要使用这种计算方式,最简单的方法是通过由您使用的集群配置文件确定的并行池,指定 train 和 sim 来执行此操作。在这种情况下,特别推荐使用 'showResources' 选项,以验证是否采用预期的硬件:
net2 = train(net1,x,t,'useParallel','yes','useGPU','yes','showResources','yes') y = net2(x,'useParallel','yes','useGPU','yes','showResources','yes')
这些代码行使用并行池中所有可用的工作单元。每个独占 GPU 都有一个对应的工作单元使用此 GPU,而其他工作单元作为 CPU 工作。在某些情况下,只使用 GPU 可能会更快。例如,如果一台计算机有三个 GPU,每个 GPU 有四个工作单元,由 GPU 加速的三个工作单元可能会被第四个 CPU 工作单元限制速度。在这些情况下,您可以指定 train 和 sim 仅使用具有独占 GPU 的工作单元。
net2 = train(net1,x,t,'useParallel','yes','useGPU','only','showResources','yes') y = net2(x,'useParallel','yes','useGPU','only','showResources','yes')
与简单的分布式计算一样,分布式 GPU 计算可以受益于手动创建的合成值。通过自己定义合成值,您可以指示要使用哪些工作单元、为每个工作单元分配多少样本以及哪些工作单元使用 GPU。
例如,如果您有四个工作单元,而只有三个 GPU,则您可以为 GPU 工作单元定义更大的数据集。此处,对每个合成元素使用不同样本加载来创建一个随机数据集:
numSamples = [1000 1000 1000 300]; xc = Composite; tc = Composite; for i=1:4 xi = rand(2,numSamples(i)); ti = xi(1,:).^2 + 3*xi(2,:); xc{i} = xi; tc{i} = ti; end
您现在可以指定 train 和 sim 使用三个可用的 GPU:
net2 = configure(net1,xc{1},tc{1});
net2 = train(net2,xc,tc,'useGPU','yes','showResources','yes');
yc = net2(xc,'showResources','yes');为了确保前三个工作单元使用 GPU,手动将每个工作单元的合成元素转换为 gpuArray 对象。每个工作单元在并行执行的 spmd 模块中执行此变换。
spmd if spmdIndex <= 3 xc = nndata2gpu(xc); tc = nndata2gpu(tc); end end
现在数据指定何时使用 GPU,因此您不需要告诉 train 和 sim 这样做。
net2 = configure(net1,xc{1},tc{1});
net2 = train(net2,xc,tc,'showResources','yes');
yc = net2(xc,'showResources','yes');确保每个 GPU 只由一个工作单元使用,这样计算效率最高。如果多个工作单元将 gpuArray 数据分配到同一个 GPU 上,计算仍然会工作,但速度会慢一些,因为 GPU 将按顺序处理多个工作单元的数据。
并行时间序列
对于时间序列网络,只对 x 和 t 使用元胞数组值,并根据需要可选地包括初始输入延迟状态 xi 和初始层延迟状态 ai。
net2 = train(net1,x,t,xi,ai,'useGPU','yes') y = net2(x,xi,ai,'useParallel','yes','useGPU','yes') net2 = train(net1,x,t,xi,ai,'useParallel','yes') y = net2(x,xi,ai,'useParallel','yes','useGPU','only') net2 = train(net1,x,t,xi,ai,'useParallel','yes','useGPU','only') y = net2(x,xi,ai,'useParallel','yes','useGPU','only')
请注意,并行发生在样本之间,或在时间序列跨不同序列的情况下发生。然而,如果网络只有输入延迟,没有层延迟,则延迟的输入可以预先计算,以便在计算时,时间步变为不同样本并且可以并行化。timedelaynet 以及 narxnet 和 narnet 的开环版本等网络即属这种情况。如果网络有层延迟,则为了计算目的,时间就不能“展平”,因此单个序列数据无法并行化。layrecnet 以及 narxnet 和 narnet 的闭环版本等网络即属这种情况。但是,如果数据由多个序列组成,则可以在不同序列之间并行化。
并行可用性、回退和反馈
如前所述,您可以查询 MATLAB 以发现当前可用的并行资源。
要查看主机上有哪些 GPU 可用,请执行以下代码:
gpuCount = gpuDeviceCount for i=1:gpuCount gpuDevice(i) end
要查看当前并行池中运行的工作单元数量,请执行以下代码:
poolSize = pool.NumWorkers
要查看在使用 MATLAB Parallel Server 的 PC 集群上运行的并行池中可用的 GPU,请执行以下代码:
spmd worker.index = spmdIndex; worker.name = system('hostname'); worker.gpuCount = gpuDeviceCount; try worker.gpuInfo = gpuDevice; catch worker.gpuInfo = []; end worker end
当 'useParallel' 或 'useGPU' 设置为 'yes' 但并行或 GPU 工作单元不可用时,遵循如下约定:当请求资源时,如果资源可用,就会使用它们。执行的计算不带误差,即使实际上带有误差。从请求的资源回退到实际资源的过程如下:
如果
'useParallel'为'yes'但 Parallel Computing Toolbox 不可用,或并行池未打开,则计算返回到单线程 MATLAB。如果
'useGPU'为'yes'但当前 MATLAB 会话的 gpuDevice 未分配或不受支持,则计算将返回到 CPU 上进行。如果
'useParallel'和'useGPU'为'yes',则每个拥有独占 GPU 的工作单元都使用该 GPU,而其他工作单元返回到 CPU 上。如果
'useParallel'为'yes'且'useGPU'为'only',则使用具有独占 GPU 的工作单元。不使用其他工作单元,除非任何工作单元都没有 GPU。在没有 CPU 的情况下,所有工作单元都使用 CPU。
当不确定实际使用的是什么硬件时,请检查 gpuDeviceCount、gpuDevice 和 pool.NumWorkers 以确保所需的硬件可用,并调用 train 和 sim 且将 'showResources' 设置为 'yes' 以验证实际使用了哪些资源。