Google Colaboratoryで始めるLLMファインチューニング(3):モデルの実行

前回の記事「ハギングフェイス モデルダウンロード」で Llama 3.1 をGoogle Colab にダウンロードしました。今回は、このダウンロードしたモデルを動かしていきます。

前回の記事

Google Colabについてはこちらの記事から

Llama 3.1を実行する基本コード

前回の記事でLlama 3.1をダウンロードしましたが、ダウンロードしただけではまだ何も始まりません。ここからは、実際にLlama 3.1を動かして会話していきます。

モデルをロードする

まず、ダウンロードしたLlama 3.1をメモリにロードします。前回取得したHugging Faceトークンを使います。新しいColabセルに以下のコードを入力してください:

from transformers import AutoTokenizer, AutoModelForCausalLM
import torch

model_name = "meta-llama/Llama-3.1-8B-Instruct"
hf_token = "hf_xxxxxxxxxx"  # 前回取得したトークンに置き換える

print("モデルをロード中...")
print("初回は数分かかる場合があります")

tokenizer = AutoTokenizer.from_pretrained(
    model_name,
    token=hf_token
)

model = AutoModelForCausalLM.from_pretrained(
    model_name,
    token=hf_token,
    torch_dtype=torch.bfloat16,
    device_map="auto"
)

print("モデルのロード完了!")
print(f"モデルのデバイス: {model.device}")

cuda:0と表示されれば、GPUが正しく使われています。cpuと表示された場合は、ランタイム設定でGPUを有効にしてください。

前回ダウンロード済みの場合:
前回の記事でモデルをダウンロードしていれば、ここで再ダウンロードは発生しません。キャッシュから読み込むので、数秒でロードが完了します。

最初の会話を試す

モデルがロードできたら、早速会話してみましょう。まずは英語で試します。新しいセルに以下のコードを入力してください:

# 会話のメッセージを準備
messages = [
    {"role": "user", "content": "Hello! Who are you?"}
]

# プロンプトを作成
prompt = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True
)

# トークン化してモデルのデバイスに送る
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)

# テキストを生成
outputs = model.generate(
    **inputs,
    max_new_tokens=256,
    temperature=0.7,
    top_p=0.9,
    do_sample=True
)

# 結果をデコードして表示
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(response)

このように英語で回答が返ってきたら成功

※実行時間について:
正常であれば、5秒〜30秒程度で回答が返ってきます。もし数分経っても回答が返ってこない場合は、GPU設定を確認してください。

日本語で会話する方法

英語で動作確認ができたら、次は日本語で会話してみましょう。Llama 3.1は多言語対応ですが、日本語で回答させるにはコツがあります。

system promptを使って日本語回答を促す:

# 日本語での会話
messages = [
    {"role": "system", "content": "あなたは親切なAIアシスタントです。日本語で回答してください。"},
    {"role": "user", "content": "こんにちは!自己紹介してください。"}
]

prompt = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True
)

inputs = tokenizer(prompt, return_tensors="pt").to(model.device)

outputs = model.generate(
    **inputs,
    max_new_tokens=256,
    temperature=0.7,
    top_p=0.9,
    do_sample=True
)

response = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(response)

このように日本語で回答が返ってくれば成功

パラメータの意味と調整方法

さきほどのコードで、temperature=0.7top_p=0.9といったパラメータを使いました。実は、これらのパラメータを変えることで、Llama 3.1の回答の質を大きく変えられます。同じ質問でも、パラメータ次第で回答が「正確で堅い」ものになったり、「創造的で自由」なものになったりします。

ここでは、各パラメータの意味と、どう調整すればいいかを解説します。

temperatureとは?温度で回答が変わる仕組み

temperature(温度) は、AIの回答の創造性を制御するパラメータです。

  • 低い温度(0.1〜0.3): 決定論的で安定した回答。毎回似たような答えが返る
  • 中程度の温度(0.6〜0.8): バランスが取れた回答。適度な多様性
  • 高い温度(1.0〜1.5): 創造的で予測できない回答。毎回違う答えが返る

値の範囲: 0.0〜2.0(一般的には0.0〜1.0を使用)

temperatureがどういうものなのかイメージしやすいように以下に書きます:

生成AIの「Temperature」は、料理のレシピをどれだけ忠実に守るか、あるいはアレンジを加えるか、に例えるとイメージしやすくなります。

低い温度(例: 0.1〜0.5): レシピ通りに作る

  • 指示:「カレーのレシピを教えて」
  • AIの出力:
    1. 玉ねぎ、にんじん、じゃがいも、肉を用意する。
    2. 具材を炒める。
    3. 水を加え、煮込む。
    4. ルーを溶かし入れる。
    5. 完成。
  • 解説:誰もが知っている、最も一般的なカレーの作り方を忠実に出力します。間違いがなく、一貫性のある、予測通りの回答になります。

高い温度(例: 1.0以上): 自由にアレンジして作る

  • 指示:「カレーのレシピを教えて」
  • AIの出力:
    1. 隠し味にチョコレートとコーヒーを入れる。
    2. スパイスから調合して作る本格派カレー。
    3. 具材にリンゴとハチミツを加えてフルーティーな甘みにする。
    4. トマト缶を使って酸味とコクをプラスする。
  • 解説:確率の低い、珍しいアイデアも積極的に採用するため、独創的で意外性のあるレシピを提案してきます。ただし、時に風変わりで、奇抜な組み合わせになることもあります。

top_pとは?多様性をコントロールする方法

「Top_p」は、AIが次に生成する単語(トークン)の候補を、累積確率によって絞り込むためのパラメーターです。

「Top_pサンプリング」または「ニュークリアス(核)サンプリング」とも呼ばれ、この値を調整することで、AIが予測する単語の候補範囲を動的に変化させることができます。

値の範囲: 0.0〜1.0

重要な注意点: temperatureとtop_pは、両方同時に変更しないことが推奨されています。どちらか一方を調整して、効果を確認しましょう。

Top_pの値を調整した場合の変化

Top_pとTemperatureの違い

Top_pとTemperatureはどちらも文章の創造性を調整するパラメーターですが、アプローチが異なります。

Top_pをイメージしやすくするのに、「犬が」に続く単語を予測する場合を基に考えてみます。

AIが次に続く単語の候補を以下のように予測したとします。

  • 単語:「走る」(確率:0.5)
  • 単語:「吠える」(確率:0.3)
  • 単語:「寝る」(確率:0.1)
  • 単語:「飛ぶ」(確率:0.05)

Top_pを0.9に設定した場合:
「走る」(0.5)と「吠える」(0.3)の累積確率は0.8です。「寝る」(0.1)を足すと累積確率が0.9を超えるため、AIは「走る」「吠える」「寝る」の3つを候補として考慮します。

Top_pを0.7に設定した場合:
「走る」(0.5)と「吠える」(0.3)の累積確率が0.8となり、設定値の0.7を超えるため、候補は「走る」と「吠える」の2つに絞られます。

max_tokensとは?回答の長さを調整

max_tokens(正確にはmax_new_tokens)は、AIが生成する回答の最大長を指定します。

トークンとは? トークンは「単語の断片」のようなものです。英語では1単語=1〜2トークン日本語では1文字=2〜3トークン程度です。

設定例:

# 短い回答(50トークン)
max_new_tokens=50

# 通常の回答(256トークン)
max_new_tokens=256

# 長い回答(512トークン)
max_new_tokens=512

注意点:

  • 大きな値を設定すると、メモリを多く消費します
  • 無料版Colabでは、512程度が限界です
  • 回答が途中で切れる場合は、max_tokensを増やしてください

推奨値:

  • 一般的な会話: 256
  • 短い回答: 100
  • 長文生成: 512

do_sampleとは?ランダム性のON/OFF

do_sampleは、大規模言語モデル(LLM)が応答を生成する際のサンプリング(確率に基づいた単語選択)を行うかどうかを制御するパラメータです。

: TrueまたはFalse

do_sample=True(推奨):

  • temperatureやtop_pが有効になります
  • 毎回少し違う回答が返ります
  • 自然な会話に適しています

do_sample=False:

  • 常に確率が最も高い単語を選びます(貪欲法)
  • 毎回まったく同じ回答が返ります
  • temperatureやtop_pは無視されます

実際に比較してみよう!パラメータ別の回答例

理論だけではわかりにくいので、実際に異なるパラメータで回答を比較してみましょう。

同じ質問で、temperatureだけを変えてみます:

質問:「AIの未来について教えてください」

messages = [
    {"role": "system", "content": "あなたは親切なAIアシスタントです。日本語で回答してください。"},
    {"role": "user", "content": "AIの未来について教えてください"}
]

prompt = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True
)

inputs = tokenizer(prompt, return_tensors="pt").to(model.device)

outputs = model.generate(
    **inputs,
    max_new_tokens=256,
    temperature=0.2, # ← ここを変更
    top_p=0.9,
    do_sample=True
)

response = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(response)

設定:
temperature=0.2

top_p=0.9
max_new_tokens=256

設定:
temperature=0.7

top_p=0.9
max_new_tokens=256

設定:
temperature=1.2

top_p=0.9
max_new_tokens=256

同じ質問を3回実行して、temperature=0.2、0.7、1.2でそれぞれ試してみてください。回答の「堅さ」と「多様性」の違いが体感できるはずです。

会話を続ける方法

これまでは「1回だけの質問」をしてきました。
次は、このまま会話を続ける方法についてやっていきます。

あなた: Pythonとは何ですか?
AI: Pythonはプログラミング言語です...
あなた: Pythonの特徴を教えて
AI: Pythonの特徴は...

このように、前の会話を覚えたまま次の質問に答える方法をやります。

messagesリストの仕組み

会話を続けるカギは、messagesリストにあります。

さきほど使ったコードを思い出してください:

messages = [
    {"role": "user", "content": "こんにちは!"}
]

このmessgesは、会話の履歴を保存するリストです。
会話を続けるには、このリストに新しいメッセージを追加していきます。

roleの3つの種類:

1. system: AIへの指示(ずっと有効)
例:「日本語で答えてください」
「親切なアシスタントとして振る舞ってください」

2. user:ユーザー(あなた)の発言
例:「Pythonとは何ですか?」

3. assistant:AI(Llama 3.1)の回答
例:「Pythonはプログラミング言語です」

会話の流れ(図解)

messages = [
① {“role”: “system”, “content”: “日本語で答えてください”} ← 最初の指示
② {“role”: “user”, “content”: “Pythonとは?”} ← 1回目の質問
③ {“role”: “assistant”, “content”: “Pythonは…”} ← 1回目の回答
④ {“role”: “user”, “content”: “特徴を教えて”} ← 2回目の質問
⑤ {“role”: “assistant”, “content”: “特徴は…”} ← 2回目の回答
]

このように、会話の履歴をすべて保存することで、AIは前の会話を「覚えている」ように振る舞います。

文脈を保持して会話を続けるコード

実際に、会話を続けるコードを書いてみましょう。

ステップ1:最初の会話

# 会話の履歴を保存するリスト
messages = [
    {"role": "system", "content": "あなたは親切なAIアシスタントです。日本語で回答してください。"},
    {"role": "user", "content": "Pythonとは何ですか?"}
]

# プロンプトを作成して実行
prompt = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True
)

inputs = tokenizer(prompt, return_tensors="pt").to(model.device)

outputs = model.generate(
    **inputs,
    max_new_tokens=256,
    temperature=0.7,
    top_p=0.9,
    do_sample=True
)

# 回答を取得
response = tokenizer.decode(outputs[0], skip_special_tokens=True)

# AIの回答部分だけを抽出(プロンプト部分を除く)
ai_response = response.split("assistant")[-1].strip()

print("AI:", ai_response)

ステップ2:会話履歴に追加

# AIの回答をmessagesに追加
messages.append({"role": "assistant", "content": ai_response})

print("\n現在の会話履歴:")
for msg in messages:
    print(f"{msg['role']}: {msg['content'][:30]}...")

ステップ3:2回目の質問

# 新しい質問を追加
messages.append({"role": "user", "content": "Pythonの特徴を3つ教えてください"})

# 同じ手順で実行
prompt = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True
)

inputs = tokenizer(prompt, return_tensors="pt").to(model.device)

outputs = model.generate(
    **inputs,
    max_new_tokens=256,
    temperature=0.7,
    top_p=0.9,
    do_sample=True
)

response = tokenizer.decode(outputs[0], skip_special_tokens=True)
ai_response = response.split("assistant")[-1].strip()

print("\nAI:", ai_response)

messages.append():会話履歴の追加

複数回の会話例

実際に会話例をさせてみます。
最初に以下のコードを実行してください。
そのあとに、会話を続けます。

messages = [
    {"role": "system", "content": "あなたは親切なAIアシスタントです。日本語で回答してください。"}
]

def chat(user_message):
    messages.append({"role": "user", "content": user_message})
    
    prompt = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True
    )
    
    inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
    
    prompt_length = inputs['input_ids'].shape[1]
    
    outputs = model.generate(
        **inputs,
        max_new_tokens=256,
        temperature=0.7,
        top_p=0.9,
        do_sample=True
    )
    
    generated_tokens = outputs[0][prompt_length:]
    ai_response = tokenizer.decode(generated_tokens, skip_special_tokens=True)
    

    messages.append({"role": "assistant", "content": ai_response})
    
    return ai_response

1回目の質問:

response = chat("おすすめのプログラミング言語を教えて")
print(response)

回答を確認してから、2回目の質問実行

2回目の質問:

response = chat("その言語の学習時間はどれくらい?")
print(response)

3回目の質問:

response = chat("初心者向けの学習方法は?")
print(response)

このようにすると、実際の会話の流れを再現できます。ただ、これだと本当に履歴が残ってるのかわからないので以下のコードを実行するとみることができます。

print(messages)

まとめ

この記事では、Llama3.1をGoogle Colabで実際に動かす方法について解説しました。大事なところは、「パラメータの意味と調整方法」です。これは、チューニングをするときに必要なものなので押さえておきましょう。

次の記事では実際にファインチューニングのデータセットの準備の仕方を解説します。

クラウドに出せないデータで、自社専用AIをつくる。
Nextorageでは、機密情報や社内ナレッジなど、クラウドサービスに出せないデータを活用して、自社専用AIを構築するためのオンプレミスAIソリューション 「aiDAPTIV+(アイダプティブ・プラス)」 を展開しています。

生成AIを業務で活用したい一方で、
「社外サービスに機密情報を入力できない」
「GPUコストが高く、導入・運用のハードルが高い」
「日本語業務で十分な精度を出したい」
といった課題を感じている企業・組織の方は、ぜひ以下からお問い合わせください。