1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Google Colabで98%くらい完結する機械学習web app プロトタイプ

Last updated at Posted at 2025-12-24

本記事はQiita Advent Calender 2025 わたなべの22日目です!
22日目は、私渡邊が作成します!

順番前後するのですが、15日目のカレンダー記事「街のくまさん」を実装しようとして、Google Colabを触った時の気付きです。
数日前、HTMX + FastAPIという構成でML web appのプロトを作るという記事が話題となっていましたが、その環境としてGoogle Colabを利用してみます!

ChatGPT Image 2025年12月24日 15_40_38.png

Google Colab すげー

機械学習のwebアプリプロトタイプをデモする環境に悩んでいるという方に、少しでも参考になれば幸いです。

GPU使うためにColab触ったらデフォルトでいろいろ入ってて驚いた

そもそもGoogle Colab知らないよ、という方もいらっしゃるかと思いますが、これはPythonなどの対話型実行環境です。難しい準備が不要ですぐに始められるので、入門用としても非常に広く使われています。

一方で、画像を対象としたAIを使う上で欠かせないGPUというものを、無料で使えるのも素晴らしいです。Pythonだけでなく機械学習の入門としても人気があります。

映像からくまを検知するモデルを動かそうと思い、GPUを使うためにGoogle Colabを使った時のことです。久しぶりに使うのでpip freezeでプリセットライブラリの中身を見てみました。)

200でちょん切れるので興味ある方はリンク先からどうぞ

いろいろとあるので、お時間あるときに眺めていただければと思いますが、私が驚いたのは次の2つです。

fastapi==0.123.10
uvicorn==0.38.0

webアプリ作れるじゃん。
と、いうことで機械学習させたモデルによる推論を含めたwebアプリのプロトタイプをcolabだけでどこまでいけるか試してみました。

機械学習|カリフォルニアの物件情報

機械学習のモデルを実装します。
本記事ではそれが本題ではないので、scikit-learnの学習用ファイル、カリフォルニアの物件情報をそのまま使います。

以下のコードをそれぞれセルに張り付けて順次実行するだけです。

[1]モデル実装とR2計算
from sklearn.datasets import fetch_california_housing
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split

X, y = fetch_california_housing(as_frame=True, return_X_y=True)

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

model = RandomForestRegressor(
    n_estimators=100,
    random_state=42
)
model.fit(X_train, y_train)
R2 = model.score(X_test, y_test)
print(f"R2: {R2}")
[2]
"""
fetch_california_housingで呼び出すデータと
california_housing_test.csvで呼び出すデータの
列名と順番の補正
"""

model_col_names = model.feature_names_in_
COLUMN_MAP = {
    "median_income": "MedInc",
    "housing_median_age": "HouseAge",
    "total_rooms": "AveRooms",
    "total_bedrooms": "AveBedrms",
    "population": "Population",
    "households": "AveOccup",
    "latitude": "Latitude",
    "longitude": "Longitude",
}
[3] dataframe定義とvalue / target split
import pandas as pd

df = pd.read_csv('./sample_data/california_housing_test.csv')
df_target = df.drop('median_house_value', axis='columns')
df_value = df['median_house_value']
[4] predictと結果のまとめ
df_for_model = df_target.rename(columns=COLUMN_MAP)

X = df_for_model[model_col_names].to_numpy()
y_pred = model.predict(X)
df_pred_value = pd.DataFrame(y_pred * 100_000, columns=['predict_value'])

df_result = pd.concat([df_value, df_pred_value], axis=1)
[5] RMSE
from sklearn.metrics import mean_squared_error
import numpy as np

rmse = np.sqrt(mean_squared_error(
    df_result["median_house_value"],
    df_result["predict_value"],   
))
rmse
[6] MAE
from sklearn.metrics import mean_absolute_error

mae = mean_absolute_error(
    df_result["median_house_value"],
    df_result["predict_value"]
)
mae

結果

結果
R2=0.8051230593157366
rmse=np.float64(95311.29216845041)
mae=74315.60485666666

チュートリアルなんできれいに出ますね。

プロトタイプの構成|HTMX + FastAPI以外ありえない

それでは、このモデルを利用するプロトタイプを実装していきましょう。
外部ライブラリは1つのライブラリを除いて一切インストールしません。

1. index.htmlの配置

まずは、フロントのindex.htmlを準備します。
この後jinjaを利用する都合上、templates/index.htmlという感じで配置します。

1.1. フォルダ、ファイルの作成

左サイドバーの📁ファイルアイコンをクリックして、templatesフォルダとその中にindex.htmlファイルを作成します。

image.png

1.2. コードの貼り付け

作成したindex.htmlをダブルクリックすると画面右側に開きます。

image.png

白紙のindex.htmlが開いた状態

ここに次のコードをそのまま貼り付けます。

index.html
<!doctype html>
<html>
<head>
  <script src="https://unpkg.com/htmx.org@1.9.12"></script>
</head>
<body>
  <form hx-post="/predict" hx-target="#result">
    <input name="med_inc" placeholder="Median Income">
    <input name="house_age" placeholder="House Age">
    <input name="ave_rooms" placeholder="Avg Rooms">
    <input name="ave_bedrms" placeholder="Avg Bedrooms">
    <input name="population" placeholder="Population">
    <input name="ave_occup" placeholder="Avg Occupancy">
    <input name="latitude" placeholder="Latitude">
    <input name="longitude" placeholder="Longitude">
    <button>予測</button>
  </form>
  <div id="result"></div>
</body>
</html>

HTMXですよ!皆さん!大好きですよね!!

image.png

こんな感じの画面になっていれば大丈夫です。

2. main.pyの配置

実際にやる方は、モデルをjoblibなどでdump。
main.pyでloadする形がよいです。
もともとは学習したモデルをメモリに置いた状態でuvicornを動かしていたのですが、そこに後述するpyngrokまで加えるとnotebookのメモリがクラッシュするようです。

幸いjoblibもColabのプリセットに入っています。

notebook側の画面に戻ります。
開いているセルに次のコードを張り付けてください。
main.pyを生成するだけなので、即実行してしまって構いません。

main.py
%%writefile main.py

### model_calc / R2 ###

from sklearn.datasets import fetch_california_housing
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split

X, y = fetch_california_housing(as_frame=True, return_X_y=True)

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

model = RandomForestRegressor(
    n_estimators=100,
    random_state=42
)
model.fit(X_train, y_train)
R2 = model.score(X_test, y_test)
print(f"R2: {R2}")

### model testing ###

model_col_names = model.feature_names_in_
COLUMN_MAP = {
    "median_income": "MedInc",
    "housing_median_age": "HouseAge",
    "total_rooms": "AveRooms",
    "total_bedrooms": "AveBedrms",
    "population": "Population",
    "households": "AveOccup",
    "latitude": "Latitude",
    "longitude": "Longitude",
}

import pandas as pd

df = pd.read_csv('./sample_data/california_housing_test.csv')
df_target = df.drop('median_house_value', axis='columns')
df_value = df['median_house_value']

df_for_model = df_target.rename(columns=COLUMN_MAP)

X = df_for_model[model_col_names].to_numpy()
y_pred = model.predict(X)

df_pred_value = pd.DataFrame(y_pred * 100_000, columns=['predict_value'])
df_result = pd.concat([df_value, df_pred_value], axis=1)

### rmse ###

from sklearn.metrics import mean_squared_error
import numpy as np

rmse = np.sqrt(mean_squared_error(
    df_result["median_house_value"],
    df_result["predict_value"],   
))

### mae ###

from sklearn.metrics import mean_absolute_error

mae = mean_absolute_error(
    df_result["median_house_value"],
    df_result["predict_value"]
)

### server start ###

from fastapi import FastAPI, Form
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from fastapi.requests import Request

app = FastAPI()
templates = Jinja2Templates(directory="templates")

@app.get("/", response_class=HTMLResponse)
def index(request: Request):
    return templates.TemplateResponse(
        "index.html", {"request": request}
    )

@app.post("/predict", response_class=HTMLResponse)
def predict(
    med_inc: float = Form(...),
    house_age: float = Form(...),
    ave_rooms: float = Form(...),
    ave_bedrms: float = Form(...),
    population: float = Form(...),
    ave_occup: float = Form(...),
    latitude: float = Form(...),
    longitude: float = Form(...)
):
    X = [[
        med_inc, house_age, ave_rooms, ave_bedrms,
        population, ave_occup, latitude, longitude
    ]]
    y = model.predict(X)[0]

    return f"""
    <div>
      <h3>予測結果</h3>
      <p><strong>予測住宅価格:</strong> ${y:,.0f}</p>

      <hr>

      <h4>モデル精度(検証データ)</h4>
      <ul>
        <li>MAE: ${mae:,.0f}</li>
        <li>RMSE: ${rmse:,.0f}</li>
        <li>R²: {R2:.3f}</li>
      </ul>

      <p style="color: gray; font-size: 0.9em;">
        ※ この予測は統計モデルによる参考値です。<br>
        高価格帯ではやや低めに予測される傾向があります。
      </p>
    </div>
    """

%%writefile main.pyが冒頭についていると、rootにmain.pyとして中身の文字列を吐き出してくれます。

FastAPIですよ!皆さん!これも大好きですよね!!

実際のところ、モデルの学習をして、それをメモリにおいてサーバーを立てる構成になっているので効率よくないです。
本項冒頭の注意書きのように、モデルはdumpしてload するようにしてください

98%の残り2%|フォワーディング

これをuvicornで実行して終わり!
だったらよかったのですが、デフォルトではサーバーの外部公開ができないため、起動しているwebアプリにアクセスできません。

残念ながらフォワーディングだけは外部のサービスを利用しなければならないようです。

調べた範囲ではできなさそうだったのですが、もし間違ってたら指摘していただけると嬉しいです。

1. ngrokの利用

今回はngrok(エングロックと読みます)というサービスを利用しました。

ローカルで動いているプロトタイプを外部からもアクセスできるようにする鉄板かと思います。

1.1. アカウント登録

手順としては

  1. アカウントの登録
  2. アクセストークンの発行

という感じでトークン発行まで行っていただければと思います。
トークンは後ほど利用するので控えておいてください。

1.2. pyngrokのインストール

今回はnotebook上から実行できるようにpyngrokというライブラリをインストールしました。

!pip install pyngrok

をnotebookのセルで実行してください。
インストールされるかと思います。

このコマンドは1回実行されればよいので、実行した後は

# # 初回のみ実行
# !pip install pyngrok

こんな感じでコメントアウトしておきましょう。

実はColabにngrokはプリセットで入っています。

これを使うことも考えたのですが、ngrokを利用する以上アクセストークンが必要になり、なおかつターミナルからコマンドを毎度実行することが不便と感じたので、本記事ではPyngrokを利用しました。

1.3. コードの貼り付け

セルに次のコードを張り付けましょう。
まだ実行はしないでください。

from pyngrok import ngrok
from google.colab import userdata
NGROK_AUTHTOKEN = userdata.get('NGROK_AUTHTOKEN')

ngrok.set_auth_token(NGROK_AUTHTOKEN)

public_url = ngrok.connect(8000)
print("Public URL:", public_url)
ngrok.get_tunnels()

実行してしまったとしてもエラーが出るだけなので問題ないです。

2. access tokenの利用

ngrok(pyngrok)を利用するためには、先ほど控えたアクセストークンが必要です。
コード上にべた張りすると何かの拍子に見えてしまうので、シークレットに保存しましょう。

サイドバーの🔑シークレットアイコンをクリックしてください

image.png

環境変数を登録できます

2.1. 手順1

名前のところには

NGROK_AUTHTOKEN

をコピペ

2.2. 手順2

値のところに先ほどのngrok のアクセストークンをコピペ

2.3. 手順3

ノートブックからのアクセスのトグルスイッチをオン(青くなっている状態)

先ほど張り付けたコードにも

from google.colab import userdata
NGROK_AUTHTOKEN = userdata.get('NGROK_AUTHTOKEN')

ngrok.set_auth_token(NGROK_AUTHTOKEN)

という部分があり、トークンを読み込んでいることがわかるかと思います。

実行

1. ngrokを実行

先ほどのセルを実行してください。

Public URL: NgrokTunnel: "https://<文字列>.ngrok-free.app" -> "http://localhost:8000"
[<NgrokTunnel: "https://<文字列>.ngrok-free.app" -> "http://localhost:8000">]

という感じの表示が出れば成功です。

2. FastAPI(uvicorn)の起動

次に別のセルで

!uvicorn main:app --host 0.0.0.0 --port 8000

これを張り付けて実行してください。
少しした後で

INFO:     Started server process [25927]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

という感じの表示が出れば成功です。
プロセスは終了せず、ずっと起動した状態が保たれます。

3. アクセスして実行

先ほどのhttps://<文字列>.ngrok-free.appのURLにアクセスしてください。
ここまでの手順に成功していればシンプルなFormが表示されるかと思います。

適当に数値を入力して、価格が予想されれば成功です。

IMG_3957.jpg

適当に入れたので3$の物件らしいです

お疲れ様でした!

非常にサクッと作れて驚いた

さすがはgoogleという感じ。
Colabってこんなに充実していたんですね、という感じです。

データサイエンス系のライブラリはもちろん。Langchainやlangsmithも入っていて、はやりどころ抑えているなという感じです。

ただ、polarsが入っていないせいで、普段使い慣れていないpandasを使わねばならず、しんどかったです。と書こうして、本当に入ってないよなと思い検索したら入っており、普通に見落としていたようです

image.png

うむ・・・

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?