` 1

다음 글을 참조하여 번역합니다(+ 개인 공부), 예제는 tf 2.0을 기준으로 합니다.

https://www.tensorflow.org/guide/data?hl=en

 

Batching dataset elements

Simple batching

가장 간단한 형태의 배치는 단일 원소를 n개만큼 쌓는 것입니다. Dataset.batch() 변환은 정확히 이 작업을 수행하는데, tf.stack() 연산자와 거의 동일하게 작동합니다. 예를 들면, 각 구성 요소가 가지는 모든 원소는 전부 동일한 shape을 가져야 합니다.

inc_dataset = tf.data.Dataset.range(100)
dec_dataset = tf.data.Dataset.range(0, -100, -1)
dataset = tf.data.Dataset.zip((inc_dataset, dec_dataset))
batched_dataset = dataset.batch(4)

for batch in batched_dataset.take(4):
  print([arr.numpy() for arr in batch])
  • [array([0, 1, 2, 3]), array([ 0, -1, -2, -3])]
    [array([4, 5, 6, 7]), array([-4, -5, -6, -7])]
    [array([ 8,  9, 10, 11]), array([ -8,  -9, -10, -11])]
    [array([12, 13, 14, 15]), array([-12, -13, -14, -15])]

tf.data가 동일한 shape를 전파하는 동안, Dataset.batch는 가장 마지막 배치의 배치 크기를 알 수 없기 때문에 None shape를 default로 지정합니다. 예를 들어, 배치 크기가 32이고 데이터가 100개라면 마지막 배치 크기는 4입니다.

batched_dataset
  • <BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.int64)>

drop_remainder 인자를 사용하면, 마지막 배치 크기를 무시하고 지정한 배치 크기를 사용할 수 있습니다.

batched_dataset = dataset.batch(7, drop_remainder=True)
batched_dataset
  • <BatchDataset shapes: ((7,), (7,)), types: (tf.int64, tf.int64)>

Batching tensors with padding

위의 예제에서는 전부 같은 shape의 데이터를 사용했습니다. 그러나 많은 모델(e.g. sequence models)에서 요구되는 입력의 크기는 매우 다양할 수 있습니다(sequence data의 length는 일정하지 않습니다). 이러한 경우를 다루기 위해, Dataset.padded_batch 변환은 패딩을 사용하여 다른 크기의 배치를 사용할 수 있게 도와줍니다.

dataset = tf.data.Dataset.range(100)
dataset = dataset.map(lambda x: tf.fill([tf.cast(x, tf.int32)], x))
dataset = dataset.padded_batch(4, padded_shapes=(None,))

for batch in dataset.take(2):
  print(batch.numpy())
  print()
  • [[0 0 0]
     [1 0 0]
     [2 2 0]
     [3 3 3]]

    [[4 4 4 4 0 0 0]
     [5 5 5 5 5 0 0]
     [6 6 6 6 6 6 0]
     [7 7 7 7 7 7 7]]
  • tf.fill([tf.cast(x, tf.int32)], x)는 임의의 숫자 x를 x개만큼 채워넣는 것을 의미합니다.

Dataset.padded_batch는 각 특성에 따라 다르게 패딩을 설정할 수 있으며, 패딩 설정은 가변 길이 또는 일정한 길이로 할 수 있습니다. 또한, 기본값은 0이지만, 다른 수를 채워넣을 수 있습니다.


Training workflows

Processing multiple epochs

tf.data API는 동일한 데이터에 대해 multiple epochs를 수행할 수 있는 두 가지 주요한 방법을 제공합니다.

multiple epochs에서 데이터셋을 반복하는 가장 단순한 방법은 Dataset.repeat()을 사용하는 것입니다. 먼저, 타이타닉 데이터셋을 불러오도록 하죠.

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
def plot_batch_sizes(ds):
  batch_sizes = [batch.shape[0] for batch in ds]
  plt.bar(range(len(batch_sizes)), batch_sizes)
  plt.xlabel('Batch number')
  plt.ylabel('Batch size')

아무런 인자를 제공하지 않고, Dataset.repeat()을 사용하면 input을 무한히 반복합니다.

Dataset.repeat은 한 에폭의 끝과 다음 에폭의 시작에 상관없이 인자만큼 반복합니다. 이 때문에 Dataset.repeat 후에 적용된 Dataset.batch는 에폭과 에폭간의 경계를 망각한 채, 데이터를 생성합니다. 이는 이번 예제가 아닌 다음 예제를 보면 이해할 수 있습니다. epoch간의 경계가 없습니다.

titanic_batches = titanic_lines.repeat(3).batch(128)
plot_batch_sizes(titanic_batches)

명확하게 epoch을 구분하기 위해서는 batch 이후에 repeat을 사용합니다.

titanic_batches = titanic_lines.batch(128).repeat(3)

plot_batch_sizes(titanic_batches)

만약 각 에폭의 끝에서 사용자 정의 연산(예를 들면, 통계적 수집)을 사용하고 싶다면, 각 에폭에서 데이터셋 반복을 restart하는 것이 가장 단순합니다.

epochs = 3
dataset = titanic_lines.batch(128)

for epoch in range(epochs):
  for batch in dataset:
    print(batch.shape)
  print("End of epoch: ", epoch)
  • (128,) (128,) (128,) (128,) (116,) End of epoch: 0
    (128,) (128,) (128,) (128,) (116,) End of epoch: 1
    (128,) (128,) (128,) (128,) (116,) End of epoch: 2

Randomly shuffling input data

Dataset.shuffle()은 고정 크기의 버퍼를 유지하면서, 해당 버퍼에서 다음 요소를 무작위로 선택합니다.

결과 확인을 위해 데이터에 인덱스를 추가합니다.

lines = tf.data.TextLineDataset(titanic_file)
counter = tf.data.experimental.Counter()

dataset = tf.data.Dataset.zip((counter, lines))
dataset = dataset.shuffle(buffer_size=100)
dataset = dataset.batch(20)
dataset
  • <BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.string)>

buffer_size가 100이고, batch_size가 20이므로, 첫 번째 배치에서는 120 이상의 인덱스 요소가 존재하지 않습니다. 사용하는 데이터의 인덱스 수가 uniform하게 증가합니다.(아마도 전체 데이터를 사용하기 위해)

n,line_batch = next(iter(dataset))
print(n.numpy())
  • [ 73  71  16  28   6  65  91  12  42  68  54  40  81  46   4  98 105  89
      67  11]

이번에도 Dataset.batchDataset.repeat을 고려해야 합니다.

Dataset.shuffle은 셔플 버퍼가 빌 때까지 에폭의 끝에 대한 정보를 알려주지 않습니다. repeat 전에 shuffle을 사용하면 다음으로 넘어가기 전에 한 에폭의 원소를 전부 확인할 수 있습니다.

dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.shuffle(buffer_size=100).batch(10).repeat(2)

print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(60).take(5):
  print(n.numpy())
  • Here are the item ID's near the epoch boundary:

    [541 569 508 599 578 418 559 595 401 594]
    [282 522 395 552 362 442 389 619 506 523]
    [612 585 482 518 604 617 608 622]
    [85 27 73 57 16 47 43 50 55 64]
    [ 90  89  24  59   9 101  97  65  14  99]
shuffle_repeat = [n.numpy().mean() for n, line_batch in shuffled]
plt.plot(shuffle_repeat, label="shuffle().repeat()")
plt.ylabel("Mean item ID")
plt.legend()

shuffle 전에 repeat을 사용하면 epoch의 경계가 무너집니다.

dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.repeat(2).shuffle(buffer_size=100).batch(10)

print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(55).take(15):
  print(n.numpy())
repeat_shuffle = [n.numpy().mean() for n, line_batch in shuffled]

plt.plot(shuffle_repeat, label="shuffle().repeat()")
plt.plot(repeat_shuffle, label="repeat().shuffle()")
plt.ylabel("Mean item ID")
plt.legend()

 

'# Machine Learning > TensorFlow doc 정리' 카테고리의 다른 글

tf.data tutorial 번역 (5)  (0) 2020.02.28
tf.data tutorial 번역 (4)  (1) 2020.02.27
tf.data tutorial 번역 (2)  (0) 2020.02.18
tf.data tutorial 번역 (1)  (0) 2020.02.14
tensorflow 2.0 keras Write custom callbacks  (2) 2019.04.18

다음 글을 참조하여 번역합니다(+ 개인 공부), 예제는 tf 2.0을 기준으로 합니다.

https://www.tensorflow.org/guide/data?hl=en

 

tf.data: Build TensorFlow input pipelines  |  TensorFlow Core

The tf.data API enables you to build complex input pipelines from simple, reusable pieces. For example, the pipeline for an image model might aggregate data from files in a distributed file system, apply random perturbations to each image, and merge random

www.tensorflow.org


Reading Input Data

Consuming NumPy arrays

다양한 NumPy array를 로딩하는 예제는 다음을 참조하세요.

https://www.tensorflow.org/tutorials/load_data/numpy

만약 모든 데이터가 메모리에 존재한다면, 이들로부터 Dataset을 만드는 가장 간단한 방법은 Dataset.from_tensor_slices()를 사용하여 tf.Tensor로 변환하는 것입니다.

train, test = tf.keras.datasets.fashion_mnist.load_data()

images, labels = train
images = images/255

dataset = tf.data.Dataset.from_tensor_slices((images, labels))
dataset
  • <TensorSliceDataset shapes: ((28, 28), ()), types: (tf.float64, tf.uint8)>
  • (28, 28)은 이미지이며, ()은 스칼라 형태로 label을 나타냅니다.

Consuming Python generators

tf.data.Dataset으로 쉽게 데이터를 살펴 볼 수 있는 또다른 방법은 파이썬 제네레이터를 사용하는 것입니다.

def count(stop):
  i = 0
  while i<stop:
    yield i
    i += 1
for n in count(5):
  print(n)
  • 0 1 2 3 4

Dataset.from_generator 생성자는 tf.data.Dataset을 제네레이터처럼 사용할 수 있게 합니다.

이 생성자는 반복자가 아닌, 입력을 사용합니다. 이는 데이터의 끝에 도달했을 때, 제네레이터가 재시작할 수 있도록 도와줍니다. 선택적 args 인자를 가지는데, 이는 호출이 가능합니다.

output_types 인자는 tf.data가 내부적으로 tf.Graph를 빌드하고, graph edge에 tf.dtype을 요구하기 때문에 필요합니다.

ds_counter = tf.data.Dataset.from_generator(count, args=[25], output_types=tf.int32, output_shapes = (), )
  • count는 위에서 선언한 파이썬 제네레이터입니다.
  • arg는 인자로서 count 함수의 stop 인자로 통합니다.
for count_batch in ds_counter.repeat().batch(10).take(10):
  print(count_batch.numpy())
  • [0 1 2 3 4 5 6 7 8 9] [10 11 12 13 14 15 16 17 18 19] [20 21 22 23 24 0 1 2 3 4] [ 5 6 7 8 9 10 11 12 13 14] [15 16 17 18 19 20 21 22 23 24] [0 1 2 3 4 5 6 7 8 9] [10 11 12 13 14 15 16 17 18 19] [20 21 22 23 24 0 1 2 3 4] [ 5 6 7 8 9 10 11 12 13 14] [15 16 17 18 19 20 21 22 23 24]
  • 잘 보면 batch(10).take(10)이므로 총 10번에 10개씩 받아오며, stop 25까지 작동합니다. 25에 도달한 경우는 제네레이터가 restart하는 것을 3번째 배열에서 볼 수 있습니다.

output_shapes 인자는 필요하진 않지만, tensorflow의 많은 연산이 알 수 없는 rank 단위는 지원하지 않으므로 권장 사항입니다. 특정 축 또는 길이가 알 수 없거나, 가변인 경우, None 으로 지정하세요.

다른 dataset 메소드를 사용하기 위해 output_shapesoutput_types는 중요한 요소입니다.

다음은 두 가지 요소의 필요성을 보여주는 제네레이터 예제입니다. 두 배열은 길이가 알려지지 않은 벡터입니다.

def gen_series():
  i = 0
  while True:
    size = np.random.randint(0, 10)
    yield i, np.random.normal(size=(size,))
    i += 1
for i, series in gen_series():
  print(i, ":", str(series))
  if i > 5:
    break
  • 0 : [ 0.3464 -0.306 ] 1 : [-0.7686] 2 : [1.9423] 3 : [] 4 : [ 0.5828 -0.0588 0.0094 -0.9467] 5 : [-0.8972 -0.4949 1.1115 0.8208 0.843 0.2968 -2.7236 -0.844 -1.7327] 6 : [ 1.2727 -0.6278 0.1622 -1.4087 -0.7683 -0.3966 0.3112]

첫 번째 출력값의 형태는 int32, 두 번째는 float32입니다.

첫 번째 요소는 스칼라이며, () shape입니다. 두 번째 요소는 길이가 알려지지 않은 벡터로 (None, ) shape입니다.

ds_series = tf.data.Dataset.from_generator(
    gen_series, 
    output_types=(tf.int32, tf.float32), 
    output_shapes=((), (None,)))

ds_series
  • <FlatMapDataset shapes: ((), (None,)), types: (tf.int32, tf.float32)>

이제 일반적인 tf.data.Dataset처럼 사용할 수 있습니다. variable shape과 함께 배치를 뽑아올 때는 Dataset.padded_batch를 사용합니다.

ds_series_batch = ds_series.shuffle(20).padded_batch(10)

ids, sequence_batch = next(iter(ds_series_batch))
print(ids.numpy())
print()
print(sequence_batch.numpy())
  • [ 3 2 5 8 0 11 24 26 16 12] [[ 3.3757 0.791 -0.7864 -0.5299 -0.5024 0. 0. 0. 0. ] [-0.8493 0. 0. 0. 0. 0. 0. 0. 0. ] [-0.3736 0.2187 0.3256 -0.8628 2.3045 0.7726 1.9534 0.1123 0.3906] [ 0.3752 1.0399 -1.6983 -1.2217 -1.2176 -1.1055 0.7014 0. 0. ] [ 0.2049 -0.5775 -1.5055 0. 0. 0. 0. 0. 0. ] [-2.0829 0.7266 -0.0104 -1.2408 -0.715 -0.232 0.2391 0. 0. ] [-0.0439 -0.3391 1.5569 -0.7063 1.3729 -0.31 0.9572 -0.0446 0.0635] [ 0. 0. 0. 0. 0. 0. 0. 0. 0. ] [-0.5468 0.3916 -0.432 0.6168 -1.0789 0.8624 -1.2116 -1.1322 0.2158] [ 0. 0. 0. 0. 0. 0. 0. 0. 0. ]]
  • padded_batch(10)이기 때문에 10개씩 가져옵니다(첫 번째 int 요소가 10개인 것을 보면 알 수 있습니다). padded_batch는 가변길이(최소 0부터 최대 10까지)에서 나머지 요소에 0을 넣어줍니다.
  • ds_series는 (0, 10) 범위의 값을 뽑아내니까요. shuffle(20)은 버퍼 사이즈가 20이라는 의미입니다. 아마 데이터의 개수가 21개를 넘어갈 경우 elements가 뽑히지 않을 것입니다.

좀 더 현실적인 예시를 위해 precessing.image.ImageDataGenerator를 사용해봅니다. 데이터를 다운로드받습니다.

flowers = tf.keras.utils.get_file(
    'flower_photos',
    'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
    untar=True)

image.ImageDataGenerator를 만듭니다.

img_gen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255, rotation_range=20)
images, labels = next(img_gen.flow_from_directory(flowers))
print(images.dtype, images.shape)
print(labels.dtype, labels.shape)
  • float32 (32, 256, 256, 3)
  • float32 (32, 5)
ds = tf.data.Dataset.from_generator(
    img_gen.flow_from_directory, args=[flowers], 
    output_types=(tf.float32, tf.float32), 
    output_shapes=([32,256,256,3], [32,5])
)

ds
  • <FlatMapDataset shapes: ((32, 256, 256, 3), (32, 5)), types: (tf.float32, tf.float32)>
  • ImageDataGenerator를 tf.data.Dataset으로 래핑하여 사용하는 것을 볼 수 있습니다.

Consuming TFRecord data

end-to-end example을 원한다면 Loading TFRecords를 참고하세요.

tf.data API는 메모리에 적재하기 힘든 매우 큰 데이터셋을 다룰 때, 다양한 file format을 다룰 수 있도록 도와줍니다. 예를 들어, TFRecord file format은 많은 TF app가 학습 데이터에 사용하는 간단한 record-oriented 이진 형식입니다. tf.data.TFRecordDataset 클래스는 인풋 파이프라인에서 하나 또는 그 이상의 TFRecord 파일의 내용이 흐르도록 합니다.

French Street Name Signs (FSNS)을 사용하는 예제입니다.

# Creates a dataset that reads all of the examples from two files.
fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")

TFRecordDataset을 초기화하는 filenames 인자는 string, string 배열 또는 string tf.Tensor를 전달받을 수 있습니다. 만약 학습과 검증을 위해 두 개의 파일을 사용한다면, 파일 이름을 입력으로 사용하여 데이터셋을 생성하는 팩토리 메소드로 만들 수 있습니다.

dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
  • <TFRecordDatasetV2 shapes: (), types: tf.string>

많은 TensorFlow 프로젝트는 TFRecord에서 직렬화된 tf.train.Example을 사용합니다. 따라서 사용하기 전에 디코딩해야 합니다.

raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())

parsed.features.feature['image/text']
  • bytes_list { value: "Rue Perreyon" }

Consuming text data

end to end example은 다음을 참고하세요.

많은 데이터셋은 하나 또는 그 이상의 text 파일에 분산되어 있습니다. tf.data.TextLineDataset은 준비된 텍스트 파일에서 line 단위로 추출하는 쉬운 방법을 제공합니다. 주어진 하나 또는 그 이상의 파일 이름에서, TExtLineDataset은 line 단위로 string-value를 생성해 줄 것입니다.

directory_url = 'https://storage.googleapis.com/download.tensorflow.org/data/illiad/'
file_names = ['cowper.txt', 'derby.txt', 'butler.txt']

file_paths = [
    tf.keras.utils.get_file(file_name, directory_url + file_name)
    for file_name in file_names
]
dataset = tf.data.TextLineDataset(file_paths)

첫 번째 파일의 5개 행을 보여줍니다.

for line in dataset.take(5):
  print(line.numpy())
  • b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;"
    b'His wrath pernicious, who ten thousand woes'
    b"Caused to Achaia's host, sent many a soul"
    b'Illustrious into Ades premature,'
    b'And Heroes gave (so stood the will of Jove)'

Dataset.interleave는 파일을 번갈아 가면서 사용할 수 있게 해줍니다. 다음은 각 파일에서 나오는 문장의 예를 보여줍니다. cycle_length=3이므로 파일당 3개의 행씩 번갈아가면서 보여주겠군요.

files_ds = tf.data.Dataset.from_tensor_slices(file_paths)
lines_ds = files_ds.interleave(tf.data.TextLineDataset, cycle_length=3)

for i, line in enumerate(lines_ds.take(9)):
  if i % 3 == 0:
    print()
  print(line.numpy())
  • b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;"
    b"\xef\xbb\xbfOf Peleus' son, Achilles, sing, O Muse,"
    b'\xef\xbb\xbfSing, O goddess, the anger of Achilles son of Peleus, that brought'

    b'His wrath pernicious, who ten thousand woes'
    b'The vengeance, deep and deadly; whence to Greece'
    b'countless ills upon the Achaeans. Many a brave soul did it send'

    b"Caused to Achaia's host, sent many a soul"
    b'Unnumbered ills arose; which many a soul'
    b'hurrying down to Hades, and many a hero did it yield a prey to dogs and'

기본적으로 TextLineDataset은 파일의 모든 line을 살펴보기 때문에 만약 파일에 header 행이나 주석이 포함된 경우 사용이 바람직하지 않을 수 있습니다. header 행이나 주석과 같은 불필요한 내용은 Dataset.skip(), Dataset.filter()를 사용하여 배제할 수 있습니다. 다음 예제는 첫 번째 행을 건너뛰고, 생존자 데이터만 찾는 경우입니다.

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
for line in titanic_lines.take(10):
  print(line.numpy())
  • b'survived,sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone'
    b'0,male,22.0,1,0,7.25,Third,unknown,Southampton,n'
    b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
    b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
    b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
    b'0,male,28.0,0,0,8.4583,Third,unknown,Queenstown,y'
    b'0,male,2.0,3,1,21.075,Third,unknown,Southampton,n'
    b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
    b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
    b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
  • titanic 데이터에서 10개의 행을 불러오고 있습니다. 또, 우리에게 불필요한 header 행이 포함되어 있는 것을 볼 수 있습니다.
def survived(line):
  return tf.not_equal(tf.strings.substr(line, 0, 1), "0")

survivors = titanic_lines.skip(1).filter(survived)
for line in survivors.take(10):
  print(line.numpy())
  • b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
    b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
    b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
    b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
    b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
    b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
    b'1,male,28.0,0,0,13.0,Second,unknown,Southampton,y'
    b'1,female,28.0,0,0,7.225,Third,unknown,Cherbourg,y'
    b'1,male,28.0,0,0,35.5,First,A,Southampton,y'
    b'1,female,38.0,1,5,31.3875,Third,unknown,Southampton,n'
  • tf.strings.substr(line, 0, 1): 0번째의 str 형태의 문자가 "0"인 것을 모두 걸러내고 있습니다. 아마 1이 생존자를 타나내는 것 같습니다. 또 skip(1)을 통해 header 행을 걸러내었음을 볼 수 있습니다.
  • tf.not_equal(x, y)는 (x != y)에 대한 boolean 값을 반환합니다. 즉, 1인 경우는 True를 반환하겠군요. filter는 false 값은 전부 제외 처리하는 것 같습니다.

Consuming CSV data

더 많은 예제는 다음_1과 다음_2를 참조하세요.


CSV 파일 포맷은 일반 텍스트를 테이블 형태의 데이터로 저장하기 위해 사용하는 대중적인 방법입니다.

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")

df = pd.read_csv(titanic_file, index_col=None)
df.head()

만약 메모리에 데이터가 존재한다면 Dataset.from_tensor_slices를 사용하여 사전 형태로 쉽게 불러올 수 있습니다.

titanic_slices = tf.data.Dataset.from_tensor_slices(dict(df))

for feature_batch in titanic_slices.take(1):
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
  • take(1)을 통해 1 크기의 배치를 불러오고, dict 형태이기 때문에 items()로 value, key를 받습니다.
  •  'survived'          : 0 
     'sex'               : b'male' 
     'age'               : 22.0 
     'n_siblings_spouses': 1 
     'parch'             : 0 
     'fare'              : 7.25 
     'class'             : b'Third' 
     'deck'              : b'unknown' 
     'embark_town'       : b'Southampton' 
     'alone'             : b'n'

보다 확장 가능한 방법은 필요에 따라 디스크에서 로드하는 것입니다.
tf.data 모듈은 RFC 4180을 준수하는 하나 또는 그 이상의 CSV 파일로부터 데이터를 추출하기 위한 메소드를 제공합니다. + RFC 4180은 CSV 파일 구축을 위해 제안되는 규칙입니다.

experimental.make_csv_dataset는 csv 파일을 읽어오는 고수준 인터페이스 함수입니다. 이 함수는 column type을 추론하거나
batching, shuffling과 같은 많은 특성들을 쉽게 사용할 수 있도록 도와줍니다.

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived")
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  print("features:")
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
  • 'survived': [1 0 1 0]
    features:
      'sex'               : [b'female' b'male' b'female' b'male']
      'age'               : [30. 28.  2. 28.]
      'n_siblings_spouses': [3 0 0 0]
      'parch'             : [0 0 1 0]
      'fare'              : [21.      7.7958 12.2875 26.55  ]
      'class'             : [b'Second' b'Third' b'Third' b'First']
      'deck'              : [b'unknown' b'unknown' b'unknown' b'C']
      'embark_town'       : [b'Southampton' b'Southampton' b'Southampton' b'Southampton']
      'alone'             : [b'n' b'y' b'n' b'y']

select_columns 인자를 사용해서 원하는 column만 사용할 수 있습니다.

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived", select_columns=['class', 'fare', 'survived'])
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
  • 'survived': [0 0 0 0]
      'fare'              : [29.125   7.8958 77.2875  7.75  ]
      'class'             : [b'Third' b'Third' b'First' b'Third']

섬세한 제어를 가능하게 하는 low-level의 experimental.CsvDataset도 있습니다. 이는 column type 추론을 제공하지 않습니다. 대신 각 컬럼의 type을 꼭 구체화해야 합니다.

titanic_types  = [tf.int32, tf.string, tf.float32, tf.int32, tf.int32, tf.float32, tf.string, tf.string, tf.string, tf.string] 
dataset = tf.data.experimental.CsvDataset(titanic_file, titanic_types , header=True)

for line in dataset.take(10):
  print([item.numpy() for item in line])
  • [0, b'male', 22.0, 1, 0, 7.25, b'Third', b'unknown', b'Southampton', b'n']
    [1, b'female', 38.0, 1, 0, 71.2833, b'First', b'C', b'Cherbourg', b'n']
    [1, b'female', 26.0, 0, 0, 7.925, b'Third', b'unknown', b'Southampton', b'y']
    [1, b'female', 35.0, 1, 0, 53.1, b'First', b'C', b'Southampton', b'n']
    [0, b'male', 28.0, 0, 0, 8.4583, b'Third', b'unknown', b'Queenstown', b'y']
    [0, b'male', 2.0, 3, 1, 21.075, b'Third', b'unknown', b'Southampton', b'n']
    [1, b'female', 27.0, 0, 2, 11.1333, b'Third', b'unknown', b'Southampton', b'n']
    [1, b'female', 14.0, 1, 0, 30.0708, b'Second', b'unknown', b'Cherbourg', b'n']
    [1, b'female', 4.0, 1, 1, 16.7, b'Third', b'G', b'Southampton', b'n']
    [0, b'male', 20.0, 0, 0, 8.05, b'Third', b'unknown', b'Southampton', b'y']

만약 컬럼에서 몇 가지 데이터가 비어있을 경우, low-level 인터페이스는 column type 대신에 기본값을 제공하도록 할 수 있습니다.

%%writefile missing.csv
1,2,3,4
,2,3,4
1,,3,4
1,2,,4
1,2,3,
,,,
  • Writing missing.csv
# Creates a dataset that reads all of the records from two CSV files, each with
# four float columns which may have missing values.

record_defaults = [999,999,999,999]
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults)
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
  • <MapDataset shapes: (4,), types: tf.int32>
for line in dataset:
  print(line.numpy())
  • [1 2 3 4]
    [999   2   3   4]
    [  1 999   3   4]
    [  1   2 999   4]
    [  1   2   3 999]
    [999 999 999 999]

기본적으로 CsvDataset은 모든 행과 열을 반환합니다. 이는 header 행 또는 원하는 column이 포함되어 있는 경우 바람직하지 않을 수 있습니다. header와 select_cols 인자를 통해 제거할 수 있습니다.

# Creates a dataset that reads all of the records from two CSV files with
# headers, extracting float data from columns 2 and 4.
record_defaults = [999, 999] # Only provide defaults for the selected columns
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults, select_cols=[1, 3])
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
  • <MapDataset shapes: (2,), types: tf.int32>
for line in dataset:
  print(line.numpy())
  • [2 4]
    [2 4]
    [999   4]
    [2 4]
    [  2 999]
    [999 999]

Consuming sets of files

데이터셋은 여러 가지 파일에 분산되어 저장되어 있을 수 있습니다.

flowers_root = tf.keras.utils.get_file(
    'flower_photos',
    'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
    untar=True)
flowers_root = pathlib.Path(flowers_root)

root directory는 각 클래스의 directory를 포함합니다.

for item in flowers_root.glob("*"):
  print(item.name)

다음 예는 각 클래스의 directory를 보여줍니다.

list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))

for f in list_ds.take(5):
  print(f.numpy())
  • b'/home/kbuilder/.keras/datasets/flower_photos/roses/6409000675_6eb6806e59.jpg' b'/home/kbuilder/.keras/datasets/flower_photos/tulips/4520577328_a94c11e806_n.jpg' b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/4933229889_c5d9e36392.jpg' b'/home/kbuilder/.keras/datasets/flower_photos/roses/22506717337_0fd63e53e9.jpg' b'/home/kbuilder/.keras/datasets/flower_photos/daisy/20182559506_40a112f762.jpg'
  • Dataset.list_file() 함수는 클래스 디렉토리를 받아 하위에 존재하는 이미지의 경로를 가져다 주는 것 같아보입니다.

tf.io.read_file 함수를 사용해서 경로에서 레이블을 추출하고, (image, label) 쌍을 반환합니다.

def process_path(file_path):
  label = tf.strings.split(file_path, '/')[-2]
  return tf.io.read_file(file_path), label

labeled_ds = list_ds.map(process_path)
  • tf.strings.split을 통해 label만 추출하고 있습니다.
for image_raw, label_text in labeled_ds.take(1):
  print(repr(image_raw.numpy()[:100]))
  print()
  print(label_text.numpy())
  • b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xdb\x00C\x00\x03\x02\x02\x03\x02\x02\x03\x03\x03\x03\x04\x03\x03\x04\x05\x08\x05\x05\x04\x04\x05\n\x07\x07\x06\x08\x0c\n\x0c\x0c\x0b\n\x0b\x0b\r\x0e\x12\x10\r\x0e\x11\x0e\x0b\x0b\x10\x16\x10\x11\x13\x14\x15\x15\x15\x0c\x0f\x17\x18\x16\x14\x18\x12\x14\x15\x14\xff\xdb\x00C\x01\x03\x04\x04\x05\x04\x05'

    b'sunflowers'