[CVPR 2017] Learning by Association – A versatile semi-supervised training method for neural networks

 

[CVPR 2017] Learning by Association – A versatile semi-supervised training method for neural networks (Blog, Paper)

1. 서론 

우리가 모델링을 하면서 가장 힘든게 무엇일까? 아마도 데이터를 전처리하는 일이라고 볼 수 있을 것이다. 데이터 전처리에는 변수 선택이나, 정규화 처리, 파생변수 생성 등 모델러의 일이라고 볼 수 있는 부분들도 있지만, 어쩔 수 없이 발생하는 레이블링 작업은 너무 큰 부담이라고 볼 수 있다. 이러한 부분을 조금이라도 덜어주기 위하여 Semi Supervised Algorithm 등을 활용 할 수 있는데, 여러가지 방법중에 최근 DL 에서 많이 사용되는 Learning by association 방법을 설명해보고자 한다.

2. 핵심 IDEA

(1) Concept 
사람이 적은 수의 Sample 로도 기존의 지식과 연관하여 학습을 할 수 있는 것과 같이, 기계도 레이블링 되지 않은 데이터와 연관지어 학습을 할 수는 없을까? 라는 생각에서 출발한다.

위에 처럼 레이블 데이터와 레이블되지 않은 데이터가 혼재되어 있다고 할때, 훈련과정에 어떠한 형태로든 레이블되지 않은 데이터를 활용하는 것이 이 논문의 핵심 아이디어인데, 이 논문에서는 중간의 레이블되지 않은 데이터를 활용하기 위하여, Walker 와 Visit 이라는 두가지 개념을 사용한다. 그래서 결국은 일반적으로 우리가 훈련할때 Class 일치를 가지고 Cross Entropy 로 Loss Function 을 구성하는 것에 추가적으로 Walker 와 Visit 라는 Loss 를 추가하여 위와 같은 Concept 을 실행하는데 최종적으로는 아래 한줄이 핵심이라고 생각한다.

(2) Walker
Classification Loss 는 우리가 일반적으로 DNN 을 구성하여 Back Propagation 을 수행할때 사용하는 Loss Function 으로 별도의 설명은 생략하도록 한다.  Walker 는 Label -> Unlabel -> Label 순서로 갔다왔을 때, 원래의 Label 이 유지되도록 Loss Function 을 구성하게 된다. 이때, 우리가 어떤 목적에 따라 구성한 Deep Learning Architecture 의 앞 부분은 3가지 Loss 에 대해서 동일하게 사용하게 된다. 간단히 말하면 Loss Function 만 3개가 되는 것이고 Optimizer 를 통해 훈련하는 신경망의 Weight 값을 동일한 형태가 되겠다.

A : Labeld,  B : UnLabeld 라고 했을때, 위의 그림과 같이  A -> B- > A 를 갔다가 왔을때, 클래스가 유지될 확률을 예측하고 y^, 실제로 클래스가 유지되었는지 y 를 가지고 Cross Entropy 로 Loss 함수를 구하면 Walker 에 대한 설명은 결론적으로 끝이다.

예를 들면 y 는 0, 아니면 1이고(실제로 유지가 되었는지 안되었는지) 그리고 예측값은 0~1사이로 표현이 될 것이다.

그러면 P(aba) 를 어떻게 구하는지 역으로 설명하여 보고자 한다. 기본적으로 이 논문에서는 만약 A(Labeled 의 Feature), B(UnLabeled의 Feature) 가 제대로 훈련된 신경망을 통해서 생성되었다면, 유사할 것이다라는 가정을 두고 있다. (A,B 값은 우리가 설계한 신경망의 마지막에 Feature 가 표현된 Layer 의 값이 될 것이다)

그러면 A Feature Matrix 와 B Feature Matrix 가 얼마나 유사한지는 어떻게 계산할까? 유클리시안, 코사인시뮬러 등 다양한 메트릭스간 유사도를 구하는 방식을 사용하여도 좋으나, 논문에서는 내적을 하는 것이 가장 효과적이였다라고 말하고 있다.

그래서 M 의 A,B 간의 Vector 유사도가 되는 것이고, 그러면 우리가 알고 싶은 것은 A -> B -> A 다시 왔을때 P(aba)를 구하고 싶은 것이다. 구하는 것은 아래와 같다.

지금까지의 설명을 이해를 돕기 위해서 해당하는 부분의 Tensorflow Code 로 설명해보고자 한다. 위에서 설명한 것 처럼 A, B 는 각각 우리가 설계한 신경망을 통과하여 추출된 Feature Matrix 이다. 아래의 내용이 A, B 를 추출하는 과정이다.

model = semisup.SemisupModel(model_function, num_labels,
                             image_shape)

# Compute embeddings and logits.
t_sup_emb = model.image_to_embedding(t_sup_images)
t_unsup_emb = model.image_to_embedding(t_unsup_images)

아래의 과정은 y (실제로 클래스가 유지 되었니?), y^(예측하는 유지될 확률, 약간 애매한게 유지될 확률이라기 보다는, 그러한 Path 를 탈 확률로 이해가 되기는 하는데.. 그게 그거 아닌가!?)그렇게 하고 나서, Cross Entropy 로 y, y^ 을 가지고 Loss 를 구하고 있다 !

def add_semisup_loss(self, a, b, labels, walker_weight=1.0, visit_weight=1.0):
  """Add semi-supervised classification loss to the model.
  The loss constist of two terms: "walker" and "visit".
  Args:
    a: [N, emb_size] tensor with supervised embedding vectors.
    b: [M, emb_size] tensor with unsupervised embedding vectors.
    labels : [N] tensor with labels for supervised embeddings.
    walker_weight: Weight coefficient of the "walker" loss.
    visit_weight: Weight coefficient of the "visit" loss.
  """

  equality_matrix = tf.equal(tf.reshape(labels, [-1, 1]), labels)
  equality_matrix = tf.cast(equality_matrix, tf.float32)
  p_target = (equality_matrix / tf.reduce_sum(
      equality_matrix, [1], keep_dims=True))

  match_ab = tf.matmul(a, b, transpose_b=True, name='match_ab')
  p_ab = tf.nn.softmax(match_ab, name='p_ab')
  p_ba = tf.nn.softmax(tf.transpose(match_ab), name='p_ba')
  p_aba = tf.matmul(p_ab, p_ba, name='p_aba')

  self.create_walk_statistics(p_aba, equality_matrix)
  
  loss_aba = tf.losses.softmax_cross_entropy(
      p_target,
      tf.log(1e-8 + p_aba),
      weights=walker_weight,
      scope='loss_aba')
  self.add_visit_loss(p_ab, visit_weight)

  tf.summary.scalar('Loss_aba', loss_aba)

(3) Visit
Classification Loss 와 Walker Loss 는 구했으니, 이제 Visit Loss 만 남았다.

Walker Loss 는 A,B Vector 내적을 통해서 실제로 같은 Class 일 확률일 높은 쪽으로 훈련되도록 Loss 함수가 구성되어 있었다면, Visit 는 아래와 같이 모든 B 에 대해서 일정한 확률로 방문할 수 있도록 Loss 함수를 구성하고 있다.

V 를 구하는 부분을 코드로 보면 tf.fill([1, t_nb], 1.0 / tf.cast(t_nb, tf.float32)), 와 같다. 그러니까 예를 들자면, P(ab)는 위에서 구한 M 기반으로 뭐 얼마나 유사하다라는 값인것이고, V 는 모든 B 에 대해서 동일한 값을 취하고 있으니, 가까운 쪽으로만(유사한 쪽으로만) 가지말고 모든 B 를 방문할 수 있도록 유도하는 성격을 갖는다.

def add_visit_loss(self, p, weight=1.0):
  """Add the "visit" loss to the model.
  Args:
    p: [N, M] tensor. Each row must be a valid probability distribution
        (i.e. sum to 1.0)
    weight: Loss weight.
  """
  visit_probability = tf.reduce_mean(
      p, [0], keep_dims=True, name='visit_prob')
  t_nb = tf.shape(p)[1]
  visit_loss = tf.losses.softmax_cross_entropy(
      tf.fill([1, t_nb], 1.0 / tf.cast(t_nb, tf.float32)),
      tf.log(1e-8 + visit_probability),
      weights=weight,
      scope='loss_visit')

  tf.summary.scalar('Loss_Visit', visit_loss)

자 그러면 제일 처음에 이야기 했던, 아래의 Loss Function 에 대한 설명이 완료 되었다 !

3. 결론 

이렇게 훈련하면, 결국 Labled Data 와 UnLabled 를 동시에 사용하면서 훈련하는 형태가 되는 것이다. Unlabled 를 Lableing 하고 다시 훈련하는 형태가 아니고, 동시에 사용하면서 훈련을 하는 형태가 되는 것이다. 그러면 효과는 어떨까? 아래는 A->B->A 전이가 어떻게 변하는지를 보여준다. 위에는 훈련전으로 A와 B 의 Feature Matrix 가 같은 Class 임에도 다르게 표현이 되어서, A->B->A 로 Round Trip 을 하는데 실제로 같은 Class 가 아닌 다른 Class 를 왔다 갔다 하는 경우가 굉장히 많은 것을 볼 수 있다.

아래는 훈련 후로 A->B->A 를 Round Trip 하는데 같은 Class 를 찾아서 잘 왕복하는 것을 볼 수 있다. 그만큼, 같은 Class 에 대해서 유사한 Feature 를 잘 생성해 내는 Weight 값이 우리가 설계한 신경망에 훈련되었다고 보면 되겠다.

그래서 결론적으로 최종적인 성능은 어떠한가? 위에서 보는 것 처럼 Unlabeld 데이터를 많이 포함하여 훈련하면 할 수록 성능이 좋아지는 것을 볼 수 있다.

Learning by Association 은 Loss 함수 부분만 조작하면 어떠한 아키태쳐에도(아주 특이한 Reinforce , Gan 같은 구조가 아니라면) 손쉽게 적용가능한 장점이 있다.

개인적으로는 앞으로 Classification 모델을 만든다면, 무조건 써야하는 기법이 아닐까? 생각해 본다!  Why Not !!

끝 .

Leave a Reply

Your email address will not be published. Required fields are marked *