이전 이야기: https://rnltls.tistory.com/9
TensorFlow (이하 TF)에서 batch로 무언가 추론하고싶다면, 어떻게 해야할까?
방법을 알기에 앞서, 공식 문서를 확인해 보았다.
Args | |
---|---|
x | Input samples. It could be: - A Numpy array (or array-like), or a list of arrays (in case the model has multiple inputs). - A TensorFlow tensor, or a list of tensors (in case the model has multiple inputs). - A tf.data dataset. - A generator or keras.utils.Sequence instance. - A more detailed description of unpacking behavior for iterator types (Dataset, generator, Sequence) is given in the Unpacking behavior for iterator-like inputs section of Model.fit. |
predict의 x부분을 가져왔다.
보는것처럼, 총 4가지를 입력으로 받을 수 있다.
- 먼저 Numpy array를 입력으로 받을 수 있다. 또는, list of arrays를 입력으로 받을 수 있는데, list of arrays를 입력으로 받는 경우에는 model이 다중 입력을 요구할 때 입력으로 사용한다.
- 또는 TF tensor를 입력으로 받을 수 있다. 또는 list of tensors를 입력으로 받을 수 있는데, Numpy array와 동일하게 model이 다중 입력을 요구할 때 입력으로 사용한다.
- tf.data의 dataset을 입력으로 받을 수 있다.
- Generator나 keras.utils.Sequence를 입력으로 받는다.
나는 단일 입력 추론과 다중 입력 추론을 다음과 같이 생각했다.

단일 데이터 입력은 한가지의 입력을 주면 한가지의 결과를 반환해주는 것이고, 다중 데이터 입력은 여러가지의 입력을 배치 처리하여 결과를 반환해 주는 것이라 생각했다.
이를 표로 정리하자면 다음과 같다
단일 데이터 입력 | 다중 데이터 입력 |
- A Numpy array, list of arrays - TensorFlow tensor, list of tensors |
- A tf.data dataset - A generator or keras.utils.Sequence |
사실 generator 또는 keras.utils.Sequence는 다중 입력 추론이 맞는지는 모르겠으나, 공식 문서를 살펴본 결과 keras.utils.Sequence도 dataset의 역할을 하고 있다고 생각되어, 표에 추가하였다.
현재 나는 VGG19를 tensorflow.keras.applications.vgg19에서 import한 뒤에 따로 model을 save해서 사용하고 있었고,
data는 TensorFlowDataSet (이하 tfds)에서 받은 caltech101을 사용하고 있었지만, tf.data의 형태로 사용하고 있지 않고, 직접 load해서 사용하고 있었다.
# Data load
dataPath = 'C:/Users/(사용자이름)/tensorflow_datasets/downloads/extracted/TAR_GZ.ucexport_download_id_137RyRjvTBkBiIfeYBNZ_Ewspr27OLzOXkcog-FWUPYtV3WCJLAolEF_NYx7j1kMPmSY/101_ObjectCategories'
dirs = [f for f in os.listdir(dataPath)]
datasets=[]
for categories in dirs:
newpath = os.path.join(dataPath, categories)
for f in os.listdir(newpath):
datasets.append(os.path.join(newpath,f))
def loadImage(imgPath, size):
try:
img = image.load_img(imgPath, target_size=size)
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)
return x
except Exception as e:
print(f'Error: {e}, loadImage Failed')
for i in range(len(datasets)):
datasets[i] = loadImage(datasets[i],(224,224))
datasets=datasets[:1000]
# Model load
model = tf.keras.models.load_model('VGG19')
tfds에서 caltech101을 불러온다면 dataPath위치에 실제 사진이 저장된다.
왜 내가 이미지 로드를 tfds로 하지 않았는지는 기억나지 않지만, 일단 이대로 진행했다.
만약에 나의 경우와 동일하게, 이미 존재하는 dataset을 이용하는것이 아닌 로컬 파일에 존재하는 이미지를 사용하고, 해당 이미지를 다중 데이터 입력으로 처리하고자 하면 어떻게 하면 될까?
다음은 load된 dataset중 하나를 나타낸다.
[[[[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
...
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]]
[[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
...
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]]
[[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
...
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]]
...
[[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
...
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]]
[[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
...
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]]
[[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
...
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]
[-103.939 -116.779 -123.68 ]]]]
아마 이는 list of numpy array에 해당할 것이다. 내 dataset은 이와같은 list of numpy array를 여러개로 묶은 list of numpy array이다.
초기에는 단일 데이터 입력과 다중 데이터 입력을 제대로 구분짓지 못하여, 다음과 같이 코드를 수행했다.
model.predict(datasets)
이 경우에는 다음과 같은 오류가 나타난다.
ValueError: Layer "vgg19" expects 1 input(s), but it received 1000 input tensors.
아, tf.data로 입력을 주지 않으면 단일 데이터 입력 (또는 입력이 여러개 필요한 모델) 로 인식한다는것을 깨달았다.
오직
model.predict(datasets[0])
과 같이 수행할 경우에만 추론이 잘 작동하였다.
따라서 Numpy array 또는 Tensor로 이루어진 list를 dataset의 형태로 변환하여야만 다중 데이터 입력이 가능하리라.
방법은 간단했다.
datasets = tf.data.Dataset.from_tensor_slices(datasets)
나의 경우에는 다음과 같이 내가 local에서 load한 데이터를 tf.data.Dataset의 형태로 변환하였다.
이 경우에는 아주 잘 작동되었다.
따라서 이전보다 사진 개수를 늘려 5000장의 이미지에 대해서 tf.dataset으로 변환하여 batch_size를 1,10,32,10000로 한 경우, 이미지를 하나씩 predict하는 경우 (batch_size를 1로 한것과 같을것 같지만, 우선은 진행하였다), 이미지를 하나씩 model.call하여 수행한 경우, 이미지를 하나씩 layer by layer로 수행한 경우를 종합하여 결론을 내고자 하였다.
predict는 마지막에 numpy로 결과를 반환하여 주기 때문에, call하는 경우에는 최종 결과를 numpy로 변환하는것 까지를 수행시간으로 계산하였다.
tf.dataset으로 변환하여 수행하는 경우, tf.data.Dataset으로 변환하는데 약 130초 정도 걸렸다. 하지만 전체 수행시간이 아닌, model의 수행시간이 궁금했기 때문에 따로 추가하지는 않았다.
ds_time=time.process_time()
datasets = tf.data.Dataset.from_tensor_slices(datasets)
de_time=time.process_time()
print('tf.data.Dataset으로 변환되는데 결리는 시간: ', de_time-ds_time)
start_time = time.process_time()
model.predict(datasets, batch_size=10, verbose=0)
end_time = time.process_time()
print('Time elapsed: ', end_time-start_time)
batch_size | 1 | 10 | 32 | 10000 |
---|---|---|---|---|
수행시간 | 33.03125 | 32.921875 | 32.96875 | 32.453125 |
측정에 오차가 조금 있을 수 있지만, 지난 포스팅에서 batch_size가 증가할수록 속도가 빨라진다는것은 어느정도 영향이 있는것 같지만, 그닥 큰 효과를 보지는 못한것 같다.
또한,
2023-01-27 14:24:28.682440: W tensorflow/core/common_runtime/bfc_allocator.cc:290] Allocator (GPU_0_bfc) ran out of memory trying to allocate 2.15GiB with freed_by_count=0. The caller indicates that this is not a failure, but this may mean that there could be performance gains if more memory were available.
과 같은 에러는 아니지만, 메모리 부족의 메시지가 떴기 때문에, 이전 포스팅인 1000장으로도 테스트 해봤다.
1000장의 경우 tf.data.Dataset으로 변환하는데에 약 27초정도 소요되었다.
batch_size | 1 | 10 | 32 | 10000 |
---|---|---|---|---|
수행시간 | 7.546875 | 7.59375 | 7.53125 | 7.46875 |
1000장의 경우 다음과 같은 에러는 발생하지 않았고, 역시 batch_size에 따라 어느정도 영향은 있지만 큰 영향은 아니었다.
다음은 5000장의 이미지에 대해 이미지를 한장씩 predict하는 경우, 한장씩 model,call 하는 경우, 한장씩 layer by layer 수행하는 경우를 보여준다. 코드는 아래와 같다
elaptime=0
# predict
for data in datasets:
start_time = time.process_time()
rlt=model.predict(data, verbose=0)
end_time = time.process_time()
elaptime += (end_time-start_time)
print('Time elapsed: ', elaptime)
elaptime=0
# call
for data in datasets:
start_time = time.process_time()
rlt=model(data)
rlt=rlt.numpy()
end_time = time.process_time()
elaptime+=end_time-start_time
print('Time elapsed: ', elaptime)
elaptime=0
# layer by layer
def calcLayerTime(x):
global elaptime
result = x
for layer in model.layers:
start_time = time.process_time()
result = layer.call(result)
end_time = time.process_time()
elaptime += end_time-start_time
return result
for data in datasets:
calcLayerTime(data)
print('Time elapsed: ', elaptime)
predict | call | layer by layer | |
---|---|---|---|
수행 시간 | 235.28125 | 39.8125 | 37.5 |
이미지가 1000장인 경우에도 측정을 해보았다.
predict | call | layer by layer | |
---|---|---|---|
수행 시간 | 47.0 | 9.171875 | 9.984375 |
5000장일때, 1000장일대를 종합하여 비교해 보았다.
b_x는 batch_size를 x로 하여 다중 데이터 입력을 수행한 것이다.
b_1 | b_10 | b_32 | b_10000 | predict | call | layer by layer | |
---|---|---|---|---|---|---|---|
1000 | 7.546875 | 7.59375 | 7.53125 | 7.46875 | 47.0 | 9.171875 | 9.984375 |
5000 | 33.03125 | 32.921875 | 32.96875 | 32.453125 | 235.28125 | 39.8125 | 37.5 |
predict를 dataset을 이용하여 하는 경우가 가장 수행시간이 짧았다.
그러나 dataset으로 기존 데이터를 변환하는데에 걸린 시간을 포함하게 된다면 b_x는 1000장과 5000장 각각 27초, 130초를 더해야 하기 때문에, 실시간성을 고려한다면 call방식과 layer by layer 방식이 가장 적합하다고 판단된다.
Dataset 이미지 출처: caltech101
'끄적끄적' 카테고리의 다른 글
Non-Sequential Model 묶기 (0) | 2023.02.02 |
---|---|
Yolo 개척기 (2) | 2023.01.31 |
(TensorFlow) 첫번째 추론 시간, First inference time (0) | 2023.01.30 |
predict, call, batch_size (0) | 2023.01.26 |
MuZero 코드 (0) | 2021.08.17 |