機械学習
記事内に商品プロモーションを含む場合があります

擬似的な会話で大規模言語モデル(LLM)の回答を制御する方法|LLM活用テクニック

tadanori

調べていて面白いテクニックを見つけたので紹介します。テクニックは「擬似的な会話をつけることで、大規模言語モデル(LLM)の出力をコントロールするという方法です。Few-shot Promptingと言われる手法の一種だと思いますが、テンプレートを活用してLLMが回答したようにするのが面白いです。

はじめに

大規模言語モデル(LLM)の出力をコントロール

一般的に、LLMの出力をコントロールするには、プロンプトに具体的な指示を書くという方法をとります。例えば、プロンプトに「箇条書きで記述してください」や「なるべく簡単に説明してください」などの指示を与える方法です。

この方法は、ChatGPTのような大規模なモデルではうまくいきますが、2Bや7Bなどのモデルではどうもうまくいかないことが多いです

小さいモデルは、プロンプトで思ったようにコントロールすることが結構難しいと感じています。

擬似的な会話でコントロール

どこで見たか忘れましたが、擬似的な会話を加えて質問することでLLMの回答をコントロールすることができるようなので、試しにやってみました。

例えば、「日本で一番高い山は?」と質問した場合、どのようなフォーマットで返答が返ってくるかわモデル次第です。

もし、これまでの会話が以下のようだとしたらどうでしょうか。

ユーザー
ユーザー

世界で一番高い山は?

アシスタント
アシスタント

エベレストです。標高は8,888mです。

ユーザー
ユーザー

世界で2番目に高い山は?

アシスタント
アシスタント

ゴドウィンオースチンです。標高は8,611mです。

ユーザー
ユーザー

世界で3番目に高い山は?

アシスタント
アシスタント

カンチェンジュンガです。標高は8,586mです。

ユーザー
ユーザー

日本で一番高い山は?

このパターンで「日本で一番高い山は?」と聞かれたら「富士山です。標高はxxxxmです」と答えてくれそうな気がしませんか。

実際、これはうまく動作します。llama2-7bでは、「 日本で一番高い山は、富士山です。標高は3,776mです。」と答えてくれました。

以下、実際に試した内容を解説します。

この手法はFew-shot promptingと呼ばれる手法の派生だと思いますが、テンプレートを使って質問と回答を作成していることがポイントです(こちらの方がうまくいきました)。

Llama2を使って試して見る

今回利用したLLMはllama2-7bモデルです。特にどのモデルでもよかったのですが、チャットテンプレートに対応していて手軽に使えるモデルということで、llama2を選択しました。

ここでは、llama2の使い方については詳しく解説しません。興味がある方は以下の記事を参考にしてください。

Llama2の使い方について知りたい場合はこちら
大規模言語モデル(Llama2/Gemma/Calm2)を動かしてみる(使い方)
大規模言語モデル(Llama2/Gemma/Calm2)を動かしてみる(使い方)

トークナイザーとモデルをロードする

まず、以下のコードで、トークナイザー(tokenizer)とモデル(model)を準備します。

今回利用したのは、meta-llama/Llama-2-7b-chat-hfというチャット用のモデルになります。モデルがローカルにない場合は、ダウンロードが開始されます(ネット環境により時間がかかります)。

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained(
    "meta-llama/Llama-2-7b-chat-hf",
)
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b-chat-hf",
    torch_dtype=torch.float16, device_map="auto", trust_remote_code=False,
)

普通に質問する

まず、普通に質問をしてみます。

シンプルな質問

最初は、シンプルな質問です。ここでは、プロンプトの生成にapply_chat_template()を使っています。チャットテンプレートの使い方につては以下の記事が詳しいです。

チャットテンプレートについてはこちら
HuggingFace|大規模言語モデル(LLM)のチャットテンプレートを使う
HuggingFace|大規模言語モデル(LLM)のチャットテンプレートを使う

回答は出力の末尾の[/INST]以降になりますので、その部分だけ取り出して表示しています。

question = "日本で一番高い山は?"
chat = [
    { "role": "user", "content": question },
]
prompt = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)
print(prompt)
# <s>[INST] 日本で一番高い山は? [/INST]

input_ids = tokenizer(prompt, return_tensors='pt')
with torch.no_grad():
  tokens = model.generate(
      **input_ids.to(model.device),
        max_new_tokens=100,
        do_sample=True,
        temperature=0.5,
        top_p=0.9,
        repetition_penalty=1.05,
  )
output = tokenizer.decode(tokens[0], skip_special_tokens=True)
output = output.rsplit("[/INST]", 1)
print(output[-1])

回答は以下のようになりました。

とりあえず、回答は得られましたが、期待していたものとは異なります(一応、山の名前と標高書かれていますが少し冗長です)。

  Japan has many high mountains, but the highest mountain in Japan is Mount Fuji, which stands at an elevation of 3,776 meters (12,388 feet) above sea level. It is located on Honshu Island and is considered one of the country's most iconic landmarks. Mount Fuji is a popular destination for hiking and climbing, and is also a UNESCO World Heritage Site.

「名前と標高」を尋ねる

少しプロンプトを修正して「名前と標高」を尋ねるように変更しました。

question = "日本で一番高い山は?山の名前と標高を教えてください"
chat = [
    { "role": "user", "content": question },
]

prompt = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)
input_ids = tokenizer(prompt, return_tensors='pt')
with torch.no_grad():
  tokens = model.generate(
      **input_ids.to(model.device),
        max_new_tokens=100,
        do_sample=True,
        temperature=0.5,
        top_p=0.9,
        repetition_penalty=1.05,
  )
output = tokenizer.decode(tokens[0], skip_special_tokens=True)
output = output.rsplit("[/INST]", 1)
print(output[-1])

回答は以下のようになりました。名前と標高が箇条書きで追加されましたが、まだ冗長です。

  Japan has many mountains, and the highest one is Mount Fuji, which is located on Honshu Island. It has a height of 3,776 meters (12,388 feet) above sea level. Here are some other notable mountains in Japan, along with their names and heights:

1. Mount Fuji (Fuji-san) - 3,776 meters (12,388 feet)
2.

限定するように指示

「日本で一番高い山は?山の名前と標高だけを教えてください」と指示を変えてみました。

question = "日本で一番高い山は?山の名前と標高だけを教えてください"
chat = [
    { "role": "user", "content": question },
]

prompt = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)
input_ids = tokenizer(prompt, return_tensors='pt')
with torch.no_grad():
  tokens = model.generate(
      **input_ids.to(model.device),
        max_new_tokens=100,
        do_sample=True,
        temperature=0.5,
        top_p=0.9,
        repetition_penalty=1.05,
  )
output = tokenizer.decode(tokens[0], skip_special_tokens=True)
output = output.rsplit("[/INST]", 1)
print(output[-1])

出力は以下になります。なかなか思った通りに出力されません。

  Japan has many high mountains, but the highest mountain in Japan is Mount Fuji, which is located on Honshu Island. It has a height of 3,776 meters (12,388 feet) above sea level. Here are some other notable mountains in Japan, along with their names and heights:

1. Mount Fuji - 3,776 meters (12,388 feet)
2. Mount Kitadake

このように、プロンプトで出力をコントロールするのはかなり難しいです。

ということで、会話を模擬してみます。

会話を模擬してから質問

まず、これまでの会話を擬似的に作成します。

以下の例では、世界で高い山を3つ質問する内容になっています。また、それぞれの質問に対するモデルの答えも擬似的に追加しています。

prev_chat = [
    ['世界で一番高い山は?', 'エベレストです。標高は8,888mです。'],
    ['世界で2番目に高い山は?', 'ゴドウィンオースチンです。標高は8,611mです。'],
    ['世界で3番目に高い山は?', 'カンチェンジュンガです。標高は8,586mです。']
]

chat = []

for user, assistant in prev_chat:
  chat.append({ "role": "user", "content": f"次の内容を、日本語で完結にまとめてください。\n{user}"})
  chat.append({ "role": "assistant", "content": assistant})

chat.append({ "role": "user", "content": question },
)
print(chat)

作成した会話は以下のようになります。世界で高い山を3つ聞いて、その後に日本で一番高い山を聞いたというシチュエーションを作っている感じになります。

[{'role': 'user', 'content': '次の内容を、日本語で完結にまとめてください。\n世界で一番高い山は?'},
 {'role': 'assistant', 'content': 'エベレストです。標高は8,888mです。'},
 {'role': 'user', 'content': '次の内容を、日本語で完結にまとめてください。\n世界で2番目に高い山は?'},
 {'role': 'assistant', 'content': 'ゴドウィンオースチンです。標高は8,611mです。'},
 {'role': 'user', 'content': '次の内容を、日本語で完結にまとめてください。\n世界で3番目に高い山は?'},
 {'role': 'assistant', 'content': 'カンチェンジュンガです。標高は8,586mです。'},
 {'role': 'user', 'content': '日本で一番高い山は?'}]

では、これをLLMに投げてみます。

prompt = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)
input_ids = tokenizer(prompt, return_tensors='pt')
with torch.no_grad():
  tokens = model.generate(
      **input_ids.to(model.device),
        max_new_tokens=100,
        do_sample=True,
        temperature=0.5,
        top_p=0.9,
        repetition_penalty=1.05,
  )
output = tokenizer.decode(tokens[0], skip_special_tokens=True)

output = output.rsplit("[/INST]", 1)
print(output[-1])

すると、以下のような回答を得ることができました。欲しかったフォーマットです!

このように擬似的な会話(質問と答えのペア)を入力することで、続く質問の回答パターンをある程度コントロールすることが可能です。

 日本で一番高い山は、富士山です。標高は3,776mです。

回答が日本語に変化している部分もポイントです

番外編

試しに、他のパターンもやってみました。キーワードから連想する3つのものを列挙するという内容です。

prev_chat = [
    ['パソコンといえば', '* モニター\n * キーボード \n * マウス'],
    ['音楽といえば', '* 楽器\n * ジャンル\n * 楽譜'],
    ['旅といえば', '* パスポート\n * スーツケース\n * 地図']
]
question = "車といえば"

chat = []

for user, assistant in prev_chat:
  chat.append({ "role": "user", "content": user})
  chat.append({ "role": "assistant", "content": assistant})

chat.append({ "role": "user", "content": question },
)

prompt = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)
input_ids = tokenizer(prompt, return_tensors='pt')
with torch.no_grad():
  tokens = model.generate(
      **input_ids.to(model.device),
        max_new_tokens=100,
        do_sample=True,
        temperature=0.5,
        top_p=0.9,
        repetition_penalty=1.05,
  )
output = tokenizer.decode(tokens[0], skip_special_tokens=True)

output = output.rsplit("[/INST]", 1)
print(output[-1])

llama2の回答は以下の通りでした。

 * 自動車
 * ナンバープレート
 * ガススティン

これもうまくいきました!モデルによると思いますがプロンプトでコントロールするより楽な気がします。

うまいこと使えば、LLMの出力をコントロールできるかもしれません。

まとめ

大規模言語モデル(LLM)の使い方で面白いものを見つけたので、実際に試してみました。

意外とうまく動作することが確認でき、うまく利用すればLLMの出力をある程度コントロールできるのでは?と感じました。

おすすめ書籍

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

記事URLをコピーしました