LLMアプリケーションの新定番、Microsoft guidanceライブラリのgenメソッドを詳細に追ってみる
MicrosoftのguidanceライブラリはLLMアプリケーションを作成する際の新たな定番となりそうな気がしています。そういう訳で、今回はguidanceのgenメソッドについて詳しく追ってみたいと思います。
基本的な使い方
import guidance
gpt3 = guidance.llms.OpenAI("text-davinci-003")
gpt3_5 = guidance.llms.OpenAI("gpt-3.5-turbo")
gpt4 = guidance.llms.OpenAI("gpt-4", api_key=API_KEY)
guidance.llm = gpt3
まずはguidanceライブラリを読み込み、使用するLLMを宣言します。OpenAI APIを使用する場合、初期化パラメータとして以下のパラメータを使用できます。
テキスト補完モデルの例として、GPT-3では以下のようにプロンプトを作成することができます。
program = guidance("""This is a sentence about {{gen "completion" stop="."}}""")
executed_program = program()
genの部分がLLMの生成する箇所になります(上図で緑でハイライトされている箇所)。genの第一引数は「位置引数(positional argument)」と呼ばれており、返ってきたインスタンスに位置引数で指定した値をキーとして読み出すと、その位置引数に対応した生成文字列が返ります。
executed_program["completion"]
name引数
また、以下のようにプロンプト内で複数のgen関数を呼び出すことも可能です。
program = guidance("""This is a sentence about {{gen "about" stop="."}}. Because {{gen "because" stop="."}}""")
executed_program = program()
この場合も「about」「because」をそれぞれ読み出すことはできますが、出力されたキーと文字列をひとまとめにする方法もあります。以下の例では「obj」という名前のキーでひとまとめにしています。
out = guidance("""This is a sentence about {{gen "obj.about" stop="."}}.
Because {{gen "obj.because" stop="."}}""")(obj={})
out["obj"]
stop引数
stop引数は生成を止めるタイミングをあらわす引数です。ここまでの例では「.(ピリオド)」を指定してきましたが、以下のように複数の任意の文字列を指定することもできます。
out = guidance("""We are gonna {{gen 'text' stop=[" the", " of", " a"]}}""")()
out["text"]
stop_regex引数とsave_stop_text引数
stopキーワードに正規表現を指定することもできます。以下の例ではプロンプト内の例に従って、問題を解く際の方程式を表すコードを出力させます。
import guidance
guidance.llm = guidance.llms.OpenAI("text-davinci-003", caching=False)
prompt = """次の問題を解き、回答に方程式の計算が必要な場合は以下の文字列によって計算機を呼び出して下さい。
CALC(EQUATION) = ANSWER
例えばCALC((4+3) * 2) = 14のようになります。
問題: {{problem}}
ステップバイステップで問題を解いてください: {{gen 'text' stop_regex="CALC\\(.*?\\) =" max_tokens=400 save_stop_text=True}}"""
program = guidance(prompt)
out = program(problem="ジョーはリンゴを10個持っていて、それぞれのリンゴについて5つのテストを行う必要があります。1つのテストに7分かかるとしたら、全てのテストを終えるまでにどのぐらいの時間がかかるでしょうか?")
save_stop_text引数をTrueに設定しておくと、以下のようにストップ時に出力されたテキストを取得することができます。
out["text_stop_text"]
max_tokens引数、n引数、temperature引数
n引数を利用することで、gen関数による生成結果を複数得ることができます。max_token引数によって最大トークン数を制限し、temperature引数で一時的にランダム性を上げて生成してみます。
out = guidance("""This is a sentence about {{gen 'text' n=5 max_tokens=10 temperature=1.0}}""")()
out["text"]
複数の生成結果を得られました。ここでtemperatureを0にするとランダム性がなくなるため、同じ生成結果しか得られなくなります。gen関数ではtemperatureはデフォルトで0に設定されています。
out = guidance("""This is a sentence about {{gen 'text' n=5 max_tokens=10 temperature=0}}""")()
out["text"]
top_p引数
top_pもtemperatureと同様、生成される文字列のランダム性を表す値です。temperatureが全体的な確率分布を調整するのに対し、top_pは最も可能性の高い単語群から選択する範囲を調整します。
例えばtop_pを0.95(95%)に設定した場合、モデルは予測した各単語の確率を降順に並べ、それらの累積確率が初めて95%を越えるところまでの単語集合から、次の単語をランダムに選択する動きを取ります。そのため、1に近づくほど選択される単語は多様になり、0に近づくほど確定的になります。
gen関数ではデフォルトで1に設定されています。
out = guidance("""This is a sentence about {{gen 'text' n=5 max_tokens=5 top_p=0.4 temperature=1.0}}""")()
out["text"]
logprobs引数
logprobsに0以上の数値が設定されると、LLMはテキスト内の各トークンについて、その数だけ上位のトークンの対数確率を返します。試しに3を指定してみたのが以下のコードです。
対数確率が0に近づくほど選択される可能性が高くなると言うわけで、上記のケースでは最終的に「\n\ntraveling.」が生成されていることが分かります。
pattern引数
ReLLMのように正規表現に準じた生成結果を返すこともできます。ただしpattern引数はOpenAI APIで呼び出せるLLMには対応しておらず、transformersでロードするLLMでのみ使用することができます。
以下のコードではLLMに数字のみを返させています。
import guidance
gpt2 = guidance.llms.Transformers("gpt2", device=1)
guidance.llm = gpt2
out = guidance("""This is a sentence about {{gen 'text' stop=" " pattern="[0-9 ]+"}}""")()
out["text"]
hidden引数
LLMに文字列を生成させるがユーザーには生成結果を表示したくない、というケースに使えるのがhidden引数です。
以下のコード例ではtext1の文字列を表示しません。生成結果はtext1キーを指定することで取得することができます。
out = guidance("""This is a sentence about {{gen 'text1' stop=" " hidden=True}}{{gen 'text2' stop="."}}""")()
out["text1"], out["text2"]
parse引数(parse関数に変更されている?)
ドキュメントでは引数として紹介されていますが、最新版で実行しようとすると「そんな引数はない」とエラーになります。
一方で同じような機能を持つparse関数があるため、そちらを紹介します。
例えば {{charactor_name}} のような特殊文字にキャラクターの名前を割り当てたいとします。これをgen関数で書くと以下のようになりますが、キャラクターの名前を割り当てることができません。LLMは {{charactor_name}} を使えと指示されているためです。
# 埋め込みが変換されないように \{{charactor_name}} という形でエスケープしています
guidance("""Write a story about a person and use \{{character_name}} whenever you need to write their name:
STORY
{{gen max_tokens=100}}""", character_name="Jill")()
この場合、{{charactor_name}}をエスケープせずにそのまま埋め込み変数として使うようにすれば「Jill」という名前を割り当てることができますが、プロンプトとしては異なる内容になってしまいます。
guidance("""Write a story about a person and use {{character_name}} whenever you need to write their name:
STORY
{{gen max_tokens=100}}""", character_name="Jill")()
このようなケースを踏まえ、生成された文字列に対して再度値割り当てをするような動作(再帰的な動作)をサポートしているのがparse関数です。
guidance("""Write a story about a person and use \{{character_name}} whenever you need to write their name:
STORY
{{parse (gen max_tokens=100)}}""", character_name="Jill")()
LLMによって生成されたであろう {{charactor_name}} の部分がJillに置き換わっていることが分かります。
list_append引数
最後のlist_append引数は、同じ位置引数が設定されているgen関数の生成結果をリストにするかどうかを設定する引数です。
例えば以下のコード例では同じ「story」位置引数が設定されているgen関数が並んでいますが、それぞれlist_append引数がTrueに設定されているため、生成結果が全てリストで保存されます。
out = guidance("""Write three story title options about the arctic circle:
OUTLINE
1. "{{gen 'story' max_tokens=100 list_append=True}}"
2. "{{gen 'story' max_tokens=100 list_append=True}}"
3. "{{gen 'story' max_tokens=100 list_append=True}}"
""")()
out["story"]
Trueにしないと一つにまとめられてしまいます。
out = guidance("""Write three story title options about the arctic circle:
OUTLINE
1. "{{gen 'story' max_tokens=100}}"
2. "{{gen 'story' max_tokens=100}}"
3. "{{gen 'story' max_tokens=100}}"
""")()
out["story"]
One more thing: guidance関数にllmを指定することもできる
基本的にはguidance.llmグローバル変数に利用するLLMを設定してguidanceを使うわけですが、GPT-4とGPT-3.5を使い分けたいケースも当然ありますよね?
その場合はguidance関数の引数として利用するLLMを指定することが可能です。以下にコード例を示します。
gpt3 = guidance.llms.OpenAI("text-davinci-003")
out = guidance("""This is a sentence about {{gen 'text' n=5 max_tokens=5 top_p=0.4 temperature=1.0}}""",
llm=gpt3)()
out["text"]
今回はgen関数の使い方にフォーカスしましたが、本命はチャットモデルの使い方かなと思います。引き続き、guidanceライブラリの利用方法について試しながらまとめていきたいと思います。
現場からは以上です。
この記事が気に入ったらサポートをしてみませんか?