tf.data 삽질해보기 (two iterator, feed_dict, GAN)

2020. 4. 8. 19:48분석 Python/Tensorflow

728x90

텐서플로우를 사용하면서 기존에는 feed_dict 방식을 사용했는데, tf.data를 사용 시 여러 가지 장점이 있는 것 같아서 만들 때마다 사용하는 것 같다. 실제로 모델링까지 해본 것은 다른 글에 있으니 눌러서 확인해보시면 될 것 같다(https://data-newbie.tistory.com/441)

필자는 GAN을 할 때 시도를 해봤는데, 먼가 코드에서 꼬이는 현상이 발생했다. 지금도 완벽히 해결하지 못했지만,
여러 가지 삽질을 해보면서 얻은 것을 공유해보고자 한다.

1. tf.data 2개의 iterator 생성해보기

일단 GAN을 학습 시 보통 다음과 같이 한다.

for _ in range(10) :
	####
    while True :
		sess.run([dloss, doptimizer])
		sess.run([gloss, goptimizer])
    except ###
    ###

이런 식으로 session을 2개를 열다 보니 하나의 iterator를 사용하게 되면 2개의 배치가 소모되는 현상이 있었다.
그래서 이것을 해결해보기 위해 일단 2개의 iterator를 만들고, sess.run을 했을 때 실제 같은 값이 나오는지 확인해봤다.

import tensorflow as tf
import numpy as np
tf.reset_default_graph()
X = tf.placeholder(tf.float32, [None,2], name="X")
batch_size = tf.placeholder(tf.int64, name="batch_size")
seed = tf.placeholder(tf.int64, name="seed")
os.environ["CUDA_DEVICE_ORDER"]="PCI_BUS_ID"   # see issue #152
os.environ["CUDA_VISIBLE_DEVICES"]="-1"
dataset = tf.data.Dataset.from_tensor_slices(X).\
batch(batch_size=batch_size, drop_remainder=True).\
shuffle(buffer_size=100,seed=seed)

이제 위에서 만든 dataset에서 2개의 iterator를 만든다.

iter = dataset.make_initializable_iterator()
iter2 = dataset.make_initializable_iterator()
####
x_copy1 = iter.get_next()
x_copy2 = iter2.get_next()

사용한 데이터는 다음과 같다.

a = np.arange(20).reshape(-1,1)
b = np.concatenate((a,a[::-1]),axis=1)
b

이제 실제로 데이터를 다 사용하는지 확인하기 위해서 nIter에 도달하는지 확인해본다.

BATCH=2
nIter = len(b)/BATCH
with tf.Session() as sess:
    for i in range(3) :
        sess.run(iter.initializer , 
                 feed_dict= {X : b, 
                             batch_size : BATCH,
                             seed : i
                            }) # switch to train dataset
        sess.run(iter2.initializer , 
                 feed_dict= {X : b, 
                             batch_size : BATCH,
                             seed : i 
                            }) # switch to train dataset
        j = 0 
        while True:
            try: 
                print(j/nIter)
                j += 1
                test = sess.run(x_copy1)
                test1 = sess.run(x_copy2)
            except tf.errors.OutOfRangeError as e :
                print("--")
                break

위의 그림처럼 1.0이 되면, nIter를 다 돈 것인데, 실제로 도는 것을 확인했다. 
그래서 2개의 iterator를 만드는 것은 잘 작동한다.

2. iterator와 feed_dict을 같이 사용할 때 Neural Network의 성능 차이 현상 있는지 확인

실제 추론 시에는 학습 시 iterator랑은 동일하게 안 만들어도 되기 때문에 feed_dict을 활용하기로 했다.
그때 먼가 굉장히 성능 차이가 나서 실험을 해봤다. 여기서 성능 차이는 GAN의 생성물이 많이 차이가 난 것을 의미한다.
간단한 실험을 위해 여기서는 회귀모형을 만들어봤다.

XX = np.random.normal(size=[20,2])
YY= 5*XX[:,[0]] - 2*XX[:,[1]] + 0.1
tf.reset_default_graph()
X = tf.placeholder(tf.float32, [None,2], name="X")
Y = tf.placeholder(tf.float32, [None,1], name="y")
batch_size = tf.placeholder(tf.int64, name="batch_size")
seed = tf.placeholder(tf.int64, name="seed")
dataset = tf.data.Dataset.from_tensor_slices((X,Y)).batch(batch_size=batch_size, drop_remainder=True)#.shuffle(buffer_size=100,seed=seed)
iter = dataset.make_initializable_iterator()
x , y = iter.get_next()

with tf.variable_scope("regression") :
    w = tf.get_variable("w", shape=[2,10])
    b = tf.get_variable("b", shape=[10])
    finalw = tf.get_variable("Finalw", shape=[10,1])
    finalb = tf.get_variable("Finalb", shape=[1])
    
def layer(X, reuse= False) :
    with tf.variable_scope("regression", reuse = reuse) :
        layer = tf.add(tf.matmul(X,w) ,b)
        logit = tf.add(tf.matmul(layer,finalw) ,finalb)
    return logit
    

 여기서 학습 시에는 $x$를 사용하고, 추론 시에는 $X$를 사용해봤다.

logit = layer(x,reuse=False)
########## inference
inference_logit = layer(X,reuse=True)
mse = tf.reduce_mean(tf.square(tf.subtract(y,logit)),name="mse")
w_list = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES ,scope = "regression")
opt = tf.train.AdamOptimizer(1e-5).minimize(mse, var_list = w_list)

BATCH = 2
nIter = int(len(XX)/BATCH)
with tf.Session() as sess:
    init = tf.group(tf.global_variables_initializer(), tf.local_variables_initializer())
    sess.run(init)
    for i in range(3) :
        sess.run(iter.initializer , 
                 feed_dict= {X : XX, 
                             Y : YY,
                             batch_size : BATCH,
                            }) # switch to train dataset
        j = 0 
        while True:
            try: 
                print(f"{j}/{nIter}",end="\r")
                j += 1
                _ = sess.run(opt)
            except tf.errors.OutOfRangeError as e :
                break
        #### point ####
        sess.run(iter.initializer , 
                 feed_dict= {X : XX, 
                             Y : YY,
                             batch_size : len(XX),
                            }) # switch to train dataset
        result = sess.run(logit)
        inference_result = sess.run(inference_logit,feed_dict = {X : XX })
        

다행히도 결과는 동일하게 나왔다. 근데 왜 GAN은 다르게 나왔을까... 또 삽질을 시작했다.

암튼 이러한 문제를 겪었고, 결국 2개의 iterator를 만들었는데, 실제 테스트할 때는 문제가 없지만, GAN에서 학습할 때는 또 문제가 발생했다. 굉장히 스트레스를 받으면서 여기저기 뒤적거리다가 드디어 iterator에 대한 문제의 원인을 발견했다.

Batch Normalization!!!!!!!!!!!!!!!

사실 이 문제는 GAN 알고리즘을 tf.data에 적용할 때만 생기는 문제일 것이다.
왜냐하면 GAN 알고리즘은 각각의 값을 서로의 loss에서 사용하다 보니 먼가 연결되어 있는 것 같다. 
그래서 batch normalization을 안 쓰고 2개의 iterator를 만들어서 loss를 만들어서 해봤는데, 성능이 기존보다 아주 엉망이다. (이 원인은 아직도 찾지 못했다.) 실제로 loss 부분에서도 기존과 굉장히 다른 loss를 패턴이 나왔다. 어차피 iterator는 데이터만 넣어주는 역할로 필자는 생각하고 있는데, 왜 이렇게 구성하니 학습이 안되는지 도무지 이해가 되지 않는다. ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ

실제로 사람들이 GAN 알고리즘을 적용할 때, tf.data를 적용한 것을 본 적이 없다. 
아마도 성능에서 먼가 문제가 발생하기 때문에  사용하지 않는 것 같기도 하다는 생각이 들었다.


암튼 결론을 말하자면,
tf.data + GAN을 쓸 때는 `batch normalization`을 쓰지 않으면  iterator 문제는 해결되지만, 성능 문제는 여전히 미스터리로 남아있다.(언젠가는 해결할 수 있기를...)

 

728x90