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

HERO 논문 설명(HERO - Hierarchical Encoder for Video+Language Omni-representation Pre-training)

|

이 글에서는 2020년 EMNLP에 게재된 HERO: Hierarchical Encoder for Video+Language Omni-representation Pre-training 논문을 살펴보고자 한다.

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


HERO: Hierarchical Encoder for Video+Language Omni-representation Pre-training

논문 링크: HERO: Hierarchical Encoder for Video+Language Omni-representation Pre-training

초록(Abstract)

대규모 Video + Language omni-representation(전체표현) 학습을 위한 새로운 framework, HERO를 제시한다. HERO는 계층적 구조에서 multimodal 입력을 인코딩한다. Video frame의 local 문맥은 multimodal fusion에 의해 Cross-modal Transformer로 잡아내고, global video 문맥은 Temporal Transformer에 의해 추출된다. Masked Language Modeling(MLM)과 Masked Frame Modeling(MFM)에 더해 새로운 사전학습 task를 제시한다.

  1. Video-Subtitle Matching(VSM): 모델이 global/local temporal alignment를 예측함.
  2. Frame Order Modeling(FOM): 모델이 순서를 섞인 video frame의 올바른 순서를 예측함.

HERO는 HowTo100M과 대규모 TV 데이터셋으로 학습하여 multi-character 상호작용과 복잡한 사회적 dynamics에 대한 깊은 이해를 얻을 수 있게 한다. 종합적인 실험은 HERO가 Video Retreival, VQA, Video Captioning 등의 task에서 새로운 SOTA를 달성하였음을 보인다. 또한 How2QA와 How2R이라는 Video QA and Retrieval benchmark를 제안한다.


1. 서론(Introduction)

시각+언어 multimodal 연구에서 BERT, ViLBERT, LXMERT, UNITER, VL-BERT, Unicoder-VL 등 대규모 사전학습 방식 모델이 여럿 발표되었다. 그러나 이러한 모델은 정적인 이미지에 맞춰져 있을 뿐 동적인 영상에는 적합하지 않다. VideoBERT가 영상-텍스트 쌍에 대한 결합표현을 학습하는 첫 시도이기는 했으나 이산적인 token만 사용되었으며 전체 frame feature가 사용되지 않았다. 이후 CBT, UniViML 등이 이를 해결하기 위한 시도를 하였다.

여러 시도가 있었으나 한계가 있었는데,

  1. BERT 기반으로 구성되었다. 이는 텍스트와 영상 정보를 단순히 이어붙인 형태로 사용하여 두 modality가 같은 시간에 있었다는 정보를 사용하지 않는다.
  2. 사전학습 task는 이미지+텍스트 task에서 가져온 것으로 영상의 특성을 활용하지 못한다.
  3. 영상 데이터셋은 요리나 일부 형식의 영상만 존재하여 한계가 있다.

이를 해결하기 위해 HERO: Hierarchical Encoder for Video+Language Omni-representation Pre-training를 제안한다.

HERO

그림 1에서와 같이, video clip frame과 대응되는 자막 문장을 입력으로 받는다. BERT의 encoder와 같은 것이 아닌, HERO는 multimodal 입력을 구조적인 형태로 받는데,

  1. Cross-modal Transformer는 자막 문장과 해당하는 local video frame을 결합하고,
  2. 그 다음에 Temporal Transformer가 각 frame마다 주변의 frame을 전역 문맥으로 사용하여 연속적으로 contextualized embedding을 얻는다.

제안하는 계층적 모델은 frame 수준에서 먼저 visual/textual 지역 문맥을 얻어 전역 영상수준 시간적 문맥으로 변환한다.
실험은 이러한 구조가 BERT와 같은 구조의 모델보다 성능이 좋음을 보여준다.

사전학습 task는 다음 4가지이다.

  1. Masked Language Modeling(MLM)
  2. Masked Frame Modeling(MFM)
  3. Video-Subtitle Matching(VSM)
  4. Frame Order Modeling(FOM)

VSM과 FOM은 연속적 영상입력 전체에서 modality 간 시간적 일치 정보를 활용하는 것이 핵심이다.

YouCook2나 MSR-VTT와 같은 교육용(instructional) 영상만을 쓰는 한계를 벗어나기 위해 데이터셋은 HowTo100M과 대규모 TV 데이터셋을 사용하였다. 또한 요리에만 국한되거나(YouCook2) 매우 간단한(MSR-VTT) 데이터셋의 문제를 해결하기 위해서 새로운 task인 How2R과 How2QA를 제안한다.

그래서 본 논문의 기여한 바는,

  1. 시각+언어 표현 학습을 위한 Transformer 기반 계층적 모델 HERO를 제안한다.
  2. Modality 간 alignemnts를 더 잘 학습할 수 있도록 하는 사전학습 task VSM과 FOM을 제시한다.
  3. 단순한 데이터셋을 넘어 HowTo100M과 대규모 TV 데이터셋을 사용하여 모델이 더욱 풍부한 표현을 학습할 수 있게 하였다.
  4. HowTo100M, video-moment retrieval/QA에서 데이터셋을 모아 새로운 benchmark로 How2R과 How2QA를 제안한다.

2. 관련 연구(Related Work)

  • BERT이후 BERT-like한 많은 모델이 발표되었다. 이후 모델 압축이나 생성 task로 발전이 있었다.
  • 텍스트만 다루는 것을 넘어 시각적 정보까지 사용하는 모델이 등장하였다(ViLBERT, LXMERT, VL-BERT, Unicoder-VL 등).
  • 대개 텍스트에 이미지를 사용하는 것이 많았는데, 이후 영상을 다루는 모델(VideoBERT, CBT, MIL-NCE, Act-BERT, UniViLM)이 제안되었다.

본 논문에서는 시각+언어 전체표현(omni-representation)을 4가지 차원에서 학습하는 모델을 개발하는 것을 목표로 하였다.

  1. 더 나은 모델구조
  2. 더 나은 사전학습 task 설계
  3. 학습 말뭉치의 다양화
  4. 후속 평가를 위한 새로운 고품질의 benchmark

3. 계층적 시각+언어 인코더(Hierarchical Video+Language Encoder)

3.1 Model Architecture

HERO

그림 1에서 전체 구조를 볼 수 있다.

  • 입력은 video clip과 자막(일련의 token으로 된 문장)이다.
  • 이들 입력은 Video Embedder과 Text Embedder를 통과하여 초기 표현을 뽑는다.
  • HERO는 계층적 과정으로 영상표현을 추출하는데,
    • 각 video frame의 local textual context는 Cross-modal Transformer에 의해 만들어진다. 자막 문장과 연관된 video frame의 contextualized multi-modal 임베딩을 계산하여 얻는다.
    • 전체 영상의 frame 임베딩은 이후 Temporal Transformer에 집어넣어 global video context와 최종 contextualized video 임베딩을 얻는다.

Input Embedder

Notation:

[\text{video clip} \ v=\lbrace v_i\rbrace^{N_v}{i=1}, \quad \text{subtitle} \ s=\lbrace s_i\rbrace^{N_s}{i=1}]

Text Embedder에서, WordPieces를 사용하여 자막 문장 $s_i$를 토큰화하고 각 sub-word token에 대한 최종표현은 token/position embedding을 합한 후 layer normalization(LN)을 통과시켜 얻는다.
Video Embedder에서, ImageNet에서 사전학습한 ResNet과 Kinetics에서 사전학습한 Slow-Fast를 사용, 각 video frame에 대한 2D/3D visual feature를 얻는다. 이들을 이어붙인 뒤 FC layer에 통과시켜 낮은 차원의 공간으로 전사, token embedding으로 사용한다.
Video frame은 연속적이기 때문에 position embedding은 Text Embedder에서와 같은 방법으로 계산된다. Frame에 대한 최종 표현은 FC 출력과 position embedding을 합하여 LN layer를 통과시켜 얻는다.
Input Embedder 이후 $w_{s_i}, v_{s_i}$에 대한 token과 frame embedding은 각각 다음과 같이 표시된다. $d$는 hidden size이다.

[W_{s_i}^{emb} \in \mathbb{R}^{L \times d}, V_{s_i}^{emb} \in \mathbb{R}^{K \times d}]

Cross-modal Transformer

자막과 video frame 간 alignments를 활용하기 위해 각 자막 문장 $s_i$에 대해 대응되는 token $w_{s_i}$와 연관된 visual frames $v_{s_i}$ 사이의 contextualized embedding을 cross-modal attention을 통해 학습한다. 이를 위해 multi-layer Transformer를 사용한다.
Cross-modal Transformer의 출력은 각 자막과 frame에 대한 일련의 contextualized embedding이며 다음과 같이 표시한다.

[\mathbf{V}{s_i}^{cross}, \mathbf{W}{s_i}^{cross} = f_{cross}(\mathbf{V}{s_i}^{emb}, \mathbf{W}{s_i}^{emb}), \quad \mathbf{V}{s_i}^{cross} \in \mathbb{R}^{L \times d}, \mathbf{W}{s_i}^{cross} \in \mathbb{R}^{K \times d}]

$f_{cross}$는 Cross-modal Transformer이다.

Temporal Transformer

Cross-modal Transformer의 출력으로부터 모든 visual frame embedding을 모은 후 video clip의 global context를 학습하기 위해 다른 Transformer를 temporal attention으로 사용한다.

[\mathbf{V}^{cross} = \lbrace \textbf{V}{s_i}^{cross}\rbrace^{N_s}{i=1} \in \mathbb{R}^{N_v \times d}]

위치정보의 손실을 막기 위해 residual connection을 $\textbf{V}^{emb} \in \mathbb{R}^{N_v \times d}$의 뒤에 추가한다. 최종 contextualized video embedding은

[\mathbf{V}^{temp} = f_{temp}(\mathbf{V}^{emb} + \mathbf{V}^{cross}), \quad \mathbf{V}^{temp} \in \mathbb{R}^{N_v \times d}]

$f_{temp}$는 Temporal Transformer이다.

모든 textual/visual feature를 그냥 이어 붙이는(concat) BERT류와 달리 본 논문의 모델은 좀 더 세밀하게 시간적 일치정보를 활용한다. 실험에서 이 방식이 더 나음을 증명한다.

3.2 Pre-training Tasks

MFM과 MLM은 BERT의 것과 비슷하다. VSM은 local & global alignment를 학습하기 위한 것이며, FOM은 영상의 연속적 특성을 모델링하기 위한 task이다.

3.2.1 Masked Language Modeling

입력은 다음과 같다.

  • $i$번째 자막 문장 $\mathbf{w}_{s_i}$
  • 이와 연관된 visual frames $\mathbf{v}_{s_i}$
  • mask index $\mathbf{m} \in \mathbb{N}^M$

15%의 확률로 임의의 word를 [MASK]로 치환하고 이를 맞추는 task이다. 다음 음의 우도를 최소화한다.

[\mathcal{L}{MLM}(\theta) = -\mathbb{E}_D \log P{\theta}(\mathbf{w}_{s_i}^{\mathbf{m}} \mathbf{w}{s_i}^{\backslash \mathbf{m}}, \mathbf{v}{s_i})]

$\theta$는 학습 가능한 parameter이며 각 $\mathbf{w}, \mathbf{v}$는 $D$로부터 샘플링된다.

3.2.2 Masked Frame Modeling

MLM과 비슷하지만, MLM은 local context에서 진행되는 데 비해 MFM은 global context에서 수행된다. 목적은 word 대신 masking된 frame $\mathbf{v}_{\mathbf{m}}$을 복구하는 것이다.

Masked frame의 visual feature는 0으로 대체된 상태에서 나머지 frame $\mathbf{v}_{\backslash \mathbf{m}}$과 자막 문장 $\mathbf{s}$을 갖고 복원을 하게 된다. 이산값인 텍스트와는 달리 visual feature는 class 우도로 학습할 수 없고 따라서 다른 목적함수를 도입한다.

[\mathcal{L}{MFM}(\theta) = \mathbb{E}_D f{\theta}(\mathbf{v}_{\mathbf{m}} \mathbf{v}_{\backslash \mathbf{m}}, \mathbf{s})]

Masked Frame Feature Regression (MFFR)

MFFR은 각 masked frame $\mathbf{v}_{\mathbf{m}}^{(i)}$를 visual feature로 회귀시키는 task이다.

출력 frame 표현을 FC layer에 통과시켜 입력 visual feature와 같은 차원인 벡터 $h_{\theta}(\mathbf{v}_{\mathbf{m}}^{(i)})$로 변환한다.

참고로 visual feature는 $r(\mathbf{v}_{\mathbf{m}}^{(i)})$이며, 변환 이후 L2 regression을 적용한다.

[f_{\theta}(\mathbf{v}_{\mathbf{m}} \mathbf{v}{\backslash \mathbf{m}}, \mathbf{s}) = \sum^M{i=1} \Vert h_{\theta}(\mathbf{v}{\mathbf{m}}^{(i)}) - r(\mathbf{v}{\mathbf{m}}^{(i)}) \Vert_2^2]

Masked Frame Modeling with Noise Contrastive Estimation (MNCE)

Masked visual feature로 바로 회귀시키는 대신 자기지도 표현학습에서 널리 쓰이는 Noise Contrastive Estimation(NCE) loss를 사용한다. NCE loss는 모델이 올바른 frame을 판별할 수 있게 한다.

MFFR과 비슷하게 masked frame $\mathbf{v}_{\mathbf{m}}^{(i)}$을 FC layer에 통과시켜 다음 벡터로 전사한다.

[g_{\theta}(\mathbf{v}_{\mathbf{m}}^{(i)})]

이후 부정 선택지에도 같은 과정을 적용한다.

[\mathbf{v}{\mathbf{neg}} = \lbrace \mathbf{v}{\mathbf{neg}}^{(j)} \mathbf{v}{\mathbf{neg}}^{(j)} \in \mathbf{v}{\backslash \mathbf{m}} \rbrace]

최종 목적함수는 다음과 같다.

[f_{\theta}(\mathbf{v}_{\mathbf{m}} \mathbf{v}{\backslash \mathbf{m}}, \mathbf{s}) = \sum^M{i=1} \log \text{NCE}(g_{\theta}(\mathbf{v}{\mathbf{m}}^{(i)}) - g{\theta}(\mathbf{v}_{\mathbf{neg}}))]

3.2.3 Video-Subtitle Matching

VSM의 입력은 아래와 같다.

  • 모든 자막 문장에서 얻은 query $s_q$
  • 전체 video clip $\mathbf{v}$
  • video clip에 대한 나머지 자막 문장 $\mathbf{s}_{\backslash q}$

모델은 두 가지를 학습해야 한다.

  1. local alignment - query와 연관된 frame의 시작과 끝 = $y_{st}, y_{ed} \in \lbrace 1, …, N_v \rbrace $
  2. global alignment - query와 match되는 video

학습 방식은 XML 모델을 따라간다. Temporal Transformer의 출력을 최종 frame 표현 $\mathbf{V}^{temp} \in \mathbb{R}^{N_v \times d}$으로 추출한다.
Query는 Cross-modal Transformer에 넣어 다음 textual 표현을 얻는다.

[\mathbf{W}{s_q}^{cross} = f{cross}(\mathbf{0}, \mathbf{W}_{s_q}^{embed})]

이에 기반하여 self-attention layer로 구성된 query encoder, 2개의 선형 layer, LN layer을 사용하여 $ \mathbf{W}_{s_q}^{cross}$로부터 최종 query 벡터 $\mathbf{q} \in \mathbb{R}^d$를 얻는다.

Local Alignment

Local query-video matching 점수는 내적을 사용한다.

[S_{local}(s_q, \mathbf{v}) = \mathbf{V}^{temp} \mathbf{q} \in \mathbb{R}^{N_v}]

2개의 학습가능한 1D convolution filter가 이 점수에 적용되고 softmax를 통과하면 다음 확률벡터를 얻는다. 이는 ground-truth span의 시작과 끝을 나타낸다.

[\mathbf{p}{st}, \mathbf{p}{ed} \in \mathbb{R}^{N_v}]

학습 동안 15%의 자막을 선택하여 cross-entropy loss를 사용한다.

[\mathcal{L}{local} = -\mathbb{E}_D \log (\mathbf{p}{st}[y_{st}]) + \log (\mathbf{p}{ed}[y{ed}])]

$\mathbf{p}[y]$는 벡터 $\mathbf{p}$의 $y$번째 원소이다.

XML에서는 각 modality에 대해 독립적으로 점수를 계산하며 최종점수는 2개의 점수를 합친 것이다.
HERO에서는 multimodal fusion이 그 이전에 행해진다.

Global Alignment

Frame과 query 간 cosine 유사도를 계산하여 matching score를 얻는다.

HERO

Positive/Negative query-video 쌍에 대해 결합 hinge loss $\mathcal{L}_h$를 사용한다. Positive 쌍에 대해서 각 원소를 같은 mini-batch 안에 있는 다른 sample로 대체하여 총 2개의 negative sample을 만든다.
목적함수는 다음과 같다.

HERO

$\delta$는 margin hyper-parameter이다. 최종 손실함수는 다음과 같다.

[\mathcal{L}{VSM} = \lambda_1 \mathcal{L}{local} + \lambda_2 \mathcal{L}_{global}]

3.2.4 Frame Order Modeling

입력은 다음과 같다.

  1. 모든 자막 문장 $\mathbf{s}$
  2. visual frames $\mathbf{v}$
  3. 재배치 순서 $\mathbf{r} = \lbrace r_i \rbrace^R_{i=1} \in \mathbb{N}^R$

15%의 frame을 순서를 섞으면 모델은 원래 시간순서(timestamp) $ \mathbf{t} = \lbrace t_i \rbrace^R_{i=1}, \ t_i \in \lbrace 1, …, N_v \rbrace$ 를 알아내야 한다.

본 논문에서, FOM은 분류 문제로 형식화하고, $\mathbf{t}$는 재배치 순서의 ground-truth label이다.

재배치는 자막과 frame의 fusion 이후 행해진다. 재배치된 feature는 Temporal Transformer에 넣어 재배치된 visual frame embedding $\mathbf{V}_r^{temp}$를 얻는다.
이 embedding은 FC layer, softmax layer를 통과하여 확률행렬 $\mathbf{P} \in \mathbb{R}^{N_v \times N_v}$를 얻는다. 여기서 각 열 $\mathbf{p}_i \in \mathbb{R}^{N_v}$는 $i$번째 timestamp가 속할 $N_v$개의 timestamp class의 점수를 나타낸다.

FOM에서, 다음 손실함수를 최소화해야 한다.

[\mathcal{L}{FOM} = -\mathbb{E}_D \sum^R{i=1} \log \mathbf{P}[r_i, t_i]]

4. 실험(Experiments)

Text-based Video, Video-moment Retreival, VQA, Video-and-Language Inference, Video Captioning 분야에서 평가한다.
6개의 benchmark(TVR, TVQA, VIOLIN, TVC, DiDeMo, MSR-VTT)을 사용하였다.

4.1 Pre-training Datasets

7.6M개의 video clip을 포함하며 downstream task에 등장하는 모든 영상은 사전학습 데이터셋에서 제거하였다.

TV Dataset

의학 드라마, 시트콤, crime show의 세 장르에서 6개의 TV show를 포함한다. 총 21,793개의 video clip이며 각 clip은 대략 60-90초 사이이다. 인물 간 상호작용과 사회적/전문적 활동 등을 포함하며 대화 목록이 제공된다.

HowTo100M Dataset

YouTube에서 모은 대부분 교육용(instructional) video인 12개의 분류, 1.22M개의 영상을 포함한다. (Food & Entertaining, Home & Garden, 등)
각 영상은 설명(narration)을 자막으로 포함하며 이는 수동으로 혹은 ASR로 얻어졌다. 평균 길이는 6.5분이지만 TV dataset과 비슷하게 맞추기 위해 1분 단위로 잘라 총 7.56M개의 clip을 만들었다.

4.2 New Benchmarks

다양한 내용을 학습하기 위해 text based video-moment retrieval를 위한 How2R, VQA를 위한 How2QA를 제안한다.

How2R

HowTo100M 영상에 대한 annotation을 얻기 위해 AMT를 이용하였다. 영상 자체에 집중하기 위해 narration은 미제공 상태로 이루어졌다.

  • 하나의 self-contained scene을 포함하는 영상의 일부분을 구분하는 것
  • 분절된 각 영상에 대해 설명을 쓰는 것

최종 분절영상은 10-20초 정도이며 query의 길이는 8-20단어이다.

51390개의 Query, 9371개의 영상을 얻고 각 clip당 2-3개의 query를 포함한다. 데이터셋은 train/val/test가 각 80/10/10%으로 구분된다.

How2QA

역시 AMT를 이용, 분절영상에 대해 1개의 질문과 4개의 선택지를 만들게 하였다.

만들어진 QA 중 매우 편향되게 작성된 것이 많은 관계로(모델이 영상이나 자막으로부터 어떤 정보도 얻지 않고도 답을 맞힐 수 있음) 이를 완화하기 위해 adversarial matching(3개의 오답을 다른 질문의 정답으로 대체)을 사용하였다.

TVQA와 비슷하게 각 질문과 연관된 영상의 시작/끝 부분을 제공하였다. 저품질 annotation을 제거하고 22k개의 60초 clip, 44007개의 QA 쌍을 얻었다. 비슷하게 80/10/10 비율로 나눈다.

4.3 Ablation Study

Optimal Setting of Pre-training Tasks

HERO

각 데이터셋별로 다른 사전학습 세팅을 적용하여 실험하였다.

  • TV dataset에서 학습된 모델만이 계산량에서 이점을 가진다.
  • MLM과 비교하여 MNCE를 추가하는 것은 모든 데이터셋에서 효과가 있다.
  • 제일 좋은 조합은 MLM + MNCE + FOM + VSM이다.

Effect of FOM and VSM

  • MLM, MNCE, FOM이 같이 학습되면 TVQA에서 큰 향상이 있고, How2R과 How2QA에서도 마찬가지이다.
    • 이는 FOM이 QA task와 같이 temporal reasoning에 의존하는 downstream task에 효과적임을 뜻한다.
  • VSM에서 상당한 성과가 있었는데 특히 local/global alignments를 학습함으로써 TVR과 How2R에서 특히 효과적이다.
  • 추가적인 MFFR을 더하는 것은 좋지 않았다.
  • MFFR은 사전학습 동안 MNCE와 비등하지만 나머지의 경우는 무시할 만한 수준이다.

Effect of Pre-training Datasets

  • HowTo100M에서만 사전학습한 모델은 TV dataset에서만 사전학습한 모델보다 TVR에서는 성능이 낮고, 나머지 TVQA, How2R, How2QA에서는 더 좋은 결과를 얻었다.
    • 하나의 가정은 text-based video-moment retreival이 video domain에 더 민감하다는 것이다.
  • HowTo100M이 더 많은 수의 영상을 포함하지만 TV 영상에 대해서는 TV로 학습한 것이 더 낫다.

Hierarchical Design vs. Flat Architecture

HERO의 계층적 구조가 좋은 것인지 확인하기 위해 다른 기준모델 2개와 비교하였다.

  1. Hierarchical Transformer(H-TRM). Cross-modal Transformer을 RoBERTa 모델로 대체한 것
  2. Flat BERT-like encoder(F-TRM)
HERO
  1. 사전학습 없이 실험한 경우, F-TRM은 HERO에 비해 좋지 않다. 이는 HERO의 영상의 두 modality 간 temporal alignment의 탐색 효과 때문으로 보인다.
  2. 사전학습한 경우 차이는 더 큰데, 이는 cross-modal 상호작용과 temporal alignments가 downstream task에 대해 더 나은 표현을 제공함을 뜻한다.

HERO vs. SOTA with and w/o Pre-training

표 2에서 보듯이 사전학습이 있든 없든 HERO가 더 좋은 성능을 보인다.

Key Conclusions

  • 최적의 사전학습 세팅은 MLM + MNCE + FOM + VSM이며 HowTo100M과 TV dataset을 모두 사용한다.
  • FOM은 특히 시간적 추론을 요하는 task에서 효과적이다.
  • VSM은 frame-subtitle 일치정보를 학습하게 하여 특히 video-moment retreival task에서 효과적이다.
  • HERO의 계층적 구조는 명시적으로 subtitle과 frame 간 일치 정보를 학습하기 한다.
  • HERO는 사전학습이 있든 없든 SOTA를 일관되게 앞선다.

4.4 Results on Downstream Tasks

HERO

HERO는 위에서 언급한 최적의 사전학습 세팅을 사용하였다. XML, STAGE, Multi-stream, MMT 등을 능가하는 결과를 볼 수 있다.

Results on Multi-channel Tasks

표 3a에서 다채널 영상에 대한 결과를 볼 수 있다. TVR R@1에서는 XML의 거의 두 배를 기록하는 등 꽤 좋은 성능을 보인다.

Results on Single-channel Tasks

표 3b는 단채널 영상에 대한 결과이다. 역시 DiDeMo, MSR-VTT에 비해 일관되게 좋은 성능이다. 더 자세한 내용은 부록 A.1에 있다.


5. 결론(Conclusion)

Video+Language omni-representation 사전학습을 위한 계층적 encoder를 제안하였다. HERO 모델은 Cross-modal & Temporal Transformer를 사용한 계층적 구조로 새로운 사전학습 task들이 지역적으로 그리고 전역적으로 temporal alignment 정보를 학습하기 위해 제안되었다. HERO는 다수의 vision-language task에서 SOTA를 달성하였고 downstream task에 대한 추가적인 평가방법으로 2개의 새로운 데이터셋(task) How2R과 How2QA를 제시하였다. 추후 연구로는 모델의 확장 버전을 검토하고 더 나은 사전학습 task를 고안할 예정이다.


참고문헌(References)

논문 참조!


Comment  Read more

ClusterGCN 설명

|

이번 글에서는 ClusterGCN이란 알고리즘에 대해 다뤄보겠다. 상세한 내용을 원하면 논문 원본을 참고하길 바라며, 본 글에서는 핵심적인 부분에 대해 요약 정리하도록 할 것이다.

torch_geomectric을 이용하여 ClusterGCN를 사용하는 방법에 대해서 간단하게 Github에 올려두었으니 참고해도 좋을 것이다.


Cluster-GCN: An Efficient Algorithm for Training Deep and Large Graph Convolutional Networks 설명

1. Background

Classic Graph Convolutional Layer의 경우 Full-batch 학습이 필연적이었기 때문에 Graph의 크기가 커질수록 메모리 부담이 굉장히 커진다는 단점을 갖는다. Mini-batch SGD를 적용한 알고리즘 또한 Layer가 증가함에 따라 필요한 이웃 node와 임베딩의 수가 급속도로 증가하는 Neighborhood Expansion Problem에서 자유로울 수 없었다. 결국 GCN의 깊이가 깊어질 수록 연산량이 지수함수적으로 증가하는 것이다.

이를 위해 이웃 sample의 수를 고정하는 GraphSAGE, Importance Sampling을 도입한 FastGCN 등이 제안되었지만, 여전히 완전히 문제를 해결할 수는 없었다. VR-GCN 또한 분산 감소 기술을 통해 이웃 Sampling node의 수를 줄였지만, 여전히 메모리 문제에서 자유로울 수 없었다.

본 논문에서는 위와 같은 문제를 해결하기 위해 ClusterGCN이란 알고리즘을 제안한다. ClusterGCNMETIS라는 Graph Clustering 알고리즘을 통해 Batch Data를 구성하고 이후 이에 적합한 GCN Layer 구조를 취함으로써 효과적으로 Graph 데이터에 대해 학습하게 된다.

ClusterGCN의 경우 오직 현재 Batch에 존재하는 node에 대해서만 임베딩 벡터를 저장하면 되기 때문에 메모리 사용량을 줄일 수 있다. 그리고 복잡한 이웃 Sampling 과정을 필요로 하지 않고 단지 행렬 곱 연산만 수행하면 되기 때문에 구현에 있어서도 편리한 장점을 지닌다.

추가적으로 본 논문에서는 GCN Layer를 더욱 깊게 쌓는 방법에 대해서도 인사이트를 공유한다. (GNN의 경우 Layer를 깊게 쌓는 것이 꼭 긍정적인 결과를 낳지 않으며, 오히려 정교한 설계 없이 Layer를 쌓으면 역효과가 나는 경우가 많다.)

Graph $G = (\mathcal{V}, \mathcal{E}, \mathcal{A})$ 가 존재하고 이 때 node의 수는 $N$ 이며, node feature matrix의 형상은 $(N, F)$ 이다.

다음이 2017년에 발표된 GCN의 기본 update 식임을 기억하자.

[H^{(l+1)} = \sigma(D^{-1} A H^l W^l)]

[A^{\prime} = D^{-1}A, H^0 = X]

원 논문에서는 이 식에 대하여 Full Gradient Descent를 이용하게 되는데, 앞서 언급하였듯이 이러한 세팅은 대용량의 현실 데이터에 적합하지 않다. mini-batch SGD를 적용한다면 각 SGD 단계는 아래 Gradient Estimation을 계산하게 될 것이다.

[\frac{1}{\vert \mathcal{B} \vert} \Sigma_{i \in \mathcal{B}} \triangledown loss (y_i, z_i^{(L)})]

다만 이렇게 진행할 경우 상대적으로 속도가 크게 느려지는 단점이 있다.


2. Vanilla Cluster-GCN

Cluster-GCN의 Batch를 구성하는 아이디어는 어떤 subgraph를 추출하고 이 subgraph에 포함된 node 집합이 $\mathcal{B}$ 이라고 할 때 각 layer의 계산에 필요한 Adjacency Matrix는 $A_{\mathcal{B}, \mathcal{B}}$ 라는 사실에서 출발한다.

그리고 이 때 생성되는 Embedding 벡터들은 위 subgraph 내에서의 관계에 기초하여 구성될 것이다. 그렇다면 Embedding Utilization을 더욱 향상시키기 위해서는 within-batch edges를 최대로 하는 Batch $\mathcal{B}$ 를 만들어 내야 할 것이다.

Graph $G$ 에 존재하는 Node들을 총 $c$ 개로 나눠보자. 이 때 $\mathcal{V}_t$ 는 t번째 파티션에 속해있는 nodes를 의미한다.

[\mathcal{V} = [\mathcal{V}_1, …, \mathcal{V}_c]]

이제 우리는 아래와 같이 $c$ 개의 subgraph를 갖게 되었다.

[\bar{G} = [\mathcal{G}_1, …, \mathcal{G}_c] = [{ \mathcal{V}_1, \mathcal{E}_1}, …]]

위 규칙에 맞추어 Adjacency Matrix도 나누어보면 아래와 같은 구조를 생각할 수 있다.

[A = \bar{A} + \Delta = \left[ \begin{matrix} A_{11} & … & A_{1c}
\vdots & \ddots & \vdots
A_{c1} & … & A_{cc}
\end{matrix} \right]]

[\bar{A} = \left[ \begin{matrix} A_{11} & … & 0
\vdots & \ddots & \vdots
0 & … & A_{cc}
\end{matrix} \right],

\Delta = \left[ \begin{matrix} 0 & … & A_{1c}
\vdots & \ddots & \vdots
A_{c1} & … & 0
\end{matrix} \right]]

그러니까 $A_{1c}$ 는 파티션1에 속한 node와 파티션c에 속한 node 사이의 link를 담은 Adjacency Matrix인 것이다.

$\bar{A}$ 와 $\Delta$ 의 가치는 명확히 다르다. 앞서 기술하였듯이 within-link edges를 많이 갖고 있는 Batch를 구성하기 위해서는 $\bar{A}$ 를 잘 구성하는 것이 중요하다고 볼 수 있다.

$\bar{A}$ 를 정규화하면 $A^{\prime} = D^{-1} A$ 가 된다.

최종 Embedding Matrix는 아래와 같이 구성된다.

Loss 함수는 아래와 같이 작성할 수 있다.

[\mathcal{L}{\bar{A}^{\prime}} = \Sigma_t \frac{\vert \mathcal{V}_t \vert}{N} \mathcal{L}{\bar{A}^{\prime}{tt}}, \mathcal{L}{\bar{A}^{\prime}{tt}} = \frac{1}{\vert \mathcal{V}_t \vert} \Sigma{i \in \mathcal{V}_t} loss(y_i, z_i^{L})]

그렇다면 $c$ 개의 그룹으로 나누는 기준은 무엇일까? 본 논문에서는 within-clusters links가 between-cluster links 보다 더 많도록 클러스터를 나누기 위해 METIS라는 Graph 군집화 방법론을 적용했다고 밝히고 있다. 원문은 이 곳에서 확인할 수 있다.

위 그림을 보면 더 깊은 Layer로 진행하면서도 설정한 Cluster의 범위를 벗어나지 않는 ClusterGCN의 특성을 확인할 수 있다.

ClusterGCN의 경우 각 Batch 마다 $\bar{A}^{\prime}_{tt} X_t^{(l)} W^{(l)}$ 와 몇몇 부가적인 계산 만 수행하면 되기 때문에 수행 시간 상의 큰 이점을 누린다. 또한 오직 subraph에 해당하는 부분만 GPU 메모리에 올리면 되기 때문에 메모리 Overhead가 발생할 가능성도 줄어든다.


3. Stochastic Multiple Partitions

이전 Chapter까지의 내용만 보고 ClusterGCN을 적용하려고 하면, 아래와 같이 2개의 문제가 발생한다.

먼저, Graph를 여러 조각으로 쪼개면서 $\Delta$ 에 대한 손실이 발생한다. 이는 데이터 손실이므로 성능에 있어 이슈가 발생할 수 있다.

다음으로 Graph 군집화 알고리즘이 유사한 Node를 모아주므로 Cluster의 분포는 원본 데이터의 분포와는 분명 다를 것이기 때문에, 최종적으로 Full Gradient를 계산하여 반환한 결과물의 경우 bias가 발생할 수 있다.

이를 해결하기 위해 Stochastic Multiple Partitions라는 방법론을 도입한다. Graph를 $p$ 개로 나눈다고 하면, 여기서 1개를 선택해서 Batch로 돌리는 것이 아니라 이 중 $q$ 개를 다시 선택해서 이들을 통합한 뒤 하나의 Batch로 취급한다. 즉, 원래 1개만 쓸 것을 여러 개를 합쳐서 쓴다는 의미이다. 이를 통해 Batch 사이의 분산은 줄이면서 between-cluster links는 통합하는 효과를 거둘 수 있다. 실제로 아래 그림을 보면 이와 같은 방법이 효과적이라는 것을 알 수 있다.


4. Issues of training deeper GCNs

본 논문에서는 더욱 깊은 GCN을 학습시키기 위한 간단한 방법을 제시한다. 직관적으로 생각했을 때, 인접한 위치에 있는 node는 멀리 떨어진 node보다 더 큰 영향력을 행사해야 하므로, 각 GCN Layer에서 사용되는 Adjacency Matrix의 대각 원소의 영향력을 더 확대하는 방안이 도입될 수 있다. 즉, 각 GCN Layer의 통합 과정에서 이전 layer에서 넘어온 representation에 더욱 큰 가중치를 부여하는 것이다. 그런데 이 때 그냥 Identity Matrix를 더하면 layer가 증가함에 따라 numerical instability가 지수 함수적으로 커질 수 있기 때문에 이를 고려햐여 아래와 같은 방법이 제안된다.

[\tilde{A} = (D + I)^{-1} (A + I)]

[X^{(l+1)} = \sigma ( (\tilde{A} + \lambda diag (\tilde{A})) X^l W^l )]

최종적으로 ClusterGCN 알고리즘을 정리해보면 아래와 같다.

학습 과정과 실험 과정의 경우 논문 원본을 직접 참고하길 바란다. 특징적인 부분은, 실험 데이터셋으로 새롭게 Amazon2M이라는 데이터가 추가적으로 사용되었다는 것이다. 이 데이터 속에서 node는 상품이고, 같은 장바구니에서 구매되었으면 상품 끼리의 link가 존재한다는 설정이 도입되었다.

0.01의 Learning Rate을 가진 Adam Optimizer와 0.2의 Drop Rate, 그리고 512의 Batch Size를 사용했다는 점은 기억해둘 필요가 있으며, 모든 실험은 NVIDIA Tesla V100 GPU (16GB Memory), 20-core Intel Xeon CPU와 192GB의 RAM 환경에서 이루어졌다. Memory 사용량을 확인하기 위해서 Tensorflow의 경우 tf.contrib.memory_stats.BytesInUse(), Pytorch의 경우 torch.cuda.memory_allocated() 함수를 사용하였다고 밝히고 있다.

마지막으로 언급할 부분은 구현할 때 고려해야할 부분이다. ClusterGCN이 기반으로 하고 있는 Layer의 경우 $D^{-1}AX$ 를 첫 번째 Layer에서 미리 계산해두면 이후에 재사용함으로써 시간을 크게 절약할 수 있기에 이 부분은 반드시 구현하는 것이 필요하다. 그리고 학습 시에는 테스트용 node의 경우 Adjacency Matrix 및 subgraph에서 아예 제거하고, 최종적으로 Test Performance를 확인할 때 다시 삽입하는 방식으로 진행되었다.

그리고 ClusterGCN은 그 구조적 특징 때문에 새로운 node가 들어오는 Inductive한 예측 환경에서는 유연하게 대처할 수 없다는 한계는 지니고 있다.

Comment  Read more

Graph Fourier Transform 설명

|

본 글에서는 Graph Neural Networks 이론의 근간 중 하나라고 할 수 있는 Graph Fourier Transform에 대해 설명할 것이다.
Notation의 경우 최대한 가장 자주 쓰이는 형태를 고려하여 작성하였고, 글로써 완전히 전달하는 것이 어렵기 때문에 여러 자료들을 함께 참고하길 바라며, 관련 강의를 들을 수 있다면 더욱 좋을 것이다.


Graph Laplacian

위와 같은 Graph $\mathcal{G}$ 가 존재한다고 할 때, 각 node $v$ 는 feature를 갖고 있다.
각각의 node가 갖고 있는 feature를 그 node의 signal이라고 설정해 볼 때, node $v_1$ 의 signal은 $f_1$ 이라는 함수에 의해 정의된다.

node의 집합 $\mathcal{V}=[v_1, v_2, v_3, v_4]$ 에 대한 node feature matrix는 $(4, d)$ 형태의 2차원 행렬일 것인데,

이 행렬의 각 행이 한 node에 대한 signal이라고 생각해보면 아래와 같이 표현할 수 있다.

[\mathcal{V} \rightarrow \left[\begin{matrix} f_1\f_2\f_3\f_4\ \end{matrix} \right] = \mathbf{f}]

이 Graph의 인접 행렬(Adjacency Matrix)를 표현하면 아래와 같다.

[A = \left[ \begin{matrix} 0 & 1 & 1 & 0
1 & 0 & 1 & 0
0 & 1 & 1 & 1
0 & 0 & 1 & 0
\end{matrix} \right]]

그리고 Graph의 Degree Matrix는 $D$ 이며 이 두 행렬을 이용하여 Laplacian Matrix를 정의한다.

[\mathbf{L} = \mathbf{D} - \mathbf{A}]

Laplacian Matrix를 difference operator로 활용해보자.

위 예시를 적용해보면 아래와 같이 쓸 수 있다.

[h_2 = 2 f_2 - f_1 - f_3]

이를 일반화하여 적어보면 다음과 같다.

[h_i = \Sigma_{j \in N_i} (f_i - f_j)]

이어서 이를 Quadratic Form으로 재표현한 과정은 아래와 같다.

마지막 줄을 보면, 결국 위 식에서 남는 것은 node $i$ 와 $j$ 사이의 연결이 존재할 때, $f_i - f_j$ 의 값이 작으면 연결된 node의 signal이 유사하다는 의미로 생각할 수 있다.

참고로 $\mathbf{D}^{-1} \mathbf{A}$ 혹은 $\mathbf{D}^{-\frac{1}{2}} \mathbf{A} \mathbf{D}^{-\frac{1}{2}}$ 같은 경우는 Transition Matrix라고도 부른다.


Eigen-decomposition of Laplacian Matrix

지난 Chapter에서 정의했던 Laplacian Matrix $\mathbf{L} = \mathbf{D} - \mathbf{A}$ 에 고유값 분해를 적용해보자.

하나의 eigen value에 대해서 다시 살펴보면 아래와 같고, 이 때 $\lambda_i$ 가 클수록 signal $u_i$ 의 frequency가 크다고 해석할 수 있다.

[\mathbf{u}_i^T \mathbf{L} \mathbf{u}_i = \mathbf{u}_i^T \lambda_i \mathbf{u}_i = \lambda_i]


Graph Fourier Transform

푸리에 변환은 다양한 분야에서 활용되는 굉장히 중요한 개념인데, Graph 이론에서도 변형되어 사용된다.
푸리에 변환의 개념을 간단히만 설명하자면, 어떤 입력 signal을 여러 종류의 frequency를 갖는 함수들의 합으로 표현하는 것이라고 볼 수 있다.
자세한 설명은 이 곳을 참조할 것을 권한다.

그러니까 우리는 Graph를 표현하는 어떤 signal이 존재할 때, 이를 우리가 표현할 수 있는 어떤 함수들의 합으로 의미있게 표현하고 싶은 것이다.

핵심부터 말하자면, $\mathbf{L}$ 의 eigen space $\mathcal{F}$ 에 속해있는 $\mathbf{f} \in \mathcal{F}$ 이 존재할 때, 이를 $\hat{\mathbf{f}}$로 변환하는 것을 Graph Fourier Transform라고 한다.

Graph Fourier Mode 혹은 Basis Graph로 $\mathbf{u_i}$ 를 설정하자, 이 벡터는 사실 Graph Laplacian Matrix에서의 orthogonal eigen vector이다.
참고로 $\lambda_i$ 는 frequency, $\hat{f_i}$ 를 Graph Fourier 계수를 의미한다.

Graph Fourier Transform은 아래와 같이 정의한다.

[\hat{\mathbf{f}} = \mathbf{U}^T \mathbf{f} = \Sigma_i f_i \mathbf{u_i}]

[\hat{f_i} = \mathbf{u_i}^T \mathbf{f}]

이 과정은 $\mathbf{f}$ 를 $\mathcal{F}$ 로 projection하는 것을 의미한다. 즉 기존에 존재하던 Graph Signal을 Graph Laplacian Matrix를 통해 새롭게 정의한 eigen space로 투사하는 것이다. 이 과정은 또한 signal을

앞서 설명하였듯이 $\hat{\mathbf{f_i}}$ 는 Graph Fourier Transform의 결과물인 Graph Fourier 계수 벡터인데, Graph Fourier Transform의 반대 방향 진행은 Inverse Graph Fourier Transform라고 부르며, 다음과 같이 정의한다.

[\mathbf{f} = \mathbf{U}^T \hat{\mathbf{f}} = \Sigma_i \hat{f_i} \mathbf{u_i}]


Spectral Graph Filtering

본 Chapter의 내용은 이후에 GFT를 어떻게 적용하느냐에 관한 내용인데, 간단하게만 짚고 넘어가겠다. 추후 다른 글에서 자세히 다루도록 할 것이다.

GFT를 통해 우리가 얻고자 하는 것은, Graph 내에 있는 어떠한 중요한 특성을 포착하는 것이다. 그리고 이 목적은 Graph Filtering이라는 과정을 통해 구체화할 수 있다.

Graph Filtering은 크게 Spectral Filtering과 Spatial Filtering으로 구분할 수 있으며, spectral이란 단어는 Graph 인접 행렬의 eigenvalue와 eigenvector를 구한다는 뜻을 내포한다.

1번 과정에서 GFT를 적용하여 Signal을 변환하여 Graph Fourier 계수를 얻었다. 이후 2번 과정에서 $\hat{g(\Lambda)}$ 으로 정의되는 필터를 곱한 후 IGFT 과정을 통해 최종 결과물을 얻는다.

위 과정은 앞서 기술하였듯이 Input Signal $\mathbf{f}$ 혹은 Input Data $\mathbf{X}$ 가 주어졌을 때, 특정한 필터를 선택하여 곱합으로써 이 Input에서 중요한 특성을 추출하는 의미를 갖는다.

이를 다시 표현하면,

[g_{\theta} * x = \mathbf{U} g_{\theta} \mathbf{U}^T x]

여기서 $\theta$ 는 파라미터를 의미하는데, 이 파라미터의 학습 가능 여부에 따라 다른 방식으로 문제를 풀어나갈 수 있다.

이후 위 식을 통해서 Graph Convolution을 정의하는 과정으로 내용이 이어지게 된다.


References

  1. GCN 논문
  2. CS224W Spectral Clustering 강의
  3. 푸리에 변환 설명 블로그 글
Comment  Read more

Minikube 설치하기

|

본 글은 Windows10 환경에서 Minikube를 설치하는 과정을 기록한 글이다.

이미지가 잘 보이지 않는다면, 새 탭에서 이미지 열기를 누르시길 바랍니다.

1. Minikube 설치 및 동작 확인

Minikube 설치는 본 문서를 참고하였다. 명령 프롬프트를 관리자 권한으로 실행한 후 아래와 같은 메시지를 출력해보자.

systeminfo

아래와 같은 문구를 발견하였다면 Hypervisor를 사용할 준비가 된 것이다.

만약 펌웨어에 가상화 사용이 아니오로 되어 있다면 BIOS 모드로 들어가서 이를 변경해주어야 한다.

설치 문서에 따르면 Hypervisor로 Hyper-V 또는 VirtualBox를 선택할 수 있다고 한다. 필자의 경우 Windows Home을 사용하고 있었기 때문에 VirtualBox를 설치하였다. 설치 링크는 이 곳이며 아래 색칠된 링크를 클릭하여 다운로드를 진행하면 된다.

이제 Chocolatey를 이용하여 Minikube를 설치해보자.

물론 Chocolatey를 사용하지 않아도 설치할 수 있지만, 과정이 상당히 간단하므로 이를 추천한다. Chocolatey 설치 링크는 이 곳이며 아래와 같은 Command를 입력하면 된다.

Chocolatey 설치가 완료되었으면 아래 Command를 통해 Minikube를 설치한다.

이제 Minikube를 구동시킬 차례이다. 시작/작업/중지/삭제 과정을 거치게 되는데 작업을 제외한 커맨드는 아래와 같다.

minikube start
minikube stop
minikube delete

위 그림은 Minikube를 start한 후 stop 했을 때 까지의 과정을 보여주며, start 이후 status command를 입력했을 때 2번째 그림과 같은 화면을 확인할 수 있다.

본격적인 작업은 다음 Chapter에서 설명하도록 하고, 일단 중지한 Minikube 클러스터를 삭제해보자.

당황스러운 메시지를 확인할 수 있는데, 이는 지금 Hypervisor가 실행되고 있기 때문이다. 작업 관리자를 켜서 모두 종료해준다.

다시 시도해보면 아래와 같이 삭제가 완료되었음을 알 수 있다.

2. Kubernetes, Kustomize, k9s

Minikube를 이용하면 복잡한 Kubernetes 설치 과정을 생략할 수 있다.

Kustomize는 Kubernetes 오브젝트의 배포를 보조하는 도구인데, Kubernetes 1.14 버전 이상을 사용하면 따로 설치가 필요하지 않다. 즉, 지금까지의 과정을 마쳤으면 Kubernetes, Kustomize 까지 준비가 된 것이다.

일반적으로 Kubernetes를 사용하기 위해서는 kubectl이라는 커맨드 라인 툴을 사용하는데, 로그 및 모니터링 등 작업에 대한 편의성이 떨어지기 때문에 k9s라는 툴이 사용된다고 한다. k9s는 이 곳에서 다운로드 받을 수 있다.

앞서 Windows 환경에 적합한 chocolatey를 설치했기 때문에 이를 이용하여 설치를 진행해보자. 이렇게 간단할 수가 없다.

choco install k9s

성공적으로 설치가 완료되었다. 다음을 입력해보면,

k9s

위와 같은 초기 화면을 확인할 수 있다. 우측 상단에는 여러 단축키가 보이는데, 이를 활용하면 편리하게 명령을 실행할 수 있다. 콜론(:)을 누르면 명령어를 입력할 수 있게 된다. 네임스페이스의 리스트를 확인하기 위해 ns를 입력해보자.

kube-system을 방향키로 선택한 후 엔터를 입력하면 이 곳에 속한 파드 리스트를 확인할 수 있다.

References

  1. 쿠버네티스에서 머신러닝이 처음이라면! 쿠브플로우!, 이명환 저
  2. Minikube 설치 문서
  3. k9s 깃헙
Comment  Read more

Kubeflow 튜토리얼1

|

본 글은 Local 환경에서 Standard 모드로 설치한 Kubeflow에 대한 튜토리얼 내용을 담고 있다. WSL2-Ubuntu 환경에서의 Kubeflow 설치 방법에 대해 확인하고 싶다면 이전 글을 참고하길 바란다.


1. Notebook Servers

1.1. 노트북 생성

노트북 서버는 Kubernetes 위에서 실행되는 Jupyter Notebook 서버를 의미한다.

+NEW SERVER를 눌러 필요한 설정에 맞게 항목을 입력해준 뒤, Launch 버튼을 클릭하면 노트북 생성이 시작된다.

생성 완료 후 화면은 아래와 같다.

필자는 Dashboar에 처음 접속할 당시 Namespace를 Youyoung으로 지정하였다. 따라서 방금 생성한 노트북은 이 Namespace 아래에 생성된다. 확인해보자.

kubectl get pods –all-namespaces
kubectl get pods -n Youyoung

방금 생성한 testgraph가 보인다.

1.2. 노트북 사용

위 화면에서 CONNECT를 클릭하면 익숙한 Jupyter 환경이 보인다.


2. Pipeline Quickstart

2.1. Data Passing in python components 튜토리얼 실행

Kubeflow 파이프라인은 컨테이너 기반의 ML 워크플로우를 생성/배포할 수 있게 해주는 툴이다. 확장성과 재사용성이 좋아 편리하게 사용할 수 있다. 첫 튜토리얼을 위해서는 공식 문서 가이드에 친절한 설명을 따라가면 된다. 튜토리얼 파이프라인을 클릭해보자.

이후 +Create Experiment 버튼을 클릭하고 Experiment Name에 My experiment를 입력해주자. 다음 화면에서 Run NameMy First Run으로 해준 후, Start 버튼을 누르자.

잠시 기다린 후 Run name을 클릭하면 아래와 같이 실행된 Graph의 Component들을 확인할 수 있다.

Config 탭을 누르면 세부 사항을 확인할 수 있다.

2.2. Source 코드 확인

소스코드 페이지를 확인해보고 넘어가자. 먼저 서두의 주석을 확인하자.

1) Kubeflow 파이프라인은 Component 인스턴스를 생성하고 이들을 연결함으로써 구성됨  
2) 각 Component는 Input/Output을 가짐. Component 간 연결은 이 Input/Output 연결을 통해 이뤄짐  
3) 한 Task의 Output을 다른 Task의 Input에서 argument로 취급함  

데이터는 Small DataBigger Data가 존재한다. Small Data는 커맨드 라인의 인자로 전달되며 수 킬로바이트를 초과하면 안된다. 예를 들면 숫자, URL, 칼럼 명 등이 이에 해당할 것이다. 작은 리스트나 딕셔너리 혹은 JSON 구조도 괜찮지만 용량 체크는 필수적이다.

Small Data는 string으로 serialized 되었다가 커맨드 라인 인자로 전달될 때 deserialized 되는데, str, int, float, bool, list, dict의 경우 빌트인 serializer를 통해 이 과정이 자동으로 수행되지만 그 외의 경우 직접 data를 반환하기 전에 serialized 되어 있어야 한다.

Bigger Data는 파일로부터 읽기/쓰기된다. 이 때 Input/Output 파일은 문자열로서 함수로 전달된다. InputPath 파라미터를 쓰면 함수는 이에 상응하는 input data를 파일로서 consume한다. 데이터는 다운로드 된 후 로컬 파일로 쓰여진 후, 그 위치(path)를 함수에게 전달할 것이다. OutputPath 파라미터는 반대로 output data를 파일로 생성하고 이를 storage 시스템에 업로드하여 이후의 components에게 전달될 수 있도록 하는 역할을 수행한다. 전달되는 데이터의 Type 역시 명시적으로 지정할 수 있다. OutputPath('TFModel')과 같이 말이다.

자 이제 Bigger Data를 쓰기/읽기 해볼 것인데, 먼저 이전에 확인했던 예제 Graph의 구조를 다시 한 번 확인해보자.

Repeat line, Print Text 부분을 먼저 살펴보자.

from typing import NamedTuple
import kfp
from kfp.components import func_to_container_op, InputPath, OutputPath

# Writing bigger data
@func_to_container_op
def repeat_line(line: str, output_text_path: OutputPath(str), count: int = 10):
    '''Repeat the line specified number of times'''
    with open(output_text_path, 'w') as writer:
        for i in range(count):
            writer.write(line + '\n')

# Reading bigger data
@func_to_container_op
def print_text(text_path: InputPath()):
    # The "text" input is untyped so that any data can be printed
    '''Print text'''
    with open(text_path, 'r') as reader:
        for line in reader:
            print(line, end = '')
            
# 먼저 repeat_line, print_text 함수를 정의한다.
# 이 때 각각의 함수는 인자로 OutputPath와 InputPath를 사용하는 것에 주목하자.
# repeat_line의 경우 OutputPath에 쓸 대상을 전달하고,
# print_text의 경우 InputPath에 읽을 대상을 전달한다.

# 이제 실제로 실행시킬 함수를 정의하자
def print_repeating_lines_pipeline():
    repeat_lines_task = repeat_line(line='Hello', count=5000)
    print_text(repeat_lines_task.output) # Don't forget .output !

# Submit the pipeline for execution:
kfp.Client(host=kfp_endpoint).create_run_from_pipeline_func(
    print_repeating_lines_pipeline, arguments={})

다음 단계로 넘어간다.

# ### Processing bigger data
@func_to_container_op
def split_text_lines(
    source_path: InputPath(str),
    odd_lines_path: OutputPath(str),
    even_lines_path: OutputPath(str)
    ):
    with open(source_path, 'r') as reader:
        with open(odd_lines_path, 'w') as odd_writer:
            with open(even_lines_path, 'w') as even_writer:
                while True:
                    line = reader.readline()
                    if line == "":
                        break
                    odd_writer.write(line)
                    line = reader.readline()
                    if line == "":
                        break
                    even_writer.write(line)

def text_splitting_pipeline():
    text = '\n'.join(['one', 'two', 'three', 'four', 'five',
        'six', 'seven', 'eight', 'nine', 'ten'])
    split_text_task = split_text_lines(text)
    print_text(split_text_task.outputs['odd_lines'])
    print_text(split_text_task.outputs['even_lines'])

# Submit the pipeline for execution:
kfp.Client(host=kfp_endpoint).create_run_from_pipeline_func(
    text_splitting_pipeline, arguments={})

이제 마지막 단계이다.

# Writing many numbers
@func_to_container_op
def write_numbers(numbers_path: OutputPath(str), start: int = 0, count: int = 10):
    with open(numbers_path, 'w') as writer:
        for i in range(start, count):
            writer.write(str(i) + '\n')


# Reading and summing many numbers
@func_to_container_op
def sum_numbers(numbers_path: InputPath(str)) -> int:
    sum = 0
    with open(numbers_path, 'r') as reader:
        for line in reader:
            sum = sum + int(line)
    return sum


# Pipeline to sum 100000 numbers
def sum_pipeline(count: int = 100000):
    numbers_task = write_numbers(count=count)
    print_text(numbers_task.output)

    sum_task = sum_numbers(numbers_task.outputs['numbers'])
    print_text(sum_task.output)


# Submit the pipeline for execution:
kfp.Client(host=kfp_endpoint).create_run_from_pipeline_func(
    sum_pipeline, arguments={})

# Combining all pipelines together in a single pipeline
def file_passing_pipelines():
    print_repeating_lines_pipeline()
    text_splitting_pipeline()
    sum_pipeline()

if __name__ == '__main__':
    # Compiling the pipeline
    kfp.compiler.Compiler().compile(file_passing_pipelines, __file__ + '.yaml')

2.3. pods status 확인

만약 어떤 pipeline을 실행하고 있는 과정에서 아래와 같이 Status가 Pending execution이라면 Container를 생성하고 있는 중일 것이다.

아래 명령어를 통해 본인이 생성한 Kubeflow의 Namespace에 있는 pods의 상태를 확인할 수 있다.

kubectl get pods -n {YOUR_NAMESPACE}

시간이 지나면 위와 같이 running 상태로 바뀔 것이다.


References

Kubeflow Pipelines QuickStart

Comment  Read more