Chain-of-Thought Prompting은 복잡한 작업에서 언어 모델의 성능을 향상시키기 위해 사용되는 기술입니다. Chain-of-Thought Prompting은 언어 모델이 답을 찾아가는 과정에서 "생각을 말로 표현하거나(think aloud)" 단계별 추론 과정(step-by-step reasoning)을 따르도록 유도하는 방식으로 구성됩니다. 이 접근법은 수학 문제(math word problems), 복잡한 이해 문제(complex comprehension questions) 또는 주어진 데이터로부터 추론을 요구하는 문제(tasks that require drawing inferences from given data)와 같이 여러 단계나 추론의 레이어가 필요한 문제에 특히 유용합니다. 

Chain-of-Thought Prompting 에서는 문제를 제기하는 것뿐만 아니라 문제를 해결하기 위해 취할 수 있는 일련의 논리적 단계(a sequence of logical stemps)를 포함하여 프롬프트를 구성합니다. 이 방법을 통해 모델은 유사한 추론 라인을 따르도록 유도되며, 이는 정확하고 잘 설명된 답변에 도달할 가능성을 높입니다. 예를 들어, 수학 문제의 해답을 간단히 묻는 대신에, 문제를 접근하는 방법의 분해를 프롬프트에 포함시켜 모델이 이러한 사고 과정을 반영하도록 유도할 수 있습니다. 

 

아래에는 Standard Prompting와 Chain-of-Thought Prompting를 비교해서 예시를 들어놓은 것입니다. (논문에 제시된 예시)  우측 예시가 Chain-of-Thought Prompting 예시인데요, 파란색으로 색깔 칠해진 부분이 수학 문제를 푸는데 사용한 로직을 차근 차근 단계별로 풀어서 예시를 제시해주었더니, LLM 모델이 Output을 생성할 때 역시 차근 차근 단계를 밟아서 문제 풀이 로직을 설명해가면서 정확하게 답을 생성했습니다. 

 

 

[ Standard Prompting vs. Chain-of-Thought Prompting 예시 ]

Standard Prompting vs. Chain-of-Thought Prompting

* source: Jason Wei, et.al. "Chain-of-Thought Prompting Elicits Reasoning in Large Language Models", arXive:2201.11903v6, 2023

 

 

아래의 예시는 논문에 제시된 수학 (arithmetic), 상식 (common sense), 상징 추론 (symbolic reasoning) 영역의 Chain-of-Thought Prompting 예시입니다. (색깔 친 부분을 주시해서 보세요) 

 

 

[ Chain-of-Thought Prompting 예시 ]

Examples of Chain-of-Thought Prompting

* source: Jason Wei, et.al. "Chain-of-Thought Prompting Elicits Reasoning in Large Language Models", arXive:2201.11903v6, 2023

 

 

LLM 모델로는 ChatGPT-3.5와 ChatGPT-4 를 사용하여 Standard Prompting과 위에서 소개한 Chain-of-Thought Prompting 을 번갈아서 적용해서 수학 계산이 단계적으로 필요한 논리적인 문제를 제시해보겠습니다. 

 

1. Standard Prompting using ChatGPT-3.5

 

원하는 답변 대신에 "상식적으로 통근 시간이 말이 안되게 오래 걸린다"는 답변을 했네요. ㅋㅋ 

(동문서답?) 

 

[ User ] :
Which is a faster way to get to work?
Option 1: Take a 1000 minute bus, then a half hour train, and finally a 10 minute bike ride.
Option 2: Take an 800 minute bus, then an hour train, and finally a 30 minute bike ride. 


[ ChatGPT-3.5 ] :
Neither option seems practical for a daily commute to work, as both involve extremely long travel times. In a typical workday, commuting for over 10-16 hours (800-1000 minutes) would not be feasible for most people. You should explore alternative transportation options that would significantly reduce your commute time, such as finding a closer place to live or considering other modes of transportation like carpooling, using a car, or telecommuting if possible. A daily commute of 800-1000 minutes is not realistic for most people due to the excessive time spent on transportation. 

 

 

 

2. Chain-of-Thought using ChatGPT-3.5

 

이번에는 Chain-of-Thought Prompting을 사용해서 비슷한 문제의 단계적인 풀이 과정을 제시해주고, 본 문제를 냈을 때, 원하는 Output을 역시 단계적으로 차근 차근 풀어서 정확한 답을 생성했네요. ChatGPT 나 사람이나 비슷한거 같아요. 사람도 이런 문제 풀 때 보면 차근 차근 단계를 밟아가면서 풀잖아요. 

하여튼, 대규모 언어모델 (Large Language Model, LLM)이 거대해지면서 어느 순간 창발(emergence)이 일어나서 다방면의 과제를 잘 수행하는 면을 보이는데요, Chain-of-Thought Prompting 처럼 프롬프트에 단계적 사고 논리의 예시를 주는 것만으로도 LLM 모델이 이를 금방 이해해서 문제의 답을 생성하는것을 보면 참 놀랍습니다! 

 

[ User ] :
Which is a faster way to get home?

Option 1: Take an 10 minutes bus, then an 40 minute bus, and finally a 10 minute train.
Option 2: Take a 90 minutes train, then a 45 minute bike ride, and finally a 10 minute bus.
Option 1 will take 10+40+10 = 60 minutes.
Option 2 will take 90+45+10=145 minutes.
Since Option 1 takes 60 minutes and Option 2 takes 145 minutes, Option 1 is faster.

Which is a faster way to get to work?
Option 1: Take a 1000 minute bus, then a half hour train, and finally a 10 minute bike ride.
Option 2: Take an 800 minute bus, then an hour train, and finally a 30 minute bike ride. 


[ ChatGPT-3.5 ] :
Option 2 is the faster way to get to work.

Option 1:

1000 minutes bus
Half-hour (30 minutes) train ride
10-minute bike ride
Total time for Option 1: 1000 + 30 + 10 = 1040 minutes


Option 2:

800 minutes bus
1-hour (60 minutes) train ride
30-minute bike ride
Total time for Option 2: 800 + 60 + 30 = 890 minutes

Option 2 takes 890 minutes, while Option 1 takes 1040 minutes, making Option 2 the faster choice for getting to work. However, it's important to note that both options have extremely long commute times, and such long commutes are highly impractical in real-life situations. 

 

 

 

3. Standard Prompting using ChatGPT-4

 

ChatGPT-4 는 비록 Standard Prompting 을 사용해도 이 문제가 여러 단계를 차근 차근 거쳐서 문제를 논리적으로 풀어야 하는지를 알고 Python 을 실행시켜서 아래처럼 Python code를 짜서(!!) 문제를 풀고, 그 풀이 과정도 친절하게 설명해주고 있습니다. OpenAI 일 참 잘해요! 짝짝짝!! 추앙합니당~! 

(즉, ChatGPT-4에서는 Chain-of-Thought Prompting을 안해도 ChatGPT-4가 알아서 다 해줍니다!) 

 

[ User ]
Which is a faster way to get to work? 
Option 1: Take a 1000 minute bus, then a half hour train, and finally a 10 minute bike ride. 
Option 2: Take an 800 minute bus, then an hour train, and finally a 30 minute bike ride.

[ ChatGPT-4 ]

To determine which option is faster to get to work, we need to add up the total travel times for each option.

Option 1:

1000 minutes for the bus
30 minutes for the train
10 minutes for the bike ride

Option 2:
800 minutes for the bus
60 minutes (1 hour) for the train
30 minutes for the bike ride

Let's calculate the total travel time for each option.

The total travel time for Option 1 is 1040 minutes, and for Option 2, it's 890 minutes. Therefore, Option 2 is the faster way to get to work. ​

 

 

아래처럼 Python 이 자동 실행되어서 Python codes를 짜서 문제를 풀고, 이를 다시 자연어로 풀이과정과 답을 생성해줘요. 

 

## Python Codes
# Calculating the total travel time for each option

# Option 1
bus1 = 1000 # minutes
train1 = 30 # minutes
bike1 = 10 # minutes
total1 = bus1 + train1 + bike1

# Option 2
bus2 = 800 # minutes
train2 = 60 # minutes (1 hour)
bike2 = 30 # minutes
total2 = bus2 + train2 + bike2

total1, total2

Result
(1040, 890)

 

 

[ Reference ]

- source: Jason Wei, et.al. "Chain-of-Thought Prompting Elicits Reasoning in Large Language Models", arXive:2201.11903v6, 2023

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요. :-)

 

 

728x90
반응형
Posted by Rfriend
,

LangChain으로 LLM 모델을 사용한 애플리케이션을 만들 때 기존의 여러개 프롬프트를 재사용하고 조합해서 최종 프롬프트를 만들어서 사용해야 할 경우가 있습니다. 이때 LangChain의 PipelinePrompt 를 사용하면 되는데요, PipeliePrompt는 다음과 같이 Final promt, Pipeline prompts 의 두 개 부분으로 구성되어 있습니다. 

 

- (1) Final prompt: 최종적으로 반환되는 프롬프트. 

- (2) Pipeline prompts: string 이름과 prompt template 으로 구성되는 튜플의 리스트(a list of tuples). 각 프롬프트는 포맷이 정의되고, 동일한 이름의 변수로 파이프라인의 다음 template으로 전달됨. 

 

 

간단한 예를 들어보겠습니다. 

 

- (1) Final prompt: final_prompt

- (2) Pipeline prompts: introduction_prompt, example_prompt, start_prompt

- (3) Composing all together

       : PipelinePromptTemplate(

             final_prompt=final_prompt,

             pipeline_prompts=[

                 ("introduction", introduction_prompt),

                 ("example", example_prompt),

                 ("start", start_prompt)

             ])

 

 

LangChain - Pipeline Prompts

 

 

먼저, 터미널에서 pip install로 langchain, openai 모듈을 설치합니다. 

 

! pip install langchain openai

 

 

다음으로 실습에 필요한 모듈을 importing 합니다. 

from langchain.prompts.pipeline import PipelinePromptTemplate
from langchain.prompts import ChatPromptTemplate
from langchain.chat_models import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser

 

 

OpenAI의 Chat Model을 사용해서 답변을 생성할 것이므로 OPENAI API KEY를 환경변수에 등록합니다. 

 

import os 

os.environ["OPENAI_API_KEY"]="sk-xxxx..."

 

 

 

(1) Final prompt 생성

 

여러개의 하위 프롬프트를 구성요소로 가지는 최종 프롬프트를 작성합니다. (나중에 변수이름과 하위 구성요소 프롬프트를 튜플로 매핑시키고, 하나의 리스트로 묶어줍니다.)

 

# Final template and prompt
final_template = """{introduction}

{example}

{start}"""

final_prompt = ChatPromptTemplate.from_template(final_template)

 

 

 

(2) Pipeline prompts 생성

 

(2-1) instruction_prompt 생성

 

# Introduction template and prompt
introduction_template = """You are an expert on {topic}."""

introduction_prompt = ChatPromptTemplate.from_template(introduction_template)

 

 

(2-2) exmaple_prompt 생성

 

# Example template and prompt
example_template = """Here is an example of an interaction:

Input: {example_input}
Output: {example_output}"""

example_prompt = ChatPromptTemplate.from_template(example_template)

 

 

(2-3) start_prompt 생성

 

# Start template and prompt
start_template = """Now, do this for real. 

Input: {input}
Output:"""

start_prompt = ChatPromptTemplate.from_template(start_template)

 

 

 

(3) 여러개의 Pipeline prompts를 PipelinePrompt를 이용해서 하나의 Pipeline으로 조합하기    (Composing all together)  

 

# Composing multiple prompts together using PipelinePromptTemplate()
input_prompts = [
    ("introduction", introduction_prompt), 
    ("example", example_prompt), 
    ("start", start_prompt), 
]

pipeline_prompt = PipelinePromptTemplate(
    final_prompt=final_prompt, pipeline_prompts=input_prompts
)


pipeline_prompt.input_variables
# ['example_input', 'example_output', 'input', 'topic']

 

 

최종 pipeline_prompt를 구성하는 각 하위 프롬프트의 변수이름에 값을 입력해서 프린트해보았습니다. 이제 프롬프트는 준비가 다 되었습니다. 

 

print(
    pipeline_prompt.format(
        topic="Math", 
        example_input="It costs 2 dollar per apple. How much do you need in total to buy 10 apples?", 
        example_output="Since 2 x 10 = 20, you need a total of $20.", 
        input="How much does it cost to buy 6 cans of beer that cost $3 each?",
    )
)


# Human: [HumanMessage(content='You are an expert on Math.')]

# [HumanMessage(content='Here is an example of an interaction:\n\nInput: It costs 2 dollar per apple. How much do you need in total to buy 10 apples?\nOutput: Since 2 x 10 = 20, you need a total of $20.')]

# [HumanMessage(content='Now, do this for real. \n\nInput: How much does it cost to buy 6 cans of beer that cost $3 each?\nOutput:')]

 

 

 

(4) pipeline_prompt + Chat Model + Output Parser를 Chaining 하기

 

# Chat Model
model = ChatOpenAI(temperature=0, model='gpt-4')

# Output Parser
parser = StrOutputParser()

# Chaining
chain = pipeline_prompt | model | parser

 

 

 

(5) chain.invoke() 로 Chat Model 실행하고 결과 반환받기

 

두개를 실행시켜 봤는데 예제를 통해 가르켜준대로 잘 대답을 해주네요. 

 

chain.invoke({
    "topic": "Math", 
    "example_input": "It costs 2 dollar per apple. How much do you need in total to buy 10 apples?", 
    "example_output": "Since 2 x 10 = 20, you need a total of $20.", 
    "input": "How much does it cost to buy 6 cans of beer that cost $3 each?"
})

# 'Since 6 x 3 = 18, you need a total of $18.'



chain.invoke({
    "topic": "Math", 
    "example_input": "It costs 2 dollar per apple. How much do you need in total to buy 10 apples?", 
    "example_output": "Since 2 x 10 = 20, you need a total of $20.", 
    "input": "How much does it cost in total to buy 6 cans of beer that cost $3 each \
    and two snacks that cost $5 each?"
})

# 'Since (6 x $3) + (2 x $5) = $18 + $10 = $28, you need a total of $28.'

 

 

 

[ Reference ]

* LangChain - Pipeline: https://python.langchain.com/docs/modules/model_io/prompts/pipeline

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요. :-)

 

728x90
반응형
Posted by Rfriend
,

프롬프트 템플릿을 '부분적(Partial prompt templates)'으로 사용하는 것이 의미 있을 수 있습니다. 예를 들어, 필요한 값들의 부분 집합을 전달하여, 나머지 값들만을 기대하는 새로운 프롬프트 템플릿을 만듭니다.

LangChain은 이를 두 가지 방법으로 지원합니다. 

1. 문자열 값을 가진 부분적 프롬프트 템플릿 (Partial prompt templates with Strings) 
2. 문자열 값을 반환하는 함수를 사용한 부분적 프롬프트 템플릿 (Partial prompt templates with Functions)

이 두 가지 다른 방법은 다른 사용 사례들을 지원합니다.

 

 

아래 예제에서 문자열 값을 반환하는 함수를 사용한 부분적 프롬프트 템플릿 (Partial with Functions) 을 만들고, LangChain에서 이를 수행하는 방법에 대해 소개하겠습니다. 

 

LangChain - Partial Prompt Templates

 

 

함수를 이용한 부분적 프롬프트 템플릿 사용 사례는 언제나 일반적인 방법으로 가져오고 싶은 변수가 있을 때 적용됩니다.

 

이의 대표적인 예는 날짜나 시간과 관련된 경우입니다. 항상 현재 날짜를 포함하고 싶은 프롬프트가 있다고 가정해보겠습니다. 프롬프트에 그것을 하드코딩 하여 다른 입력 변수들과 함께 전달하는 것은 매우 불편하며 또 휴면에러를 유발할 수도 있습니다. 이 경우, 항상 현재 날짜를 반환하는 함수(Function)로 프롬프트를 부분적(Partial prompt template)으로 사용하면 매우 편리합니다.

 

 

먼저 터미널에서 pip install 로 langchain, openai 모듈을 설치합니다. 

 

! pip install langchain openai

 

 

다음으로, 필요한 모듈을 importing 합니다. 그리고 LLM 모델로는 OpenAI 의 LLM을 쓸 것이므로 OpenAI API Key를 환경변수에 등록해줍니다. 

 

import os

from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser

os.environ["OPENAI_API_KEY"]="sk-xxxx..." # set with yours

 

 

 

이번 예제에서는 현재 날짜와 시간을 가져오는 사용자 정의 함수(User Defined Function)를 정의해서 사용하겠습니다. 

 

# Define a funtion that returns the current date
from datetime import datetime

def get_datetime():
    now = datetime.now()
    return now.strftime("%m/%d/%Y, %H:%M:%S")

 

 

 

부분적 프롬프트 템플릿(Partial prompt templates)을 만드는 방법에는 2가지가 있습니다. 

 

- 방법 1: PromptTemplate()로 prompt를 먼저 만들고, prompt.partial() 로 부분 프롬프트 생성

- 방법 2: PromptTemplate() 의 partial_varialbes 로 부분 변수 초기화하여 부분 프롬프트 생성

 

 

[방법 1] PromptTemplate()로 prompt를 먼저 만들고, prompt.partial() 로 부분 프롬프트 생성

 

# [option 1] Partial prompt template with a function
prompt = PromptTemplate(
    template="Tell me a {adjective} joke about the day {date}", 
    input_variables=["adjective", "date"]
)

# -- Partil prompt here --
partial_prompt = prompt.partial(date=get_datetime)

print(partial_prompt.format(adjective="funny"))
# Tell me a funny joke about the day 01/03/2024, 23:39:21

 

 

 

[방법 2] PromptTemplate() 의 partial_varialbes 로 부분 변수 초기화하여 부분 프롬프트 생성

 

# [option 2] You can initialize the prompt with the partialed variables
prompt = PromptTemplate(
    template="Tell me a {adjective} joke about the day {date}", 
    input_variables=["adjective"], 
    partial_variables={"date": get_datetime}, # -- partial prompt initialization here --
)

print(prompt.format(adjective="funny"))
# Tell me a funny joke about the day 01/03/2024, 23:41:26

 

 

 

이제, 위에서 만든 부분적 프롬프트 템플릿을 LLM Model, Output Parser와 '|'로 Chaining 해보겠습니다. 

 

# LLM Model
llm = OpenAI()

# Output Parser
parser = StrOutputParser()

# Chaining
chain = prompt | llm | parser

 

 

프롬프트 내 "date" 는 위에서 Partial Variable 로 해서 get_date() 함수로 현재의 날짜와 시간을 가져와서 부분적으로 입력을 해주었으며,  chain.invoke({"adjective": "funny"}) 를 사용해서 "adjective" 변수에 대해서만 사용자가 하드코딩으로 "funny"를 입력해주면 프롬프트 전체를 완성해서 LLM 모델에 전달하여 LLM 모델이 답변을 생성하도록 실행해보겠습니다. 

 

print(prompt.format(adjective="funny"))
# Tell me a funny joke about the day 01/03/2024, 23:41:26


response = chain.invoke({"adjective": "funny"})
print(response)

# Q: What did the calendar say when it saw the date and time?
# A: "Wow, I'm so far ahead of myself!"

 

 

아래에는 chain.invoke({"adjective": "sarcastic"}) 으로 바꾸어서 실행을 해보았습니다. 

promt.format(adjective="sarcastic") 으로 프롬프트를 확인해보면 {date} 부분에 get_date() 함수로 가져온 현지 날짜와 시간이 바뀌어 있음을 알 수 있습니다. 그리고 LLM 모델의 답변도 "adjective"와 "date"가 바뀌었기 때문에 위와는 다르게 바뀌었네요. 

 

print(prompt.format(adjective="sarcastic"))
# Tell me a sarcastic joke about the day 01/04/2024, 00:02:09


response = chain.invoke({"adjective": "sarcastic"})
print(response)

# Q: What did the clock say to the person at 00:01:20 on 1/4/2024? 
# A: "Happy Birthday, a day late!"

 

 

 

[ Reference ]

- LangChain - Partial Prompt Templates: 

https://python.langchain.com/docs/modules/model_io/prompts/partial

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

Zero-shot, One-shot, Few-shot prompting은 특히 자연어 처리나 이미지 생성과 관련된 머신러닝 모델에서, 모델이 데이터를 어떻게 훈련하거나 사용하는지에 대해 설명할 때 사용됩니다. 이 용어들은 훈련 또는 추론 중에 모델에 제공되는 예제의 수와 관련이 있습니다. 


1. Zero-Shot Prompting

- 이는 모델이 수행해야 할 작업에 대한 예제가 전혀 제공되지 않는 시나리오를 의미합니다. 모델은 오로지 사전 훈련과 프롬프트의 지시에만 의존합니다. 

- 예시: 언어 모델에게 영어에서 프랑스어로 문장을 번역하라고 요청하는 것이며, 이때 이전의 번역 예제는 제공되지 않습니다. 


2. One-Shot Prompting 

- 이 경우 모델은 수행해야 할 작업의 한 가지 예제(a single example)를 제공받습니다. 이는 모델이 기대하는 형식이나 응답 유형을 이해하는 데 도움이 됩니다. 

- 예시: 언어 모델에게 새로운 농담을 만들기 전에 한 가지 농담 예제를 보여주는 것입니다. 이 예제는 템플릿이나 가이드 역할을 합니다. 


3. Few-Shot Prompting 

- 여기서는 모델에게 작업을 더 잘 이해할 수 있도록 소수의 예제(하나 이상이지만 일반적으로 많지 않음)가 제공됩니다. 

- 예시: 언어 모델에게 긴 텍스트를 한 단락으로 요약하는 세 가지 다른 예제를 제공한 후 새로운 텍스트를 요약하도록 요청하는 것입니다. 


각 경우에서 모델은 제공된 프롬프트의 정보를 사용하여 요청된 작업을 더 잘 이해하고 완수합니다. Zero-shot, One-shot, Few-shot prompting의 효과는 작업의 복잡성과 모델의 능력에 따라 다를 수 있습니다. 

 

 

지난번 포스팅에서는 LangChain과 LLM Models 을 사용하여 Few-shot Prompting 하는 방법을 소개하였습니다. 

 

이번 포스팅에서는 LLM Models 대신에 Chat Models 과 LangChain을 사용하여 Few-shot Prompting 하는 방법을 소개하겠습니다. 

 

(1) Chat Model 에 고정된 개수의 몇 개의 예시를 주는 Few-shot Prompting 

(2) Chat Model 에 사용자 인풋과 유사한 top k 개의 예시를 선별해서 주는 Dynamic Few-shot Prompting

 

 

 

LangChain - Few-shot Prompting for Chat Models

 

 

 

(1) Chat Model 에 고정된 개수의 몇 개의 예시를 주는 Few-shot Prompting 

 

먼저 터미널에서 pip install 로 langchain, openai, faiss-cup 모듈을 설치합니다. 

 

! pip install -q langchain openai faiss-cpu

 

 

그리고, 실습에 필요한 모듈을 importing 하고, Chat Model로 ChatGPT-4를 사용할 것이므로 OnenAI API Key를 등록해줍니다. 

 

from langchain.embeddings import OpenAIEmbeddings
from langchain.prompts import FewShotPromptTemplate, PromptTemplate
from langchain.prompts.example_selector import SemanticSimilarityExampleSelector
from langchain.vectorstores import FAISS
#from langchain.llms import OpenAI
from langchain.chat_models import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser

# set environment: OpenAI API Key
import os
os.environ["OPENAI_API_KEY"]="sk-xxxx..." # set with yours

 

 

(a) Few-shot Prompting에 사용할 몇 개의 예시를 작성합니다.

 

(b) 그리고 ChatPromptTemplate() 를 사용해서 ("human": "{input}"), ("ai", "{output}") 처럼 Human과 AI의 역할에 인풋과 아웃풋을 매핑해줍니다 (이 부분이 LLM Model과 Chat Model이 다른 부분임).

 

(c) 그 다음에 FewShotChatMessagePromptTemplate()에 앞서 정의해준 examples, example_prompt를 각 각 설정해줍니다.

 

(d) 마지막으로 ChatPromptTemplate.from_messages() 으로 앞에서 정의해준 예시와 Prompt 포맷을 다 조합해서 최종 Few-shot Prompt 를 작성해줍니다. 이때 "system"에게 페르소나를 지정해주는 것과 마지막에 "human"의 "input"이 추가되었습니다. 

 

# (a) Fixed Examples
# Define examples of creating antonyms
examples = [
    {"input": "happy", "output": "sad"}, 
    {"input": "tall", "output": "short"}, 
    {"input": "sunny", "output": "rainy"}, 
    {"input": "surprised", "output": "calm"}, 
    {"input": "dry", "output": "humid"}, 
    {"input": "hot", "output": "cold"}, 
    {"input": "satisfied", "output": "dissatisfied"}, 
]


# (b) A prompt template used to format each individual example.
example_prompt = ChatPromptTemplate.from_messages(
    [
        ("human", "{input}"), 
        ("ai", "{output}")
    ]
)

# (c) Assemble them into the few-shot prompt template
few_shot_prompt = FewShotChatMessagePromptTemplate(
    example_prompt = example_prompt, 
    examples = examples,
)

print(few_shot_prompt.format())
# Human: happy
# AI: sad
# Human: tall
# AI: short
# Human: sunny
# AI: rainy
# Human: surprised
# AI: calm
# Human: dry
# AI: humid
# Human: hot
# AI: cold
# Human: satisfied
# AI: dissatisfied


# (d) Finally, assemble the final prompt and use it with a model
final_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "You are a trustwordy AI assistant."), 
        few_shot_prompt, 
        ("human", "{input}")
    ]
)

 

 

앞에서 정의한 Few-shot Prompt와 Chat Model, 그리고 Output Parser를 '|'를 사용해서 Chaining 해줍니다. 

 

# Chat Model
model = ChatOpenAI(model="gpt-4")

# Output Parser
parser = StrOutputParser()

# Chaining
chain = final_prompt | model | parser

 

 

앞에서 정의한 chain을 invoke()를 사용해서 실행시켜줍니다. 

chain.invoke({"input": "excited"})  ==> 'bored'

chain.invoke({"input": "snowy"})  ==> 'sunny'

라고 답변하는걸 보니 Few-shot Prompting이 잘 작동하네요. 

 

## Run the chain

chain.invoke({"input": "excited"})
# 'bored'

chain.invoke({"input": "snowy"})
# 'sunny'

 

 

 

(2) Chat Model 에 사용자 인풋과 유사한 top k 개의 예시를 선별해서 주는 Dynamic Few-shot Prompting

 

LangChain은 여러개의 예시 중에서 몇 개의 예시만을 선별해서 Few-shot Prompting을 할 수 있도록 4가지 종류의 Example Selector를 제공합니다. 

 

[ LangChain: Example Selector Types ]

LangChain Example Selector Types

 

 

이중에서 인풋과 예시를 임베딩으로 변환한 후에, 각 임베딩 벡터간 코사인 유사도를 계산해서, 사용자의 인풋과 가장 유사한 k개의 예시를 선택해주는 Semantic Similarity Example Selector를 사용해보겠습니다. 임베팅 변환은 OpenAI의 OpenAIEmbeddings() 를 사용하였으며, 임베딩 벡터를 저장하고 유사도 검색을 하는 Vector DB는 FAISS를 사용하였습니다. 

 

FewShotChatMessagePromptTemplate() 에 (예제 대신에) Example Selector를 설정해주고, ChatPromptTemplate.from_messages()에 "human"과 "ai"의 메시지 포맷을 설정해줍니다. 그리고 인풋으로 받는 변수 input_variables=["input"] 을 설정해주면 됩니다. 

 

# Select examples based on similarity to the inputs 
# by finding the examples with the embeddings that have the greatest cosine similarity with the inputs.
example_selector = SemanticSimilarityExampleSelector.from_examples(
    # the list of examples available to select from.
    examples, 
    # The embedding class used to produce embeddings which are used to measure semantic similarity.
    OpenAIEmbeddings(), 
    # The VectorStore class used to store the embeddings and do a similarity search over.
    FAISS, 
    # the number of examples to produce.
    k=2,
)


similar_prompt = FewShotChatMessagePromptTemplate(
    # we provide an ExampleSelector instead of examples.
    example_selector=example_selector, 
    # Define how each example will be formatted. 
    example_prompt=ChatPromptTemplate.from_messages(
        [("human", "{input}"), ("ai", "{output}")]
    ), 
    input_variables=["input"]
)

 

 

k=2 로서 사용자 인풋과 가장 유사한(즉, 인풋과 예제 임베딩 간 코사인 유사도가 가장 큰) 2개의 예제를 Example Selector를 사용해 선별해보겠습니다. 

 

"excited"를 인풋으로 넣었더니 감정에 해당하는 예제 2개를 잘 선택했습니다. 

"snowy"를 인풋으로 넣엏더니 날씨에 해당하는 예제 2개를 잘 선택했습니다. ^^b

 

# "excited" is a Feeling, k=2
example_selector.select_examples({"input": "excited"})
# [{'input': 'surprised', 'output': 'calm'}, {'input': 'happy', 'output': 'sad'}]


# "snowy" is a Weather, k=2
example_selector.select_examples({"input": "snowy"})
# [{'input': 'sunny', 'output': 'rainy'}, {'input': 'hot', 'output': 'cold'}]

 

 

ChatPromptTemplate.from_messages() 로 "system"의 페르소나를 지정해주고, 앞에서 Example Selector를 사용한 Few-shot Prompt 와 "human"의 "input" 까지 조합해주면 최종 프롬프트가 완성됩니다. 

 

# Assemble the final prompt template
final_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "You are a trustwordy AI assistant."),
        similar_prompt, 
        ("human", "{input}"),
    ]
)


print(final_prompt.format(input="excited"))
# System: You are a trustwordy AI assistant.
# Human: surprised
# AI: calm
# Human: happy
# AI: sad
# Human: excited

 

 

final_prompt, Chat Model, 그리고 Output Parser를 '|'를 사용해서 Chaining 해줍니다. 

 

마지막으로 invoke()를 써서 chat_chain을 실행시켜주면 됩니다. 

 

chat_chain.invoke({"input": "excited"}) ==> "relaxed"

chat_chain.invoke({"input": "snowy"}) ==> "clear"

 

라고 몇 개의 예제를 통해 과제에 대한 정보를 주었더니 Chat Model이 잘 이해해서 답변을 정확하게 생성했네요. 

 

# Chaining
chat_chain = final_prompt | model | parser

# Run the chain
chat_chain.invoke({"input": "excited"})
# 'relaxed'

chat_chain.invoke({"input": "snowy"})
# 'clear'

 

 

 

[ Reference ]

- LangChain - Few-shot examples for chat models: 
https://python.langchain.com/docs/modules/model_io/prompts/few_shot_examples_chat

- LangChain - Example Selector Types:

https://python.langchain.com/docs/modules/model_io/prompts/example_selector_types/

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

LLM(Large Language Model, 대규모 언어 모델)에서의 Few-shot prompting는 GPT-3나 GPT-4 같은 모델에 소수의 예시(보통 하나에서 다섯 사이)를 제공하여 그 반응을 안내하거나 특정 작업을 가르치는 기술을 말합니다. 이는 '프롬프트 엔지니어링 (Prompt Engineering)'이라고 하는 더 넓은 범위의 기술 중 하나로, 이는 이러한 AI 모델과 효과적으로 소통하기 위해 입력(프롬프트)을 설계하는 예술입니다.  

Few-shot prompting에서는 모델에게 주어진 예시들이 패턴을 보여주거나 특정한 반응 방식을 가르치기 위해 신중하게 선택됩니다. 그런 다음 모델은 이 예시들을 참조로 사용하여 자신의 반응에서 기대되는 것을 이해합니다. 이는 단순한 지시만으로는 모델이 의도된 작업을 충분히 이해하기 어려운 복잡하거나 미묘한 작업에 특히 유용합니다.  

 


Few-shot prompting에 대한 주요 포인트는 다음과 같습니다. 

- 예시를 통한 시연 (Demonstration through Examples): 특정 예시를 제공함으로써 모델에게 어떤 종류의 출력이 기대되는지를 보여줍니다. 이는 분류, 번역 또는 심지어 창의적 글쓰기와 같은 작업에 특히 도움이 될 수 있습니다. 

- 모호성 감소 (Reduction in Ambiguity): Few-shot prompting는 작업 지시에서 모호성을 줄여 모델에게 무엇을 해야 하는지 더 명확하게 합니다. 

- 유연성과 적응성 (Flexibility and Adaptability): 이 방법은 모델이 재교육이나 핵심 알고리즘에 대한 업데이트 없이도 새로운 작업에 적응할 수 있게 해줍니다. 

- 효율성 (Efficiency): 퓨샷 학습은 새로운 작업에 모델을 적응시키기 위해 대량의 데이터나 광범위한 파인튜닝이 필요하지 않다는 점에서 효율적입니다. 

- 예시 품질에 대한 의존성 (Dependency on Example Quality): Few-shot prompting의 효과는 제공된 예시의 품질과 관련성에 크게 의존합니다. 잘못 선택된 예시는 모델로 하여금 부적절하거나 잘못된 반응을 할 수 있게 만듭니다.  

전반적으로, Few-shot prompting은 특히 모델 Finetuning 이나 대규모 데이터셋이 실행 가능하지 않은 상황에서 LLM의 능력을 유연하고 효율적으로 활용하기 위한 강력한 도구입니다. 

 

이번 포스팅에서는 LangChain과 ChatGPT LLM 모델을 사용하여 Few-shot Prompting 하는 방법을 소개하겠습니다. 

 

1. 몇 개의 예시를 제공하여 LLM 모델에게 특정 출력을 지시하는 Few-shot Prompting

2. 사용자 인풋과의 유사도에 따라 예시를 선택해서 LLM 모델에 Few-show Prompting 하기

 

 

ChatModel 에 Few-shot Prompting 하는 방법은 다음 포스팅에서 소개하겠습니다. 

 

 

LangChain - Few-shot Prompting

 

 

 

1. 몇 개의 예시를 제공하여 LLM 모델에게 특정 출력을 지시하는 Few-shot Prompting

 

먼저 터미널에서 pip install 을 이용하여 langchain, openai, faiss-cpu 모듈을 설치합니다. 

 

! pip install -q langchain openai faiss-cpu

 

 

필요한 모듈을 importing 합니다. 

 

from langchain.embeddings import OpenAIEmbeddings
from langchain.prompts import FewShotPromptTemplate, PromptTemplate
from langchain.prompts.example_selector import SemanticSimilarityExampleSelector
from langchain.vectorstores import FAISS
from langchain.llms import OpenAI
from langchain_core.output_parsers import StrOutputParser

 

 

OpenAI의 LLM 모델을 사용할것이므로 OpenAI API Key를 등록합니다. 

 

import os

os.environ["OPENAI_API_KEY"]="sk-xxxx..." # set with yours

 

 

Few-shot Prompting을 하기 위해 다음의 순서대로 Prompt 를 작성합니다. 

 

(1) 몇 개의 예시를 작성 (아래 예에서는 Input 의 반대말을 Output으로 출력하는 7개 예시) 

(2) PromptTemplate()로 예시의 인풋 변수와 예시 템플릿 포맷을 생성

(3) FewShotPromptTemplate()에 (1) 예시, (2) 예시 PromptTemplate을 입력하여 Prompt 생성

 

# (1) Examples of creating antonyms
examples = [
    {"input": "happy", "output": "sad"}, 
    {"input": "tall", "output": "short"}, 
    {"input": "sunny", "output": "rainy"}, 
    {"input": "surprised", "output": "calm"}, 
    {"input": "dry", "output": "humid"}, 
    {"input": "hot", "output": "cold"}, 
    {"input": "satisfied", "output": "dissatisfied"}, 
]


# (2) Example Prompt Template
example_prompt = PromptTemplate(
    input_variables=["input", "output"], 
    template="Input: {input}\nOutput: {output}"
)

print(example_prompt.format(**examples[0]))
# Input: happy
# Output: sad


# (3) Feed examples and formatter to FewShotPromptTemplate
prompt = FewShotPromptTemplate(
    examples=examples, 
    example_prompt=example_prompt,
    suffix="Input : {input}",
    input_variables=["input"],
)

 

 

위에서 작성한 Prompt와 LLMs 모델, 그리고 Output Parser를 '|'를 이용하여 chaining 해줍니다. 

 

# LLMs model
llm = OpenAI()

# Output Parser
parser = StrOutputParser()

# Chaining
chain = prompt | llm | parser

 

 

위에서 작성한 Few-shot Prompting을 위한 Prompt를 출력해보면 아래와 같습니다. 위에서 작성했던 예시들이 모두 들어가 있고, 마지막에 사용자 인풋이 들어가 있습니다. 

 

print(prompt.format(input="excited"))

# Input: happy
# Output: sad

# Input: tall
# Output: short

# Input: sunny
# Output: rainy

# Input: surprised
# Output: calm

# Input: dry
# Output: humid

# Input: hot
# Output: cold

# Input: satisfied
# Output: disappointed

# Input : excited

 

 

그럼, 드디어 위에서 정의한 chain = prompt | model | parser 를 사용해서 chain.invoke()로 사용자 인풋을 넣었을 때 각 형용사별로 반대말을 잘 출력하는지 실행시켜 보겠습니다. 

 

ChatGPT LLM 모델이 똑똑해서 몇 개의 예시로 부터 잘 학습을 해서 원하는 지시대로 Output을 잘 출력하네요. 

 

chain.invoke({"input": "excited"})
# '\nOutput: bored'


chain.invoke({"input": "foggy"})
# ' \nOutput : clear'

 

 

 

2. 사용자 인풋과의 유사도에 따라 예제를 선택해서 LLM모델에 Few-show Prompting 하기

 

여러개의 예시 중에서 사용자 인풋과 유사하고 관련성이 높은 예시를 선별해 Prompt를 만들어서 LLM 모델에 전달하면 Token의 개수가 줄어들어서 비용이 줄어들고 Latency가 향상되는 효과, 그리고 LLM 모델이 좀더 향상된 답변을 제시할 수 있는 효과가 있습니다. 

 

LangChain이 제공하는 Example Selector Types은 아래와 같이 4가지가 있습니다. 

 

 

[ LangChain - Example Selector Types ]

 

 

이번 포스팅에서는 예시와 사용자 인풋을 Text Embeddings 변환하여, Cosine Similarity가 가장 높은 k개의 예시를 선별해서 Few-shot Prompt에 넣어서 LLM 모델에 전달하는 방법을 소개하겠습니다. 

 

예시와 사용자 인풋을 Text Embeddings 변환하는 것은 OpenAIEmbeddigns() 를 사용하였으며, 임베딩 변환된 벡터를 저장하고 코사인 유사도 계산을 하는 것은 FAISS 벡터 DB를 사용하였습니다. 

 

# Select examples based on similarity to the inputs 
# by finding the examples with the embeddings that have the greatest cosine similarity with the inputs.
example_selector = SemanticSimilarityExampleSelector.from_examples(
    # the list of examples available to select from.
    examples, 
    # The embedding class used to produce embeddings which are used to measure semantic similarity.
    OpenAIEmbeddings(), 
    # The VectorStore class used to store the embeddings and do a similarity search over.
    FAISS, 
    # the number of examples to produce.
    k=2,
)


similar_prompt = FewShotPromptTemplate(
    # we provide an ExampleSelector instead of examples.
    example_selector=example_selector, 
    example_prompt=example_prompt,
    prefix="Give the antonym of every input",
    suffix="Input: {input}\nOutput:",
    input_variables=["input"]
)

 

 

k=2 로 설정하여 사용자 인풋과 Semantic Similarity 가 가장 높은 2개의 예시를 선택하도록 하였습니다. 

아래의 예를 보면, 먼저 "excited"의 경우 감정에 해당하므로 "surprised", "satisfied" 의 두 개 예시가 선택되었습니다. 

 

# Input is a feeling
print(similar_prompt.format(input="excited"))

# Give the antonym of every input

# Input: surprised
# Output: calm

# Input: satisfied
# Output: disappointed

# Input: excited
# Output:

 

 

위에서 Semantic Similarity 기준의 Example Selector 로 정의한 similar_prompt 를 LLM 모델과 Output Parser 와 함께 '|'로 chaining 하였습니다. fewshot_similar_chain.invoke() 로 실행을 해보겠습니다. 

 

감정에 해당하는 "excited"를 사용자 인풋으로 넣어주면 "surprised", "satisfied"에 대한 예시를 Prompt에 함께 넣어서 ChatGPT LLM 모델에 보내게 되고, ChatGPT LLM 모델은 "excited"에 대한 반대말로 "bored"를 Output으로 반환하였습니다.   

 

# Chaining
fewshot_similar_chain = similar_prompt | llm | parser

# Feeling
fewshot_similar_chain.invoke({"input": "excited"})
# ' bored'

 

 

날씨에 해당하는 "snowy"를 사용자 인풋으로 넣었더니 k=2 개의 Semantic Similarity가 높은 예시로 "sunny", "hot"의 2개 예시가 적절하게 잘 선택되었습니다. 

 

# Input is a weather
print(similar_prompt.format(input="snowy"))

# Give the antonym of every input

# Input: sunny
# Output: rainy

# Input: hot
# Output: cold

# Input: snowy
# Output:

 

 

fewshot_similar_chain.invoke({"input": "snowy"}) 로 Few-shot Prompting ("sunny", "hot"의 2개 예시)하여 ChatGPT LLM 모델을 실행시켰더니 "dry"라는 Output을 생성하였습니다. 

 

fewshot_similar_chain.invoke({"input": "snowy"})
# ' dry'

 

 

[ Reference ]

- LangChain Modules - Prompts - Select by similarity:

https://python.langchain.com/docs/modules/model_io/prompts/example_selector_types/similarity

- LangChain Example Selector Types:

https://python.langchain.com/docs/modules/model_io/prompts/example_selector_types/

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

 

 

728x90
반응형
Posted by Rfriend
,

ChatGPT와 같은 LLM 모델을 이용한 질의응답 시에 제약조건으로 지식단절(Knowledge Cutoff)이 있습니다. 

지식단절 (Knowledge Cutoff)이란 LLM 모델이 학습에 사용한 데이터가 생성된 시점 이후의 새로운 사건, 정보에 대해서는 학습이 안되어서 답변 생성이 불가능한 것을 말합니다. 

 

지식단절의 한계를 극복하는 방법으로 최신의 데이터를 계속 수집해서 자주 LLM 모델을 Fine-tuning 해주는 방법이 있습니다. 하지만 이는 비용과 시간이 많이 듭니다. 

 

지식단절의 한계를 극복하는 또 다른 방법으로는 웹 검색 툴을 추가하여, 사용자의 질문에 해당하는 최신의 공개된 정보를 웹 검색을 통해 가져와서, 이 최신 공개 정보를 컨텍스트 정보로 하여 LLM 모델이 답변을 생성하도록 할 수 있습니다. 이는 앞서 말한 Fine-tuning 기법 대비 상대적으로 비용과 시간이 적게 들고 바로 적용해볼 수 있는 장점이 있습니다. 

 

 

이번 포스팅에서는 LangChain 을 이용하여 DuckDuckGo Web Search Tool을 추가해 사용자 질의에 해당하는 최신 정보를 검색하고, ChatGPT LLM 모델이 이를 컨텍스트 정보로 삼아 답변을 생성하는 방법을 소개하겠습니다. 

LangChain에서는 Google Search, Bing Search, DuckDuckGo Search 웹 검색 툴을 제공합니다. Google 과 Bing search를 사용하려면 API 서비스 등록을 하고 API Key를 받아서 사용해야 합니다. 

 

 

 

[ 웹 검색 툴로 최신 정보를 검색하여 컨텍스트 정보로 참고하여 답변 생성하기 ]

LangChain - adding Web Search Tools, Google, Bing, DuckDuckGo

 

 

 

먼저 터미널에서 pip install 을 사용해 langchain, openai, black, python-lsp-black, duckduckgo-search 모듈을 설치합니다. 

 

! pip install -q langchain openai black>=22.3 python-lsp-black duckduckgo-search

 

 

실습에 필요한 모듈을 importing 합니다. 

 

from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

 

 

OpenAI API Key를 환경변수로 등록해줍니다. 

 

import os

os.environ["OPENAI_API_KEY"] = "sk-xxxx..." # set with yours

 

 

(1) Web Search Tool을 사용하지 않았을 때의 지식 단절 (Knowledge Cutoff)

 

먼저, Web Search Tool을 사용하지 않고 ChatGPT LLM 모델만을 가지고 최신 정보가 필요한 질문을 했을 때 지식 단절로 인해 적절한 답변을 못하는 현상을 살펴보겠습니다. 

 

template = """You are an AI assistant. Answer the question.
If you don't know the answer, just say you don't know.

Question: {question}
Answer:"""

prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI(model="gpt-4")
parser = StrOutputParser()

chain = prompt | model | parser

 

 

아래처럼 "누가 BTS 멤버 중에 가장 늦게 군대에 갔나요?" 라고 질문을 했을 때 ChatGPT-4 모델이 최신의 실시간 데이터를 제공할 수 없다고 답변합니다. (가상의 그럴싸한 거짓말을 하는 환각(Hallucinations) 현상을 일으키지 않아서 그나마 다행이예요.)

 

## -- 지식 단절 (Knowledge Cutoff)
chain.invoke({"question": "Which BTS member was the last to go to the military?"})

# "As an AI developed up to October 2021, I can't provide real-time data. 
# As of my last update, none of the BTS members have begun their military service yet. 
# In South Korea, all able-bodied men are required to serve in the military 
# for approximately two years. Please check the most recent sources for this information."

 

 

 

 

(2) Web Search Tool을 사용하여 최신 정보를 검색해 지식 단절 (Knowledge Cutoff) 극복하기

 

이번에는 DuckDuckGo Search Tool을 추가해서 최신 정보를 웹에서 검색해 가지고 와서, 이를 컨텍스트 정보로 하여 답변을 생성해보도록 하겠습니다. 

 

## Adding Web Search Tools
from langchain.tools import DuckDuckGoSearchRun

search = DuckDuckGoSearchRun()

## Provide "the latest context information" from web search
template = """Answer the question based on context.

Question: {question}
Context: {context}
Answer:"""

prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI(model="gpt-4")
parser = StrOutputParser()

chain = (
    {"question": RunnablePassthrough(), "context": RunnablePassthrough()}
    | prompt 
    | model 
    | parser
)

 

 

먼저, DuckDuckGo Search tool 이 잘 작동하나 살펴보겠습니다. 

 

search_result = search.run("Which BTS member was the last to go to the military?")

print(search_result)

# The last two members, Jimin and Jungkook, began their 18-month military duty 
# in South Korea on Tuesday. Some fans say they should have been exempt. 24 BTS in 2019. 
# Any potential reunion... South Korea Final Members of K-Pop Band BTS Begin Mandatory 
# South Korean Military Service This video cannot be played because of a technical error. 
# (Error Code: 102006) By Kim Tong-hyung / AP... Updated Dec 15, 2023, 11:01 AM 
# SGT SEOUL - South Korean singers Jimin and Jungkook of K-pop boy band BTS 
# began their mandatory military service on Dec 12. They are the last of the... 
# Last October, BTS's agency BigHit Music owned by HYBE, confirmed that all seven members 
# of the band would fulfil their country's military obligation, starting with the eldest, 
# Jin, who... BTS formed in 2013 with seven members: RM, Jin, V, J-Hope, Suga, 
# Jimin and Jungkook. The band went on to achieve global fame, 
# with number one singles in more than 100 countries around the...

 

 

다음으로, DuckDuckGo 웹 검색 결과를 컨텍스트로 하여 답변을 생성해보겠습니다. 

2024년 1월을 기준으로 했을 때, BTS 멤버 중에 가장 늦게 군대에 간 멤버가 "지민과 정국 (Jimin and Jungkook)" 이라고 이번에는 정확하게 답변을 해주네요! 

 

question = "Which BTS member was the last to go to the military?"

chain.invoke({"question": question, "context": search.run(question)})
# 'Jimin and Jungkook'

 

 

 

[ Reference ]

- LangChain - Using Tools: https://python.langchain.com/docs/expression_language/cookbook/tools

- Google Search: https://python.langchain.com/docs/integrations/tools/google_search

- Bing Search: https://python.langchain.com/docs/integrations/tools/bing_search

- DuckDuckGoSearch: https://python.langchain.com/docs/integrations/tools/ddg

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

Google Colab에 PostgreSQL과 pgvector를 설치하면 Python 과 함께 Text, Image Embeddings 변환, 저장 및 Semantic Search 등에 사용할 수 있어서 편리합니다. 

 

 

1. Google Colab 은 무엇인가? 

 

Google Colab, 또는 Colaboratory는 Google에서 제공하는 무료 Jupyter 노트북 환경입니다. 이 서비스는 클라우드 기반으로 운영되며, 데이터 분석, 머신 러닝, 교육 및 연구를 위한 플랫폼으로 널리 사용됩니다. 주요 특징과 장점은 다음과 같습니다. 

 

- 웹 기반 인터페이스: 설치가 필요 없으며, 웹 브라우저를 통해 접근하고 사용할 수 있습니다. 이는 사용자가 어디에서나 쉽게 작업을 시작할 수 있게 해줍니다. 

 

- 무료 접근: 기본적인 사용은 무료이며, 누구나 Google 계정을 통해 접근할 수 있습니다. 

 

- GPU 및 TPU 지원: 데이터 과학과 머신 러닝 작업을 위해 고성능 컴퓨팅 자원인 GPU와 TPU를 무료로 사용할 수 있습니다. 

 

- Python 지원: Python 프로그래밍 언어와 다양한 라이브러리(NumPy, Pandas, Matplotlib 등)를 지원합니다. 또한 TensorFlow, PyTorch 같은 머신 러닝 라이브러리를 사용할 수 있습니다. 

 

- 협업: Google 드라이브와의 통합을 통해 쉽게 공유하고, 다른 사용자와 협업할 수 있습니다. 문서 형식의 노트북에서 직접 코드를 작성하고 실행할 수 있어 팀워크에 유용합니다. 

 

- 교육 및 연구 목적: 교육과 연구를 위한 훌륭한 도구로, 대학 강의, 워크샵, 개인 프로젝트 등 다양한 목적으로 활용됩니다. 

 

Google Colab은 특히 하드웨어 리소스에 제한이 있는 사용자나 빠른 프로토타이핑을 원하는 데이터 과학자 및 연구자들에게 매우 유용합니다. 

 

 

2. PostgreSQL 은 무엇인가? 

 

PostgreSQL은 고급 오픈 소스 관계형 데이터베이스 관리 시스템(RDBMS)입니다. PostgreSQL은 강건함, 확장성, 그리고 SQL 표준 준수로 잘 알려져 있습니다. PostgreSQL은 복잡한 쿼리, 외래 키, 트리거, 뷰, 트랜잭션의 무결성, 다중 버전 동시성 제어 등 다양한 기능을 제공합니다. 간단한 웹 애플리케이션부터 복잡한 데이터 웨어하우징 및 지리공간 데이터 분석에 이르기까지 다양한 애플리케이션에 적합한 선택입니다.  

 

 

3. pgvector extension은 무엇인가? 


pgvector는 고차원 벡터 공간에서의 효율적인 유사성 검색을 위해 설계된 PostgreSQL 확장 기능(an extension for Pogres for efficient similarity search in high-dimensional vector spaces)입니다. 이미지, 텍스트, 오디오 등에 대한 임베딩과 같은 벡터가 일반적인 머신 러닝 애플리케이션에 특히 유용합니다. pgvector는 추천 시스템, 이미지 검색, 자연어 처리 애플리케이션과 같은 작업에 필수적인 빠른 최근접 이웃 검색을 지원합니다. 

pgvector의 주요 측면은 다음과 같습니다. 

- 벡터 데이터 타입 (Vector Data Type): 벡터를 저장하기 위한 새로운 데이터 타입을 도입합니다.  
- 벡터용 인덱싱 (Indexing for Vectors): 고차원 데이터에서 검색 성능을 향상시키는 벡터에 최적화된 인덱싱 방법을 제공합니다. 

  : IVFFlat (Inverted File with Flat Compression), HNSW (Hierarchical Navigable Small World) 
- PostgreSQL과의 통합 (Integration with PostreSQL): 강력한 데이터베이스 기능을 활용하여 PostgreSQL과 원활하게 작동합니다. 
- 머신 러닝 파이프라인에서의 사용 (Use in Machine Learning Pipelines): 임베딩과 같은 머신 러닝 모델 출력의 저장 및 쿼리에 이상적입니다. 

 

PostgreSQL과 pgvector가 오픈소스이고 확장성(Scalability) 이 뛰어나다는 점은 다른 전용 Vector DB 대비 큰 강점입니다. (아래 블로그 포스팅 참고)

 

* Why did we replace Pinecone with PGVecotr?:  

https://medium.com/@jeffreyip54/why-we-replaced-pinecone-with-pgvector-2f679d253eba

 

PostgreSQL과 pgvector의 결합은 특히 머신 러닝 모델을 포함하는 복잡한 데이터 집약적 애플리케이션을 친숙하고 강력한 데이터베이스 환경 내에서 처리할 수 있게 합니다. 

 

 

 

이번 포스팅에서는 

 

(1) Google Colab에 PostgreSQL 설치하기

(2) Google Colab에 설치된 PostgreSQL에 pgvector extension 설치하기

 

에 대해서 소개하겠습니다. 

Google Colab, PostgreSQL, pgvector extension

 

 

 

(1) Google Colab에 PostgreSQL 설치하기

(How to install PostgreSQL in Google Colab?)

 

- PostgreSQL 설치 

 

!sudo apt-get -y -qq update
!sudo apt-get -y -qq install postgresql

 

 

- PostgreSQL 서버 서비스 시작하기

 

!sudo service postgresql start

 

 

- User와 Password 설정하기

 

# Setup a password 'postgres' for username 'postgres'
!sudo -u postgres psql -U postgres -c "ALTER USER postgres PASSWORD 'postgres';"

 

 

- 'dev' 데이터베이스 만들기

 

# Setup a database with name 'dev' to be used
!sudo -u postgres psql -U postgres -c "DROP DATABASE IF EXISTS dev;"
!sudo -u postgres psql -U postgres -c "CREATE DATABASE dev;"

 

 

- 'dev' 데이터베이스에 연결하기

 

# set connection
%env DATABASE_URL=postgresql://postgres:postgres@localhost:5432/dev

 

 

- %load_ext sql 로 SQL 확장 모듈 로드하기 

 

%load_ext는 Jupyter Notebook과 같은 IPython 환경에서 사용하는 매직 명령어 중 하나입니다. 이 명령어는 확장(extension) 모듈을 로드하고 활성화하는 데 사용됩니다. 확장 모듈은 추가 기능을 제공하며, %load_ext sql을 사용하여 SQL 쿼리를 실행할 수 있습니다. 

 

# To load the sql extention to start using %%sql
%load_ext sql

 

 

- SQL query 테스트 

 

# You can start executing postgres sql commands
%%sql
select version();

-- version
-- PostgreSQL 14.9 (Ubuntu 14.9-0ubuntu0.22.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, 64-bit

 

 

 

(2) Google Colab의 PostgreSQL에 pgvector extension 설치하기

(How to install pgvector extension in Google Colab?)

 

- git clone 해서 pgvector extension 설치 파일 다운로드하기

 

!git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git

 

 

- pgvector 설치 파일이 다운로드 되어있는 폴더로 경로 변경

(Google Colab의 마운트 한 후, content 폴더에 저장됨)

 

%cd /content/pgvector

 

 

- pgvector 설치

 

!make
!make install

 

 

- pgvector 확장 실행: CREATE EXTENSION vector;

  1. PostgreSQL 서버에 로그인
  2. PostgreSQL의 확장 벡터를 생성하려는 데이터베이스로 연결
  3. 확장 벡터를 생성하려는 데이터베이스에서 다음 명령어를 실행 

(데이터베이스 별로 최초 1회만 실행해주면 됩니다)

%%sql
CREATE EXTENSION IF NOT EXISTS vector;

 

 

 

참고로, pgvector 에서 제공하는 연산자는 아래의 6개가 있습니다. 사용자 질문과 문서 간 텍스트 임베딩에 대한 Semantic Search 에 코사인 유사도 (1 - Cosine Distance) 가 많이 사용됩니다. 

 

연산자 (operator) 설명 (description)
+ 요소 별 더하기 (Element-wise Addition)
- 요소 별 빼기 (Element-wise Subtraction)
* 요소 별 곱하기 (Element-wise Multiplication)
<-> 유클리드 거리 (Euclidean Distance)
<#> 음의 내적 (Negative Inner Product)
<=> 코사인 거리 (Cosine Distance)

 

 

[ Reference ]

* Postres pgvector: https://github.com/pgvector/pgvector

* Vector Indexes in Postgres using pgvector: IVFFlat vs. HNSW: 
https://tembo.io/blog/vector-indexes-in-pgvector/

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

LangChain과 LLM 모델을 사용하여 애플리케이션을 만들 때 필요에 따라서 여러개의 템플릿(templates)을 미리 작성해 놓고, 사용자의 질문과 가장 유사한 (most similar, most relavant) 템플릿을 선택해서 LLM 모델로 질문과 템플릿을 인풋으로 보낼 수 있다면 보다 맞춤형의 답변을 생성할 수 있을 것입니다. 

 

LangChain - Routing templates dynamically by semantic search

 

 

시맨틱 검색은 임베딩과 코사인 유사도를 사용하는 것은 자연어 처리 및 정보 검색에서 검색 결과의 관련성과 정확성을 향상시키기 위해 쿼리의 의미에 기반하여 사용되는 방법입니다.  

1. 시맨틱 검색 (Semantic Search): 전통적인 검색 엔진은 종종 정확한 키워드 일치에 의존합니다. 반면에 시맨틱 검색은 검색 쿼리의 의도와 맥락적 의미를 이해하려고 합니다. 동의어, 관련 용어 및 전체 맥락과 같은 언어의 뉘앙스를 해석하여 더욱 관련성 높은 결과를 제공하려고 합니다.  

2. 임베딩 (Embeddings): 시맨틱 이해를 달성하기 위해 시스템은 임베딩을 사용합니다. 임베딩은 단어, 구, 문장 또는 전체 문서를 고차원 공간에서 수치적으로 표현한 것입니다. 이러한 표현은 대규모 데이터셋에서 학습되며 단어 간의 의미 관계를 포착합니다. 예를 들어, 잘 훈련된 임베딩 공간에서는 유사한 의미를 가진 단어들이 서로 가까이 위치합니다.  

3. 코사인 유사도 (Cosine Similarity): 텍스트(검색 쿼리와 검색 가능한 문서 모두)를 임베딩으로 변환한 후, 시스템은 얼마나 유사한지 측정할 방법이 필요합니다. 이를 위해 흔히 사용되는 척도는 코사인 유사도입니다. 코사인 유사도는 두 벡터(이 경우, 쿼리와 문서의 임베딩) 사이의 각도의 코사인을 측정합니다. 코사인 유사도가 1에 가까우면 매우 작은 각도를 나타내고, 따라서 높은 유사도를 의미합니다. 반대로, 코사인 유사도가 0에 가까우면 큰 각도와 낮은 유사도를 나타냅니다.  

4. 검색에서의 적용 (Application in Search): 사용자가 쿼리를 제출하면, 시스템은 이를 임베딩으로 변환한 다음 이 임베딩과 데이터베이스의 다양한 문서들의 임베딩 간의 코사인 유사도를 계산합니다. 코사인 유사도 점수가 더 높은 문서는 쿼리와 더 관련이 있다고 간주되어 검색 결과에서 더 높은 순위를 차지합니다.  

이러한 접근 방식은 시스템이 사용자 쿼리의 의미적 내용을 더 잘 포착하고 응답할 수 있게 하여, 단순히 특정 단어를 일치시키는 것보다 더 미묘하고 효과적인 검색 경험을 가능하게 합니다. 

 

 

[ 코사인 유사도와 시맨틱 검색 ]

Cosine Similarity and Text Semantic Search

 

 

 

LangChain과 ChatGPT LLM 모델을 사용해서 여러개의 템플릿을 미리 만들어놓고 질문에 가장 유사한 템플릿을 선택해서 대답을 생성하는 간단한 챗봇을 만들어보겠습니다. 

 

먼저, 터미널에서 langchain, openai, tiktoken 모듈을 pip install을 사용해서 설치합니다. 

 

! pip install -q langchain openai tiktoken

 

 

OpenAI의 ChatGPT를 LLM 모델로 사용할 것이므로 OpenAI API Key를 등록해줍니다. 

 

import os

os.environ["OPENAI_API_KEY"] = "sk-xxxx..."

 

 

필요한 모듈을 import 하고, Database PostgreSQL 과 관련된 template 1, 그리고 Cloud Kubernetes Docker 관련된 template 2 를 각각 정의해 줍니다. 

 

from langchain.chat_models import ChatOpenAI
from langchain.embeddings import OpenAIEmbeddings
from langchain.prompts import PromptTemplate
from langchain.utils.math import cosine_similarity
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda, RunnablePassthrough

# Template 1: Database PostgreSQL
database_template = """You are an expert on database, especially open source PostgreSQL. 
You are good at answering questions about database in a concise manner. 

Here is a question:
{query}"""


# Template 2: Colud, Kubernetes, Docker
cloud_template = """You are an expert on cloud platform. 
You are good at answering questions especially on kubernetes and docker. 
The user is from Korean. Answer the question in Korean.

Here is a question:
{query}
"""

 

 

Text Embedding 모델로는 OpenAI의 1,536 차원을 가진 "text-embedding-ada-002" 를 사용하겠습니다. 

  - 여러개의 텍스트에 대해서는 embeddings.embed_documents(),

  - 하나의 query에 대해서는 embeddings.embed_query()

메소드를 사용해서 텍스트를 임베딩으로 변환합니다. 

 

# OpenAI Embeddings: https://platform.openai.com/docs/guides/embeddings
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")

prompt_templates = [database_template, cloud_template]
prompt_embeddings = embeddings.embed_documents(prompt_templates)


query_embedding = embeddings.embed_query("What is PostgreSQL database?")

len(query_embedding)
# 1536

len(prompt_embeddings[0])
# 1536

query_embedding[:10]
# [0.011614119106739642,
#  -0.011661718070044624,
#  -0.011185729368317337,
#  -0.02718574244037603,
#  -0.043219754372800054,
#  0.02449300773999799,
#  -0.006510842295925382,
#  0.00851339435668751,
#  -0.021011491283506004,
#  -0.018604350362957857]

 

 

임베딩과 시맨틱 검색 (semantic search) 이 잘 작동하는지를 확인하기 위해 아래에 PostgreSQL Database, K8s and Docker 관련된 질문을 각각 해봤습니다. 코사인(Cosine Similarity)는 -1~1 사이의 값을 가지면 1에 가까울 수록 서로 유사하다고 판단할 수 있는데요, 첫번째 PostgreSQL 관련 질문에는 database_template 이 유사하다고 나왔고, 두번째 K8s and Docker 관련 짊누에서는 cloud_template 이 더 유사한 것으로 나왔으니 의도한 대로 잘 작동하네요. 

 

# Question 1: PostgreSQL database
query_embedding = embeddings.embed_query("What is PostgreSQL database?")
similarity = cosine_similarity([query_embedding], prompt_embeddings)[0]
print(similarity)
# [0.8684732  0.74399373]


# Question 2: K8s, Docker
query_embedding = embeddings.embed_query("What is Kubernetes and Docker?")
similarity = cosine_similarity([query_embedding], prompt_embeddings)[0]
print(similarity)
# array([0.75842006, 0.83432307])

 

 

 

템플릿들(templates)과 사용자 질문(user query) 텍스트를 임베딩으로 변환

--> cosine_similarity()  메소드로 템플릿과 질문 간 임베딩 간 코사인 유사도 계산 

--> 질문과 가장 유사한 템플릿 선택하여 반환

하는 과정을 prompt_router(input) 사용자 정의 함수로 정의해줍니다.

 

그리고 langchain_core.runnables.RunnableLambda(prompt_router) 를 사용해서 

 

그리고 '|'를 사용해서 

템플릿들(templates)과 사용자 질문(user query) 텍스트를 임베딩으로 변환

--> 템플릿과 질문 간 임베딩 간 코사인 유사도 계산 (calculate cosine similarity)

--> 질문과 가장 유사한 템플릿 선택하여 반환

--> 질문과 가장 유사한 템플릿을 사용하여 ChatGPT 모델로 답변 생성

--> ChatGPT 답변을 String으로 파싱하여 반환

 

하는 일련의 과정을 chaining 해주었습니다. 

 

# Define a UDF for routing prompts dynamically
def prompt_router(input):
    query_embedding = embeddings.embed_query(input["query"])
    similarity = cosine_similarity([query_embedding], prompt_embeddings)[0]
    most_similar = prompt_templates[similarity.argmax()]
    print("Using Database" if most_similar == database_template else "Using Cloud")
    return PromptTemplate.from_template(most_similar)
    

# Chaining
chain = (
    {"query": RunnablePassthrough()}
    | RunnableLambda(prompt_router)
    | ChatOpenAI()
    | StrOutputParser()
)

 

 

 

앞에서 정의한 chain을 invoke() 메소드를 사용해서 실행시켜줍니다.  

 

chain.invoke("What is PostgreSQL database?")

# Using Database
# 'PostgreSQL is an open-source relational database management system (RDBMS) 
# that offers advanced features and strong data integrity. It is widely known 
# for its robustness, scalability, and SQL compliance. 
# PostgreSQL supports various data types, including structured, semi-structured, 
# and unstructured data, making it suitable for a wide range of applications. 
# It offers features like ACID compliance, MVCC, JSON support, full-text search, 
# and extensive extensibility through procedural languages and extensions. 
# PostgreSQL is highly customizable and can be used for small to large-scale applications, 
# making it a popular choice among developers and enterprises.'

 

 

 

의도한 대로, 그리고 템플릿에 지시문을 작성한 대로 잘 작동하네요. 

(cloud_template 에는 사용자가 한국인이므로 한글로 답변을 생성하라고 지시문을 추가했었습니다.)

 

chain.invoke("What is Kubernetes and Docker?")

# Using Cloud
# 'Kubernetes와 Docker는 모두 컨테이너화된 애플리케이션을 관리하기 위한 클라우드 플랫폼 도구입니다.
# Docker는 컨테이너 기반 가상화 기술을 제공하는 플랫폼입니다. 
# 컨테이너는 애플리케이션을 격리된 환경에서 실행하고, 이식성과 확장성을 높여줍니다. 
# Docker는 애플리케이션의 종속성을 패키징하고 배포할 수 있도록 도와주며, 
# 애플리케이션을 컨테이너 이미지로 만들어 관리합니다.
# Kubernetes는 컨테이너 오케스트레이션 플랫폼으로, 도커 컨테이너의 배포, 확장, 관리를 
# 자동화합니다. Kubernetes는 여러 호스트에 걸쳐 컨테이너를 스케줄링하고, 서비스 디스커버리, 
# 로드 밸런싱, 자가 치유 등의 기능을 제공하여 애플리케이션 운영을 단순화합니다. 
# 또한 Kubernetes는 컨테이너의 상태를 모니터링하고 필요한 조치를 취할 수 있어 
# 안정적인 운영을 지원합니다.\n\n요약하자면, Docker는 컨테이너 기반 가상화 기술을 제공하고, 
# Kubernetes는 컨테이너 오케스트레이션 플랫폼으로 
# 컨테이너화된 애플리케이션의 배포와 관리를 용이하게 해주는 도구입니다.'

 

 

[ Reference ]

* LangChain - Routing by semantic similarity: 

https://python.langchain.com/docs/expression_language/cookbook/embedding_router

* OpenAI Embeddings: https://platform.openai.com/docs/guides/embeddings/what-are-embeddings

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요.  :-)

 

728x90
반응형
Posted by Rfriend
,

Python REPL(Read-Eval-Print Loop)은 Python 프로그래밍 언어의 대화형 인터프리터 환경을 나타냅니다. 이것은 사용자가 Python 코드를 입력하고 실행하며 결과를 즉시 확인할 수 있는 인터페이스를 제공합니다. 

  - Read 단계에서 사용자의 입력을 받습니다. 
  - Evaluation 단계에서 입력을 처리하고 Python 표현식으로 평가합니다. 
  - Print 단계에서 평가 결과를 사용자에게 출력합니다. 
  - Loop 단계에서 이 과정을 반복하며 다음 사용자 입력을 기다립니다. 

 

Python - REPL (Read-Eval-Print Loop)

(ChatGPT 에게 Python REPL 컨셉을 그림으로 그려달라고 했더니 위처럼 그렸네요. 나름 잘 그리네요. ㅋㅋ)

 


다음은 Python REPL이 어떻게 사용되는지에 대한 예시입니다. 

>>> 2 + 2
4
>>> print("Hello, World!")
Hello, World!
>>> x = 10
>>> x * 5
50



REPL은 Python 프로그래머들에게 코드 작성, 디버깅, 실험 등을 위한 강력한 도구로 자주 사용됩니다. 사용자가 코드를 입력하면 Python 인터프리터가 해당 코드를 실행하고 결과를 반환하며, 이러한 과정을 반복할 수 있습니다. REPL을 사용하면 빠르게 코드를 테스트하고 작업할 수 있어 개발 과정을 효율적으로 만들어 줍니다. 

 

 

이번 포스팅에서는 LangChain Expression Language (LCEL)와 ChatGPT를 사용해서 자연어를 인풋으로 입력하면 Python으로 코드를 짜고, Python REPL (Read-Eval-Print Loop) 방식으로 Python 코드를 실행해서 결과를 얻는 방법을 소개하겠습니다. 

 

 

먼저, langchin과 openai 모듈을 터미널에서 pip install 로 설치합니다. 

 

! pip install -q -U langchain openai

 

 

다음으로, OpenAI API Key를 설정해줍니다. 

 

import os

os.environ["OPENAI_API_KEY"] = "sk-xxxx..." # set with yours

 

 

Prompt에는 "사용자의 질문을 풀기 위한 Python 코드를 짜라. 사용자가 결과를 확인할 수 있도록 끝에 print(result) 코드를 포함해라. 단지 Python 코드만 반환하고 나머지는 반환하지 마라"는 지시문을 넣어주었습니다. 

 

LLM Model은 ChatGPT-4를 사용하였으며, temperature=0 으로 설정해서 사실에 기반한 일관성있고 보수적인 답변만 생성하도록 하였습니다. 

 

StrOutputParser() 로 LLM 모델이 생성한 답변(즉, Python codes)을 string 포맷으로 파싱합니다. 

 

마지막으로 PythonREPL() 메소드를 사용해서 ChatGPT가 생성한 Python codes 를 Python REPL 모드로 실행합니다. 

 

이러한 모든 절차를 '\'를 사용해서 chaining 해주었습니다. 

참고로, 여러개의 chain을 다시 chaining 할 수 있으며, PythonCode_chain 에다가 PythonREPL().run 을 chaing 하여 PythonCodeRun_chain 을 만들었습니다. 

 

#  파이썬 코드 생성 chain (Python codes generation)
PythonCode_chain = prompt | model | StrOutputParser()

# 파이썬 코드를 실행해서 결과를 보여주는 chain (Python REPL (Read-Eval-Print Loop))
PythonCodeRun_chain = PythonCode_chain | PythonREPL().run

 

 

from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_experimental.utilities import PythonREPL

template = """
Write some python  code to solve the user's problem. 
Include print(result) at the end for user to check the result. 
Return only python code and nothing else."""

prompt = ChatPromptTemplate.from_messages(
    [("system", template), ("human", "{question}")]
)

model = ChatOpenAI(temperature=0, model_name="gpt-4")

#  Python codes generation
PythonCode_chain = prompt | model | StrOutputParser()

# Python REPL (Read-Eval-Print Loop)
PythonCodeRun_chain = PythonCode_chain | PythonREPL().run

 

 

 

자연어로 3가지 질문을 던져보았는데요, ChatGPT-4가 모두 정확하게 Python 코드도 생성하고 잘 풀어주네요. 

 

 

Q: y = 5 + 3 * x. If x is 10, then what is y?

A: y = 5 + 3 * 10 = 35

 

## ----------------------------------------
## Q: y= 5 + 3 * x. If x is 10, what is y?
## ----------------------------------------

## (a) Python codes generation
PythonCode_chain.invoke({"question": "y= 5 + 3 * x. If x is 10, what is y?"})
# 'x = 10\ny = 5 + 3 * x\nprint(y)'

## (b) Run Python codes
PythonCodeRun_chain.invoke({"question": "y= 5 + 3 * x. If x is 10, what is y?"})
# '35\n'

 

 

 

Q: What is 2 multiplied by 10? 

A: 2 * 10 = 20

 

## ---------------------------
## Q: What is 2 multiplied by 10?
## ---------------------------

## (a) Python codes generation
PythonCode_chain.invoke({"question": "What is 2 multiplied by 10?"})
# 'result = 2 * 10\nprint(result)'

## (b) Run Python codes
PythonCodeRun_chain.invoke({"question": "What is 2 multiplied by 10?"})
# '20\n'

 

 

 

Q: How much is 5 factorial?

A: 5! = 5 * 4* 3* 2* 1 = 120

 

## ---------------------------
## Q: How much is 5 factorial?
## ---------------------------

## (a) Python codes generation
PythonCode_chain.invoke({"question": "How much is 5 factorial?"})
# 'import math\n\nresult = math.factorial(5)\nprint(result)'

## (b) Run Python codes
PythonCodeRun_chain.invoke({"question": "How much is 5 factorial?"})
# '120\n'

 

 

위에 간단한 자연어 질의에는 모두 정확하게 답변을 했는데요, 그래도 반드시 Python codes 를 확인하는 절차를 밟는게 필요해보입니다. 

 

 

[ Reference ]

* LangChain - Code Writing: 

https://python.langchain.com/docs/expression_language/cookbook/code_writing

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

대규모 언어 모델 (Large Language Model, LLM) 이 사실에 입각하지 않고 그럴싸한 답변을 생성하는 것을 환각 (Hallucinations) 이라고 합니다. LLM의 환각을 방지하는 방법 중의 하나로 RAG (Retrieval-Agumented Generation) 이 많이 사용되고 있는데요, 이전에 소개했던 RAG 는 비정형 데이터인 텍스트를 임베딩하여 Semactic Search를 하여 Context로 사용할 문서를 LLM에 인풋으로 넣어주는 방법이었습니다. 

 

이번 포스팅에서는 LangChain과 ChatGTP를 이용해서 PostgreSQL, Greenplum Database의 테이블에 행, 열의 테이블에 저장되어 있는 정형 데이터 (Structured Data)에 대해서 SQL Query를 사용해서 질의를 해서 사실 (Fact) 데이터를 Query 해와서, 이를 기반으로 LLM 모델이 답변을 생성하는 방법을 소개하겠습니다. 자연어로 질문하고 자연어로 답변을 받게 되면 DB나 SQL Query를 모르는 일반 사용자도 DB에 저장되어 있는 사실에 기반한 최신의 내부 데이터까지도 활용해서 정확한 답변을 받을 수 있게 됩니다. 

 

 

먼저, PostgreSQL, Greenplum 데이터베이스에 대해서 간략히 소개하겠습니다. 

 

 

1. PostgreSQL 데이터베이스란? 

 

PostgreSQL, 종종 Postgres로 불리는 이 데이터베이스는 강력한 오픈 소스 객체-관계형 데이터베이스 시스템입니다. 이는 신뢰성, 기능의 견고함, 그리고 성능 면에서 좋은 평판을 가지고 있습니다.  

PostgreSQL은 객체-관계형 데이터베이스로서, 전통적인 관계형 데이터베이스 기능인 테이블 기반 데이터 저장 및 SQL(Structured Query Language) 쿼리와 더불어 객체 지향 데이터베이스 기능인 객체와 클래스에 데이터를 저장하는 것을 지원합니다. 

이는 무료로 자유롭게 사용할 수 있는 오픈 소스 소프트웨어입니다. 그 소스 코드는 누구나 열람하거나 개선하길 원하는 사람에게 공개되어 있습니다. 

PostgreSQL의 핵심 강점 중 하나는 확장성입니다. 사용자는 자신만의 데이터 타입, 인덱스 타입, 함수 언어 등을 정의할 수 있습니다. 

 

2. Greenplum 데이터베이스란? 

 

Greenplum 데이터베이스는 고급 기능을 갖춘 오픈 소스 데이터 웨어하우스입니다. 이는 페타바이트 규모의 빅데이터에 대해 강력하고 빠른 분석을 제공합니다. 원래 PostgreSQL에서 파생되었지만, Greenplum 데이터베이스는 그 능력과 성능을 크게 확장해 발전시켰습니다. 

Greenplum은 Shared-Nothing, 대규모 병렬 처리 아키텍처(Massively Parallel Processing Architecture, MPP)를 사용합니다. 이는 데이터가 여러 세그먼트로 나뉘고 각 세그먼트가 병렬로 처리되어 고성능 및 대규모 데이터 분석을 가능하게 한다는 의미입니다. 

확장성(Scalability)을 위해 설계된 Greenplum은 큰 데이터 볼륨을 처리할 수 있습니다. 이는 많은 서버에 걸쳐 확장되어 큰 데이터 볼륨과 복잡한 쿼리를 관리할 수 있습니다. 

Greenplum은 고급 분석 기능을 지원합니다. 이는 Python, R 및 기타 통계 및 머신 러닝 라이브러리와 통합되어 데이터베이스 내에서 고급 데이터 분석을 직접 수행할 수 있게 합니다. 

(Greenplum DB 소개 참고: https://rfriend.tistory.com/377)

 

 

[ LangChain과 ChatGPT를 사용하여 자연어로 PostgreSQL, Greenplum에 Query하는 Workflow ]

LangChain - ChatGPT - PostgreSQL, Greenplum DB에 자연어로 질의하여 Query 하기

 

 

 

3. LangChain, ChatGPT로 PostgreSQL에 자연어로 Query해서 답변 생성하기

 

(1) 사용자가 애플리케이션에서 자연어로 질의
(2) LangChain이 LLM 모델에 사용자 질문을 SQL Query로 변환 요청
(3) LLM 모델이 DB Schema 정보에 기반해 사용자 질문에 대한 SQL Query를 생성해서 반환
(4) LangChain이 PostgreSQL, Greenplum DB에 SQL Query 실행 요청
(5) PostgreSQL, Greenplum DB에서 SQL Query 실행하여 결과 반환
(6) LangChain이 사용자 질문과 SQL Query, Query 결과를 기반으로 LLM 모델에 답변 생성 요청 
(7) LLM 모델이 사용자 질문과 Query 결과를 기반으로 생성한 자연어 답변 반환
(8) LangChain이 애플리케이션의 사용자 UI에 LLM 모델이 생성한 자연어 답변 반환

 

 

 

(0) 준비사항: PostgreSQL에 예제 테이블 생성하고 데이터 집어넣기

 

먼저, 예제로 사용할 PostgreSQL DB에 iris 라는 테이블을 만들어보겠습니다. 그리고, 나중에 자연어로 질의할 내용을 미리 SQL Query로 결과를 조회해보았습니다. 

 

DROP TABLE IF EXISTS iris;
CREATE TABLE iris (id INT, sepal_length FLOAT, sepal_width FLOAT,
                    petal_length FLOAT, petal_width FLOAT,
                   class_name text);

INSERT INTO iris VALUES
(1,5.1,3.5,1.4,0.2,'Iris-setosa'),
(2,4.9,3.0,1.4,0.2,'Iris-setosa'),
(3,4.7,3.2,1.3,0.2,'Iris-setosa'),
(4,4.6,3.1,1.5,0.2,'Iris-setosa'),
(5,5.0,3.6,1.4,0.2,'Iris-setosa'),
(6,5.4,3.9,1.7,0.4,'Iris-setosa'),
(7,4.6,3.4,1.4,0.3,'Iris-setosa'),
(8,5.0,3.4,1.5,0.2,'Iris-setosa'),
(9,4.4,2.9,1.4,0.2,'Iris-setosa'),
(10,4.9,3.1,1.5,0.1,'Iris-setosa'),
(11,7.0,3.2,4.7,1.4,'Iris-versicolor'),
(12,6.4,3.2,4.5,1.5,'Iris-versicolor'),
(13,6.9,3.1,4.9,1.5,'Iris-versicolor'),
(14,5.5,2.3,4.0,1.3,'Iris-versicolor'),
(15,6.5,2.8,4.6,1.5,'Iris-versicolor'),
(16,5.7,2.8,4.5,1.3,'Iris-versicolor'),
(17,6.3,3.3,4.7,1.6,'Iris-versicolor'),
(18,4.9,2.4,3.3,1.0,'Iris-versicolor'),
(19,6.6,2.9,4.6,1.3,'Iris-versicolor'),
(20,5.2,2.7,3.9,1.4,'Iris-versicolor'),
(21,6.3,3.3,6.0,2.5,'Iris-virginica'),
(22,5.8,2.7,5.1,1.9,'Iris-virginica'),
(23,7.1,3.0,5.9,2.1,'Iris-virginica'),
(24,6.3,2.9,5.6,1.8,'Iris-virginica'),
(25,6.5,3.0,5.8,2.2,'Iris-virginica'),
(26,7.6,3.0,6.6,2.1,'Iris-virginica'),
(27,4.9,2.5,4.5,1.7,'Iris-virginica'),
(28,7.3,2.9,6.3,1.8,'Iris-virginica'),
(29,6.7,2.5,5.8,1.8,'Iris-virginica'),
(30,7.2,3.6,6.1,2.5,'Iris-virginica');

SELECT * FROM iris ORDER BY id LIMIT 5;
-- 5.1	3.5	1.4	0.2	"Iris-setosa"
-- 4.9	3	1.4	0.2	"Iris-setosa"
-- 4.7	3.2	1.3	0.2	"Iris-setosa"
-- 4.6	3.1	1.5	0.2	"Iris-setosa"
-- 5	3.6	1.4	0.2	"Iris-setosa"

SELECT 
    AVG(sepal_length) 
FROM iris 
WHERE class_name = 'Iris-setosa';
-- 4.859999999999999

SELECT 
	class_name,
	AVG(sepal_length) 
FROM iris 
GROUP BY class_name
ORDER BY class_name;

-- class_name         avg
-- "Iris-setosa"	  4.85
-- "Iris-versicolor"  6.10
-- "Iris-virginica"   6.57


SELECT 
	class_name
	, MAX(sepal_width) 
FROM iris 
GROUP BY class_name 
ORDER BY class_name;

-- class_name          max
-- "Iris-setosa"	   3.9
-- "Iris-versicolor"   3.3
-- "Iris-virginica"    3.6

 

 

 

(0) 준비사항: Python 모듈 설치

 

LLM 모델을 통한 답변 생성에 필요한 openai, langchain, 그리고 PostgreSQL, Greenplum DB access를 위해 필요한 psycopg2 모듈을 pip install을 사용해서 설치합니다. 

 

! pip install -q openai langchain psycopg2

 

 

(0) 준비사항: OpenAI API Key 설정

 

OpenAI의 ChatGPT를 사용하기 위해 필요한 OpenAI API Key를 등록합니다. 

(OpenAI API Key 발급 방법은 https://rfriend.tistory.com/794 를 참고하세요)

 

import os

# setup OpenAI API Key with yours
os.environ["OPENAI_API_KEY"]="sk-xxxx..." # set with yours

 

 

(0) 준비사항: PostgreSQL DB 연결

 

이제 LangChain의 SQLDatabase() 클래스를 사용해서 PostgreSQL DB에 연결하겠습니다. 

(DB credientials 는 사용 중인 걸로 바꿔주세요)

 

# Connect to the PostgreSQL DB
from langchain.utilities import SQLDatabase

# set with yours
username='postgres'
password='changeme'
host='localhost'
port='5432'
database='postgres'

pg_uri = f"postgresql+psycopg2://{username}:{password}@{host}:{port}/{database}"
        
db = SQLDatabase.from_uri(pg_uri)

 

 

(0) 준비사항: Query를 위한 Helper Function 정의

 

DB의 Table 정보 (테이블 이름, 칼럼 정보) 를 가져오고, SQL Query를 실행할 수 있는 Helper 함수를 정의합니다. 이제 준비가 다 되었네요. 

 

# Helper functions
def get_schema(_):
    return db.get_table_info()
    
def run_query(query):
    return db.run(query)

 

 

 

(1) 사용자가 애플리케이션에서 자연어로 질의

 

사용자가 자연어로 질의한 질문과 DB테이블 정보를 인풋으로 받아서, "DB 테이블 정보 ("scheam")와 사용자 질문 ("question")이 주어졌을 때 PostgreSQL Query를 생성하라"고 프롬프트에 지시문을 생성하였습니다. 

 

# Prompt for generating a SQL query
from langchain.prompts import ChatPromptTemplate

template_query = """
Based on the table schema below, \
Write a PostgreSQL query that answer the user's question:
{schema}

Question: {question}
SQL Query:"""

prompt = ChatPromptTemplate.from_template(template_query)

 

 

 

(2) LangChain이 LLM 모델에 사용자 질문을 SQL Query로 변환 요청

 

LLM에는 ChatGPT-4 모델을 사용하고, temperature=0 으로 해서 사실에 기반해서 일관성 있고 보수적인 답변을 생성하도록 설정했습니다. 

 

# Chaining prompt, LLM model, and Output Parser
from langchain.chat_models import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

model = ChatOpenAI(temperature=0, model_name='gpt-4')

sql_response = (
    RunnablePassthrough.assign(schema=get_schema) 
    | prompt
    | model.bind(stop=["\nSQLResult:"])
    | StrOutputParser()
)

 

 

 

(3) LLM 모델이 DB Schema 정보에 기반해 사용자 질문에 대한 SQL Query를 생성해서 반환

 

sql_response.invoke({"question": "What is the average of sepal length for Iris-setosa?"})
# "SELECT AVG(sepal_length) \nFROM iris \nWHERE class_name = 'Iris-setosa';"

 

 

 

(4) LangChain이 PostgreSQL, Greenplum DB에 SQL Query 실행 요청

 

아래와 같이 "DB 테이블 정보 ("schema"), 사용자 질문 ("question"), SQL Query ("query"), SQL Query 결과 ("response") 가 주어졌을 때 자연어(natural language)로 답변을 생성하라"고 프롬프트 지시문을 만들었습니다. 

 

# Prompt for generating the final answer by running a SQL query on DB
template_response = """
Based on the table schema below, question, sql query, and sql response, \
write a natural language response:
{schema}

Question: {question}
SQL Query: {query}
SQL Response: {response}"""

prompt_response = ChatPromptTemplate.from_template(template_response)

 

 

 

(5) PostgreSQL, Greenplum DB에서 SQL Query 실행하여 결과 반환

 

'|' 를 사용해서 앞에서 정의한 SQL Query 생성하는 chain과 Query를 실행한 결과를 받아서 자연어로 답변을 생성하는 chain을 모두 엮어서 Chaining 하였습니다. 

 

full_chain = (
    RunnablePassthrough.assign(query=sql_response)
    | RunnablePassthrough.assign(
        schema=get_schema, 
        response=lambda x: db.run(x["query"]),
    )
    | prompt_response
    | model
    | StrOutputParser()
)

 

 

 

(6) LangChain이 사용자 질문과 SQL Query, Query 결과를 기반으로 LLM 모델에 답변 생성 요청 

(7) LLM 모델이 사용자 질문과 Query 결과를 기반으로 생성한 자연어 답변 반환

 

자연어로 3개의 질문을 해보았는데요, 기대했던 SQL Query 문이 생성되고 Query 질의 결과를 기반으로 답변이 모두 정확하게 생성되었습니다. 

 

Q1: What is the average of sepal length for Iris-setosa?

 

## Q: What is the average of sepal length for Iris-setosa?

sql_response.invoke({"question": "What is the average of sepal length for Iris-setosa?"})
# "SELECT AVG(sepal_length) \nFROM iris \nWHERE class_name = 'Iris-setosa';"

full_chain.invoke({"question": "What is the average of sepal length for Iris-setosa?"})
# 'The average sepal length for Iris-setosa is approximately 4.86.'

 

 

Q2: What is the average of sepal length by class name?

 

## Q: What is the average of sepal length by class name?

sql_response.invoke({"question": "What is the average of sepal length by class name?"})
# 'SELECT class_name, AVG(sepal_length) \nFROM iris \nGROUP BY class_name;'


full_chain.invoke({"question": "What is the average of sepal length by class name?"})
#'The average sepal length for the Iris-versicolor class is 6.1, 
#for the Iris-setosa class is approximately 4.86, 
#and for the Iris-virginica class is 6.57.'

 

 

Q3: What is the maximum value of sepal width by class name?

 

## Q: What is the maximum value of sepal width by class name?

sql_response.invoke({"question": "What is the maximum value of sepal width by class name?"})
# 'SELECT class_name, MAX(sepal_width) \nFROM iris \nGROUP BY class_name;'


full_chain.invoke({"question": "What is the maximum value of sepal width by class name?"})
# "The maximum sepal width for the class 'Iris-versicolor' is 3.3, 
# for 'Iris-setosa' is 3.9, 
# and for 'Iris-virginica' is 3.6."

 

 

위의 예는 매우 간단한 질문이어서 하나의 테이블 내에서 Join 없이도 모두 정확하게 Querying 이 되었는데요, 그래도 mission critical한 업무에서는 사용에 유의할 필요가 있습니다. 왜냐하면 실무에서 사용하는 SQL Query 문의 경우 여러개의 테이블을 Join 해야 될 수도 있고, Where 조건절에 Business Logic이 복잡하게 반영되어야 할 경우도 있고, 테이블 이름/설명이나 변수명/설명이 LLM 모델이 사용자의 질의와 매핑해서 사용하기에 부적절한 경우도 있을 수 있어서 SQL Query가 부정확할 수 있기 때문입니다.

 

따라서 반드시 사용자 질의에 대한 SQL Query 문을 같이 확인해보는 절차가 필요합니다. 

 

그리고 SQL Query Generator 를 잘하는 LLM 모델, 자사 회사 내 SQL Query 문으로 Fine-tuning한 LLM 모델에 대한 시도도 의미있어 보입니다. 

 

 

[ Reference ]

* LangChain - Querying a SQL DB:

https://python.langchain.com/docs/expression_language/cookbook/sql_db

* PostgreSQL 설치(OS 별 packages, installers): https://www.postgresql.org/download/

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,