第6回:訓練データの設計 — 5種類のデータをどう統合したか
![]()
はじめに
前回、OpenAI APIのファインチューニングで約8分の学習時間と安定したJSON出力が得られたことをお伝えしました。しかし「データ準備が9割」と書いたように、ファインチューニングの成否を最も大きく左右するのは、学習に使うデータの質と設計です。
今回は、訓練データの設計に焦点を当てます。5種類のデータをどのように統合し、正解ラベルをどう作り、どのような試行錯誤を経て最終版のデータセットに至ったのか。データ設計の思想と具体的な実装について、詳しくお伝えします。
データソース
千里眼サービスのデータは、meloikプロジェクト(PHP + MySQL)が収集・生成しています。
- ニュース・IR情報: 適時開示情報閲覧サービス(TDnet)など、企業が公的に発表した情報をもとに収集
- 株価データ: 各銘柄の日足OHLCV(始値・高値・安値・終値・出来高)
- 企業情報: 業種、時価総額、会社概要、市場区分など
- 財務データ: 決算情報(売上高、利益率、EPS、ROA、ROE)
- マクロ経済指標: CPI、GDP、失業率、政策金利、為替レート
ニュースやIR情報の出典としては「適時開示情報閲覧サービス(TDnet)」など企業が公的に発表した情報をもとにしています。正確な情報は必ず原典をご確認ください。
入力データの構造 — 1サンプルの全体像
訓練データ1件分のJSON構造を示します。これがファインチューニング時に「user」メッセージとして入力される情報です。
{
"企業情報": {
"コード": "4972",
"企業名": "綜研化学(株)",
"業種": "化学",
"市場区分": "東証STD",
"時価総額": 28967000000,
"発行済株数": 8300000,
"株価": 3490,
"会社概要": "アクリル原料粘着剤大手。化成品が主力。..."
},
"ニュース": {
"日付": "20241105",
"時間": "15:30",
"種別": "決算",
"見出し": "綜研化学、今期経常を51%上方修正・最高益予想を上乗せ",
"内容": "(要約されたニュース本文)"
},
"株価データ": [
{"日付": "20241030", "始値": 3260, "高値": 3265, "安値": 3195,
"終値": 3195, "出来高": 8300},
{"日付": "20241031", "始値": 3210, "高値": 3250, "安値": 3185,
"終値": 3250, "出来高": 11200},
{"日付": "20241101", "始値": 3255, "高値": 3300, "安値": 3230,
"終値": 3265, "出来高": 15400},
{"日付": "20241102", "始値": 3270, "高値": 3280, "安値": 3220,
"終値": 3240, "出来高": 9100},
{"日付": "20241105", "始値": 3490, "高値": 3500, "安値": 3440,
"終値": 3490, "出来高": 52600}
],
"財務データ": {
"2023": {"売上高": 38129000000, "利益率": 5.33, "EPS": 173.9,
"ROA": 3.72, "ROE": 4.92},
"2024": {"売上高": 41318000000, "利益率": 9.26, "EPS": 317.7,
"ROA": 6.29, "ROE": 8.38}
},
"マクロ経済指標": {
"CPI": 107.95,
"GDP成長率": 0.32,
"失業率": 2.53,
"政策金利": 0.75,
"為替レート": 151.37
}
}
5種類のデータ、それぞれの役割
1. 企業情報 — 「どんな会社か」の文脈
業種、時価総額、会社概要は、ニュースの影響を判断する上で不可欠な文脈です。
同じ「売上高10%増」でも、化学メーカーとIT企業では市場の反応が異なります。時価総額100億円の中小型株と1兆円の大型株でも、ニュースのインパクトは違います。モデルにこの文脈を与えることで、より的確な予測が可能になります。
2. ニュース — 予測の主要材料
ニュースは予測の最も重要な材料です。決算発表、業績修正、新製品発表、M&Aなど、企業に関する情報がここに含まれます。
LLMの強みは、この自然言語のニュースを「理解」できることです。「経常利益51%上方修正」がポジティブなニュースであること、「赤字転落」がネガティブであることを、人間と同じように判断できます。
3. 株価データ — 直近5営業日の値動き
直近5営業日のOHLCV(始値・高値・安値・終値・出来高)を含めています。
過去5日間の値動きのパターン——上昇トレンドにあるのか、下落トレンドにあるのか、出来高が急増しているのか——は、翌日の予測に重要な手がかりです。
4. 財務データ — 企業の体力
2年分の売上高、利益率、EPS(1株あたり利益)、ROA(総資産利益率)、ROE(自己資本利益率)を含めています。
財務データは「この企業が健全かどうか」「成長しているかどうか」の指標です。好調な財務基盤を持つ企業のポジティブニュースと、業績が低迷している企業のポジティブニュースでは、市場の反応が異なることが期待されます。
5. マクロ経済指標 — 市場全体の温度感
CPI(消費者物価指数)、GDP成長率、失業率、政策金利、為替レートを含めています。
個別企業のニュースがどれほどポジティブでも、マクロ経済環境が悪化していれば株価は下がる可能性があります。逆に、マクロ環境が好調であれば、個別のネガティブニュースの影響が和らぐこともあります。
出力データの構造 — 正解ラベルの作り方
訓練データにおける「正解ラベル」は、ファインチューニング時に「assistant」メッセージとして使われます。つまり、「この入力に対して、モデルはこう答えるべき」という模範回答です。
3つの予測値
{
"当日終値→翌日始値": {"価格": 3045, "変動率": 0.0, "傾向": "中立"},
"当日終値→翌日終値": {"価格": 3075, "変動率": 0.98, "傾向": "中立"},
"翌日始値→終値": {"価格": 3075, "変動率": 1.0, "傾向": "中立"}
}
3つの予測値を出力する理由は、投資判断のスタイルに応じて使い分けられるようにするためです。
- 当日終値 → 翌日始値: ニュースが出た日の引け後に買って翌朝売る(オーバーナイト取引)
- 当日終値 → 翌日終値: 翌日1日通しての変動を見たい場合
- 翌日始値 → 翌日終値: 翌朝の寄り付きで買って大引けで売る(デイトレード)
正解ラベルの計算
正解ラベルは、実際の翌営業日の株価から算出します。
ニュース発表日: 2024/11/05
当日の終値: 3,490円
翌営業日の始値: 3,045円
翌営業日の終値: 3,075円
当日終値→翌日始値の変動率 = (3045 - 3490) / 3490 * 100 = -12.75%
当日終値→翌日終値の変動率 = (3075 - 3490) / 3490 * 100 = -11.89%
翌日始値→翌日終値の変動率 = (3075 - 3045) / 3045 * 100 = 0.98%
ニュースが発表された時点の株価と、翌営業日の実際の株価を使って変動率を計算し、これを正解ラベルとします。
Colab版 vs OpenAI版 — データの違い
第4回でも触れましたが、Colab版とOpenAI版ではデータの構成が大きく異なります。
| 項目 | Colab版(ELYZA / LLM-jp) | OpenAI版(gpt-4o-mini) |
|---|---|---|
| ニュース | 使用 | 使用 |
| 企業情報 | 使用 | 使用 |
| 株価データ | 前日比率のみ | フルOHLCV |
| 財務データ | 削除 | 使用 |
| マクロ経済指標 | 削除 | 使用 |
| max_length | 1,024 tokens | 16,385 tokens |
Colab版の出力形式もシンプルでした。
{
"予測": {
"前日終値から翌日始値の変動比率": -0.23,
"前日終値から翌日終値の変動比率": 3.09,
"翌日始値から翌日終値の変動比率": 3.33
}
}
OpenAI版では「価格」「傾向」も含むリッチな出力形式にしています。トークン数に余裕があるため、出力にも情報を詰め込めます。
データの進化 — 8つのバージョン
最終版のデータに至るまでに、8つのバージョンを経ました。
| バージョン | ファイル | 件数 | 内容 |
|---|---|---|---|
| v1 | train_data.json | 2件 | 手作りのテスト用。動作確認のためのダミーデータ含む |
| v2 | training_data.jsonl | 少数 | prompt-completion形式。最初の自動生成データ |
| v3 | training_data_chat.jsonl | 少数 | v2をchat(messages)形式に変換 |
| v4 | training_4972.jsonl | 中規模 | 特定銘柄(綜研化学)のみのデータ |
| v5 | train_combined.jsonl | 中規模 | 複数銘柄を統合(小規模) |
| v6 | train_combined_final.jsonl | 大規模 | クリーニング済み大規模データ |
| v7 | train_combined_1738729676133.jsonl | 1,009件 | OpenAI FT最終版 |
| v8 | train_combined…(2ファイル) | 2,011件 | Colab用(削減データ) |
試行錯誤のポイント
v1〜v3: フォーマットの試行錯誤
最初は手作りの2件で「ファインチューニングが動くか」を確認。その後、自動生成に移行し、prompt-completion形式で作ったデータをchat形式に変換するステップが加わりました。
v4: 単一銘柄からスタート
まず1銘柄(綜研化学 4972)のデータだけで学習し、予測テスト。「特定の銘柄に特化したモデル」としてどの程度機能するかを確認しました。
v5〜v6: 複数銘柄への拡大とクリーニング
複数銘柄のデータを統合。この段階でPHPエラーの混入やHTMLタグの残存が発覚し、クリーニング処理を強化しました。
v7: OpenAI FT最終版
1,009件のクリーンなデータ。平均1,303トークン/件、合計約130万トークン。このデータでファインチューニングしたモデルが本番運用されています。
データクリーニングの全体像
前回の第5回で一部触れましたが、データクリーニングの全ステップを改めて整理します。
Step 1: JSONパースエラーの除去
# JSONとしてパースできない行をスキップ
for line in f_in:
line = line.strip()
if not line:
continue
try:
data = json.loads(line)
except json.JSONDecodeError:
skipped += 1
continue
PHPのエラーメッセージ(Fatal error: Allowed memory size...)や空行を除去。
Step 2: HTMLタグ・エンティティの除去
import re
import html
def clean_text(text):
# HTMLタグ除去
text = re.sub(r"<br\s*/?>", " ", text)
text = re.sub(r"<.*?>", "", text)
# HTMLエンティティのデコード
text = html.unescape(text)
return text
ニュース本文に残っていた<br />、<、»などを処理。
Step 3: フォーマット変換
prompt-completion形式からchat(messages)形式への変換。
Step 4: バリデーション
OpenAI公式のバリデーションスクリプトで最終チェック。
最終データセットの統計:
サンプル数: 1,009件
平均トークン数: 1,303 tokens/件
最小: 847 tokens
最大: 3,168 tokens
assistant応答: 平均112 tokens
16K超過: 0件
なぜ1,009件で十分なのか
1,009件というデータ量は、一般的なLLMファインチューニングの文脈では決して多くありません。しかし、OpenAIのファインチューニングの場合、ベースモデル(gpt-4o-mini)が既に膨大な事前学習を受けているため、比較的少ないデータでも効果的に学習できます。
ファインチューニングの目的は、モデルをゼロから学習させることではなく、既存の知識を特定のタスクに特化させることです。gpt-4o-miniは既に金融用語の理解や数値の推論能力を持っています。1,009件の株価予測データは、その能力を「株価予測の出力形式」に合わせるのに十分でした。
とはいえ、データ量を増やせば精度がさらに向上する可能性はあります。これは今後の改善課題です。
データ設計で意識したこと
JSON形式の採用
入力データをJSON形式にした理由は、構造化された情報をLLMに正確に伝えるためです。
自然言語で「綜研化学は化学業種の企業で、時価総額は約290億円です」と書くこともできますが、JSONの方がデータの階層構造と型が明確です。LLMはJSON形式のデータをよく学習しているため、構造化データの理解が優れています。
日本語キーの使用
JSONのキー名には日本語(「企業情報」「ニュース」「株価データ」)を使いました。英語のキー名(“company_info”、“news”、“stock_data”)でも良かったのですが、値(ニュース本文など)が日本語であるため、キーも日本語で統一した方がモデルにとって扱いやすいと判断しました。
5日分の株価データ
株価データは直近5営業日分を含めています。5日間という長さは、短期的なトレンドを把握するのに適切な期間と考えました。1日だけでは傾向がわからず、20日では情報量が多すぎてトークンを圧迫します。
訓練データ生成のパイプライン
訓練データは手作りではなく、meloikプロジェクトのバッチ処理で自動生成しています。大まかな流れは以下の通りです。
1. 対象ニュースの選定
└─ MySQLの ai_news テーブルから、is_checked=0 のニュースを取得
2. 関連データの収集
├─ 企業情報: ai_companies テーブル
├─ 株価データ: ai_stock_prices テーブル(直近5営業日)
├─ 財務データ: ai_financials テーブル(2年分)
└─ マクロ経済指標: ai_macro_indicators テーブル
3. 正解ラベルの計算
└─ 翌営業日の実際の株価から変動率を算出
4. JSONフォーマットへの変換
└─ 上記を1つのJSONオブジェクトにまとめる
5. JSONL形式で出力
└─ 1行1サンプルのJSONLファイルとして保存
このパイプラインにより、ニュースが増えれば自動的に訓練データも増えます。将来的にはデータを定期的に追加して、モデルを再ファインチューニングすることも可能です。
データ設計の反省と今後
反省: データの多様性
現在の1,009件には、業種や銘柄の偏りがある可能性があります。特定の業種(例: 化学、情報通信)のデータが多く、他の業種(例: 金融、不動産)が少ない場合、モデルの予測が偏る可能性があります。
今後の改善
- データ量の拡大: 1,009件から数千件規模へ。多様な銘柄と業種をカバー
- 時期の多様性: 上昇相場、下落相場、ボラティリティの高い時期など、市場環境のバリエーションを含める
- ニュース種別の充実: 決算だけでなく、M&A、新製品発表、規制変更など多様なニュースタイプ
まとめ
訓練データ設計のポイントです。
- 5種類のデータ(企業情報、ニュース、株価、財務、マクロ指標)をJSON形式で統合
- 正解ラベルは翌営業日の実際の株価変動から算出
- 8バージョンの試行錯誤を経て最終版(1,009件)に到達
- データクリーニング(PHPエラー除去、HTML処理、バリデーション)が品質を左右
- Colab版ではトークン制限のため情報を削減せざるを得なかったが、OpenAI版ではフルデータ投入
- 適時開示情報閲覧サービス(TDnet)などの公開情報をデータソースとして活用
「データの質がモデルの質を決める」——これはファインチューニングにおける最も重要な教訓です。
次回は、サービスの多言語対応で苦労した翻訳LLMの話です。DeepSeekのコストに惹かれて導入したものの、予想外の問題に直面した経験をお伝えします。