2015년 11월에 공개된 Tensorflow에는 "이름공간(Name Space)"의 기능이 추가됐다. 이것은 TensorBoard에서 그래프 시각화를 위해 사용되지만 이 기능만 하는 것은 아니다. 이름 공간은 식별자 관리에 매우 도움이 된다.
한편, Python의 변수 스코프는 로컬, 글로벌, 논글로벌 밖에 없기 때문에 TensorFlow의 코어 부분을 c++레벨 수준의 이름 공간을 서포트하기 위해 Google 엔지니어가 개발한 것으로 보인다.
Neural Network의 경우 층이 별로 깊지 않은 MLP(Multi-Layer Pereptron)등에서는 이름 관리로 힘들지 않지만, 깊은 CNN이나 RNN에서 볼 수 있는 커다란 모델의 경우에는 가중치 등을 공유할 필요도 있고, 제대로 된 변수 스코프가 필요하다. 또한, 스케일업을 고려하면 Multi-Device(GPU), 클러스터과 같은 분산 환경에 코드를 적용해야할 필요가 있다. 이러할 경우도 변수 스코프가 필요하게 된다.
따라서 이번 포스팅에서는 Tensorflow의 "이름공간"을 이해하기 위한 관련 API를 확인해보고자한다.
TensorFlow 변수 스코프 관계의 포인트
공식 문서를 읽었을 때, 애매했던 부분은 아래의 두 가지였다.
- 스코프를 정의할 때 사용하는 tf.name_scope()와 tf.variable_scope의 차이점은 무엇인가?
- TensorFlow의 변수 정의시에는 tf.Variable()을 사용하는 것은 알겠지만, tf.get_variable()은 언제 사용하는 것인가?
먼저 답변을 적자면, 첫 번째 질문의 경우 tf.name_scope()는 보다 범용적으로 사용하는 스코프정의를 위해, tf.variable_scope()는 변수(식별자)를 관리하기 위해 전용의 스코프를 정의하기 위해 사용한다. 두 번째 질문의 경우는 tf.Variable()은 보다 저레벨의 변수 정의인 반해, tf.get_variable()은 변수 스코프를 고려한 보다 고레벨의 변수 정의이다.
코드의 동작을 살펴보면서 더욱 이해를 깊이해보자.
# tf.name_scope
with tf.name_scope("my_scope"):
v1 = tf.get_variable("var1", [1], dtype=tf.float32)
v2 = tf.Variable(1, name="var2", dtype=tf.float32)
a = tf.add(v1, v2)
print(v1.name) # var1:0
print(v2.name) # my_scope/var2:0
print(a.name) # my_scope/Add:0
먼저, tf.name_scope()를 사용해 그 안에 변수를 정의했다. 그리고 각 출력 결과는 print문의 오른쪽에 커맨드에 달아놓았다. tf.Variable()로 정의한 변수 v2와 가산 연산 a에 대해서는 제대로 "my_scope"가 정의되어 있다. 한편으로, get.variable()로 정의한 v1의 경우 스코프를 무시하고 있다.
# tf.variable_scope
with tf.variable_scope("my_scope"):
v1 = tf.get_variable("var1", [1], dtype=tf.float32)
v2 = tf.Variable(1, name="var2", dtype=tf.float32)
a = tf.add(v1, v2)
print(v1.name) # my_scope/var1:0
print(v2.name) # my_scope_1/var2:0 ... 스코프 이름이 update됐다.
print(a.name) # my_scope_1/Add:0 ... update후의 스코프명이 유지되고 있다..
다음은 tf.variable_scope()를 사용했다. tf.get_variable()로 정의한 v1은 바란대로 "my_scope"가 변수명에 붙어 식별자가 만들어졌다. 또한 그 아래의 v2와 연산a는 tf.variable_scope("my_scope")로 정의했음에도 불구하고 "my_scope"가 아닌 "my_scope_1"가 됐다. 그 이유는 원래 "my_scope"가 되어야 하지만, 이미 동일한 식별자("my_scope/var2:0")가 바로 앞의 코드 스니펫에서 사용됐으므로 자동으로 update된 것이다.
헷갈릴 수 있기 때문에 여기서 한 번 정리를 하자면,
- tf.name_scope()는 범용적으로 사용하는 이름 스코프를 정의한다. (TensorBoard 출력시의 식별자 설정)
- tf.variable_scope()는 변수의 관리에 사용하는 스코프를 정의한다.
- tf.get_variable()은 변수명의 식별자를 관리하면서, 변수를 정의한다. 반드시 tf.variable_scope()와 세트로 사용해야한다.
위 두 개의 스니펫에서는 실험을 위해 여러모로 복잡하게 됐지만, "tf.get_variable()는 tf.variable_scope()와 세트로 사용한다"라는 기본을 지키면 특별히 어렵지 않다.
그럼, tf.get_variable()를 사용해, 공유변수를 사용하는 방법에 대해 알아보자.
with tf.variable_scope("my_scope2"):
v4_init = tf.constant_initializer([4.])
v4 = tf.get_variable("var4", shape=[1], initializer=v4_init)
print(v4.name) # my_scope2/var4:0
먼저, 스코프 "my_scope2"안에 변수 v4를 정의했다. tf.get_variable()에서는 변수 initializer를 지정해 변수를 정의한다. 여기서는 정수의 initializer를 사용해, v4에 4.가 들어있는 상태로 했다. TensorFlow에서 식별자는 제 1인수에 "var4"를 지정햇다.
다음은 동일한 식별자를 정의해 변수가 확보되어 있는지 확인해보았다.
with tf.variable_scope("my_scope2"):
v5 = tf.get_variable("var4", shape=[1], initializer=v4_init)
ValueError: Variable my_scope2/var4 already exists, disallowed. Did you mean to set reuse=True in VarScope? Originally defined at:
File "name_scope_ex.py", line 47, in <module>
v4 = tf.get_variable("var4", shape=[1], initializer=v4_init)
예상대로 에러가 발생했다. 동일한 식별자를 사용해 변수를 재할당하고 싶다면 아래와 같이 reuse 옵션을 사용하면 된다.
with tf.variable_scope("my_scope2", reuse=True):
v5 = tf.get_variable("var4", shape=[1])
print(v5.name) # my_scope2/var4:0
혹은 아래와 같이 작성해도 괜찮다.
with tf.variable_scope("my_scope2"):
tf.get_variable_scope().reuse_variables()
v5 = tf.get_variable("var4", shape=[1])
print(v5.name) # my_scope2/var4:0
이상, tf.variable_scope()와 tf.get_variable() 기본 기능을 확인해봤다.
공유변수의 예 - AutoEncoder
그럼 공유 변수의 사용 예를 살펴보자. TensorFlow-Sharing Variable의 문서에는 다음과 같이 예가 나와있다.
- models/image/cifar10.py, Model for detecting objects in images.
- models/rnn/rnn_cell.py, Cell functions for recurrent neural networks.
- models/rnn/seq2seq.py, Functions for building sequence-to-sequence models.
그러나 모두 코드가 꽤 길기 때문에, 여기서는 AutoEncoder의 가중치 공유하는 방법을 통해 알아보고자한다. AutoEncoder은 encode쪽과 decode쪽을 다음과 같이 정의하는 것이 가능하다.
이와 같이 대칭적인 형태의 AutoEncoder에서는 다음과 같이 가중치 공유를 사용할 수 있다.
위와 같은 구성의 네트워크를 TensorFlow의 공유 변수를 이용해 정의해보았다. 처음에는 Encoder클래스를 정의하였다.
# Encoder Layer
class Encoder(object):
def __init__(self, input, n_in, n_out, vs_enc='encoder'):
self.input = input
with tf.variable_scope(vs_enc):
weight_init = tf.truncated_normal_initializer(mean=0.0, stddev=0.05)
W = tf.get_variable('W', [n_in, n_out], initializer=weight_init)
bias_init = tf.constant_initializer(value=0.0)
b = tf.get_variable('b', [n_out], initializer=bias_init)
self.w = W
self.b = b
def output(self):
linarg = tf.matmul(self.input, self.w) + self.b
self.output = tf.sigmoid(linarg)
return self.output
변수 스코프를 옵션 vs_enc로 지정하여 설정하고, 그 안에 tf.get_variable()를 이용해 W로 정의히고 있다. 다음은 Decoder 클래스이다.
# Decoder Layer
class Decoder(object):
def __init__(self, input, n_in, n_out, vs_dec='decoder'):
self.input = input
if vs_dec == 'decoder': # independent weight
with tf.variable_scope(vs_dec):
weight_init = tf.truncated_normal_initializer(mean=0.0, stddev=0.05)
W = tf.get_variable('W', [n_in, n_out], initializer=weight_init)
else: # weight sharing (tying)
with tf.variable_scope(vs_dec, reuse=True): # set reuse option
W = tf.get_variable('W', [n_out, n_in])
W = tf.transpose(W)
with tf.variable_scope('decoder'): # in all case, need new bias
bias_init = tf.constant_initializer(value=0.0)
b = tf.get_variable('b', [n_out], initializer=bias_init)
self.w = W
self.b = b
def output(self):
linarg = tf.matmul(self.input, self.w) + self.b
self.output = tf.sigmoid(linarg)
return self.output
커다란 부분은 Encoder 클래스와 동일하지만, 변수 W의 정의문을 분기문으로 처리하고 있다. 네트워크 정의 부분은 다음과 같다.
# make neural network model
def make_model(x):
enc_layer = Encoder(x, 784, 625, vs_enc='encoder')
enc_out = enc_layer.output()
dec_layer = Decoder(enc_out, 625, 784, vs_dec='encoder')
dec_out = dec_layer.output()
return enc_out, dec_out
Decoder 옵션을 생성할 때, vs_dec='decoder'를 지정하거나, 이 옵션을 생략하는 것으로 공유 변수 W는 신규로 확보되어, 위에 작성한 vs_dec='encoder'와 같이 Encoder에 이용한 변수 스코프와 동일하게 한 경우, 가중치 변수는 공유 변수로써 W를 재사용하도록 구현된다.
MNIST 데이터의 계산 실행 예를 나타내고 있지만, 먼저 가중치를 공유하지 않은 경우, 다음과 같은 결과가 나타난다.
Training...
step, loss = 0: 0.732
step, loss = 1000: 0.271
step, loss = 2000: 0.261
step, loss = 3000: 0.240
step, loss = 4000: 0.234
step, loss = 5000: 0.229
step, loss = 6000: 0.219
step, loss = 7000: 0.197
step, loss = 8000: 0.195
step, loss = 9000: 0.193
step, loss = 10000: 0.189
loss (test) = 0.183986
공유한 경우 다음과 같이 출력된다.
Training...
step, loss = 0: 0.707
step, loss = 1000: 0.233
step, loss = 2000: 0.215
step, loss = 3000: 0.194
step, loss = 4000: 0.186
step, loss = 5000: 0.174
step, loss = 6000: 0.167
step, loss = 7000: 0.154
step, loss = 8000: 0.159
step, loss = 9000: 0.152
step, loss = 10000: 0.152
loss (test) = 0.147831
가중치 공유 설정으로 동일한 학습임에도 손실이 낮아지고 속도도 빨라졌다.
참고자료
'IT > AI\ML' 카테고리의 다른 글
[python/TensorFlow2.x] TensorFlow의 GradientTape에 대한 간단한 설명 (0) | 2022.01.15 |
---|---|
[python/Tensorflow1.x] 그래프, 세션의 기초와 디버그 방법 그리고 변수의 기초와 재사용 방법 (0) | 2022.01.13 |
[python] 결정계수 R2와 자유도 조정 결정 계수 R*2 (0) | 2021.11.28 |
[Tensorflow] Tensorflow Profiler 사용법 (0) | 2021.09.21 |
[python/tensorflow2.x] Tensorflow2.x버전 작성법(2) (0) | 2021.09.14 |