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

Self Attention

|

A Structured Self-Attentive Sentence Embedding

본 글은 Zhouhan Lin, Minwei Feng, Cicero Nogueira dos Santos, Mo Yu, Bing Xiang, Bowen Zhou, Yoshua Bengio가 2017년에 Publish한 위 논문을 리뷰한 것이다.
특별히 본 글은 Presentation에 사용된 PDF 발표자료로 대체하겠다.

Comment  Read more

YOLOv2

|

You Only Look Once: Unified, Real-Time Object Detection

본 글은 Joseph Redmon, Santosh Divvala, Ross Girshick, Ali Farhadi가 2016년에 Publish한 위 논문을 리뷰한 것이며, 추가적으로 구현을 위한 Loss Function 설명과 코드를 첨부하였다.

Unified Detection

YOLO version2는 기본적으로 end-to-end training 방법을 취하고 있으며 학습이 완료되었을 때 실시간으로 테스트가 가능할 만큼 빠른 속도를 보이는 강점을 갖고 있다.
본 글은 416 X 416 이미지를 기준으로 설명을 진행하도록 하겠다.
YOLO의 핵심은 이미지를 Grid Cell로 나누어 각각의 Cell이 Object Detection을 위한 정체성을 갖게끔 만든다는 것이다.
예를 들어 416 X 416의 이미지는 아래와 같은 Darknet이라는 CNN 구조를 거치게 된다.
(참고로 아래는 Pytorch 기준으로 Channels_first를 적용하였다.)

layer size
0 [None, 3, 416, 416]
1 [None, 32, 208, 208]
2 [None, 64, 104, 104]
5 [None, 128, 52, 52]
8 [None, 256, 26, 26]
13 [None, 512, 13, 13]
18 [None, 1024, 13, 13]
19 [None, 1024, 13, 13]
20 [None, 1024, 13, 13]
skip: [None, 64, 26, 26] ->
skip: [None, 256, 13, 13]
21 [None, 1280, 13, 13]
22 [None, 1024, 13, 13]
23 [None, 35, 13, 13]

Output으로 나온 [35, 13, 13]에서 13 X 13은 Grid의 size이다. 35는 추후에 설명하겠다.

출처: https://blog.paperspace.com/how-to-implement-a-yolo-object-detector-in-pytorch/

위 그림과 같이 정리된 13 X 13 = 169개의 Cell은 이제 각각 Object을 detect하기 위한 정체성을 갖게 된다. 만일 실제(Ground-truth: GT) Object의 중심 좌표(center coordinate)가 Cell 내부에 위치한다면, 그 Cell은 해당 Object를 detect할 책임을 지게 되는 것이다.
(If the center of an object falls into a grid cell, that grid cell is reponsible for detecting that object)

각각의 Grid Cell은 이제 5개의 bbox를 예측하게 되고, 각각의 box에 대해 confidence score를 계산하게 된다. 5개는 YOLOv2에서 정한 숫자이고, YOLOv3에선 총 9개가 등장하게 된다.

자세한 설명을 위해 35라는 숫자에 대해 부연 설명을 하도록 하겠다.

[35 = 5 * (1 + 4 + 2)]

5 = bbox 개수
1 = box_confidence = P(Object) * IOU_truth_pred
4 = boxes = box coordinates (bounding box 좌표 4개: x, y, w, h)
2 = box_class_probs (예측하고자 하는 class의 개수와 길이가 같다.)

box_confidence는 그 Cell에 Object가 있을 확률에 IOU_truth_pred를 곱하게 되는데, P(Object)는 당연히 0 또는 1이다. 이 값에 GT_bbox(truth)와 pred_bbox(pred)의 IOU를 계산하여 곱해주면 box_confidence가 되는 것이다. P(Object)가 0일 경우 이 값은 물론 0이 된다.

boxes의 경우 bbox 좌표를 뜻하는데, 후에 IOU를 계산할 때에는 이와 같이 중심 좌표(x, y)와 box 길이(w, h)를 기준으로 계산하는 것이 불편하기 때문에 왼쪽 상단 좌표(x1, y1)과 오른쪽 하단 좌표(x2, y2)로 고쳐주도록 한다.

def box_to_corner(box1, box2):
    """
    abs_coord 형식인 bbox의 [x, y, w, h]를 [x1, y1, x2, y2]로 변환한다.
    :param box1: [..., 4]
    :param box2: [..., 4]
    :return: [..., 1] X 8
    """
    b1_x, b1_y, b1_w, b1_h = box1[..., 0], box1[..., 1], box1[..., 2], box1[..., 3]
    b2_x, b2_y, b2_w, b2_h = box2[..., 0], box2[..., 1], box2[..., 2], box2[..., 3]

    b1_x1, b1_x2 = b1_x - b1_w/2, b1_x + b1_w/2
    b1_y1, b1_y2 = b1_y - b1_h/2, b1_y + b1_h/2

    b2_x1, b2_x2 = b2_x - b2_w/2, b2_x + b2_w/2
    b2_y1, b2_y2 = b2_y - b2_h/2, b2_y + b2_h/2

    return b1_x1, b1_x2, b1_y1, b1_y2, b2_x1, b2_x2, b2_y1, b2_y2

참고로 현재 https://github.com/KU-DIA/BasicNet에서 관련 코드를 확인할 수 있다.

이제 box_class_probs를 보면 이 값은 P(Class_i | Object)를 뜻하는데, Object가 있을 경우 i번째 Class일 조건부 확률을 뜻한다. 사실 이 값만으로는 추후 과정 진행이 어렵기 때문에 위에서 구한 box_confidence와 이 box_class_probs를 브로드캐스팅 곱을 통해 계산해주면,

class_scores = box_confidence * box_class_probs
             = P(Object) * IOU * P(Class_i | Object)
             = P(Class_i) * IOU

위와 같이 class_scores를 구할 수 있다.
이 class_scores 텐서는 본인이 설정한 Class 수만큼의 길이를 가지는데(정확히 “길이”는 아니지만), 본 예에서는 2로 설정하였기 때문에 앞으로도 2라는 숫자를 계속 사용하도록 하겠다.

이 class_scores는 각각의 box가 가지는 class-specific confidence score를 의미하게 된다. 만약 설정한 Class를 사람, 고양이라고 한다면, 각 class_scores 텐서는 그 Cell이 “사람”을 detect할 확률, “고양이”를 detect할 확률을 담고 있는 것이다.

이후에 여러 과정이 추가되기는 하지만, 본질적으로 이렇게 표현된 class_scores에서 가장 높은 값을 갖는 class_index를 찾아 output으로 반환하게 된다.

다시 위로 돌아가서 [None, 35, 13, 13] 구조에서, 35는 5 X 7이라는 것을 확인하였다.
바로 위에서 7은 1(box_confidence), 4(bbox 좌표), 2(box_class_probs)로 분해되는 것을 보았는데,
1과 2가 브로드캐스팅 곱을 통해 길이 2의 class_scores로 정리되었다.
위 class_scores는 cell 기준으로 존재하는데, cell 하나당 5개의 bbox를 갖고, 이러한 cell은 총 13 X 13개 있으므로, 이제 우리는 13 X 13 X 5개의 class_scores 텐서를 갖게 되었다.

그런데 그냥 이런식으로 진행하게 되면, 845개의 텐서가 등장하는데, 너무 많다.
이제부터는 텐서의 수를 효과적으로 줄여 학습을 준비하는 과정에 대해 설명하겠다.
사실 아예 삭제하는 것은 아니고, 필요없는 텐서의 값들을 죄다 0으로 바꿔주는 작업이다.

  1. filter_by_confidence
    def filter_by_confidence(confidence, boxes, class_probs, threshold=0.6):
     """
     confidence가 threshold보다 낮은 경우 제거해준다.
     남은 confidence와 class_probs를 곱하여 class_scores를 생성한다.
     :param confidence: (None, 1)
     :param boxes: (None, 4)
     :param class_probs: (None, C)
     :param threshold: 필터링 threshold
     """
     confidence[confidence < threshold] = 0.0
        
     (하략: class_scores를 계산하여 반환함)
    

용어를 정리하자면, confidence = box_confidence, class_probs = box_class_probs이다.
위 함수는 일정 threshold보다 작은 box_confidence를 갖는 box_confidence를 아예 0으로 바꿔준다.
왜냐하면 P(Object)가 0에 근접할 경우, background(Object가 없음)라는 의미인데, 이들의 bbox를 찾는 것은 의미가 없기 때문이다.

  1. Non_max_suppression
    def nms(boxes, class_scores, threshold=0.6):
     """
     :param boxes: bbox 좌표, (None, 4)
     :param class_scores: confidence * class_prob_scores, 클래스 별 score, (None, C)
     :param threshold: NMS Threshold
     """
    
     for class_number in range(C):
         target = class_scores[..., class_number]
    
         # 현재 class 내에서 class_score를 기준으로 내림차순으로 정렬한다.
         sorted_class_score, sorted_class_index = torch.sort(target, descending=True)
    
         # idx: 아래 bbox_max의 Index
         # bbox_max_idx: 정렬된 class_scores를 기준으로 가장 큰 score를 가지는 bbox Index
         for idx, bbox_max_idx in enumerate(list(sorted_class_index.numpy())):
             # 기준 class_score가 0이라면 비교할 필요가 없다.
             # 아래 threshold 필터링에서 0으로 바뀐 값이기 때문이다.
             if class_scores[bbox_max_idx, class_number] != 0.0:
                 # 0이 아니라면 순서대로 criterion_box(bbox_max)로 지정된다.
                 bbox_max = boxes[bbox_max_idx, :]
    
                 # criterion_box(bbox_max)가 아닌 다른 box들을 리스트로 미리 지정한다.
                 #others = [index for index in list(sorted_class_index.numpy()) if index != i]
                 others = list(sorted_class_index.numpy())[idx+1:]
    
                 # 비교 대상 box들에 대해서
                 # bbox_cur_idx: 비교 대상 bbox Index
                 for bbox_cur_idx in others:
                     bbox_cur = boxes[bbox_cur_idx, :]
                     iou = get_iou(bbox_max, bbox_cur)
                     # print(bbox_max_idx, bbox_cur_idx, iou)
    
                     # iou가 threshold를 넘으면 (기준 box와 비교 대상 box가 너무 많이 겹치면)
                     # 그 해당 box의 현재 class의 class_score를 0으로 만들어 준다.
                     if iou > threshold:
                         class_scores[bbox_cur_idx, class_number] = 0.0
    
     return boxes, class_scores
    

사실 Cell 별로 각각 bbox를 5개씩 갖게 되면 인접한 Cell들끼리는 bbox가 마구 겹칠 것이다. 또, bbox의 크기가 충분히 클 경우 이미지 바깥으로 벗어나기도 할 것인데, 한 이미지에 Object 수가 수백개 있는 것이 아닌 이상, 이렇게 많은 bbox는 필요하지 않은 것이 자명하다.

NMS 작업은 이 문제를 효과적으로 해결해준다. 쉽게 말해서 “왕”을 뽑는 느낌인데, 특정 class_scores가 높은 bbox와 과하게 겹치는 (IOU가 높은) 다른 녀석들을 제거하는 것이다.

“사람”을 detect하는 class_scores를 기준으로 class_scores를 내림차순으로 정렬한다. 제일 큰 첫 번째 값과 나머지 값들을 쌍으로 IOU를 계산하여 과하게 겹치는 (기준 threshold)를 넘는 값은 0으로 바꿔준다.

이 과정이 끝나면 [None, 35, 13, 13]이라는 크기 자체는 바뀌진 않지만, 중간중간에 많은 숫자가 0으로 바뀌어 있을 것이다. (1, 2번 기준을 충족하지 못한 값들)
이제 이를 바탕으로 training을 시키면 된다.

Training

위 그림은 YOLOv2의 Loss Function이다. YOLO의 최대 장점은 이렇게 Loss Function을 하나로 통합하여 효과적인 학습을 가능하게 했다는 점이다.

https://curt-park.github.io/2017-03-26/yolo/에서 Loss Function과 관련한 기본적인 설명을 얻을 수 있는데, 추가적으로 설명을 하도록 하겠다.

먼저 1, 2줄은 bbox 좌표에 관한 Loss Function이다. 앞에 있는 $ \lambda_{coord} $는 5로 설정되었다.
$ S^2 $은 Grid Cell의 개수를 의미하며 본 예에서는 13 X 13을 의미한다. $ B $는 정해둔 bbox (anchors) 개수이며 본 예에서는 5를 의미한다. 이렇게 모든 Cell에서 5개 씩의 bbox를 계산하여 GT_bbox와 차이를 좁혀나가는 것이다.

여기서 앵커에 대해 잠깐 설명하자면, 이 앵커는 빠른 학습을 위해 설정된 bbox의 초기값이라고 생각하면 된다. 그냥 무작정 Cell에다가 bbox를 그린다면 그 크기의 편차가 매우 심할 것이다. 미리 object들의 크기를 대략적으로 계산하여 가장 많이 등장할 법한, 가장 유사한 크기의 bbox 크기를 미리 계산해두어 저장한 것이 바로 앵커인데, 이는 보통 Clustering 기법을 통해 미리 계산된다.

이렇게 미리 계산된 앵커를 초기값으로 투입하고, GT_bbox 좌표와의 차이를 빠르게 줄여 나가는 것이 1, 2번째 줄의 목표라고 하겠다.

그리고 $ 1_{i, j}^{obj} $ 라는 Indicator Function의 기능이 매우 중요한데, 이 지시함수는 i번째 Grid Cell에서 j번째 bbox에 Object가 존재할 경우 1이고, 아니면 0이다.

아래의 $ 1{i, j}^{noobj} $ 는 반대의 의미를 가지며, $ 1{i}^{obj} $ 는 오직 Cell 소속 여부와 관련이 있다.

3, 4번째 줄은 Object가 있는지 없는지에 대한 Loss를 계산하게 되고,
5번째 줄은 P(Class_i | Object) = Conditional Class Probability의 Loss를 계산하게 된다.

Conclusion

빠른 속도와 괜찮은 정확도를 가졌지만 YOLOv2의 단점은 작은 물체나 겹치는 물체들을 효과적으로 Localization하지 못한다는 것이다. 이는 version3에서 상당부분 업그레이드 된다.

Comment  Read more

Attention

|

Neural Machine Translation by Jointly Learning to Align and Translate

본 글은 Dzmitry Bahdanau, Kyunghyun Cho, Yoshua Bengio가 2014년에 Publish한 위 논문을 리뷰한 것이다.

Introduction

Basic Encoder-Decoder 모델은 source sentence의 모든 정보를 fixed-length vector로 압축하는 방식을 골자로 한다.
그러나 이 모델은 긴 문장을 대상으로 할 때 어려움을 겪는 것이 일반적이다.
이를 해결하기 위해 본 논문에서 제안된 새로운 모델은 target word를 예측하는 것과 관련된 source sentence의 부분을 자동적으로 soft-search하여 이와 같은 문제를 해결해낸다.
(learns to align and translate jointly)

encodes the input sentence into sequence of vectors and
chooses a subset of these vectors adaptively while decoding the translation.

Background: NMT

translation의 핵심은 source sentence x가 주어졌을 때의 y의 조건부확률을 최대화하는 target sentence y를 찾는 것이다.

[arg\max_{y} p(\vec{y} x)]

번역 모델에 의해 이러한 조건부 분포가 학습되면, source sentence가 주어졌을 때 상응하는 번역된 문장은 위의 조건부 확률을 최대화하는 문장을 찾음으로써 generate된다.

RNN Encoder-Decoder는 이전 리뷰에서 다룬 적이 있으므로 생략하도록 한다.

Learning to align and translate

모델의 구성 요소를 하나하나 살펴보기 이전에 notation에 관한 정리를 진행하겠다.

$y_i$: i time에서의 target word
$s_i$: i time에서의 디코더 Hidden State
$c_i$: i time에서의 context vector = annotations의 가중합
$\alpha_{ij}$: attention weight = normalized score = 연결 확률
$h_j$: j time에서의 인코더 Hidden State = annotations
$e_{ij}$: attention score = unnormalized score
$f, g$ = 비선형 함수

명확히 하자면, subscript i는 디코더를 명시하며, subscript j는 인코더를 명시한다.

이제 모델의 구성 요소를 살펴볼 것이다.
먼거 타겟 word $y_i$를 예측하기 위한 조건부 확률은 아래와 같이 정의된다.

[p(y_i y_1, …, y_{i-1}, \vec{x}) = g(y_{i-1}, s_i, c_i)]

이 중 디코더의 i time Hidden State인 $s_i$를 먼저 살펴보면,

[s_i = f(s_{i-1}, y_{i-1}, c_i)]

Basic Encoder-Decoder 모델과 달리 target word를 예측하기 위한 조건부 확률은 분리된 context vector $c_i$에 의존한다.

Context Vector $c_i$는 annotations $h_j$의 가중합이다.

[c_i = \sum_{j=1}^{T_x} \alpha_{ij} h_j]

여기서 $h_j$는 j time annotation으로, input sequence의 i번째 단어 주위 부분에 강하게 집중하여 input sequence에 대한 정보를 담게 된다.

Bidirectional RNN
이 $h_j$는 forward RNN의 Hidden States와 backward RNN의 Hidden States를 세로로 합친 열벡터이다.

[h_j = [\overrightarrow{h_j}^T \overleftarrow{h_j}^T]^T]

이러한 방식으로 $h_j$는 두 방향 모두로 words들을 요약한 정보를 담게 된다.

이제 attention weight $a_{ij}$가 어떻게 계산되는지 살펴보겠다.

[a_{ij} = \frac{ exp(e_{ij}) } {\sum_{k=1}^{T_x} exp(e_{ik}) }]

이 $a_{ij}$는 Normalized Score라고 할 수 도 있다. 왜냐하면 softmax함수의 확률로서 계산되기 때문이다.

Unnormalized Score인 $e_{ij}$는 아래와 같이 계산된다.

[e_{ij} = a(s_{i-1}, h_j)]

여기서 a함수는 alignment model이다. 이 a를 다른 component와 함께 학습되는 순전파 신경망으로서 모수화한다.
이 alignment model은 j time 인풋이 i time 아웃풋과 얼마나 유사한지를 평가하게 된다.
또한 이 모델은 잠재변수로 설정되지 않고, soft alignment를 직접적으로 계산하여 cost function의 gradient가 역전파될 수 있도록 하게 만든다.
계산 방법은 마지막 부분에서 설명하도록 하겠다.

위 설명을 보면, 결국 i번째 Context Vector인 $c_i$는 expected annotation over all the annotations with probabilities $\alpha_{ij}$라고 할 수 있다.
이 $\alpha_{ij}$는 다음 Hidden State인 $s_i$를 결정하고 target word $y_i$를 generate하는 데에 있어 $h_j$의 중요성을 결정하는 역할을 하게 된다.

즉 이는 일종의 attention이라는 개념으로 설명될 수 있다.
디코더는 source sentence의 어떤 부분에 집중해야 하는지 결정하게 되는 것이다.

Conclusion

제안된 모델은 다음 target word를 generate하는 데에 관련이 있는 정보에만 집중하며 이 때문에 source sentence의 길이에 상당히 robust하다.
다만 unknown or rare words를 다루는 데 있어서는 좀 더 보완이 필요하다.

Details about Model Architecture

이 부분에서는 Appendix에 나와 있는 수식들을 종합하여, 본 논문에서 제안한 RNNSearch라는 모델의 구조에 대해 정리하도록 하겠다.
논문이 굉장히 친절하여 Matrix의 차원이 정확하고 자세하게 나와있으므로 반드시 참고할 필요가 있다.

  1. 상수에 대한 설명은 아래와 같다.
    $m$: word embedding 차원, 본 모델에선 620
    $n$: 인코더/디코더 Hidden Units의 수, 본 모델에선 1000
    $n’$: Alignment Model 내에서의 Hidden Units의 수, 본 모델에선 1000
    $l$: , 본 모델에선 500
    $T_x$: source sentence의 길이
    $K_x$: source language의 vocab_size

  2. 벡터들의 크기는 아래와 같다.
    $y_i$: (k, 1)
    $s_i$: (n, 1)
    $h_i$: (n, 1)
    $v_a$: (n’, 1)
    $z_i$: (n, 1)
    $r_i$: (n, 1)

  3. 행렬들의 크기는 아래와 같다. W, U, C는 모두 Parameter Matrix이다.
    $X$: ($T_x$, $K_x$)
    $Y$: ($T_y$, $K_y$)
    $E$: (m, K), x와 결합할 때는 $K_x$, y와 결합할 때는 $K_y$
    $W$: (n, m), $W, W_z, W_r$에 한정
    $W_a$: (n’, n), Alignment 모델에서 사용
    $U$: (n, n), $U, U_z, U_r$에 한정
    $U_a$: (n’, 2n), Alignment 모델에서 사용
    $C$: (n, 2n), $C, C_z, C_r$에 한정

Encoder
source sentence Matrix X는 번역 대상인 하나의 문장을 뜻한다.
각각의 열벡터는 $\vec{x_i}$로 표기되며 이 벡터의 크기는 $K_x$로,
source language의 vocab_size를 의미한다.

인코더의 Bidirectional RNN은 아래와 같이 계산된다.
(Bias항은 잠시 삭제한다.)

[h_j = (1 - z_i) \odot h_{j-1} + z_i \odot \tilde{h_j}]

위에서 $z_i$가 Update Gate이며, 각 Hidden State가 이전 activation을 유지하느냐 마느냐를 결정한다.

[\tilde{h_j} = tanh(W*Ex_j + U[r_j \odot h_{j-1}])]

위에서 $r_j$가 Reset Gate이며, 이전 State의 정보를 얼마나 Reset할지 결정한다.

[z_j = \sigma(W_z * Ex_j + U_z * h_{j-1})]

[r_j = \sigma(W_r * Er_j + U_x * h_{j-1})]

위에서 계산한 식은 $\overrightarrow{h_j}$, $\overleftarrow{h_j}$ 모두에게 통용되며,
이를 stack하여 annotation $h_j$를 만들게 되는 것이다.

Decoder
디코더의 Hidden State인 $s_i$ 역시 계산 방식은 유사하다.

[s_i = (1 - z_i) \odot s_{i-1} + z_i \odot \tilde{s_i}]

[\tilde{s_i} = tanh(WEx_i + U[r_i \odot s_{i-1}] + Cc_i)]

[z_i = \sigma(W_z * Ex_i + U_z * s_{i-1} + C_zc_i)]

[r_i = \sigma(W_r * Er_j + U_x * s_{i-1} + C_rc_i)]

[c_i = \sum_{j=1}^{T_x}a_{ij}h_j]

[a_{ij} = \frac{ exp(e_{ij}) } {\sum_{k=1}^{T_x} exp(e_{ik}) }]

[e_{ij} = v_a^T tanh(W_a * s_{i-1} + U_a * h_j)]

최종적으로 Decoder State $s_{i-1}$, Context Vector $c_i$, 마지막 generated word $y_{i-1}$을 기반으로, target word $y_i$의 확률을 아래와 같이 정의한다.

[p(y_i s_i, y_{i-1}, c_i) \propto exp(y_i^T W_o t_i)]

즉 오른쪽 편에 있는 스칼라값에 정비례한다는 뜻이다.
잠시 행렬의 차원을 정의하고 진행하겠다.

$W_o$: ($K_y$, $l$)
$U_o$: ($2l$, n)
$V_o$: ($2l$, m)
$C_o$: ($2l$, 2n)
이들은 모두 Parameter이다.

이제 $t_i$를 정의할 것인데, 그 전에 두 배 크기인 candidate $\tilde{t_i}$를 먼저 정의하겠다.

[\tilde{t_i} = U_o * s_{i-1} + V_o * Ey_{i-1} + C_oc_i]

차원을 맞춰보면 위 벡터는 크기가 ($2l$, 1)인 것을 알 수 있을 것이다.
이제 이 벡터에서 아래와 같은 maxout과정을 거치면,

$t_i$는 아래와 같이 정의된다.

[t_i = [ max(\tilde{t_{i, 2j-1}}, \tilde{t_{i, 2j}}) ]_{j=1, …, l}^T]

아주 멋지다.
The End

Comment  Read more

RNN Encoder-Decoder

|

Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation

본 글은 Kyunghyun Cho, Bart van Merrienboer, Caglar Gulcehre, Dzmitry Bahdanau, Fethi Bougares, Holger Schwenk, Yoshua Bengio가 2014년에 Publish한 위 논문을 리뷰한 것이다.

Abstract
Encoder의 역할은 a sequence of symbols를 고정된 길이의 vector representation으로 나타내는 것이고, Decoder의 역할은 그 representation을 다시 sequence of symbols로 나타내는 것이다.

두 모델은 jointly train되는데, 그 목적은 source sequence가 주어졌을 때,
target sequence가 나타날 조건부 확률을 최대화하는 것이다.
= (Maximize the conditional probability of a target sequence given a source sequence.)

Introduction
본 논문은 SMT(Statistical Machine Translation)의 영역에서 모델을 분석하는 것을 주요 목적으로 하고 있다.

연구자들은 memor capacity와 학습 효율을 향상시키기 위해 다소 복잡한(sophiscated) Hidden Unit을 사용하였으며, 영어를 프랑스어로 번역하는 과정을 통해 그 성능을 평가하였다.

본 논문에서 제안한 RNN Encoder의 경우 phrase table에서 언어학적 규칙(regularities)을 잡아내는 역할을 수행하였으며 이는 사실 전반적인 번역 성능의 향상을 꾀하는 과정의 일부로 평가된다. Decoder의 경우 phrase의 continuous space representation을 학습하는데, 이는 phrase의 의미론적(semantic)이고 통사론적(syntactic) 구조를 저장하는 역할을 수행한다.

RNN Encoder-Decoder
기본적인 RNN은 sequence에서 다음 symbol을 예측하는 방향으로 학습됨으로써 sequence의 확률 분포를 학습한다.
이 때 시간t에 대한 output은 $ p(x_t | x_{t-1}, …, x_1) $와 같은 조건부 확률로 표현된다.
따라서 sequence x의 확률은 아래와 같이 표현할 수 있다.

[p(x) = \prod_{t=1}^T p(x_t x_{t-1}, …, x_1)]

(위 $p(x)$의 x는 x vector이며, 위와 같은 식으로 표현되는 이유는 곱셈정리에 의한 것임)
이전의 symbol들에 근거하여 다음 symbol을 예측한다고 볼 수 있다.

본 논문에서 제안하는 RNN Encoder-Decoder 모델은 다소 새로운 구조를 갖고 있다. Encoder는 input sequence x의 원소 symbol들을 연속적으로 읽어 들인다.
(reads each symbol of an input sequence x sequentially)

이 과정 속에서 시간 t의 hidden state는 아래와 같이 업데이트 된다.

[h_{<t>} = f(h_{<t-1>}, x_t)]

즉, 이전 hidden state와 시간t의 새로운 input $x_t$에 의해 업데이트 되는 것이다.
모든 reading이 끝나고 나서 나면 RNN의 hidden state는 모든 input sequence에 대한 summary c이다.

Decoder는 주어진 hidden state $h_{}$을 바탕으로 다음 symbol $ y_{} $를 예측함으로써 output sequence를 생성하도록 학습된다.

다만 여기서 주목할 점은, 기본 RNN과 달리 새로운 hidden state는 summary c
이전 output symbol $ y_{t-1} $에도 conditioned 되어 있다는 것이다.
즉 아래와 같이 표현될 수 있다.

[h_{<t>} = f(h_{<t-1>}, y_{t-1}, c)]

다음 symbol의 조건부 확률 분포는 아래와 같이 나타낼 수 있다.

[P(y_t y_{t-1}, …, y_1, c) = g(h_{<t>}, y_{t-1}, c)]

정리하자면, RNN Encoder-Decoder의 두 성분은 아래의 조건부 로그 가능도를 최대화하도록 결합하여 학습된다.

[\max_{\theta} {1 \over N} \sum_{n=1}^N log p_{\theta} (y_n x_n)]

여기서 $\theta$는 모델 parameter의 집합을 의미하며,
$y_n$은 output sequence를 $x_n$은 input sequence를 의미한다.

이렇게 학습된 RNN Encoder-Decoder는 크게 2가지 방법으로 활용될 수 있다.

  1. 주어진 input sequence에 대해 새로운 target sequence를 생성할 수 있다.
  2. input & output sequences 쌍의 적합성을 평가할 수 있다. (Score)

2.3절인 Hidden Unit that Adatively Remembers and Forgets부분은 LSTM Unit의 기본 형식을 따르고 있기 때문에 식에 대한 리뷰는 생략하겠다. 다만 사용된 용어만을 살펴 보면 아래과 같다.

Reset Gate $r_j$, Update Gate $z_j$, Proposed Unit $h_j^{t}$

효과에 대해 설명하자면,

  1. Reset Gate가 0에 가까워질 때, 시간 t의 Candidate hidden state는 이전 hidden state를 잊고(ignore, forget) 시간 t의 현재 input x로 Reset하게 된다.
  2. Updated Gate는 이전(시간 t-1) hidden state의 정보가 얼마나 현재(시간 t) hidden state에 영향을 줄 것인가를 결정한다. 이를 통해 lont-term 정보를 효과적으로 보존(rembember)한다.
  3. 각각의 hidden unit은 Reset/Update Gate를 각각 갖고 있기 때문에, different time scales에 나타나는 종속성(dependencies)를 포착(capture)하는 법을 학습하게 된다. short-term dependencies를 포착하는 법을 학습한 unit들의 Reset Gate는 자주 활성화 될 것이며, long-term dependencies를 포착하는 법을 학습한 unit들의 Update Gate는 자주 활성화될 것이다.

Statistical Machine Translation: SMT
앞에서도 설명하였듯이 SMT의 기본 목표는 주어진 source sentence에 대하여 translation을 찾는 것이고, 식으로 표현하자면 아래와 같다.

[p(f e) \propto p(e f) * p(f)]

일단 Phrase Pairs를 평가하는 측면에서 본 모델을 살펴보도록 하겠다.
RNN Encoder-Decoder를 학습시킬 때, 기존 말뭉치들에서 각각의 phrase pair들의 출현 빈도는 고려하지 않는다.
그 이유는 2가지로 풀이 된다.

  1. 계산량 감소를 위해서이다.
  2. 본 모델이 단순히 출현빈도 Rank에 영향을 받지 않게 하기 위함이다.

사실 phrase 속에 존재하는 translation probability는 이미 원래 corpus의 phrase pairs의 출현 빈도를 반영한다. 모델이 학습 과정 속에서 이러한 출현 빈도에 따른 어떤 규칙을 학습하는 것이 아니라, 언어학적 규칙(linguistic regularities)을 학습하도록 하는 것이 핵심이라고 할 수 있다.
(learning the manifold of plausible translations)

Experiments
대규모의 데이터가 구축되었지만 실제 학습을 위해서 본 논문의 저자는 source & target vocab을 가장 자주 등장한 15,000개의 단어로 한정하였다. 이는 전체 데이터셋의 93%를 커버한다고 밝혔다.

학습과정에 대한 자세한 사항은 논문을 참조하기 바란다.

Conclusion
결과적으로 RNN Encoder-Decoder는 phrase pairs 내에 있는 언어학적 규칙을 포착하고, 적절하게 구성된 target phrases 또한 제안하는 데에도 좋은 성능을 보이는 것으로 확인되었다.

Structure of Encoder
source phrase X와 Y의 형태는 아래와 같다.

[X = (x_1, x_2, … , x_N), Y = (y_1, y_2, … , y_N)]

X.shape = (N, K), Y.shape = (N, K)

물론 여기서 각 세로 벡터는 one-hot vector이다.
source phrase의 각 단어는 500차원의 벡터로 임베딩된다.
Encoder의 Hidden state는 1000개의 unit을 갖고 있다.
(시간 t의 hidden state $h_{}$의 shape = (1000, 1))

위 hidden state가 계산되는 과정을 살펴보면,

  1. Reset Gate
    \(r = \sigma(W_r e(x_t) + U_r h_{<t-1>})\)

(1000, 1) = (1000, 500) X (500, 1) + (1000, 1000) X (1000, 1)

  1. Update Gate
    \(z = \sigma(W_z e(x_t) + U_z h_{<t-1>})\)

shape은 위와 같다.

  1. Candidate
    \(\tilde{h}^{<t>} = tanh(W e(x_t) + U(r \odot h_{<t-1>} ))\)

(1000, 1) = (1000, 500)X(500, 1) + (1000, 1000)X(1000, 1)$\odot$(1000, 1)

  1. Hidden State
    \(h^{<t>} = z h^{<t-1>} + (1-z) \tilde{h}^{<t>}\)

  2. Representatino of the source phrase: 농축된 정보
    \(c = tanh(V h^{<t>})\)

Structure of Decoder
Soon to be updated

Comment  Read more

파이썬 정규표현식(re) 사용법 - 09. 기타 기능

|

파이썬 정규표현식(re) 사용법 - 01. Basic
파이썬 정규표현식(re) 사용법 - 02. 문자, 경계, flags
파이썬 정규표현식(re) 사용법 - 03. OR, 반복
파이썬 정규표현식(re) 사용법 - 04. 그룹, 캡처
파이썬 정규표현식(re) 사용법 - 05. 주석, 치환, 분리
파이썬 정규표현식(re) 사용법 - 06. 치환 함수, 양방탐색, 조건문
파이썬 정규표현식(re) 사용법 - 07. 예제(숫자)
파이썬 정규표현식(re) 사용법 - 08. 예제(단어, 행)
파이썬 정규표현식(re) 사용법 - 09. 기타 기능


이 글에서는 re 패키지에 포함된, 지금까지의 글에서 다루지 않았던 함수와 속성 등을 다루도록 하겠다.

본 글에서 정규표현식은 regex와 같이, 일반 문자열은 ‘regex’와 같이 표시하도록 한다.

파이썬 버전은 3.6을 기준으로 하나, 3.x 버전이면 (아마) 동일하게 쓸 수 있다.
2.7 버전은 한글을 포함한 비 알파벳 문자 처리가 다르다.


함수

re.escape(string)

re.escape 함수는 문자열을 입력받으면 특수문자들을 이스케이프 처리시켜 준다.

pattern = r'((\d)\2{4,})'
print(re.escape(pattern))

결과

\(\(\\d\)\\2\{4\,\}\)

re.purge()

사실 설명하지 않은 것이 있는데, re 패키지는 re.compile로 만들어 놓은 객체들을 cache에 저장해 둔다. 최대 100개까지라고 알려져 있으며, 그 수를 넘어갈 경우 초기화된다고 한다.
물론 여러분은 아마 한 프로그램 내에서 100개 이상의 다른 정규식을 쓸 일은 없으니 크게 신경 쓸 필요는 없다.

re.purge 함수는 이 cache를 초기화하는 함수이다.

re.purge()

결과

결과는 아무것도 출력되지 않는다.


속성

re.RegexFlag

이전 글에서 flags를 설명했었는데, 이 flag들이 어떤 것이 있는지 알려주는 객체가 re 안에 내장되어 있다.

for flag in re.RegexFlag:
    print(flag)

결과

RegexFlag.ASCII
RegexFlag.IGNORECASE
RegexFlag.LOCALE
RegexFlag.UNICODE
RegexFlag.MULTILINE
RegexFlag.DOTALL
RegexFlag.VERBOSE
RegexFlag.TEMPLATE
RegexFlag.DEBUG

re.TEMPLATE

아마 쓸 일이 없을 듯하므로 설명은 생략한다. (?)

다만 이런 것이 있다는 것만 소개한다.


re.DEBUG

reObj를 출력하면 컴파일한 정규식을 그대로 출력하던 것을 기억할 것이다. re.debug는 일종의 디버깅 모드로서, 정규식의 대략적인 구조를 알 수 있다.
말 그대로 디버깅용으로 쓰면 될 듯하다.

r = re.compile('\d{3,6}', re.DEBUG)
print(r)
print(r.findall('AS 123123 ars'))

결과

MAX_REPEAT 3 6
  IN
    CATEGORY CATEGORY_DIGIT
re.compile('\\d{3,6}', re.DEBUG)
['123123']

reObj의 사용법은 기본 compile된 객체와 완전히 같다.


re.error

re.error는 compile 함수에 전달된 문자열이 유효하지 않은 정규식일 때 발생하는 에러 타입이다. try-except 구문으로 처리하면 된다. 자세한 사용법은 아래 예시로만 보여도 충분할 듯 하다.

참고로 아래 코드의 phi는 원주율을 소수점 1만 자리까지 저장한 문자열이다.

regex_list = [
    r'((\d)\2{4,})',
    r'((\d)\1{4,})'
]

for regex in regex_list:
    try:
        reObj = re.compile(regex)
        print(list(map(lambda x: x[0], reObj.findall(phi))))
    except re.error:
        print("<Invalid regular expression %s>" % regex)
    finally:
        print('done')

결과

['999999']
done
<Invalid regular expression ((\d)\1{4,})>
done

무엇이 유효하지 않은지는 연습문제로 남겨두도록 하겠다.

조금 더 자세한 사용법은 여기를 참조한다.


이것으로 정규표현식에 대한 글을 마치도록 한다.
조금 더 복잡한 예제를 정리해 두면 좋겠지만, 그때그때 맞게 쓰는 것이 더 나을 것 같아서 굳이 따로 정리할 필요는 없을 것 같다.

Comment  Read more