Skip to main content
LadeStack LogoLadeStack
AI Development 約 13 分 2024年6月25日

Web開発者のための機械学習パイプライン:データ取り込みからブラウザ&サーバー推論まで

Webエンジニア向けMLパイプライン完全ガイド。データ収集、データリーク防止、ONNX Runtime Webによるブラウザ推論、FastAPIによるサーバー推論、MLOpsドリフト監視の実装手法。

データ収集、モデル学習、ONNXブラウザ推論、FastAPIマイクロサービスを結ぶ機械学習パイプラインのアーキテクチャ図

Web開発の現場において、機械学習(ML)の重要性はかつてないほど高まっています。静的なルールベースのロジックから、動的なレコメンデーション、自然言語による意図推定、画像処理、異常検知に至るまで、最新のWebサービスは知的な予測エンジンを直接組み込むことが求められています。

しかし、多くのWeb開発チームにとって、データサイエンスチームが作成したJupyter Notebookを本番のWebサービスに組み込む作業は依然として大きな課題です。トレーニング時の前処理ロジックの不一致(トレーニング/サービングスキュー)、データリーク、推論レイテンシの増大、モデルのパフォーマンス低下(データドリフト)など、本番環境特有の障害が頻繁に発生します。

本記事では、Webエンジニアの視点に立ち、堅牢な機械学習パイプラインの構築手法を解説します。データ収集から検証、ブラウザ推論とサーバー推論のトレードオフ、ONNX Runtime WebおよびFastAPIを用いた実装、そして本番の監視体制までを網羅します。


1. エンドツーエンドの機械学習パイプラインアーキテクチャ

本番稼働するML対応Webシステムは、分離された4つのフェーズで構成されます。

[ フェーズ 1: 収集と検証 ]
ユーザー操作ログ / RDBMS / イベントストリーム
       │
       ▼
スキーマ検証 & データクリーニング (Pydantic / Great Expectations)
       │
       ▼
時系列トレイン / 検証 / テスト分割 (データリーク防止)

─────────────────────────────────────────────────────────────────

[ フェーズ 2: 学習 & エクスポート ]
特徴量ストア & パイプライン学習 (Scikit-Learn / PyTorch)
       │
       ▼
モデル形式変換 (ONNX / TorchScript) & 量子化 (INT8 / FP16)
       │
       ▼
モデルレジストリ (S3 / MLflow) & バージョン管理

─────────────────────────────────────────────────────────────────

[ フェーズ 3: デプロイ & サービング ]
       ┌───────────────────────────────┐
       ▼                               ▼
[ クライアント側推論 ]          [ サーバー側推論 ]
ONNX Runtime Web (WASM/WebGL)    FastAPI / Triton推論サーバー
ゼロサーバーコスト・高プライバシー   複雑モデル・厳格なIP保護

─────────────────────────────────────────────────────────────────

[ フェーズ 4: MLOps & ドリフト監視 ]
入力特徴量分布の追跡 (KS検定 / PSI) & モデルリコール監視

2. データ収集とリーク防止

WebエンジニアがMLパイプラインを設計する際、最も頻繁に遭遇する重大な欠陥が**データリーク(Data Leakage)**です。

偶発的なデータリークの発生原因

  1. スケーリングパラメータの漏洩: テストデータを含めた全データセットに対して StandardScaler.fit() を実行してしまうと、将来の平均や分散が学習データに混入し、検証スコアが不当に高く算出されます。
  2. 時系列データのランダム分割: 時系列イベントログ(例:明日の解約予測)に対してランダムK分割交差検証を適用すると、モデルが未来の情報を利用して過去を予測することになります。

堅牢なデータ前処理スクリプト(Python)

# pipeline/preprocess.py
import pandas as pd
import numpy as np
from sklearn.model_selection import TimeSeriesSplit
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression

def build_leak_free_pipeline(df: pd.DataFrame, target_col: str, timestamp_col: str):
    # 時系列順にソートして厳格に順序を保持
    df = df.sort_values(timestamp_col).reset_index(drop=True)
    X = df.drop(columns=[target_col, timestamp_col])
    y = df[target_col]

    # 直近20%を本番ホールドアウト検証用として分離
    split_index = int(len(df) * 0.8)
    X_train, X_test = X.iloc[:split_index], X.iloc[split_index:]
    y_train, y_test = y.iloc[:split_index], y.iloc[split_index:]

    # Pipelineオブジェクト内にスケーラーを封じ込め、推論時のスキューを排除
    pipeline = Pipeline([
        ('scaler', StandardScaler()),
        ('classifier', LogisticRegression(C=1.0, solver='lbfgs', max_iter=500))
    ])

    pipeline.fit(X_train, y_train)
    holdout_score = pipeline.score(X_test, y_test)
    print(f"ホールドアウト検証精度: {holdout_score:.4f}")

    return pipeline

3. クライアント側推論 vs サーバー側推論の比較

本番環境で推論を実行する場所の選定は、ユーザー体験とインフラコストに直結します。

評価軸 クライアント側推論 (ONNX Runtime Web / TF.js) サーバー側推論 (FastAPI / Triton)
レイテンシ ネットワーク遅延ゼロ(0〜10ms) ネットワークラウンドトリップ依存(50〜250ms)
コンピュートコスト 無料(エンドユーザーのCPU/GPUを活用) サーバー費用発生(オートスケーリングが必要)
オフライン耐性 完全対応(Service Workerで動作可能) 不可(インターネット接続が必須)
プライバシー 最高(機密データがブラウザ外に出ない) サーバーへの送信が必要(コンプライアンス管理要)
モデル知財の保護 不可(モデルバイナリがブラウザにダウンロード) 最高(独自モデルの重みを完全秘匿)
最大モデルサイズ 小〜中規模(< 50MB推奨) 大規模(数十GB〜数百億パラメータ)

4. クライアント側推論の実装:ONNX Runtime Web

クライアント側で軽量モデルを実行する場合、WebAssembly(WASM)およびWebGPUバックエンドをサポートするONNX Runtime Webが最も安定した選択肢です。

// src/lib/ml/browserInference.ts
import * as ort from 'onnxruntime-web';

ort.env.wasm.numThreads = 2;
ort.env.wasm.simd = true;

export interface PredictionResult {
  probabilities: number[];
  predictedClass: number;
  inferenceDurationMs: number;
}

export class BrowserInferenceService {
  private session: ort.InferenceSession | null = null;

  async loadModel(modelPath: string): Promise<void> {
    this.session = await ort.InferenceSession.create(modelPath, {
      executionProviders: ['wasm'],
      graphOptimizationLevel: 'all'
    });
  }

  async predict(features: number[]): Promise<PredictionResult> {
    if (!this.session) {
      throw new Error("モデルセッションが初期化されていません。先にloadModel()を実行してください。");
    }

    const start = performance.now();
    const inputTensor = new ort.Tensor('float32', Float32Array.from(features), [1, features.length]);
    const inputName = this.session.inputNames[0];
    const feeds: Record<string, ort.Tensor> = {};
    feeds[inputName] = inputTensor;

    const results = await this.session.run(feeds);
    const outputName = this.session.outputNames[0];
    const outputTensor = results[outputName];
    const outputData = Array.from(outputTensor.data as Float32Array);

    let maxIdx = 0;
    let maxVal = outputData[0];
    for (let i = 1; i < outputData.length; i++) {
      if (outputData[i] > maxVal) {
        maxVal = outputData[i];
        maxIdx = i;
      }
    }

    return {
      probabilities: outputData,
      predictedClass: maxIdx,
      inferenceDurationMs: performance.now() - start
    };
  }
}

5. サーバー側推論の実装:FastAPIマイクロサービス

知的財産の保護や計算負荷の高いディープラーニングモデルには、FastAPIを用いた非同期マイクロサービスが適しています。

# server/app.py
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field
import numpy as np
import onnxruntime as ort
import time

app = FastAPI(title="ML推論APIサービス", version="1.0.0")

class InferenceRequest(BaseModel):
    user_id: str
    features: list[float] = Field(..., min_items=5, max_items=5)

class InferenceResponse(BaseModel):
    user_id: str
    prediction: int
    confidence: float
    latency_ms: float

session = ort.InferenceSession("models/production_classifier.onnx")
input_name = session.get_inputs()[0].name

@app.post("/api/v1/predict", response_model=InferenceResponse)
async def predict_endpoint(payload: InferenceRequest):
    try:
        start_time = time.perf_counter()
        input_array = np.array([payload.features], dtype=np.float32)

        raw_outputs = session.run(None, {input_name: input_array})
        probabilities = raw_outputs[0][0]
        predicted_class = int(np.argmax(probabilities))
        confidence = float(probabilities[predicted_class])
        duration_ms = (time.perf_counter() - start_time) * 1000

        return InferenceResponse(
            user_id=payload.user_id,
            prediction=predicted_class,
            confidence=confidence,
            latency_ms=duration_ms
        )
    except Exception as exc:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"推論エンジン実行エラー: {str(exc)}"
        )

6. MLOpsと本番ドリフト監視

モデルを本番公開した後、時間経過とともに実世界のデータ分布が変化し、予測精度が低下する現象を**データドリフト(Data Drift)**と呼びます。

監視すべき主要メトリクス

  1. 入力特徴量ドリフト: Kolmogorov-Smirnov検定(KS検定)や人口安定性指数(PSI)を用いて、本番のリクエスト特徴量が学習時の分布から逸脱していないかを自動検証します。
  2. 予測確信度スコアの低下: モデルが出力する確信度の平均値が急激に低下した場合、入力データの異常や新パターンの出現が疑われます。
  3. シャドウデプロイメント: 新バージョン(v2)を本番トラフィックに並行送信し、レスポンスは既存のv1を返しつつ、バックグラウンドでv2のレイテンシと精度を安全に検証します。

7. まとめ

Webエンジニアが機械学習を現場で実用化するためには、単なるモデル学習にとどまらず、データ検証、適切な推論場所の選定、そして本番稼働後の監視を含めた包括的なパイプライン設計が欠かせません。

さらなる実践アーキテクチャについては、当ブログのベクトルデータベースを活用したRAGアプリケーション構築や、ドメイン特化タスクのためのLLMファインチューニングも併せてご参照ください。

More Engineering Articles