이 포스트는 Francesco Saverio 님의 How to use Dataset in TensorFlow 를 한글로 번역한 것 입니다.

TensorFlow에서 Dataset을 사용하는 방법

TensorFlow에서 feed-dict만을 사용해서 데이터를 처리하는 것은 느리고 별로 권장되지는 않는다. 모델에 데이터를 제대로 공급하려면 입력 파이프라인을 만들어서 GPU로 들어올 데이터를 멈춰있게 하지 않아야 한다.

다행스럽게도 TensorFlow에서는 Dataset 이라는 built-in-API를 제공하고 있어서 위의 작업을 쉽게 처리할 수 있다. 이 포스트에서는 입력 파이프라인을 만들어서 모델에 데이터를 효율적으로 공급하는 방법을 살펴볼 것이다. 또한, 흔하게 볼 수 있는 예시를 다루면서 Dataset의 기본적인 메커니즘을 설명할 것이다.


개요

Dataset을 사용하려면 세 가지 단계를 거쳐야한다.

  1. 데이터 불러오기. 사용하려는 데이터로부터 Dataset 인스턴스를 만든다.
  2. Iterator(반복자) 생성하기. 생성된 데이터를 사용해서 Iterator 인스턴스를 만들어 Dataset을 iterate시킨다.
  3. 데이터 사용하기. 생성된 iterator를 사용해서 모델에 공급할 dataset으로부터 요소를 가져올 수 있다.

데이터 불러오기

일단 dataset안에 넣을 데이터가 필요하다.

numpy에서 불러오기

numpy 배열이 있고 그걸 tensorflow로 넣는 흔한 케이스이다.

# create a random vector of shape (100,2)
x = np.random.sample((100,2))
# make a dataset from a numpy array
dataset = tf.data.Dataset.from_tensor_slices(x)

또한 데이터를 특성(feature)과 라벨(label)로 나누어 사용하는 경우처럼, 한 개 이상의 numpy 배열을 넣을 수도 있다.

features, labels = (np.random.sample((100,2)), np.random.sample((100,1)))
dataset = tf.data.Dataset.from_tensor_slices((features,labels))

tensor에서 불러오기

tensor를 사용해서 dataset을 초기화 할 수도 있다.

# using a tensor
dataset = tf.data.Dataset.from_tensor_slices(tf.random_uniform([100, 2]))

Placeholder에서 불러오기

dataset안의 데이터의 동적 변경을 할 때 유용한 방법인데, 자세한 내용은 아래에서 살펴보겠다.

x = tf.placeholder(tf.float32, shape=[None,2])
dataset = tf.data.Dataset.from_tensor_slices(x)

generator에서 불러오기

generator를 사용해서 dataset을 초기화 할 수 있는데, 데이터의 원소들이 다른 크기를 가지고 있을 때 유용하다. 이런 경우에 tensor를 만들 때 사용할 데이터의 type과 shape도 지정해야 한다.

# from generator
sequence = np.array([[[1]],[[2],[3]],[[3],[4],[5]]])
def generator():
    for el in sequence:
        yield el
dataset = tf.data.Dataset().batch(1).from_generator(generator,
                                           output_types= tf.int64, 
                                           output_shapes=(tf.TensorShape([None, 1])))
iter = dataset.make_initializable_iterator()
el = iter.get_next()
with tf.Session() as sess:
    sess.run(iter.initializer)
    print(sess.run(el))
    print(sess.run(el))
    print(sess.run(el))

출력은 다음과 같다.

[[1]]
[[2]
 [3]]
[[3]
 [4]
 [5]]

csv파일에서 불러오기

csv파일에서 dataset으로 직접적으로 읽어올 수도 있다. 다음과 같이 트윗(text)과 좋아요(sentiment)가 들어있는 csv파일이 있다고 해보자.

tweets.csv

여기서 tf.contrib.data.make_csv_dataset을 사용하면 쉽게 Dataset을 만들 수 있다. Iterator는 column의 이름을 key로 하고 row 값을 가진 Tensor를 value로 하는 딕셔너리를 만든다.

# load a csv
CSV_PATH = './tweets.csv'
dataset = tf.contrib.data.make_csv_dataset(CSV_PATH, batch_size=32)
iter = dataset.make_one_shot_iterator()
next = iter.get_next()
print(next) # next is a dict with key=columns names and value=column data
inputs, labels = next['text'], next['sentiment']
with  tf.Session() as sess:
    sess.run([inputs, labels])

next는 다음을 나타낸다.

{'sentiment': <tf.Tensor 'IteratorGetNext_15:0' shape=(?,) dtype=int32>,
'text': <tf.Tensor 'IteratorGetNext_15:1' shape=(?,) dtype=string>}

Iterator(반복자) 생성하기

Dataset을 생성하고 나면 어떻게 데이터를 받을 수 있을까? 이 때는 Iterator를 사용해서 dataset을 통해 iterate하고 데이터에서 실제 값을 받아올 수 있다.
Iterator에는 다음과 같이 네 가지 타입이 존재한다.

  1. One shot. Dataset을 통해 한 번만 iterate할 수 있고, 여기에 추가적으로 어떠한 값도 공급할 수 없다.
  2. Initializable. feed-dict를 통해서 초기화 연산 호출과 새로운 데이터 전달을 동적으로 변경할 수 있다. feed-dict는 데이터를 담을 수 있는 버킷이다.
  3. Reinitializable. 다른 Dataset을 통해서 초기화 될 수 있다. 셔플과 같이 추가적인 변형이 필요한 training dataset과 testing dataset을 가지고 있을 때 효과적으로 쓸 수 있다. 마치 각기 다른 컨테이너를 고르는 타워 크레인같은 역할을 한다.
  4. Feedable. 사용하려는 iterator를 선택하는데 사용할 수 있다. 위의 비유와 연관지으면 타워 크레인을 고르는 타워 크레인이라고 할 수 있겠다. 필자의 의견으로는 쓸모가 없어보인다.

One-shot Iterator

가장 간단한 iterator이다. 처음의 예를 사용하면

x = np.random.sample((100,2))
# make a dataset from a numpy array
dataset = tf.data.Dataset.from_tensor_slices(x)
# create the iterator
iter = dataset.make_one_shot_iterator()

그러면 데이터를 포함할 tensor를 얻기 위해서 get_next()를 호출해야 한다.

...
# create the iterator
iter = dataset.make_one_shot_iterator()
el = iter.get_next()

값을 보려면 el 을 실행시키면 된다.

with tf.Session() as sess:
    print(sess.run(el)) # output: [ 0.42116176  0.40666069]

Initializable Iterator

데이터의 런타임을 변경할 수 있는 동적인 dataset을 만들고 싶을 때, placeholder를 사용할 수 있다. 그리고 feed-dict를 사용해서 placeholder를 초기화 할 수 있는데, 이게 initializable iterator로 수행되는 것이다. 아까 위에서 했던 예를 사용하면 다음과 같은데,

# using a placeholder
x = tf.placeholder(tf.float32, shape=[None,2])
dataset = tf.data.Dataset.from_tensor_slices(x)
data = np.random.sample((100,2))
iter = dataset.make_initializable_iterator() # create the iterator
el = iter.get_next()
with tf.Session() as sess:
    # feed the placeholder with data
    sess.run(iter.initializer, feed_dict={ x: data }) 
    print(sess.run(el)) # output [ 0.52374458  0.71968478]

이때 make_initializable_iterator을 호출 한다. 그러면 sess가 있는 with 블록 안에서 데이터를 보내기 위해서 initializer명령어를 실행시키는데, 여기에서는 랜덤 numpy 배열이 데이터에 해당한다.

이제 흔하게 볼 수 있는 train과 test set이 있는 상황을 생각해보자.

train_data = (np.random.sample((100,2)), np.random.sample((100,1)))
test_data = (np.array([[1,2]]), np.array([[0]]))

그러면 모델을 학습시킨 후에 test dataset으로 평가해야 할 텐데, iterator를 초기화해서 사용한다.

# initializable iterator to switch between dataset
EPOCHS = 10
x, y = tf.placeholder(tf.float32, shape=[None,2]), tf.placeholder(tf.float32, shape=[None,1])
dataset = tf.data.Dataset.from_tensor_slices((x, y))
train_data = (np.random.sample((100,2)), np.random.sample((100,1)))
test_data = (np.array([[1,2]]), np.array([[0]]))
iter = dataset.make_initializable_iterator()
features, labels = iter.get_next()
with tf.Session() as sess:
#     initialise iterator with train data
    sess.run(iter.initializer, feed_dict={ x: train_data[0], y: train_data[1]})
    for _ in range(EPOCHS):
        sess.run([features, labels])
#     switch to test data
    sess.run(iter.initializer, feed_dict={ x: test_data[0], y: test_data[1]})
    print(sess.run([features, labels]))

Reinitializable Iterator

데이터끼리의 동적 변경을 하는 initializable iterator와 컨셉은 비슷한데, 같은 dataset에 새로운 데이터를 넣는 것 대신에 dataset 자체를 바꿀 것이다. 아까처럼 train과 test data가 있다고 해보자.

# making fake data using numpy
train_data = (np.random.sample((100,2)), np.random.sample((100,1)))
test_data = (np.random.sample((10,2)), np.random.sample((10,1)))

여기서 두 개의 Dataset을 만들 수 있는데,

# create two datasets, one for training and one for test
train_dataset = tf.data.Dataset.from_tensor_slices(train_data)
test_dataset = tf.data.Dataset.from_tensor_slices(test_data)

여기에서 generic Iterator를 만들어보자.

# create a iterator of the correct shape and type
iter = tf.data.Iterator.from_structure(train_dataset.output_types,
                                           train_dataset.output_shapes)

그리고 두 개의 초기화 연산도 만들자.

# create the initialisation operations
train_init_op = iter.make_initializer(train_dataset)
test_init_op = iter.make_initializer(test_dataset)

전과 같이 get_next를 통해서 다음 원소를 얻을 수 있다.

features, labels = iter.get_next()

이제 session을 사용해서 직접 두 개의 초기화 연산을 실행 시킬 수 있는데, 위에 했던걸 합치면 다음과 같다.

# Reinitializable iterator to switch between Datasets
EPOCHS = 10
# making fake data using numpy
train_data = (np.random.sample((100,2)), np.random.sample((100,1)))
test_data = (np.random.sample((10,2)), np.random.sample((10,1)))
# create two datasets, one for training and one for test
train_dataset = tf.data.Dataset.from_tensor_slices(train_data)
test_dataset = tf.data.Dataset.from_tensor_slices(test_data)
# create a iterator of the correct shape and type
iter = tf.data.Iterator.from_structure(train_dataset.output_types,
                                           train_dataset.output_shapes)
features, labels = iter.get_next()
# create the initialisation operations
train_init_op = iter.make_initializer(train_dataset)
test_init_op = iter.make_initializer(test_dataset)
with tf.Session() as sess:
    sess.run(train_init_op) # switch to train dataset
    for _ in range(EPOCHS):
        sess.run([features, labels])
    sess.run(test_init_op) # switch to val dataset
    print(sess.run([features, labels]))

Feedable Iterator

reinitializable iterator와 매우 비슷한데, dataset의 전환 대신에 iterator의 전환을 한다. 다음과 같이 두 개의 dataset을 만들고,

train_dataset = tf.data.Dataset.from_tensor_slices((x,y))
test_dataset = tf.data.Dataset.from_tensor_slices((x,y))

그 다음에 iterator를 만들 수 있다. 여기에서는 initializable iterator를 사용하는데, one shot iterator 또한 사용가능하다.

train_iterator = train_dataset.make_initializable_iterator()
test_iterator = test_dataset.make_initializable_iterator()

이제 동적으로 변경이 가능한 placeholder인 handle을 만든다.

handle = tf.placeholder(tf.string, shape=[])

그리고 전과 같이, dataset의 shape를 이용해서 generic iterator를 만든다.

iter = tf.data.Iterator.from_string_handle(
    handle, train_dataset.output_types, train_dataset.output_shapes)

또 get_next로 다음 원소를 받을 수 있고,

next_elements = iter.get_next()

iterator의 변경을 위해서 next_elements을 실행시켜서 feed_dict에 정확한 handle이 들어갈 수 있게 한다. 예를 들어 train set에서 한 개의 원소를 받아오려면 다음과 같이 한다.

sess.run(next_elements, feed_dict = {handle: train_handle})

initializable iterator를 사용하려면 그냥 시작 전에 초기화 시켜주기만 하면 된다.

sess.run(train_iterator.initializer, feed_dict={ x: train_data[0], y: train_data[1]})
sess.run(test_iterator.initializer, feed_dict={ x: test_data[0], y: test_data[1]})

이걸 종합해보면 다음과 같다.

# feedable iterator to switch between iterators
EPOCHS = 10
# making fake data using numpy
train_data = (np.random.sample((100,2)), np.random.sample((100,1)))
test_data = (np.random.sample((10,2)), np.random.sample((10,1)))
# create placeholder
x, y = tf.placeholder(tf.float32, shape=[None,2]), tf.placeholder(tf.float32, shape=[None,1])
# create two datasets, one for training and one for test
train_dataset = tf.data.Dataset.from_tensor_slices((x,y))
test_dataset = tf.data.Dataset.from_tensor_slices((x,y))
# create the iterators from the dataset
train_iterator = train_dataset.make_initializable_iterator()
test_iterator = test_dataset.make_initializable_iterator()
# same as in the doc https://www.tensorflow.org/programmers_guide/datasets#creating_an_iterator
handle = tf.placeholder(tf.string, shape=[])
iter = tf.data.Iterator.from_string_handle(
    handle, train_dataset.output_types, train_dataset.output_shapes)
next_elements = iter.get_next()

with tf.Session() as sess:
    train_handle = sess.run(train_iterator.string_handle())
    test_handle = sess.run(test_iterator.string_handle())
    
    # initialise iterators. 
    sess.run(train_iterator.initializer, feed_dict={ x: train_data[0], y: train_data[1]})
    sess.run(test_iterator.initializer, feed_dict={ x: test_data[0], y: test_data[1]})
    
    for _ in range(EPOCHS):
        x,y = sess.run(next_elements, feed_dict = {handle: train_handle})
        print(x, y)
        
    x,y = sess.run(next_elements, feed_dict = {handle: test_handle})
    print(x,y)

데이터 사용하기

위의 예제에서 Dataset에 있는 next 원소의 값을 출력하기 위해서 session을 사용했다.

...
next_el = iter.get_next()
...
print(sess.run(next_el)) # will output the current element

모델에 데이터를 공급하기 위해서는 get_next()로 만들어진 tensor로 공급해야한다.

다음 부분에서는 이전의 예제에서 사용했던 두 개의 numpy 배열을 가진 Dataset을 사용했다. 데이터를 batch로 처리하는 데 필요한 차원을 추가하려면 다른 numpy 배열에 .random.sample을 래핑해야한다.

# using two numpy arrays
features, labels = (np.array([np.random.sample((100,2))]), 
                    np.array([np.random.sample((100,1))]))
dataset = tf.data.Dataset.from_tensor_slices((features,labels)).repeat().batch(BATCH_SIZE)

그리고 언제나 그랬듯이 iterator를 만들어야한다.

iter = dataset.make_one_shot_iterator()
x, y = iter.get_next()

간단한 neural network model을 만들어보았다.

# make a simple model
net = tf.layers.dense(x, 8) # pass the first value from iter.get_next() as input
net = tf.layers.dense(net, 8)
prediction = tf.layers.dense(net, 1)
loss = tf.losses.mean_squared_error(prediction, y) # pass the second value from iter.get_net() as label
train_op = tf.train.AdamOptimizer().minimize(loss)

첫 번째 레이어의 입력과 loss function의 라벨로 iter.get_next()에서 나온 Tensor를 직접적으로 사용한다. 이제 종합해보자.

EPOCHS = 10
BATCH_SIZE = 16
# using two numpy arrays
features, labels = (np.array([np.random.sample((100,2))]), 
                    np.array([np.random.sample((100,1))]))
dataset = tf.data.Dataset.from_tensor_slices((features,labels)).repeat().batch(BATCH_SIZE)
iter = dataset.make_one_shot_iterator()
x, y = iter.get_next()
# make a simple model
net = tf.layers.dense(x, 8, activation=tf.tanh) # pass the first value from iter.get_next() as input
net = tf.layers.dense(net, 8, activation=tf.tanh)
prediction = tf.layers.dense(net, 1, activation=tf.tanh)
loss = tf.losses.mean_squared_error(prediction, y) # pass the second value from iter.get_net() as label
train_op = tf.train.AdamOptimizer().minimize(loss)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    for i in range(EPOCHS):
        _, loss_value = sess.run([train_op, loss])
        print("Iter: {}, Loss: {:.4f}".format(i, loss_value))

결과는 다음과 같다.

Iter: 0, Loss: 0.1328 
Iter: 1, Loss: 0.1312 
Iter: 2, Loss: 0.1296 
Iter: 3, Loss: 0.1281 
Iter: 4, Loss: 0.1267 
Iter: 5, Loss: 0.1254 
Iter: 6, Loss: 0.1242 
Iter: 7, Loss: 0.1231 
Iter: 8, Loss: 0.1220 
Iter: 9, Loss: 0.1210

유용한 것들

Batch

일반적으로 데이터를 batch 시키는 것에는 문제가 많은데, Dataset API를 사용하면 주어진 크기로 데이터 세트를 자동으로 처리하는batch(BATCH_SIZE)메서드를 사용할 수 있다. 기본 batch size는 1이고, 다음의 예에서는 4이다.

# BATCHING
BATCH_SIZE = 4
x = np.random.sample((100,2))
# make a dataset from a numpy array
dataset = tf.data.Dataset.from_tensor_slices(x).batch(BATCH_SIZE)
iter = dataset.make_one_shot_iterator()
el = iter.get_next()
with tf.Session() as sess:
    print(sess.run(el)) 

결과는 다음과 같다.

[[ 0.65686128  0.99373963]
 [ 0.69690451  0.32446826]
 [ 0.57148422  0.68688242]
 [ 0.20335116  0.82473219]]

Repeat

.repeat()를 사용하면 dataset이 몇 번 반복해서 사용될 지 정할 수 있다. 파라미터가 없다면 계속 반복하고 보통 계속 반복시키고 epoch 값을 직접 제어하는 것이 좋다.

Shuffle

shuffle()을 사용하면 설정된 epoch마다 Dataset을 섞을 수 있다.

Dataset의 shuffle은 overfitting을 피할 때 매우 중요하다.

또한 아래의 예처럼 다음 원소가 일정하게 선택되는 고정된 버퍼의 크기인 buffer_size 파라미터를 설정 할 수 있다.

# BATCHING
BATCH_SIZE = 4
x = np.array([[1],[2],[3],[4]])
# make a dataset from a numpy array
dataset = tf.data.Dataset.from_tensor_slices(x)
dataset = dataset.shuffle(buffer_size=100)
dataset = dataset.batch(BATCH_SIZE)
iter = dataset.make_one_shot_iterator()
el = iter.get_next()
with tf.Session() as sess:
    print(sess.run(el))

첫 실행 결과:

[[4]
 [2]
 [3]
 [1]]

두 번째 실행 결과:

[[3]
 [1]
 [2]
 [4]]

원한다면 shuffle에 seed parameter를 설정 할 수도 있다.

Map

map 함수를 이용해서 dataset의 각 멤버에 사용자 지정 함수를 적용할 수 있다. 다음 예제에서는 각 원소에 2를 곱한다.

# MAP
x = np.array([[1],[2],[3],[4]])
# make a dataset from a numpy array
dataset = tf.data.Dataset.from_tensor_slices(x)
dataset = dataset.map(lambda x: x*2)
iter = dataset.make_one_shot_iterator()
el = iter.get_next()
with tf.Session() as sess:
#     this will run forever
        for _ in range(len(x)):
            print(sess.run(el))

결과:

[2]
[4]
[6]
[8]

전체 예제

Initializable iterator

이 예제에서는 batching을 이용해서 간단한 모델을 학습시키고 Initializable iterator를 사용해서 train과 test dataset간에 전환을 할 것이다.

# Wrapping all together -> Switch between train and test set using Initializable iterator
EPOCHS = 10
# create a placeholder to dynamically switch between batch sizes
batch_size = tf.placeholder(tf.int64)

x, y = tf.placeholder(tf.float32, shape=[None,2]), tf.placeholder(tf.float32, shape=[None,1])
dataset = tf.data.Dataset.from_tensor_slices((x, y)).batch(batch_size).repeat()

# using two numpy arrays
train_data = (np.random.sample((100,2)), np.random.sample((100,1)))
test_data = (np.random.sample((20,2)), np.random.sample((20,1)))

iter = dataset.make_initializable_iterator()
features, labels = iter.get_next()
# make a simple model
net = tf.layers.dense(features, 8, activation=tf.tanh) # pass the first value from iter.get_next() as input
net = tf.layers.dense(net, 8, activation=tf.tanh)
prediction = tf.layers.dense(net, 1, activation=tf.tanh)

loss = tf.losses.mean_squared_error(prediction, labels) # pass the second value from iter.get_net() as label
train_op = tf.train.AdamOptimizer().minimize(loss)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    # initialise iterator with train data
    sess.run(iter.initializer, feed_dict={ x: train_data[0], y: train_data[1], batch_size: BATCH_SIZE})
    print('Training...')
    for i in range(EPOCHS):
        tot_loss = 0
        for _ in range(n_batches):
            _, loss_value = sess.run([train_op, loss])
            tot_loss += loss_value
        print("Iter: {}, Loss: {:.4f}".format(i, tot_loss / n_batches))
    # initialise iterator with test data
    sess.run(iter.initializer, feed_dict={ x: test_data[0], y: test_data[1], batch_size: test_data[0].shape[0]})
    print('Test Loss: {:4f}'.format(sess.run(loss)))

학습 후 dataset을 동적으로 전환 하려면 batch size에 대해서 placeholder를 사용해야 한다.

결과:

Training...
Iter: 0, Loss: 0.2977
Iter: 1, Loss: 0.2152
Iter: 2, Loss: 0.1787
Iter: 3, Loss: 0.1597
Iter: 4, Loss: 0.1277
Iter: 5, Loss: 0.1334
Iter: 6, Loss: 0.1000
Iter: 7, Loss: 0.1154
Iter: 8, Loss: 0.0989
Iter: 9, Loss: 0.0948
Test Loss: 0.082150

Reinitializable Iterator

이 예제에서는 batch를 사용해서 간단한 모델을 학습시키고 Reinitializable Iterator을 사용해서 train과 test dataset간의 전환을 할 것이다.

# Wrapping all together -> Switch between train and test set using Reinitializable iterator
EPOCHS = 10
# create a placeholder to dynamically switch between batch sizes
batch_size = tf.placeholder(tf.int64)

x, y = tf.placeholder(tf.float32, shape=[None,2]), tf.placeholder(tf.float32, shape=[None,1])
train_dataset = tf.data.Dataset.from_tensor_slices((x,y)).batch(batch_size).repeat()
test_dataset = tf.data.Dataset.from_tensor_slices((x,y)).batch(batch_size) # always batch even if you want to one shot it
# using two numpy arrays
train_data = (np.random.sample((100,2)), np.random.sample((100,1)))
test_data = (np.random.sample((20,2)), np.random.sample((20,1)))

# create a iterator of the correct shape and type
iter = tf.data.Iterator.from_structure(train_dataset.output_types,
                                           train_dataset.output_shapes)
										   features, labels = iter.get_next()
# create the initialisation operations
train_init_op = iter.make_initializer(train_dataset)
test_init_op = iter.make_initializer(test_dataset)

# make a simple model
net = tf.layers.dense(features, 8, activation=tf.tanh) # pass the first value from iter.get_next() as input
net = tf.layers.dense(net, 8, activation=tf.tanh)
prediction = tf.layers.dense(net, 1, activation=tf.tanh)

loss = tf.losses.mean_squared_error(prediction, labels) # pass the second value from iter.get_net() as label
train_op = tf.train.AdamOptimizer().minimize(loss)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    # initialise iterator with train data
    sess.run(train_init_op, feed_dict = {x : train_data[0], y: train_data[1], batch_size: 16})
    print('Training...')
    for i in range(EPOCHS):
        tot_loss = 0
        for _ in range(n_batches):
            _, loss_value = sess.run([train_op, loss])
            tot_loss += loss_value
        print("Iter: {}, Loss: {:.4f}".format(i, tot_loss / n_batches))
    # initialise iterator with test data
    sess.run(test_init_op, feed_dict = {x : test_data[0], y: test_data[1], batch_size:len(test_data[0])})
    print('Test Loss: {:4f}'.format(sess.run(loss)))

마무리 지으면서…

Dataset API를 통해서 최적화된 입력 파이프라인을 만들어 모델을 학습, 평가, 테스트 할 수 있는 빠르고 강력한 방법을 사용할 수 있다. 이 포스트에서는 우리가 흔히 볼 수 있는 일반적인 작업의 대부분을 살펴보았다.


Other resources

TensorFlow dataset tutorial: https://www.tensorflow.org/programmers_guide/datasets

Dataset docs:

https://www.tensorflow.org/api_docs/python/tf/data/Dataset