반응형


Python으로 큐(Queue) 구현하기

Python으로 Stack 구현하기의 다음 시리즈이다. 이번엔 Queue를 다양한 방법으로 구현해보자.

2021.12.18 - [Development/Python] - [Python / 자료구조] Python으로 Stack 구현하기

 

[Python / 자료구조] Python으로 Stack 구현하기

Python으로 스택(Stack) 구현하기 이번에 면접 대비를 하게 되면서, 간만에 기본에 충실하자! 라는 느낌으로 Python으로 기본적인 자료구조 구현을 진행해보았다. C++로 할 때보다 확실히 쉽고 금방 구

cryptosalamander.tistory.com

 

 

큐(Queue)의 기본 구조


출처 : 위키피디아

 

  • 스택과 달리 FIFO(First In First Out)의 구조를 가진다.

  • 한국말로 번역하면 "대기열"로, 먼저 줄 선 사람이 먼저 빠져나가는 구조이다. 

  • 롤할 때 "큐"돌린다의 Queue가 이거다. 은근히 모르시는 분이 많았다.

  • 운영체제의 스케줄러에서도 프로세스를 처리할 때 Queue를 쓴다.

 

큐가 가져야 할 Method 목록


  • push(x) : 큐의 맨 뒤에 x라는 값을 집어 넣는다.

  • pop() : 큐의 맨 앞 부분(먼저 들어온 원소)를 꺼내고 반환한다.

  • size() : 큐의 길이를 반환한다.

  • empty() : 큐가 비어있는지에 대한 유무를 반환한다.

  • front() : 큐의 맨 앞 부분 원소를 반환한다.

  • back() : 큐의 맨 뒷 부분 원소를 반환한다.

 

 

구현


 

from collections import deque

 

사실 Python에서 가장 편하게 Queue를 쓰는 방법은 그냥 deque을 가져다 쓰는 거다.

내부적으로 이중 링크드리스트를 쓰고 있기 때문에, enqueue, dequeue를 앞에서 할 수도 있고, 뒤에서 할 수도 있어 알고리즘 문제 풀이에 자주 쓰인다.

deque의 코드가 궁금하신 분들은 https://hg.python.org/cpython/file/3.5/Modules/_collectionsmodule.c#l21를통해확인할 수 있다. (C로 구현되어있음)

원소 삽입, 삭제의 경우 list에선 빈공간 만큼 앞으로 원소를 당겨주는 과정을 수행하지만, deque은 그냥 링크드리스트의 포인터 부분만 바꾸면 되기 때문에 오버헤드가 적어, O(1)의 시간 복잡도를 가진다.

 

반면, 인덱스를 활용한 탐색의 경우 Linked List를 타고타고 들어가야 하다보니, 그냥 list방식의 시간복잡도 O(1)보다 느린 O(N)의 시간 복잡도를 지닌다.

본 포스팅에서는 List로 구현하기, 면접 단골 출제 문제인 스택 2개로 Queue 구현하기 두 가지 방식을 다룬다.

 

 

List로 구현하기


'''
    push(x) - input x to queue
    pop() - delete last element of queue and return it
    size() - return length of queue
    empty() - return boolean that checks whether queue has an element
    front() - return first element in queue
    back() - return last element in queue
'''
class HyunQueue:
    def __init__(self):
        self.que = []

    def push(self, x):
        self.que.append(x)

    def pop(self):
        return self.que.pop(0) if self.que else -1

    def size(self):
        return len(self.que)

    def empty(self):
        return not self.que

    def front(self):
        return self.que[0] if self.que else -1

    def back(self):
        return self.que[-1] if self.que else -1

if __name__ == "__main__":
    q = HyunQueue()
    q.push(1)
    q.push(2)
    print(q.front())
    print(q.back())
    print(q.size())
    print(q.empty())
    print(q.pop())
    print(q.pop())
    print(q.pop())
    print(q.size())
    print(q.empty())
    q.push(3)
    print(q.empty())
    print(q.front())
    print(q.back())

 

 

Stack 2개로 구현하기


 

파이썬에서는 사실 Stack 구조를 안쓰고 List를 쓰는데다가, List 내부적으로 index 기반 pop이 가능하다.

즉, stack.pop()이 아니라 stack.pop(0)을 하면 그 자체가 Queue의 dequeue와 같이 동작하는 것이다.

 

본 방식은 C++, Java에서 자주 나오는 빈출 유형이라 Python으로 푸는게 어불성설이긴 하지만, 그래도 List에 인덱스 기반 pop이 없다고 가정하고 풀어보면 아래와 같은 구현이 가능하다.

 

Stack1에 데이터를 순차적으로 넣고, dequeue할 때 Stack1의 원소를 pop한뒤 Stack2에 담은 후, Stack2에서 pop을 진행하면 FIFO 구조를 가지게끔 할 수 있다.

 

이 때, 중요한 것은, dequeue 요청이 왔을 때,  Stack2가 비어있을 경우에만 Stack1의 원소들을 모두 Stack2에 넣는 것이다.

그렇지 않으면 순서가 꼬이게 된다. 

 

Stack1 = [1, 2, 3, 4]

Stack2 = [ ] 
위 상황에서 pop 요청이 오면, Stack1의 데이터를 Stack 2로 하나씩 pop해서 넣는다. Stack의 원소는 후입선출이므로 4부터 빠져서 Stack2에 들어가게 된다.


Stack1 = [ ] 

Stack2 = [4, 3, 2, 1] -> pop() -> Stack2 = [4, 3, 2]

 

예를 들어 위와 같은 상황에서, Stack2가 차있는 상태로 Stack1에 새로운 데이터 5, 6이 들어왔고 이를 옮긴다고 가정해보자.
Stack1 = [5, 6]

Stack2 = [4, 3, 2]

Stack1의 원소를 빼서 Stack2에 넣는 과정에서, 6, 5가 Stack2에 뒤에 추가된다.
즉, Stack2 = [4, 3, 2, 6, 5]가 된다. 순서가 완전히 깨져버렸다.
그래서, Stack2가 비어 있지 않으면 일단은 원소를 Stack1에 쌓아두는 방식을 사용한다.

 

말보다 코드로 보면 오히려 바로 이해가 갈 것이다. 아래 구현을 보자.

 

'''
    push(x) - input x to queue(enqueue)
    pop() - delete last element of queue and return it(dequeue)
    size() - return length of queue
    empty() - return boolean that checks whether queue has an element
    front() - return first element in queue
    back() - return last element in queue
'''
class HyunQueue():
    def __init__(self):
        self.stack1 = []
        self.stack2 = []

    def push(self,x):
        self.stack1.append(x)

    def pop(self):
        if not self.stack2:
            while self.stack1:
                self.stack2.append(self.stack1.pop())
        return self.stack2.pop() if self.stack2 else -1

    def size(self):
        return len(self.stack1) + len(self.stack2)

    def empty(self):
        return not self.stack1 and not self.stack2

    def front(self):
        if self.stack2:
            return self.stack2[-1]
        elif self.stack1:
            return self.stack1[0]
        else:
            return -1

    def back(self):
        if self.stack1:
            return self.stack1[-1]
        elif self.stack2:
            return self.stack2[0]
        else:
            return -1

if __name__ == "__main__":
    q = HyunQueue()
    q.push(1)
    q.push(2)
    print(q.front())
    print(q.back())
    print(q.size())
    print(q.empty())
    print(q.pop())
    print(q.pop())
    print(q.pop())
    print(q.size())
    print(q.empty())
    q.push(3)
    print(q.empty())
    print(q.front())
    print(q.back())
반응형
블로그 이미지

Hyunsoo Luke HA

석사를 마치고 현재는 Upstage에서 전문연구요원으로 활동중인 AI 개발자의 삽질 일지입니다! 이해한 내용을 정리하는 용도로 만들었으니, 틀린 내용이 있으면 자유롭게 의견 남겨주세요!

,
반응형


Python으로 스택(Stack) 구현하기

이번에 면접 대비를 하게 되면서, 간만에 기본에 충실하자! 라는 느낌으로 Python으로 기본적인 자료구조 구현을 진행해보았다. C++로 할 때보다 확실히 쉽고 금방 구현이 가능하다. 특히 Stack의 경우 list에서 애초에 내부적으로 거의 다 구현이 되어있어서 추가적으로 뭔가 만들 필요가 없을 정도다.

 

 

스택(Stack)의 기본 구조


 

  • 구현에 앞서 간략히 스택에 대해 설명해보자면, 아래의 특성을 가진다.

  • 스택은 기본적으로 LIFO(Last In First Out) 구조를 지니고 있는 선형 자료구조이다.

  • 말 그대로 스택(쌓다) 이므로, 주사위 쌓기를 생각해보면 어떤 구조인지 알 수 있다. 쌓기를 시작할 때는 당연히 맨 밑부터 쌓아나가야하며, 데이터를 뺄 때는 맨 위에서부터 뺄 수 밖에 없다.

  • LIFO의 특성을 활용해야하는 다양한 알고리즘 문제 풀이에서 자주 사용된다. 

 

 

스택이 가져야 할 Method 목록


  • push(x) : 스택에 x라는 값을 집어 넣는다.

  • pop() : 스택의 맨 윗부분(마지막에 들어온 원소)를 꺼내고 반환한다.

  • size() : 스택의 길이를 반환한다.

  • empty() : 스택이 비어있는지에 대한 유무를 반환한다.

  • top() : 현재 스택의 맨 위에 있는 원소를 보여준다. (꺼내지는 않음)

 

 

구현


사실 Python에서 기본적으로 쓰이는 list 자료구조가 stack과 같다. 딱히 직접 구현해야 할 내용이 없지만 Class화를 시켜본다면 아래와 같이 구현할 수 있다.

 

그리고 백준에 이를 다루는 문제가 있으니, 직접 풀어보는 것도 좋을 것 같다!
https://www.acmicpc.net/problem/10828

 

'''
    push(x) - input x to stack
    pop() - delete last element of stack and return it
    size() - return length of stack
    empty() - return boolean that checks whether stack has an element
    top() - return top of stack
'''
class HyunStack():
    def __init__(self):
        self.stack = []

    def push(self,x):
        self.stack.append(x)

    def pop(self):
        return self.stack.pop() if self.stack else -1

    def size(self):
        return len(self.stack)

    def empty(self):
        return not self.stack

    def top(self):
        return self.stack[-1] if self.stack else -1

if __name__ == "__main__":
    q = HyunStack()
    q.push(1)
    q.push(2)
    print(q.top())
    print(q.size())
    print(q.empty())
    print(q.pop())
    print(q.pop())
    print(q.pop())
    print(q.size())
    print(q.empty())
    q.push(3)
    print(q.empty())
    print(q.top())

 

반응형
블로그 이미지

Hyunsoo Luke HA

석사를 마치고 현재는 Upstage에서 전문연구요원으로 활동중인 AI 개발자의 삽질 일지입니다! 이해한 내용을 정리하는 용도로 만들었으니, 틀린 내용이 있으면 자유롭게 의견 남겨주세요!

,
반응형


Kullback Leibler Divergence(KL Divergence)

DeiT 논문을 분석하다보니 Kullback Leibler Divergence(쿨백-라이블러 발산) 개념이 나왔는데, 전에도 Autoencoder를 공부하면서 봤었던 내용인데 이참에 제대로 정리를 하고자 포스팅을 작성하게 되었다. 정리를 안해두니까 매번 볼 때마다 새롭다. 내 기억력....

 

Kullback Leibler Divergence란 기본적으로 두 개의 서로 다른 확률 분포를 비교하는 방법이다.

관찰된 데이터 혹은 복잡한 데이터 분포를 이를 나타낼 수 있는 확률 통계 모델로 나타내었을 때, 이러한 모델이 실제 관측치 대비 얼마나 정보를 잃어버렸는지 측정한다.

 

잘 정리된 외국 블로그의 예시를 참고해보자
(https://www.countbayesie.com/blog/2017/5/9/kullback-leibler-divergence-explained)

 

 

우리는 우주에 나와서 실험을 진행하고 있는 과학자들이다.
우리의 주 연구과제는, 외계행성에 존재하는 우주벌레에 관한 연구이다.

각 벌레들은 10개의 이빨을 가질 수 있으나, 음식을 씹는등 살아가는 과정에서 벌레들은 이빨을 잃기도 한다.
벌레들의 이빨의 분포를 충분히 많은 관찰을 통해 다음과 같이 얻어냈다.

 

 

이를 이대로 지구로 전송해도 되겠지만, 

우리는 연구자가 아닌가! 이빨의 개수를 잘 나타낼 수 있는 확률 모델을 만들어 지구로 전송해야 한다.

 

먼저 이를 Uniform Appoximation을 통해 나타내면 다음과 같다.

이빨이 존재할 수 있는 경우의 수 0개~ 10개 까지의 11가지의 경우의수의 확률이 약 9%로 동일하다고 가정한다.

 

 

둘째로는, 이빨의 평균 개수가 5.7개라는 정보를 미리 알고 있다면 이를 Binomial Approximation을 통해 표현할 수 있다.

n이 10, p가 x일 때, 10x = 5.7이므로 확률 p는 0.57과 같고, 이를 통해 이항 분포로 그려내보자.

 

 

 

얼핏 보기엔, 균등 분포보다 훨씬 좋아보인다. 이제 값들을 실제 관측값과 비교해보자.

 

 

각각의 모델들이 실제 관측값 대비 얼마나 정보가 손실되었는지 객관적으로 확인할 수 있다면, 

어떤 분포 모델이 가장 적합한지 수치화하여 표현할 수 있을 것이다. 

이 때, 사용되는 개념이 ML을 하다보면 흔히 알게 되는 Entropy이다.

 

 

Entropy


Entropy에서 만약 log2를 사용하게 된다면, 이 값을 통해 데이터가 가진 정보량을 나타내는데에 필요한 최소한의 Bit 수를 알 수 있게 된다. 물론 Entropy 자체에서 어떻게 해야 데이터가 가진 정보를 최적화해서 Encoding할 수 있는지에 대한 사항은 알 수가 없다. 단지 정보를 나타낼 때 필요한 최소한의 Bit 수를 알 수 있다는것에 의의가 있다.

 

즉, Entropy 개념을 사용하면 데이터에 담긴 정보량을 객관적으로 수치화할 수 있으며, 이를 각각의 확률 분포 모델에 적용한 뒤 비교하면 각 모델들이 얼마나 정보를 소실하는지 알아낼 수 있는 것이다.

 

 

 

 

KL Divergence


 

KL Divergence는 결국 실제 확률 분포 p에 대한 log값과 q에 대한 log값의 차이에 대한 entropy를 구하는 것과 같다.

이를 통해 실 관측값과 추측 모델간에 발생하는 정보량의 소실을 알아낼 수 있다. 

 

그리고, 이를 전개해보면 꽤나 익숙한 모양의 식이 나타나는데, 

 

 

결국 해당 값은 p와 q의 Cross Entropy에서, Entropy(p)를 뺀 것과 같다는 것을 확인할 수 있다.

이 때, Entropy(p)는 결국 상수값이므로, KL Divergence를 최소화시키기 위해서는 결국 Cross Entropy를 최소화시켜야 한다.

 

이 개념이 바로 딥러닝에서 사용되는 Cross Entropy Loss이다.

특정 Parameter에 대해 미분하면서 Backpropagation을 수행하는 Neural Network의 구조상, 어차피 Parameter와 관련이 없는 Entropy, H(p)는 결국 상수처리되어 미분시 사라져버린다. 따라서 결국 Cross Entropy를 최소화 하는 방향으로 학습이 진행되게 된다.

 

 

 

Result


KL Divergence로 확인한 결과, Uniform이 Binomial 방식보다 정보의 손실이 적다는 것이 나타났다.

따라서, 우주벌레의 이빨의 개수를 더 잘 나타내는 확률분포는 이항 분포가 아닌 균등 분포라는 것을 확인할 수 있다.

 

이 때, 한가지 유의해야 할 점은 KL Divergence는 대칭구조가 아니므로, Distance의 개념이 아니다.

즉, p에 대한 q의 KL Divergence와 q에 대한 p의 KL Divergence는 아래와 같이 값이 완전히 달라진다.

 

 

여기까지, KL Divergence에 대해 알아보았다.

딥러닝에서 CE Loss를 통해 학습시킬 때, KL Divergence 개념이 들어가 있었다는 것은 이번에 정리를 하면서 깨닫게 되었다. 역시 딥러닝은 수학적으로 공부해야할 것이 끝도 없는 것 같다.

 

 

반응형

'Machine Learning > ML기초' 카테고리의 다른 글

[ML 이론] Feature Normalization, SoftMax  (0) 2021.12.20
[ML 이론] Evaluation Metric 정리  (0) 2021.12.20
블로그 이미지

Hyunsoo Luke HA

석사를 마치고 현재는 Upstage에서 전문연구요원으로 활동중인 AI 개발자의 삽질 일지입니다! 이해한 내용을 정리하는 용도로 만들었으니, 틀린 내용이 있으면 자유롭게 의견 남겨주세요!

,
반응형


Training data-efficient image transformers& distillation through attention

DeiT로 널리 알려져있는 논문을 리뷰해보자.

 

 

Abstract


  • 최근 Pure Attention 기반 Transformer Architecture가 이미지 분류와 같은 Vision Task에 꽤 효과적으로 적용되는 것이 발표되었다. (ViT)

  • 하지만 높은 성능을 가지는 Vision Transformer는 수억에 가까운 이미지를 거대한 인프라와 함께 학습해야하며, 이러한 점은 Vision Transformer의 다양한 활용에 큰 걸림돌이 되고 있다.

  • 본 논문에서 저자들은 CNN와 비교했을 때에도 경쟁력있는 Vision Transformer 모델을 오로지 JFT-300M같이 거대한 데이터셋없이 오로지 ImageNet 만을 학습하여 구현하였다.

  • 또한, 모델은 1대의 컴퓨터로 3일 이내에 학습이 완료되었으며 추가적인 데이터없이 오로지 ImageNet만으로 83.1%의 Top-1 Accuracy를 달성하였다.

  • 추가적으로 본 논문에서는 Transformer에 특화된 Teacher - Student 학습 전략을 제안한다.

  • BERT의 CLS 토큰과 같은 방식의 Distillation token을 통해서 수행되는데, 특히 Teacher Model로는 Convolution Network를 사용했을 때 기존 CNN SOTA모델과 경쟁력있는 유의미한 정확도를 얻도록 하였다. (최대 85.2%)

 

 

Introduction


  • CNN이 Vision 분야에서 메인 패러다임으로 활용되고 있었지만, NLP에서의 Transformer의 선전을 보고 감명을 받아, Vision 영역에 Attention을 적용하고자 하는 시도가 나타나고있다.

  • CNN의 일부에 Attention 메카니즘을 적용하거나, Transformer 구조를 수정하여 Convolution Network의 특성을 가지게끔 하거나 하는 연구가 존재한다.

  • 최신 논문중 ViT는, NLP에서 사용되던 Architecture를 그대로 유지하면서 Raw Image Patch를 Input으로 사용하여 이미지 분류 Task에 사용되었다. 그들의 연구는 거대한 데이터셋(JFT-300M)을 사용했을 때 유의미한 결과를 얻었지만, 소량의 데이터에는 Generalize되지 않는다는 한계점이 존재한다.

  • 뿐만 아니라, ViT의 경우 막대한 컴퓨팅 자원이 필요하다. 비록 Inference에는 적은 연산량이 필요했지만, 수 많은 데이터를 학습시키기 때문에 2.5k TPUv3-core-day라는 어마어마한 시간이 들게 된다. 즉, 일반 GPU를 사용하는 Single Device로는 현실적으로 학습이 어려운 실정이다.

  • 본 논문에서는 Vision Transformer를 8-GPU Single Device로 약 3일만에 학습을 성공하였으며 이는 비슷한 Params 규모를 가지는 CNN 모델을 학습시키는데 드는 시간과 큰 차이가 없다.

  • 저자들은 별도의 거대 외부 데이터셋 없이 ImageNet 만으로 학습을 진행한다. ViT를 구조를 그대로 사용하였지만, Facebook의 특징인 "잘 학습시키는 방법"을 적극 적용하여 Data Efficient한 방식의 DeiT(Data-efficient Image Transformer)를 제안한다.

  • CNN Layer 없는 Transformer 방식으로도 오로지 ImageNet 데이터셋만으로 학습을 진행했을 때 CNN과 경쟁력을 가지는 성능을 얻을 수 있다는 것을 확인하였으며, 4개의 GPU로 3일 정도면 학습을 마칠 수 있다.

  • distillation token을 통해서 Transformer 구조에 특화된 Distillation Procedure를 제안하였으며, CLS 토큰과 같은 방식으로 활용되어 Teacher Model이 예측한 Label을 나타내는데 사용된다. 두 토큰은 모두 Transformer 내에서 Attention을 통해 상호작용된다. 이러한 방식의 Distillation은 기존 Vanilla Distillation 방식보다 높은 성능을 보였다.

  • 흥미로운 사실은, Teacher Model로 CNN을 사용했을 때가 비슷한 성능을 가지는 Transformer 모델을 Teacher Model로 썻을 때 보다 더 많은 정보를 습득할 수 있었다는 것이다.

  • 위 방법을 적용하여 ImageNet만을 학습한 DeiT 모델은, CIFAR-10, CIFAR-100, Oxford-102 flowers와 같은 Downstream Task들에 대해서도 경쟁력있는 성능을 가졌으며, Generalization이 잘 이뤄졌음을 확인할 수 있다.

 

 

Distillation through attention


저자들은 Strong Image Classifier를 Teacher Model로써 사용하여 Student Model인 Transformer를 향상시켰다.

Teacher Model은 단일 CNN일 수도 있으며, Classifier의 결합체일 수도 있다. 

본 논문에서는 Soft distillation vs Hard distillation, 그리고 classical distillation vs 제안하는 방식인 distillation token을 비교 분석하였다.

 

Soft distillation

  • Teacher Model과 Student Model과의 Softmax 값의 Kullback-Leibler Divergence를 최소화시키는 Loss를 활용한다. 

  • KL(Kullback-Leibler Divergence Loss)와 Ground Truth와의 CE(Cross Entropy)를 통해 Global Loss를 얻어내게 되는데 이는 다음 수식과 같다.

 

Hard-label distillation

  • 본 논문에서 소개된 Hard-label distillation의 경우, Teacher Model에서 가장 큰 Softmax 값을 가진 Label을 True Label로 처리하여 Cross Entropy를 구한다.
  • 그리고 Ground Truth와의 Cross Entropy를 구하고, 이 둘을 평균 내는 방식으로 Global Loss를 얻어내게 되며 보다 직관적이고 실제 실험결과 Soft distillation보다 좋은 성능을 얻어냈다고 한다.

 

  • hard label은 soft labels를 label smoothing과 함께 사용하는 것으로 변환될 수 있으며, True Label에게 1 - epsilon의 확률을 부여하고, 나머지 모든 클래스에게 epsilon을 나눠가지도록 하는 방식으로 구현될 수 있다. 본 논문에서 저자들은 모든 실험에서 epsilon의 값을 0.1로 고정하였다.

 

Distillation Token

  • 본 논문에서 제안하는 방식인 Transformer specific distillation strategy인 distillation token 방식이다.

  • 초기 임베딩 값(이미지 패치들과 CLS 토큰)에 Distillation 토큰을 임의로 추가한다. 

  • 해당 토큰은 CLS 토큰과 동일한 방식으로 동작하며 다른 임베딩들과 Self Attention을 통해 상호작용하며 값이 변하게 되고, 최종적으로 output을 내게 된다. 각각의 output은 distillation loss와 ground truth loss를 통해 back propagation으로 학습된다.

  • 이러한 방식으로 학습을 진행하였을 때, class token과 distillation token이 다른 벡터임을 확인할 수 있었고, (최초 코사인 유사도는 0.06) 학습을 진행해나감에 따라 cosine 유사도가 점차 올라가면서 (0.93) distillation을 통해 teacher model의 정보가 전달된다. 최종적으로 Student는 Teacher와 비슷한 결과물을 내는 것이지 완전히 같은 결과물을 내는 것은 아니기 때문에 1보다 작은 0.93 정도의 Cosine 유사도를 가지는 것은 충분히 납득 가능하다.

  • 반면에, 똑같은 target label을 가지는 class token을 단순히 하나 더 부착했을 때에는 양상이 완전히 달라졌다. 처음에 분명히 초기값을 임의로 다르게 배정하였음에도 불구하고, 학습을 진행하면서 두개의 토큰은 거의 동일한 (cosine 유사도 0.999) 벡터로 Converge 되어 버리고, output 또한 사실상 같은 값이 나오게 되어 분류 성능에 전혀 영향을 미치지 않는다. 

  • 반면 Teacher의 Pseudo Labeling을 통해 학습된 distillation token 방식의 경우 vanilla distillation을 능가하는 유의미한 성능 향상을 보여주었다.

 

 

Result


  • 저자들은 다양한 실험을 통해 Teacher Model의 Architecture가 Distillation 성능에 유의미한 결과를 미친다는 것을 확인하였으며, CNN Architecture를 사용했을 때, 추측컨대 Inductive Bias의 성질이 얻어질 수 있기 때문이라고 주장한다.

  • 실제로 위 테이블을 보면, DeiT-B의 정확도 81.8%과 거의 비슷한, 심지어 조금 낮은 CNN 모델인 RegNetY-8GF를 Teacher로 사용했을 때 더 큰 정확도를 가지는 것을 확인할 수 있다.

  • 저자들은 Distillation을 통해 Teacher의 분류를 얼마나 잘 답습했는지 표현하기 위해 Table 4를 활용하였다. 실제로 class+distill 방식을 보게 되면 CNN과 DeiT의 중간쯤으로 수렴하는 모습을 확인할 수 있는데, 이를 통해 일종의 상호보완 효과를 기대할 수 있다.

  • 또한 해당 방식을 통해 CIFAR-10과 같은 Downstream Task를 진행했을 때에도 Distillation을 사용한 모델이 단일 RegNetY-16GF나 DeiT-B 보다 더 높은 성능을 가지는 것을 확인할 수 있었다.

  • 최종 결과, CNN의 끝판왕 모델급으로 알려져있는 EfficientNet-B7과도 경쟁력있는 성능을 보유하고 있으며, 심지어 ImageNet Classification에서 조금 앞선 모습을 보인다. (0.1 차이라서 실험을 다시하면 뒤바뀔 가능성이 커보이긴 한다.)

  • 성능 자체는 B7과 거의 비슷한 수준이지만, 초당 추론속도를 보면 60% 가까이 빠르다는 것을 확인할 수 있으며, 이는 ViT의 구조를 그대로 사용했으므로 ViT가 주장하던 Vision Transformer의 장점과 일맥상통한다.

 

Conclusion


  • 학습 잘 시키기로 유명한 Facebook답게 ViT라는 새로운 연구분야가 개척되자, 바로 Facebook팀의 노하우를 담아 다양한 Augmentation과 Distillation을 통해 높은 성능을 달성하였다.

  • 본 논문에서 저자들은, ViT의 한계점이라고 할 수 있는 초고사양 장비, 거대 데이터셋 의존성을 완전히 탈피시킨 Data Efficient 한 Transformer인 DeiT를 제안한다.

  • 별도의 외부 Data없이 ImageNet 데이터셋만을 학습하였고, 4 GPU Device로 약 3일 조금 안되게 학습을 완료할 수 있으면서도 EfficientNet-B7과 맞먹는 성능을 가질 뿐만 아니라, B7보다 초당 처리량이 더 큰 결과를 얻어내었다.
  • ViT가 Vision Transformer의 유망성을 제시했다면, 본 논문은 Vision Transformer를 이제 Vision 영역의 Dominant 패러다임으로 발돋움시킨 논문이라고 할 수 있다.
반응형
블로그 이미지

Hyunsoo Luke HA

석사를 마치고 현재는 Upstage에서 전문연구요원으로 활동중인 AI 개발자의 삽질 일지입니다! 이해한 내용을 정리하는 용도로 만들었으니, 틀린 내용이 있으면 자유롭게 의견 남겨주세요!

,
반응형


An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale

ViT(Vision Transformer)를 최초로 제안한 논문을 리뷰해보자.

 

 

Abstract


  • Transformer Architecture는 NLP분야에 있어서는 이제 표준이 되었다고 해도 무방할정도로 잘 적용되고 있다.
  • 반면, Vision 영역에서 Transformer는 효과적으로 활용되지 못하고 있는 상황이다.

  • Attention은 Vision Task에서, 전체적인 CNN의 구조는 그대로 유지하면서, CNN Component의 일부를 대체하는 정도로 사용되는 연구가 많다.

  • 본 논문에서 저자들은 이러한 CNN의 대한 의존성은 불필요하며, Pure Transformer 만으로도 약간의 변형만 가해주면 CNN SOTA모델들과 비교해도 큰 차이가 없을 정도로 좋은 성능을 보임을 확인하였다.

  • 해당 방식은 많은 이미지 데이터셋을 필요로 하지만, 학습에 필요한 연산 자원은 상대적으로 적다는 장점이 있다.

 

 

 

Introduction


  • Self Attention 기반의 Architecture인 Transformer는 NLP에서 부정할 수 없는 선택지가 되었다. Transformer는 일반적으로 거대한 Text Corpus를 바탕으로 학습을 진행한 뒤, 특정 Task를 수행하기 위하여 Specific한 Dataset을 통해 Fine Tuning과 함께 사용된다.

  • 이러한 Transformer 구조의 계산적 효율성과 높은 확장성으로 인해 지금까지도 계속 Parameter 수가 늘어난 거대한 모델을 보다 거대해진 Dataset을 통해 학습함으로써 계속적으로 성능이 개선되고 있으며, 아직까지는 성능 지표의 발전에 있어서 Saturation이 나타나지 않고 있다.

  • 반면, Computer Vision 영역에서는 여전히 CNN이 우세한 상황이다. 

  • 본 논문에서는, Transformer의 NLP에서의 큰 선전에 영감을 받아서 표준 Transformer 구조를 이미지에서 직접적으로 활용할 수 있게끔 최소한의 수정을 통해 변경하였다.

  • 해당 방식은 하나의 Image를 작은 Patch로 나누고, 이러한 Patch들의 Sequence를 Transformer에 전달하는 방식으로 이루어진다. 각각의 Patch들은 마치 NLP에서의 Token(word)와 같이 처리되며 본 논문에서는 일반적인 지도 학습의 방식으로 이미지 분류를 학습시켰다.

  • 실험 결과, ImageNet에서는 비슷한 크기의 ResNet과 비교했을 때 정확도가 많이 떨어지는 모습을 보였다. 이러한 현상은 모두 예측 가능한 것으로, Transformer는 CNN에서 활용되는 Locality 정보와 Translation Equivariance등의 Inductive Bias가 존재하지 않기 때문에 충분하지 않은 데이터에서는 학습이 잘 이뤄지지 않기 때문이다.

  • 반면, 데이터셋의 크기가 커졌을 때(14M~300M), ViT가 이러한 CNN의 Inductive Bias를 능가하는 모습을 보여주었다. 

 

 

ViT Method


  • ViT는 Input으로 Image를 고정된 사이즈의 Patch로 분할한 뒤, 각각의 Patch들을 Linear Projection을 통해 임베딩하고, Sequential Position 정보를 담고 있는 Position Embedding을 추가하여 Transformer의 Encoder에 입력하게 된다.

  • 이러한 방식은 단순히 Image를 Sequence 형태로 바꾸는 부분만 추가되었을 뿐, 실제로는 Original Transformer와 거의 유사한 방식으로 동작하게 된다. 이를 통해, 기존에 잘 연구되었던 Transformer Architecture를 사용할 수 있을 뿐만 아니라 효율적인 구현이 가능하도록 했다.

  • 단순히 Image Patch로 나누는 방식은 Transformer로 하여금 2D Spartial Information을 매우 제한적으로 사용하게 만들기 때문에 CNN에 비해서 낮은 Inductive Bias를 가지기 때문에 충분히 많은 데이터를 통해 학습을 진행하여야 한다. 본 논문에서는 JFT-300M dataset을 통해 Pretrain을 진행했다.

  • 이를 개선하고 Spartial Information을 최대한 활용하게끔 유도하기 위해서, Raw Image Patch 대신, CNN의 Feature Map을 얻어낸 뒤, 이를 Flatten하여 Input Sequence를 만들어낼 수도 있다.

  • 일반적인 Transformer의 방식을 그대로 차용하여 Large Dataset에 일단 Pretrain을 진행하고, 그 후 작은 규모의 downstream task dataset에 fine tuning을 진행한다. 

  • 최근 연구들의 결과에 따라서, Pretrain에 사용됐던 Resolution 보다 더 높은 Resolution을 통해 fine tuning을 진행하여 성능을 더욱 높이고자 하였다.

  • 이 때, 해상도를 늘린다고 하여 Patch Size를 키우지는 않고, Patch Size는 항상 Fix상태로 유지하고, 단순히 Sequence의 길이를 늘려주는 방식으로 해상도를 키운다.

  • 하지만 이런식으로 Resolution을 키워주게 되면 Pretrain에서 사용됐던 Position Embedding의 정보가 훼손될 수 있다. 본 논문에서는 이를 방지 하기 위해서, 원본 Image에서의 상대적인 위치를 파악하기 위한 2D interpolation을 진행한다. 저자들은 이것이  2D 구조에 대한 정보를 임의로 집어넣는 유일한 Inductive Bias라고 주장한다.

 

 

 

Result


  • 학습 결과, ViT 방식이 충분한 크기의 데이터셋(JFT-300M)으로 학습된 경우 CNN SOTA 모델들과 비교했을 때, 뒤쳐지지 않는 모습을 보였다.

  • 또한, 모델을 학습하는데 걸리는 Resource에 대한 지표인 TPUv3-core-days 또한 논문이 발표될 당시 SOTA였던 EfficientNetL2에 비해서 훨씬 작은 값을 가지는 것을 통해 비교적 적은 Resource로도 학습이 가능하다고 주장한다.

  • 다만 개인적으로는 EfficientNetL2의 경우에는 굳이 JFT-300M과 같은 거대한 데이터셋을 쓸 필요가 없다는 사실이 간과되어서는 안된다고 생각한다. (CNN의 경우 Dataset이 어느 크기를 넘어서면 Saturation 될 것이므로...)

  • Attention Score가 높은 영역을 확인해보면 실제로 Classification에 유의미한 부분이 Attention Score가 높다는 것을 확인할 수 있으며, 이는 의도한대로 Image를 학습했다는 것을 의미한다.

  • 저자들은 대규모 데이터셋을 활용한 Pretraining 방식 외에도, BERT와 같이 Masked Patch Prediction을 사용한 Self Supervised Learning을 시도해보았지만, 대규모 데이터셋에 대한 전이학습에 비해서 성능이 많이 떨어졌다고 한다.

 

 

Conclusion


  • 본 논문에서는 NLP에서의 Transformer의 크나큰 활약을 보고 감명을 받아, Vision Task에서도 BERT와 같은 형태의 Transformer Encoder를 활용할 수 있도록 약간의 수정을 진행한 ViT Architecture를 제안하였다.

  • 비록 Transformer는 CNN에 비해서 낮은 Inductive Bias를 가지고 있기 때문에, ImageNet 학습만으로는 CNN의 성능을 이길 수 없었지만, JFT-300M과 같은 거대 데이터셋을 활용한 학습을 통해 CNN의 Inductive Bias를 능가하였다.

  • 뿐만 아니라, CNN 대비 낮은 학습 리소스를 활용하며, SOTA 모델에도 맞먹는 높은 정확도를 가지고 있어 Vision 영역에서의 Transformer의 유망성을 보인 사례라고 할 수 있다.

  • 본 논문에서는 비록 SOTA를 크게 뛰어넘지도 못했고, Object Detection, Segmentation에서의 활용등의 실험을 진행하지는 못했지만 Transformer를 Vision Task에 적용하는 새로운 시도를 진행하였으며, CNN과 비슷한 성능을 얻어냈다는 점에 큰 의의가 있다. 연구분야를 새롭게 개척했다고 해야 하나?

  • 실제로 본 논문이 발표되고 얼마 되지 않아서 DeiT, Swin Transformer, CMT, CvT 등 수많은 방식이 연구되었으며 끊임없는 성능 향상을 나타내고 있다. 최근엔 아예 SOTA Leaderboard를 Transformer류가 점령한 상태이다.

  • 남들이 생각만 해봤던 아이디어를 빠르게 구현하고 실험을 통해 입증하는 것이 얼마나 중요한지를 보여주는 논문이었다!
반응형
블로그 이미지

Hyunsoo Luke HA

석사를 마치고 현재는 Upstage에서 전문연구요원으로 활동중인 AI 개발자의 삽질 일지입니다! 이해한 내용을 정리하는 용도로 만들었으니, 틀린 내용이 있으면 자유롭게 의견 남겨주세요!

,
반응형


CMT: Convolutional Neural Networks Meet Vision Transformers

CNN의 Locality 개념을 단순히 ViT에 주입하고자했던 Swin Transformer와 같은 연구들과 달리 정말 Vision Transformer에 CNN을 결합한 방식으로 효과적인 결과를 얻어낸 CMT 논문을 리뷰해보자.

 

Abstract


  • 학계의 다양한 연구를 통해 Vision Transformer는 이미지를 전반적으로 확인하는 Long Range Dependency에 강한 모습을 보이며, 이미지 인식 분야에 성공적으로 적용되고 있다. 
  • 하지만 여전히 CNN과 ViT는 그 연산량과 성능에 있어서 꽤나 큰 간극이 존재한다.
  • 본 논문에서는 단순히 Transformer만으로 Vision Task를 해결하지 않고, 고성능의 CNN을 함께 사용하는 방식을 취함으로써 기존 ViT 뿐만 아니라 CNN Legacy 모델들 또한 능가하는 모델을 제안한다.
  • Long Range에 강력한 Transformer와 Feature Map을 통한 Local Feature에 강력한 CNN의 장점을 결합하여 기존 방식보다 더 성능이 높으면서도 비교적 연산량이 적은 CMT 모델을 제안한다.
  • CMT-S는 적은 연산량으로 ImageNet에서 83.5%의 Top-1 Accuracy를 달성하였고, CIFAR10에서는 99.2%, CIFAR100에서는 91.7%의 높은 정확도를 달성했다.

 

Introduction


  • 최근 몇년간 CNN의 압도적인 활약으로 인해 Computer Vision 분야는 특징점을 잘 추출해내어 이미지를 분류하는등의 Task에서 크나큰 발전을 이룩하였다.
  • 반면, Self Attention 기반의 Transformer 모델은 Token 간의 거리가 멀때도 그 연관관계를 효과적으로 찾아낼 수 있는 강점이 있기 때문에 NLP분야에서 높은 성능을 보이고 있다.
  • 최근, ViT와 같이 이러한 Transformer 구조를 Vision 영역에 도입하고자 하는 시도가 증가하고 있는데, Object Detection, Segmentation, Classification등에서 꽤나 유의미한 성능을 보이고, 때로는 CNN을 능가하기도 하면서 아주 유망하다고 평가되고 있다.
  • 그럼에도 불구하고, 동일한 크기의 Small CNN 모델에 비해서 ViT는 확연히 성능이 떨어지는 모습을 보이는데, 본 논문의 저자들은 그 이유를 3가지로 정의했다.
  • 첫째로, ViT의 경우 단순히 이미지를 패치형태로 나누고 1D Sequence의 형태로 입력을 진행한다. 비록 Transformer의 이점덕분에 Patch들 간의 Long Range Dependency를 효과적으로 찾아내긴 하지만, 어찌됐던 이는 이미지의 2D Locality와 같은 근본적인 차이점을 고려하지 않게 된다.
  • 둘째로, ViT의 경우 항상 fix된 patch size를 사용하여 Multi Scale Feature Map을 추출할 수 없으며, 특히 Low Resolution Feature Map에 취약한 모습을 보인다.
  • 셋째로, ViT는 이미지를 패치로 잘라 입력하는 방식을 취하게 되고, 이미지 크기에 대해 Quadratic한 연산량이 필요로 한다. ViT는 O(N^2*C)인 반면, CNN은 O(N*C^2) 형태이므로, COCO와 같이 1333x800 해상도, Cityscape와 같은 2048x1024등을 학습시키는데에 무리가 있다.
  • 본 논문에서는 이러한 CNN과 ViT를 적절히 결합한 Novel Architecture인 CMT를 제안한다.

 

CMT Architecture


  • 저자들은 CNN과 Transformer의 장점을 모두 활용하기 위해 위와 같은 Hybrid 방식의 Architecture를 제안한다.
  • DeiT(ViT) 방식의 경우 이미지를 패치로 잘라서 입력을 하고 Linear Projection을 통해 이를 합치다보니 기존의 위치 정보가 많이 손실되고, Poor Modeling이 되는 한계점이 존재한다.
  • 이를 해결하기 위해서 CMT에서는 CMT Stem이라는, 마치 ResNet의 도입부와 같이 Input을 처리하여 Input의 사이즈를 줄이고 핵심 정보를 추출해낸다.
  • 그리고 기존 ResNet, EfficientNet과 같은 CNN구조들과 같이 4 Stage 구조를 사용하였는데, 이는 마치 ResNet50, ResNet101과 같은 CNN의 표준 모형을 따르는 것처럼 보인다.
  • CMT 블록 내부에서는 Local Feature와, Long Range Dependency를 모두 고려할 수 있도록 제작되었으며 최종 Output은 Global Average Pooling Layer와 함께 FC를 거쳐 Classification Layer로 연결된다. CNN의 기본 구조와 거의 일치하는 구조이기 때문에, 다양한 영역에서 Backbone으로 CNN을 대체하여 사용되기 용이하다.
  • 위와 같이 4 Stage 중간중간에 2x2 Convolution Layer를 Stride 2와 함께 사용하여 마치 Modern CNN구조와 같이 Feature Map의 Scale을 점점 바꿔가면서 학습을 진행하는 효과를 가지며, 이를 통해 입력 이미지에 대한 Multi Scale Feature Map을 얻어낼 수 있고, Detection 혹은 Segmentation과 같은 downstream task에 효과적인 모습을 보인다.

 

CMT Block


LPU(Local Perception Unit)

  • Rotation과 Shift는 Vision Task에서 흔히 사용되는 Augmentation 기법이다. 이러한 변형은 모델의 최종 예측에 영향을 미치면 안된다는 Translation-Invariance를 만족시키기 위함이다.
  • 하지만 기존 ViT 구조에서 사용하던 Absolute Position Encoding은 각각의 Patch에 대해 Unique한 값을 할당하기 때문에, 이러한 Invariance의 원칙을 손상시킨다. 또한 이러한 방식은 이미지의 Locality 정보를 무시할 수 있으며, Patch의 구조적인 특성 또한 간과될 수 있다.
  • 이러한 한계점을 개선하기 위해서 본 논문에서 제안하는 LPU 방식은 Depth Wise Convolution을 3x3로 진행하여 Locality 정보를 추출해내고 Residual을 더해준다.

 

Lightweight Multi-head Self-attention

기존 Attention
LightweightAttention과 Relative Position Bias

  • 기존 Self Attention 방식의 경우, 입력 X가 nxd의 형태를 가질 때, 이를 Linear하게 Transform하여 Query와 Key, Value를 각각 쪼개서 처리하였지만, 이는 매우 많은 연산량을 요구로하게 된다.
  • 본 논문의 저자들은 보다 k x k 크기의 depth-wise convolution을 stride k와 함께 진행하여 Query와 Key의 크기를 감소시켜 연산량을 줄였다.
  • 또한 Swin Transformer에서 사용되었던 Relative Position Bias 개념을 채택하였다. Swin Transformer에서는 그냥 단순히 좌표의 상대거리를 통해 Relative Position을 구했던 반면, 해당 논문에서는 이를 임의로 초기화 한 후 학습 가능한 방식으로 학습을 진행하였다.

 

Inverted Residual Feed-forward Network

  • 기존 ViT의 경우, FFN으로 2개의 Linear Layer를 사용하였다. 또한 첫번째 Layer는 expansion인 4만큼 차원을 증가시켜주고, 두번째 Layer를 거치면서 다시 원래의 차원으로 돌아오는 방식이다. (ResNet의 BottleNeck구조)

  • 본 논문에서 저자들은 기존 Bottleneck 구조인 wide-narrow-wide 방식이 아닌 MobileNetV2에서 사용됐던 narrow-wide-narrow 방식을 사용한다. 이는 이미 bottleneck에 충분히 필요한 데이터가 모두 있다고 가정하고, input과 output의 차원을 줄여서 메모리 사용량을 개선시킨다. 해당 방식은 1x1 Conv를 거쳐 채널을 확장시킨 뒤, 3x3 depth wise convolution을 수행하고 다음 1x1 Conv에서 다시 채널을 축소시키는 방식이라서 Inverted Residual Feed-Forward Network로 명명되었다.
  • 또한 깊은 Layer 구조에서의 효과적인 학습을 위해서 ResNet 방식으로 Shortcut을 더해주어 정보를 보충하는 방식을 적용하였다.

 

Scaling Strategy

  • EfficientNet 구조의 영감을 받아 Compound Scaling 전략을 도입하였다.
  • 이를 통해 Depth, Dimension, Resolution에 대해서 가장 최적의 값을 Grid하게 Search하게 된다.

 

 

Result


ImageNet Classification

 

 

Transfer Learning

 

 

Object Detection, Instance Segmentation

 

Conclusion


  • 본 논문에서는 Vision Transformer들의 장점인 Long Range Dependency 추론 능력과, CNN의 강점인 Locality를 고려한 추론 능력의 이점을 모두 얻기 위해서 두가지 모델을 함께 섞은 Hybrid Architecture인 CMT를 제안하였다.
  • Inverted Residual FFN, Lightweight MHSA등을 적용하여 연산량을 효과적으로 감소시켰으며, 이로 인해 적은 FLOPS를 가지면서도 CNN을 포함한 기존 모델들에 비해서 높은 정확도를 가진다.
  • Classification 뿐만 아니라, 기본적으로 마치 CNN과 같은 Architecture 구조를 가지고 있으므로 기존 CNN을 사용하던 Backbone에 손쉽게 대체될 수 있으며, 실제로 downstream task에서도 개선된 성능을 보여주었다.
반응형
블로그 이미지

Hyunsoo Luke HA

석사를 마치고 현재는 Upstage에서 전문연구요원으로 활동중인 AI 개발자의 삽질 일지입니다! 이해한 내용을 정리하는 용도로 만들었으니, 틀린 내용이 있으면 자유롭게 의견 남겨주세요!

,
반응형


YOLOX: Exceeding YOLO Series in 2021

 

YOLOv4와 달리 YOLO의 Architecture를 충실히 따르면서도, SOTA를 달성했던 YOLOX에 대해 리뷰해보자.

 

 

 

Abstract


  • 본 논문에서 저자들은 기존의 YOLO Detector에 Anchor-free 방식을 적용하였고, Decoupled Head, SimOTA와 같은 다양한 탐지 테크닉을 적용하여 SOTA를 달성하였다.
  • 또한 YOLOX-L 모델은 CVPR2021의 자율주행 워크샵에서 별도의 Ensemble없이 단일 모델 성능만으로 1위를 차지하였다.

 

 

Introduction


  • YOLO 시리즈들은 실시간 이미지 처리를 위해서 최적의 Speed / Accuracy Trade-off를 가지게끔 설계되곤 했었다.
  • 최근에 YOLOV5 모델의 경우 13.7ms 만에 48.2% AP를 가지는 최적의 Trade Off를 가지고 있다.
  • 뿐만 아니라, 2년간 Object Detection 기술이 발전하면서 학계에서는 Anchor Free Detector, Advanced Label Assignment Strategies, End-to-end Detector등 다양한 기법을 연구해나가고 있다.
  • YOLO 시리즈는 아직까지 이러한 최신 기법들이 적용된 사례가 존재하지 않으며, 따라서 본 논문에서 저자들은 YOLO 에 이러한 최신 기법들을 적용하는 시도를 진행하였다.
  • YOLOv4와 YOLOv5의 파이프라인은 Anchor Based 위주로 최적화가 진행되어있기 때문에, General 한 성능이 오히려 떨어질 수 있다고 생각하여 본 논문의 저자들은 YOLOv3-SPP를 기본 베이스 모델로 삼았다.
  • 다양한 기법을 적용한 결과, Baseline에 비해서 AP가 크게 개선되었으며, 기존에 공개됬던 ultralytics의 YOLOv3 보다도 높은 성능을 지닌다.

 

 

YOLOX


  • YOLOX는 기본적으로 1 Stage Detector로 Input - Backbone - Neck - Dense Prediction의 구조를 가진다.
  • YOLOX는 Darknet53의 Backbone을 통해 Feature Map을 추출하며, SPP Layer를 통해 성능을 개선한다.
  • FPN을 통해 Multi-Scale Feature Map을 얻고 이를 통해 작은 해상도의 Feature Map에서는 큰 Object를 추출하고 큰 해상도의 Feature Map에서는 작은 Object를 추출하게끔 한 Neck 구조를 차용하였다.
  • 최종적으로 Head 부분에서는 기존 YOLOv3와 달리 Decoupled Head를 사용하였다.

 

 

Decoupled Head

  • YOLOv3에서는 하나의 Head에서 Classification과 Localization을 함께 진행하였으나, Classification과 Bbox Regression는 서로 다른 특성을 가진다는 내용이 연구되었다. Classification에는 Fully Connected Layer가 효과적이지만, 반면에 Localization에는 Convolution Head가 보다 적절한데 이 두가지가 서로 상충된다.
  • 따라서 다양한 연구에서 이러한 Head 부분을 Double-Head 방식으로 변경하여 Classification엔 FC Head를, Localization에는 Convolution Head를 적용함으로써 성능 향상을 이룩해내었다.
  • 본 논문에서는 YOLO에 이러한 최신 연구를 반영하여 Decoupled Head 방식을 적용하였다. Classification 에는 BCE Loss를 사용하고 Localization에는 IoU Loss를 사용하여 학습을 진행한다.
  • 실험 결과, 해당 방식이 기존 Coupled Head 방식보다 Convergence 속도가 빠르고 AP가 향상되었다고 한다.

 

 

Strong Augmentation

 

  • 본 논문에서는 ultralytics의 YOLOv3 에서 적용된 Mosaic Augmentation과 Object Detection에서 사용되는 MixUp Augmentation을 적용하여 데이터를 증강시키고 학습을 진행하였다.
  • 또한, Strong Data Augmentation을 활용하여 학습을 했을 때, ImageNet 사전 학습 모델이 그다지 큰 효용이 없었기 때문에 단순히 Train from scratch 방식으로 처음부터 학습을 시작했다고 한다.

 

 

Anchor-free Manner

 

  • 최근 2년간 빠르게 발전한 Anchor Free 방식의 Detector들은 Anchor 방식의 방법론과 견줄 정도의 성능을 가질 정도로 그 수준이 많이 올라왔다.
  • 기존 Anchor 기반의 Detector들은 비록 그 성능은 뛰어날 수 있지만, 개발자들이 직접 Heuristic 하게 Tuning을 진행해주어야 하는 불편함이 존재했다. 또한 그렇게 Tuning된 Anchor Size 또한 특정 Task에 종속적이므로 General한 성능은 떨어지는 이슈가 존재하였다.
  • Anchor Free 방식은 학습을 보다 간편하고 편하게 해주고, 다양한 Hyperparameter들을 Tuning해야 하는 필요성이 없으며, 그로 인해 다양한 분야에 General 하게 일정한 성능을 보장한다.

 

 

Multi Positive

 

  • 기존 YOLOv3의 Assigning Rule을 그대로 유지한다면 원래 Anchor Free Version에서도 중앙 위치 값 1개 만을 Positive Sample로 지정하여야 하지만, 이는 그 주변에 꽤 괜찮게 예측한 다른 데이터들을 모두 무시하게 되는 효과를 가진다.
  • 따라서 Positive Sample을 중앙 위치 값 주변 3x3 사이즈로 모두 지정함으로써 이러한 고품질의 예측 값에 대해서 이득을 취할 수 있도록 한다. (FCOS의 Center Sampling 기법)
  • 이렇게 positive Sample을 증강해줌으로써, 심각한 class imbalance도 어느정도 상쇄시킬 수 있다.

 

SimOTA

 

  • 향상된 Label Assign 전략을 사용하였다. Object Detection에서의 Label Assignment는 각 지점에 대하여 Positive과 Negative를 할당해주는 것이다.
  • Anchor Free방식은 Ground Truth의 박스 중앙 부분을 Positive로 처리하는데, 문제는 하나의 지점이 다수의 박스 내부에 존재할 때이다.
  • 이런 경우 단순히 point by point가 아닌 Global Labeling이 필요한데, 이를 최적화하는 방식으로 저자는 SimOTA를 적용하였다.
  • OTA(Optimal Transportation Algorithm)은 Sinkhorn-knopp iteration등의 방법을 통해서 최적의 값을 찾아내는데 사용되는데, 이러한 iteration으로 인해 약 25%의 추가 학습 연산이 필요하게 된다.
  • 약 300Epoch의 학습이 필요한 YOLOX에게 있어서 이는 꽤나 큰 오버헤드이므로, 저자들은 이를 간단하게 iteration 없이 수행하는 Simple OTA(SimOTA)를 적용하였으며 AP 45.0%를 47.3%로 향상시키는 효과가 있었다.

 

 

Result


 

  • SOTA를 달성하였으며 여태 나온 YOLO Series 모두를 능가하는 AP를 얻었다.
  • 기존 YOLO 모델들과 마찬가지로 속도와 성능간의 Trade Off가 존재하지만, 다른 모델들과 비교했을 때 높은 성능과 FPS를 동시에 얻어내는 모습을 보인다.
  • CVPR 2021의 WAD Challenge에서 단일 모델만으로 1위를 달성하였다.

 

 

Conclusion


  • 본 논문의 저자들은 YOLO에 최신 Object Detection 기법들을 적용한 YOLOX를 소개하였다.
  • Decoupled Head, Multi-Postive, SimOTA, Strong Augmentation등 최신 연구 내용을 바탕으로 YOLOv3 기반의 모델을 효과적으로 향상시켰으며, YOLOv5에 적용했을 때도 유의미한 성능 향상을 보였다.
  • Anchor Free 방식을 적용하여 General한 성능을 보장하며, 모델 구현자로 하여금 Anchor와 관련된 다양한 Hyperparameter를 Tuning할 필요없이 간편하게 학습이 가능하도록 하였다.
반응형
블로그 이미지

Hyunsoo Luke HA

석사를 마치고 현재는 Upstage에서 전문연구요원으로 활동중인 AI 개발자의 삽질 일지입니다! 이해한 내용을 정리하는 용도로 만들었으니, 틀린 내용이 있으면 자유롭게 의견 남겨주세요!

,
반응형


Swin Transformer: Hierarchical Vision Transformer using Shifted Windows

얼마전, 기업 코딩 테스트를 진행하면서, 딥러닝 과제를 푸는 전형이 있었는데, Swin Transformer가 그 역할을 확실히 해주었었다. Microsoft에서 발표하고, Object Detection, Segmentation에서 SOTA를 달성했었고, 아직까지도 준수한 성능을 가지는 Backbone으로 활용되고 있는 Swin Transformer에 대해 간단히 알아보자.

 

 

연구의 배경


  • 대부분의 Vision Task는 CNN이 우세한 성능을 가지고 있으나, 최근 ViT와 같은 논문이 발표됨에 따라, Transformer구조를 Vision 분야에 가져오려고 하는 시도가 많다.
  • 본 논문의 저자들은 Transformer 기반의 Backbone을 제공하되, CNN이 Vision Task에서 수행하는 Role을 일부 차용하여 Vision Task에서 효과적으로 Backbone으로 사용될 수 있는 Swin Transformer를 제안한다.
  • 저자들은 Transformer가 Vision Task에서는 효과가 적고, NLP 분야에서는 효과적인 이유에 대해 2가지 차이점을 바탕으로 설명한다.
  • 첫째는, Vision Task에서는 시각적 객체들이 서로 다른 Scale을 가진다는 점으로 ViT 모델들이 항상 Fixed Scale로 접근하기 때문에 Object Detection이나, Segmentation과 같이 Scale에 민감한 Task를 잘 처리하지 못한다는 점
  • 둘째는, Image Segmentation과 같은 경우 Pixel 하나 하나에 대해서 굉장히 민감한 Task인데 ViT 방식의 경우 고해상도 Image에 대해서 Quadratic 하게 증가하는 연산량으로 인해서 고해상도 이미지를 그대로 사용할 수 없거나, 학습에 굉장히 오랜 시간과 비용이 든다는 점이다.
  • Swin Transformer는 Shifted Window 개념을 통해 다양한 Scale을 살필 수 있도록 계층 구조를 만들고, 이미지 Size에 대해서 Linear하게 연산량이 증가하는 방식의 Backbone을 제안한다.
  • Small-Size Patch부터 계산을 진행하며, 점점 병합을 통해 큰 Patch Size를 확인하는 계층적 방식을 가지고 있으며 이를 통해 마치 FPN이나 U-Net과 같이 다양한 Object Scale을 고려할 수 있게 된다.
  • 또한 Shifted Window 내부에 존재하는 patch들 간에만 Self Attention을 계산하는 방식을 통해 계산량을 획기적으로 감소시켰다.

 

 

Architecture


 

  • Patch Partition, Linear Embedding, Swin Transformer Block, Patch Merging으로 구성되어 있다.
  • Swin Transformer Block은 2개의 Encoder로 구성되어 있으며, Window Multi-Head Self Attention, Shifted Window Multi-Head Self Attention으로 구성된다.
  • 이러한 W-MSA, SW-MSA는 Window 내부에 존재하는 Patch끼리만 Self Attention 연산을 수행한다. 이는 Vision Task에서는 주변 Pixel끼리의 연관성이 높기 때문이다. 기존 Self Attention의 경우 NLP에서는 문장 맨 앞에 있던 Token과 맨 뒤에 있던 Token끼리의 연관성이 높은 경우도 더러 존재하고, 효과적으로 문맥을 이해하기 위해 모든 Attention을 계산하다보니 Image Size가 커지면 Patch가 기하급수적으로 많아지며 연산량이 Quadratic하게 증가하는 반면, Swin Transformer에서는 Vision의 특성을 고려하여 인접 영역만 체크하도록 한 것이다. 이는 CNN이 Filter를 통해 연산하는 것과 비슷한 방식의 접근이라고 보인다.
  • 하지만 W-MSA만을 이용할 경우, Window간의 연관관계를 파악할 수 없고 이로 인해 이미지 전체를 인식하는데 어려움이 발생한다. 저자들은 Window간의 Connection을 학습하면서, 연산량의 효율성은 그대로 유지하기 위해서 Shifted Window MSA를 제안한다.

 

 

SW-MSA


  • Window Partition을 나누고, 각 윈도우 간의 상관관계를 찾아내는 것이 Classification, Object Detection, Semantic Segmentation등에 큰 효과를 지니기 때문에 본 논문의 저자들은 SW-MSA를 제안하였다.
  • Window를 Shift하고 Padding을 추가하는 방식도 가능하고 구현상 더 편리하지만, 그렇게 할 경우 연산량이 크게 증가하는 부작용이 존재한다.
  • 따라서 본 논문에서는 위 그림과 같이 Cyclic Shift를 진행한 뒤, 실제로는 이웃하지 않은 Patch인 A,B,C에 대해서는 Mask를 통해 Attention을 계산하지 않도록 구현한다.
  • 그 후, 원상태로 복원시킴으로써, 효율적으로 Window간의 상관관계를 학습할 수 있도록 한다.

 

 

Relative Position Bias


  • Swin Transformer는 ViT와 달리 Positional Embedding을 입력부분에서 추가하지 않고 Relative Position Bias를 활용한다.
  • 또한, ViT에 사용된 Absolute Position Embedding 보다는, 각 Patch간에 위치를 의미하는 Relative Position Bias가 효과적이었다는 것을 실험을 통해 확인하였다.

 

 

Result


  • ImageNet Classification 에서 SOTA 달성
  • 뿐만 아니라 ImageNet을 통해 학습된 모델을 Backbone으로 사용하였을 때, Object Detection, Segmentation 쪽에서도 SOTA를 달성하였다.
  • 또한, Vision Transformer 류의 특성답게 학습 데이터가 많아질 수록 Accuracy와 mAP가 증가하는 추세를 보였다.
  • Classification은 금방 SOTA에서 밀려났지만, Multi-Scale이 중요한 Object Detection, Segmentation 쪽에서는 아직까지도 SOTA를 유지하고 있다.

 

 

Conclusion


  • 본 논문은 Vision Task와 NLP의 차이점을 명확히 구분하고, 가장 Vision Task에 잘 맞는 방식으로 Transformer를 적용한 Swin Transformer를 제안하였으며, 다양한 Vision Task에서 EfficientNet B7을 능가하거나 맞먹는 수준의 높은 성능을 보여주었다.
  • 또한 Linear한 계산 복잡도를 가지므로, 고 해상도 이미지를 효과적으로 학습시킬 수 있는 방식을 제안하였다.

 

 

개인적으로 의문이 든 점


  • 과거 진행했던 ViT와 CNN을 결합한 DeepFake Detection 연구에서는 Vision Transformer가 낮은 Inductive Bias를 바탕으로 높은 Generalization 성능을 가지고 있어서 Ensemble을 통해 측면 얼굴, 저해상도, 그림자 진 얼굴등에 대해 효과적으로 분류하는 것을 확인할 수 있었는데, 과연 CNN과 같이 Inductive Bias가 부여된 Swin Transformer와 Ensemble 해도 동일한 효과가 날까?
  • 결국 이런 방식으로 간다면 CNN을 잘 개선하는 것과 유의미한 차이가 있는가? 오히려 Transformer의 장점이 옅어질 수도 있을 것 같다.

 

반응형
블로그 이미지

Hyunsoo Luke HA

석사를 마치고 현재는 Upstage에서 전문연구요원으로 활동중인 AI 개발자의 삽질 일지입니다! 이해한 내용을 정리하는 용도로 만들었으니, 틀린 내용이 있으면 자유롭게 의견 남겨주세요!

,
반응형


Attention Is All You Need

 

Transformer 구조를 다룬 논문인 Attention Is All You Need를 리뷰해보자.

(제가 이해한 내용을 바탕으로 적다보니 혹시 잘못된 부분이 있으면 댓글 부탁드립니다!)

 

Abstract & Introduction


  • LSTM과 Gated Recurrent Neural Network 등의 모델이 Sequence Modeling에 있어서 독보적인 성능을 확립하였고, Language Modeling과 Machine Translation 부문에서 크나큰 발전을 가져왔다.
  • 하지만, LSTM류의 알고리즘들은 입력값을 순차적으로 받고 입력을 받으면서 매번 hidden state를 갱신하는 과정을 거치기 때문에 병렬화에 어려움이 있다.
  • 뿐만 아니라, 긴 Sequence 길이에 취약하고, 메모리를 많이 잡아먹는 등의 문제점이 존재하였다. 비록 이를 효율화하기 위해 수많은 연구들이 진행되었지만, 순차적 입력 방식에 따른 한계점은 근본적으로 해결되기 어려운 양상을 보였다.
  • 본 논문에서는 주로 RNN과 함께 사용되던 Attention Mechanism만을 사용한 새로운 Architecture인 Transformer를 제안하였으며, SOTA를 달성함과 동시에 8개의 P100 GPU로 12시간만에 학습을 이뤄내는등 계산효율성도 크게 향상시켰다.

 

Background


  • 기존 연구(Extended Neural GPU, ByteNet, ConvS2S)의 경우 Sequence의 계산을 최대한 줄이는 방향으로 연구를 진행하였다.
  • CNN을 Basic Building Block으로 사용하여 모든 입력과 출력 위치에 대한 Hidden Representation을 병렬적으로 계산하는 방식을 통해 효율성을 증대시켰다.
  • 하지만 위와 같은 방식은, Position이 멀어지면 이를 계산하기 위한 추가적인 연산량이 증가한다. ConvS2S의 경우엔 선형적으로 증가하고, ByteNet의 경우 Logarithmical하게 증가한다. 
  • 즉, 거리가 멀어질수록 학습이 어려워지게 만든다.
  • Transformer 방식은 이러한 연산량을 효과적으로 감소시켰으며, 비록 가장 효율적인 Resolution을 찾지 못할 수 있다는 단점도 존재하지만 이는 Multi-Head Attention 방식으로 상쇄할 수 있다고 주장한다.

 

Model Architecture


Encoder

  • 6개의 Stacked Layer로 구성되어 있다.
  • 각각의 Layer는 2개의 Sub-Layer를 가지는데, 한가지는 Multi-Head Self-Attention Layer이고, 나머지 하나는 Position-Wise Feed Forward Layer이다.
  • 각각의 Layer는 ResNet에서 제안됐던 Residual Connection을 적용하였으며 LayerNorm을 적용하였다.
  • 임베딩 Layer와 같이 각각의 Layer의 Output의 Dimension은 512이다.

Decoder

  • Encoder와 마찬가지로 6개의 Stacked Layer로 구성되어 있다.
  • 그리고 1개의 Sub Layer가 추가되었는데, 이는 Encoder Stack의 Output을 받아서 Multi-Head Attention을 수행한다.
  • 해당 Layer는 즉, Attention에 필요한 Q,K,V중 Query를 이전 Decoder Layer로 부터 입력받고, Key, Value를 Encoder의 Output 값을 활용한다.
  • 첫 Sub Layer에는 Masked Multi-Head Attention을 적용하였는데 이는 Encoder 부분의 현재 시점에서 아직 등장하지 않은 "미래시점"의 단어를 제거해줌으로써 Cheating을 방지하고, 모델이 보다 General하게 학습될 수 있도록 한다.
    이는, Transformer가 입력을 순차적으로 받지 않고 한번에 받기 때문인데, i번째 위치에서 예측을 진행할 때에는 i번째 위치 이후에 등장하는 단어들에 대해서는 접근이 불가능하도록 Mask값에 음의 무한대를 취해주는 식이다.
  • Ex) I Love You. 가 들어오고 이를 독일어로 번역한다고 했을 때, I 만 입력됐을 때, I Love You에 대한 독어 정답값을 미리 알고 있는 Decoder에서 독일어로 I에 해당하는 부분만 남기고 다른 부분을 모두 Masking 해주는 방식

Attention

  • Attention의 3가지 요소는 Query, Key, Value이다.
  • I Love You 라는 단어가 있을 때, Query는 Attention을 구하고 싶은 주체이다. 즉 I라는 단어가 I, Love, You라는 단어 각각에 대해서 얼마나 연관성을 가지는지를 구한다고 한다면, Query는 I, Key의 후보군은 I, Love, You가 된다.
  • 이렇게 Query와 Key를 통해 구해진 값을 통해 각 단어들간의 연관관계를 파악하고, Softmax를 취해주어 적절한 확률값을 구한뒤, Value와 곱해주어 Weight가 고려된 최종적인 Attention Value를 얻는다.
  • 본 논문에서 사용하는 방식은 Query와 Key 벡터를 행렬곱해주고 Scale을 진행하여 Scaled Dot-Product Attention이라고 명명되었다.
  • Dot Product Attention과 똑같은 구조이지만, 얻어지는 값에 dimension의 루트값으로 나눠주는 Scale 과정을 거친다는것이 차이점이다. 이는 작은 D값을 가질 때는 거의 차이가 없지만, 큰 Dimension값을 가질 때, Softmax가 극도로 작은 Gradient 값을 가지게끔 하는 부작용을 방지하여 성능 향상에 유의미한 효과가 있다고 저자들은 주장한다.

 

Multi-Head Attention

  • Single Attention을 통해 단순히 Attention을 계산하는 것보다, 저자들은 Q,K,V를 h번 다르게 수행하여 병렬적으로 연산하는것이 더 이득이 크다고 주장한다.
  • 단일 어텐션을 수행하지 않고, Q,K,V에 대해서 각각 d_k, d_k, d_v 차원으로 변환하는 서로 다른 h개의 Linear Projection을 진행하여 각각의 Projected Value에 대해서 병렬적으로 연산한뒤 concat, linear를 통해 최종 결과 벡터를 얻는 방식이다.
  • 이러한 방식은 다양한 Q,K의 조합을 통해서 다양한 Concept으로 학습이 가능하도록 하고 저자들이 주장하듯이, 가장 효율적인 Resolution에 가까운 결과를 얻을 수 있도록 한다.

 

Applications of Attention in Transformer

  • 이전 Decoder Layer로 부터 Query를 받아오고, Key와 Value를 Encoder Layer의 최종 output으로 부터 받아오는 방식을 통해 Decoder가 모든 Position에서 입력 문장의 전체를 확인할 수 있도록 하는 구조로, 전형적인 Encoder Decoder Attention 방식을 충실히 따랐다. (Seq2Seq)
  • Encoder는 자체적으로 문장에 대한 Attention을 학습하는 Self Attention Layer를 가지고 있으며, 마찬가지로 이전 Encoder의 Output 값을 받아 이전 영역의 모든 Positon에 대한 정보를 활용할 수 있다.
  • Decoder도 마찬가지로 Self Attention Layer를 가지고 있으나, Decoder의 Auto Regressive 성질을 보장하기 위해서 leftward information flow(i번째 시점에서, i보다 미래에 등장하는 단어를 미리 조회함으로써 i번째 단어를 결정하는 부적절한 현상)을 막도록 Masking을 진행한다. 이는 -inf의 값을 곱해주어 softmax에서 해당 위치의 원소값이 0에 수렴하도록 만드는 방식이다.

 

Positional Encoding

  • RNN이나 CNN을 전혀 사용하지 않다보니, Sequence에 대한 순서 정보를 전달할 수 없다.
  • 따라서 저자들은 상대/절대 적인 token의 위치를 전달해주기 위해서 Positional Encoding을 진행하였다.
  • 학습 가능한 방식, 주기 함수를 통한 고정 방식등 다양하게 사용자들이 선택할 수 있다.
  • 저자는 Sine, Cosine 함수를 통한 주기함수를 통해서 진행하였으며, 학습을 통한 Positional Encoding 방식과 정확도 차이가 크게 없었다고 한다.
  • 저자들은 성능차이는 그다지 없었지만, 학습에서 미처 접하지 못한 길이의 Sequence가 들어왔을 때도 적절히 처리가 가능한 Sinusoidal 방식을 채택하였다.

 

Result


 

  • 실험 결과 SOTA를 달성하였으며, 학습에 필요한 계산량 또한 획기적으로 줄인 것을 확인할 수 있었다.
  • BLEU 벤치마크 뿐만 아니라, Machine Translation, English Constituency Parsing 등 General Task에 대해서도 좋은 결과를 나타냈다.

 

Conclusion


  • 저자들은 본 논문을 통해 Attention만을 사용한 Architecture인 Transformer를 제안하였다.
  • Attention 방식과 효율적인 병렬 연산을 통해 계산량을 크게 감소시켰으며, 학습시간을 단축하면서 정확도는 기존 SOTA를 뛰어넘는 결과를 얻어냈다. (심지어 여태까지 발표된 모든 방식을 Ensemble한 것 보다도 높은 성능이다.)
  • 또한, 특정 Task에 종속적이지 않고 General하게 이곳저곳 적용이 가능하다는 것 또한 실험을 통해 입증하였다.
반응형
블로그 이미지

Hyunsoo Luke HA

석사를 마치고 현재는 Upstage에서 전문연구요원으로 활동중인 AI 개발자의 삽질 일지입니다! 이해한 내용을 정리하는 용도로 만들었으니, 틀린 내용이 있으면 자유롭게 의견 남겨주세요!

,
반응형

SCI 저널에 논문을 제출할 때 항상 큰 고민은, 바로 영어다.

논문을 읽는 것과, 직접 쓰는 것은 아무래도 큰 차이가 있다.

 

읽을 때는 술술 읽히는 내용이여도, 내가 직접 제2외국어로 작성을 하다보면,

내용이 굉장히 두루뭉술해지거나, 적절하지 않은 단어를 선택한다던가,

특히 영어 단어의 경우에는 단순히 사전적 의미 외에도 "뉘앙스"의 차이가 굉장히 크다.

 

똑같이 어떤 결과를 보이더라도,

It shows that ~ 과 It is indicated that~은 엄연히 그 느낌이 다르다.

 

이러한 사소한 차이를 해결하기 위해서, 

논문을 작성할때는 대부분 논문영어교정을 받거나, 아예 한영 번역을 받기도 한다.

 

필자는 후기가 많이 보였던 에세이리뷰(essayreview.co.kr)라는 업체에서 교정을 진행하였고,

확실히 간과했던 부분들을 많이 고칠 수 있었다.

 

 

Abstract만으로도 저렇게 많은 오류(혹은 문법적으로 어색한 부분)가 있었고, 교정을 통해 수정할 수 있었다.

 

또한, 기술적인 용어의 경우에는 바로 삭제하고 고치기보다는, 위 그림과 같이 의견을 제시하며,

만약 그대로 유지하고 싶다면 유지할 수 있고, 교정자분의 의견이 합당하다고 생각되면 제시한 의견에 맞게 최종적으로 수정할 수 있다.

 

사실 이전에도 교정업체에 논문 영문 교정을 맡겼던 적이 있는데, 이전에 맡겼던 업체의 경우, 위와 같이 의견을 제시하기보단 그냥 교정하시는 분 생각대로 바꿔서 전달해주시는 바람에, 쿠버네티스와 도커에 대한 내용을 관련 연구 섹션에서 소개하는 부분에서 마치 내가 도커와 쿠버네티스를 개발한 사람인 것 처럼 내용이 크게 변질되는 경우가 있었다. 결국 기껏 교정 받은 논문을 처음부터 다시 읽어가며 잘못된 부분을 하나하나 고쳐나가야했는데, 굉장히 피로하고 돈 아깝다고 생각이 되었던 경험이었다.

 

반면에, 에세이리뷰는 기술 용어나 은어에 대해서 저런식으로 확실하지 않은 부분은 의견만 남겨주셔서 오히려 최종본을 작성할 때 굉장히 편리했다.

 

또한 한가지 더 좋았던 점으로는, 글자와 단어수를 바탕으로 비용을 산정하는데,

미리 작성해둔 논문을 전송드리고 교정 및 검수를 의뢰했는데

먼저 전화를 걸어주시더니, "Reference 부분은 교정 및 검수가 필요없을 것 같은데, 빼드려도 될까요?" 라고 말씀하셔서

Reference를 빼고나니 가격이 전보다 저렴해졌었다. 굉장히 양심적으로 일하신다는 느낌을 받았다. 
굳이 전화로 안빼고 가격을 청구할 수도 있었는데 말이다.

 

여러모로 만족스러운 교정이었다.

이제 Accept만 받았으면 좋겠다!

 

 

 

반응형
블로그 이미지

Hyunsoo Luke HA

석사를 마치고 현재는 Upstage에서 전문연구요원으로 활동중인 AI 개발자의 삽질 일지입니다! 이해한 내용을 정리하는 용도로 만들었으니, 틀린 내용이 있으면 자유롭게 의견 남겨주세요!

,