- 사전 제공 DNNClassifier Estimator로 구현된 Iris의 소스 코드는 여기를 참조하세요.
- 커스텀 Estimator로 구현된 Iris의 소스 코드는 여기를 참조하세요.
사전 제공 Estimator와 커스텀 Estimator의 비교
그림 1에 나오는 것처럼, 사전 제공 Estimator는
tf.estimator.Estimator 기본 클래스의 서브클래스이고, 커스텀 Estimator는
tf.estimator.Estimator:의 인스턴스입니다.
그림 1. 사전 제공 Estimator와 커스텀 Estimator는 모두 Estimator입니다.사전 제공 Estimator는 그 자체가 완성된 버전입니다. 하지만 때로는 Estimator의 동작을 보다 세부적으로 제어할 필요가 있습니다. 그것이 바로 커스텀 Estimator가 필요한 이유입니다.
커스텀 Estimator를 생성하여 원하는 동작을 수행할 수 있습니다. 숨겨진 계층을 특별한 방식으로 연결하고 싶으면 커스텀 Estimator를 작성하세요. 모델의 고유 측정함수를측정항목을 사용계산하려면 커스텀 Estimator를 작성하세요. 기본적으로 특정 문제에 맞게 최적화된 Estimator를 원하면 커스텀 Estimator를 작성하세요.
모델 함수(
model_fn)는 모델을 구현합니다. 사전 제공 Estimator와 커스텀 Estimator 사용 시 유일한 차이점은 다음과 같습니다.
- 사전 제공 Estimator의 경우, 누군가가 이미 모델 함수를 작성해 두었습니다.
- 커스텀 Estimator의 경우, 자신이 직접 모델 함수를 작성해야 합니다.
모델 함수는 모든 종류의 숨겨진 계층과 측정함수를 정의하는 광범위한 알고리즘을 구현할 수 있습니다. 입력 함수와 마찬가지로, 모든 모델 함수는 입력 매개변수로 구성된 표준 그룹을 받아들이고 출력 값으로 구성된 표준 그룹을 반환해야 합니다. 입력 함수가 Dataset API를 활용할 수 있는 것처럼, 모델 함수는 Layers API 및 Metrics API를 활용할 수 있습니다.
Iris를 사전 제공 Estimator로: 간략히 살펴보기
Iris를 커스텀 Estimator로 구현하는 방법을 시연하기 전에 이 시리즈의
제1부에서 Iris를 사전 제공 Estimator로 구현한 방법을 다시 떠올려 보세요. 제1부에서는 다음과 같이
사전 제공 Estimator의 인스턴스를 생성하는 방식으로 Iris 데이터세트에 완벽하게 연결된
심층 신경망을 만들었습니다.
# Instantiate a deep neural network classifier.
classifier = tf.estimator.DNNClassifier(
feature_columns=feature_columns, # The input features to our model.
hidden_units=[10, 10], # Two layers, each with 10 neurons.
n_classes=3, # The number of output classes (three Iris species).
model_dir=PATH) # Pathname of directory where checkpoints, etc. are stored.
앞의 코드는 다음과 같은 특성을 가진 심층 신경망을 만듭니다.
- 특성 열의 목록. (특성 열의 정의는 앞의 스니펫에는 표시되어 있지 않습니다.) Iris의 경우, 특성 열은 4개의 입력 특성을 숫자로 표현한 것입니다.
- 각각 10개의 뉴런을 가진 2개의 완전히 연결된 계층. 완전히 연결된 계층(조밀한 계층이라고도 함)은 후속 계층의 모든 뉴런에 연결됩니다.
- 3요소 목록으로 구성된 출력 계층. 이 목록의 요소는 모두 부동 소수점 값으로, 이들 값의 합은 1.0이어야 합니다(이 값은 확률 분포임).
- 학습된 모델과 다양한 체크포인트가 저장되는 디렉토리(PATH).
그림 2는 Iris 모델의 입력 계층, 숨겨진 계층 및 출력 계층을 보여줍니다. 명확성과 관련된 이유 때문에 각각의 숨겨진 계층에 4개의 노드만 그렸습니다.
그림 2. Iris의 구현에는 4개의 특성, 2개의 숨겨진 계층 및 로짓(logit) 출력 계층이 포함되어 있습니다.커스텀 Estimator로 동일한 Iris 문제를 해결하는 방법을 알아봅시다.
입력 함수
Estimator 프레임워크의 가장 큰 장점 중 하나는 데이터 파이프라인을 변경하지 않고 다양한 알고리즘으로 실험할 수 있다는 점입니다. 따라서
제1부에서 언급한 입력 함수 중 많은 부분을 재사용할 것입니다.
def my_input_fn(file_path, repeat_count=1, shuffle_count=1):
def decode_csv(line):
parsed_line = tf.decode_csv(line, [[0.], [0.], [0.], [0.], [0]])
label = parsed_line[-1] # Last element is the label
del parsed_line[-1] # Delete last element
features = parsed_line # Everything but last elements are the features
d = dict(zip(feature_names, features)), label
return d
dataset = (tf.data.TextLineDataset(file_path) # Read text file
.skip(1) # Skip header row
.map(decode_csv, num_parallel_calls=4) # Decode each line
.cache() # Warning: Caches entire dataset, can cause out of memory
.shuffle(shuffle_count) # Randomize elems (1 == no operation)
.repeat(repeat_count) # Repeats dataset this # times
.batch(32)
.prefetch(1) # Make sure you always have 1 batch ready to serve
)
iterator = dataset.make_one_shot_iterator()
batch_features, batch_labels = iterator.get_next()
return batch_features, batch_labels
입력 함수는 다음 2개의 값을 반환합니다.
- batch_features: Dictionary사전입니다. Dictionary사전의 키는 특성의 이름이고 사전의 값은 특성의 값입니다.
- batch_labels: 배치의 레이블 값 목록입니다.
입력 함수에 대한 자세한 내용은
제1부를 참조하세요.
특성 열 생성
시리즈의
제2부에서 자세히 설명했듯이, 모델의 특성 열을 정의하여 각 특성의 표현을 지정해야 합니다. 사전 제공 Estimator를 사용하든 커스텀 Estimator를 사용하든, 동일한 방식으로 특성 열을 정의합니다. 예를 들어, 다음은 Iris 데이터세트의 네 가지 특성(모두 숫자)을 나타내는 특성 열을 생성하는 코드입니다.
feature_columns = [
tf.feature_column.numeric_column(feature_names[0]),
tf.feature_column.numeric_column(feature_names[1]),
tf.feature_column.numeric_column(feature_names[2]),
tf.feature_column.numeric_column(feature_names[3])
]
모델 함수 작성
이제 커스텀 Estimator용
model_fn을 작성할 준비가 되었습니다. 함수 선언부터 시작해 봅시다.
def my_model_fn(
features, # This is batch_features from input_fn
labels, # This is batch_labels from input_fn
mode): # Instance of tf.estimator.ModeKeys, see below
처음 2개의 인자는 입력 함수에서 반환된 특성과 레이블입니다. 즉,
features 및
labels는 모델에서 사용할 데이터에 대한 핸들입니다.
mode 인자는 호출자가 훈련, 예측 또는 평가를 요청하는지 여부를 나타냅니다.
일반적인 모델 함수를 구현하려면 다음을 수행해야 합니다.
- 모델의 계층을 정의합니다.
- 세 가지 서로 다른 모드로 모델의 동작을 지정합니다.
모델의 계층 정의
커스텀 Estimator가 심층 신경망을 생성하는 경우 다음 3개의 계층을 정의해야 합니다.
- 입력 계층
- 하나 이상의 숨겨진 계층
- 출력 계층
Layers API(
tf.layers)를 사용하여 숨겨진 계층과 출력 계층을 정의합니다.
커스텀 Estimator가 선형 모델을 생성하는 경우 단일 계층을 생성하기만 하면 되며, 이에 대해서는 다음 섹션에서 설명하겠습니다.
입력 계층 정의
tf.feature_column.input_layer를 호출하여 심층 신경망의 입력 계층을 정의합니다. 예:
# Create the layer of input
input_layer = tf.feature_column.input_layer(features, feature_columns)
앞의 행은 입력 계층을 생성하고 입력 함수를 통해
features를 읽고 이전에 정의한
feature_columns를 통해 필터링합니다. 특성을 통해 데이터를 표현하는 다양한 방법에 대한 자세한 내용은
제2부를 참조하세요.
선형 모델의 입력 계층을 생성하려면
tf.feature_column.input_layer 대신
tf.feature_column.linear_model을 호출하세요. 선형 모델에는 숨겨진 계층이 없으므로
tf.feature_column.linear_model의 반환 값이 입력 계층과 출력 계층의 역할을 모두 수행합니다. 즉, 이 함수의 반환 값은 예측입니다.
숨겨진 계층 설정
심층 신경망을 생성하는 경우 하나 이상의 숨겨진 계층을 정의해야 합니다. Layers API는 컨벌루션, 풀링 및 드롭아웃 계층을 포함한 모든 숨겨진 계층 유형을 정의하는 다양한 함수 세트를 제공합니다. Iris의 경우, 단순히
tf.layers.Dense를 두 번 호출하여 각각 10개의 뉴런이 있는 2개의 조밀한 숨겨진 계층을 생성합니다. '조밀하다'는 것은 첫 번째 숨겨진 계층의 각 뉴런이 두 번째 숨겨진 계층의 각 뉴런에 연결된다는 의미입니다. 다음은 관련 코드입니다.
# Definition of hidden layer: h1
# (Dense returns a Callable so we can provide input_layer as argument to it)
h1 = tf.layers.Dense(10, activation=tf.nn.relu)(input_layer)
# Definition of hidden layer: h2
# (Dense returns a Callable so we can provide h1 as argument to it)
h2 = tf.layers.Dense(10, activation=tf.nn.relu)(h1)
tf.layers.Dense에 대한
inputs 매개변수는 선행 계층을 식별합니다. 선행
h1 계층은 입력 계층입니다.
그림 3. 입력 계층은 숨겨진 계층 1로 전달됩니다.
h2에 대한 선행 계층은
h1입니다. 따라서 이제 계층 문자열이 다음과 같이 나타납니다.
그림 4. 숨겨진 계층 1은 숨겨진 계층 2로 전달됩니다.
tf.layers.Dense의 첫 번째 인자는 출력 뉴런 수를 정의합니다(이 경우에는 10).
activation 매개변수는 활성화 함수를 정의합니다(이 경우에는
Relu).
tf.layers.Dense는 다양한 정규화 매개변수를 설정할 수 있는 기능을 비롯한 많은 추가 기능을 제공합니다. 하지만 간단히 하기 위해 다른 매개변수의 기본값을 그냥 받아들이겠습니다. 또한,
tf.layers를 살펴보면 소문자 버전을 목격할 수 있습니다(예:
tf.layers.dense). 일반적으로 대문자로 시작하는 클래스 버전을 사용해야 합니다(
tf.layers.Dense).
출력 계층
tf.layers.Dense를 다시 호출하여 출력 계층을 정의하겠습니다.
# Output 'logits' layer is three numbers = probability distribution
# (Dense returns a Callable so we can provide h2 as argument to it)
logits = tf.layers.Dense(3)(h2)
출력 계층은
h2에서 입력을 받는다는 점에 유의하세요. 따라서 다음과 같이 전체 계층 세트가 연결됩니다.
그림 5. 숨겨진 계층 2는 출력 계층으로 전달됩니다.출력 계층을 정의할 때
units 매개변수는 가능한 출력 값의 수를 지정합니다. 따라서
units를 3으로 설정하여
tf.layers.Dense 함수가
3요소 로짓 벡터를 구성합니다. 로짓 벡터의 각 셀에는 각각 Setosa, Versicolor 또는 Virginica인 Iris의 확률이 포함되어 있습니다.
출력 계층은 최종 계층이므로
tf.layers.Dense를 호출하면 선택적
activation 매개변수가 생략됩니다.
훈련, 평가 및 예측 구현
모델 함수 생성의 마지막 단계는 예측, 평가 및 훈련을 구현하는 분기 코드를 작성하는 것입니다.
모델 함수는 누군가 Estimator의
train,
evaluate 또는
predict 메서드를 호출할 때마다 호출됩니다. 모델 함수의 서명은 다음과 같은 형태입니다.
def my_model_fn(
features, # This is batch_features from input_fn
labels, # This is batch_labels from input_fn
mode): # Instance of tf.estimator.ModeKeys, see below
세 번째 인자
mode에 주목하시기 바랍니다. 다음 표에 나오는 것처럼 누군가
train,
evaluate 또는
predict를 호출하면 Estimator 프레임워크는
mode parameter가 다음과 같이 설정된 모델 함수를 호출합니다.
표 2. 모드의 값.
호출자가 커스텀 Estimator 메소드 호출...
|
Estimator 프레임워크는 mode 매개변수가 다음으로 설정된 모델 함수 호출...
|
train()
|
ModeKeys.TRAIN
|
evaluate()
|
ModeKeys.EVAL
|
predict()
|
ModeKeys.PREDICT
|
예를 들어 커스텀 Estimator를 인스턴스화하여
classifier라는 객체를 생성한다고 가정해 봅시다. 이어서 다음 호출을 수행합니다. (이 시점에서
my_input_fn의 매개변수는 신경 쓰지 마세요.)
classifier.train(
input_fn=lambda: my_input_fn(FILE_TRAIN, repeat_count=500, shuffle_count=256))
그러면 Estimator 프레임워크는
mode가
ModeKeys.TRAIN으로 설정된
model 함수를 호출합니다.
모델 함수는 3개의
mode 값을 모두 처리할 코드를 제공해야 합니다. 각 모드 값에 대해, 코드에서 호출자가 필요로 하는 정보가 포함된
tf.estimator.EstimatorSpec의 인스턴스를 반환해야 합니다. 각 모드를 살펴봅시다.
PREDICT
model_fn이
mode == ModeKeys.PREDICT 상태로 호출되면, 모델 함수는 다음 정보를 포함한
tf.estimator.EstimatorSpec을 반환해야 합니다.
- 모드(tf.estimator.ModeKeys.PREDICT)
- 예측
예측을 수행하기 전에 모델에 대한 훈련을 실시해야 합니다. 훈련된 모델은 Estimator를 인스턴스화할 때 설정된 디스크의 디렉토리에 저장됩니다.
여기서 다루는 사례에서는 예측 생성 코드가 다음과 같습니다.
# class_ids will be the model prediction for the class (Iris flower type)
# The output node with the highest value is our prediction
predictions = { 'class_ids': tf.argmax(input=logits, axis=1) }
# Return our prediction
if mode == tf.estimator.ModeKeys.PREDICT:
return tf.estimator.EstimatorSpec(mode, predictions=predictions)
블록이 놀라울 정도로 짧습니다. 이 코드들은 단지 굴러오는 예측을 잡는 긴 호스 끝의 통일 뿐입니다. 결국, Estimator는 예측을 수행하기 위한 어려운 과정을 이미 모두 완료했습니다.
- 입력 함수는 모델 함수에 추론할 데이터(특성 값)를 제공합니다.
- 모델 함수는 이러한 특성 값을 특성 열로 변환합니다.
- 모델 함수는 이전에 훈련된 모델을 통해 해당 특성 열을 실행합니다.
출력 계층은 입력되는 꽃인 3가지 Iris 종 각각의 값을 포함하는
logits 벡터입니다.
tf.argmax 메서드는 해당
logits 벡터에서 가장 높은 값을 가진 Iris 종을 선택합니다.
가장 높은 값은
class_ids라는 사전 키에 할당됩니다. 우리는
tf.estimator.EstimatorSpec의 predictions 매개변수를 통해 해당 사전을 반환합니다. 그러면 호출자가 Estimator의
predict 메서드로 다시 전달된 사전을 검사하여 예측을 가져올 수 있습니다.
EVAL
model_fn이
mode == ModeKeys.EVAL로 호출되면 모델 함수가 모델을 평가하여 손실과 아마도 하나 또는 그 이상의 측정함수를 반환해야 합니다.
tf.losses.sparse_softmax_cross_entropy를 호출하여 손실을 계산할 수 있습니다. 전체 코드는 다음과 같습니다.
# To calculate the loss, we need to convert our labels
# Our input labels have shape: [batch_size, 1]
labels = tf.squeeze(labels, 1) # Convert to shape [batch_size]
loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)
이제는 측정함수에 주목해 봅시다. 측정함수 반환은 선택 사항이지만 대부분의 커스텀 Estimator는 하나 이상의 측정함수를 반환합니다. TensorFlow는 다양한 종류의 측정함수를계산하는 Metrics API(
tf.metrics)를 제공합니다. 간단히 하기 위해, accuracy만 반환하도록 하겠습니다.
tf.metrics.accuracy는 예측을 '실제 레이블', 즉 입력 함수가 제공한 레이블과 비교합니다.
tf.metrics.accuracy 함수에서는 레이블과 예측이 (이전에 작성한 것과) 동일한 형태를 가져야 합니다. 다음은
tf.metrics.accuracy에 대한 호출입니다.
# Calculate the accuracy between the true labels, and our predictions
accuracy = tf.metrics.accuracy(labels, predictions['class_ids'])
모델이
mode == ModeKeys.EVAL로 호출되면, 모델 함수는 다음 정보를 포함한
tf.estimator.EstimatorSpec을 반환합니다.
- mode(즉, tf.estimator.ModeKeys.EVAL)
- 모델의 손실
- 일반적으로 사전에 포함된 하나 이상의 측정함수
따라서 우리는 유일한 측정함수(
my_accuracy)을를 포함하는 사전을 만들 것입니다. 다른 측정함수를 계산했다면 동일한 사전에 추가 키/값 쌍으로 추가했을 것입니다. 그런 다음 해당 사전을
tf.estimator.EstimatorSpec의
eval_metric_ops 인자에 전달합니다. 블록은 다음과 같습니다.
# Return our loss (which is used to evaluate our model)
# Set the TensorBoard scalar my_accurace to the accuracy
# Obs: This function only sets value during mode == ModeKeys.EVAL
# To set values during training, see tf.summary.scalar
if mode == tf.estimator.ModeKeys.EVAL:
return tf.estimator.EstimatorSpec(
mode,
loss=loss,
eval_metric_ops={'my_accuracy': accuracy})
TRAIN
model_fn이
mode == ModeKeys.TRAIN으로 호출되면 모델 함수가 모델을 훈련해야 합니다.
먼저 옵티마이저 객체를 인스턴스화해야 합니다. 다음 코드 블록에서 우리는 Adagrad(
tf.train.AdagradOptimizer)를 선택했습니다. 역시 Adagrad를 사용하는
DNNClassifier을 모방할 것이기 때문입니다.
tf.train 패키지는 다른 많은 옵티마이저 도구를 제공하므로 자유롭게 실험해 보세요.
이어서 옵티마이저에 대한 목표를 설정하여 모델을 훈련합니다. 목표는 단순히
loss를 최소화하는 것입니다. 그 목표를 분명히 하기 위해
minimize 메서드를 호출합니다.
아래 코드에서 선택적
global_step 인자는 TensorFlow가 처리된 배치 수를 계산하는 데 사용하는 변수를 지정합니다.
global_step을
tf.train.get_global_step으로 설정하면 멋지게 작동할 것입니다. 또한, 훈련 도중에 TensorBoard에
my_accuracy를 보고하기 위해
tf.summary.scalar를 호출합니다. 이 과정에 대한 자세한 설명은 아래의 TensorBoard 섹션을 참조하세요.
optimizer = tf.train.AdagradOptimizer(0.05)
train_op = optimizer.minimize(
loss,
global_step=tf.train.get_global_step())
# Set the TensorBoard scalar my_accuracy to the accuracy
tf.summary.scalar('my_accuracy', accuracy[1])
모델이
mode == ModeKeys.TRAIN으로 호출되면 모델 함수는 다음 정보를 포함한
tf.estimator.EstimatorSpec을 반환해야 합니다.
- 모드(즉, tf.estimator.ModeKeys.TRAIN)
- 손실
- 훈련 연산의 결과
코드는 다음과 같습니다.
# Return training operations: loss and train_op
return tf.estimator.EstimatorSpec(
mode,
loss=loss,
train_op=train_op)
이제 모델 함수가 완성되었습니다!
커스텀 Estimator
새 커스텀 Estimator를 만들고 나면 얼른 사용해보고 싶을 것입니다. 다음과 같이
Estimator 기본 클래스를 통해 커스텀
Estimator의 인스턴스화부터 시작하세요.
classifier = tf.estimator.Estimator(
model_fn=my_model_fn,
model_dir=PATH) # Path to where checkpoints etc are stored
Estimator를 사용하여 훈련, 평가 및 예측하는 나머지 코드는
제1부에서 설명한 사전 제공
DNNClassifier의 경우와 동일합니다. 예를 들어, 다음 줄은 모델 교육을 트리거합니다.
classifier.train(
input_fn=lambda: my_input_fn(FILE_TRAIN, repeat_count=500, shuffle_count=256))
TensorBoard
제1부에서와 같이 TensorBoard에서 일부 훈련 결과를 볼 수 있습니다. 이러한 보고 내용을 보려면 다음과 같이 명령줄에서 TensorBoard를 시작하세요.
# Replace PATH with the actual path passed as model_dir
tensorboard --logdir=PATH
이어서 다음 URL로 이동합니다.
localhost:6006
모든 사전 제공 Estimator는 많은 정보를 TensorBoard에 자동으로 기록합니다. 그러나 커스텀 Estimator를 사용하면 TensorBoard가 단 하나의 기본 로그(손실 그래프)와 TensorBoard에 명시적으로 기록하도록 지시한 정보만 제공합니다. 따라서 TensorBoard는 커스텀 Estimator에서 다음을 생성합니다.
그림 6. TensorBoard는 3개의 그래프를 표시합니다.
간단히 말해, 3개의 그래프가 나타내는 내용은 다음과 같습니다.
- global_step/sec: 특정 배치(x축)에서 초당 처리된 배치(y축) 수(그라데이션 업데이트)를 보여주는 성능 표시기입니다. 이 보고서를 보려면 (tf.train.get_global_step()에서와 마찬가지로) global_step을 제공해야 합니다. 또한, 충분히 오랜 시간 동안 훈련해야 합니다. 이를 위해 train 메서드를 호출할 때 Estimator에 500 에포크(epoch) 동안의 교육을 요구합니다.
- loss: 보고된 손실입니다. 실제 손실 값(y축)은 그다지 의미가 없습니다. 중요한 것은 그래프의 모양입니다.
- my_accuracy: 다음 두 가지를 모두 호출할 때 기록된 정확도입니다.
- eval_metric_ops={'my_accuracy': accuracy}): EVAL 동안(EstimatorSpec 반환 시)
- tf.summary.scalar('my_accuracy', accuracy[1]): TRAIN 동안
my_accuracy 및
loss 그래프에서 다음 사항에 유의하세요.
- 주황색 선은 TRAIN을 나타냅니다.
- 파란색 점은 EVAL을 나타냅니다.
TRAIN 동안 주황색 값은 배치가 처리될 때 계속 기록되므로 x축 범위에 걸친 그래프가 됩니다. 반대로
EVAL은 모든 평가 단계 처리에서 단 하나의 값만 산출합니다.
그림 7에 제시된 것처럼 왼쪽의 훈련 및 평가에 대한 보고 기능을 보거나 선택적으로 비활성화/활성화할 수 있습니다. (그림 7은 두 가지 모두에 대해 계속 보고하고 있음을 보여줍니다.)
그림 7. 보고 기능 활성화 또는 비활성화
주황색 그래프를 보려면 전역 단계를 지정해야 합니다. 가장 좋은 방법은
global_steps/sec 보고와 함께
tf.train.get_global_step()을
optimizer.minimize 호출의 인자로 전달하여 항상 전역 단계를 등록하는 것입니다.
요약
사전 제공 Estimator는 새 모델을 신속하게 만드는 효과적인 방법일 수 있지만, 종종 커스텀 Estimator가 제공하는 추가적인 유연성이 필요한 경우가 있습니다. 다행히 사전 제공 Estimator와 커스텀 Estimator는 동일한 프로그래밍 모델을 따릅니다. 실제로 유일한 차이점은 커스텀 Estimator의 경우 모델 함수를 작성해야 한다는 점입니다. 그 밖의 모든 것은 동일합니다!
자세한 내용은 다음을 참조하세요.
- 이 블로그 게시물의 전체 소스 코드.
- 커스텀 Estimator를 사용하는 MNIST의 공식 TensorFlow 구현. 또한, 이 모델은 특성 열(그리고 input_layer)을 사용하지 않고 원시 픽셀에서 숫자 값을 취하는 예시입니다.
- 커스텀 Estimator를 사용하여 큐레이팅된 예시를 추가로 포함할 수 있는 TensorFlow 공식 모델 저장소.
- TensorFlow Dev Summit에서 제공하는 TensorBoard 동영상에서는 재미있고 교육적인 방식으로 TensorBoard를 소개합니다.
다음 시간까지 TensorFlow 코딩을 즐기시기 바랍니다!