レバレジーズ データAIブログ

インハウスデータ組織のあたまのなか

自然言語だけでkaggleのチュートリアルやってみた

はじめに

こんにちは!レバレジーズのテクノロジー戦略室でデータエンジニアをしている鈴木です。
半年くらい前に、kaggleのチュートリアル(タイタニックの問題)を、FastAPI MCP + Claude Desktopを使って、自然言語だけでやってみたので紹介します!

背景

Googleのイベントに参加した際に、Looker in Geminiの対話型分析をみて、クエリを書かなくても、データからインサイトを得られる時代が来るのだなと思いました。そこで、専門的な知識を要する機械学習も、自然言語である程度やれたら、もっと身近なものになって良いな~と思い、実験してみることにしました!

前準備

kaggleやったことがある人は、お馴染みかと思いますが、、
今回、例題として扱うのが「タイタニック号生存予測」という問題で、難破したタイタニック号でどの乗客が生き残ったかを予測するものになります。
kaggleでは、学習に使えるデータをダウンロードできるので、前準備として、csvに落として、BigQueryにデータを事前に入れておきます

やったこと

大きく分けて、やったことは以下の3つです。

  • FastAPI MCPを使って、BigQueryを操作できるMCPサーバーの構築
  • Claude Desktopから、作成したMCPサーバーに接続
  • 自然言語で分析してみる

FastMCPを使って、BigQueryを操作できるMCPサーバーの構築

コードは以下の通りです! MCPサーバーで用意したのは、以下の機能です。

  • クエリを流して、データの取得
  • プロジェクトリストの取得
  • データセットリストの取得
  • テーブルリストの取得
  • テーブルスキーマの取得
  • ロジスティック回帰のモデルをBigQueryMLで作成
  • BigQueryMLのモデルを使った推論

ちょこっと工夫したポイントとしては、クエリを流す関数で、更新、削除などはできないようにしました。今思うと、BigQueryのDry Runだけ行って、コストが高いクエリは、実行前に確認する!みたいな機能をつけても良かったですね。

from fastapi import FastAPI
from fastapi_mcp import FastApiMCP
from google.cloud import bigquery

# Your existing FastAPI application
app = FastAPI()

# define your BigQuery client
client = bigquery.Client.from_service_account_json(
    "key.json",
    project="your project"
)

# Define your endpoints as you normally would
@app.get("/get_data_from_bigquery/{query}", operation_id="get_data_from_bigquery")
async def get_data_from_bigquery(query: str):
    # Comfirm the query is safe to execute
    if "DROP" in query or "DELETE" in query or "UPDATE" in query:
        return {"error": "Unsafe query detected. Query execution aborted."}
    # Execute the query
    query_job = client.query(query)
    # Wait for the job to complete
    results = query_job.result()
    # Process the results
    print(f"Query results: {results}")
    return {"query_results": [dict(row) for row in results]}

@app.get("/get_project_list_from_bigquery", operation_id="get_project_list_from_bigquery")
async def get_project_list_from_bigquery():
    # Get the list of projects
    projects = client.list_projects()
    # Process the projects
    project_list = [project.project_id for project in projects]
    return {"projects": project_list}

@app.get("/get_dataset_list_from_bigquery/{project_id}", operation_id="get_dataset_list_from_bigquery")
async def get_dataset_list_from_bigquery(project_id: str):
    # Get the list of datasets for the given project
    datasets = client.list_datasets(project=project_id)
    # Process the datasets
    dataset_list = [dataset.dataset_id for dataset in datasets]
    return {"datasets": dataset_list}

@app.get("/get_table_list_from_bigquery/{project_id}/{dataset_id}", operation_id="get_table_list_from_bigquery")
async def get_table_list_from_bigquery(project_id: str, dataset_id: str):
    # Get the list of tables for the given project and dataset
    dataset_id = f"{project_id}.{dataset_id}"
    tables = client.list_tables(dataset_id)
    # Process the tables
    table_list = [table.table_id for table in tables]
    return {"tables": table_list}

@app.get("/get_table_schema_from_bigquery/{project_id}/{dataset_id}/{table_id}", operation_id="get_table_schema_from_bigquery")
async def get_table_schema_from_bigquery(project_id: str, dataset_id: str, table_id: str):
    # Get the schema for the given project, dataset, and table
    table_ref = client.dataset(dataset_id, project=project_id).table(table_id)
    table = client.get_table(table_ref)
    # Process the schema with datatypes
    schema = {field.name: field.field_type for field in table.schema}
    return {"schema": schema}

@app.get("/create_logistic_reg_model_by_bigquery_ml/{model_name}/{project_id}/{dataset_id}/{train_table_id}/{target_variable}", operation_id="create_logistic_reg_model_by_bigquery_ml")
async def create_logistic_reg_model_by_bigquery_ml(model_name: str, project_id: str, dataset_id: str, train_table_id: str, target_variable: str):
    
    # Execute the query
    query = f"""
        CREATE OR REPLACE MODEL `{project_id}.{dataset_id}.{model_name}`
        OPTIONS(
          model_type='logistic_reg'
        ) AS
        SELECT
          {target_variable} as label,
          * except({target_variable})
        FROM
          `{project_id}.{dataset_id}.{train_table_id}`
    """
    
    query_job = client.query(query)
    # Wait for the job to complete
    query_job.result()
    
    return {"model_name": model_name, "status": "Model created successfully"}

@app.get("/predict_by_bigquery_ml/{model_name}/{project_id}/{dataset_id}/{test_table_id}/{id}/{limit}", operation_id="predict_by_bigquery_ml")
async def predict_by_bigquery_ml(model_name: str, project_id: str, dataset_id: str, test_table_id: str, id: str, limit: int):

    # Execute the query
    query = f"""
        SELECT
            {id} as id,
            predicted_label
        FROM
            ML.PREDICT(MODEL `{project_id}.{dataset_id}.{model_name}`, (
            SELECT
                *
            FROM
                `{project_id}.{dataset_id}.{test_table_id}`
            ))
        ORDER BY
            id
        LIMIT {limit}
    """
    
    query_job = client.query(query)
    # Wait for the job to complete
    results = query_job.result()
    
    return {"predictions": [dict(row) for row in results]}

# Add the MCP server to your FastAPI app
mcp = FastApiMCP(
    app,  
    name="My API MCP",  # Name for your MCP server
    description="MCP server for my API",  # Description
    base_url="http://localhost:8000",  # Where your API is running
    describe_all_responses=True,  # Describe all responses
    describe_full_response_schema=True  # Describe full response schema
)

# Mount the MCP server to your FastAPI app
mcp.mount()

Claude Desktopから、作成したMCPサーバーに接続

Claude Desktopで、設定>開発者から、claude_desktop_config.jsonファイルに以下のように追記します。

{
  "mcpServers": {
    "my-api-mcp-proxy": {
      "command": "/Users/naozumi/.local/bin/mcp-proxy",
      "args": ["http://127.0.0.1:8000/mcp"]
    }
  }
}

自然言語使って、分析してみる

準備が整ったので、いよいよ自然言語で分析してみましょう!
まずは、データ探索からやってみます

BigQueryから探してくれそうですね!お願いしてみます!

いい感じですね!プロジェクト→データセット→テーブル→スキーマの順番で探してくれてますし、親切にも提案してくれてますね!(これ何から見ればわからない人からしたら、結構ありがたいかも??)
せっかくお勧めされたので、乗船クラスによる生存率の違いを見てみます。(ちょっとプロンプトおかしいですが、汲み取ってくれました、助かるw)

クエリ書いてデータ取得してくれた

{
  `query`: `SELECT Pclass, SUM(Survived) AS survived_count, COUNT(*) AS total_count FROM `bq-sample-project-456713.titanic.train` GROUP BY Pclass ORDER BY Pclass`
}
{
  "query_results": [
    {
      "Pclass": 1,
      "survived_count": 136,
      "total_count": 216
    },
    {
      "Pclass": 2,
      "survived_count": 87,
      "total_count": 184
    },
    {
      "Pclass": 3,
      "survived_count": 119,
      "total_count": 491
    }
  ]
}

グラフも書いてくれそうです。

おお〜!なんだか良さそうなアウトプットですね!
グラフ部分は、Claude Desktopがjsで書いてくれてます
世知辛い結果ですが、「お金持ちの方が生存の可能性が高い」という傾向が得られましたw

次は、簡単な機械学習をやらせてみます!
今回は、MCP側でロジスティック回帰しか用意してないので、当然ロジスティック回帰のモデルになりますが、決定木とかK-meansとか、他のモデルを作成する関数を追加すれば、他のモデルでもできそうですね!

試しに5つ推論してみます!
(Claude Desktopが無料版ということもあり、全件は無理でした、、w)

{
  "predictions": [
    {
      "id": 892,
      "predicted_label": 0
    },
    {
      "id": 893,
      "predicted_label": 0
    },
    {
      "id": 894,
      "predicted_label": 0
    },
    {
      "id": 895,
      "predicted_label": 0
    },
    {
      "id": 896,
      "predicted_label": 1
    }
  ]
}

おお〜推論できてますね!
結果に対する説明もつけてくれてますし、このモデルが重視するポイントまでまとめてくれました。

まとめ

今回は、FastAPI MCP + Claude Desktopで簡単な分析をやってみましたが、自然言語だけでも、データ探索とシンプルな機械学習を行うことができました。
MCPサーバーの機能を追加したり、機械学習アルゴリズムを増やしていけば、もっと幅の広い活用もできそうです。 とはいえ、これをやったの半年前なので、今はもっと良い選択肢もある気がしますね!
最近は、MLOps関連をメインでやってますが、AI分野もキャッチアップして、機会があれば改良版を出そうと思います