Main Content

使用并行和 GPU 计算的浅层神经网络

注意

对于深度学习,自动支持并行和 GPU 计算。您可以使用 trainnet 函数来训练卷积神经网络(CNN、ConvNet)或长短期记忆网络(LSTM 或 BiLSTM 网络),并使用 trainingOptions 选择执行环境(CPU、GPU、多 GPU 和并行)。

并行训练或在 GPU 上的训练需要 Parallel Computing Toolbox™。有关使用 GPU 和以并行方式进行深度学习的详细信息,请参阅在并行运行的 CPU、GPU 上和云上使用大数据进行深度学习

并行机制模式

神经网络本质上是并行算法。多核 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 中,您可以将任何数据(如前面示例代码中的 xt)划分到各采样中。如果 xt 各只包含一个样本,则不存在并行关系。但是,如果 xt 包含成百上千或成千上万个样本,则并行机制既可以提高速度,又可以求解更大的问题。

分布式计算

通过使用 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 4 workers.

parpool 运行时,它会显示池中可用的工作进程数量。确定工作进程数量的另一种方法是查询池:

pool.NumWorkers
   4

现在您可以在所有工作进程中按样本拆分数据的情况下训练和仿真神经网络。为此,请将 trainsim 参数 '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

当调用 trainsim 时,它们会在训练和仿真之前将输入矩阵或元胞数组数据划分为分布式合成值。当 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

由于数据是按顺序定义的,因此您定义的总数据集可以大于可放入主机内存中的数据量。计算机内存一次只能容纳该数据集的一部分。

现在,您可以按顺序在多个并行工作进程上加载数据集,并基于合成数据训练和仿真网络。当使用合成数据调用 trainsim 时,'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 要求的信息,请参阅 Parallel Computing Toolbox 的网页;或者查询 MATLAB 以确定您的 PC 是否有支持的 GPU。此函数返回系统中 GPU 的数量:

count = gpuDeviceCount
count =

    1

如果结果是一个或多个,您可以通过索引查询每个 GPU 的特征。这包括它的名称、多处理器的数量、每个多处理器的 SIMDWidth 以及总内存。

gpu1 = gpuDevice(1)
gpu1 = 

  CUDADevice with properties:

                      Name: 'NVIDIA RTX A5000'
                     Index: 1
         ComputeCapability: '8.6'
            SupportsDouble: 1
             DriverVersion: 11.6000
            ToolkitVersion: 11.2000
        MaxThreadsPerBlock: 1024
          MaxShmemPerBlock: 49152 (49.15 KB)
        MaxThreadBlockSize: [1024 1024 64]
               MaxGridSize: [2.1475e+09 65535 65535]
                 SIMDWidth: 32
               TotalMemory: 25553076224 (25.55 GB)
           AvailableMemory: 25153765376 (25.15 GB)
       MultiprocessorCount: 64
              ClockRateKHz: 1695000
               ComputeMode: 'Default'
      GPUOverlapsTransfers: 1
    KernelExecutionTimeout: 0
          CanMapHostMemory: 1
           DeviceSupported: 1
           DeviceAvailable: 1
            DeviceSelected: 1

利用 GPU 的最简单方法是指定在参数参量 'useGPU' 设置为 'yes''no' 为默认值)的情况下调用 trainsim

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 上执行。通常情况下,您可以使用 gpuArraygather 函数在 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 上运行计算。

要使用这种计算方式,最简单的方法是通过由您使用的集群配置文件确定的并行池,指定 trainsim 来执行此操作。在这种情况下,特别推荐使用 '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 工作进程限制速度。在这些情况下,您可以指定 trainsim 仅使用具有独占 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

您现在可以指定 trainsim 使用三个可用的 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,因此您不需要告诉 trainsim 这样做。

net2 = configure(net1,xc{1},tc{1});
net2 = train(net2,xc,tc,'showResources','yes');
yc = net2(xc,'showResources','yes');

确保每个 GPU 只由一个工作进程使用,这样计算效率最高。如果多个工作进程将 gpuArray 数据分配到同一个 GPU 上,计算仍然会工作,但速度会慢一些,因为 GPU 将按顺序处理多个工作进程的数据。

并行时间序列

对于时间序列网络,只对 xt 使用元胞数组值,并根据需要可选地包括初始输入延迟状态 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 以及 narxnetnarnet 的开环版本等网络即属这种情况。如果网络有层延迟,则为了计算目的,时间就不能“扁平化”,因此单个序列数据无法并行化。layrecnet 以及 narxnetnarnet 的闭环版本等网络即属这种情况。但是,如果数据由多个序列组成,则可以在不同序列之间并行化。

并行可用性、回退和反馈

如前所述,您可以查询 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。

当不确定实际使用的是什么硬件时,请检查 gpuDeviceCountgpuDevicepool.NumWorkers 以确保所需的硬件可用,并调用 trainsim 且将 'showResources' 设置为 'yes' 以验证实际使用了哪些资源。