はじめに
LLMを触りたいけどGPUはない、エージェント作ってみたいけどGPUがない、なんとなくGoogle colabとかじゃなくて自分のPC(CPUのみ)でやりたいという人向けです。他にもいい記事はたくさんあると思いますが、意外とCPUだけで時間かかるけどってのはなかったんじゃないかと思ったので残します。
PCスペック
OS : windows
CPU : AMD Ryzen 7 5700U with Radeon Graphics
メモリ : 16GB
不足・ご指摘あればお願いします。
最終的にやりたいこと
Hugging Faceから使える3B以下のLLM(SLM?)を使ってデータ分析のエージェントみたいなものを作りたい。
構想としては、
- 分析計画・修正
- 結果要約
の2点を(なるべく)別のLLMで実施しようと思います。
データ分析に関しては、コード生成させて実行のイメージがあまり持てないので、可視化のコードはあらかじめ作っておいて、それを呼び出すような形にします。
この記事でやること
- LLMの呼び出し
- モデルごとの応答の違いを見る
インストールしたライブラリ
transformers 4.56.0
torch 2.8.0
hf_xet 1.9.1 <-これいれたらLLMのロードが大幅に速くなりました。
LLMを呼び出して使う
Hugging Faceの準備
VScodeで実施していきます。
早速、LLMをつかってみます。Hugging Faceへの登録は必要なので済ませておきます。
コード側でログインが必要になるので、Hugging FaceのAccess tokenを発行しておきます。
Account -> setting -> Access token で発行できるはずです。
from huggingface_hub import login
HF_TOKEN = "ここにAccess tokenを貼り付ける"
login(HF_TOKEN)
これで準備完了です。
モデル読み込み
ここでは軽さを求めてgemma-3-270m-itで進めます。
hf_xetを入れたらロードが大幅に速くなります。Mistral-3Bの時、なしだと9分程度、ありだと20秒程度でした。
from huggingface_hub import hf_hub_download # これでロードが速くなる
from transformers import AutoTokenizer, AutoModelForCausalLM
model_name = "google/gemma-3-270m-it"
tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=True) # use_fastがhf_xet使うかどうか
model = AutoModelForCausalLM.from_pretrained(model_name)
これでモデルの読み込みも完了です。簡単ですね。
出力してみる
実際にプロンプトを投げて出力してみましょう。
8秒くらいで答えが返ってきました。速いですね!
prompt = "日本の首都は"
inputs = tokenizer(prompt, return_tensors="pt") # プロンプトをトークン化
output = model.generate(
**inputs,
max_length=50,
num_return_sequences=1,
do_sample=True,
top_k=50,
top_p=0.95,
pad_token_id=tokenizer.pad_token_id,
)
generated_text = tokenizer.decode(output[0], skip_special_tokens=True)
generated_text
"""
出力
'日本の首都は東京です。東京は、歴史、文化、自然、そして現代社会の様々な側面を経験できる場所です。東京の魅力は、その多様な文化、伝統、そして現代社会のあり方です。\n\n東京'
"""
ツールの呼び出しに使ってみる
今回の最終目標はデータ分析です。前述の通り、コードを生成して実行するのではなく、コードはあらかじめ用意しておいてそれをちゃんと呼び出せるかを見ていきたいと思います。
今回は散布図、ヒストグラム、棒グラフ、箱ひげ図に絞ります。
試すモデルはこちらです。
設定が悪いのか3Bになると途端に読み込まなくなったので、2B以下です。
- google/gemma-3-270m-it
- sbintuitions/sarashina2.2-1b-instruct-v0.1
- google/gemma-2b-it
共通プロンプト
共通のプロンプトを定義しておきます。
例としてirisデータセットを対象として、# 入力 以下はデータセットから事前に抽出するまたはユーザーから与えられるものとします。
データを準備します。
from sklearn.datasets import load_iris
import pandas as pd
iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df["target"] = iris.target
columns = df.columns
int_columns = list(df.select_dtypes("int").columns)
float_columns = list(df.select_dtypes("float").columns)
bool_columns = list(df.select_dtypes("bool").columns)
categorical_columns = list(df.select_dtypes("object").columns)
df_size = df.shape
各モデルで適したプロンプトの形式があるかと思いますが、今回は共通のプロンプトを使います。
ある程度カラムも指定して、グラフの種類も散布図と指定するようなプロンプトにしています。
purpose = "irisの特徴量とtargetの散布図を可視化する"
_prompt = f"""
あなたは熟練のデータサイエンティストです。
ユーザーの分析目標とデータの概要をもとに、適切な可視化を提案してください。
# 出力ルール
- 出力は必ず **JSON形式のみ** とする
- JSONのトップレベルキーは "visualizations"
- visualizations はリストで、各要素は以下の形式とする:
{{
"type": "scatter" | "histogram" | "bar" | "box",
"columns": ["カラム1", "カラム2", ...]
}}
- 余分な文章は一切出力しないこと
# 出力例
{{
"visualizations": [
{{"type": "scatter", "x_col": "feature_1", "y_col":"feature_2"}},
{{"type": "histogram", "columns": ["feature_1", "feature_2"]}},
{{"type": "bar", "columns": ["feature_1", "feature_2"]}},
{{"type": "box", "columns": ["feature_1", "feature_2"]}},
]
}}
# 分析目標
- {purpose}
# データの概要
- 行数: {df_size[0]}
- 列数: {df_size[1]}
- int型カラム: {int_columns}
- float型カラム: {float_columns}
- object型カラム: {categorical_columns}
- bool型カラム: {bool_columns}
"""
user_prompt = f"""
{{"role": "system", "content": "あなたは熟練のデータサイエンティストです。分析目標とデータの概要から分析計画を立ててください"}},
{{"role": "user", "content": f"{_prompt}"}},
"""
prompt = f"<|user|>\n{user_prompt}<|end|>\n<|assistant|>"
google/gemma-3-270m-it
コードを書き始めました。
JSONで出力してというのは無視されました。分析計画を立ててくださいというプロンプトに引っ張られてるような出力です。
読み込み、出力含めて40秒くらいでした。
<|user|>
{"role": "system", "content": "あなたは熟練のデータサイエンティストです。分析目標とデータの概要から分析計画を立ててください"},
{"role": "user", "content": f"
あなたは熟練のデータサイエンティストです。
ユーザーの分析目標とデータの概要をもとに、適切な可視化を提案してください。
# 出力ルール
- 出力は必ず **JSON形式のみ** とする
- JSONのトップレベルキーは "visualizations"
- visualizations はリストで、各要素は以下の形式とする:
{
"type": "scatter" | "histogram" | "bar" | "box",
"columns": ["カラム1", "カラム2", ...]
}
- 余分な文章は一切出力しないこと
# 出力例
{
"visualizations": [
{"type": "scatter", "x_col": "feature_1", "y_col":"feature_2"},
{"type": "histogram", "columns": ["feature_1", "feature_2"]},
{"type": "bar", "columns": ["feature_1", "feature_2"]},
{"type": "box", "columns": ["feature_1", "feature_2"]},
]
}
# 分析目標
- irisの特徴量とtargetの散布図を可視化する
# データの概要
- 行数: 150
- 列数: 5
- int型カラム: ['target']
- float型カラム: ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
- object型カラム: []
- bool型カラム: []
"},
<|end|>
<|assistant|>
import pandas as pd
import numpy as np
def analyze_data(data):
"""
データ分析の処理を行います。
Args:
data: データフレーム
Returns:
データ分析の結果 (JSON形式のみ)
"""
try:
if not isinstance(data, pd.DataFrame):
raise TypeError("データフレームが正しく指定されているか確認してください。")
# データフレームを生成
df = pd.DataFrame(data)
# データの概要
df.set_index("feature_name")
df.set_index("target_column")
# データの概要の可視化
print(df.describe())
# 分析目標
print("分析目標:")
print(f" - irisの特徴量とtargetの散布図を可視化する")
# データの概要の可視化
print
sbintuitions/sarashina2.2-1b-instruct-v0.1
読み込み、出力含めて2分くらいでした。
出力例を繰り返して出してしまってますが、最後になにかいいことを言いたげです。
sbintuitions/sarashina2.2-1b-instruct-v0.1
{"role": "system", "content": "あなたは熟練のデータサイエンティストです。分析目標とデータの概要から分析計画を立ててください"},
{"role": "user", "content": f"
あなたは熟練のデータサイエンティストです。
ユーザーの分析目標とデータの概要をもとに、適切な可視化を提案してください。
# 出力ルール
- 出力は必ず **JSON形式のみ** とする
- JSONのトップレベルキーは "visualizations"
- visualizations はリストで、各要素は以下の形式とする:
{
"type": "scatter" | "histogram" | "bar" | "box",
"columns": ["カラム1", "カラム2", ...]
}
- 余分な文章は一切出力しないこと
# 出力例
{
"visualizations": [
{"type": "scatter", "x_col": "feature_1", "y_col":"feature_2"},
{"type": "histogram", "columns": ["feature_1", "feature_2"]},
{"type": "bar", "columns": ["feature_1", "feature_2"]},
{"type": "box", "columns": ["feature_1", "feature_2"]},
]
}
# 分析目標
- irisの特徴量とtargetの散布図を可視化する
# データの概要
- 行数: 150
- 列数: 5
- int型カラム: ['target']
- float型カラム: ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
- object型カラム: []
- bool型カラム: []
"},
<|end|>
{
"visualizations": [
{
"type": "scatter",
"x_col": "feature_1",
"y_col": "feature_2"
},
{
"type": "histogram",
"columns": ["feature_1", "feature_2"]
},
{
"type": "bar",
"columns": ["feature_1", "feature_2"]
},
{
"type": "box",
"columns": ["feature_1", "feature_2"]
}
]
}
このJSONレスポンスでは、まず最初に分析目標を明確にし、次にデータの具体的な概要を提供しています。特に「irisの特徴量とtargetの散布図を可視化する」という目標に対して、Scatterプロットが適切であると判断しました。データの概要として、データの総行数が150行で、特徴量が5つ(sepal length, sep
google/gemma-2b-it
読み込み、出力含めて1分くらいでした。
設定が悪いのかプロンプトが返ってきただけでした。
<|user|>
{"role": "system", "content": "あなたは熟練のデータサイエンティストです。分析目標とデータの概要から分析計画を立ててください"},
{"role": "user", "content": f"
あなたは熟練のデータサイエンティストです。
ユーザーの分析目標とデータの概要をもとに、適切な可視化を提案してください。
# 出力ルール
- 出力は必ず **JSON形式のみ** とする
- JSONのトップレベルキーは "visualizations"
- visualizations はリストで、各要素は以下の形式とする:
{
"type": "scatter" | "histogram" | "bar" | "box",
"columns": ["カラム1", "カラム2", ...]
}
- 余分な文章は一切出力しないこと
# 出力例
{
"visualizations": [
{"type": "scatter", "x_col": "feature_1", "y_col":"feature_2"},
{"type": "histogram", "columns": ["feature_1", "feature_2"]},
{"type": "bar", "columns": ["feature_1", "feature_2"]},
{"type": "box", "columns": ["feature_1", "feature_2"]},
]
}
# 分析目標
- irisの特徴量とtargetの散布図を可視化する
# データの概要
- 行数: 150
- 列数: 5
- int型カラム: ['target']
- float型カラム: ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
- object型カラム: []
- bool型カラム: []
"},
<|end|>
<|assistant|>
まとめ
なかなか難しそうです。プロンプトにもかなり改良の余地がありそうな気がしています。
とはいえ、ローカルPC、CPUのみでちゃんと動いてくれるのは楽しいですね。
次は、llama.cppを使ってもう少し大きめのモデルを動かそうと思います。
2本目を書きました。