'finetuning'에 해당되는 글 2건

프롬프트 튜닝

AI/AI 툴 2025. 4. 22. 08:24

이론적 소개

프롬프트 튜닝은 효율적인 매개변수 미세 조정 방법입니다.

핵심 아이디어는 다음과 같습니다.

지식이 풍부한 교과서(사전 훈련된 대형 모델)를 수정하는 대신 기술자는 책의 시작 부분(입력 계층)에 몇 개의 매우 똑똑하고 학습하기 쉬운 스티키 노트 (소프트 프롬프트/가상 토큰(소프트 프롬프트) 또는 가상 토큰)를 추가합니다.

스터키 노트의 내용은 고정된 텍스트가 아니라, 모델이 스스로 학습하고 조정할 수 있는 매개변수(벡터)입니다.

훈련하는 동안 우리는 원래 모델의 대부분의 매개변수를 동결하고 새로 추가된 스티키 노트 매개변수만 훈련합니다.

그러면 모델이 특정 스티키 노트를 볼 때 우리가 예상하는 방식으로 작업을 수행하게 됩니다.

핵심 원리 다이어그램

PLM(사전 학습된 모델)은 변경되지 않고, W(모델 가중치)는 변경되지 않으며, X(모델 입력)는 변경됩니다.

작업 관련 프롬프트 템플릿을 설계하고 프롬프트 임베딩을 미세 조정하여 사전 훈련된 모델이 특정 작업에 적응하도록 안내합니다. 전체 모델 매개변수 대신 소수의 프롬프트(프롬프트 임베딩)만 미세 조정하면 됩니다.

기존의 미세 조정에 비해 다음과 같은 장점이 있습니다.

  • • 빠른 학습 및 리소스 절약: 매우 적은 매개변수만 학습됩니다.
  • • 작은 저장소: 각각의 새로운 작업은 전체 모델이 아닌, 작은 부착 메모 매개변수만 저장하면 됩니다.
  • • 우수한 결과: 많은 작업에서 Prompt-Tuning은 전체 미세 조정과 비슷한 결과를 얻을 수 있습니다.
  • • 원래 모델은 영향을 받지 않습니다. 기본 모델은 변경되지 않으므로 다양한 작업에 맞게 다양한 부착 메모를 쉽게 장착할 수 있습니다.

코드를 통한 원칙 해석

이 코드는 Prompt-Tuning의 전체 구현을 보여주고 작동 방식을 설명합니다. (운영 환경에는 PEFT 패키지를 설치하기 위한 pip가 필요하며, 이 코드에서 사용된 PEFT 버전은 0.14.0입니다.)

프롬프트 튜닝 방법은 주로 네 번째와 여덟 번째 단계 에 반영되어 있으므로 주의 깊게 읽어야 합니다. 

1단계: 관련 패키지 가져오기

import torch
from datasets import Dataset
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer
from peft import PromptTuningConfig, get_peft_model, TaskType, PromptTuningInit, PeftModel

2단계: 데이터 세트 로드

# Contains 'instruction', 'input' (optional extra input), 'output' (expected answer)
ds = Dataset.load_from_disk("../data/alpaca_data_zh/")

3단계: 데이터 세트 전처리

각 샘플을 input_ids, attention_mask, label을 포함하는 사전으로 처리합니다.

tokenizer = AutoTokenizer.from_pretrained("D:\\git\\model-download\\bloom-389m-zh") 
defprocess_func(example):

    MAX_LENGTH = 256

    
    prompt = "\n".join(["Human: " + example["instruction"], example["input"]]).strip() + "\n\nAssistant: "
    
    instruction_tokenized = tokenizer(prompt, add_special_tokens=False)
    
    response_tokenized = tokenizer(example["output"] + tokenizer.eos_token, add_special_tokens=False)
    
    input_ids = instruction_tokenized["input_ids"] + response_tokenized["input_ids"]
    
    attention_mask = instruction_tokenized["attention_mask"] + response_tokenized["attention_mask"]
    
    labels = [-100] * len(instruction_tokenized["input_ids"]) + response_tokenized["input_ids"]
    
     # Truncate
    iflen(input_ids) > MAX_LENGTH:
        input_ids = input_ids[:MAX_LENGTH]
        attention_mask = attention_mask[:MAX_LENGTH]
        labels = labels[:MAX_LENGTH]

    # Return the processed data
    return {
        "input_ids": input_ids,
        "attention_mask": attention_mask,
        "labels": labels
    }
# The .map() method applies the processing function to all samples in the entire dataset.
tokenized_ds = ds.map(process_func, remove_columns=ds.column_names)    # `remove_columns` removes the original columns, keeping only the new columns returned by process_func.    
print("\nChecking the results of the 2nd data process:")
print("Input sequence (input_ids decoded):", tokenizer.decode(tokenized_ds[1]["input_ids"]))
target_labels = list(filter(lambda x: x != -100, tokenized_ds[1]["labels"])) 
print("Sequence of labels (labels decoded, after filtering -100):", tokenizer.decode(target_labels))

4단계: 모델 및 PEFT 구성 만들기

이 단계는 Prompt-Tuning의 핵심 단계 입니다 . "가상 프롬프트 단어"를 초기화하려면 텍스트를 삽입해야 합니다.

텍스트가 분할된 후, 해당 단어 벡터(임베딩)가 가상 프롬프트 단어의 초기값으로 사용됩니다.

마지막 항목은 num_virtual_tokens가상 프롬프트 단어 임베딩 벡터의 개수를 나타내며 학습이 필요한 유일한 매개변수입니다.

model = AutoModelForCausalLM.from_pretrained("D:\\git\\model-download\\bloom-389m-zh") 
# Configure Prompt Tuning
config = PromptTuningConfig(
    task_type=TaskType.CAUSAL_LM,    # causal language model
    prompt_tuning_init=PromptTuningInit.TEXT,    # PromptTuningInit.TEXT means to initialize the “virtual tuning word” with a text embedding, which is better than random initialization.
    prompt_tuning_init_text="The following is a dialog between a human and a robot.",     # Segment the text and use the corresponding word vector (embedding) as the initial value for the virtual prompt.
    num_virtual_tokens=len(tokenizer("The following is a conversation between a human and a robot")["input_ids"]),  # The number of virtual tokens, equal to the length of the initialized text split, this `num_virtual_tokens` embedding vector of virtual tokens, is the only parameter to be trained!
    tokenizer_name_or_path="D:\\git\\model-download\\bloom-389m-zh"
)

# Apply the Prompt Tuning configuration to the base model via the `get_peft_model` function, which adds the learnable Prompt Encoder inside the model and automatically freezes all other parameters of the base model.
model = get_peft_model(model, config)
# Look at the changes to the model structure, there will be an additional prompt_encoder section
print(“PEFT model structure:”, model)
# Check trainable parameters: print and compare the number of trainable parameters to the total number of parameters.
model.print_trainable_parameters()   # 'trainable parameters' is much smaller than 'all parameters'.

5단계: 훈련 매개변수 구성

args = TrainingArguments(
    output_dir=". /chatbot_prompt_tuning_explained_zh",  
    per_device_train_batch_size=1,  
    gradient_accumulation_steps=8, # gradient_accumulation: equivalent to an effective batch size of 1 * 8 = 8, useful for cases with limited video memory
    logging_steps=10, # print logging information (e.g., loss) every 10 steps of training
    num_train_epochs=1, # number of training rounds
    save_steps=100, # Save model checkpoints for every 100 training steps
    # learning_rate=1e-3, # Prompt Tuning can usually use a slightly larger learning rate than full fine-tuning
    # gradient_checkpointing=True, # Can save memory, a little slower, can be turned on if memory is low.
)

6단계: 트레이너 만들기

trainer = Trainer(
    model=model,
    args=args,
    tokenizer=tokenizer,
    train_dataset=tokenized_ds, 
    data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),# Data organizer: responsible for composing a batch of samples from the dataset with the necessary padding.
)

7단계: 모델 학습

trainer.train() # Optimize only the parameters of the virtual token, the weights of the base model are frozen.

8단계: 모델 추론 및 효과 표시

프롬프트 튜닝 추론:

  1. 1. 추론 중에 훈련된 가상 토큰은 자동으로 입력 시퀀스에 추가됩니다.
  2. 2. 가상 토큰은 모델에 특정 스타일이나 콘텐츠의 응답을 생성하도록 지시하는 암시적 힌트를 제공하는 것과 같습니다.
  3. 3. 사용자는 이러한 가상 토큰을 볼 필요가 없으며, 모델 내에서만 작동합니다.

표준 프로세스:

  1. 1. 원래 기본 모델을 로드합니다.
  2. 2. 훈련된 PEFT 어댑터 가중치를 장착합니다.
  3. 3. PeftModel.from_pretrained두 가지를 결합하는 데 사용합니다.
# 1. Load the base model
base_model = AutoModelForCausalLM.from_pretrained("D:\\git\\\model-download\\bloom-389m-zh")

# 2. Specify the directory where the PEFT adapter weights are located
peft_model_path = ". /chatbot_prompt_tuning_explained_zh/checkpoint-3357/"

# 3. load the PEFT adapter and apply it to the base model
peft_model = PeftModel.from_pretrained(model=base_model, model_id=peft_model_path)

if torch.cuda.is_available().
    peft_model = peft_model.cuda()
    print("Model has been moved to the GPU.")
print("The model has been moved to the GPU.") else.
    print("CUDA not detected, will run inference on CPU.")

# Prepare the input text
instruction = "What are the tips for the exam?"
input_text = ""
prompt = f "Human: {instruction}\n{input_text}".strip() + "\n\nAssistant: "
print(f"\nInput for reasoning Prompt:\n{prompt}")

# Segment the input text, convert it to tensors, and move it to the device (CPU or GPU) where the model resides
ipt = tokenizer(prompt, return_tensors="pt").to(peft_model.device)

# Use the `.generate()` method to generate the answer, the PEFT model automatically handles the injection of soft prompts
print("Generating responses...")
response_ids = peft_model.generate(**ipt, max_length=128, do_sample=True, top_k=50, top_p=0.95, temperature=0.7)

# Decode the generated token IDs back into text
full_response = tokenizer.decode(response_ids[0], skip_special_tokens=True) # `skip_special_tokens=True` removes special tokens like <|endoftext|>

# Focus on what comes after "Assistant: "
assistant_response = full_response.split("Assistant: ")[-1]
print(f"\n model generated response:\n{assistant_response}")

 

 

반응형
블로그 이미지

루이스파파

한계를 뛰어 넘어서..........

,

오늘날 정보 폭발 시대에 AI 시스템은 우리 삶의 모든 측면에 침투했습니다. 의료 및 건강 보조원부터 교육 튜터링 도구, 기업 지식 관리 로봇에 이르기까지 AI는 우리가 지식을 더 효율적으로 습득하고 처리하는 데 도움이 됩니다. 그러나 적용 시나리오가 점점 더 복잡해짐에 따라 기존 AI 시스템은 많은 과제에 직면하게 됩니다. 정말로 관련성 있는 답변을 생성하는 방법은 무엇일까요? 복잡하고 여러 차례 주고받는 대화를 이해하는 방법은? 오류 메시지를 자신있게 피하는 방법은? 이러한 문제는 RAG(Retrieval-Augmented Generation) 기반 시스템에서 특히 두드러집니다.

RAG는 ​​문서 검색 능력과 언어 생성의 유창성을 결합하여 시스템이 맥락에 따라 정보에 기반한 응답을 제공할 수 있도록 합니다. 그러나 기본 RAG 시스템은 복잡한 질의, 여러 차례의 대화, 도메인별 전문 지식을 처리할 때 종종 실패하며, 종종 "환각"(잘못된 정보 생성)을 경험하거나 맥락을 놓치는 경우가 많습니다. 그렇다면 RAG 시스템을 어떻게 업그레이드하여 더욱 스마트하고 안정적으로 만들 수 있을까요? 오늘은 첨단 RAG 기술을 통해 질의응답 시스템의 성능을 개선하는 방법을 알아보겠습니다!

기본 RAG의 한계

먼저 기본 RAG 시스템의 아키텍처를 살펴보겠습니다. 작업 흐름은 대략 다음과 같습니다. 먼저 문서가 로드되고 다양한 블록 기술을 통해 작은 블록으로 분할됩니다. 그런 다음 이러한 작은 블록이 임베딩 모델을 사용하여 벡터로 변환되고 벡터 데이터베이스에 저장됩니다. 사용자가 질문을 하면 시스템은 벡터 데이터베이스에서 해당 질문과 관련된 문서 조각을 검색하고, 이 조각들을 질문과 함께 언어 모델에 전달한 후 마지막으로 답변을 생성합니다.

간단하죠? 하지만 이러한 단순성 때문에 기본 RAG 시스템에는 많은 문제가 발생합니다.

  • 환각 문제 : 모델은 원본 문서와 관련이 없거나 심지어 잘못된 콘텐츠를 생성할 수 있습니다. 정확성이 가장 중요한 의학이나 법률 분야에서는 이는 치명적인 결함이 될 수 있습니다.
  • 도메인 특이성 부족 : 기본 RAG 시스템은 특정 도메인의 복잡한 주제를 다룰 때 종종 실패하는데, 이는 관련성이 없거나 부정확한 정보를 검색하기 때문입니다.
  • 여러 라운드로 구성된 대화의 딜레마 : 여러 라운드로 구성된 대화에서는 기본 RAG 시스템이 맥락을 쉽게 잃어버려 사용자 요구를 충족할 수 없는 단편적인 답변이 나올 수 있습니다.

그러면 이러한 한계를 어떻게 극복할 수 있을까? 이를 위해서는 RAG 시스템의 각 링크(인덱싱, 검색, 생성)를 최적화하고 업그레이드하기 위한 고급 RAG 기술의 도입이 필요합니다.

인덱싱 및 청킹: 견고한 기반 구축

좋은 지수는 RAG 시스템의 핵심입니다. 먼저, 데이터를 효율적으로 가져오고, 분할하고, 저장하는 방법을 고려해야 합니다. 다음으로, 몇 가지 고급 인덱싱 및 청킹 방법을 살펴보겠습니다.

1. HNSW: 효율적인 검색을 위한 강력한 도구

HNSW(Hierarchical Navigable Small Worlds) 알고리즘은 대규모 데이터 세트에서 유사한 항목을 빠르게 찾을 수 있는 강력한 도구입니다. 근사 최근접 이웃(ANN)을 효율적으로 찾기 위해 그래프 기반 구조를 구축합니다. 구체적으로 다음과 같은 주요 특징이 있습니다.

  • 근접 그래프 : HNSW는 각 지점이 근처 지점에 연결된 그래프를 구축하여 검색 프로세스를 보다 효율적으로 만듭니다.
  • 계층적 구조 : 이 알고리즘은 점들을 여러 층으로 구성하는데, 맨 위 층은 멀리 떨어진 점들을 연결하고 맨 아래 층은 가까이 있는 점들을 연결하여 검색 속도를 높입니다.
  • 탐욕적 라우팅 : HNSW는 검색할 때 상위 계층의 한 지점에서 시작하여 점차 하위 계층으로 이동하여 국소 최소값을 찾습니다. 이를 통해 유사한 항목을 찾는 데 필요한 시간이 크게 줄어듭니다.

실제 응용 프로그램에서는 매개변수(예: 각 노드의 이웃 수, 그래프를 구축할 때 고려하는 이웃 수 등)를 설정하여 HNSW의 성능을 최적화할 수 있습니다. HNSW를 사용하면 방대한 데이터에서 사용자 질문과 가장 관련성이 높은 문서 조각을 빠르고 정확하게 찾을 수 있으며, 이를 통해 후속 답변 생성을 위한 견고한 기반을 제공할 수 있습니다.

HNSW의 실무 경험

다음으로, 코드를 통해 HNSW 알고리즘을 구현해 보겠습니다. 여기서는 FAISS 라이브러리를 사용합니다. 이는 효율적인 유사성 검색 라이브러리이며 HNSW와 함께 사용하기에 매우 적합합니다.

import faiss
import numpy as np

# HNSW 인자 설정
d = 128  # 벡터의 차원
M = 32   # 노드당 이웃 수

# HNSW 인덱스 초기화
index = faiss.IndexHNSWFlat(d, M)

# efConstruction 인자를 설정하여 인덱스를 작성할 때 고려할 이웃의 수를 제어합니다
efConstruction = 200
index.hnsw.efConstruction = efConstruction

# 랜덤 데이터를 생성하고 색인에 추가하기
n = 10000  # 인덱싱할 벡터의 수
xb = np.random.random((n, d)).astype('float32')
index.add(xb)  # 인덱스 만들기

# efSearch 인자를 설정하여 검색 과정에 영향을 줍니다.
efSearch = 100
index.hnsw.efSearch = efSearch

# 검색 실행
nq = 5 # 쿼리 벡터의 수
xq = np. random. random((nq, d)). astype('float32')
k = 5 # 가장 가까운 이웃의 수 검색하기
distances, indices = index. search(xq, k)

# 결과 출력
print('쿼리 벡터:\n", xq)
print("\n가장 가까운 이웃 색인:\n", indices)
print("\n가장 가까운 이웃:\n", distances)

 

위의 코드를 통해 HNSW가 대규모 데이터 세트를 처리하는 데 있어 효율성과 정확성을 얼마나 높이는지 확인할 수 있습니다. 이를 통해 쿼리 벡터와 가장 유사한 문서 조각을 빠르게 찾아 후속 언어 모델 생성 프로세스에 고품질 입력을 제공할 수 있습니다.

2. 의미적 청킹: 정보를 더 의미 있게 만들기

기존의 청킹 방법은 일반적으로 고정된 크기에 따라 텍스트를 분할하지만, 이 접근 방식은 완전한 개념이나 정보를 조각낼 수 있습니다. 의미적 청킹은 다릅니다. 이는 텍스트를 의미에 따라 여러 개의 덩어리로 나누고, 각 덩어리는 일관된 정보 단위를 나타냅니다. 구체적인 작업은 문장 임베딩 사이의 코사인 거리를 계산하고, 두 문장이 의미적으로 유사한 경우(특정 임계값 아래) 동일한 블록으로 그룹화하는 것입니다. 이 접근 방식의 장점은 보다 의미 있고 일관된 청크를 생성하여 검색 정확도를 향상시킬 수 있다는 것입니다. 하지만 BERT 등의 기반 인코더를 사용해야 하므로 계산 비용이 상대적으로 높습니다.

 

의미적 청킹의 실습

다음으로, 코드를 통해 의미적 분할을 구현합니다. 여기서는  OpenAI의 임베딩 모델을 활용하여 의미적 청킹을 구현하는 LangChain 라이브러리의 클래스를 사용합니다  .SemanticChunker

from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai.embeddings import OpenAIEmbeddings

# 의미 분류기 초기화
text_splitter = SemanticChunker(OpenAIEmbeddings())

# 문서를 의미 관련 조각으로 나누기
docs = text_splitter.create_documents([document])
print(docs[0].page_content)

위의 코드에서 우리는 의미적 청킹을 통해 텍스트의 의미적 내용을 기반으로 더욱 의미 있는 청크를 생성할 수 있다는 것을 알 수 있습니다. 이는 후속 검색 및 생성 단계에 매우 유용합니다.

3. 언어 모델 기반 청킹: 텍스트 구조를 정확하게 포착

이 접근 방식은 강력한 언어 모델(예: 70억 개의 매개변수를 가진 모델)을 사용하여 텍스트를 처리하고, 완전한 문장으로 분할한 다음, 이 문장들을 블록으로 결합합니다. 이를 통해 각 블록의 무결성을 보장할 뿐만 아니라 맥락 정보도 고려합니다. 이 방법은 계산 집약적이기는 하지만, 텍스트의 구체적인 내용에 따라 세분화 방법을 유연하게 조정하고 고품질 세그먼트를 생성할 수 있습니다. 특히 텍스트 구조에 대한 요구 사항이 높은 애플리케이션 시나리오에 적합합니다.

언어 모델 기반 청킹의 실습

다음으로, 코드를 통해 언어 모델 기반 청킹을 구현합니다. 여기서는 OpenAI의 GPT-4o 모델을 사용하여 비동기 호출을 통해 각 블록에 대한 컨텍스트 정보를 생성합니다.

import asyncio
from langchain_openai import ChatOpenAI

async def generate_contexts(document, chunks):
    async def process_chunk(chunk):
        response = await client.chat.completions.create(
            model="gpt-4o",
            messages=[
                {"role": "system", "content": "Generate a brief context explaining how this chunk relates to the full document."},
                {"role": "user", "content": f"<document> \n{document} \n</document> \nHere is the chunk we want to situate within the whole document \n<chunk> \n{chunk} \n</chunk> \nPlease give a short succinct context to situate this chunk within the overall document for the purposes of improving search retrieval of the chunk. Answer only with the succinct context and nothing else."}
            ],
            temperature=0.3,
            max_tokens=100
        )
        context = response.choices[0].message.content
        return f"{context} {chunk}"
    
    # 모든 조각들을 병렬로 처리합니다.
    contextual_chunks = await asyncio.gather(
        *[process_chunk(chunk) for chunk in chunks]
    )
    return contextual_chunks

위의 코드를 통해 언어 모델 기반 청킹을 통해 고품질 청크를 생성하고 각 청크에 대한 컨텍스트 정보를 생성할 수 있다는 것을 알 수 있습니다. 이는 후속 검색 및 생성 단계에 매우 유용합니다.

4. 메타데이터 활용: 검색에 더 많은 맥락을 추가하세요

메타데이터는 날짜, 환자 나이, 과거 병력 등과 같은 문서에 추가적인 맥락적 정보를 제공할 수 있습니다. 검색할 때 이러한 메타데이터를 필터링하면 관련 없는 정보를 제외하고 검색 결과를 더 정확하게 만들 수 있습니다. 예를 들어, 의료 분야에서 어린이와 관련된 콘텐츠를 쿼리하는 경우 18세 이상 환자의 기록을 직접 필터링할 수 있습니다. 인덱싱 중에 텍스트와 함께 메타데이터를 저장하면 검색의 효율성과 관련성을 크게 향상시킬 수 있습니다.

메타데이터를 활용한 실습

다음으로, 코드를 통해 메타데이터의 사용을 구현합니다. 여기서는 LangChain 라이브러리의 클래스를 사용하여  Document 문서에 메타데이터를 저장할 수 있습니다.

from langchain_core.documents import Document

# 메타데이터가 있는 문서 만들기
doc = Document(
    page_content="This is a sample document.",
    metadata={"id": "doc1", "source": "https://example.com"}
)

# 문서 내용과 메타데이터
print(doc.page_content)
print(doc.metadata)

위의 코드에서 우리는 메타데이터가 문서에 대한 더욱 맥락적인 정보를 제공할 수 있다는 것을 알 수 있는데, 이는 이후의 검색 및 생성에 매우 유용합니다.

검색: 핵심 정보 정확히 파악

검색은 RAG 시스템의 핵심 링크로, 방대한 데이터에서 사용자 질문과 실제로 관련성이 있는 문서를 찾을 수 있는지 여부를 결정합니다. 다음으로, 검색 성능을 개선하기 위한 몇 가지 기술을 살펴보겠습니다.

5. 하이브리드 검색: 의미론과 키워드의 완벽한 조합

하이브리드 검색은 벡터 검색(의미 검색)과 키워드 검색을 결합하여 두 가지의 장점을 최대한 활용합니다. AI 기술과 같은 일부 분야에서는 알고리즘 이름, 기술 용어 등 구체적인 키워드인 용어가 많습니다. 벡터 검색만 하면 이러한 중요한 정보를 놓칠 수 있지만, 키워드 검색을 하면 이러한 주요 용어가 고려됩니다. 두 가지 검색을 동시에 실행하고 가중치 시스템에 따라 결과를 결합하고 정렬하면 더욱 포괄적이고 정확한 검색 결과 목록을 얻을 수 있습니다.

하이브리드 검색에 대한 실무 경험

다음으로, 코드를 통해 하이브리드 검색을 구현합니다. 여기서는  Weaviate 벡터 데이터베이스의 벡터 검색과 키워드 검색 기능을 결합한 LangChain 라이브러리의 클래스를 사용합니다  .WeaviateHybridSearchRetriever

from langchain_community.retrievers import WeaviateHybridSearchRetriever

# 혼합 검색기 초기화
retriever = WeaviateHybridSearchRetriever(
    client=client,
    index_name="LangChain",
    text_key="text",
    attributes=[],
    create_schema_if_missing=True,
)

# 혼합 검색 실행
results = retriever.invoke("the ethical implications of AI")
print(results)

위의 코드를 통해 하이브리드 검색은 벡터 검색과 키워드 검색의 장점을 결합하여 더욱 포괄적이고 정확한 검색 결과를 생성할 수 있음을 알 수 있습니다.

6. 쿼리 재작성: 질문을 더 "친숙하게" 만들기

사람들이 묻는 질문은 데이터베이스나 언어 모델이 이해하기에 가장 적합한 형태가 아닌 경우가 많습니다. 언어 모델을 사용하여 쿼리를 다시 작성하면 검색 효과를 크게 개선할 수 있습니다. 예를 들어, "AI 에이전트란 ​​무엇이고 왜 2025년의 차세대 주요 기술일까요?"라는 내용을 "AI 에이전트의 2025년 주요 기술"로 다시 쓰는 것이 데이터베이스의 검색 논리에 더 부합합니다. 또한, 프롬프트 단어를 다시 작성하여 언어 모델과의 상호 작용을 최적화하여 결과의 ​​품질과 정확성을 개선할 수 있습니다.

실습 쿼리 재작성

다음으로, 코드를 통해 쿼리를 다시 쓰는 것을 구현합니다. 여기서는  LangChain 라이브러리의 클래스를 사용하여  OpenAI의 언어 모델을 사용하여 쿼리를 다시 작성할 수 있습니다.ChatOpenAI

from langchain_openai import ChatOpenAI

# 언어모델 초기화
chatgpt = ChatOpenAI(model_name="gpt-4o", temperature=0)

# 조회 다시 쓰기
query = "what are AI agents and why they are the next big thing in 2025"
rewritten_query = chatgpt.invoke(query)
print(rewritten_query)

위의 코드를 통해 쿼리를 다시 작성하면 사람의 질문을 데이터베이스와 언어 모델 이해에 더 적합한 형태로 변환하여 검색 효과를 향상시킬 수 있다는 것을 알 수 있습니다.

7. 다중 쿼리 검색: 다양한 각도에서 정보 마이닝

검색 결과는 질의의 표현에 따라 크게 달라질 수 있습니다. 다중 쿼리 검색기는 대규모 언어 모델(LLM)을 사용하여 사용자 입력을 기반으로 다양한 각도에서 여러 쿼리를 생성한 다음, 각 쿼리에 대한 관련 문서를 별도로 검색하고 마지막으로 모든 쿼리의 결과를 집계하여 더 광범위한 관련 문서 컬렉션을 제공합니다. 이 접근 방식을 사용하면 광범위한 수동 조정이 필요하지 않고도 유용한 정보를 찾을 확률이 높아집니다.

다중 쿼리 검색을 통한 실습

다음으로, 코드를 통해 다중 쿼리 검색을 구현합니다. 여기서는 LangChain 라이브러리의 클래스를 사용하여  MultiQueryRetriever OpenAI의 언어 모델을 사용하여 여러 쿼리를 생성하고 Chroma 벡터 데이터베이스에서 관련 문서를 검색할 수 있습니다.

from langchain.retrievers.multi_query import MultiQueryRetriever

# 다중 쿼리 검색기 초기화
mq_retriever = MultiQueryRetriever.from_llm(
    retriever=similarity_retriever3, llm=chatgpt,
    include_original=True
)

# 다중 조회 검색 실행
query = "what is the capital of India?"
docs = mq_retriever.invoke(query)
print(docs)

위의 코드에서 볼 수 있듯이 다중 쿼리 검색은 여러 각도에서 쿼리를 생성하고 벡터 데이터베이스에서 관련 문서를 검색하여 유용한 정보를 찾을 확률을 높일 수 있습니다.

생성: 고품질 답변 생성

마지막으로 RAG 시스템의 생성에 대해 알아보겠습니다. 이 단계의 목표는 관련 없는 정보로 인한 "환각"을 피하기 위해 질문과 최대한 관련성이 높은 맥락을 언어 모델에 제공하는 것입니다. 빌드 품질을 개선하기 위한 몇 가지 팁을 소개합니다.

8. 자동 자르기: 관련 없는 정보 제거

자동 정리 기술을 사용하면 질문과 관련이 없는 데이터베이스에서 검색된 정보를 걸러내고 언어 모델이 잘못 이해되는 것을 방지할 수 있습니다. 구체적인 작업은 검색 중에 유사도 점수를 기반으로 상당히 감소하는 임계점을 찾고, 이 임계점보다 낮은 점수를 가진 객체를 제외하여 언어 모델에 전달되는 정보가 가장 관련성이 높도록 하는 것입니다.

자동 자르기 실습

다음으로, 코드를 통해 자동 자르기를 구현합니다. 여기서는 LangChain 라이브러리의 클래스를 사용하는데  PineconeVectorStore , 이를 통해 Pinecone 벡터 데이터베이스를 사용하여 유사성 검색을 수행하고 유사성 점수에 따라 정보를 필터링할 수 있습니다.

from langchain_pinecone import PineconeVectorStore
from langchain_openai import OpenAIEmbeddings

# 벡터 메모리 초기화
vectorstore = PineconeVectorStore.from_documents(
    docs, index_name="sample", embedding=OpenAIEmbeddings()
)

# 유사도 검색 및 유사도 점수 획득
docs, scores = vectorstore.similarity_search_with_score("dinosaur")
for doc, score in zip(docs, scores):
    doc.metadata["score"] = score
print(docs)

위의 코드에서 자동 자르기를 통해 유사도 점수에 따라 관련 없는 정보를 걸러내고, 이를 통해 언어 모델에 전달되는 정보의 품질을 향상시킬 수 있음을 알 수 있습니다.

9. 재정렬: 중요한 정보의 우선순위를 정하세요

재순위 매기기 기술은 보다 진보된 모델(일반적으로 교차 인코더)을 사용하여 처음 검색된 객체를 다시 평가하고 순위를 매깁니다. 이는 쿼리와 각 객체 간의 쌍별 유사성을 고려하고, 관련성을 다시 결정하고, 가장 관련성이 높은 문서를 맨 위에 놓습니다. 이런 방식으로 언어 모델이 수신하는 데이터의 품질이 높아지고, 생성된 답변의 정확도가 높아집니다.

실습 재정렬

다음으로, 코드를 통해 재정렬을 구현합니다. 여기서는 LangChain 라이브러리의 클래스를 사용하는데  FlashrankRerank , 이를 통해 고급 모델을 사용하여 검색된 문서를 다시 평가하고 순위를 매길 수 있습니다.

from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import FlashrankRerank

# 초기화 재배열기
compressor = FlashrankRerank()
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor, base_retriever=retriever
)

# 정렬 실행
query = "What did the president say about Ketanji Jackson Brown"
compressed_docs = compression_retriever.invoke(query)
print([doc.metadata["id"] for doc in compressed_docs])
print(compressed_docs)

위의 코드에서 우리는 재순위 지정을 통해 쿼리와 문서 간의 유사성을 기반으로 재순위를 지정함으로써 언어 모델에 전달되는 정보의 품질을 향상시킬 수 있음을 알 수 있습니다.

10. 언어 모델 미세 조정: 모델이 해당 분야를 더 잘 이해하도록 합니다.

사전 훈련된 언어 모델을 미세 조정하면 검색 성능을 크게 향상시킬 수 있습니다. 특정 분야(예: 의학)에서는 관련 데이터(예: MedCPT 시리즈)로 사전 학습된 모델을 선택하고, 자체 데이터를 수집하고, 미세 조정을 위해 양성 및 음성 샘플 쌍을 만든 다음, 모델이 해당 분야의 특정 관계를 학습하도록 할 수 있습니다. 미세 조정된 모델은 도메인별 검색 및 생성 작업에서 더 나은 성능을 보입니다.

언어 모델 미세 조정의 실습

다음으로, 코드를 통해 언어 모델을 미세 조정하는 작업을 구현하겠습니다. 여기서는 LangChain 라이브러리의 클래스를 사용하는데  ChatOpenAI , 이를 통해 OpenAI의 언어 모델을 사용하여 세부 조정할 수 있습니다.

from langchain_openai import ChatOpenAI

# 언어모델 초기화
chatgpt = ChatOpenAI(model_name="gpt-4o", temperature=0)

# 미세 조정 언어 모델
# 여기에는 미세 조정을 위한 자체 데이터 세트를 제공해야 합니다.
# 예를 들어, 의학 분야의 데이터 세트를 사용하여 미세 조정합니다.
# fine_tuned_model = chatgpt.fine_tune(dataset)

위의 코드를 통해 언어 모델을 미세 조정하면 특정 분야에서 모델의 성능이 크게 향상되고, 생성된 답변의 품질도 향상되는 것을 알 수 있습니다.

고급 RAG 기술: AI 답변의 신뢰성을 더욱 높여줍니다

위에 나열된 일련의 고급 RAG 기술을 통해 인덱싱, 검색 및 생성 등 RAG 시스템의 각 링크를 최적화하고 업그레이드하여 시스템의 전반적인 성능을 개선할 수 있습니다. 의료 보조원, 교육 상담 도구, 기업 지식 관리 로봇 등 어떤 기술이든 이러한 기술은 AI 시스템이 복잡한 정보 요구 사항을 처리하는 데 더욱 능숙해지고, 더 정확하고 신뢰할 수 있으며 상황에 맞는 답변을 생성할 수 있도록 해줍니다.

간단히 말해, 응용 시나리오가 더욱 복잡해짐에 따라 AI 시스템은 계속해서 발전해야 합니다. 첨단 RAG 기술은 더욱 스마트하고 강력한 질의응답 시스템을 구축할 수 있는 효과적인 방법을 제공하며, AI를 지식 습득 및 문제 해결에 있어 실질적인 조수로 만들어줍니다!

반응형

'AI' 카테고리의 다른 글

2025년 4월 18일 AI 뉴스  (0) 2025.04.18
블로그 이미지

루이스파파

한계를 뛰어 넘어서..........

,