AI/Prompt Engineering

ChatGPT Prompt Engineering for Developers 정리 [1]

sangwonYoon 2023. 4. 30. 00:08
  • 스탠퍼드 대학교의 Andrew Ng 교수님이 강의하신 ChatGPT Prompt Engineering for Developers 강의를 듣고 정리한 글입니다.
  • 단순히 강의 내용을 번역한것이 아니라 내용을 이해한 뒤 정리하였고, 제 개인적인 생각은 +) 표시 뒤에 덧붙였습니다.
  • 해당 글은 Introduction과 Guidelines까지의 내용을 담고 있으며, 이후 내용은 추후에 포스팅할 예정입니다.
  • 훌륭한 강의이므로 아래 링크에서 직접 들어보시는 것을 추천드립니다.
 

ChatGPT Prompt Engineering for Developers

What you’ll learn in this course In ChatGPT Prompt Engineering for Developers, you will learn how to use a large language model (LLM) to quickly build new and powerful applications.  Using the OpenAI API, you’ll...

www.deeplearning.ai


Introduction

인터넷에 돌아다니는 프롬프팅에 관한 자료들의 대부분은 특정 작업 또는 일회성 작업을 위한 ChatGPT 웹 유저 인터페이스에 초점이 맞춰져 있다. 개발자로서 LLM(Large Language Model)의 힘은 LLM의 API 호출을 사용하여 소프트웨어 어플리케이션을 빠르게 구축하는 것이지만, 아직까지는 상당히 과소평가되어 있다.

LLM의 개발에 있어서 LLM을 크게 Base LLMInstruction Tuned LLM 두가지 유형으로 나눌 수 있다.

 

Base LLM

Base LLM은 인터넷을 포함한 다양한 출처의 많은 양의 텍스트 훈련 데이터를 기반으로 다음에 등장할 단어를 예측하도록 훈련된다.

예를 들어, “Once upon a time, there was a unicorn”이라는 문장을 모델에 입력하게 되면 “that lived in a magical forest with all her unicorn friends”라는 내용을 생성하여 문장을 완성할 것이다.

반면, “What is the capital of France?”라는 문장을 입력하게 되면 모델이 학습한 인터넷 게시물을 기반으로 “What is France’s largest city? What is France’s population? What is the currency of France?” 와 같이 예상한 것과는 다른 형태의 답변을 내놓을 것이다.

Base LLM

 

Instruction Tuned LLM

Instruction Tuned LLM은 지시 사항을 따르도록 훈련된다.

따라서 “What is the capital of France?” 라는 문장을 입력하게 되면 “The capital of France is Paris.”와 같은 답변을 내놓을 것이다.

일반적으로 Instruction Tuned LLM을 학습시키는 방법은 많은 양의 텍스트 데이터로 학습한 Base LLM을 기반으로 목적에 맞는 input과 output들을 통해 fine tuning을 진행한다.

여기에 추가적으로 RLHF(Reinforcement Learning with Human Feedback)이라는 테크닉을 사용해 더 적절한 답변을 생성할 수 있도록 정제한다.

또한 Helpful, Honest, Harmless한 답변을 생성하기 위해 훈련되기 때문에 Base LLM에 비해 부적절하거나 문제의 여지가 있는 텍스트를 생성할 확률이 적다.

Instruction Tuned LLM

Instruction Tuned LLM을 사용하는 것은 매우 똑똑하지만 우리가 하고자 하는 일에 대해 모르는 사람에게 지시를 내린다고 생각하면 편하다.

예를 들어, 단순히 Alan Turing에 관한 글을 써달라고 하는 것보다 정확히 어떤 내용의 글을 쓰기를 원하는지, 글의 어조는 어떤 어조를 원하는지, 글을 쓸 때 참고할만한 snippet이 있는지 정확하게 명시하는 것이 중요하다.

 

Guidelines

주의) 이 강의에서 backslash( \ )를 줄바꿈 문자를 대신하여 사용하고 있지만, GPT-3가 아닌 다른 일반적인 LLM에서는 이 문자가 모델의 성능에 영향을 미칠 수 있다.

아래는 OpenAI의 gpt-3.5-turbo 모델을 사용하기 위해 강의에서 사용한 코드이다.

import openai
import os

from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())

openai.api_key  = os.getenv('OPENAI_API_KEY')

def get_completion(prompt, model="gpt-3.5-turbo"):
    messages = [{"role": "user", "content": prompt}]
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=0, # this is the degree of randomness of the model's output
    )
    return response.choices[0].message["content"]

 

프롬프팅 기본 원칙

  1. 명확하고 구체적인 지시를 작성하라.
  2. 모델에게 생각할 시간을 줘라.

명확하고 구체적인 지시를 작성하라

프롬프트를 명확하게 작성하는 것을 짧게 작성하는 것으로 이해해서는 안된다. 대부분의 경우 긴 프롬프트는 모델에게 명확한 맥락을 전달하기 때문에 더 상세하고 연관성 있는 답변을 얻을 수 있다.

팁 1) 구분자를 사용해라.
text = f"""
You should express what you want a model to do by \ 
providing instructions that are as clear and \ 
specific as you can possibly make them. \ 
This will guide the model towards the desired output, \ 
and reduce the chances of receiving irrelevant \ 
or incorrect responses. Don't confuse writing a \ 
clear prompt with writing a short prompt. \ 
In many cases, longer prompts provide more clarity \ 
and context for the model, which can lead to \ 
more detailed and relevant outputs.
"""
prompt = f"""
Summarize the text delimited by triple backticks \ 
into a single sentence.
```{text}```
"""
response = get_completion(prompt)
print(response)

위 코드에서 요약할 텍스트를 지시사항과 구분하기 위해 구분자로 3개의 백틱(```)을 사용했다. 구분자는 백틱, 따옴표, XML 태그, 섹션 제목 등 프롬프트와 구분할 내용을 명확하게 분리할 수 있는 것이라면 모두 사용할 수 있다.

구분자를 사용하는 것은 Prompt Injection을 방지할 때에도 유용한 테크닉이다.

Prompt Injection이란, 소프트웨어를 사용하는 사용자가 프롬프트에 입력을 추가할 수 있는 경우, 원래의 목적과 상충되는 지시를 하여 개발자가 원하는 방식으로 모델이 작동하는 것이 아닌, 사용자의 지시를 따르도록 만드는 것을 말한다.

Avoiding Prompt Injection

 

팁 2: 구조화된 출력을 요청하라.

HTML 또는 JSON 구조의 출력을 요청하면 결과물을 더 쉽게 분석할 수 있다.

prompt = f"""
Generate a list of three made-up book titles along \ 
with their authors and genres. 
Provide them in JSON format with the following keys: 
book_id, title, author, genre.
"""
response = get_completion(prompt)
print(response)
# 출력:
[
  {
    "book_id": 1,
    "title": "The Lost City of Zorath",
    "author": "Aria Blackwood",
    "genre": "Fantasy"
  },
  {
    "book_id": 2,
    "title": "The Last Survivors",
    "author": "Ethan Stone",
    "genre": "Science Fiction"
  },
  {
    "book_id": 3,
    "title": "The Secret of the Haunted Mansion",
    "author": "Lila Rose",
    "genre": "Mystery"
  }
]

 

팁 3: 모델에게 조건을 제시하라.
text_1 = f"""
Making a cup of tea is easy! First, you need to get some \ 
water boiling. While that's happening, \ 
grab a cup and put a tea bag in it. Once the water is \ 
hot enough, just pour it over the tea bag. \ 
Let it sit for a bit so the tea can steep. After a \ 
few minutes, take out the tea bag. If you \ 
like, you can add some sugar or milk to taste. \ 
And that's it! You've got yourself a delicious \ 
cup of tea to enjoy.
"""
prompt = f"""
You will be provided with text delimited by triple quotes. 
If it contains a sequence of instructions, \ 
re-write those instructions in the following format:

Step 1 - ...
Step 2 - …
…
Step N - …

If the text does not contain a sequence of instructions, \ 
then simply write \"No steps provided.\"

\"\"\"{text_1}\"\"\"
"""
response = get_completion(prompt)
print("Completion for Text 1:")
print(response)

위 코드에서 프롬프트를 통해 3개의 큰 따옴표로 구분되는 텍스트의 내용이 만약 지시 사항들을 나열한 것이라면, Step N - 내용의 형식으로 출력하고, 그렇지 않다면 No steps provided라는 내용을 출력하라고 모델에게 지시하고 있다.

출력:
Completion for Text 1:
Step 1 - Get some water boiling.
Step 2 - Grab a cup and put a tea bag in it.
Step 3 - Once the water is hot enough, pour it over the tea bag.
Step 4 - Let it sit for a bit so the tea can steep.
Step 5 - After a few minutes, take out the tea bag.
Step 6 - Add some sugar or milk to taste.
Step 7 - Enjoy your delicious cup of tea!

 

팁 4: Few-shot 프롬프팅

few-shot이란, 모델에게 수행할 task의 성공적인 출력 예시를 제공하는 것을 말한다.

prompt = f"""
Your task is to answer in a consistent style.

<child>: Teach me about patience.

<grandparent>: The river that carves the deepest \ 
valley flows from a modest spring; the \ 
grandest symphony originates from a single note; \ 
the most intricate tapestry begins with a solitary thread.

<child>: Teach me about resilience.
"""
response = get_completion(prompt)
print(response)
출력:
<grandparent>: Resilience is like a tree that bends with the wind but \
never breaks. It is the ability to bounce back from adversity and keep \
moving forward, even when things get tough. Just like a tree that grows \
stronger with each storm it weathers, resilience is a quality that can be \
developed and strengthened over time.

 

모델에게 생각할 시간을 줘라

모델이 추론 오류를 일으키는 경우, 관련된 추론을 순서대로 모델에게 물어본 뒤에 최종 답변을 하도록 질문을 재구성해야 한다.

팁 1: task를 완료하기 위해 수행해야할 작업들을 순서대로 지정하라.
text = f"""
In a charming village, siblings Jack and Jill set out on \ 
a quest to fetch water from a hilltop \ 
well. As they climbed, singing joyfully, misfortune \ 
struck—Jack tripped on a stone and tumbled \ 
down the hill, with Jill following suit. \ 
Though slightly battered, the pair returned home to \ 
comforting embraces. Despite the mishap, \ 
their adventurous spirits remained undimmed, and they \ 
continued exploring with delight.
"""
# example 1
prompt_1 = f"""
Perform the following actions: 
1 - Summarize the following text delimited by triple \
backticks with 1 sentence.
2 - Translate the summary into French.
3 - List each name in the French summary.
4 - Output a json object that contains the following \
keys: french_summary, num_names.

Separate your answers with line breaks.

Text:
```{text}```
"""
response = get_completion(prompt_1)
print("Completion for prompt 1:")
print(response)

위 코드의 프롬프트에서 수행해야할 작업들을 순서대로 지정해주고 있다.

출력:
Completion for prompt 1:
Two siblings, Jack and Jill, go on a quest to fetch water from a well on a hilltop, but misfortune strikes and they both tumble down the hill, returning home slightly battered but with their adventurous spirits undimmed.

Deux frères et sœurs, Jack et Jill, partent en quête d'eau d'un puits sur une colline, mais un malheur frappe et ils tombent tous les deux de la colline, rentrant chez eux légèrement meurtris mais avec leurs esprits aventureux intacts. 
Noms: Jack, Jill. 

{
  "french_summary": "Deux frères et sœurs, Jack et Jill, partent en quête d'eau d'un puits sur une colline, mais un malheur frappe et ils tombent tous les deux de la colline, rentrant chez eux légèrement meurtris mais avec leurs esprits aventureux intacts.",
  "num_names": 2
}

 

여기서 추가적으로 모델에게 출력 포맷을 제공하면 더 정돈된 출력 결과물을 얻을 수 있다.

prompt_2 = f"""
Your task is to perform the following actions: 
1 - Summarize the following text delimited by 
  <> with 1 sentence.
2 - Translate the summary into French.
3 - List each name in the French summary.
4 - Output a json object that contains the 
  following keys: french_summary, num_names.

Use the following format:
Text: <text to summarize>
Summary: <summary>
Translation: <summary translation>
Names: <list of names in Italian summary>
Output JSON: <json with summary and num_names>

Text: <{text}>
"""
response = get_completion(prompt_2)
print("\nCompletion for prompt 2:")
print(response)
출력:
Completion for prompt 2:
Summary: Jack and Jill go on a quest to fetch water, but misfortune strikes and they tumble down the hill, returning home slightly battered but with their adventurous spirits undimmed. 
Translation: Jack et Jill partent en quête d'eau, mais la malchance frappe et ils dégringolent la colline, rentrant chez eux légèrement meurtris mais avec leurs esprits aventureux intacts.
Names: Jack, Jill
Output JSON: {"french_summary": "Jack et Jill partent en quête d'eau, mais la malchance frappe et ils dégringolent la colline, rentrant chez eux légèrement meurtris mais avec leurs esprits aventureux intacts.", "num_names": 2}

 

팁 2: 모델에게 결론을 내리기 전에 자체적으로 솔루션을 수행하도록 지시하라.

먼저, 모델이 잘못된 결론을 내리는 경우를 살펴보자.

prompt = f"""
Determine if the student's solution is correct or not.

Question:
I'm building a solar power installation and I need \
 help working out the financials. 
- Land costs $100 / square foot
- I can buy solar panels for $250 / square foot
- I negotiated a contract for maintenance that will cost \ 
me a flat $100k per year, and an additional $10 / square \
foot
What is the total cost for the first year of operations 
as a function of the number of square feet.

Student's Solution:
Let x be the size of the installation in square feet.
Costs:
1. Land cost: 100x
2. Solar panel cost: 250x
3. Maintenance cost: 100,000 + 100x
Total cost: 100x + 250x + 100,000 + 100x = 450x + 100,000
"""
response = get_completion(prompt)
print(response)
출력: The student's solution is correct.

위 코드는 모델에게 학생의 풀이가 올바른지 여부를 판단하라고 지시한다.

학생의 풀이에서 Maintenance cost를 100,000 + 100x에서 100,000 + 10x로 수정해야 올바른 정답이지만 모델은 학생의 풀이가 올바르다고 잘못 판단하고 있다.

+) 문제와 학생의 풀이를 연관지어 생각하지 않고, 학생의 풀이만 보고 오류가 없기 때문에 올바르다는 판단을 내리는 것 같다.

 

이러한 문제는 아래와 같이 모델에게 추가적인 지시를 내려 해결할 수 있다.

prompt = f"""
Your task is to determine if the student's solution \
is correct or not.
To solve the problem do the following:
- First, work out your own solution to the problem. 
- Then compare your solution to the student's solution \ 
and evaluate if the student's solution is correct or not. 
Don't decide if the student's solution is correct until 
you have done the problem yourself.

Use the following format:
Question:
```
question here
```
Student's solution:
```
student's solution here
```
Actual solution:
```
steps to work out the solution and your solution here
```
Is the student's solution the same as actual solution \
just calculated:
```
yes or no
```
Student grade:
```
correct or incorrect
```

Question:
```
I'm building a solar power installation and I need help \
working out the financials. 
- Land costs $100 / square foot
- I can buy solar panels for $250 / square foot
- I negotiated a contract for maintenance that will cost \
me a flat $100k per year, and an additional $10 / square \
foot
What is the total cost for the first year of operations \
as a function of the number of square feet.
``` 
Student's solution:
```
Let x be the size of the installation in square feet.
Costs:
1. Land cost: 100x
2. Solar panel cost: 250x
3. Maintenance cost: 100,000 + 100x
Total cost: 100x + 250x + 100,000 + 100x = 450x + 100,000
```
Actual solution:
"""
response = get_completion(prompt)
print(response)
Let x be the size of the installation in square feet.

Costs:
1. Land cost: 100x
2. Solar panel cost: 250x
3. Maintenance cost: 100,000 + 10x

Total cost: 100x + 250x + 100,000 + 10x = 360x + 100,000

Is the student's solution the same as actual solution just calculated:
No

Student grade:
Incorrect

 

+) 모델의 출력이 프롬프트에 작성한 포맷을 따르지 않아서 포맷을 제시하는 내용을 빼고 입력했더니 모델이 다시 잘못된 판단을 내린다.

prompt = f"""
Your task is to determine if the student's solution \
is correct or not.
To solve the problem do the following:
- First, work out your own solution to the problem. 
- Then compare your solution to the student's solution \ 
and evaluate if the student's solution is correct or not. 
Don't decide if the student's solution is correct until 
you have done the problem yourself.

Question:
```
I'm building a solar power installation and I need help \
working out the financials. 
- Land costs $100 / square foot
- I can buy solar panels for $250 / square foot
- I negotiated a contract for maintenance that will cost \
me a flat $100k per year, and an additional $10 / square \
foot
What is the total cost for the first year of operations \
as a function of the number of square feet.
``` 
Student's solution:
```
Let x be the size of the installation in square feet.
Costs:
1. Land cost: 100x
2. Solar panel cost: 250x
3. Maintenance cost: 100,000 + 100x
Total cost: 100x + 250x + 100,000 + 100x = 450x + 100,000
```
Actual solution:
"""
response = get_completion(prompt)
print(response)
The student's solution is correct. \
The total cost for the first year of operations is indeed 450x + 100,000, \
where x is the size of the installation in square feet.

모델에게 자체 솔루션을 우리에게 출력하라는 지시를 하지 않으면 자체 솔루션을 수행하여 비교하지 않는 것 같다.

 

환각을 줄이기 위해 모델에게 제한을 두는 방법

모델은 많은 양의 정보를 통해 학습되지만, 모든 학습 정보를 완벽히 기억하지 못하기 때문에 지식의 경계가 모호하다. 이로 인해 모델은 그럴듯하지만 사실이 아닌 답변을 생성할 수 있다. 이를 환각(Hallucination)이라고 부른다.

+) 모델이 모든 지식을 기억하고 답변하는 것이 아니라 제한적인 지식만이 모델 내부에 저장되어 있고, 그것을 바탕으로 문장을 말이 되게 짜맞추다 보니 사실과 다를 수 있다는 의미인 것 같다.

ChatGPT에서 유명한 세종대왕 맥북프로 던짐 사건이 그 예이다.

 

환각을 줄이기 위한 방법

모델에게 관련된 자료를 찾아달라고 요청한 다음, 해당 자료를 바탕으로 답변을 해달라고 요청하면 잘못된 답변을 줄일 수 있다.