프롬프트 엔지니어링 vs 블라인드 프롬프팅
원문은 Mitchell Hashimoto님이 에 게재했습니다. 이 블로그 구독하기
"프롬프트 엔지니어링"이라는 용어는 언어 모델의 성장과 함께 등장했으며, 일반적으로 실제 애플리케이션에서 활용하기 위해 프롬프팅을 적용해 언어 모델로부터 정보를 효과적으로 추출하는 과정을 설명한다.
오늘날 프롬프트 엔지니어링을 하고 있다고 주장하는 많은 사람들은 실제로는 그저 블라인드 프롬프팅을 하고 있을 뿐이다.1 "블라인드 프롬프팅"은 최소한의 테스트도 없이, 혹은 거의 없이 조잡한 시행착오 방식으로 프롬프트를 만들고 프롬프팅에 대한 피상적인 지식만을 곁들여 만드는 방법을 설명하기 위해 내가 사용하는 용어다. 블라인드 프롬프팅은 프롬프트 엔지니어링이 아니다.
프롬프트 엔지니어링을 정말로 "엔지니어링"이라고 부를 수 있는지, 아니면 그저 유행에 편승한 사람들이 떠드는 "마법"에 불과한지에 대한 회의론도 많다. 내 생각에 대부분의 회의론은 내가 봐온 프롬프트 엔지니어링을 다룬다는 수많은 트윗과 블로그 글이 기껏해야 블라인드 프롬프팅보다 한 겹 정도 나은 수준에 불과하다는 사실에 뿌리를 두고 있다.
이 블로그 글에서는 프롬프트 엔지니어링이 실제 실험 방법론에 기반해 개발할 수 있는 진짜 기술이라는 주장을 펼치고자 한다. 애플리케이션에 실질적인 가치를 제공하는 문제를 해결하는 과정을 현실적인 예시를 통해 프롬프트 엔지니어링 관점에서 단계별로 살펴볼 것이다.
이 글 전체는 텍스트 출력을 전제로 편향되어 있다. 내가 언어 모델을 주로 텍스트 출력 용도로 사용해 왔기 때문이다. 테스트 기법과 같은 일부 기법은 이미지와 같은 다른 형태의 출력에는 1:1로 그대로 적용되지 않는다. 다만 이 글에서 다루는 모든 내용은 멀티모달 입력에는 잘 동작한다.
프롬프팅이란 무엇인가?
"프롬프팅"이라는 용어나 "프롬프트"가 무엇인지 이미 잘 알고 있다면 이 섹션은 건너뛰어도 좋다.
언어 모델에서(이 용어가 낯설다면 ChatGPT를 떠올리면 된다) "프롬프트"는 사용자가 모델에 입력하는 값이다. ChatGPT에서는 프롬프트를 사실상 텍스트 입력창에 타이핑하는 내용이라고 이해하면 된다. 그러면 언어 모델이 프롬프트에 대한 "완성(completion)"을 "추론"한다. 예를 들어 ChatGPT에 "4 + 3 = "이라고 입력하면 "7"이라고 응답할 것이다(아마도). 이 경우 "4 + 3 = "이 프롬프트이고 "7"이 완성이다.
"프롬프팅"은 모델로부터 원하는 정보를 추출하는 방법으로 프롬프트를 사용하는 행위다. 대규모 오프라인 학습 데이터셋이 필요 없고, 모델에 대한 오프라인 접근이 필요 없으며, 비엔지니어에게도 직관적으로 느껴진다는 점에서 정보를 추출하는 매력적인 접근법이다. 프롬프팅은 모델을 튜닝하는 여러 방법 중 하나일 뿐이다.
마지막으로 "프롬프트 엔지니어링"은(이 글에서 보여주겠지만) 프롬프팅을 활용해 실제 애플리케이션을 위한 신뢰할 수 있는 기능을 구축하려는 보다 엄격한 분야를 의미한다. ChatGPT 스타일의 프롬프팅과 다른 점은, 프롬프트 엔지니어링을 통해 만들어진 프롬프트는 일반적으로 애플리케이션의 특정 문제를 안정적으로 해결하기 위해 대량의 다양한 상황에서 반복적으로 사용되도록 만들어진다는 것이다.
문제
시작하려면 해결하려는 문제가 있어야 한다. 그 문제를 통해 프롬프팅이 최선의 해결책인지, 혹은 더 적합할 수 있는 다른 대안이 존재하는지 평가할 수 있다. 엔지니어링은 방법을 위한 방법을 쓰는 것에서 시작하지 않는다. 그 방법이 옳은 방법이라는 믿음에 의해 추진된다.
이번 예시에서는 우리가 캘린더 클라이언트를 만드는 회사라고 가정해 보자. 사용자가 자연어로 일정을 입력할 수 있게 하고 싶다. 예를 들면 다음과 같다:
Dinner with Alice next Tuesday at Taco Bell
CorpConf on 11/4
1:1 with Bob tomorrow at 10 AM
언어 모델은 이러한 자연어 입력을 받아 이벤트를 설명하는 구조화된 출력을 추출한 뒤, 이를 애플리케이션 내부에서 활용하기에 좋은 해결책이 될 수 있다.
물론 다른 잠재적 해결책도 있다. 정규 표현식과 문자열 검색을 활용해 흔한 표현(on <Day of Week>, tomorrow, today, next week 등)을 찾는 방법을 쓸 수도 있다.
언어 모델 나름의 장점도 있다. 다른 언어를 더 잘 처리하거나, 오타나 문법 오류를 더 잘 처리하는 접근법을 제공할 수도 있고, 최악의 경우에도 정규 표현식이 실패했을 때 좋은 백업이 될 수 있다. 어느 쪽이든 프롬프팅을 잠재적 해결책으로 계속 추구할 만큼의 가능성은 충분하다.
데모 세트
다음으로 데모 세트를 구성해야 한다. 데모 세트는 예상 입력과 그에 대한 예상 출력을 함께 담고 있다. 이 세트는 여러 가지 목적을 수행한다:
프롬프트의 정확도를 측정하는 데 사용된다. 단일 데모의 입력을 사용해 예상한 출력이 나오는지 검증할 수 있다.
프롬프트의 입력과 출력이 어떤 형태여야 하는지를 명시하므로, 엔지니어인 우리가 해당 형태가 우리 문제에 적합한지 판단할 수 있다.
퓨샷 프롬프트를 사용하기로 한다면, 이 데모 세트의 일부를 퓨샷 접근법을 위한 예시로 활용할 수 있다. "퓨샷"이라는 용어가 낯선 분들을 위해 설명하면, 퓨샷은 프롬프트에 더해 예시를 함께 제공하는 프롬프트 스타일이다. 퓨샷 vs 제로샷 프롬프팅에 대한 좋은 개요는 여기를 참고하면 된다.
위에서 (2)번 항목은 매우 중요하다. 입력으로 무엇을 기대하고 출력으로 무엇을 기대하는지에 대해 전반적인 이해가 필요하다. 양쪽 모두 [일반적으로] 데이터가 특정 형식으로 들어오는지 보장하고 특정 형식을 출력으로 기대하는 소프트웨어가 있기 때문이다. 이는 문제를 입출력 기대치가 있는 일련의 함수들로 분해하는 일반적인 소프트웨어 엔지니어링과 다르지 않다.
앞서 든 예시들을 활용해 완전한 데모 형태로 확장해 보자:
Q: Dinner with Alice next Tuesday at Taco Bell
A: next Tuesday
Q: CorpConf on 11/4
A: 11/4
Q: 1:1 with Bob tomorrow at 10 AM
A: tomorrow
데모 세트 크기에 대한 참고: 이 블로그 글에서는 데모가 세 개뿐이다. 실제로는 최소 열두 개 정도는 갖는 것이 좋다. 데모가 많을수록 더 좋은 테스트를 할 수 있지만, 토큰 사용량 때문에 비용도 더 많이 든다. 일정 규모 이상이 되면 언어 모델을 파인튜닝하는 편이 더 경제적인 경우가 많다.
위 데모에는 두 가지 중요한 결정이 담겨 있다. 어떤 프롬프팅 문제에서든 비슷한 결정을 내려야 할 것이다.
첫째, 단 하나의 정보만 추출하고 있다. 이벤트 이름, 참석자, 시간, 장소 등 이벤트 전체를 모델이 추출해 바로 쓸 수 있는 아름다운 JSON이나 다른 형식으로 출력하게 하고 싶은 유혹이 들 수도 있다. 모델이 그것을 해낼 수도 있다. 하지만 새로운 문제에 접근할 때는 먼저 단일 문제로 분해하는 것을 권장한다. 이렇게 하면 문제를 다루기 쉬워지고, 궁극적으로 다중 출력 접근법이 실제로 가치가 있는지 벤치마킹할 수 있는 기준 정확도도 얻을 수 있다.
둘째, 출력이 변환되지 않았다. 모든 것을 날짜로 변환하거나 대문자를 올바르게 맞추는 등의 작업을 하려는 것이 아니다. 문자 그대로 텍스트를 추출하는 것이다. "next Tuesday" 같은 문자열을 매우 높은 정확도로 타임스탬프로 변환해 주는 훌륭한 결정적 라이브러리들이 이미 존재한다. 이는 언어 모델이 대신 해줄 필요가 없는 일이다. 따라서 거친 날짜 형식("next Tuesday", "11/4", "tomorrow")만 추출하고, 이를 타임스탬프로 바꾸는 문제는 전통적인 프로그래밍 방법으로 해결하자. 이는 자명한 문제이기 때문이다. 출력이 단순할수록 높은 정확도를 얻기가 더 쉽다.
마지막으로, 출력 디코딩에 대한 간략한 참고: LLM은 다양한 방식으로 프롬프트를 완성한다. 완전한 문장일 수도 있고, 마침표를 추가할 수도 있으며, 대문자로 시작할 수도 있다. LLM의 출력이 얼마나 완벽해야 하는지, 그리고 데모 세트를 검증하기 전에 어느 정도까지 정규화할 의향이 있는지 정해야 한다.
예를 들어 텍스트 추출을 할 때 나는 보통 공백과 마침표를 제거하고 전체 출력을 소문자로 바꾸는 정도는 합리적이라고 본다. JSON 생성처럼 더 고급 작업을 할 때는 JSON을 파싱한 뒤 결정적인 순서와 스타일로 다시 인코딩해 비교가 결정적으로 이루어지도록 할 수도 있다. 이런 식이다.
내 권장 사항은 LLM의 출력을 가능한 한 단순하고 유연하게 유지하고, 정규화 작업은 애플리케이션에서 수행하라는 것이다. 처음부터 LLM이 완벽한 형식으로 출력하도록 강제하지 마라. 초기에 LLM에서 "출력 형태 맞추기"를 너무 많이 시도하면, LLM의 핵심 작업 수행 능력(이 경우에는 정보 추출)과 출력을 구조화하는 능력을 구분하기 어려워진다.
프롬프트 후보
이제 몇 가지 프롬프트 후보를 만들어 보자. 프롬프트 후보란 언어 모델로부터 우리가 원하는 동작을 이끌어낼 수 있을 것 같은 프롬프트를 말한다. 한 번에 최고의 프롬프트를 고를 가능성은 낮기 때문에 여러 후보를 만든다.
입문 수준의 글인 만큼 프롬프트는 수동으로 만들어 볼 것이다. 효과적으로 만들기 위해 프롬프트 엔지니어가 프롬프트를 구축할 때 활용해야 할 몇 가지 기본 지식이 있다. 예를 들어 방어적으로 쓰기보다 단호하게 쓰는 것이 좋고, 반복적이고 길게 쓰기보다 명확하고 간결하게 쓰는 것이 좋은 경우가 많다. 퓨샷 프롬프트를 만들 때는 라벨을 균등하게 분배하는 것이 중요하고, 전체 라벨 세트를 보여주는 것이 중요하다. 예시를 고를 때는 LLM이 틀릴 가능성이 높은 예시가 일반적으로 가장 좋은 성능을 내며, 예시는 짧은 것부터 긴 순서로 정렬했을 때 가장 좋은 성능을 보이는 것으로 나타났다.
출처 필요! 죄송하지만 이러한 권장 사항을 뒷받침하는 실험 연구를 인용하지 않았다. 솔직히 말하면, 관련 논문들(항목당 여러 개인 경우가 많다)을 찾아보는 것이 귀찮았다. 나를 믿지 않기로 해도 괜찮다. 더 중요한 점은 프롬프팅 기법과 그 효능에 대한 실험 연구가 존재한다는 것이다. 다만 약속하건대, 내가 지어낸 얘기는 아니며, 일부는 최신 모델에서는 구식이 되었을 가능성도 있다.
이러한 기법들을 하나하나 다루는 것만으로도 글 하나가 될 수 있으며, 이 글의 목표는 그게 아니다. 이 글의 목표는 전반적인 엔드 투 엔드 과정을 보여주고, LLM으로부터 가치를 추출하는 데 엔지니어링 방법이 존재한다는 것을 보여주는 것이다.
이 단계에서의 목표는 좋은 제로샷 프롬프트 몇 개를 만드는 것이다. 제로샷 프롬프트는 퓨샷으로 변환할 수 있고, 퓨샷은 다시 체인 오브 쏘트(chain of thought)로 변환할 수 있다. 그리고 각각은 다시 배치 프롬프트 등으로 변환할 수 있다. 따라서 제로샷은 시도를 위한 기본 요건이므로 여기에 집중한다.
내가 만든 세 가지 프롬프트 후보는 다음과 같다:
Identify the date or day mentioned in the given text and provide it as the output.
Identify the date or day mentioned in the given event description.
Determine the date or day from each input and provide the output accordingly as a single word or date.
모두 합리적인 프롬프트들이다. 교육을 받은 사람이라면 누구나 이 프롬프트들로 매우 높은 정확도를 낼 것이다. 하지만 언어 모델은 사람이 아니므로 동일한 성능을 당연하게 기대할 수는 없다. 나는 이전에 매우 합리적으로 보이는 프롬프트가 얼마나 처참한 성능을 낼 수 있는지 보여준 바 있다. 따라서 다음 단계는 테스트를 수행하고 측정값을 통해 결정을 뒷받침하는 것이다.
프롬프트 테스트
후보 프롬프트 세트와 데모 세트가 준비되었으니 이제 정확도를 측정할 수 있다. 지금까지 내가 찾은 가장 좋은 방법은 LangChain 같은 라이브러리를 사용해 간단한 Python 스크립트를 만드는 것이다. 테스트할 때는 보통 각 데모를 순회하며 다음과 같은 프롬프트 템플릿을 사용한다:
{{prompt}}. Q: {{input}} A:
나는 항상 제로샷부터 테스트한다. 기준이 되는 정확도 지표를 얻고 싶기 때문이다. 그 다음 퓨샷을 테스트해 서로 다른 후보끼리뿐만 아니라 서로 다른 프롬프팅 유형끼리도 비교할 수 있다. 이런 식이다.
이 작업은 모델별로 수행해야 한다. 더 강력한 모델에서도 동일한 프롬프트가 동일하거나 더 나은 정확도를 보장하지 않는다. 정확도가 떨어질 수도 있다.2
프롬프트 테스트의 가장 기본적인 결과는 아래와 같은 표가 되어야 한다. 모델(GPT-3.5 vs GPT-4 등)과 같은 추가적인 차원을 넣을 수도 있다.
| Zero-Shot | Few-Shot | ... |
-----------------------------------------
Prompt 1 | 64 | 68 | ... |
-----------------------------------------
Prompt ... | 44 | 52 | ... |
-----------------------------------------
Prompt N | 23 | 22 | ... |
이 표는 Y축에 프롬프트 후보를, X축에 해당 프롬프트를 활용한 프롬프트 유형을 보여준다. 값은 정답 비율을 백분율로 나타낸 정확도다. 캘린더 앱 예시를 계속 이어가면, 하나의 데모와 함께한 제로샷 프롬프트로서의 "prompt 1"은 다음과 같이 보일 수 있다:
Identify the date or day mentioned in the given text and provide it as the output. Q: CorpConf on 11/4 A:
그리고 정답으로 "11/4"를 기대한다. 퓨샷 버전은 다음과 같이 보일 수 있다:
Identify the date or day mentioned in the given text and provide it as the output.
Q: Dinner with Alice next Tuesday at Taco Bell. A: next Tuesday
Q: 1:1 with Bob tomorrow at 10 AM. A: tomorrow
Q: CorpConf on 11/4. A:
숙련된 프롬프터를 위한 참고: 퓨샷 예시에는 "아래 예시를 모방하세요"와 같은 문구가 없다. 실험 연구에 따르면 이러한 문구가 정확도를 안정적으로 높이지 않으므로, 토큰을 절약하기 위해 나는 먼저 이를 빼고 테스트하는 것을 선호한다. 둘째, 퓨샷 예시의 예시들은 "MM/DD" 형식의 추출을 전혀 보여주지 않는데, 이는 좋지 않은 형태다. 실제 퓨샷 설정에서는 모든 추출 스타일을 보여주는 것이 중요할 수 있다(Zhao 외 2021).
분류 문제와 같은 특정 유형의 문제에서는 혼동 행렬(confusion matrix)(Strobelt 외 2022)을 사용해 다른 라벨들의 확률을 시각화하고, 이를 통해 라벨 세트를 더 잘 튜닝할 수 있는지 판단할 수 있다.
정확도 외에도 사용된 토큰 수, 요청 수 등을 측정하고 싶을 것이다. 이 모든 것은 최종 프롬프트를 선택할 때 고려해야 한다.
프롬프트 선택
마지막으로 애플리케이션에 통합할 프롬프트 후보 중 하나를 선택한다. 반드시 가장 정확한 프롬프트를 고르는 것은 아니다. 사용된 모델, 필요한 토큰 수, 그리고 나타난 정확도를 기반으로 한 비용 대 정확도 분석이다.
예를 들어 퓨샷 변형이 가장 좋은 성능을 보이지만 테스트 세트에서 정확도가 불과 4% 더 높고 토큰은 200% 더 많이 필요할 수 있다(현재 API 기반 모델에서는 사실상 비용이 두 배가 된다). 비즈니스 관점에서 비용을 절반으로 줄이기 위해 정확도가 4% 낮아지는 것을 감수할 가치가 있다고 판단할 수도 있다.
혹은 정확도를 높이기 위해 다른 접근법을 다시 테스트하기로 결정할 수도 있다. 예를 들어 더 저렴한 모델에서 self-consistency 디코딩 전략(Wang 외 2022)을 시도해 정확도가 충분히 개선되는지 볼 수 있다. 때로는 더 저렴한 모델에서 토큰을 더 많이 사용하는 것이 더 비싼 모델에서 토큰을 적게 사용하는 것보다 비용을 크게 절약할 수 있다. 예를 들어 오늘날 GPT-4는 GPT-3.5보다 약 15배 비싸다. 즉, GPT-3.5 프롬프트의 정확도를 높이는 데 사실상 15배 더 많은 토큰 예산을 쓸 수 있다는 뜻이다(속도 제한에 대한 주의사항은 염두에 둬야 한다).
이 블로그 글의 예시에서는 표에서 Prompt 1의 제로샷 버전을 선택할 수도 있다. 64%의 정확도를 가지면서도 토큰 수는 훨씬 적을 가능성이 높기 때문이다. 어쩌면 64% 정확도면 적어도 캘린더 애플리케이션의 이벤트 템플릿을 채우는 데는 충분하다고 판단할 수도 있다. 이 특정 문제에서는 64%보다 훨씬 더 잘할 수 있다고 생각하지만, 이 글에서는 그냥 예시로 든 숫자일 뿐이다.
가장 중요한 것은 정보에 기반한 결정을 내릴 수 있는 데이터를 갖게 된다는 점이다.
신뢰하되 검증하고 지속적으로 개선하기
생성형 AI의 확률적 특성 때문에 프롬프트에는 아마도 문제가 있을 수밖에 없다. 테스트 세트에서 정확도가 100%라 하더라도 잘못된 출력을 만드는 알려지지 않은 입력이 존재할 가능성이 높다. 따라서 신뢰하되 검증해야 하며, 검증을 실패한 사례를 데모 세트에 추가해 새로운 프롬프트를 개발하고 정확도를 높여야 한다.
검증 방법은 문제에 따라 크게 달라진다. 캘린더 애플리케이션 예시에서는 사용자에게 "이 일정이 맞나요?"라고 명시적으로 물어볼 수도 있다. 사용자가 "아니요"라고 답하면 자연어 입력을 로그로 남겨 사람이 검토하도록 한다. 혹은 자동 정보 추출 후 사용자가 수동으로 수정한 일정을 자동으로 추적하는 더 나은 방법을 쓸 수도 있다.3
다른 예로, 프롬프트가 코드(정규 표현식이나 프로그래밍 언어 텍스트 등)를 생성하는 경우라면, 최소한 파싱을 시도해 볼 수 있다. 파싱은 보안 문제를 일으켜서는 안 되며,4 적어도 구문이 올바르다는 가장 기본적인 검증이라도 제공한다. 그리고 다시, 이 검증이 실패하면 입력과 출력을 로그로 남기고 데모 세트를 확장해 더 나은 프롬프트를 개발할 수 있다.
검증은 적대적 프롬프팅에 대해서도 도움이 된다. 적대적 프롬프팅은 그 자체로 하나의 큰 주제이며, 이 글에서는 다루지 않겠다.
그리고 앞으로...
이 블로그 글은 프롬프트를 개발하는 과정이 — 내 생각에는 — 어떻게 엔지니어링 실천이 될 수 있는지를 보여준다. 문제를 식별하고, 해결책을 구성하며, 그 해결책을 검증하고, 지속적인 개선을 적용해 해결책을 다듬는 체계적인 접근법을 설명한다.
이 글의 접근법을 일화적인 경험과 만연한 시행착오에 의존해 어떤 제안된 해결책에 도달하고, 시간이 지나도 프롬프트를 안정적으로 반복 개선할 수 있는 적절한 체계적 인프라를 구축하지 않는 경우가 많은 "블라인드 프롬프팅"과 비교해 보라.
이 블로그 글이 매우 기초적인 수준이라는 점을 밝혀두고 싶다. 이 글에는 이미 잘 알려진 더 고급 기법으로 개선할 수 있는 부분이 여러 곳 있다. 또한 적대적 프롬프팅과 같은 중요한 주제도 다루지 않았다. 구체적인 예로, 퓨샷 프롬프트에 가장 좋은 예시를 고르는 더 과학적인 접근법들이 있지만, 이 글에서는 가능한 한 기초에 충실하고자 했다. 더 고급 기법을 배우고 싶다면 Prompt Engineering by Lilian Weng이 훌륭한 개요를 제공한다.
더불어 모두가 프롬프트 체이닝, 에이전트 등과 같은 더 고차원적인 LLM 통합으로 빠르게 이동하고 있다. 이러한 혁신과 그 이상의 발전이 인간의 프롬프팅을 쓸모없게 만들 것이라고 주장하는 사람들도 있다. 그것이 사실이든 아니든, 나는 "제1원칙(first principles)"5부터 배우는 것이 가치 있다고 믿는 사람이며, 이러한 프롬프팅 기법을 배우는 것이 고차원 언어 모델 기법을 활용하는 내 능력을 오히려 향상시켰다고 생각한다. 또한 이러한 기초적인 프롬프팅이 여전히 고차원 개념의 더 나은 성능을 가능하게 한다고 믿는다.
각주
어쩌면 "시끄러운" 사람들이 많다는 얘기일 수도 있다. 나는 이 분야에 훌륭한 엔지니어링 실천을 실제로 적용하고 있는 정말 뛰어난 프롬프트 엔지니어들을 많이 만나고 그들에게서 배웠다. 안타깝게도 트위터나 다른 플랫폼에서 내가 보는 노이즈의 상당수는 그렇지 않은 경우가 많다. ↩
또 출처 필요. 다시 말하지만 귀찮아서 생략한 것이며, 이는 내가 여러 논문에서 읽은 실험 연구에 기반한 것이다. 나를 믿지 않아도 좋다. 직접 테스트해 보면 된다. ↩
여기에는 명백히 프라이버시 문제가 따를 수 있다. 단순히 예시를 공유하는 것일 뿐이며, 실제 상황에 따라서는 해당 기법이 적절하지 않을 수도 있다. ↩
YAML. 😐 ↩
실제 "제1원칙"은 더 낮은 수준일 것이라는 점을 알고 있다. 여기서는 "제1원칙"이라는 표현을 더 많은 지식을 쌓을 수 있는 임의의 낮은 수준의 지점을 가리키는 일반적인 관용구로 사용했다. ↩
글을 무작위로 읽기
댓글
로그인하고 댓글 남기기