ColBERT 다루기
ColBERT 다루기 (fastembed-fastembed-colbert)
ColBERT는 원문을 행렬(multivector) 형태로 표현하는 임베딩 모델이에요. 문장 전체를 벡터 하나로 축약하는 대신, 토큰(머신러닝 모델이 다루는 의미 있는 텍스트 단위)마다 벡터를 하나씩 생성해요. 그래서 입력의 의미를 더 섬세하게 포착할 수 있죠. 문장을 하나의 벡터로 만들던 조밀(dense) 임베딩 모델들과는 확실히 다른 접근이라서, 더 세밀한 표현을 만들어내는 덕분에 강력한 검색기(retriever) 역할을 해요.
다만 이 장점에는 대가가 따르는데요, 속도와 메모리 모두 기존 조밀 임베딩 모델보다 자원을 더 많이 써요.
ColBERT를 언제 쓸까요?
강력한 검색기긴 하지만, 속도 제한 때문에 대규모 검색의 1차 검색 단계에는 부적합할 수 있어요. 그래서 일반적으로는 이렇게 권장해요. 이미 검색된 작은 후보 집합을 재정렬(rerank)하는 용도로 ColBERT를 쓰라는 거예요. 간단한 조밀 검색기로 먼저 100~500개 정도의 후보를 뽑아 둔 다음, ColBERT로 그 후보를 재정렬해서 가장 관련성 높은 결과를 맨 위로 끌어올리는 방식이죠.
재정렬 모델로서 ColBERT는 크로스-인코더(cross-encoder)의 훌륭한 대안이기도 해요. late interaction 메커니즘 덕분에 추론(inference) 시간이 더 빠른 편이라서 그렇죠.
그럼 late interaction이 뭘까요? 크로스-인코더는 쿼리와 문서를 하나의 입력으로 이어 붙여서 받아요. 그리고 이 입력을 모델이 의미 있는 부분으로 나눈 뒤, 그 부분들이 어떻게 관계 맺는지 확인해요. 즉 쿼리와 문서 사이의 모든 상호작용이 모델 안에서 "일찍" 일어나는 거예요. 반면 ColBERT 같은 late interaction 모델은 그중 첫 번째 부분만 수행해요. 문서와 쿼리를 비교하기 좋은 조각들로 만들어 내는 것까지만 하는 거죠. 이 조각들 사이의 상호작용은 모델 밖에서 "나중에" 수행하도록 넘겨요.
Qdrant에서 ColBERT 사용하기
Qdrant는 멀티벡터(multivector) 표현을 기본으로 지원해요. 덕분에 ColBERT나 ColPali 같은 late interaction 모델을 추가적인 전/후처리 없이 그대로 쓸 수 있어요.
이 튜토리얼에서는 장난감(toy) 데이터셋을 대상으로 ColBERT를 1차 검색기로 사용하는 예시를 보여줘요. ColBERT를 재정렬기로 쓰는 방법은 다단계 쿼리 문서에서 확인할 수 있어요.
설정 (Setup)
먼저 fastembed를 설치해요.
pip install fastembed
텍스트 임베딩용 late interaction 모델을 import 해요.
from fastembed import LateInteractionTextEmbedding
FastEmbed가 지원하는 late interaction 모델 목록을 확인할 수 있어요.
LateInteractionTextEmbedding . list_supported_models ()
이 명령은 사용 가능한 모델들을 보여줘요. 출력에는 모델의 출력 임베딩 차원, 모델 설명, 모델 크기, 모델 소스, 모델 파일 같은 상세 정보가 담겨요.
[{ 'model' : 'colbert-ir/colbertv2.0' , 'dim' : 128 , 'description' : 'Late interaction model' , 'size_in_GB' : 0.44 , 'sources' : { 'hf' : 'colbert-ir/colbertv2.0' }, 'model_file' : 'model.onnx' }, { 'model' : 'answerdotai/answerai-colbert-small-v1' , 'dim' : 96 , 'description' : 'Text embeddings, Unimodal (text), Multilingual (~100 languages), 512 input tokens truncation, 2024 year' , 'size_in_GB' : 0.13 , 'sources' : { 'hf' : 'answerdotai/answerai-colbert-small-v1' }, 'model_file' : 'vespa_colbert.onnx' }]
이제 모델을 로드해요.
model_name = "colbert-ir/colbertv2.0" embedding_model = LateInteractionTextEmbedding ( model_name )
모델 파일을 가져와서 다운로드하는데, 진행 상황이 표시돼요.
장난감 영화 설명 데이터셋을 ColBERT로 벡터화해볼게요.
영화 설명 데이터셋
descriptions = [ "In 1431, Jeanne d'Arc is placed on trial on charges of heresy. The ecclesiastical jurists attempt to force Jeanne to recant her claims of holy visions." , "A film projectionist longs to be a detective, and puts his meagre skills to work when he is framed by a rival for stealing his girlfriend's father's pocketwatch." , "A group of high-end professional thieves start to feel the heat from the LAPD when they unknowingly leave a clue at their latest heist." , "A petty thief with an utter resemblance to a samurai warlord is hired as the lord's double. When the warlord later dies the thief is forced to take up arms in his place." , "A young boy named Kubo must locate a magical suit of armour worn by his late father in order to defeat a vengeful spirit from the past." , "A biopic detailing the 2 decades that Punjabi Sikh revolutionary Udham Singh spent planning the assassination of the man responsible for the Jallianwala Bagh massacre." , "When a machine that allows therapists to enter their patients' dreams is stolen, all hell breaks loose. Only a young female therapist, Paprika, can stop it." , "An ordinary word processor has the worst night of his life after he agrees to visit a girl in Soho whom he met that evening at a coffee shop." , "A story that revolves around drug abuse in the affluent north Indian State of Punjab and how the youth there have succumbed to it en-masse resulting in a socio-economic decline." , "A world-weary political journalist picks up the story of a woman's search for her son, who was taken away from her decades ago after she became pregnant and was forced to live in a convent." , "Concurrent theatrical ending of the TV series Neon Genesis Evangelion (1995)." , "During World War II, a rebellious U.S. Army Major is assigned a dozen convicted murderers to train and lead them into a mass assassination mission of German officers." , "The toys are mistakenly delivered to a day-care center instead of the attic right before Andy leaves for college, and it's up to Woody to convince the other toys that they weren't abandoned and to return home." , "A soldier fighting aliens gets to relive the same day over and over again, the day restarting every time he dies." , "After two male musicians witness a mob hit, they flee the state in an all-female band disguised as women, but further complications set in." , "Exiled into the dangerous forest by her wicked stepmother, a princess is rescued by seven dwarf miners who make her part of their household." , "A renegade reporter trailing a young runaway heiress for a big story joins her on a bus heading from Florida to New York, and they end up stuck with each other when the bus leaves them behind at one of the stops." , "Story of 40-man Turkish task force who must defend a relay station." , "Spinal Tap, one of England's loudest bands, is chronicled by film director Marty DiBergi on what proves to be a fateful tour." , "Oskar, an overlooked and bullied boy, finds love and revenge through Eli, a beautiful but peculiar girl." ]
벡터화는 embed 제너레이터 함수로 해요.
descriptions_embeddings = list ( embedding_model . embed ( descriptions ) )
생성된 임베딩 하나의 크기를 확인해볼게요.
descriptions_embeddings [ 0 ] . shape
결과는 다음과 같아요.
( 48, 128 )
즉 첫 번째 설명은 길이 128인 벡터 48개로 표현된다는 뜻이에요.
qdrant-client를 설치해요.
pip install "qdrant-client>=1.14.2"
Qdrant 클라이언트에는 작은 데이터로 로컬에서 실험해볼 수 있는 간단한 인메모리 모드가 있어요. 대안으로 Qdrant Cloud의 무료 클러스터를 실험에 쓸 수도 있어요.
from qdrant_client import QdrantClient , models qdrant_client = QdrantClient ( ":memory:" ) # Qdrant is running from RAM.
이제 영화 데이터로 작은 컬렉션(collection)을 만들어볼게요. 이를 위해 Qdrant가 지원하는 멀티벡터 기능을 사용할 거예요. 멀티벡터 컬렉션을 구성하려면 다음을 지정해야 해요.
- 벡터 사이의 유사도 지표(metric)
- 각 벡터의 크기 (ColBERT의 경우 128)
- 멀티벡터(행렬) 사이의 유사도 지표, 예를 들어
maximum. 행렬 A의 벡터마다 행렬 B에서 가장 유사한 벡터를 찾고, 그 유사도 점수가 행렬 사이의 유사도가 돼요.
qdrant_client . create_collection ( collection_name = "movies" , vectors_config = models . VectorParams ( size = 128 , #size of each vector produced by ColBERT distance = models . Distance . COSINE , #similarity metric between each vector multivector_config = models . MultiVectorConfig ( comparator = models . MultiVectorComparator . MAX_SIM #similarity metric between multivectors (matrices) ), ), )
컬렉션을 읽기 쉽게 만들기 위해, 임베딩된 설명과 함께 영화 메타데이터(제목, 설명 텍스트, 영화 길이)를 저장할게요.
영화 메타데이터
metadata = [{ "movie_name" : "The Passion of Joan of Arc" , "movie_watch_time_min" : 114 , "movie_description" : "In 1431, Jeanne d'Arc is placed on trial on charges of heresy. The ecclesiastical jurists attempt to force Jeanne to recant her claims of holy visions." }, { "movie_name" : "Sherlock Jr." , "movie_watch_time_min" : 45 , "movie_description" : "A film projectionist longs to be a detective, and puts his meagre skills to work when he is framed by a rival for stealing his girlfriend's father's pocketwatch." }, { "movie_name" : "Heat" , "movie_watch_time_min" : 170 , "movie_description" : "A group of high-end professional thieves start to feel the heat from the LAPD when they unknowingly leave a clue at their latest heist." }, { "movie_name" : "Kagemusha" , "movie_watch_time_min" : 162 , "movie_description" : "A petty thief with an utter resemblance to a samurai warlord is hired as the lord's double. When the warlord later dies the thief is forced to take up arms in his place." }, { "movie_name" : "Kubo and the Two Strings" , "movie_watch_time_min" : 101 , "movie_description" : "A young boy named Kubo must locate a magical suit of armour worn by his late father in order to defeat a vengeful spirit from the past." }, { "movie_name" : "Sardar Udham" , "movie_watch_time_min" : 164 , "movie_description" : "A biopic detailing the 2 decades that Punjabi Sikh revolutionary Udham Singh spent planning the assassination of the man responsible for the Jallianwala Bagh massacre." }, { "movie_name" : "Paprika" , "movie_watch_time_min" : 90 , "movie_description" : "When a machine that allows therapists to enter their patients' dreams is stolen, all hell breaks loose. Only a young female therapist, Paprika, can stop it." }, { "movie_name" : "After Hours" , "movie_watch_time_min" : 97 , "movie_description" : "An ordinary word processor has the worst night of his life after he agrees to visit a girl in Soho whom he met that evening at a coffee shop." }, { "movie_name" : "Udta..." ]
qdrant_client . upload_points ( collection_name = "movies" , points = [ models . PointStruct ( id = idx , payload = metadata [ idx ], vector = vector ) for idx , vector in enumerate ( descriptions_embeddings ) ], )
임베딩을 암시적으로 계산하며 업로드하기
description_documents = [ models . Document ( text = description , model = model_name ) for description in descriptions ] qdrant_client . upload_points ( collection_name = "movies" , points = [ models . PointStruct ( id = idx , payload = metadata [ idx ], vector = description_document ) for idx , description_document in enumerate ( description_documents ) ], )
쿼리하기 (Querying)
ColBERT는 FastEmbed에서처럼 문서와 쿼리를 임베딩할 때 서로 다른 두 가지 방식을 사용해요. 다만 FastEmbed는 ColBERT의 쿼리 전처리를 다르게 바꿔서, 모든 쿼리를 32토큰 길이로 자르지 않고 더 긴 쿼리를 그대로 받아들여요.
qdrant_client . query_points ( collection_name = "movies" , query = list ( embedding_model . query_embed ( "A movie for kids with fantasy elements and wonders" ))[ 0 ], #converting generator object into numpy.ndarray limit = 1 , #How many closest to the query movies we would like to get #with_vectors=True, #If this option is used, vectors will also be returned with_payload = True #So metadata is provided in the output )
임베딩을 암시적으로 계산하며 쿼리하기
query_document = models . Document ( text = "A movie for kids with fantasy elements and wonders" , model = model_name ) qdrant_client . query_points ( collection_name = "movies" , query = query_document , limit = 1 , )
결과는 다음과 같아요.
QueryResponse ( points =[ ScoredPoint ( id = 4, version = 0, score = 12.063469, payload ={ 'movie_name' : 'Kubo and the Two Strings' , 'movie_watch_time_min' : 101, 'movie_description' : 'A young boy named Kubo must locate a magical suit of armour worn by his late father in order to defeat a vengeful spirit from the past.' } , vector = None, shard_key = None, order_value = None )])
쿼리에 가장 가까운 영화로 Kubo and the Two Strings가 반환됐어요. 멀티벡터 방식으로 검색이 잘 동작하는 걸 확인할 수 있죠.
출처: Qdrant 공식문서