이 글은 Advanced RAG Series: Retrieval 포스팅을 보고 정리한 글입니다.


Retrieval 과정에서는 Retrieval 한 이후 Post Processing 이 중요하다:

  • 사용자의 쿼리와 관련있는 Top K 개의 Chunk 를 뽑아오더라도, 이게 Context Window 공간과는 아직 안맞을 수 있으므로 좀 더 후처리가 필요하다고 함.

 

계속해서 누누히 말하지만 LLM 영역에서 확실하게 올바른 방법은 아직까지는 없음. UseCase 나 Document 데이터에 따라서 더 나은 방법이 다를거임.

 

 

Reranking 방법:

  • Retrieval 한 데이터에서 다시 ranking 을 매기는 reranking 방법은 효율적일 수 있다고 함.
  • raranking 하는 방법도 여러가지가 있음. 하나씩 살펴보자.

 

Reranking 1) Increasing Diversity:

  • Increasing Diversity:
  • Maximum Marginal Relevance (MMR) 공식을 이용해서 Retrieval 한 데이터를 최대한 관련성이 높고, 다양성 또한 높은 문서를 고르도록 하는 방법임.
  • 중복되는 문서를 제거하기 위한 방법.
  • 주로 두 가지 변수에 의해서 제어된다. a) 쿼리와의 문서와의 유사성이 높은 문서, b) 이미 선택된 문서와 연관이 적은 문서
  • Haystack 에서 제공해주는 DiversityRanker 를 이용해서 이를 구현할 수 있다고 함.

 

 

Reranking 2)LostInTheMiddleReranker:

  • LLM 은 Retrieval 한 문서에서 처음과 끝이 아닌 중간에 있는 문서의 경우에는 잘 활용하지 못한다는 특징을 사용하는 기법임. 가져온 문서에서도 다시 재랭킹을 해서 문서의 처음과 끝에 중요한 문서를 배치하도록 하는 기법.
  • 앞의 기법인 Increasing Diversity 와 같이 사용하는 걸 추천한다고 한다.

 

 

Reranking 3)Cohere Rerank:

  • RAG 에서 Vector Database 에서 검색한 문서에 대해 재랭킹을 적용하는 기법임. 재랭킹을 할 때 초기 사용자 쿼리와 문서를 다시 관련성을 비교해서 재랭킹하는 것.
  • 벡터 간 검색을 한 이후에 Cohere Rarank 라는 기법을 한번 더 적용시키는 거임.
  • 벡터 간 검색도 유사도를 판단하지만, 단어의 빈도나 위치에 의존해서 의미적인 맥락을 충분히 반영하지 못할 수 있음. 그래서 Cohere Rerank 를 적용해서 의미적인 비교까지 하는거임.

 

LangChain 에서 Cohere Rerank 를 사용하는 예시는 다음과 같음. 여기에서는 Compression 매커니즘도 적용됨. 

from langchain_community.llms import Cohere
from langchain.retrievers import  ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CohereRerank

compressor = CohereRerank(model="rerank-english-v2.0", top_n=3)
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor, base_retriever=retriever
)

compressed_docs = compression_retriever.get_relevant_documents(question)

prompt = ChatPromptTemplate.from_template(
    """
    Asnwer the given question using the provided context.\n\nContext: {context}\n\nQuestion: {question}
    """
)


cohere_with_rag_chain = (
    {'context': compression_retriever, 'question': RunnablePassthrough()}
    | prompt
    | ChatOpenAI(model='gpt-4', temperature=0)
    | StrOutputParser()
)

cohere_with_rag_chain.invoke(question)

 

 

Reranking 4) bge-rerank:

  • 여러가지 Rerank 매커니즘을 사용하는 것과 rerank 알고리즘과 시너지를 내는 임베딩 모델을 같이 사용하는 것도 중요하다.
  • 다음과 같이 여러 알고리즘과 embedding 모델을 이용해서 평가해보고 우리의 데이터에 가장 잘 맞는 알고리즘과 임베딩 모델을 선택하자.
  • 평가에 사용할 수 있는 기준은 Hit Rate 와 MRR (Mean Reciprocal Rank) 가 있다. Hit rate 는 top k 문서 안에 포함되느냐 를 평가한 지표이고, MRR 은 관련된 문서의 랭킹을 이용한 점수이다.

 

 

Reranking 5) mxbai-rerank-v1:

  • 현재 가장 성능이 좋다고 알려진 rerank 알고리즘임.

 

 

Reranking 6) RankGPT (GPT 3.5):

  • GPT 3.5 를 ranking 에 rerank 에 사용할 경우 Cohere rerank 보다 훨씬 더 랭킹을 잘 매긴다고 함.
  • GPT 3.5 의 context window 를 극복하기 위한 방법으로는 sliding window 기법이 있음. 아래 이미지와 같이 나오는거지만 누적식으로 랭킹을 매겨서 랭킹 범위를 벗어나는 문서는 제외하는 기법임.
  • 이런 기법을 사용할 경우 훨씬 더 간단하지만 비용적인 측면이나 latency 측면이 더 많이 나온다는 문제점도 있다.
  • https://arxiv.org/abs/2304.09542?utm_source=div.beehiiv.com&utm_medium=referral&utm_campaign=advanced-rag-series-retrieval

 

 

Prompt Compression:

  • 관련된 Doucument 를 조회한 이후 쿼리와 연관이 없다고 생각하는 부분은 compression 하는 방법이다.
  • 이를 위한 여러가지 기법들이 있음.

 

 

Prompt Compression 1) LongLLMLingua:

  • 문서를 처음에는 크게 압축하면서 직접적으로 관련이 없는 정보를 대량으로 제거한다. 그 다음 두 번째에는 남은 정보를 세밀하게 분석해서 주요 정보는 유지하면서도 세밀하게 압축을 한번 더 한다.
    • 압축을 수행할 때는 사용자의 질문과의 관련성을 고려해서 압축한다. 질문과 관련이 있는 부분은 압축이 되지 않고, 질문과 관련이 없는 부분은 더 압축됨.
    • 문서의 각 부분이 가진 중요도에 따라서 압축 비율은 다르게 적용됨. 중요한 부분은 덜 압축되고, 중요하지 않은 부분은 더 압축된다.
    • 압축 후에는 평가를 거쳐서 중요한 정보가 손실되었는지 확인하고 복구 매커니즘을 해서 중요한 정보가 사라지지 않도록 만든다.
  • 압축되고 난 이후 문서를 재정렬(reranking) 을 해서 더 중요한 문서가 더 앞단에 오게 만든다.
  • 특히 긴 문서에서 효과적이라고 함.
  • https://arxiv.org/abs/2310.06201?utm_source=div.beehiiv.com&utm_medium=referral&utm_campaign=advanced-rag-series-retrieval

 

 

Prompt Compression 2) RECOMP

  • compressors 를 사용해서 문서에서 핵심 문장을 추출하거나, 여러 문서를 종합해서 재구성해서 요약본을 만드는 두 가지 방법을 지원함. 
  • RECOMP 는 두 가지 압축 방법 중 하나를 적용하면 됨. 
    • Extractive Compressor:
      • 문서에서 사용자의 쿼리와 관련 있는 문장을 선택해서 요약을 만듬.
    • Abstractive Compressor:
      • 여러 문서를 종합해서 새로은 요약을 만들어낸다. 단순히 종합해서 새로운 요약을 만들어내는게 아니라 이해하고 이를 재구성한다.
  • https://arxiv.org/abs/2310.04408?utm_source=div.beehiiv.com&utm_medium=referral&utm_campaign=advanced-rag-series-retrieval

 

 

Prompt Compression 3) Walking Down the Memory Maze

  • 이 방식은 일단 Indexing 단계에서 Chunking 을 요약-트리 구조로 저장해둬야 함.
    • 그러니까 문서가 일정한 크기의 Chunk 로 쪼개지면, Chunk 를 합친 상태로 요약한 부모 Chunk 로 이뤄지는 식으로 문서를 저장해둠.
  • 이렇게 저장해둔 상태에서 이제 사용자가 검색을 하면 최상단의 요약된 Chunk 부터 순회하면서 질문과 연관성이 있는 CHunk 를 찾아 나서는거임.
  • 특히 이 방법은 긴 문서에서 효과적이라고 함.

 

 

RAG-fusion 기법:

 

 

RAG Fusion 의 Reciprocal Rank Fusion 정렬 알고리즘을 사용하는 예제는 다음과 같음: 

def rrf(results: List[List], k=60):
    # Initialize a dictionary to hold fused scores for each unique document
    fused_scores = {}

    # Iterate through each list of ranked documents
    for docs in results:
        # Iterate through each document in the list, with its rank (position in the list)
        for rank, doc in enumerate(docs):
            # Convert the document to a string format to use as a key (assumes documents can be serialized to JSON)
            doc_str = dumps(doc)
            # If the document is not yet in the fused_scores dictionary, add it with an initial score of 0
            if doc_str not in fused_scores:
                fused_scores[doc_str] = 0
            # Retrieve the current score of the document, if any
            previous_score = fused_scores[doc_str]
            # Update the score of the document using the RRF formula: 1 / (rank + k)
            fused_scores[doc_str] += 1 / (rank + k)

    # Sort the documents based on their fused scores in descending order to get the final reranked results
    reranked_results = [
        (loads(doc), score)
        for doc, score in sorted(fused_scores.items(), key=lambda x: x[1], reverse=True)
    ]

    # Return the reranked results as a list of tuples, each containing the document and its fused score
    return reranked_results

from langchain.prompts import ChatPromptTemplate

question = "What is QLoRA?"

prompt = ChatPromptTemplate.from_template(
    """
    You are an intelligent assistant. Your task is to generate 4 questions based on the provided question in different wording and different perspectives to retrieve relevant documents from a vector database. By generating multiple perspectives on the user question, your goal is to help the user overcome some of the limitations of the distance-based similarity search. Provide these alternative questions separated by newlines. Original question: {question}
    """
)

generate_queries = (
    {"question": RunnablePassthrough()}
    | prompt
    | ChatOpenAI(model='gpt-4', temperature=0.7)
    | StrOutputParser()
    | (lambda x: x.split("\n"))
)


fusion_retrieval_chain = (
    {'question': RunnablePassthrough()}
    | generate_queries
    | retriever.map()
    | rrf
)

fusion_retrieval_chain.invoke(question)

def format_context(documents: List):
    return "\n\n".join([doc[0].page_content for doc in documents])


prompt = ChatPromptTemplate.from_template(
    """
    Asnwer the given question using the provided context.\n\nContext: {context}\n\nQuestion: {question}
    """
)

rag_with_rrf_chain = (
    {'context': fusion_retrieval_chain | format_context, 'question': RunnablePassthrough()}
    | prompt
    | ChatOpenAI(model='gpt-4', temperature=0)
    | StrOutputParser()
)

rag_with_rrf_chain.invoke(question)

 

 

 

Refinement 1) CRAG (Corrective Retrieval Augmented Generation):

  • 일반적인 RAG 기법에서 조회한 문서의 관련성을 평가할 수 있는 경량화 된 Evaluator 를 활용하는 기법임.
  • RAG Evaluator 는 조회된 문서를 평가해서 Correct, Ambiguous, Incorrect 로 평가함. Correct 인 경우에는 해당 문서를 활용하고, Ambiguous 는 웹 검색을 추가로 활용해서 문서의 데이터를 보완하고, Incorrect 는 조회된 문서를 폐기하고 웹 검색 결과를 적극적으로 이용하는 기법임.
  • 웬만한 RAG 보다는 성능이 좋다고 함.

 

 

Refinement 2) FLARE (Forward Looking Active Retrieval):

  • 이 기법은 긴 문장을 생성할 때 할루시네이션이 더 발생하도록 하는데 유용한 기법임.
  • 작동 원리는 다음과 같다:
    • 1) 사용자 질문에서 관련된 문서를 뽑아오고, 이를 바탕으로 임시 텍스트를 생성한다.
    • 2) 생성한 임시 텍스트를 평가하고 low-probability tokens 를 포함하는지 평가한다. (low-probability tokens 는 문맥과 관련되지 않은 어색한 토큰을 말함)
    • 3) 이 토큰이 발견된다면 관련된 문서를 더 검색한 후 다시 문장을 생성하도록 한다.

+ Recent posts