Nov 20 10:15:30 server sshd[1234]: Accepted publickey for jsmith from 192.168.1.100 port 22 ssh2
Nov 20 10:15:30 server sshd[1234]: pam_unix(sshd:session): session opened for user jsmith by (uid=0)
Nov 20 10:15:30 server systemd-logind[567]: New session 1234 of user jsmith.
로그를 확인해 보았을 때, audit log에 정상적으로 success가 찍혀있고, secure log를 확인해 보니, 연결 시도부터까지의 시간이 느린 것을 발견하였다.
방법 발견
최초에 SSH가 느려지는 상황을 구글링 하였을 때, 가장 많이 나오는 방법이 /etc/ssh/sshd_config 파일의 UseDNS 옵션을 "no"로 바꾸는 것이다.
사실 이 방법이 효과적일 것으로 생각했지만, 회사의 엄격한 보안 정책과 운영 중인 시스템에서 변경점을 가하기 어려워(sshd restart가 필요하기 때문), 운영 중이지 않은 서비스로 실험을 해보았다.
/etc/ssh/sshd_config에 아래 내용을 추가한다. (만약 UseDNS yes로 되어 있으면 no로 변환한다) 그러고 나서, sshd를 재시작해준다.
$ sudo vim /etc/ssh/sshd_config
---------------------------------
UseDNS no
---------------------------------
$ sudo systemctl restart ssh
원리를 알아보니, server에서 SSH로 연결을 시도할 때, SSH 데몬에 DNS를 사용하도록 설정되어 있으면, target 서버에서 /etc/resolv.conf 파일을 읽어서, 해당 파일에 명시된 DNS 서버에, hostname을 묻는 DNS query를 날리고, 이 결과를 통해, 현재 SSH 연결을 시도한 서버에 해당하는 서버가 있으면 해당 hostname으로 연결을 진행한다.
이 과정을 reverse DNS lookup이라고 하는데, resolve.conf의 각 서버와 통신하여, hostname을 묻는 과정이 포함되기 때문에 conf 파일 내의 모든 서버와의 연결이 정상적이어야 한다.
UseDNS를 no로 변경하니, 이슈 전처럼 정상적인 속도로 SSH가 진행되었다. 하지만, 단순히 DNS connection 몇 개가 추가되었다고, 이렇게 오랜 속도가 걸리는 것은 많이 이상하였다.
이슈 해결
결론부터 말하면, resolv.conf에 해당한 서버 중, 특정 서버가 IP를 변경하여 생긴 문제였다. 문제가 되는 대역대는 VM 서버를 사용하는 서버들이었고, 하나의 서버를 copy 하는 방식으로 서버를 setup 했기 때문에, 이 대역대의 모든 서버에 대해 동일 현상이 발생하였다.
resolv.conf에서 문제가 되는 IP들을 제거해 주니, UseDNS 옵션을 다 시켜도, 예전처럼 빠른 속도로 SSH를 붙을 수 있게 되었다.
특히, 문제가 발생한 서비스의 경우, shell script 상에 expect 명령어를 이용하도록 되어 있는데, DNS와의 연결이 되지 않으니, Target Server에서 적절한 response를 주지 못하고, 이로 인해 Target 서버와의 연결이 안 되는 것처럼 느껴진 것이었다.
돌이켜보니, 매우 간단하고, 황당한 이슈였지만, 원인을 찾는 과정에서 많은 것을 배운 것 같다.
교훈
최근 SSH에서는 UseDNS의 default 설정이 no라고 한다. 다만, 예전 버전을 사용하는 경우에는 UseDNS의 기본 옵션은 yes이다. 만약 보안적 문제가 없다면, UseDNS를 끄는 것이 더 좋을 것 같다.
하지만, 불가피하게 사용하는 경우에는 꼭 DNS 설정을 잘 관리해야 한다. 해당 DNS도 현재는 거의 사용하지 않지만, History 및 영향도 파악이 불가하여, 별도의 삭제를 하지 않다가 이슈를 맞이하게 된 상황이다.
최근 회사에서 plsql로 수 천 줄이 넘는 query를 python으로 바꾸었다. 최초에는 기존 병렬 처리를 그대로 바꾸기 위해, pyspark로 바꾸는 방법을 선택했지만, 생각보다 속도가 매우 느리고, Python UDF 사용 시, 데이터 정합성이 틀어지는 문제등이 발생하였다. (아마 비동기 처리 때문일 것으로 예상된다.) 결국, 여러 가지 시도 후, 이 프로젝트에서는 pandas가 더 유용할 것이라고 판단되었고, 속도를 끌어올리기 위해 여러 방법을 사용해 보다가 발견한 방법을 소개한다.
Pandas Apply의 문제
Pandas는 C를 이용한 연산을 하는 numpy 기반으로 되어 있기 때문에, 어느 정도의 벡터화는 가능하지만, 무거운 연산등을 apply로 수행할 때, 속도적 한계를 가진다.
이는 Python의 고질적 문제인 GIL(Global Interpreter Lock)으로 인한 병렬 처리의 한계와 타입 추론에 추가적인 cost가 든다는 점등이 있다.
특히, GIL 때문에, pandas는 하나의 쓰레드 만이 python 바이트 코드를 실행하기 때문에, 많은 row에 apply를 수행할 때, 병렬 처리의 효과를 보지 못해 오랜 연산 시간이 걸리게 된다.
이를 해결하기 위해, dask나 swifter 같은 다양한 multiprocessing 지원 라이브러리가 나왔지만, ⓐ경험상 특정 연산이 아니면 속도적 이점이 뚜렷하지 않고, ⓑ처리해야 할 row수가 적은 경우에는 오히려 작업 병렬화에 드는 cost가 더 큰 경우가 많다.
보통, 이런 경우에 다음 step으로 고민하는 것이 spark인데, 경험상 처리할 데이터가 수 GB 이하인 경우에는 pandas가 유리하다. 특히, 복잡한 로직이 전처리 과정에 많이 들어갈 경우에는 직렬화 cost가 많다는 점, API가 확실히 유연하지 않다는 점(예를 들어, 여러 칼럼에 대한 동시에 unnest를 수행할 때, pandas는 explode 한 번에 가능하지만, spark는 여러 칼럼을 묶었다 푸는 과정이 추가적으로 필요하다) 때문에 pandas가 소규모 데이터 처리에 더 큰 강점을 가진다고 생각된다.
Python Multiprocessing
이를 해결하기 위해, 기존 apply를 python "multiprocessing" 라이브러리를 사용하여 처리 방식을 바꿔서 처리할 수 있다.
원리는 처리해야하는 전체 row를 사용 가능한 process 수만큼으로 나눠서 처리하고, 그 연산 결과를 concat 하는 것이다.
특히, 이 방식은 apply에서 참조하는 칼럼이 하나인 경우에는 연산처리 시간이 매우 크지 않아 차이가 덜한데, 여러 칼럼을 참조하는 경우(apply의 axis가 1인 경우)에는 그 효과가 매우 크다.
물론, 이 방식이 항상 장점만 있는 것은 아니다. 우선 당연히 단일 코어가 아니여야하고, 적은 데이터에서는 프로세스 간 통신 오버헤드나 cocat 과정에서 발생하는 cost 등이 존재하여, 성능 상 오히려 손해를 볼 수도 있다. 또한, 메모리 사용량이 증가할 수 있다는 것이 단점이 될 수 있다.
따라서, 모든 apply를 해당 방식으로 바꾸기보다는 time debugging 이후, 연산 시간이 너무 많이 소요되는 apply를 대체하여 비교하는 방식으로 적용하는 것을 추천한다.
대체 코드는 다음과 같다. parallel_apply의 인자는 처리할 pandas 데이터와 그 함수에 적용할 func를 넘겨주면 된다.(이때, func에 lambda를 사용할 수 없는데, lambda는 직렬화가 되지 않기 때문이다. 따라서 일반 함수를 정의해서 사용하면 된다.)
[DataFrame으로 나누기]
import pandas as pd
from multiprocessing import Pool, cpu_count
def parallel_apply(data, func, num_processes=None):
if num_processes is None:
num_processes = cpu_count()
chunk_size = len(data) // num_processes
chunks = [data.iloc[i:i + chunk_size] for i in range(0, len(data), chunk_size)]
with Pool(num_processes) as pool:
results = pool.map(func, chunks)
return pd.concat(results, axis=0)
[Row로 나누기]
def parallel_apply(data, func, num_processes=None):
if num_processes is None:
num_processes = cpu_count()
# print("core 갯수 ", num_processes)
with Pool(num_processes) as pool:
results = pool.map(func, [row for index, row in df.iterrows()])
return results
데이터를 chunk 단위로 나누는 방식은 DataFrame 단위(하나의 큰 DataFrame을 여러 DataFrame으로 나눔)나, Row 단위(각 row의 연산을 병렬 수행함) 경험적으로 연산의 CPU 사용량 클 때는 Row 단위의 나눔이 성능 상 우위가 있는 것 같고, 아니면 chunk 단위로 나누는 것이 빠른 것 같다.
사용 예시
우선 여러 DataFrame으로 나눠서 처리하는 예시이다. 실제 사용하는 함수에서는 apply에 들어가는 함수가 매우 heavy하여 그 효과가 더 컸지만, 그만큼 heavy 한 함수가 생각나지 않아 row의 크기를 늘렸다.
import time
import pandas as pd
import numpy as np
from multiprocessing import Pool, cpu_count
import math
def custom_func(row):
return row['data1'] ** 2 - math.sqrt(row['data2'])
def parallel_custom_func(chunk):
result = chunk.apply(custom_func, axis=1)
return result
def parallel_apply(data, func, num_processes=None):
if num_processes is None:
num_processes = cpu_count()
print("core 갯수 ", num_processes)
chunk_size = len(data) // num_processes
chunks = [data.iloc[i:i + chunk_size] for i in range(0, len(data), chunk_size)]
with Pool(num_processes) as pool:
results = pool.map(func, chunks)
return pd.concat(results, axis=0)
if __name__ == '__main__':
df = pd.DataFrame({'data1': np.arange(1000000), 'data2': np.arange(1000000)})
# pandas apply
start_time = time.time()
df["data"] = df.apply(lambda row: row['data1'] ** 2 - math.sqrt(row['data2']), axis=1)
print("일반 Apply",time.time() - start_time)
# multiprocessing apply
start_time = time.time()
df["parallel_data"] = parallel_apply(df, parallel_custom_func)
print("Parallel Apply",time.time() - start_time)
Apply 외 다른 함수에 적용 & 주의점
row 단위의 연산을 하는 다른 함수에도 적용 가능하다. 예를 들어, unnest를 위한 explode 함수등에도 사용 가능하다. 다만, 연산이 매우 무겁지 않은 경우에는 속도적 차이를 느끼지 못했다.
경험상, pandas의 내장 함수가 가장 빠르기 때문에 내장 함수로 처리 가능한 것은 내장 함수를 우선적으로 선택하고(C를 이용한 벡터화가 매우 잘 되어 있을 것으로 예상됨), apply 함수 중, 연산 수행 시간이 매우 오래 걸리는 부분을 선택적으로 multiprocessing을 이용한 함수로 바꾸면 효과가 큰 것 같다.
연산이 가볍고, row수가 많지 않을 때는 단순 apply가 빠르다. 꼭 time debugging 후 바꿔야한다!
그럼에도 불구하고, multiprocessing이 swifter 등보다 좋은 점은 연산의 병렬 처리 정도를 연산에 참여하는 process 수를 조절하면서 적절하게 조정할 수 있다는 것이다. 단순히 apply를 사용하거나 전체 process를 다 사용하는 것이 아니라, concat 과정에서의 overhead와 비교해가면서 적절한 수준의 병렬처리를 해주면서 조절할 수 있다.
항상 문제에 봉착하기 전에는 내가 모르는 것이 뭐인지 모르게 된다. 항상 Batch Normalization은 당연하게 사용하였지, 그 의미에 대해서 대략적으로만 알고 있었던 것 같아서, 이번 기회에 Batch Normalization의 논문을 읽으면서 기본기부터 다시 쌓고자 한다.
Batch Normalization 배경 설명
Batch Normalization은 딥러닝을 접해본 사람이면, 누구나 알 것이다. Batch Normalization은 2015년 구글에서 ICML에 발표한 논문이다.
Internal Covariate Shift 문제를 정의하고, 이를 해결하기 위한 mini batch 단위의 Normalization 방법에 대해서 제안한다.
Abstract
DNN의 학습 과정에서 앞선 layer들의 parameter 변화에 따라, 뒷 layer들의 input의 분포가 변하기 때문에 학습이 매우 어렵다.
이 현상으로 인해 학습의 learning rate를 크게 가져가지 못하고, parameter 초기화를 신중히 해야 한다. 따라서, 비선형성 모델의 학습을 느리게 만든다.
이 논문에서는 이 현상을 "internal covariate shift"라 부르고, 이를 해결하기 위해, layer의 input을 normalize 하는 방법을 사용한다.
이 모델에서는 normalization을 model 아키텍처의 일부분으로 각 training mini-batch마다 normalization을 수행하는 방법을 사용한다.
Batch Normalization을 통해, 더 높은 learning rate를 사용할 수 있고, 모델이 parameter 초기화에 덜 민감해지게 된다. 또한, regularizer로 작동하기 때문에, 일부 상황에서는 Droupout을 대체하기도 한다.
그 당시 SOTA image classification model에 적용하여, Batch Normalization은 training step을 14 단계나 줄이면서 비슷한 accuracy를 달성하였다.
Introduction
[배경]
stochastic gradient descent(SGD)는 deep network 학습에 효과적인 방법이라는 것이 밝혀졌다. SGD의 변형 버전들은 SOTA 성능을 이끌고 있다.
SGD는 parameter를 optimize 하기 위해 loss를 minimize 한다. SGD에서 학습은 단계적으로 이뤄지고, 각 학습의 단계는 mini batch로 여겨진다. 이 mini-batch는 loss function의 gradient를 추정하기 위해, 아래와 같은 식을 사용한다.
mini-batch들을 사용하면, 데이터를 하나씩 학습하는 것에 비해 몇 가지 장점이 있다.
mini-batch를 통해 구해진 loss의 gradient를 통해, training set의 gradient를 추정할 수 있다.
bach 계산은 병렬 연산을 이용하기 때문에 각 example을 m번 연산하는 것보다 효과적이다.
[문제]
이렇게, SGD는 간단하고 효과적이지만, model hyper-parameter를 신중하게 tuning 해야 한다. 특히, 앞선 layer들의 parameter 변화가 뒷 layer들에 영향을 미치기 때문에, model parameter나 learning rate 같은 hyper parameter 선택 시 주의를 요한다.
특히, layer input의 분포 변화는 layer들이 새로운 분포를 계속 처리해야 하는 문제를 야기한다. 이러한 input 분포의 변화를 covariate shift라 한다. 예를 들어, 아래와 같은 gradient descent step 연산이 있을 때, x (input 값)이 고정된다면, parameter는 x의 새로운 분포에 적응하는데, 연산을 낭비하는 것 없이, parameter optimize에 집중할 수 있다.
추가적으로, 이런 고정된 input의 distribution은 다른 장점을 가진다. 예를 들어, sigmoid를 생각해 보았을 때, sigmoid의 input의 절댓값이 커지면(큰 음수값을 가질 때), sigmoid의 값은 0에 가까워진다. 이는, 학습의 속도를 늦추게 된다. 이러한 현상은 모델의 깊이가 깊어질수록 가속화되는데, 이러한 현상(staturation problem으로 야기된 gradient vanishing)을 실제 학습에서는 ReLu function과 좋은 parameter initialization, 또는 작은 learning rate 적용을 통해 방지하곤 한다.
하지만, 이러한 비선형의 input들이 조금 더 안정적인 분포를 띄게 된다면, optimizer는 saturate에 덜 빠지게 되어, 학습이 가속화될 것이다.
이 논문에서는 deep network의 internal node의 분포 변화를 "internal Covariate Shift"라고 명명하고, 이것을 없애면 더 빠른 학습이 된다는 것을 밝힌다.
[Batch Normalization]
이를 위해, "Batch Normalization"이라는 방법을 소개한다. 이 방법은 input layer들의 평균과 분산을 고정된 값으로 normalization 하는 과정을 포함한다.
Batch Normalization을 사용하면, parameter들의 초깃값의 scale에 따른 gradient scale의 민감도를 줄여줘서, divergence 위험 없이 더 큰 learning rate를 선택할 수 있게 해 준다.
추가적으로 Batch Normalization은 model을 regularize 해줘서, Dropout의 필요성을 줄여준다.
Internal Covariate Shift를 줄이기 위한 방법
이전부터, input을 whitned(평균 0, 표준편차 1의 값)하게 되면 network training이 빠르게 수렴된다는 것이 알려져 있었다.
whiening을 모든 학습 과정이나, 일정한 interval 단위로 network를 직접 바꾸거나, optimization algorithm을 활용한 parameter 변경을 할 수 있지만, 이러한 변화들이 optimization 단계에서 gradient step의 영향을 줄여, 학습의 속도를 저하시킬 수 있다. (normalization이 되기 때문에)
만약, network가 항상 원하는 형태의 분포를 갖게 된다면, loss에 대한 model parameter의 gradient가 normalization을 고려하고, model parameter에 대한 의존성을 동시에 고려할 수 있게 된다.
x가 layer input vector이고, X가 training dataset의 전체의 input vector 집합이라고 할 때, normalization은 다음과 같이 계산될 수 있다
이 식의 backpropagation을 위해서 Jacobian을 계산해야 하는데, 아래 식에서 두 번째 term을 무시하면 explosion이 일어날 수 있다. 이 framework에서 layer의 input을 normalization 하기 위해서는 매우 큰 비용이 드는데, covariance matrix를 구해야 하기 때문이다.
이를 해결하기 위해, 저자들은 미분 가능한 다른 대안을 생각해 보았고, 앞서 통계적 방법이나 feature map들을 사용한 방법들이 있었지만, network의 성능을 떨어뜨릴 수 있기 때문에, network의 정보를 유지하면서 전체 training data의 통계값에 대한 trainign example을 normalization 하는 방법들을 고안하게 된다.
Mini-Batch 통계값을 이용한 Normalization
각 scalar feature들을 독립적으로 평균 0, 분산 1을 갖도록 normalize 한다. input들의 모든 dimension을 아래와 같이 normalization 한다.
단순히, 위처럼 input을 normalization 하면, 각 layer의 output 값들이 변경될 수 있기 때문에, 아래처럼 original model param을 구해 준다. 이때, gamma과 beta는 학습가능한 parameter이다. 즉, normalization 한 값을 original 값으로 돌리기 위한 parameter들을 학습하는 것이다.
Normalize를 위해 전체 training set을 모두 통계내는 것은 비효율적이다. 따라서, mini-batch의 개념을 이용하여, 각 mini-batch에서 평균과 분산을 계산한다. (즉, batch 내에서 각 dimension에 대한 평균과 분산을 구한다.)
이 과정을 Batch Normalization이라고 하고, 그 자세한 과정은 아래와 같다.
Batch Normalization은 단순히 하나의 batch 내에 있는 training example들에 의존하는 것이 아니라, 다른 mini-batch들을 고려할 수 있게 된다. 이를 통해, 모델의 학습 과정에서는 항상 평균이 0이고, 분산이 1인 input, 즉 고정된 distribution이 사용되게 된다.
CNN에서 Batch Normalization
Batch Normalization은 CNN에도 적용할 수 있는데, 아래와 같은 식이 있다고 할 때, (u: layer input, g: activation function, W: weight, b: bias) Convolutional layer 직후 (activation function 전)에 Batch Normalization을 걸어 줄 수 있다.
BN을 model input인 u에 걸어주지 않는 이유는, model input은 다른 nonlinearity의 output이기 때문에 (전 layer의 activation function의 결과이기 때문에) 분포가 학습과정에서 계속 바뀔 수 있기 때문이다. 반면에, convolutional layer 직후(Wu+b)는 더 대칭적이고, non-sparse 한 분포를 띌 가능성이 높고, 더 "Gaussian"하기 때문에 조금 더 안정적인 distribution을 만들 수 있다고 한다. b는 합항이기 때문에 당장 고려하지 않아도 된다. (어차피 학습될 Beta가 해당 값을 포괄할 수 있기 때문에) 따라서, layer의 output은 다음과 같이 나타낼 수 있다.
convolutional layer에서는 BN을 위해, 각 mini batch에서 모든 위치에 걸쳐 공통적으로 정규화하는 방법을 사용하고 있다. (즉, 기존은 dimension 단위의 normalization이었다면, 이번엔 feature의 spatial 부분까지 고려한 normalization이다.) 따라서, learned parameter인 gamma와 beta도 feature map의 크기를 갖는다.
Batch Normalization으로 인한 high learning rate 사용 가능
기존 deep network에서 큰 learning rate를 적용하면 gradient의 explosion이나 vanish가 일어나게 되어, local minima에 빠지게 된다.
Batch Normalization은 parameter의 변화로 인한 gradient의 큰 변화를 방지하여, high learning rate를 사용 가능하게 해준다.
실험 결과
[batch normalization으로 인한 high learning rates 사용 가능]
batch normalization으로 인해 큰 learning rates를 사용할 수 있게 되었다.
[ImageNet Classification]
아래 그래프의 가로축은 training step이고 세로축은 validation accuracy이다. Batch Normalization을 이용하면 더 적은 step에서 좋은 성능을 발휘하는 것을 확인할 수 있다.
Reference
Ioffe, Sergey, and Christian Szegedy. "Batch normalization: Accelerating deep network training by reducing internal covariate shift."International conference on machine learning. pmlr, 2015.
총평
당연히 Batch Normalization에 대해 이해하고 있다고 생각했고, 따로 논문 찾아볼 생각을 안했는데, 읽기를 잘했다.
논문이라는 게 문제를 정의하고 푸는 과정이기 때문에, "왜?" 쓰는지를 깊이 알려면, 논문을 직접 읽는 게 나은 것 같다.
이 논문에서는 한 번에 물체의 위치와 classification을 진행할 수 있는 DETR이라는 새로운 네트워크를 소개한다.
기존에 Object Detection에서 존재하던 NMS(Non-maximum suppression)이나, anchor box 생성 같은 manual 작업들을 제거한 detection pipeline을 구성하였다.
DETR의 주요 아이디어는 bipartite matching을 통한 unique predictions를 강제하는 "set-based global loss"와 transformer의 encoder-decoder 구조이다.
object query가 있을 때, DETR은 object들과 전체적 이미지의 context 간의 관계를 추론하고, 이 정보를 기반으로 최종 예측을 병렬적으로 수행한다.
새로운 모델은 구조적으로 간단하고, 별도의 라이브러리를 사용하지 않는다는 장점이 있다.
DETR은 accuracy와 run-time 측면에서 Faster R-CNN 기반의 최적화된 모델들에 버금갈 정도의 좋은 성능을 보인다.
Introduction
[배경]
기존 Object detection 방법들은 미리 구성해 놓은 anchor box를 이용하여, 많은 object들의 후보군을 만들어 놓고, 이를 regression과 classification을 통해, 예측하고, 비슷한 예측결과를 지우는(NMS) 과정들을 통해 진행된다.
이러한 과정을 간단하게 하기 위해, 이 논문에서는 이미지에서 직접 물체의 위치를 추론하는 방법으로 접근하고자 한다.
이미 음성인식등의 다른 분야에서는 이러한 end-to-end 방식이 성공을 거두었지만, object detection 분야에서는 이러한 시도들이 prior knowledge를 다른 방식으로 사용하거나, 성능 상에서 경쟁력이 없었다.
[소개]
이를 위해, 이 논문에서는 object detection의 학습 과정을 direct set prediction problem으로 접근한다.
논문에서는 transformer의 encoder-decoder 구조를 사용하였다. transformer의 self-attention 메커니즘은 비슷한 예측들을 제거하는데 적절한 constraints가 될 것이다. (아마, NMS를 대체할 수 있을 것이다.)
DETR은 모든 object를 한번에 예측하고, 예측값과 ground-truth object들 간의 bipartite matching을 이용한 set loss function을 통해 end-to-end로 학습된다.
이를 통해서, 기존 object detection에서 prior knowledge를 담은 anchor box나 NMS 등을 제거할 수 있었다.
[기존 set prediction과의 비교]
기존의 direct set prediction과 비교하면, DETR은 bipartite maching loss와 transformer를 이용한 parallel decoding의 결합이 차이다.
기존 방식들은 RNN 방식에 의존하여, 예측 object들의 배열에 의존하는 것에 반해, DETR은 transformer를 이용한 parallel 연산으로 예측 object들의 배열에 의존하지 않는다.
[실험]
DETR을 가장 많이 사용되는 object detection의 dataset인 COCO에서 Faster R-CNN 기반의 좋은 성능 모델들과 비교해 보았다.
최근(그 당시) 모델들은 최초 Faster R-CNN에서 구조 변화를 통해 성능이 크게 향상되었음에도 불구하고, DETR은 그들과 필적할만한 좋은 성능을 보였다.
조금 더 상세하게는, DETR은 large object들에 대해서는 더 좋은 성능을, small object들에 대해서는 더 낮은 성능을 보였다.
DETR Model
DETR의 핵심요소는 (1) 예측과 ground truth 간의 unique matching을 만들어주는 set prediction loss와 (2) object 집합들을 예측하고, 그들 사이의 관계를 modeling 하는 아키텍처이다.
[object detection set prediction loss]
<1번째 : matching 구하기>
첫 번째로, DETR은 고정된 개수 N개에 대한 예측을 한다. 이때, N은 일반적인 이미지의 object 개수보다 큰 숫자이다.
이러한 구조의 학습과정에서 주요 이슈는 예측된 object를 ground truth와 어떻게 비교하냐이다. 이를 위해, 예측과 ground truth 간에 최적의 matching을 만드는 loss를 사용하였다.
y가 object들의 ground truth이고, y는 no object의 class도 가질 수 있다고 할 때, 예측과 ground truth 간의 bipartite matching을 갖는 조합을 찾기 위해 아래 식을 계산한다.
위 식에서 L_match는 pair-wise matching cost인데, 이 는 class의 prediction과 예측과 ground-truth 간의 box 차이를 모두 고려한 cost이다.
이러한 방식은 기존 detector들의 방식의 anchor 등과 유사한데, 그들과의 차이점은 기존 detector에서는 비슷한 예측들이 많이 존재하는데, matching loss에서는 1대 1 매칭이 된다는 것이다.
<2번째 : loss 구하기>
두 번째로, 앞서 구했던 전체 matching 쌍들에 대해 Hungarian loss function을 계산한다. 이때의 loss는 일반적인 object detection의 loss와 비슷하게, class prediction에 대한 loss와 box loss의 합이다.
위의 식에서 우선 no object class의 matching cost는 prediction의 영향을 받지 않는다. (상수이기 때문에 학습 과정에서 영향을 미치지 않는다.)
위의 식에서 box loss를 구성할 때, 기존 object detection의 box loss와 달리, loss의 scale에 대한 이슈가 있다. (기존에는 anchor에 대한 상대적 위치를 loss로 썼기 때문에) 이를 해결하기 위해, box loss로 주로 사용되는 L1 loss 뿐 아니라, IoU loss를 추가적으로 사용했다.
따라서 box loss는 아래와 같다.
[DETR 아키텍처]
DETR의 전체 아키텍처는 아래 그림에서 보이는 것처럼 매우 간단하다.
DETR은 크게 3가지 주요 구조로 구성되어 있다. (1) compact feature를 뽑기 위한 CNN backbone (2) encoder-decoder transformer (3) 최종 예측을 위한 feed forward network(FFN)
DETR은 Pytorch로 50줄 미만으로 inference code를 짤 수 있을 만큼 간단하다.
Backbone : 이미지를 CNN backbone을 통과시켜, feature를 얻는다. 일반적으로 2048 X (H/32) X (W/32) shape의 feature를 뽑았다.
Transformer encoder : 우선 1X1 convolution을 통해, channel dimension을 줄여줬다. encoder의 input으로 sequence 값이 필요하기 때문에, spatial 부분(H와 W)을 HW개의 sequence로 만들었다. encoder는 일반적인 transformer의 encoder이다. transformer 구조는 배열 순서에 무관하게 연산되기 때문에, 고정된 positional encoding 값을 더해서 이를 보충했다.
Transformer decoder : decoder도 일반적인 transformer의 구조를 따랐다. 조금 다른 점은 DETR은 N개 object를 병렬적으로 각 decoder layer에서 연산하였다는 점이다. decoder도 마찬가지로 배열 순서에 무관하게 연산되기 때문에, positional encoding을 decoder의 각 attention layer에 추가하여 연산한다. decoder에 의해 N개의 object query들은 output embedding으로 변환되고, 각각 독립적으로 FFN에 의해 box coordinate와 class label을 예측하게 된다.
FFN : 최종 prediction은 ReLU activation을 사용하는 3개의 layer에 의해 연산된다. FFN은 box의 중심 좌표와 height, width와 class label을 예측한다. 최종 N개의 결과가 나오는데, 이중 no object를 제외하고, 나머지들이 DETR의 최종 예측이 된다.
Auxiliary decoding loss : auxiliary loss를 사용하는 것이 decoder의 학습 중 각 class의 정확한 object 개수를 추정하는 데에 도움이 된다는 것을 발견했다. 모든 prediction FFN은 parameter를 공유한다.
Experiments
COCO dataset에서 Faster-RCNN 기반의 최근(그 당시) object detection model과 필적할 정도의 좋은 성능을 보인다.
그밖에 encoder size나, selt-attention, segmentation에 적 등의 실험이 있다. 논문을 참조 바란다.
Reference
Carion, Nicolas, et al. "End-to-end object detection with transformers."European conference on computer vision. Cham: Springer International Publishing, 2020.
총평
Transformer가 당연(?)하게도 OD 분야에 적용되었다. 2020년에 발표되어 늦은 감이 있지만, 지금이라도 읽어서 다행이다.
Object Detection을 처음 접했을 때, anchor box를 사용하는 것과 NMS를 사용하는 것이 매우 비효율적이라고 생각하면서도 어쩔 수 없다고 생각하였는데, 결국 해결되는 것 같다.
Python으로 데이터를 다루는 업무에서 Pandas는 거의 필수적이다. Pandas는 쉽고, 빠르고(Python에 비해), 유용하고, 간편하기 때문이다. 또한, 테이블 형태의 구조를 가지고 있는 pandas DataFrame은 매우 친숙하고, 안정적인 느낌을 준다. 평소에는 Pandas의 사용하는 기능만 사용하고, 필요한 기능이 있으면 그때그때 알아보면서 사용하고 있는데, 이번 기회에 Pandas의 함수를 정리해보고자 한다.
Series : Pandas의 series는 1D array와 유사한 데이터구조로, index와 value로 구성되어 있다. Series가 1D의 형태를 가지고 있다 보니, axis가 1개 존재하는데, 이 axis를 index라고 한다. value로는 ndarray나, dictionary, 스칼라 값들이 들어갈 수 있다.
DataFrame : DatFrame은 2D의 데이터 구조로, SQL 테이블과 비슷하게, column과 row로 구성되어있다. DataFrame의 각혈은 Series로, DataFrame은 칼럼명이 key인 Series들의 dictionary로 생각할 수 있다. DataFrame은 Series나 2D ndarray, 다른 DataFrame, 1D 데이터(ndarray, list, Series 등등..)들로 구성된 Dictionary를 통해 만들 수 있다.
기본적으로 DataFrame은 Numpy를 기반으로 만들어졌다. 따라서, Series는 Numpy 1D 배열(ndarray)과 동일하다고 생각하면 된다. 때문에, numpy에 있는 거의 모든 함수들이 Pandas 데이터 구조에 호환된다.
Pandas의 함수
[Indexing/Selection]
Indexing/Selection은 Python의 리스트처럼 매우 직관적이여서, 공식 사이트의 부분으로 설명을 대체한다.
query : 위에서 나온 DataFrame 기본 indexing 말고도, SQL 형식으로 DataFrame에서 조건에 맞는 row를 가지고 올 수 있다.
import seaborn as sns
if __name__ == '__main__':
titanic_data = sns.load_dataset("titanic")
print("[query 출력값]")
print(titanic_data.query('age > 30'))
between : 특정 숫자형 열의 범위 내의 데이터를 filtering하기 위해 사용할 수 있는 함수이다.
import seaborn as sns
if __name__ == '__main__':
titanic_data = sns.load_dataset("titanic")
print("[between 출력값]")
print(titanic_data[titanic_data['age'].between(30,40)])
[데이터 연산]
사실 Series와 DataFrame이 많이 사용되는 이유이기도 한데, Series나 DataFrame 끼리 연산이 된다.
단순 연산이 간단할 뿐 아니라, 간단한 연산 위주로 수행한다면, 속도가 매우 빠르다. (Pandas는 numpy로 구성되고, numpy는 C로 변환하여 연산을 하기 때문에)
import pandas as pd
import numpy as np
if __name__ == '__main__':
df1 = pd.DataFrame(np.arange(10))
df2 = pd.DataFrame(np.arange(10)*2)
print(df1+df2)
print(df1-df2)
print(df1*df2)
print(df1/df2)
[데이터 읽기&쓰기]
read_csv, read_excel : 각각 csv 형태, excel 형태를 쉽게 읽어서, DataFrame 형태로 반환한다.
to_csv, to_execel : DataFrame을 각각 csv, excel 형태로 export 한다.
[데이터 요약&추출]
head : DataFrame의 처음 N개(인자값) 행을 출력한다. (default:5)
tail : DataFrame의 마지막 N개(인자값) 행을 출력한다. (default:5)
sample : DataFrame에서 무작위 행 N개를(인자값) 추출한다. (default:1), DataFrame의 행을 무작위로 섞고 싶다면, frac 인자(추출 비율)를 1로 지정하면, 전체 DataFrame이 무작위로 섞여서 나온다.
describe : DataFrame의 칼럼 중, 숫자 칼럼에 대한 통계값(평균, 표준편차, 최솟값, 사분위수, 백분위수, 최댓값)을 표현해 준다.
value_counts :Series 객체 내의 unique value의 빈도를 반환한다. Categorical 데이터 개수 파악에 주로 사용한다.
dtypes :dtypes는 함수가 아닌 속성이다. 각 열의 데이터 유형을 출력한다. pandas의 DataFrame은 기본적으로 열의 데이터 유형을 추정하는 방식을 사용하기 때문에, 추정한 유형을 알기 위해 출력이 필요하다.
info : DataFrame의 각 칼럼 값과, Index 범위, not null 인자 개수, 데이터 타입, 메모리 정보를 출력해 준다.
combine_first : 두 DataFrame을 결합할 때, 첫 번째 DataFrame이 누락된 경우, 두 번째 DataFrame의 값으로 대체하는 값이다. DataFrame을 이용한 coalesce or nvl 함수라고 보면 된다. 어떠한 DataFrame에 비어있는 값을 다른 DataFrame의 값으로 대체하기 위한 함수이다.
combine : 두 DataFrame을 결합할 때, 개발자가 정의한 함수에 따라서 결합하기 위한 함수이다. combine_first가 단순히 하나의 DataFrame에 없는 값을 대체해 주는 것에 비해, combine은 결합 조건 및 결합 방식을 직접 정의 가능하다.
concat : DataFrame을 단순히 쌓는다. axis가 0이면, row에 append 하는 구조로 DataFrame을 결합하고, axis가 1이면 칼럼이 늘어나는 방식이다.
join : SQL에서의 join처럼 두 DataFrame 간의 join을 위해 사용되는 함수이다. 다만, 인덱스 기준으로 DataFrame을 결합하기 때문에, index를 가공하지 않으면 사용이 제한적이다. join 방법을 지정하는 how 인자에 left, right, inner, outer를 지정할 수 있다.(default left)
merge : 조금 더, 유연한 방식의 join 방법이다. join은 DataFrame 함수이고, merge는 pandas 함수이기 때문에, join은 인덱스 기준으로만 join이 가능한 반면, merge는 join key를 지정해 줄 수 있다. 인자는 다양하지만, join key를 지정하는 "on", join 방식을 결정하는 "how" 정도만 사용하면 된다. "how" 인자로는 join과 마찬가지로 left, right, inner, outer를 지정할 수 있지만, default 값이 inner join이다.
mean, max, min, sum, median, std, count : pandas의 DataFrame은 Table 형태의 구조를 띄고 있기 때문에, 집계가 매우 용이하다. DataFrame의 특정 칼럼을 지정하고, 해당 함수를 사용하면, 칼럼의 통계값을 쉽게 얻을 수 있다.
agg : 집계를 한 번에 적용할 때, 쉽게 사용할 수 있다. 보통 groupby와 함께 자주 사용된다. axis가 0이면 칼럼별로, 1이면 행별로 함수를 적용한다.
explode : SQL의 Unnest와 같다. DataFrame 내에 List 또는 Series로 저장된 요소들을 분해하여 풀어내는 데 사용된다. 길이가 같은 여러 칼럼의 list를 한 번에 풀 수 있는 게 매우 유용하다. (spark에선 이 기능이 직관적이지 않다.)
if __name__ == '__main__':
titanic_data = sns.load_dataset("titanic")
group_titanic_data = titanic_data.groupby('sex')
# group by를 통해 각 age 요소를 list 형태로 만듬
group_data = group_titanic_data['age'].agg(list).reset_index()
print(group_data)
print(group_data.explode('age'))
나는 업무 중에 Oracle Database(이하, Oracle)를 직접적으로 사용하지는 않는다. 간접적으로나마 Oracle을 사용 중인데, 그마저도 이젠 더 사용하지 않게 될 것 같다. 그럼에도 Oracle에 대해 알아보기로 결심한 건, Oracle의 내부 동작이 굉장히 복잡하고, 체계적이기 때문에 이번 기회에 공부해 놓으면, 다른 DB나 데이터 처리 동작 관련하여 더 잘 이해할 수 있게 될 것 같기 때문이다. 전문가가 아니라, Oracle의 내부 동작은 거의 모르고, Oracle 동작에 대한 별도의 서적을 찾지 못해, Release Note를 보면서 공부하기로 했다.
Introduction
[Database]
Database는 Application 들이 사용할 수 있게, information을 수집하고, 저장하고, 검색하기 위해 존재한다.
Database Management system(DBMS)은 위의 Database의 목적을 충족할 수 있도록 조정하는 소프트웨어이다.
[Relational Model]
1970년에 "A Relational Model of Data for Large Shared Data Banks"에서 E.F.Codd란 사람이 수학 집합론에서 찾아 하여 relational model 개념을 제안했고, 이것이 요즘 Database 들에서 채택되어 relational database의 형태로 사용 중이다.
relational model은 아래와 같은 특징을 갖는다.
Structure : Database에 저장 및 접근 가능하도록 잘 정의된 object
Operations : Application이 data를 잘 조작할 수 있도록 명확하게 정의된 action
Integrity rules : 무결성 규칙을 지키면서, Database 직접을 제어
[RDBMS]
Relational model을기반으로, relational database management system(RDBMS)가 만들어졌다. RDBMS는 데이터를 database 안에 넣고, 데이터를 저장하고, application이 조정 가능하도록 데이터를 검색할 수 있도록 한다.
RDBMS는 2개 type의 operation을 지원한다.
Logical Operation : Application이 필요한 내용을 지정하는 Operation. 즉, application에서 데이터를 요청하거나, 데이터베이스에 정보를 추가하는 경우
Physical Operation : RDBMS가 어떻게 작업을 수행할지를 결정하고, 그 작업을 수행하는 것. 즉, application이 요청한 작접을 처리하기 위해, 어떻게 데이터를 찾고, 어느 메모리에서 읽을 것인지 등등의 과정을 포함함. Application 단에서는 해당 작업을 신경 쓸 필요가 없어야 함.
Oracle Dtabase도 1977년 개발된 RDBMS이다.
Oracle 특징
[Schema 구조]
앞서 언급한 대로, RDBMS에서는 Logical Operation과 Physical Operation을 분리하기 위해, Logical Data Structure와 Physical Data Storage는 분리되어야 한다.
이를 위해, Oracle Database는 logical data structure들의 집합인 schema를 사용하고, 각 user들이 schema를 소유하는 방식을 사용한다.
Database는 다양한 schema를 지원하는데, 가장 중요한 것은 Table과 Index이다.
Table : Column과 Row로 이루어진 2D 형태의 데이터. user는 Table에 무결성 제약 조건을 지정할 수 있다.
Index : Data 검색을 빠르도록, row의 위치 정보를 일정 규칙 형태로 가지고 있는 것. Table마다 하나 이상의 index를 생성할 수 있다. Index는 logical 영역이기 때문에, 실제 데이터의 위치를 변경하지 않음. 따라서, Index를 지워도 다른 부분에 영향을 미치지 않음.
[Transaction 관리]
Oracle database는 여러 user가 사용할 수 있도록 설계되었다. 따라서, database는 여러 user의 요청을 다른 user들의 data에 영향을 미치지 않도록 유지하면서, 동시에 처리해야 한다.
이를 위해, Oracle에서는 Transaction의 개념을 사용한다. Transaction은 1개나 그 이상의 SQL 질의문에 대한 logical, atomic unit이다.
RDBMS는 이러한 transaction을 database에 모두 적용(commit)되거나, 취소(rollback) 되도록 할 수 있어야 한다. 즉, data 처리를 위한 여러 SQL들을 묶음으로 묶어, 데이터 무결성을 유지하는 것이다. 갑작스러운 장애로 인해, transaction 내에 존재하는 하나의 SQL이 수행되지 않는다면, database는 해당 trasaction에 속한 모든 SQL을 rollback 하여 데이터 무결성을 유지한다.
[Data 동시성]
Data 무결성을 유지하기 위해, DBMS는 데이터 동시성에 대한 처리가 필요하다.
Oracle에서는 OS나 C언어처럼,Lock의 개념을 사용하여 데이터 동시성을 제어한다.
Lock은 공유 리소스에 접근하는 transaction 간에 충돌을 방지하여, 데이터 무결성을 보장하면서 최대한의 동시 접근을 허용한다.
[Data 일관성]
Data 일관성은 database에 접근한 사용자가 data에 대해 일관된 내용을 볼 수 있어야 하는 것을 의미한다
Oracle database에서는 항상 statement-level의 read 일관성을 시행한다. 이것은 하나의 query가 반환하는 데이터가 단일 시간 시점에서 commit 되고, 일관성을 보장한다는 것을 의미한다.
Database는 tansaction 수준에서 read 일관성을 제공한다. 이 경우에는 transaction 내의 문장이 동일한 시간 시점에서 data를 볼 수 있다.
→ Sub Query를 이용한 SQL이 처리에서 각기 다른 sub query에서 동일 데이터를 조회했을 때, 실제 physical 단위의 수행 시점에 따라 data가 달라지지는 않는다는 것을 의미한다.
Oracle Database 아키텍처
database server가 information 관리에 키다.
일반적으로 server는 위에서 언급한 Oracle 특징인, 여러 사용자가 동시에 동일 데이터에 안정적으로 접근할 수 있도록 대량의 데이터를 관리해야 한다.
database server는 또한, 보안적인 관점과 failure recovery에 대해 효과적인 solution을 제공해야 한다.
[Database and Instance]
Oracle Database Server는 하나의 database와 한 개 이상의 database instance로 구성된다.
일반적으로 database와 instance는 밀접하게 연관되어 있어, Oralce database는 둘을 포함하는 말이지만, 엄격하게 구별하면 다음과 같다.
Database : Database는 data 저장을 목적으로 disk 상에 위치한 file들의 집합을 의미한다. 이 file들은 실제로 data를 저장한다. database file은 database와 독립적으로 존재할 수 있다.
Database instance : Database file을 관리하는 역할을 한다. Instance는 공유 메모리 영역인 system global area(SGA)와 background process들을 포함한다. instance는 datavbase file과 독립적으로 존재한다.
Oracle Database와 instance의 구조도는 아래와 같다.
Client의 요청에 의해, SQL과 Transactuion을 처리하는 역할을 하는 Server Process가 존재하는데. 이 sever process가 사용하는 메모리 공간이 program global area(PGA)이다.
엄격하게 구분하자면, Oracle database의 Physical 구조와 Logical 구조를 어떻게 연결하느냐에 따라 2가지 아키텍처가 존재하는데, 각가 Multitenant Architecture과 Sharding Architecture이다.
[Multitenant Architecture]
Multitenant Architecture는 Oracle database가 하나의 physical database에서 다중 테넌트 공유할 수 있도록 하는 것이다.
※ 테넌트란? : Database 시스템 내에서 독립적으로 관리되는 하나의 사용자 또는 Application을 나타냄. 다중 테넌트에서는 여러 테넌트가 하나의 Database 시스템을 공유하지만, 각 테넌트는 자체 데이터 및 application을 logical 하게 격리하여 사용함.
이 아키텍처의 가장 큰 장점은 장점은 관리 machine 수가 줄어든다는 것이다. 장점을 세부적으로 보면 다음과 같다.
하드웨어 비용 감소 : 여러 대 physical server에서 운영되는 여러 database를 단일 machine의 단일 database로 통합 가능
Data와 Code 이동이 쉬움 : 단일 machine에 존재하기 때문에, 데이터 마이그레이션에 추가적인 network I/O 등이 들지 않는다.
Physical Database 관리 편이 : 관리 machine의 수가 줄어, 관리가 용이하다.
Data와 Code를 분리
권한과 역할 분리
[Sharding Architecture]
Oracle Sharding은 여러 Oracle database들에 수평적인 분할을 통해 database를 scaling 하는 방법이다. Application 단에서는 이 database pool을 하나의 logical database로 인식한다.
이 아키텍처의 장점은 선형 확장과 장애 격리이다.
Sharding 아키텍처에서 각 database는 고유한 server에 호스팅 되며(physical 분리) 각 database는 다른 machine이다. 이때 각 database를 shard라 부린다.
수평 분할 시, database 테이블을 여러 shard에 각각 나눠 보관하는데, 이러한 방식으로 분할된 table은 shared table이라고 부른다.
Oracle Database Storage 구조
Database 저장 구조는 Dataase를 Physical 및 Logical 관점에서 고려할 수 있다.
Physical 저장소와 Logical 저장소는 분리되어 있기 때문에, Pysical 저장소를 관리할 때, Logical 저장소 구조에 영향을 미치지 않고 관리 가능하다.
[Physical Storage 구조]
Physical database 구조는 data를 저장하기 위한 file들이다.
CREATE DATABASE를 입력하는 순간 아래의 file들이 생성된다.
Data File : 모든 Oracle Database는 database의 data를 가지고 있는 하나 이상의 physical data file들을 포함한다. Logical Database 구조는 Data File 형태로 저장된다.
Control File : 모든 Oracle Dtabase는 control file을 가지고 있다. control file은 database의 physical 구조를 정의하는, database name 등이나 위치정보 같은 metadata를 포함한다.
Online redo log file: databse에 발생하는 모든 변경 사항을 기록하는 File이다. Transaction Log 기록 등을 보관한다.
Parameter File : Database server의 설정과 동작을 제어하는 데 사용한다.
Networking File : Database server와 client 간 통신을 구성하는 데 사용한다.
Backup File : Database 복구를 지원하기 위해 사용된다. 일정 간격으로 Database의 복사본을 저장한다.
File에는 Offline File과 Online File이 있는데, 두 용어는 File의 상태를 나타낸다.
Online File : database가 활성화되어 잇고, 접근 가능한 상태에서 사용되는 File. Database의 일부로 사용되고, Application이 Server와 상호작용하고 Data를 읽고 쓸 수 있는 파일. Database Table, Index, View, Online Redo log File 등이 해당됨.
Offline File : Database가 사용 중이 아니거나, 접근 불가능한 상태에서 사용되는 File. 주로 Backup, 복구, Data 이동, File 관리 등의 작업을 수행할 때, Database File을 일시적으로 Offline 상태로 전환할 때 사용된다. Backup File, Archived Redo log file 들 중, 이미 사용된 것은 Offline File에 해당됨.
[Logical Storage 구조]
Logical Storage 구조는 Oracle Database가 disk space를 잘 사용할 수 있도록 control 한다.
Logical Storage 구조는 다음을 포함한다.
Data Block :가장 작은 저장 단위. 하나의 Datablock은 Disk 상의 특정 바이트 개의 영역으로 할당됨.
Extent : 논리적으로 연속적인 Data Block의 특정 개수. 한 번의 할당으로 얻은 것으로 특정 유형의; 정보를 저장하는 데 사용됨. Data의 물리적, 저장 관리를 위한 단위로 사용됨.
Segments : user object, undo data, 임시 data 등을 할당하기 위한 extent들의 집합
Tablespaces : Database를 Logical Storage 단위로 분할한 것을 의미함. Tablespace는 segment의 logical container로, 각 tablespace는 적어도 하나의 data file로 구성됨. Tablespace를 사용하여, data를 조직하고, segment를 배치하여 database의 성능과 관리 최적화가 가능함.
최근 LLM을 이용해 instruction-following data를 생성하여 instruction 능력을 향상하는 연구들이 많이 이뤄지고 있다. 하지만, multimodal 분야에서는 아직 많이 연구되지 않았다.
이 논문에서는 언어만을 사용하는 GPT-4를 이용하여 multimodal language-image instruction-following 데이터를 만든다.
이를 이용한 instruction tuning을 통해, 이 논문에서는 LLaVa(Large Language and Vision Assistant)라는 vision encoder와 LLM을 연결한 end-to-end model을 소개한다.
초기 실험에서 LLaVa는 multimodal chat 능력에서 multimodal GPT-4에 대한 85.1%의 상대적 score를 보였다.
Science-QA로 finetuning 하였을 때, LLaVA와 GPT-4의 시너지는 새로운 SOTA인 92.53%의 정확도를 보였다.
Introduction
인간은 vision과 language 등 다양한 채널을 통해 세상을 인지한다.
LLM은 최근 다양한 분야에서 좋은 성능을 내고 있지만, text만을 다룬다.
이 논문에서는 "visual instruction-tuning"을 소개한다. 먼저, instruction-tuning을 multimodal space로 확장하여, vision 분야의 general-purpose를 위한 초석을 쌓는다.
논문의 Contribution은 다음과 같다.
Multimodal instruction-following data : 가장 큰 제약은 vision-language instruction-following data가 적다는 것이다. 논문에서는 data 생성 방법을 소개하고, ChatGPT나 GPT-4를 통해 image-text pair들을 적절한 instruction-following format으로 변환하는 파이프라인을 소개한다.
Large multimodal models : open-set visual encoder인 CLIP과 LLaMA를 연결하고, 앞서 만든 데이터를 통해, 이 둘을 end-to-end로 학습한다. 특히, GPT-4로 진행했을 때, Science QA multimodal reasoning dataset에 대해 SOTA 성능을 보였다.
Open_Source : Code와 model checkpoint 등을 모두 공개하였다.
GPT를 이용한 Visual Instruction Data 생성
multimodal instruction following 데이터는 부족한데, 최근 GPT model의 text-annotation task에서 성공에서 영감을 받아, ChatGPT/GPT-4를 이용한 multimodal instruction-following 데이터를 생성하는 방법을 제시한다.
Image와 그에 대한 Caption이 있는 데이터가 있을 때, 가장 간단한 방법은 GPT-4를 통해 Caption을 해석하고, 그에 대한 Question들을 가지고 질문하는 것이다.
→ 쉽게 생각하면, 아래와 같이 이미지 묘사를 넣고 퀴즈를 내는 것이다.
하지만, 이렇게 하면 만들어지는 데이터의 다양성이 떨어지고, 깊이 있는 추론을 포함하는 instruction과 response를 만들지 못한다. 이 현상을 완화하기 위해, GPT에 이미지를 묘사할 수 있는 2개 type의 데이터를 제공한 뒤 질문을 진행한다.
1. Image를 묘사하는 Caption 데이터
2. 이미지 내, 물체들에 대한 Bounding Box 데이터(각 box에는 해당 물체의 종류와 위치 정보를 담고 있다. 즉, GPT가 이미지를 input으로 사용하지는 못하지만, text 형태로 이미지 내 물체들의 위치 정보와 종류를 인식할 수 있는 것이다.)
COCO image를 이용하여 3개 타입의 instruction-following dataㄹ르 생성하였다. 데이터 생성 시에는 인간이 만든 prompt를 이용하여, 데이터를 얻었다. (총 158K)
Coversation : 인간과 GPT간 이미지에 대한 Q&A 대화를 생성했다. GPT가 이미지를 보고 있는 것처럼 질문을 했다. 질문에는 물체 종류, 물체 개수, 물체의 행동, 물체의 위치, 물체 사이의 상대적 위치 등이 포함된다. (58K)
Detailed Description : Image에 대한 자세한 설명을 요청하는 질문들을 만들어 요청하였다.(23K)
Complex Reasoning : 앞선 2개의 데이터들은 이미지 자체에 집중하지만, GPT에 깊은 추론을 요하는 질문들을 진행했다. (77K)
초기에는 ChatGPT와 GPT-4를 모두 활용했으나, GPT-4의 quality가 더 좋아서 GPT-4만 사용했다.
Visual Instruction Tuning
[구조]
Model의 주된 구조적 목표는 visual model과 pre-trained LLM을 효과적으로 결합하는 것이다.
LLM으로는 LLaMA를 사용하였다.
우선 이미지(Xv)를 CLIP visual encoder에 넣어서, Feature(Zv)를 뽑고, training 가능한 Linear Layer를 거쳐, Language model의 embedding space와 동일한 dimension의 language embedding token(Hv)를 만든다.
논문에서는 가볍고 간단한 구조를 생각해서 Linear layer를 사용했다고 하는데, 다른 여러 방식을 사용한 embedding은 future work로 남기겠다고 한다.
[학습]
각 이미지마다 T개의 turn이 있는 conversation data를 생성하였고(첫 번째 Question, 첫 번째 Answer, 두 번째 Question,... 이런 형식으로), 이들을 sequence형태로 연결하였다.
이때, 첫번째 turn의 경우에는 이미지 Feature를 (1) 질문 앞에 두는 경우, (2) 질문 뒤에 두는 경우를 random 하게 결정한다.
학습을 위해서는 기존 LLM의 방식처럼 이전 token들을 통해, 현재 token을 추정하는 방식의 학습을 진행한다. 따라서, 학습을 위한 Loss도 기존 LLM과 비슷하다. 수식에 보면, 현재 token 추정 시, 단순 과거 Q&A 데이터뿐만 아니라 이미지 Feature도 함께 사용한다는 것이 기존 LLLM과의 차이이다.
LLaVa model 학습에는 2가지 stage가 존재한다.
1. Pre-training for Feature Alignment
LLaVa의 구조에서 이미지를 word embedding으로 변환할 때, Linear Layer를 사용하는데, 해당 논문의 관심사가 multi-modal Language Model인 만큼, 이미지를 얼마나 잘 word embedding으로 변환하냐가 매우 중요하다.
이를 위해, CC3M 이미지-텍스트 데이터를 single turn conversation 형식으로 만들어, LLM과 visual encoder의 파라미터를 freeze 한 뒤, Linear layer만 학습을 진행했다.
아마, visual encoder와 LLM은 pre-trained model을 사용했지만, Linear layer는 별도의 학습된 weight를 사용한 게 아니어서 해당 stage가 있는 것으로 생각된다.
2. Fine-tuning End-to-End
visual encoder는 계속 freeze 시켜놓고, LLM과 Linear layer를 같이 학습한다.
학습에는 앞서 위에서 언급했던 3개 type의 데이터 (Conversation, Detailed Description, Complex Reasoning)와 저자들이 추가적으로 고안한 ScienceQA 데이터를 이용하였다. ScienceQA 데이터는 과학적 질문에 대한 multimodal 데이터 셋이다. ScienceQA는 single turn conversation 형식으로 사용했다.
Experiments
[multi modal chatbot]
LLaVa의 이미지 이해력과 conversation 능력을 보기 위해, Chatbot 데모를 고안했다. 우선, GPT-4 논문에 있는 Example들을 사용했다.
비교를 위해, prompt와 response는 multimodal GPT-4 논문에 존재하는 것을 사용하였고, 다른 모델과 결과도 해당 논문에서 인용했다. (multimodal GPT-4, BLIP-2, OpenFlamingo)
놀랍게도, LLaVA는 매우 적은 양의 multimodal 데이터로 학습했음에도 불구하고, 2개의 example에서 multimodal GPT-4와 거의 비슷한 수준의 reasoning 결과를 보임을 확인했다. 이때, 2개의 example들은 LLaVA의 학습에 사용된 이미지의 범위가 아님에도 (학습 시, 데이터를 외운 것이 아니다.) 장면을 이해하고, question에 대한 적절한 대답을 했다.
단순 example 비교 뿐 아니라, LLaVA의 성능을 측정하기 위해, GPT-4를 이용하여, ouput의 quality를 측정했다.
구체적으로 COCO validation에서 30개의 이미지를 무작위로 추출하여, 앞서 언급한 3개 종류의 question을 만들고, LLaVA가 이미지와 해당 question을 기반으로 answer를 내도록 했다. 그리고, question, ground-truth bounding box, captions을 이용하여 GPT-4에게 answer을 내도록 하여, reference를 생성했다. (즉, GPT-4 대비, LLaVA의 성능을 측정하겠다는 뜻)
그러고 나서, GPT-4를 이용하여 helpfulness, relevance, accuracy, detail 정도의 quality를 1~10까지 점수로 나타내게 했다. (점수가 높을수록 좋은 것)
가장 좋은 모델은 GPT-4의 85.1% 수준의 매우 좋은 성능을 보였다.
Reference
Liu, H., Li, C., Wu, Q., & Lee, Y. J. (2023). Visual instruction tuning.arXiv preprint arXiv:2304.08485.
총평
Open source로 모든 것을 공개했다는 점이 우선 제일 놀라웠다.
GPT-4를 이용해서 데이터를 만드는 방식에서 bounding box를 텍스트 형식으로 제공하는 부분에서 어떻게 이런 생각을 할 수 있지라는 생각이 들었다.
multimodal LLM을 Q&A에 적용하였는데, Open source로 소스가 제공된 만큼 objective function이라던지, 데이터의 방식에서 금방 새로운 아이디어가 적용되어, 더 다양한 부분에 사용될 수 있지 않을까 싶다.
ViT의 등장 이후, CNN 처럼 ViT를 깊게 쌓기 위한 방법을 제시한 논문으로, ImageNet classification에서 기존 CNN 기반의 SOTA를 넘어서는 성능을 보였다고 한다.
Abstract
이 논문에서는 Image Classification에서 Layer가 깊어질수록 좋은 성능을 내는 CNN과 달리, ViT의 performance는 layer가 깊어질수록 성능이 더 빨리 saturate 되는 것을 발견했다.
이것은 transformer의 사이즈가 커지면서, attention map들이 점점 비슷한 형태를 띠는 "attention collapse issue" 때문이다.
이것은 ViT에 deeper layer들을 적용하기 위해서 self-attention 방식이 효과적이지 못함을 보여준다.
이러한 결과로, 이 논문에서는 간단하면서 효과적인 "Re-attention"이라는 attention map을 재생성해서, layer들의 다양성을 향상하는 방법을 제안한다.
이 방법은 기존 ViT를 조금만 수정하더라도 좋은 성능을 보인다.
Introduction
[배경]
CNN 방식은 Image Classification 학습 시에 더 깊은 layer를 사용할 수록, 더 풍부하고, 복잡한 형태의 representations를 학습하기 때문에, layer를 어떻게 더 효과적으로 쌓는지에 대해서 연구가 많이 되고 있다.
ViT가 좋은 성능을 보이면서, 자연스럽게 ViT도 model을 깊게 쌓으면 CNN 처럼 성능이 좋아질 것인가? 에 대한 관심이 생기고 있다.
[ViT 깊이 실험]
이를 검증하기 위해, 각기 다른 block number의 ViT를 가지고 ImageNet classification 성능을 비교해보았다.
실험 결과, ViT 깊이가 커질수록 성능이 좋아지지 않는다는 것을 발견했고, 심지어 성능이 떨어지기도 하였다.
실험적으로 이유를 확인해 보았을 때, ViT의 깊이가 깊어지면, 특정 layer 이상에서 attention map이 비슷해지는 현상을 발생하였다. (즉, attention의 역할을 제대로 수행하지 못함.) 더 깊어지면, 아예 모든 값들이 같아짐을 확인했다.
즉, ViT의 깊이가 깊어지면 self-attention 방식이 더 이상 working 하지 않음을 의미한다.
[방법 제안]
이러한 "attention collapse" 문제를 해결하고, ViT를 효과적으로 scale 하기 위해, 이 논문에서는 간단하지만 효과적인 self-attention 방식인, "Re-Attention" 방식을 소개한다.
Re-Attention은 Multi-Head self-attention 구조를 따르고, 다른 attention head들의 information을 이용하여, 좋은 성능을 내게 한다.
이 방식을 도입하면, 별도의 augmentation이나 regularization 추가 없이도, 더 깊은 block의 ViT를 효과적으로 학습하여, 성능향상을 확인할 수 있다. (SOTA)
Attention Collapse
ViT에서 Transformer block 개수를 다르게 ImageNet Classification을 수행해 보았을 때, 기존 ViT에서는 block 개수가 커질수록 Improvement가 점점 감소되고, 심지어 성능이 줄어드는 것을 확인할 수 있다.
이 이유를 CNN에는 없는 self-attention 때문으로 지목했는데, model의 깊이에 따른 attention을 확인해 보았다.
Transformer block이 32개 일 때, 각 block layer에서 다른 layer (인접한 k개의 layer) 와의 유사도를 구해본 결과 17번째 block을 넘어서는 순간 90% 이상의 거의 비슷한 output을 냄을 확인할 수 있다. 즉, 이후의 attention map들이 거의 비슷한 형태를 보이고, MHSA가 MLP를 악화시킬 수 있다는 것을 의미한다.
Re-Attention
Attention Collapse를 해결하기 위해, 두 가지 방법을 제안한다. 첫 번째는, self-attention 연산을 위한 hidden dimension의 수를 늘리는 것이고, 두 번째는, re-attention 방식이다.
[Self-Attention in Higher Diemnsion Space]
Self-Attention이 비슷해지는 것을 방지하기 위해, Dimension size를 늘리면, 더 많은 정보를 가지고 있게 되고, attention map이 더 다양해지고, 비슷해지지 않게 된다.
아래 그림과 표를 보면, 12 Block의 ViT에서 Dimension size를 늘렸을 때, 비슷한 Block들의 수가 줄어들면서, ImageNet 성능이 향상됨을 확인할 수 있다.
하지만, 이러한 방식은 성능적 한계가 있다는 점과, Parameter 숫자가 매우 늘었다는 단점이 있다.
[Re-Attention]
다른 Transformer block 사이의 attnention map은 매우 비슷하지만, 동일 transformer block에서 다른 head 사이에서는 similarity가 작음을 확인했다.
같은 attention layer의 다른 head들은 각기 다른 aspect에 집중하고 있기 때문이다.
이 결과를 바탕으로, cross-head communication을 위해, attention map들을 재생성하는 방식을 제안한다.
이를 위해, learnable parameter인 transformation matrix(H X H)를 개념을 도입하여, self-attention map의 head dimension 방향으로 곱해준다. 그 후 layer normalization을 진행하여 "Re-Attention" 구성한다.
Re-Attention의 장점은 크게 2가지이다. 첫 번째는 Re-Attention map은 다른 attention head 들 사이에 정보를 교환할 수 있어, 상호 보완이 가능하고, attention map의 다양성을 늘린다. 또한, Re-Attention map은 효과적이면서 간단하다.
→ 쉽게 말하면, self-attention을 진행할 때, 기존처럼 단순 softmax 값으로 값 참조를 하는 것이 아닌, 별도의 learnable parameter로 다양성을 향상하자는 개념임.
Experiments
실험에서는 attention collapse 문제에 대한 설명을 위한 실험을 진행한다. 추가적으로 Re-attention의 장점에 대한 추가적인 실험을 진행한다. (생략)
논문에서 주장한 것처럼 Re-Attention을 사용하였을 때, 비슷한 attention 패턴이 매우 줄고, 이로 인해 image classification에서 기존 ViT보다 더 높은 성능을 보인다. (ImageNet)
Image Classification SOTA 모델들과 비교해 보았을 때도, 더 좋은 성능을 보인다.
Reference
ZHOU, Daquan, et al. Deepvit: Towards deeper vision transformer.arXiv preprint arXiv:2103.11886, 2021.
논문 총평
내 식견이 넓지 않은 까닭인지 저자들이 주장하는 Attention Collapse 현상과 Re-Attention 논리 구조를 100% 이해하진 못했다.
다만, CNN SOTA와 비견할 정도로 높은 성능을 보인다는 점에서 좋은 연구였다고 생각한다.