기울기와 자동 미분
기울기와 자동 미분 (Gradients and automatic differentiation)
모델을 학습한다는 건 결국 "손실을 줄이려고 가중치를 어느 방향으로 얼마나 움직이면 되는지"를 알려면 기울기(gradient) 가 필요하다는 뜻이에요. TensorFlow는 tf.GradientTape 로 순전파에서 수행한 연산을 기억했다가 역전파에서 기울기를 자동으로 계산해 줘요. 손으로 미분하지 않아도 되게 만드는 편리한 API예요.
출처: TensorFlow 공식 문서 — Introduction to gradients and automatic differentiation
GradientTape로 기울기 계산하기
자동 미분을 하려면 TensorFlow가 순전파에서 어떤 연산이 어떤 순서로 일어났는지 기억해야 해요. tf.GradientTape 컨텍스트 안에서 연산을 수행하면 앞으로 역전파에서 그 목록을 역순으로 훑으며 기울기를 계산할 수 있어요. 기울기는 주로 tf.Variable 에 대해 구해요.
# dy/dx = 2x
dy_dx = tape.gradient(y, x)
dy_dx.numpy() # 6.0
스칼라뿐 아니라 어떤 텐서에도 똑같이 동작해요. 여러 변수를 딕셔너리로 묶어서 한 번에 기울기를 구할 수도 있어요.
my_vars = {'w': w, 'b': b}
grad = tape.gradient(loss, my_vars)
grad['b']
모델에 대한 기울기
보통 tf.Variable 들을 tf.Module 이나 그 하위 클래스(tf.keras.layers.Layer, tf.keras.Model)로 묶어 관리해요. 그렇게 하면 모델의 모든 학습 가능 변수에 대한 기울기를 한 번에 구할 수 있어요.
layer = tf.keras.layers.Dense(2, activation='relu')
x = tf.constant([[1., 2., 3.]])
with tf.GradientTape() as tape:
# 순전파
y = layer(x)
loss = tf.reduce_mean(y ** 2)
# 모든 학습 가능 변수에 대한 기울기
grad = tape.gradient(loss, layer.trainable_variables)
테이프가 무엇을 감시할지 제어하기
tf.GradientTape 는 기본적으로 tf.Variable 을 자동으로 감시하지만 tf.Tensor 는 감시하지 않아요. 텐서에 대한 기울기를 기록하고 싶으면 tape.watch(x) 를 호출해야 해요. 감시되지 않는 텐서의 기울기는 None 으로 돌아와요.
with tf.GradientTape() as tape:
y = (x0 ** 2) + (x1 ** 2) + (x2 ** 2)
grad = tape.gradient(y, [x0, x1, x2, x3])
for g in grad:
print(g)
# tf.Tensor(6.0, shape=(), dtype=float32)
# None
# None
# None
gradient 가 None 을 돌려주는 경우
대표적인 경우가 몇 가지 있어요. 변수를 텐서로 치환한 경우(예: x = x + 1 대신 x.assign_add(1) 을 써야 하는 경우)나, 상태를 가진 객체를 통과해 기울기를 계산한 경우예요. tf.Tensor 는 불변이라 값은 있어도 상태가 없어서, 상태를 가진 객체를 통해 미분을 시도하면 기울기를 얻지 못할 수 있어요.
등록된 기울기가 없는 연산
어떤 연산에는 미분을 통과시킬 기울기가 등록되어 있지 않을 수 있어요. 그런 연산을 통해 미분해야 한다면 tf.RegisterGradient 로 기울기를 직접 구현해 등록하거나, 다른 연산들로 함수를 다시 구현해야 해요.