API 요청을 관리하는 데에는 여러 이유가 있습니다. 그 중에서도 토큰 버킷을 사용하는 이유는 다음과 같습니다:
트래픽 제어: 토큰 버킷을 사용하면 API로 들어오는 트래픽을 제어할 수 있습니다. 이를 통해 서버에 과도한 부하가 걸리는 것을 방지하고, 서비스의 안정성을 유지할 수 있습니다.
사용량 제한: 토큰 버킷을 이용하면 API를 사용하는 클라이언트의 요청 수를 제한할 수 있습니다. 이는 과도한 사용량으로 인한 서버 과부하를 방지하고, 공정한 서비스 이용을 보장합니다.
TokenBucket 만들기
토큰 버킷 패턴은 고정된 속도로 토큰을 생성하고, 요청이 들어올 때마다 토큰을 소비하여 일정량의 토큰을 가지고 있는지 확인하여 처리하는 방식입니다. 알고리즘은 아래와 같습니다.
TokenBucket은 버켓(바구니)가 있다고 가정합니다.
요청이 들어오면, 버켓에서 1개를 소비해서 그 다음의 로직을 진행시킵니다. 요청이 들어왔을 때 버킷에 토큰이 없으면 drop합니다.
고정된 속도(r)로 버킷에 토큰채웁니다.
TokenBucket 을 middle ware 에 추가
- FastAPI의 starlette을 베이스로 작성되어있어서, 이 미들웨어를 서브클레싱해서 손쉽게 token bucket을 사용할 수 있습니다.
- starlette.middleware.base.BaseHTTPMiddleware을 초기화시 app을 받게되어있고 이 베이스클레스는 dispatch라는 함수를 구현하게어 있습니다.
class BaseHTTPMiddleware:
...
async def dispatch(
self, request: Request, call_next: RequestResponseEndpoint
) -> Response:
raise NotImplementedError() # pragma: no cover
dispatch을 함수를 아래와 같이 구현해서 bucket.consume_token()이라는 함수가 bool으로 false or True을 구현하게 함에따라, 예외를 일으킬것인지 다음 로직을 실행시킬것인지를 결정하게 작성합니다.
위의 구현을 위해 아래와 같이 tokenbucket을 정의합니다.
초기화시, 버킷의 토큰 최대 저장량(max_len)과 토큰을 채우는 시간(r, refill_rate)을 지정하여 아래와 같이 작성합니다.
실행
- 다음과 같이 python3으로 fastAPI을 구동시킵니다.
(misc) (base) heon@gpusvr03:~/repositories/misc/tokenbucket_ex$ python3 app.py
INFO: Started server process [4121818]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:30000 (Press CTRL+C to quit)
그 후 요청 15개를 보내봅니다. 아래와 같이 10개를 토큰을 다 소비한경우 5개의 요청이 예외처리됩니다.
$y'=r(x \cdot sin(\theta) + y \cdot cos(\theta))$ (x을 앞으로 정렬)
위 식에서 r이 1인 경우는 단순히 선형결합형태이므로, 선형결합 형태로 나타낼 수 있는 메트릭스로 표현 할 수 있습니다.
$\begin{bmatrix} x' \\ y' \end{bmatrix}= \begin{bmatrix} cos\theta & -sin\theta \\ sin\theta & cos\theta \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix}$
Python implementation
파이썬으로 위의 방법을 구하면 아래와 같이 작성할 수 있습니다. 주의할 것은 여기서 θ는 각도이며, 여기서는 30도를 라디안 단위로 변환하여 사용해야 합니다.
import numpy as np
# 각도를 라디안으로 변환
theta = np.deg2rad(30)
# 회전 행렬 생성
R = np.array([[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]])
# G 벡터 생성 (임의의 값으로 설정)
G = np.array([1, 0]) # 예를 들어, G = (1, 0)으로 설정
# G' 벡터 계산
G_prime = np.dot(R, G)
print("G' 벡터:", G_prime)
print("회전 행렬:")
print(R)
메인루틴(Main routine): 메인루틴은 보통 프로그램의 시작점이며, 프로그램의 주 흐름을 담당합니다. 메인루틴은 일련의 작업을 수행하고 다른 서브루틴이나 코루틴을 호출할 수 있습니다. 프로그램이 시작되는 메인코드라고 생각하면 됩니다.
서브루틴(subroutine): 메인루틴에서 호출되는 함수, 또는 서브루틴에서 호출되는 함수들을 의미합니다. 즉, 다른 루틴에서 호출되는 경우를 의미합니다.
코루틴(Coroutine): 메인루틴에서 호출되지만, 코루틴은 실행되는 도중에, 일시중단되어 다시 메인루틴으로 전환되었다가 다시 코루틴으러 전환될 수 있는 "제어흐름"이 가능한 루틴을 의미합니다.
코루틴, 메인루틴, 서브루틴은 프로그래밍에서 중요한 개념이며, 이를 이해하는 것은 프로그래밍의 성능을 향상시키는 데 도움이 됩니다. 각각의 이유를 자세히 설명하겠습니다.
프로그램의 구조 이해(메인루틴/서브루틴의 이해):
메인루틴은 프로그램의 주요 흐름을 담당하며, 프로그램의 시작점입니다. 이를 이해함으로써 프로그램이 어떻게 실행되는지 전반적으로 이해할 수 있습니다.
서브루틴은 재사용 가능한 작은 조각의 코드를 나타냅니다. 이를 통해 코드를 더 모듈화하고 유지 보수하기 쉽게 만들 수 있습니다.
코드의 재사용과 모듈화(서브루틴/코루틴의 이해):
서브루틴을 사용하면 비슷한 작업을 수행하는 코드를 여러 곳에서 재사용할 수 있습니다. 이로써 코드의 중복을 피하고 유지보수성을 향상시킬 수 있습니다.
코루틴은 서브루틴과 유사하지만, 일시 중단되고 다시 시작될 수 있기 때문에 작업의 흐름을 제어하는 데 유용합니다. 이를 통해 비동기 작업이나 상태 유지가 필요한 작업을 효율적으로 처리할 수 있습니다.
동시성과 비동기 프로그래밍(코루틴의 의해):
코루틴은 동시성과 비동기 프로그래밍에서 중요한 역할을 합니다. 코루틴을 이용하면 여러 작업을 동시에 실행하고 작업 간에 상태를 공유할 수 있습니다. 이를 통해 복잡한 동작을 효과적으로 조정하고 성능을 향상시킬 수 있습니다.
효율적인 코드 실행:
코루틴은 작업의 일시 중단과 다시 시작을 가능하게 함으로써 I/O 바운드 작업을 효율적으로 처리할 수 있습니다. 이를 통해 대기 시간이 긴 작업을 실행하는 동안 다른 작업을 수행할 수 있으며, 시스템 자원을 더 효율적으로 활용할 수 있습니다.
이러한 이유들로 인해 코루틴, 메인루틴, 서브루틴을 이해하는 것은 프로그래밍에서 중요한 역할을 합니다. 이러한 개념을 잘 숙지하고 활용함으로써 보다 효율적이고 유연한 코드를 작성할 수 있습니다.
메인루틴(Main routine)
메인루틴은 프로그램의 주요 흐름을 담당하는 부분으로, 일반적으로 프로그램의 시작점이며 종료 지점이 될 수도 있습니다. 메인루틴은 전체 프로그램의 실행 흐름을 정의하고 조절하며, 다른 서브루틴이나 함수를 호출하여 작업을 수행하고 그 결과를 처리합니다.
메인루틴은 보통 다음과 같은 역할을 수행합니다:
프로그램의 시작점: 메인루틴은 일반적으로 프로그램이 실행되면 가장 먼저 실행되는 부분입니다. 프로그램의 시작 지점을 정의하고 초기화 작업을 수행합니다.
주요 작업 수행: 메인루틴은 프로그램이 수행해야 하는 주요 작업을 정의하고 호출합니다. 이는 사용자의 입력을 받는, 데이터를 처리하는, 다른 모듈이나 서브루틴을 호출하는 등의 작업을 포함할 수 있습니다.
서브루틴 호출: 메인루틴은 필요에 따라 다른 서브루틴이나 함수를 호출하여 작업을 분할하고 모듈화합니다. 이를 통해 코드를 재사용하고 유지보수성을 향상시킬 수 있습니다.
프로그램의 종료: 메인루틴은 프로그램이 종료되는 지점을 나타냅니다. 모든 주요 작업이 완료된 후 정리 작업을 수행하고, 필요에 따라 리소스를 해제하고 프로그램을 종료합니다.
간단한 예시로 메인루틴을 파이썬코드로 구현해보겠습니다. 아래와 같이 input으로 이름, 나이를 입력받아 출력하는 프로그램입니다. 다른 모듈에서 호출되는 것 없이 main()함수가 호출되고 종료됩니다. 이 main함수에서 프로그램이 시작되고, 종료되기에, "프로그램의 흐름이 정의"되었다라고 말할 수 있습니다. 즉, main()이 메인루틴이 됩니다.
def main():
print("프로그램 시작")
# 사용자 입력 받기
name = input("이름을 입력하세요: ")
age = int(input("나이를 입력하세요: "))
# 정보 출력
print("입력된 정보:")
print("이름:", name)
print("나이:", age)
# 종료 메시지 출력
print("프로그램 종료")
if __name__ == "__main__":
main()
서브루틴(Subroutine): 메인루틴이나 서브루틴에서 호출되는 함수
서브루틴은 다른 프로그램에서 호출되는 (=피호출)되는 함수를 의미합니다. 예는 아래와 같습니다. 위의 예시와 같이 main()이 호출되고 종료되기에 메인루틴은 main()이라는 함수입니다. 그리고 이 main()내에서 calculate_sum()이 호출되고 종료됩니다. 즉, main인 메인루틴에 의해서 호출되는 calculate_sum이 서브루틴입니다.
def main():
print("메인루틴 시작")
result = calculate_sum(3, 4)
print("결과:", result)
print("메인루틴 종료")
def calculate_sum(a, b): # 서브루틴
return a + b
if __name__ == "__main__":
main()
시간에 흐름에 따라, 두 루틴을 시각화하면 아래와 같습니다. 메인루틴이 서브루틴을 호출하고, 그 동안 메인루틴은 서브루틴이 일을 다 처리할 때까지, 잠시 작업대기를 합니다. 그리고, 서브루틴의 일이 끝나면, 메인루틴으로 다시 작업이 넘어가서 메인루틴의 테스크가 종료됩니다.
코루틴(Coroutine): "일시중지 후 재시작"이 가능한 함수
코루틴은 일시 중단되고 다시 시작될 수 있는 함수로, 일반적인 함수와 다르게 실행 중에 일시 중단되어 값을 주고 받을 수 있습니다. 이를 통해 비동기적인 작업이나 상태를 유지하는 작업을 효율적으로 처리할 수 있습니다. 특히, 코루틴은 서브루틴과 다르게 메인루틴으로 작업상태가 넘어가도, 코루틴내 내용을 잃지않고 유지할 수 있습니다. 그리고, 이런 메인루틴->코루틴, 코루틴->메인루틴의 작업상태를 넘겨 실행하는 단계를 "진입점"이라고합니다. 이런 진입점은 서브루틴은 "메인루틴->서브루틴"의 1개의 진입점을 가지지만, 코루틴은 여러 개의 진입점을 가집니다.
코루틴을 사용하려면, 코루틴의 성격을 정리해보겠습니다.
코루틴은 generator의 특별한 형태입니다. (generator도 데이터를 yield와 함께 하나씩 얻어올 수 있고, 그 다음 차례가 그대로 유지되기에 코루틴으로 취급합니다.)
코루틴의 시작: 코루틴은 보통 while True 구문을 이용해서 계속해서 일반적으로 코루틴의 시작을 알립니다.
코루틴에 값보내기: 코루틴에 값을 보내는 방법은 코루틴객체.send(객체)로 외부의 값을 코루틴으로 전달합니다.
코루틴 내에서 값 얻기: 이 값을 가져오기 위해서, `yield` 키워드를 이용하여 값을 얻어옵니다.
아래의 예시에서는 `searcher()`가 코루틴 함수입니다. 하나 씩 내용을 확인해보겠습니다.
while구문으로 코루틴을 계속 실행하는 상태로 둡니다.
while 구문이 이하가 도는 경우, yield구문에서 값이 올 때까지 기다립니다.
next()가 호출되어야하는 이유는 제너레티너나 코루틴을 실행하고 첫번째 값을 얻기위해 호출합니다. 코루틴은 yield 표현식이 보이면 일시중단됩니다.
코루틴객체.send(객체)의 구문으로 코루틴에 값을 보내면, 그 보내는것과 동시에 코루틴이 실행됩니다. 다시 while True 구문에 의해서 yield 키워드가 등장하고, 코루틴이 일시중지됩니다. 이와 동시에 다시 메인루틴으로 작업이 옮겨갑니다.
def searcher():
"""
검색어를 받아들이고 출력하는 코루틴
"""
while True:
query = yield # 일시 중단하고 외부로부터 값을 받음
print("검색어:", query)
# 코루틴 생성
search = searcher()
next(search) # 코루틴 시작
# 코루틴에 값 전달
search.send("파이썬")
search.send("데이터 분석")
또, 다른 예시입니다. 코루틴은 일시정지를 함으로, 코루틴 내의 그 내용을 잃어버리지 않고 유지할 수 있다고 했습니다. 코루틴에서 average, count, total라는 변수를 계속 코루틴에서 들고 있고, send로 더할 때 마다, 코루틴의 변수가 변경되는 것을 확인할 수 있습니다.
def running_average():
"""
입력된 숫자들의 평균을 계산하는 코루틴
"""
total = 0
count = 0
average = None
while True:
value = yield average # 새로운 값 받기
total += value
count += 1
average = total / count
# 코루틴 생성
calc_average = running_average()
next(calc_average) # 코루틴 시작
# 코루틴에 숫자 전달하고 평균 계산
print("평균:", calc_average.send(10)) # 출력: 10.0
print("평균:", calc_average.send(20)) # 출력: 15.0
print("평균:", calc_average.send(30)) # 출력: 20.0
코루틴을 이용한 async, await의 활용
asyncio을 사용해서 비동기적으로 작업을 수행할 수 있습니다. asyncio을 사용하기위해 대표적인 키워드 2개를 살펴보겠습니다.
async 키워드: async def로 함수를 정의하면 해당 함수는 코루틴 함수로 선언됩니다. 이는 함수가 비동기적으로 실행될 수 있음을 나타냅니다. 아래와 같이, async def 로 정의된 함수는 인자를 담아 호출하면, coroutine object로 정의됨을 알 수 있습니다.
await 키워드: await 키워드는 비동기 작업이 완료될 때까지 대기하고, 해당 작업의 결과를 반환합니다. await 있는 키워드가 실행되면, 이 라인의 구문이 실행되는 동안, "비동기적으로 메인루틴으로 잡이 넘어가고", "완료되면 메인루틴의 작업에서 다시 코루틴으로 작업이 넘어옵"니다. Q. "async함수를 정의하면 꼭 await구문을 써야하냐?"할수도있는데, 꼭 사용하지 않아도 됩니다만, 비동기작업을 안쓸꺼면 굳이 async로 정의할 필요는 없을 것 같습니다
전자장비에서 처리하는 컬러이미지는 기본 요소인 픽셀(Pixel)을 구성하고, 이 픽셀을 R, G, B 값을 갖습니다. RGB은 컬러가 아니라, 숫자들의 조합을 컬러로 매핑한 컬러코드에 불과합니다. 이 컬러코드를 어떤식으로 매핑했느냐가 RGB, CIEXYZ, CIELAB의 흐름이됩니다. 요약하면 아래와 같습니다.
CIELab: 색차(컬러-컬러)간의 차이를 균일하게 만든 표준색공간으로 등간격인 색공간입니다. L(명도, lightness), A*(red & green), B*(Yellow & Blue)로 구성됩니다.
사전지식
CIE (Commision Internationale de l'Eclairage): 국제조명기구
색도: 색의 정도를 의미합니다. 색은 밝기와 색도로 나누는데 흰색과 회색은 같은 색도이지만, 밝기만 차이납니다.
CIE 1931RGB color system
CIE 1931RGB color system은 국제조명기구가 인간의 시각에 기반한 컬러시스템을 만들었습니다. 이 컬러시스템 인간이 최초로 색을 정량적으로 표현한 것입니다.이 컬러시스템은 RGB컬러시스템과 전혀 관계가 없습니다. 단지 사람이 느끼기에, R, G, B 의 광량이 어느정도 합쳐지면 색이 되는지를 표시한것에 불과합니다. 이 실험을 "컬러 매칭 실험"이라고합니다. 또는 등색실험이라고 종종 번역됩니다.
1920년대에 W. 데이빗 라이트(Wright)와 존 길드(Guild)란 사람이 컬러매칭실험을 진행했습니다. 이 컬러매칭 실험은 단색광(Mocochromatic light)을 통해 특정 파장을 하나 빛추고, 실험광(R), 실험광(G), 실험광(B)의 광량을 조절해가면서 합성한후에, 단색광과 합성광이 색이 동일한지 외부관찰자한테 관찰하여, 동일한지만 확인하는 것입니다. 그렇기에, CIE 1931 RGB color system에서 구분될 수 있는 색상은 인간이 실제로 구분할 수 있는 색상수와 동일하다고 종종 여겨집니다. 스크린의 한 쪽에서는 단색광을 비추고(reference color), 다른 한쪽은 관찰자가 R,G,B을 합성해서 단색광과 동일할 때까지 밝기를 조절했습니다. 이후에 CIE 에서 이를 표준으로 채택하면서, 컬러 매칭실험에서 얻어진 광량의 조합을 표시한것이 CIE 1931RGB입니다 [1].
컬러매칭실험에서는 하나 문제가 있었습니다. 예상과는 달리, 컬러매칭시에 R,G,B로 어떻게 하더라도 만들 수 없는 색이 있었습니다. 예를 들어, 특정 청록색깔은 R,G,B을 어떻게해서도 합성이 안되었습니다. 이경우에는 테스트 색깔인 청록에 R을 혼합하고, 청록+R을 테스트컬러로 두었습니다. 그리고, 나머지 G와 B의 조합으로 등색을 만들었습니다. 이렇게 RGB와의 혼합량에 의해 나타내는 컬러를 그래프로 나타낸 것을 "색 대응 함수"라고 합니다. 그리고, 이 R,G,B의 각각의 광량을 삼색 자극값(Tristriumulus value)라고합니다(또는 삼자극치...).
이 실험은 사람의 눈에 있는 빛의 수용기인 원추세포(Cone cells)를 기반으로 합니다. 이 원추세포는 3가지 종류(subtype)이 있고, 이 3가지 종류가 각각 다른파장을 인지하도록 되어있습니다. 단파장(Short)은 420nm~440nm, 중파장(M)은 530nm~540nm,이며, 장파장(long) 560nm~580nm입니다. 이 세가지 원추세포를 가중치를 두어 조절하면, 특정 파장길이의 색상을 인지할 수 있습니다. 즉, 어떤 색이든 이 삼원색의 조합으로 다 만들 수 있으니, 단색광 하나와의 비교로 광량을 기록하여 두면 되지 않겠느냐는 것입니다. 이 세가지의 광원의 광량을 "삼자극치(Tristimulus)"라고 합니다. ("삼", "자극", "치"라고 이해하면됩니다)
이 색 대응함수는 아래와 같습니다. X축은 단색광의 파장을 의미하고, 이 단색광의 파장을 재현하기위한 광량을 Y축에 그린 것입니다. Y축은 절대적인 밝기는 아니고, 상대적인 밝기로 색 대응함수 R,G,B의 아래 면적의합이 모두 1이 되도록 정규화한것입니다. 그래서, 각 RGB은 0에서 1값을 갖습니다. 이 RGB색공간은 컬러매칭을 통해 얻어지는 광량의 조합이기에, 전자장비에서 얻어진 RGB와는 다릅니다.
CIE 1931XYZ color system
컬러매칭시험에서 색 대응함수가 음수값을 보이는지점이 있어 ,색재현 등에서 단점으로 꼽혔습니다(그림 2). 그림2에서 r의 색대응함수에서 500nm 파장대가 0이하인 것을 볼수 있습니다. 즉, 이 마이너스 부분을 플러스로 변환한 것이 CIEXYZ입니다. 이 RGB 컬러 매칭함수의 마이너스 값을 없애기 위해서, Dean Judd 마이너스부분을 없앨수 있는 컬러 매칭 함수를 제안했다. 그래서, RGB매칭함수를 XYZ컬러매칭함수로 변환하는 함수가 아래와 같습니다.
XYZ 컬러 공간에서도 좌표는 각 컬러가 0에서 1사이 입니다.
이 XYZ 컬러 공간은 아래와 같이 특징을 가집니다.
CIE RGB색공간을 단순히 선형변환 한 것입니다.
X,Y,Z의 모든 값이 0또는 양수 값을 가집니다.
인간이 볼 수 있는 색은 X, Y, Z의 [0, 1]로 이루어져야한다.
흰색은 x=y=z=1/3인 지점으로 정의되어야한다.
이 XYZ컬러 공간은 CIE RGB color system의 얻어진 RGB 이므로, 전자장비에서 처리되는 RGB값을 매칭해도 의미가 없습니다.
CIE xyY Color system
CIE RGB로부터 CIE XYZ로 컬러매칭함수를 변경했습니다. 이제는 XYZ가 삼자극치가 됩니다. 이 X, Y, Z을 다른 공간인 xyz에 매핑할 수 있습니다.
아래의 그림에서보면, XYZ좌표가 있고, 각 X, Y, Z의 점은 RGB가 적절하게 섞인 값이고 각 축이 1이며, 나머지는 0인좌표들입니다([1,0,0], [0, 1, 0], [0, 0, 1]). 근데 이공간을 잘보면, Y축에 대해서 XY평면에 정사영(Projection)시키고, 그 밝기(또는 조도)를 Y축이라고하면 색깔을 똑같이 얻을 수 있습니다.
x = X/(X+Y+Z)
y = Y/(X+Y+Z)
z = Z/(X+Y+Z) # z=1-x-y
이렇게 정사영된 좌표를 xyY color space라고 합니다. 아래의 수식과 같이 X Y Z의 합이 1이도록 만들들면, x+y+z=1인 등식이 하나 생겨서 z=1-x-y가 될 수 있고, 이 때의 소문자 x,y와, 대문자 Y(조도)로 구성된 컬러공간이 xyY clor space입니다.
그렇기에 아래와 같은 CIE Chromaticity diagram에서는 x축이 한색깔이 아니라 여러색깔을 의미하고, 이에 해당하는 소문자 x, y축 또한 한 색깔이아닌 여러색깔이며 소문자 y축으로 표현합니다. 그리고, XYZ color space와 동일하게 정사영된것뿐이므로, 중앙은 흰색이됩니다. 그리고, 아래의 chromaticity diagram에서는 색만 보기에, 명도(밝기)인 Y가 없어 보이지 않습니다.
HSV color system
HSV color system은 색을 표현하는 또 하나의 방법입니다. 색상(Hue), 채도(Saturation) 명도(value)를 각각 이용하여 색상을 표현합니다. 이 세가지 개념은 아래와 같습니다.
색상(hue): 가장 기본적인 색을 빨강으로 하여, 빨강을 기준으로 0도로 각 스펙트럼을 360도로 표현합니다. 360도와 0도에 위치하는 색상은 모두 빨강입니다. 이 색상(hue)은 학문적으론, 빨강 노랑, 초록, 파랑 중 하나 또는 이 두가지의 색의 조합과 유사하게보이는 시각적인 감각이라고 합니다. 즉, 순수한 색에서의 다양성만 의미합니다[3] .
채도(Saturation): 채도는 가장 진한 상태를 100%로 하는 색상의 진한정도입니다. 흔히, 진한 빨강와 연한 빨강이라고 색상의 진한정도를 표현하듯이, 이는 채도를 의미합니다. 아래의 그림6과 같이, 진한 빨강화 연한 빨강 (세탁한 다음에 물빠진 옷 같은 색상)과 같이 사람은 색상의 진한 정도를 파악할 수 있고, 이 개념을 채도라고 합니다.
명도(Value): 색상의 밝음과 어두움을 나타내며, 모든 색상의 명도가 0은 검정입니다. 단 모든 색상의 명도가 100%인 경우는 흰색은 아닙니다.
HSV의 탄생배경은 대부분의 컴퓨터나 텔레비전의 디스플레이에서 사용되는 광원은 RGB각각의 광원의 강도로 조절이 가능합니다(이를 RGB additive primary color system)이라고합니다. 이 RGB은 색공간(Gamut)에서 모두가 R,G,B광원의 강도로 비슷하게하니 재현성은 좋았지만, 예술계쪽에서 사용하기 직관적이지 않았습니다. 일반인들은 와닿지 않지만, 아티스트들은 색조, 음영에 기반하여 작업하기때문에, 이를 H,S,V에 재매핑 할 필요가 있었습니다. 그림6과 같이 아티스트들은 어떤색에, 흰색을 조합한 것을 "틴트", 검정과의 조합은 "쉐이드", 이 두 조합을 "톤"이라고 구분하여 색상을 정하고 있었는데, RGB은 다른 표현방식이기에 직관적이지 않습니다. 예를 들어, 그림7과 같이 오렌지색의 색감을 줄이고싶은데, R,G,B에서는 각각 얼마나 줄여야하는지 와닿지 않습니다. 하지만 HSV에서는 Saturation만 줄이면되기에 상대적으로 더 직관적입니다. 즉, 인간이 느끼는 색의 변화가 HSV가 보다 직관적이고, HSV에서 원하는 색을 찾기위한 가감(additive, subtract)가 더 계산이 편하기에 HSV가 1970년대에 제시되었습니다. (동일한 문제를 제기하면서, HSL color system도 비슷한시기에 제안되었습니다)
보통 HSV color system은 원뿔형 또는 원기둥형으로 나타내기도 합니다. 원기둥형과 원뿔형 모두에서 각 각도는 색상을 의미합니다. 위에서 언급했듯이, 명도가 충분히 밝지 않으면 모든 색깔은 검정이기에 원뿔형을 나타냅니다. 반대로 말하면, 색상이 진하지 않으면 명도의 변화게 크게 됩니다. 이와 다르게 원기둥 모형에서는 각 색상의 명도가 0인부분이 중복되어 검정으로 표시되며, 원뿔형의 여집합에 해당하는 부분은 사실상 중복이됩니다.
Reference
Fairman H.S., Brill M.H., Hemmendinger H. (February 1997). "How the CIE 1931 Color-Matching Functions Were Derived from the Wright–Guild Data". Color Research and Application 22 (1): 11-23. 및 Fairman H.S., Brill M.H., Hemmendinger H. (August 1998). "Erratum: How the CIE 1931 Color-Matching Functions Were Derived from the Wright–Guild Data". Color Research and Application 23 (4): 259.
CLIP은 2021년에 언어-이미지 쌍을 사전학습하는 방법을 제시한 연구입니다. (이미지, 이미지에 대한 텍스트)를 쌍으로 대조적학습(Contrastive learning)을 사전학습하여, 자연어처리에서와의 접근방식과 유사하게 비전문제에서도 사전학습결과후, 각 테스크에서 좋은 성능을 낼 수 있음을 보여줍니다.
Preliminary
Zero shot learning(제로샷 러닝): 모델이 훈련 데이터에서 직접 학습하지 않은 클래스 또는 레이블을 인식하고 분류할 수 있게 하는 학습방법
Introduction
언어모델에서의 MLM(Masked language model)같은 특정 테스트와 무관한 학습을 하는 것으로 자연어쪽에서는 성공적인 연구성과를 보여주었습니다. 흔히, 일반화된 아키텍처(General model)에서 다운스트림으로 제로샷(Zero shot)으로 전이학습하기가 용이해져서, 아키텍처에 예측할 해더(분류기레이어)등을 추가할필요가 없어져왔습니다. 그 결과로 GPT-3같이 여러 목적에도 다양하게 사용할 수 있는 모델이 데이터가 부족해도 사용할 수 있게 되었습니다. 이런 학습방법은 웹에서 수집한 데이터로 사전학습을 하니, 라벨링을 직접하지 않습니다. 그럼에도, 크라우드소싱으로 라벨링한 것보다 좋은 성능을 보였고, 사전학습을 하는 것이 표준적인 관행이 되었습니다. 하지만, 비전문제에서도 이런 접근방법이 가능한지에 대해서는 아직 연구된바 없었습니다. 특히, 비전문제에서는 보통 클라우드 라벨링으로 모은 ImageNet으로 사전학습데이터를 사용하는데, 웹에서 사용한 데이터로 이런 사전학습을 할 수 없을까?가 이 연구의 motivation입니다.
여태까지는, 이미지를 표현할 때, 언어를 이용한 표현을 해볼 수 있겠는지와 관련된 연구는 별로 없었습니다. 실제로, 이전의 연구성과들에서는성능이 거의 안나왔었습니다 (11.5% 정확도, 이 때, SOTA가 88.4%). 이후, 인스타그램 이미지에서 ImageNet 관련 이미지 해시테그를 예측 하는 사전학습을 이용하자, SOTA보다 더 좋은 결과들을 보였습니다 (역시, 데이터가 많아야..). 이후에도 비슷한 접근방식으로 JFT-300M dataset을 만들어 사전학습하여 성능향상을 보였던 논문도 있었습니다. 하지만, 이런 방식도 결국 클레스를 N개로 정하다보니, "zero-shot"의 학습성능을 제한한다는 것에 한계를 제안합니다(결국 softmax하여 분류기를 추가하기에 일반화 목적성능의 달성이 어려움). 생각해보면, 자연어는 이미지보다 더 많은 클레스의 표현이 가능하기에, 이런 지도학습에도 더 도움을 줄 수 있을 것 같습니다.
CLIP 방법론: 이미지 표현 < 자연어표현이 더 풍부, 자연어기반 지도학습으로 zero-shot구현
CLIP의 핵심아이디어는 이미지의 인식방법이 자연어로부터 배울 수 있다는 것입니다. 이를 위해 아래의 단계대로 실험을 진행했습니다.
대규모 데이터셋 구축(creating a sufficient large dataset)
CLIP(Pre-training method) 구성(selecting an efficient pretraining method)
모델 크기 선택(Choosing and scaling a model)
학습(Training)
1. 대규모 데이터셋구축
새로운 아이디어는 아니고, 이전에는 N-gram이나 크라우드소싱데이터를 이용했다면, CLIP에서는 대규모데이터를 학습하려 노력했습니다. 자연어로 지도가 됨을 보여주기 위해서, 대규모데이터가 필요했습니다.
특히 1) 직접 클레스에 라벨링이 아니라 이미지에 대한 설명을 학습해볼 수도 있는 것이구요(이미지의 특징의 학습이 자연어로 지도됨). 2) 라벨링이 되어있지 않아도, 온라인상에서 일반적인 더 큰 이미지를 구축하려고 노력했습니다. ImageNet이나 MS-COCO 데이터셋 10만장밖에안되고, YFCC100M 데이터도 100million이지만 데이터 퀄리티가 좋지 않아(title, 설명등이 미포함), 전처리하고나면 ImageNet정도 사이즈밖에안되서 적다고합니다. 실제로 OpenAI에서는 4억장의 이미지-텍스트 쌍을 인터넷에서 구했다고 합니다. 이미지를 얻기위해서 5만개의 쿼리를 사용했고, 클레스 벨런스때문에, 쿼리당 20,000개의 이미지-텍스트 쌍을 넘지않도록했다고합니다. 이 데이터셋을 WebImageText(WIT)라 명명했습니다.
2. CLIP(Pre-training method)
CLIP은 효율적인 사전학습을 찾으려고 노력한 결과물입니다. 연구 초기에 사전학습방식을 결정할때, 아래와 같은 3가지의 방법을 적용해보았습니다. 3가지 목적으로 각각 이미지는 ViT(Vision Transformer)로 인코딩하고, 텍스트는 Transformer로 인코딩한다음에 이미지의 캡션을 예측하도록 Joint traning을 진행했습니다. 각 3가지는 Transformer Language model은 언어 생성, Bag of words은 순서가 없는, Bag of words contrasative (CLIP)은 BOW에 대조적학습까지 적용한 방법입니다. 같은 파라미터라면, 성능은 CLIP이 훨씬 효율이 높아 CLIP으로 사전학습을 하는 것을 목표로 했습니다.
CLIP은 N개의 배치로 튜플인 (이미지, 텍스트)의 페어를 입력을 받습니다. 만일 N개가 배치로 들어온다면, N x N의 메트릭스를 만들 수 있고, N개의 이미지와 N개의 텍스트 각각 하나씩만 실제 연관된 이미지-텍스트여서 N개의 Positive로 정의하고, $N^{2}-N$의 negative 페어를 만들어냅니다. 이를 대조적학습으로 학습하는 방법입니다 (아래의 Figure 3).
3. 모델 크기 선택(Choosing and scaling a model)
이미지 인코딩을 위한 아키텍처를 고를때는 처음에는 ResNet-50을 고르고 일부 변형했다고 합니다. 이는 이미 일반적으로 성공적인 성능을 보여왔기에 첫 선택이었다고 합니다. 그리고, 두 번째로는 ViT을 이용했다고합니다. 텍스트 인코딩은 Transformer을 이용했고, 65M Parameter + 12 Layer + 8 attention head + BPE 인코딩으로 얻어진 voca size은 49,152사이즈입니다.
CLIP official github에 제시된 것처럼 clone 후에, jupyter notebook에서 모델을 다운로드 받아보겠습니다.
import torch
import clip
from PIL import Image
device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load("ViT-B/32", device=device)
CLIP.png은 아래와 같은 다이어그램입니다
위와 같은 다이어그램을 preprocessing (resize->CenterCrop -> RGB -> 텐서변환->정규화)를 거쳐 전처리하고 CLIP에 포워딩해서 백터를 얻어보겠습니다. 또한, CLIP에 넣을 3개의 배치로 텍스트를 넣어보겠습니다. 텍스트는 토큰화되어서 (3, 77)의 77개의 시퀀싱을 갖는 백터며, 각 순서에 맞춰 인덱싱으로 원소가 구성되어있습니다.
CLIP논문에 맞춰, 이미지 표현(image_features), 텍스트 피쳐(text_features)을 구하고, 정규화 후 코사인유사도를 계산해보겠습니다. 유사도 계산후, 유사도(logit)으로 3차원의 벡터가 얻어졌고, "CLIP.png"와 "a diagram", "a dog", "a cat"의 각각의 쌍(pairwise)의 유사도가 [25.5528, 20.0899, 19.7495]로 얻어졌습니다. softmax로 구하면, 0.9928로 0번째 텍스트와 가장 유사도가 높음을 알 수 있습니다. CLIP은 이와 같이 이미지를 가장 잘 설명할 수 있는 text와의 유사도를 구하는 방식입니다.