카테고리 보관물: Report

From R-CNN to Mask R-CNN

Athelas의 블로그에 이미지 분할image segmentation에 관한 최근의 연구 동향을 간단하게 짚어주는 포스트가 올라왔습니다. 바로 R-CNN, Fast R-CNN, Faster R-CNN, Mask R-CNN입니다.

R-CNN

0*Sdj6sKDRQyZpO6oH.

이미지를 분류하는 것보다 이미지 안에 어떤 물체들이 들어 있는지를 구분해내는 것이 훨씬 어려운 작업입니다. R-CNN은 이를 위해 몇단계를 거쳐 임무를 처리합니다. 먼저 가능한 이미지 영역을 찾아내는 리전 프로포잘region proposal 혹은 바운딩 박스bounding box를 만드는 단계가 있습니다. 바운딩 박스를 찾기 위해 셀렉티브 서치selective search 알고리즘을 사용합니다. 가령 색상이나 강도 패턴 등이 비슷한 인접한 픽셀을 합치는 방식입니다. 그런 다음 추출한 바운딩 박스를(대략 2,000여개) CNN의 입력으로 주입하기 위해 강제로 사이즈를 일원화시킵니다. 여기서 사용한 CNN은 미리 훈련된 AlexNet의 변형된 버전입니다. CNN의 마지막 단계에서 서포트 벡터 머신support vector machine을 사용하여 이미지를 분류합니다. 그리고 최종적으로 분류된 오브젝트의 바운딩 박스 좌표를 더 정확히 맞추기 위해 선형 회귀linear regression 모델을 사용합니다.

Fast R-CNN

1*E_P1vAEbGT4HNYjqMtIz4g

R-CNN의 문제점은 모든 바운딩 박스마다 CNN을 돌려야 하고 분류를 위한 SVM, 바운딩 박스를 위한 선형 회귀까지 세가지 모델을 모두 훈련시키기 어렵다는 점입니다. Fast R-CNN은 이 문제들을 해결했습니다. 먼저 바운딩 박스들 사이에 겹치는 영역이 많은데 이들을 따로 따로 CNN을 통과시키는 것은 비용 낭비라고 생각했습니다. 여기에서 RoIPoolRegion of Interest Pooling의 개념을 도입하여 셀렉티브 서치에서 찾은 바운딩 박스 정보를 CNN을 통과하면서 유지시키고 최종 CNN 특성 맵으로 부터 해당 영역을 추출하여 풀링pooling합니다. 이렇게 하면 바운딩 박스마다 CNN을 돌리는 시간을 획기적으로 단축할 수 있습니다. 또한 SVM와 선형 회귀 모델을 모두 하나의 네트워크에 포함시켜 훈련을 시킵니다. SVM 대신 CNN 뒤에 소프트맥스softmax를 놓고, 선형 회귀 대신 소프트맥스 레이어와 동일하게 CNN에 뒤에 따로 추가했습니다.

Faster R-CNN

Fast R-CNN에서 남은 한가지 성능의 병목은 바운딩 박스를 만드는 리전 프로포잘 단계입니다. Faster R-CNN은 리전 프로포잘 단계를 CNN안에 넣어서 마지막 문제를 해결했습니다. CNN을 통과한 특성 맵에서 슬라이딩 윈도우를 이용해 각 지점anchor마다 가능한 바운딩 박스의 좌표와 이 바운딩 박스의 점수를 계산합니다. 대부분 너무 홀쭉하거나 넓은 물체는 많지 않으므로 2:1, 1:1, 1:2 등의 몇가지 타입으로도 좋다고 합니다. Faster R-CNN은 작년에 마이크로소프트에서 내놓은 대표적인 컴퓨터 비전 연구 결과 중 하나입니다.

Mask R-CNN

1*BiRpf-ogjxARQf5LxI17Jw

그리고 올해 초 페이스북 AI 팀이 분할된 이미지를 마스킹하는 Mask R-CNN을 내놓았습니다. Faster R-CNN에 각 픽셀이 오브젝트에 해당하는 것인지 아닌지를 마스킹하는 네트워크(CNN)를 추가한 것입니다. 이를 바이너리 마스크binary mask라고 합니다. 페이스북 팀은 정확한 픽셀 위치를 추출하기 위해 CNN을 통과하면서 RoIPool 영역의 위치에 생기는 소숫점 오차를 2D 선형보간법bilinear interpolation을 통해 감소시켰다고 합니다. 이를 RoIAlign이라고 합니다. 그래서 아래와 같은 멋진 마스킹 이미지가 만들어 집니다.

1*6CClgIKH8zhZjmcftfNoEQ

그런데 아직 감이 잘 안잡히시나요? 조금도 걱정하지 마세요. R-CNN에서 Faster R-CNN까지 자세히 설명해 주시는 아래 이진원님의 동영상을 보시면 해결됩니다! 🙂

TF의 텐서와 상수, 변수, 플레이스홀더

텐서플로우TensorFlow의 기본 데이터 구조인 텐서Tensor는 보통 다차원 배열이라고 말합니다. 텐서플로우에는 세 가지의 핵심 데이터 구조인 상수Constant, 변수Variable, 플레이스홀더Placeholder가 있습니다. 텐서와 이 세 가지 타입은 어떤 관계가 있는 것일까요? “텐서플로 첫걸음“에서는 이들에 대한 자세한 설명이 없이 바로 선형회귀 모델을 간단히 만드는 것으로 시작하고 있습니다. 이 글에서 책에서 부족했던 텐서와 상수, 변수, 플레이스홀더에 대해 살펴 보겠습니다.

텐서플로우의 텐서를 다차원 배열로 많이 설명하지만 이는 맞기도 하고 틀리기도 합니다. 이로 인해 다소 오해도 발생합니다. C++ API에서 말하는 텐서는 다차원 배열에 가깝습니다. 메모리를 할당하고 데이터 구조를 직접 챙깁니다. 하지만 파이썬 API 입장에서 텐서는 메모리를 할당하거나 어떤 값을 가지고 있지 않으며 계산 그래프의 연산(Operation) 노드(Node)를 가리키는 객체에 가깝습니다. 우리가 주로 다루는 파이썬 API의 텐서는 넘파이(NumPy)의 다차원 배열 보다는 어떤 함수를 의미하는 수학 분야의 텐서에 더 비슷합니다.

상수 텐서 하나를 만들어 보겠습니다(이 글에 있는 코드는 주피터 노트북에서 작성된 것으로 명시적으로 print 문을 사용하지 않고 있습니다. 이 노트북은 깃허브에서 볼 수 있습니다). 기본 그래프에 상수나 변수를 계속 추가하면 들여다 보기가 어려우므로 각기 그래프를 따로 만들겠습니다.

계속 읽기

Model evaluation, selection and algorithm selection

아래 글은 파이썬 머신 러닝의 저자 세바스찬 라쉬카(Sebastian Raschka)의 블로그인 “Model evaluation, model selection, and algorithm selection in machine learning“를 저자의 동의하에 번역한 것입니다. 전체 포스트는 4편으로 연재될 예정이었지만 현재 3편까지 쓰여졌습니다. 첫 번째 글은 성능추정에 대한 전반적인 소개와 홀드아웃 방법, 신뢰구간 등을 이항분포를 예로 들어 설명합니다. 두 번째 글은 반복적인 홀드아웃 방법과 부트스트래핑 방식을 설명합니다. 세 번째 포스트는 하이퍼파라미터 튜닝을 위한 홀드아웃 방법과 크로스밸리데이션, 모델 선택에 대해 설명합니다.

  1. 머신 러닝의 모델 평가와 모델 선택, 알고리즘 선택 – 1장. 기초
  2. 머신 러닝의 모델 평가와 모델 선택, 알고리즘 선택 – 2장. 부트스트래핑과 불확실성
  3. 머신 러닝의 모델 평가와 모델 선택, 알고리즘 선택 – 3장. 크로스밸리데이션과 하이퍼파라미터 튜닝

Momentum & Nesterov momentum

경사하강법gradient descent 최적화 알고리즘의 한 종류인 모멘텀momentum과 네스테로프 모멘텀nesterov momentum 방식은 여러 신경망 모델에서 널리 사용되고 있습니다. 비교적 이 두가지 알고리즘은 직관적이고 쉽게 이해할 수 있습니다. 이 글에서는 두 알고리즘이 실제 구현에서는 어떻게 적용되는지 살펴 보겠습니다.

모멘텀 알고리즘은 누적된 과거 그래디언트가 지향하고 있는 어떤 방향을 현재 그래디언트에 보정하려는 방식입니다. 일종의 관성 또는 가속도처럼 생각하면 편리합니다. 머신 러닝의 다른 알고리즘들이 그렇듯이 모멘텀 공식도 쓰는 이마다 표기법이 모두 다릅니다. 여기에서는 일리아 서스키버Ilya Sutskever의 페이퍼1에 있는 표기를 따르겠습니다. 모멘텀 알고리즘의 공식은 아래와 같습니다.

v_{t+1} = \mu v_t - \epsilon g(\theta_t) \\ \theta_{t+1} = \theta_t + v_{t+1}

\epsilon 은 학습속도이고 \mu 는 모멘트 효과에 대한 가중치입니다. v_{t} 는 0 으로 초기화되어 있고 반복이 될 때마다 현재의 그래디언트 - \epsilon g(\theta_t) 가 다음번 모멘트 v_{t+1} 에 누적됩니다. 그리고 다음번 반복에서 v_{t+1} 가 현재의 모멘트 v_{t} 로 사용됩니다. 경사하강법에서 모멘트 항이 추가된 것입니다.

스크린샷 2017-03-21 오후 2.53.35

계속 읽기

GANs in 50 lines of pytorch

이안 굿펠로우(Ian Goodfellow)의 GANs(Generative Adversarial Networks) 페이퍼를 파이토치로 구현한 블로그가 있어서 재현해 보았습니다.(제목과는 달리 전체 코드는 100줄이 넘습니다) 이 블로그에 있는 파이토치 소스는 랜덤하게 발생시킨 균등 분포를 정규 분포 데이터로 만들어 주는 생성기(G, Generator)와 생성기가 만든 데이터와 진짜 정규 분포를 감별하는 분류기(D, Discriminator)를 학습시키는 것입니다.

분류기에는 감별하려는 분포의 데이터 한 벌이 모두 들어가서 참, 거짓의 이진 판별을 내 놓습니다. 생성기는 한번에 하나의 데이터 포인트가 들어가서 출력값도 하나입니다. 원하는 분포를 얻기 위해서 생성기에 필요한 만큼 데이터를 계속 주입하면 됩니다. 행렬로 생각하면 분류기에는 하나의 행이 들어가고, 생성기에는 배치 효과를 위해 하나의 열 벡터가 들어갑니다.

그런데 기대한 것처럼 정규분포와 비슷한 생성기를 학습시키기가 어려웠습니다. 하이퍼파라미터 세팅에 많이 민감하여 종종 에러가 폭주하기도 했습니다. 그리고 학습이 되어도 정규분포의 모양을 가진다기 보다 평균과 표준편차만 같은 분포를 만들어 내었습니다. 즉 평균과 표준편차를 학습한 것 같은 모습니다(물론 이 코드는 결과보다 예시를 위한 것이라고 합니다만..). 그래서 생성기의 출력을 한번에 전체 분포를 얻을 수 있도록 마지막 레이어의 노드 수를 늘렸습니다. 이렇게 했더니 손쉽게 정규 분포와 비슷한 결과물을 얻었습니다만 생성기의 목적을 제대로 수행한 것인지는 확신이 서지 않네요. 🙂

전체 노트북은 깃허브에 있습니다. 코드의 중요 부분은 아래와 같습니다.

학습시키고자 하는 정규 분포는 평균이 4, 표준 편차가 1.25 입니다. 분류기는 입력 유닛이 100개이고 히든 유닛의 수는 50, 출력 노드는 하나입니다. 생성기는 100개의 입출력 유닛을 가지고 히든 유닛 수는 50개 입니다. 이 예제의 분류기, 생성기는 모두 각각 두 개의 히든 레이어를 가지고 있는데 두 개의 히든 레이어의 노드 수는 동일합니다.

data_mean = 4
data_stddev = 1.25

d_input_size = 100
d_hidden_size = 50
d_output_size = 1

g_input_size = 100
g_hidden_size = 50
g_output_size = 100

분류기와 생성기의 클래스 정의입니다.

class Discriminator(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(Discriminator, self).__init__()
        self.map1 = nn.Linear(input_size, hidden_size)
        self.map2 = nn.Linear(hidden_size, hidden_size)
        self.map3 = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        x = F.relu(self.map1(x))
        x = F.relu(self.map2(x))
        return F.sigmoid(self.map3(x))

class Generator(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(Generator, self).__init__()
        self.map1 = nn.Linear(input_size, hidden_size)
        self.map2 = nn.Linear(hidden_size, hidden_size)
        self.map3 = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        x = F.relu(self.map1(x))
        x = F.relu(self.map2(x))
        return self.map3(x)

렐루(ReLU)를 사용해서 레이어를 감싸고 분류기의 출력은 시그모이드로, 생성기의 출력은 마지막 레이어의 값을 그대로 뽑았습니다. 다음은 분류기와 생성기가 서로 학습하는 과정입니다.

for epoch in range(num_epochs):
    # 분류기와 생성기의 학습 횟수가 서로 다를 수 있습니다.
    for d_index in range(d_steps):
        D.zero_grad()  # 그래디언트를 비웁니다.
        real_data = Variable(real_sampler(d_input_size)) # 정규분포
        real_decision = D(real_data) # 정규분포 판별
        real_error = loss(real_decision, Variable(torch.ones(1))) # 무조건 참이어야 합니다
        real_error.backward() # 역전파

        fake_input = Variable(fake_sampler(g_input_size)) # 균등분포
        fake_data = G(fake_input) # 생성기가 정규분포로 변조시킵니다
        fake_decision = D(fake_data) # 가짜인지 구분해야 합니다
        fake_error = loss(fake_decision, Variable(torch.zeros(1))) # 무조건 거짓이어야 합니다
        fake_error.backward()  # 역전파
        d_optimizer.step()     # 분류기 파라미터 업데이트

    # 생성기 학습 차례
    for g_index in range(g_steps):
        G.zero_grad()  # 그래디언트를 비웁니다.
        fake_input = Variable(fake_sampler(g_input_size)) # 균등분포
        fake_data = G(fake_input) # 생성기가 정규분포로 변조합니다
        fake_decision = D(fake_data) # 가짜인지 구분합니다.
        fake_error = loss(fake_decision, Variable(torch.ones(1))) # 생성기 입장에서는 분류기가 참으로 속아야 합니다.
        fake_error.backward()  # 역전파
        g_optimizer.step()     # 생성기 파라미터 업데이트

이렇게 학습시키게 되면 초기에는 평균과 표준편차가 잠시 널뛰지만 반복이 어느 시점을 넘어서면 거의 변동이 없는 상태가 됩니다. 아래 그림에서 파란색이 평균, 붉은 색이 표준편차 입니다.

gan-pytorch-1

에러는 총 세가지로 분류기가 학습할 때 계산하는 정답 분포에 대한 에러(파란)와 가짜 분포(녹색)에 대한 에러, 그리고 생성기가 학습할 때 계산하는 가짜 분포에 대한 에러(적색) 입니다. 아래 그래프에서 볼 수 있듯이 세 에러가 거의 변동이 없이 일치하고 있습니다.

gan-pytorch-2

학습이 끝난 후 만들어진 생성기로 랜덤한 균등 분포를 입력해서 정규 분포로 바꾸어 보았습니다. 눈으로 볼 때에도 구분할 수 없을 만큼 비슷했습니다. 깃허브에 있는 노트북 코드원본 코드와는 조금 다르며 코드를 간소화하기 위해 람다 함수 등을 수정했습니다.

PyTorch MNIST Example

파이토치(PyTorch)로 텐서플로우 튜토리얼에 있는 MNIST 예제를 재현해 보았습니다. 이 코드는 파이토치의 MNIST 예제를 참고했으며 주피터 노트북으로 작성되어 깃허브에 올려져 있습니다. 당연하지만 분류 결과는 텐서플로우로 만든 예제와 큰 차이가 없습니다.

파이토치는 버전 0.1.6을 사용하였습니다. 파이토치 설치는 이 포스트를 참고하세요. 파이토치는 코어 C 라이브러리를 토치(Torch)와 공유하고 있습니다. 그 외에는 모두 파이썬으로 구현되어 있으며 별도로 C API 가 없습니다. 오직 파이썬을 위해서 구현된 라이브러리이고 또 다이나믹 컴퓨테이션 그래프(Dynamic Computation Graph)를 지원하기 때문에 코드 모습이 일반 프로그래밍처럼 자연스러웠습니다.

파이토치의 예제와 유사하게 torch.nn.Module 클래스를 상속받아 모델 클래스를 만들었습니다.

class MnistModel(nn.Module):
    def __init__(self):
        super(MnistModel, self).__init__()
        # input is 28x28
        # padding=2 for same padding
        self.conv1 = nn.Conv2d(1, 32, 5, padding=2)
        # feature map size is 14*14 by pooling
        # padding=2 for same padding
        self.conv2 = nn.Conv2d(32, 64, 5, padding=2)
        # feature map size is 7*7 by pooling
        self.fc1 = nn.Linear(64*7*7, 1024)
        self.fc2 = nn.Linear(1024, 10)
        
    def forward(self, x):
        x = F.max_pool2d(F.relu(self.conv1(x)), 2)
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, 64*7*7)   # reshape Variable
        x = F.relu(self.fc1(x))
        x = F.dropout(x, training=self.training)
        x = self.fc2(x)
        return F.log_softmax(x)
    
model = MnistModel()

Module 를 상속받은 클래스는 뉴럴 네트워크의 정방향 계산을 수행하는 forward() 메소드를 구현해야만 합니다. 이 forward() 메소드는 model 오브젝트를 데이터와 함께 호출하면 자동으로 실행이됩니다.

풀링과 활성화 함수들은 torch.nn.functional 아래에 있고 함수처럼 불러서 사용할 수 있습니다. 이런 함수들은 Conv2d 클래스처럼 유사하게 forward(), backward() 메소드를 가진 클래스를 감싸고 있는 형태입니다. 최종 loss 에서 backward() 함수가 호출되면 거꾸로 짚어가면서 역방향 계산이 연쇄적으로 일어납니다. Conv2d 함수의 파라미터에서 볼 수 있듯이 패딩과 스트라이드를 명시적으로 지정해 주어야 합니다. 콘볼루션의 패딩 계산은 이 포스트를 참고하세요.

Linear 함수는 일반적인 완전연결(fully connected) 레이어를 의미하는 것으로 이것뿐만 아니라 많은 요소들이 토치의 네이밍을 따르고 있는 것 같습니다. 아마도 토치를 썼던 사람은 쉽게 사용할 수 있을 것 같습니다.

model.train()
train_loss = []
train_accu = []
i = 0
for epoch in range(15):
    for data, target in train_loader:
        data, target = Variable(data), Variable(target)
        optimizer.zero_grad()
        output = model(data)
        loss = F.nll_loss(output, target)
        loss.backward()    # calc gradients
        train_loss.append(loss.data[0])
        optimizer.step()   # update gradients
        prediction = output.data.max(1)[1]   # first column has actual prob.
        accuracy = prediction.eq(target.data).sum()/batch_size*100
        train_accu.append(accuracy)
        if i % 1000 == 0:
            print('Train Step: {}\tLoss: {:.3f}\tAccuracy: {:.3f}'.format(i, loss.data[0], accuracy))
        i += 1

모델을 학습시키는 부분도 직관적입니다. Negative Log Likelihood를 의미하는 nll_loss 함수에 출력과 타겟값을 넣고 loss.backward() 를 호출한 다음 optimizer.step() 으로 파라미터를 업데이트 합니다. 여기서 target 은 원-핫-인코딩이 아닌 타겟 레이블을 그대로 넣습니다. loss 와 accuracy 를 리스트에 저장하여 맷플롯립으로 그래프를 그렸습니다.

pytorch-mnist-loss

loss

pytorch-mnist-accuracy

accuracy

모델을 훈련할 땐 model.train() , 테스트할 땐 model.eval() 함수를 호출하면 Module 클래스의 훈련 상태 여부를 바꾸게 되며 이는 dropout 같은 함수가 드롭아웃을 적용할지 안할지를 결정하게 만듭니다.

model.eval()
correct = 0
for data, target in test_loader:
    data, target = Variable(data, volatile=True), Variable(target)
    output = model(data)
    prediction = output.data.max(1)[1]
    correct += prediction.eq(target.data).sum()

print('Test set: Accuracy: {:.2f}%'.format(100. * correct / len(test_loader.dataset)))

최종결과는 99.29%를 얻었습니다. 전체 코드는 깃허브에서 확인할 수 있습니다.

[Review]Introduction to ML with Python

catIntroduction to ML with Python은 사이킷런(scikit-learn) 0.18 버전이 출시된 후 가장 먼저 나온(작년 10월 출간) 파이썬 머신러닝 책입니다. 이전 포스트에서도 잠깐 언급한 적이 있지만 사이킷런 0.18 버전에는 피드포워드(feed-forward) 뉴럴 네트워크 클래스가 추가되었습니다. 그래서 이 책에도 이와 관련된 섹션이 추가되어 있네요. 이 책의 저자 중 한명인 앤디(Andreas C. Muller)는 뉴욕 대학교의 리서치 엔지니어이면서 사이킷런의 핵심 개발자입니다. 머신러닝 PhD로서 머신러닝에 대해서 잘 알고 있을  뿐만 아니라 사이킷런에 대해서 누구보다도 잘 알고 있음은 두말할 필요가 없습니다.

머신러닝의 실용서와 이론서의 측면을 모두 담은 책은 쉽게 보기 힘듭니다. 특히나 여러 종류의 알고리즘에 걸쳐서라면 더욱 그렇습니다. 이 책은 후자 보다는 전자에 무게를 실었습니다. 수식은 과감히 덮고 가능하면 직관적으로 이해할 수 있도록 배려했으며 무엇보다도 상당히 많은 종류의 알고리즘을 설명하고 사이킷런으로 예시를 보여주고 있습니다. 이 책을 번역서로 내기 위해 제가 번역 작업을 진행하고 있고 대략 70% 정도 진척이 되었습니다. 전체 내용을 가늠하실 수 있도록 챕터별 내용을 요약했습니다.

계속 읽기

역전파, 직접 짜봐야 하나요?

OpenAI의 안드레이 카패시(Andrej Karpathy)가 얼마전 ‘Yes you should understood backprop‘란 글을 미디엄 사이트에 올렸습니다. 안드레이는 OpenAI에 오기 전에 스탠포드 대학에서 PhD 학생으로 근무했고 CS231n 강의를 진행했습니다. 이 강의는 영상과 강의 노트 모두 인터넷에 공개되어 있어 인공지능에 관심있는 연구자나 학생들에게 인기가 많습니다. 그런데 학생들이 가끔 CS231n 의 숙제에 불평을 하는 경우가 있는 모양입니다. 텐서플로우 같은 라이브러리가 역전파 알고리즘을 모두 자동으로 처리해 주는 데 굳이 numpy 로 정방향(forward pass), 역방향(backward pass, backpropagation) 코드를 직접 구현하는 숙제를 할 필요가 있느냐고 말이죠.

역전파 코드를 직접 만들어 봐야할 이유가 지적 호기심이나 더 나은 역전파 알고리즘을 만들기 위해서가 아니라 역전파를 알아야 뉴럴 네트워크를 만들 때 오류를 범하지 않고 디버깅을 하는데 도움을 주기 때문입니다. 안드레이는 역전파가 불완전한 추상화(leaky abstraction)라고 말하고 있습니다. 불완전한 추상화 또는 누수 추상화는 조엘 스폴스키의 블로그를 통해서 알려졌었습니다. 한마디로 구멍이 많다는거죠!

계속 읽기

해커가 알려주는 뉴럴 네트워크

이 글은 스탠포드 대학의 Ph.D 학생이었고 현재 OpenAI 연구원인 안드레이 카패시(Andrej Karpathy)가 블로그에 쓴 ‘Hacker’s guide to Neural Networks‘를 저자의 동의하에 번역한 것입니다. 글 서두에 저자가 밝혔듯이 원래 이 글은 좀 더 길게 연재될 예정이었지만 스탠포드 대학의 CS231n 강의를 진행하면서 두개의 챕터만 쓰여진 채 중단된 상태입니다.

이 글은 최대한 이론을 배제하고 머신러닝과 뉴럴 네트워크를 처음 접하는 사람들을 위해 직관적 이해를 돕기 위해 쓰여졌습니다. 그래서인지 단 두 챕터만으로도 많은 사람들에게 크게 환영받고 있는 것 같습니다. 언제 다시 글이 이어질지는 모르겠습니다만 혹시라도 내용이 추가된다면 업데이트 하도록 하겠습니다.

조금 더 전문적이고 폭 넓은 내용을 담고 있는 것은 저자가 추천하는 CS231n 강의 노트입니다. 이 강의 노트의 번역은 AIKorea 블로그나 해현용님의 블로그에서 읽을 수 있습니다.

아래 글에 포함된 코드의 파이썬 버전은 여기에 있습니다.

해커가 알려주는 뉴럴 네트워크

1장: 실수치 회로

기본적인 구조: 하나의 게이트로 된 회로

중첩된 구조: 여러개의 게이트로 된 회로

예제: 단일 뉴런

역전파 고수되기

2장: 머신 러닝

이진 분류

전통적인 방법: 손실 함수

DNN for YouTube Recommendations

youtube-recomm-1

출처: DNN for YouTube Recomm. 페이퍼

이번달에 열리는 10th ACM Conference on Recommender Systems에 맞춰 구글 리서치에서 사용자에게 유투브 동영상을 추천하는 시스템에 대한 개괄적인 소개를 담은 페이퍼(pdf)를 공개하였습니다. 페이퍼의 내용으로 미루어 보아 유투브의 추천 시스템은 구글 브레인팀에서 텐서플로우를 이용하여 구축한 것 같습니다.

유투브에서는 추천을 위해 수백만개의 동영상을 수백개의 후보로 좁히고 이를 바탕으로 다시 사용자에게 추천할 영상을 고르는 두 단계로 구성되어 있습니다. 두 단계 모두 몇단계의 ReLU(Rectified Linear Unit) 활성화 함수를 가진 완전 연결(fully connected) 레이어로 구성되어 있습니다. 후보자를 생성하는 첫 단계는 소프트맥스 함수를 이용한 분류(Classification) 문제로 변환하여 생각하였습니다. 사용된 훈련 데이터는 전체 유투브 동영상 시청 데이터를 기반으로 하고 있고 갯수는 수천억건에 이릅니다.

youtube-recomm-2

출처: DNN for YouTube Recomm. 페이퍼

학습에 필요한 데이터를 추출할 때 효과적인 학습을 위해 워드 임베딩에서 사용하는 네거티브 샘플링(negative sampling)을 기법을 이용하였고 소수의 유저가 일으키는 대량의 트래픽으로 학습이 오염되지 않게 하기 위해 사용자마다 고정된 양의 샘플을 추출했습니다. 이렇게 유투브 트래픽으로 뉴럴 네트워크를 학습시키면 이 모델은 매트릭스 인수분해(matrix factorization) 부담으로 실제 적용이 어려운 user-user 협업 필터링을 간접적으로 구현하는 효과를 가집니다.

youtube-recomm-3

출처: DNN for YouTube Recomm. 페이퍼

첫번째 네트워크에서 학습된 모델을 사용하여 사용자에게 맞는 후보 영상을 고르기 위해 해싱기법을 사용하는 Nearest Neighbor 근사 알고리즘을 사용하였습니다. 네트워크의 입력은 그림에서 볼 수 있듯이 비디오 시청 데이터, 사용자 검색어, 지역, 영상이 제공된 기간, 성별 등 많은 특성들을 사용하고 있습니다. 특별히 가변 길이의 시청 데이터를 언어 모델링에서 사용하는 임베딩 방법을 사용하여 압축(dense representation)하여 입력으로 사용하였습니다.

이 시스템은 유투부의 홈페이지에 맞춤 동영상 영역에 나타나는 항목을 결정합니다. 사용자는 시리즈를 본다거나 한 종류의 영상을 지속적으로 보는 경향이 있습니다. 예를 들면 지금 ‘테일러 스위프트’를 검색했다고 해서 홈페이지의 추천 영역을 테일러 스위프트와 관련있는 것으로 바꾸어 버리는 것이 A/B 테스트 결과 더 좋지 않았습니다. 따라서 시청이 어느 정도 완료된(hold out) 영상까지의 데이터만 추론에 사용합니다(테일러 스위프트 뮤직비디오를 다 시청하니 바로 맞춤 동영상 영역에 테일러 스위프트의 영상이 추천되었습니다).

이와 비슷한 맥락으로 유투브의 추천 시스템은 ‘좋아요’나 ‘싫어요’ 같은 명확한 사용자의 액션 뿐만 아니라 시청 완료 같은 겉으로 드러나지 않는 사용자의 액션을 함께 사용합니다. 이는 전자의 데이터가 너무 드물기(sparse) 때문입니다.

 

youtube-recomm-4

출처: DNN for YouTube Recomm. 페이퍼

두번째 네트워크는 수백개의 후보 가운데 사용자의 디바이스에 노출할 동영상을 결정하는 모델(ranking model)을 학습시킵니다. 이 모델은 동영상을 클릭할 확률을 예측하는 것이 아니라 동영상의 기대 시청 시간을 예측하는 로지스틱 회귀 분석입니다. 이는 동영상을 시청하지는 않고 클릭만 이리 저리 누르는 사람들이 있기 때문에 시청 시간을 모델링 하는 것이 더 좋은 성과를 냅니다. 두번째 모델도 A/B 테스트를 통해 지속적으로 실시간 튜닝이 됩니다.

두번째 네트워크도 수백개의 입력을 사용하여 완전 연결 ReLU 레이어를 쌓아서 구성하며 주된 학습 재료는 비디오 노출 데이터와 사용자가 시청한 데이터입니다. 이 때 사용자와 비디오 데이터를 적절하게 가공하기 위해 각각 상당한 피처 엔지니어링이 불가피 합니다.

이 페이퍼는 뉴럴 네트워크 뿐만이 아니라 추천 시스템과 동영상 사이트의 특징들을 이해해야 해서 읽기 어려웠던 것 같습니다. 부족한 부분은 댓글로 채워주세요.