다음 글을 참조하여 번역합니다(+ 개인 공부), 예제는 tf 2.0을 기준으로 합니다.
https://www.tensorflow.org/guide/data?hl=en
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_shapes와 output_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
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)
- sunflowers
daisy
LICENSE.txt
roses
tulips
dandelion
다음 예는 각 클래스의 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'
'# Machine Learning > TensorFlow doc 정리' 카테고리의 다른 글
tf.data tutorial 번역 (4) (1) | 2020.02.27 |
---|---|
tf.data tutorial 번역 (3) (0) | 2020.02.26 |
tf.data tutorial 번역 (1) (0) | 2020.02.14 |
tensorflow 2.0 keras Write custom callbacks (2) | 2019.04.18 |
tensorflow 2.0 keras Saving and Serializing Models with Tensorflow Keras (0) | 2019.04.15 |