소소한 컴퓨터 이야기

Corrective Retrieval Augmented Generation (CRAG)

by Cori

해당 포스트는 Medium 'Florian June'이 작성한 Advanced RAG 포스트 시리즈 그 열번째 내용을 정리하며, 교정된 정보 검색 증강 생성 방법인 CRAG에 대해 다루고 있다.


흔히 접하는 시나리오인 오픈북 시험에 참여하는 것에서 시작한다.

 

오픈북 시험에서, 보통 우리는 세 가지 전략을 가진다:

방법 1: 익숙한 주제는 빠르게 답하고, 익숙하지 않은 주제는 참고서를 참조한다. 관련 부분을 신속하게 찾아내어, 그것을 정리 및 요약한 후 시험지에 답을 작성한다(Self-RAG).

방법 2: 각 주제에 대해 책을 참조한다. 관련 부분을 찾아내어 요약한 후 시험지에 답을 작성한다(RAG).

방법 3: 각 주제에 대해 책을 참조하고 관련 부분을 찾아낸다. 의견을 형성하기 전에 수집한 정보를 '올바른 정보', '잘못된 정보', '애매한 정보'의 세 그룹으로 분류한다. 각각의 정보를 별도로 처리한 후, 처리된 정보를 바탕으로 정리하고 요약한다. 그런 다음 시험지에 답을 작성한다(CRAG).

 

Motivation of CRAG

The examples show that a low-quality retriever is prone to introducing a substantial amount of irrelevant information, https://miro.medium.com/v2/resize:fit:720/format:webp/1*qXfZjsePcVb101dMhZT68Q.png

위 그림은 대부분의 전통적인 RAG 방법이 문서의 질문 관련성을 고려하지 않고 단순히 검색된 문서들을 병합한다는 것을 보여준다. 이는 관련 없는 정보를 도입할 수 있으며, 모델이 정확한 지식을 얻는 것을 방해하고, 잠재적으로 잘못된 정보를 제공하여 환각(hallucination) 문제를 초래할 수 있다. 또한, 대부분의 전통적인 RAG 방법은 검색된 문서 전체를 입력으로 사용한다. 검색된 문서의 텍스트 중 상당 부분은 생성에 필요하지 않으며, RAG에 동일하게 포함되지 않아야 한다.

 

Key Idea of CRAG

CRAG는 특정 쿼리에 대해 검색된 문서의 전반적인 품질을 평가하기 위해 경량 검색 평가기를 설계한다. 또한 검색 결과를 개선하기 위해 웹 검색을 보조 도구로 사용한다. CRAG는 플러그 앤 플레이 방식으로, RAG를 기반으로 한 다양한 방법과 원활하게 통합될 수 있으며 아키텍처는 다음과 같다.

The position of CRAG(red dashed box) in RAG, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*PcB3G8QRz3NWKU7pmWGlDA.png

CRAG는 검색된 문서와 쿼리 간의 관계를 평가하기 위해 검색 평가기를 도입하여 전통적인 RAG를 향상시키는데, 세 가지 가능한 판단 결과가 있다.

Case 01. 정확한 경우: 검색된 문서가 쿼리가 요구하는 필요한 내용을 포함하고 있음을 의미한다. 이 경우, 지식 정제 알고리즘을 사용하여 검색된 문서를 다시 작성한다. 

Case 02. 잘못된 경우: 검색된 문서가 쿼리와 관련이 없음을 의미한다. 문서를 대형 언어 모델로 보낼 수 없고, CRAG에서는 외부 지식을 검색하기 위해 웹 검색 엔진을 사용한다.

Case 03. 모호한 경우: 검색된 문서가 답을 제공하기에 충분하지 않을 수 있음을 의미한다. 추가 정보를 웹 검색을 통해 확보해야 하며, 지식 정제 알고리즘과 검색 엔진을 모두 사용한다. 

 

처리된 정보는 응답 생성을 위해 LLM으로 전달되며, 이 과정은 다음 그림과 같다. 

웹 검색은 사용자의 입력 쿼리를 직접 검색에 사용하지 않는다. 대신 프롬프트를 구성하고 이를 GPT-3.5 Turbo에 몇 번의 샷(few-shot) 방식으로 제시하여 검색에 사용할 쿼리를 얻는다. 

 

Retrieval Evaluator

검색 평가기는 이후 절차의 결과에 큰 영향을 미치며 전체 시스템 성능을 결정하는 데 매우 중요하다.

Knowledge correction in CRAG, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*sYqAD-mGe4jE9eNcFmkQtA.png

CRAG는 검색 평가기로 경량 T5-large 모델을 사용하고, 이를 미세 조정한다. 대형 언어 모델 시대에서 T5-large도 경량으로 간주된다는 점은 주목할 만하다.

각 쿼리에 대해 일반적으로 10개의 문서가 검색되며, 쿼리는 각 문서와 개별적으로 결합되어 관련성을 예측하는 입력으로 사용된다. 미세 조정 과정에서 긍정 샘플에는 1, 부정 샘플에는 -1의 레이블을 할당한다. 추론 시, 평가자는 각 문서에 대해 -1에서 1 사이의 관련성 점수를 할당하며, 점수들은 임계값에 따라 세 가지 수준으로 분류된다. 분류를 위해 두 개의 임계값이 필요하며, CRAG에서는 실험 데이터에 따라 임계값 설정이 달라질 수 있다:

세 가지 행동 중 하나를 촉발하기 위한 두 개의 신뢰 임계값은 경험적으로 설정되었으며, PopQA에서는 (0.59, -0.99), PubQA와 ArcChallenge에서는 (0.5, -0.91), Biography에서는 (0.95, -0.91)로 설정되었다.

 

Knowledge Refinement Algorithm

검색된 문서에 대해 CRAG는 가장 중요한 지식 진술을 추출하기 위해 분해 후 재구성하는 지식 추출 방법을 설계한다.

Step 1. 세밀한 결과를 얻기 위해 휴리스틱 규칙을 적용하여 각 문서를 세분화된 지식 조각으로 나눈다. 검색된 문서가 한두 문장으로만 구성되어 있으면 독립된 단위로 간주되며, 문서를 전체 길이에 따라 여러 문장으로 구성된 더 작은 단위로 나눈다. 각 단위는 독립적인 정보를 포함하도록 한다.

Step 2. 검색 평가기를 사용하여 각 지식 조각의 관련성 점수를 계산하며, 관련성 점수가 낮은 조각은 필터링된다. 남은 관련 지식 조각은 내부 지식을 형성하기 위해 재조립된다.

 

Code Explanation

CRAG는 오픈 소스이며, llama index와 langchain에서 모두 지원한다. 

ENV Setting

conda create -n crag python=3.11
conda activate crag
pip install llama-index llama-index-tools-tavily-research
mkdir "YOUR_DOWNLOAD_DIR"

Test Code

import os
os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"
from llama_index.core import Document

# Option: Download CorrectiveRAGPack
# The first execution requires the download of CorrectiveRAGPack
# Subsequent executions can comment this out.
from llama_index.core.llama_pack import download_llama_pack
CorrectiveRAGPack = download_llama_pack(
    "CorrectiveRAGPack", "YOUR_DOWNLOAD_DIR"
)

# Create testing documents
documents = [
    Document(text="A group of penguins, known as a 'waddle' on land, shuffled across the Antarctic ice, their tuxedo-like plumage standing out against the snow."),
    Document(text="Emperor penguins, the tallest of all penguin species, can dive deeper than any other bird, reaching depths of over 500 meters."),
    Document(text="Penguins' black and white coloring is a form of camouflage called countershading; from above, their black back blends with the ocean depths, and from below, their white belly matches the bright surface."),
    Document(text="Despite their upright stance, penguins are birds that cannot fly; their wings have evolved into flippers, making them expert swimmers."),
    Document(text="The fastest species, the Gentoo penguin, can swim up to 36 kilometers per hour, using their flippers and streamlined bodies to slice through the water."),
    Document(text="Penguins are social birds; many species form large colonies for breeding, which can number in the tens of thousands."),
    Document(text="Intriguingly, penguins have excellent hearing and rely on distinct calls to identify their mates and chicks amidst the noisy colonies."),
    Document(text="The smallest penguin species, the Little Blue Penguin, stands just about 40 cm tall and is found along the coastlines of southern Australia and New Zealand."),
    Document(text="During the breeding season, male Emperor penguins endure the harsh Antarctic winter for months, fasting and incubating their eggs, while females hunt at sea."),
    Document(text="Penguins consume a variety of seafood; their diet mainly consists of fish, squid, and krill, which they catch on their diving expeditions."),
]

from llama_index.packs.corrective_rag import CorrectiveRAGPack
corrective_rag = CorrectiveRAGPack(documents, "YOUR_TAVILYAI_API_KEY")

# From here, you can use the pack, or inspect and modify the pack in ./corrective_rag_pack.
# The run() function contains around logic behind Corrective Retrieval Augmented Generation - CRAG paper.
query = "How tall is the smallest penguins?"
print('-' * 100)
print("The response of the query " + query + " is:")
response = corrective_rag.run(query, similarity_top_k=2)
print(response)

corrective_rag.run() 함수를 자세히 살펴보자

 

The constructor of class CorrectiveRAGPack

class CorrectiveRAGPack(BaseLlamaPack):
    def __init__(self, documents: List[Document], tavily_ai_apikey: str) -> None:
        """Init params."""
        llm = OpenAI(model="gpt-4")
        self.relevancy_pipeline = QueryPipeline(
            chain=[DEFAULT_RELEVANCY_PROMPT_TEMPLATE, llm]
        )
        self.transform_query_pipeline = QueryPipeline(
            chain=[DEFAULT_TRANSFORM_QUERY_TEMPLATE, llm]
        )

        self.llm = llm
        self.index = VectorStoreIndex.from_documents(documents)
        self.tavily_tool = TavilyToolSpec(api_key=tavily_ai_apikey)

class CorrectiveRAGPack:: run()

class CorrectiveRAGPack(BaseLlamaPack):
    ...
    ...
    def run(self, query_str: str, **kwargs: Any) -> Any:
        """Run the pipeline."""
        # Retrieve nodes based on the input query string.
        retrieved_nodes = self.retrieve_nodes(query_str, **kwargs)

        # Evaluate the relevancy of each retrieved document in relation to the query string.
        relevancy_results = self.evaluate_relevancy(retrieved_nodes, query_str)
        # Extract texts from documents that are deemed relevant based on the evaluation.
        relevant_text = self.extract_relevant_texts(retrieved_nodes, relevancy_results)

        # Initialize search_text variable to handle cases where it might not get defined.
        search_text = ""

        # If any document is found irrelevant, transform the query string for better search results.
        if "no" in relevancy_results:
            transformed_query_str = self.transform_query_pipeline.run(
                query_str=query_str
            ).message.content
            # Conduct a search with the transformed query string and collect the results.
            search_text = self.search_with_transformed_query(transformed_query_str)

        # Compile the final result. If there's additional search text from the transformed query,
        # it's included; otherwise, only the relevant text from the initial retrieval is returned.
        if search_text:
            return self.get_result(relevant_text, search_text, query_str)
        else:
            return self.get_result(relevant_text, "", query_str)

위 코드와 표준 CRAG 프로세스에는 세 가지 주요 차이점이 있다: 

Diff 1. 모호한 문서에 대한 판단이나 처리가 없다.

Diff 2. 훈련된 T5-large 모델을 사용하는 대신, LLM을 사용하여 검색된 정보를 평가한다.

Diff 3. 지식 정제 과정이 생략되었다

하지만 이러한 차이들에도 불구하고, Llama Index는 비슷한 아이디어를 통해 CRAG 개념을 구현한다. 

 

Use LLM to Evaluate Retrieved Information

class CorrectiveRAGPack(BaseLlamaPack):
    ...
    ...
    def evaluate_relevancy(
        self, retrieved_nodes: List[Document], query_str: str
    ) -> List[str]:
        """Evaluate relevancy of retrieved documents with the query."""
        relevancy_results = []
        for node in retrieved_nodes:
            relevancy = self.relevancy_pipeline.run(
                context_str=node.text, query_str=query_str
            )
            relevancy_results.append(relevancy.message.content.lower().strip())
        return relevancy_results

LLM을 호출하는데 사용되는 코드는 다음과 같다. 

DEFAULT_RELEVANCY_PROMPT_TEMPLATE = PromptTemplate(
    template="""As a grader, your task is to evaluate the relevance of a document retrieved in response to a user's question.
    Retrieved Document:
    -------------------
    {context_str}
    User Question:
    --------------
    {query_str}
    Evaluation Criteria:
    - Consider whether the document contains keywords or topics related to the user's question.
    - The evaluation should not be overly stringent; the primary objective is to identify and filter out clearly irrelevant retrievals.
    Decision:
    - Assign a binary score to indicate the document's relevance.
    - Use 'yes' if the document is relevant to the question, or 'no' if it is not.
    Please provide your binary score ('yes' or 'no') below to indicate the document's relevance to the user question."""
)

Rewrite query for search

웹 검색은 사용자의 입력 쿼리를 직접 사용하지 않는다. 대신 프롬프트를 구성하여 GPT-3.5 Turbo에 몇 샷(few-shot) 방식으로 제시하여 검색 쿼리를 얻는다:

DEFAULT_TRANSFORM_QUERY_TEMPLATE = PromptTemplate(
    template="""Your task is to refine a query to ensure it is highly effective for retrieving relevant search results. \n
    Analyze the given input to grasp the core semantic intent or meaning. \n
    Original Query:
    \n ------- \n
    {query_str}
    \n ------- \n
    Your goal is to rephrase or enhance this query to improve its search performance. Ensure the revised query is concise and directly aligned with the intended search objective. \n
    Respond with the optimized query only:"""
)

 

The difference between CRAG and self-RAG

과정 관점에서 보면, self-RAG는 검색 없이 LLM을 사용하여 직접 응답을 제공할 수 있는 반면, CRAG는 평가 레이어를 추가하기 전에 검색을 수행해야 한다. 구조적 관점에서 self-RAG는 CRAG보다 더 복잡하며, 더 복잡한 훈련 절차와 생성 단계에서 여러 레이블 생성 및 평가를 요구하여 추론 비용이 불가피하게 증가한다. 따라서 CRAG는 self-RAG보다 더 경량이며, 대부분의 상황에서 self-RAG보다 더 우수하다.

Overall evaluation results on the test sets of four datasets, https://miro.medium.com/v2/resize:fit:786/format:webp/1*5bLhCWD2mDrgmprS1om__A.png

Improvement of the Retrieval Evaluator

검색 평가기는 점수 매기는 분류 모델로 볼 수 있으며, 쿼리와 문서의 관련성을 판단하는 데 사용된다. 이는 RAG의 재랭킹 모델과 유사하며, 관련성 판단 모델은 실제 시나리오와 맞아떨어지는 더 많은 기능을 통합함으로써 개선될 수 있다. 예를 들어, 과학 논문 질의응답 RAG는 많은 전문 용어를 포함하고 있는 반면, 관광 분야의 RAG는 더 구어체의 사용자 쿼리를 포함하는 경향이 있다.

Improvement in the training of the retrieval evaluator within CRAG through the integration of additional features, https://miro.medium.com/v2/resize:fit:786/format:webp/1*QCTaBsirXX12IxxHPUWnUQ.png

T5-Large로 얻은 결과를 고려할 때, 경량 모델들도 좋은 결과를 얻을 수 있는 것으로 보이며 이는 0소규모 팀이나 회사에서 CRAG를 적용하는 데 희망을 제공한다.

 

Scores and Thresholds of the Retrieval Evaluator

앞서 언급했듯이, 임계값은 다양한 유형의 데이터에 따라 다르다. 또한, 모호한 경우와 잘못된 경우에 대한 임계값이 기본적으로 -0.9 주변에 있다는 것을 알 수 있다. 이는 검색된 지식 대부분이 쿼리와 관련이 있음을 시사하며, 이 검색된 지식을 전적으로 버리고 웹 검색에만 의존하는 것은 바람직하지 않을 수 있다.


Ref. 

https://medium.com/towards-artificial-intelligence/advanced-rag-02-unveiling-pdf-parsing-b84ae866344e

'AI > Natural Language Processing' 카테고리의 다른 글

Enhancing Global Understanding  (0) 2024.06.19
Query Classification and Refinement  (2) 2024.06.18
Prompt Compression (이론)  (0) 2024.06.18
Self-RAG (이론)  (0) 2024.06.17
Exploring RAG for Tables (이론)  (0) 2024.06.14

블로그의 정보

코딩하는 오리

Cori

활동하기