Skip to main content

TensorFlow 2.0에서 학습 루프 커스터마이징

TF 2.0과 W&B로 처음부터 직접 학습 루프 만들기 이 글은 AI 번역본입니다. 오역이 의심되면 댓글로 알려주세요.
Created on September 15|Last edited on September 15
개인적으로 저는 TensorFlow 2.0을 정말 좋아합니다. TensorFlow 팀이 전체 생태계를 확장하고 상호운용성을 높인 방식이 마음에 들고, tf.keras 통합을 강력하게 밀어붙여 이제 tf.keras를 네이티브 TensorFlow 모듈과 쉽게 결합할 수 있게 된 점도 좋습니다. 하지만 가장 마음에 드는 것은 이전과 비교할 수 없을 정도로 학습 루프를 자유롭게 커스터마이징할 수 있다는 점입니다. Keras의 고전적인 Sequential API로 모델을 만들더라도, 처음부터 직접 학습 루프를 작성해 학습 과정을 완전히 내 것으로 만들 수 있습니다. TensorFlow 2.0에서 새롭게 도입된 사항을 간단히 정리한 내용을 보고 싶다면, 이 글 시작하기에 좋은 곳이 될 수 있습니다.
사용하여 Weights & Biases Keras에서 W&B를 사용하는 방법은 간단합니다. model.fit 또는 model.fit_generator 함수에 WandbCallback만 추가하면 됩니다. 하지만 직접 학습 루프를 작성한다면 어떻게 해야 할까요? 그 경우에는 W&B 통합이 다소 번거로울 수 있습니다. 이 글에서는 그 방법을 설명합니다.
동반 코드는 이 Colab 노트북에서 확인할 수 있습니다. 코드의 일부는 TensorFlow 공식 튜토리얼과 가이드에서 영감을 받았습니다.

커스터마이즈된 학습 루프란 무엇인가요?

학습 루프를 어떻게 커스터마이즈하거나 처음부터 직접 작성하는지 궁금할 수 있습니다. 그래서 커스텀 루프에서 W&B를 사용하는 방법의 자세한 내용을 살펴보기 전에, 먼저 맞춤형 학습 루프에 대해 알아봅시다.
fit이나 fit_generator 같은 함수는 머신러닝 엔지니어의 작업을 훨씬 수월하게 해 주지만, 연구를 하거나 모델의 파라미터와 그 업데이트 방식을 더 세밀하게 제어하고 싶다면 최선의 선택이 아닐 수 있습니다. 바로 이런 이유로 TensorFlow 2.0에는 GradientTape 개념이 도입되었습니다. 이를 사용하면 모델을 학습시키는 동안 기울기가 어떻게 계산되는지, 그리고 그 기울기를 사용해 모델의 파라미터가 어떻게 업데이트되는지 직접 관찰할 수 있습니다. 예를 들어 보겠습니다:
# Train the model
@tf.function # Speeds things up
# https://www.tensorflow.org/tutorials/customization/performance
def model_train(features, labels):
# Define the GradientTape context
with tf.GradientTape() as tape:
# Get the probabilities
predictions = model(features)
# Calculate the loss
loss = loss_func(labels, predictions)
# Get the gradients
gradients = tape.gradient(loss, model.trainable_variables)
# Update the weights
optimizer.apply_gradients(zip(gradients, model.trainable_variables))

# Update the loss and accuracy
train_loss(loss)
train_acc(labels, predictions)
여기에서 주의할 점 두 가지가 있습니다:
  • tf.GradientTape 이 컨텍스트를 사용하면 모델의 학습 가능한 변수를 관찰할 수 있고, 자동 미분을 수행하기 위한 연산이 기록됩니다. 자세한 정보 여기.
  • 다음 두 단계는 일반적입니다. 입력 특성을 모델에 통과시키고 손실을 계산합니다.
  • 이제 gradients = tape.gradient(loss, model.trainable_variables) 실제로 미분을 수행하여 손실 함수에 대한 매개변수, 즉 모델의 학습 가능한 변수들에 대한 기울기를 구합니다. 손실 함수와 학습 가능한 변수들 사이의 미분을 위한 필수 상호 의존성은 자동으로 관리됩니다. tf.GradientTape 컨텍스트.
  • optimizer.apply_gradients(zip(gradients, model.trainable_variables)) 모델에서 매개변수를 업데이트합니다.
이것으로 끝입니다!
이것은 고전적인 방식의 거친 시뮬레이션입니다 fit Keras에서 제공하는 함수이지만, 이제는 매개변수 업데이트가 모델에서 어떤 방식으로 이루어질지 등 여러 요소를 유연하게 직접 제어할 수 있다는 점에 주목하세요.


선언형 API와 명령형 API에 관한 참고 사항

위 단계에서 사용한 모델은 고전적인 방식으로 만든 모델입니다 Sequential Keras의 API:
model = Sequential()
model.add(Conv2D(16, (5, 5), activation="relu",
input_shape=(28, 28,1)))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(32, (5, 5), activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))
model.add(Flatten())
model.add(Dense(128, activation="relu"))
model.add(Dense(len(CLASSES), activation="softmax"))
특별한 건 없다는 거, 잘 알겠습니다. 흥미로운 점은 이제 이 Sequential 모델로 학습 과정을 완전히 유연하게 바꿀 수 있다는 것입니다. 더 흥미로운 건, 이것이 커스터마이징의 끝이 아니라는 점입니다. 모델 내부에서 연산 흐름까지 완전히 제어할 수 있습니다. 예시는 다음과 같습니다:
class CustomModel(tensorflow.keras.Model):
def __init__(self):
super(CustomModel, self).__init__()
self.do1 = tf.keras.layers.Dropout(rate=0.2, input_shape=(shape,))
self.fc1 = tf.keras.layers.Dense(units=64, activation="relu")
self.do2 = tf.keras.layers.Dropout(rate=0.2)
self.fc2 = tf.keras.layers.Dense(units=64, activation="relu")
self.do3 = tf.keras.layers.Dropout(rate=0.2)
self.out = tf.keras.layers.Dense(units=1, activation="sigmoid")

def call(self, x):
x = self.do1(x)
x = self.fc1(x)
x = self.do2(x)
x = self.fc2(x)
x = self.do3(x)
return self.out(x)
TensorFlow에서 선언형과 명령형 API 설계가 어떻게 시너지를 내는지에 대해서는 다음을 참고하세요 여기연습으로, 위에서 본 것처럼 Sequential 모델 정의를 명령형 방식으로 바꿔 보세요.

이 섹션에서는 다음 단계를 차례대로 살펴봅니다:
  • 사용자 정의 설정에서 모델을 학습할 때 W&B로 손실과 정확도를 추적하기
  • 테스트 세트에서 무작위로 이미지 묶음을 선택하고 모델의 예측 값을 가져옵니다 (스포일러: FashionMNIST 데이터셋을 사용합니다). 모델을 학습하는 동안이제 이 이미지들과 예측값을 W&B로 함께 로그하려면 어떻게 해야 할까요?
그럼 시작해 봅시다.



이 데이터셋을 다뤄 본 경험이 있다면, 로딩과 전처리 단계는 꽤 간단합니다.
# Load the FashionMNIST dataset, scale the pixel values
(X_train, y_train), (X_test, y_test) = tf.keras.datasets.fashion_mnist.load_data()
X_train = X_train/255.
X_test = X_test/255.
아마 눈치채셨겠지만, 우리가 만든 Sequential 모델은 얕은 합성곱 신경망(CNN)입니다. 그리고 이미지가 CNN(특히 Keras로 정의한 모델)에서 제대로 작동하려면 채널 차원이 필요합니다. 이제 그 작업을 해봅시다.
# Reshape input data
X_train = X_train.reshape(-1, 28, 28, 1)
X_test = X_test.reshape(-1, 28, 28, 1)

이제 데이터셋을 배치로 묶어 봅시다:
# Batches of 64
train_ds = tf.data.Dataset.from_tensor_slices((X_train, y_train)).batch(64)
test_ds = tf.data.Dataset.from_tensor_slices((X_test, y_test)).batch(64)

이것으로 끝입니다!
여기에서 tf.data를 사용한 점에 주목하세요. TensorFlow의 데이터 모듈은 유연하고 빠른 데이터 파이프라인을 구축하는 데 유용한 기능을 많이 제공합니다. 데이터 모듈로 시작해 보세요. 여기.

모델 학습과 W&B 활용

모델은 이미 정의되어 있습니다. 모델을 학습하는 방법은 우리가 앞에서 다시 살펴본 다음 함수에 정의되어 있습니다:
# Train the model
@tf.function
def model_train(features, labels):
# Define the GradientTape context
with tf.GradientTape() as tape:
# Get the probabilities
predictions = model(features)
# Calculate the loss
loss = loss_func(labels, predictions)
# Get the gradients
gradients = tape.gradient(loss, model.trainable_variables)
# Update the weights
optimizer.apply_gradients(zip(gradients, model.trainable_variables))

# Update the loss and accuracy
train_loss(loss)
train_acc(labels, predictions)
마찬가지로, 모델을 평가하는 간단한 함수를 정의할 수 있습니다:
# Validating the model
@tf.function
def model_validate(features, labels):
predictions = model(features)
v_loss = loss_func(labels, predictions)

valid_loss(v_loss)
valid_acc(labels, predictions)
이제 모델 학습을 시작할 완벽한 준비가 되었습니다:

# Train the model for 5 epochs
for epoch in range(5):
# Run the model through train and test sets respectively
for (features, labels) in train_ds:
model_train(features, labels)

for test_features, test_labels in test_ds:
model_validate(test_features, test_labels)
# Grab the results
(loss, acc) = train_loss.result(), train_acc.result()
(val_loss, val_acc) = valid_loss.result(), valid_acc.result()
# Clear the current state of the metrics
train_loss.reset_states(), train_acc.reset_states()
valid_loss.reset_states(), valid_acc.reset_states()
# Local logging
template = "Epoch {}, loss: {:.3f}, acc: {:.3f}, val_loss: {:.3f}, val_acc: {:.3f}"
print (template.format(epoch+1,
loss,
acc,
val_loss,
val_acc))
# Logging with W&B
wandb.log({"train_loss": loss.numpy(),
"train_accuracy": acc.numpy(),
"val_loss": val_loss.numpy(),
"val_accuracy": val_acc.numpy()
})
get_sample_predictions() # More on this later

Locally, it prints something like so:

Epoch 1, loss: 0.544, acc: 0.802, val_loss: 0.429, val_acc: 0.845
Epoch 2, loss: 0.361, acc: 0.871, val_loss: 0.377, val_acc: 0.860
Epoch 3, loss: 0.309, acc: 0.888, val_loss: 0.351, val_acc: 0.869
Epoch 4, loss: 0.277, acc: 0.899, val_loss: 0.336, val_acc: 0.873
Epoch 5, loss: 0.252, acc: 0.908, val_loss: 0.323, val_acc: 0.882
그리고 계속해서 W&B 실행 페이지, 모든 플롯의 섬세함을 그대로 얻을 수 있습니다:



모델이 올바르게 학습되고 있는 것 같습니다. 학습과 검증 지표를 하나의 플롯에 겹쳐서 보고 싶다는 생각을 늘 해왔죠. W&B 덕분에 위처럼 개별 플롯을 볼 때보다 모델의 학습 진행 상황을 훨씬 더 명확하게 파악할 수 있습니다. W&B를 사용하면 정말 간단합니다 — 다음은 짧은 동영상 학습 정확도와 검증 정확도를 하나의 플롯에 겹쳐 그리는 방법에 대해.
이제 겹쳐진 플롯을 확인할 수 있습니다:





get_sample_predictions 함수

함수 정의를 살펴보겠습니다:
# Grab random images from the test and make predictions using
# the model *while it is training* and log them using WnB
def get_sample_predictions():
predictions = []
images = []
random_indices = np.random.choice(X_test.shape[0], 25)
for index in random_indices:
image = X_test[index].reshape(1, 28, 28, 1)
prediction = np.argmax(model(image).numpy(), axis=1)
prediction = CLASSES[int(prediction)]
images.append(image)
predictions.append(prediction)
wandb.log({"predictions": [wandb.Image(image, caption=prediction)
for (image, prediction) in zip(images, predictions)]})

위의 댓글에서 알 수 있듯이, get_sample_predictions 모델이 학습되는 동안 테스트 세트에서 무작위로 선택한 이미지와 해당 예측 값을 함께 기���하는 데 도움이 됩니다. 특히 다음을 사용하고 있다면 매우 간단하게 구현할 수 있습니다. WandbCallback 와 함께 data_type="image", label=CLASSES 인자들. 하지만 사용자 정의 학습 루프에서도 동일한 작업을 똑같이 수행할 수 있습니다.
이제 W&B 실행 페이지로 가서 맨 아래로 스크롤해 보면 쉽게 확인할 수 있습니다. get_sample_prediction 제 역할을 톡톡히 해주어:




결론

자, 이번 글은 여기까지입니다. 이제 이 글에서 소개한 TensorFlow 2.0의 기능들을 마음껏 실험해 보세요. 여러분이 학습 루프를 어떻게 맞춤화하고, 모델의 학습 진행 상황을 자동으로 추적하기 위해 W&B를 어떻게 활용하는지 보여 주세요.



이 글은 AI 번역본입니다. 오역이 의심되는 부분이 있다면 댓글로 알려 주세요. 원문 링크는 다음과 같습니다: 원문 보고서 보기