Llama 3 RAG 검색 성능 최적화: 한국어 쿼리 및 문맥 이해 극대화

Llama 3를 활용한 한국어 Retrieval-Augmented Generation (RAG) 시스템의 검색 성능을 극대화하는 방법을 소개합니다. 이 가이드는 한국어 쿼리 이해도 향상 및 관련 문맥 추출을 위한 구체적인 기술적 접근 방식과 최적화 전략을 제시하여, RAG 시스템의 답변 정확도와 사용자 만족도를 획기적으로 향상시킬 수 있도록 돕습니다.

1. The Challenge / Context

최근 Llama 3와 같은 대규모 언어 모델(LLM)을 활용한 RAG 시스템이 많은 관심을 받고 있지만, 한국어 쿼리의 미묘한 뉘앙스를 정확하게 이해하고 관련된 문서를 효과적으로 검색하는 데에는 여전히 어려움이 존재합니다. 특히, 한국어는 교착어의 특성상 어미 변화가 심하고, 문맥 의존성이 높아 LLM이 정확한 의미를 파악하기 어렵습니다. 또한, 다양한 분야의 전문 용어와 속어, 은어가 혼재되어 있어, RAG 시스템의 검색 정확도를 떨어뜨리는 주요 원인이 됩니다. 이러한 문제점을 해결하고 한국어 RAG 시스템의 성능을 극대화하는 것이 본 가이드의 핵심 목표입니다.

2. Deep Dive: 한국어 임베딩과 코사인 유사도

RAG 시스템의 핵심은 쿼리와 문서 간의 관련성을 측정하여 가장 관련성이 높은 문서를 검색하는 것입니다. 이 과정에서 임베딩 모델은 텍스트를 고차원 벡터 공간에 표현하여, 의미적으로 유사한 텍스트는 벡터 공간에서 가까운 거리에 위치하도록 합니다. 현재 한국어에 특화된 다양한 임베딩 모델이 존재하며, 각 모델은 학습 데이터와 아키텍처에 따라 성능 차이를 보입니다. 쿼리와 문서 임베딩 간의 유사도는 주로 코사인 유사도를 사용하여 측정됩니다. 코사인 유사도는 두 벡터 사이의 각도의 코사인 값을 계산하여, -1(완전히 반대)부터 1(완전히 동일)까지의 값을 반환합니다. RAG 시스템은 코사인 유사도 값이 가장 높은 문서를 검색 결과로 반환합니다.

3. Step-by-Step Guide / Implementation

Llama 3 RAG 시스템의 한국어 검색 성능을 최적화하기 위한 구체적인 구현 단계를 소개합니다.

Step 1: 한국어 임베딩 모델 선택 및 적용

먼저, 한국어에 특화된 임베딩 모델을 선택해야 합니다. Sentence-BERT의 한국어 모델(예: paraphrase-multilingual-mpnet-base-v2를 한국어 데이터로 fine-tuning한 모델) 또는 KoELECTRA와 같은 모델을 고려할 수 있습니다. Hugging Face Transformers 라이브러리를 사용하여 이러한 모델을 쉽게 로드하고 사용할 수 있습니다.


from transformers import AutoTokenizer, AutoModel
import torch

model_name = "snunlp/KR-SBERT-V40K-klueNLI-augSTS" # 예시 모델, 실제로는 fine-tuning된 모델을 사용하는 것이 좋습니다.
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)

def get_embedding(text):
    encoded_input = tokenizer(text, padding=True, truncation=True, return_tensors='pt')
    with torch.no_grad():
        model_output = model(**encoded_input)
    return model_output.last_hidden_state[:, 0, :].squeeze()  # CLS 토큰 임베딩 반환

query = "최신 인공지능 기술 동향"
document = "최근 딥러닝 기반 자연어 처리 모델의 성능이 크게 향상되었다."

query_embedding = get_embedding(query)
document_embedding = get_embedding(document)

# 코사인 유사도 계산 (PyTorch 사용)
def cosine_similarity(a, b):
    a_norm = torch.nn.functional.normalize(a, p=2, dim=0)
    b_norm = torch.nn.functional.normalize(b, p=2, dim=0)
    return torch.dot(a_norm, b_norm)

similarity = cosine_similarity(query_embedding, document_embedding)
print(f"Cosine Similarity: {similarity.item()}")
    

Step 2: 데이터 전처리 및 정제

한국어 텍스트 데이터는 노이즈가 많고, 다양한 형태의 오탈자, 비표준어, 특수문자 등을 포함할 수 있습니다. 따라서, 임베딩 모델에 입력하기 전에 데이터 전처리 및 정제 작업을 수행해야 합니다. 띄어쓰기 오류 수정, 불필요한 특수문자 제거, 정규 표현식을 활용한 데이터 정제 등을 수행합니다.


import re
from hanspell import spell_checker # 한국어 맞춤법 검사기

def preprocess_text(text):
    # 1. HTML 태그 제거
    text = re.sub('<[^>]*>', '', text)
    # 2. 특수 문자 제거 (필요에 따라 더 많은 문자 추가)
    text = re.sub(r'[^\w\s]', '', text)
    # 3. 띄어쓰기 정규화 (연속된 공백을 하나의 공백으로 변경)
    text = re.sub(r'\s+', ' ', text).strip()
    # 4. 맞춤법 검사 (hanspell 사용) - 성능 고려하여 선택적으로 적용
    try:
        spelled_sent = spell_checker.check(text)
        checked_text = spelled_sent.checked
    except Exception as e:
        print(f"맞춤법 검사 오류: {e}, 원본 텍스트 사용")
        checked_text = text
    return checked_text

example_text = "오늘 날씨가 너무 좋네요!! 하지만...미세먼지가 심해요ㅠㅠ"
preprocessed_text = preprocess_text(example_text)
print(f"Original Text: {example_text}")
print(f"Preprocessed Text: {preprocessed_text}")
    

Step 3: 벡터 데이터베이스 구축 및 인덱싱

임베딩된 문서를 저장하고 효율적으로 검색하기 위해 벡터 데이터베이스를 사용합니다. FAISS, Annoy, Pinecone과 같은 벡터 데이터베이스를 선택하여 사용할 수 있습니다. 벡터 데이터베이스는 인덱싱 기술을 활용하여 유사도 검색 속도를 높입니다. 적절한 인덱싱 방법을 선택하여 쿼리 응답 시간을 최적화합니다.


import faiss
import numpy as np

# 임베딩 차원 설정
embedding_dimension = 768 # KR-SBERT-V40K-klueNLI-augSTS 모델의 임베딩 차원

# FAISS 인덱스 생성
index = faiss.IndexFlatL2(embedding_dimension) # L2 거리 기반 인덱스 (유클리드 거리)

# 예시 데이터 (실제로는 문서 임베딩을 사용)
num_vectors = 1000
embeddings = np.float32(np.random.rand(num_vectors, embedding_dimension))

# 인덱스에 벡터 추가
index.add(embeddings)

# 검색 쿼리 (실제로는 쿼리 임베딩을 사용)
query_vector = np.float32(np.random.rand(1, embedding_dimension))

# 검색 수행
k = 5 # 상위 5개 유사한 벡터 검색
distances, indices = index.search(query_vector, k)

print(f"Distances: {distances}")
print(f"Indices: {indices}")
    

Step 4: 쿼리 확장 (Query Expansion)

쿼리 확장은 사용자의 검색 의도를 명확하게 파악하고, 검색 정확도를 향상시키기 위한 기술입니다. 한국어의 경우, 동의어, 유의어, 관련어 등을 활용하여 쿼리를 확장할 수 있습니다. 시소러스(WordNet) 또는 LLM을 활용하여 쿼리 확장을 구현할 수 있습니다.


from nltk.corpus import wordnet as wn
import nltk

nltk.download('wordnet') # 최초 실행 시 다운로드 필요
nltk.download('omw-1.4')

def get_synonyms(word):
    synonyms = []
    for syn in wn.synsets(word, lang='kor'): # lang='kor' 추가
        for lemma in syn.lemmas(lang='kor'): # lang='kor' 추가
            synonyms.append(lemma.name())
    return list(set(synonyms))

query = "사과"
synonyms = get_synonyms(query)
print(f"Query: {query}")
print(f"Synonyms: {synonyms}")

# LLM을 이용한 쿼리 확장 예시 (OpenAI API 사용, 비용 발생)
import openai
import os

openai.api_key = os.getenv("OPENAI_API_KEY") # 환경 변수에 API 키 설정 필요

def expand_query_with_llm(query):
    prompt = f"다음 단어와 관련된 유사하거나 관련된 단어를 5개만 추천해주세요: {query}\n\n추천 단어:"
    response = openai.Completion.create(
        engine="text-davinci-003", # 또는 다른 모델
        prompt=prompt,
        max_tokens=50,
        n=1,
        stop=None,
        temperature=0.7,
    )
    expanded_words = response.choices[0].text.strip().split(", ")
    return expanded_words

# try:
#     expanded_query = expand_query_with_llm(query)
#     print(f"Expanded Query with LLM: {expanded_query}")
# except Exception as e:
#     print(f"LLM 쿼리 확장 오류: {e}")
    

Step 5: 재랭킹 (Re-ranking)

초기 검색 결과에서 상위 N개의 문서를 선택한 후, 더 정교한 모델을 사용하여 문서를 재정렬하는 방법입니다. Cross-Encoder 모델은 쿼리와 문서를 함께 입력으로 받아 관련성 점수를 직접 예측합니다. 재랭킹을 통해 검색 결과의 정확도를 더욱 향상시킬 수 있습니다.


from sentence_transformers import CrossEncoder

model_name = 'cross-encoder/ms-marco-MiniLM-L-6-v2' # 예시 모델, 한국어 fine-tuning된 모델이 있다면 더 좋습니다.
model = CrossEncoder(model_name)

query = "한국 드라마 추천"
documents = [
    "최근 넷플릭스에서 인기 있는 한국 드라마는 '오징어 게임'입니다.",
    "일본 애니메이션 '귀멸의 칼날'이 전 세계적으로 큰 인기를 얻고 있습니다.",
    "한국 영화 '기생충'은 아카데미 시상식에서 작품상을 수상했습니다.",
    "한국 드라마 '도깨비'는 판타지 로맨스 장르의 대표작입니다."
]

# Cross-Encoder 입력 형식: [(query, document1), (query, document2), ...]
pairs = [(query, doc) for doc in documents]

# 관련성 점수 예측
scores = model.predict(pairs)

# 점수를 기준으로 문서 정렬
ranked_documents = sorted(zip(documents, scores), key=lambda x: x[1], reverse=True)

print("Ranked Documents:")
for doc, score in ranked_documents:
    print(f"{doc}: {score}")
    

4. Real-world Use Case / Example

한 국내 스타트업은 자사의 고객 지원 챗봇에 Llama 3 RAG 시스템을 적용하여 고객 문의 응대 시간을 50% 단축하고, 고객 만족도를 20% 향상시켰습니다. 이 스타트업은 KR-SBERT 모델을 fine-tuning하여 자사의 고객 문의 데이터에 최적화된 임베딩 모델을 구축하고, 쿼리 확장 및 재랭킹 기술을 적용하여 검색 정확도를 극대화했습니다. 특히, 고객 문의에 자주 등장하는 은어와 속어를 학습 데이터에 포함시켜 쿼리 이해도를 향상시킨 것이 성공 요인이었습니다. 이 경험을 바탕으로, 해당 스타트업은 RAG 시스템 구축 컨설팅 서비스를 제공하며 사업 영역을 확장하고 있습니다.

5. Pros & Cons / Critical Analysis

  • Pros:
    • 한국어 쿼리 이해도 향상 및 문맥 정보 활용
    • 검색 정확도 향상을 통한 사용자 만족도 증가
    • 다양한 한국어 임베딩 모델 및 기술 적용 가능
    • RAG 시스템의 성능 향상을 통한 비즈니스 가치 창출
  • Cons:
    • 한국어 데이터 전처리 및 정제에 대한 높은 이해도 필요
    • 임베딩 모델 fine-tuning 및 유지보수에 대한 비용 발생 가능
    • 쿼리 확장 및 재랭킹 기술 적용에 대한 추가적인 개발 노력 필요
    • RAG 시스템의 성능은 데이터 품질에 크게 의존

6. FAQ

  • Q: 어떤 한국어 임베딩 모델을 선택해야 할까요?
    A: 사용 사례와 데이터에 따라 최적의 모델이 다릅니다. KR-SBERT, KoELECTRA 등 다양한 모델을 비교 평가하고, 필요한 경우 fine-tuning을 통해 성능을 향상시킬 수 있습니다.
  • Q: 쿼리 확장은 필수적인 단계인가요?
    A: 쿼리 확장은 검색 정확도를 향상시키는 데 도움이 되지만, 필수적인 단계는 아닙니다. 쿼리의 모호성이 높거나, 사용자의 검색 의도를 명확하게 파악하기 어려운 경우에 특히 유용합니다.
  • Q: 벡터 데이터베이스 선택 시 고려해야 할 사항은 무엇인가요?
    A: 데이터 규모, 쿼리 빈도, latency 요구 사항 등을 고려해야 합니다. FAISS는 빠른 검색 속도를 제공하지만, 대규모 데이터에는 적합하지 않을 수 있습니다. Pinecone과 같은 클라우드 기반 벡터 데이터베이스는 확장성이 뛰어나지만, 비용이 발생할 수 있습니다.

7. Conclusion

Llama 3 RAG 시스템의 한국어 검색 성능 최적화는 단순한 기술적 개선을 넘어, 사용자 경험 향상과 비즈니스 가치 창출로 이어지는 중요한 과정입니다. 이 가이드에서 제시된 방법들을 통해 한국어 쿼리 및 문맥 이해를 극대화하고, RAG 시스템의 잠재력을 최대한 활용할 수 있기를 바랍니다. 지금 바로 코드를 적용해보고, 한국어 RAG 시스템 성능 향상을 경험해보세요!