캡슐 네트워크(캡스넷 - Capsnet) - 2

Dynamic Routings Between Capsules(CapsNet) - 2

저번 포스트에서는 CNN의 단점, 그중에서도 max pooling의 단점에 대해 자세히 알아보았습니다. 이러한 단점을 극복하기 위해 캡스넷(CapsNet)이 나왔으며 다이나믹 루팅(Dynamic routing)이라는 풀링의 단점을 보완한 방법이 있다고 설명드렸습니다. 이번 포스트에서는 캡스넷(CapsNet)의 네트워크 구조와 다이나믹 루팅(Dynamic routing)의 작동 방식에 대해 자세히 알아보도록 하겠습니다.

CapsNet architecture

Imgur

캡스넷 네트워크 구조는 위 그림과 같습니다. 숫자로 정리해보면 아래 과정을 거치게 됩니다.

  • Input : 28 x 28 x 1 (우리가 잘 알고 있는 MNIST 데이터)
  • Conv1 kernel : 9 x 9 x 256, stride 1 + ReLU
    • Output : 20 x 20 x 256
  • Primary Caps : 9 x 9 x (32 x 8), stride 2 + ReLU(?)
  • Digit Caps : dynamic routing

처음 입력으로 들어오는 28 x 28 x 1의 MNIST digit이 들어옵니다. 그 다음까지는 우리가 알고있는 기존 CNN 구조와 똑같습니다. 9 x 9짜리 256개의 필터를 가진 커널을 거쳐 20 x 20짜리 256갤의 피쳐 맵을 만듭니다. 그 이후에 PrimaryCaps를 형성하는 과정에 들어가게 되는데, 이 부분이 굉장히 헷갈립니다. 이름은 PrimaryCaps지만 우리가 원래 알고있던 콘볼루션 필터를 거치는 작업을 거칩니다. 20 x 20짜리 256개의 피쳐맵에 9 x 9짜리 32 x 8개의 피쳐맵을 만드는 콘볼루션 필터를 거치면 PrimaryCaps가 나오게 됩니다.

Imgur

그렇게 나온 벡터를 위 그림처럼 reshape을 하게 됩니다. 즉, 캡슐 하나 당 8개의 property를 가질 수 있게(property의 설명이 기억이 안나신다면 이전 포스트를 참고하시면 됩니다) 콘볼루션 필터를 거친 6 x 6 x (32 x 8) 피쳐맵을 (6 x 6 x 32) x 8로 reshape을 시켜주게 됩니다. 이렇게 되면 총 6 x 6 x 32 = 1152개의 각각 8개의 property를 가지는 첫 번째 캡슐들이 생성됩니다. 이해가 잘 안되시는 분들을 위해 텐서플로우로 캡스넷을 구현 한 깃허브 페이지 중에서 가장 별이 많은 코드를 가져왔습니다.

# version 1
'''
Args:
        input : [batch_size, 9, 9, 256]
        num_outputs: the number of capsule in this layer. 논문에선 32
        vec_len: integer, the length of the output vector of a capsule. 논문에선 8
        kernel size : 9 x 9
        stride : 2 
    
'''
capsules = []
for i in range(vec_len):
    # each capsule i: [batch_size, 6, 6, 32]
    with tf.variable_scope('ConvUnit_' + str(i)):
        caps_i = tf.contrib.layers.conv2d(input, num_outputs,
                                            kernel_size, stride=[1,2,2,1],
                                            padding="VALID", activation_fn=tf.nn.relu)
        caps_i = tf.reshape(caps_i, shape=(cfg.batch_size, -1, 1, 1))
        capsules.append(caps_i)
assert capsules[0].get_shape() == [cfg.batch_size, 1152, 1, 1]
capsules = tf.concat(capsules, axis=2)

위 코드는 코드 작성자가 제일 처음 만들었던 코드입니다. 사실 처음 논문 그림을 봤다면 저렇게 for문을 걸어서 여러번 콘볼루션 필터를 돌리는게 맞습니다. 하지만 잘 생각해보면 굳이 그렇게 할 필요 없이 한 번에 (32 x 8)개의 피쳐 맵을 생성하고 reshape을 하는게 계산 복잡도가 더 낮습니다. 논문을 다 읽고 그런 생각이 들었었는데 오랜만에 다시 저 깃허브 페이지에 들어갔더니 코드가 아래처럼 좀 더 효율적인 계산복잡도를 가지게 수정되었습니다.

# version 2
capsules = tf.contrib.layers.conv2d(input, num_outputs * vec_len,
                                                    kernel_size, stride, padding="VALID",
                                                    activation_fn=tf.nn.relu)

출처 : https://github.com/naturomics/CapsNet-Tensorflow/blob/master/capsLayer.py

Imgur

논문 그림 중 PrimaryCaps라고 써진 직육면체(?) 하나를 가져온다 생각하면 위 그림의 왼쪽 그림처럼 생각하시면 편합니다. 저 초록색 벡터가 하나의 캡슐이고 총 8개의 property를 가지게 됩니다. 즉, (36 x 32 = 1152)개의 캡슐이 한 캡슐 당 8개의 element(property)를 가지게 됩니다. 그리고 이 캡슐들이 다이나믹 루팅(dynamic routing) 과정을 통해 상위 레벨의 캡슐과 이어집니다.

최종적으로는 총 10개의 캡슐이 output으로 나옵니다. MNIST데이터셋의 클래스가 0부터 9까지로 10개이기 때문이겠죠? 각 캡슐은 16개의 원소로 이루어진 벡터이며 원소는 전부 entity(여기서는 digit)의 property를 나타냅니다. 이 property들이 과연 무엇을 뜻하는지는 실험 결과 설명 부분에서 말씀드리겠습니다.

Dynamic routing

이제 이전 포스트 그리고 이 포스트에서 계속 이야기해온 다이나믹 루팅(dynamic routing)에 관하여 설명드리도록 하겠습니다.

Imgur

논문에서 다이나믹 루팅 알고리즘을 설명한 그림입니다. 이 논문에서는 총 2개의 캡슐 레이어가 있기 때문에 위 설명 과정에서 PrimaryCaps가 layer l이 되고 DigitCaps가 layer l+1이 된다고 생각하시면 될 것 같습니다. 그리고 PrimaryCaps는 총 1152개가 있으며 DigitCaps는 10개입니다. 즉, i가 1부터 1152까지, j가 1부터 10까지 있습니다.

다이나믹 루팅은 몇 번의 이터레이션을 거칠지 하이퍼 파라미터를 지정해줄 수 있습니다. 먼저 PrimaryCaps와 DigitCaps를 이어주는 값 ${ b }_{ ij }$을 0으로 설정을 합니다.

그리고 이 ${b}_{ij}$를 softmax에 통과시켜 coupling coefficient를 만듭니다.

\[C_{ ij }=softmax({ b }_{ ij })=\frac { { b }_{ ij } }{ \sum _{ k }^{ }{ { b }_{ ik } } }\]

위 식이 coupling coefficient ${C}_{ij}$를 만드는 식입니다. 이렇게 소프트맥스를 거쳐주면 모든 coupling coefficient의 합이 1이 되게 만들 수 있으며 이 값은 캡슐 i와 캡슐 j의 연결 강도가 됩니다. 그 이후에는 PrimaryCaps의 property를 8개에서 16개로 바꿔주는 작업을 거칩니다.

\[{\hat { u }_{ j|i } } ={ W }_{ ij }{ u }_{ i }\] \[{ s }_{ j }=\sum _{ i }^{ }{ { c }_{ ij }{ \hat { u } }_{ j|i } }\]
여기서 ${W}_{ij}$은 8 x 16의 크기를 가진 가중치 행렬(weight matrix)이며 PrimaryCaps가 이 행렬과 곱해지면 벡터의 크기가 8개에서 16개로 바뀝니다. 이제 DigitCaps의 벡터 길이와 같아지게 만들어주는 식이라고 생각하시면 됩니다. 그 이후에는 ${\hat { u }_{ j|i } }$ 와 ${C}_{ij}$를 곱해서 길이 16으로 변환시킨 PrimaryCaps를 DigitCaps로 얼마나 보낼지 정해줍니다. 그리고 같은 digitcaps로 연결된 캡슐 벡터들을 더해주면 ${s}_{j}$를 구할 수 있습니다.
\[{ v }_{ j }=\frac { ||{ s }_{ j }||^{ 2 } }{ 1+||{ s }_{ j }||^{ 2 } } \frac { { s }_{ j } }{ ||{ s }_{ j }|| }\]
마지막으로 squash function을 거쳐줍니다. 이 함수를 통과하게 되면 ${s}_{j}$의 크기가 작을 경우 0에 가깝게(그러나 0이 되지는 않습니다) 수렴하고 크기가 클 경우 1보다 약간 작은 값을 가지는 벡터가 됩니다. 이렇게 squash function을 거쳐주게 되면 캡슐에 non-linearity를 추가할 수 있고 우리가 최종적으로 알고 싶어하는 entity의 존재 확률을 0과 1 사이의 값으로 표현할 수 있게 됩니다. ${s}_{j}$를 정리하면 아래와 같습니다.
  • L2 norm : probability of entity exists
  • Elements : properties of entity

이렇게 DigitCaps까지 완료가 되었다면, 마지막으로 ${b}_{ij}$를 업데이트 하는 과정을 거치게 됩니다.

\[{ a }_{ ij }={ v }_{ j }\cdot { \hat { u } }_{ j|i },\quad { b }_{ ij }\leftarrow { b }_{ ij }+{ \hat { u } }_{ j|i }\cdot { v }_{ j }\]
여기서 ${ a }_{ ij }$를 agreement라고 부릅니다. ${ \hat { u } }_{ j|i }\cdot { v }_{ j }$는 서로 차원 수가 같기 때문에 결과값이 스칼라가 됩니다. 이를 logit ${ b }_{ ij }$에 업데이트 해주면 드디어 routing iteration이 한 번 끝납니다. 위 과정을 계속 거치는게 dynamic routing이라고 할 수 있겠습니다.

Imgur

이번 포스트에서는 캡스넷의 네트워크 구조와 다이나믹 루팅에 대하여 자세히 알아보았습니다. 다음 포스트에서는 캡스넷에서 사용되는 margin loss와 digit reconstruction을 통한 정규화 그리고 실험 결과들에 대해 살펴보겠습니다.

Reference

  • Sabour, Sara, Nicholas Frosst, and Geoffrey E. Hinton. “Dynamic routing between capsules.” Advances in Neural Information Processing Systems. 2017.