TensorFlow more detail
앞서 Deep Learning Framework에 대한 개요를 짚어봤습니다.
(이 글이 처음이신 분은 아래 링크를 우선 보고 오시기를 추천드립니다!)
모두를 위한 cs231n
모두를 위한 cs231n 😀
👉🏻 시작합니다! 🎈
모두를 위한 cs231n (feat. 모두의 딥러닝 & cs231n)
👉🏻 Neural Network 기초 📗
* Backpropagation
Lecture4. Backpropagation and Neural Network. 오차역전파에 대해서 알아보자😃
👉🏻 Training Neural Network Part I 📑
- Activation Function 파헤치기
Lecture 6. Activation Functions에 대해 알아보자
Lecture 6. Activation Functions - ReLU함수의 모든 것
- Data Preprocessing
Lecture 6. Training Neural Network - Data Preprocessing
- Weight Initialization
Lecture 6. Weight Initialization
- Batch Normalization
Lecture 6. Batch Normalization
👉🏻 Deep Learning CPU와 GPU ⚙️
Lecture 8 - Part1. Deep Learning 을 위한 CPU와 GPU
👉🏻 Deep Learning Framework ☕️
Lecture 8 - Part2. Deep Learning Framework
👉🏻 TensorFlow Fraemwork 🧱
Lecture 8 - Part3. TensorFlow Framework
👉🏻 PyTorch Framework 🔥
Lecture 8 - Part3. PyTorch Framework
👉🏻 Generative Model
이제 본격적으로 TensorFlow에 대해 알아보도록 하겠습니다
TensorFlow Neural Net Example
2개의 FC layer(Fully-connected Layer)와 ReLU(Activation Function)를 학습시키는 네트워크를 통해 TensorFlow Framework를 알아보도록 하겠습니다. 손실 함수로는 L2 Euclidean함수를 사용합니다.
TensorFlow에서는 코드가 두 가지 스테이지로 나눠집니다.
Computational graph를 구성하는 영역(Define)
graph를 동작하는 영역(Run)입니다.
일반적으로 TensorFlow는 Define and Run 설계 사상을 따른다고 알려져 있습니다. 바로 그 두 스테이지가 TensorFlow의 일반적인 순서입니다. 그러면 그래프를 구성하는 부분을 하나씩 자세히 살펴보도록 하겠습니다.
Create Computational Graph
TensorFlow placeholder
우선 맨 처음에 X, Y, w1, w2를 정의합니다. 이들 모두 tf.placeholder 객체입니다. 이들은 computational graph의 입력 노드가 됩니다. 가령 데이터를 넣어주려면 이쪽으로 넣어주면 됩니다.
하지만 이 부분에서 실제적인 메모리 할당은 일어나지 않습니다. 단지 그래프만 구성합니다.
TensorFlow Forward Pass
앞서 선언한 변수들을 사용합니다.
tf.placeholder에서 선언한 변수들을 기반으로 다양한 Tensorflow연산을 수행합니다.
우리가 만들고 싶은 연산을 구성할 수 있습니다.
위의 예제의 경우 X와 w1의 행렬곱 연산을 수행합니다. (첫 째줄)
그리고 tf.maximum()을 이용해 ReLU를 구현할 수 있습니다. (이상 첫째줄)
그리고 행렬곱 연산을 한 번 더 수행해 그래프의 최종 출력 값을 계산할 수 있습니다.(둘 째줄)
그리고 예측값과 정답 값 Y의 유클리디안 거리(Euclidean Distance)를 계산하기 위해 Tensor Operation을 사용합니다.(셋째, 넷 째줄)
한 가지 주목할 점은 이 코드는 현재 아무런 연산도 수행하지 않는다는 점입니다. 아직까지는 계산할 데이터가 존재하지 않습니다.
지금은 단지 그래프 구조만 만들어 놓아서 실제 데이터가 들어왔을 때 어떻게 연산을 진행해야 하는지만 구성합니다.
그리고 TensorFlow에게 loss에 대한 w1과 w2의 gradient구해달라고 명령하면 각각의 gradient를 구할 수 있게 됩니다.
빨간색의 라인을 수행하면 loss를 계산하고, w1과 w2의 그레디언트를 계산해주는 마법적인 일을 할 수 있데 되는 것입니다.
여기서도 실제 연산은 수행하지 않고 그래프에 그레디언트를 계산해주는 추가적인 연산을 더해주기만 했습니다.
지금까지는 Computational graph를 구성했습니다.
Loss와 gradient를 계산하기 위한 커다란 Graph를 만든 것입니다.
TensorFlow Session
이제 TensoFlow session으로 들어가 봅니다. 이를 통해 실제 graph를 실행시키고 데이터를 넣어줄 수 있습니다.
Session에 진입하려면 그래프에 들어갈 실제 값을 만들어 줘야 합니다.
대부분의 경우 TensorFlow는 Numpy arrays를 이용할 수 있습니다. 위의 예제에서도 Numpy를 이용해서 x, y, w1, w2의 값을 할당해 주었습니다.
TensorFlow session.run
session.run을 통해 그래프의 일부를 실행시킬 수 있습니다.
sess.run의 첫 번째 인자로 loss, grad_w1, grad_w2가 있는 것은 그래프의 출력으로 어떤 부분을 원하는지 말해주는 부분입니다. 그리고 feed_dict를 통해 실제 값을 전달해줍니다.
다시 말해 이 한 줄만 실행시키면 그래프가 실행되고 loss, grad1, grad2가 계산됩니다. 그리고 출력 값도 Numpy array입니다.
out이란 변수를 나눠보게 되면 loss와 gradient가 Numpy array의 형태로 반환됨을 알 수 있습니다. 이 값을 가지고 원하는 작업을 하면 됩니다.
TensorFlow 그래프 여러 번 학습시키기
지금까지는 한 번의 forwad/backward pass를 진행했습니다.
그래프를 여러 번 학습시키기 위해서는 어떻게 해야 할까요?
그래프를 여러번 실행시키기 위해서는 for loop를 사용하면 됩니다. 반복적으로 session.run을 호출해 loss와 grad를 계산합니다.
이 코드의 loss를 계산해보면 loss가 아주 잘 내려가며 이는 네트워크의 학습이 잘 이루어지고 있음을 의미합니다.
이 예제는 TensorFlow에서 네트워크가 어떤 식으로 학습이 되는지를 아주 명확하게 알려줍니다.
Forward pass에서 그래프가 실행될 때마다 가중치를 넣어주어야 합니다. 현재 Numpy로 된 가중치를 가지고 있고 이 값을 그래프에 넣어줘야 하는 것입니다.
그래프가 한 번 실행되고 나면 gradient를 반환해줬습니다. gradient는 가중치와 동일한 크기의 행렬입니다. 이것이 의미하는 바는 graph를 실행할 때마다 우선 Numpy array로 된 가중치 행렬을 TensorFlow로 복사합니다.
만약 코드가 CPU에서 실행이 되고 있다면 큰 문제가 되지는 않습니다. 하지만 지난번에 GPU의 보틀넥에 대해서 말씀드린 적이 있습니다. CPU/GPU 메모리 상의 데이터 교환은 상당히 비용이 큽니다. 때문에 네트워크가 엄청 크고 가중치가 엄청 많다면 GPU/CPU 간의 데이터 교환은 엄청 느리고 비용이 큽니다. TensorFlow에서는 이에 대한 해결책이 있습니다.
TensorFlow Variable
굳이 매번 placeholder를 써서 가중치를 넣어줄 필요가 없습니다. 대신 variable로 선언하는 것입니다.
variable은 Computational Graph 안에 서식하는 변수입니다. 그래프가 실행될 때마다 지속적으로 그래프 안에 상주합니다. 따라서 w1과 w2를 placeholder 대신에 variable로 선언해 줍니다.
하지만 이제는 variable들이 그래프 안에 상주하기 때문에 TensorFlow에게 어떻게 초기화 시킬 것인지를 알려줘야 합니다. 기존에 그래프 밖에 있을 때는 그래프에 넣어주기 전에 Numpy를 이용해서 초기화를 시켜주면 그만이었습니다. 하지만 지금은 그래프 안에 variable이 있기 때문에 그 변수들을 초기화 시킬 권한은 TensorFlow에 있는 것입니다. 따라서 tf.randomnormal이 필요합니다.
사실 tf.randomnormal 명령어 자체가 변수들을 초기화시켜 주는 것은 아니고 Tensorflow에게 어떻게 이 값들이 초기화되어야 하는지 알려주는 것입니다.
그래프 내에서 weights update
이전 예제에서는 Computational Graph의 외부에서 가중치를 업데이트했습니다. 이전 예제에서는 gradient를 계산하고 Numpy array로 가중치를 업데이트 했습니다. 그리고 계산된 가중치를 다음 step에 넣어줬습니다.
하지만 가중치를 그래프 안에서 업데이트하기 위해서는 그 연산 자체가 그래프에 포함되어야 합니다. 이를 위해 assign 함수를 이용합니다. 이를 통해 변수가 그래프 내에서 업데이트가 일어나도록 하는 것입니다. 업데이트된 값들은 항상 그래프 내부에 존재하게 됩니다.
TensorFlow 학습 진행하기
실제 학습을 진행하기에 앞서 tf.global_variables_initializer()를 통해 그래프 내부의 변수들을 초기화시켜주는 명령어가 필요합니다. 초기화가 끝나면 그래프를 여러 번 학습해 줍니다.
위의 코드를 보시면 이제는 데이터와 레이블만 value값으로 넣어주면 되고 가중치는 그래프 내부에 항상 상주하게 됩니다.
그리고 TensorFlow에게 loss를 계산해 달라고 하면 됩니다. 하지만 이 코드를 학습시켜보면 네트워크의 학습이 전혀 일어나지 않습니다. 아래 슬라이드를 참고하도록 하겠습니다.
왼쪽 그래프에서 볼 수 있듯이 loss값에는 전혀 변화가 없습니다.
왜 그런 걸까요?
Computational Graph 내에서 가중치를 업데이트 하기 위해 assign코드도 작성했고 Graph를 계산해서 loss와 gradient도 잘 계산을 했는데 loss가 변하지 않습니다. 왜 그런 걸까요?
가중치가 매 번 초기화되기 때문일까요?
정답은 바로 우리가 Tensorflow에게 w1과 w2를 업데이트하라고 명시적으로 말을 해줘야 한다는 것입니다.
우리는 지금 큰 Computational Graph 데이터를 구축했습니다. 구조를 메모리에 저장하고 실행을 호출할 때, 그리고 Graph 내부의 dependence를 고려해보면 loss를 계산하기 위해서 굳이 업데이트를 할 필요는 없다는 것을 알게 됩니다.
TensorFlow는 아주 스마트하기 때문에 output에 필요한 연산만 수행합니다. 이는 TensorFlow의 장점으로 필요한 부분만 실행합니다. 하지만 간혹 이러한 특성이 우리를 헷갈리게 하고 예상치 못했던 결과를 초래할 수도 있습니다. 이 경우에는 우리가 TensorFlow에게 업데이트를 수행하라고 명시적으로 말을 해줘야 합니다.
해결법 중 하나는 new_w1과 new_w2를 출력으로 추가하면 됩니다.
하지만 new_w1과 new_w2의 사이즈가 큰 tensor라면 상황이 안 좋아집니다. TensorFlow에게 출력을 요청하는 것은 매 반복마다 CPU/GPU 간의 데이터 전송이 요구되기 때문입니다.
이 대신에 할 수 있는 트릭이 하나 있습니다. 더미 노드(dummy node) 하나를 그래프에 추가하는 것입니다. 이 fake data dependencies를 이용하면 new_w1과 new_w2를 업데이트할 수 있습니다. 그리고 계산 그래프를 실행시키면 loss와 더미 노드를 계산하게 됩니다.
이 더미 노드는 아무것도 반환하지 않지만 dependence를 만들었기 때문에 이를 통해 가중치가 업데이트될 수 있는 것입니다.
TensorFlow Loss
이전까지는 우리가 직접 loss를 계산하는 연산을 만들었습니다. TensorFlow는 일반적인 Neural Network 모델에 들어가는 편리한 함수들을 제공합니다. 이 tensor operation을 사용하면 L2 loss를 직접 구현하지 않아도 됩니다.
그리고 하나 더 해야 할 것이 있습니다(Tensor Flow... 어려 어려...)
입력과 가중치를 정의하고 행렬 곱 연산으로 둘을 묶는 그런 일들입니다.
이 예제에서는 bias도 넣지 않았습니다. bias를 넣으려면 bias를 또 초기화시켜줘야 하고 bias 하나를 추가하는데도 엄청나게 많은 코드가 필요한 것입니다.
여러분께서 딥러닝에 들어가는 기본적인 레이어들 가령 convolution이나 batch norm을 구현하려 치면 입/출력도 선언해줘야 하고 이를 모두 묶어서 computational graph도 만들어줘야 하는데 이 가중치를 초기화시켜주고 크기를 맞춰주는 것(shape을 잘 맞춰주는 것)은 정말로 성가신 일입니다.
이에 TensorFlow를 warpping한 higher lever libraries들이 존재합니다.
이 예제를 보시면 X와 Y만 placeholder로 선언해줍니다.
그리고 밑에 줄을 보시면 h = tf.layers라고 선언합니다.
그리고 inpuput = x, units = H를 넣어줍니다.
내부적으로 w1과 b2를 variable로 만들어주고 graph 내부에 적절한 shape로 선언해줍니다. 다만 우리에게는 보이지 않습니다.
그리고 xavier initialize 객체를 사용하여 어떻게 초기화시킬 것인지를 말해줍니다.
다음 레이어를 보면 이전 레이어에서 출력한 h를 받아서 똑같은 일을 해줍니다.
그리고 activation=tf.nn.relu를 볼 수 있는데 이를 통해 레이어에 relu를 추가할 수 있습니다.
이처럼 이들 모두 우리를 대신해서 대부분의 아키텍처와 관련된 세부사항들을 다뤄줍니다.
Keras Package
TensorFlow를 기반으로한 아주 다양한 High-level library들이 있습니다. 그중 아주 유명한 Package로 Keras가 있습니다.
Keras는 아주 훌륭한 API로 TensorFlow를 backend로 해서 Computational Graph를 알아서 만들어줍니다. Keras는 Theano backend도 지원합니다.
그 외에 다양한 Wrapper들이 있습니다.
이 외에 Pretrained Model, Tensorboard 등도 있으니 참고하시면 좋을 것 같습니다.
오늘은 대표적인 Deep Learning Framework인 Google의 TensorFlow에 대해 알아보았습니다.
저는 PyTorch 유저라 제게는 TensorFlow의 사용법이 어렵게 다가옵니다...ㅠㅠ
다음 시간에는 PyTorch Framework에 대한 포스팅이 준비되어 있습니다.
긴 글 읽어주셔서 감사합니다. 그럼 다음 시간에 찾아뵙겠습니다. 이상 Steve-Lee였습니다. 감사합니다^^
Reference
- cs231n Lecture 8 - Deep Learning Software
- cs231n Syllabus | cs231n
'Deep Learning > 모두를 위한 cs231n' 카테고리의 다른 글
[모두를 위한 cs231n] Lecture 6. Weight Initialization (0) | 2020.05.18 |
---|---|
[모두를 위한 cs231n] Lecture 6. Activation Functions에 대해 알아보자 (1) | 2020.05.18 |
[모두를 위한 cs231n] Lecture 8 - Part2. Deep Learning Framework (0) | 2020.05.11 |
[모두를 위한 cs231n] Lecture 8 - 개요 (0) | 2020.05.08 |
[모두를 위한 cs231n] Lecture 8 - Part1. Deep Learning 을 위한 CPU와 GPU (3) | 2020.05.08 |
댓글