본문 바로가기

Deep Learning for Computer Vision

EECS 498-007 / 598-005 Lecture 9 : Hardware and Software

이번 시간에는 딥러닝에서 쓰이는 하드웨어 - 소프트웨어 순으로 알아볼 것인데, 딥러닝에서의 연산은 CPU와 GPU를

주로 사용한다.

현재 대부분의 GPU는 NVIDIA와 AMD의 GPU가 쓰이고 있으며, 그중에서 딥러닝 분야에서는 NVIDIA GPU만이

딥러닝 관련 소프트웨어를 폭넓게 지원하고 있어 딥러닝에서의 GPU는 NVIDIA GPU를 뜻한다고 보면 된다.

위 슬라이드는 CPU와 GPU의 달러 당 GigaFLOPs를 나타낸 것인데, CPU와 GPU 모두 우상향 하지만 특히 GPU의 경우

2012년부터 급격한 증가를 보여주는 데, 이 때문에 AlexNet 이후부터 모델의 크기가 급격히 커질 수 있었다.

CPU의 경우 적지만 강력한 코어를 가지고 있고, GPU의 경우 1개만 놓고 보면 성능이 좋다 하기 힘들지만, 많은 코어들을 통해서 딥러닝과 같이 단순한 작업을 병렬적으로 처리해야 하는 작업에서 뛰어난 성능을 보여준다.

GPU에 대해 자세히 알아보자면, GPU는 자체적인 프로세서와 메모리를 가지고 있어 하나의 작은 컴퓨터라도

봐도 될 정도이다.

그리고 GPU의 프로세서는 Streaming Multiprocessors (SMs)라는 작은 유닛들로 구성되어 있으며, 각 SM에는 

부동 소수점 연산을 위한 FP 코어가 있다.

예를 들어 RTX Titan의 경우 72개의 SM으로 이루어져 있으며 각 SM은 64개의 FP32 코어로 구성되어 있다.

그리고 FP32 코어 옆에는 주로 머신러닝을 위해 활용되는 텐서 코어가 있는데, 이 코어에서는 4 x 4 행렬 곱과 합을

한 사이클에 수행할 수 있다고 한다. 하지만 이를 위해 정확도를 소폭 희생한 mixed precision 방식을 사용한다.

RTX Titan에서 텐서 코어를 활용하기 전에는 16.3 TFLOP/sec였지만 텐서 코어를 활용하면 성능이 크게 개선되어 

130 TFLOP/sec임을 볼 수 있다. 그리고 텐서 코어를 활용하려면 파이토치에서 데이터 타입을 16비트 실수로 정하고

텐서 코어를 가진 GPU와 그에 따른 소프트웨어를 설치하면 파이토치에서 알아서 텐서 코어를 활용한다.

앞에서 딥러닝에서 GPU가 CPU에 비해 우월한 성능을 발휘한다고 했는데, 왜냐하면 딥러닝에서 대부분의 연산은

행렬곱이 차지하기 때문인데, 행렬곱에서는 모든 output의 원소들이 모두 독립적이라서 병렬 계산을 수행해도 상관

없기 때문이다. 심지어 일부 GPU 모델에서는 행렬곱에 특화된 텐서 코어가 존재하기에 그런 모델과 CPU의 격차는

압도적이라 할 수 있다.

GPU를 활용하기 위해선는 NVIDIA에서는 CUDA를 프로그래밍해야 하는데, 다행히 NVIDIA에서 관련 API를 제공하기에

프로그래밍해볼 일은 그다지 없다

GPU는 한 번에 1개만 사용할 수 있는 것이 아니고 여러 개의 GPU를 가진 서버를 구성하거나 서버들을 모아

데이터 센터를 구성해 이용할 수 있다

최근에는 NVIDIA의 GPU 말고도 딥러닝 목적으로 한 구글의 TPU도 사용되고 있는데, 일반인의 경우 colab 등의

클라우드 환경에서 사용할 수 있으며, TPU는 보통 단독으로 사용하기보다는 여러 개를 병렬적으로 사용한다.

그리고 TPU의 경우 아직까지는 텐서플로우를 이용해야만 사용할 수 있다.

 

 

 

 

 

 

 

지금부터는 소프트웨어에 대해 다룰 것인데, 현재 딥러닝 프레임워크에는 여러 가지 있지만, 오늘날엔

대부분 파이토치와 텐서플로우를 사용하며 강의에서도 이 두 가지 위주로 다룬다.

딥러닝 프레임워크에서는 가장 중요한 것은 위 3가지 사항이고  당연히 파이토치와 텐서플로우 등의 프레임 워크에서는 위 3가지 사항을 보장한다. 그리고 파이토치와 텐서플로우는 다른 용도로도 쓰이는데,

예를 들어 과학 분야에서의 계산 등에서 GPU를 쉽게 활용하게 해 준다.

먼저 파이토치에 대해서 알아볼 것인데, 파이토치는 3가지 수준의 추상화를 제공한다.

  1. Tensor. GPU에서 사용되는 Numpy 배열
  2. Autograd. Computational graph를 알아서 만들고, 알아서 gradient를 계산해줌
  3. Module. 객체 지향을 Neural Network에 적용하게 해주는 것으로 클래스가 클래스 멤버 변수의 정보를 저장하는  것처럼, 모듈은 네트워크의 weight 등의 상태를 저장하고, 이 모듈을 조합해서 복잡한 모델을 쉽게 만들 수 있음

파이토치에서 텐서를 이용한 neural network는 위와 같은 코드로 구현할 수 있고,

 

CPU를 사용할 것이냐, GPU를 사용할 것이냐는 텐서 선언할 때 원하는 디바이스를 선택해서 선언해주면 된다.

 

이전에 사용한 텐서와 함께 Autograd 기능을 이용하면 코드를 더 간결하게 짤 수 있는데, 

gradient를 계산하고 싶은 텐서만 'requires_grad=True'와 함께 선언을 하고

Forward pass를 진행하면 파이토치가 자동으로 back prop에 필요한 정보들을 제공하고, computational graph에서

back prop을 어떻게 진행해야 할지를 정해준다.

그리고 backwrad 메소드를 통해 back prop이 사전에 정의된 대로 수행된다.

위 슬라이드는 파이토치의 forward pass와 backward pass를 표현한 것인데, 파이토치는 먼저 forward pass로 그래프를 그린 후, 모든 Input 중에서 requires_grad=True로 선언된 텐서를 찾는다. 그리고 해당 텐서들과 Loss를 잇는 경로를 찾고 그 경로를 따라 backward pass를 진행한다.

 

그리고 backward pass가 끝나면 gradient는 자동으로 저장되며, 만들었던 그래프를 폐기하며 메모리를 반환한다.

마지막으로 그래프를 그리지 않고 진행한다는 의미인 with torch.no_grad(): 구문과 함께 각 weight을 갱신해주고

각 weight의 gradient는 0으로 초기화해준다. 파이토치에서 gradient 계산은 덮어쓰기가 아니라 더하기로 구현되어 

있어서 마지막에 꼭 초기화해주어야 한다.

 

파이토치는 임의로 정의한 파이썬 함수도 computational graph의 노드가 될 수 있는데, 함수의 입력을 텐서로 받는

함수라면 함수 내부 구현은 computational graph의 형태로 구현되어 전체 모델의 그래프에 포함된다.

그런데 sigmoid와 같은 경우, computational graph로 값을 구하면 값이 Nan이 되거나 오버플로우가 발생하는 등의

문제가 생길 수 있는 함수의 경우,

 

함수의 계산 단계 하나하나를 computational graph의 노드로 만드는 게 아니라 함수 통째로 모델 전체의 computational graph의 단일 노드로 들어갈 수 있게 구현할 수도 있는데 다행히 이런 경우는 드물다.

 

지금부터는 파이토치에 객체 지향 개념을 구현하게 해주는 'nn'에 대해서 알아볼 것이다.

nn 객체는 여러 개의 레이어와 그에 따른 weight과 bias를 가지고 있으며

 

nn 객체를 이용하면 매우 간단한 코드로 forward pass와 backward pass를 수행할 수 있다.

 

또한 파이토치는 복잡한 optimization과정도 optimizer를 사용하면 쉽게 수행할 수 있다.

 

sequenctial model보다 복잡한 모델을 구현해야 하는 경우에는 nn.Module을 상속받는 클래스를 정의함으로써

임의의 객체를 구현할 수 있다. 그리고 이때 forward pass만 구현하면 파이토치의 autograd가 알아서 backward pass를

진행하므로 bardward pass는 구현할 필요가 없다.

그리고 nn.Module을 상속받은 클래스의 객체를 조합해 더 복잡한 모델을 만들 수도 있다.

파이토치에서는 DataLoader 기능도 있는 데, 이걸 이용하면 mini batch를 만들거나 데이터를 섞는 등의 작업을 쉽게

할 수 있다.

또한 파이토치는 pretrained 된 모델도 제공해서 이것들이 필요하면 언제든지 사용할 수 있다.

 

파이토치의 특징으로는 Dynamic Computation Graph이 있는데, 매 epoch마다 학습을 진행할 때,

computation graph를 매번 forward pass 과정에서 만들고, backprop 과정이 끝나면 없앤다.

 

단순한 모델에서는 비효율적이지만, 모델에 control flow가 포함되어야 하는 경우 유용하다.

Dynamic Computation Graph 말고도 한번 그래프를 만들면 계속해서 재사용하는 Static Computation Graph도 있다.

파이토치에서는 모델을 함수의 형태로 구현하면 이를 JIT(Just in Time Compile)로 그래프의 형태로 컴파일하는 식으로

구현한다. 이때 computational graph에 control node가 포함되는 식으로 구현이 된다.

torch.jit.script 메소드를 이용하는 방법 말고도 '@torch.jit.script' annotation을 붙여주면 동일하게 구현된다.

 

모델을 Static 그래프의 형태로 사용하면 그래프의 재사용 말고도 Conv와 ReLU 레이어와 같이 서로 합칠 수 있는 것들은 합쳐주는 등의 최적화가 자동으로 일어나고, 그래프를 'serialize'가 가능해져서 data structure 형태로 메모리에 올려

느린 파이썬으로 실행되는 것이 아니라 C++ 형태로 수행될 수 있다.

 

하지만 Static으로 사용하는 것은 항상 좋은 것만은 아닌데, 코드 구현은 파이썬, 구동은 C++로 구동되기에 디버깅하기 쉽지 않다.

 

디버깅 말고도 Static Graph로는 불가능한 모델의 경우 Dynamic Graph를 써야 하는데

모델의 구조가 입력에 따라 결정되는 Modular Network나 Recursive Network, RNN 등이 있다.

 

지금부터는 파이토치 말고도 널리 쓰이는 텐서플로우에 대해서 다룰 것이다. 텐서플로우는 텐서플로우 1과

텐서플로우 2가 있는데, 1과 2는 이름만 같지 서로 코드가 다르므로 텐서플로우 코드를 찾아볼 때 유의해야 한다.

 

먼저 텐서플로우 1부터 다룰 것인데, 텐서플로우 1에서 Static  Graph로 모델을 구현하는 코드는 두 부분으로 나뉘는데,

하나는 computational graph 정의, 하나는 forward pass, backward pass 정의하는 부분으로 구성되어 있다.

그리고 텐서플로우 1에서는 shape error와 같은 간단한 에러조차 텐서플로우 내부 모듈에서 에러 메세지를 띄우기에

디버깅이 쉽지 않다.

텐서플로우 2는 파이토치와 비슷한데, 위에서 데이터와 wieght을 정의하고 밑에서 computational graph를 만들면서 

학습을 진행해나간다. 이때 forward pass는 with tf.GradientTape() 구문 속에서 backward pass의 경로를 저장하며
진행되고, backward pass는 해당 구문 다음에서 gradient 메소드로 진행된다. 

텐서플로우 2를 Static으로 사용하려면 함수를 만들어주고 파이토치에서처럼 annotation을 써주면 된다.

그리고 파이토치와 달리 함수에 forward pass와 backward pass 모두 포함시킨다.

 

텐서플로우 2에는 kears라는 high level API도 제공하는데, keras는 객체 지향 개념이 적용된 API로 이를 이용하면

손쉽게 모델을 만들 수 있다. 또한 유용한 loss function이나 optimizer도 제공한다. 

그리고 optimizer는 단순히 어떻게 optimization을 진행하는지 알려주는 것만이 아니고,

forward pass, backward pass를 쉽게 수행할 수 있게 해 준다. 

 

또한 텐서플로우 2는 텐서보드라는 유용한 시각화 툴을 지원하는 데, 이 기능은 파이토치에서도 api를 통해

사용할 수 있다.

 

요약

 

 

 

 

============================================================================Assignment 3 어제 다 끝냈는데, 양이 좀 너무 많은 것 같다. 이게 3학점짜리 과제라고...?

진짜 쉽지 않다. 어쨌든 혹시라도 Assignment 3 아직 안 했다면, Dropout과 initializer에 대해서도 나오기에

Lecture 10 듣고 하는 것을 추천한다. 갑자기 kaiming initializer를 구현해라고 해서 이게 뭔가 하고 찾아봐야만 했는데

10강 들으면 문제없이 할 수 있을 것이다.

 

그리고 지금 강의 포스팅 웬만하면 강의 듣고 바로는 아니지만 밀리지 않고 꾸준히 포스팅하려고 하는데, 포스팅만 

할 것도 아니고 좀 천천히 해야겠다. 그냥 강의만 2월까지 듣고 포스팅은 Assignment만 하면서 천천히 해야 할 거 같다.

728x90