本文へスキップ
ML.NET で機械学習を内製化した、ある製造業チームの記録のアイキャッチ画像
C# Tips

ML.NET で機械学習を内製化した、ある製造業チームの記録

公開: 更新: 約6分で読めます

「精度は出ているんです。ただ、現場で使えなくて」。最初の打ち合わせで、先方の開発リーダーが口にした一言を今でも覚えている。ある精密部品メーカーの品質検査チームが、機械学習で不良品の一次検出を自動化しようとしていた。モデルは Python で組み上げていて、検証データでの見え方は悪くない。それでも本番に載せられず止まっていた。この記事は、その相談から本番運用にたどり着くまで、私たちが伴走したおよそ半年の取り組みを、差し支えのない範囲で振り返るものだ。数字は具体的な KPI ではなく、あくまで変化の方向として読んでほしい。

相談は「精度は出ているのに動かせない」から始まった

チームがつまずいていたのは、モデルの中身ではなく、それを動かす場所だった。学習済みモデルは Python 製の推論サーバーに置かれ、既存の製造実行システム(MES)からは REST API 経由で呼び出す構成になっていた。ところが生産ラインは 1 秒あたり数個のペースで部品が流れる。API を 1 件ずつ叩くと、ネットワークとシリアライズのオーバーヘッドだけでラインの制御周期に間に合わない。現場からは「これでは止まる」という反応が返ってきていた。

精度の議論はひとまず置いて、私たちはまず制約のほうを整理した。既存資産はほぼすべて .NET で組まれている。運用担当者が読めるのも C# のコードだ。推論だけが別言語・別プロセスに切り離されていることが、レイテンシと保守性の両面で足を引っ張っている。問題はモデルではなく統合の設計にある、というのが最初の見立てだった。

コンサルティングチームと顧客が、ノートPCに映る機械学習の予測結果を一緒にのぞき込みながら協働している情景。奥には製造ラインが見える。
顧客の現場担当者と伴走しながら、推論の結果を一緒に確かめていく

なぜ ML.NET を選んだのか

選択肢はいくつかあった。Python の推論サーバーをチューニングして押し切る道も、gRPC に載せ替えて往復コストを削る道もある。ただ、このチームの事情に照らすと ML.NET が素直だった。理由は突飛なものではない。

第一に、既存の .NET アプリケーションと同じプロセス内で推論を回せる。プロセス間の往復そのものが消えるので、レイテンシの主要因を根本から断てる。第二に、すでにある Python 製モデルを ONNX に書き出せば、ML.NET 側から ApplyOnnxModel で読み込める。学習資産をゼロから作り直さずに済むのは、納期のあるチームには大きい。第三に、入出力を C# のクラスとして型付けできるため、特徴量の並びや型を取り違えるという運用でいちばん起きやすい事故を、コンパイル時に近いところで抑えられる。

「.NET で機械学習が実用になるのか」という懸念は、先方にも私たちにも最初はあった。小さなプロトタイプを一週間で組み、同じ入力に対する応答時間を Python 経由の構成と並べて比べてみると、インプロセス化の効果ははっきりと数字に出た。往復のなくなった分だけ、体感でわかるほど速くなった。ここでようやく、モデルの精度をラインの速度に乗せられる目処が立った。

モデルを ASP.NET Core に組み込む

本番の推論は、MES と連携する ASP.NET Core のサービスに載せることにした。ここで最初に持ち込んだのが PredictionEnginePool だ。PredictionEngine はスレッドセーフではないので、リクエストごとに生成すると重く、使い回すと競合する。プールは内部でその面倒を引き受けてくれるので、DI に登録するだけで安全に共有できる。

// Program.cs
builder.Services
    .AddPredictionEnginePool<SensorReading, QualityPrediction>()
    .FromFile(modelName: "quality", filePath: "Models/quality.zip", watchForChanges: true);

watchForChanges を有効にしておくと、モデルファイルを差し替えたときにアプリを止めずに読み替えてくれる。再学習したモデルを本番に反映する運用を考えると、この一行の効き目は大きかった。呼び出し側はプールから受け取ったエンジンに読み取り値を渡すだけで、あとはインプロセスで結果が返る。

public class QualityInspectionService
{
    private readonly PredictionEnginePool<SensorReading, QualityPrediction> _pool;

    public QualityInspectionService(PredictionEnginePool<SensorReading, QualityPrediction> pool)
        => _pool = pool;

    public bool IsLikelyDefective(SensorReading reading)
    {
        var prediction = _pool.Predict(modelName: "quality", example: reading);
        return prediction.PredictedLabel && prediction.Probability > 0.9f;
    }
}

Python から持ち込んだ前処理は、学習時と推論時で必ず同じロジックを通す必要がある。ここがずれると、検証では良かったモデルが本番でだけ崩れる。私たちは前処理を独立したクラスに切り出し、学習パイプラインと推論サービスの双方から同じ実装を参照させた。地味だが、後々の事故をいちばん減らしてくれた判断だった。

本番で直面した壁

段階的に広げる方針は最初から決めていた。いきなり全ラインではなく、一本のラインの一部トラフィックから始め、想定どおり動かなければ元の運用に戻せるようにしておく。この慎重さが初日に効いた。

本番投入の初日、誤検出が検証時よりも明らかに多かった。原因の切り分けにはそう時間はかからなかった。開発環境で校正したセンサーと、実ラインに据えられたセンサーとで、温度や圧力の基準値がわずかにずれていたのだ。学習データの分布から少し外れた値が入り、モデルがそれを異常と受け取っていた。モデルの善し悪しではなく、現場とデータのあいだのギャップだった。

対処は、直近の良品データからセンサーごとの基準値を継続的に見直し、入力を補正するレイヤーを推論の手前に足すことだった。学習時には見えなかった現場差分は、機械学習の案件ではむしろ起きる前提で設計しておくべきものだ。本格運用の前にこうしたカナリア期間を設け、その間に観測と補正の余地を残しておいたことが、致命傷になる前に立て直せた理由だった。

得られたもの、そして学び

半年後、推論は全ラインで安定して回るようになった。誇れる KPI を並べるより、現場で何が変わったかで語るほうが正確だと思う。ライン速度に推論が追いつくようになり、一次検出を機械に任せられるようになった。熟練の検査員は全数を目視で追う作業から解放され、機械が「判断に迷った」と印を付けた境界線上のケースの確認や、不良の原因をたどる改善活動に時間を回せるようになった。見逃しも体感でわかるほど減り、後工程からの差し戻しが目に見えて落ち着いた。

このプロジェクトを振り返って、先方のチームと私たちの双方に残った学びはいくつかある。

  • 既存システムが .NET 中心なら、推論だけを別プロセスに切り出すよりインプロセス化を先に検討する価値がある — レイテンシと保守性の両方が動く
  • Python で作った学習資産は ONNX 経由でそのまま活かせる — 言語を理由に作り直す必要はない
  • 前処理は学習と推論で同じ実装を共有する — ここのずれが本番だけの不調を生む
  • 現場とデータのギャップは起きる前提で、カナリア期間と補正の余地を最初から設計に織り込む
  • 成果は断定的な数値より、現場の役割がどう再配置されたかで捉えるほうが実態に合う

いちばん大きかったのは、機械学習で人を「置き換えた」のではなく、人と機械の役割を「組み替えた」ことだと思う。単純な全数チェックは機械に寄せ、人は判断と改善に集中する。その構図に落ち着いたとき、ようやくこの仕組みは現場に根付いた。

私たちエンハンスドは、ML.NET をはじめとした .NET エコシステム上での機械学習の設計・実装から、本番運用と社内への内製化定着まで伴走しています。「モデルは動くのに現場で使えない」段階で止まっているなら、統合と運用の設計から一緒に見直せます。お気軽にご相談ください。

この記事をシェア

コピーしました

関連記事