ド素人がClaudeと2人3脚でばんえい競馬AI予想アプリを作っちゃった話

![52cfe946f19a520aadb8095713a69dbe.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564568/18aebb02-7f37-4284-b363-18017720fbbc.jpeg)
## はじめに



みなさん、「ばんえい競馬」って知っていますか？
北海道帯広市でのみ行われている、1トンもある馬が重い荷物を積んだそりを引くという世界でも珍しいレース。私は現地観戦がキッカケでこの競馬の魅力にハマっていたんですが、「AIで予測して不労所得にならないかな？」という思いつきが浮かんだんです。

とはいえ、プログラミングはほぼ素人。
学生時代に授業でPythonを触ってちょっとしたWebスクレイビングの知識しかありませんでした。しかし、昨今の生成AIのありがたすぎるパワーを借りれば僕も夢の不労所得、寝ながら自分の生活費を稼げる勝ちまくりモテまくりになれるかも？！

そんな邪な気持ちで始まったのがこのプロジェクトでした。

## で、どのLLMを選んだの？？

一通り全部触ってみたのですが、コードの正確性・あいまいなニュアンスの具現化という点で選んだのはAnthropicのAIアシスタント「Claude」。
日本語も自然に出力でき、プロジェクト機能としてClaudeのプロジェクト内で作ったArtifactをそのままProject knowledgeとしてボタン１つで登録できるのが最強すぎた。業務上、情シスあるある割り込みタスクが飛んでくるので常にゲームのコンティニューみたいな感じでキャッチアップ出来たのが非常に効率的でした。

「Claude、アプリってどうやって作るの？」という無茶振りから始まった二人三脚の開発物語を、今日はみなさんに共有したいと思います。

## 目次

1. [プロジェクトの概要](#プロジェクトの概要)
2. [開発環境の準備](#開発環境の準備)
3. [データ処理](#データ処理)
4. [AIモデルの構築](#AIモデルの構築)
5. [アプリケーションの実装](#アプリケーションの実装)
6. [遭遇した問題と解決策](#遭遇した問題と解決策)
7. [成果と感想](#成果と感想)
8. [ソースコード](#ソースコード)

## プロジェクトの概要

**目標**: ばんえい競馬のレース結果を予測し、わかりやすく解説するデスクトップアプリを作る

**主な機能**:
- レース情報（馬名、騎手名、枠番、馬体重など）の自動・手動入力
- AIによるレース結果の予測
- 自然言語での予想解説の生成
- レースURLからの情報自動取得（スクレイピング）

**使用技術**:
- Python 3.8+
- tkinter（GUIフレームワーク）
- LightGBM（機械学習モデル）
- Anthropic API（Claude）（主役）
- BeautifulSoup（Webスクレイピング）
- pandas/NumPy（データ処理）

## 開発環境の準備

昔大学で触ったときは開発環境作るのですら苦痛でしたが、Claudeさんにお願いしたらいい感じに用意してくれました。

```bash
# 仮想環境の作成
python -m venv banei-env

# 環境の有効化
# Windows
banei-env\Scripts\activate
# macOS/Linux
source banei-env/bin/activate

# 必要なライブラリのインストール
pip install pandas numpy scikit-learn lightgbm requests beautifulsoup4 matplotlib seaborn
```

加えて、アプリミリしらな僕に開発に必要なプロジェクト構造も提案してくれました：

```
banei-ai-app/
├── models/                  # 学習済みAIモデル
├── data/                    # データファイル
├── src/
│   ├── prediction/          # 予測エンジン
│   ├── anthropic/           # AI解説連携
│   ├── gui/                 # 画面表示
│   └── utils/               # 便利ツール
├── config/                  # 設定ファイル
└── main.py                  # メインプログラム
```

この構造は、初心者でも迷わないようにモジュールごとに機能を分離していて、とても参考になりました。実際にこの通りにフォルダを作ることで、「次は何をしたらいいのか」が見えやすくなりました。

## データ分析

次に、AIモデルの学習に必要なデータを集める必要がありました。ばんえい競馬のデータはネット上にあるものの、機械学習用に整形されたものはありません。そこでスクレイピングの力を借りることにしました。
物量的な問題で、SeleniumではなくBeautifulSoupを使って収集しました。行き詰まった部分はClaudeに相談しながらHTML解析のトライアンドエラーを一緒に乗り越えた感じですね。

デバッグの際には、こまめにログを出力したり、取得した情報を確認したりする習慣がついたのも大きな収穫でした。これってプログラミング全般に役立つスキルだと思います！

### データの前処理

収集したデータは、機械学習に使えるように前処理（クレンジング）が必要でした。以下のような処理を行いました。

1. 欠損値の処理
2. カテゴリカルデータの数値変換
3. 特徴量エンジニアリング

欠損値の処理は考えれば考えるほど自分のロジックの浅さが顕著になり、構築が難しかったです。また、型の制御や何をもって欠損とするかなど、思考と技術力両方試される部分でした。
特徴量エンジニアリングは奥が深く、単に「馬の名前」「騎手の名前」だけでなく、「過去5走の平均順位」「騎手の勝率」「馬と騎手の相性」など、予測に役立つ情報を追加しました。

```python
def create_jockey_features(df):
    """騎手に関する特徴を作成"""
    # 日付でソート
    df = df.sort_values('race_date')
    
    # 騎手ごとの集計を作成
    jockey_stats = []
    
    for jockey_id in df['jockey_id'].unique():
        jockey_df = df[df['jockey_id'] == jockey_id]
        
        # オッズ帯ごとの成績を集計
        odds_ranges = [(0, 2), (2, 5), (5, 10), (10, 20), (20, 999)]
        odds_stats = {}
        
        for odds_min, odds_max in odds_ranges:
            # 成績集計ロジック
            # ...
        
        jockey_stats.append({
            'jockey_id': jockey_id,
            'jockey_name': jockey_df['jockey'].iloc[0],
            **odds_stats
        })
    
    return pd.DataFrame(jockey_stats)
```

## AIモデルの構築

データが準備できたら、いよいよAIモデルの構築です。
今回はLightGBMというアルゴリズムを使用しました。
理由はなんか処理の軽そうな言葉の響きとインターネットに置いてある先駆者たちの情報がたくさんあったためです。
先輩方ありがとうございます。

### モデルトレーニング

Claudeに提案されたモデル構築コードはこんな感じでした。
意外と簡単にモデルが作れちゃってビックリ。
ライブラリってすげえや、、、。

```python
def train_model(X_train, y_train, X_test, y_test):
    """モデルを訓練して評価する"""
    model = LGBMRegressor(
        objective='regression',
        n_estimators=1000,
        learning_rate=0.05,
        num_leaves=31,
        min_child_samples=20,
        random_state=42
    )
    
    model.fit(
        X_train, y_train,
        eval_set=[(X_test, y_test)],
        eval_metric='mae',
        callbacks=[
            lightgbm.early_stopping(stopping_rounds=50),
            lightgbm.log_evaluation(period=100)
        ]
    )
    
    y_pred = model.predict(X_test)
    y_pred = np.clip(y_pred, 1, 10)  # 着順は1-10の範囲に制限
    
    # 評価指標の計算
    metrics = {
        'MAE': mean_absolute_error(y_test, y_pred),
        'MSE': mean_squared_error(y_test, y_pred),
        'R2': r2_score(y_test, y_pred)
    }
    
    return model, metrics, y_pred
```

### ハイパーパラメータの最適化

モデルの性能を上げるため、Optunaというライブラリを使ってハイパーパラメータの最適化も行いました。
このプロジェクトで一番「俺AIエンジニアやってる！！」感があった個所です。
ハイパーパラメータについては別記事にて勉強したことをまとめました。
良ければ読んでね。
https://qiita.com/zatrie78/items/0619cea8b003aaa1f2cc

```python
def objective(trial):
    param = {
        'learning_rate': trial.suggest_float('learning_rate', 0.005, 0.05),
        'n_estimators': trial.suggest_int('n_estimators', 500, 5000),
        'num_leaves': trial.suggest_int('num_leaves', 20, 150),
        # その他のパラメータ...
    }
    
    # 時系列交差検証
    cv_scores = []
    for train_idx, test_idx in tscv.split(df.sort_values('race_date')):
        # 検証ロジック...

    return np.mean(cv_scores)

# Optunaによる最適化の実行
study = optuna.create_study(direction='minimize')
study.optimize(objective, n_trials=30)
```

最適化したパラメータでモデルを作り直すことで、予測精度を向上させることができました。大体着順で言うと0.2着分くらい精度向上することができました。セッティングによってはまだまだ行けそうなので1.5くらいを目指したいです。

## アプリケーションの実装

### GUI開発（tkinter）

GUIの作成には、Pythonの標準ライブラリであるtkinterを使いました。初めてのGUI開発でしたが、タブ形式のインターフェースや入力フォームなどを実装できました。はるか昔にダイヤログ出すだけのために使ったことがあったくらいなので、実際にデスクトップアプリのルックスをしているとちょっと驚きます。笑

```python
def setup_gui(self):
    """GUIコンポーネントをセットアップする"""
    # ナビゲーションタブ
    self.tab_control = ttk.Notebook(self.root)
    
    # 予測タブ
    self.prediction_tab = ttk.Frame(self.tab_control)
    self.tab_control.add(self.prediction_tab, text="レース予想")
    self.setup_prediction_tab()
    
    # 履歴タブ
    self.history_tab = ttk.Frame(self.tab_control)
    self.tab_control.add(self.history_tab, text="予測履歴")
    self.setup_history_tab()
    
    # 設定タブ
    self.settings_tab = ttk.Frame(self.tab_control)
    self.tab_control.add(self.settings_tab, text="設定")
    self.setup_settings_tab()
    
    self.tab_control.pack(expand=1, fill="both")
```

### Anthropic API連携

予測結果を自然な言葉で解説するために、Anthropic API（Claude）と連携する機能も実装しました。ここは正直まだベータプロトタイプなのでプロンプトエンジニアリングしてないです。場合によってはBedrockとの連携も検討しています。

```python
def generate_prediction_explanation(self, race_info, predictions):
    """予測結果の説明を生成する関数"""
    horses_info = "\n".join([
        f"- {i+1}着予想: {horse['horse_name']} (騎手: {horse['jockey']}, 信頼度: {horse['confidence']:.1f}%)"
        for i, horse in enumerate(predictions["ranked_horses"][:3])
    ])
    
    prompt = f"""あなたはばんえい競馬の専門家です。以下の情報に基づいて、
    このレースの予想解説を300-400文字程度で提供してください。

    レース情報:
    - レース名: {race_info['race_name']}
    - 距離: {race_info['distance']}m
    - 馬場状態: {race_info.get('track_condition', '不明')}
    - 天候: {race_info.get('weather', '不明')}

    AI予想:
    {horses_info}

    本レースの展開予想、注目すべき馬の特徴、勝負どころについて解説してください。
    """
    
    return self.call_api(prompt)
```

## 遭遇した問題と解決策

開発中には様々な問題に遭遇しました。
周りに相談できる人がいなかったのでClaudeがいなければ頓挫していました。
その一部をご紹介します。

### 1. モジュールのインポートエラー

```
ImportError: No module named 'src'
```

**解決策**: Pythonのパス設定を正しく行う

```python
import os
import sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
```

### 2. スクレイピングの精度問題

ウェブサイトのHTML構造が複雑で、必要な情報を正確に取得するのに苦労しました。

**解決策**: CSSセレクタを複数用意し、順番に試行する方法を実装

```python
weather_elem = soup.select_one('.Weather')
if not weather_elem:
    weather_elem = soup.select_one('.RaceData01 .Item01')
if not weather_elem:
    weather_elem = soup.select_one('.RaceData .Item01')
```

### 3. 馬情報の入力フォーム問題

最初、各馬のデータ入力フォームが正しく動作せず、入力した情報が取得できないという問題がありました。

**解決策**: デバッグ用のログ出力を追加し、問題箇所を特定

```python
def collect_horse_data(self):
    """入力フォームから馬データを収集する"""
    horse_data = []
    
    for i, entries in enumerate(self.horse_entries):
        print(f"馬{i+1}のデータ収集: {entries[0].get()}")
        # ...
```

### 4. APIキーの安全な管理

Anthropic APIキーを安全に保存・使用する必要がありました。

**解決策**: 設定ファイルを使用し、APIキーを安全に保存

```python
def get_api_key(self):
    """API keyを取得する"""
    # 設定ファイルから読み込む
    if os.path.exists(self.config_path):
        with open(self.config_path, "r") as f:
            settings = json.load(f)
            return settings.get("api_key", "")
    
    # 環境変数をフォールバックとして使用
    return os.environ.get("ANTHROPIC_API_KEY", "")
```

## 成果と感想

### 完成したアプリの機能

なんやかんや作業4日目くらいで形になったアプリには、以下の機能が実装できました！！偉業！！

- レース情報の手動入力機能（馬名、騎手名、体重などを入力）
- URLからのレース情報自動取得機能（面倒な入力が一発で完了！）
- AIによるレース結果予測機能（信頼度付きで順位を表示）
- Claudeによる自然言語での予想解説生成機能（まるで競馬新聞のように解説）

最初は「本当にできるかな？」と不安だったのですが、今見ると結構しっかりしたアプリになっていて自分でも驚いています！
何より、「こういうの作りてえ！！」ってモチベーションだけで形にまとめ上げられるＡＩエージェントのサポートに感動しました。

### 学んだこと

このプロジェクトで学んだことは山ほどあります。プログラミング初心者の私にとって、全て学びでしかなかったです。

1. **モジュール設計の重要性**: 最初は「全部一つのファイルでいいじゃん」と思っていましたが、機能ごとにファイルを分けたおかげで、バグ修正もどこを直せばいいかすぐわかるようになりました。面倒くさがらないでちゃんとやるのマジ大事。

2. **デバッグの方法**: エラーメッセージを見ると最初は「何言ってるの？」という感じでしたが、今では「あ、このモジュールが見つからないのね」とか理解できるようになりました。ログ出力の大切さも身に染みています！迷ったら全部Print！

3. **機械学習の基礎**: 正直、最初は「特徴量？L1？」と意味不明でしたが、今では「あ、このデータはこう変換しないと使えないな」とか考えられるようになりました。一発で正解までは行かないけど思考するクセができました。

4. **GUIアプリケーション開発**: tkinterでのボタン配置やイベント処理など、見た目と機能をつなげる楽しさを知りました！シンプルかつロジカルなＵＩにすることで開発のしやすさと使いやすさの双方を担保できました。

5. **APIの活用**: 外部サービスを使って自分のアプリを強化する方法を学びました。特にClaudeのAPIは、テキスト生成の可能性を広げてくれました。想像の100倍簡単にAPI使えたのでAPIを社内で布教しようと思います。

### Claudeとの共同作業の感想

Claudeに作ってもらったと言っても過言ではないくらい、非常に助けられました。一緒に開発する中で感じた良かった点：

- **わかりやすい説明**: 難しい概念も「あ、そういうことか！」と腑に落ちる説明をしてくれました。専門用語のオンパレードにならないのが良かった！困ったら「小学生にわかるようにガンダムに例えて」ってお願いしてました。みんなジークアクスを観よう。

- **デバッグの達人**: 「このエラー、何が原因？」と聞くと、「ここをこう直してみて」と的確なアドバイス。何度「助かった〜！」と声が出たことか。直接的な解決にならなくても、考え方や気を付けるところをリマインドしてくれたのが便利でした。

ただ、気をつけないといけないのは、AIの提案をただコピペするだけでは学びが浅くなること。「なぜこのコードがいいのか」「どういう仕組みで動いているのか」を毎回確認した上で自分の要件を再度言語化し、数回ラリーしながら作っていました。そのおかげで、単なる「コード生成機」ではなく、本当の意味での「先生」になってくれました。

## ソースコード

完成したプロジェクトのソースコードは、以下のGitHubリポジトリで公開しています：

[GitHub:banei-ai-predictor](https://github.com/zatrie78/banei-ai-predictor)

## 今後の開発予定

アプリの基本形はできましたが、むしろここからがスタートです。
### 近い将来の改良予定

1. **予測精度の向上**：
   現在のモデルは基本的なものなので、的中率をもっと上げるため、レース履歴を分析して自動的にモデルを改良する機能を追加したいです。ユーザーがレース結果を入力すると、その情報を基に学習を続けるイメージです。

2. **AWSとLINE連携**：
   今は手動で予想を実行していますが、AWSのLambdaとEventBridgeを使って、毎日自動的に予想を行い、その結果をLINEで配信するシステムを構築予定です。「今日のばんえい予想」が毎日届く仕組みを作るのが目標です！

3. **UI/UXの改善**：
   今のインターフェースはシンプルすぎるので、もっと視覚的にわかりやすく、使いやすいデザインにしたいです。特に予想結果の表示部分は、グラフやチャートを使って「なぜその予想になったのか」を直感的に理解できるようにしたいですね。

### 長期的な構想

将来的には、このプロジェクトをオープンソース化して、他の競馬ファンやプログラミング初心者も参加できるコミュニティにしたいと思っています。みんなでAI予想の精度を競い合いながら、技術も向上させていけたら最高ですね！

## おわりに

ばんえい競馬の予想アプリという、ニッチだけど自分が本当に作りたかったものを形にできた喜びは何にも代えがたいです。これからも「作ってみたい！」と思うものに挑戦し続けたいと思います。

みなさんも「難しそう…」と尻込みしているアイデアがあるなら、ぜひClaudeなどの生成AIを相棒に挑戦してみてください。きっと予想以上の結果が見えるはずです！

「ド素人でもできた」という生きた証として、この記事が誰かの背中を押せたら嬉しいです。

---

この記事が少しでも参考になりましたら、いいねやコメントをいただけると嬉しいです。ご質問があればお気軽にコメントしてください！