tensorflow 2.0 Dataset, batch, window, flat_map을 활용한 loader 만들기
Apr 15, 2020

tf.data.Dataset을 활용하여 다양한 Dataset 로더를 만들 수 있습니다. 그리고, 로더를 활용하여, shuffle, batch_size, window 데이터셋 생성등 다양한 종류를 데이터 셋을 상황에 맞게 생성하고 모델에 feed할 수 있도록 제공해 줍니다.

더 이상 numpy로 한땀 한땀 만들어 줄 필요없이, 간단한 옵션 몇 개면 데이터세트를 완성할 수 있습니다.

# 필요한 라이브러리 import
import numpy as np
import tensorflow as tf

1. dimension을 1만큼 늘려주기

1-1. tensorflow 의 expand_dim : 차원 늘리기

x = np.arange(20)
tf.expand_dims(x, 1).shape
TensorShape([20, 1])

1-2. numpy의 expand_dims와 동일

np.expand_dims(x, 1).shape
(20, 1)

1-3. from_tensor_slices: numpy array나 list를 tensor dataset으로 변환

from_tensor_sliceslistnumpy array 모두 변환하도록 지원하고 있습니다.

ds = tf.data.Dataset.from_tensor_slices([1, 2, 3, 4, 5])

for d in ds:
    print(d)
tf.Tensor(1, shape=(), dtype=int32)
tf.Tensor(2, shape=(), dtype=int32)
tf.Tensor(3, shape=(), dtype=int32)
tf.Tensor(4, shape=(), dtype=int32)
tf.Tensor(5, shape=(), dtype=int32)
ds = tf.data.Dataset.from_tensor_slices(np.arange(10))
ds

for d in ds:
    print(d)
tf.Tensor(0, shape=(), dtype=int64)
tf.Tensor(1, shape=(), dtype=int64)
tf.Tensor(2, shape=(), dtype=int64)
tf.Tensor(3, shape=(), dtype=int64)
tf.Tensor(4, shape=(), dtype=int64)
tf.Tensor(5, shape=(), dtype=int64)
tf.Tensor(6, shape=(), dtype=int64)
tf.Tensor(7, shape=(), dtype=int64)
tf.Tensor(8, shape=(), dtype=int64)
tf.Tensor(9, shape=(), dtype=int64)

2. batch

batch는 model에 학습시킬 때 batch_size를 지정하여 size만큼 데이터를 읽어 들여 학습시킬 때 유용한 method입니다.

이미지와 같은 큰 사이즈는 memory에 한 번에 올라가지 못하기 때문에, 이렇게 batch를 나누어서 학습시키기도 하구요.

또한, model이 weight를 업데이트 할 때, 1개의 batch가 끝나고 난 후 업데이트를 하게 되는데, 업데이트 빈도를 조절하는 효과도 있습니다.

drop_remainder는 마지만 남은 데이터를 drop 할 것인지 여부

ds = tf.data.Dataset.range(8) 
ds = ds.batch(3, drop_remainder=True)
list(ds.as_numpy_iterator())
[array([0, 1, 2]), array([3, 4, 5])]

원래는 6, 7 이 batch로 출력되어야 하지만 drop_remainder=True 옵션이 나머지를 버린다

ds = tf.data.Dataset.range(8)
for d in ds.batch(3, drop_remainder=True):
    print(d)
tf.Tensor([0 1 2], shape=(3,), dtype=int64)
tf.Tensor([3 4 5], shape=(3,), dtype=int64)

3. window: Time Series 데이터셋 생성에 유용

Time Series 데이터셋을 구성할 때 굉장히 유용하게 활용할 수 있습니다.

  • window: 그룹화 할 윈도우 크기(갯수)
  • drop_remainder: 남은 부분을 버릴지 살릴지 여부
  • shift는 1 iteration당 몇 개씩 이동할 것인지

drop_remainder=False 인 경우

ds = tf.data.Dataset.range(10) 
ds = ds.window(5, shift=1, drop_remainder=False)
for d in ds:
    print(list(d.as_numpy_iterator()))
[0, 1, 2, 3, 4]
[1, 2, 3, 4, 5]
[2, 3, 4, 5, 6]
[3, 4, 5, 6, 7]
[4, 5, 6, 7, 8]
[5, 6, 7, 8, 9]
[6, 7, 8, 9]
[7, 8, 9]
[8, 9]
[9]

drop_remainder=True 인 경우

ds = tf.data.Dataset.range(10) 
ds = ds.window(5, shift=1, drop_remainder=True)
for d in ds:
    print(list(d.as_numpy_iterator()))
[0, 1, 2, 3, 4]
[1, 2, 3, 4, 5]
[2, 3, 4, 5, 6]
[3, 4, 5, 6, 7]
[4, 5, 6, 7, 8]
[5, 6, 7, 8, 9]

shift=2로 설정: 2칸씩 이동

ds = tf.data.Dataset.range(10) 
ds = ds.window(5, shift=2, drop_remainder=True)
for d in ds:
    print(list(d.as_numpy_iterator()))
[0, 1, 2, 3, 4]
[2, 3, 4, 5, 6]
[4, 5, 6, 7, 8]

4. flat_map

flat_map은 dataset에 함수를 apply해주고, 결과를 flatten하게 펼쳐 줍니다.

아래는 lambda 함수를 통해 3개의 batch를 읽어들인 뒤 flatten된 리턴값을 받습니다.

ds = tf.data.Dataset.range(10) 
ds = ds.window(5, shift=1, drop_remainder=True)
ds = ds.flat_map(lambda w: w.batch(3))
for d in ds:
    print(d)
tf.Tensor([0 1 2], shape=(3,), dtype=int64)
tf.Tensor([3 4], shape=(2,), dtype=int64)
tf.Tensor([1 2 3], shape=(3,), dtype=int64)
tf.Tensor([4 5], shape=(2,), dtype=int64)
tf.Tensor([2 3 4], shape=(3,), dtype=int64)
tf.Tensor([5 6], shape=(2,), dtype=int64)
tf.Tensor([3 4 5], shape=(3,), dtype=int64)
tf.Tensor([6 7], shape=(2,), dtype=int64)
tf.Tensor([4 5 6], shape=(3,), dtype=int64)
tf.Tensor([7 8], shape=(2,), dtype=int64)
tf.Tensor([5 6 7], shape=(3,), dtype=int64)
tf.Tensor([8 9], shape=(2,), dtype=int64)
ds = tf.data.Dataset.range(10) 
ds = ds.window(5, shift=1, drop_remainder=True)
ds = ds.flat_map(lambda w: w.batch(5))
for d in ds:
    print(d)
tf.Tensor([0 1 2 3 4], shape=(5,), dtype=int64)
tf.Tensor([1 2 3 4 5], shape=(5,), dtype=int64)
tf.Tensor([2 3 4 5 6], shape=(5,), dtype=int64)
tf.Tensor([3 4 5 6 7], shape=(5,), dtype=int64)
tf.Tensor([4 5 6 7 8], shape=(5,), dtype=int64)
tf.Tensor([5 6 7 8 9], shape=(5,), dtype=int64)

5. shuffle

shuffle은 Dataset을 섞어주는 역할을 하며, 반드시 학습전에 shuffle을 통해 적절하게 Dataset을 섞어주어야 합니다.

# shuffle을 해주지 않은 경우
ds = tf.data.Dataset.from_tensor_slices(np.arange(10))#.shuffle()

for d in ds:
    print(d)
tf.Tensor(0, shape=(), dtype=int64)
tf.Tensor(1, shape=(), dtype=int64)
tf.Tensor(2, shape=(), dtype=int64)
tf.Tensor(3, shape=(), dtype=int64)
tf.Tensor(4, shape=(), dtype=int64)
tf.Tensor(5, shape=(), dtype=int64)
tf.Tensor(6, shape=(), dtype=int64)
tf.Tensor(7, shape=(), dtype=int64)
tf.Tensor(8, shape=(), dtype=int64)
tf.Tensor(9, shape=(), dtype=int64)
# shuffle 설정
ds = tf.data.Dataset.from_tensor_slices(np.arange(10)).shuffle(buffer_size=5)
for d in ds:
    print(d)
tf.Tensor(0, shape=(), dtype=int64)
tf.Tensor(3, shape=(), dtype=int64)
tf.Tensor(5, shape=(), dtype=int64)
tf.Tensor(2, shape=(), dtype=int64)
tf.Tensor(6, shape=(), dtype=int64)
tf.Tensor(8, shape=(), dtype=int64)
tf.Tensor(7, shape=(), dtype=int64)
tf.Tensor(9, shape=(), dtype=int64)
tf.Tensor(4, shape=(), dtype=int64)
tf.Tensor(1, shape=(), dtype=int64)

위의 shuffle함수에서 꼭 지정해주어야하는 인자는 buffer_size 입니다.

텐서플로우 공식 도큐먼트에 의하면,

  • 데이터세트는 buffer_size 요소로 버퍼를 채운 다음이 버퍼에서 요소를 무작위로 샘플링하여 선택한 요소를 새 요소로 바꿉니다.

  • 완벽한 셔플 링을 위해서는 데이터 세트의 전체 크기보다 크거나 같은 버퍼 크기가 필요합니다.

  • 예를 들어, 데이터 집합에 10,000 개의 요소가 있지만 buffer_size가 1,000으로 설정된 경우 셔플은 처음에 버퍼의 처음 1,000 개 요소 중 임의의 요소 만 선택합니다.

  • 요소가 선택되면 버퍼의 공간이 다음 요소 (즉, 1,001-st)로 대체되어 1,000 요소 버퍼를 유지합니다.

6. map

Dataset의 map함수는 pandas의 map과 유사합니다.

Dataset 전체에 함수를 맵핑합니다.

Time Series Dataset을 만드려는 경우, train/label 값을 분류하는 용도로 활용할 수 있습니다.

x[:-1], x[-1:] 의 의도는 각 row의 마지막 index 전까지는 train data로, 마지막 index는 label로 활용하겠다는 의도입니다.

window_size=5

ds = tf.data.Dataset.range(10) 
ds = ds.window(window_size, shift=1, drop_remainder=True)
ds = ds.flat_map(lambda w: w.batch(window_size))
ds = ds.shuffle(10)

# 첫 4개와 마지막 1개를 분리
ds = ds.map(lambda x: (x[:-1], x[-1:]))
for x, y in ds:
    print('train set: {}'.format(x))
    print('label set: {}'.format(y))
train set: [4 5 6 7]
label set: [8]
train set: [1 2 3 4]
label set: [5]
train set: [3 4 5 6]
label set: [7]
train set: [2 3 4 5]
label set: [6]
train set: [5 6 7 8]
label set: [9]
train set: [0 1 2 3]
label set: [4]

실습 예제: Sunspots 데이터셋을 활용하여 window_dataset 만들기

import csv
import tensorflow as tf
import numpy as np
import urllib


url = 'https://storage.googleapis.com/download.tensorflow.org/data/Sunspots.csv'
urllib.request.urlretrieve(url, 'sunspots.csv')
('sunspots.csv', <http.client.HTTPMessage at 0x7f0a380dd8d0>)
with open('sunspots.csv') as csvfile:
    reader = csv.reader(csvfile, delimiter=',')
    next(reader)
    i = 0
    for row in reader:
        print(row)
        i+=1
        if i > 5:
            break
['0', '1749-01-31', '96.7']
['1', '1749-02-28', '104.3']
['2', '1749-03-31', '116.7']
['3', '1749-04-30', '92.8']
['4', '1749-05-31', '141.7']
['5', '1749-06-30', '139.2']

각 row의 2번 index의 데이터를 우리가 time series 데이터로 만들어 보려고 합니다.

train_data = []

with open('sunspots.csv') as csvfile:
    reader = csv.reader(csvfile, delimiter=',')
    # 첫 줄은 header이므로 skip 합니다.
    next(reader)
    for row in reader:
        train_data.append(float(row[2]))
train_data[:5]
[96.7, 104.3, 116.7, 92.8, 141.7]

일단, list에 모든 데이터를 담았습니다.

이제 Dataset 모듈을 통해 window_dataset을 만들어 보겠습니다.

train_data = np.asarray(train_data)
train_data.shape
(3235,)

train_data의 dimension을 늘려주었습니다.

train_data = np.expand_dims(train_data, 1)
train_data.shape
(3235, 1)

tensor slices로 변환하겠습니다.

dataset = tf.data.Dataset.from_tensor_slices(train_data)
i=0
for data in dataset:
    print(data)
    i+=1
    if i > 5:
        break
tf.Tensor([96.7], shape=(1,), dtype=float64)
tf.Tensor([104.3], shape=(1,), dtype=float64)
tf.Tensor([116.7], shape=(1,), dtype=float64)
tf.Tensor([92.8], shape=(1,), dtype=float64)
tf.Tensor([141.7], shape=(1,), dtype=float64)
tf.Tensor([139.2], shape=(1,), dtype=float64)

그 다음으로는 원하는 window_size만큼 묶어 주어야합니다.

내가 과거의 20일의 데이터를 보고 21일 째의 데이터를 예측해야한다라고 가정한다면,

window_size = 20 + 1 로 잡아줍니다.

20개는 train data의 갯수, 1은 label의 갯수입니다.

window_size=20 + 1

dataset = dataset.window(window_size, shift=1, drop_remainder=True)

그 다음에는 flat_map을 통해서 각 batch 별로 flatten하게 shape을 펼쳐줍니다.

dataset = dataset.flat_map(lambda w: w.batch(window_size + 1))
# 2개만 출력해서 결과를 살펴보겠습니다.
for data in dataset.take(2):
    print(data)
tf.Tensor(
[[ 96.7]
 [104.3]
 [116.7]
 [ 92.8]
 [141.7]
 [139.2]
 [158. ]
 [110.5]
 [126.5]
 [125.8]
 [264.3]
 [142. ]
 [122.2]
 [126.5]
 [148.7]
 [147.2]
 [150. ]
 [166.7]
 [142.3]
 [171.7]
 [152. ]], shape=(21, 1), dtype=float64)
tf.Tensor(
[[104.3]
 [116.7]
 [ 92.8]
 [141.7]
 [139.2]
 [158. ]
 [110.5]
 [126.5]
 [125.8]
 [264.3]
 [142. ]
 [122.2]
 [126.5]
 [148.7]
 [147.2]
 [150. ]
 [166.7]
 [142.3]
 [171.7]
 [152. ]
 [109.5]], shape=(21, 1), dtype=float64)

그 다음은 batch별로 shuffle을 해주면 좋겠네요~

buffer_size는 임의로 500개를 지정하겠습니다.

dataset = dataset.shuffle(500)

가장 중요한 마지막 단계 입니다.

train/label이 섞여서 21개의 데이터가 각 batch에 잡혀 있습니다.

train/label로 섞인 batch를 train (20개), label (1개)로 분리해주면 됩니다.

분리해줄 때 tuple로 묶어주지 않으면 error를 내뱉습니다.

dataset = dataset.map(lambda x: (x[:-1], x[-1:]))
for train, label in dataset.take(2):
    print('train: {}'.format(train))
    print('label: {}'.format(label))
train: [[276.2]
 [196.7]
 [241.7]
 [233.3]
 [189.5]
 [238.3]
 [186.7]
 [185. ]
 [206.7]
 [190. ]
 [183.3]
 [116.7]
 [163.3]
 [163.3]
 [158.3]
 [178.7]
 [146.7]
 [143.3]
 [143.3]
 [156.2]]
label: [[128.3]]
train: [[ 97.8]
 [164.5]
 [124.5]
 [ 88.3]
 [113.8]
 [174.5]
 [162.8]
 [122.5]
 [110. ]
 [ 85. ]
 [ 45.5]
 [111.7]
 [ 58.7]
 [ 90. ]
 [ 62.5]
 [ 61.7]
 [ 68.3]
 [ 90.5]
 [ 63.3]
 [ 61.7]]
label: [[73.3]]
for d in dataset.batch(10).take(2):
    print(d)
(<tf.Tensor: shape=(10, 20, 1), dtype=float64, numpy=
array([[[223.3],
        [225.8],
        [171.7],
        [212.5],
        [160.5],
        [156.7],
        [155. ],
        [151.7],
        [115.5],
        [145. ],
        [128.8],
        [140.5],
        [136.7],
        [123.3],
        [121.2],
        [103.3],
        [123.3],
        [128.7],
        [122.8],
        [107. ]],

       [[ 78. ],
        [109. ],
        [ 92.8],
        [ 73. ],
        [ 85.5],
        [ 47.5],
        [ 29.2],
        [ 11. ],
        [ 13.2],
        [ 23.3],
        [ 29.5],
        [ 20.3],
        [  7.3],
        [  0. ],
        [ 19.3],
        [ 18.7],
        [  6.5],
        [ 20.5],
        [  1.7],
        [ 13.2]],

        ...
        ...

       [[185.8],
        [187.2],
        [193.3],
        [187.8],
        [224.5],
        [176.7],
        [145.7],
        [212. ],
        [224.7],
        [165.3],
        [213.3],
        [228.7],
        [262.2],
        [261.7],
        [235.8],
        [290. ],
        [230. ],
        [215.3],
        [238.8],
        [180.8]]])>, <tf.Tensor: shape=(10, 1, 1), dtype=float64, numpy=
array([[[146.7]],

       [[ 60.8]],

       [[143.3]],

       [[187.8]],

       [[  6.5]],

       [[ 58.3]],

       [[ 19.3]],

       [[ 63.3]],

       [[185.8]],

       [[188.3]]])>)

종합하여 함수형으로 만들면 다음과 같이 됩니다.

train_data = []

with open('sunspots.csv') as csvfile:
    reader = csv.reader(csvfile, delimiter=',')
    # 첫 줄은 header이므로 skip 합니다.
    next(reader)
    for row in reader:
        train_data.append(float(row[2]))
def windowed_dataset(data, window_size, batch_size, shuffle_buffer):
    data = np.expand_dims(data, axis=1)
    ds = tf.data.Dataset.from_tensor_slices(data)
    ds = ds.window(window_size + 1, shift=1, drop_remainder=True)
    ds = ds.flat_map(lambda w: w.batch(window_size + 1))
    ds = ds.shuffle(shuffle_buffer)
    ds = ds.map(lambda w: (w[:-1], w[-1:]))
    return ds.batch(batch_size)
window_ds = windowed_dataset(train_data, window_size=20, batch_size=10, shuffle_buffer=500)

window_ds의 shape은 (10, 20, 1)로 출력이 되게 됩니다.

  • 10은 batch_size
  • 20은 window_size (train data)
  • 1은 label

로 return 되는 것을 확인할 수 있습니다.

for d in window_ds:
    print(d)
    break
(<tf.Tensor: shape=(10, 20, 1), dtype=float64, numpy=
array([[[  1.7],
        [ 13.2],
        [  5.3],
        [  9.3],
        [ 25.2],
        [ 13.2],
        [ 36.2],
        [ 19.3],
        [ 10.5],
        [ 36.3],
        [ 18.7],
        [ 31.7],
        [  1.7],
        [ 40.3],
        [ 26.7],
        [ 50. ],
        [ 58.3],
        [ 66.7],
        [ 75. ],
        [ 60.8]],

       [[ 43.8],
        [ 68.2],
        [ 72. ],
        [ 78. ],
        [109. ],
        [ 92.8],
        [ 73. ],
        [ 85.5],
        [ 47.5],
        [ 29.2],
        [ 11. ],
        [ 13.2],
        [ 23.3],
        [ 29.5],
        [ 20.3],
        [  7.3],
        [  0. ],
        [ 19.3],
        [ 18.7],
        [  6.5]],

       [[ 39.2],
        [ 38.7],
        [ 47.5],
        [ 73.3],
        [ 58.3],
        [ 83.3],
        [118.3],
        [ 98.8],
        [ 99.5],
        [ 66. ],
        [130.7],
        [ 48.8],
        [ 45.2],
        [ 77.7],
        [ 62.7],
        [ 66.7],
        [ 73.3],
        [ 53.3],
        [ 76.2],
        [ 63.3]],

       [[125.7],
        [116.7],
        [ 72.5],
        [ 75.5],
        [ 94. ],
        [101.2],
        [ 84.5],
        [110.5],
        [ 99.7],
        [ 39.2],
        [ 38.7],
        [ 47.5],
        [ 73.3],
        [ 58.3],
        [ 83.3],
        [118.3],
        [ 98.8],
        [ 99.5],
        [ 66. ],
        [130.7]],

       [[ 60. ],
        [ 77. ],
        [ 77.8],
        [108.2],
        [254.5],
        [199.2],
        [112.8],
        [ 97.5],
        [169. ],
        [150. ],
        [166.2],
        [159.5],
        [168.2],
        [151.3],
        [ 51.8],
        [153.7],
        [ 63.3],
        [ 95. ],
        [128.8],
        [ 93.7]],

       [[  5.5],
        [  6.7],
        [  7.2],
        [  8.3],
        [  9.5],
        [ 32. ],
        [ 45.7],
        [ 50. ],
        [ 71.7],
        [ 54.8],
        [ 49.7],
        [ 55.5],
        [ 36.5],
        [ 68. ],
        [ 71.2],
        [ 73.5],
        [ 91.2],
        [ 88.8],
        [ 89.2],
        [110.2]],

       [[ 49.7],
        [ 55.5],
        [ 36.5],
        [ 68. ],
        [ 71.2],
        [ 73.5],
        [ 91.2],
        [ 88.8],
        [ 89.2],
        [110.2],
        [ 77.2],
        [ 71.2],
        [129.5],
        [129. ],
        [ 87.7],
        [111.3],
        [124.7],
        [129.7],
        [151. ],
        [186.3]],

       [[159.5],
        [168.2],
        [151.3],
        [ 51.8],
        [153.7],
        [ 63.3],
        [ 95. ],
        [128.8],
        [ 93.7],
        [ 84.2],
        [131. ],
        [102.2],
        [106.7],
        [ 91. ],
        [ 48.3],
        [ 85.3],
        [ 54.8],
        [ 68.5],
        [ 47.3],
        [ 46.2]],

       [[ 60.8],
        [ 65. ],
        [159.2],
        [133.8],
        [134.5],
        [158.3],
        [186.7],
        [193.7],
        [177.5],
        [243.3],
        [262.2],
        [295.5],
        [182.2],
        [223.3],
        [241.7],
        [398.2],
        [286. ],
        [255. ],
        [233.3],
        [286.2]],

       [[260.5],
        [250.5],
        [175. ],
        [191.2],
        [276.2],
        [196.7],
        [241.7],
        [233.3],
        [189.5],
        [238.3],
        [186.7],
        [185. ],
        [206.7],
        [190. ],
        [183.3],
        [116.7],
        [163.3],
        [163.3],
        [158.3],
        [178.7]]])>, <tf.Tensor: shape=(10, 1, 1), dtype=float64, numpy=
array([[[ 65. ]],

       [[ 20.5]],

       [[ 60. ]],

       [[ 48.8]],

       [[ 84.2]],

       [[ 77.2]],

       [[123.2]],

       [[ 21.2]],

       [[260.5]],

       [[146.7]]])>)


관련 글 더보기

- TensorBoard 활용법 및 colab에서 로드하기

- TensorFlow Datasets API 활용법

- tensorflow 2.0 ImageDataGenerator / Convolution Neural Network(CNN) 을 활용한 이미지 분류

- TensorFlow 2.0 - 단어 토큰화, Embedding, LSTM layer를 활용한 뉴스 데이터 sarcasm 판단

- Digit Recognizer (Kaggle) - over 99% accuracy

데이터 분석, 머신러닝, 딥러닝의 대중화를 꿈 꿉니다.