세상의 변화에 대해 관심이 많은 이들의 Tech Blog search

KnowIT VQA - Answering Knowledge-Based Questions about Videos(KnowIT VQA 논문 설명)

|

이 글에서는 KnowIT VQA: Answering Knowledge-Based Questions about Videos에 대해 알아보고자 한다.

VQA task는 이미지(Visual, 영상으로도 확장 가능)와 그 이미지에 대한 질문(Question)이 주어졌을 때, 해당 질문에 맞는 올바른 답변(Answer)을 만들어내는 task이다.

KnowIT VQA는 VQA 중에서도 Video를 다루는 QA이며, 전통적인 VQA와는 달리 이미지 등 시각자료에서 주어지는 정보뿐만 아니라 외부 지식(상식 베이스, Knowledge Base)이 있어야만 문제를 풀 수 있는 질의들로 이루어진 새로운 task이다.

KnowIT VQA 홈페이지는 https://knowit-vqa.github.io/ 이다.
데이터셋 중 Annotation도 같은 링크에서 다운받을 수 있다.

중요한 부분만 적을 예정이므로 전체가 궁금하면 원 논문을 찾아 읽어보면 된다.


KnowIT VQA: Answering Knowledge-Based Questions about Videos

논문 링크: KnowIT VQA: Answering Knowledge-Based Questions about Videos

초록(Abstract)

지식기반 그리고 Video QA를 결합한 새로운 영상이해(video understanding) task를 제안한다. 먼저, 유명한 시트콤(참고: 빅뱅이론이다)에 대해 24,282개의 사람이 만든 질답 쌍을 포함하는 video dataset인 KnowIT VQA를 소개한다. 데이터셋은 시각적, 텍스트, 시간적 일관성 추론을 지식기반 질문과 결합하여 이는 시리즈를 봄으로써 얻을 수 있는 경험을 필요로 한다. 그리고 시트콤에 대한 세부적인 지식과 함께 시각적, 텍스트 비디오 자료를 모두 사용하는 영상이해 모델을 제안한다. 이 논문의 주된 결과는 1) (외부) 지식의 결합이 Video QA에 대한 성능을 크게 높여주는 것과 2) KnowIT VQA에 대한 성능은 사람의 정확도에 비해 많이 낮다는 것(현 video 모델링의 한계에 대한 연구의 유용성을 내포함)이다.


서론(Introduction)

VQA는 자연어와 이미지 이해를 함께 평가하는 중요한 task로 지난 몇 년간 많은 종류의 VQA가 제안되고 연구되어왔다. 특히 초기 연구와 그 모델들은 주로 이미지의 내용에만 집중하는 형태를 보여 왔다(질문의 예: 안경을 쓴 사람이 몇 명이나 있는가?).

단지 이미지-이미지에 대한 질문을 묻는 것은 한계가 명확했다.

  1. 이미지의 features는 오직 이미지의 정적인 정보만을 잡아낼 뿐 video에 대한 시간적인 일관성이나 연관성은 전혀 고려하지 않았다(예: 그들은 어떻게 대화를 끝내는가?).
  2. 시각 자료(이미지나 영상)는 그 자체로는 외부 지식을 필요로 하는 질문에 대답하는 충분한 정보를 포함하지 않는다.

이러한 한계를 해결하기 위해 단순 이미지와 질문만을 사용하는 Visual QA에서 1) 영상을 사용하는 VideoQA, 2) 외부 지식을 활용하는 KBVQA 등이 등장하였다. 하지만, 여러 종류의 질문을 다루는 일반적인 framework는 여전히 나오지 않았다.

이 논문의 기여하는 바는 여기서부터 출발한다. 영상에 대한 이해와, 외부 지식에 기반한 추론을 모두 활용하는 방법을 제안한다.

  1. 영상을 활용하기 위해 유명 시트콤인 빅뱅이론에다 annotation을 더하여 KnowIT VQA dataset을 만들었다. 질문들은 시트콤에 익숙한 사람만이 대답할 수 있는 수준으로 만들어졌다.
  2. 특정 지식을 continuous representation으로 연결하여 각 질문에 내재된 의도를 추론하는 모듈과 얻어진 지식을 영상 및 텍스트 내용과 결합하여 추론하는 multi-modal 모델을 제안한다.
KnowIT VQA

관련 연구(Related Work)

  • Video Question Answering은 행동인식, 줄거리 이해, 시간적 연관성을 추론하는 등의 시간적 정보를 해석하는 문제들을 다뤄 왔다. 영상의 출처에 따라 시각자료는 자막이나 대본 등 텍스트 자료와 연관되어 해석을 위한 추가 수준의 문맥을 제공한다. 대부분의 연구는 영상이나 텍스트 자체의 정보를 추출할 뿐 두 modality의 결합은 제대로 활용하지 않았다. MovieFIB, TGIF-QA, MarioVQA는 행동인식, Video Context QA는 시간적 일관성을 다룬다.
    • 오직 몇 개의 데이터셋, PororoQA와 TVQA만이 multi-model video representation을 함께 해석하는 benchmark를 제안하였다. 하지만 영상자료를 넘어서는 추가 지식에 대한 고려는 이루어지지 않았다.
  • Knowledge-Based Visual Question Answering: 시각자료와 텍스트만 가지고서는 그 자체에 내재된 정보 외에 추가 지식은 전혀 쓸 수가 없다. 그래서 외부 지식을 포함하는 VQA가 여럿 제안되었다. KBVQA(Knowledge-based VQA)는 외부 지식을 활용하는 대표 VQA 중 하나이다. 이는 지식을 포함하는 데이터셋이지만 초기 단계에 머물러 있다.
    • 그 중 일부 dataset은 질문의 형태가 단순하거나 외부 지식이 정해진 형태로만 제공되었다. KB-VQA는 template에서 만들어진 질문-이미지 쌍을 활용했으며, R-VQA는 각 질문에 대한 relational facts만을 사용했다. FVQA는 entity 식별, OK-VQA는 자유형식의 질문을 사용하였으나 지식에 대한 annotation이 없었다.
    • KnowIT-VQA는 영상을 활용하며, 지식을 특정 데이터에서 직접 추출한 것이 아닌 일반지식을 가져왔기에 이러한 단점들을 해결하였다고 한다.

KnowIT VQA 데이터셋(KnowIT VQA Dataset)

TV Show는 인물, 장면, 줄거리의 일반적인 내용들을 사전에 알 수 있기에 영상이해 task에서 현실 시나리오를 모델링하는 좋은 과제로 여겨진다. 그래서 이 논문에서는 유명 시트콤인 빅뱅이론을 선정하여 KnowIT VQA dataset으로 만들었고, 이는 영상과 show에 대한 지식기반 질답을 포함한다.

Video Collection

영상은 빅뱅이론 TV show에서 가져왔으며, 이는 각 20분 정도의 207개의 episode들로 구성된다. 텍스트 데이터는 DVD에 있는 자막(대본)을 그대로 가져왔다. 각각의 대사는 시간과 화자(speaker)의 순서에 맞게 주석을 달았다. 영상은 scene으로 나누어지며 이는 또 20초 정도의 clip으로 분리되어, 총 12,264개의 clip이 있다.

QA Collection

Amazon Mechanical Turk (AMT)를 사용하였으며 빅뱅이론에 대해 깊은 이해도를 갖는 사람들로 하여금 질답을 생성하도록 했다. 목표는 show에 친숙한 사람들만이 질문에 대답할 수 있도록 하는 질문(그리고 답)을 만드는 것이었다. 각 clip에 대해, 영상과 자막을 각 episode의 대본과 요약과 연결시키도록 했다. 각 clip은 질문, 정답, 3개의 오답과 연관된다.

Knowledge Annotations

여기서 “knowledge”(지식)이란 주어진 영상 clip에 직접 포함되지 않는 정보를 가리킨다. 질답 쌍은 추가 정보로 annotated되어:

  • Knowledge: 짧은 문장으로 표현되는 질문에 대답하기 위해 필요한 정보.
    • 질문: 왜 Leonard는 Penny를 점심에 초대했는가?
    • Knowledge: Penny는 이제 막 이사를 왔다.
    • 정답: 그는 Penny가 빌딩에서 환영받는다고 느끼기를 원했다.
  • Knowledge Type: Knowledge가 특정 episode에서 왔는지, 아니면 show의 많은 부분에서 반복되는지(recurrent)를 말한다. 6.08%의 Knowledge만이 반복되며 나머지 9개의 season에 속하는 Knowledge는 거의 균등하다. 분포와 Knowledge의 예시를 아래 그림에서 볼 수 있다.
KnowIT VQA
  • Question Type: 범용 모델의 개발을 장려하기 위해 test set에서만 제공되며, 4가지 종류로 구분된다:
    • visual-based(22%): video frame 안에서 답을 찾을 수 있다.
    • textual-based(12%): 자막에서 답을 찾을 수 있다.
    • temporal-based(4%): 현재 video clip의 특정 시간에서 답을 예측 가능하다.
    • knowledge-based(62%): clip만으로는 답을 찾을 수 없다(외부 지식이 필요하다).
KnowIT VQA

Data Splits

12,087개의 video clip으로부터 24,282개의 sample을 추출하였으며 train/val/test set으로 구분되었다. 일반적인 QA dataset처럼 정답은 오답보다 살짝 더 길이가 긴 편향이 존재한다.

KnowIT VQA

Dataset Comparison

KnowIT VQA 데이터셋과 다른 데이터셋을 비교한 표를 아래에서 볼 수 있다:

KnowIT VQA
  • QA 생성이 더 까다롭기 때문에 KBVQA의 크기가 작다.
  • KnowIT VQA는 질문의 수가 24k개로 2.4k의 KB-VQA, 5.8k의 FVQA, 14k의 OK-VQA에 비해 훨씬 많다.
  • TVQA와 영상이 일부 겹치지만 KnowIT VQA는 더 많은 clip을 사용하였다.

인간 평가(Human Evaluation)

다음의 목적에 따라 평가를 진행하였다:

  1. video clip이 질문에 답하는 것과 연관이 있는지
  2. 질문이 답변하는데 Knowledge를 실제로 필요로 하는지
  3. Knowledge가 답변하는 데 정말로 도움이 되는지
  4. 모델 비교를 위한 human performance baseline을 설정하기 위해

Evaluation Design

AMT를 사용하였고, worker를 1) 9개의 시즌 전부를 적어도 한 번은 본 masters 그룹과 2) 빅뱅이론을 조금도 본 적 없는 rookies로 구분하였다. 질문에 대한 평가와 knowledge에 대한 평가를 중심으로 진행하였다.


Evaluation on the questions

위의 두 그룹은 각각 Blind(질답만 제공), Subs(질답과 자막 제공), Video(질답, 자막, 영상 clip 제공)으로 구분되었다. worker들은 4지선다형 중 맞는 답과 그 답을 선택한 이유를 6지선다형으로 풀게 하였다.

  • SubsVideo 그룹의 차이는 video의 영향을 보여준다.
  • mastersrookies 그룹의 차이는 knowledge의 중요성을 보여주며 KnowIT VQA는 show를 보지 않았을 때 굉장히 어려운 task임을 보여준다.

Evaluation on the knowledge

모아진 Knowledge의 품질과 질문 간 연관성을 분석하였다. rookies들에게 test set의 질문에 대답하도록 하였다. 각 질문과 선택지에 대해 자막과 video clip이 제공되었다. 답을 한 후에는 연관된 Knowledge를 보여주고 다시 답변하도록 하였다. 결과적으로 Knowledge가 주어지기 전후로 정확도는 0.683에서 0.823으로 증가하였다. 이는 답변을 하는 데 있어 Knowledge의 연관성(그리고 그 중요성)을 보여 준다.

KnowIT VQA

ROCK Model

ROCK (Retrieval Over Collected Knowledge) 모델의 구조는 다음과 같다.

KnowIT VQA
  • Knowledge Base(KB)에 있는 show 정보를 표현하는 언어 instance에 기초한다.
  • 언어와 시공간적 영상 표현을 합하여 질문에 대해 추론하고 답을 예측한다.

Knowledge Base

시청자가 시리즈를 볼 때 정보를 습득하는 것을 모방한다. show에 대해 특정 정보에 접근해야 하기 때문에, Knowledge의 AMT worker 주석에 의존한다.

모아진 Knowledge는 자연어 문장의 형태로 주어진다. ‘Raj가 Penny 집에서 한 것은 무엇인가?’라는 질문에서 주석 Knowledge는 다음과 같다:

Raj는 Missy에게 데이트하자고 요청하길 원했는데, Howard와 Leonard는 이미 그녀에게 물어봤지만 실패했기 때문이지만, (그가 먹었던) 약효가 사라졌고 그는 그럴(요청할) 수 없었다.

Knowledge Graphs와 같은 복잡한 구조에서 어떻게 정보를 가져오는지 불분명하기에, knowledge instance $w$는 자연어 문장으로 표현된다. 거의 중복되는 instance를 제거하였고, $N$은 24,282에서 19,821로 줄어들었다.

Knowledge base $K$는 다음과 같다.

[K = \lbrace w_j \vert j=1, …, N \rbrace]

Knowledge Base Cleaning

거의 중복되는 문장을 제거하기 위해 Knowledge instance 사이의 유사도를 측정하였다. 각 $w_j \in K$에 대해 다음과 같은 입력 문장을 만들었다:

[w_j^{‘} = [\text{CLS}] + w_j + [\text{SEP}]]

[CLS]는 문장의 시작을, [SEP]은 끝을 나타내는 token이다. 이를 60개의 token으로 나눈 뒤 BERT를 통과시켜 고차원의 projection을 얻었다.

[\mathbf{p}j = \text{BERT}{\mathbf{p}}(\tau_j)]

두 문장의 유사도는 cosine 유사도를 사용하였으며 다음과 같이 구해진다.

[\beta_{i, j} = sim(\mathbf{p}_i, \mathbf{p}_j)]

그리고 무향 그래프 $V$를 만들고 유사도 $\beta$가 0.998 초과인 경우에만 edge를 만들었다. 해당 그래프에서 크기 $L$ 이상의 clique를 찾아 임의로 1개만 남기고 나머지는 삭제하였다.

[V = \lbrace w_j \vert j=1, …, N\rbrace]

Knowledge Retrieval Module

전체 과정은 다음과 같다.
질문과 답안 선택지 $q_i, a_i^c, \quad c \in \lbrace 0, 1, 2, 3\rbrace$를 사용하여 Knowledge base $K$에 질의를 한다.
그리고 연관도 점수 $s_{ij}$에 따라 $w_j \in K$의 순위를 매긴다.

먼저 입력이 될 표현 $x_{ij}$를 다음과 같이 문자열들을 이어 붙여 구한다.

[x_{ij} = [\text{CLS}] + q_i + \sum_{k=0}^3 a_i^{\alpha_k} + [\text{SEP}] + w_j + [\text{SEP}]]

이전 연구들은 $a_i^c$의 순서는 별다른 영향이 없다고 하였지만, 불변성을 확보하기 위해 연관도가 높은 순($a_i^0 \rightarrow a_i^3$)으로 정렬하였다.

  • 그리고 입력 표현을 $n$개의 단어로 이루어진 sequence $\textbf{x}_{ij}^{10}$으로 토큰화하고 BERT에 입력으로 준다.
  • 점수를 매기기(scoring) 때문에 $\text{BERT}^{\text{S}}(\textbf{x}_{ij})$로 표기한다.
  • $s_{ij}$를 계산하기 위해 fully connected layer를 사용하고 sigmoid를 붙인다.
  • $\textbf{w}, b$를 각각 FC Layer의 weight와 bias라 할 때

[s_{ij} = \text{sigmoid} (\textbf{w}{\text{S}}^{\top} \cdot \text{BERT}^\text{S}(\textbf{x}{ij}) + b_{\text{S}} )]

$\text{BERT}^{\text{S}}, \textbf{w}, b$는 매칭되거나 되지 않는 QA-knowledge 쌍과 다음의 Loss로 미세조정(fine-tuned)된다:

[\mathcal{L} = -\sum_{i=j} \log(s_{ij}) -\sum_{i\neq j} \log(1 - s_{ij})]

각 $q_i$에 대해 $K$ 안의 모든 $w_j$는 $s_{ij}$에 의해 순위가 매겨진다. 이중 상위 $k$개가 질의로 주어진 질문과 가장 연관 있는 것으로써 이것들이 얻어진다.

Prior Score Computation

모델이 다른 답변 후보에 대해 다른 출력을 내는 것을 막기 위해 답안들을 정렬함으로써 순서에 불변적인 모델을 만들었다. prior score $\xi^c$에 따라

[a^c, \quad c \in \lbrace 0, 1, 2, 3\rbrace]

질문 $q$가 주어지면, $\xi^c$는 정답에 대한 $a^c$의 점수를 예측함으로써 얻어진다. 먼저 입력문장 $e^c$를 문자열을 이어 붙여 얻는다:

[e^c = [\text{CLS}] + q + [\text{SEP}] + a^c + [\text{SEP}]]

그리고 $e^c$를 120개의 토큰 $\mathbf{e}^c$로 토큰화한다. $\text{BERT}_{\text{E}}(\cdot)$를 output이 [CLS]인 BERT 네트워크라 한다면,

[\xi^c = \textbf{w}{\text{E}}^{\top} \text{BERT}{\text{E}}(\mathbf{e}^c) + b_E]

이제 모든 $\xi^c$를 내림차순으로 정렬한 후 이에 맞춰 답변 후보 $\alpha_c$를 재배열한다.

Video Reasoning Module

이 모듈에서는 얻어진 knowledge들이 영상 내용으로부터 얻은 multi-modal 표현과 함께 처리되어 정답을 예측한다. 이 과정은 다음 3가지 과정을 포함한다.

  1. Visual Representation
  2. Language Representation
  3. Answer Prediction

Visual Representation

각 video clip으로부터 $n_f$개의 프레임을 뽑아 영상 내용을 설명하기 위해 아래 4가지의 다른 과정을 거친다:

  1. Image features: 각 frame은 마지막 FC Layer가 없는 Resnet50에 들어가 2048차원의 벡터로 변환된다. 모든 frame에서 나온 벡터를 모아 새로운 FC Layer를 통과시켜 512차원으로 만든다.
  2. Concepts features: 주어진 frame에 대해, bottom-up object detector를 사용하여 object과 특성들의 리스트를 얻는다. 이를 전부 인코딩하여 $C$차원의 bag-of-concept 표현으로 만들고, FC Layer를 통해 512차원으로 변환된다. $C$는 가능한 모든 object와 특성들의 전체 수이다.
  3. Facial features: 주요 인물의 사진 3~18개를 사용하여 최신 얼굴인식 네트워크를 학습시켰다. 각 clip에 대해 $F$차원의 bag-of-faces 표현으로 인코딩하여, FC Layer를 통과시켜 512차원으로 만든다. $F$는 네트워크에서 학습되는 모든 사람의 수이다.
  4. Caption features: 각 frame에 대해 영상 내용을 설명하는 캡션을 Xu et al. 2015으로 만들었다. 각 clip으로부터 얻은 $n_f$개의 캡션은 언어표현 모델의 입력으로 사용된다.

Language Representation

텍스트는 미세조정된 BERT-reasoning을 통과한다. 언어입력은 다음과 같이 구해진다.

[y^c = [\text{CLS}] + caps + subs + q + [\text{SEP}] + a^c + w + [\text{SEP}]]

$caps$는 $n_f$개의 캡션을 시간순으로 이어 붙인 것이고 $subs$는 자막, $w$는 $k$개의 knowledge instance를 합친 것이다.
각 질문 $q$에 대해, 답변 후보 $a^c, \ c = \lbrace 0, 1, 2, 3\rbrace$ 하나당 하나씩 총 4개가 생성된다.

$m$개의 단어로 이루어진 $\mathbf{y}^c$로 토큰화하고 언어표현 $\mathbf{u}^c = \text{BERT}_{\text{R}}(\mathbf{y}^c)$를 얻는다. (R은 reasoning)

Answer Prediction

정답을 예측하기 위해, 시각표현 $\textbf{v}$(images, concepts, facial features)를 언어표현 $\mathbf{u}^c$를 이어 붙인다:

[\textbf{z}^c = [\mathbf{v}, \mathbf{u}^c]]

$ \textbf{z}^c$는 scalar로 사영된다.

[o^c = \textbf{w}_{\text{R}}^{\top}\textbf{z}^c + b_R]

예측된 답안 $\hat{a} = a^{\text{argmax}_c\textbf{o}}$는 $\textbf{o} = (o^0, o^1, o^2, o^3)^\top$ 중 최댓값의 index로 구해진다. $c^*$를 정답이라고 하면 multi-class cross-entropy loss를 써서 미세조정한다:

[\mathcal{L}(\mathbf{o}, c^) = - \log \frac{\exp(o^{c^})}{\Sigma_c \exp{(o^c)}} \qquad \text{for} \ \text{BERT}{\text{R}}, \textbf{w}{\text{R}}, b_{\text{R}}]


실험 결과(Experimental Results)

ROCK 모델을 여러 기준 모델과 비교하여 아래에 정리하였다. SGD를 사용하여 momentum 0.9, learning rate는 0.001을 사용하였으며 BERT는 사전학습된 초기값을 사용한 uncased based 모델을 사용하였다.

KnowIT VQA

Answers

데이터셋에 존재할 수 있는 잠재적 편향을 찾기 위해서 단지 답변 자체만을 고려하여 정답을 예측하는 평가를 진행하였다:

  • Longest / Shortest: 예측 답변이 가장 많거나 적은 단어를 포함하는 답을 선택한다.
  • word2vec / BERT sim: word2vec은 300차원의 사전학습된 벡터를 사용하였다. BERT에서는 3번째~마지막 layer의 출력을 사용하였다. 답변은 word representation의 평균으로 인코딩되었다. 예측은 가장 높은 코사인 유사도를 갖는 것을 선택한다.

전체적으로, baseline은 매우 낮은 성능을 보였으며, Longest만이 찍는 것보다 간신히 나은 성능을 보였다. 정답이 긴 경우가 조금 많은 것을 제외하면 데이터셋에서는 특별한 편향은 발견되지 않았다.

KnowIT VQA

QA

오직 질문과 답변만을 고려하여 여러 baseline에 대해 실험을 진행하였다.

  • word2vec / BERT sim: 위의 Answers와 비슷하게 진행되었으며 질문이 고려 대상에 추가되었다.
  • TF IDF: tf-idf에 의한 단어벡터 가중으로 질문과 답변을 표현한 후 512차원으로 만들었다. 질문과 4개의 답변을 이어 붙여 4-class 분류기에 집어넣었다.
  • LSTM Emb. / BERT: 질문과 답변들의 단어를 사전학습된 BERT와 LSTM에 통과시킨다. LSTM의 마지막 layer는 512차원의 문장표현으로 사앵되었다. 역시 각각을 이어 붙여 4-class 분류기에 넣었다.
  • ROCK_QA: $m = 120$ token을 사용하는 ROCK 모델을 질문과 답변만으로 학습시켰다.

유사도에 기반한 방법을 찍는 것보다 형편없지만, 분류기를 사용한 경우는 훨씬 더 나은 결과를 보이며, 사람보다 더 잘하기도 했다.

KnowIT VQA

Subs, QA

자막, 질문, 답변을 사용한다.

  • LSTM Emb. / BERT: 자막은 LSTM에 의해 인코딩된 후 자막-질문-답변으로 이어 붙여 4-class 분류기에 넣는다.
  • TVQA_SQA: 언어는 LSTM layer에 의해 인코딩되며 다른 시각정보는 쓰지 않았다.
  • ROCK_SQA: $m = 120$ token을 사용하는 ROCK 모델을 자막, 질문, 답변만으로 학습시켰다.

LSTM BERT와 ROCK_SQA는 질문과 답변만을 사용할 때보다 성능이 5.7% 향상되었다. LSTM Emb.는 전혀 향상되지 않았으며 자막의 긴 문장을 인코딩하는 데 한계가 있었기 때문이라 생각된다.

KnowIT VQA

Vis, Sub, QA

이제 언어와 시각 정보 둘 다에 기반한 Video QA 모델이다.

  • TVQA: SOTA VideoQA 방법이다. 언어는 LSTM layer로, 시각 데이터는 visual concepts으로 인코딩된다.
  • ROCK_VSQA: $m = 120$ token과 $n_f=5$ frame을 사용하는 ROCK 모델을 질문과 답변만으로 학습시켰다. 4개의 다른 시각표현이 사용되었다.

ROCK_VSQA는 TVQA를 6.6%만큼 앞질렀다. 그러나, 어떤 visual 모델이든지 ROCK_SQA를 능가하는데 이는 현 영상모델링 기법의 큰 한계를 암시한다.

KnowIT VQA

Knowledge

모델이 이제 Knowledge를 정답을 예측하는 데 사용한다. 전체 버전의 ROCK 모델은 $n=128, k=5$의 지식검색 모듈과 $m=512$의 영상추론 모듈을 사용한다.
non-knowledge 모델과 비교하여, 지식검색 모듈을 포함함으로써 6.5%의 성능 향상이 있으며, 지식기반 접근법의 상당한 잠재력을 보인다(시각표현인 Image, Concepts, Facial과 같이). 그러나, 전문가인 사람과 비교하여, ROCK은 여전히 뒤처지며, 이는 향상시킬 여지가 충분하다는 것을 뜻한다. 지식검색 대신 딱 맞는 지식을 그대로 갖고 오는 모델(ROCK_GT)를 사용하면 정확도는 0.731까지 오르며, 지식검색 모듈도 개선할 여지가 있음을 보여준다.

KnowIT VQA

마지막으로 아래 그림에서 질적 평가 결과를 볼 수 있다.

KnowIT VQA

Knowledge Retrieval Results

지식검색 모듈에 대한 결과로, recall at K와 median rank(MR)에 대한 결과를 아래 표에서 볼 수 있다.

  • 질문만 있는 경우
  • QA parameter sharing: 같은 parameter가 4개의 후보 답변에 사용됨
  • QA prior score: prior score에 따라 순서를 배열하는 방법
KnowIT VQA

질문만 사용하는 경우와 다른 두 경우에 큰 차이가 있는데 이는 후보 답변에 올바른 지식을 가져오는 데 필요한 정보가 포함되어 있음을 나타낸다. 가장 좋은 결과는 prior score에 따라 순서를 정한 것인데, 이는 모든 후보 답변을 전부 사용하는 것이 더 정확한 정보를 얻는 데 도움이 된다는 뜻이다.


결론(Conclusion)

새로운 지식 필요 영상 VQA를 제시하였고 multi-modal 영상정보와 지식을 결합하여 사용하는 영상추론 모델을 제시하였다. 진행된 실험은 영상이해 문제에서 지식기반 접근법의 상당한 잠재력을 보였다. 그러나, 사람의 능력에 비하면 크게 뒤처지며, 이 데이터셋을 통해 더 강력한 모델의 개발을 장려하며 이를 바라는 바이다.

Acknowledgements

New Energy and Industrial Technology Development Organization


참고문헌(References)

논문 참조!


Comment  Read more

KVQA - Knowledge-Aware Visual Question Answering(KVQA 논문 설명)

|

이 글에서는 KVQA: Knowledge-Aware Visual Question Answering에 대해 알아보고자 한다.

VQA task는 이미지(Visual, 영상으로도 확장 가능)와 그 이미지에 대한 질문(Question)이 주어졌을 때, 해당 질문에 맞는 올바른 답변(Answer)을 만들어내는 task이다.

KVQA는 VQA의 일종이지만, 전통적인 VQA와는 달리 이미지 등 시각자료에서 주어지는 정보뿐만 아니라 외부 지식(상식 베이스, Knowledge Base)이 있어야만 문제를 풀 수 있는 질의들로 이루어진 새로운 task이다.

KVQA 홈페이지는 http://malllabiisc.github.io/resources/kvqa/ 이다.
데이터셋도 같은 링크에서 다운받을 수 있다(총 90G 상당).

중요한 부분만 적을 예정이므로 전체가 궁금하면 원 논문을 찾아 읽어보면 된다.


KVQA: Knowledge-Aware Visual Question Answering

논문 링크: KVQA: Knowledge-Aware Visual Question Answering

초록(Abstract)

Visual Question Answering(VQA)는 CV, NLP, AI에서 중요한 문제로 떠오르고 있다. 전통적인 VQA에서는, 이미지에 대한 질문이 주어지는데 이는 이미지에 있는 정보만으로도 답변할 수 있다: 예를 들어, 사람들이 있는 이미지가 주어지면, 이미지 안에 사람이 몇 명이나 있는지를 묻는 것과 같은 식이다.
좀 더 최근에는 고양이/개 등 일반명사를 포함하는 일반상식(commonsense knowledge)을 요구하는 질문에 답변하는 것이 주목받고 있다. 이러한 흐름에도 버락 오바마 등 고유명사에 대한 세계 지식(world knowledge)를 요하는 질문에 답변하는 것은 다뤄진 적이 없었다. 그래서 이러한 부분을 이 논문에서 다루며, (세계) 지식을 알아햐 하는 VQA인 KVQA를 제안한다.
이 데이터셋은 18K개의 고유명사와 24K개의 이미지를 포함하는 183K개의 질답 쌍으로 이루어져 있다. 데이터셋의 질문들은 여러 객체와 다중 관계, 큰 Knowledge Graphs(KG) 상에서 다중도약 추론을 요구한다. 이러한 점에서 KVQA는 KG를 갖는 VQA에서 가장 큰 데이터셋이다. 또한, KVQA에 SOTA 방법을 사용한 결과도 보인다.


서론(Introduction)

ovieQA Dataset

VQA는 AI와 소통하는 데 있어 아주 훌륭한 도구로 자리잡았다. 지난 몇 년간 많은 발전이 있었는데, 우선은 VQA의 시초가 되는 VQA 논문가 있다. 전통적인 VQA는 이미지 등의 시각자료가 주어지면, 이미지에 관련한 질문을 묻고, 이미지에서 얻은 정보를 바탕으로 답변을 하는 방식이다.

위 그림이 주어졌을 때, 전통적인 VQA 질문의 예시는 다음과 같다.

  • Q: 이미지에 몇 명의 사람이 있는가?
  • A: 3

최근에는 Commonsense Knowledge를 사용해야만 문제를 풀 수 있는 VQA가 더 관심을 받고 있다. 이러한 일반상식의 예시는 다음과 같다.

  • Microphone은 소리를 증폭시키는 데 쓴다.
    • Microphone은 일반명사이다.

일반상식을 필요로 하는 질문은 다음과 같다.

  • Q: 이미지에 있는 것 중 무엇이 소리를 증폭시키는 데 쓰이는가?
  • A: Microphone

그러나 실생활에서 문제는 이렇게 간단하지 않고 이미지에 존재하는 고유명사(named entities)에 대한 정보를 필요로 하는 경우가 많다. 예를 들면,

  • Q: 버락 오바마의 왼쪽에 있는 사람은 누구인가?
  • A: Richard Cordray

  • Q: 이미지에 있는 사람들은 공통 직업을 가지는가?
  • A: Yes

그래서 이러한 경우를 다루기 위해 논문에서 KVQA(Knowledge-aware VQA)를 제안한다. 이러한 방향의 연구는 별로 다뤄지지 않았고 또한 필요할 것이라 생각된다.

Wikidata(Vrandecic and Kr¨otzsch 2014)는 대규모 KG로서 사람들, 생물 분류, 행정상 영토, 건축구조, 화합물, 영화 등 5천만 개의 객체를 포함하는 KG이다. 여기서 필요한 것을 추출하는 것은 어렵기 때문에, 논문에선 생략되었다. 대신 이에 대한 배경지식과 관련한 사람들과 수동으로 검증된 질답 쌍을 포함하는 KVQA 데이터셋을 제안한다.

KVQA 데이터셋에서는 visual 고유명사와 Wikidata의 객체를 연결하기 위해 Wikidata에서 69K 명의 사람들 이미지를 포함하도록 했다.

이 논문이 기여한 바는:

  1. 이미지의 고유명사를 다로는 VQA를 처음으로 다룬다.
  2. Knowledge-aware VQA인 KVQA 데이터셋을 제안하며 이는 일반상식을 요구하는 기존 데이터셋보다 12배 크며 고유명사를 다룬 것으로는 유일하다.
  3. KVQA에 SOTA 방법을 적용하여 그 성능을 제시하였다.

관련 연구(Related Works)

  • VQA는 2015년 제시된 이후 계속해서 발전하여 2017년 v2.0 데이터셋 발표, (2020년까지) 챌린지를 매년 열고 있다. 비슷한 연구가 많이 발표되었지만 문제가 단순하다는 점에서 한계점이 명확하다.
  • 이후에는 일반상식을 필요로 하는 VQA가 많이 연구되어 왔다.
  • Computer Vision and Knowledge Graph: 컴퓨터비전과 KG를 연결하려는 데 관심이 늘고 있다. 이는 단지 VQA의 활용에만 그치는 것이 아니라 이미지 분류 등의 전통적인 CV 문제에서도 사용된다. 하지만, 대부분은 이미지와 일반상식 그래프를 연결짓는 데에 치중하고 있다.
  • Other Datasets: CV에서 사람을 인식하고 판별하는 것은 주류 연구 분유 중 하나이다. 유명인사나 사람의 자세 추정, 장소 추정 등이 있다.
  • Face identification and context: 이미지에 나타나는 고유명사와 KG를 연결하는 것은 web scale에서 얼굴식별의 필요로 이어진다. 얼굴식별은 연구가 많이 이루어졌지만 대부분 작은 규모에서 이루어지고 있다. KVQA이 더 규모가 크기 때문에 좋다는 뜻이다.

KVQA 데이터셋과 다른 데이터셋을 비교한 표를 아래에서 볼 수 있다:

ovieQA Dataset

KVQA 데이터셋(KVQA)

간략한 통계는 아래 표와 같다. 이미지와 질문의 수 등을 볼 수 있고, train/val/test set이 구분되어 있다.

ovieQA Dataset

데이터는 다음 과정을 통해 모았다:

  1. Image Collection: Wikidata로부터 운동선수, 정치인, 배우 등의 사람들의 리스트를 가져와 Wikipedia에서 사람(30K)의 이미지(70K)와 설명을 가져왔다.
  2. Counting, identifying and ordering persons in image: 이미지의 사람 수를 세고, 사람 수에 따라 human annotator를 부여하였다. 사람이 없거나 너무 많은 경우 등은 제외하고 중복 제거 등을 수행하였다. 그리고 왼쪽부터 오른쪽으로 맞는 human annotator를 부여하였다.
  3. Obtaining ground-truth answers: 이제 고유명사(사람)와 이미지 안의 관계(왼쪽/오른쪽 등)를 파악하였으니 이미지에 있는 사람에 어떤 질문을 할 수 있는지를 정하여 질문 템플릿을 정하고 SPARQL는 오직 ground truth를 얻기 위해 사용되었다.
  4. Training, Validation, and testing: train/val/test set을 70/20/10%의 비율로 다른 VQA와 동일하게 구분하였다.
  5. Paraphrasing questions: task의 복잡도를 늘리고 실세계와 유사하게 만들기 위해 test set의 질문을 다른 표현으로 바꿔서(https://paraphrasing-tool.com/) 이를 수동으로 검증하여 일부를 사용하였다. 예시는:
  • 원래 질문: 이미지에 있는 사람들 중 Paula Ben-Gurion와 결혼했던 사람은 누구인가?
  • 바뀐 질문: Paula Ben-Gurion과 어떤 시점에선 결혼한 적 있는 사람은 누구인가?
ovieQA Dataset

KVQA 데이터셋 분석(Analysis of KVQA)

KVQA는 자세나 인종 등 광범위한 사람들을 다루거나 KG에서 다중도약 추론을 해야 하는 challenge를 포함한다. 질문은 총 10가지 분류로 나뉜다:

spatial, 1-hope, multi-hop, Boolean, intersection, subtraction, comparison, counting, multi-entity, multi-relation

위 분류는 상호 배제적인 것은 아니다. 위 그림의 (c)에서, 추론은 다음과 같이 여러 단계를 거쳐야 한다:

  • 왼쪽부터 2번째 사람을 찾고
  • Narendra Modi는 BJP의 일원이며
  • BJP는 Syama Prasad Mookerjee에 의해 설립되었다.

질문의 종류와 답변의 간략한 분포는 아래와 같이 확인할 수 있다.

ovieQA Dataset

일반적인 VQA에서는 ‘어떤 스포츠인가’라는 질문에는 높은 확률로 ‘테니스’가 정답이고, Yes/no 질문에서는 ‘Yes’가 87%으로 꽤 편향된 것을 볼 수 있다.
단 KVQA는 꽤 균등한 분포를 보이는데, 이는 질문을 다양하게 바꿈으로써 해결한 것이라 한다.

Knowledge Graph(KG)

논문 발표 시점 최신인 2018-05-05 버전의 Wikidata를 KG로 사용하였다. 이는 ‘주어-관계-목적어’의 triplet 형태를 가지고 각 객체와 관계는 고유한 Qid와 Pid로 표현된다(예. Barack Obama는 Q76, has-nickname은 P1449). 이 KG는 5200개의 관계, 1280만개의 객체와 5230만 개의 지식을 포함한다. 이럴 114명K의 사람에 대한 것을 고려하여 Wikidata에서 사람들의 이미지를 가져왔다. 단 데이터가 불완전하여 69K명의 것만 가져왔다. 이 이미지는 visual entity를 연결하는 데 사용된다.

Baseline을 평가하는 두 세팅은 다음과 같다:

  1. Closed-world setting: 18K의 객체와 3단계 추론을 사용한다. 사실(Facts)은 18종류의 관계만을 포함한다(직업, 시민권 국적, 출생지 등).
  2. Open-world setting: 훨씬 open된 3단계 추론 실험적 세팅이다. 69K의 객체와 상위 200개의 관계를 포함한다.

접근법(Approach)

Visual Entity Linking

Visual entity linking은 visual entity를 KG에 연결하는 것인데, KVQA에서 visual entity는 사람이다. 그래서 visual entity linking은 안면 식별이 되는데, 사람의 수가 매우 많으므로 그 규모가 매우 크다.

Face identification at scale

Face Localization은 KVQA에서 안면 식별의 선구자격 역할을 한다. 그래서 두 가지 방법(Zhang et al. 2016; Hu and Ramanan 2017)을 사용해보고 더 좋은 방법(Zhang)을 써서 face localization을 수행한다. 이후 얼굴 식별(face identificatino)을 수행하는데, Baseline으로 Facenet을 선정하여 사용하였다.

그래서 참고 이미지의 face representation을 얻고 1-최근접 이웃을 18K/69K명의 고유명사 이미지에 대해 찾았다. 결과는 아래에서 볼 수 있다.

ovieQA Dataset

VQA over KG

일단 Visual Entity를 KG에 열결하면, VQA 문제는 KG로부터 (질문과) 관련 있는 fact를 찾아 추론한 뒤 답변하는 것을 학습하는 것과 같아진다. 이 논문에서는 visual entity linking과 QA를 두 개의 분리된 모듈로 다룬다(end-to-end를 쓰지 않음).

KVQA의 baseline으로 memNet(memory network, Weston at al., 2014)을 썼는데 이 모델은 외부 지식으로부터 학습하는 일반적인 구조를 제공한다(memNet은 상식 요구 VQA에서 SOTA 성능을 보임). 이 논문에서 사용한 모델의 구조는 다음과 같다:

ovieQA Dataset

1. Entity Linking

이미지, Wikipedia 캡션(선택적 요소), 질문이 주어지면 이미지와 질문에 있는 entity(visual entities: 사람)를 찾는 것이다. Wikidata에서 가져온 참고 이미지를 활용하며, 텍스트(캡션과 질문)에 있는 entity를 찾기 위해 Dexter 라고 하는 오픈소스 entity recognizer를 사용한다. Visual entity linking과 Text entity linking은 위 그림에서 보듯이 따로 이루어진다.

이 부분에서 entity를 모두 찾고 또한 추가적으로 이미지 상의 entity들의 위치(상대적 위치나 얼굴의 좌표 등)를 얻는다.

2. Fetching facts from KG

이제 entity와 연관 있는 정보를 KG에서 찾는다. 여기서는 3단계(3-hop)만 거치도록 제한하였다. 이러한 knowledge fact들을 공간적 fact(ex. Barack Obama(의 얼굴의 중심)는 (x1, y1) 좌표에 있다)와 함께 보강한다.

그림에서 Facts + Face coornidates라 표시된 부분이다.

3. Memory and question representation

각 knowledge와 공간적 fact는 연관된 memory embedding $m_i$를 얻기 위해 BLSTM에 입력으로 주어진다. 질문 $Q$에 대한 embedding ($q$) 또한 비슷하게 얻어진다.

그리고 $q$와 $m_{ij}$ 사이의 매치되는 정도를 구하기 위해 내적과 softmax를 거친다:

[p_{ij} = \text{softmax}(q^Tm_{ij})]

여기서 $p_{ij}$는 knowledge fact들과 $Q$ 사이의 soft attention과 같은 역할을 한다. 이를 얻으면 모든 fact에 대한 출력 representation을 선형결합하여 출력 representation을 구한다:

[O = \sum_j p_{ij}o_{ij}]

$o_{ij}$는 fact $j$에 대한 representation이다.

4. Question answering module

출력 representation $O$와 $q$의 합은 MLP $f_\theta$에 입력으로 들어간다($\theta$는 학습가능한 parameter). 그리고 정답 $\hat{a}$를 구하기 위해 softmax를 적용한다:

[\hat{a} = \text{softmax}(f_\theta(O+q))]

학습하는 동안, memory/question representation과 QA 모듈을 함께 학습한다. 손실함수는 cross-entropy loss를 사용하였으며 SGD로 최소화한다.

다중도약(multi-hop) facts 탐색을 효율적으로 하기 위해 memory layer를 쌓고 질문 representation을 다음과 같이 구한다:

[q_{k+1} = O_{k+1} + q_k]

첫 번째 레이어의 질문 representation은 BLSTM으로부터 구해진다. 최상층 레이어(layer K)에서 질문에 대한 답은 다음과 같이 구해진다.

[\hat{a} = \text{softmax}(f_{\theta}(O_K+q_{K-1}))]

이 논문에서는 3-hop을 사용하므로 $K=3$이다.

Memory Network의 대안은 knowledge facts를 표현하기 위해 BLSTMs를 사용하는 것이다. 이는 baseline의 하나로 사용되었다.

KVQA의 양적평가(quantitative results)는 아래 표의 왼쪽 부분과 같다.

ovieQA Dataset

Closed/Open World 각각에 대해 평가하였으며, Wikipedia Caption을 사용했으면 +wikiCap, 아니면 -wikiCap으로 표시되어 있다. 질문을 paraphrase했으면 PRP, 아니면 ORG(original)이다.
모든 평가에서는 고정된 크기의 사전을 사용하였으며 질문에서 각 token, Wikipedia caption, visual entity들은 1-hot 벡터로 표현하였다.

결과를 보면,

  • BLSTMs는 fact의 수가 증가할수록 비효율적이었으며 memNet이 이를 압도한다.
    • visual entity linking은 많은 수의 다른 요인에 의해 open world에서 실수가 많았으며
    • 수많은 fact에서 질문과 관련된 fact를 찾는 것이 어려워지기 때문이다.
  • Ablation study에서(위 표에서 오른쪽 부분)는
    • KG에서 논리적 추론만에 의한 부분을 연구하기 위한 세팅을 oracle setting이라 한다.
    • 이 oracle setting은 각 사람과 이미지 내에서 사람의 순서는 알려져 있다고 가정한다.
    • memNet은 공간적, 다중도약, 다중관계, subtraction 등에서 부적합했지만 1-hop, 이지선다 및 교차질문에서는 괜찮은 성능을 보였다.
    • 이것은 질문의 종류에 따라 어떤 가이드가 필요하다는 것을 뜻할 수 있다. 이는 추후 연구로 남겨둔다.

논의 및 요약(Discussion and Summary)

얼굴 식별과 VQA에서의 진전에도 불구하고 Knowledge-aware VQA에서 더 많은 연구가 필요함을 보였다. 기존의 Facenet이나 memory network는 수많은 distractor가 존재하거나 규모가 커진 경우에는 제대로 성능을 내지 못하였다.

이 논문에서는 Knowledge-aware VQA인 KVQA를 제안하였고 평가방법과 baseline을 제시하였다. KVQA의 현재 버전은 KG의 중요 entity인 사람에만 국한되어 있다. 하지만 Wikidata와 같은 KG는 기념물이나 사건 등 여러 흥미로운 entity에 대한 정보도 갖고 있기 때문에 추후 확장할 수 있다. 이 분야에 많은 연구가 이루어지길 희망한다.

Acknowledgements

MHRD와 인도 정부, Intel이 지원하였다고 한다..


참고문헌(References)

논문 참조!


Comment  Read more

Self-Supervised Learning(자기지도 학습 설명)

|

이 글에서는 Self-Supervised Learning(자기지도 학습)에 대해 알아본다. Self-Supervised Learning은 최근 Deep Learning 연구의 큰 트렌드 중 하나이다.

Self-Supervised Learning의 기본적인 개념과 여러 편의 논문을 간략히 소개하고자 한다.


Self-Supervised Learning

일반적으로 Supervised Learning(지도학습)이 높은 성능의 모델을 만드는 것이 유리하지만, 수많은 데이터에 label을 전부 달아야 한다는 점에서 데이터셋 모으기가 어려우며 따라서 활용하는 방법도 제한적일 수밖에 없다.
이와 같은 문제를 해결하고자 나온 방법이

  • 아주 일부분만 label이 존재하는 데이터셋을 사용하는 Semi-Supervisd Learning(준지도 학습)
  • label이 전혀 없는 데이터셋을 사용하는 Unsupervised Learning(비지도 학습)

이고, 최근 주목받는 연구 방법이 Self-Supervised Learning(자기지도 학습)이다. 보통 Self-Supervised Learning을 연구할 때, 다음과 같은 과정을 따른다:

  1. Pretext task(연구자가 직접 만든 task)를 정의한다.
  2. Label이 없는 데이터셋을 사용하여 1의 Pretext task를 목표로 모델을 학습시킨다.
    • 이때, 데이터 자체의 정보를 적당히 변형/사용하여 (label은 아니지만) 이를 supervision(지도)으로 삼는다.
  3. 2에서 학습시킨 모델을 Downstream task에 가져와 weight는 freeze시킨 채로 transfer learning을 수행한다(2에서 학습한 모델의 성능만을 보기 위해).
  4. 그래서 처음에는 label이 없는 상태에서 직접 supervision을 만들어 학습한 뒤, transfer learning 단계에서는 label이 있는 ImageNet 등에서 Supervised Learning을 수행하여 2에서 학습시킨 모델의 성능(feature를 얼마나 잘 뽑아냈는지 등)을 평가하는 방식이다.

여기서 Self-Supervised Learning의 이름답게 label 등의 직접적인 supervision이 없는 데이터셋에서 스스로 supervision을 만들어 학습하기 때문에, supervision이 전혀 없는 Unsupervised Learning의 분류로 보는 것은 잘못되었다는 시각이 있다.

I now call it “self-supervised learning”, because “unsupervised” is both a loaded and confusing term. In self-supervised learning, the system learns to predict part of its input from other parts of it input. In other words a portion of the input is used as a supervisory signal to a predictor fed with the remaining portion of the input. Self-supervised learning uses way more supervisory signals than supervised learning, and enormously more than reinforcement learning. That’s why calling it “unsupervised” is totally misleading. That’s also why more knowledge about the structure of the world can be learned through self-supervised learning than from the other two paradigms: the data is unlimited, and amount of feedback provided by each example is huge. Self-supervised learning has been enormously successful in natural language processing. For example, the BERT model and similar techniques produce excellent representations of text. BERT is a prototypical example of self-supervised learning: show it a sequence of words on input, mask out 15% of the words, and ask the system to predict the missing words (or a distribution of words). This an example of masked auto-encoder, itself a special case of denoising auto-encoder, itself an example of self-supervised learning based on reconstruction or prediction. But text is a discrete space in which probability distributions are easy to represent. So far, similar approaches haven’t worked quite as well for images or videos because of the difficulty of representing distributions over high-dimensional continuous spaces. Doing this properly and reliably is the greatest challenge in ML and AI of the next few years in my opinion. - Yann Lecun

게시물을 보면 예전에는 이러한 학습 방식을 “주어진” supervision이 없기 때문에 Unsupervised Learning이라 불렀지만, 사실은 모델이 “스스로” supervision을 만들어 가며 학습하기 때문에 Self-Supervised Learning이라 부르는 것이 맞다고 설명하는 내용이다.
그리고 (label은 없어도) 데이터는 무궁무진하며, 그로부터 얻을 수 있는 feedback 역시 엄청나기 때문에 “스스로” supervision을 만드는 Self-Supervised Learning 방식이 매우 중요하게 될 것이라고 언급한다.


Image Representation Learning

Discriminative Unsupervised Feature Learning with Exemplar Convolutional Neural Networks

NIPS 2014

Examples

이 논문에서는 Examplarpretext task로 한 Examplar-CNN이라고 하는 모델을 소개한다.

$N$개의 이미지에서 중요한 부분, 정확히는 considerable gradients를 가지고 있는 32 $\times$ 32 크기의 patch를 하나씩 추출한다(Gradient 크기의 제곱에 비례하는 확률로 patch를 선정함). 이렇게 추출한 Seed patch를 갖고 여러 가지 data augmentation을 진행한다. 위 그림의 오른쪽에서 볼 수 있듯이 translation, scaling, rotation, contrast, color 등을 이동하거나 조정하여 만든다.

분류기는 Data augmentation으로 얻어진 patch들은 하나의 class로 학습해야 한다. 여기서 Loss는 다음과 같이 정의된다.

[L(X) = \sum_{\text{x}i\in X} \sum{T \in T_i} l(i, T\text{x}_i)]

$l(i, T\text{x}_i)$은 Transformed sample과 surrogate true lable $i$ 사이의 loss를 의미한다. 즉 하나의 이미지가 하나의 class를 갖게 되는데, 이는 데이터셋이 커질수록 class의 수도 그만큼 늘어난다는 문제를 갖고 따라서 학습하기는 어렵다는 단점을 안고 있다.

아래는 실험 결과이다.

Examples

Unsupervised Visual Representation Learning by Context Prediction

ICCV 2015

이 논문에서는 context prediction 종류의 pretext task를 제안하는데, 간단하게 말하면 이미지에 $ 3 \times 3 = 9$개의 patch를 가져와, 중간 patch와 다른 1개의 patch를 보여 주고, 다른 1개의 patch가 중간 patch의 어느 뱡향(왼쪽 위, 위, 오른쪽 등 8방향)에 위치하는지를 찾는 문제이다. 아래 그림을 보면 이해가 빠를 것이다:

Examples
위 그림에서 무엇이 정답인지 보기

Answer key: Q1: Bottom right Q2: Top center

두 장의 이미지를 한 번에 입력으로 받아야 하기 때문에 아래와 같이 patch를 받는 부분을 두 부분으로 만들었다.

Examples

그리고 “자명한” 해를 쉽게 찾을 것을 방지하기 위해, 9개의 patch들이 딱 붙어 있지 않도록 하였으며 중심점의 위치도 랜덤하게 약간 이동하여 patch를 추출하여 자명한 경우를 최대한 피하고자 하였다(하지만 충분치는 않았다고 한다).

아래는 실험 결과이다.

Examples

Unsupervised Learning of Visual Representations using Videos

ICCV 2015

CNN에 의미적으로 레이블링된(semantically-labeled) 것이 꼭 필요한지 의문을 가지며 출발하는 이 논문은 시각적 표젼(visual representatinos)을 학습하기 위해 레이블이 없는 십만 개의 영상을 웹에서 가져와 사용하였다. 핵심 아이디어는 visual tracking이 supervision을 준다는 것이다. track으로 연결된 두 개의 패치는 같은 물체나 그 일부분을 포함할 것이기에, deep feature space에서 비슷한 시각적 표현을 갖고 있을 것이라 생각할 수 있다. 이러한 CNN 표현을 학습하기 위해 ranking loss function을 사용하는 Siamese-triplet 네트워크를 사용한다.
ImageNet에서 단일 이미지를 가져오는 대신 레이블이 없는 10만 개의 영상과 VOC 2012 dataset을 사용하여 비지도 학습을 수행, 52% mAP를 달성하였다. 이는 지도학습을 사용한 ImageNet 기존 결과의 54.4% mAP에 거의 근접하는 수치이다.
또한 이 논문은 surface-normal 추정 문제와 같은 것도 충분히 잘 수행함을 보여준다.

Examples
  • (a) 레이블이 없는 영상이 주어지면 그 안에서 비지도 tracking을 수행한다.
  • (b) 첫 frame에서 Query 패치, 마지막 frame에서 추적하는 patch, 다른 영상에서 가져온 무작위 패치로 구성된 총 3개의 패치가 siamese-triplet 네트워크에 입력으로 들어가 학습된다.
  • (c) 학습 목표: Query 패치-추적된 패치 사이의 거리가 Query 패치-무작위 패치 사이의 거리보다 더 작게 되도록 학습한다.

Joint Unsupervised Learning of Deep Representations and Image Clusters

CVPR 2016

JULE는 deep representations와 image cluster의 Joint Unsupervised LEarning을 위한 recurrent framework를 말한다. 이 framework에서, clustering 알고리즘에서 연속적인 동작이 recurrent process의 한 스텝으로 표현되어, CNN의 representation 출력의 위에 쌓아지는 형태를 갖는다. 학습하는 동안, image clusters와 representations는 공동으로 업데이트된다: image clustering은 forward pass로, representation 학습은 backward pass로 수행된다.
좋은 표현은 image clustering에 유익하며, 그 결과는 representation 학습에 지도를 해 줄 수 있을 것이라는 것이 핵심 부분이다. 두 과정을 하나의 모델에 통합하여 합쳐진 가중 triplet loss를 사용하여 end-to-end 방식으로 최적화했기에, 더 강력한 표현뿐만 아니라 더 정확한 image cluster도 얻을 수 있다.
여러 실험은 이 방법이 여러 데이터셋에서 image clustering을 할 때 기존 SOTA를 뛰어넘는다는 것을 보여준다.

Examples
Examples

Colorful Image Colorization

ECCV 2016

이 논문에서는 회색조 이미지가 입력으로 주어지면 적절히 색깔을 입혀 그럴 듯한 컬러 이미지로 바꾸는 작업을 수행한다. 이 문제는 명백히 제약조건이 부족하기 때문에, 기존 접근법은 사용자와 상호작용하거나 채도를 감소시킨 채색에 의존하였다.
여기서는 선명하고 사실적인 채색 결과를 도출하는 완전히 자동화된 접근법을 제시한다. 저자들은 이 문제에 내재하는 불확실성을 포용하여 이 문제를 분류 문제로 생각하고 채색 결과의 색깔 다양성을 증가시키기 위해 학습 시간에서 class별 rebalancing을 사용한다. 시스템은 CNN에서 feed-forward pass로 구성된다.

결과는 ‘채색 튜링 테스트(colorization Turing test)’으로 평가하는데, 사람 지원자에게 실제 이미지와 생성된 이미지 중 어떤 것이 실제 이미지인지 판별하도록 하였다. 결과는 32%가 답을 틀렸다는 점에서 이전 방법에 비해 매우 뛰어난 결과를 보여준다.

더욱이, 채색이라는 문제가 cross-channel encoder로서 기능하는 self-supervised feature 학습을 위한 강력한 pretext task로 기능할 수 있음을 보였다.

아래에서 채색(colorization) 결과를 확인할 수 있다. 꽤 뛰어난 결과를 보여준다.

Examples

전체 모델 구조는 아래와 같다.

Examples

Unsupervised Learning of Visual Representations by Solving Jigsaw Puzzles

ECCV 2016

이 논문은 직소 퍼즐처럼 이미지에 9개의 patch를 만들어 순서를 뒤섞은 뒤 원래 배치를 찾아내는 문제를 pretext task로 지정하였다. 대부분은 너무 비슷한 순열이기 때문에 딱 100개의 순열만을 지정하여 이를 분류하는 것을 목표로 한다.

Examples

다른 문제 간 호환성을 위해 context-free network(CFN, siamese-enread CNN)을 사용하였다. 이미지 패치를 입력으로 하고 CFN은 AlexNet에 비해 적은 parameter를 가지면서 동일한 의미 학습 능력을 유지한다고 한다.


Stacked Denoising Autoencoders: Learning Useful Representations in a Deep Network with a Local Denoising Criterion

입력 이미지에 무작위 noise를 추가한 뒤 원래 이미지를 복원하는 것을 pretext task로 지정하였다.

Examples

Self-supervised learning of visual features through embedding images into text topic spaces

CVPR 2017

대규모의 multimodal (텍스트와 이미지) 문서를 사용함으로써 visual features를 self-supervised learning을 수행한 논문이다. 특정 이미지가 그림으로 나타날 가능성이 더 높은 의미론적 문맥을 예측하는 CNN을 학습함으로써 구별되는 visual features이 학습될 수 있음을 보였다고 한다.

이를 위해 잘 알려진 주제 모델링 기술을 사용하여 텍스트 말뭉치에서 발견 된 숨겨진 의미 구조를 활용하였고, 최근의 자기지도 접근 방식과 비교하여 이미지 분류, object detection 및 multimodal 검색에서 SOTA 성능을 보여주었다.

Examples

Colorization as a Proxy Task for Visual Understanding

여기서는 Proxy Task라는 이름으로 self-supervised learning을 수행했다. 이 논문에서는 ImageNet의 학습 paradigm을 재검토하여 다음 질문에 대한 답을 찾는다:

  • 얼마나 많은 학습 데이터가 필요한가?
  • 얼마나 많은 레이블이 필요한가?
  • 미세조정할 시 얼마나 많은 features가 변화하는가?

그래서 Proxy task로 채색(colorization) 문제를 사용하여 강력한 지도를 해줄 수 있음을 보인다.

Examples

Learning Image Representations by Completing Damaged Jigsaw Puzzles

WACV 2018

이 논문은 직소 퍼즐을 사용하는데 위의 논문과는 달리 일부 손상이 가해진 직소 퍼즐을 pretext task로 삼아 손상된 직소 퍼즐을 맞추는 것을 목표로 하였다.

3 $\times$ 3개의 패치를 만들고 순서를 섞은 뒤, 9개 중 1개를 제거하고 회색조 이미지로 변환한다. 이를 원래 이미지로 복원하는 것이 목표이다.

Examples
Examples

Unsupervised Representation Learning by Predicting Image Rotations

ICLR 2018

Examples

이 논문은 이미지를 0°, 90°, 180°, 270° 회전시킨 후 얼마나 회전시켜야 원본 이미지가 나오는지를 맞히는 4-class 분류 문제를 pretext task로 사용하였다.

Examples

회전시킨 4종류의 이미지를 한 번에 넣는 것이 효율이 좋고, 2 또는 8방향 회전도 고려하였으나 4방향이 가장 좋은 성능을 보인다고 한다.


Cross-Domain Self-supervised Multi-task Feature Learning using Synthetic Imagery

CVPR 2018

보다 일반적인 고수준의 시각표현을 얻기 위해 하나의 task가 아닌 여러 개의 task를 학습시키는 방법을 제안하는 이 논문은 합성된 이미지를 학습한다. 실제 이미지와 합성된 이미지의 도메인 차이를 극복하기 위해 적대적 학습 방법에 기초한 비지도 domain adaptation 방법을 사용한다. 합성된 RGB 이미지가 입력으로 들어오면 네트워크는 그 표면 normal, depth, 등고선 등을 동시 추정하며 실제와 합성된 이미지 domain 간의 차이를 최소화하려 한다.

Examples

Depth prediction에서 기존 feature 학습 방법은 패치의 상대적 위치를 예측하능 등의 task를 pretext task로 한다. 이 논문에서, pretext task는 pixel prediction task를 채택하였다.

Examples

이 논문에서, Instance contour detection(객체 등고선 탐지), Depth Prediction(깊이 추정), Surface normal estimation(표면 수직벡터 추정)이 multi-task를 구성한다.


Self-Supervised Representation Learning by Rotation Feature Decoupling

ICML 2019

이 논문에서는 회전과 관련된 부분과 무관한 부분을 포함하는 split representation을 학습하는 모델을 제시한다. 이미지 회전과 각 instance를 동시에 식별하는데, 회전 식별과 instance 식별을 분리함으로써 회전의 noise 영향을 최소화하여 회전 각도 예측의 정확도를 높이고 이미지 회전과 관계없이 instance를 식별하는 것 역시 향상시키도록 한다.

Examples

방법은 다음으로 구성된다:

  • Rotation Feature Decoupling
    • 이미지 회전 예측
    • Noisy한 회전된 이미지 식별
    • Feature Decoupling
      • 회전 분류
      • 회전과 무관한 부분
      • 이미지 instance 분류
Examples

Unsupervised Deep Learning by Neighbourhood Discovery

ICML 2019


##


##


##


##


Examples
Comment  Read more

Python Selenium 사용법 [파이썬 셀레늄 사용법, 크롤링]

|

이 글에서는 Python 패키지인 Selenium에 대해 알아본다. 파이썬으로 크롤링할 때 Beautifulsoup4와 더불어 빼놓을 수 없는 훌륭한 라이브러리이다.

고급 기술(?)까지 전부 정리했기 때문에, 처음 사용하는 사람이라면 우선 필요한 기능부터 보면 된다.

예시 코드의 일부는 Selenium Documentation의 코드를 참조하였음을 밝혀둔다.


Install

일반 python 환경이라면 pip(pip3)을, conda 환경이라면 conda를 사용한다.

pip install selenium
conda install selenium

일반적인 파이썬 라이브러리와는 다르게, 하나 더 필요한 것이 있다.

브라우저별로 selenium webdriver를 다운로드해야 한다. 필자는 크롬을 추천한다:

Examples

버전이 여러 개가 있는데, 본인이 사용하는 Chrome의 버전에 맞는 webdriver를 다운받아야 한다.
크롬의 버전은 여기에서 확인하거나 오른쪽 위 점 3개 > 도움말 > Chrome 정보에서 확인할 수 있다.

다운받은 파일을 Python 파일과 같은 디렉토리에 둔다. 다른 곳에 두어도 상관없지만, driver 경로를 입력하기 아주 조금 더 귀찮아질 수 있다.


Import

import selenium
from selenium import webdriver
from selenium.webdriver import ActionChains

from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By

from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support.ui import WebDriverWait

불러오기(Driver & Web Load)

URL = 'https://www.miraeassetdaewoo.com/hki/hki3028/r01.do'

driver = webdriver.Chrome(executable_path='chromedriver')
driver.get(url=URL)

먼저 webdriver.Chrome(executable_path) 함수를 사용하여 드라이버를 로드한다. 여기서는 driver라는 변수에 저장한다.
조금 전 다운로드한 파일 이름이 chromedriver.exe라면 경로는 같은 디렉토리인 경우 그냥 파일 이름(chromedriver)만 입력하면 된다. 확장자는 필요 없으며, 파일 경로가 다르다면 상대 경로나 절대 경로를 이용하자.

그리고 get(url) 함수를 사용하면, 해당 URL을 브라우저에서 띄운다.

현재 url 얻기

참고로, 현재 url은 다음 코드를 쓰면 된다.

print(driver.current_url)

브라우저 닫기

driver.close()

Wait till Load Webpage(로딩 대기)

브라우저에서 해당 웹 페이지의 요소들을 로드하는 데 시간이 좀 걸린다. 따라서 element가 존재하지 않는다는 error를 보고 싶지 않다면 해당 요소가 전부 준비가 될 때까지 대기해야 한다.

Implicit Waits(암묵적 대기)

driver.implicitly_wait(time_to_wait=5)

찾으려는 element가 로드될 때까지 지정한 시간만큼 대기할 수 있도록 설정한다. 이는 한 webdriver에 영구적으로 작용한다. 인자는 초 단위이며, Default 값은 0이다. 위의 예시는 5초까지 기다려 준다는 의미이다.

Explicit Waits(명시적 대기)

간단하게는 time.sleep(secs) 함수를 사용하여 무조건 몇 초간 대기하는 방법이 있다. 생각하기 귀찮다면 매우 편리하긴 하지만, 형편없는 효율을 자랑하므로 지양하자.

아래 코드를 살펴보자.

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Chrome('chromedriver')
driver.get(url='https://www.google.com/')
try:
    element = WebDriverWait(driver, 5).until(
        EC.presence_of_element_located((By.CLASS_NAME , 'gLFyf'))
    )
finally:
    driver.quit()

위의 코드는 웹페이지에서 class가 gLFyf인 어떤 element를 찾을 수 있는지를 최대 5초 동안 매 0.5초마다 시도한다. expected_conditions(EC)는 만약 element를 찾을 수 있었으면 True를, 아니라면 False를 반환한다.

이 예시에서는 element가 존재하는지를 조건으로 사용했는데, 이것 말고도 아래와 같은 예시들이 있다:

제목이 어떤 문자열인지, 어떤 문자열을 포함하는지, 특정/모든 요소가 로드되었거나/볼 수 있거나/볼 수 없거나/클릭 가능하거나 등등의 여러 조건이 가능하다.

  • title_is
  • title_contains
  • presence_of_element_located
  • visibility_of_element_located
  • visibility_of
  • presence_of_all_elements_located
  • text_to_be_present_in_element
  • text_to_be_present_in_element_value
  • frame_to_be_available_and_switch_to_it
  • invisibility_of_element_located
  • element_to_be_clickable
  • staleness_of
  • element_to_be_selected
  • element_located_to_be_selected
  • element_selection_state_to_be
  • element_located_selection_state_to_be
  • alert_is_present

Custom으로 조건을 설정하는 것도 가능한데, __init__ 함수와 __call__ 함수를 구현한 class를 작성하면 된다. 여기를 참조한다.

참고로, until(method, message='') 함수는 method의 반환값이 False인 동안 계속 method를 실행한다.
반대로 until_not(method, message='') 함수는 True인 동안 실행한다.


시작하기 전에…

먼저 다음 코드를 실행해보자. 앞에서 설명한 대로 chromedriver.exe를 같은 위치에 놓았다면 문제 없이 실행될 것이다.
구글 홈페이지가 바뀌었다면 오류가 생길 수도 있다. 이건 글쎄..뭐..어쩔 수 없다.

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from time import sleep

options = webdriver.ChromeOptions()
options.add_argument('window-size=1920,1080')

driver = webdriver.Chrome('chromedriver', options=options)
driver.implicitly_wait(5)

driver.get(url='https://www.google.com/')

search_box = driver.find_element_by_xpath('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input')

search_box.send_keys('greeksharifa.github.io')
search_box.send_keys(Keys.RETURN)

elements = driver.find_elements_by_xpath('//*[@id="rso"]/div[*]/div/div[1]/a/h3/span')

for element in elements:
    print(element.text)
    print(element.text, file=open('gorio.txt', 'w', encoding='utf-8'))

sleep(3)
driver.close()

구글 검색의 결과가 출력되면서 동시에 gorio.txt라는 파일이 생성되며 같은 내용이 출력되어 있음을 확인할 수 있다.


요소 찾기(Locating Elements)

Selenium은 다양한 요소(element)를 찾는 방법을 지원한다. HTML을 조금 다뤄봤다면, class, id, parent-child 관계 등등을 알고 있을 것이다. 모른다면… 크롤링 하기 전에 그 공부부터(?)

먼저 어떤 요소를 찾을지부터 정해야 한다. 그냥 크롬을 켜서, Ctrl + Shift + C를 눌러 원하는 요소를 클릭한다.

Examples

그러면 해당 요소의 정보를 볼 수 있을 것이다. 원하는 요소를 아래쪽 Elements 창에서 우클릭하여 내용을 복사하거나(Copy element 혹은 Copy XPath 등), 아니면 그냥 직접 보고 입력해도 된다.

Examples
<input class="gLFyf gsfi" maxlength="2048" name="q" type="text" 
jsaction="paste:puy29d" aria-autocomplete="both" aria-haspopup="false" 
autocapitalize="off" autocomplete="off" autocorrect="off" autofocus="" 
role="combobox" spellcheck="false" title="검색" value="" aria-label="검색" 
data-ved="0ahUKEwjsxYnKytzsAhUVQd4KHXjpCvsQ39UDCAQ">

각 요소에는 class나, XPath나, id 등의 여러 속성이 존재한다. 이 속성이나 경로를 갖고 요소를 찾을 수 있다.
위에서 예시로 든 것은 구글의 검색창을 나타낸다. 이 검색창은 id가 없고 class가 특징적인 것 같으니, class로 찾아보자.

class로 찾는 방법은 다음과 같다.

search_box = driver.find_element_by_class_name('gLFyf')
# 아래는 키보드 입력을 해 주는 코드이다. 나중에 설명하겠지만 한번 해 보자.
search_box.send_keys('gorio')

그러면 search_box라는 변수에는 구글의 검색창 요소가 담겨 있는 것이다. 선택한 요소에 키보드 입력을 보내거나, 클릭하는 등의 행동을 할 수 있다. 이건 조금 있다가 살펴보자.

class 말고도 선택하는 방법은 많다. 위에서 class_name로 끝나는 함수를 쓰는 대신, id, name, xpath, css_selector, 등으로 끝나는 함수를 사용할 수 있다.

Examples

총 18개의 함수를 지원한다. 9개의 쌍이 있는데, find_element로 시작하는 함수는 조건에 맞는 요소를 하나만 반환하고, find_elements로 시작하는 함수는 해당 조건을 만족하는 모든 요소를 반복가능한(iterable) 형태로 반환한다.

위에서 볼 수 있듯이 class나, css selector, id, name, tag_name, xpath, link_text, partial_link_text 등으로 선택 가능하다.

맨 위의 함수인 그냥 find_element 함수의 경우만 인자를 3개 받는다. self는 설명할 필요 없고, by는 조금 전 explicit_waits에서 보았던 그 selenium.webdriver.common.by이다. by도 CLASS_NAME 등으로 속성을 지정 가능하다.

  • ID = “id”
  • XPATH = “xpath”
  • LINK_TEXT = “link text”
  • PARTIAL_LINK_TEXT = “partial link text”
  • NAME = “name”
  • TAG_NAME = “tag name”
  • CLASS_NAME = “class name”
  • CSS_SELECTOR = “css selector”

link_text<a> tag의 링크를 대상으로 찾는다. 비슷하게 partial_link_text는 요소의 링크가 전달한 링크를 일부분으로 포함되어 있으면 해당 요소가 선택된다.

<html>
 <body>
  <p>Are you sure you want to do this?</p>
  <a href="continue.html">Continue</a>
  <a href="cancel.html">Cancel</a>
  <p class="content">Site content goes here.</p>
</body>
<html>

여기서 다음 코드로 찾을 수 있다.

continue_link = driver.find_element_by_link_text('Continue')
continue_link = driver.find_element_by_partial_link_text('Conti')

CSS Selector는 다음 코드를 쓰면 된다.

content = driver.find_element_by_css_selector('p.content')

여기서 find_element_by_xpath는 매우 강력한 찾기 기능을 제공한다. 말하자면 웹페이지 상에서 해당 요소의 전체경로(혹은 상대경로)를 갖고 찾기 기능을 수행할 수 있는데, 원하는 요소에서 Copy XPath만 한 다음 그대로 갖다 쓰면 해당 요소를 정확히 찾아준다.

search_box = driver.find_element_by_xpath('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input')

똑같은 효과를 갖는 코드는 다음과 같다.

from selenium.webdriver.common.by import By

search_box = driver.find_element(By.XPATH, '//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input')

XPath로 요소 찾기

표현식 설명
nodename nodename을 name으로 갖는 모든 요소 선택
/ root 요소에서 선택
// 현재 요소의 자손 요소를 선택
. 현재 요소를 선택
.. 현재 요소의 부모 요소를 선택
@ 속성(attibutes)를 선택
* 모든 요소에 매치됨
@* 모든 속성 요소에 매치됨
node() 모든 종류의 모든 요소에 매치됨
| OR 조건의 기능

예시는 다음과 같다.

표현식 설명
/div root 요소의 div 요소
./div 현재 요소의 자식 요소 중 div 요소
/* name에 상관없이 root 요소를 선택
./* 또는 * context 요소의 모든 자식 요소를 선택
//div 현재 웹페이지에서 모든 div 요소를 선택
.//div 현재 요소의 모든 자손 div 요소를 선택
//* 현재 웹페이지의 모든 요소를 선택
.//* 현재 요소의 모든 자손 요소를 선택
/div/p[0] root > div > p 요소 중 첫 번째 p 요소를 선택
/div/p[position()<3] root > div > p 요소 중 첫 두 p 요소를 선택
/div/p[last()] root > div > p 요소 중 마지막 p 요소를 선택
/bookstore/book[price>35.00] root > bookstore > book 요소 중 price 속성이 35.00 초과인 요소들을 선택
//*[@id="tsf"]/div[2]/ id가 tsf인 모든 요소의 자식 div 요소 중 3번째 요소를 선택
//title | //price title 또는 price 요소를 선택

텍스트 입력(키보드 입력)

어떤 요소를 find_element... 함수를 통해 선택했다고 하자.

search_box = driver.find_element_by_xpath('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input')

선택한 요소에 키보드 입력을 명령으로 주어 텍스트 입력 등을 수행할 수 있다.
키보드 입력은 send_keys(*value) 함수를 통해 할 수 있다.

search_box.send_keys('greeksharifa.github.io')

기본적으로 send_keys(*value) 함수는 문자열을 받는다. 그런데, enter 같은 특수 키 입력의 경우도 문자열로 처리할 수 있지만(RETURN = '\ue006'), 다음과 같이 입력할 수 있다.

from selenium.webdriver.common.keys import Keys

search_box.send_keys(Keys.RETURN)

경우에 따라 조금씩 다르긴 하지만, 일반적으로 enter 키의 경우 Keys.ENTER 또는 Keys.RETURN으로 입력할 수 있다.

아래는 입력할 수 있는 Keys의 목록이다.

class Keys(object):
    """
    Set of special keys codes.
    """

    NULL = '\ue000'
    CANCEL = '\ue001'  # ^break
    HELP = '\ue002'
    BACKSPACE = '\ue003'
    BACK_SPACE = BACKSPACE
    TAB = '\ue004'
    CLEAR = '\ue005'
    RETURN = '\ue006'
    ENTER = '\ue007'
    SHIFT = '\ue008'
    LEFT_SHIFT = SHIFT
    CONTROL = '\ue009'
    LEFT_CONTROL = CONTROL
    ALT = '\ue00a'
    LEFT_ALT = ALT
    PAUSE = '\ue00b'
    ESCAPE = '\ue00c'
    SPACE = '\ue00d'
    PAGE_UP = '\ue00e'
    PAGE_DOWN = '\ue00f'
    END = '\ue010'
    HOME = '\ue011'
    LEFT = '\ue012'
    ARROW_LEFT = LEFT
    UP = '\ue013'
    ARROW_UP = UP
    RIGHT = '\ue014'
    ARROW_RIGHT = RIGHT
    DOWN = '\ue015'
    ARROW_DOWN = DOWN
    INSERT = '\ue016'
    DELETE = '\ue017'
    SEMICOLON = '\ue018'
    EQUALS = '\ue019'

    NUMPAD0 = '\ue01a'  # number pad keys
    NUMPAD1 = '\ue01b'
    NUMPAD2 = '\ue01c'
    NUMPAD3 = '\ue01d'
    NUMPAD4 = '\ue01e'
    NUMPAD5 = '\ue01f'
    NUMPAD6 = '\ue020'
    NUMPAD7 = '\ue021'
    NUMPAD8 = '\ue022'
    NUMPAD9 = '\ue023'
    MULTIPLY = '\ue024'
    ADD = '\ue025'
    SEPARATOR = '\ue026'
    SUBTRACT = '\ue027'
    DECIMAL = '\ue028'
    DIVIDE = '\ue029'

    F1 = '\ue031'  # function  keys
    F2 = '\ue032'
    F3 = '\ue033'
    F4 = '\ue034'
    F5 = '\ue035'
    F6 = '\ue036'
    F7 = '\ue037'
    F8 = '\ue038'
    F9 = '\ue039'
    F10 = '\ue03a'
    F11 = '\ue03b'
    F12 = '\ue03c'

    META = '\ue03d'
    COMMAND = '\ue03d'

텍스트 입력 지우기

위에 나와 있는 것처럼, 입력한 텍스트를 지우는 방법은 Keys.BACKSPACE 또는 Keys.BACK_SPACE를 사용하는 것이다.

만약 전체를 지우고 싶다면 Keys가 아니라, 선택한 요소에서 clear() 함수를 호출하면 된다.

search_box.clear()

파일 업로드

파일을 받는 <input>을 선택한 뒤, send_keys(file_path)를 호출하면 된다.

upload = driver.find_element_by_tag('input')
upload.send_keys(file_path)

상호작용

클릭하기(click)

클릭은 어렵지 않다. find_element 함수로 요소를 선택한 다음에, click() 함수를 호출하면 끝이다.

search_box.send_keys(Keys.RETURN)까지 입력했다면, 검색창은 다음과 같다.

Examples

이제 첫 번째 검색 결과를 클릭해 보자. Ctrl + Shift + C을 누른 뒤 제목 부분을 클릭하고, 위에서 설명한 Copy XPath를 이용하여 XPath를 얻자. XPath는 다음과 같다.

//*[@id="rso"]/div[1]/div/div[1]/a/h3/span

다음 코드를 써 보자.

posting = driver.find_element_by_xpath('//*[@id="rso"]/div[1]/div/div[1]/a/h3/span')
posting.click()

실행하면 첫 번째 검색 결과가 클릭되어 다음과 같은 화면을 볼 수 있을 것이다.

Examples

옵션 선택 및 제출(submit)

XPath 등으로 select 요소를 선택한 다음에 각 옵션을 선택할 수 있지만, 그것보다 더 좋은 방법이 있다.

from selenium.webdriver.support.ui import Select

select = Select(driver.find_element_by_name('select_name'))

select.select_by_index(index=2)
select.select_by_visible_text(text="option_text")
select.select_by_value(value='고리오')

selenium.webdriver.support.ui.Selectselect 요소를 선택하여 쉽게 다룰 수 있도록 한다.
위 코드에서 볼 수 있듯이 select 내에서 인덱스로 선택하거나, 옵션의 텍스트, 혹은 어떤 값을 통해 선택이 가능하다.

특정 선택을 해제하려면 다음 코드를 사용한다.


select.deselect_by_index(index=2)
select.deselect_by_visible_text(text="option_text")
select.deselect_by_value(value='고리오')

# 전부 해제
select.deselect_all()

선택된 옵션 리스트를 얻으려면 select.all_selected_options으로 얻을 수 있고, 첫 번째 선택된 옵션은 select.first_selected_option, 가능한 옵션을 모두 보려면 select.options를 사용하면 된다.

제출(submit)하려면 요소를 찾은 뒤 click()을 수행해도 되지만, 다음과 같이 써도 된다.

submit_btn.submit()

만약 선택한 요소가 form 형식이 아니라면 NoSuchElementException 오류를 볼 수 있을 것이다.

Drag and Drop

어떤 일련의 동작을 수행하기 위해서는 ActionChains를 사용하면 된다. source 요소에서 target 요소로 Drag & Drop을 수행한다고 하자.

from selenium.webdriver import ActionChains

action_chains = ActionChains(driver)
action_chains.drag_and_drop(source, target).perform()

Window / Frame 이동

최신 웹 페이지에서는 frame 같은 것을 잘 사용하지 않지만, 예전에 만들어진 사이트라면 frame을 사용한 경우가 있다.
이렇게 frame 안에 들어 있는 요소는 find_element 함수를 써도 그냥 찾아지지 않는다. find_element 함수는 frame 내에 있는 요소를 찾아주지 못한다.

그래서 특정 frame으로 이동해야 할 때가 있다.

driver.switch_to_frame("frameName")
driver.switch_to_window("windowName")

# frame 내 subframe으로도 접근이 가능하다. 점(.)을 쓰자.
driver.switch_to_frame("frameName.0.child")

windowName을 알고 싶다면 다음과 같은 링크가 있는지 살펴보자.

<a href="somewhere.html" target="windowName">Click here to open a new window</a>

혹은 webdriver는 window 목록에 접근할 수 있기 때문에, 다음과 같이 모든 window를 순회하는 것도 가능하다.

for handle in driver.window_handles:
    driver.switch_to_window(handle)

frame 밖으로 나가려면 다음과 같이 쓰면 기본 frame으로 되돌아간다.

driver.switch_to_default_content()

경고창으로 이동할 수도 있다.

alert = driver.switch_to.alert

경고창은 여기서 다룬다.


JavaScript 코드 실행

driver.execute_script() 함수를 실행할 수 있다.

아래는 Name이 search_box인 요소의 값을 query의 값으로 변경하는 코드이다.

driver.execute_script("document.getElementsByName('id')[0].value=\'"+query+"\'")

브라우저 창 다루기

뒤로가기, 앞으로 가기

브라우저는 뒤로가기(back)와 앞으로 가기(forward) 기능을 제공한다. 이를 selenium으로 구현이 가능하다.

driver.forward()
driver.back()

아주 간단하다.

화면 이동(맨 밑으로 내려가기 등)

크롤링을 하다 보면 화면의 끝으로 내려가야 내용이 동적으로 추가되는 경우를 자주 볼 수 있다.
이런 경우에는 웹페이지의 최하단으로 내려가는 코드를 실행할 필요가 있다.

driver.execute_script('window.scrollTo(0, document.body.scrollHeight);')

물론 전체를 내려가야 할 필요가 없다면 document.body.scrollHeight) 대신 지정된 값만큼 이동해도 된다.

특정 요소까지 계속 찾으려면 ActionChain을 써도 된다.

from selenium.webdriver import ActionChains

some_tag = driver.find_element_by_id('gorio')

ActionChains(driver).move_to_element(some_tag).perform()

브라우저 최소화/최대화

driver.minimize_window()
driver.maximize_window()

Headless 설정

말하자면 브라우저 창을 띄우지 않고 수행하는 방법이다.

여기를 참조한다.

브라우저 크기 설정

여기를 참조한다.

스크린샷 저장

driver.save_screenshot('screenshot.png')

Option(ChromeOption)

여러 옵션을 설정할 수 있다. 브라우저의 창 크기, 해당 기기의 정보 등을 설정할 수 있다.

기본적인 사용법은 다음과 같다. 브라우저가 실행될 때 창 크기를 설정할 수 있다.

options = webdriver.ChromeOptions()
options.add_argument('window-size=1920,1080')

driver = webdriver.Chrome('chromedriver', options=options)

다른 기능들은 여기에 적어 두었다. 코드를 보면 역할을 짐작할 수 있을 것이다.

options.add_argument('headless')
options.add_argument('window-size=1920x1080')
options.add_argument('disable-gpu')

options.add_argument('start-maximized')
options.add_argument('disable-infobars')
options.add_argument('--disable-extensions')

options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)
options.add_argument('--disable-blink-features=AutomationControlled')

options.add_experimental_option('debuggerAddress', '127.0.0.1:9222')

ActionChains (마우스, 키보드 입력 등 연속 동작 실행)

from selenium.webdriver import ActionChains

menu = driver.find_element_by_css_selector('.nav')
hidden_submenu = driver.find_element_by_css_selector('.nav #submenu1')

ActionChains(driver).move_to_element(menu).click(hidden_submenu).perform()

# 위 한 줄은 아래와 같은 동작을 수행한다.
actions = ActionChains(driver)
actions.move_to_element(menu)
actions.click(hidden_submenu)
actions.perform()

마우스 클릭, Drag & Drop, 키보드 입력 등을 연속적으로 수행할 수 있다.

  • on_element 인자를 받는 함수는, 해당 인자가 주어지지 않으면 현재 마우스 위치를 기준으로 한다.
  • element 인자를 받는 함수는, 해당 인자가 주어지지 않으면 현재 선택이 되어 있는 요소를 기준으로 한다.
  • key_down, key_up 함수는 Ctrl 등의 키를 누를 때 쓰면 된다.
# Ctrl + C를 누른다.
ActionChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
동작 수행 함수 설명
click(on_element=None) 인자로 주어진 요소를 왼쪽 클릭한다.
click_and_hold(on_element=None) 인자로 주어진 요소를 왼쪽 클릭하고 누르고 있는다.
release(on_element=None) 마우스를 주어진 요소에서 뗀다.
context_click(on_element=None) 인자로 주어진 요소를 오른쪽 클릭한다.
double_click(on_element=None) 인자로 주어진 요소를 왼쪽 더블클릭한다.
drag_and_drop(source, target) source 요소에서 마우스 왼쪽 클릭하여 계속 누른 채로 target까지 이동한 뒤 마우스를 놓는다.
drag_and_drop_by_offset(source, xoffset, yoffset) 위와 비슷하지만 offset만큼 이동하여 마우스를 놓는다.
key_down(value, element=None) value로 주어진 키를 누르고 떼지 않는다.
key_up(value, element=None) value로 주어진 키를 뗀다.
move_to_element(to_element) 마우스를 해당 요소의 중간 위치로 이동한다.
move_to_element_with_offset(to_element, xoffset, yoffset) 마우스를 해당 요소에서 offset만큼 이동한 위치로 이동한다.
pause(seconds) 주어진 시간(초 단위)만큼 입력을 중지한다.
perform() 이미 쌓여 있는(stored) 모든 행동을 수행한다(chaining).
reset_actions() 이미 쌓여 있는(stored) 모든 행동을 제거한다.
send_keys(*keys_to_send) 키보드 입력을 현재 focused된 요소에 보낸다.
send_keys_to_element(element, *keys_to_send) 키보드 입력을 주어진 요소에 보낸다.

경고 창 다루기(alerts)

브라우저를 사용하다보면 상단에 경고창이 뜰 때가 있다. (확인/취소 등)
이 경고창을 무시하는 등의 처리를 할 수 있는 기능을 제공한다.

아래 코드는 경고창에서 수락/거절을 누르거나, 경고창의 내용을 출력, 혹은 경고창에 특정 키 입력을 보낼 수 있다.

from selenium.webdriver.common.alert import Alert

Alert(driver).accept()
Alert(driver).dismiss()

print(Alert(driver).text)
Alert(driver).send_keys(keysToSend=Keys.ESCAPE)

각각의 코드는 알아보기 어렵지 않을 것이다.


기타 기능

  • Touch Actions: 마우스/키보드 입력과 비슷하게 chaining이 가능하다. 터치와 관련한 여러 기능을 제공한다. selenium.webdriver.common.touch_actions.TouchActions
  • Proxy: Proxy 기능을 사용할 수 있다. selenium.webdriver.common.proxy.Proxy
  • 쿠키(Cookies): 쿠키를 추가하거나 가져올 수 있다.
# Go to the correct domain
driver.get('http://www.example.com')

# Now set the cookie. This one's valid for the entire domain
cookie = {name : foo, value : bar}
driver.add_cookie(cookie)

# And now output all the available cookies for the current URL
driver.get_cookies()

References

Comment  Read more

Adversarial AutoEncoder (AAE) 설명

|

본 글에서는 VAEGAN을 결합한 Adversarial Autoencoder (이하 AAE)에 대한 논문을 리뷰하면서 이론에 대해 설명하고 이를 Tensorflow로 구현하는 과정을 보여줄 것이다. 이 알고리즘을 이해하기 위해서는 앞서 언급한 2가지 알고리즘에 대해 숙지하고 있어야 하며, VAE에 대해 알고 싶다면 이 글을, GAN에 대해 알고 싶다면 이 글을 참조하길 바란다.


1. Adversarial Autoencoders Paper Review

1.1. Introduction

오디오, 이미지, 영상 등과 같은 rich distribution을 포착하기 위해 Scalable한 생성 모델을 구성하는 것은 머신러닝에서도 굉장히 중요하고도 어려운 문제로 여겨진다. RBM, DBNs, DBM 등은 MCMC 기반의 알고리즘으로 학습되었다. 이러한 방법론들은 학습이 진행될수록 부정확하게 Log Likelihood의 Gradient를 포착하는 경향을 보였다. 왜냐하면 Markov Chain에서 온 Sample들은 여러 Mode를 빠르게 혼합하지 못하기 때문이다.

최근에는 이 대신 Direct Back-propagation을 이용하여 이 같은 단점을 극복한 VAE, GAN과 같은 알고리즘이 제안되었다.

본 논문에서는 autoencoder를 생성 모델로 변환하는 AAE라는 알고리즘을 제안할 것이다. 우리의 모델에서 이 autoencoder는 autoencoder의 Latent Representation의 Aggregated PosteriorArbitrary Prior에 연결하는 2개의 목적함수 (Traditional Reconstruction Error Criterion, Adversarial Training Criterion)로 학습을 진행할 것이다. 본 논문에서는 이러한 학습 방법이 VAE의 학습과 강력한 연관성을 보여준다는 것을 보여줄 것이다. Encoder가 데이터 분포를 Prior 분포로 변환하는 방법에 대해 학습하고 Decoder는 Imposed Prior를 데이터 분포에 매핑하는 Deep 생성 모델을 학습하게 된다.

1.1.1. Generative Adversarial Networks

GAN은 생성 모델 G와 판별 모델 D라는 2개의 신경망 사이의 Min-Max 적대적 게임을 구축하는 프레임워크이다. 판별 모델 $D(x)$ 는 데이터 공간의 포인트 $\mathbf{x}$ 가 실제 데이터 분포 (Positive Samples) 로 부터 추출되었는지를 계산하는 신경망이다. 생성자는 $G(\mathbf{z})$ 라는 함수를 사용하게 되는데, 이 함수는 Prior $p(\mathbf{z})$ 로부터 추출된 Sample $\mathbf{z}$ 를 데이터 공간에 연결시키는 역할을 한다. $G(\mathbf{z})$ 는 최대한 판별 모델로 하여금 Sample이 실제 데이터 분포로부터 추출되었다고 믿게 만드는, 속이는 역할을 하게 된다. 이 생성자는 x에 대하여 $D(x)$ 의 Gradient를 레버리지하여 학습된다. 그리고 이를 활용하여 파라미터를 조정한다. 이 게임의 해는 아래와 같이 표현할 수 있다.

[\underset{G}{min} \underset{D}{max} E_{\mathbf{x} \sim p_{data}} [logD(\mathbf{x})] + E_{\mathbf{z} \sim p(\mathbf{z})} [log(1 - D(G(\mathbf{z})))]]

alternating SGD를 이용하여 2단계로 학습이 진행된다. 먼저 판별자가 생성자로부터 생성된 가짜 Sample로부터 진짜 Sample을 구별하는 방법을 학습하고, 생성자는 이후 생성된 Sample을 통해 판별자를 속이는 방법을 학습한다.


1.2. Adversarial Autoencoders

잠시 기호에 대해 살펴보자.

[p(\mathbf{z}), q(\mathbf{z} \mathbf{x}), p(\mathbf{x} \mathbf{z})]

위 기호는 차례대로 1) Code에 투사하고 싶은 Prior 분포, 2) Encoding 분포, 3) Decoding 분포를 의미한다.

[p_d (\mathbf{x}), p(\mathbf{x})]

위 기호는 차례대로 4) 실제 데이터 분포, 5) Model 분포를 의미한다. Encoding 함수는 autoencoder의 (잠재 표현) Hidden Code 벡터에 대한 Aggregated Posterior 분포를 아래와 같이 정의한다.

[q(\mathbf{z}) = \int_{\mathbf{x}} q(\mathbf{z} \mathbf{x}) p_d (\mathbf{x}) d\mathbf{x}]

Adversarial AutoencoderAggregated Posterior인 $q(\mathbf{z})$ 를 Arbitrary Prior인 $p(\mathbf{z})$ 와 매칭시킴으로써 regualarized 된다. 그렇게 하기 위해서 이 적대적 네트워크는 아래 그림과 같이 autoencoder의 Hidden Code 벡터의 상위에 추가된다.

autoencoder는 그동안 Reconstruction Error를 최소화한다. 적대적 네트워크의 생성자는 사실 autoencoder의 encoder이다.

[q(\mathbf{z} \mathbf{x})]

Encoder는 Hidden Code인 $q(\mathbf{z})$ 가 실제 Prior 분포 $p(\mathbf{z})$ 로부터 왔다고 판별 모델을 착각하게 만들어 Aggregated Posterior 분포가 판별 모델을 속이도록 만든다.

적대적 네트워크와 autoencoder 모두 2단계를 통해 SGD로 결합하여 학습된다. (Reconstruction 단계 Regularization 단계) Reconstruction 단계에서 autoencoder는 Encoder와 Decoder가 Input에 대한 Reconstruction Error를 최소화하도록 업데이트하게 된다. Regularization 단계에서 적대적 네트워크는 먼저 판별 모델이 진짜 Sample을 구별하도록 업데이트한 후, 생성자가 판별 네트워크를 속이도록 업데이트를 진행한다.

이러한 학습과정이 끝나면, autoencoder의 Decoder는 투사된 Prior인 $p(\mathbf{z})$ 를 실제 데이터 분포에 매핑하는 생성 모델을 정의하게 된다.

AAE의 Encoder를 정의하는 방법에는 다음과 같이 3가지가 존재한다.

[Encoder: q(\mathbf{z} \mathbf{x})]

1) Deterministic
Encoder가 $\mathbf{x}$ 의 deterministic 함수라고 가정해보자. 그렇다면 이 때의 Encoder는 가장 기본적인 형태의 autoencoder의 Encoder과 유사할 것이고 $q(\mathbf{z})$ 내부의 Stochasticity는 오직 실제 데이터 분포 $p_d (\mathbf{x})$ 에서만 찾을 수 있게 된다.

2) Gaussian Posterior
Encoder가 Encoder 네트워크에 의해 예측된 평균과 분산을 따르는 정규 분포라고 가정해보자.

[z_i \sim \mathcal{N} (\mu_i(\mathbf{x}), \sigma_i(\mathbf{x}))]

이 때 $q(\mathbf{z})$ 의 Stochasticity는 실제 데이터 분포와 Encoder의 결과의 정규 분포의 Randomness 모두에서 나온다. VAE에서 보았듯이 Encoder 네트워크의 Back-propagation은 Reparametrization Trick을 통해 이루어진다.

3) Universal Approximator Posterior
AAE의 Encoder네트워크가 정규 분포와 같은 고정된 분포에서 추출한 Random Noise $\eta$ 와 Input $\mathbf{x}$ 의 함수 $f(\mathbf{x}, \eta)$ 라고 해보자. 우리는 여러 $\eta$ 를 Sampling 한 뒤 이에 따른 $f(\mathbf{x}, \eta)$ 를 평가하여 아래와 같은 (Encoder) 임의의 사후 분포를 추출할 수 있다.

[q(\mathbf{z x})]

우리는 Aggregated Posterior인 $q(\mathbf{z})$ 를 아래와 같이 표현할 수 있을 것이다.

[q(\mathbf{z x}) = \int_{\eta} q(\mathbf{z x}, \eta) p_{\eta} (\eta) d \eta]
[\rightarrow q(\mathbf{z}) = \int_{\mathbf{x}} \int_{\eta} q(\mathbf{z x}, \eta) p_d(\mathbf{x}) p_{\eta} p_{\eta}(\eta) d\eta d\mathbf{x}]
[q(\mathbf{z x})]

이 때 위와 같은 Posterior는 더 이상 Gaussian일 필요가 없고, Encoder는 Input $\mathbf{x}$ 가 주어졌을 때 어떠한 임의의 사후 분포도 학습할 수 있다. Aggregated Posterior $q(\mathbf{z})$ 로부터 Sampling을 하는 효과적인 방법이 존재하기 때문에, 적대적 학습 과정은 Encoder 네트워크 $f(\mathbf{x}, \eta)$ 를 통한 직접적인 Back-propagation으로 $q(\mathbf{z})$ 를 $p(\mathbf{x})$ 에 연결할 수 있다.

지금까지 확인한 것처럼, 위 Posterior를 여러 다른 종류로 선택하게 되면 이는 또 다른 학습 Dymamics를 가진 다른 종류의 모델로 귀결된다. 예를 들어 1) Deterministic Case에서 네트워크는 데이터 분포로부터 Stochasticity를 뽑아내서 $q(\mathbf{z})$ 를 $p(\mathbf{x})$ 와 매칭시켜야 한다. 그러나 데이터의 경험적 분포가 학습 데이터 셋에서 고정되어 있기 때문에 Mapping이 Deterministic하면 부드럽지 못한 $q(\mathbf{z})$ 를 만들지도 모른다.

하지만 나머지 2개의 케이스에서는 네트워크가 Stochasticity의 추가적인 원천에 접근할 수 있기 때문에 이는 $q(\mathbf{z})$ 를 부드럽게 만들어 Adversarial Regularization 단계에서 개선이 이루어진다. 그럼에도 불구하고, 굉장히 광범위한 Hyper-parameter Search를 통해서 우리는 Posterior의 각 경우에 맞는 유사한 Test-Likelihood를 얻을 수 있었고 따라서 지금부터 본 논문에서는 Posterior의 Deterministic한 버전을 통해서 논의를 전개해 나가도록 할 것이다.

1.2.1. Relationship to Variational Autoencoders

VAE에서는 KL divergence 페널티를 사용하여 autoencoder의 Hidden Code 벡터Prior 분포를 투사하지만, Hidden Code의 Aggregated PosteriorPrior 분포에 매칭시키는 적대적 학습 과정을 사용할 것이다. VAE는 아래와 같이 $\mathbf{x}$ 의 Negative Log-Likelihood에 대한 상한선을 최소화한다.

[E_{\mathbf{x} \sim p_d(\mathbf{x})} [-logp(\mathbf{x})] < E_{\mathbf{x}} [-log(p(\mathbf{x z}))] + E_{\mathbf{x}} [KL(q(\mathbf{z x})   p(\mathbf{z}))]]
[= E_{\mathbf{x}} [-log(p(\mathbf{x z}))] - E_{\mathbf{x}} [H(q(\mathbf{z x}))] + E_{q(\mathbf{z})} [-logp(\mathbf{z})]]
[= E_{\mathbf{x}} [-log(p(\mathbf{x z}))] - E_{\mathbf{x}}[\Sigma_i log \sigma_i (\mathbf{x})] + E_{q(\mathbf{z})} [-logp(\mathbf{z})] + Const]

[= Reconstruction - Entropy + CrossEntropy(q(\mathbf{z}), p(\mathbf{z}))]

첫 번째 항은 Reconstruction 항으로, 나머지 항은 Regularization 항으로 생각할 수 있다. 만약 Regularization 항이 없다면 모델은 단순히 Input을 재현하는 기본적인 autoencoder의 형태를 취할 것이다. 두 번째 항이 사후 분포의 분산을 크게 만든다면, 세 번째 항은 Aggregated Posterior $q(\mathbf{z})$ 와 Prior $p(\mathbf{z})$ 사이의 Cross-Entropy를 최소화한다.

위 목적함수에서 KL divergence 또는 Cross-Entropy 항은 $q(\mathbf{z})$ 가 $p(\mathbf{z})$ 의 Modes를 고르도록 만든다. AAE에서 우리는 두 번째 두 항을 $q(\mathbf{z})$ 가 $p(\mathbf{z})$ 의 전체 분포와 매칭되게 하는 적대적 학습 과정으로 대체하였다.

본 섹션에서 우리는 구체적인 Prior 분포 $p(\mathbf{z})$ 를 Coding 분포에 투사하는 능력에 대해 AAEVAE를 비교해볼 것이다.

위 그림에서 E부분은 MNIST 숫자 데이터 셋을 학습한 AAE로부터 테스트 데이터의 Coding Space $\mathbf{z}$ 를 보여주며, 이 때 구형의 2차원 Gaussian Prior 분포가 Hidden Codes $\mathbf{z}$ 에 투사되었다.

A부분을 보면, 학습된 Manifold는 날카로운 변화를 보이는데 이는 Coding Space가 채워져 있고 ‘구멍’은 존재하지 않음을 의미한다. 실제로 Coding Space의 날카로운 변화는 $\mathbf{z}$ 내부에서 덧붙여져 생성된 이미지들이 데이터 Manifold 위에 있음을 의미한다.

반대로 C부분을 보면, VAE의 Coding Space는 AAE의 그것과 같은 구조를 보인다는 것을 확인할 수 있다. 이 경우 VAE가 대체로 2차원의 정규 분포의 형태와 일치한다는 것을 알 수 있는데, 그러나 어떠한 데이터 포인트도 Coding Space의 일부 Local Region에 매핑되지 않은 것을 볼 때 VAEAAE 만큼 데이터 Manifold를 잘 포착하지 못했다는 것을 알 수 있다.

B, D 부분을 보면 AAEVAE의 Coding Space의 투사된 분포가 10개의 2차원 Gaussian의 혼합임을 확인할 수 있다. AAE는 성공적으로 Aggregated PosteriorPrior 분포에 매칭시켰다. 반대로 VAE는 10개의 Gaussian 혼합과는 구조적으로 다른 결과를 보여주는데, 이는 VAE가 앞서 언급하였듯이 분포의 Modes를 매칭시키는 데 집중하기 때문인 것으로 파악된다.

VAE에서 Monte-Carlo Sampling로 KL divergence를 Back-propagate하기 위해서는 Prior 분포의 정확한 함수 형태를 알고 있어야 한다. 그러나 AAE에서는 $q{\mathbf{z}}$ 를 $p(\mathbf{z})$ 에 매칭시키기 위해 Prior 분포로부터 Sampling만 할 수 있으면 된다는 점이 VAEAAE의 큰 차이이다.

1.2.3 절에서 우리는 AAE가 분포의 정확한 함수 형태를 알지 못한다 하더라도 (Swiss Roll 분포와 같이) 복잡한 분포를 투사할 수 있음을 증명할 것이다.

1.2.2. Relationship to GANs and GMMNs

(생략)

1.2.3. Incorporating Label Information in the Adversarial Regularization

데이터에 Label이 존재하는 경우 우리는 학습 과정에서 이 정보를 활영하여 더 나은 형태의 Hidden Code의 분포를 얻을 수 있다. 본 섹션에서 우리는 autoencoder의 잠재 표현을 규제하기 위해 부분적 또는 완전한 Label 정보를 활용하는 방법에 대해 설명할 것이다. 이러한 구조를 살펴보기 위해 1.2.1 절에서 확인하였던 B그림을 참조해보자. 이 때 AAE는 10개의 2차원 Gaussian의 혼합에 적합한 것으로 보인다. 지금부터 우리는 이 정규분포의 혼합의 각 Mode가 MNIST의 1개의 Label을 표현한다는 것을 보이도록 할 것이다.

위 그림은 준지도 학습에 관한 학습 과정을 보여준다. 판별 네트워크의 Input에 One-Hot 벡터가 추가되어 분포의 Mode에 Label이 개입하도록 하였다. 이 One-Hot 벡터는 Class Label이 주어졌을 때 판별 네트워크의 적절한 결정 범위를 선택하는 스위치 역할을 하게 된다.

만약 Label이 존재하지 않을 경우 이 One-Hot 벡터는 다른 Class를 갖게 된다. 예를 들어, 10개의 2차원 Gaussian 혼합을 투사하는 경우, One-Hot 벡터는 11개의 Class를 갖게 될 것이다.

첫 10개의 Class의 각각은 적절한 개별 Mixture Component를 위한 결정 범위를 선택하게 된다. 모델에 Label이 없는 데이터 포인트가 주어졌을 때, Extra Class가 등장하여 정규 분포의 Full Mixture를 위한 결정 범위를 선택하게 된다.

AAE 학습의 Positive 단계에서 우리는 One-Hot 벡터를 통해 판별자에게 Mixture Component의 Label에 대한 정보를 제공한다. Label이 없는 경우를 위한 Positive Sample은 특정 Class가 아닌 Gaussian의 Full Mixture로부터 오게 된다. Negative 단계에서 우리는 One-Hot 벡터를 통해 판별자에게 학습 이미지의 Label을 제공한다.

위 그림의 A 부분을 보면, 10K labeled MNIST 예시와 40K unlabeled MNIST 예시로 학습되었고, 10개의 2차원 Gaussian의 혼합 Prior로 학습된 AAE의 잠재 표현을 보여주고 있다. 이경우 Prior의 i번째 Mixture Component는 준지도 방식으로 i번째 Class에 할당되어 있다.

B 부분을 보면, 첫 번째 3개의 Mixture Component의 Manifold를 확인할 수 있다. Class와 독립적으로 각 Mixture Component 안에 Style Representation이 일관적으로 표현되어 있음을 알 수 있다. 예를 들어 B 부분의 좌상단 부분을 보면 Upright 필기 스타일로, 우하단을 보면 기울어진 스타일로 표현되어 있음을 알 수 있다.

이 방법은 Parametric 형식 없이고 임의의 표현으로 확장될 수 있다. C 부분은 Coding Space $\mathbf{z}$ 를 묘사하고 있고, D 부분은 잠재 공간의 Swiss Roll 축을 따라 생성된 이미지를 강조하고 있다.


1.3. Likelihood Analysis of Adversarial Autoencoders

이전 섹션에서 소개되었던 실험은 오직 질적인 결과만을 보여주었다. 이번 Chapter에서는 MNIST와 Toronto Face 데이터셋에 기반하여 hold-out 이미지를 생하는 모델의 Likelihood를 비교하여 데이터 분포를 포착하는 생성 모델로서 AAE의 성능을 평가해볼 것이다.

우리는 MNIST와 TFD에 AAE를 학습시켰는데, 이 때 모델은 근본적인 Hidden Code에 고차원적 정규 분포를 투사한다. 아래 그림은 데이터셋의 일부를 가져온 것이다.

AAE의 성능을 평가하기 위해 hold-out 테스트셋을 대상으로 Log Likelihood를 계산하였다. 사실 Likelihood를 사용하여 평가를 하는 방식은 그리 직관적이지는 못한데, 왜냐하면 사실 이미지의 확률을 직접적으로 계산한다는 것은 불가능하기 때문이다. 따라서 우리는 논문1, 논문2, 논문3 에 제시되었던 것처럼 True Log Likelihood의 Lower Bound를 계산하였다.

우리는 모델이 생성한 10,000개의 Sample에 대해 Gaussian Parzen Window (Kernel Density Estimator)를 계산하였고, 이 분포 하에서 테스트 데이터의 Likelihood를 계산하였다. Parzen Window의 자유 파라미터인 $\sigma$ 는 CV를 통해 선택되었다. 다음은 테스트 결과이다.

(중략)

1.4. Supervised Adversarial Autoencoders

준지도 학습은 머신러닝에서 오래된 개념적인 문제이다. 최근에 생성 모델들은 준지도 학습에 있어서 유명한 방법론들이 외덨는데, 왜냐하면 이러한 모델들은 원칙에 의거한 방법으로 Variation의 많은 다른 잠재 요인들로부터 Class Label 정보를 구분해낼 수 있었기 때문이다.

이번 Chapter에서 우리는 먼저 완전한 지도학습 시나리오에 초점을 맞추고 이미지의 Style 정보로부터 Class Label 정보를 분리해내는 AAE의 구조에 대해 논할 것이다. 이후 우리는 이러한 구조를 1.5 절에서 준지도 학습 세팅으로 확장해볼 것이다.

우리는 Label 정보를 통합하는 측면에서, Label의 One-Hot 벡터 인코딩을 Decoder에 제공하기 위해 네트워크 구조를 변경하였다. Decoder는 Label을 식별하는 One-Hot 벡터와 이미지를 재구성하는 Hidden Code $\mathbf{z}$ 를 사용한다. 이러한 구조는 네트워크가 Hidden Code $\mathbf{z}$ 에 있는 Label과 독립적인 모든 정보를 유지하도록 만든다.

아래 그림은 Hidden Code가 15차원 Gaussian으로 설정된 MNIST 데이터셋에 학습된 네트워크의 결과를 보여준 것이다.

위 그림의 각 행은 Hidden Code $\mathbf{z}$ 는 특정한 값으로 고정되어 있지만 Label은 구조적으로 탐색되었을 때 재구성된 이미지를 나타낸다. 행 속에서 재구성된 이미지들은 상당히 Style 측면에서 일관적인 것을 알 수 있다.

이 실험에서 One-Hot 벡터는 이미지의 central 숫자와 관련있는 Label을 의미한다. 각 행의 Style 정보는 제일 왼쪽과 제일 오른쪽의 숫자의 Label에 대한 정보를 담고 있는데, 왜냐하면 양 끝의 숫자들은 One-Hot 인코딩 속에서 Label 정보가 주어지지 않았기 때문이다.

1.5. Semi-Supervised Adversarial Autoencoders

이전 Chapter에 이어 이제 AAE를 준지도학습에 사용해 볼 것이다. 이 때 준지도학습은 오직 labeled된 데이터를 사용하여 얻어진 분류 성능을 개선하기 위해 unlabeled된 데이터의 generative description을 이용하는 것을 말한다. 구체적으로, 우리는 범주형 분포를 갖는 잠재 class 변수 $\mathbf{y}$ 와 정규 분포를 갖는 연속형 잠재 변수 $\mathbf{z}$ 로부터 데이터가 생성되었다고 가정할 것이다.

[p(\mathbf{y}) = Cat(\mathbf{y}), p(\mathbf{z}) = \mathcal{N} (\mathbf{z 0, I})]
[Encoder: q(\mathbf{z, y x})]

네트워크 구조를 변경하여 AAE의 추론 네트워크가 위와 같은 Encoder를 사용하여 범주형 Class 변수 $\mathbf{y}$ 와 연속형 잠재 변수 $\mathbf{z}$ 를 모두 예측하도록 해보자. 구조는 아래 그림과 같다.

Decoder는 Class Label을 One-Hot 벡터로 활용하고 연속형 Hidden Code $\mathbf{z}$ 를 활용하여 이미지를 재구성한다. 첫 번째 적대적 네트워크는 범주형 분포를 Label Representation에 투사한다.

이 적대적 네트워크는 잠재 Class 변수 $\mathbf{y}$ 가 어떠한 Style 정보도 갖고 있지 않고 $\mathbf{y}$ 의 Aggregated Posterior 분포가 범주형 변수와 일치한다는 것을 보장한다. 두 번째 적대적 네트워크는 정규 분포를 Style Representation에 투사하여 잠재 변수 $\mathbf{z}$ 가 연속형 정규 변수라는 것을 보장한다.

적대적 네트워크와 autoencoder 모두 3가지 단계를 통해 학습된다. 3단계는 Reconstruction, Regularization, Semi-supervised Classification 단계를 의미한다.

Reconstruction 단계에서, autoencoder는 unlabeled 미니배치에 대해 Input의 Reconstruction Error를 최소화하기 위해 Encoder와 Decoder를 업데이트한다.

Regularization 단계에서 각 적대적 네트워크는 먼저 범주형 및 정규 Prior를 이용하여 생성된 진짜 Sample과 autoencoder에 의해 계산된 Hidden Code인 생성된 가짜 Sample를 구별하기 위해 판별 네트워크를 업데이트한다. 이후 적대적 네트워크는 판별 네트워크를 혼란시키기 위해 생성자를 업데이트한다.

[q(\mathbf{y x})]

마지막으로 Semi-supervised Classification 단계에서 autoencoder는 Labeled 미니배치에 대한 Cross-Entropy Cost를 최소화하기 위해 위 함수를 업데이트한다.

아래 표는 테스트 결과이다.

1.6. Unsupervised Clustering with Adversarial Autoencoders

어떠한 지도 없이 unlabeled 데이터로부터 강력한 표현을 학습할 수 있는 방법은 없을까? 이번 Chapter에서는 AAE가 오직 비지도 방법으로 연속형의 잠재 Style 변수로부터 이산적인 Class 변수를 구분해낼 수 있다는 것을 보여줄 것이다.

네트워크 구조는 사실 이전 Chapter에서 보았던 것과 유사하다. 다만, 이 때 준지도 분류 단계를 제거하였기 때문에 labeled 미니배치에 대해 네트워크를 학습하는 과정은 존재하지 않는다.

[q(\mathbf{y x})]

또 다른 점은, 위 추론 네트워크가 데이터가 속하는 클러스터를 의미하는 카테고리의 수만큼의 차원을 갖는 One-Hot 벡터를 예측하는 데 사용된다는 것이다.

위 그림은 클러스터의 수가 16일 때 MNIST 데이터셋에 대한 AAE의 클러스터링 성능을 보여준다. 각 행의 첫 번째 이미지는 클러스터 Head로, Style 변수를 0으로 고정하고 Label 변수를 1-of-16 One-Hot 벡터로 설정하여 생성된 숫자이다. 나머지 이미지들은 추론 네트워크에 기반하여 상응하는 카테고리로 배정된 테스트 데이터셋 이미지이다. 우리는 AAE가 몇몇 이산적 Style을 Class Label로서 포착한 것을 알 수 있다. 예를 들어 클러스터 11, 16을 보면 6과 1이 기울어져서 클러스터 10, 15와 다른 클러스터로 분류하였다. 또 클러스터 4, 6을 보면 같은 숫자 2라도 Style에 따라 다른 클러스터로 분류되어 있음을 알 수 있다.

AAE의 비지도 클러스터링 성능을 평가하기 위해 실험을 진행하였다. 평가 방법은 다음과 같다. 학습이 끝나면 각 클러스터 i에 대해 아래와 같은 식을 최대화하는 Validation Example $x_n$ 을 찾는다.

[q(y_i x_n)]

이후 $x_n$의 Label을 클러스터 i에 존재하는 모든 포인트에 할당한다. 그리고 나서 각 클러스터에 배정된 Class Label에 기반하여 테스트 에러를 계산한다. 그 결과는 다음과 같다. Cluster의 개수가 증가할 수록 에러는 감소하는 것을 알 수 있다.

1.7. Dimentionality Reduction with Adversarial Autoencoders

고차원 데이터의 시각화는 많은 분야에서 굉장히 중요한 문제이다. 왜냐하면 데이터의 생성 과정에 대한 이해를 촉진하고 분석가로 하여금 데이터에 대한 유용한 정보를 추출할 수 있도록 만들어주기 때문이다. 데이터 시각화의 유명한 방법은 유사한 개체에 상응하는 가까운 데이터 포인트에 존재하는 저차원의 Embedding을 학습하는 것이다. 지난 10년간 t-SNE와 같은 많은 비모수적 차원 축소 기법이 제안되었다. 이러한 방법의 큰 단점은 새로운 데이터 포인트를 위한 Embedding을 찾기 위해 사용될 수 있는 모수적 Encoder가 존재하지 않는다는 것이다. autoencoder는 이러한 Embeddding에 필요한 비선형적 Mapping을 제공해줄 수 있는 흥미로운 대안이지만, non-regularized autoencoder는 manifold를 많은 domain으로 분리해버려 유사한 이미지에 대한 다른 Code를 만들어 버리는 문제점을 갖고 있다.

이번 Chapter에서는 우리는 AAE를 차원 축소와 데이터 시각화를 위한 기법으로 소개할 것이다. 이 알고리즘에서 Adversarial Regularization은 유사한 이미지들의 Hidden Code를 서로 붙여주고 autoencoder에 의해 학습된 Embedding에서 전형적으로 맞닥드리는 문제였던 manifold를 분리하는 현상을 방지한다.

우리는 $m$ 개의 Class Label이 존재하는 데이터셋을 갖고 있고, 우리는 이를 $n$ 차원으로 줄이고 싶다고 하자. 이 때 $n$ 은 시각화를 위해 2~3이 적당할 것이다. 모델의 구조를 아래와 같이 바꿔보자.

이 때 최종 Representation은 클러스터의 $n$ 차원의 분포 Representation을 $n$ 차원의 Style Representation에 추가하는 것으로 얻을 수 있다. 클러스터 Head Representation은 $m$ 차원의 One-Hot Class Label 벡터를 $m * n$ 크기의 행렬 $W_c$ 와 곱함으로써 얻을 수 있다. 이 때 $W_c$ 의 행은 SGD에 의해 학습된 $m$ 개의 클러스터 Head Representation을 나타낸다. 모든 2개의 클러스터 Head 사이의 유클리디안 거리를 규제하는 추가적인 Cost 함수를 소개할 것이다. 구체적으로 만약 유클리디안 거리가 어떤 threshold $\eta$ 보다 크면, Cost 함수는 0이 될 것이고, 그 반대의 경우라면 이 Cost 함수가 거리를 선형적으로 규제하게 될 것이다.

위 그림 중 a, b 부분을 보면 $m=10, n=2$ 일 때, 1000개/100개의 Label을 가진 MNIST 데이터셋에 대해 준지도학습 차원 축소의 결과를 보여준다. 각각 4.2, 6.08%의 준지도 분류 Error를 보여주면서 상당히 깨끗하게 숫자 클러스터를 분류한 것을 알 수 있다. 2차원 제약조건 때문에 분류 Error는 고차원 케이스만큼 좋지는 못하다. 그리고 각 클러스터의 Style 분포는 딱히 정규분포라고 보기 어렵다.

c 부분은 $n=2$ 차원, $m=20$ 개의 클러스터가 존재한다고 가정한 비지도 차원축소 결과를 나타낸다. 우리는 네트워크가 숫자 클러스터와 sub-클러스터의 깨끗한 구분을 보여준다는 것을 알 수 있다. 예를 들어 네트워크는 2개의 다른 클러스터를 숫자 1(초록 클러스터)에 할당하였는데, 이는 그 숫자가 곧거나 기울어져있는 것에 따라 결정되었다. 이는 빨간 클러스터인 숫자 2의 경우에도 동일하게 적용된다.

앞서 보았던 AAE의 구조는 또한 $n>2$ 일 때 더 큰 차원으로 이미지를 임베딩하는 데에 사용될 수 있다. 예를 들어 d 부분을 보면, 100개의 라벨이 존재할 때 $n=10$ 차원의 준지도 차원 축소의 결과를 보여준다. 이 경우 우리는 $W_c = 10 \mathbf{I}$ 로 고정했기 때문에 클러스터 Head는 10차원 simplex의 코너를 의미한다. Style Representation은 표준편차1의 10차원 정규 분포로 학습되었고, 최종 Representation을 구성하기 위해 클러스터 Head에 직접 추가되었다.

네트워크가 학습되면 10차원의 학습된 Representation을 시각화하기 위해, 10차원 Representation을 클러스터 Head가 2차원 원에 균일하게 위치하는 데이터 포인트에 mapping되는 2차원 공간에 mapping되도록 선형 변환을 사용하였다. 우리는 고차원 케이스에서 Style Representation이 정말 정규 분포를 갖는다는 것을 확인할 수 있다. 총 100개의 Label에서 이 모델은 3.9%의 분류 에러를 보여주었는데, 이는 1.5 Chapter에서 보여주었던 1.9%의 에러보다 좋지 못한 결과이다.

1.8. Conclusion

본 논문에서 우리는 확률론적 autoencoder에 있는 이산형/연속형 잠재 변수들을 위한 변분 추론 알고리즘으로서 GAN 프레임워크를 사용하였다. 이 방법은 AAE라고 명명하며, MNIST와 Toronto Face 데이터셋에 대해 경쟁력 있는 테스트 Likelihood를 얻은 생성 autoencoder로 정의할 수 있다. 우리는 이 방법이 어떻게 준지도 학습 시나리오로 확장될 수 있는지 보여주었으며 이 방법이 MNIST, SVHN 데이터셋에 대해 경쟁력있는 준지도 분류 성능을 갖는다는 것을 보여주었다. 최종적으로 우리는 이 AAE 알고리즘이 이미지의 Style과 Content를 구별하고 비지도 클러스터링이나 차원축소, 데이터 시각화 등에도 사용될 수 있다는 것을 증명하였다.


2. 핵심 내용

코드로 넘어가기 전에, AAE의 핵심에 대해서 한 번만 더 짚고 넘어가도록 하겠다.

[\mathcal{L} (\theta, \phi; \mathbf{x}^{(i)}) = -D_{KL} (q_{\phi} (\mathbf{z} \mathbf{x}^{(i)})   p_{\theta} (\mathbf{z}) ) + E_{q_{\phi} (\mathbf{z} \mathbf{x}^{(i)})} [log p_{\theta} (\mathbf{x}^{(i)} \mathbf{z}) ]]

위 식은 이 글에서 상세히 설명하였듯이, VAE의 목적함수이다. 이 때 KL-divergence는 일종의 규제 역할을 하게 되는데, 의미론적으로 보면 아래 두 분포를 유사하게 만드는 역할을 하게 된다.

[q_{\phi} (\mathbf{z} \mathbf{x}^{(i)}), p_{\theta} (\mathbf{z})]

Encoder와 Prior를 유사하게 만들어야 하는데, VAE에서는 이 두 분포에서 Sampling이 가능하고, numerical하게 계산이 가능해야 한다는 전제가 필수적이다. 그렇기 때문에 이들 분포로 정규 분포가 널리 사용되는 것이다.

AAE는 두 전제 중 2번째 전제를 불필요하게 만드는 알고리즘이다. 두 분포를 유사하게 만들기 위해 위와 같은 KL-divergence를 계산하는 것이 아니라 GAN으로 하여금 이 역할을 수행하게 만든 것이다. 즉, GAN이 KL-divergence를 대체한 것이다.

따라서 전체 Loss는 1) Autoencoder의 Reconstruction Loss, 2) Discriminator Loss, 3) Generator Loss 이렇게 3가지로 구분할 수 있다.

이제 Tensorflow로 구현을 시작해보자.


3. Tensorflow로 구현

논문을 자세히 읽어보았다면 이 알고리즘을 구현하는 방법은 꽤 다양하다는 것을 알 수 있을 것이다. 본 글에서는 MNIST 데이터셋을 이용하여 Supervised Adversarial Autoencoder를 구현해보겠다. 본 Chapter에서의 구현 방법은 굉장히 간단하고 분포를 변경하거나 Layer의 구조를 선택하는 등 변형에 있어 다소 불편하게 되어 있는데, 추후 글에서 불균형 학습을 해결하기 위해 AAE를 활용하는 방법에 대해 다루면서 개선된 코드를 제공하도록 하겠다.

autoencoder는 Deterministic하게 구성하였으며, Prior로는 정규 분포를 사용하였다. (논문에서는 Gaussian Mixture와 Swiss Roll 분포를 예로 들었다.) 모델 구조를 살펴보자.

class AAE(tf.keras.Model):
    def __init__(self, latent_dim):
        super(AAE, self).__init__()
        self.latent_dim = latent_dim
        self.encoder = tf.keras.Sequential(
            [
                tf.keras.layers.InputLayer(input_shape=(28*28 + 1)),
                tf.keras.layers.Flatten(),
                tf.keras.layers.Dense(units=512, activation='relu'),
                tf.keras.layers.Dropout(rate=0.2),
                tf.keras.layers.Dense(units=256, activation='relu'),
                tf.keras.layers.Dropout(rate=0.2),
                tf.keras.layers.Dense(units=64, activation='relu'),
                tf.keras.layers.Dense(units=latent_dim),
            ])

        self.decoder = tf.keras.Sequential(
            [
                tf.keras.layers.InputLayer(input_shape=(latent_dim+1)),
                tf.keras.layers.Dense(units=64, activation='relu'),
                tf.keras.layers.Dropout(rate=0.2),
                tf.keras.layers.Dense(units=256, activation='relu'),
                tf.keras.layers.Dropout(rate=0.2),
                tf.keras.layers.Dense(units=512, activation='relu'),
                tf.keras.layers.Dense(units=784),
            ])

        self.discriminator = tf.keras.Sequential(
            [
                tf.keras.layers.InputLayer(input_shape=(latent_dim+1)),
                tf.keras.layers.Dense(units=64, activation='relu'),
                tf.keras.layers.Dropout(rate=0.2),
                tf.keras.layers.Dense(units=128, activation='relu'),
                tf.keras.layers.Dropout(rate=0.2),
                tf.keras.layers.Dense(units=64, activation='relu'),
                tf.keras.layers.Dropout(rate=0.2),
                tf.keras.layers.Dense(units=1),
            ])

    @tf.function
    def encode(self, x, y):
        inputs = tf.concat([x, y], 1)
        z = self.encoder(inputs)
        return z
    
    @tf.function
    def discriminate(self, z, y):
        inputs = tf.concat([z, y], 1)
        output = self.discriminator(inputs)
        return output

    @tf.function
    def decode(self, z, y, apply_sigmoid=False):
        inputs = tf.concat([z, y], 1)
        logits = self.decoder(inputs)
        if apply_sigmoid:
            probs = tf.sigmoid(logits)
            return probs
        return logits

Discriminator의 마지막 Layer는 True와 Fake를 구별하기 위해 1개의 Node로 마무리하였다. Encoder의 결과물인 $\mathbf{z}$ 가 Fake z이며, Prior에서 나온 결과물이 True z에 해당한다.

AAE는 학습이 3단계로 이루어지기 때문에 Loss도 개별적으로 계산하는 것이 좋다. 아래 코드를 보면 이해가 될 것이다.

def compute_reconstruction_loss(x, x_logit):
    # Reconstruction Loss
    marginal_likelihood = tf.reduce_sum(x * tf.math.log(x_logit) + (1 - x) * tf.math.log(1 - x_logit), axis=[1])
    loglikelihood = tf.reduce_mean(marginal_likelihood)
    reconstruction_loss = -loglikelihood
    return reconstruction_loss

def compute_discriminator_loss(fake_output, true_output):
    # Discriminator Loss
    d_loss_true = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=true_output,
                                                                         labels=tf.ones_like(true_output)))
    d_loss_fake = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=fake_output,
                                                                         labels=tf.zeros_like(fake_output)))
    discriminator_loss = d_loss_true + d_loss_fake
    return discriminator_loss

def compute_generator_loss(fake_output):
    # Generator Loss
    generator_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=fake_output,
                                                                            labels=tf.ones_like(fake_output)))
    return generator_loss

이 때 x는 실제 데이터 Input을, x_logit은 autoencoder를 통과한 결과물이다. fake_output은 Encoder의 결과물인 z가 Discriminator를 통과한 후의 결과물이며, true_output은 Prior true_z가 Discriminator를 통과한 후의 결과물이다.

학습 함수는 아래와 같다. 이 때 기본적인 GAN의 특성상 Generator의 학습 속도가 느린 점을 반영하여 Generator는 2번 학습하도록 하였다.

@tf.function
def train_step(model, x, y, r_optimizer, d_optimizer, g_optimizer):
    # Results
    x = tf.reshape(x, [-1, 784])
    y = tf.reshape(y, [-1, 1])

    # Propagation
    with tf.GradientTape() as tape:
        z = model.encode(x, y)
        x_logit = model.decode(z, y, True)
        x_logit = tf.clip_by_value(x_logit, 1e-8, 1 - 1e-8)
        reconstruction_loss = compute_reconstruction_loss(x, x_logit)
    r_gradients = tape.gradient(reconstruction_loss, model.trainable_variables)
    r_optimizer.apply_gradients(zip(r_gradients, model.trainable_variables))

    with tf.GradientTape() as tape:
        z = model.encode(x, y)
        true_z = tf.random.normal(shape=(z.shape))
        fake_output = model.discriminate(z, y)
        true_output = model.discriminate(true_z, y)
        discriminator_loss = compute_discriminator_loss(fake_output, true_output)
    d_gradients = tape.gradient(discriminator_loss, model.trainable_variables)
    d_optimizer.apply_gradients(zip(d_gradients, model.trainable_variables))

    for _ in range(2):
        with tf.GradientTape() as tape:
            z = model.encode(x, y)
            fake_output = model.discriminate(z, y)
            generator_loss = compute_generator_loss(fake_output)
        g_gradients = tape.gradient(generator_loss, model.trainable_variables)
        g_optimizer.apply_gradients(zip(g_gradients, model.trainable_variables))

    total_loss = reconstruction_loss + discriminator_loss + generator_loss

    return total_loss

Epoch30, 잠재 차원은 4로하고, Discriminator의 학습률은 다소 낮게 설정하여 학습을 진행하여 보자. (Reference 2 강의 참조)

epochs = 30
latent_dim = 4
model = AAE(latent_dim)

r_optimizer = tf.keras.optimizers.Adam(1e-4)
d_optimizer = tf.keras.optimizers.Adam(1e-4/5)
g_optimizer = tf.keras.optimizers.Adam(1e-4)

# Train
for epoch in range(1, epochs + 1):
    train_losses = []
    for x, y in train_dataset:
        total_loss = train_step(model, x, y, r_optimizer, d_optimizer, g_optimizer)
        train_losses.append(total_loss)

    print('Epoch: {}, Loss: {:.2f}'.format(epoch, np.mean(train_losses)))

Epoch 30 이후의 Loss는 126.8까지 감소하였다. 이전 글에서 보았던 Test 함수를 통해 결과물을 확인해보자. 위쪽 그림이 원본, 아래쪽 그림이 AAE의 결과물이다.

CVAE의 결과보다 더욱 선명한 결과를 보여준 점이 만족스럽다. 튜닝에 더욱 신경을 쓴다면 더욱 좋은 결과가 있지 않을까 생각해본다.


Reference

1) 논문 원본
2) 오토인코더의 모든 것

Comment  Read more