Sunwoo Kim's Computer Vision, Machine & Deep Learning Blog search

CS231n-Lecture13(Generative Models)

|

저번 강의 때는 CNN의 visualizing에 대하여 배웠었다. CNN의 레이어 사이사이에 어떤 일들이 벌여지는지 보는 것은 매우 흥미로웠던 것 같다. 그리고 또한 오늘 generative model의 기초가 되는 fooling image기법에 대해서도 살펴보았다.

오늘은 unsupervised learning및 generative model에 대해서 배워보도록 하겠다.

Unsupervised Learning

supervised learning은 훈련을 진행할 때, 데이터와 레이블페어가 모두 존재하는것을 의미한다. 그래서 목표는 입력이미지를 넣었을 때, 올바른 레이블을 얻는 것이었다.

unsupervised learning은 데이터는 존재하나 레이블이 없는 상황을 가정한다. 그래서 존재하는 데이터들 안에서 특징 및 상관관계 및 숨겨진 구조를 도출해내는게 목표이다.

즉 unsupervised learning은 레이블이 없으므로, 훈련시킨는데 들어가는 비용이 supervised learning에 비해서 압도적으로 적다. 레이블을 하는 비용이 올라갈수록 그런 특징은 더욱 두드러질것이다. 그렇기에 비용도 적을 뿐더러, 비용부담이 없으므로 데이터를 많이 모을 수 있다는 장점이 있다.

하지만 레이블없이 학습시킨다는 것은 매우 어렵다. 만약 잘 해낼 수 있다면 visual world의 구조를 이해할 수 있는 아주 좋은 발판을 마련할 좋은 기회라고 한다.

오늘 배울 것은 생성 모델(generative models)이다. 훈련 데이터로 네트워크를 훈련시켜 네트워크가 데이터의 분포를 학습할 수 있도록 훈련시키며, 테스트시에는, 모델의 분포를 따라하는 이미지를 생성해내는것이 목표이다.

  • explict density estimation은 우리가 직접적으로 모델의 분포를 정의하여 그 분포를 따라 이미지를 생성하는 것이다.
  • implict density estimation은 위에 설명한대로 모델을 훈련시키는 것이다. 즉 분포 추정을 하는 것이다.

데이터 분포로부터 사실적인 샘플들을 생성해낼 수 있으면 이를 활용해서 다양한 것들을 하고 있다. 위 슬라이드의 예시들을 보면 그 말이 이해가간다. 디자인, 아바타, 예술작품, data augmentation, 등 다양한게 가능할 것 같다.

또한 생성모델을 통해서 잠제적인 특징들(latent features)를 추정해볼 수 있다. 또한 잘 학습시켜 놓아 나중에 다른 테스크에도 유용하게 쓸 수 있다.

강의에서는 generative model을 위 슬라이드와 같이 분류해놓았다. 이 세가지 중에 요즘 연구가 활발한 PixelRNN/CNN 과 VAE 그리고 GAN에서 다룰 예정이다.

PixelRNN/CNN은 명시적 분포 모델, VAE는 근사적 밀도추정(approximate density)그리고 GAN은 간접적 분포 추정(implicit density) 이다.

위 식은 이미지 픽셀들에 대한 결합확률을 최대로 하는 식인것 같다. 위 슬라이드에 추가 해놓은 것 처럼, 결합확률분포의 식같이(p(x condition)) 분해(decompose)할 수 있다.

우리는 이때 이 우도(likelihood)를 최대화 시키면 되는데, 딱 봐도 식이 매우 복잡한 것을 알 수 있다. 강의에서는 이를 neural network로 표현하기에 제격이라 한다.

여기서 중요한것은, 픽셀들의 순서를 어떻게 다루어야 할 지이다. 또한 우리가 다루려는 분포 p(현제 pixel 이전 모든 픽셀)에서 이전 모든픽셀이 의미하는 바에대해서 살펴보는 것이다.

PixelRNN and PixelCNN

PixelRNN은 이 문제를 풀기위하여 제안되었다. 제일 왼쪽의 상단의 픽셀부터 생성을 시작해보자. 그리고 화살표의 연결성을 기반으로 순차적으로 픽셀을 생성해낸다. 단점은 이렇게 순차적으로 픽셀을 생성해 내기 때문에 너무 느리다는 것이다. (순서와 종속성이 RNN에 내포되있는것 같다. 한 픽셀 생성시 생성된 픽셀의 값을 입력으로 넣어주면 되기 때문이지 않을까?)

PixelCNN의 RNN과 다른점은, RNN은 현재 픽셀을 기점으로 이전에 생성된 모든 픽셀들을 고려하는 반면, CNN은 지역적으로 고려하는 모습이 보인다. 훈련속도가 CNN이 더 빠르다고 하는데 자세히 어떻게 빠르게 되는지 감이 잘 잡히지는 않는다.

위 결과는 PixelCNN의 결과이다. 꽤 잘 생성해낸 모습을 볼 수 있으나 의미론적 표현 부분이 명확하지 않다.

요약하자면, 이 방법은 가능도를 명시적으로계산하는 방법이다. 우리가 최적화 할 수 있는 분포를 명시적으로 정의한다. 이렇게 하면 evaluation metric을 정의할 수 있다는 장점이 존재한다. 하지만 순차적으로 생성해야 하기 때문에 그 과정이 매우 느리다는 단점이 있다.

VAE(Variational Autoencoders)

introduction

VAE는 PixelRNN, PixelCNN과 달리 직접 계산이 불가능한(intractable) 확률 모델을 정의한다. 여기서 우리는 잠재변수(latent variable), z를 도입할 것이다. 이에 대해서 알아보자.

위 슬라이드에서 VAE식을 보면 p(x)의 값이 적분의 형태로 구해지는 모습이 보인다. 식의 형태를 보면, $p_{\theta}(x z)$의 $z$에 대한 평균을 구하는 식과 동일하다. 하지만 이는 문제가 된다고 하는데 왜냐하면, 직접 이 식을 최적화시킬 수 없기 때문이라 한다. 대신에 가능도 $p_{\theta}(x)$의 하한(lower bound)를 유도해서 최적화시켜야 한다.

Autoencoder

VAE를 살펴보기전에 AE(autoencoder)부터 살펴보자. AE는 데이터 생성이 목적이 아니며 레이블링 되지 않은 데이터로부터 저차원의 feature representation을 학습하기위한 방법이다. 즉 우리는 입력데이터 x가 있을 때 그 어떤 특징벡터 z를 학습하기를 원하는 것이다. 즉 encoder 입력데이터 x를 z로 변하는 함수의 역할을 한다.

슬라이드에 나와있는 것처럼 z로 맵핑시키기 위하여 처음에는 linear + nonlinearity 그다음에는 fully-connected, 그다음에는 ReLU CNN과 같은 순으로 발전을 해나갔다고 한다.

일반적으로 z는 x보다 크기가 작다. 그렇기 때문에 차원 축소의 효과를 기대할 수 있다. 그렇다면 왜 이렇게 크기가 작아지게 설계를 할까? 바로 z가 x의 가장 중요한 특징들을 잘 담고 있어야 하기 때문이다. 그렇다면 z가 x의 feature representation(중요한 특징들을 가지고 있으므로, 특징을 대표하는 벡터라는 뜻)역할을하게 학습시킬 수 있을까? 그것은 AE가 x를 복원하는 방식으로 학습을 시키는 것이다.

위 슬라이드를 보면 decoder는 z를입력으로 받고 input인 x와 동일한 크기의 벡터를 출력하도록 설계되어있다. 즉 우리는 데이터를 복원시키기를 원한다. 또한 encoder와 decoder는 기본적으로 동일한, 대칭구조(conv -> upconv)를 지닌다. 보통 CNN으로 구성된다고 한다. loss는 보통 L2 loss를 사용하여 픽셀간 비교를 진행한다. (여기까지보면 지난 12강에서 배운 visualizing기법 비슷한것 같다?)

훈련 후 우리는 디코더를 사용하지 않는다. 즉 encoder만 사용하여 이미지를 압축해서 표현한 벡터 z를 다른 supervised learning을 하는데 네트워크의 초기값으로 사용한다고 한다. 그리고 그 뒷단에 클래스 개수만큼 출력하는 매트릭스를 붙히면, classification이 되는것이고 용도에 따라서 뒤에 붙는 레이어를 달리하는 것이다.

즉, 이렇게 함으로써, 레이블링 되지 않은 데이터를 이용하여 데이터로부터 양질의 일반적인 특징 표현(general feature representation)을 얻어낼 수 있다는 장점이 있는 것이다. 데이터가 적은 경우, AE로 레이어의 가중치를 초기화 시키는것은 좋은 방법중 하나가 될 것이다.

우리는 이러한 AE의 성질을보고 잠재 벡터인 z가 데이터의 variation을 꽤 잘 가지고 있다는 것을 알 수 있다. 그렇다면 이렇게 다시 복원해내는 모습을 보고 새로운 데이터를 생성해낼 순 없을까 생각해 볼 수 있다.

VAE(Variational AutoEncoder)

VAE에서는 이제 새로운 데이터를 생성할 것이고, 이것을 위하여 모델에서 데이터를 샘플링 할 것이다.

위 슬라이드에 나온것 처럼, 학습데이터가 잠재 표현 z에 의해서 생성된다고 가정해보자. 그러면 z는 어떤 벡터일까? z는 우리가 생성하고자 하는 데이터의 성질을 가지고 있는 벡터일 것이다. 강의에서는 사람의 얼굴을 예로 들고 있는데, 얼만큼 웃는지, 눈썹이 위 아래 위치가 어딘지, 머리가 긴지 짧은지 등이 될 수가 있다.

이미지를 생성시z에 대한 prior로 부터 샘플링을 할 것이다. 속성에 대한 정도(얼마나 웃는가, 눈썹이 얼마나 아래로 쳐져있는가, 머리가 얼마나 긴가)를 표현하기 위해서는 속성들이 어떠한 분포(distribution)을 따르는지에 대한 prior를 정의해야 한다. 그 예로, z에 대한 prior로 우리는 가우시안 분포를 선택해볼 수 있을 것이다.

생성모델이 데이터를 잘 생성하려면 true parameter인 $\theta ^$을 잘 추론해야 한다. $\theta ^$은 z가 따르는 어떤 분포의 모수(parameter)를 의미한다.

그런데 $p(z)$를 가우시안과 같이 단순한 모델을 합리적으로 잘 선택했다 하더라도, $p(x z)$는 복잡해질 수밖에 없는데, 우리가 이것을 가지고 이미지를 생성하기 때문이다. 앞서 말했던것처럼, 복잡한 분포를 추론하기에 neural network가 제격이다! 이 뉴럴 네트워크가 바로 디코더가 된다.

모델의 파라미터를 추정하기 위해서는 모델을 학습시켜야 한다. 우리가 계속 했던것 처럼 가능도를 최대화 시키기 위하여 그라디언트를 계산하여 역전파를 하면 되겠지만 가능하지않다. 왜냐하면 위 적분식은 계산이 불가능(intractable)하기 때문이다.

즉, $p_{\theta}(z), p_{\theta}(x z)$는 계산이 가능하지만 해당 적분식은 계산이 안된다. 그렇다고해서 이를 베이즈룰을 통하여 사후확률로 바꾸어 계산하려고 해도, $p_{\theta}(x)$의 계산이 어렵다.
이 모델을 학습시키 위한 솔루션은 디코더$p_{\theta}(x z)$에 대응되는 인코더($q_{\Phi}(z x)$)를 추가적으로 만드는 것이다. 해당 인코더는 $p_{\theta}(z x)$를 근사하는 용도이다. 사후확률을 근사시크는 인코더를 이용하면 p(x)의 하한을 구할수 있고, 계산이 가능하므로 최적화 문제로 문제를 해결할 수 있게 된다.
VAE도 AE와 마찬가지로 인코더/디코더 구조이나, 여기에 확률론적 의미가 추가된다. 먼저 인코더부터 살펴보자. 인코더의 출력은 슬라이드에서 볼 수 있듯이, z x의 평균과 (대각)공분산이다. 디코더의 출력은 x z의 평균과 공분산이다. 그리고 인코더와 디코더에는 각기 다른두 파라미터(모수($\Phi, \theta$))가 존재한다.
이 때, 실제로 z x, x z 를 얻으려면 이들의 분포로부터 샘플링을 해야하기 때문에, encoder와 decoder는 z와 x에대한 분포를 생성한다. 그리고 샘플링을 한다.

위 슬라이드를 보면, 정규분포를 가정하고 그 중에서 샘플링을 하는 모습을 볼 수 있다. 우리는 encoder network를 recognition/inference network라 부르기도 한다. 왜냐하면 z라는 잠재 변수를 추론하니까 말이다. decoder는 gernerate network 즉 생성네트워크라고도 부른다.

위 슬라이드들은 VAE의 $p(x)$에 대한 하한을 구하는 유도과정이다. 여기에 받은 내용을 적은 것보다. 강의를보면서 참고한 사이트에서 내용을 참고해보면 좋을것 같다.

GAN(Gaereative Adversarial Networks)

Comment  Read more

CS231n-Lecture12(Visualizing and Understanding)

|

저번 강의 때는 object detecion 및 segmentation모델에 대해서 학습했었다.

First Layer: visualize filters(weight)

CNN의 시각화에 대해서 배워볼 것이다. 과연 CNN의 내부는 어떻게 생겼을까? 어떻게 문제들을 해결할까/ 어떤 종류의 feature를 갖고 있는것과 같은 질문들에 대해서 다룰 것이다.

우리가 가장 만만하게 접근할 수 있는 것은 첫 번째 레이어이다. Alexnet을 살펴보면 컨볼루션을 통해서 첫번째 레이어에는 64개의 filter가 생기게된다. 첫 번째 conv layer는 이미지와 직접적으로 내적을 수행하기 때문에, 이 필터를 시각화 시키는것 만으로 이 필터가 이미지에서 무엇을 찾고 있는지 알아낼 수 있다.

AlexNet의 필터는 총 64개 이므로, 64개의 11x11이미지를 시각화 시킬 수 있다. 그리고 다른 network들의 첫 번째 레이어들의 모습이다. 단순히 보는 것만으로 무엇을 찾고있는지 예상할 수 있는가? 이것들은 주로 이미지의 엣지를 찾고있는 모습이라고 볼 수 있다. 흰/검의 직선, 사선도 보이고, 다양한 초록색과 분홍색같은 보색도 보인다.

학습된 필터를보면 우리가 첫강의에서 다루었었던 hubel and wiesel의 실험이 떠오른다(인간의 시각체계가 hierarchical 하게 lowlevel 을 인식하고 안쪽으로 갈수록 high level을 인식한다는것.) 인간의 시각체계도 이처럼 겉 피질에서 oriented edges를 감지한다고 말했었다. CNN의 첫 번째 레이어 에서도 이와 비슷하게 작동하는 것 같다. 가장 흥미로운 점은 CNN을 어떤 모델과 데이터로 학습하여도 첫 번째 레이어는 위와 같은 모습을 띤다는 점이다.

위 이미지는 히든레이어의 시각화가 아니고, 가중치를 시각화 한것이다. 그렇다면 왜 가중치를 보아야 하는 것일까? 먼저 CNN의 가중치는 필터라고 불리는것 부터 생각해보자. 우리가 초등학교때 보았던 빨간색 셀로판지를 생각해보자. 빨간색 셀로판지를 보면 세상이 빨갛게 보인다. 왜그런 것일까? 해당 필터가 빨간색 빛만 흡수했기 때문이다. 즉, 필터는 우리가 원하는 특징을 걸러내는 장치인 것이다. 그렇기 때문에, 가중치 행렬을 시각화 시켜보면, 어떤 특징을 얻고자 하는지 알 수 있는 것이다. 조금 더 수학적으로 얘기하자면, 컨볼루션은 필터와 이미지를 내적(matrix 곱 아님)하는 것과 동일한데, 우리는 내적의 값이 최대가 되려면 내적하는 두 벡터가 동일해야 한다는 것을 알고있다. 그러므로, 필터와 이미지의 내적(matrix 곱 아님)이 최대가 되려면 두 값이 동일해야 한다는 것이다.

2,3 Layer: visualize filters(weight)

그렇다면 이러한 개념을 레이어 중간에도 이런 시각화를 동일하게 적용할 수 있을지 살펴보자. 우선 답은 첫 번째 레이어에 했던 시각화 기법과 동일하게 적용하면 우리가 보고 해석하기가 더 어려워진다는 것이다. 위 슬라이드를 보고 해석이 잘 되는가? 잘 되지 않을 것이다. 왜냐하면, 직접적으로 이미지와 연결되어있지 않기 때문이다. 두 번째 레이어의 가중치들은 첫 번째 레이어와 연결되있다. 즉, 우리가 시각화한 내용은 두 번째 히든레이어의 결과를 최대화 시키는 첫 번째 레이어의 출력 패턴이 무엇인지 파악하는 것이다.

하지만 이미지 관점에서 첫 번째 레이어의 출력이 어떻게 생겼는지 감을 잡고 해석하기 쉽지 않은데, 그래서 네트워크 중간 레이어의 필터들이 무엇을 찾고 있는지 알아내려면 좀 더 발전된 방법이 필요하다.

중간에 질문이 나왔다. 질문에 대한 답은 필터의 값은 원래 [0-255]가아니지만 시각화를 위해서 normalizing 되었으며, bias는 고려하지 않았기 때문에 시각화 결과를 있는 그대로 믿지 말라는 답변이 왔다.

Last Layer: visualizing

이제 CNN의 마지막 레이어를 살펴보자. CNN의 마지막 FC layer를 시각화 시키는데 필요한 방법중 하나는 nearest neighbor를 이용한 방법이다.

Nearest Neighbors

맨 왼쪽의 그림은 CIFAR-10의 데이터를 픽셀공간에서 nearest neighbor를 한 결과이다. 꽤 비슷한 이미지 들이 모였다. (색, 형태, 텍스쳐 등이 성질이 되서 그 거리를 비교하는것 같다. 자세한 방법은 잘 모르겠다.) 하지만 이번에 nearest neighbor를 할 것은 CNN에서 나온 4096-dim의 벡터 공간에서 계산을 할 것이다.

그 결과가 중간에 나온 그림이다. 이 결과를 보면 확실히 픽셀 공간에서의 nearest neighbor와 다른것을 볼 수 있다. feature 공간에서 nearest neighbor의 결과를 보면 서로 픽셀 값의 차이가 큰 경우도 있다. 하지만 픽셀 값의 차이는 커도, 특징 공간 내에서는 아주 유사한 특징을 지닌다는 것을 알 수 있다.

두 번째 줄에 있는 코끼리의 예를 살펴보자. 테스트 이미지를 보면 코끼리가 왼쪽 앞에 서있고, 풀이 있다. 그런데 끝에서 세번 째 사진을보면 코끼리가 오른쪽에 서있는 것을 볼 수 있다. 왜냐하면 코끼리가 왼쪽에 서있는 이미지와 오른쪽에 서있는 이미지의 픽셀 값은 완전히 다를 것이기 때문이다. 하지만 네트워크가 학습한 특징 공간 내에서는 서로 비슷한 벡터로 맵핑이 된다. CNN이 semantic 한 내용을 잘 캐치해냈다고 볼 수 있다. 이처럼 nearest neighbor를 통한 시각화 기법은 어떤 일이 일어나는지 살펴보기 아주 좋은 방법중 하나이다.

Dimensionality Reduction

PCA를 통해서 해당 결과를 좀 더 fancy하게 시각화 시킬 수 있다. PCA는 차원 축소 방법 중 하나인데, 이 경우 2-dim으로 차원 축소를 시킨 것이다. PCA로 이런 일을 할 수 있지만, t-SNE(t-distributed stochastic neighbor)라는 방법을 사용하면 더욱 파워플하게 이 작업을 수행할 수 있다. 위 슬라이드들이 t-SNE를 이용한 시각화 결과이다.

t-SNE에 관해서 아주 좋은 질문들이 나왔다.

  • 2차원 공간에서 대강 얼마나 많은 데이터를 표현할 수 있을까?

    잘 모르겠다. 정확히 얼마나 표현할 수 있을지 모르겠다. t-SNE는 비선형 차원축소 기법이라 상당히 복잡하다.

  • 이미지들이 차원축소 과정에서 서로 겹쳐지지 않을까?
    물론 겹칠 수 있다. regular grid를 기준으로 nearest neighbor, 즉 각 gride point에 가장 가까운 이미지를 뽑아낸다. 따라서 t-SNE는 특징 공간 분포의 모양을 보여주는것이 아니다.

MIddle layer: visualizing(neuron or patch)

Visualizing Activations

중간 레이어에 있는 가중치를 시각화 해도 해석하기 쉽지 않다고 했는데, activation map을 시각화 해보면 일부 해석할 수 있다. 지금 보고 있는 툴은 Jason Yasenski가 만들었다. 대부분의 grid 들은 뭐를 하고 있는지 모르겠지만, 초록색으로 확대해놓은 부분을 보면 웬지 사람을 형상화 시켜놓고 있는것 같다. 즉, 네트워크의 어떤 레이어에서는 사람의 얼굴을 찾고 있을지 모른다는 것이다.

여기서 아주 흥미로운 질문이 있다. 만약 ImageNet에 사람의 얼굴이 없다면 어떻게 네트워크가 사람의 얼굴을 인식할 수 있는지 있을까? ImageNet은 다른 이미지 들에 섞여서 사람의 이미지가 많이 등장한다 하지만, 1000개의 카테고리중에 사람은 없다. 이것은 또 아주 흥미로운 사실이다 왜냐하면 ImageNet에 사람이라는 클래스가 없지만 다른 클래스를 잘 분류하기 위하여 유용한 특징들을 알아서 학습한 것이기 때문이다.

Maximally activiating patches

중간 레이어의 특징을 시각화 시킬수 있는 또다른 방법이 있다. 바로 어떤 이미지가 들어와야 각 뉴런들의 활성이 최대화되는지를 시각화해보는 방법이다. 이 예시에서도 다시 한 번 AlexNet의 conv5 layer를 사용한다. 우리는 128x13x13의 activation volume 에서 한 개의 activation map을 선택해 시각화 할 것이다. 이 예시에서는 17번 째 체널을 골랐다. CNN에서 생각해볼때, 한 activation map의 픽셀이 이미지 전체를 보고있지 않다. 특정영역을 보고있다. 그렇기 때문에, 일부 패치 영역을 시각화 시킬것이다. 그리고 특정 레이어의 활성화 정도를 기준으로 패치들을 정렬시키면 된다. 오른쪽에 그 결과가 있다.

  • 특정 레이어의, activation map 선택
  • 여러장 훈련 시키면서 해당 activation map의 value기록
  • 해당 map 전체 혹은 뉴런의 value를 최대화 시키는 순서로 이미지(혹은 뉴런에 대응되는 패치부분)를 정렬.

오른쪽에 보이는 패치들이 바로 해당 레이어의 활성을 최대화시키는 패치들이다. 패치를 보고 무엇을 찾고 있는지 알 수 있다. 눈을 찾거나, 당야한 생삭의 문자를 찾거나, color, orientation을 가진 엣지를 찾는 다거나 여러가지가 있다.

오른쪽 상단의 결과보다 오른쪽 하단의 결과는 더 깊은 레이어를 시각화 한 것이다. 더 깊으면 그만큼 receptive field가 넓기 때문에, 조금 더 넓은 지역의 특징이 드러나는 것을 볼 수 있다.

Occlusion Experiments

새로운 실험에 대해서 살펴보자. 이 실험은 입력의 어떤 부분이 분류를 결정짓는 근거가 되는지에 관한 실험이다. 이미지의 일부를 가리고, 가린 부분을 데이터셋의 평균 값으로 채워버린다. 그 가려진 이미지를 네트워크에 통과시키고, 네트워크가 이 이미지를 에측한 확률을 기록한다. 그리고 전체 이미지에 이와 같은 작업을 반복하면서 계속해서 확률을 기록해나간다. 오른쪽의 히트맵(heatmap)은 가린 patch 위치에 따른 예측 확률의 변화를 시각화 해놓은 것이다. 빨간색은 확률값이 낮고, 노란색 지역은 확률 값이 높은 것을 나타낸다.

Go-kart의 차가 있는 앞쪽을 가렸을 때, 판별 확률이 낮아진 것을 볼 수 있다. (잘 안보이겠지만, go-kart 이미지 뒤쪽에 트랙과, 작은 많은 kart들이 있다고 한다.)

Saliency Maps

이와 관련된 또 하나의 방법은 saliency map을 살펴보는 것이다. 이 방법은 입력 이미지의 각 픽셀들에 대해서, 예측한 클래스 점수의 그라디언트를 계산하는 방법이다. 이것은 일종의 1차 근사적 방법으로 어떤 픽셀이 영향력 있는지를 알려준다. 이것은 이렇게 질문으로 바뀔 수 있다. “입력 이미지의 각 픽셀에 대해서, 우리가 그 픽셀을 조금 바꿨을 때 클래스 스코어가 얼마나 바뀔까?” 이 질문은 어떤 픽셀이 “개”를 분류하는데 있어서 어떤 픽셀들이 필요한지알 수 있는 또 다른 방법이 될 수 있다. 이 방법을 통해 “개” 이미지의 Saliency map을 만들어보면 “개”의 윤곽이 나타남을 알 수 있다.

다른 이미지에 적용해봐도 네트워크가 올바른 곳을 보고 있다는 것을 파악이 가능하다.

segmentation task에서 label없이 saliency map만 가지고 segmentation을 수행하는 모습이다. 이 논문에서는 grabcut(interactive segmentation algorithm)을 이용하였다. 즉, saliency map과 grabcut을 잘 조합하면 객체를 segmentation할 수 있다는 것이다. (잘 되지는 않지만.)

이번에는 클래스 점수가 아니라 네트워크의 중간 뉴련을 하나 고른다. 그리고, 입력 이미지의 어떤 부분이, 내가 선택한 중간 뉴런의 값에 영향을 주는 지를 찾는다. 그리고 각 픽셀의 “중간 뉴런”에 대한 그라디언트를 계산하는 것이다. 이 때 guided back propagation이라는 트릭을 가미하면 좀 더 깨끗한 이미지를 얻을 수 있다. 역전파 시, ReLU를 통과할 때 조금의 변형을 가한다. **ReLU를 거칠 시, 역전파된 총 그라디언트가 양수이면 그 다음 역전파를 계속 진행하고, 음수이면 역전파를 멈추는 것이다. ** 그래서 양수인 그라디언트만 고려를 하게 된다. (이 방법이 왜 좋은가에 대해서는 논문을 읽어야 한다.)

Visualizing CNN features: Gradient Ascent

우리는 여태까지 고정된 입력 이미지 또는 패치의 어떤 부분이 해당 뉴런에 영향을 미치는지 살펴보았다. 그렇다면, 넣기만 하면, 어떤 뉴런을 활성화 시킬 수 있는 일반적인 입력이미지가 있을까? 에 대해서도 생각해볼 법 하다. 이 부분에 대해서는 gradient ascent가 해답을 줄 수 있다.

지금까지 loss를 최소하시키기 위하여 gradient descent(GD)를 사용해왔다. 하지만 이제 네트워크의 가중치들을 전부 고정시켜볼 것이다. 그리고 gradient ascent(GA)를 통해 중간 뉴런 혹은 클래스 스코어를 최대화 시키는 이미지의 픽셀들을 만들어 볼거다.

GD가 가중치들을 바꿔준다면, GA는 뉴런, 클래스 점수가 최대화 될 수 있도록 입력 이미지의 픽셀 값을 바꿔주는 방법이다.

또한 regularization term이 필요하다. 가중치를 규제하여 과적합을 방지하기 위해서 썻듯이, 이번 경우에도, 생성된 이미지가 특정 네트워크의 특성에 완전히 과적합 되는 것을 방지하기 위해 사용된다. 그리고 이 term의 목적은 첫 번째, 생성된 이미지가 자연 영상에서 일반적으로 볼 수 있도록 원하는 것, 두 번째, 이미지가 특정 뉴런의 값을 최대화 시키는 방향으로 생성되는 것이다.

Gradient Ascent를 위해서는 초기 이미지가 필요합니다. 이 이미지는 zeros, uniform, noise 등으로 초기화 시켜 준다. 초기화를 하고나면 이미지를 네트워크에 통과시키고 관심있는 뉴런의 스코어를 계산한다($S_c(I)$가 클래스에 대한 스코어가 될수도 있고, 뉴런에 대한게 될수도 있다. 우리가 알고자 하는 대상에 따라 그 대상에 대한 출력값을 구하는 것이다). 그리고 이미지의 각 픽셀에 대한 해당 뉴런 스코어의 그레디언트를 계산하여 back prop을 수행 한다. 여기에서는 Gradient Ascent를 이용해서 이미지 픽셀 자체를 업데이트한다. 해당 스코어를 최대화시키려 한다.

여기서는 단순하게 L2 norm을 regularizer로 넣어준다. 이게 큰 의미가 있는 것은 아니다. 초기 문헌에서 종종 보이는 방법이라고 한다. 생성된 이미지를 보면 덤벨, 컵 거위 등이 중첩되어 있는 모습을 볼 수 있다. 강의에서는 달마시안이 되게 인상깊다고 한다. 검정/흰색 반점의 무늬를 볼 수 있기 때문이다.

강의를 들으면서 느낀거지만 스탠포드 분들의 수준이 높은것 같다. 지식뿐 아니라, 모든 부분에 대해서 궁금해하려는 그 태도가 놀라운것 같다.

  • 왜 이미지들이 무지개 색을 띠는가?

    이 시각화 방법을 통하여 실제 색상을 시각화 하려면 굉장히 까다롭다. 실제 모든 이미지들은 [0, 255]의 값을 지녀야한다. 이는 constrained optimization 문제다. 하지만 GA와 같은 일반적인 방법들은 unconstrained case이다. 그러므로 Projected gradient descent 와 같은 알고리즘을 사용하고 마지막에 rescale한 경우라면 시각화 할 때 나타나는 색상에 대해서는 크게 상관할 필요가 없다.

  • regularizer를 사용하지 않으면 어떻게 될까?

    score를 최대화 시키는 어떤 이미지가 생성 되기는 하나, 랜덤 노이즈처럼 보여 아무것도 아닌것 처럼 보일 것이다. 그래도 그 이미지 자체가 가지는 흥미로운 특징이 있다고 한다. 하지만, 네트워크가 무엇으 찾고있는지 이해하기는 힘들것이다. 즉, 자연스럽게 생성하기 위하여 regularizer를 사용하는게 좋다.

  • Multimodality를 다루는 다른 방법은 없는가?

    당연히 있다. 시각화에 대한 얘기는 이제 시작이다.

Jason Yesenki의 논문이 있는데 아주 인상적인 그라디언트를 추가했다. L2 norm은 여전히 존재한다. 그리고 여기다가 최적화 과정에서 이미지에 일정 주기로 가우시안 블러를 적용한다. 또 주기적으로 값이 작은 픽셀들은 모두 0으로 만든다. 낮은 그라디언트 값도 0으로 만든다. 이는 일종의 projected Gradient descent라고 볼 수 있다. 이는 생성된 이미지를 더 좋은 특성을 가진 이미지 집합으로 주기적으로 매핑시키는 방법이다. 이처럼 regularizer에 따라서 이미지가 더 깔끔해질 수 있다는 것을 보여준다. 이 과정은 최종 스코어에만 적용하는게 아니라 중간 뉴런에도 적용해볼 수 있습니다. “당구대” 클래스의 스코어를 최대화시키는 것이 아니라 중간의 뉴런을 최대화시키는 이미지를 생성해볼 수도 있다.

이를 통하여 중간 뉴런이 무엇을 찾고 있는지 짐작해볼 수 있는데, 네 번째 레이어의 경우에는 나선형의 무언가, 어떤 거는 애벌레 같은 것들을 찾고 있는것 같다. 예제 이미지가 더 클수록 receptive fields가 더 큰 뉴런들이다(즉 깊은곳에 위치한 뉴런).

방금 전 누군가가 질문한 multimodality 를 이 논문에서 아주 잘 다루고 있다. 이 논문에서는 최적화 과정 속에 multimodality를 아주 명시적으로 다루고 있다. 각 클래스마다 클러스터링 알고리즘을 수행한다. 한 클래스 내 서로 다른 모드들 끼리 다시 한번 클래스가 나뉜다. 그리고 나뉜 모드들과 가까운 곳으로 초기화를 해주는 것이다 . 이 방법을 통해서 multimodality를 다룰 수 있는 것이다.

직관적으로 보면, 가령 여기 있는 여덟개의 이미지는 모두 식료품점(grocery store) 이다(Later5, Layer4). 가장 상위의 이미지들(Layer5)은 선반 위에 전시된 물건들을 클로즈업 한 것 같아 보인다. 이들의 레이블은 “식료품점” 이다. 그리고 하단의 이미지들(Layer4)은 사람들이 식료품점을 돌아다니고 있는 모슴인 것 같다. 이 또한 식료품점으로 레이블링된다. 하지만 이 둘은 아주 다르게 생겼다. 많은 클래스들이 이렇게 multimodality를 가지고 있다. 이미지를 생성할 때 이런 식으로 multimodality를 명시하게 되면 아주 좋은 (다양한) 결과를 얻을 수 있게 된다.

위 슬라이드에 나온 논문은 이미지를 훨씬더 잘 생성해내기 위하여 강력한 사전 정보(prior)를 이용한다. 여기 보이는 이미지들이 모두 ImageNet의 특정 클래스를 최대화하는 이미지를 생성해 낸 것이다. 기본 아이디어는 입력 이미지의 픽셀을 곧장 최적화하는 것 대신에 FC6를 최적화하는 것이다. 이를 위해서는 feature inversion network 등을 사용해야 하지만 자세히 설명하지는 않았다. 논문을 읽어봐야 한다.

Adverserial Exmaple, Fooling Image(Fake image)

위에서 설명한 것처럼 이미지 픽셀의 그라디언트를 이용하여 이미지를 합성하는 방법은 꽤 강력하다. 이를 기반으로 아이디어를 내어 시도해볼 수 있는 아주 재밌는 방법은 네트워크를 속이는 이미지(fooling image)를 만드는 것이다.

코끼리 이미지를 골랐다고 해보자. 이 때, 네트워크가 코끼리를 코알라로 분류하도록 이미지를 조금씩 바꾸어 보자. 이렇게 하다보면 네트워크는 이것을 코알라라고 분류해버린다. 바꾸다 보면 모습이 코알라 같이 변할까 생각이들지만 전혀 그렇지 않다. 육안으로 보기에는 아무 차이가 없다. 오른쪽 그림을 봐도 무언가 랜덤한 노이즈가 추가됬을 뿐인 것 같다.

재밌는 질문이 있었다.

  • 우리가 왜 이런 stuff?를 배워야 하는가? (이거 왜 배워?)

    결국 딥러닝은 흔히 알려져있듯이 blackbox이다. 기계학습은 설명하기 더 쉬운 부분이 있는데 딥러닝은 그러지 못하다. 그래서 딥러닝도 복잡하긴 하지만 보면 해석 가능한 부분들이 있을거야를 보여주고, 딥러닝이 아무렇게나 학습하는게 아니고 의미있고 논리적인 행동을 하고 있음을 증명하기 위해 이런 시각화가 중요하다.

이미지에 그라디언트로 업데이하는 방식으로 가능한 재밌는 아이디어인 Deepdreamdl 이있다. DeepDream의 과학적 가치의 측면에서 보자면 그저 재미만을 위한 것이다. DeepDream의 목적은 “재미있는 이미지를 만드는 것” 이다.

DeepDeram에서는 입력 이미지를 CNN의 중간 레이어를 어느정도 통과시킵니다. 그리고 역전파를 하고, 해당 레이어의 그라디언트를 activation값으로 설정하면서 계속 역전파를 진행하면서 이미지를 업데이트한다. 이 과정을 계속 반복한다.

이는 네트워크에 의해 검출된 해당 이미지의 특징들을 증폭시키려는 것으로 해석할 수 있다. 해당 레이어에 어떤 특징들이 있던지 그 특징들을 그레디언트로 설정하면, 이는 네트워크가 이미지에서 이미 뽑아낸 특징들을 더욱 증폭시키는 역학을 하는 것이다. 그리고 이는 해당 레이어에서 나온 특징들의 L2 norm을 최대화시키는 것으로 볼 수 있다.

DeepDream의 코드는 아주 심플하다. 여기에는 몇 가지 트릭이 존재한다. 트릭 중 하나는 그라디언트를 계산하기에 앞서 이미지를 조금씩 움직이는 것이다(jitter) .원본 이미지를 그대로 네트워크에 통과시키는 것 대신에 이미지를 두 픽셀 정도 이동시킨다. 이는 regularizer 역할을 해서 자연스럽고 부드러운 이미지를 만들어준다. 그리고 여기에 L1 Normalization도 들어갑니다. 이는 이미지 합성 문제에서 아주 유용한 트릭이다. 그리고 픽셀 값을 한번 클리핑(Clipping) 해주기도 한다. 이미지라면 값이 [0, 255] 사이에 있어야만 한다. 이는 일종의 projected gradient decent인데 실제 이미지가 존재할 수 있는 공간으로 매핑시키는 방법이다.

위와 같은 하늘에

궁전, 동물 ,개 등 여러가지가 보인다. 그런데 특정 눈에띄는 클래스들이 있다. 내 눈에는 개와 건물? 정도가 눈에 자주 띤다. 그 이유는 트레이닝 시키는 데이터셋과 관련이 있는데, 이미지넷엣는 개가 200클래스나 차지한다고 한다.

이렇게 합성된 이미지가 생서오디는게 흥미롭고, 개가 자주 섞이는것은 위와 같은 이유이다.

Deepdream의 레이어가 깊지 않다면, low level feature가 섞여 들여가는 모습을 볼 수 있다.

multiscale processing을 하면 위와 같은 결과를 얻을 수 있다고 한다. 작은 이미지로 시작해서 점점 더 큰 이미지로 키워나간다고 한다. (어떻게 이미지를 키운다는 뜻인지 잘 모르겠다.)

Feature inversion

이번에는 스코어를 최대화 시키는것 대신 feature vector간의 거리를 줄이는 방법을 사용할 것이다. 기존의 feature vector와 새롭게 생성한 이미지 간의 거리를 측정하는 것이다.

regularizer는 Total variation regularizer가 있으며, 상하좌우 인접한 픽셀간의 차이에 대한 패널티를 부여하여 spatial smoothness를 더 용이하게만든다.

코끼리이미지와, 사과 바나나 이미지를 VGG-16에 통과시켜 feature vector를 기록하고, 이 feature vector와 차이가 없도록 하는 입력 이미지를 생성한다.

결과를 보면 레이어가 낮을수록 알아보기 쉽고, 깊을 수록 알아보기가 어렵다. 하지만 형체는 유지되고 있는것 같다. 이것은 레이어가 깊어질 수록 low leve feature가 사라진다는 것을 의미한다. 즉 detail은 사라졌지만 그 물체 고유의 형태는 남아있는, 약간의 색이나 텍스쳐의 미세한 변화에 덜 민감한 정보들이 남는 것이다.

Texture synthesis

이 문제는 작은 패턴을 보고 더 큰 이미지 패턴을 생성하는 것이다. 이는 그래픽스에서 오래된 문제라고 한다.

nearest neighbor

nearest neighbor를 이용한 방법도 상당히 좋다고 한다. scan line을 따라서 한 픽셀식 이미지를 생성해 나가는 방식이다. 빨간 선이 scan line 이다. 즉 이미 생성된 주변 픽셀들을 참고해서 그 다음 픽셀을 생성해 나간다. 하지만 이 방법은 신경망을 쓰지 않는 고전적인 방식이다. 간단한 텍스쳐는 잘 생성하지만 복잡해질수록 문제가 생긴다.

gram matrix

2015년에 신경망을 활용해서 텍스쳐 합성을 하려는 시도가 있었다고 한다. 위 슬라이드에 제시된 방법은 neural texture synthesis를 위하여 Gram matrix(그람 행렬)이라는 개념을 사용한다. 위 슬라이드에 나와있듯이, 빨간색 막대, 파란색 막대는 해당 위치에 존재하는 이미지의 특징을 담고있다고 할 수 있다. 그리고 빨간색 막대 벡터와 파란색 막대 벡터를 이용하여 외적을 수행하여 매트릭스(맵)을 만들고 이것을 이용할 것이다. 위 행렬은 두 지역간의 co-occurence를 가지고 있다. (공분산 구할때랑 비슷한 느낌..)

그리고 이 것을 HxW 크기에서 나올수 있는 모든 짝(pair)에 대해서 수행해준다. 그러면, HW(HW-1)(서로 같은위치의 짝은 존재하지 않으므로)개의 CxC 매트릭스 가나 오는데 이것을 평균을 내면, CxC크기의 Gram matrix가 나오게 된다. 이 그람 행렬은 이미지의 텍스쳐를 기술하는 텍스쳐 기술자로 사용된다.

그람 행렬의 흥미로운점은, 공간 정보를 싹 다 날리고, co-occurrence 만을 남겨둔 것이다.

오 공분산 생각했었는데, 강의에 공분산 얘기가 나왔다. 공분산 행렬을 써도 아주 잘 작동하지만 비용적으로 너무 소모가 크다고 한다.

이제 이 기술자를 이용하여 이미지를 생성해보자. gradient ascent와 비슷한 과정을 거친다.목표는 gram matirx를 재구성하도록 하는 것이다. 과정은 다음과 같다.

  • pretrained VGGnet을 다운로드 받는다.
  • 이미지를 VGGnet에 통과시키고 다양한 레이어에서 그람 행렬을 계산하고 저장한다.
  • 생성할 이미지를 랜덤으로 초기화하고, 네트워크에 통과시켜 그람행렬을 계산한다.
  • 본 이미지의 그람행렬과 생성 이미지의 그람행렬 간의 L2 norm을 이용하여 loss를 계산한다.
  • 역전파를 통해 생성 이미지 픽셀의 그라디언트를 계산한다.
  • Gradient ascent를 통하여 픽셀을 업데이트 해나간다. (강의에서는 이렇게 말했는데, descent아닌가? loss를 감소시켜야하니까 말이다.. 이부분은 잘 모르겠다.)

결과를 보면 꽤 잘 작동한 모습을 볼 수 있다. 레이어가 깊어질수록 공간정 정보를 더 살려낸듯 보인다.

이 텍스쳐의 입력의 유명 화가의 그림을 사용하면 어떻게 될까? 생성된 이미지의 모습을 보면 흥미롭게 재구성해나가는 모습을 볼 수 있는다. texture synthesis와 feature inversion을 조합하면 아주 흥미로운 일이 벌어지는데, 이 아이디어가 바로 Style transfer이다.

Style transfer

style transfer에는 배경이미지와 텍스쳐를 제공해주는 이미지 이렇게 두 개의 이미지가 입력으로 들어간다. 그리고 배경 이미지에 해당 텍스쳐를 입히게 된다.

style을 옮기기 위해서, gram matrix의 loss를 최소화시키고(neural texture synthesis) 또한,content이미지의 구조를 따오기 위해서 reconstruction loss(feature inversion)까지 최소화(minimize) 시키면 위 슬라이드와 같이 아주 fancy한 이미지가 생성된다.

우리가 항상 multi-task loss와 같이 여러 loss를 합할 때에는 항상 어느쪽에 더 중심을 둘지 가중치를 선택하게된다. 이떄, 어느쪽으로 더 중심을 두냐에 따라 다른 이미지가 생성된다 위 슬라이드를 참고해보자.

또한 여러 장의 style이미지 혹은 content이미지를 가지고도 transfer를 진행해줄 수 있다. 동시에 여러 gram matrix를 계산하는 것이다.

위 슬라이드는 동시에 만든 것은 한 이미지에 다른 style을 덧댄것이다. (즉 그냥 style transfer한 번 했다는것 같다. 동시에 진행하는게 아니고 한 번수행하고 또 수행한다는 뜻. 또 content, style이미지가 어떤 것인가에 따라 또 결과가 달라질것 같다. 그 결과도 위에 슬라이드에 있다.)

위 슬라이드의 그림들은 deepdream과 style transfer를 조합한 것이다.

하지만, style transfer의 단점은 매우 느리다는 것이다. 4k image 하나를 만드는데 엄청 좋은 GPU를 사용하여도 수십 분이 소요된다고 한다(왜냐하면 content나, style 둘 중에 하나만 바뀌어도 다시 훈련시켜 이미지를 만들어내야하기 때문.). 해결책은 style transfer를 위한 다른 네트워크를 학습시키는 것이다.

위와 같은 문제를 해결하기 위해 network에 style image 1장을 학습시키고 그 network를 그대로 이용하는 방법을 제안하였다.. 즉, 한 컨텐츠 이미지에 대해서 여러 style로 전환을 한다거나 한 스타일 이미지에 대해서 여러 컨텐츠 이미지를 만든다거나 할 때, 매번 재학습없이 단순 inference만 하면 되기 때문에, real-time이 가능하다고 한다.

강의에서 보면 실제 오른쪽그림은 webcam으로 실시간 style transfer가 되는 모습을 보여준다.

이 논문에서는 instance normalization이라는 것이 등장한다.

(이 강의를 했던당시 2017년)때 쯤에 나온 google의 논문에는 한 네트워크로 여러가지 style transfer를 하는 논문이 나왔다고 한다. (이는 아주 중요하다. 여러가지 스타일을 만들려고 여러 네트워크를 다 훈련시키에는 비용이 많이 들기 때문이다.)

Comment  Read more

CS231n-Lecture11(Detection and Segmentation)

|

저번 강의 때는 RNN모델에 대해서 학습했었다.

오늘은 CNN을 이용한 computer vison분야 중 하나인 segmentation, localization, detection에 대해서 배워보도록 하겠다.

Introduction

classification, localization, detection, segmentation간의 관계는 위 슬라이드와 같다.

의미론적 분할(semantic segmentation)은 이미지의 분할을 클래스에 따라서 수행하는 것이다. 즉, 어느 픽셀이 고양이 인지, 나무 인지, 하늘인지 구별하는 것이다.

슬라이드의 두 번째 사진은, 단일 물체에 대해서 분류 및 위치를 잡아주는 작업을 수행하는 것이다. 이게 다중 객체로 늘어나게 되면, 물체 탐지(object detection)이 된다. 그리고 localization과 contable object에 관한 semantic segmentation을 합하면 instance segmentation이 된다.

Semantic Segmentation

Fully convolution

위 슬라이드와 같이 소 머리 주변에 패치들을 추출해서 semantic segmentation을 해본다고 하자. 왜냐하면 패치별로 겹치는 부분이 있으며, 이런방식으로 해서 모든 구역의 픽셀에 클래스 레이블링을 하려고 하면 매우 computationaly expensive일 것이다.

이것보다 좋은 방법으로 fully convolution이란 방법이 있다. 즉, 우리가 배웠었던 classification을 위한 네트워크에서는 마지막 부분이 FC layer로 구성되어 있었는데, segmentation을 위해서는 convolutional layer로 구성하는 것이다. 왜냐하면 이렇게 하는 데에는 이유가 있는데, 바로 local information을 잊지 않기 위해서이다. 위 슬라이드에서는 Channel의 개수 = class개수 라고 생각하면 된다. 즉 한 channel당 해당 위치의 픽셀이 한 클래스일 확률을 표시한다고 생각하면 된다.

로스를 구하는 과정도 픽셀단위의 분류이기 때문에, 픽셀 단위로 구해진다. 그렇기 때문에, classification에 비해서 computational cost가 더 많이 든다고 보면 된다.

이를 supervised learning을 위해서 역시 pixel 단위의 labeling이 필요한데 보통 사람들이 툴을 만들어서 많이 한다고 한다. 직접 하는 경우도 있다. 이 역시 아주아주 노동집약적이며, 비용이 많이든다.

구조를 더 자세히 뜯어보면, 다음과 같이 생겼다. 앞단에서 convolution을 조금 한 후, 그 후부터 convolution, max pooling을 통하여 downsampling을 한 후, upsampling을 통하여 spatial resolution을 다시 키운다. 이렇게 하면 계산의 효율이 좋아지고, lower resolution을 처리할 수 있도록 네트워크를 더 깊게 만들 수도 있다.

Upsampling

그렇다면 upsampling은 어떻게 진행될까? 그 방법들을 살펴보자

Nearest Neighbor & Bed of Nails

위 슬라이드에는 두 가지 방법이 제시되어있다. 우선 2x2 grid가 4x4 gride로 upsampling 된 것을 기억하자. 두 방법의 접근은 아주 간단하다. nearest neighbor는 해당하는 receiptive field에 그 값을 그대로 복사하고, bed of nails unpooling(upsampling)은 해당 하는 픽셀에만 값을 넣고 나머지는 0으로 만들어 버린다. bed of nails라고 불리는 이유는 zero region은 평평하고 non-zero region은 바늘처럼 값이 튀기 때문이라고 한다.

Max Unpooling(Bed of Nails for Maxpooling)

Maxpooling에 대해서 unpooling을 하는 방법이다. 기본적으로 위의 네트워크 구조는 대칭적인 구조이다. 즉, downsampling layer와 upsampling layer의 행렬의 크기가 웬만하면 동일하다는 뜻이다. 그래서 maxpooling시 선택되었던 위치를 기억하고 unpooling 때, 그 위치로 값을 복원시키는 것이다. 그리고 나머지 부분은 0으로 채운다.

이렇게 하는 이유는 해당 특징이 어느 위치로부터 왔는가를 알려주기 위해서이다. segmentaiton은 pixel단위의 분류로 매우 정교해야 한다. 즉 maxpooling시 잃어버린 공간정보를 어느정도 다시 복원해주는 효과를 띄게 된다.

Transpose convolution

우리가 convolution을 통해서 어떻게 downsampling하면 좋을지를 배웠듯이, upsampling에도 이에 대응하는 방법이 있으니 그 방법이 바로 transpose convolution 방법이다.

기존의 convolution 방법을 다시 살펴보면 위 슬라이드와 같다. 컨볼루션의 출력은 입력과 가중치 행렬의 행렬곱을 통하여 얻어졌다. 문헌에 따라서 부르는 방법이 아주 다양한데, 그 목록들은 왼쪽 상단에 나와있다. 다만 deconvolution은 신호처리에서 convolution의 역연산을 의마하는데 사실 transpose convolution은 convoluiton의 역연산이 아니기 때문에, 이런 네이밍은 좋지 않다. 하지만 많은 곳에서 보이기 때문에, deconvolution이란 용어는 주의해서 이해를 해야한다.

fractionally strided convolution이라 불리는 이유는, upconvolution을 stride 1/2 upconvolution이라 볼 수 있기 때문이다. 이렇기 이해하면 편하다. 일반적인 컨볼루션에서 stride=2이면 출력의 가로세로가 각각 2배씩 줄어 들었다. 반대로 2배씩 키우기 위해서는 stride=1/2가 되어야하는 것이다.

backward strided convolution이라 하는 이유는 transpose convolution의 forward pass가 일반적인 convolution의 backward pass와 수식이 동일하기 때문이다.

이제 transpose convolution을 살펴보자. 위 슬라이드를 보면 확실히 그냥 똑같이 반대로 확대하는것 같다. 겹쳐진 부분은 서로 더해준다.

1dimension에서 살펴보자. matrix 연산이아니고, 입력이 가중치로써 곱해지는 모습을 볼 수 있다.

그렇다면 조금 더 일반적인 형태를 살펴보자. 위 슬라이드의 왼쪽은 1-D convolution, 오른쪽은 1-D upconvolution이다. 수식의 형태라 약간 적응이 안될 수 있는데, 행렬 연산을 해보면 이게 컨볼루션 연산과 동일하다는 것을 알 수 있을 것이다. [0,a,b,c,d,0]라는 입력을 [x, y, x]라는 필터로 컨볼루션을 진행한 것이다. 0은 패딩이다.

이 때, upconvolution이 어떻게 진행되는지 살펴보자. 앞서 컨볼루션 때 사용한 행렬을 전치시켜서 input에 곱해주면 된다. 이것이 stride=1 일때 transpose convolution이다.

stride=2 일때 transpose convolution의 결과는 위와 같다.

그런데 강의에서 다음 질문이 나왔다. 겹쳐지는 영역을 평균을 내지 않고, 그냥 더하기만 하는 이유는 무엇인가? 이다. transpose convolution을 처음에 이렇게 구성했기 때문이라고 말하는데, 사실 이게 문제가 될 수 있다고 한다. receptive field의 크기에 따라서 크기(magnitudes)가 달라진다. 실제로, 3x3 stride 2 transpose convolution을 사용하면 checkboard artifact가 발생되기도 한다고 한다. 그래서 4x4 stride 2 혹은 2x2 stride 2를 하면 조금 완화가 된다고 한다.

위 구조는 매우 일반적인 구조라 한다. 모든 픽셀에 대한 cross-entropy를 계산하면 네트워크 전체를 end-to-end로 학습시킬 수 있다.

Classification + Localization

이번에는 Classification + Localization에 대해서 다루도록 하겠다. 앞서서 classification에 대한건 꽤 많이 다루었다. 그런데 우리는 cat이라고 분류할 뿐 아니라, cat이 어느 위치에 있는지 까지 알고 싶을 수 있다. 이 문제와 object detection문제는 상당히 유사한것 같지만 구별되는 문제이다. 왜냐하면 localization문제에서는 이미지 내에서 내가 관심있는 객체는 단 하나라고 가정되어있기 때문이다. 즉, 이미지에서 객체 하나를 찾고, 거기에 레이블을 매기게 된다. 이 작업이 바로 classification + localization이다.

위 슬라이드를 보면 알 수 있듯이, 기존 classifcation과는 다르게 2개의 FC layer가 존재한다. 한 개는 classification을 위한 용도이고 다른 한 개는 localization을 위한 용도로 (x,y,w,h)=(중심,너비,높이)를 표시하기 위한 용도로 존재한다. 그렇기 때문에, loss function도 두 가지가 존재한다. 그리고 이 문제는 fully supervised setting을 가정한다. 그러므로 class label + bounding box ground truth를 동시에 가지고 있어야 한다.

두 가지 로스가 존재하는데, classification loss는 우리가 익히 알던 softmax loss로 구성이 된다. 그런데 bounding box를 위한 L2 loss가 제시되어 있다. 이 loss로 가장 쉽게 BBox(bounding box) loss를 가장 쉽게 구성할 수 있다고 한다. L1 이나 smooth L1을 사용해도 된다고 하면 약간은 달라지겠지만 기본적인 아이디어는 동일하다 한다. 이 loss들은 예측완bbox 와 GT bbox좌표 간의 regression loss이다.

여기서 질문이 나왔다. classification 과 localization을 동시에 학습해도 문제가 되지 않는지? 에 대한 질문이다. 일반적으로 문제가되지 않는다고 한다. 하지만 mis-classification에 대해서는 까다롭다고 한다. 이 문제를 해결하기 위한 방법중 하나는 bbox를 하나만 예측하는 것이 아니고 bbox를 클래스마다 하나 씩 예측하는 것이다. 그러고나서 GT의 클래스를 맞춘 bbox에 대해서만 loss를 계산한다고 한다.

이 슬라이드에서도 질문이 나왔다. 첫 번째 질문은 두 loss의 단위가 다른데 gradient계산에 문제가 되지 않을지에 대한 질문이다. 두 가지 loss를 합한 loss를 multi-task loss라고 한다. loss가 두개 이므로 두 가지 경로에서 미분값을 두 개를 구해야 하고, 각각의 loss를 모두 최소화 시켜야 한다. 위에서 말한 스케일 문제를 해주며, 두 loss의 반영비율을 결정해주는 스케일러(scaler)가 실제로 곱해진다고 한다. ($\alpha, 1-\alpha$와 같은 방식으로.) 하지만 이런 파라미터를 찾는 것은 매우 어렵다고 한다. 왜냐하면 손실함수의 값 자체를 바꿔버리고 loss자체의 속성이 변하기 때문이다.

하지만 이런 loss가 낮아지는지 높아지는지보다 중요한 것은 다른 성능지표로 판단하는 것이라고 한다. 즉, 모델의 성능지표, detection의 경우 MAP같은 것들을 보아야한다고 한다.

이 네트워크는 classification + localization 뿐 아니라 human pose estimation에도 적용시켜 볼 수 있다고 한다. 이것은 즉, bounding box의 좌표가 사람 관절의 좌표로 바뀌는 것이다. 14개의 사람 관절 좌표만 얻으면 되기 때문에, FC layer가 따로 두 개가 존재하여 loss가 두개일 필요가 없다.

Regression loss가 무엇인지에 대한 질문이 있었다. 질문에 대한 답은 다음과 같다. cross-entropy, softmax loss말고 L1, L2 loss같은 loss를 regression loss라 칭한다 한다. classifcation과 regression의 일반적인 차이는 결과가 categorical 인지 continuous이다. 즉, 고정된 클래스 개수가 있고, 네트워크가 이를 결정하기 위하여 클래스의 점수를 출력하는 경우면 cross entropy, sofrmax, SVM marginloss같은 것들을 사용가능하고, 출력이 연속적인 값(continuous)라면 pose estimation같이, 이런 경우에는 다른 종류의 loss를 사용해야 한다. (쉽게 생각하면 이렇게 생각해볼 수 있을것 같다. 개, 고양이, 소를 구분한다고 할 떄, 개의 확률이 1이면 나머지는 0이고, 소의 확률이 1이면 나머지가 0이다 즉, 한 클래스의 값이 다른 클래스에 영향을 준다. 하지만, pose estimation같은 경우, 각 클래스가 독립적이다. left foot의 위치가 (10,10)->(20,20)으로 변했다고 해서 right foot의 위치가 그 수만큼 감소하거나 증가해야 하는것이 아니다.)

Object detection

다음으로 object detection에 대해서 살펴볼 것이다. object detection에 대해서는 한 클래스를 모두 object detection으로만 진행할 수 있을 정도로 방대하다고 한다. 이번 강의에서는 deep learning에 관련되어 설명한다고 한다.

object detection은 classification + localization(C+L)과 차이가 있는데, 그 중 하나는 C+L은 이미지에 단 하나의 객체, 하나의 클래스를 추론하는 것을 알고 있다는 점이며, object detection은 이미지에 따라서, 예측해야 하는 bbox의 숫자가 달라진다. 각 이미지마다 존재하는 객체의 수가 다르기 때문에 더 어려운 문제이다.

이 그래프는 object detection 분야의 대가인 Ross Girshick의 슬라이드에서 가져왔다. PASCAL VOC데이터셋이 아주 오래되었다… 성능의 상승이 저하되었다가, deep learning의 등장 이후 성능의 향상이 비약적으로 높아졌다.

만약에 C+L 방식으로 object detection을 하려면 물체의 bbox를 나타내기 위하여 FC layer가 필요하므로 이미지에 등장하는 수많은 객체의 후보를 표현하기 위하여 수 많은 FC layer가 필요할 것이다. 그러므로 이는 다른 패러다임으로 해결이 되야 한다.

semantic segmentation에서 안좋은 방법으로 소개되었던 작은 패치로 쪼갰던 아이디어와 비슷한 방식이 사용된다. sliding window 방식을 이용하려면 입력 이미지에서 다양한 영역을 나누어서 처리해야 한다. 만약에 위 이미지에서 왼쪽 하단에 있는 작은 패치를 크롭하여 CNN에 집어넣으면 아무것도 없다는 output을 내놓을텐데 그러기 위해서는 배경(background)클래스를 추가해야 한다.

그러면 위 슬라이드에서 3가지 영역에 대해서 살펴보자. 각기 다른 결과를 내놓아야 할 것이다. 그런데 이 방식의 문제점이 무엇일까? 어떻게 영역을 추출할 지가 어렵다는 것이다. 이미지에 object가 몇 개 존재하는지, 어디에 존재하는지, 크기는 어떨지 알 수가 없다. 따라서 이런 brute force 방식은 sliding window 의 경우의수가 셀 수 없이 많다는 것이다. 이것 대신에 Region proposal이라는 방법이 존재한다. 사실 region proposal 방식은 딥러닝을 사용하지 않는다고 한다.

Region proposal

Region proposal network는 전통적인 신호처리 방식을 이용한다고 한다. region proposal은 객체가 있을법한 1000개 가량의 bbox를 제공해 준다. 후보 지역을 찾기 위하여 edge, texture등 다양한 방법을 이용할 수 있겠지만, blobby한 곳들을 찾음으로써 제공한다.

이러한 region proposal(후보 지역) 을 만들어낼 수 있는 방법에는 selective search방법이 있다. selective search는 이 두 배인 2000개 가량의 후보 지역을 추출해낼 수 있다고 한다. 다만 이 방법은 노이즈가 매우 심하다고 한다. 다만 recall이 높기 때문에, 객체가 존재한다면 region proposal 안에 존재할 확률이 높다고 할 수 있다. (너무 bbox를 많이 생성하는게 문제.)

Deep learning approach for object detection

이제 무식하게 이미지 내의 모든 위치와 스케일을 고려하는 방법이 아닌 다른 방법을 살펴보도록 하자.

  • R- CNN

첫 번째 방법은 R-CNN이다. selective search를 통하여 region proposal을 우선적으로 얻어낸다. 그 후보 지역이 바로 CNN의 입력이 되는 것이다. region proposal은 region of interest(ROI)라고도 한다.

하지만 여기서 생기는 문제점은 ROI의 사이즈가 다 제각각이라는 것이다. 즉, CNN의 입력은 항상 동일한 입력을 받기 때문에 문제가 생기는 것이다. 그러므로 ROI를 추출하고 나면 고정된 사이즈로 크기를 바꾸어 준다. 그리고 최종적으로 SVM을 이용하여 classification을 진행한다.

RCNN에는 많은 문제점 들이 있는데 그 중하나가 바로 ROI 2000개가 독립적으로 각각 1개씩 CNN의 입력으로 들어간다는 것이다. 그 말은 이미지 한 개에 대해서 object detection을 수행하기 위해서는 2000번이나 iteration을 해야한다는 것이다. 논문에서는 학습시간이 81시간이나 걸리고, test하는데도 한 장당 30초나 걸린다는 것이다. 또한 R-CNN의 원본 구현체를 보면 CNN의 feature를 디스크에 저장해놓기 때문에 용량이 어마어마 하다는 것이다.

  • Fast R-CNN

하지만 이러한 단점의 상당부분은 Fast R-CNN에서 상당부분 해결되었다. RCNN과 ROI를 추출하는 과정은 똑같지만, 각 ROI들에 대해서 CNN을 개별적으로 통과시키지 않는다. 즉, 전체 이미지를 CNN에 집어 넣는다.

우리는 CNN의 구조를 생각해보면 해당 Feature map이 원본 이미지의 어느 지역을 거쳐 왔는지 알 수 있다. (3x3 filter를 사용했다면, convolution 출력의 한 픽셀은 이전 피쳐맵의 3x3지역으로 부터 온게 된다. 그렇게 쭉쭉 거슬러 올라가보자.) 그렇기 때문에 ROI를 전체 이미지를 컨볼루션한 출력에 투영(projection)시킬 수 있게된다. 물론 이 ROI는 다음 연산이 수행될 수 있도록, 크기를 동일하게 바꾸어 주어야 한다. 그 후, loss를 계산하는 과정은 동일하며, classification 부분이 softmax classification으로 바뀐것을 확인할 수 있다.

다른 object detection network인 SPPNet과 비교해보자. training 시간이 매우 줄어든 것을 확인할 수 있다. 테스트 시에도 Fast RCNN이 무척 빠른 모습을 확인할 수 있다. 왜냐하면 모든 region proposal이 CNN feature를 공유하기 때문이다. 즉, Fast R-CNN에서 속도향상에 병목을 일으키는 것은 ROI를 뽑아내는 시간이다.

  • Faster R-CNN

Faster R-CNN은 ROI를 직접 만들어낼 수 있다. Faster R-CNN은 ROI를 만들어내는 별도의 Region proposal network(RPN)이 존재하여, 입력 이미지로부터 뽑아낸 피쳐 맵을 가지고 ROI를 만들어낸다. 그 후 동작은 Fast R-CNN과 동일하다.

여러가지 질문이 있었지만 그 중 흥미로웠던 질문은 RPN의 GT가 없는데 어떻게 학습되냐는 것이었다. 즉, 학습할 수록 bbox를 더 잘 추출해야만 할 것이다. 그런데 이게 GT의 개수보다 많은 ROI를 처음에 만들어낼수도 있고 더 적은 ROI를 만들어 낼 수도 있을 것이다. 여기서 답변이 GT 의 bbox와 조금 겹치는 ROI는 negative, 많이 겹치면 postive라는 식으로 학습하는데 이 사이에 여기선 설명하기 복잡한 하이퍼파라미터가 많아 설명을 하지 않는다고 했다. 나중에 찾아봐야겠다.

그리고 또한 RPN은 binary classification을 수행한다고 한다. 즉 region에 물체가 있는지 없는지를 판단한다. 그러므로 loss는 binary classification loss이다.

위 그래프를 보면 faster-RCNN이 정말 빠른 모습을 볼 수 있다.

여태까지 살펴본 네트워크들은 R-CNN계열 네트워크로써 region-based method라고 불린다. 이런 R-CNN 계열 말고도 다른 방법들이 존재한다.

YOLO와 SSD라는 네트워크가 존재한다. 이 두 네트워크의 주요 아이디어는 각 task를 따로 계산하지말고 하나의 regression problem으로 간주하자는 것이다. 이 방법들은 둘 다 single shot method이다.

다양한 네트워크들이 존재한다. 기본 구조에서 base network를 바꿔볼 수도 있고, R-CNN의 계열 방법과 sigle shot 방법들을 섞어볼 수도 있을 것이다.

최근에 보았던 흥미로운 논문이 있는데 바로, detection과 captioning을 동시에 수행하는 논문을 보았다고 한다. 이것을 보여준 이유는 다양한 문제를 서로 묶어서 해결할 수 있다는 것을 보여주기 위하여 추가하였다고 한다.

Instance Segmentation

마지막으로 instance segmentation에 대해서 살펴볼 것이다. instance segmentation은 지금까지 배운 것들의 종합 선물 세트이다.

위 슬라이드에 나와있는 것 처럼, 입력 이미지가 주어지면 객체별로 위치를 알아내야 한다. 하지만 bbox를 예측하는것이 아니로 객체별로 segmentation mask를 예측해야 한다는 점이 다르다. 즉, semantic segmentation + object detection 문제이다.

슬라이드의 그림 처럼 이미지 내에 개 두라미가 있으면 instance segmentation은 개1, 개2와 같은 식으로 구분을 해야한다. 이는 2017년에 나온 Mask R-CNN에서 다루고 있다.

Mask R-CNN은 faster R-CNN과 비슷하다. 이미지가 CNN과 RPN을 거치는 부분은 faster R-CNN과 똑같다. 그리고 ROI를 projection시켜 해당 영역에 해당하는 feature map을 convolution layer를 통과시켜 각 bbox 마다 segmentation mask를 예측하도록 한다.

첫 번째 갈래에서는 각 ROI가 어떤 클래스에 속하는지 계산하고, ROI의 좌표를 보정해주는 bbox regression도 추가된다.

두 번째 갈래에서는 semantic segmentation을 수행한다.

이 방식은 잘 작동하여 GT와 구분이 안갈정도로 매우 자세한 모습을 보여준다.

또한, pose estimation에도 이용이될 수 있다. 즉, bbox 갈래, segmentation갈래만 있었다면, pose estimation갈래를 하나 추가하여 추가적인 작업을 수행해주면 된다.

Comment  Read more

CS231n-Lecture10(Recurrent Neural Networks)

|

저번 강의 때는 CNN모델에 대해서 배웠었다.

이번 시간에는 순환 신경망(Recurrent Neural Networks)에 대해서 배워보는 시간을 갖자. 지금 예제에서는 이렇게 펼쳐진 그림을 그려 놨지만, 말 그대로 순환하는 신경망이기 때문에, 간략하게 표시하기 위하여 다시 되돌아 오는 화살표로 표시하기도 한다.

Introduction

위 그림에서는 빨간색이 input, 파란색이 output이다. 입력이 몇개고 출력이 몇개인지에 따라서 순화 신경망의 종류를 나눠놓은 모습이다. 이미지 캡셔닝(Image captioning)같은 경우, 이미지 한개가 입력으로 들어가면 이미지에 대한 설명이 나오게 된다. 여기서 설명은 sequence of words로 단어가 모인 문장이다. 그러므로 이 경우에 파란색은 하나의 단어를 의미한다. many to one는 감성 분류(Sentiment Classification)이다. 이 경우 텍스트가 들어가는데, “나 저 사람 때리고 싶어” -> “화남” 이런식으로 결과가 나오는 것이다. many to many는 기계 번역(machine translation)이 그 예시인데, 파파고나 구글 번역기를 생각해보면 된다. many to many는 그 형태에 따라 좀 달라지기도 하는데, 그 예로, 프레임 단위 영상 분류(video classification on frame level)이 존재한다. 말그대로 매 프레임을 입력으로 받아 거기에 대한 분류 결과를 출력으로 내놓는 것이다.

이미지 자체는 sequential 데이터가 아니다. 하지만, sequential 하게 처리하는 방법을 소개하는데, 일부 부분을 캡쳐해 입력으로 넣고, 그다음 부분을 캡쳐해 입력으로 넣는 식으로 RNN을 이용하여 분류를 하거나 생성해내는 작업을 할 수도 있다고 한다.

RNN

위에서 말한것 처럼, RNN은 일정 time step마다 입력을 받으며, 필요한 특정 time step에 출력값을 내놓는 방식으로 작동한다. (그 말은, 매 time step마다 입력이 들어오는게 아닐 수도 있으며, 매 time step 마다 출력이 나올 필요도 없다는 뜻이다. 위에서 보았던 RNN의 종류 그림에서 보았듯이 말이다.)

RNN의 기본식을 보면 위 슬라이드와 같다. 제일 첫번째 슬라이드에서 연두색 부분이 바로 RNN의 히든 스테이트(hidden state)를 나타내는 부분이다. 입력과 hidden state가 어떤 함수f에 의하여 연산이 되고 그 결과로 y가 나온다. f(h,x)는 저장되어, 그 다음 time step에 입력이 들어올 때, 그 전 시간에 입력과 히든 스테이트로 계산된 결과가 새로운 히든 스테이트로 들어오게 된다.

기본 RNN(Vanilla RNN)의 식은 간단하다. hidden state에 곱해지는 가중치와 입력에 곱해지는 가중치가 있어서 서로의 가중치를 곱하고 더한다음 tanh로 활성 함수를 지나면 이게 그 시간에 hidden state가 되며, 거기에 ouput을 내기 위해서는 가중치를 곱해서 출력하면 한 time step의 연산이 완료 된다.

이렇게 time step별로 동일한 연산이 계속 이어지면 그것이 RNN이다.

**여기서 주목할 점은, W~h~ W~x~ W~y~ 는 모두 다르지만, 매 time step 마다 달라지는게 아니며 동일하게 계속 사용된다는 점이다. **

Many-to-Many(MTM), Many-to-One(MTO), One-to-Many(OTM) 의 예시이다.

MTO 와 OTM을 섞어놓은 모델도 있다고 한다. MTO에서 나온 출력이 OTM의 새로운 input이 되는 것이다.

Character-level Language Model에 관한 예제이다. 우리가 hello라는 단어를 훈련 시켰다고 하고, 우리의 클래스는 [h, e, l, o]가 있다고 한다. 이 때, h, e, l, l을 차례대로 집어넣었을 떄, 그 다음 단어를 예측하고자 한다. 그러므로 e, l , l, o가 나와야 한다.

그런데 첫 번째 output layer의 결과값을 보면 o의 score가 제일 높은것을 볼 수 있다. 즉 잘못 예측 된 것이다. 그리도 두 번째 output layer의 결과값도 o의 위치가 제일 스코어가 높아 잘못 예측된 모습을 보인다.

이번 예시는 위에 예시와 하려는 작업이 똑같긴 하지만 조금 다르다. 그림을 보면 알겠지만, 이전 상태에서 예측한 결과가 입력으로 들어가고, 가장 다른 점은 softmax가 취해지며, 가장 점수가 높은 레이블을 고르는게 아니고, 확률분포를 통하여 sampling 한다는 점이다. (첫 번째 output layer를 예로 들면, p(h)=.03, p(e)=.13, p(l)=0, p(o)=.84인 확률을 두고 확률적으로 한 단어를 고르는 것이다.)

왜 이렇게 할까? 나도 바로 이 생각을 했는데, 역시 강의에서 누군가 질문을 하였다. 먼저, 이 예제에서는 무조건 확실한 값을 뽑아내는건 어렵기 때문에 이러한 sampling을 하는 것이라고 한다. (주변 컨텍스트가 아예없어서 그런가.. 흠..) 실제로는 이러한 방식도 사용하고, 우리가 익숙한 argmax를 취해서 최대 점수를 갖는 출력값을 사용하는 경우도 있다고 한다. 이런 sampling하는 방식의 이점은, 모델의 다양성을 추구할 수 있다는 것이다.

예를 들어, 이미지 캡셔닝을 수행할 때, 다양한 아웃풋을 낼 수가 있다. 사람들이 같은 그림이나 같은 상황을 보고도 다른 말을 하듯이 이러한 동작을 수행할 수 있다는 것이다.

강의에서 한 또 다른 질문은, 테스트 타임시에 input으로 one hot vector가 아닌 softmax vector를 넣으면 어떻냐는 질문이었다. 이 질문에 대해서 training 시에는 one hot이 들어갔기 때문에, test시에 다른 동작을 보이면 아주 안좋은 결과를 보게 될 것이라 하였다. 두 번째로, 모든 곳에 확률이 존재하는 dense vector를 사용하면, sparse vector operation을 수행할 수 없기 때문에, comptation 측면에서 매우 비효율적이고 비용이 많이 들게 된다고 말한다.

Backpropagation

RNN의 역전파는 어떻게 진행될까? 우리가 CNN에서 배웠던 것과 다르지 않다. 원하는 sequence까지 학습을 진행한후 loss를 구하고 역전파 하여 학습을 진행하면 된다.

그런데, sequence의 길이가 엄청나게 길 경우, 예를 들어 wikipedia의 모든 글을 학습시킨다거나 할 경우에 이런 방식을 채택하면 어떻게 될까? 학습이 너무너무 느리고, 메모리 소모도 너무 심할 것이다. 예를 들어, CNN을 훈련 시킬 때, 100만장의 이미지가 있을 때, 512장씩 미니배치로 훈련시키는게 속도가 빠를까? 100만장씩 배치로 훈련시키는게 빠른가를 생각해보면 당연히 미니배치가 속도가 빠르다(SGD vs GD).

그렇기에, RNN도 이와 비슷하게 적용하여 한 sequence가 있을 때, 그것을 일부 sequence까지 진행한 후 역전파를 하고, 그 다음 sequence를 진행한 다음 역전파를 진행한다. 이 때, 시작한 부분부터 자른 부분까지만 역전파가 된다는 사실을 기억하자. 즉, 한 시퀀스를 훈련할 때, 첫 부분이 여러번 역전파 되는게 아니라는 뜻이다.

위에서 진행한 min-char-rnn에 대한 예제의 링크를 소개한다.

우리는 위에서 배운 RNN을 이용하여 셰익스피어의 소설을 학습할 수 있다.

학습을 진행해 나가면서 자연스러운 모습을 볼 수 있으며, 단어 선택, 문법, 문단 분할 등 자연스럽게 학습된 부분을 확인할 수 있다.

비슷하게 algebric topology(대수적 위상수학)의 한 부분도 이렇게 텍스트로 훈련시킨 모습을 볼 수 있다.

위의 리눅스 코드도 훈련시켜 RNN으로 출력시키는 것을 해보았더니 꽤 잘 해낸 모습을 볼 수 있다. 문법, 주석, 16진수표현등이 잘 지켜진게 인상적인것 같다.

Searching for Interpretable cells

CNN에서 우리는 레이어별로 피쳐맵이 무엇을 의미할까를 분석해보았고 그 결과 레이어가 쌓여감에 따라 low level feature -> high level feature를 묘사함을 알았다. RNN에서도 이와 같은 상관성을 찾을 수 있을지 한 번 살펴보자.

논문의 Figure를 참고해 봤는데, 파란색은 c에 대한 tanh(c)=+1, 빨간색은 tanh(c)=-1인 부분이라고 한다(논문자체가 LSTM에 대한 논문이니, cell state에 대핸 tanh(c)값인가?? 확실하진 않다. 문맥상 맞는것 같은데 잘은 모르겠다.) cell state라 생각해보면, cell state는 과거 state와 현재 값간의 상관성을 의미하기 때문에, 색이 바뀐다는 것은 연관성의 관계가 반전된다는 것을 의미한다. (연관성 스위치? 즉 새로운 색으로 바뀌면 새로운 연관성에 대한 표시)

이 때, 위의 슬라이드 처럼, 의미를 알아내기 어려운 경우가 대부분이라고 한다.

하지만 이 처럼 상관 관계를 찾을수도 있는 부분이 존재한다. 인용구(quote, ““)가 시작하는 부분과 끝나는 점까지 모두 파란색이다. 이는 LSTM이 관계가 있는 부분이라고 인식한 것이다. 즉 파랑과 빨강을 나누어, 인용구인 부분, 아닌부분 이라고 구분한 것이다.

위 슬라이드는 언제 문장을 바꿀지를 LSTM이 추적한다는 것이다. 문장 바꾸는 부분에 도달할수록 색이 희미해지면서 점차 빨간색으로 바꾸어가는 모습을 볼 수 있다. 즉 문장의 안쪽 부분, 문장을 곧 띄어쓸 부분으로 구분 짓는 것을 LSTM이 수행하고 있는 모습이다.

위에서 봤던 예제이다. 문법적인 구조체끼리 서로 연관지어 색을 나눈 모습을 확인할 수 있다.

우리는 단순히 다음 단어를 예측하는 모델을 학습시켰을 뿐인데, 이러한 결과를 나타내다니 과연 놀라울 따름이다. 왜냐하면 다음 단어를 예측하는 훈련 -> 입력 데이터의 구조 학습이 되었기 때문이다.

Image captioning

위에서 몇 번 보았었던 이미지 캡셔닝에 대해서 조금 더 자세하게 살펴보자.

앞서 살펴보았던 CNN을 이용하여 이미지에서 특징을 추출 한 후, 그것을 RNN의 인풋으로 사용하여 캡션을 추출한다. 이렇게 훈련과정은 별반 다르지 않다.

이미지 캡셔닝의 테스트 과정에 대해서 살펴보자. 이 경우는 약간 시작이 다른데, start token이 존재하는 것이다. 이것이 왜 필요한지 생각해보자. (나의 추측이지만, 처음 시작토큰이 이미지의 벡터라면 (입력:이미지벡터, 출력:텍스트)가 된다. 그런데 이후 부터는 (입력: 텍스트, 출력:텍스트)의 형태이다. 이렇게 형태가 다르기 때문에 더미 텍스트 노드를 넣어주는데 이것을 start token이라 하는것 같다.)

그렇다면 추출한 이미지의 벡터는 어디에 넣어줄까? 바로 위 슬라이드에도 나와있듯이 v라는 벡터로 추가입력으로써 넣어주는 것이다. 그 후, 일반적인 RNN이 동작하는 것처럼 순환을 반복하며 캡션을 얻고, 캡션이 끝날 때에는, end token을 받아 캡셔닝을 종료한다. 우리도 할 말을 다했으면 끝을 맺듯이, RNN의 구조에서 끝을 맺어주어야 한다. end token이 sampling되면 이미지에 대한 caption이 완성된다.

train시에는 모든 캡션의 종료지점에 end토큰을 삽입한다고 한다. 왜냐하면 학습시에 end 토큰을 넣어서 문장이 끝난다는 것을 알려줘야 하기 때문이다. 그렇기 때문에 test time에서도 end token을 sampling하여 문장을 끝낼 수 있는 것이다. 이것은 완전히 supervised learning으로 학습시키는 구조이다.

그렇기에 이런 학습을 위해서는 이미지+이미제에 대한 설명이 같이 존재해야 하는데 이를 위한 가장 큰 데이터셋은 COCO데이터 셋이라고 한다.(강의 하시는 분이 알기로는..)

captioning에 대한 결과이다.

위와 같이 항상 성공적인 것만은 아니다. 더욱 성능을 높히기 위하여 고안된게 있는데 바로 어텐션(attention)이라는 것이다. 즉, 해당 time step에서 집중해서 볼 곳을 선택한다는 것이다. CNN에서 이미지의 특징벡터로 nx1의 벡터를 추출하는게 아닌 LxD 차원의 각 벡터가 공간정보를 가지고 있는 gird of vector를 추출한다고 한다.

우리가 각 time step에서 텍스트에 대한 확률분포를 만들었던것 처럼, 이미지에서 보고 싶은 위치에 대한 확률 분포 또한 만들어낸다. 그것이 $a_1, a_2,…$이다. 그리고, $z_{t}=f(a_t, Featrue(L*D)$를 생성해낸다. 그 z는 또 예측한 캡션과 같이 RNN의 입력으로 들어가게 되고 이 구조가 반복되게 된다. 이 때, 이미지의 집중할 위치에 대한 확률분포를 나타내는 $a_n$은 이미지의 크기와 같은 매트릭스를 상상하고 [0,1]사이의 값이 분포해있다고 생각하면된다. 즉 집중할 영역은 1에 가까운 값을, 덜 중요한 위치의 값은 0에 가까워지는 것이다.

우리가 제일 처음에 보았던 이미지를 RNN으로 분석한다는 슬라이드가 기억나는가? (CS231n 10강을 보면 플래시로 아주 잘 나와있다. 하지만 여기서는 멈춰 있어서 이해도가 떨어질 수 있다.) 원래 그 사진은 탐색영역이 멈춰있는게 아니고, 숫자의 일정 구역을 계속해서 훑어 나간다. 이와 비슷하게, attention 방법도 계속해서 중요하게 생각되는 위치를 바꿔나가며 이미지를 파악한다고 볼 수 있겠다.

위 슬라이드를 보았을 때, soft attention과 hard attention이 나와있다. 이 개념은 우리가 max값을 취할때를 살펴보면 된다, hardmax vs softmax 어떤 차이가 있을까? 말을 바꿔보자. argmax vs softmax, argmax: 가장큰 값 의 인덱스를 반환해주는 함수, softmax: 가장 큰 값 하나만 반환하는게 아닌, 상대적인 크기를 확률값으로 반환해주는 기능. 이렇게 말하면 soft attention과 hard attention의 차이에 대해서 감이 올 것이다.

soft attention은 모든 특징과 모든 이미지 위치간의 weighted combination을 취하는 경우(즉 이미지 전체에 대한 상대적인 값을 모두 관찰하는 경우) 그리고 hard attention은 모델이 각 타임 스텝마다 단 한곳만 보도록 강제하는 경우가 되는 것이다. 그렇기 때문에 이것은 사실상 까다랍도. 따라서 hard attention을 학습시키려면 기본적인 역전파 보다는 조금더 fancier한 방법이 필요하다고 한다.

attention을 사용한 image captioning의 결과이다.

또한 이와 비슷한 사례로 Visual Question Answering(VQA)가 존재한다. VQA는 이미지+질문이 입력으로 들어가고 4개의 보기중에 알맞은 답지를 고르는 테스크이다. (질문 개수, 질문 내용은 달라질 수 있다.)

이런 경우의 문제는 many-to-one이라고 볼 수 있다.

이 문제에 대한 질문에서 RNN의 output과 CNN의 output이 어떻게 조합할 수 있는지에 대한 질문이 나왔는데 가장 간단하게는 concat을 써서 이어 붙히거나, 더 강력한 방법을 사용하기도 한다고 한다.

그런데 사실 나는 그 부분보다. CNN의 input이 RNN으로 처음 들어가는데 서로 다른 데이터인 텍스트와 어떻게 엮이면서 LSTM이 동작하는지 그게 더 궁금하긴하다…

Multilayer RNN

지금 까지는 단층 RNN만 알아봤다 CNN으로만 따지면, 레이어가 1개인 네트워크만 알아본 셈이다.(성능이 그정도 급은 아니지만..) 어쨋든, RNN도 똑같이 CNN처럼 층을 쌓아 성능을 더 좋게 만들 수 있다. 위 슬라이드에 나온 그림과 같이 레이어를 쌓으면 된다. RNN한 sequence가 돌았을 때, 해당 RNN의 히든 스테이트가 다른 RNN의 입력이 되는 것이다. 보통 2-4layer의 RNN이 사용된다고 한다.

일반적인 RNN의 backpropagation을 진행할 때, 전 time step에 대한 미분값을 구하려면 곱해졌던 가중치 값이 튀어나온다. 일반적으로는 많은 가중치에대해서 행렬로 연산이 되어있기 때문에, $W_{hh}^T$가 나올 것이다.

그런데 이런 RNN의 구조가 문제를 일으키는데, 우리가 전에 배웠던 multiplication gate를 생각해보자. 미분할 때, 어떤 값이 튀어나온다. 그리고 chain rule에 의해서 그 값이 계속 곱해진다. RNN의 hidden state는 곱에 의해서 나오기 때문에, 역전파를 하면 계속해서 가중치의 값이 곱해지는 형식으로 역전파가 진행된다

그럴 경우 위 슬라이드에 나와있는 것처럼, 그 값이 계속해서 곱해지다보면, 전파되는 값이 너무 커져 exploding이 일어나거나, 너무 작아져 vanishing이 일어날 수 있다.

그래서 이를 방지하기 위하여 사람들이 고안한 방법이 있으니 바로 gradient clipping이라는 방법이다. 위 슬라이드의 식 처럼, gradient의 L2 norm이 일정 값을 넘을 경우, 최대 임계값을 넘지 못하도록 스케일링 해주는 방법이다. 이 방법은 그렇게 좋은 방법은 아니지만 많은 경우에 쓰인다고 한다.

exploding gradient를 해결하기에는 어느정도 쓰일 수 있지만, vanishing gradient 문제를 해결하기 위해서는 더 복잡한 RNN구조가 있어야 한다고 한다.

이를 위해 등장한 것이 바로 Long Short Term Memory( LSTM)이다. LSTM은 vanishing과 exploding문제를 해결하기 위하여 등장했다. L?STM에는 위 슬라이드와 같이 총 4개의 게이트가 존재한다. 우선 각 gate의 출력은 hidden state의 크기와 동일하다는 것을 알아두자. (물론 바뀔 수 있다. 다른 아이디어를 사용하려면) gate 4개는 [i, f, o, g]인데 기억하기 쉬우라고 ifog라고 부른다고 한다.

LSTM에 대해서는 wegonmakeit님의 사이트에 잘 정리되있는 것 같다.

  • input gate: cell에서 입력 $x_t$에 대한 가중치
  • forget gate : 이전 스텝의 cell의 정보를 얼마나 버릴지 결정하는 게이트
  • output gate: cell state를 얼마나 밖에 드러내 보일지에 대한 가중치
  • gate gate: input cell을 얼마나 반영할지(포함할지)에 대한 게이트

기본적으로 LSTM의 구조는 위와 같다. 각 gate에 사용되는 활성화 함수와 그 구조가 다른데 의미적 측면에서 이를 파악해보자.

input gate는 현재 input과 hidden state의 연산의 값에 sigmoid를 취한 값과, tanh취한 값을 곱한 값을 출력한다. 그리고 그 값은 $C_{t-1}$과 더해진다. 왜 시그모이드와 tanh가 둘 다 있을까 생각해보면 시그모이드는 cell state에 이번 time step의 입력과 히든스테이트를 반영 시킬지 말지에 대한 것에 대한 확률을 소프트멕스로 나타내고

gate gate 는 tanh는 얼만큼 더하게 할지에 대한 가중치 [-1,1]을 결정짓게 되는 것이다. 그래서 input gate와 gate gate가 곱해지게 되어 현재 타임스텝을 cell state에 얼만큼 더할지가 결정된다.

forget gate는 이와 반대로, 이번 time step의 정보를 고려해 봤을 때, 이전 cell state의 정보를 얼만큼 잊어야 하는지를 알려주는 gate이다 그렇기 때문에 sigmoid로 표현된다.

output gate는 위 과정을 거쳐 최종적으로 업데이트된 cell state를 얼만큼 내보내면 될지 그 비율을 결정짓기 위하여 시그모이드를 사용하여 결정된다.

그렇다면 LSTM의 backpropagation은 어떨까? 기존 RNN에서는 hidden state에 대한 역전파가 중요했지만, LSTM에서 최종 아웃풋으로 중요한것은 cell state이기 때문에 ${\partial C_t}/{\partial C_{t-1}}$에 대하여 값을 구할 것이다. 그렇게 되면 graident가 그대로 들어오고 +gate이므로 역전파된 그라디언트가 그대로 통과하고 multiplication gate에서 forget gate가 곱해지면 되므로 upstraem gradient x forget gate가 최종적인 그라디언트가 되는 셈이다.

그리고 최종 hidden state 에서 최초의 cell state로 역전파를 한다고 해도 매 step별로 tanh연산을 거쳤던 RNN과는 달리 LSTM은 한번만 거치면 된다. 왜냐하면, 이를 두 단계 전 까지 표현해보면 $h_t = o \odot tanh( (f\odot (f \odot c_{t-2}+ i\odot g) + i\odot g) ) $ 와 같기 때문이다. 이것을 최초의 cell state까지 표현해보면 $h_t = o \odot tanh( (f\odot (f \odot … c_{1}+ i\odot g) + … + i\odot g) ) $ 가 될것이다. 그렇기 때문에 tanh 연산을 한 번만 거쳐도 되는 것이다.

그리고 내가 궁금해 하는 부분을 강의 듣는 분들도 궁금해 하셔서 다행인것 같다. cell state로 부터 역전파가 될 때, forget gate의 값이 계속해서 곱해지는데, forget gate의 값은 시그모이드 이므로, 결국 vanishing gradient문제가 해결되지 못하지 않을까? 라는 생각이 있었다.

이에 대한 대안으로 사람들이 많이 하는 trick이 존재하는데 forget gate의 biases를 양수로 초기화시키는 방법이 있다고 한다. 즉 초기에 시그모이드 출력의 값이 1에 가깝게 나와서 학습을 안정적으로 해나가게 만든다는 것이다. 하지만, 이런 트릭도 완벽하지는 않다. 결국 vanishing 문제를 일으킬 수 있기 때문이다. 하지만 기존의 RNN만큼 심각하지는 않다고 한다. 그 이유는

  • 매 타입스텝별로 forget gate의 값이 변한다는 것이다.
  • 두 번째 이유는 LSTM에서는 element-wise multiplication이 일어나기 때문이다.

LSTM의 구조는 resnet과 많이 흡사하다고 한다. resnet이 identity mapping을 통하여 그라디언트를 위한 고속도로 역할을 했듯이, LSTM의 element-wise곱이 이와 같은 역할을 수행한다는 것이다. resnet의 구조를 보면 이전 레이어의 출력 + activation function(현재 레이어의 출력)이라고 볼 수 있다. LSTM을 보면 (이전 cell state * forget gate) + (현재 입력의 일정비율)이라고 볼 수 있는 것이다. 그렇기에 구조적으로도 비슷하다. 그리고 또한, element-wise곱은 CNN에서 이미지크기와 동일한 필터로 컨볼루션을 하는것과 동일하다. 그렇기 때문에 resnet에서 역전파와 LSTM의 역전파는 닮은 꼴을 띨 수 있게되는 것이다. (matrix multiplication이었다면 역전파 형태가 달랐을 것이기 때문.)

LSTM을 변형시켜 더 좋은 성능을 얻고자 했으나 성능은 고만고만 했다고 한다. 왜냐하면 LSTM과 GRU는 gradient flow를 고려하여 아주 잘 설계된 네트워크이기 때문이다. 그래서 변형하면 특정 문제에서는 더 좋은 성능을 가지나, 보편적으로 봤을 때, LSTM, GRU보다 딱히 뛰어나지 않고, 성능이 좋지 않은 모습을 보였다고 한다.

CPU vs GPU

Comment  Read more

CS231n-Lecture08(Deep Learning Software)

|

저번 강의 때 배웠던 부분을 살펴보자.

우리는 optimizer, regularization, transfer learning에 대하여 간락하게 배웠었다.

오늘은 딥러닝을 프로그램을 구축하기 위한 소프트웨어에 관하여 간략하게 배워보는 시간을 가질 것이다.

CPU vs GPU

먼저 cpu와 gpu의 차이에 대해서 살펴보자.

이번 강의를 진행해주시는 Justin Johnson(강의당시에는 박사, 현재는?)님의 컴퓨터 내부라고 한다. CPU의 자리는 그림에 나와있는것과 같은 자리이다.

GPU는 이에 반해서 크기가 더 큰 모습을 볼 수가 있다.

deep learning에서는 두 회사의 제품중 NVIDIA의 그래픽카드가 장착된다고 한다. 왜냐하면 NVIDIA의 GPU가 deep learning에 더 적합하게 설계가 되어있기 때문이다. (지금도 그런것 같다.)

CPU와 GPU의 차이를 살펴보자. 우선 코어의 수 부터 엄청 차이가 난다. 하지만 그 코어가 처리하는 역할이 다르다고 볼 수 있다. CPU는 CPU의 메모리에 현재 실행되는 프로그램이 들어가 각 코어가 해당 프로그램이 잘 작동하도록 처리하는 역할을 하지만, GPU의 코어는 CPU에 비해 아주작은 단위 연산을 수행할 뿐이다.

각 코어는 독립적으로 연산을 수행할 수 있으므로, 단순 계산을 할 때에는 코어수가 많은 GPU가 당연히 좋을 것이다. 왜냐하면 3840개의 연산을 독립적으로 수행할 수 있기 때문이다. 하지만 위에서 말한 것 처럼, 각 코어의 속도는 당연히 CPU가 훨씬 빠르다. 즉, GPU의 코어는 단순연산에 맞추어져있다는 뜻이다.

위와 같은 행렬 연산을 생각해보자. 행렬 연산은 결과값을 내는데 있어서 각 원소의 값이 모두 독립적으로 계산될 수 있다. 그런데 이것을 적은 수의 CPU로 sequential하게 일일히 계산하면 매우 느릴 것이다. 반면에 GPU는 코어수가 많으므로 이 행렬 계산을 한번에 수행해낼 수 있다. 즉, CPU는 AxC행렬의 원소를 하나씩 계산하는 반면, GPU는 AxC행렬의 원소를 각 코어가 하나씩 계산하여 동시에 딱! 내놓을 수 있는 것이다.

GPU 프로그래밍을 위한 소프트웨어가 있다. 딥러닝에 주료 사용되는 CUDA가 대표적인 예이며, CUDA를 편하게 사용하기 위한 higher level API인 cuBLAS, cuFFT, cuDNN등이 존재한다.

그림은 CPU와 GPU로 훈련을 시킬 때 시간 차이를 나타낸다. 그냥 비교도 안될만큼 GPU가 빠르다…

훈련시 가장 중요하게 생각해야할 부분 중 하나가 바로 data transfer의 병목현상(bottleneck)이다. 어떤 뜻이냐 하면, GPU에 처리해야할 데이터가 올라가야 한다. 그런데 우리가 훈련시킬 데이터가 100GB인데 이게 한 번에 다 올라가서 처리가 될 수가 없다고 하자. 그러면, 우리는 나머지 데이터는 다른데 저장해놓고, 필요할 때마다 GPU로 불러와 연산을 해야하는 것이다.

그런데 이 때 데이터가 전부 HDD에 저장되있다면, GPU가 다음 데이터를 가져오는데 시간이 너무 오래걸려 GPU가 아무리 좋아도, 훈련 시간이 더 오래 걸릴 것이다. 이를 위한 방법은, HDD를 SSD로 바꾸어 RAM으로 전송속도를 빠르게 하고, RAM의 용량을 늘려, GPU로 전송할 수 있는 대기 데이터의 양이 많아지게 하는 것이다. (SSD->RAM->GPU)

deep learning 프로그래밍을 위한 프레임워크는 Caffe2, Pytorch, Tensorflow가 많이쓰인다고 한다. 주변에는 대부분 pytorch, tensorflow를 사용한다. 나머지는 거의 못봤다.

넘파이를 이용하여 딥러닝을 위한 그래프를 작성하려면 우리가 일일히 그라디언트를 작성해줘야하는 문제점이 존재하고, GPU에서 작동시키지 못하기 때문에 문제가 생긴다.

그에 반해 tensorflow는 위 슬라이드의 문장을 통해서 CPU에서 동작할지, GPU에서 동작할지 설정을 해줄 수 가 있다. (cpu:0 은 첫번 째 cpu를 의미한다. 첫 번째 gpu는 gpu:0으로 고치면된다.)

그 후, 간단한 문장을 통하여 그라디언트를 계산할 수 있다.

pytorch도 간단하다. 변수를 선언하고, 포워드를 한 후 백워드를 하면 훈련이 진행된다.

변수 뒤에 .cuda라는 것을 붙혀, gpu에서 연산을 하도록 바꾸어 줄 수 있다.

넘파이, 텐서플로우, 파이토치간의 코드를 비교하면 위 슬라이드와 같다.

Deep learning framework

Tensorflow

텐서플로우의 기본 흐름은 위와 같다.

하지만 위와같이 코드를 작성하면 문제가 생기는데, 바로 CPU에서 GPU로, GPU에서 CPU로 데이터의 복사가 일어난다는 점이다. 두 장치간 복사는 매우 비용이 큰 연산이다. 그렇다면 어떤 부분에서 복사가 일어난다는 것일까?

바로 placeholder에 sees.run에서 feed_dict를 통해 값을 넣어주는 과정이다. values는 CPU, placeholder는 GPU에 있다고 해보자. 그러면 feed_dict에서 데이터는 매번 저장장치에서 가져오므로 복사가 되는것은 어쩔 수 없는 일이다. 하지만 저장된 가중치는 매번 CPU에 있을 필요가 없다. GPU에 존재하여 업데이트를 계속 진행하면 되는데, GPU에서 연산을하고 values라는 CPU에 존재하는 변수에 저장하고, 다시 feed_dict를 통해 GPU에 올려 업데이트를 진행하니 아주 비효율적인 코드가 되는 것이다.

이와 같은 문제점을 막기 위하여 가중치를 placeholder로 사용하는것이 아닌 tf.Variable로 사용해주는 것이 아주 바람직하다.

그 후, assign함수를 통하여 variable이 그래프의 일부가 될 수 있도록 업데이트 해준다.

Variable을 사용할 경우에는 위 슬라이드와 같이 golbal-variables_initializer를 실행시켜주어야 하고, sees.run은 똑같이 실행시켜주면 된다.

그런데 이상하다? loss가 업데이트가 되지 않는다. 왜 업데이트가 되지 않는걸까? 그 이유는 바로 sess.run에 new_w1과 new_w2를 넣어주지 않아서 그렇다. 즉, sess.run([loss, new_w1, new_w2])가 되어야 한다. **여기서 알아야할 점은, new_w1.new_w2는 출력값이다. 즉, w1.assign(a)라고 하면 w1=a가 되고, w1의 값을 내뱉는 것이므로. w1은 이미 값이 바뀌어져 있다. **

이것이 어떤 말이냐 하면, tensorflow는 computational graph방식으로 구성되어있다. 즉 sess.run([loss])를 하게 되면 loss를 계산해주는 forward pass까지밖에 진행이 안된다는 것이다. grad_w1즉, 그라디언트를 계산하는것, 가중치를 업데이트 하는 연산등 그래프에는 정의되어 있지만, 해당 노드를 계산하도록 run을 안해주었기 때문에 계산이 안되는 것이다.

그렇다면 모든 노드를 일일히 넣어줘야되는가? loss만 넣어준 모습을 보면 아니라는 것을 알 수 있다. computational graph의 계산의 끝에 존재하는 노드만 넣어준다면 이전까지 정의된 그래프의 연산이 모두 실행된다. 그 후 최종적인 노드만 출력하는 것이다.

그러므로 backward시에는 backward의 최종노드인 new_w1과 neww_2를 넣어주면 되는것이다. 하지만 이렇게 두 개 다 쓰면 가독성이 떨어지므로, 위 슬라이드에서는 updates라는 더미노드로 묶어 해당 노드를 sess.run에 넣어준 모습을 볼 수 있다.

하지만 이 강의를 듣지않고, 그냥 텐서플로우 실습만 해보았다면, 어 이런? assign함수는 써본적이 없는것 같은데? 라고 생각하는 사람들이 많을것이다. 그 이유느 바로 optimizer함수를 사용해왔기 때문이다. 이 함수 안에서 해당 과정을 모두 처리해주고 있는 것이다. 그렇기 때문에 코드가 훨씬 간단해지는 모습을 볼 수 있다.

우리는 가중치를 변수로 선언할 때, 곱해져야할 matrix multiplication의 차원을 고려하여 설정해 주어야한다. 그런 부분까지 해결해주는게 tf.layers.dense라는 함수이다.

tensorflow의 wrapper인 keras를 강의에서 소개해준다. 이게 의마하는것은 안에는 텐서플로우로 동작하는데 더 사용하기 편하기 쉽게 감싸놓은 프레임워크인 것이다.

이와 같은 wrapper의 종류는 위 슬라이드에 나와있다. 아마 더 존재하지 않을까 싶다.

강의에서 텐서보드에 대해서 간략하게 언급하고 있다. 우리가 설계한 computational graph, loss, 등을 가시화해서 보여주는 아주 고마운 도구이다.

Pytorch

이번에는 pytorch에 대해서 살펴볼 것이다. 조금더 pytorch가 tensorflow에 비해서 단순화 되있는 느낌을 받을 수 있다.

random tensor를 생성하고, forward와 backward가 있으며 gradient를 update하는 과정이 존재한다. tensorflow의 큰 흐름과 다르지 않은 모습을 볼 수 있다. 하지만 세부적으로 봤을 때, tensorflow는 computational graph를 그려 계산한다는 느낌을 받는다면, pytorch는 computational graph보다 sequential한 프로그래밍 코드를 짜는 느낌에 가깝다. 즉 우리가 평소에 프로그래밍 하는 방법과 비슷하다는 것이다.

그 예로, tensorflow는 sess.run을 통하여 graph의 operation을 직접 실행시켜주어야 하지만, pytorch는 코드를 쓰기만 하면 해당 연산이 진행되는 모습을 볼 수 있다.

파이토치의 텐서는 넘파이+GPU라고 생각하면 된다고 한다. 즉 numpy를 감싸 GPU에서 연산할 준비를 마친게 텐서라고 생각하면 된다. 크게 특별할 것 없이 gpu 자료형을 정의해 준 후, torch.tensor.type(dtype)과 같이 사용해주면 된다고 한다.

파이토치의 텐서를 Variable로 감싸면 그제서야 텐서프로우와 같이 computational graph에 들어갈 준비를 마치게 된다. 그 말은 즉슨, 텐서플로우에서 gradient를 계산해주거나, optimizer을 이용하여 그라디언트를 계산하고 역전파를 해주었듯이, 파이토치에서도 해당 연산이 가능해졌다는 점이다. 텐서만을 이용해서 학습하려고 했을 때는 우리가 직접 그라디언트를 계산하고 업데이트까지 해주었지만, 위 슬라이드를 보면 loss.backward()를 통하여 그라디언트 계산및 역전파 까지 해준 모습을 볼 수 있다.

그라디언트 업데이트는 직접 코드 작성을 하여야한다.

p.s 파이토치의 텐서와 variable은 같은 API를 사용하기 때문에, 텐서에서 가능한 연산은 variable에서 거의 다 가능하며, 그 역도 마찬가지이다.

그렇다면 나만의 Autograd를 만들어보자(Autograd DIY라 할 수 있겠다.) 이 때, torch.autograd.Function클래스를 상속받는 것 부터 출발한다. forward부분에는 입력을 받고 ReLU함수의 기능대로 출력값을 내보내주면 된다. backward에서는 함수의 파라미터로 grad_y를 갖는다. grad_y는 backpropagation 될 때, 이전 까지 역전파 되어온 미분 값이라 생각하면 된다.

backward함수를 보니, autograd.Function에는 saved_tensors라고 forward때 입력받았던 텐서를 저장해두는 변수가 있는것 같다.

사실 Autograd를 우리가 만든다는 것은 그 함수의 미분값을 직접 구해줘야 한다는 것이다. 그 부분을 수행하는 것이 grad_input[x<0], return grad_input인 부분이다. 만약에 입력값이 0보다 작았다면 ReLU에 의해 기울기가 0 이므로 역전파의 값은 0이 될것이고, 0초과라면, 기울기가 1이되어, 여태까지 받았던 역전파값에x1이 됨으로, grad_input을 그대로 return한 것이다.

이렇게 만든 ReLU클래스는 상속받은 클래스에 미리 정의해놓은 함수 때문에, 일반 함수들 처럼 사용하면 된다.

p.s pytorch의 Autograd에 대하여 다음 유튜브 영상이 자세히 설명해주고 있다. requires_grad의 설정에따라 forward와 backward관계의 변화에 따른 cyclie graph의 변화와, backward가 어떻게 진행되는지 설명해주고 있다. 이 동영상에서 주의깊게 볼것은 grad_fn이라는 속성이다. grad_fn은 사용자가 직접 만든 텐서에는 grad_fn=None으로 초기화된다. 즉, 곱, 합, 차 등의 연산으로 나온 텐서에는 grad_fn에 해당 연산이 입력되게 된다. 이 속성을 이용하여 backward함수를 출력시 함수 체인을 통하여 모든 연산에 관한 backward가 차례대로 불려지면서 미분값이 전달되는 것이다. YangSpace블로그의 pytorch의 AUtograd를 제대로 이해하기글을 참고해봐도 좋을것 같다.

pytorch의 nn module을 이용하여 keras와 비슷하게 동작시킬 수 있다.

텐서플로우에 옵티마이저가 있다면, 당연히 파이토치에도 옵티마이저가 존재한다. 가중치 업데이트룰을 optmizer로 설정해주는걸로 바꾸고, 가중치를 수동으로 업데이트 하는 구문을 써줬던 것을 optimizer.step()으로 바꾸어주면 된다.

우리만의 Autograd를 만든것 처럼, 우리만의 nn module을 만들 수 있다. autograd 방식과 비슷하게, torch.nn.Module클래스를 상속받는다. init에 우리가 필요한 레이어를 정의한다. 그 후, forward부분에 우리가 정의한 레이어를거쳐 어떻게 연산이 될지를 정의하고, 결과값을 반환한다.

위 예제처럼 전체 레이어를 담는 모듈을 만들어 아예 모델을 만들어버릴 수 있으며, 특정 기능만을 수행(attention, residual connection 등)하는 모듈을 만들어낼 수 있다. 사용법은 nn module을 사용하는것과 동일하다.

다음에 살펴볼 것은 파이토치의 DataLoader이다.데이터로더는 데이터셋을 감싸서(wrapping) 미니배치, 셔플링, 멀티스레딩등을 더 쉽게 제공해주는 파이토치의 도구이다.

이터레이터는 데이터로더에서 미니배치를 주기 때문에, 그것을 받아 Variable로 감싸기만 하면 computational graph를 완성시킬 수 있다.

pytorch에서 pretrained model을 제공하니, 이것을 사용하여 실습을 해보는 것도 좋을것 같다.

텐서플로우에 텐서보드가 있다면, 파이토치에는 비즈덤(visdom)이 존재한다. 이것 또한 시각화 도구이다.

강의에서 루아(Lua)로 쓰여진 토치(Torch)가 있다는 것만 간략히 설명하고 넘어갔다.

TF vs PT (static vs dynamic graphs)

텐서플로우는 그래프를 정적으로 만드는 반면, 파이토치는 그래프를 동적으로 만든다. 정적으로 만든다는 것은 한번 그래프의 빌드가 끝나면 그래프의 구조는 변하지 않는다는 것이다. 반면 파이토치는 반복문을 반복할 때 매번 새로운 그래프를 만들어낸다. 심지어 똑같은 그래프라도 말이다.

정적인 그래프는 최적화에 아주 용이하다. 위 슬라이드를 보자. Convolution 다음 ReLU를 거친다고 할 때, 이것을 두 개의 레이어로 표현하는것 보다. 하나로 합쳐 표현할 수 있다면, 함수를 참조하는 시간이 줄어들게 된다. 정적인 그래프에서는 모든 연산의 위치, 값, 관계등을 다 파악하고 있기 때문에, 이상이 없는 한에서 이런 최적화를 진행할 수 있는 것이다.

또한, 정적인 그래프의 장점은 직렬화(serialize)시킬 수 있고, 코드없이 빌드한 그래프를 실행시킬 수 있다는 것이다. 왜냐하면 바뀔 것이 없기 때문이다. 하지만 동적그래프는 다르다. 그래프가 빌드되었지만 상황에 따라서 그래프의 구조가 바뀔수 있기 때문에 주변에 항상 코드가 옆에 같이 존재해야 한다.

이런 성질은 분기만에서 더욱 두드러 진다. 파이토치는 일반적으로 프로그래밍하듯이 그래프를 작성하면 되지만, 텐서플로우는 tf.cond라는 분기문을 작성해주어야 한다. 조금 더 복잡한 gate가 추가되는 셈이다.

입력의 크기가 매번 달리질 수 있는 반복문을 생각해보자. 파이토치의 경우 그냥 파이썬의 반복문을 작성하듯이 작성하면 끝이난다. 하지만 텐서플로우에서는 그렇지 않다. 왜냐하면 일정 크기로 고정이 되야 하는데 계속 변하기 때문이다. 이 때, tf.foldl라는 구문을 넣음으로써 해결을 할 수 있다. 텐서플로우는 이렇게 명시적으로 표시를 해주어야한다. 이런 텐서플로우의 폴드(fold)는 dynamic batching을 통하여 동적 그래프를 더 쉽게 만들게 해주는 장치이다. (차라리 그냥 파이토치를 쓰는게 편할지도..)

동적 그래프가 중요한 분야는 위와 같다. 아까 설명한 loop가 주를 이루는 알고리즘 인것 같다.

Caffe

강의에서는 Caffe 에서도 약간 다루고 있다.

구글의 텐서플로우는 한 프레임워크안에서 모든걸 처리하려는 반면, 페이스북에서는 파이토치와 카페를 나눔으로써, 연구와 실제 응용분야에서 쓰이는 생산에 유리한 Caffe2로 나눈것 같다고 강의에서는 설명한다.

Comment  Read more