第4回:Colabで株価予測 — 3モデルの試行錯誤

はじめに

前回の技術解説と富士山実験で、LoRA + 8bit量子化によるファインチューニングが機能することを確認しました。いよいよ本番の株価データで挑戦です。

Google ColabのT4 GPU(16GB VRAM)を使い、ELYZA 8BとLLM-jp 7.2Bの2つのオープンソースモデルで株価予測のファインチューニングに取り組みました。

結論を先に言うと、このフェーズでは実用的な精度を得ることができませんでした。しかし、この過程で得た知見——データの前処理、モデル固有のフォーマット変換、追加学習の仕組み——は、最終的なOpenAI APIでの成功に大きく貢献しています。


Colab環境

項目
GPUNVIDIA T4(16GB VRAM)
CUDA12.x
Python3.10 / 3.11
データ保存Google Drive経由
主要ライブラリtransformers, peft, trl, bitsandbytes, datasets

T4 GPUは無料枠でも使えるため、個人開発にはありがたい環境です。ただし、GPU使用時間に制限があるため、試行錯誤のサイクルは決して速くありませんでした。「コードを書く → 学習を開始 → GPU制限に引っかかって中断 → 翌日再開」という流れが何度もありました。


ELYZA 8Bで株価予測に挑む

最初の壁 — トークン数の制限

株価予測のために用意したデータには、第1回で紹介した5種類の情報(企業情報、ニュース、株価データ、財務データ、マクロ経済指標)が含まれています。これをそのままトークナイズすると、1サンプルあたり数千トークンになります。

ところが、Colabの限られたVRAMでは最大シーケンス長を1,024トークンに制限せざるを得ませんでした。1,024トークンを超えるサンプルはtruncate(切り捨て)されてしまいます。

5種類のフルデータを1,024トークンに収めるのは不可能です。そこで、大幅にデータを削ぎ落とすことにしました。

【削除した項目】
- マクロ経済指標(CPI、GDP、失業率、金利、為替 → 全削除)
- 財務データ(売上高、利益率、EPS、ROE → 全削除)
- 株価データの詳細(始値・高値・安値・終値・出来高 → 全削除、前日比率のみ残す)

【残した項目】
- ニュース(見出し + 内容)← 最重要
- 企業情報(コード、企業名、業種、市場区分、時価総額、概要)
- 株価データの前日比率のみ(直近5日分)

ニュースが最も重要な情報だと判断し、それ以外を可能な限り削りました。しかし、後から振り返ると、この情報の削減が精度に悪影響を与えた可能性が高いです。財務データやマクロ指標がないと、モデルはニュースの内容だけで判断することになり、文脈が大幅に失われます。

ELYZA独自のフォーマット変換

ELYZAはLlama 3をベースにしており、独自の特殊トークンを使ったプロンプトフォーマットが必要です。ChatGPT形式のデータをそのまま入れても正しく学習しません。

<|start_header_id|>system<|end_header_id|>
あなたは株価予測アシスタントです。ニュースと株価データを分析し、
翌日の変動比率を予測してください。
<|eot_id|>
<|start_header_id|>user<|end_header_id|>
ニュース: 日付:20241113, 時間:12:00, 種別:決算,
見出し:ウェリタス、1-9月期経常が赤字転落...
企業情報: コード:130A, 企業名:(株)Veritas In Silico, 業種:医薬品...
株価データ: 前日比率:2.52, 前日比率:-1.51, 前日比率:-2.11,
前日比率:8.59, 前日比率:0.0
<|eot_id|>
<|start_header_id|>assistant<|end_header_id|>
{"予測": {"前日終値から翌日始値の変動比率": -0.23,
"前日終値から翌日終値の変動比率": 3.09,
"翌日始値から翌日終値の変動比率": 3.33}}
<|eot_id|>

<|start_header_id|><|eot_id|>はLlama 3の特殊トークンです。これらを正しく挿入しないと、モデルはどこがシステムメッセージで、どこがユーザー入力で、どこが期待される回答なのかを理解できません。

ChatGPT形式(messages配列)からELYZAフォーマットへの変換関数を実装しました。地味な作業ですが、モデルごとにフォーマットが異なるのはオープンソースLLMの悩ましいところです。

LoRA設定と学習

前回の技術解説で紹介した設定をそのまま適用しました。

from peft import LoraConfig, get_peft_model
from trl import SFTTrainer
from transformers import TrainingArguments

lora_config = LoraConfig(
    r=8,
    lora_alpha=32,
    lora_dropout=0.1,
    bias="none",
    task_type="CAUSAL_LM"
)

model = get_peft_model(model, lora_config)

training_args = TrainingArguments(
    num_train_epochs=3,
    per_device_train_batch_size=1,
    save_steps=100,
    logging_steps=10,
    report_to=["tensorboard"]
)

trainer = SFTTrainer(
    model=model,
    train_dataset=tokenized_dataset,
    tokenizer=tokenizer,
    args=training_args
)

trainer.train()

学習データ: 2つのJSONLファイルを結合した2,011サンプル。最大シーケンス長1,024トークンで3エポック学習しました。T4 GPUで数時間の処理です。

LoRAマージとGGUF変換

学習が完了したら、LoRAアダプターをベースモデルに統合し、GGUF形式に変換してローカルでも推論できるようにしました。

from peft import PeftModel

# LoRAアダプターをベースモデルに統合
model = PeftModel.from_pretrained(base_model, "fine_tuned_model")
merged_model = model.merge_and_unload()
merged_model.save_pretrained("full_model")
# GGUF形式に変換(FP16)
python convert_hf_to_gguf.py full_model/ --outtype f16 --outfile model.gguf

# 8bit量子化
./llama-quantize model.gguf model_q8.gguf q8_0

# 4bit量子化
./llama-quantize model.gguf model_q4.gguf q4_0

Phase 1で確立したGGUF変換パイプラインが、ここで役に立ちました。

予測テストの結果

136件のテストデータで予測を実行しました。

結果は期待に反するものでした。

  • モデルが予測JSONを正しく出力しないケースが頻発
  • JSON形式ではなく自然言語で回答を返すことがある
  • 正しいJSON形式で返ったとしても、数値の精度が低い

例えば「経常利益51%上方修正」というポジティブなニュースに対して、翌日の株価変動を大きくマイナスと予測するようなケースがありました。


LLM-jp 7.2Bでの挑戦

ELYZA以外のモデルでも試してみました。国立情報学研究所(NII)が主導して開発したllm-jp-3-7.2b-instruct3です。

モデルの選定理由

  • 日本語LLM: ELYZAとは異なるアーキテクチャの日本語モデルで比較実験
  • 7.2Bパラメータ: ELYZAの8Bより少し小さく、Colabでの扱いが若干楽

追加学習(Incremental Fine-tuning)の仕組み

LLM-jpでの実験では、追加学習の仕組みも実装しました。データが増えるたびにゼロから学習し直すのではなく、前回のファインチューニング結果を引き継いで学習を重ねるアプローチです。

# 追加学習の切り替え
is_additional_fine_tuning = True

if is_additional_fine_tuning:
    # 前回のFT済みフルモデルをベースにロード
    model = AutoModelForCausalLM.from_pretrained(
        "/content/drive/MyDrive/.../full_model",
        quantization_config=bnb_config,
        device_map={"": torch.cuda.current_device()}
    )
else:
    # 初回はベースモデルをロード
    model = AutoModelForCausalLM.from_pretrained(model_id, ...)

# その上にLoRAアダプターを追加して学習
model = get_peft_model(model, lora_config)

仕組みとしてはシンプルです。初回学習ではベースモデルにLoRAを追加して学習し、フルモデルとして保存。2回目以降は、前回保存したフルモデルを新たなベースとして、その上にまたLoRAを追加して学習します。

モデルのバージョン管理

追加学習を繰り返すと、世代管理が必要になります。タイムスタンプ付きのディレクトリ名で旧モデルを退避し、最新版をシンボリックリンクで参照する仕組みにしました。

# 学習完了後
# 1. 旧モデルをタイムスタンプ付きで退避
mv full_model full_model_202502051200

# 2. 新モデルを最新版として配置
mv full_model_updated full_model

これにより、何か問題があった場合に過去のバージョンに戻すことも可能です。

予測結果のファイル出力

テスト時の予測結果をJSONファイルとしてGoogle Driveに保存する仕組みも作りました。

output_dir = "/content/drive/MyDrive/.../predictions"

for i, sample in enumerate(converted_predict_dataset):
    # 予測を実行
    result = predict(model, tokenizer, sample)

    # 結果をJSONファイルに保存
    with open(f"{output_dir}/prediction_{i}.json", "w") as f:
        json.dump(result, f, ensure_ascii=False, indent=2)

こうしておくと、後から予測結果を分析したり、モデル間の比較を行ったりするのが楽になります。

LLM-jpの結果

ELYZA同様、予測精度は不十分でした。追加学習の仕組み自体は正常に動作しましたが、根本的な精度の問題は解決しませんでした。


Phase 2の総括 — なぜ精度が出なかったか

2つのモデルで試行錯誤しましたが、いずれも実用的な精度には達しませんでした。原因を分析します。

項目ELYZA(8B)LLM-jp(7.2B)
LoRAファインチューニング動作動作
追加学習なし実装済み
予測精度不十分不十分
出力形式の安定性低い低い
GGUF変換成功成功
学習時間長い(数時間)長い(数時間)

原因1: モデルの基礎能力不足

7〜8Bクラスのモデルでは、複雑な金融データの分析と数値予測を両立するのが難しかったと考えています。

株価予測タスクは、単なるテキスト分類(ポジティブ/ネガティブ)ではありません。具体的な変動率を数値で予測し、それをJSON形式で正確に出力する必要があります。これは7〜8Bモデルにとってかなり高い要求でした。

原因2: 出力形式の不安定さ

「JSONで返してください」と指示しても、自然言語で回答を返したり、JSONのキー名が変わったりする問題が頻発しました。

期待する出力:
{"予測": {"前日終値から翌日始値の変動比率": -0.23, ...}}

実際の出力例:
"この銘柄の翌日の株価は上昇すると予測されます。変動率は約..."

これはモデルのインストラクション追従能力の問題です。大規模なモデル(GPT-4クラス)なら「JSON形式で出力せよ」という指示に確実に従えますが、7〜8Bクラスでは指示追従が安定しませんでした。

原因3: 情報の削りすぎ

トークン数制限のために財務データとマクロ経済指標を削除し、株価データも前日比率だけに圧縮しました。この情報の欠落が予測精度に悪影響を与えた可能性が高いです。

「経常利益51%上方修正」というニュースの重みは、企業の過去の財務データを知っているかどうかで大きく変わります。時価総額100億円の企業と1兆円の企業では、同じニュースのインパクトがまったく異なるのです。

原因4: 学習時間とリソースの制約

Colabの無料枠ではGPU使用時間に制限があり、試行錯誤のサイクルが遅かったことも影響しています。ハイパーパラメータの調整や、データの前処理を変えて再学習するといった反復的な改善が十分にできませんでした。


Colabのコスト vs OpenAI API

ここで、コストについても考えてみました。

Colab Proに課金すれば、より長くGPUを使えます。しかし、Colab Pro(月額約1,179円)を数ヶ月使って試行錯誤を続けるのと、OpenAI APIのファインチューニング(1回あたり数ドル〜数十ドル)を使うのと、どちらが効率的でしょうか?

観点Colab(ローカル/クラウド)OpenAI API
初期コストGPU購入 or Colab Proなし
学習コスト電気代・GPU時間トークン課金(約数十ドル)
学習時間数時間〜数十時間約8分
精度低〜中高い
出力形式の安定性低い高い(JSON確実)
運用の手軽さインフラ管理必要API呼ぶだけ
モデルの所有権手元にあるOpenAI上

「モデルが手元にない」というデメリットはありますが、個人開発で重視すべきは精度とコストパフォーマンスです。Colabでの数ヶ月の試行錯誤で出なかった精度が、OpenAI APIなら数十ドルと8分で得られるかもしれない。

方針転換を決断しました。


Phase 2で得た財産

結果的にオープンソースモデルでの株価予測は断念しましたが、Phase 2での試行錯誤は決して無駄ではありませんでした。

技術的な知見

  1. LoRA + 8bit量子化 + SFTTrainerの組み合わせで、T4 GPUでも8Bモデルのファインチューニングが可能
  2. 「ファインチューニングが動く」と「実用的な精度が出る」は別問題
  3. データの質と量が精度を左右する。情報を削りすぎると予測に必要な文脈が失われる
  4. 追加学習の仕組みは有用(データが増えた時に全学習やり直し不要)
  5. GGUF変換パイプラインが確立済み → 将来の蒸留モデルに活用可能

コード資産

ファインチューニングのパイプライン、データ前処理、GGUF変換のコードはすべてColab上に残してあります。将来、より大きなモデル(70Bクラス)やより多くの訓練データで再挑戦する際に、そのまま再利用できます。

重要な教訓

「モデルの大きさ < データの質」

これはPhase 2を通じて最も強く感じたことです。7〜8Bのオープンソースモデルをいくらチューニングしても、入力データが貧弱では良い予測は得られません。逆に、モデルの基礎能力が高ければ(GPT-4oクラス)、フルデータを投入して高い精度を期待できます。


まとめ

Phase 2(Google Colab)での試行錯誤をまとめます。

  • ELYZA 8BとLLM-jp 7.2Bの2モデルで株価予測ファインチューニングを実施
  • LoRA + 8bit量子化で、T4 GPUでの学習は正常に動作
  • しかし、予測精度は不十分。出力形式の安定性にも課題
  • トークン制限による情報の削減が、精度低下の一因
  • 追加学習の仕組みとGGUF変換パイプラインは成果として確立
  • コストパフォーマンスを比較し、OpenAI APIへの方針転換を決断

「うまくいかなかった」フェーズですが、ここでの経験がなければOpenAI APIファインチューニングの重要性も、データ設計の勘所も理解できなかったと思います。

次回は、方針転換先であるOpenAI APIファインチューニングについて。たった8分で学習が完了し、安定したJSON出力が得られた驚きの体験をお伝えします。


次回: 第5回「OpenAI APIファインチューニング — 約8分で完了した最終解」

この記事をシェア

関連記事