Main Content

병렬 및 GPU 연산을 사용한 얕은 신경망

참고

딥러닝의 경우 자동으로 병렬 및 GPU가 지원됩니다. trainNetwork 함수를 사용하여 컨벌루션 신경망(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에서는 위 예제 코드의 x, t처럼 데이터를 여러 샘플로 나눌 수 있습니다. xt가 각각 샘플 한 개씩만 포함한다면 병렬 처리가 필요하지 않습니다. 그러나 xt가 수백 개 또는 수천 개의 샘플을 포함한다면 병렬 처리를 통해 속도와 문제 크기 면에서 이점을 얻을 수 있습니다.

분산 연산

Parallel Computing Toolbox는 신경망 훈련과 시뮬레이션을 하나의 PC에 있는 여러 CPU 코어에서 실행하거나 MATLAB® Parallel Server™를 사용하여 신경망 상의 여러 대의 컴퓨터의 여러 CPU에서 실행할 수 있습니다.

다중 코어를 사용하면 계산 속도를 높일 수 있습니다. 컴퓨터를 여러 대 사용하면 한 대 컴퓨터의 RAM에는 너무 커서 담을 수 없는 데이터 세트를 사용하는 문제를 풀 수 있습니다. 문제 크기에 대한 유일한 제한 사항은 모든 컴퓨터에서 사용 가능한 RAM의 총 양입니다.

클러스터 구성을 관리하려면 MATLAB 환경 메뉴의 병렬연산 > 클러스터 프로파일 생성 및 관리에서 클러스터 프로파일 관리자를 사용하십시오.

디폴트 클러스터 프로파일(일반적으로 로컬 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을 호출하면 이 두 함수는 훈련과 시뮬레이션을 진행하기 전에 입력 행렬 또는 셀형 배열 데이터를 분산 Composite형 값으로 나눕니다. sim이 Composite형 값을 계산하면 이 출력값이 동일한 행렬 또는 셀형 배열 형식으로 다시 변환된 후 반환됩니다.

그러나 다음과 같은 경우에는 이러한 데이터 분할을 수동으로 수행해야 할 수 있습니다.

  • 호스트 컴퓨터에서 실행하기에 문제 크기가 너무 큰 경우. Composite형 값의 요소를 순차적으로 수동으로 정의하면 훨씬 큰 문제를 정의할 수 있습니다.

  • 일부 워커가 더 빠른 컴퓨터 또는 다른 컴퓨터에 비해 더 많은 메모리가 탑재된 컴퓨터에 있다는 사실을 아는 경우. 데이터를 워커별로 서로 다른 개수의 샘플로 분산시킬 수 있습니다. 이를 부하 분산이라고 합니다.

다음 코드는 일련의 무작위 데이터셋을 순차적으로 만들고 이를 별도의 파일에 저장합니다.

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

데이터가 순차적으로 정의되었으므로 전체 크기가 호스트 PC 메모리에 담을 수 있는 것보다 더 큰 데이터셋을 정의할 수 있습니다. PC 메모리는 한 번에 하나의 서브 데이터셋만 수용해야 합니다.

이제 여러 병렬 워커에서 데이터셋을 순차적으로 불러온 다음 Composite형 데이터에 대해 신경망을 훈련시키고 시뮬레이션할 수 있습니다. train 또는 sim을 Composite형 데이터와 함께 호출하면 'useParallel' 인수가 자동으로 'yes'로 설정됩니다. Composite형 데이터를 사용할 때는 훈련 전에 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에서 반환하는 Composite형 출력값을 변환해야 할 때 메모리 제한이 우려된다면 각 요소에 개별적으로 액세스할 수 있습니다.

for i=1:pool.NumWorkers
  yi = yc{i}
end

메모리 제한이 우려되지 않는다면 Composite형 값을 하나의 로컬 값으로 결합합니다.

y = {yc{:}};

부하 분산을 진행할 때 동일한 프로세스가 일어나지만 각 데이터셋이 동일한 샘플 개수(이전 예제에서는 1000개)를 갖도록 하는 대신 여러 워커 호스트 컴퓨터의 메모리 및 속도 차이를 가장 잘 활용할 수 있도록 샘플 개수를 조정할 수 있습니다.

각 워커가 데이터를 가질 필요는 없습니다. Composite형 값의 요소 i가 정의되지 않은 경우 워커 i는 계산에서 사용되지 않습니다.

단일 GPU 연산

GPU 카드의 세대가 바뀔 때마다 코어 개수, 메모리 크기와 속도 효율이 급속히 늘어나고 있습니다. 향상된 GPU 성능은 오랜 기간 비디오 게임의 발전을 뒷받침해 왔으며, 이제는 신경망 훈련과 같은 일반적인 수치 계산 작업을 수행할 수 있을 만큼 GPU 카드가 유연해졌습니다.

최신 GPU 요구 사항은 Parallel Computing Toolbox 웹 페이지를 참조하거나 MATLAB을 쿼리하여 사용 중인 PC에 지원되는 GPU가 있는지 확인하십시오. 다음 함수는 시스템에 있는 GPU의 개수를 반환합니다.

count = gpuDeviceCount
count =

    1

결과가 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를 활용하는 가장 간단한 방법은 모든 trainsim 호출을 파라미터 인수 'useGPU''yes'로 설정된 상태(디폴트 값은 'no')로 지정하는 것입니다.

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

많은 MATLAB 함수가 입력 인수 중 하나라도 gpuArray이면 자동으로 GPU에서 실행됩니다. 보통은 함수 gpuArraygather를 사용하여 GPU에서 또는 GPU로 배열을 이동합니다. 그러나 GPU에서 신경망 계산이 효율적으로 수행되려면 행렬을 전치하고 열에 채우기를 적용해서 각 열의 첫 번째 요소가 GPU 메모리에서 올바르게 정렬되도록 해야 합니다. Deep Learning Toolbox는 배열을 GPU로 이동하여 적절하게 구성해 주는 nndata2gpu라는 특수 함수를 제공합니다.

xg = nndata2gpu(x);
tg = nndata2gpu(t);

이제 'useGPU' 인수를 지정하지 않고도 이미 GPU에 있는 변환된 데이터를 사용하여 신경망을 훈련시키고 시뮬레이션할 수 있습니다. 그런 다음 상보 함수 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 시그모이드 전달 함수를 사용하는 신경망의 속도가 느려질 수 있습니다. 대신 쓸 수 있는 함수로 Elliot 시그모이드 함수가 있는데, 이 함수의 표현에는 고차 함수에 대한 호출이 포함되지 않습니다.

(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를 사용하여 클러스터에서 계산을 실행할 수 있습니다.

이를 수행하기 위한 가장 간단한 방법은 사용 중인 클러스터 프로파일에 의해 지정된 병렬 풀을 사용하여 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만 사용하는 것이 더 빠른 경우도 있습니다. 예를 들어, 컴퓨터 하나에 3개의 GPU와 4개의 워커가 있다면 3개의 GPU로 가속되는 3개의 워커는 네 번째 CPU 워커에 의해 속도가 제한될 수 있습니다. 이 경우 trainsim이 고유 GPU를 갖는 워커만 사용하도록 지정할 수 있습니다.

net2 = train(net1,x,t,'useParallel','yes','useGPU','only','showResources','yes')
y = net2(x,'useParallel','yes','useGPU','only','showResources','yes')

간단한 분산 연산과 마찬가지로, 분산 GPU 연산에서도 수동으로 만든 Composite형 값을 사용하면 얻을 수 있는 이점이 있습니다. Composite형 값을 직접 정의하면 어느 워커를 사용할지, 각 워커에 몇 개의 샘플을 할당할지, 어느 워크가 GPU를 사용할지 지정할 수 있습니다.

예를 들어, 워커는 4개인데 GPU는 3개밖에 없다면 이 GPU 워커들에 더 큰 데이터셋을 정의할 수 있습니다. 아래에서는 Composite형 요소당 서로 다른 샘플 부하로 무작위 데이터셋을 생성합니다.

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이 사용 가능한 3개의 GPU를 사용하도록 지정할 수 있습니다.

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

처음 3개의 워커가 반드시 GPU를 사용하도록 하려면 수동으로 각 워커의 Composite형 요소를 gpuArray로 변환하십시오. 각 워커는 병렬로 실행되는 spmd 블록 내에서 이 변환을 수행합니다.

spmd
  if spmdIndex <= 3
    xc = nndata2gpu(xc);
    tc = nndata2gpu(tc);
  end
end

이제 데이터가 언제 GPU를 사용해야 할지 지정하므로 trainsim에 언제 GPU를 사용해야 할지 알려줄 필요가 없습니다.

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

PC 클러스터에서 MATLAB Parallel Server를 사용하여 실행 중인 병렬 풀에서 사용 가능한 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를 가진 워커가 하나도 없는 경우는 예외입니다. GPU가 없는 경우에는 모든 워커가 CPU를 사용합니다.

실제로 어느 하드웨어가 사용 중인지 잘 모르겠으면 gpuDeviceCount, gpuDevice, pool.NumWorkers를 조사하여 원하는 하드웨어를 사용 가능한지 확인하고, 'showResources''yes'로 설정하여 trainsim을 호출하여 실제로 어느 리소스가 사용되고 있는지 확인합니다.