본 포스팅은 다음 과정을 정리 한 글입니다.
Custom and Distributed Training with TensorFlow
지난 시간 리뷰
지난 시간에는 Neural Network 훈련과 관련하여 TensorFlow의 몇 가지 기본 기능을 살펴보았습니다.
Tensor가 무엇이고 어떻게 작동하는지 살펴보았습니다. 또한 gradientTape에 대해 배웠습니다. gradientTape를 활용해 variable, function들을 미분하고 이 값을 optimizer에게 전달하여 model을 학습하는 법을 배웠습니다.
이번 시간부터 이 모든 것을 활용하여 Custom Training Loop를 만드는 법에 대해서 공부해 보겠습니다.
우리는 TensorFlow와 Keras를 사용하여 model.compile()을 사용하여 모델을 구성하고 model.fit()을 사용하여 학습을 할 수 있었습니다.
model.compile()에서 사용할 optimizer와 loss를 지정하였고 model.fit()을 통해 feature들을 label들에 가깝게 학습시킵니다.
model.fit은 내부 루프라고도 하며 Epoch 또는 가끔 Epic이라고도 불립니다. 각 루프에서 model은 batch 단위로 학습하고 각 반복에서 학습 가능한 모델의 가중치를 선택한 loss function으로 loss를 줄이기 위해 업데이트됩니다.
각 훈련 가능한 weight(가중치)의 업데이트 양과 가중치의 실제 업데이트는 optimizer에 의해 수행됩니다.
여태까지 네트워크에서 model.compile 및 model.fit을 사용하는데 우리는 익숙해져 왔습니다.
이제는 Custom Training 즉 루프를 통해 Training을 교체하는 방법을 살펴보겠습니다.
데이터 batch를 관리하고 loss를 계산하고 loss를 최소화하고, optimizer를 사용하여 가중치를 update 하게 구현이 될 것입니다.
그럼 네트워크를 훈련시키는 방법을 단계별로 살펴보겠습니다.
1. 먼저 네트워크 모델을 정의합니다. 예를 들면 뉴런 층, LSTM, GRU, CNN 같은 것들이 있습니다.
첫 번째 예제에서는 single 뉴런이 있는 네트워크를 설계할 것이며 여기에는 weight와 bias라는 두 개의 훈련 가능한 값이 있습니다.
2. 훈련 데이터를 수집하고 준비합니다.
3. 손실 및 최적화 함수를 정의합니다. (훈련 데이터와 훈련 레이블이 주어지면 훈련 데이터를 훈련 레이블로 변경시켜 줄 수 있는 네트워크의 가중치 값을 찾아내게 되는데, 이러한 가중치를 알게 되면 향후 다른 데이터에 사용할 수 있다는 이론에 근거한 것입니다.)
4. 훈련은 옵티마이저를 사용하여 loss를 측정한 다음 가중치를 조정하여 loss를 최소화합니다.
5. 모델이 훈련 중에 보지 못한 새 데이터 (validate set)을 사용하여 모델을 검증할 수 있습니다.
이제 이러한 단계를 실제 예제로 살펴보겠습니다.
먼저 모델을 정의합시다. 처음부터 모든 것을 구축하기 때문에 TensorFlow 클래스를 상속하지 않고 model이라는 클래스를 정의합니다.
생성자는 self.w와 self.b라는 두 개의 변수가 있습니다. w와 b는 모델의 학습 가능한 가중치입니다. 이 것들은 임의의 값, 예를 들어 5와 0으로 초기화됩니다.
Model에서는 self.w*x + self.b라는 선형 방정식의 출력을 반환하는 call 함수가 정의되어 있습니다.
훈련이 진행됨에 따라 w와 b는 loss를 최소화하기 위해 Gradient descent optimization에 의해 업데이트됩니다.
다음으로 훈련 데이터를 준비해야 합니다.
이 예제에서는 random 데이터를 사용하겠습니다.
우리는 w와 b에 대해 원하는 참 값을 각각 3과 2라고 설정합니다.
그런 다음 1000개의 임의의 x값을 만들고 w 및 b에 실제 값에 대해 y에 대한 실제 값이 무엇인지를 계산할 수 있습니다.
손실 함수 (Loss function)의 경우 Mean Squared Error(평균 제곱 오차) Loss를 사용합니다.
이 것은 실제 y값과 예측 y 값 간의 차이 제곱의 평균입니다.
보통 Y_true, Y_pred라고 정의합니다.
여기서는 tensor인 y_true와 y_pred를 parameter로 받는 loss function을 정의합니다.
Mean Sqaured Error는 이러한 텐서 간의 차이를 취하고 그 차이를 제곱하여 계산할 수 있습니다. 그런 다음 tf.reduce_mean을 사용하여 이러한 모든 차이의 평균을 구하고 MSE를 나타내는 scalar값을 반환합니다.
이제 Gradient와 도함수가 손실을 최소화하는 방법, 즉 최적화 함수가 작동하는 방식을 이해하는데 도움을 주기 위해 손실 함수를 그려보겠습니다. 앞서 우리는 평균 제곱이 오차임을 보았고, 그래서 그것은 어떤 것의 제곱이고, 제곱은 이와 같은 포물선 모양을 가집니다.
손실 함수의 최솟값은 가장 낮은 값이 있는 곳이므로 빨간색 선으로 그려진 곳에 있습니다.
초기 가중치와 bias를 기반으로 네트워크의 손실을 계산한다고 가정하면 손실 곡선의 어딘가에 있는 값을 얻게 될 것입니다. 아마도 최소가 아닌 곳에 있을 것입니다.
손실에 대한 값의 기울기를 계산하면, 그것은 다음과 같이 보일 것이고, 우리가 움직일 수 있는 방향을 알려줍니다.
Gradient를 제공하는 도함수는 실제로 최솟값에서 멀리 떨어져 있지만 더하는 대신 가중치에서 값을 빼서 방향으로 사용할 수 있습니다.
여기서 중요한 부분은 손실 함수에 대한 손실 값을 미적분학을 사용하여 곡선의 최솟값으로 향하기 위해 어느 방향으로 이동해야 하는지를 알 수 있다는 점입니다.
그래서 우리는 그 방향으로 움직입니다. 가중치를 업데이트하고 수행하는 단계의 크기는 빨간색으로 표시된 Gradient의 크기가 아니라 학습률 (learning rate)을 곱한 크기가 우리가 이동하는 크기가 됩니다. (Size of step)
예를 들어, Gradient가 2이고 learning rate가 0.1인 경우 가중치를 업데이트하는 양은 0.1 * 2 = 0.2가 됩니다. 따라서 크기와 방향은 검은색 화살표로 표시됩니다. 그리고 이 step 이 끝나면 그 위치에 멈추게 됩니다.
그런 다음 프로세스를 반복하고 매개변수에 대한 새 위치에서 Gradient를 가져오고 이를 사용하여 다음 단계의 방향을 결정합니다. 그런 다음 학습률에 따라 조정되는 방향으로 나아갈 수 있습니다.
이 것을 계속 반복하고 반복하면
이제 최솟값에 가까워질 수 있습니다.
다음 단계에서는 Step의 크기가 크기 때문에 overshoot가 발생하여 여기까지 올 수도 있습니다.
하지만 괜찮습니다. 새 Gradient가 여전히 최소 값의 방향을 제공하여 왼쪽으로 돌아갈 수 있기 때문입니다.
그래서 우리는 여기서 다시 overshoot을 하게 되더라도 다시 최솟값을 향해 돌아갈 수 있는 것입니다.
정리하면 좋은 optimizer는 우리를 minmum loss로 인도할 것입니다.
최적의 학습률을 선택하는 것이 매우 중요합니다. 이것이 우리에게 step의 크기, 즉 gradient가 우리에게 준 방향으로 얼마나 멀리 step을 갈지를 결정한다는 것을 기억해야 합니다.
값이 너무 크면 최솟값에 도달도 못하고 심하게 변동하면서 단계를 너무 많이 반복해서 진동하는 것 처럼 보일 수 있게 됩니다.
또는 너무 학습률이 작으면 최소값에 도달하는데 오랜 시간이 걸릴 수 있습니다.
천천히 minimum값을 향해 내려가게 되기 때문이죠!
우리는 위에서 봤던 것을 위의 코드로 구현할 수 있습니다.
손실 함수를 사용하여 현재 손실을 계산하고 볼의 위치를 알려줍니다. (current_loss)
그런 다음 gradient tape를 사용하여 gradient를 계산합니다.
기울기의 음수는 학습률에 따라 공을 이동하는데 필요한 방향과 크기를 제공합니다. 그래서 우리는 w에 학습률을 곱한 것에 대해 gradient 방향으로 w를 업데이트합니다. w.assign_sub 함수는 새 값으로 모델 변수를 업데이트하고 gradient의 올바른 방향을 사용하기 위한 작업을 수행합니다. 마찬가지로 b.assign_sub는 b에서 학습률 곱하기 db를 빼서 이 결과를 다시 할당하여 업데이트 합니다.
이제 훈련 루프를 순서도로 살펴보겠습니다.
먼저 가중치라고 하는 학습 가능한 변수를 포함하여 모델을 초기화합니다.
그런 다음 여러 Epoch에서 학습을 합니다. Epoch내에서 입력 샘플에 대한 예측 값의 손실을 계산합니다. Gradient tape를 사용하여 학습 가능한 각 변수에 대한 loss를 구합니다. 그런 다음 학습률에 의해 조정된 기울기의 음수를 사용하여 학습 가능한 각 모델의 가중치를 업데이트합니다.
모든 Epoch에 대해 이 작업이 완료될 때까지 계속 반복하면 끝납니다.
train 함수 내에서 우리는 앞에서 보여준 것을 수행하고, 손실을 계산하고, Gradient를 얻은 다음, 훈련 가능한 변수를 업데이트 합니다.
순서도에서 보여준 루프를 구현하기 위해 원하는 Epochs만큼 돌 수 있는 epoch for문을 구현하여 epoch 내에서 train 함수를 호출하여 훈련을 하게 됩니다.
훈련하는 동안 훈련 가능한 변수가 업데이트되고 개선됨에 따라 모델 손실 값을 Floating 할 것입니다. 또한 훈련 가능한 변수의 값을 Floating 합니다. 그런 다음 마지막으로 훈련 종료 후 최종 손실을 계산해볼 것입니다.
먼저 손실에 대한 W와 B의 값은 다음과 같습니다.
최소 값을 향해 내려가기 시작하는 것을 볼 수 있습니다. 이것은 우리가 앞에서 보여준 커브를 따라 움직이는 공처럼 보입니다.
다음은 각 Epoch동안 업데이트되는 빨간색 호인 w의 플롯입니다. w의 실제 값을 나타내는 빨간색 수평선으로 시간이 지남에 따라 수렴하고 있음을 보시면 됩니다. w의 값은 합성 데이터를 생성하는 데 사용되고 있음을 떠올리시면 됩니다.
마찬가지로 파란색 호는 b의 값을 나타내며 매 epoch마다 업데이트됩니다. 또한 b에 대한 실제 값으로 수렴합니다.
마지막으로 15개 epoch에 대한 네트워크의 동작에 따른 w와 b, loss 값을 출력해 보았습니다.
첫 번째는 상당히 큰 1.9로 최솟값에서 멀리 떨어져 있음을 볼 수 있습니다. 그러나 epoch가 증가할 때마다 최소값에 점점 가까워지고 15번째 epoch에서 0.00529에서 끝납니다.
이것으로 Custom Training Loops에 대한 설명을 마치겠습니다.
감사합니다!
댓글