Transformer는 2017년 구글 브레인의 'Attention is all you need'이라는 논문을 통해 등장한 모델로 처음에는 번역 성능을 높이기 위한 목적으로 고안되었으나 현재 NLP와 Vision 분야 모두에서 활발히 활용되고 있다. Transformer는 attention 메커니즘을 근간으로 한 모델으로 기존에는 attention과 RNN이 결합되어 사용되었지만 Transformer는 RNN 없이 오직 attention만 사용하여 학습 속도는 높이면서도 성능을 더욱 높일 수 있었다. 오직 attention만을 사용하기 위해서는 self-attention 방식을 적용해야 했는데 Transformer 등장 이후 이러한 self-attention 방식을 사용하는 것이 주류를 이루게 되었다. 그렇다면 Transformer가 어떤 구조를 가지고 어떻게 동작하는지 한번 파헤쳐보도록 하겠다. 

 

1. Attention

그림1

 

Transformer를 이해하려면 우선 어텐션 메커니즘을 이해해야 한다. 어텐션이 무슨 뜻인가? 바로 주의를 기울인다는 뜻이다. 바로 이 직역한 바 자체에 어텐션의 핵심이 담겨있다. 어텐션 메커니즘은 출력 단어를 예측하는 매 시점 마다 입력 문장을 참고하는 데 입력문장의 각 단어 중 현재 예측하려는 단어와 연관성이 높은 부분에 더 주의를 기울여(attention) 예측을 하는 방식이다. 그림1은 영어 문장을 한국어로 번역하는 과정이다. 번역기가 '나는' 다음 단어를 예측할 때 '나는'이라는 단어와 'I', 'am', 'a', 'student' 각각의 단어가 서로 유사한 정도를 반영하여 다음 정보를 예측한다는 것이다. am, a 보다는 상대적으로 I, student의 embedding vector에 대한 정보가 더 많이 반영되어 다음 단어를 예측하게 된다는 것이다. 

1.1 Scaled Dot-Product Attention

그림2
그림3
그림4. decoder의 query가 encoder의 key, value값을 통해 attention 계산하는 과정

 

그렇다면 이 과정이 구체적으로 어떻게 진행되는지 살펴보자. 우선 어텐션을 수행하기 이전에 Query, Key, Value 값들이 필요하다. 이는 그림2에서 확인할 수 있는 바와 같이 Q = W_q*X, K = W_k*X, V = W_v*X으로 구해진다. 그리고 나서 '나는' 다음 단어를 예측할 때는 우선 '학생'이라는 Query값과 'I', 'am', 'a', 'student' 각각의 Key값의 내적값들을 구하고 여기에 softmax 함수를 적용해  가중치인 Attention score를 산출한다. 이후 Value값을 다시 곱하여 Attention value를 구한 뒤 이를 다음 글자를 예측하는 데에 활용하는 것이다. 즉 attention score는 가중치가 되고 attention value는 value값들의 가중합으로 볼 수 있다. 이러한 과정을 담은 식이 그림2의 식이다. 이렇게 되면 Attention value는 입력문장인 'I am a student'에서 '학생'과 다른 단어들과의 유사도가 혼합된 정보를 담고있는 채로, 특히 '학생'과 더 유사한 글자에 대한 정보를 더 많이 담고 있는 채로 다음 글자를 예측하게 된다. 이때 '학생'의 Query의 크기를 1x2라 했을 때 K의 전치는 2X4로 attention socre의 크기는 1x4가 되고 vaule의 크기는 4x2이므로 attention value는 1x2의 크기를 가지게 된다. 

그림2의 식에서 d_k는 keys의 차원을 나타낸다. 입력 문장이 길어져 keys의 차원이 크게 증가하게 되면 Q*K^T의 값은 크게 증가하여 softmax 함수 상에서 기울기가 거의 0에 가까워지는 부분의 값을 갖게될 것이다. 이에 따라 기울기가 매우 작아 역전파 과정에서 학습이 제대로 되지 않을 수 있기 때문에 루트(d_k) 만큼의 값으로 scaling을 해주는 것이다. 

 

1.2 Self Attention

지금까지 본 내용들은 Decoder에서 다음 단어 예측을 위해 Encoder를 참조(attention)하는 방식이었다. 하지만 self attention은 encoder, decoder 각각 그 내부에서 각 단어들이 다른 단어를 참조하는 방식이다. 즉 encoder, decoder가 attention을 자기 자신에게 취하는 방식인 것이다. 다음 두 문장을 보면 왜 self attention 방식을 취하는지 이해하기 쉽다. 

① The animal didn't cross the street because it was too tired.
② The animal didn't cross the street because it was too wide.

 

다음 두 문장을 보면 it이 가리키는 바가 각각 animal, street로 다르다는 것을 알 수 있다. 문장을 제대로 번역하기 위해서는 문장 내 혹은 문장간 문맥을 제대로 파악하는 것이 중요하다. Transformer 모델에서는 self attention을 통해 이러한 문맥 파악 작업을 할 수 있는 것이다. 문맥을 파악하는 작업은 번역의 대상이 되는 문장에서도, 순차적으로 번역을 해나가는 과정 속에서도 모두 중요하기 때문에 self attention은 encoder와 decoder에 모두 활용이 된다. 

* RNN과 결합된 attention 모델에서는 decoder가 encoder를 참조하는 과정에서 attention이 활용되었고 문맥을 파악하는 작업은 RNN이 담당했다. 하지만 self attention 구조의 도입으로 RNN을 attention으로 완전히 대체할 수 있게 되면서 순수 attention 혈통을 가진 Transformer가 탄생하게 된다...!

그림4. 문장 통채로 Q, K, V 계산
그림5. self attention의 attention score

 

좀 더 간단한 문장을 통해 self attention이 수행되는 과정을 한번 보자. 실제로 attention이 수행될 때는 단어별로 계산되는 것이 아니라 문장 채로 들어가 병렬로 처리를 해주게 되어 'I am a student'라는 문장의 Q, K, V가 한꺼번에 구해진다. 이를 통해 attention을 수행하면 그림5의 오른쪽과 같이 attention score matrix가 도출되고 softmax( )*V 과정을 거쳐  attention value가 구해지게 된다. 이러한 작업은 encoder 뿐만 아니라 decoder에서도 수행되는데 decoder는 번역 대상이 되는 문장의 번역값 즉 답이기 때문에 완전한 문장 속에서 attention을 수행하기 어렵다. 이에 따라 masking 방식이 활용되는데 이는 1.4 Masked Multi-head Attention에서 좀 더 자세히 다루도록 하겠다.

1.3 Multi-head Attention

그림6 single-head attention
그림7 multi-head attention

multi head attention은 attention을 병렬로 처리해주는 개념이라 생각하면 된다. Q, K, V를 head의 개수 만큼 나누어 주어진 문장에 대해 attention을 수행하고 나온 결과값들을 다시 합쳐주는 방식이다. 위 예시에서는 [4x8] 사이즈인 Q, K, V를 [4x2] 사이즈로 4개로 나누어 각각 attention value를 뽑아내고 마지막에 다시 합쳐주어 [4x8] 사이즈의 원래 크기의 attention vaule를 만들어 주고 있다. 그렇다면 이러한 multi-head attention은 왜 수행하는 것일까? 그 이유는 multi-head attention을 통해서 문장 내 단어관계를 다방면으로 살펴보면서 더욱 다양하고 정확한 의미를 추출할 수 있어 복잡한 문장을 다룰 때 더 유의미하기 때문이다. 아래의 예시를 보자.

I am a college student really like to play badminton and study artificial intelligence.

다음과 같은 문장에서 single-head attention을 사용하면 동사/형용사 라는 문법적인 부분에 초점을 맞춰 like, play, study와 같은 부분에 대한 유사도가 높게 책정될 수 있다.

I am a college student really like to play badminton and study artificial intellignence.

하지만 multi-head에서는 이전과 같은 문법적인 부분에서의 유사성 뿐만 아니라 내가 좋아하는 것이 배드민턴과 인공지능 이라는 문장 내의 관계를 포착하여 유사하다고 판단해 더욱 다양한 의미를 학습할 수 있는 것이다. 따라서 multi-head attention을 사용하면 문장 내 단어간의 종속성에 대해 다양하게 파악할 수 있고 더 복잡한 문장도 충분히 이해할 수 있을 뿐만 아니라 단어간 미묘한 관계까지 잘 잡아낼 수 있는 것이다. 

1.4 Masked Multi-head Attention

그림8 Transformer의 구조

Transformer는 학습을 할 때 Masked Multi-head Attention 방식을 사용한다. 이는 'I am a student'의 문장이 학습데이터로 주어지고 그에 대한 답으로 '나는 학생 입니다'의 문장이 주어졌을 때 순차적으로 다음 단어를 예측하는 방식(auto-regressive)을 보존하기 위해 사용된다. decoder 부분에서도 현재까지의 문맥을 파악하기 위해 sefl-attention이 사용되지만 아직 '나는'까지밖에 예측하지 않은 상황인데 정답으로 주어진 뒤의 단어 '학생', '입니다'에 attention을 취해 학습에 반영하게 되면 정답을 보고 학습을 하는 방식이 되어 제대로 된 성능을 나타낼 수가 없다. 따라서 decoder 내부에서 self-attention을 취하되 현재까지 예측한 단어들만을 토대로 self-attention을 취해 학습할 수 있도록 하는 것이다. 쉽게 말하면 모델이 '답을 외우는 방식'의 학습이 아니라 실제로 '다음 단어를 예측'하도록 하는 학습을 할 수 있도록 masking이 multi-head attention에 적용되는 것이다.

그림9 maked self attention 적용 방식

 

masked self attention은 그림9와 같은 방식으로 진행된다. decoder에서 정답으로 주어진 문장에 대해 selft attention을 수행하되 현재까지 나온 단어가 아닌 부분에 대해서는 -inf 값을 주어 활성화함수를 통과했을 때 값이 0에 근사하도록 만들어 주는 것이다. 이렇게 되면 현재까지 나온 값만을 토대로 다음 값을 예측할 수가 있다. 

2. Positional Encoding

attention 방식에서는 문장 내 단어들의 선후 관계를 고려하지 못한다. I am a student라는 문장과 am I a student라는 문장의 의미는 완전히 다르다. 하지만 각 단어들의 문장 내 위치를 고려하지 못하는 attention은 두 문장을 동일하게 인식할 수 있다는 것이다. 따라서 단어들의 위치를 구분할 수 있도록 하는 장치가 필요한데 이것이 바로 Positional Encoding인 것이다. 

그림10 Positional Encoding 수식

Positional Encoding은 위와 같은 식을 통해 이루어진다. 먼저 pos는 말그대로 단어의 위치를 나타내는데 'I am student'라는 문장에서 I의 pos=0, am의 pos=1, student의 pos=2가 되는 것이다. 

그림11 location of embedding vector

위 수식에서 i는 embodding vector의 위치를 의미한다. 예를들어 한 단어가 1x4의 embedding vector를 가질 때 첫번째 벡터는 i=0이 되고 두번째 벡터는 i=1이 되는 식이라는 것이다. 이때 짝수 벡터에 대해서는 sin 함수를, 홀수 벡터에 대해서는 cos 함수를 사용하는데 이와 같이 홀짝에 다른 함수를 사용하는 것은 경우의 수를 늘려 더 긴 문장에서도 좋은 성능을 보이기 위함이다. 

그림12 size of embedding vector

 

문장의 길이가 길어져 embedding 벡터의 크기가 커지면 pos/10000^2i는 점차 0에 수렴해간다. 이에 따라 PE의 값은 0이나 1로 수렴하기 때문에 제대로 된 Positional Encoding이 이루어지기 힘들다. 따라서 2i를 d_model 값으로 나누어 더 긴 벡터들도 Positional Encoding이 이루어질 수 있도록 할 수 있다. 

 

 

코디오페라 GOAT

https://codingopera.tistory.com/43

 

4-1. Transformer(Self Attention) [초등학생도 이해하는 자연어처리]

안녕하세요 '코딩 오페라'블로그를 운영하고 있는 저는 'Master.M'입니다. 현재 저는 '초등학생도 이해하는 자연어 처리'라는 주제로 자연어 처리(NLP)에 대해 포스팅을 하고 있습니다. 제목처럼

codingopera.tistory.com

https://codingopera.tistory.com/44

 

4-2. Transformer(Multi-head Attention) [초등학생도 이해하는 자연어처리]

안녕하세요 '코딩 오페라'블로그를 운영하고 있는 저는 'Master.M'입니다. 현재 저는 '초등학생도 이해하는 자연어 처리'라는 주제로 자연어 처리(NLP)에 대해 포스팅을 하고 있습니다. 제목처럼

codingopera.tistory.com

https://www.youtube.com/watch?v=-z2oBUZfL2o

https://www.blossominkyung.com/deeplearning/transformer-mha

 

트랜스포머(Transformer) 파헤치기—2. Multi-Head Attention

트랜스포머(Transformer) Attention is All You Need 중 Multi-head Attention에 대해 정리한 내용입니다.

www.blossominkyung.com

 

 

요건 깃허브에 코드+설명 정리해놓은거

https://github.com/ksostel10/AI-X_DL_Project/blob/main/README.md

 

AI-X_DL_Project/README.md at main · ksostel10/AI-X_DL_Project

Contribute to ksostel10/AI-X_DL_Project development by creating an account on GitHub.

github.com

 

 

1. 어떤 데이터를 이용해야 하지?

데이터를 어떻게 구할 수 있을 지 고민하다 처음에 내놓은 답은 단순히 유튜브에서 축구경기 풀영상을 가져오는 것. 하지만 영상이 많지 않을 뿐더러 영상별로 길이가 다 달라 모델에 넣어주기 상당히 애매했다. 가장 긴 영상 길이 기준으로 패딩처리를 해야될 것으로 생각되지만 영상을 학습하는 과정에서 패딩 처리된 영상들은 그냥 아무것도 없는 화면이나 다름없기 때문에 모델의 성능저하에 큰 영향을 미칠 것으로 판단해 다른 데이터를 찾아보기로 했다.

구글링을 하던 와중 우리랑 유사한 프로젝트를 진행한 사람들이 SoccerNet이라는 사이트에서 축구 경기 영상 데이터를 활용한 것을 발견했고 SoccerNet의 데이터를 들여다본 결과 전후반 영상 길이가 각각 45분씩 모든 경기들이 똑같고 심지어 하이라이트 장면을 구분하기 좋게 골, 유효슈팅, 패널티킥 등 각종 상황마다 발생시간이 라벨링이 돼있는 json 파일까지 제공해줌을 확인할 수 있었다. 따라서 이 데이터를 사용하기로 결정했다.

 

https://www.soccer-net.org/data

 

SoccerNet - Data

Downloading our data

www.soccer-net.org

 

여기에 SoccerNet의 모듈을 사용해 각종 데이터를 다운받을 수 있는 코드를 정리해놨는데 우리는 여기서 Broadcast Videos, Action spotting labels 두 데이터를 활용했다. 해당 코드를 통해 데이터를 다운받으면 아래와 같은 파일이 생성된다. 

다운로드된 파일
json-v2 파일 내용

 

json 파일은 이렇게 구성돼있는데 우리는 label이 Goal, Penalty, Shots on target(유효슈팅), Shots off target(유효슈팅이 아닌 슈팅) 네 상황을 하이라이트 장면이라 정하고 이들에 해당되는 영상들을 학습 데이터로 활용하기로 결정했다.

 

2. 학습 데이터를 어떻게 구성하지?

다음의 문제는 학습데이터를 구성하는 방법이었다.  일반적으로 하이라이트 장면에서는 골장면이라고 하면 해당 골장면이 나오기까지의 빌드업 과정도 포함된다. 위 json 파일에 지정된 시간대를 직접 확인해본 결과 골장면이라고 라벨링된 시간대는 정확히 골이 들어가는 시간대를 기준으로 설정돼있었다. 따라서 "골이 들어가기까지의 빌드업 과정 + 다음장면의 자연스러운 연결을 위한 골 이후의 상황"을  고려해 골장면 이전 10초 + 이후 5초를 추가해 라벨링된 시간대 기준으로 -10~+5초 즉 15초간의 영상을 학습 데이터로 구성하기로 했다. 

여기에 이전에 오디오 데이터를 다뤄본 적 있는 팀원이 하이라이트 부분 때 해설자+관중들의 소리가 커지는 것을 보고 오디오 데이터도 학습에 활용하는 방법을 제안해 영상+오디오 데이터를 모두 활용하는 방향으로 학습을 진행하기로 하였다. 

 

이때 영상 추출과 오디오 추출은 ffmpeg-python 모듈을 이용했다. ffmpeg는 원래 쉘에서 실행되는 명령어이기 때문에 외부부 명령어를 실행시킬 수 있는 subprocess 모듈을 활용해 파이썬 코드만으로 실행될 수 있도록 하였다. 근데 찾아보니 ffmpeg-python 모듈로는 subprocess 없이 파이썬만으로 충분히 실행될 수 있었던 것 같다. 오디오추출은 먼저 영상데이터부터 추출하고 추출된 영상데이터에서 오디오를 따로 추출할 수 있도록 하였다.

def ffmpeg_extract_subclip_accurate(input_path, start_time, end_time, output_path):
    command = [
        "ffmpeg",
        "-y",  # 기존 파일 덮어쓰기
        "-i", input_path,  # 입력 파일
        "-ss", str(start_time),  # 시작 시간
        "-to", str(end_time),  # 종료 시간
        "-c:v", "libx264",  # 비디오 코덱: H.264 (효율적이고 호환성 높음)
        "-preset", "fast",  # 인코딩 속도와 품질의 균형 (fast 추천)
        "-crf", "23",  # 비디오 품질 설정 (낮을수록 고품질, 23은 적절한 기본값)
        "-c:a", "aac",  # 오디오 코덱: AAC (효율적이고 품질 유지)
        "-b:a", "128k",  # 오디오 비트레이트 (128 kbps는 일반적인 설정)
        "-strict", "experimental",  # AAC 관련 호환성
        output_path
    ]
    subprocess.run(command, check=True)

 

영상추출하는 코드를 보면 인코딩관련  옵션이 비디오는 "-c:v", "libx264" 오디오는 "-c:a", "aac"라 돼있는 것을 확인할 수 있다. 처음에는 "-c", "copy"만으로 추출을 하였는데 한창 오디오 데이터를 모델에 넣으려고 하는데 계속 텐서가 0으로 나오는 부분이 나와서 한참을 고민하다 해당 오디오 파일을 열어봤더니 소리가 아예 나오지가 않았었다. 그 오디오 파일에 해당하는 비디오 파일에서도 영상은 나오는데 소리는 따로 나오지 않아서 gpt와 구글링을 통해 찾아보니 "-c", "copy" 옵션이 문제였음을 확인할 수 있었다. 이 옵션은 코덱(인코딩, 디코딩 하는 주체)을 사용하지 않고 원본을 그대로 복사하는데 그러다보니 오디오 부분에서 데이터 손실이 발생하였던 것 같다. 이를 "-c:v", "libx264" 과 "-c:a", "aac" 같이 비디오, 오디오를 디코딩, 인코딩하는 코덱을 따로 지정하여 해결할 수 있었다. 

https://dongle94.github.io/cv/ffmpeg-python-module/

 

[ffmpeg] FFMPEG Python library

ffmpeg를 python 언어에서 다루기

dongle94.github.io

https://m.blog.naver.com/jin696/100078863430

 

코덱(Codec)그리고 인코딩/디코딩 설명

코덱(CODEC)이란 동영상을 압축(COmpressor)하거나 해제(DECompressor) 할 수 있는 프로그램을 ...

blog.naver.com

 

3. 데이터를 어떻게 학습시키고 모델은 어떻게 설계해야 될까?

비디오 데이터

우선 비디오 데이터를 학습하기 위해서는 15초간의 학습 데이터 영상에서 일부 프레임을 추출해 각 시점의 특징을 뽑아낸 뒤 해당 특징들을 시간에 따라 학습할 수 있도록 해야 했다. 일단 우리가 활용한 영상이 25fps(초당 25프레임)이였기 때문에 초당 5프레임씩 추출해 하나의 영상당 75개의 프레임을 통해 학습을 진행할 수 있도록 하였다. 또 원본영상의 height, width가 224x398이었기 때문에 224x224로 resizing 해주어 (25*15, 3, 224, 398)이었던 raw video data를 (5*15, 3, 224, 224)로 맞춰주었다. 이 75개의 프레임의 각각의 특징을 추출하기 위한 모델로는 처음에 VIT를 활용하려 했지만 기껏해야 로컬 or 코랩에서 돌리는 환경 대비 모델의 크기가 너무 커서 resnet을 활용해 특징을 추출하기로 했다. 이때 ImageNet -1K Pretrained Model을 사용해 해당 resnet 모델을 통과한 데이터의 크기는 (75, 1000)이 된다. 이후 시간축에 따라 학습하도록 하는 모델은 LSTM보다 학습속도가 빠른 GRU를 사용하였다. 이때 hidden size를 512로 사용하고 bidirectional 옵션을 True로 지정했기 때문에 output은 (512*2)가 되고 이를 linear layer에 통과시켜 크기를 512로 만들어주었다. 여기에 layernorm, relu, dropout을 적용하면 비디오 데이터 특징을 담은 텐서가 준비된다!

 

오디오 데이터

self.mel_spectrogram = MelSpectrogram(
            sample_rate=48000, n_fft=400, hop_length=160, n_mels=n_mels
)

 

오디오 데이터는 그대로 학습할 수 없기 때문에 먼저 mel spectrogram 변환을 활용해 sequence 데이터로 바꿔줄 수 있도록 하였다. 그리고 하이라이트 장면에서는 소리가 확연히 커지기 때문에 mel spectrogram을 주파수가 아닌 데시벨 기준으로 바꿔주었다. 위 코드에서 보면 mel spectrogram으로 바꿔줄 때 초당 sample_rate를 48,000(48hz)로 맞춰주었다. 그럼 총 15초 영상에서 48,000*15 = 720,000 개의 샘플이 추출되고 여기서 오디오 신호(샘플) 400(n_fft)개씩, 160(hop_length)개의 간격으로 추출하여 하나씩 데이터를 만들어 주었다.

 

https://velog.io/@eunjnnn/Understanding-the-Mel-Spectrogram 

 

Understanding the Mel Spectrogram

다음 블로그를 번역했습니다.signal은 시간이 지남에 따라 특정 양의 변화입니다. Audio의 경우 변화하는 양은 기압(air pressure)입니다. 이 정보를 디지털 방식(digitally)으로 캡처하려면 어떻게 해야

velog.io

mel spectrogram 예시

 

예를 들면 위 사진에서 15초간의 오디오 신호는 총 720,000개의 신호로 이루어져 있고 여기서 앞에서부터 신호 400개를 추출해 하나의 데이터를 구성하고 160만큼 이동한 뒤 다시 신호 400개를 추출하여 또 다른 데이터를 구성하게 된다. 그렇게 하면 대략 4500개의 데이터가 도출된다. 이때 시간의 흐름에 따라 조금씩 이동하며 데이터를 추출했기 때문에 이는 sequence 데이터가 된다. 이때 400만큼의 신호는 1x64로 표현되는데 400개의 오디오 신호를 주파수 대역별로만 나눠 한번에 표현해놓은 것으로 이해하면 된다. 이때 위 그림에 db이 있는건 주파수 대역별 db크기를 나타낸 것이므로 이를 통해 sequence 데이터를 추출한다면 400개의 오디오 신호에서 주파수 대역별로 db크기를 합쳐놓은 것으로 이해하면 되겠다. 이렇게 되면 하나의 오디오 파일이 (1, 64, 4500) = (channel, freq, time)의 텐서로 변환된다. 

 

class ConvStridePoolSubsampling(torch.nn.Module):
    def __init__(self, conv_channels=32):
        super(ConvStridePoolSubsampling, self).__init__()
        self.conv_block = torch.nn.Sequential(
            # Conv Layer 1 (1, 1, 64, 4500)
            torch.nn.Conv2d(1, conv_channels, kernel_size=3, stride=2, padding=1),  # (1, 64, 4500) -> (32, 32, 2250)
            torch.nn.BatchNorm2d(conv_channels),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)),  # (32, 32, 2250) -> (32, 16, 1125)

            # Conv Layer 2x
            torch.nn.Conv2d(conv_channels, conv_channels * 2, kernel_size=3, stride=2, padding=1),  # (64, 16, 1125) -> (64, 8, 563)
            torch.nn.BatchNorm2d(conv_channels * 2),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)),  # (64, 8, 564) -> (64, 4, 281)

            # Conv Layer 3
            torch.nn.Conv2d(conv_channels * 2, conv_channels * 4, kernel_size=3, stride=2, padding=1),  # (64, 4, 281) -> (128, 2, 141)
            torch.nn.BatchNorm2d(conv_channels * 4),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d(kernel_size=(2, 1), stride=(2, 1)),  # (128, 2, 141) -> (128, 2, 70)

            # Conv Layer 4
            torch.nn.Conv2d(conv_channels * 4, 256, kernel_size=3, stride=(2, 2), padding=1),  # (128, 2, 70) -> (256, 1, 35)  
            torch.nn.BatchNorm2d(256),
            torch.nn.ReLU(),
        )

    def forward(self, x):
        x = self.conv_block(x)
        batch_size, feautres, channel, time = x.size() # (1, 256, 1, 35)
        x = x.view(batch_size, time, -1) #(1, 35, 256) : (batch, time, features)
        return x

 

하지만 이렇게 나온 텐서를 바로 학습시키기엔 크기가 너무 크다. 그래서 합성곱 신경망을 통해 downsampling을 하는 작업을 진행해주었다. 이렇게 되면 크기가 (1, 64, 3400) 이었던 오디오 텐서를 (1, 35, 256) 사이즈로 줄이면서 특징까지 뽑아낼 수 있다. 코드 오른쪽 주석을 보면 크기가 어떻게 줄어드는지 확인할 수 있다. 이렇게 크기까지 줄어든 오디오 데이터의 텐서를 GRU 모델에 넣어준 뒤 layer norm, relu, dropout을 적용해주면 오디오 데이터의 시간적 특징을 뽑아낸 텐서들이 준비된다!

 

 

데이터 합치기

우리는 비디오 데이터와 오디오 데이터를 모두 활용해 학습을 하이라이트를 판별하기로 하였기 때문에 각각 다른 모델을 통과해 비디오, 오디오의 특징을 각각 담은 텐서를 합친 뒤 fc layer와 softmax를 통과시켜 최종 하이라이트 확률을 도출할 수 있도록 하였다. 이진분류임에도 softmax를 사용한 이유는 sigmoid를 사용했을 때 성능이 훨씬 더 안좋게 나왔고 그 이유를 확인하기 어려워 softmax를 사용하였다.

 

전체적인 모델의 구조와 코드는 아래와 같다

Highlights Classifier 구조 영상: resnet -> GRU(seq2vec), 오디오: mel-spectrogram 변환 -> subsmapling -> GRU(seq2vec) 을 적용한 뒤 둘을 합쳐 linear 통과시키는 방향으로 설계

 

class HighlightsClassifier(torch.nn.Module):
    def __init__(self):
        super(HighlightsClassifier, self).__init__()
        self.ConvStridePoolSubsampling = ConvStridePoolSubsampling()
        self.Seq2VecGRU_Video = Seq2VecGRU_Video()
        self.Seq2VecGRU_Audio = Seq2VecGRU_Audio()
        self.resnet = resnet50(weights=ResNet50_Weights.DEFAULT)
        self.fc1 = torch.nn.Linear(768, 768)
        self.fc2 = torch.nn.Linear(768, 2)
        self.audio_norm = torch.nn.LayerNorm(256)
        self.video_norm = torch.nn.LayerNorm(512)
        self.relu = torch.nn.ReLU()
        self.dropout_audio = torch.nn.Dropout(p=0.2)  # 오디오 경로 dropout
        self.dropout_video = torch.nn.Dropout(p=0.2)  # 비디오 경로 dropout
        self.dropout_fc = torch.nn.Dropout(p=0.3)     # Fully Connected Layer dropout

    def forward(self, x1, x2):
        # 오디오 경로 처리
        x1 = self.ConvStridePoolSubsampling(x1)
        x1 = self.Seq2VecGRU_Audio(x1)
        x1 = self.audio_norm(x1)  # LayerNorm 적용
        x1 = self.relu(x1)        # ReLU 적용
        x1 = self.dropout_audio(x1)  # Dropout 적용

        # 비디오 경로 처리
        num_frames, channels, height, width = x2.shape
        batch_size = num_frames // 75
        x2 = self.resnet(x2)
        x2 = x2.view(batch_size, num_frames // batch_size, -1)  # Reshape for GRU: (batch_size, num_frames, feature_size)
        x2 = self.Seq2VecGRU_Video(x2)
        x2 = self.video_norm(x2)  # LayerNorm 적용
        x2 = self.relu(x2)        # ReLU 적용
        x2 = self.dropout_video(x2)  # Dropout 적용

        # 두 경로 결합
        x3 = torch.cat((x1, x2), dim=1)

        # Fully Connected Layers
        x3 = self.fc1(x3)
        x3 = self.relu(x3)  # ReLU
        x3 = self.dropout_fc(x3)  # Dropout 적용
        x3 = self.fc2(x3)
        x3 = torch.softmax(x3, dim=1)

        return x3

 

이렇게 준비된 모델에 준비된 데이터를 넣고 학습을 시키면 highlights classifiers 모델 완성이다!

 

4. 준비된 highlights classifiers 모델로 영상을 어떻게 추출할 수 있을까?

영상추출 알고리즘

 

이 부분에선 크게 좋은 방식이 떠오르진 않아 다소 무식한 방법을 사용했다. 우선 새로 주어진 45분 비디오 풀영상 전체를 텐서로 바꿔놓고 비디오 풀영상을 오디오로 변환시킨 뒤 mel spectrogram을 적용하고 텐서로 바꿔놓았다. 이후 오디오, 비디오 텐서에서 처음부터 각각 15초 정도에 해당하는 추출하고 모델에 넣어 도출된 확률이 임계치를 넘었을 때 해당 시간대(시작, 종료시간)을 highlights 리스트 변수에 넣어놓았다. 이때 연속적으로 하이라이트 장면이 더 이어질 수도 있으므로 3초 뒤의 지점부터 다시 15초간 추출해 모델에 넣어 도출된 확률이 임계치를 넘으면 기존의 리스트에 저장된 종료시간에서 3초를 더해준 값으로 갱신하는 방식으로 하이라이트 시간대를 추출했다. 이 작업을 지속적으로 하면 우리의 highlights classifier 모델이 판별한 하이라이트 장면들의 시간대가 리스트에 쭉 담기게 되고 이 리스트에 저장된 값들을 토대로 기존의 풀영상에서 하이라이트 영상을 추출해내면 된다! 코드는 깃허브에 extract_highlights.py 파일을 확인하자.

 

5. 한계점

처음에 학습 데이터의 길이를 15초로 설정한게 미스였다. 실제 골장면은 1~2초정도에 그치는 반면 빌드업 장면, 세레머니 때문에 클로즈업 되는 장면이 나머지 13~14초를 차지한다. 패스하며 빌드업하는 장면은 전체 학습 데이터 길이중에 상당부분을 차지하여 더 중점적으로 학습되고 세레머니때문에 클로즈업되는 장면은 학습데이터 대부분이 멀리에서 촬영되는 장면인것 과는 확연히 차이가 나는 장면이기 때문에 더 치중되어 학습되어 오히려 하이라이트라고 판별한장면이 골/유효슈팅장면보다 패스를 돌리는 장면, 별 상황이 아닌데도 선수들이 클로즈업 돼있는 장면들이 더 많았던 것 같다. 

차라리 슈팅장면 전후 2~3초로 짧게 학습데이터를 구성하여 학습시킨 뒤 하이라이트라고 판별된 장면에서 앞으로 8초 뒤로 4~5초가량을 덧붙여 추출하는 방식이 하이라이트 장면 탐지를 더 잘할 수 있지 않았을까 하는 아쉬움이 남는다. 

 

 

 

 

event loop란 무엇인가?

이벤트 루프는 Node.js에서 여러 비동기 작업들을 모아서 관리하고 순서대로 실행할 수 있게 하는 도구이다.

ex) file.readFile('file.txt', callback) 

 

event loop의 내부 동작 과정

이벤트 루프는 위와 같은 과정을 거쳐 동작한다. 각 박스들은 특정 작업을 수행하기 위한 페이즈(phase)를 의미하고 다음 페이즈로 넘어가는 것을 틱(Tick)이라고 한다. 각 페이즈마다 FIFO 큐가 있는데 함수가 호출되면 큐에 쌓이게 된다. 이벤트 루프는 싱글 스레드이기 때문에 이전 페이즈가 끝나야 혹은 페이즈의 최대 콜백 수에 도달하면 다음 페이즈가 실행되게 된다.  

 

각 phase들의 역할

timer: setTimeout()이나 setInterval()에 의해 예약된 콜백을 실행한다. 이때 타이머는 콜백이 실행될 정확한 시간을 지정하는 것이 아니라 해당 콜백이 실행될 수 있는 최소 시간을 지정한다. 콜백은 지정된 시간이 지난 후 최대한 빨리 실행되지만 다른 콜백들의 실행에 의해 지연될 수 있다. 

pending callbacks: TCP 오류 유형과 같은 일부 시스템 작업에 의한 콜백을 실행

idle, prepare: 내부적으로만 사용되고 이 단계에서 이벤트 루프는 아무 작업도 수행하지 않음. 

poll: close 콜백, 타이머에 의해 예약된 콜백, setImmediate()을 제외한 거의 모든 I/O관련 콜백들이 실행(파일 읽기/쓰기, 네트워크 요청 등 다양한 비동기 작업이 수행되는 곳)

check: setImmediate() 콜백을 실행

close callbacks: socket.on('close', fn) 또는 process.exit()과 같은 종료 이벤트와 관련된 콜백 실행. 각 pahse 사이 모든 작업들이 수행됐는지 확인하고 완전히 종료.

 

이전에 이벤트 루프에서는 비동기 작업을 OS 커널에 맡기거나 워커스레드에거 넘겨 처리한다고 하였다. 특정 함수(작업)이 들어오면 이벤트 루프는 각 phase를 훑어가며 들어온 작업을 할당하고(큐에 콜백 할당) 해당 phase에서 OS 커널이나 워커스레드에 넘겨 비동기 작업을 처리하는(콜백실행) 방식으로 동작한다. 아래의 예시와 함께 보면 더 이해가 수월할 것이다. 

fs.readFile(__filename, () => {
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
    console.log('immediate');
  });
});

 

이 코드를 실행하면 이벤트루프는 어떻게 동작할까?

1. 이벤트 루프는 fs.readFile이라는 작업은 poll phase에 추가하고 poll phase에서는 OS 커널에 작업을 넘겨 처리한다. 이때 OS 커널에서 작업을 처리할 때는 논블로킹 방식으로 하기 때문에 fs.readFile() 뒤에 코드가 있다면 readFile 작업이 수행될 때까지 처리될 수 있다.

2. poll phase에서 fs.readFile()를 읽어들인 후에 콜백을 실행하는데 이때 순차적으로 setTimeout() 콜백함수가 timer에 추가되고 이후 setImmediate() 콜백함수가 check에 추가된다. 

3. check phase로 넘어가 check 큐에 대기중이던 setImmediate()의 콜백이 실행된다.

4. close callbacks phase에서 timer에 작업에 남아있는 것을 확인하고 timer로 다시 이동한다.

5. timer 큐에 남아있던 setTimeout()의 콜백함수를 실행한다.

6. 그 뒤의 비어있는 phase들을 거쳐 close callbacks phase에 오면 모든 작업들이 수행됨을 확인하고 작업을 종료한다.

 

이렇게 작업의 수행과정을 살펴보면 위 코드에서 setImmediate()가 setTimeout()보다 먼저 실행됨을 확인할 수 있다.

'NodeJS' 카테고리의 다른 글

Node.js는 무엇인가?  (16) 2024.09.19

브라우저 환경

JavaScript를 실행하려면 JS 엔진이 필요한데 브라우저에는 기본적으로 JS 엔진이 탑재되어 있다.

 

Chrome - V8

Firefox - SpiderMonkey

Safari - JavascriptCore

Internet Explorer - Chakra

 

최초의 JS 엔진은 단순한 interpreter이었지만 위와 같은 최신 JS 엔진들은 성능 문제로 JIT Compiliation(Just-In-Time)을 사용한다.

 

JIT Compilitation이란?

Interpreter : 코드를 한줄씩 번역 후 실행 => interpreter는 코드를 한줄씩 해석하고 실행하기 때문에 heavy한 기능을 수행하기에는 너무 느림

Compiler : 코드를 한번에 기계어로 변환한 뒤 실행

JIT Compiliation : 코드가 실행되는 시점에 실시간으로 interpreter 방식으로 기계어 코드 생성, 해당 코드가 컴파일 대상이 되면 코드를 컴파일하고 캐싱함. 동일 코드 다시 나오면 컴파일하지 않고 캐싱한 부분 재사용

interpreter의 장점(한줄씩) + compiler의 장점(캐싱-컴파일한 부분 저장해두는 것)을 합친 방식이라 보면 됨

 

브라우저 밖의 외부 환경

runtime(환경)이란?

런타임이란 프로그래밍 언어가 구동되는 환경을 말한다. 여러 웹 브라우저들도 JS의 런타임 환경이 되고 웹 브라우저 외부에서는 Node.js 와 같은 런타임을 사용한다. Node.js는 프로그래밍 언어도, 프레임워크도 아닌 자바스크립트 런타임이다. 

출처: https://goldenrabbit.co.kr/2024/03/19/node-js-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%A1%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%9E%85%EB%AC%B8%ED%95%98%EA%B8%B0-%E2%9D%B6/

 

Node.js 시스템은 위와같이 V8, Node.js API, Node.js 바인딩, Libuv로 구성되어 있다.

V8: 자바스크립트 코드를 실행하는 엔진으로 1+1과 같은 간단한 작업을 실행한다.

Node.js API: 파일 시스템. http, crypto 암호화 처리 등 복잡한 기능을 수행하는 여러 api, 이 api들은 자바스크립트로 작성된 것도 있지만 C++이나 C와 같은 low level 언어로 작성된 것도 있다.

Node.js 바인딩: V8에서 처리하지 못하는 C, C++, 파일시스템 등과 같은 코드들은 libuv에서 처리해야됨. Node.js 바인딩이 이 libuv에 작업을 넘겨주는 역할을 수행한다. 

Libuv: event loop를 기반로 비동기 I/O task를 처리하는 다중 플랫폼  C 라이브러리다. 플랫폼 별로 가장 빠른 비동기 IO 인터페이스로 통일된 코드를 돌릴 수 있다는 장점이 있다. 이는 특정 함수를 실행할 때 플랫폼(widow, mac 등)별로 따로 코드를 짜야되지만 libuv는 그럴 필요 없이 libuv 하나만 있으면 알아서 플랫폼에 최적화된 코드를 실행해준다는 의미이다.

Node.js는 v8이 코드를 해석하고 Node.js API들 중 하나의 함수를 호출한 뒤 Node.js 바인딩이 libuv에서 원하는 작업을 처리하게 하는 방식으로 동작한다(API에서 바인딩을 호출하고 바인딩에서 libuv를 호출하는 구조).

 

libuv는 어떠한 방식으로 동작하는가?

node.js는 자바스크립트 언어를 사용하는데 자바스크립트 언어는 싱글스레드이기 때문에 한 번에 하나의 작업만 처리할 수 있다. 그렇다면 node.js는 비동기로 여러 작업을 한번에 수행할 수 없는 것일까?

그렇지 않다! node.js는 libuv에 있는 Event loop를 통해 비동기로 여러 작업을 처리할 수 있다. 작업 처리의 순서는 아래와 같다.

1, 우선 V8엔진에서 libuv로 전달된 작업은 libuv 내부 이벤트 큐에 추가된다.

2. 이벤트 큐에 쌓인 요청은 차례대로 이벤트 루프에 전달되고 운영체제 커널에 넘겨져 비동기 처리 작업이 수행된다(논블로킹 처리).

3. 운영체제 내부적으로 비동기 처리가 힘든경우(DB, DNS lookup, Crypto, 파일처리 등) 스레드 풀에 넘겨져 작업이 수행된다(블로킹 처리).

4. 완료된 작업은 이벤트 루프로 전달되고 이벤트 루프에서는 콜백으로 전달된 요청에 대한 완료 처리를 한 뒤 호출된 부분으로 다시 넘긴다.

이러한 과정을 통해 node.js는 비동기 작업을 수행한다.

근데 위 그림을 보면 블로킹 처리, 논블로킹 처리가 모두 존재하는데 이 부분이 살짝 헷갈린다. 

출처: 패스트캠퍼스 10개 프로젝트로 끝내는 Node.js의 모든 것(Express & Nest.js) 초격차 패키지 Online 강의

 위 그림에서 main thread는 event loop를 담당하는 일꾼이다. 이는 운영체제 커널에서 지원하는 비동기 작업들을 받으면 커널의 비동기 함수들을 호출한다. 하지만 커널에서 지원하지 않는 작업이나 이벤트 루프를 블로킹할 수 있는 시간이 오래 걸리는 몇몇 작업들이 들어오면 스레드 풀의 워커 스레드에 넘겨준다. 위 그림에서 main thred를 제외한 나머지 부분이라고 보면 되겠다. 워커스레드는 기본적으로 4개로 구성되어 있고 128개까지 늘어날 수 있다. 만약 워커 스레드가 4개일 때 5개 이상의 작업이 들어오면 스레드가 작업이 끝날 때까지 대기해야 한다. 이 때문에 위위 그림에서 블로킹 처리라고 적어놓은게 아닐까 싶다. 

 

요약

- 코드가 호출 스택에 쌓인 후 실행되는데 그 작업이 비동기 작업이면 이벤트 루프는 OS커널 or 스레드풀에 작업 위임

- 비동기 작업 수행 뒤 콜백함수 호출

 

다음 글에서는 event loop가 무엇인지 자세히 알아봐야겠다.

 

 

 

https://ko.wikipedia.org/wiki/JIT_%EC%BB%B4%ED%8C%8C%EC%9D%BC

 

JIT 컴파일 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. JIT 컴파일(just-in-time compilation) 또는 동적 번역(dynamic translation)은 프로그램을 실제 실행하는 시점에 기계어로 번역하는 컴파일 기법이다. 전통적인 입장에서 컴

ko.wikipedia.org

https://hyeinisfree.tistory.com/26

 

[Java] JIT 컴파일러란?

JIT 컴파일(just-in-time compilation) 또는 동적 번역(dynamic translation)은 프로그램을 실제 실행하는 시점에 기계어로 번역하는 컴파일 기법이다. 컴파일러 vs 인터프리터 컴파일러와 인터프리터 모두 high

hyeinisfree.tistory.com

https://joe-cho.tistory.com/16

 

Libuv 라이브러리(feat. 이벤트 루프) - 3

✏️ Node.js ➡️ 네트워크 서버 구축에 특화되어 있고, V8엔진은 기존의 인터프리터 언어인 자바스크립트를 컴파일 방식으로 처리해 빠른 속도로 작업을 수행할 수 있도록 합니다. 또한, Livub 라

joe-cho.tistory.com

 

'NodeJS' 카테고리의 다른 글

[Node.js] Event Loop 이해하기  (4) 2024.09.20

컴퓨터 비전 분야의 여러 과제

 

컴퓨터 비전 분야의 여러 과제2

 

컴뷰터 비전분야의 대표적인 과제로는 Image Classification(이미지 분류), Localization, Object Detection(객체탐지), Semantic Segmentation(의미론적 분할), Instance Segmentation(객체분할)이 있다. Image classification은 이미지가 무엇을 나타내는지 분류하는 것으로 간단하다. Localization하나의 객체에 대해 bounding-box를 그림으로써 위치를 표현하는 작업이다. Object Detection여러 객체에 대해 Localization과 Classification을 모두 수행하는 것이라 할 수 있다. Segmentation은 bounding-box가 아닌 픽셀 단위로 Classification을 수행하는 문제로 Sementic Segmentation은 객체 각각을 구분하지 않고 동일 class일 경우 하나로 구분하는 것, instance segmentation은 동일 class의 서로 다른 객체들을 모두 구분하는 것을 의미한다. 각각의 과제들에 대해 한번 자세히 알아보도록 하자.

 

1. Classification

분류 모델은 입력된 이미지가 미리 정해둔 카테고리 중 어느 곳에 해당하는 지 구별하는 작업이다. 일반적으로 데이터 기반 방법(Data-Driven Approach)가 많이 쓰이는데 이는 이미지를 통해 모델을 학습시킨 뒤 분류작업을 수행하는 방식이다. 분류 모델들은 Object Detection이나 Segmentation 등 여러 작업들의 Backbone으로 이용되기도 한다.

 

발전사에 대해 간단하게 다뤄보자면 CNN과 같은 Deep Neural Network가 등장하기 이전에는 일반적인 머신러닝 기법들이 사용되었다. 이진 분류만 가능한 모델에는 로지스틱 회귀, 서포트 벡터 머신 classifier가 있고 다중 클래스 분류에서는 SGD, 랜덤 포레스트, 나이브 베이즈 classifier 같은 모델들이 사용되었다. 이진 분류기의 경우 여러개를 사용해 다중 클래스 분류에 활용되기도 하였다. 

 

1998년 얀 르쿤 교수가 처음으로 CNN을 분류에 활용한 모델인 LeNet-5을 제안한 이후로 CNN은 줄곧 분류 문제에 활용되어 왔다. CNN을 활용한 분류에 있어서 초기의 패러다임은 "합성곱 층을 얼마나 깊게 쌓을 수 있느냐" 였다. 이 패러다임 하에서 분류 모델의 발전은 ILSVRC 대회가 주도한다. 아래 그림은 ILSVRC 우승 모델들을 2010년부터 에러율과 함께 나열해 놓은 것인데 2012년 AlexNet부터 쭉 CNN이 활용된 모델들이다. 해당 모델들의 합성곱 층을 중심으로 어떻게 발전해왔는지 한번 확인해보자. 

https://www.researchgate.net/figure/Algorithms-that-won-the-ImageNet-Large-Scale-Visual-Recognition-Challenge-ILSVRC-in_fig2_346091812

 

LeNet-5(1998): 초기 CNN 분류 모델로 4개의 layer를 가지고 있다.

 

AlexNet(2012): 16.4%의 에러율로 2012 ILSVRC에서 우승한 모델이다. Lenet-5과 구조는 유사하지만 8개의 layer를 가진 조금 더 깊은 구조이다. GPU의 활용, ReLU, dropout, data augmentation(데이터 증강) 같이 지금은 아주 일반적으로 사용하는 것들이 이 모델에서 처음으로 사용되었다. 또한 LPN이라는 정규화 단계의 활용을 통해 더욱 다양한 특징을 학습할 수 있도록 하였다. 이를 통해 성능 개선이 1~2% 정도로 정체돼있던 상황에서 10% 이상의 성능 개선을 이끌어냈다.

 

ZFNet(2013): 11.7%의 에러율을 기록하며 2013 ILSVRC에서 우승한 모델이다. AlexNet의 각 layer를 시각화해 CNN 구조를 수정한 모델로 AlexNet과 같이 8개의 layer를 가졌다.

 

GoogLeNet(2014): 6.7%의 에러율을 기록하며 2014 ILSVRC 우승한 모델이다. "Inception"이라는 독특한 구조를 통해 22개의 layer를 가진 상당히 깊은 네트워크를 구성하면서도 AlexNet보다 10배나 적은 파라미터를 사용한다. 

 

VGGNet(2014): 7.3%의 에러율로 2014 ILSVRC에서 준우승한 모델이지만 아주 단순한 구조로 GoogLeNet보다 더 많은 주목을 받았다. 16, 19개의 layer를 가지며 5x5의 filter를 사용한 이전 모델들과는 다르게 3x3 filter를 사용하고 padding을 처음으로 도입하면서 깊은 층을 쌓을 수 있게 됐다. 

 

 ResNet(2015): 3.6%의 에러율로 2015 ILSVRC에서 우승한 모델이다. 20층 이상에서부터는 layer를 깊게 쌓을수록 성능이 낮아지는 현상인 Degradation 문제가 발생했는데 Residual Learning이라는 개념을 도입해 152층까지 네트워크를 쌓은 모델이다. (단순한 발상 하나로 저렇게까지... 진짜 대단한 것 같다)

 

이후에 등장하는 GoogLeNet-v4(2016), SENet(2017)은 모두 GoogLeNet의 inception과 ResNet의 residual learning을 베이스로한 모델들이며 최종적으로 에러율이 인간의 절반 수준인 2.3%를 달성하며 Classification 모델의 눈부신 성장을 일궈낸 ILSVRC은 종료되었다.

https://paperswithcode.com/sota/image-classification-on-imagenet

 

ResNet(2016)의 등장 이후 다양한 변형모델들이 출현하였고 RNN과 강화학습을 활용한 NasNet(2018) 같은 새로운 모델도 두각을 보였다. 2019년 후반부터 EfficientNet(2019)이 등장하는데 이는 경량화를 위한 Conv구조인 MBConv을 사용하고 AutoML을 통해 depth, width, resolution의 최적의 조합을 찾아 구현한 모델이다. 이를 점차 개선하여 2020년 전후로 나온 모델들은 높은 정확도를 보이며 SOTA를 달성한 모습(NoisyStudent, Meta Psudo Labels 등)을 확인할 수 있다. 

 

하지만 2021년 구글에서 "AN IMAGE IS WORTH 16x16 WORDS: TRANSFORMERS FOR IMAGE RECOGNITION AT SCALE"이라는 논문을 발표하면서 분류모델의 패러다임은 Transformer를 컴퓨터 비전에 활용하는 방향으로 바뀌게 된다. 이런 Transformer을 활용한 비전 모델을 VIT(Vision Transformer) 모델이라고 한다. single encoder, dual encoder, encoder-decoder 등을 활용한 여러 VTI 모델들이 등장했고 Convolution과 Transformer(Attention)이 합쳐진 CoAtNet(2022)과 같은 모델들도 등장했다. 위 그림의 출처에 들어가보면 VIT 등장 이후 현재까지 5개의 SOTA중 4개가 VIT를 활용한 모델인 것을 확인할 수 있다. 

 

2. Object Detection

https://itsjb13.medium.com/building-an-advanced-object-detection-application-for-autonomous-vehicles-yolov7-intel-pytorch-478ee5cedd39

 

object detection은 위 사진과 같이 하나의 이미지에서 여러 물체를 분류하고 위치를 추정하는 작업이다. 이때 분류와 위치 추정 작업을 동시에 하느냐 구분해서 하느냐에 따라 1-satge object detecter, 2-satge object detecter로 나뉜다. 

 

2-stage Object Detector

 

2-stage object dectector는 객체 후보지역들을 선정(region proposal) 한 이후 해당 지역들에 대한 classification이 이루어지는 구조이다. 객체 후보지역 선정 방식에는 selective search, RPN(Region Proposal Network)와 같은 기법들이 있는데 이들은 이미지 내 모든 영역들을 커버(탐색)하기 때문에 1-stage 방식보다 정확성이 높다. 하지만 많은 영역을 커버하는 만큼 많은 시간과 컴퓨팅 자원들이 소모된다. 따라서 자율주행과 같이 실시간으로 빠르게 객체를 탐지해야 하는 분야에서는 활용하기 어려운 방식이다. 

 

1-stage Object Detector

 

1-stage object detector는 region proposal과 classification이 동시에 이루어지는 구조이다. 위 사진에서 보면 conv layer에서 특징 추출이 이루어진 뒤 특성 맵에서 각 grid or spatial location마다 바로 분류가 이루어진다고 나와있는데..... 사실 저대로는 직관적으로 이해가 잘 가지는 않을 것이다. 

VGG architecture

1-stage를 이해하려면 일단 backbone architecture를 확인해볼 필요가 있다. 좌측 그림을 보면 7x7x512 이후에 FC layer가 나온다. 이미지의 특징을 뽑아낸 뒤 FC layer에서 각 클래스 별로 속할 확률을 계산하여 분류 작업을 수행해주는 것이 일반적인 CNN 구조이다. 하지만 1-stage detector에서는 Convolutional layer를 거쳐 나온 이미지에 대한 풍부한 특징을 가진 7x7x512의 특성맵을 가지고 새로운 작업을 수행해 준다. 

 

7x7x512는 자동차의 특징들을 추출한 feature map들이다. CNN은 대략적인 공간적 특징들은 보존하므로 원본 이미지를 7x7 grid로 나눈 것 중 대응하는 부분의 특징들을 feature map이 잡아낸다고 볼 수 있다. 그렇게 되면 원본 이미지에서 각 grid마다 특정 class에 대한 확률을 계산할 수 있다. 이때 각 grid마다 해당 grid를 중심으로 하는 bouding box에 대한 정보들도 포함하고 있다. 우측 그림에 보이는 사람과 오토바이의 bounding box와 같이 말이다. 따라서 각 grid마다 박스의 정보를 표현하는 t_x, t_y, t_w, t_h와 각 클래스에 속할 확률을 포함하는 c_1, c_2, c_3, c_4 그리고 물체가 있음을 감지하는 p_obj과 같은 정보가 포함되어 최종적으로 7x7x__과 같은 기존 CNN 구조에서와는 다른 텐서가 도출되는 것이다. 이는 특성 맵에서 각 grid마다 클래스에 속할 확률을 계산해주고 bounding box의 위치를 감지하여 최종적으로 각 grid별 위치정보분류 정보를 도출해내는 과정을 처리한다고 볼 수 있다. 앞선 2-stage는 지역을 추출한 이후 분류작업이 이루어지는 형태였으나 1-stage에서는 위와 같은 과정을 거쳐 나타나는 텐서를 통해  region proposal과 분류가 한꺼번에 이루어지는 것이다! 이와 같이 대부분의 1-stage object detector 모델들은 위치정보와 분류정보가 포함된 텐서를 한번에 도출한다. 특징을 추출하는 과정에서 차이가 있지만 말이다. 

 

이러한 1-stage detector는 2-stage보다는 정확도가 낮지만 속도가 빠르기 때문에 real-time detection이 중요한 자율주행과 같은 분야에 적용되기 더 적합하다는 특징이 있다. 아래는 2-stage와 1-stage의 발전과정을 모델 별로 나타낸 표이다. 차례대로 공부해보면 좋을 듯 하다.

object detection 모델의 발전과정

3.  Segmentation

https://wikidocs.net/148872

Classification이미지 전체에 대한 분류, Object Detection지역 단위 분류작업이었다면 Segmentation픽셀 단위 분류작업이다. 위 그림과 같이 이미지의 모든 픽셀을 지정된 class로 분류하는 작업인 것이다. segmentation은 각 pixel이 속하는 class만 구분하는 방식인 Semantic Segmentation과 같은 class라 하더라도 서로 다른 객체는 구분하는 Instance Segmentation 방식으로 구분된다. Semantic Segmentation의 대표적인 모델에는 FCN, SegNet, U-Net, DeepLab 등이 있고 Instance Segmenation에는 Mask RCNN, MMDetection, YOLACT 등이 있다. 

 

Segmentation은 각각의 픽셀을 분류하는 작업이기 때문에 원본 이미지 내 각 사물들의 위치를 보존하는 특성맵이 필요하다. 하지만 CNN은 연산량 문제로 pooling layer가 필연적인데 여기를 통과할 때마다 이미지의 크기가 줄어들어 위치에 대한 세부적인 정보는 조금씩 잃게 된다. 심지어 네트워크 끄트머리의 FC layer를 통과하면 위치에 대한 정보는 아예 사라진다. 따라서 이러한 부분을 고려한 네트워크가 필요하다.

 

① Sliding Window

Sliding Window 방식

 

초기의 해결방법은 Sliding Window 방법이었다. 특정한 크기의 window가 전체 이미지를 한 픽셀 단위로 훑는데 각각의 window 만큼의 이미의 중앙 부분의 픽셀을 CNN에 집어넣어 분류 작업을 수행하는 것이다. 그렇게 되면 CNN을 통한 분류작업이 픽셀 수만큼 진행된다. 연산량이 어마어마하게 든다는 것이다.

 

② FC Layer -> Convolutional Layer

FC layer를 Convolutional layer로 바꾸는 방식
FCN의 위치정보 보존 원리

 

 

Sliding Window의 연산상의 문제와 위치보존의 문제를 해결하기 위해 FC layer를 C(클래스 수) x H(세로) x W(가로) 크기 만큼의 Convolutional layer로 대체하는 방법을 사용한다. FC layer는 고정된 크기의 이미지만 입력받을 수 있다는 취약점도 존재한다. 위 그림의 아래 그림을 보면 FC layer는 고양이를 분류하기 위해 고양이만큼 잘린 이미지를 사용할 수 밖에 없지만 FC를 Conv로 바꾼 구조에서는 이미지 크기에 구애받지 않는 것을 확인할 수 있다. 이는 연산량의 감소에도 큰 영향을 미치는데 이 부분에 대한 자세한 내용은 아래 첨부한 게시글에서 확인할 수 있다. Conv layer를 사용한 구조를 보면 해상도가 낮기는 하지만 고양이가 있을 가능성이 높은 지역에 heatmap 값들이 높게 표현된 것을 확인할 수 있다. 이처럼 FC를 Conv로 대체하면 연산량을 줄이면서도 원본 이미지 자체에서 위치정보를 어느정도 보존할 수 있는 것이다.

 

사실상 위 그림의 두 구조를 보면 다른 점이 있다. 아래 그림은 특성맵의 크기가 점차 작아지는데 위쪽 그림은 특성맵의 크기가 그대로다. 후자가 초기에 제시된 Conv layer를 사용한 구조인데 특성맵의 크기가 원본과 동일하기 때문에 각각의 픽셀의 위치 정보들이 그대로 보존된다. 위 그림의 아래쪽 그림에서 heatmap의 해상도가 낮은 것은 특성맵의 크기가 줄어들었기 때문인데 위쪽의 초기 구조에서는 특성맵의 크기가 원본과 동일하기 때문에 원본과 동일한 해상도를 지닌 채로 각각의 픽셀이 분류된다. 즉 원본의 픽셀 수 만큼 각각의 픽셀이 자신에 해당하는 이미지의 특징을 나타내는 것이다. 하지만 Conv net을 원본 이미지 크기를 그대로 밀고 나가며 계산하기 때문에 Sliding Window보다 계산량이 적다 해도 여전히 연산이 많은 부분이 있다. 

 

FC layer를 Convolution layer로 대체하면 연산량이 감소하는 이유가 궁금하다면 아래 글을 확인하자.

https://calmdevstudy.tistory.com/5

 

Object Detection: 초기 Region Proposal 방식(Sliding Window, Selective Search)

Object detection을 수행하려면 객체가 존재할만한 지역의 후보군들을 추려내는 작업이 필요한데 이를 Region Proposal이라고 한다. 위 사진에서 2번과 같이 지역을 각각 나눠 하나씩 모델에 집어넣어 분

calmdevstudy.tistory.com

 

③ Encoder-Decoder 구조

donwsampling과 upsampling을 사용하는 방식

 

이전의 구조는 원본 이미지의 크기를 유지하며 CNN을 통과시키기 대문에 연산량이 많이 들었다. 이에 대한 해결책으로 제시된 것이 Encoder - Decoder구조이다. 이는 특징을 추출할 때에는 크기를 줄이고 특징이 추출되면 이를 다시 자츰 원본의 크기로 늘려주는 방식이다. 전자를 donwsampling, 후자를 upsampling이라고 한다. donwsampling은 pooling, stride를 통해서 진행이 된다. 그렇다면 upsampling은 어떻게 진행될까?

 

Upsampling에는 unpooling, max unpooling, bilinear interpolation(선형보간법), Deconvolution 등 여러 방법들이 있지만 여기서는 transposed convolution(전치 합성곱)에 대해 알아보도록 하겠다. 

일반적인 convolution의 연산 과정

 

우선 일반적인 합성곱 연산 과정을 확인해볼 필요가 있다. 상단 그림과 같이 실제 연산이 진행될 때는 kernel, input, output이 행렬로 표현된 뒤 계산이 진행된다. 3*3 kernel과 4*4 input의 연산을 통해 2*2 output이 도출된다고 하면 합성곱 연산은 아래와 같은 과정을 거쳐 이루어지는 것이다. 

3*3 kernel -> 4*16 행렬로 변환

4*4 input  -> 16*1 벡터로 변환

행렬 연산 후 4*1 벡터 도출

* 위 그림의 빈칸은 0으로 채워지는데 kernel의 크기 내에 포함되지 않는 input값들은 연산이 진행되지 않기 때문에 0으로 채워 넣는 것이라 할 수 있다. 

 

Transposed convolution의 연산과정

 

Transposed convolution은 원본 이미지의 특징을 담고 있는 특성맵을 원본으로 되돌려놓는 작업이다. 따라서 4*1의 특성맵을 input으로 하여 16*1의 원본을 output으로 도출한다. 이때 합성곱 연산은 아래와 같은 과정을 거친다.

3*3 kernel -> 16*4 행렬로 변환

4*4 input  -> 4*1 벡터로 변환

행렬 연산 후 16*1 벡터 도출

일반적인 convolution의 연산과 어떤 차이가 있는지 바로 눈치챘을 것이다. kernel의 행렬이 전치(transposed)되었다! 4*1 벡터과의 연산을 통해 16*1의 벡터를 도출해야 하므로 16개의 행과 4개의 열을 가진 행렬이 필요하고 이를 위해서는 기존 kernel를 전치시킬 필요가 있는 것이다. 연산 과정을 살펴보면 상단 좌측 그림에서 3*3 kernel이 2만큼 padding이 이루어진 특성맵에서 연산이 이루어질 때 kernel의 처음 위치에서의 연산이 전치행렬의 첫번째 행, kernel이 오른쪽으로 1px 이동했을 때의 연산이 두번째 행으로 쭉 대응되는 과정을 거쳐 4*4의 output이 도출된다. 위 그림에서는 가중치 w의 값들이 보존되고 위치만 전치되는 것으로 표현돼있지만 실제로는 학습을 통해 새로운 w의 값들을 찾아나가게 된다. 원본 이미지(output)과 특성맵(input) 값을 알고 있으니 학습 과정에서 특성맵을 원본으로 복원하기 위한 최적의 가중치를 찾아낼 수 있다. 

 

 

 

 

<출처>

https://wikidocs.net/216077

https://wikidocs.net/book/6651

https://velog.io/@qtly_u/Object-Detection-Architecture-1-or-2-stage-detector-%EC%B0%A8%EC%9D%B4

https://wikidocs.net/163644

https://medium.com/hyunjulie/1%ED%8E%B8-semantic-segmentation-%EC%B2%AB%EA%B1%B8%EC%9D%8C-4180367ec9cb

https://computing-jhson.tistory.com/58

https://cs231n.stanford.edu/slides/2017/cs231n_2017_lecture11.pdf

https://arxiv.org/pdf/1411.4038

https://towardsdatascience.com/understand-transposed-convolutions-and-build-your-own-transposed-convolution-layer-from-scratch-4f5d97b2967

https://hugrypiggykim.com/2019/09/29/upsampling-unpooling-deconvolution-transposed-convolution/

https://gaussian37.github.io/dl-concept-checkboard_artifact/

Object detection을 수행하려면 객체가 존재할만한 지역의 후보군들을 추려내는 작업이 필요한데 이를 Region Proposal이라고 한다. 위 사진에서 2번과 같이 지역을 각각 나눠 하나씩 모델에 집어넣어 분류를 하는 방식을 취하게 되는데 2번의 과정이 region proposal이다. 대표적인 방법들에는 Sliding Window, Selective Search, RPN 등이 존재하는데 해당 방법들을 초기 방식이었던 sliding window와 selective search에 대해 알아보도록 하자.

① Sliding Window

sliding window은 초기의 localization 방식이다. 위 사진을 보면 초록 박스가 이미지 전체를 훑고 지나간 뒤 이미지 크기가 조절되며 해당 과정이 반복된다. 초록 박스를 window라고 하며 이 크기만큼의 이미지가 CNN에 입력되는 것이다. window의 위치가 이동할 때마다 그 크기만큼 crop된 이미지가 입력되며 window가 전체 이미지를 훑고 지나가면 region proposal이 전체 이미지를 커버하게 되는 것이다. 이렇게 되면 window가 전체 이미지를 훑으며 이미지 내 해당 크기로 검출할 수 있는 객체를 모두 찾아낼 수 있게 된다. 이후 window의 크기나  이미지의 사이즈를 바꿔가며 다양한 크기의 객체를 탐지할 수 있도록 한다. 

 

알고리즘을 공부해봤던 사람은 바로 알아챘겠지만 이러한 방식은 완전탐색(Exhausted Search)의 개념을 그대로 가져다 쓴 방식이다. 검출하려는 객체가 전체 이미지 내에서 위치할 수 있는 모든 경우의 수를 그대로 때려넣은 구조라 당연하게도 계산량이 아주 많아진다. 기존 컴퓨터 비전에서는 선형분류기를 사용했기 때문에 연산량이 그나마 적어 쓸만 했지만 CNN과 같은 딥러닝 기반 classifier는 매우 많은 연산비용이 든다. 선형분류기는 wx+b 구조에서 파라미터가 2개 밖에 없지만 딥러닝 기반 CNN 구조에서는 필터의 사이즈, 개수, FC layer의 노드 수 만큼 파라미터가 증가하고 층이 깊어질수록 처리하는 행렬 연산도 많아지기 때문이다. 또한 하나의 객체를 찾기 위해 sliding window가 아주 많은 crop된 이미지를 CNN의 입력값으로 보내는 것도 연산량 증가에 한몫을 하게 된다. 하지만 이러한 문제점을 아래의 방식을 도입하며 어느정도 해결해 나간다.

Convolutional Implementation of Sliding Windows

 

 

우선 sliding window가 연산이 많이 걸리는 이유를 생각해 보면 다음과 같다.

1. CNN의 입력값의 크기는 고정
2. FC layer의 많은 연산량

 

1번의 이유는 FC layer 때문이라 할 수 있는데 위의 그림을 통해 알아보면 우선 FC layer는 400개의 노드들간의 연결이 파라미터로 개수가 고정돼있다. 이는 학습 과정에서 400x400개의 가중치를 학습하게 된다는 것을 의미한다. 이에 따라 FC layer 바로 전의 conv layer에서는 이미지를 1차원 배열로 펼쳤을 때의 크기가 400개가 되어야 하고 크기가 다르면 FC layer를 통과할 수 없는 것이다. 따라서 필터의 크기 등을 손보지 않는 한 초기 입력값의 크기는 14x14x3으로 고정돼 있어야 하고 각각의 crop된 이미지를 해당 크기로 resizing 하여 CNN의 입력값으로 넣게 된다. 

2번에 대해서 위쪽 network의 첫번째 conv layer 파라미터를 계산해 보면 5x5x3x16=1200개이지만 FC layer의 파라미터는 400x400=160,000개다. FC에서는 모든 노드간의 연결이, conv layer에서는 필터의 크기, 개수만큼이 파라미터가 되기 때문에 이와 같은 차이가 발생하는 것이다. 

 

이러한 문제의 해결책으로 제시된 것이 FC layer를 Convolutional layer로 바꾸는 것이다. 핵심부터 말하자면 이 방법으로

1)입력 사이즈의 제한이 사라졌고

2)중복된 연산과 여러 후보 region들을 한번에 처리할 수 있게 됐으며

3)학습에 필요한 연산량을 획기적으로 줄이게 되었다.

FC layer가 있을 때와 conv layer로 대체됐을 때의 차이

 

입력 사이즈에 제한이 생겼던 것은 FC layer에서는 노드간의 연결이 고정돼있었기 때문이었다. 하지만 conv layer에서는 필터의 크기와 개수가 고정돼 있으므로 어떤 크기의 입력값이 들어오든 필터 사이즈에 맞춰 연산값을 도출할 수 있다. 위 그림을 보면 맨 위의 구조가 14x14x3의 입력값을 고정으로 받는 conv net이고 중간은 FC layer를 conv layer로 바꾼 것으로 14x14x3 크기의 객체를 검출하는데에 있어 16x16x3이 입력값으로 들어 올 수 있는 것을 확인할 수 있다. 입력값으로 들어온 16x16x3에서는 14x14x3을 stride=2만큼 움직였을 때 총 4개(빨강, 초록, 주황, 보라)가 들어갈 수 있는 것을 확인할 수 있다. 즉 기존 FC layer에서 window size만큼 크롭된 이미지를 입력사이즈에 맞게 보정한 값을 4번에 걸쳐 받았던 것을 한번에 처리할 수 있게 된 것이다. 이는 FC에서는 1x1x400으로 크기가 고정되어 하나의 이미지만 처리할 수 있었지만 conv layer는 pooling layer 이후의 값에 필터가 stride=1만큼 이동해 연산을 진행하여 2x2x400의 값을(1x1x400이 4개나!!) 도출할 수 있기 때문이다. 이에따른 검출값은 좌측상단은 검출o, 나머지는 검출x 부분이라 볼 수 있다. 

 

또한 빨강, 초록, 주황, 보라색의 14x14x3 사이즈 이미지들은 겹치는 부분이 대부분이다. 각각을 따로 계산하는 FC layer의 구조에서는 이 겹치는 부분을 4번이나 다시 연산을 해야했겠지만 conv layer로 바꾼 구조에서는 중복된 연산을 한번에 처리가 가능한 것이다. 입력 사이즈를 28x28x3로 더 키우면 연산의 효율성은 더욱 극대화된다. 4개가 아닌 64개의 window size에 맞춰 crop된 이미지를 한번에 처리할 수 있는 것과 동일해지기 때문이다! 이러한 이유로 convolutional implementation of sliding windows는 학습의 측면에서도 입력값에 대한 결과 도출의 측면에서도 상당한 연산상의 이득을 얻게 된다.

한번에 객체 위치를 인식하는 Convolution implementation of sliding windows

 

위 그림을 보면 더욱 잘 이해가 될 것이다. 입력 이미지의 크기가 28x28일 때 합성곱 신경망에 한번 통과시키는 것만으로 객체의 위치를 특정하게 될 수 있고 겹치는 부분들은 중복으로 연산되지 않아 계산이 빠르고 효율적으로 진행된다. 하지만 이러한 sliding window 어디까지나 크기가 정해져 있기 때문에 정확한 경계 상자를 그리는 것이 어렵다. Window 사이즈에 따라 object가 여러개 포함될 수도, 아예 포함되지 않을 수도 있다. 또한 FC layer을 CNN으로 바꾸면서 파라미터 수가 줄면서 복잡한 문제를 해결하기에는 더 어려워진 부분이 있다.

 

② Selective Search

exhausted search(=sliding window)는 window 크기를 조절해가며 이미지 내 모든 구역을 조사하기에는 연산량이 무지하게 많다. 따라서 많은 제약조건들이 필요했다. 또한 하나의 객체를 찾기 위해서 쓸데없는 지역을 너무 많이 조사하기 때문에 이에 따른 비효율성도 상당했다. 이러한 문제를 해결하기 위해 "객체가 위치할 만한 지역을 조사하자"는 발상으로 등장한 것이 Selective Search이다. 

Selective Search의 진행 과정

 

객체가 있을만한 지역을 조사하기 위해서는 일단 그러한 지역을 찾아내야 한다. Selective Search는 이를 segmentation을 활용해 구현해 낸다. Segmentation은 class independent object hypotheses를 생성할 수 있다는 장점이 있는데 이 말은 내가 검출하려는 class와 무관하게 객체 후보군이 생성된다는 것이다. 이렇게 되면 하나의 객체만 포착되는 것이 아니라 다른 객체들, 심지어 배경까지 포착해낼 수 있다. 위 사진의 (a)를 보면 selective search는 사람 뿐만 아니라 양들, 초원, 하늘까지도 모두 잡아내고 있다.

exhausted search도 한 가지 이점이 있는데 어떤 크기의 객체든 이미지 내에서 찾아낼 수 있다는 점이다. 뭐 완전탐색방식이니 당연한 부분이다. 그리고 이러한 이점을 selective search에서도 다른 방식을 통해 보존해 간다. 결국 selective search는 한마디로 exhausted search segmentation의 장점을 합친 것이라 할 수 있다. 그렇다면 sliding window 방식을 사용하지 않고 완전탐색의 이점을 어떻게 유지할 수 있는걸까?

Hierarchical Grouping Algorithm

 

그 해답은 hierarchical grouping algorithm에 있다. Selective Search에서는 이 알고리즘을 활용해 모든 scale의 객체를 포착해 낸다. 이는 은 구역을 조금씩 합쳐나가며 더 큰 scale의 구역을 만들고 그렇게 모든 scale을 커버할 수 있도록(Capture All Scales) 하는 알고리즘이다. 아래는 hierarchical grouping algorithm의 구체적인 동작 과정이다.

Hierarchical Grouping Algorithm의 작동과정

작동과정은 다음과 같다. 

1. 이미지 입력

2. segmentation을 사용해 초기구역 R 획득, 구역 간 유사도 집합 S는 공집합

    *초기 구혁 획득할 때는 "Efficient Graph-Based Image Segmentation(2004)"이라는 논문(=[13])에서 제시된 방법을 사용 

3. 각 구역간의 유사도를 구해 S에 추가

    *n개의 구역이 있으면 총 nC2개의 구역 쌍에 대한 유사도가 나올 것

4. 유사도가 가장 높은 두 구역을 합쳐 r_t라 정의

    *r_i와 r_j의 유사도가 가장 높다면 r_t = r_i U r_j

5. r_i와 다른 모든 구역의 유사도, r_j와 다른 모든 구역의 유사도를 S에서 제외

6. r_t와 나머지 구역들의 유사도 집합을 구해 S에 추가 & r_tR에 추가

7. 이를 S가 공집합이 될 때까지 반복한 뒤 R에 남아있는 지역들이 selective search의 결과 L!

   *이때 L초기 지역+그리디를 통해 합친 지역들이다.

 

간단히 말하자면 입력된 이미지에서 segmentation을 활용해 초기구역을 나누고 가장 유사한 두 구역을 합쳐나가면서 더 큰 scale을 커버할 수 있는 region들을 만들어 최종적으로 전체 이미지가 하나의 region이 될 때 까지 반복하는 과정이라 할 수 있다. sliding window가 완전탐색 방식이었다면 selective search는 그리디(greedy) 방식을 취하는 것이다. 

 

이 방식이 효과적으로 작동하려면 유사도를 빨리 계산해야 되는데 이를 위해서는 유사도가 계층적으로 전달될 수 있는 특징에 근거해야 한다. 즉 r_i와 r_j를 합쳐 r_t를 만들 때 r_t의 특징은 image pixel에 직접 접근할 필요 없이 이전에 계산되었던 r_i와 r_j의 특징을 기반으로 계산되어야 한다는 것이다. 아마 저게 뭔소린지 싶을 텐데 이는 selective search에서 제시하는 Diversification Strategies에서 구체적으로 알 수 있다.

Diversification Strategies

 

이는 한가지 특징만을 고려해 객체를 탐지하는 것이 아니라 여러 특징들을 종합적으로 고려하여 객체를 탐지하고자 하는 전략이다. 아래 사진에서 texture이라는 특징 하나만을 고려해 TV와 여성을 구분하고자 하면 어려울 수도 있다. 하지만 selective search에서는 추가적인 다른 특징들을 종합적으로 고려하여 둘을 구분해낼 수 있는 것이다. 세부적으로는 complementary color spaces, complementary similarity measures, complementary starting regions 세 가지 전략이 있는데 이를 모두 사용해 diversification strategies를 효과적으로 구현한다.

 

Complementary Color Spaces

color channels, colour spaces의 특징

 

이는 서로 다른 특징 갖는 다양한 color space(색공간)들을 사용하는 전략이다. 위 표를 보면 색공간 별로 Light Intensity, Shadows/shading, Highlights의 부분에서 각기 다른 특징을 지닌다. +는 해당 특성이 invariant 하다는 것, -는 variant 하다는 것을 의미하고 1/3은 3개의 colour channels 중 1개의 channel만 variant 하다는 것이다. HSV의 Highlights부분을 예로들면 H, S, V 3개의 colour channel 중 H 1개의 채널만 + 성질을 띄기 때문에 1/3인 것이다. 이같이 색공간 별로 특징이 다르기 때문에 이를 종합적으로 활용하면 더욱 정확한 bounding-box를 그릴 수 있게 되는 것이다.(하지만 시간은 좀 더 오래 걸린다.)

 

Complementary Similarity Measures

유사도 산출 수식

 

이는 유사도의 계산과 관련된 부분이다. 최종 유사도는 colour, texture, size, fill 유사도 4가지의 합으로 계산된다. 이때 유사도의 값들은 계산 편의를 위해 [0, 1] 범위로 조정된다. 우선 colour 유사도부터 알아보자. 

https://www.google.com/url?sa=i&url=https%3A%2F%2Fpyimagesearch.com%2F2021%2F04%2F28%2Fopencv-image-histograms-cv2-calchist%2F&psig=AOvVaw1VTkQK3Gqke0gdWQ7mFEOn&ust=1712821324503000&source=images&cd=vfe&opi=89978449&ved=0CBQQjhxqFwoTCOCs4OaSt4UDFQAAAAAdAAAAABAE

 

<colour 유사도>

두 region의 유사도를 계산을 이해하기 위해서는 우선 color histogram에 대한 이해가 필요하다. 일반적으로 색은 RGB로 표현되는데 R, G, B 각각이 밝기에 따라 0~255 사이의 값을 가지고 합쳐져서 색이 표현되는 것이다.(0 가장 어두움, 255 가장 밝음) 이때 색은 픽셀 단위로 표현된다. 위 사진을 보면 픽셀이 가지는 밝기(gray scale)를 x축으로, 해당 밝기를 가지는 pixel의 개수를 y축으로 R, G, B를 각각 표현한 그래프가 나타나는데 이를 히스토그램으로 표현한 것이 color histogram이다.

colour histogram(n=bins)
colour 유사도(좌) = histogram intersection(우)

 

selective search의 similarity measures에서 colour 유사도를 구할 때는 255 bins를 사용하는 것이 아니라 25 bins를 사용한다. colour channel별로 25개의 bins를 가지기 때문에 만약 RGB를 사용한다고 하면 n=75인 colour histogram을 가지게 된다. 만약 r_i와 r_j의 유사도를 구한다면 C_i와 C_j를 각각 구해야 한다. 그 다음 이 둘의 histogram intersection을 구하면 colour 유사도가 나온다. histogram intersection은 수식을 보면 어려울 수 있으나 아래를 보면 이해하기 쉽다.

https://blog.datadive.net/histogram-intersection-for-change-detection/

위 그래프를 각각 75개의 bins를 가진 colour histogram이고 x축이 정규화 돼 있는 것이라 생각해 보자. histogram intersection은 말그대로 두 히스토그램이 교차하는 지점을 의미한다. 이때 교차하는 지점은 두 히스토그램의 x값이 같은 지점에서 y값이 더 작은 것을 의미한다. 그래서 수식이 min(  ,  )을 다 더하는 구조가 되는 것이다. 

 

오케이. 이제 유사도를 구하는 방식은 완전히 이해가 될 것이다. 그렇다면 아~까전에 유사도 계산 속도 향상을 위해 r_i와 r_j의 특징을 기반으로 r_t의 특징을 구한다는 것은 그래서 도대체 무슨 의미인 것일까? 

colour 부분에서 특징은 colour histogram을 의미한다. 따라서 위 말은 r_i와 r_j의 colour histogram을 바탕으로 r_t의 colour histogram을 구한다는 것을 의미한다. 이는 아래의 수식을 통해 구할 수 있다.

r_t의 color histogram 구하는 수식

 

처음에 유사도는 계산 편의를 위해 [0, 1]의 범위로 맞춰준다고 하였기 때문에 colour histogram의 y값 즉 해당 gray에서의 픽셀 수는 전체 픽셀에서의 비율로 조정된 상태이다. 따라서 위 수식에서는 size(r_i), size(r_j)와 같은 값들이 곱해지고 그 합으로 나눠주는 것이다. 의미적으로 C_t = C_i+C_j이라 봐도 무방하다. 즉 r_i와 r_j가 합쳐져서 만들어진 새로운 구역 r_t의 colour histogram은 r_i와 r_j의 colour histogram을 단순히 합하는 것 만으로 구할 수 있다는 것이다.

 

<texture 유사도>

texture histogram(n=bins)
texture 유사도 = histogram intersection

 

texture 유사도를 산출하는 메커니즘도 colour와 동일하다. 똑같히 histogram을 구하고 histogram intersection을 구함으로써 유사도를 산출하는 방식이다. 하지만 colour 유사도에서는 단순하게 colour histogram을 썼지만 texture 유사도에서는 fast SIFT-like measurements를 활용해 texture histogram을 구성한다. texture histogram이 어떻게 구성되는지 자세히 알기 위해서는 "Exploring features in a bayesian framework for material recognition(2010)"이라는 논문을 참조해야 하지만 내용이 너무 비대해지므로 여기선 생략하도록 하겠다. 대충 CNN이 대상의 특징을 추출하는 것처럼 texture 특성을 추출하는 방법이라고만 알아놓자. 자세한 내용이 궁금하면 아래 링크를 통해 한번 참조해 보시길!

https://www-bcs.mit.edu/pub_pdfs/maltRecogCVPR10.pdf 

 

<size 유사도>

 

size 유사도는 작은 지역들이 먼저 합쳐지도록 유도한다. 작은 지역들부터 병합해 나가는 것은 모든 scale의 객체들을 탐지할 수 있도록 하기 위함이다. 사실상 sliding window의 이점이 hierarchical grouping algorithm 내의 size 유사도를 통해 구현되는 것이다. 위 식을 보면 1에서 두 지역이 전체 이미지에서 차지하는 비율을 빼준다. 두 지역의 크기가 작을수록 size 유사도의 크기는 증가해 전체 유사도의 크기는 증가하게 된다. hierarchical grouping algorithm은 유사도가 높은 지역들부터 합쳐나가므로 size 유사도를 통해 작은 지역들이 먼저 합쳐지도록 유도된다고 볼 수 있는 것이다. 

 

<fill 유사도>

 

fill 유사도는 가까이 있는 regions 부터 합쳐지도록 유도한다. 만약 멀리 떨어져 있는 두 region이 합쳐지면 각 region들과는 관계없는 다른 region이 포함되기 때문에 꼭 필요한 부분이라 할 수 있다. BB_ij는 r_i와 r_j를 tight하게 감싸는 bounding-box이다. 위 수식의 분모는 bounding-box 내 r_i와 r_j와는 관련없는 지역이라 할 수 있는데 r_i와 r_j가 가까이 있을수록 그 값은 작아진다. 이렇게 되면 fill 유사도는 높아지고 hierarchical grouping algorithm에 따라서 더 빨리 합쳐지게 되는 것이다. 

 

이렇게 각각 구해진 유사도 측정치들을 다 더하면 최종 유사도가 산출된다. a는 0 or 1의 값으로 해당 측정치를 사용 유무를 정하는 값이다. 저렇게 a를 추가로 달아놓은 이유는 특별한건 없고 그냥 다양한 조합으로 실험해보기 위함이다. 

 

Complementary Starting Regions

이는 초기 regions을 다양화하며 그 질을 높이는 방식이다. 여기서는 새로운 방식을 제시하지는 않고 "Efficient Graph-Based Image Segmentation[13]" 에서 제시된 oversegmetation 방식을 그대로 차용한다.

 

평가 방식

 

selective search의 논문을 보면 위와 같은 평가지표를 통해 성능을 측정한다. ABO는 한 클래스에 대한 성능이라고 할 수 있는데 G^c는 c라는 클래스의 ground truth이고 g_i^c는 이미지 내 c라는 클래스에 해당하는 객체가 여러개 있을 때 각각의 ground truth를 나타낸다(사람을 검출하려 할 때 이미지 내 사람이 여러명인 경우). maxOverlap(g_i^c, l_j)는 g_i^c와 selective search로 생성된 여러 locations인 L이 겹치는 부분중 가장 큰 부분을 나타낸다. c라는 클래스에 해당하는 객체가 여러개 있을 때 각각의 ground truth의 maxOverlap을 다 더한 후 평균을 낸 것을 ABO라고 할 수 있겠다. 클래스가 1개가 아닌 여러개라면 각 클래스에 해당하는 ABO를 모두 더해준 후 평균을 낸 MABO를 통해 성능을 측정할 수 있다.

 

이전에 최종 유사도를 계산할 때 각각의 유사도에 a가 덧붙여졌는데 이는 위쪽 표와 같이 다양한 조합을 통해 실험하기 위해 붙은 것이다. selective search는 위와 같이 유사도 조합, color space, 초기지역을 낼 때의 변수인 thtresholds를 다양하게 조합하며 성능을 측정한다. 아래쪽 표는 greedy search를 통해 각각 단일 전략, 속도 우선 전략, 성능 우선 전략에서 가장 좋은 조합을 찾아낸 것이다. 성능 우선 전략을 보면 color space 5개, 특징조합 4개, thresholds 4개로 총 80개의 전략이 도출되고 각 전략에 따른 locations 중 중복된 것은 하나를 제거한 뒤 bounding box 생성에 활용한다. 

 

이러한 방식을 거쳐 생성된 selective search의 bounding-box는 무지하게 많고 또 ground-truth와 차이가 꽤나 있다. 따라서 여러 박스 중 최선의 박스를 하나 선택하고 이를 ground-truth에 더 가까이 이동시켜 주는 작업이 필요한데 이에 각각 NMS(Non-Max Suppresion) Bounding-box regression이 사용된다. NMS를 계산하는데는 일반적으로 IoU(Intersection of Union)라는 개념도 필요하다. 이 부분까지 추후에 다른 글로 다뤄보도록 하겠다. 

 

 

 

 

<참고자료>

https://wikidocs.net/216077

 

17. Recent advances in deep learning for object detection

### $\equiv$ CONTENTS 1. Introduction 2. Problem Settings 3. Detection Components 3.1. Detection …

wikidocs.net

 

 

 

https://wikidocs.net/136492

 

2) Localization

## Sliding window object detection Sliding window는 (1)에서 간략하게 소개가 되었었는데요, 모든 **location**에 대해 **sc…

wikidocs.net

 

https://www.youtube.com/watch?v=XdsmlBGOK-k&list=PL_IHmaMAvkVxdDOBRg2CbcJBq9SY7ZUvs&index=4

 

https://velog.io/@pabiya/Selective-Search-for-Object-Recognition

 

Selective Search for Object Recognition

오늘 리뷰할 논문은 R-CNN에서 region proposal을 하는 원천 기술인 selective search 알고리즘에 대한 논문이다. R-CNN 논문을 읽고 궁금해져서 리뷰하게 되었다.이 논문의 목표는 object recognition에 이용할

velog.io

 

selective search 논문

 

http://www.huppelen.nl/publications/selectiveSearchDraft.pd

 

 

 

머신러닝/딥러닝을 공부하다보면 아주 다양한 모델 평가지표들을 볼 수 있다. confusion matrix(혼동행렬)은 분류 모델의 평가지표의 뼈대를 이룬다고 할 수 있는데 confusion matrix의 여러 값들을 조합하여 다양한 평가지표를 구성하게 된다. 

confusion matrix

 

혼동행렬은 실제 범주를 어떻게 예측했냐에 따라 다음과 같이 네 가지로 상황이 나뉜다.

True positive(TP): 실제 참인 값을 참이라 예측(오류)
False positive(FP): 실제 거짓인 값을 참이라 예측
False negative(FN): 실제 참인 값을 거짓이라 예측
True negative(TN): 실제 거짓인 값을 거짓이라 예측(오류)

 

그리고 이 값들로 적절한 수식을 만들어 분류모델의 평가지표를 구할 수 있다. 평가지표로는 정밀도(Precision), 민감도(Sensitivity) 특이도(Specificity), 정확도(Accuracy)가 있다. 하나씩 차근차근 알아가보자.

* 아래 설명은 위의 혼동행렬 표를 참고해가며 읽어보도록 하자

 

 

정밀도(Precision)

 

$\displaystyle\frac{TP}{TP+FP}$

 

정밀도는 긍정으로 예측했을 때 들어맞은 예측의 비율을 계산하는 지표다. 정밀도가 높다는 것은 긍정적인 예측이 적중할 확률이 높다는 것을 의미하고 이는 곧 데이터의 분산이 낮음을 의미한다. 실제 범주가 1인 데이터를 1로 예측한 비율이 많아지면 실제, 예측치의 차이의 정도를 나타내는 분산이 낮아지는 것은 자연스럽다. 따라서 높은 정밀도를 보이는 데이터는 안정성이 높은 데이터로 간주될 수 있다. 하지만 정밀도는 예측 값이 긍정적일 경우에 대한 정보만 나타내기 때문에 해당 모델이 부정적인 값들을 예측한 경우에 대해 그 결과를 얼마나 신뢰해야 하는지는 알 수 없다. 정밀도는 후술할 F1 지표를 나타내는데 사용된다.

정밀도가 평가지표로 사용되는 예시로 스팸메일 분류기가 있다. 스팸메일 분류기에서는 중요한 메일을 스팸이라 판별하면 문제가 심각해진다. 따라서 스팸메일이라 판별한 것(Positive) 중에서 중요 메일(Negative)의 비율(FP)을 낮추는 것이 핵심이다. 정밀도는 FP가 낮아질수록 더 높아지므로 스팸메일 분류기의 성능을 잘 나타낼 수 있다.

 

 

민감도(Sensitivity) = 재현율(Recall)

 

$\displaystyle\frac{TP}{TP+FN}$

 

민감도(=재현율)은 실제값이 긍정인 경우들 중에서 긍정의 예측값을 도출할 비율을 나타내는 지표이며 TPR(True Positive Rate)라고도 한다. '재현율'이라는 워딩에 대해 생각해보면 이는 실제 긍정의 값들을 모델이 얼만큼 긍정으로 재현해 낼 수 있는지에 대한 정보를 제공하는 지표임을 알 수 있다. 긍정의 결과에 민감에게 반응하는 정도로 이해하면 '민감도'라는 말이 와닿을 것이다. 민감도는 현실이 긍정인 경우들만 고려하기 때문에 현실이 부정일때의 예측에 관한 정보는 알 수 없다. 재현율 또한 F1 지표를 나타내는 데 사용되고 ROC 곡선을 산출하는 데에도 쓰인다. 1 - sensitivity를 계산하면 실제값 긍정일 때 부정으로 예측할 확률인 FNR(False Negative Rate)을 구할 수 있다.

민감도가 평가지표로 사용되는 예시로는 암 판별기가 있다. 암 판별기에서는 실제 암인데 암이 아니라 판별하면 문제가 심각해진다. 따라서 암이 아니라 판별한 것(Negative) 중에서 실제 암(Positive)인 비율(FN)을 낮추는 것이 핵심이다. 민감도는 FN이 낮아질수록 더 높아지므로 암 판별기의 성능을 잘 나타낼 수 있다. 

 

민감도(재현율)과 정밀도의 trade-off 관계
민감도와 정밀도의 trade-off 관계란 민감도와 정밀도는 서로 반대로 움직인다는 것이다. 민감도가 높아지면 정밀도는 낮아지고 정밀도가 높아지면 반대로 민감도가 낮아진다. 그럼 왜 그렇게 되는 것일까?
일반적으로 이진분류의 상황에서는 Positive와 Negative로 구분하는 임계값(threshold)가 존재한다. 임계값을 낮추게 되면 모델이 Positive로 예측하는 횟수가 많아지고 Negative로 예측하는 횟수는 적어진다. Negative 예측 수가 줄어드니 당연히 FN(실제 Positive, 예측 Negative)값은 낮아지고 민감도는 커지게 된다. Positive 예측 수가 늘어나면 거짓 Positive(FP)가 늘어날 수 밖에 없다. 따라서 정밀도는 낮아지게 되는 것이다.

 

 

특이도(Specificty)

 

$\displaystyle\frac{TN}{TN+FP}$

 

특이도는 실제값이 부정인 경우들 중에서 부정의 예측값을 도출할 비율을 나타내는 지표이며 TNR(True Negative Rate)라고도 한다. 이는 민감도와는 반대로 실제값이 긍정인 경우에 대한 정보를 알 수 없다. 1 - spectificity를 계산하면 실제값이 부정일 때 긍정으로 예측할 확률인 FPR(False Positive Rate)을 구할 수 있다. FPR(= 1 - specificity)는 ROC curve의 x값으로 활용된다. 

 

 

 정확도(Accuracy)

 

$\displaystyle\frac{TP+TN}{TP+TN+FP+FN}$

 

confusion metrix in balanced data
confusion metrix in imbalanced data

 

정확도는 전체 예측 중에서 들어맞은 예측의 비율을 계산하는 지표다. 정확도는 균형 데이터에서는 좋은 성능 지표가 된다. 위쪽 표의 균형 데이터에서 정밀도를 평가지표로 활용하면 0.56으로 썩 좋은 모델은 아니지만 민감도를 평가지표로 활용하면 0.90으로 꽤나 좋은 성능을 내는 모델이 된다. 애초에 threshold를 낮춰 Positive로 비교적 많은 예측을 때려버린 모델이라 정밀도는 낮아지고 민감도는 커지게 되기 때문에 이 둘을 성능지표로 활용하는 것은 별로 좋은 생각이 아니다. 이때 정확도가 효과적인 것이다. 정확도는 confusion matrix의 모든 값들을 활용해 지표를 계산하므로 민감도와 정밀도에 비해 꽤나 중립적인 값을 도출한다. 

 

하지만 아래쪽 표의 불균형 데이터를 보면 모델은 20개의 Positive중에 2개밖에 제대로 예측하지 못하고 있는데 정확도는 92%이다. 위 데이터에서는 Positive를 정확하게 예측하는 것이 중요한데 이를 하나도 예측하지 못한다 하더라도 정확도가 90%가 넘는다. 본 데이터에서는 정확도는 전혀 쓸모가 없는 것이다. 따라서 Positive class의 예측값과 실제값을 잘 반영하는 정밀도와 민감도를 사용하는 것이 적절하다. 하지만 정밀도와 민감도를 사용할 때에도 문제가 있다. 만약 모델이 모든 경우를 Pregnant라고 예측한다면 민감도는 1이 되지만 정밀도는 최대 0.04이다. 민감도를 평가지표로 쓰면 아주 완벽한 모델이 되지만 실제로 모든 경우를 Positive로 예측하는 모델은 형편없는 모델이다. 차라리 정밀도가 모델의 성능을 제대로 나타낸다고 할 수 있다. 그렇다고 무턱대고 정밀도를 쓰면 문제인게 임신 판단에 있어서는 FN를 줄이는 것이 중요하지만 정밀도는 이에 대한 고려가 전혀 없기 때문이다.  따라서 민감도와 정밀도를 적절히 고려할 수 있는 지표가 필요하다. 그 대표적인 예가 F-Measures이다.

 

⑤ F- Measures

 

F1 score

$ F1 = \displaystyle\frac{2*Precision*Recall}{Precision+Recall}$

 

F1은 정밀도와 민감도의 조화평균으로 둘 사이의 중간값을 나타낸다. 일반적으로 조화평균은 더 적은 값에 영향을 많이 받기 때문에 정밀도와 민감도 중 더 작은 값에 치우쳐진 상태로 값이 나온다. 이때 F1은 단순히 정밀도민감도의 조화평균이므로 FNFP가 동등하게 중요한 데이터의 모델 성능지표로 적합하다. 

 

F2 score

$ F2 = 5*\displaystyle\frac{Precision*Recall}{4*Precision+Recall}$

 

F2의 식을 보면 F1과 같이 그냥 조화평균을 낸 것은 아니라는 것을 알 수 있다. 정밀도의 값을 4배 시킨 뒤 조화평균을 낸 후 그 값에 5/8을 곱해주면 F2 score가 나온다. 이렇게 되면 F2의 값은 재현율(민감도)에 더 가까운 값을 도출하게 된다. 만약 정밀도가 0.1, 재현율이 0.8이라면 이 둘을 그냥 조화평균을 내기 보다 정밀도를 0.4로 키워준 뒤 조화평균을 내는 것이 재현율에 값을 더 근접하게 도출시킬 수 있는 것은 당연하다. 이는 곧 재현율에 더 가중치를 두어 평가지표를 도출하는 것이라 할 수 있다. 앞서 말한 암 판별기와 같이 예측한 양성이 틀리더라도 실제 양성을 하나라도 더 분류해내고자 할 때(FN↓ 중요한 경우) F2를 사용한다. 

 

F0.5 score

$ F0.5 = \displaystyle\frac{4}{5}*\frac{Precision*Recall}{0.5^{2}*Precision+Recall}$

 

반대로 F0.5는 정밀도에 더 작게 만드는 작업이 들어간다. 따라서 이는 정밀도에 더 가깝게 값을 도출하는 즉 정밀도에 더 가중치를 두는 평가지표라고 할 수 있다. F0.5는 FP를 낮추는 것이 더 중요할 때 사용되는 지표이기 때문에 스팸메일 분류기와 같은 모델에 사용하는 것이 유리한 지표라 할 수 있다. 

 

이와 같이 F 뒤에 오는 숫자에 따라 정밀도와 민감도 중 어느 곳에 더 가중치를 둘지 조정할 수 있는데 이를 일반화된 식으로 표현하면 다음과 같이 된다. 이를 F-베타 score 라고 한다.

 

$F_{\beta} = \displaystyle(1+\beta^2)\cdot\frac{Precision\times Recall}{(\beta^2\times Precision)+Recall}$

 

⑥ ROC-curve(AUC) & PR-curve(AP)

ROC curve와 PR curve는 다양한 임계값(threshold)에서 모델의 성능을 나타내는 그래프이다. ROC curve는 x축에 1-특이도(FPR) y축에 민감도(TPR)을 PR은 x축에 민감도(재현율)을 사용하여 모든 임계값에서의 각각의 수치를 그래프로 표현하였다. 이는 모델이 양성으로 판단한 것이 맞는지와 틀린지 모두 고려한다고 할 수 있다. 즉 모델의 정확성과 오류를 동시에 고려해 표현하는 것이다. PR curve는 x축에 재현율(민감도), y축에 정밀도를 사용해 모든 임계값에서 각각의 수치를 표현한 그래프이다. 이렇게 나온 ROC와 PR 그래프의 아래 면적을 계산한 값을 AUC라고 하고 각각을 AUROC, AUPRC라 할 수 있다. F score와의 차이점으로는 F score는 여러 임계값에 따른 모델의 성능을 한번에 표현하지는 못하지만 ROC와 PR은 AUC를 통해 이를 실현한다. 

 

위의 그림은 임계값의 변화에 따라 ROC curve와 PR curve가 그려지는 과정을 나타낸 것이다. 임계값이 올라갈수록 FP(파란x)는 TN(파란점)으로 바뀌며 FPR은 점차 줄어든다. 임계값이 어느정도 이상에서는 TP(주황점)이 FN(주황x)으로 바뀌게 되고 민감도(재현율) 또한 점차 줄어든다. 임계값이 커질수록 예측 양성 중 TP의 비율은 증가하는데 이에 따라 정밀도는 점차 커진다. 이를 그래프로 표현한 것이 ROC curve, PR curve인 것이다.

 

ROC를 사용하면 (1)임계값에 따른 모델의 TPR과 FPR을 한번에 확인할 수 있고 (2)임계값을 어디로 잡을지에 대한 판단을 하기 쉬우며 AUC를 통해 (3)모든 임계값에 따른 모델의 정확성과 오류를 종합적으로 고려한 성능지표를 도출해낼 수 있다. 이렇게 보면 ROC가 상당히 괜찮은 지표인데 PR은 왜 사용하는 것인지 의문이 들 것이다.

 

ROC curve and PR curve in imbalanced data

 

그것은 바로 ROC가 음성이 아주 많은 불균형 데이터에 취약하기 때문이다. 위의 그림을 한번 보자. 왼쪽 그림을 보면 데이터의 음성 비율이 아주 많지만 음성과 양성 class의 prediction score가 겹치는 부분이 아주 많다. 모델이 음성과 양성을 제대로 분리하지 못하고 있는 것이다. 임계값이 낮아질수록 실제 양성을 양성이라 판별을 잘 하게 되지만 그만큼 아주 많은 음성의 데이터들에도 그냥 양성이라고 판정을 때려버리게 된다. 위의 그림에서 양성의 데이터와 음성의 데이터가 겹쳐있는 부분에서 대부분의 양성 데이터가 분포하고 있지만 음성은 겹치지 않는 아래부분에 데이터가 훨씬 더 집중적으로 분포돼있다. 따라서 임계점이 낮아질 때 FPR은 조금씩 증가하는 반면 TPR(민감도)는 급격하게 증가한다. 따라서 음성과 양성을 판단하는 지점이 겹쳐있을수록 모델의 성능은 더 떨어짐에도 불구하고 ROC curve는 그 부분을 포착해내기 어렵게 된다. 이때 PR curve를 사용하면 이러한 문제를 해결할 수 있다!

PR curve는 y축으로 정밀도를 사용한다. 정밀도는 분모가 예측 양성이고 분자가 예측양성중 실제 양성이다. 음성의 비율이 아주 많은 위와 같은 데이터에서 좋지 않은 모델은 임계값이 낮아질수록 그  많은 음성들을 양성으로 예측해버리기 때문에 분모(예측 양성)는 많이 커진다. 하지만 실제 양성 데이터 자체는 매우 적기 때문에 분자(예측양성 중 실제양성)은 조금밖에 커지지 않는다. 따라서 y 값인 정밀도가 급격히 낮아질 수 밖에 없다. 즉 모델이 음성과 양성을 제대로 분리하지 못하는 것을 ROC는 놓쳤던 반면 PR은 '정밀도' 덕분에 포착할 수 있는 것이다. 그 결과 ROC-AUC로는 꽤나 좋은 성능을 내는 것으로 평가받던 모델의 실상이 PR-AUC를 통해 적나라하게 드러나는 것을 확인할 수 있다. 이러한 PR curve는 이상탐지 같이 양성의 데이터가 희박한 분야에서 활용하면 아~주 유용하다. 

 

⑦ AP & mAP

AP는 그래프 아래의 넓이를 구한다는점에서 AUROC와 유사하지만 조금 다른 점이 있다. AP는 위 그래프와 같이 PR곡선이 단조적으로 감속하는 그래프가 되도록 수정한 후 그 아래 면적을 구하는 방식으로 산출된다는 것이다. 그렇다면 왜 이러한 변환을 시켜주는 것일까? 그 이유는 위의 불균형한 데이터셋의 그림을 보면 알 수 있다. 임계값이 맨 위에서 내려올 때 처음에는 양성 1개가 양성으로 예측되기 때문에 precision은 1이다. 조금 더 내려오면 양성1개 음성1개가 둘다 양성으로 예측되므로 precision은 0.5가 된다. 그다음 더 내려오면 양성2개 음성1개가 양성으로 예측되므로 precision은 0.66이 된다. 보면 precision의 값은 실제 음성이 양성으로 예측될수록 낮아진다. 그 결과 precision의 값은 계속 요동치게 되고 모델이 양성을 제대로 판별하는지 평가하는데에 지장을 준다. 따라서 좀 더 정확한 평가지표의 구성을 위해 음성 데이터의 영향을 제거한 것이 AP인 것이다. 

 

일반적으로 객체 탐지 모델은 탐지하고자 하는 객체(class)가 강아지, 고양이, 사람 등등 다양하다. 이에 따라 각 객체별 AP의 평균인 mAP가 평가지표로 사용되는 것이다.

 

 

 

 

 

 

 

 

 

 

 

https://github.com/rafaelpadilla/Object-Detection-Metrics?tab=readme-ov-file#precision-x-recall-curve

 

GitHub - rafaelpadilla/Object-Detection-Metrics: Most popular metrics used to evaluate object detection algorithms.

Most popular metrics used to evaluate object detection algorithms. - rafaelpadilla/Object-Detection-Metrics

github.com

 

 

참고자료

 

https://namu.wiki/w/%ED%98%BC%EB%8F%99%ED%96%89%EB%A0%AC#s-2.2

 

혼동행렬

混 同 行 列 / confusion matrix 어떤 개인이나 모델 , 검사도구, 알고리즘 의 진단·분류·판별

namu.wiki

 

https://medium.com/@danyal.wainstein1/understanding-the-confusion-matrix-b9bc45ba2679

 

Understanding the Confusion Matrix

Once a model is developed and ready for use in production there are important decisions to be made regarding exactly how the predictions…

medium.com

 

https://techblog-history-younghunjo1.tistory.com/101

 

[ML] Precision 과 Recall의 Trade-off, 그리고 ROC Curve

이번 포스팅에서는 분류 문제 성능을 평가하는 대표적인 metric으로서 Precision과 Recall에 대해 알아보고 이 둘 간의 관계, 그리고 ROC Curve와 이를 Score로 환산한 AUC에 대해서 알아보려고 한다. 그리

techblog-history-younghunjo1.tistory.com

 

https://velog.io/@kwjinwoo/mAP

 

mAP(mean average precision)

object detection에 관한 공부를 진행하다보면 모델의 평가 지표로 mAP가 등장한다. 이전까지는 mAP가 등장하면 그저 모델이 좋은 정도를 나타내는구나 생각하고 넘어갔는데, 이번 기회에 mAP에 대해

velog.io

 

https://rfriend.tistory.com/774

 

[Python] 불균형 데이터에 대한 분류 모델 성과평가 지표 (performance metrics of binary classification for imbal

지난번 포스팅에서는 불균형 데이터(imbalanced data)가 무엇이고, 분류 모델링 시 무엇이 문제인지에 대해서 알아보았습니다. --> https://rfriend.tistory.com/773 이번 포스팅부터는 불균형 데이터를 가지

rfriend.tistory.com

 

 

https://towardsdatascience.com/on-roc-and-precision-recall-curves-c23e9b63820c

 

On ROC and Precision-Recall curves

How do they differ, what information do they carry and why one does not replace the other

towardsdatascience.com

 

 

※ 논문을 필자가 이해한 대로 정리한 내용이니 내용적 오류가 있는 부분은 댓글로 남겨주기 바랍니다!

Abstract

신경망의 깊이는 모델의 성능개선에 있어 핵심적으로 중요한 요소이다. 하지만 신경망이 깊어질수록 모델은 더 복잡해지고 기울기 소실, degradation, overfitting 등으로 인한 성능저하를 야기할 수 있다는 문제점이 있다. 본 논문에서는 residual learning framework을 통해 이 문제를 개선하였음을 제시한다. residual learning의 핵심은 해당 층의 입력값으로 활용되는 이전 층의 결과값을 학습에 활용하는 것이다. 이러한 방법을 사용한 ResNet은 단순히 층을 깊게 쌓아올린 VGG 방식보다 ImageNet dataset에 있어 오차를 3.57% 더 줄일 수 있었다. residual learning은 단순히 classification 뿐만 아니라 localization, object detection, segmentation에서도 성능 개선을 보였다. 

 

1. Introduction

심층 신경망에서 "깊이"는 매우 중요한 요소이다. 더욱 높은 수준의 특성을 추출하여 더 복잡한 문제를 해결해 줄 수 있기 때문이다.

Is learning better networks as easy as stacking more layers?

 

하지만 층을 깊이 쌓는 것은 쉬운 반면 그러한 신경망을 학습시키는 것은 별개의 문제였다. 깊은 층을 가진 인공신경망을 학습시키는데 발생하는 악명높은 문제들이 있었기 때문이다. 대표적으로 vanishing/exploding gradient 문제는 네트워크의 값의 수렴을 방해하는 문제를 야기했지만 가중치 초기화(Initialization), 정규화 층(Normalization layer)의 도입으로 해결되었다.

CIFAR-10에서 각각 20, 56개의 층을 가진 네트워크의 성능차이

 

값이 수렴하기 시작하자 이제는 degradation 문제가 나타났다. 이는 네트워크가 깊어질수록 성능이 저하되는 문제였다. 위 사진을 보면 56개의 층을 가진 네트워크가 26개의 층을 가진 것보다 에러가 더 높게 나타는 것을 알 수 있다. 이 현상은 overfitting 문제는 아니었다. 보다시피 training error조차 깊은 층에서 더 높았기 때문이다.

 

한가지 해결책은 깊은 층에서는 identity mapping을 적용하는 것이었다. 얕은 층의 모델의 구조를 그대로 가져오되 identity mapping이 적용된 층들을 더 추가하는 것이다. 즉 깊은 층에서는 입력값(input)을 그대로 출력값(output)으로 내보내는 구조인데 이는 얕은 층의 모델보다 성능이 더 낮아지는 경우는 없어야 한다는 것을 나타낸다. 얕은 층 모델의 마지막 층을 통과하여 나온 값이 깊은 층 모델의 마지막 층까지 그대로 유지되기 때문이다. 이러한 발상에서 조금 더 나아간 것이 deep residual learning framework이다. 

 

residual mapping + identity shortcut connection → degradation 해결

 

Underlying mapping(=original mapping)을 H(x)라 하자. H(x)는 모든 층들을 통과해 나온 값이다. 이때 추가된 층만을 F(x)라고 정의한다면 F(x) = H(x) - x가 된다. 이러한 F(x)는 해당 층을 지날 때 값이 얼마나 변동했는지 설명해 줄 수 있다. 다르게 말하면 "잔차 값"을 나타낸다고 할 수 있는 것이다. 따라서 H(x) - x로 정의된 F(x)잔차함수(residual function)라고 하며 F(x) = H(x) - x이와 같이 정의된 mapping을 residual mapping 이라고 한다. 각 layer에서는 H(x)에 근사하도록 학습하므로 original mapping이 H(x) = F(x) + x로 재구성된다. 기존의 original mapping은 H(x) = F(x)를, residual mapping은 H(x) = F(x) + x를 학습하는 것이다. 이때 H(x)에 대한 optimal solution이 identity mapping이라고 하면 residual mapping이 학습하기 훨씬 쉽게 된다. H(x) = F(x) = x (original mapping)에서는 F(x) = x가 되도록 하는 F(x)의 가중치들을 일일이 구해야 하지만 H(x) = F(x) + x = x (residual mapping)에서는 F(x)를 0으로 만들면 되기 때문이다. 

 

residual mapping과 identity mapping은 shortcut connection을 통해 구현되는데 이는 하나 이상의 층을 건너뛰게 만드는 방식이다. 위 그림을 보면 입력값으로 활용되는 x가 weight layer를 건너뛰고 F(x)에 따로 더해지고 identity mapping에도 활용이 된다. F(x) + x = x의 식에서 x는 shortcut connection을 통해서 전달된 값인 것이다. 이러한 identity shortcut connection은 추가적인 파라미터나 복잡한 계산을 필요로 하지 않고 아주 간단히 구현이 된다!

 

3. Deep Residual Learning

3.1 Residual Learning

Underlying mapping을 추가된 층을 최적화시키기 위한 목적함수인 H(x)라 하자. 다중 비선형 층이 복잡한 함수를 점근적으로 근사할 수 있다면 잔차함수인 H(x) - x도 점근적으로 근사시키는 것이 가능하다. 애초에 H(x)가 다중 비선형 층이기 때문에 H(x)에서 x만큼을 빼준 잔차함수도 근사가 가능하다는 것은 당연하다. 잔차함수의 형태는 F(x) = H(x) - x이고, 목적함수가 H(x)이기 때문에 최적화는 H(x)를 기준으로 진행된다. 따라서 mapping은 H(x) = F(x) + x로 새로 정의되고 여기에 optimal solution인 identity mapping을 적용하면 F(x) + x = x를 최적화시키는 문제가 되는 것이다. 새로 정의되기 이전의 mapping에서는 다중 비선형 층인 F(x)x의 값을 갖도록 가중치들을 직접 구해야하기 때문에 어려운 부분이 있었다. 하지만 새로 정의된 mapping에서는 F(x)를 0으로만 만들어주면 된다. 따라서 더 쉽게 학습이 가능한 것이다. 그렇다면 여기서 의문점이 한 가지 있다. 

identity mapping이 optimal solution이라고 어떻게 보장할 수 있는가?

실제로는 identity mapping이 optimal solution이 아닐 가능성이 크다고 한다. 하지만 identity mapping으로 가정하는 것이 자그마한 변화를 감지하기에 유리하다고 주장한다. 자그마한 변화는 F(x)를 뜻하고 F(x)F(x) + x = x를 최적화하는 과정에서 0에 가까운 값을 갖도록 학습이 되기 때문에 변화는 아주 미세하게 나타난다. 미세하게 변화되는 값을 처음부터 학습을 통해 찾는 것보다 애초에 0에 가까운 값을 갖도록 유도하는 것이 훨씬 쉽기 때문에 자그마한 변화를 감지하기 유리하다는 것이다. 

 

그렇다면 자그마한 변화를 감지하는 것이 성능 향상에 있어 왜 이점이 있는 것일까? 아쉽게도 이 부분에 대해서는 본 논문에서 명쾌하게 답을 주지 않는다. 뇌피셜로 끄적여 본다면 일단 CNN 구조에서는 초기 층에서는 단순한 특성들이 추출되고 층이 깊어질수록 추상적인 형태의 특성들이 추출되게 된다. 하지만 층이 너무 깊어지게 되면 특성맵의 형태가 너무 추상화된 나머지 기존의 이미지의 특성을 제대로 추출하지 못하게 되고 이런 것들이 성능저하를 일으키는게 아닐까 싶다. 따라서 깊은 네트워크일수록 특성맵이 이전 layer에서의 형태와 차이가 크지 않도록 미세하게 변화를 주어 깊은 층으로 가도 성능 개선에 도움이 될 정도만큼의 추상화된 특징들을 뽑아낼 수 있도록 하는 것이라는 생각이다. Idnetity mapping의 중요성은 "Identity Mappings in Deep Residual Networks"라는 후속 논문에서 자세히 다뤄지는데 이 논문은 나중에 리뷰해보도록 하겠다. 

 

3.2 Identity Mapping by Shortcuts

identity shortcut을 적용한 residual block
projection shortcut을 적용한 residual block

 

Residual block은 수식으로 다음과 같이 정의된다. 출력값 y는 잔차함수 F를 거쳐 나온 값에 shortcut connection인 x가 더해진 것이다. 이때 주의해야할 점은 위의 연산은 행렬 연산이라는 것이다. 따라서 conv layer를 통과하고 나온 행렬 F와 입력값인 행렬 x의 차원이 같아야 한다. 만약 둘의 차원이 다를 경우 projection 과정을 통해 차원을 맞춰줄 수 있다. 이때 적용된 shortcut을 projection shortcut이라고 한다. F와 x의 차원이 같아 shortcut이 그대로 적용되는 것을 identity shortcut이라 한다. 이후 서술될 실험에서 identity shortcut과 projection shortcut의 성능 차이는 별로 없으므로 연산량이 더 많은 projection은 shortcut connection에서 잘 쓰이지 않는다. 

 

3.3 Network Architectures

resnet architecture

resnet은 VGG의 기본구조를 토대로 설계되었고 대부분의 합성곱 층이 3x3 filter를 사용하며 다음 두 규칙을 따른다.

 

1. 출력값의 특성맵 크기가 같은 층들에 대하여 같은 수의 filter를 사용한다.

2. 특성맵의 크기가 절반이 되면 각 층별 시간복잡도를 보존하기 위해 fiter의 수는 2배가 된다

 

donwsampling은 stride=2인 합성곱 층을 통해 수행되고 네트워크의 마지막에는 global average pooling, softmax가 적용되는 노드 1000개인 FC layer가 사용된다. 이러한 기본 구조를 토대로 다음 residual block으로 넘어가는 부분에 shortcut connections를 추가하는데 위 그림에서 점선으로 된 부분은 이 작업을 의미한다. 이때 shortcut connections은 intput과 output의 차원이 동일해야 적용이 가능하기 때문에 두 차원을 맞춰주는 작업이 필요한데 그 방식에 따라 zero-padding shortcuts, projection shortcuts두 방식으로 나뉜다. 

layer 개수 별 resnet의 residual block 구성

 

이때 헷갈리지 말아야 할 점은 zero-padding이나 projection shortcut은 residual block을 첫번째로 돌 때 적용된다는 것이다. 위 그림의 34-layer을 보면 conv2_x에서 conv3_x으로 넘어올 때 처음에 conv3_x에는 28x28x64의 특성맵이 입력된다. conv3_x를 한차례 통과하고 나서의 출력값은 28x28x128이다. 따라서 shortcut connection을 진행하기 위해서는 zero-padding이나 projection shortcut을 적용해 두 특성맵의 차원을 맞춰주는 과정이 필요하다. 이 과정을 한번 진행하고 나면 conv3_x를 다시 돌 때는 입력값이 28x28x128이 되기 때문에 identity shortcut을 적용할 수 있다. 그렇게 총 4번 residual block을 도는 과정에서 처음을 제외한 나머지 3번동안 identity shortcut이 적용되는 것이다. 물론 이 때에도 projection shortcut을 사용할 수 있겠지만 굳이? 이다. 한번 아래의 표를 봐보자.

 

위 표에서 A는 차원을 맞춰줄 때 zero-padding shortcut, B는 차원을 맞춰줄 때 projection shortcut, 다른 부분에서는 identity shortcut, C는 모든 부분에 projection shortcut을 사용하여 성능을 책정한 것이다. A는 성능이 B, C에 비해 상대적으로 낮은데 이는 A에서는 잔차학습이 이루어지지 않기 때문이라고 한다. B와 C는 성능이 비슷하지만 C에서 파라미터가 더 많아 훨씬 더 복잡한 계산이 요구되기 때문에 C는 이 논문에서 사용되지 않았다. 애초에 입력값과 출력값의 차원이 동일한 상황에서는 그냥 입력값을 그대로 가져와 쓰면(identity shortcut) shortcut connection을 진행할 수 있기 때문에 연산량만 늘리는 projection shortcut을 굳이 쓸 필요가 없다는 것이다. 

Bottleneck 구조와 1x1 convolution

 

이 논문은 계산 시간과 복잡도의 개선을 위해 bottleneck 구조를 제안한다. 이는 기존에 하나의 building block에서 2개의 합성곱 층을 사용한 것과 다르게 1x1, 3x3, 1x1 순서로 3개의 합성곱 층을 사용한다. 

plain, bottleneck 구조에서의 identity shortcuts

 

 

오른쪽 bottleneck 구조를 보면 shortcut connection은 identity connection을 적용하고 building block의 합성곱 층들에서1x1 convolution을 통해 projection 과정이 진행되는 것을 알 수 있다. shortcut connection에서 identity connection을 사용하면 추가적인 연산 없이 입력값만 가져오면 되기 때문에 연산을 줄이는데 이점이 있다는 것이 직관적으로 와닿지만 1x1 convolution은 어떤 이점이 있는 것일까?

 

1x1 convolution은 1x1 filter의 개수만큼 특성맵을 생성해 낸다. 즉 28x28x256의 특성맵에 1x1x32의 특성맵을 적용하면 28x28x32의 특성맵이 생성되는 것이다. 만약 28x28x256의 특성맵에 3x3x64의 filter를 적용시킨다고 하면 필요한 파라미터 수는 115.6M개이다. 하지만 28x28x256의 특성맵을 1x1x32의 필터를 적용시켜 28x28x32로 크기를 줄이고 그 이후에 3x3x64를 적용시킨다면 6M+14.4M = 20.4M개의 파라미터만 있으면 된다. 확실히 1x1 convolution을 사용하면 계산량이 훠어어어얼씬 줄어들게 된다. 1x1 convolution가 서로다른 두 차원을 동일하게 맞춰주는 방식과 그 이점에 관해서는 아래 링크에서 아주 깔쌈하게 설명을 잘 해놨기 때문에 참고 바란다.

 

shotout to

https://hwiyong.tistory.com/45

 

1x1 convolution이란,

GoogLeNet 즉, 구글에서 발표한 Inception 계통의 Network에서는 1x1 Convolution을 통해 유의미하게 연산량을 줄였습니다. 그리고 이후 Xception, Squeeze, Mobile 등 다양한 모델에서도 연산량 감소를 위해 이 방

hwiyong.tistory.com

 

 

4. Experiments

4.1 ImageNet Classification

얇은 선은 training error, 두꺼운 선은 validation error를 나타냄
위 그래프에서의 Top 1 error

 

다음은 resnet 모델을 실험을 통해 비교해놓은 파트이다. plain networks에서는 더 깊은 네트워크의 오류가 높은데 18, 34층의 네트워크 모두에서 배치정규화가 적용되기 때문에 이는 기울기 소실의 문제로 보기는 어렵다. 본 논문에서는 깊은 네트워크에서 수렴률이 매우 낮아서 오류가 더 높게 나오는 것으로 추측하고 있다. residual networks에서는 모든 shortcuts에 대하여 identity mapping이 적용되고 차원을 늘리는데에(다음 residual block으로 넘어갈 때) zero-padding shortcut이 적용되었는데 얕은 층보다 더 나은 성능을 보였고 degradation 문제는 확실히 줄어든 모습을 보인다. 그런데 18 layer의 pain, resnet error를 보면 별로 차이가 없지만 resnet에서 수렴이 더 빠르다.(라고 논문에서 말하지만 내 눈에는수렴 속도엔 차이가 없어보이는디?) 이는 resnet이 최적화를 용이하게 해 수렴이 더 빨리 되는 것이라고 한다. 

10-crop testing의 오차
서로 다른 깊이를 가진 6개의 resnet을 앙상블한 모델의 오차

 

50층 이상의 resnet 구조에서부터는 bottleneck 구조가 적용된다. 이러한 구조를 활용한 resnet은 놀랍게도 152층으로 아주 깊게 쌓은 층도 효과적인 성능을 나타내고 있음을 확인할 수 있다. 서로 다른 깊이의 resnet을 앙상블 하면 성능은 더욱 개선된다. 그리고 이 앙상블 모델이 2015 ILSVRC에서 우승을 차지했다.

 

+ Recent posts