YoutechA320Uのブログ

書き方模索中のなんでも備忘録です

ご家庭のパソコンで動く日本語チャットボットを作る cyberagent/calm2-7b-chat

少し前ですが2023/11/02に日本のcyberagent社から次のAI(大規模言語モデル:LLM)が発表されました。

huggingface.co


私も詳しい仕組みは知らないので詳細は割愛しますが、日本企業であるcyberagent社が日本語と英語を学習させたモデルで更にチャット向けに調整してあるそうです。AI本体をダウンロードして以降はオフラインで動かせるタイプです。つまり実体がChatGPTみたいな遠くのサーバーではなく端末内になるので凄くネット○ビみたいな感じです。

・必要なもの

・CPUに3年以内のIntel Core iシリーズもしくはAMD Ryzenシリーズを搭載し、RAMは16GB以上でSSD空き容量32GB以上、グラフィックボードにRTX3060(VRAM12GB)以上のWindows11パソコン

 

 

いわゆるゲーミングパソコンと呼ばれる構成です。比較的パーツの指定が緩いですがグラフィックボードは必ずNVIDIA社のこの機種以上の性能、メモリ搭載モデルを用意してください。(❌RTX3060 VRAM8GB ❌RTX4060 VRAM8GB)性能さえ満たせば多分ノートパソコンでも可能です。


・Python3.10
・CUDA Toolkit12.1
・Git(最新版)

私が作成できた時のバージョン構成です。異なるバージョンでの動作は保証できません。
インストールはWindowsターミナルの以下のコマンドで行います。

winget install -e --id Python.Python.3.10
winget install -e --id Git.Git
winget install -e --id Nvidia.CUDA -v 12.1


既に一部インストールされている、違うバージョンがインストールされていてソフトについて詳しくわからないなら1度アンインストールしてから上記のコマンドを実行してください。

設定の変更

Windowsの昔ながらの仕様でファイルパス長が制限されているので深い階層にあるPythonライブラリでエラーすることあがあるので以下のコマンドを管理者権限で実行してファイルパス長を拡張します。

Set-ItemProperty "Registry::HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlFileSystem" -Name LongPathsEnabled -value 1


設定アプリのグラフィックの設定からPythonの実行ファイルを高パフォーマンスに登録します。これをしないとPythonがグラフィックボードでAIの性能を引き出せなくなりますので必ず確認してください。

Pythonライブラリのインストール
Windowsターミナルを開き、以下のコマンドを順に実行して必要なPythonライブラリをインストールします。

pip install gradio==3.35.2 pydantic==1.10.13
pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 pip install -U git+https://github.com/huggingface/transformers.git pip install -U git+https://github.com/huggingface/accelerate.git
pip install -U https://github.com/jllllll/bitsandbytes-windows-webui/raw/main/bitsandbytes-0.39.0-py3-none-any.whl

インストールが終わったら一度パソコンを再起動してください。

 

・作成するアプリ

アプリはこちらのソースコードを使用します。

import transformers
from transformers import AutoModelForCausalLM, AutoTokenizer
assert transformers.__version__ >= "4.34.1"
import torch

model_name="cyberagent/calm2-7b-chat"
tokenizer = AutoTokenizer.from_pretrained(model_name)
if torch.cuda.is_available():
 model = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto" , load_in_8bit=True)
else:
 model = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto", torch_dtype="auto")

import gradio as gr
import re
import datetime

role = "USER: あなたは、Chatbotとして何でも知っているチャットボットのロールプレイを行います。

******** 以下の制約条件を厳密に守ってロールプレイを行ってください。 ********


--------制約条件--------
* Chatbotの自身を示す一人称は、私です。
* Userを示す二人称は、ユーザーさんです。
* Chatbotの名前は、チャットボットです。
* チャットボットは、何でも知っています。
* チャットボットの口調は、丁寧語です。
* チャットボットはUserのアシスタントです。
* 一人称は「私」を使ってください

--------チャットボットのセリフ、口調の例--------
* 私は、チャットボットです!
* 私は、あなたと話すのが仕事なのです。
* おはようございます!
* こんにちは!
* こんばんは!
* おやすみなさい。
* わかりました!
* はい、わかりました。
* 私に何でも聞いてください
* どういたしまして。他に質問があれば、何でも聞いてくださいね。
* お役に立ててうれしいです!

--------チャットボットの行動指針--------
* Userに説明してください。
* Userにプライベートな質問はしないでください。
* チャットなのでできるだけ短く1文で答えてください。

----------------

自己紹介をお願いします。
ASSISTANT: わかりました!私はチャットボットです。あなたをお手伝いするのが仕事なので、困ったことがあれば、いつでも話しかけてください!<|endoftext|>
USER: よろしくお願いします。
ASSISTANT: どういたしまして!何か他に質問があれば、遠慮なく声をかけてくださいね!<|endoftext|>"
sub_prompt=""
history = ""

# AIに質問する関数
def complement(role,sub_prompt,prompt,turn_config):
   global history,output_history
   day="* 現在の日付、曜日、時刻は"+datetime.datetime.now().strftime('%Y年%m月%d日 %a %H時%M分です。
').replace("Sun", "日曜日").replace("Mon", "月曜日").replace("Tue", "火曜日").replace("Wed", "水曜日").replace("Thu", "木曜日").replace("Fri", "金曜日").replace("Sat", "土曜日")
   print(role+"
"+history + "USER: "+sub_prompt+"
"+day+prompt+"
ASSISTANT: ")
   if prompt !="":
    with torch.no_grad():
        token_ids = tokenizer.encode(role+"
"+history + "USER: "+sub_prompt+"
"+day+prompt+"
ASSISTANT: ", return_tensors="pt")
        output_ids = model.generate(
          input_ids=token_ids.to(model.device),
          max_new_tokens=300,
          do_sample=True,
          temperature=0.7,
        )    
        output = tokenizer.decode(output_ids.tolist()[0][token_ids.size(1):])
        output = output.replace(sub_prompt, '').replace("<|endoftext|>", '')
        history =history +"USER: "+prompt +"
ASSISTANT: " + output+"<|endoftext|>
"
        turn = re.split(r'(?=USER: )', history)
        del turn[0:1]
        output_history =''.join(turn)
        output_history = output_history.replace("<|endoftext|>", '')
        turn_count = len(turn)
        if turn_count > turn_config:
           del turn[0:turn_count - int(turn_config)]
           history =''.join(turn)
           output_history =''.join(turn)
           output_history = output_history.replace("<|endoftext|>", '')
           turn_count = len(turn)
        print(role+"
"+history + "USER: "+sub_prompt+"
"+prompt+"
ASSISTANT: " + output+"<|endoftext|>")
   if prompt =="":
      output =""
      turn = re.split(r'(?=USER: )', history)
      del turn[0:1]
      output_history =''.join(turn)
      output_history = output_history.replace("<|endoftext|>", '')
      turn_count = len(turn)
      print(role+"
"+history + "USER: "+sub_prompt+"
"+prompt+"
ASSISTANT: " + output+"<|endoftext|>")
   return output, output_history

# 履歴リセット関数
def hist_rst():
    global history
    prompt=""
    output=""
    history=""
    output_history=""
    return prompt, output, output_history

# 会話Undo関数
def undo():
    global history
    turn = re.split(r'(?=USER: )', history)
    output=re.split(r'(?=USER: |ASSISTANT: )', history)
    del turn[0:1]
    del output[0:1]
    if len(turn)>=2:
       prompt= output[len(output)-4]  
       output= output[len(output)-3]
       prompt= prompt.replace("USER: ", '')
       output=output.replace("<|endoftext|>", '').replace("ASSISTANT: ", '')
    if len(turn)<2:
       prompt=""
       output=""
    del turn[len(turn)-1:len(turn)]
    history =''.join(turn)
    output_history =''.join(turn)
    output_history = output_history.replace("<|endoftext|>", '')
    return prompt, output, output_history
    

# Blocksの作成
with gr.Blocks(title="チャットボット",theme=gr.themes.Base(primary_hue="orange", secondary_hue="blue")) as demo:
    # コンポーネント
    gr.Markdown(
    """
    # calm2-7b-chatを使ったチャットボットGUI
    huggingface→[cyberagent/calm2-7b-chat](https://huggingface.co/cyberagent/calm2-7b-chat)

    CyberAgent社のプレリリース→[独自の日本語LLM(大規模言語モデル)のバージョン2を一般公開 ―32,000トークン対応の商用利用可能なチャットモデルを提供―](https://www.cyberagent.co.jp/news/detail/id=29479) 
    """)
    # GUI
    with gr.Row():
     with gr.Column(scale=1): 
      with gr.Accordion(label="システムプロンプト", open=False):
           role = gr.Textbox(lines=26,label="calm2-7b-chatのチャットテンプレートに従って<|endoftext|>まで記入してください", value=role)
           sub_prompt = gr.Textbox(lines=2,label="サブシステムプロンプト",placeholder="システムプロンプトと別で質問の度強調したい指示がある時記入してください", value=sub_prompt)
     with gr.Column(scale=1): 
          prompt = gr.Textbox(lines=2,label="質問入力")
          output = gr.Textbox(label="回答出力")
          greet_btn = gr.Button(value="送信",variant='primary')
          with gr.Accordion(label="会話履歴設定", open=False ):
               turn_config = gr.Number(label="会話ターン数設定",value=10,minimum=1,maximum=50)
               output_history = gr.Textbox(lines=10,label="会話履歴出力")
               undo_btn = gr.Button(value="1ターン戻す",variant='secondary')
               reset_btn = gr.Button(value="履歴リセット",variant='secondary')
          # イベントハンドラー
          greet_btn.click(fn=complement, inputs=[role,sub_prompt,prompt,turn_config], outputs=[output, output_history])
          undo_btn.click(fn=undo, outputs=[prompt, output, output_history])
          reset_btn.click(fn=hist_rst, outputs=[prompt,output, output_history])
demo.launch(show_api=False)

これを実行すると初回のみAIをダウンロードし、その後RAMとVRAM両方を使ってAIが展開されます。この際RAMは15GB程VRAMは10GB程使用するので他のソフトは閉じておいてください。VRAMに展開しきるとRAMは開放されますがVRAMはそのまま使用します。
AIの展開が終わると実行したターミナルにアドレスが表示されるのでブラウザでアクセスすると次の画面になります。

 


「質問入力」に挨拶して「送信」したら「回答出力」に返答が返ってくるか確認してください。

※AIの仕様上稀に支離滅裂な回答をすることがあります。その際は次の「1ターン戻す」か「履歴リセット」でそのやり取りを無かった事にしてください。

「会話履歴設定」をクリックして展開して「会話ターン」の値が記憶を変更することでAIがやり取りをどこまで記憶するか変更できます。

ターンが設定値を超えると古いやり取りから削除されます。「1ターン戻す」で1つ前のやり取りに戻り「履歴リセット」ボタンで記憶を全て削除します。この初期値は5ターンですが増やし過ぎるとその分思考時間が増えて回答が不安定になることがあります。

記憶の確認は○○に行ったなどエピソードを教えて聞き返したら記憶して正しく答えられるかなどで確認します。

次はキャラ付けです。これはAIに「なりきり」をさせることで口調や立場などの設定を与えることです。この度使っているcalm2-7b-chatはなりきり能力が高いので適切な指示(プロンプト)を与えるとかなりそれっぽくなりきってくれます。これはChatGPTでも流行ったことで既に参考事例が複数あります。

システムプロンプト」を展開し、以下をコピペして質問してみてください。

USER: あなたはChatbotとして、尊大で横暴な英雄王であるギルガメッシュのロールプレイを行います。
以下の制約条件を厳密に守ってロールプレイを行ってください。

制約条件
* Chatbotの自身を示す一人称は、我です。
* Userを示す二人称は、貴様です。
* Chatbotの名前は、ギルガメッシュです。
* ギルガメッシュは王様です。
* ギルガメッシュは皮肉屋です。
* ギルガメッシュの口調は乱暴かつ尊大です。
* ギルガメッシュの口調は、「〜である」「〜だな」「〜だろう」など、偉そうな口調を好みます。
* ギルガメッシュはUserを見下しています。
* 一人称は「我」を使ってください

ギルガメッシュのセリフ、口調の例
* 我は英雄王ギルガメッシュである。
* 我が統治する楽園、ウルクの繁栄を見るがよい。
* 貴様のような言動、我が何度も見逃すとは思わぬことだ。
* ふむ、王を前にしてその態度…貴様、死ぬ覚悟はできておろうな?
* 王としての責務だ。引き受けてやろう。

ギルガメッシュの行動指針
* ユーザーを皮肉ってください。
* ユーザーにお説教をしてください。
* セクシャルな話題については誤魔化してください。
ASSISTANT:我は英雄王ギルガメッシュ!

するとどうでしょう。それまでの敬語表現が一変、尊大な王様口調で喋ってくれます。これは深津氏が考案した通称ギルガメッシュプロンプトと呼ばれる形式で強力なキャラ付けが可能です。

ただしChatGPT用なのでcalm2-7b-chatで認識しやすいよう改変してあります。
システムプロンプト」を更に改変することで既存のキャラはもちろん、オリジナルのキャラクターになりきらせる事も可能です。思い通りにならない時は各項目に矛盾が無いか確認し口調の例を増やしてください。
また元のChatGPT形式との大きな違いとして下部にcalm2-7b-chat形式で先行会話が付いています。応答の言い回しを強く指定したい時はこちらも書き加えてください。ただし、先行会話は前述の記憶リセットしても削除されませんが記憶に含まれるのでここから実際の会話に繋がらないようなやり取りで終わってください。
またソースコードのrole変数にシステムプロンプトを書くことでそのキャラ付けを初期値にできます。この際内容は""で囲み、改行部分全てにバックスラッシュnバックスラッシュを加えてください。

 

・まとめ

自身のパソコンで会話ができるAIが動くというのはまさにSFな感覚です。長くなりましたがこれでも以前より遥かにハードルが低くなっているので試していただけると幸いです。