コラムCOLUMNS

機械学習の仕組みと使いどころがわかる実践ガイド

公開日:2026年8月10日

こんにちは。Anagraftの伊藤です。

機械学習は、いまやAI活用の中核技術としてすっかり定着しました。需要予測、不正検知、顧客のセグメント分け、設備の異常検知。生成AIに注目が集まる現在も、こうした「数字を予測する」「データを分類する」タスクの主役は、依然として本コラムで扱う機械学習です。ところが、その中身がどんな仕組みで動いていて、どんな課題に向き、どこでつまずきやすいのかを体系立てて説明できる人は、それほど多くないように思います。

機械学習の学習コンテンツは世の中に溢れていますが、その多くは「コードの書き方」か「数式の導出」のどちらかに寄っています。実務で本当に必要なのはその中間、すなわち「このアルゴリズムは何をしていて、なぜこの場面で選ぶのか」を自分の言葉で説明できるレベルの理解です。手法の名前とAPIの使い方だけを覚えても、目の前の課題にどれを当てるべきか、出てきた結果を信じてよいかまでは判断できません。本コラムは、その中間を埋めることを狙って、機械学習の主要な考え方とアルゴリズムを、仕組みの直感とビジネスでの使いどころの両面から一続きに解説する実践ガイドとしてまとめました。

本コラムは、当社のデータサイエンスシリーズの1冊です。統計学の基礎を扱った「データから正しく結論を導く統計学の実践ガイド」の続編にあたり、モデルの説明性を扱った「説明可能AI(XAI)の実践ガイド」と対をなす、考え方の体系編です。数式は本質の理解に必要な最小限にとどめ、主要な概念にはscikit-learnによるPythonコード例を添えました。

想定している読者は次のような方々です。

  • 機械学習を体系的に学び始めた実務者・DX推進担当の方:個別の手法を「地図付き」で学べます
  • AIプロジェクトを発注・評価する立場の管理職・経営層の方:ベンダーの報告(精度・検証方法)を吟味する目が身につきます
  • コードは書けるが理論に自信がない方:写経してきた fit / predict の裏側で何が起きているかを言葉にできるようになります
  • 深層学習や生成AIを学ぶ前に土台を固めたい方:過学習・評価・チューニングという共通言語はそのまま先端領域に持ち越せます

全12章は、機械学習の考え方(第1〜2章)→ 信頼できる評価の仕方(第3〜5章)→ 主要アルゴリズム(第6〜10章)→ 実務の壁と総仕上げ(第11〜12章)の順に積み上げる構成です。各章は独立して参照できるようにも作っていますので、業務で特定の手法に出会ったときに該当章だけ読む使い方も想定しています。

$authorName
著者伊藤 瑛志

Anagraft(アナグラフト)合同会社 代表 AXプロジェクト顧問・支援
データサイエンティスト since 2013 BCG/ALBERT(現アクセンチュア)出身

目次

序章 機械学習を「ブラックボックスのまま使う」ことの限界

モデルを作るハードルは下がり、選ぶ力が問われるようになった

ライブラリの成熟によって、機械学習モデルを「作る」ハードルは劇的に下がりました。scikit-learnなら数行で学習と予測が動き、AutoML(複数のアルゴリズムや設定の候補を自動的に試し、最も成績の良いモデルを選び出す仕組み)を使えばモデル選びまで自動化されます。それでも、機械学習プロジェクトの成否は相変わらず大きく分かれています。分かれ目は、モデルを作る腕前よりも、その手前と後ろにあります。すなわち、その課題を機械学習で解くべきかどうかの判断、検証方法から出た精度を信用してよいかどうかの見極め、その指標がビジネスの成果につながっているかどうかの確認です。いずれも、仕組みの理解なしには答えられません。ライブラリが肩代わりしてくれるのはモデルを学習させる計算部分だけで、こうした判断は依然として人間の側に残ります。

特に評価にまつわる誤りは気づかれにくく、本番運用に入ってから初めて表面化することが少なくありません。検証設計の不備で高く見えていた精度が本番で崩れる、という失敗は、原因を知ってさえいれば防げるものがほとんどです。本ガイドが全12章のうち3章(第3章から第5章)を「評価」に割いているのは、ここが実務で最も事故の多い箇所だと考えているためです。

生成AIの時代に古典的な機械学習を学ぶ意味

生成AIが台頭したいまも、従来型の機械学習を学ぶ価値はむしろ高まっていると当社は考えています。理由は3つあります。第一に、構造化データ(売上、センサー値、顧客属性といったテーブル形式のデータ)の予測・分類では、本ガイドで扱う勾配ブースティングなどの手法がいまも実務の第一選択であり続けています。第二に、過学習、汎化、評価指標、リークといった本ガイドの中核概念は、生成AIを業務に組み込む際の評価・検証にもそのまま必要になります。第三に、生成AIで何を解くべきかという課題の選別には、「機械学習で解ける課題の型」についての土地勘が欠かせません。古典を知る人ほど、新しい道具も正しく使えるということだと思います。

本ガイドの一貫した立場は、「アルゴリズムの知識は、正しい課題設計と正しい評価の上でのみ価値を持つ」というものです。各章の手法解説には、必ず「どんな場面で選ぶか」「どこでつまずきやすいか」を添えました。

Anagraftでは、AIプロジェクトの構想・課題設計から、データ分析・機械学習モデルの開発、AI人材の育成まで一貫したご支援を行っています。ご相談は、以下よりお問い合わせください。

お問い合わせ

全体の地図

本ガイドの構成です。第1部から順に積み上がりますが、各章は参照用に独立して読める形にもしています。

扱う内容こんな場面で効く
第1部 機械学習の考え方第1章 機械学習とは何かルールベースとの違い・3つの学習・向き不向き課題選定、AIで解くべきかの判断
第2章 学習の仕組み損失関数・勾配降下法・SGD「学習する」の中身を言葉で説明する
第2部 信頼できる評価第3章 過学習と汎化交差検証・リーク・検証設計PoCの精度報告を吟味する
第4章 バイアスとバリアンス複雑さの制御・正則化・Ridge/Lassoモデルの暴れと学習不足の診断
第5章 評価指標RMSE/MAE・混同行列・Precision/Recall・ROC-AUC「精度99%」の中身を問い直す
第3部 主要アルゴリズム第6章 ロジスティック回帰確率出力・係数の解釈・ベースライン解釈責任のある分類、最初の一手
第7章 kNNとSVM距離とマージン・カーネル少数データ、非線形な境界
第8章 決定木とアンサンブルランダムフォレスト・XGBoost/LightGBMテーブルデータの本命
第9章 次元削減PCA・t-SNE/UMAP高次元データの可視化と圧縮
第10章 クラスタリングk-means・階層型・GMM顧客セグメンテーション
第4部 実務の壁と総仕上げ第11章 不均衡データと異常検知サンプリング・class_weight・Isolation Forest不正検知・故障予知・解約予測
第12章 モデル選択とチューニングPipeline・グリッド/ランダムサーチ・AutoML再現性ある実験と本番化

第1章 機械学習とは何かをビジネスの言葉で理解する

「機械学習」という言葉は、この数年で経営会議や事業計画書にも当たり前に登場するようになりました。需要予測、与信審査、レコメンデーション、外観検査など、業種を問わず様々な意思決定の場面で機械学習という言葉が使われています。ところが、言葉が広まる速さに理解の深まりが追いついていないことも少なくありません。「機械学習」「AI」「深層学習」「生成AI」がほぼ同じ意味で使われていたり、統計分析と機械学習の違いが曖昧なまま予算の議論が進んでいたりする場面は、意外と珍しくないものです。

本章では、この長いガイドの出発点として、機械学習という技術の輪郭をビジネスの言葉で描き直します。まず機械学習とは何かをルールベースの仕組みと対比しながら定義し、隣接する分野である統計学との関係を整理します。そのうえで、教師あり学習・教師なし学習・強化学習という三つの型を、実際のビジネス課題と結びつけて見ていきます。さらに「AI・機械学習・深層学習・生成AI」という用語群の交通整理を行い、機械学習が向く課題と向かない課題の見極め方、そして予測をどう意思決定に組み込めば成果につながるのかを扱います。章の最後には、scikit-learnを使った最小限のコード例で、機械学習モデルを学習させ予測させるという一連の流れの感触をつかんでいただきます。

ルールを書く発想と、データから学習させる発想

機械学習とは、一言でいえば「判断のルールを人間が明示的に書き下すのではなく、大量のデータからそのパターンをコンピュータ自身に学習させる」アプローチです。この定義を理解するには、従来型のルールベースのプログラムと対比してみるのが早道です。

迷惑メール判定を例に考えてみます。ルールベースの発想では、人間がまず「怪しいメール」の特徴を言語化します。「無料」「今すぐクリック」といった単語が含まれていたら怪しい、送信元のドメインが特定のリストに載っていたら怪しい、といった条件を一つひとつ書き下し、それらをif文の連なりとしてプログラムに実装します。この方式は仕組みが明快で、なぜその判定になったのかを人に説明しやすいという利点があります。しかし弱点もはっきりしています。迷惑メールの送り手は、こちらが設定したルールをかいくぐるように文面を変えてきます。「無料」を「む料」と表記したり、画像に文字を埋め込んだりする工夫は、その典型です。ルールを追加しても追加しても、いたちごっこは終わりません。しかも、ルールの数が増えるほど、どのルールがどの判定に効いているのかが人間にも把握しきれなくなり、メンテナンスの負荷だけが膨らんでいきます。

機械学習の発想はここが逆転します。「迷惑メールである」「迷惑メールでない」というラベルが付いた大量の実例をモデルに与え、両者を分ける特徴の組み合わせをアルゴリズムに探させるのです。単語の出現頻度、URLの数、送信時刻、件名の記号の使い方など、人間があらかじめ全部を言語化しなくても、データの中に潜むパターンをモデルが自動的に拾い上げます。新しい手口の迷惑メールが登場しても、似た特徴を持つ実例を学習データに含めて再学習すれば追随できます。人が一つひとつルールを書き足す必要がない、という点が、機械学習が「スケールする」仕組みだと言われる理由です。

ただし、この転換にはトレードオフも伴います。ルールベースの判定は「なぜそう判定したか」を条件式としてそのまま説明できますが、機械学習モデル、特に複雑なモデルは、判断の根拠が人間の直感に馴染む形では見えにくくなりがちです。この点は本シリーズの後の章で改めて扱いますが、ここでは「機械学習はルールを人が書く手間を、データを集めて学習させる手間に置き換える技術である」という理解を押さえておいてください。データさえあればルールベースより必ず優れているという単純な話ではなく、置き換えの先に何を得て何を手放すのかを見極めることが、導入の判断そのものだと言えます。

ルールを人が書く従来型プログラムと、データからパターンを学習する機械学習の仕組みを対比した図

統計学と機械学習の違いと使い分け

機械学習についてもう一段深く理解しようとすると、必ずぶつかるのが統計学との関係です。「結局、統計学と何が違うのか」という疑問は、データ分析に関わる人であれば一度は抱くものだと思います。両者は使う数式や手法にかなりの重なりがあり、境界線を引くこと自体があまり生産的ではないというのが実情に近いのですが、それでも重視するものの違いを整理しておくと、手法を選ぶ際の判断軸が一つ増えます。

統計学が伝統的に重視してきたのは、説明力です。「なぜこの現象が起きているのか」「ある要因が結果にどれだけ、どの向きに影響しているのか」を、確率モデルの枠組みで検証し、推論することに主眼が置かれてきました。回帰分析で係数の符号と大きさを検討し、その効果が偶然のばらつきでは説明しにくいことを検定で確かめる、といった一連の手続きは、この説明重視の姿勢の表れです。少ないデータからでも、母集団についてできるだけ確からしい結論を導こうとする発想が根底にあります。

一方、機械学習が重視してきたのは、予測性能です。「なぜそうなるか」の説明よりも、「未知の入力に対してどれだけ正確に当てられるか」を優先します。手元にあるデータを学習用と検証用に分け、検証用データでの当てはまりの良さでモデルの善し悪しを判断する、という発想が中心にあります。そのためには、係数の解釈のしやすさをある程度犠牲にしてでも、複雑な非線形の関係を捉えられるモデルを採用することも辞さない、という姿勢につながります。

大切なのは、この違いを「どちらが優れているか」という優劣の話にしないことです。目的が違うだけであり、実務では目的に応じて使い分ければよい話です。与信審査や医療診断のように、判断の理由を本人や監督機関に説明できる必要がある場面では、説明力を重んじる統計学的な発想、あるいは解釈しやすいモデルが向いています。逆に、需要予測やレコメンデーションのように、多少ブラックボックスであっても当てる精度そのものが成果に直結する場面では、予測性能を優先する機械学習的な発想が力を発揮します。

加えて、統計学と機械学習は対立する二つの陣営というより、一本の連続した道具箱として捉えるほうが実態に近いと考えています。線形回帰は統計学の教科書にもscikit-learnの入門コードにも登場しますし、ロジスティック回帰は統計モデルとして生まれながら、いまでは機械学習の分類手法の定番としても使われています。正則化や交差検証といった、後の章で扱う考え方も、統計的な発想と機械学習的な発想が混ざり合って発展してきたものです。「統計学か機械学習か」ではなく、「この課題には道具箱のどの道具が合うか」という発想で選ぶことが、実務では最も生産的です。

教師あり学習・教師なし学習・強化学習という三つの型

機械学習は、学習のさせ方によって大きく三つの型に分類されます。この分類を押さえておくと、目の前のビジネス課題がどの型の問題に当たるのかを見立てやすくなります。

一つ目は教師あり学習です。入力データと、それに対応する正解ラベル(教師データ)の組をモデルに与え、入力から正解を予測する規則を学習させる方式です。正解ラベルが連続的な数値であれば回帰、カテゴリのようにあらかじめ決まった選択肢のどれかであれば分類と呼びます。来月の売上高を予測するのは回帰、あるメールが迷惑メールか否かを判定するのは分類にあたります。ビジネスの現場で使われる機械学習の多くは、この教師あり学習に分類されます。

二つ目は教師なし学習です。正解ラベルを与えず、入力データそのものが持つ構造やパターンをモデルに見つけさせる方式です。似た性質を持つデータをグループにまとめるクラスタリングや、多数の変数を情報の損失を抑えながら少数の軸に圧縮する次元削減が代表的な手法です。顧客を購買行動の傾向でいくつかの層に分けたい、多数の指標が絡み合ったデータを見通しよく可視化したい、といった場面で活躍します。

三つ目は強化学習です。エージェントと呼ばれる学習主体が、環境とのやり取りの中で行動を選び、その結果として得られる報酬を手がかりに、報酬を最大化する行動方針を試行錯誤で学習していく方式です。ゲームやロボット制御、在庫の発注量や広告の入札額を状況に応じて自動調整する仕組みなどに使われています。教師あり学習や教師なし学習に比べると、明確な正解データを事前に用意しにくい課題に向いている一方、実環境での試行錯誤に伴うコストやリスクをどう扱うかが実務上の論点になります。

三つの型とビジネス課題の対応関係を整理すると、次のようになります。

学習の型代表的な手法ビジネス課題の例
教師あり学習(回帰)線形回帰、決定木回帰、勾配ブースティング需要予測、売上予測、価格の適正水準の推定
教師あり学習(分類)ロジスティック回帰、SVM、ランダムフォレスト迷惑メール判定、離反予測、与信スコアリング、不良品判定
教師なし学習(クラスタリング)k-means、階層クラスタリング顧客セグメンテーション、商品のグルーピング
教師なし学習(次元削減)主成分分析(PCA)、t-SNE多変量データの可視化、特徴量の集約
強化学習Q学習、方策勾配法在庫発注量の動的最適化、広告入札の自動調整

実務でまず取り組むことが多いのは教師あり学習です。理由は単純で、多くの企業に「過去の結果が記録されたデータ」が既に蓄積されているからです。過去の売上実績、離反した顧客の履歴、検査結果といった記録は、教師あり学習における正解ラベルとしてそのまま使えることが多く、教師なし学習や強化学習に比べて着手のハードルが低くなります。本シリーズも、以降の章はまず教師あり学習を中心に扱い、教師なし学習は第9章と第10章で改めて掘り下げます。

AI・機械学習・深層学習・生成AIの関係を整理する

ここで一度、「AI」「機械学習」「深層学習」「生成AI」という四つの言葉の関係を整理しておきます。ニュースや資料でこれらの言葉がほぼ同じ意味で使われている場面をよく見かけますが、範囲の広さが異なる、入れ子になった概念だと理解しておくと混乱が減ります。

最も広い概念はAI、人工知能です。人間が知的に行うとされる作業をコンピュータに行わせようとする研究分野全体を指します。ルールベースのエキスパートシステムのように、データからの学習を伴わない古典的な手法もAIに含まれます。機械学習は、このAIを実現するための一つのアプローチであり、前節までに見てきたとおり「データからパターンを学習する」という方法でAIの機能を実現しようとするものです。つまり機械学習は、AIという大きな集合の中に含まれる一つの手段だという位置づけになります。

さらにその内側に、深層学習(ディープラーニング)があります。深層学習は機械学習の一手法であり、人間の神経回路網を模したニューラルネットワークを何層にも重ねた構造を使って学習を行います。画像認識や音声認識、自然言語処理の分野で近年大きな成果を上げてきた技術であり、後述する生成AIの多くも、この深層学習の技術を土台にしています。そして生成AIは、深層学習をベースにした技術のうち、文章や画像、音声といった新しいコンテンツを生成することに主眼を置いた応用領域を指します。整理すると、AIという最も広い枠の中に機械学習があり、その中に深層学習があり、さらにその応用として生成AIがある、という入れ子の関係です。

本シリーズでは、この第1巻を通じて、主に表形式のデータ(顧客データや取引データのような、行と列で整理できるデータ)を対象にした古典的な機械学習の考え方と手法を扱います。深層学習と生成AIについては、扱う課題の性質や実装の作法が大きく異なるため、別冊で改めて体系立てて解説する予定です。「機械学習を学べば深層学習や生成AIも自動的に理解できる」というものではありませんが、本ガイドで扱う損失関数、過学習、モデル評価といった基礎概念は、深層学習や生成AIを理解するうえでも土台になります。まずはこの土台を固めることが、遠回りに見えて実は近道になると考えています。

AI・機械学習・深層学習・生成AIは、優劣の話ではなく包含関係の話です。ニュースで見かける個々の技術がこの四つの言葉のどこに位置するのかを意識するだけでも、自社の課題にどの技術が対応しているのかを見誤るリスクはかなり減らせます。

機械学習が向く課題、向かない課題

機械学習は万能の解決策ではありません。導入を検討する際にまず確認しておきたいのが、そもそも自社の課題が機械学習に向いているかどうかという見極めです。向き不向きを判断する観点は、主に次の四つに整理できます。

  • データ量:学習に使える実例がある程度の件数そろっているか。極端に少ない件数からでは、安定したパターンを学習することが難しくなります
  • パターンの安定性:過去のデータに存在したパターンが、将来もある程度成り立ち続けると期待できるか。市場環境やルールが頻繁に変わる領域では、モデルの前提が崩れやすくなります
  • 説明責任の要否:判断の理由を本人や監督機関に説明する必要が強い領域かどうか。説明責任が厳格な場合は、モデルの種類や運用方法に制約がかかります
  • ルールで書けるかどうか:そもそも人間が数個の条件分岐で正確に表現できる単純な問題であれば、機械学習を持ち出す必要はありません

この四つの観点のうち、意外と見落とされがちなのが最後の「ルールで書けるならルールでよい」という視点です。機械学習は魅力的な技術ですが、学習用データの整備、モデルの検証、運用開始後の監視といった相応のコストがかかります。数行の条件式で十分に正確な判定ができる問題にまで機械学習を持ち込むのは、多くの場合、費用対効果に見合いません。たとえば「注文金額が一定額を超えたら承認者を上位の役職者に切り替える」といった業務ルールは、そもそも人間が明文化できる単純な条件であり、機械学習で学習させる対象にはなりません。

逆に機械学習が力を発揮しやすいのは、条件が複雑に絡み合っていて人間が全部を言語化しきれない課題です。数十種類の変数が絡み合う与信判断、季節性や天候、近隣のイベントが複合的に効いてくる需要予測、画像の中の微細な模様の違いを見分ける外観検査などがその典型です。人間が「なんとなく分かるが、言葉で全部説明するのは難しい」と感じる判断ほど、機械学習が既存のやり方を補強する余地は大きいと言えます。

データ量とパターンの安定性についても補足しておきます。目安としてよく語られる「十分なデータ量」は課題によって大きく変わり、一概に何件あればよいという単純な基準はありません。ただし、正解ラベルの種類が少なく偏りも小さい単純な問題であれば数百件規模でも実用的なモデルが作れることがありますし、逆に微妙な違いを見分ける必要がある複雑な問題では、数万件規模でも足りないことがあります。パターンの安定性についても、たとえば法制度の変更や競合の出現によって顧客行動が急に変わる局面では、過去データで学習したモデルの前提が崩れ、定期的な再学習や監視の仕組みが欠かせなくなります。この点は第3章の過学習と汎化、第12章のモデル選択とチューニングで改めて扱います。

予測を成果に変える:意思決定への組み込み方

機械学習の導入を検討する際、精度の高いモデルを作ることそのものが目的化してしまう場面をよく見かけます。しかし、予測モデルは単独では成果を生みません。予測結果が、実際の業務プロセスや人の判断とどう接続され、どんなアクションに変換されるかまで設計して初めて、投資に見合う成果につながります。

需要予測を例に考えてみます。あるモデルが「来週、この商品は120個売れる」と予測したとします。この数字自体は、まだ何も生み出していません。その予測が発注担当者の画面に表示され、発注担当者が発注量を判断する材料として使われ、実際に120個前後の在庫が確保されて初めて、予測は欠品や過剰在庫の削減という成果に変わります。予測から発注という意思決定までの経路が業務プロセスとして整備されていなければ、どれほど精度の高いモデルを作っても、担当者の手元では従来どおり経験と勘による発注が続き、モデルの出力は参照されないまま埋もれてしまいます。

離反予測も同様です。モデルが「この顧客は来月解約する確率が高い」と予測しても、それだけでは顧客は引き留められません。予測スコアの高い顧客に対して、営業担当者が優先的に連絡を取る、割引や機能追加といった引き留め施策を提示する、といった具体的なアクションと結びついて初めて、離反予測は解約率の低下という成果に変わります。しかも、そのアクションには実行するための体制やコストが伴います。誰が、どのタイミングで、どんな施策を、どの顧客に対して行うのかという運用設計がなければ、予測スコアはダッシュボード上の数字で終わってしまいます。

この「予測から成果までの距離」を意識せずに機械学習プロジェクトを進めると、モデルの精度は検証段階では高く出るのに、導入後にビジネス上の数字がまったく動かない、という結果に陥りがちです。プロジェクトの企画段階から、予測結果を受け取る担当者は誰か、その担当者がどんな行動を取れるようになっていればよいか、行動を取るための権限や仕組みが業務側に既にあるのかを、モデル開発と並行して詰めておく必要があります。

機械学習プロジェクトの成否は、モデルの精度だけでなく「予測結果がどの業務プロセスに、どんなアクションとして組み込まれるか」で決まります。予測を作ることと、予測を成果に変えることは、別の設計が必要な、別の仕事だと捉えておくことが大切です。

需要予測のスコアが発注業務に組み込まれ、在庫の最適化という成果につながる一連の流れを示すフロー図

Pythonで学習と予測の最小の流れを見る

ここまでは概念の整理が中心でしたが、最後に機械学習ライブラリscikit-learnを使い、モデルを学習させて予測させるという一連の流れをごく小さなコードで確認しておきます。scikit-learnの多くのモデルは、共通してfitpredictという二つのメソッドを持つように設計されています。fitはデータからモデルのパラメータを推定する学習の工程、predictは学習済みのモデルに新しい入力を与えて出力を得る予測の工程にあたります。このfitpredictという共通の型を押さえておくと、後の章で扱う様々なアルゴリズムも、同じ操作の流れで扱えることが見えてきます。

まず、回帰の例です。店舗の売り場面積(平方メートル)から月間売上高(万円)を予測する、最小限の線形回帰のコードです。

from sklearn.linear_model import LinearRegression
import numpy as np

# 説明変数:売り場面積(平方メートル)、目的変数:月間売上高(万円)
X = np.array([[30], [45], [60], [80], [100]])
y = np.array([120, 180, 230, 310, 400])

model = LinearRegression()
model.fit(X, y)  # 学習:データから回帰係数を推定する

new_store = np.array([[70]])
prediction = model.predict(new_store)  # 予測:学習済みモデルで新しい入力に答える
print(prediction)  # 例:[275.]に近い値が出力される

model.fit(X, y)の一行で、モデルは面積\(X\)と売上高\(y\)の関係から、直線の傾きと切片にあたるパラメータを推定します。学習が終わったモデルに対して、まだ見ぬ売り場面積70平方メートルのデータをmodel.predict(new_store)として渡すと、学習済みのパラメータに基づいた売上高の予測値が返ってきます。このfitで学び、predictで答えるという二段構えの流れが、scikit-learnのほぼ全てのモデルに共通する基本の型です。

次に、分類の例です。冒頭で取り上げた迷惑メール判定を、ごく簡略化した特徴量で再現してみます。

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
import numpy as np

# 特徴量:[広告的な単語の出現回数, メール内のURLの数]
# ラベル:1が迷惑メール、0が通常のメール
X = np.array([[5, 3], [0, 0], [6, 4], [1, 0], [4, 2], [0, 1], [7, 5], [1, 1]])
y = np.array([1, 0, 1, 0, 1, 0, 1, 0])

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=0
)

clf = LogisticRegression()
clf.fit(X_train, y_train)  # 学習:ラベル付きの実例から判定境界を推定する

y_pred = clf.predict(X_test)  # 予測:未知のメールを迷惑/通常に分類する
print(y_pred)

ここではtrain_test_splitを使い、手元のデータをあらかじめ学習用と検証用に分けています。モデルの学習には学習用データだけを使い、検証用データは学習に使わずに取っておいて、予測の答え合わせに使います。この「学習に使ったデータでの当てはまりの良さ」と「学習に使っていないデータへの当てはまりの良さ」を区別する発想は、機械学習の実務で最も重要な作法の一つです。学習データだけをいくら当てても、それは新しい入力への性能を保証しません。この論点は第3章の過学習と汎化で詳しく扱いますので、ここではfitで学びpredictで答えるという型と、学習用・検証用の分割という二つの感触を持ち帰っていただければ十分です。

回帰の例と分類の例は、どちらも数行のコードで完結しています。実際のビジネス課題では、特徴量の設計やデータの前処理、モデルの評価に多くの手間がかかりますが、モデルそのものを学習させ予測させる核の部分は、このようにシンプルな共通の型に収まっています。この型のシンプルさこそが、機械学習という技術が様々な業種・業務に横展開しやすい理由の一つだと言えます。

この章を深めたい方への参考書籍

  • 大城信晃ほか『AI・データ分析プロジェクトのすべて』(技術評論社):機械学習を技術単体としてではなく、企画から運用までのプロジェクトとしてどう進めるかを俯瞰できる一冊です。本章で扱った「予測を成果に変える」という論点を、より実務的な粒度で深掘りしたい方に向いています。
  • 秋庭伸也・杉山阿聖・寺田学『見て試してわかる機械学習アルゴリズムの仕組み 機械学習図鑑』(翔泳社):本章で紹介した回帰・分類・クラスタリングなど、代表的な機械学習アルゴリズムの考え方を図解と簡単なコードで一つずつ確認できます。次章以降で個別の手法に入る前の見取り図として役立ちます。

第2章 機械学習はどうやって学ぶのか、損失関数と最適化の仕組み

前章では、機械学習を「データからパターンを見つけ出し、未知の入力に対して予測や判断を行う技術」として位置づけ、あらかじめ人間がルールを書き込むプログラムとの違いを整理しました。本章では視点を一段深め、機械学習モデルが実際にどうやって「学ぶ」のか、その内部の仕組みに踏み込みます。取り上げるのは、予測の外れ具合を1つの数字に変換する損失関数と、その数字を小さくする方向へパラメータを動かしていく最適化の手続きです。この2つの組み合わせが、線形回帰のようなシンプルなモデルから深層学習モデルまで、あらゆる機械学習の学習プロセスに共通する骨格になります。

この章では次の内容を順に扱います。

  • 「学習」の正体:モデル=数式+調整可能なパラメータという見方
  • 損失関数:平均二乗誤差(MSE)を例に、予測のズレを1つの数字にする方法
  • 最急降下法(勾配降下法):損失を減らす方向へパラメータを動かす反復的な最適化
  • 解析的に解ける場合:正規方程式という近道と、反復法との使い分け
  • 確率的勾配降下法(SGD)とミニバッチ:大規模データに対応する現実的な学習法
  • 損失関数の設計:損失関数はビジネス上の「痛みの定義」であるという視点

「学習」の正体:モデルとは数式とパラメータの組み合わせである

「機械学習モデルが学習する」という表現は知的な営みのように聞こえますが、実体はもっと機械的です。モデルの正体は、入力を受け取って出力を返す1本の数式であり、その数式にはいくつかの調整可能な数値、すなわちパラメータが含まれています。学習とは、このパラメータを手元のデータに最もよく当てはまる値へ調整していく作業に過ぎません。

最もシンプルな例として線形回帰を考えます。広告費と売上の関係を予測したいとき、モデルは \( \hat{y} = wx + b \) という1本の式で表せます。\( x \) は入力(広告費)、\( \hat{y} \) は予測値(予測売上)、\( w \) は「広告費が1単位増えたときに売上がどれだけ増えるか」を表す傾き、\( b \) は「広告費ゼロのときの売上」を表す切片です。この \( w \) と \( b \) こそがモデルのパラメータであり、学習開始前は適当な初期値(例えばどちらもゼロ)が入っているに過ぎません。学習とは、過去の広告費と売上の実績データを使って、この \( w \) と \( b \) をより良い値に更新していくプロセスです。

この見方は線形回帰に限りません。ロジスティック回帰であれば線形の式にシグモイド関数を組み合わせた数式が、決定木であれば分岐条件の集合が、ニューラルネットワークであれば無数の重みとバイアスを持つ層を重ねた数式が、それぞれ「モデル」の実体です。パラメータの数が2個でも数十億個でも構造は変わりません。「モデル=数式+パラメータ」という枠組みを用意し、「このパラメータがどれくらい良いか」を測る物差しを決め、その物差しが良くなる方向へパラメータを調整していく、という3ステップの繰り返しが「学習」の正体であり、次節以降ではこの物差し(損失関数)とパラメータの調整手続き(最適化)を順に見ていきます。

損失関数:予測の外れ具合を1つの数字にする

パラメータの良し悪しを判断するには、まず「良し悪し」を数値化しなければなりません。この役割を担うのが損失関数です。損失関数とは、モデルの予測値と実際の正解値がどれだけズレているかを、1つの数字(スカラー値)にまとめる関数のことです。損失が小さいほど予測がよく当たり、大きいほど予測が外れていることを表します。学習とは、突き詰めればこの損失関数の値をできるだけ小さくするパラメータを探す作業だと言い換えられます。

回帰問題(数値を予測する問題)で最も広く使われる損失関数が平均二乗誤差(MSE: Mean Squared Error)です。データが \( n \) 件あり、\( i \) 番目のデータの正解値を \( y_i \)、モデルの予測値を \( \hat{y}_i \) とすると、MSEは次のように定義されます。

\( L(w, b) = \frac{1}{n}\sum_{i=1}^{n} (y_i – \hat{y}_i)^2 \)

この式は、それぞれのデータについて「正解と予測の差(誤差、あるいは残差)」を求め、2乗してから平均を取ったものです。2乗する理由は主に3つあります。1つ目は符号の打ち消し合いを防ぐためです。プラスの誤差とマイナスの誤差をそのまま平均すると、正負が相殺されてモデルの悪さを正しく反映できません。2乗すればすべての誤差が正の値になり、この相殺を防げます。2つ目は数学的な扱いやすさです。2乗した関数はどの点でも滑らかに微分可能ですが、絶対値は原点(誤差ゼロの点)で微分できず、後述する勾配降下法との相性が悪くなります。3つ目は誤差の大きさへの反応です。誤差が2倍になれば影響は4倍になるため、大きく外れた予測をより強く罰する形になり、これは平均から離れた値を重く扱う分散の考え方と同じ発想です。

実際にPythonでMSEを最小化する様子を確認してみます。以下は、真の関係が \( y = 2x + 1 \) にノイズが乗ったデータを想定し、パラメータ \( w, b \) をゼロから出発させて、損失(MSE)がどう減っていくかを追ったコードです。

import numpy as np

np.random.seed(0)
n = 100
x = np.linspace(0, 10, n)
y = 2.0 * x + 1.0 + np.random.normal(scale=1.5, size=n)  # 真の関係: y = 2x + 1(ノイズ入り)

def mse(w, b):
    y_pred = w * x + b
    return np.mean((y - y_pred) ** 2)

w, b = 0.0, 0.0          # パラメータの初期値
lr = 0.01                 # 学習率
n_iter = 1000

for i in range(n_iter):
    y_pred = w * x + b
    error = y_pred - y
    grad_w = 2 * np.mean(error * x)   # 損失をwで偏微分した勾配
    grad_b = 2 * np.mean(error)       # 損失をbで偏微分した勾配
    w -= lr * grad_w
    b -= lr * grad_b
    if i % 200 == 0:
        print(f"{i}回目: 損失={mse(w, b):.3f}, w={w:.3f}, b={b:.3f}")

print(f"勾配降下法の結果: w={w:.3f}, b={b:.3f}")

# 正規方程式による解析解(検算用)
X = np.column_stack([np.ones(n), x])
b_ne, w_ne = np.linalg.inv(X.T @ X) @ X.T @ y
print(f"正規方程式の結果: w={w_ne:.3f}, b={b_ne:.3f}")

実行すると、0回目の損失は17.915(w=1.441、b=0.222)から始まり、200回目には2.303、400回目には2.273、600回目には2.269と急速に下がり、800回目以降はほぼ横ばいになります。1000回の反復を終えた時点でw=1.956、b=1.306となり、真の関係(w=2、b=1)にかなり近い値まで学習できています。最初の数百回で急激に下がり、その後は緩やかに横ばいになっていくこの形は、勾配降下法でよく見られる典型的な収束パターンです。

最急降下法:坂を下るようにパラメータを動かす

先ほどのコードの中心にあるのが、最急降下法(勾配降下法)と呼ばれる最適化の手続きです。パラメータ \( w \) と \( b \) を横軸・縦軸に取り、それぞれの組み合わせに対する損失の値を高さとして描くと、線形回帰とMSEの組み合わせでは、谷底に最小値を持つお椀型の地形ができあがります。勾配降下法は、この地形の中で「今立っている場所から最も急な下り坂の方向へ少しだけ歩を進める」という操作を繰り返し、谷底(損失が最小になるパラメータ)を目指す方法です。

「最も急な下り坂の方向」を数学的に表したものが勾配(損失関数の各パラメータに関する偏微分)であり、その地点でパラメータを少し増やしたときに損失がどちらへどれだけ変化するかを示します。勾配の符号と逆の方向にパラメータを動かせば、十分小さい一歩である限り損失は必ず減少します。この考え方を式にしたものが、先のコードにも登場した更新式です。

\( w \leftarrow w – \eta \dfrac{\partial L}{\partial w} \)

ここで \( \eta \)(イータ)は学習率と呼ばれる数値で、一歩の歩幅を決めます。学習率の設定は勾配降下法の実務における最大の勘所です。大きすぎると、谷底を通り越して反対側の斜面まで飛び越え、行ったり来たりを繰り返しながら損失がむしろ増大していく発散という現象が起こります。逆に小さすぎると、一歩一歩が極めて小さくなり、谷底にたどり着くまでに非常に多くの反復回数を要し、限られた計算時間の中では収束前に学習を打ち切らざるを得なくなることもあります。

先ほどと同じデータで、学習率だけを変えて1000回反復した結果を比べると、この違いがはっきり表れます。

学習率 1000回反復後の状態 挙動
0.001 w=2.047、b=0.706(損失2.36) 着実に近づくが、1000回では収束しきらず途上
0.01 w=1.956、b=1.306(損失2.27) 十分に収束し、正規方程式による解析解とほぼ一致
0.05 発散(値が無限大に発散) 一歩が大きすぎて谷底を飛び越え、振動しながら悪化
0.1 発散(値が無限大に発散) 同上。より早い段階で発散

学習率0.001では1000回の反復を経てもまだ真の値(w=2、b=1)に届かず、0.01ではほぼ収束し、0.05以上ではあっという間に発散してしまいます。実務で学習率を決める際には、いきなり本番のデータ量で試すのではなく、小さいデータで複数の学習率を試し、損失が滑らかに減っていく値を探ってから本番に臨むのが安全です。学習が進むにつれて学習率を徐々に小さくしていく「学習率のスケジューリング」という工夫も広く使われています。

お椀型の損失関数の地形を勾配降下法が谷底へ向かって下っていく様子と、学習率が大きすぎて谷底を飛び越えてしまう様子を対比したイメージ

もう1つ触れておきたいのが局所解の問題です。線形回帰とMSEの組み合わせでは、損失関数の地形が単一の谷を持つお椀型(凸関数)になるため、どこから出発しても適切な学習率であれば必ず唯一の最小値(大域的最適解)にたどり着けます。しかし、ニューラルネットワークのように非線形な要素を多く含むモデルでは、損失関数の地形はでこぼこした複雑な形になり、複数の谷(局所的な最小値)や、下り坂に見えて実際には平坦な鞍点が存在します。この場合、パラメータの初期値や学習の進め方によって、たどり着く谷が変わってしまうことがあり、対処法は深層学習を扱う章で改めて取り上げます。

ここまでの内容をまとめると、機械学習の学習とは「モデル(数式+パラメータ)を用意し、損失関数で予測の外れ具合を1つの数字に変換し、その数字が小さくなる方向へ勾配を頼りにパラメータを少しずつ更新していく」という反復プロセスです。この基本の型は、線形回帰から深層学習まで共通しています。

解析的に解ける場合:正規方程式という近道

線形回帰とMSEの組み合わせには、勾配降下法のような反復計算に頼らずとも、1回の計算でぴったりの答えを求められる近道があります。これが正規方程式(Normal Equation)です。

MSEはパラメータについての2次関数(お椀型の凸関数)であるため、微分をゼロと置いて解けば、谷底の位置、つまり損失を最小にするパラメータの値を直接求められます。具体的には、切片用に1で埋めた列を加えた入力行列を \( X \)、正解値をまとめたベクトルを \( y \) とすると、最適なパラメータのベクトル \( \boldsymbol{\beta} \)(切片と傾きをまとめたもの)は次の式で求まります。

\( \boldsymbol{\beta} = (X^{\top}X)^{-1} X^{\top} y \)

先のコードの末尾で計算した正規方程式の結果(w=1.955、b=1.313)が、1000回反復した勾配降下法の結果(w=1.956、b=1.306)とほぼ一致していたのは偶然ではなく、どちらも同じ谷底を別のアプローチで探し当てているためです。「損失関数を各パラメータで偏微分してゼロと置き、得られた連立方程式を行列の形で解いたものが正規方程式である」という骨格だけ押さえておけば十分です。

それでは、なぜ実務では正規方程式ではなく勾配降下法が使われる場面が多いのでしょうか。正規方程式は \( (X^{\top}X)^{-1} \) という逆行列を計算する必要があり、この計算コストは特徴量の数(列数)の3乗におおよそ比例して増大します。特徴量が数十〜数百程度なら一瞬で計算できますが、数千・数万を超える高次元データでは非現実的な時間とメモリを要します。また、正規方程式が使えるのは損失関数がMSEでモデルが線形の場合に限られ、ロジスティック回帰や決定木、ニューラルネットワークのように組み合わせが複雑になると、そもそも正規方程式に相当するものが存在せず、勾配降下法のような反復的な最適化に頼るほかありません。

観点 正規方程式(解析的手法) 勾配降下法(反復的手法)
適用範囲 線形回帰+MSEなど、限られた組み合わせのみ ほぼ任意のモデル・損失関数の組み合わせに適用可能
特徴量の数 少数〜中程度なら高速。多いと逆行列計算が重くなる 特徴量が多くても比較的スケールしやすい
データ量 全データを一度にメモリへ載せる必要がある ミニバッチ化により大規模データにも対応しやすい
結果 1回の計算で厳密な最適解が求まる 学習率や反復回数次第で近似的な解にとどまる

実務上の目安としては、特徴量が少なく線形回帰に限られる場面では正規方程式(あるいはそれを内部で使うscikit-learnのLinearRegression)を使い、特徴量が多い場合や非線形モデルでは勾配降下法系の手法を選ぶ、と整理しておくと判断しやすくなります。

大規模データでの現実解:確率的勾配降下法(SGD)とミニバッチ

先の勾配降下法のコードでは、1回の更新のたびに全100件のデータを使って勾配を計算していました。このように毎回すべての学習データを使って勾配を求める方式をバッチ勾配降下法と呼びます。データが100件、1000件程度であれば問題ありませんが、実務で扱うデータは数百万件、数千万件に及ぶことも珍しくありません。この場合、1回の更新のたびに全データへ目を通す必要があるバッチ勾配降下法は計算コストが高く、現実的な時間で学習を終えられなくなってしまいます。

この問題への現実的な解決策が、確率的勾配降下法(SGD: Stochastic Gradient Descent)です。SGDでは、全データではなくランダムに選んだ1件(あるいは数十〜数百件程度の小さな塊、ミニバッチ)だけを使って勾配を近似的に計算し、その近似値でパラメータを更新します。少数のデータから計算した勾配はノイズを含みますが、その分1回あたりの計算ははるかに軽く、同じ時間でより多く更新できるため、多くの場合バッチ勾配降下法より学習が速く進みます。実務では1件ずつ更新する純粋なSGDよりも、数十〜数百件をひとまとまりとして扱うミニバッチ勾配降下法が一般的で、GPUでの並列計算とも相性が良い方式です。

このミニバッチ勾配降下法は、線形回帰のような比較的シンプルなモデルだけでなく、画像認識や自然言語処理で使われる深層学習モデルの学習でも、そのまま最適化の土台になっています。深層学習の文脈でよく耳にするAdamやRMSpropといった最適化手法も、根本にあるのはこのミニバッチSGDの考え方に、勾配の過去の情報を活用するなどの工夫を加えたものです。

scikit-learnには、SGDを使って回帰モデルを学習するSGDRegressorというクラスが用意されています。以下は、5000件のデータに対してSGDRegressorを適用する例です。SGDは各特徴量のスケール(値の大きさ)の影響を受けやすいため、学習の前に標準化(平均0、標準偏差1にそろえる処理)を行うのが一般的な作法です。

import numpy as np
from sklearn.linear_model import SGDRegressor
from sklearn.preprocessing import StandardScaler

np.random.seed(0)
n = 5000
x = np.random.uniform(0, 10, size=(n, 1))
y = 2.0 * x[:, 0] + 1.0 + np.random.normal(scale=1.5, size=n)

scaler = StandardScaler()
x_scaled = scaler.fit_transform(x)  # SGDは特徴量のスケールに敏感なため標準化する

model = SGDRegressor(loss="squared_error", penalty=None, max_iter=1000, tol=1e-5, random_state=0)
model.fit(x_scaled, y)

print(f"収束までの反復回数: {model.n_iter_}")
print(f"決定係数(R^2): {model.score(x_scaled, y):.3f}")
print(f"標準化後の空間での係数: w={model.coef_[0]:.3f}, b={model.intercept_[0]:.3f}")

実行すると、わずか13回のデータ全体の周回(エポック)で学習が収束し、決定係数(R\(^2\))は0.939と、モデルがデータの分散の大部分を説明できています(標準化後の空間での係数w=5.781、b=10.910は、スケールが変わった後の値であるため元の特徴量・目的変数の単位ではそのまま解釈できません。元の単位に戻すには、標準化に使った平均・標準偏差で逆変換する必要があります)。5000件規模のデータでもSGDRegressorはごく短時間で実用的な精度に到達しており、データ件数が数百万件規模に増えても、ミニバッチ処理によって同様のアプローチが通用します。

バッチ勾配降下法とミニバッチSGDで、パラメータが谷底に向かう経路を比較したイメージ(SGDの経路はノイズを含みジグザグに、バッチは滑らかに描く)

損失関数を取り替えると別のモデルになる:損失関数はビジネス上の「痛みの定義」

ここまではMSEを前提に話を進めてきましたが、損失関数の選び方はMSEだけに限られません。損失関数を取り替えると、同じ線形回帰という数式でも、学習の結果得られるパラメータはまったく違うものになります。損失関数の選択は、単なる数学的な好みの問題ではなく、モデルにどのような性質を持たせたいかという設計判断そのものです。

代表的な代替案が平均絶対誤差(MAE: Mean Absolute Error)です。MAEは誤差を2乗する代わりに絶対値を取り、平均します。

\( L(w, b) = \frac{1}{n}\sum_{i=1}^{n} |y_i – \hat{y}_i| \)

MSEは誤差を2乗するため、極端に大きな誤差(外れ値)が1つ混じっているだけで、その1点の影響が不釣り合いに大きくなり、パラメータ全体が外れ値に引っ張られてしまいます。一方、MAEは誤差を絶対値のまま扱うため2乗ほど強調されず、外れ値に対してより頑健(ロバスト)な性質を持ちます。この違いを、意図的に1件だけ極端な値を混入させたデータで確認してみます。

import numpy as np
from scipy.optimize import minimize

np.random.seed(1)
n = 30
x = np.linspace(0, 10, n)
y = 2.0 * x + 1.0 + np.random.normal(scale=1.0, size=n)
y[5] += 40  # 入力ミスなどで紛れ込んだ極端な外れ値を1点だけ想定

def mse_loss(params):
    w, b = params
    return np.mean((y - (w * x + b)) ** 2)

def mae_loss(params):
    w, b = params
    return np.mean(np.abs(y - (w * x + b)))

w_mse, b_mse = minimize(mse_loss, x0=[0, 0]).x
w_mae, b_mae = minimize(mae_loss, x0=[0, 0], method="Nelder-Mead").x

print(f"MSEで最適化: w={w_mse:.3f}, b={b_mse:.3f}")
print(f"MAEで最適化: w={w_mae:.3f}, b={b_mae:.3f}")

真の関係はw=2、b=1です。実行すると、MSEで最適化した結果はw=1.526、b=4.644と、たった1件の外れ値によって傾きが小さく切片が大きく歪んだ値になります。一方、MAEで最適化した結果はw=2.032、b=0.652と、外れ値が1件混ざっていても真の関係にかなり近い値を保っています。入力ミスやセンサーの異常値など、まれに極端な値が混入する業務データでは、MSEよりMAEを使う、あるいは両者を組み合わせたHuber損失(誤差が小さい範囲ではMSEのように、大きい範囲ではMAEのように振る舞う損失関数)を検討する価値があります。

この「損失関数を取り替えると別のモデルの性質が手に入る」という発想を一歩進めると、損失関数はビジネス上の「痛みの定義」そのものであるという視点にたどり着きます。MSEもMAEも、予測が正解より大きすぎる場合(過大予測)と小さすぎる場合(過小予測)を対称に(同じ重みで)扱う損失関数ですが、現実のビジネスの痛みは対称であるとは限りません。典型例が需要予測です。過大予測は余剰在庫や値下げ処分といった保管・廃棄コストにつながり、過小予測は欠品による機会損失や、顧客が競合に流れることによる信頼の毀損につながります。多くの業種では、この2種類の痛みの大きさは同じではなく、欠品の方が過剰在庫よりも重く見積もられる(あるいはその逆)というのが実情です。

このような非対称な痛みを表現したいとき、MSEやMAEのような対称な損失関数をそのまま使うと、実際のビジネス上のコスト構造とズレたパラメータが選ばれてしまいます。そこで、過大予測と過小予測に異なる重みを掛けた非対称な損失関数(特定のパーセンタイルを狙うクォンタイル損失や、独自に重み付けした誤差関数など)を設計し、最小化する形でモデルを学習させるアプローチが取られます。損失関数の定義という一見数学的な選択の背後には、「自社にとってどちらの間違え方がより痛いのか」という経営判断が隠れており、モデル構築に着手する前にこの非対称性を業務担当者と擦り合わせておく価値があります。

過剰在庫のコストと欠品のコストが非対称であることを示す、過大予測・過小予測それぞれのペナルティの大きさを表したグラフ

損失関数の選択は数学的な細部ではなく、モデルに何を優先させるかという設計判断です。MSEは外れ値に敏感で大きな誤差を強く罰し、MAEは外れ値に頑健で誤差を均等に扱います。さらに、過大予測と過小予測のどちらがより「痛い」かが業務上非対称であるなら、その非対称性を反映した損失関数を設計することも検討に値します。

本章では、モデル(数式+パラメータ)、損失関数、そして勾配降下法による最適化という、機械学習の学習プロセスを支える骨格を見てきました。ここで押さえた「損失を減らす方向へパラメータを調整する」という枠組みは、これから扱うほぼすべてのモデルに共通する土台になります。ただし、損失関数の値を学習データに対してどこまでも小さくできれば良いモデルになるとは限りません。学習データへの損失をひたすら追い求めた結果、未知のデータに対してはかえって性能が落ちてしまう現象が起こり得るためです。次章では、この過学習と汎化というテーマを扱います。

この章を深めたい方への参考書籍

『機械学習のエッセンス』(加藤公一、SBクリエイティブ):機械学習の理論をPythonの実装コードとあわせて基礎から積み上げていく構成の書籍で、本章で扱った勾配降下法や最小二乗法についても、数式の導出とコードの両面から丁寧に解説されています。損失関数や最適化の仕組みを、numpyレベルの実装まで踏み込んで理解し直したい読者に向いた1冊です。

第3章 過学習と汎化、機械学習で最も大切な考え方

前章では、機械学習モデルが訓練データに対する損失関数を小さくしていく過程、すなわち学習の仕組みを見てきました。損失関数さえ十分に小さくできれば良いモデルができあがる、というのは半分正しく、半分は誤解を含んだ理解です。損失を極限まで小さくすることに固執した結果、かえって使い物にならないモデルができあがってしまうことがあるからです。本章で扱う過学習(overfitting)と汎化(generalization)は、この落とし穴を理解するための、機械学習全体を通じて最も重要な考え方です。精度の数字そのものよりもまず疑うべきなのが、その数字がどのような検証設計のもとで得られたのかという点であり、本章の内容はその判断力の土台になります。

過学習とは何か、テスト勉強の比喩で捉える

過学習とは、モデルが訓練データに含まれる規則性だけでなく、そのデータ特有のノイズや偶然の偏りまで細部にわたって覚え込んでしまい、初めて見るデータへの対応力を失った状態を指します。この現象は、テスト勉強の場面に置き換えるとイメージがつかみやすくなります。

ある学生が試験対策として、過去に出題された問題とその模範解答をひたすら暗記したとします。この学生は、過去問とまったく同じ問題が出題されれば満点を取れるでしょう。しかし、数値や設定が少し変えられただけの応用問題が出題されると、途端に手が止まってしまいます。暗記していたのは「解き方」ではなく「特定の問題の答え」だったからです。これに対して、公式や考え方の背景を理解したうえで過去問に取り組んだ学生は、初めて見る問題に対しても、その根底にある考え方を応用して対応できます。

機械学習モデルにおける過学習も同じ構造です。訓練データに対する誤差(訓練誤差)が極めて小さいにもかかわらず、モデルが一度も見たことのないデータ(テストデータ)に対する誤差が大きい状態が、過学習の定義です。モデルが「答えを暗記」してしまい、データの背後にある本質的な規則性ではなく、そのデータセット固有の偶然の癖まで学習してしまった結果として起こります。決定木で言えば、枝分かれを際限なく増やして訓練データの1件1件を完璧に分類できるまで深くしてしまった状態、ニューラルネットワークで言えば、パラメータ数に対して訓練データが少なすぎるまま学習を続けてしまった状態が典型例です。

過学習の対極にあるのが学習不足(underfitting)です。これはテスト勉強で言えば、教科書をほとんど開かずに試験に臨むようなもので、モデルの表現力が単純すぎるために、訓練データに存在する規則性さえ十分にとらえきれていない状態を指します。学習不足の場合は、訓練誤差自体が高い水準にとどまるため、訓練データとテストデータのどちらに対しても性能が振るわないという形で現れます。

汎化性能、見たことのないデータへの性能で価値を測る

実務で機械学習モデルを構築する目的は、手元にある訓練データを正確に説明することではありません。将来発生する新しい顧客、新しい取引、新しい画像といった、モデルが構築された時点ではまだ存在していなかったデータに対して、妥当な予測や判断を下すことにあります。この「見たことのないデータに対する性能」のことを汎化性能(generalization performance)と呼びます。モデルのビジネス上の価値は、訓練データに対する精度ではなく、この汎化性能によってのみ測られると言っても過言ではありません。

汎化性能を推し量るための基本的な手がかりが、訓練誤差(training error)とテスト誤差(test error、汎化誤差とも呼ばれます)の比較です。両者の組み合わせから、モデルがどのような状態にあるかをおおまかに読み取ることができます。

訓練誤差テスト誤差読み取れる状態
低い低い(訓練誤差と近い水準)汎化性能が高く、望ましい状態
低い高い(訓練誤差と大きく乖離)過学習の可能性が高い
高い高い学習不足の可能性が高い
高い訓練誤差よりさらに高いデータや前処理自体に問題がある可能性

ここで注意したいのは、訓練誤差だけを見て「良いモデルができた」と判断してはならないという点です。訓練誤差はモデルがどれだけデータに適合したかを示すにすぎず、そのモデルが未知のデータにも通用するかどうかは、テスト誤差を確認するまでわかりません。訓練誤差とテスト誤差の乖離こそが、過学習を検知するための最も基本的な指標であり、この乖離を正しく測定するための仕組みが、次節以降で扱うhold-out法と交差検証です。

モデルの複雑さを横軸、誤差を縦軸にとり、訓練誤差は単調に下がり続ける一方でテスト誤差はある点を境に上昇に転じるU字カーブを示すグラフ

hold-out法、データを訓練用とテスト用に分割する

訓練誤差とテスト誤差を区別して測定するためには、そもそも「モデルが見ていないデータ」を意図的に用意しておく必要があります。最も基本的な方法が、手元のデータセットをあらかじめ訓練用とテスト用の2つに分割しておくhold-out法です。scikit-learnではtrain_test_split関数を使ってこの分割を1行で実行できます。

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# X: 特徴量、y: 目的変数(例: 解約するか否か)。自社データに置き換えて使うテンプレートです
X_train, X_test, y_train, y_test = train_test_split(
    X, y,
    test_size=0.2,      # 全体の20%をテスト用にとりわけておく
    random_state=42,    # 乱数シードを固定し、結果を再現可能にする
    stratify=y,         # 目的変数のクラス比率を訓練用・テスト用で揃える
)

model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)

train_score = accuracy_score(y_train, model.predict(X_train))
test_score = accuracy_score(y_test, model.predict(X_test))
print(f'訓練データの正解率: {train_score:.3f}')
print(f'テストデータの正解率: {test_score:.3f}')

test_sizeでテスト用に取り分ける割合を、random_stateで乱数シードを指定しています。分類問題でクラスの比率に偏りがある場合(解約する顧客が全体の5%しかいない、など)は、stratify引数に目的変数を渡しておくことで、訓練用・テスト用それぞれのクラス比率を元のデータと揃えることができます。この指定を忘れると、たまたまテストデータに少数派クラスがほとんど含まれない、といった偏った分割になってしまうことがあります。

hold-out法には、実装が単純で計算コストが低いという利点がある一方、明確な弱点があります。それは、どのデータが訓練用に、どのデータがテスト用に割り振られるかが乱数のシードに依存してしまう点です。同じデータセット、同じモデルであっても、random_stateの値を変えて何度か分割し直すと、テスト精度が意外なほど上下に振れることがあります。たまたまテストデータに、モデルが得意とするパターンのデータが多く含まれていれば精度は高めに出ますし、逆に苦手なパターンが偏って含まれていれば精度は低めに出ます。データセットの件数が少ないほど、この「たまたま」の影響は大きくなります。1回の分割だけで得られた精度を鵜呑みにすると、モデルの実力を過大にも過小にも見積もってしまう危険があるということです。この弱点を緩和するために考え出されたのが、次に説明する交差検証です。

交差検証(k-Fold CV)、分割による偏りをならして評価する

交差検証(cross validation)、なかでも最も広く使われるk分割交差検証(k-Fold Cross Validation、以下k-Fold CV)は、hold-out法の「分割の仕方によって結果が変わってしまう」という弱点を、複数回の分割を平均することで緩和する手法です。

k-Fold CVの仕組みは次のとおりです。まずデータセット全体をほぼ同じ件数になるようk個のグループ(フォールド)に分割します。次に、k個のフォールドのうち1つをテスト用として残し、残りのk-1個を訓練用としてモデルを学習させ、テスト用に残したフォールドで精度を測定します。この手順をテスト用フォールドを入れ替えながらk回繰り返すことで、すべてのデータが1回ずつテスト用として使われます。最終的に、k回分の精度の平均(および標準偏差)を、そのモデルの評価値として採用します。

k-Fold CVを、kの値をデータの件数nと同じにまで大きくした極端な場合が、Leave-One-Out Cross Validation(LOOCV)です。LOOCVでは、1件だけをテスト用に残し、残る全件を訓練用として学習させる手順を、データの件数だけ繰り返します。訓練データをほぼすべて使えるためバイアス(偏り)の少ない評価が得られる一方、データ件数と同じ回数だけ学習を繰り返す必要があり計算コストが非常に大きくなるうえ、テスト用データが1件しかないため各回の評価結果のばらつき(バリアンス)が大きくなりやすいという性質もあります。実務で使われるkの値としては、5または10が相場とされています。この範囲であれば、計算コストを現実的な範囲に抑えながら、1回のhold-out法よりも安定した評価を得られることが経験的に知られています。

from sklearn.model_selection import cross_val_score, KFold
from sklearn.linear_model import LogisticRegression
import numpy as np

# X, y は自社データ(特徴量と目的変数)に置き換えて使うテンプレートです

model = LogisticRegression(max_iter=1000)

# 5分割交差検証(shuffle=Trueでフォールドへの割り当てをランダム化)
kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=kf, scoring='accuracy')

print(f'各フォールドの正解率: {np.round(scores, 3)}')
print(f'平均正解率: {scores.mean():.3f} (標準偏差: {scores.std():.3f})')

cross_val_score関数にモデルとデータ、cvに分割方法を渡すだけで、分割から学習・評価までを自動的に実行できます。出力される標準偏差の大きさにも注目してください。平均正解率が同じ0.85であっても、標準偏差が0.01程度であれば安定したモデルと言えますし、標準偏差が0.10近くあれば、フォールドによって結果が大きく振れる不安定なモデルであることを示しています。平均値だけでなくばらつきの大きさも確認する習慣が、モデルの信頼性を見極めるうえで欠かせません。

時系列データでの交差検証、未来の情報で過去を予測しない

ここまで紹介したhold-out法もk-Fold CVも、データをランダムにシャッフルしてから分割することを前提としています。しかし、時系列データ(売上の推移、株価、センサーの計測値など、時間の順序に意味があるデータ)に対してこの前提をそのまま適用すると、深刻な問題が生じます。

ランダムなシャッフルによる分割を時系列データに適用すると、たとえば2024年のデータで学習したモデルを、時間的にそれより前にあたる2023年のデータで評価する、といった組み合わせが起こり得ます。実際の運用では、モデルは常に過去のデータをもとに未来を予測することしかできません。未来のデータを使って過去を予測する評価は、実運用では絶対に起こり得ない状況でモデルを評価していることになり、実態よりも高い精度が算出されてしまいます。シャッフルを伴うランダム分割は、時系列データにおいては使ってはならない禁じ手になるということです。

この問題に対応するため、scikit-learnにはTimeSeriesSplitというクラスが用意されています。TimeSeriesSplitは、常に時間的に前のデータを訓練用、それより後のデータをテスト用に割り当てるという制約のもとで、複数回の分割を生成します。具体的には、1回目の分割では初期の一定期間を訓練用、その直後の期間をテスト用とし、2回目の分割では訓練用の期間を広げて再びその直後をテスト用とする、という形で、訓練期間を徐々に広げながらテスト期間を後ろにずらしていきます。使い方はcross_val_scoreのcv引数にKFoldの代わりにTimeSeriesSplitのインスタンスを渡すだけで、前節のコード例をそのまま流用できます。

TimeSeriesSplitを使う際は、Xとyの行の並び順が実際の時間順になっていることが前提になります。読み込んだデータフレームが日付順にソートされていない状態でTimeSeriesSplitに渡してしまうと、意図した時系列分割にならないため、事前に日付列でソートしておく必要があります。需要予測や売上予測、解約の兆候検知のように、時間的な変化を伴うテーマを扱う場合は、通常のk-Fold CVではなくTimeSeriesSplitを使うことが原則になると考えておくとよいでしょう。

リーク(データ漏洩)、精度が高すぎるモデルの典型的な原因

モデルの評価結果で正解率99%のような極端に高い数値が出たとき、成果と判断する前にまず疑うべきなのがリーク(データ漏洩、data leakage)です。リークとは、本来モデルが予測時点では知り得ないはずの情報が、意図せず特徴量や前処理の過程に混入してしまう現象を指します。リークが起きたモデルは、検証時にはきわめて高い精度を示す一方で、実際の運用に投入した途端に精度が大きく崩れるという特徴があります。

1つ目の典型例が、目的変数に由来する情報が特徴量に紛れ込んでしまうケースです。顧客の解約予測モデルを考えます。目的変数は「解約したかどうか」ですが、特徴量の中に「解約処理完了日」や「解約処理担当者ID」、「解約手続き画面への最終アクセス日時」といった、解約が確定した後にしか値が入らない項目が紛れ込んでいたとします。これらは、解約したという事実がすでに確定していることを前提にしか存在し得ない情報であり、実質的に答えを教えてしまっているのと同じです。このようなデータで学習させると検証精度は異常に高くなりますが、実際に解約するかどうかがまだわからない時点の顧客(実運用で予測したい対象)には、そもそもこれらの特徴量の値が存在しないため、モデルはまともに機能しません。

2つ目の典型例が、前処理の段階で起こる標準化リークです。数値特徴量を標準化(平均0、標準偏差1にそろえる処理)する際、多くの実装ではデータ全体の平均と標準偏差を計算してから、その値を使って各データを変換します。訓練データとテストデータに分割する前のデータセット全体に標準化を適用してしまうと、本来はテストデータであるはずの数値の情報が、標準化のパラメータという形で訓練データ側にも間接的に混入します。テストデータは本来、モデルにとって完全に未知の存在であるべきですが、標準化の基準にその情報が使われることで独立性が崩れてしまうのです。特徴量の数が多い場合や、テストデータの件数が少ない場合には、精度に無視できない上振れが生じることがあります。

交差検証やhold-out法で「精度が高すぎる」という結果が出たときは、まず喜ぶ前にリークを疑うべきです。目的変数の情報が特徴量に混入していないか、前処理のパラメータ(平均・標準偏差・エンコーディングの対応表など)がテストデータの情報を使わずに訓練データだけから計算されているか、この2点を必ず確認する習慣が、実運用で崩れないモデルを作る第一歩になります。

標準化リークを防ぐ最も確実な設計が、scikit-learnのPipelineを使い、前処理とモデルを1つの処理としてまとめてしまう方法です。Pipelineを使ってcross_val_scoreやtrain_test_splitと組み合わせると、各フォールド(または各分割)ごとに、標準化のパラメータが訓練データだけから計算され、そのパラメータをテストデータに適用するという処理が自動的に守られます。

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.model_selection import cross_val_score, StratifiedKFold

# X, y は自社データに置き換えて使うテンプレートです

# 標準化(StandardScaler)とモデル(SVC)を1つのPipelineにまとめる
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('clf', SVC(kernel='rbf', C=1.0)),
])

skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
# cross_val_scoreはフォールドごとにPipeline全体をfitし直すため、
# StandardScalerの平均・標準偏差もその回の訓練データだけから計算される
scores = cross_val_score(pipeline, X, y, cv=skf, scoring='accuracy')

print(f'平均正解率: {scores.mean():.3f} (標準偏差: {scores.std():.3f})')

ここでの要点は、StandardScalerを先にデータ全体へ適用してからcross_val_scoreに渡すのではなく、標準化そのものをPipelineの内部に組み込み、Pipeline全体を渡している点です。こうすることで、交差検証の各フォールドで、標準化のパラメータがその回の訓練データだけから計算され、テストデータには学習済みのパラメータを適用するだけという正しい手順が自動的に維持されます。欠損値の補完、カテゴリ変数のエンコーディング、次元削減など、データ全体の統計量を必要とする前処理は同様にリークの温床になり得るため、原則としてすべてPipelineの内部に組み込んで運用することが望ましいと言えます。

検証データとテストデータの使い分け、3分割の考え方

ここまでは訓練データとテストデータという2分割を前提に説明してきましたが、モデルのハイパーパラメータ(木の深さ、正則化の強さ、学習率など。第2章で扱ったwやbのように学習の中で自動的に決まるパラメータとは違い、学習を始める前に人が決めておく値を指します)をチューニングする実務の場面では、データを3つに分ける考え方が基本になります。すなわち、訓練データ(training data)、検証データ(validation data)、テストデータ(test data)の3分割です。

訓練データはモデルのパラメータを学習させるために使い、検証データは複数のハイパーパラメータの候補やモデルを比較し、最も良さそうな設定を選ぶために使います。ここで陥りやすい誤りが、この検証データで選んだ「最も精度の良かった設定」の精度を、そのままモデルの最終的な性能として報告してしまうことです。検証データは、チューニングの過程で繰り返し参照される「最も都合の良い結果を選ぶための基準」であるため、そこでの精度には多かれ少なかれ選択バイアスがかかっており、真の汎化性能よりも高めに出やすい性質があります。

この選択バイアスを避けるために用意しておくのが、チューニングの過程では一切参照しないテストデータです。ハイパーパラメータの選定がすべて完了した後、最後に1度だけテストデータでモデルを評価し、その結果を最終的な性能として報告します。途中で覗き見してしまうと、テストデータもまた検証データと同じ「都合よく選ばれた基準」になり、本来の役割を果たせなくなります。交差検証を使う場合も考え方は同じで、交差検証はハイパーパラメータの選定に使い、最終評価用のテストデータは交差検証の外側に最初から独立して確保しておくという設計が基本になります。

ビジネスへの含意、PoCの精度報告は検証設計とあわせて確認する

本章で扱ってきた検証設計の話題は、一見すると技術的で細かい話に思えるかもしれませんが、経営層やDX推進担当者がベンダーから提出されたPoC(概念実証)の結果を評価する際にも直接関わってくる論点です。PoCの報告書に「正解率98%」「予測精度95%」といった高い数値が並んでいると、それだけで成果として評価してしまいたくなりますが、その数値がどのような検証設計のもとで得られたのかを確認しないまま導入を判断することには、相応のリスクが伴います。

PoCの評価の場では、精度の数字そのものよりも先に、その数字を生み出した検証設計を確認する姿勢が実務上重要になります。次のような観点は、専門知識がなくても質問として投げかけることができます。

  • テストデータは訓練データと明確に分けられているか。訓練に使ったデータをそのまま評価にも使っていないか
  • 時系列を伴うデータであれば、TimeSeriesSplitのように時間の順序を守った検証がされているか。未来のデータで過去を予測するような分割になっていないか
  • 目的変数が確定した後にしか値が存在しない特徴量(処理日、担当者情報、事後アンケートの結果など)が紛れ込んでいないか
  • 標準化やエンコーディングなどの前処理は、訓練データだけから計算されたパラメータをテストデータに適用する形になっているか(Pipelineなどで担保されているか)
  • ハイパーパラメータのチューニングに使ったデータと、最終的な性能評価に使ったデータは別になっているか

PoCの精度は、検証設計を確認するまでは仮の数字にすぎません。特に業務上の常識から見て説明のつかないほど高い精度が出ている場合は、リークや不適切な分割を疑い、検証設計の詳細をベンダーに確認することが、導入判断を誤らないための実務的な防衛線になります。

訓練データ・検証データ・テストデータの3分割を時系列のバーで示し、それぞれの用途(学習・チューニング・最終評価)を矢印で説明するイメージ

逆に言えば、検証設計さえきちんと確認できていれば、多少精度が低めに出ているモデルであっても、実運用に投入したときの現実的な性能に近いという意味で、むしろ信頼できる報告だと評価できる場合もあります。精度の高さだけでなく、その精度がどのように測られたのかという検証プロセスの健全性こそが、機械学習プロジェクトの成否を分ける本質的な論点です。

この章を深めたい方への参考書籍

『Pythonではじめる機械学習』(Andreas C. Muller、Sarah Guido、オライリー・ジャパン):scikit-learnを使った実装を中心に、hold-out法や交差検証、Pipelineの使い方までを豊富なコード例とともに解説しており、本章で扱った検証設計の考え方を手元で実際に動かしながら確認したい読者に適した1冊です。過学習と汎化の関係を可視化するグラフの作り方も紹介されており、モデルの複雑さと誤差の関係を直感的につかむ助けになります。

第4章 モデルの複雑さをどう制御するか、バイアスとバリアンスと正則化

前章では、モデルが訓練データに適合しすぎてしまう「過学習」と、未知のデータにも通用する力である「汎化」の関係を見てきました。訓練データの誤差だけを下げようとすると、モデルはノイズまで丸暗記してしまい、かえって本番での予測精度が落ちてしまう現象でした。本章では過学習をもう一段掘り下げ、「モデルの複雑さ」という切り口から、なぜ過学習や学習不足が起こるのか、それをどう制御すればよいのかを整理します。

キーワードは「バイアス」と「バリアンス」、そして「正則化」です。これらは一見抽象的な概念に見えますが、実務上は「どれくらいの複雑さのモデルを選ぶべきか」「特徴量をどこまで増やしてよいか」という日々の意思決定に直結する道具立てです。多項式回帰という単純な例を使いながら、複雑さの制御方法を具体的に見ていきます。

単純すぎるモデルと複雑すぎるモデル:多項式回帰の次数を上げてみる

モデルの複雑さという概念を体感するために、多項式回帰を使った実験から始めます。多項式回帰とは、説明変数\( x \)の1乗だけでなく2乗・3乗といった高次の項も加えて、曲線的な関係を表現できるようにした回帰モデルです。次数を1に固定すれば普通の直線回帰と同じになり、次数を上げるほど複雑な曲線を描けるようになります。

真の関係が緩やかな曲線であり、そこに観測ノイズが乗ったデータがあるとします。次数を変えながら多項式回帰を当てはめてみると、次のような挙動が観察されます。

  • 次数1(直線):データの曲がり具合をまったく捉えられず、どの点からも一様に外れた直線になります。訓練データに対する誤差も、テストデータに対する誤差も、どちらも大きいままです。
  • 次数3〜4程度:真の曲線の形にほどよく沿うようになり、訓練誤差・テスト誤差とも小さくなります。
  • 次数15程度(高次):訓練データの一点一点を無理やり通過させようとして、曲線が激しく波打ちます。訓練データに対する誤差は、データに乗っているノイズの水準すら下回るところまで下がりますが、データのない領域では予測値が大きく暴れ、テストデータに対する誤差はかえって悪化します。

この実験が示しているのは、「モデルを複雑にすればするほど良い」わけではないという、見落とされがちな事実です。次数を横軸に訓練誤差とテスト誤差を縦軸にとると、訓練誤差は次数を上げるほど単調に下がる一方、テスト誤差はあるところで底を打ち上昇に転じます。この「訓練誤差は下がり続けるのに、テスト誤差はU字を描く」という形が、複雑さを考える基本図です。

Pythonで確認してみます。真の関係を\( y = \sin(1.5\pi x) \)とし、ノイズを加えたデータに次数を変えて多項式回帰を当てはめ、訓練誤差とテスト誤差を比較します。

import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.metrics import mean_squared_error

rng = np.random.default_rng(42)

# ノイズ入りの非線形データを生成
X = rng.uniform(0, 1, size=30).reshape(-1, 1)
y_true = np.sin(1.5 * np.pi * X).ravel()
y = y_true + rng.normal(0, 0.2, size=30)

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

for degree in [1, 4, 15]:
    model = make_pipeline(PolynomialFeatures(degree), LinearRegression())
    model.fit(X_train, y_train)

    train_mse = mean_squared_error(y_train, model.predict(X_train))
    test_mse = mean_squared_error(y_test, model.predict(X_test))
    print(f"次数{degree:>2}: 訓練MSE={train_mse:.4f}  テストMSE={test_mse:.4f}")

実行すると、次数1では訓練MSEが0.1182・テストMSEが0.4823といずれも大きく(学習不足)、次数4では0.0255と0.0242でほぼ釣り合い、次数15では訓練MSEが0.0225とノイズの水準(分散0.04)を下回るまで下がる一方で、テストMSEは0.8060と次数4の33倍に跳ね上がります(過学習)。訓練データへの当てはまりが良くなり続けても、未知のデータへの性能は途中から悪化に転じる、という構図がそのまま数値に出ています。

次数1・4・15の多項式回帰曲線を、訓練データの散布図に重ねて3枚並べたイメージ

バイアスとバリアンスのトレードオフ

先ほどの実験で見た「学習不足」と「過学習」という2つの失敗のパターンを、もう少し理論的に整理する枠組みが「バイアスとバリアンスの分解」です。的当てのイメージで考えると理解しやすくなります。

的の中心を「真の値」だとして何度も矢を射たとき、狙いが中心から系統的にずれている状態が「バイアスが高い」状態です。矢がどこにばらけていようと、平均をとると中心からずれた場所に集まります。一方、狙いは中心付近にあるものの毎回の矢が大きくばらける状態が「バリアンスが高い」状態です。この2つは互いに独立な失敗の軸であり、理想は「中心に集まり、かつばらつきも小さい」状態です。

状態バイアスバリアンス矢の集まり方
理想低い低い中心に密集
学習不足(単純すぎるモデル)高い低いある一点に密集するが中心からずれる
過学習(複雑すぎるモデル)低い高い中心付近を狙うが広く散らばる

この比喩を予測誤差に当てはめると、期待誤差は概念的に次の3つの要素に分解できることが知られています。

\( \text{期待誤差} = \text{バイアス}^2 + \text{バリアンス} + \text{削減不可能な誤差} \)

  • バイアス:モデルの表現力不足による系統的なズレです。真の関係が曲線なのに直線でしか近似できない、といった「モデルの形そのものの限界」に由来します。
  • バリアンス:訓練データを入れ替えるたびに、学習されるモデルがどれだけ大きく変動するかを表します。モデルが複雑すぎると訓練データのノイズにまで過剰反応し、データセットが変わるたびに予測が揺れ動きます。
  • 削減不可能な誤差:データに内在するノイズによる誤差で、どんなモデルでも原理的に取り除けない部分です。

前章で扱った過学習・学習不足は、まさにこの言葉で言い換えられます。学習不足は「バイアスが高い」状態で、モデルが単純すぎて構造を捉えきれていません。過学習は「バリアンスが高い」状態で、モデルが複雑すぎて訓練データの細部まで拾ってしまっています。複雑さを上げるほど一般にバイアスは下がりバリアンスは上がるというトレードオフがあり、両者の和が最小になる複雑さを見つけることが中心的な課題になります。

バイアスとバリアンスのトレードオフを踏まえると、「テスト誤差が大きい」という1つの症状にも、原因によって処方箋がまったく異なることが分かります。学習不足(高バイアス)ならモデルを複雑にする・特徴量を増やす方向の対策が、過学習(高バリアンス)ならモデルを単純にする・データを増やす・正則化を強める方向の対策が必要です。訓練誤差とテスト誤差の両方を確認し、どちらの状態に近いかを見極めることが出発点になります。

的当ての比喩で高バイアス・高バリアンス・理想の3パターンを描いたイラスト

モデルの複雑さを制御する3つの方法

過学習、すなわちバリアンスが高すぎる状態への対処法として、実務でよく使われるアプローチは大きく3つに整理できます。

  1. データを増やす:訓練データの件数を増やすと、モデルが特定サンプルのノイズに引きずられにくくなり、バリアンスが下がる傾向があります。最も素直で効果の大きい対策ですが、収集にはコストと時間がかかります。
  2. 特徴量を減らす:説明変数の数を絞り込み、モデルが表現できる自由度そのものを下げます。業務知識に基づく除外のほか、統計的な基準を使った特徴量選択の手法もあります。
  3. 正則化する:特徴量の数は減らさず、係数が極端に大きな値をとらないよう学習の過程でペナルティを課す方法です。明示的に取捨選択しなくても、実質的にモデルの複雑さを抑える効果があります。

データを増やす方法は最も直接的ですが実行できるとは限りません。特徴量を減らす方法と正則化する方法は手元のデータと計算だけで実行できるため、実務で頻繁に用いられます。本章の後半ではこの2つ、とりわけ正則化を詳しく見ていきます。その前に、逆方向の「モデルの表現力を上げる」方法にも触れておきます。組み合わせて使う場面が多いためです。

多項式特徴量と交互作用で線形モデルの表現力を上げる

線形回帰は「係数と特徴量の掛け算の和」という単純な形をしていますが、入力する特徴量を工夫することで、モデル自体は線形のままでも表現力を高めることができます。その代表例が、冒頭の実験でも使った多項式特徴量です。

PolynomialFeaturesを使うと、元の特徴量から2乗・3乗といった高次項や、異なる特徴量どうしの掛け算である「交互作用項」を自動的に生成できます。広告費(x1)と気温(x2)にPolynomialFeatures(degree=2)を適用すると、\( x_1 \)、\( x_2 \)に加えて\( x_1^2 \)、\( x_2^2 \)、交互作用項\( x_1 x_2 \)が作られます。交互作用項は「気温が高い日ほど広告費の効果が大きくなる」といった、単独の特徴量では表現できない組み合わせ効果を捉えるために使われます。

ここで重要なのは、こうして生成した新しい特徴量を使う限り、モデル自体は依然として「線形回帰」だという点です。\( x_1^2 \)という項も線形回帰にとっては単なる1つの特徴量にすぎず、係数を通常どおり最小二乗法などで推定します。多項式特徴量とは、学習アルゴリズムを変えず入力側を加工して表現力を引き上げる手法です。ただし次数を上げるほど特徴量の数は急激に増え、バリアンスが高まりやすくなります。次に説明する正則化は、この副作用への標準的な対処法です。

正則化の考え方:係数の大きさにペナルティを課す

正則化とは、モデルの係数が不必要に大きな値をとらないように、損失関数に「ペナルティ項」を追加して学習させる手法の総称です。特徴量の数が多い場合や相関が強い場合、誤差だけを追い求めると係数が異常に大きくなり、データの微妙な変動に予測が過敏に反応してしまうことがあります。正則化は誤差最小化に「係数の大きさ」へのペナルティを加え、モデルの複雑さを制御しようとする発想です。

代表的な正則化手法として、L2正則化(Ridge回帰)とL1正則化(Lasso回帰)の2つがあります。どちらも通常の誤差項にペナルティ項を足す点は共通していますが、ペナルティのかけ方が異なります。

\( \text{Ridge}: \quad \text{誤差} + \alpha \sum_{j} \beta_j^{2} \)

\( \text{Lasso}: \quad \text{誤差} + \alpha \sum_{j} |\beta_j| \)

ここで\( \beta_j \)は各特徴量の係数、\( \alpha \)は正則化の強さを決めるハイパーパラメータです。この2乗か絶対値かというペナルティの違いが、実務上とても重要な性質の差につながります。

  • Ridge(L2):係数を全体的に縮小させますが、よほど強くかけない限り係数がちょうどゼロになることはほとんどありません。特徴量どうしが強く相関している(多重共線性がある)場合でも、推定を安定させる効果があります。
  • Lasso(L1):ペナルティのかけ方の性質上、重要度の低い特徴量の係数をちょうどゼロまで縮めてしまいます。結果として、学習と同時に「不要な特徴量を自動的に除外する」特徴量選択の効果を持ちます。

Lassoのこの特徴量選択の効果は便利ですが、弱点もあります。互いに強く相関する特徴量の集団があると、Lassoはその中のどれか1つだけを残して残りをゼロにする傾向があり、どれが選ばれるかがデータの微妙な違いで入れ替わることがあります。この不安定さを緩和するために考え出されたのが「ElasticNet」で、L2とL1のペナルティを一定の比率で組み合わせたものです。

\( \text{ElasticNet}: \quad \text{誤差} + \alpha \left( \rho \sum_{j} |\beta_j| + \frac{1-\rho}{2} \sum_{j} \beta_j^{2} \right) \)

ここで\( \rho \)(scikit-learnではl1_ratioで指定します)はL1・L2成分の混合比率です。ElasticNetは相関の強い特徴量の集団をまとめて残しつつ、不要な特徴量は係数をゼロに近づける、RidgeとLassoの中間的な性質を持ちます。「特徴量選択が目的ならLasso、相関が強くLassoの不安定さが気になるならRidgeかElasticNet」が実務上の目安です。

RidgeとLassoの係数を比較してみます。目的変数に本当に関係のある特徴量と、まったく関係のない(ノイズだけの)特徴量を混在させたデータで、それぞれの手法の係数がどう変わるかを確認します。

import numpy as np
from sklearn.linear_model import Ridge, Lasso

rng = np.random.default_rng(0)
n_samples, n_features = 100, 10

X = rng.normal(size=(n_samples, n_features))
# 効いているのは最初の3個のみ、残り7個は無関係なノイズ
true_coef = np.array([3.0, -2.0, 1.5, 0, 0, 0, 0, 0, 0, 0])
y = X @ true_coef + rng.normal(0, 0.5, size=n_samples)

ridge = Ridge(alpha=1.0).fit(X, y)
lasso = Lasso(alpha=0.1).fit(X, y)

print("特徴量   :", [f"x{i}" for i in range(n_features)])
print("真の係数 :", np.round(true_coef, 2))
print("Ridge係数:", np.round(ridge.coef_, 2))
print("Lasso係数:", np.round(lasso.coef_, 2))

この実験を実行すると、Ridgeはx3以降の無関係な特徴量にもわずかな非ゼロの係数を割り当てるのに対し、Lassoはx3以降の係数をほぼ完全にゼロへ押しつぶし、実質的にx0からx2までの3つだけを使うモデルに整理される様子が確認できます。

RidgeとLassoの係数を横棒グラフで並べ、Lassoの係数の多くがゼロに揃っている様子

正則化の強さ(alpha)をどう選ぶか

正則化を使ううえで避けて通れないのが、ペナルティの強さを表すハイパーパラメータ\( \alpha \)(alpha)をどう決めるかという問題です。\( \alpha \)が0に近ければ通常の線形回帰とほぼ同じになり抑制効果はほとんど働きません。逆に大きくしすぎると係数が押しつぶされすぎて、今度は学習不足(高バイアス)に転じます。ちょうど良い\( \alpha \)を選ぶこと自体が、複雑さを制御する実質的な作業になります。

\( \alpha \)の選び方の基本は、前章で扱った交差検証です。訓練データをいくつかのフォールドに分け、複数の\( \alpha \)の候補について検証誤差を計算し、最も小さくなる\( \alpha \)を採用します。scikit-learnにはこの探索を効率的に行うRidgeCVLassoCVが用意されており、候補のリストを渡すだけで最適な\( \alpha \)を自動的に選んでくれます。

正則化を適用するうえで、実務でしばしば見落とされがちな注意点があります。「正則化をかける前に、必ず特徴量を標準化(平均0・分散1に変換)しておく」ことです。ペナルティは係数\( \beta_j \)の大きさにかかるため、「年齢(20〜60程度)」と「年収(数百万〜数千万円)」のようにスケールが大きく異なる特徴量が混在していると、一律のペナルティが不公平にかかってしまいます。標準化でスケールを揃えて初めて公平にかけられます。

実務では、標準化と正則化付き回帰をPipelineにまとめておくのが推奨されます。交差検証の各フォールドで標準化のパラメータを訓練部分だけから計算するためです。標準化を交差検証の外側で行うと、検証データの情報が漏れ出す「データリーク」につながります。

import numpy as np
from sklearn.linear_model import RidgeCV, LassoCV
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split

rng = np.random.default_rng(1)
X = rng.normal(size=(300, 10))
X[:, 0] *= 1000  # x0だけ極端にスケールが大きい特徴量にしておく
true_coef = np.array([3.0, -2.0, 1.5, 0, 0, 0, 0, 0, 0, 0])
y = X @ (true_coef / np.array([1000] + [1] * 9)) + rng.normal(0, 0.5, size=300)

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=1
)

alphas = np.logspace(-3, 3, 50)  # 0.001~1000の範囲でalphaの候補を用意

ridge_pipe = make_pipeline(StandardScaler(), RidgeCV(alphas=alphas, cv=5))
lasso_pipe = make_pipeline(StandardScaler(), LassoCV(alphas=alphas, cv=5, max_iter=10000))

ridge_pipe.fit(X_train, y_train)
lasso_pipe.fit(X_train, y_train)

print("RidgeCVで選ばれたalpha:", ridge_pipe.named_steps["ridgecv"].alpha_)
print("LassoCVで選ばれたalpha:", lasso_pipe.named_steps["lassocv"].alpha_)
print("Ridgeのテストスコア(R^2):", ridge_pipe.score(X_test, y_test))
print("Lassoのテストスコア(R^2):", lasso_pipe.score(X_test, y_test))

この例では意図的にx0だけ極端に大きなスケールを持たせています。PipelineStandardScalerを通してから渡すことで、スケールの違いに影響されずに\( \alpha \)を選び、係数を公平に評価できます。標準化を省略すると、スケールの大きいx0にはペナルティがほとんど効かずに素通しになり、逆にスケールの小さい他の特徴量ばかりが強く縮められるという不公平が生じます。正則化のペナルティは係数の絶対値にかかるため、値の大きい特徴量ほど係数が小さくて済み、罰則を免れてしまうためです。

質的変数の扱い:one-hotエンコーディングと注意点

ここまでは数値の特徴量を前提に話を進めてきましたが、実務のデータには「都道府県」「商品カテゴリ」「契約プラン」といった、カテゴリ(質的変数)の特徴量も数多く登場します。線形回帰をはじめ多くのモデルは文字列のカテゴリを扱えないため、数値に変換する前処理が必要です。

最も標準的な変換方法が「one-hotエンコーディング」です。カテゴリ変数がとりうる値の種類ごとに、「該当するなら1、そうでなければ0」という2値の列を作る変換です。「契約プラン」が「フリー」「スタンダード」「プレミアム」の3種類であれば、それぞれ該当するか否かの3つの列が作られます。scikit-learnではOneHotEncodersparse_output=Falseで疎行列でなく通常のNumPy配列を得られます)、pandasではpd.get_dummiesを使うのが一般的です。

one-hotエンコーディングで注意すべきなのが「ダミー変数トラップ」です。カテゴリがk種類あるとき、作った列は必ずどれか1つだけが1で残りは0になるため、k個すべてを残すとその合計が常に1になり、切片を含む線形回帰では完全な多重共線性が生まれます。これを避けるため、k個のうち1つを除外し、k-1個の列だけを使うのが標準的な作法です。OneHotEncoder(drop="first")pd.get_dummies(df, drop_first=True)のように自動で列除外を行うオプションが多くのライブラリに用意されています。ただし、列を落とすことが常に正解というわけではありません。正則化を効かせたモデルでは、どのカテゴリを基準に選ぶかによって係数の縮み方が変わるため、あえて全カテゴリを残す選択も一般的です。決定木やランダムフォレストのように多重共線性の影響を受けないモデルでも、列を落とす必要はありません。切片を含む正則化なしの線形回帰では基準カテゴリを除外する、それ以外では必要に応じて判断する、と整理しておくとよいと思います。

もう1つ実務で頻繁に問題になるのが、カテゴリの種類数(カーディナリティ)が非常に多い変数への対処です。「郵便番号」や「取引先企業ID」のように種類が数百〜数万にのぼる変数をそのままone-hotエンコーディングすると、特徴量の数が爆発的に増え、多くの列がほぼ0ばかりの疎な状態になります。これは本章の「特徴量が多すぎることによるバリアンスの増大」そのものです。対策としては、低頻度カテゴリを「その他」にまとめる、ターゲットエンコーディング、主成分分析による次元圧縮などが代表的です。

ビジネスへの含意:特徴量は多ければ良いわけではない

本章の内容を、モデル構築の意思決定という観点から整理します。まず押さえておきたいのは、「特徴量をたくさん投入すれば精度の高いモデルができる」という発想は必ずしも正しくないということです。特徴量を増やすことはバイアスを下げますが、同時にバリアンスも高めます。データ量に対して特徴量が多すぎれば精度はむしろ悪化しかねず、追加する前に交差検証でテスト誤差の変化を確認する姿勢が欠かせません。

正則化やシンプルなモデルには、精度以外の面でも運用上の価値があります。第一に解釈のしやすさです。係数がゼロに整理されたLassoや特徴量数の少ないモデルは、どの要因がどう効いているかを説明しやすくなります。第二に運用の安定性です。特徴量が少なければデータ収集元の障害やフォーマット変更の影響を受けるリスクが減ります。第三に計算コストです。特徴量が少なく係数が疎であれば、推論の計算量やモデルの管理コストも抑えられます。

モデルの複雑さをどこまで許容するかは、精度だけでなく解釈性・運用の安定性・計算コストといった複数の観点を踏まえたトレードオフとして意思決定すべき論点です。バイアスとバリアンスのバランスを交差検証で確認しながら、正則化や特徴量選択を通じて、目的に見合った過不足のない複雑さのモデルに仕上げていくプロセスを定着させることが望ましいといえます。

次章では、こうして作り上げたモデルの性能をどのような指標で評価するのが適切かというテーマに進みます。正解率だけでは見落としてしまう問題や、回帰・分類それぞれに適した評価指標の使い分けを扱います。

この章を深めたい方への参考書籍

『イラストで学ぶ機械学習』(杉山将、講談社):バイアスとバリアンスのトレードオフや正則化の考え方を、数式だけでなく図解を交えて説明している一冊です。本章の多項式回帰やRidge・Lassoの直感的なイメージを、より数理的な裏付けとともに深掘りしたい読者に適しています。

第5章 モデルの良し悪しをどう測るか、回帰と分類の評価指標

こんにちは、Anagraftの伊藤です。ここまでの章では、機械学習という営みの全体像(第1章)、損失関数を最小化しながらパラメータを学習していく仕組み(第2章)、学習したモデルが未知のデータにどこまで通用するかを左右する過学習と汎化、バイアスとバリアンスのトレードオフや正則化(第3章、第4章)を扱ってきました。いずれも「モデルをどう作るか」に関わる話でした。

本章のテーマは「作ったモデルをどう評価するか」です。学習が終わった後、私たちは「このモデルはどれくらい使えるのか」を数値で確認する必要があります。この評価という工程は見た目以上に奥が深く、選ぶ指標を誤ると、実際には使い物にならないモデルを「優秀だ」と誤認してしまう事故が起こります。本章では回帰と分類それぞれの代表的な評価指標を整理し、指標同士のトレードオフや、指標を事業上の意思決定にどう橋渡しするかまでを押さえます。

評価指標が並んだダッシュボード

「精度」という言葉の曖昧さ

モデルの性能を語るとき、「精度が95パーセントです」という報告を耳にすることがあります。しかし、この一言だけでは実は何も語っていないに等しい場合があります。「精度」という言葉が、正解率(Accuracy)を指しているのか、適合率(Precision)を指しているのか、あるいは決定係数(R2)のような回帰の指標を指しているのかが、文脈から曖昧なまま使われることが少なくないためです。

さらに厄介なのは、どの指標を選ぶかによって同じモデルの評価がまったく違って見えてしまう点です。後述するように、不正利用や病気の検知のように「見逃したくない事象」を扱うタスクでは、正解率だけを見ていると致命的な見落としに気づけません。逆に迷惑メールの判定のように「誤って弾きたくない」タスクでは別の指標が重視されます。評価指標は単なる計算式ではなく、「何を成功と定義するか」というビジネス上の判断を数値に落とし込んだものです。本章では、「精度が高い」という表現に出会ったときに、まず「どの指標を指しているのか」を確認する視点を養います。

回帰モデルの評価指標、誤差をどう要約するか

回帰モデルは売上額や需要量、価格といった連続値を予測します。予測値と実際の値の差、すなわち誤差をどう要約するかによって代表的な指標が使い分けられています。

最も基本的なのが平均二乗誤差(Mean Squared Error、MSE)です。誤差を二乗してから平均を取ったもので、大きく外れた予測ほど不釣り合いに強く罰せられます。ただし単位も元の量の二乗になり、対象が「円」であればMSEの単位は「円の二乗」という扱いにくいものになります。そこで平方根を取って単位を戻したものが二乗平均平方根誤差(Root Mean Squared Error、RMSE)です。大きな誤差を強く罰する性質を引き継ぎつつ、「平均してこれくらいずれる」という解釈がしやすくなります。

これに対して平均絶対誤差(Mean Absolute Error、MAE)は誤差の絶対値をそのまま平均します。二乗を経由しないため外れ値の影響を受けにくく、より頑健です。5日分の需要予測で、実測値が100、120、90、110、95(個)、予測値が105、115、95、108、150だったとします。最後の1日だけ55個外れ、他の4日は数個のずれです。この場合、MAEはおよそ14.4個ですが、RMSEはおよそ24.9個になります。RMSEの方が大きく出るのは、55個という誤差が二乗されて計算全体を押し上げるためです。「平常時はよく当たるがたまに大外れする」癖を検出したいならRMSE、外れ値に引きずられず全体傾向を見たいならMAE、という使い分けの目安になります。

もう1つよく使われるのが平均絶対パーセント誤差(Mean Absolute Percentage Error、MAPE)です。誤差を実測値に対する比率(パーセント)で表すため単位に依存せず、規模の異なる複数店舗や複数商品の精度を横断比較する際に重宝します。先ほどの例ではMAPEはおよそ14.9パーセントです。ただし実測値が0に近いと比率の分母が小さくなり指標が不安定になり、0のときは計算そのものが不能になります。新製品の発売直後や需要ゼロの商品を扱う場合は、MAEなど絶対量の指標と併用することが推奨されます。

最後に決定係数(R2)は、実測値の平均だけで予測した場合と比べてモデルがどれだけ誤差を説明できているかを表す指標です。1に近いほど当てはまりが良く、0であれば「平均値で予測するのと変わらない」ことを意味し、負の値になることもあります。負になるのは「予測が平均値を答え続けるより悪い」ことを示し、外れ値の多いデータで起こり得ます。先ほどの5日分の例でも1日の大外れのせいでR2は大きく悪化しマイナスの値になります。単位に依存しない分かりやすい指標である一方、外れ値の影響を強く受ける点でRMSEと似た性質を持つことは覚えておく必要があります。

指標何を測るか単位外れ値への感度実務での使いどころ
MSE誤差の二乗の平均元の量の2乗(直感的でない)高い学習時の損失関数としてはよく使われるが、報告用としては単位が扱いにくい
RMSEMSEの平方根元の量と同じ高い大きな外れを厳しく見たい予測(需要予測、価格予測など)の総括指標
MAE誤差の絶対値の平均元の量と同じ低い(比較的頑健)外れ値の影響を抑えて全体傾向を見たい在庫・需要予測
MAPE誤差の実測値に対する比率パーセント(単位なし)実測値が0近辺で不安定スケールの異なる複数商品・複数店舗の横断比較
R2平均値予測に対する改善度比率(単位なし)高いモデルの当てはまりの良さを一目で伝えたい報告資料

どの指標を選ぶべきかは目的次第です。在庫予測では発注数量という実単位で語れるMAEやRMSEが扱いやすく、欠品や過剰在庫のコストが個数に比例する業態では、RMSEで大きな外れを監視しつつMAEで平常時の精度を追う併用がよく行われます。規模の大きく異なる商品や店舗の予測精度を横並びで比較したい資料であれば、比率で語れるMAPEの方が伝わりやすい場面もあります。単純な優劣ではなく「何をもって良い予測とするか」という業務上の定義に沿って指標を選ぶことが重要です。

分類モデルの土台、混同行列を読み解く

分類モデルの評価は回帰よりも一段複雑です。まず土台となるのが混同行列(Confusion Matrix)です。モデルの予測結果を「実際の正解」と「モデルの予測」の組み合わせで4つに分類した表で、2値分類では次の4つのマスに整理されます。

区分意味
真陽性(True Positive、TP)実際に陽性で、モデルも陽性と予測した件数
真陰性(True Negative、TN)実際に陰性で、モデルも陰性と予測した件数
偽陽性(False Positive、FP)実際は陰性なのに、モデルが陽性と誤って予測した件数
偽陰性(False Negative、FN)実際は陽性なのに、モデルが陰性と誤って予測した件数

ここで注意したいのは、「何を陽性、何を陰性とするか」自体は統計的にも数学的にも決まっているものではなく、ビジネス側が定義するものだという点です。不正利用検知であれば「不正である」ことを陽性とするのが自然ですし、健康診断のスクリーニングであれば「疾患がある」ことを陽性とします。何を見逃したくないかという目的が先にあり、その目的に沿って定義が決まります。この定義を誤ると後述する解釈もねじれてしまうため、分析の最初の段階で「陽性とは何を指すか」をチームで合意しておくことが欠かせません。

具体例で確認します。あるクレジットカード会社が、10,000件の取引のうち不正利用(陽性)を検出するモデルを運用しているとします。実際の内訳は不正利用が100件、正常な取引が9,900件です。このモデルが、不正利用100件のうち70件を正しく不正と判定し(TP=70)、残り30件を見逃して正常と判定してしまった(FN=30)とします。正常な取引9,900件のうち200件は誤って不正と判定し(FP=200)、残り9,700件は正しく正常と判定した(TN=9,700)とします。この結果をコードで確認します。

import numpy as np
from sklearn.metrics import confusion_matrix, classification_report

# 実際の内訳:不正利用100件、正常な取引9,900件
# 予測結果:TP=70件、FN=30件、FP=200件、TN=9,700件
y_true = np.array([1] * 100 + [0] * 9900)
y_pred = np.array([1] * 70 + [0] * 30 + [1] * 200 + [0] * 9700)

cm = confusion_matrix(y_true, y_pred)
print(cm)
# [[9700  200]
#  [  30   70]]

print(classification_report(
    y_true, y_pred, target_names=["正常", "不正"], digits=3
))

confusion_matrixが返す行列は、行が実際のラベル、列が予測ラベルに対応し、ラベルは昇順(0、1)で並びます。1行1列目がTN(9,700)、1行2列目がFP(200)、2行1列目がFN(30)、2行2列目がTP(70)です。classification_reportを使うと、後述する適合率・再現率・F1値をクラスごとにまとめて確認できるため、混同行列の数値だけを見るより実務上扱いやすいレポートになります。

Accuracyの限界、不均衡データでは正解率が意味を失う

混同行列の4つの数値から最も素朴に計算できる指標が正解率(Accuracy)です。正解率は「全件のうち予測が実際と一致した件数の割合」であり、\( \text{Accuracy} = \dfrac{TP+TN}{TP+TN+FP+FN} \) という式で表されます。先ほどの例では、正解率は(70+9,700)/10,000で97.7パーセントになります。

ここで、あえて何も学習していない「すべての取引を正常と予測する」モデルを考えてみます。不正利用100件をすべて見逃しますが、正常な取引9,900件はすべて正しく判定するため、正解率は9,900/10,000で99パーセントに達します。数値だけを比べると、この「何もしないモデル」の方が学習した97.7パーセントのモデルより優秀に見えますが、実態は正反対です。何もしないモデルは不正利用を1件も検知できておらず、事業上の価値はゼロです。これが不均衡データ(陽性と陰性の件数が大きく偏ったデータ)における正解率の落とし穴です。陽性の割合が1パーセントしかないデータでは、常に陰性と答えるだけで99パーセントの正解率が得られてしまい、正解率という指標そのものが意味を失います。不正検知、疾患診断、設備の異常検知など「見つけたい事象がごく一部しか占めない」タスクの多くはこの構造を持ち、正解率だけでの評価は避けるべきです。不均衡データへの対処法は第11章で詳しく扱いますが、まず押さえるべきは「正解率に代えて次に説明する適合率・再現率・F1値やAUCを確認する」という姿勢です。

適合率と再現率、何を優先するかはビジネスの誤りのコストで決まる

正解率の限界を補うのが適合率(Precision)と再現率(Recall)です。適合率は「陽性と予測したもののうち実際に陽性だった割合」で、\( \text{Precision} = \dfrac{TP}{TP+FP} \) で計算します。再現率は「実際に陽性だったもののうち正しく陽性と予測できた割合」で、\( \text{Recall} = \dfrac{TP}{TP+FN} \) で計算します。

先ほどの例で計算すると、適合率は70/270でおよそ25.9パーセント、再現率は70/100で70パーセントです。適合率が低いのは、モデルが不正と判定した270件のうち実際に不正だったのは70件だけで、残り200件は正常な取引を誤って疑ってしまっているためです。再現率は70パーセントで、実際の不正100件のうち7割は捕捉できています。

適合率と再現率は一般にトレードオフの関係にあります。判定基準を緩めれば、より多くの不正を捕捉できて再現率は上がりますが、正常な取引を誤って弾く数も増え適合率は下がります。逆に基準を厳しくすれば確信度の高い不正だけを検知して適合率は上がりますが、微妙な不正を見逃しやすくなり再現率は下がります。どちらを優先すべきかは数学的に決まるものではなく、「見逃す誤り(偽陰性)」と「疑いすぎる誤り(偽陽性)」のどちらのコストが大きいかという業務上の判断で決まります。

不正利用検知では一般に再現率が重視されます。1件見逃すことの損失(被害額の補填や信用の毀損)が、正常な取引を誤って一時停止する程度のコストよりはるかに大きいためです。反対に迷惑メールの判定では適合率が重視されがちです。重要な取引先のメールを誤って振り分ける(偽陽性)損失は大きく、多少の迷惑メールが残る(偽陰性)方が許容しやすいためです。医療のスクリーニング検査でも見逃しを避けるために再現率が優先され、再検査で絞り込む二段階設計がよく採用されます。適合率と再現率のどちらを重視するかを決める作業自体が、技術設計と同じくらい重要な意思決定になります。

F1値と閾値、確率の出力を2値に区切る基準の決め方

適合率と再現率のどちらか一方だけを見ていると、極端に偏ったモデルを見逃す恐れがあります。たとえば「怪しいものは1件残らず陽性と判定する」モデルは再現率100パーセントを達成できますが、適合率は極端に低くなります。この両者のバランスを1つの数値で確認したいときに使うのがF1値です。F1値は適合率と再現率の調和平均で、\( F_1 = \dfrac{2 \times \text{Precision} \times \text{Recall}}{\text{Precision}+\text{Recall}} \) という式で計算されます。算術平均ではなく調和平均を使うのは、適合率か再現率のどちらか一方が極端に低い場合にその低さをより厳しく反映させるためです。先ほどの例では、適合率25.9パーセント、再現率70パーセントからF1値はおよそ0.378と計算できます。

ここで押さえておきたいのが、分類モデルの多くは内部的には「陽性である確率」のような連続値を出力しており、その確率をどこで区切って2値に変換するかという閾値を人間が別途決めているという事実です。ロジスティック回帰(第6章で扱います)であれば標準的には確率0.5を境に分けますが、この0.5に絶対的な根拠はありません。閾値を下げれば再現率が上がる代わりに適合率は下がり、上げればその逆が起こります。つまり閾値の設定は、モデルの性能とは独立した「どちらの誤りをどれだけ許容するか」というビジネス上の意思決定そのものです。閾値の選び方次第で運用結果は大きく変わるため、技術的な後処理ではなく重要な設計判断として扱う必要があります。

ROC曲線とAUC、閾値に依存しない評価軸

閾値の置き方によって適合率や再現率が変わってしまうということは、逆に言えば、特定の閾値を決める前にモデルそのものの性能を評価する方法があれば便利だということでもあります。それがROC曲線(Receiver Operating Characteristic curve)とAUC(Area Under the Curve)です。ROC曲線は、閾値を1から0まで連続的に動かしながら、真陽性率(TPR、再現率と同じ計算式)を縦軸に、偽陽性率(FPR、実際は陰性なのに陽性と誤判定した割合、\( \text{FPR} = \dfrac{FP}{FP+TN} \))を横軸にとってプロットした曲線です。優れたモデルほど偽陽性率を低く保ったまま真陽性率を高くできるため、曲線は左上に大きく膨らむ形になり、ランダムに予測するだけのモデルでは対角線に一致します。

AUCはこのROC曲線の下側の面積を数値化したもので、0.5であればランダムな予測と変わらず、1.0に近いほど完璧な分類ができていることを意味します。特定の閾値に固定せずに「あらゆる閾値を通じてモデルが陽性と陰性をどれだけうまく順位付けできているか」を1つの数値で表せる点が優れており、複数のモデル候補そのものの優劣を比較する指標として広く使われています。実際にモデルを学習させ、AUCを計算してみます。

from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_curve, roc_auc_score

# 陽性(不正など)が全体の1割程度の不均衡データを模擬的に作成
X, y = make_classification(
    n_samples=5000, n_features=10, weights=[0.9, 0.1], random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, stratify=y, random_state=42
)

model = LogisticRegression(max_iter=1000).fit(X_train, y_train)
y_proba = model.predict_proba(X_test)[:, 1]  # 陽性である確率

fpr, tpr, thresholds = roc_curve(y_test, y_proba)
auc = roc_auc_score(y_test, y_proba)
print(f"AUC: {auc:.3f}")
ROC曲線が左上に膨らむ形と対角線を比較したグラフ

このコードを実行するとAUCは0.900となります。閾値を1つに決めなくても、モデルが陽性と陰性をどれだけうまく順位付けできているかを1つの数値で把握できるのがAUCの利点です。

ただしAUCにも注意点があります。陽性がごくわずかしか占めない強い不均衡データでは、偽陽性率の分母(実際の陰性の総数)が非常に大きくなり、偽陽性の絶対数が増えても偽陽性率自体はほとんど変化しません。そのためAUCが高く見えても、実際には適合率が低い(陽性判定に誤りが多く混ざる)モデルになっていることが起こり得ます。こうした強い不均衡のもとでは、適合率と再現率の関係を直接プロットしたPR曲線(Precision-Recall Curve)の方がモデルの実態を敏感に映し出します。不正検知や異常検知のように陽性が数パーセント以下しかないタスクでは、ROC-AUCとあわせてPR曲線も確認することが推奨されます。

閾値を動かすとどうなるか

閾値を動かすと適合率と再現率がどう変化するかを具体的な数値で確認します。先ほどのモデルの確率出力を使い、閾値0.3、0.5、0.7での適合率・再現率・F1値を比較します。

from sklearn.metrics import precision_score, recall_score, f1_score

for th in [0.3, 0.5, 0.7]:
    y_pred_th = (y_proba >= th).astype(int)
    p = precision_score(y_test, y_pred_th)
    r = recall_score(y_test, y_pred_th)
    f1 = f1_score(y_test, y_pred_th)
    print(f"閾値{th}: 適合率={p:.3f} 再現率={r:.3f} F1={f1:.3f}")

実行すると、閾値0.3のように低く設定するほど再現率は高く適合率は低くなり、0.7のように高く設定するほど逆になることが確認できます。この例ではF1値は閾値0.3で0.585、0.5で0.508、0.7で0.369となります。3つの閾値のうち、最も低い0.3のときにF1値が最大になっているということです。この例のように陽性が1割程度しかないデータでは、モデルが出す確率そのものが全体的に低めに寄るため、F1値を最大にする閾値も0.5より低い側に来ることがよくあります。ここでも0.5という値に特別な根拠がないことが確認できますが、F1値が最大になる閾値が必ずしもビジネス上の最適解とは限りません。どちらのコストが大きいかを踏まえ、F1値とは別の基準で閾値を決める場面も少なくありません。

多クラス分類の指標、macro平均とmicro平均の違い

ここまでは2値分類を前提に説明してきましたが、商品カテゴリの自動分類や顧客セグメントの判定のように3クラス以上を扱う多クラス分類も実務では頻繁に登場します。多クラスでは適合率や再現率がクラスごとに計算されるため、1つの数値にまとめる方法としてmacro平均とmicro平均が使われます。macro平均は各クラスの値を単純に算術平均したもので、件数の少ない少数クラスの性能低下も敏感に反映されます。micro平均はすべてのクラスのTP、FP、FNを合算してから計算する方法で、件数の多いクラスの結果に引きずられやすく、2値分類の正解率に近い感覚の指標になります。少数クラスも同じ重みで評価したいならmacro平均、全体としての的中率に近い感覚を知りたいならmicro平均、という使い分けが基本です。なおclassification_reportの出力に並ぶのは、通常の多クラス分類ではmacro avgと、クラスの件数で重み付けしたweighted avgの2つです。micro avgは、すべてのクラスを合算して計算すると正解率と一致してしまうため、多クラス分類では正解率の行に置き換えられて表示されません(マルチラベル分類や、評価対象のラベルを一部に絞った場合には表示されます)。

評価指標とビジネスKPIをつなぐ、誤りにコストを割り当てる

第1章で述べたとおり、機械学習プロジェクトの最終的な成功は、AUCやF1値が高いことではなく事業の成果に結びつくことで測られます。適合率や再現率、AUCといった指標はあくまで技術的な中間指標であり、これらを事業成果に橋渡しする実践的な方法が、偽陽性(FP)と偽陰性(FN)のそれぞれに金額を割り当て、モデルの運用によって発生する期待コストを見積もるという考え方です。

クレジットカード不正利用検知の例に戻ります。不正利用を1件見逃す(FN)損失を平均30万円、正常な取引を誤って停止・確認する(FP)対応コストを5,000円と仮定します。学習したモデル(TP=70、FN=30、FP=200、TN=9,700)の期待コストは、FN30件×30万円=900万円とFP200件×5,000円=100万円を合わせて合計1,000万円です。これに対し、先ほど正解率の落とし穴の例として挙げた「すべてを正常と予測する」モデルは、正解率こそ99パーセントと高く見えましたが、不正利用100件をすべて見逃す(FN=100)ため期待コストは100件×30万円=3,000万円に達します。正解率だけ見れば「何もしないモデル」の方が優秀に見えたにもかかわらず、コストに換算すると学習したモデルの方が3倍近く事業への貢献が大きいことが分かります。

適合率・再現率・AUCといった技術指標だけを比較していても、「どちらのモデルを採用すべきか」という経営判断には直結しません。偽陽性・偽陰性それぞれに発生するコストを金額として見積もり、期待コストに換算して比較する工程を挟むことで、技術指標と事業成果の間に橋を架けることができます。この橋渡しの作業自体を、モデル構築と同じ重みを持つ工程として設計に組み込むことが重要です。

この期待コストの枠組みは閾値の選定にもそのまま応用できます。閾値を下げて再現率を高めればFNによる損失は減りますが、FPによる対応コストは増えます。閾値を動かしながら期待コストを計算し合計が最小になる閾値を探せば、「F1値が最大になる閾値」ではなく「事業コストが最小になる閾値」という経営判断に直結した基準でモデルを運用できます。評価指標の選定から閾値の調整まで、「この数値の変化は事業の損益にどうつながるか」を問い続ける姿勢が、モデルを意思決定に組み込む土台になります。

偽陽性・偽陰性それぞれのコストを積み上げて期待コストを比較する棒グラフ

この章を深めたい方への参考書籍

『評価指標入門』(高柳慎一・長田怜士、技術評論社)は、回帰・分類それぞれの評価指標を、なぜその指標が必要になったのかという背景から丁寧に解説しており、本章で扱ったMSEやAUC、F1値の使い分けを体系的に学び直したい読者に適しています。指標を機械的に計算するだけでなく、その性質を理解したうえでモデル改善にどう活かすかという実務的な視点が本章の内容と重なり、副読本としておすすめできます。

第6章 分類の定番ロジスティック回帰を正しく使う

前章までは、モデルの学習の仕組み(第2章)、過学習と汎化(第3章)、バイアスとバリアンス・正則化(第4章)、モデルの当てはまりをどう数値で評価するか(第5章)という、機械学習全体を貫く共通の土台を扱ってきました。本章からは、実際に業務で使われる代表的なアルゴリズムを1つずつ見ていきます。最初に取り上げるのは、分類タスクで最も広く使われている定番手法のロジスティック回帰です。

ロジスティック回帰は、与信審査や医療診断、解約予測、迷惑メール判定など、答えが「はい」か「いいえ」かに分かれる二値分類の場面で、今なお第一候補に挙がり続けているモデルです。決定木のアンサンブルやニューラルネットワークほど注目を集めることは少ないですが、仕組みが単純で結果の理由を説明しやすく、計算コストも軽いという特性から、実務での採用率は依然として高い水準にあります。名前に「回帰」とついているため誤解されがちですが、分類のためのモデルです。本章ではその仕組みから実務での使い分けまでを整理します。

分類タスクを線形回帰で解いてはいけない理由

顧客が解約するかどうか、融資を返済できるかどうかといった二値の分類問題に取り組むとき、最初に思いつくのは「解約する場合を1、しない場合を0とラベル付けし、通常の線形回帰でこの0と1を予測してしまう」というアイデアです。この発想には、いくつかの見過ごせない欠陥があります。

第一に、線形回帰の予測値は理論上マイナス無限大からプラス無限大までの範囲を取り得ます。0と1の間に収まる保証がないため、出力を「解約する確率」として解釈できません。予測値が1.4や-0.3といった確率としてあり得ない値になることが普通に起こります。

第二に、線形回帰は二乗誤差を最小化するように設計されているため外れ値の影響を強く受けます。0か1のどちらかしか取らないラベルに直線を当てはめようとすると、極端な特徴量を持つデータが1つ混じるだけで境界線全体が引っ張られて動いてしまいます。分類の目的は「境界のどちら側にいるか」の判定であり、境界から離れた点の位置そのものには意味がなく、二乗誤差は分類の目的とかみ合っていません。

第三に、目的変数が0か1かの二値の場合、そのばらつきの構造(ベルヌーイ分布)は、線形回帰の二乗誤差が想定している連続的な誤差の構造と噛み合いません。これらの理由から、分類問題には分類のために設計された別の仕組みが必要になります。その代表がロジスティック回帰です。

シグモイド関数、出力を0から1に押し込める仕組み

ロジスティック回帰の核心にあるのがシグモイド関数(ロジスティック関数)です。まず、線形回帰とまったく同じように、特徴量 \( x_1, x_2, \ldots, x_p \) の重み付き和 \( z = w_1 x_1 + w_2 x_2 + \cdots + w_p x_p + b \) を計算します。この \( z \) はマイナス無限大からプラス無限大までの値を取り得る、線形回帰の予測値そのものです。ロジスティック回帰は、この \( z \) をそのまま予測値として使うのではなく、次のシグモイド関数に通します。

\( \sigma(z) = \dfrac{1}{1+e^{-z}} \)

シグモイド関数は、どんな実数 \( z \) を入力しても出力を必ず0から1の範囲に押し込める性質を持っています。\( z \) が大きなプラスの値になるほど出力は1に近づき、大きなマイナスの値になるほど0に近づきます。\( z=0 \) のときはちょうど0.5になります。グラフに描くと中央付近で急に立ち上がるS字型の曲線になり、これが「シグモイド(S字形)」という名前の由来です。この出力 \( \sigma(z) \) を「陽性クラスに属する確率の推定値」として扱うのが、ロジスティック回帰の基本的な発想です。

ここで改めて強調しておきたいのは、ロジスティック回帰という名前に「回帰」という単語が含まれているにもかかわらず、これは分類のためのモデルだという点です。確率という連続値を推定する仕組みそのものを指して「回帰」という言葉が残っていますが、最終的な用途は「ある閾値(多くの場合0.5)を境に陽性か陰性かを判定する分類器」です。この名前と実態のずれは、初めて学ぶ人がつまずきやすいポイントです。

シグモイド関数のS字カーブと、z=0を境に確率が0から1へ推移する様子を示すグラフ

決定境界という見方、線形の境目で分ける

ロジスティック回帰がどこで陽性と陰性を切り分けているのかを考えると、その仕組みがより具体的に見えてきます。シグモイド関数は \( z=0 \) のときに出力がちょうど0.5になり、\( z \) がプラスなら0.5より大きく、マイナスなら0.5より小さくなります。つまり、予測確率が0.5を上回るかどうかという判定は、実質的に \( z=w_1x_1+\cdots+w_px_p+b \) がプラスかマイナスかという判定に置き換えられます。

この \( z=0 \) という条件式は、特徴量の空間の中で1つの直線(特徴量が2つの場合)、あるいは平面や超平面(3つ以上の場合)を表しています。これを決定境界と呼びます。ロジスティック回帰は、この決定境界を挟んでデータを2つのクラスに分ける線形の分類器だということになります。特徴量同士を掛け合わせた項や二乗の項をあらかじめ追加すれば曲がった境界も表現できますが、素のロジスティック回帰が引く境界線は常に直線(または平面)である、という理解が実務上は重要です。

この性質は長所にも短所にもなります。長所は、データが直線に近い形で2つのクラスに分かれている場合に、少ないパラメータで安定した予測ができることです。短所は、クラス同士が入り組んだ複雑な形で分布している場合、線形の境界だけでは表現しきれず精度が頭打ちになりやすいことです。非線形な境界が必要な場面への対応は、次章のkNNやSVM、その後の決定木のアンサンブルで補っていきます。

2次元の散布図上にロジスティック回帰が引く直線の決定境界を重ねたイメージ

学習の仕組み、交差エントロピーを最小化する

第2章では、モデルの学習とは「損失関数の値をできるだけ小さくするように、パラメータを勾配法で少しずつ調整していく作業」だと説明しました。ロジスティック回帰でも考え方は同じですが、損失関数として線形回帰の二乗誤差ではなく、交差エントロピー(対数損失とも呼ばれます)という別の関数を使います。

なぜ二乗誤差を使わないのかというと、シグモイド関数と二乗誤差を組み合わせた損失関数は、パラメータに対して凸関数にならず、複数の谷(局所的な最小値)を持つ複雑な形状になってしまうためです。これでは勾配法が最良の解にたどり着けているのか保証できません。これに対し、交差エントロピーをシグモイド関数と組み合わせると、損失関数は再び一つの谷しか持たない凸関数になり、勾配法による最適化が安定して進むことが数学的に保証されます。

交差エントロピーの発想は、確率・統計の分野で古くから使われている最尤推定(観測されたデータが実際に起きる確率、すなわち尤度が最も高くなるようにパラメータを選ぶ考え方)そのものです。実際のラベルが1(陽性)であるデータに対しては予測確率 \( \hat{p} \) がなるべく1に近いほど、ラベルが0(陰性)であるデータに対しては予測確率がなるべく0に近いほど、それぞれ損失が小さくなるように設計されています。1つのデータに対する損失は次の式で表されます。

\( L = -\{y\log\hat{p}+(1-y)\log(1-\hat{p})\} \)

この式は、正解ラベル \( y \) が1のときは \( -\log\hat{p} \) だけが残り、\( y \) が0のときは \( -\log(1-\hat{p}) \) だけが残るように作られています。予測確率が正解から大きく外れているほど、対数関数の性質によって損失が急激に大きくなる点も特徴です。全データについてこの損失を平均したものがロジスティック回帰全体の損失関数になり、これを最小化するパラメータを勾配法で探すのが学習の流れです。数式を暗記する必要はありませんが、「予測が外れるほど罰則が急激に重くなる、確率の当てはまりの良さを測るものさし」という理解があると、次章以降で他の分類モデルの損失関数を見るときにも応用が利きます。

係数の解釈、オッズ比という読み方

ロジスティック回帰が実務で重宝される最大の理由は、学習後の係数を見るだけで、「どの特徴量が結果にどう影響しているか」を数値として語れる点にあります。この解釈の鍵になるのがオッズとオッズ比です。

オッズとは、ある事象が起きる確率 \( p \) と起きない確率 \( 1-p \) の比、\( \dfrac{p}{1-p} \) のことです。競馬などの賭け事で使われる「オッズ」と同じ考え方で、陽性確率が0.8ならオッズは \( 0.8/0.2=4 \) となり、「陽性は陰性の4倍起きやすい」と読みます。ロジスティック回帰の \( z=w_1x_1+\cdots+w_px_p+b \) は、実はこのオッズの対数(対数オッズ、ロジットとも呼ばれます)を表しています。

\( \log\left(\dfrac{p}{1-p}\right) = w_1x_1+w_2x_2+\cdots+w_px_p+b \)

ここから、ある特徴量 \( x_j \) の値が1単位増えたとき、対数オッズがちょうど係数 \( w_j \) の分だけ変化することが分かります。対数の世界での「足し算」は元のオッズの世界では「掛け算」に対応するため、オッズそのものは \( e^{w_j} \) 倍になります。この \( e^{w_j} \) をオッズ比と呼びます。オッズ比が1より大きければその特徴量は陽性側の確率を押し上げる方向に、1より小さければ押し下げる方向に働き、ちょうど1であれば影響がないと解釈できます。

たとえば与信審査のモデルで「延滞履歴あり」という特徴量の係数が0.7だった場合、オッズ比は \( e^{0.7}\approx2.0 \) となり、「延滞履歴があると債務不履行のオッズがおよそ2倍になる」と説明できます。この「符号で方向が分かり、大きさで影響の強さが定量的に語れる」という性質が、決定木のアンサンブルやニューラルネットワークに対するロジスティック回帰の実務上の強みです。

ただし、この解釈には注意点が2つあります。1つ目は、特徴量ごとに単位や尺度が異なると係数の大きさをそのまま比べられないという点です。「年収(円単位)」と「年齢(歳単位)」のように尺度が大きく異なる特徴量が混ざっていると、係数の絶対値の大小は「影響の強さ」ではなく「単位の大小」を反映しているだけになりがちです。複数の特徴量を横並びで比較したい場合は、学習前に全特徴量を標準化(平均0、分散1に変換)しておく必要があります。2つ目は、特徴量同士に強い相関(多重共線性)がある場合、個々の係数の値が不安定になり、解釈が本来の意味からずれることがある点です。具体的な対処は、本シリーズの回帰分析と統計モデリングの実践ガイドで扱います。

ロジスティック回帰の係数は「オッズ比」に変換して初めて、業務担当者に説明できる言葉になります。係数をそのまま報告するのではなく、\( e^{w_j} \) を計算し、「この要因があると発生しやすさが何倍になるか」という形で伝えることが、モデルの解釈性を実務価値に変える一手間です。

多クラスへの拡張、one-vs-restとソフトマックス

ここまでは「陽性か陰性か」という二値分類を前提に説明してきましたが、実務では「優良顧客・一般顧客・離反リスク顧客の3区分」のように3つ以上のクラスに分類したい場面も多くあります。ロジスティック回帰を多クラス分類へ拡張する方法は大きく2通りあります。

1つ目はone-vs-rest(一対他)と呼ばれる方法です。クラスが3つあるなら「クラスAか、それ以外か」「クラスBか、それ以外か」「クラスCか、それ以外か」という3つの二値分類器を学習させ、新しいデータが来たときは3つの確率を比較して最も高いクラスを採用します。仕組みが単純で、二値のロジスティック回帰をそのまま使い回せる利点があります。

2つ目はソフトマックス回帰(多項ロジスティック回帰)と呼ばれる方法です。こちらはクラスごとに個別のモデルを立てるのではなく、すべてのクラスを1つの枠組みの中で同時に扱います。各クラス \( k \) に対応する線形結合 \( z_k \) をそれぞれ計算し、次のソフトマックス関数によって、全クラスの確率の合計がちょうど1になるように正規化します。

\( P(y=k)=\dfrac{e^{z_k}}{\sum_{j}e^{z_j}} \)

ソフトマックス関数は、シグモイド関数を2クラスから多クラスへ一般化したものと捉えると理解しやすく、クラスが2つしかない特殊なケースで計算するとシグモイド関数と同じ式に帰着します。scikit-learnのLogisticRegressionクラスは、多クラスのデータを渡すと使用するソルバー(最適化アルゴリズム)に応じて内部的にソフトマックス回帰を自動的に選択します。かつては引数multi_classで「ovr」と「multinomial」を明示的に切り替える仕様でしたが、現行のバージョンでは、標準的なソルバーは多クラスのデータに対してソフトマックス回帰(multinomial loss)を最適化します。多クラスを直接扱えないのはliblinearソルバーで、この場合だけ一対他の方式になります。詳細は利用中のバージョンの公式ドキュメントで確認してください。

実務での位置づけ、まずロジスティック回帰から始める定石

予測精度だけを追い求めるなら、決定木のアンサンブル(第8章)やニューラルネットワークの方が高い性能を出せる場面は少なくありません。それでもロジスティック回帰が実務のベースラインとして選ばれ続けているのには理由があります。

  • 学習と推論の計算コストが軽く、大規模なデータでも短時間で結果が出せる
  • 係数とオッズ比という形で、予測の根拠を数値で説明できる
  • 過学習しにくく、データ量が少ない場合でも比較的安定した結果になりやすい
  • 実装や運用が成熟しており、監視や再学習のコストを見積もりやすい

特に解釈性の高さが重要になるのが、判断の根拠を利害関係者に説明する責任を伴う業務です。融資の可否を決める与信審査では、なぜ融資を断られたのかを申込者に説明できる仕組みが求められ、金融規制の観点からも判断根拠を追跡できることが重視されます。医療診断の補助でも、医師が「どの検査値が結果を左右したのか」を確認できることが不可欠です。保険の引受査定や採用選考のスコアリングなど人の処遇に関わる判断でも、精度と同じかそれ以上に説明責任が問われます。

こうした背景から、新しい分類課題に取り組む際にはまずロジスティック回帰でベースラインを作り、その精度と解釈のしやすさを基準に、より複雑なモデルを試す価値があるかを判断するという進め方が定石になっています。決定木のアンサンブルで精度が数パーセント改善しても、その差が業務上の意思決定を変えるほどかは、この基準点があって初めて判断できます。

確率のキャリブレーション、意思決定に使うときの注意

ロジスティック回帰が出力する確率は、多くの場合そのまま業務の意思決定に使われます。たとえば「解約確率が30パーセントを超える顧客には割引クーポンを提示する」といった運用や、「デフォルト確率と融資額から期待損失を計算し、一定額を超える申込みは謝絶する」といった期待値ベースの判断です。ここで重要になるのが、モデルが出力する確率がどれだけ現実の発生頻度と一致しているか、というキャリブレーション(較正)の問題です。

モデルが「解約確率70パーセント」と判定した顧客を100人集めたとき、実際に解約するのがおよそ70人前後であれば、その確率出力はよく較正されていると言えます。しかし、学習方法やデータの偏り(不均衡データなど)によっては、predict_probaが返す値が実際の発生頻度からずれることがあります。ロジスティック回帰は交差エントロピーを損失関数として学習しているため比較的崩れにくいとされますが、正則化の強さやクラスの不均衡が大きい場合には系統的にずれることもあり、過信は禁物です。

確率をそのまま期待値計算に使う前には、キャリブレーションカーブ(予測確率を区間ごとに区切り、実際の陽性割合と比較したグラフ)を確認し、必要であればプラット・スケーリング(モデルが出したスコアにロジスティック回帰を当てはめ直して確率に変換する手法)やアイソトニック回帰(予測確率と実際の陽性割合の対応を、単調性だけを保った階段状の関数で引き直す手法)といった較正の手法を適用することが推奨されます。不均衡データにおける確率の扱いは第11章で改めて取り上げますので、ここでは「predict_probaの数値を鵜呑みにせず、実際の発生頻度と照らし合わせる習慣を持つ」という点を押さえておいてください。

予測確率と実際の陽性割合を比較するキャリブレーションカーブのグラフ

ここまでの内容をコードで確認します。まず、与信審査を想定した合成データでロジスティック回帰を学習させ、係数とオッズ比を確認します。

import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split

# 与信審査を想定した合成データ(延滞履歴、年収、借入件数から
# 債務不履行(1)か正常返済(0)かを予測する)
rng = np.random.default_rng(0)
n = 2000
delinquency = rng.integers(0, 2, n)          # 過去の延滞履歴の有無
income = rng.normal(500, 150, n)             # 年収(万円)
num_loans = rng.integers(0, 6, n)            # 現在の借入件数

z = 0.9 * delinquency - 0.004 * income + 0.35 * num_loans - 1.0
prob_default = 1 / (1 + np.exp(-z))
y = rng.binomial(1, prob_default)

X = pd.DataFrame({
    "delinquency": delinquency,
    "income": income,
    "num_loans": num_loans,
})

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=0, stratify=y
)

# 係数を横並びで比較できるよう、標準化してから学習する
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# LogisticRegressionはデフォルトでL2正則化が有効で、
# 正則化の強さは引数Cの逆数で決まる(Cが小さいほど正則化が強くなる)
model = LogisticRegression(C=1.0, random_state=0)
model.fit(X_train_scaled, y_train)

coef_table = pd.DataFrame({
    "feature": X.columns,
    "coef(標準化後)": model.coef_[0],
    "odds_ratio": np.exp(model.coef_[0]),
})
print(coef_table)
print("切片:", model.intercept_[0])

この結果、delinquency(延滞履歴)の係数はプラス(0.352、オッズ比1.42)となり、「延滞履歴があると債務不履行のオッズが何倍になるか」という形でそのまま業務説明に使えます。incomeの係数はマイナス(-0.514、オッズ比0.60)で、年収が高いほど債務不履行のオッズが下がるという常識的な関係が確認できます。

ここで注目したいのは、標準化後の係数の絶対値を並べると num_loans(0.578)、income(-0.514)、delinquency(0.352)の順になり、データを生成する際に最も大きな係数を与えた延滞履歴が、3つの中で最も小さくなっている点です。延滞履歴は0か1しか取らない二値変数で標準偏差が0.5しかないため、標準化を通すと影響の見え方が縮みます。これは本章の前半で述べた「単位が違う特徴量の係数をそのまま比べてはいけない」という注意の実例そのものです。標準化は係数を比較可能にするための手続きですが、比較できるようになるのは「その特徴量が1標準偏差動いたときの影響」であって、「その特徴量が業務上どれだけ重要か」ではない、という点を押さえておく必要があります。

続いて、学習済みモデルのpredict_probaを使って確率を取り出し、閾値を変えながら意思決定に落とし込む例を示します。

# predict_probaは各クラスに属する確率を返す(列の順序はmodel.classes_に対応)
proba = model.predict_proba(X_test_scaled)
default_proba = proba[:, 1]  # 債務不履行(クラス1)の確率

result = X_test.copy()
result["default_proba"] = default_proba
result["actual"] = y_test

# 閾値0.5でそのまま判定した場合と、より保守的な閾値0.3で判定した場合を比較
result["judge_0.5"] = (result["default_proba"] >= 0.5).astype(int)
result["judge_0.3"] = (result["default_proba"] >= 0.3).astype(int)

print("閾値0.5で謝絶と判定した件数:", result["judge_0.5"].sum())
print("閾値0.3で謝絶と判定した件数:", result["judge_0.3"].sum())

# 期待損失に基づく判断の例(融資額100万円、デフォルト時の損失率60%と仮定)
loan_amount = 100
loss_given_default = 0.6
result["expected_loss"] = result["default_proba"] * loan_amount * loss_given_default

# 期待損失が15万円を超える申込みは謝絶する、という業務ルールの例
result["reject_by_expected_loss"] = result["expected_loss"] > 15
print(result[["default_proba", "expected_loss", "reject_by_expected_loss"]].head())

閾値を0.5から0.3に下げると、謝絶件数は増える一方、見逃していた債務不履行を捕捉できる可能性が高まります。どちらの閾値が適切かは謝絶による機会損失とデフォルトによる損失額のバランスで決まり、第5章の評価指標とあわせて業務側と合意しておくべき論点です。期待損失の計算例からも分かるとおり、predict_probaが返す確率は単なる分類の中間値ではなく、それ自体が金額換算の意思決定に使える情報だという点が実務上の価値です。

統計学の回帰分析(GLM)とのつながり

ここまで機械学習の一手法として説明してきましたが、統計学の側から見るとロジスティック回帰は一般化線形モデル(GLM)と呼ばれる枠組みの一種として古くから研究されてきたモデルです。GLMは、目的変数の確率分布(二項分布、ポアソン分布など)と、線形予測子を確率分布のパラメータへ変換するリンク関数を指定することで、線形回帰を様々な種類のデータへ拡張する統計学の体系です。ロジスティック回帰は、二項分布とロジット(対数オッズ)のリンク関数を採用したGLMの特殊ケースにあたります。

この統計学側の視点に立つと、係数の標準誤差や信頼区間、p値による有意性検定など、機械学習の文脈だけでは扱いきれない道具立てが揃っており、係数の解釈により厳密な統計的根拠を与えられます。GLMの枠組みやポアソン回帰、負の二項回帰との関係は、本シリーズの回帰分析と統計モデリングの実践ガイドで詳しく扱う予定です。本章では、両分野の橋渡し役のようなモデルである、という位置づけだけ押さえておいてください。

この章を深めたい方への参考書籍

『Pythonで動かして学ぶ!あたらしい機械学習の教科書』(伊藤真、翔泳社):ロジスティック回帰を含む代表的なアルゴリズムについて、数式の導出とscikit-learnによる実装を対応づけながら解説しており、本章で触れたシグモイド関数や交差エントロピーの計算過程を手を動かして確認したい読者に向いています。決定境界の可視化や多クラス分類の実装例も収録されており、次章以降で扱うkNNやSVMとの比較にも活用できます。

第7章 距離とマージンで分けるkNNとサポートベクターマシン

前章では、確率を出力する線形の分類手法としてロジスティック回帰を扱いました。ロジスティック回帰は、特徴量の線形結合をシグモイド関数に通すことでクラスに属する確率を推定するという、明快な仕組みを持つ手法でした。本章では、これとはまったく発想の異なる2つの分類手法、k最近傍法(kNN)とサポートベクターマシン(SVM)を扱います。

kNNは「近くにあるデータは似た性質を持つはずだ」という素朴な直感をそのままアルゴリズムにした手法です。数式による確率モデルを立てるのではなく、手元のデータそのものを地図として使い、新しいデータ点の近くにどのクラスのデータが多いかで判定します。一方のSVMは、2つのクラスを分ける境界線(超平面)を引く際に、境界とデータの間にできるだけ大きな余裕(マージン)を確保しようとする、幾何学的な発想に基づく手法です。両者とも、勾配ブースティング木(GBDT)が主役を占める現在の実務では中心的な選択肢ではなくなりつつありますが、距離やマージンという考え方を理解しておくことは、分類問題への視野を広げるうえで価値があります。

kNN、近いものは似ているという発想

k最近傍法(k-Nearest Neighbors、kNN)は、分類問題における最もシンプルな手法の1つです。学習段階で行うことは、訓練データをそのまま記憶しておくことだけです。新しいデータ点を分類する際には、その点から距離が近い順にk個の訓練データ(近傍点)を探し、その近傍点が最も多く属しているクラスを、多数決によって新しいデータの予測クラスとします。例えば、ある企業が新規顧客の解約リスクを予測したい場合、過去に解約した顧客・継続した顧客それぞれの利用パターンを訓練データとして持っておき、新規顧客に最も似た利用パターンを持つk人の過去顧客のうち、解約者が多数を占めるかどうかで予測を行う、という使い方になります。

回帰問題にもkNNは応用できます。k最近傍法による回帰(KNeighborsRegressor)では、多数決の代わりに、近傍k個のデータが持つ目的変数の値を平均して予測値とします。住宅価格の予測であれば、立地や広さが似ている物件k件の価格を平均する、という考え方に相当します。分類でも回帰でも、モデルの内部にパラメータ(係数など)を持たず、予測のたびに訓練データ全体を参照するという点が、他の多くの機械学習アルゴリズムとの大きな違いです。

2次元の散布図上で、新しいデータ点(★印)から近い順にk個の訓練データ点を円で囲み、その中の多数派クラスの色に★印が塗られる様子を示したイメージ図

kの選び方、距離尺度と標準化

kNNを使ううえで最初に決めるべきハイパーパラメータが、近傍点の個数kです。このkの値は、モデルの複雑さを直接左右します。k=1に設定すると、最も近い1点だけを見て判定するため、訓練データの細部、ノイズも含めた個々の点の並びに忠実な、非常に複雑でギザギザした決定境界ができあがります。これは第3章で扱った過学習そのものであり、訓練データに対する精度は高くなる一方で、未知のデータに対する汎化性能は低下しがちです。反対にkを大きくしすぎると、判定のたびに多数の近傍点を平均することになるため、決定境界は滑らかを通り越して単調になり、極端な場合には訓練データ全体の多数派クラスをそのまま返すだけの単調な予測に近づいていきます。これは第4章で扱ったバイアスが大きい状態、つまり未学習(underfitting)にあたります。実務では、交差検証(第12章で扱います)によって複数のkを試し、検証データでの性能が最もよいkを選ぶのが基本的な進め方です。2クラス分類では、多数決が同数で割れることを避けるため、kには奇数を選ぶことが多いという実務上の工夫もあります。

kNNのもう1つの重要な要素が、距離をどう測るかです。最も一般的なのはユークリッド距離(直線距離)ですが、各次元の差の絶対値の和を取るマンハッタン距離や、両者を一般化したミンコフスキー距離もよく使われます。さらに、単純な多数決ではなく、近い点ほど投票の重みを大きくする重み付き多数決(scikit-learnではweights=’distance’と指定します)を使う場合もあります。

距離を扱ううえで欠かせないのが、特徴量の標準化です。kNNは特徴量間の距離をそのまま判定に使うため、特徴量ごとの数値の桁が大きく異なると、桁の大きい特徴量が距離計算を実質的に支配してしまいます。例えば、顧客の「月間利用回数(数十件程度)」と「年収(数百万円単位)」を同時に特徴量として使う場合、標準化を行わずにユークリッド距離を計算すると、年収の差がわずか1万円動くだけで、利用回数の差が数十件動くよりも距離への影響が大きくなってしまいます。これでは、行動パターンが似ている顧客同士を正しく近傍として認識できません。この問題を避けるため、kNNを使う際には、StandardScalerなどによる標準化を前処理として必ず行う必要があります。

import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import NearestNeighbors

# 特徴量: [月間利用回数, 年収(円)]。桁の異なる2つの特徴量を用意する
X = np.array([
    [5.0, 8_000_000.0],   # 顧客A: 利用回数は少ないが年収は高い
    [90.0, 6_010_000.0],  # 顧客B: 利用回数が非常に多い
    [50.0, 4_000_000.0],  # 顧客C: 利用回数は中程度
])
query = np.array([[6.0, 6_000_000.0]])  # 新規顧客(利用回数はAに近い)

# 標準化なしでの最近傍
nn_raw = NearestNeighbors(n_neighbors=1).fit(X)
dist_raw, idx_raw = nn_raw.kneighbors(query)
print(f"標準化なしの最近傍: 顧客{['A','B','C'][idx_raw[0][0]]}, 距離={dist_raw[0][0]:.1f}")

# 標準化ありでの最近傍
scaler = StandardScaler().fit(X)
nn_scaled = NearestNeighbors(n_neighbors=1).fit(scaler.transform(X))
dist_scaled, idx_scaled = nn_scaled.kneighbors(scaler.transform(query))
print(f"標準化ありの最近傍: 顧客{['A','B','C'][idx_scaled[0][0]]}, 距離={dist_scaled[0][0]:.2f}")

このコードを実行すると、標準化なしでは新規顧客の最近傍として顧客Bが選ばれます。これは、絶対値の大きい年収の項(差が約1万円=10,000)が距離計算をほぼ支配し、利用回数の差(約84件)がかき消されてしまうためで、利用回数が大きく異なっていても年収が近い顧客Bが「似ている」と判定されてしまう、直感に反する結果です。標準化を行うと、利用回数と年収がそれぞれ平均0・分散1の同じ尺度に揃えられるため、利用回数が新規顧客に近い顧客Aが正しく最近傍として選ばれるようになります。この例が示すとおり、kNNにおける標準化は精度向上のためのおまけの工夫ではなく、正しい距離計算を成立させるための前提条件です。

kNNの長所と短所

kNNの際立った特徴は、学習コストがほぼゼロである一方、予測コストが大きいという、他の多くの機械学習手法とは逆の性質を持つ点です。学習段階では訓練データを記憶するだけなので、fit()の実行はほぼ一瞬で終わります(この性質から、kNNは怠惰学習(lazy learning)と呼ばれることがあります)。ところが予測段階では、新しいデータ点1件ごとに訓練データ全件との距離を計算し、その中からk個の近傍を探し出す必要があります。訓練データの件数が数百万件規模になると、1件を予測するたびに毎回これだけの計算を行うことになり、リアルタイム性が求められるシステムへの組み込みでは無視できない負荷になります(KD-treeやBall-treeといったデータ構造を使うことで探索を高速化できますが、後述する高次元データではその効果も薄れます)。

もう1つの弱点が、次元の呪いと呼ばれる現象です。特徴量の数(次元)が増えていくと、高次元空間では、任意の2点間の距離がどれも似たような値に近づいていき、「近い」と「遠い」の区別そのものが意味を失っていくことが知られています。特徴量が数十、数百に及ぶような高次元のデータにkNNをそのまま適用すると、本来近傍とすべきでない点まで距離的に大差がなくなってしまい、予測精度が大きく劣化します。この問題への対処としては、特徴量選択によって次元数を絞り込む、あるいは第9章で扱う主成分分析(PCA)などの次元削減手法によって本質的な情報を保ったまま次元を圧縮してからkNNを適用する、という工夫が有効です。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
from sklearn.neighbors import KNeighborsClassifier

X, y = make_moons(n_samples=200, noise=0.3, random_state=0)

x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 300),
                      np.linspace(y_min, y_max, 300))

fig, axes = plt.subplots(1, 3, figsize=(13, 4))

for ax, k in zip(axes, [1, 15, 60]):
    clf = KNeighborsClassifier(n_neighbors=k).fit(X, y)
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
    ax.contourf(xx, yy, Z, alpha=0.3, cmap='coolwarm')
    ax.scatter(X[:, 0], X[:, 1], c=y, cmap='coolwarm', edgecolors='k', s=20)
    ax.set_title(f'k={k}(訓練データ正解率={clf.score(X, y):.3f})')

plt.tight_layout()
plt.show()

三日月型に入り組んだ2クラスのデータに対して、k=1では境界が訓練データの1点1点に忠実に張りつき、ギザギザに入り組んだ複雑な形状になります。訓練データに対する正解率は100%に達しますが、これは典型的な過学習の兆候です。k=15にすると境界はかなり滑らかになり、訓練正解率はやや下がるものの、未知のデータに対してはむしろ安定した予測が期待できる形になります。k=60まで大きくすると、境界はほぼ直線に近い単調な形状になり、三日月型の入り組んだ構造を捉えきれなくなって、訓練正解率も一段と低下します。このように、kの値を動かしながら決定境界の複雑さの変化を観察すると、過学習と未学習の間でちょうどよい複雑さを探すという、モデル選択の基本的な考え方を具体的に確認できます。

SVMの発想、マージン最大化

サポートベクターマシン(Support Vector Machine、SVM)は、2つのクラスを分ける境界線(2次元なら直線、一般には超平面)を引くための手法です。仮に2つのクラスが完全に分離できるデータだとすると、両者を正しく分ける境界線の引き方は、実は無数に存在します。わずかに傾きや位置を変えても、訓練データを正しく分類できる境界線は他にいくつも見つかるはずです。SVMは、この無数にある候補の中から、境界線と両クラスの最も近いデータ点との距離(マージン)が最大になる、ただ1つの境界線を選び出します。

マージンを最大化するという発想の背景には、境界線に余裕を持たせておくほど、多少のノイズを含む新しいデータに対しても頑健に分類できるはずだ、という考え方があります。境界ぎりぎりに引かれた線は、訓練データではたまたま正しく分類できていても、新しいデータが少しでも境界線側に寄れば誤分類してしまう危険があります。境界とデータの間に十分な余白を確保しておくことで、この危険を減らそうというのがSVMの基本思想です。

このマージンの決定に直接関わるのは、境界に最も近い位置にある一部のデータ点だけです。この点はサポートベクターと呼ばれ、SVMという名前の由来になっています。サポートベクター以外のデータ点は、境界線から離れた位置にあるため、それらの点が多少動いたり増えたりしても、境界線の位置には影響しません。少数の際どいデータ点だけが境界の位置を決定するという性質は、SVMを理解するうえで中心となる考え方です。

2つのクラスの散布図に、マージン最大化により選ばれた境界線と、その両側にある2本の平行なマージン境界線、境界線上に位置するサポートベクター(点を四角で強調)を描いた図

ソフトマージン、Cパラメータの意味

実務で扱うデータの多くは、2つのクラスが完全にきれいに分離できるとは限りません。ノイズを含むデータや、両クラスの分布が一部重なり合っているデータでは、どのように境界線を引いても、必ず何らかの誤分類が生じてしまいます。このような状況に対応するため、SVMには、一定の誤分類やマージン侵害をあえて許容する、ソフトマージンという考え方が導入されています。

ソフトマージンの許容度を調整するのが、正則化パラメータCです。Cを大きく設定すると、誤分類やマージン侵害に対する罰則が強くなるため、モデルは訓練データをできるだけ正確に分類しようとし、結果としてマージンは狭くなり、訓練データの細部に適合した複雑な境界線になります。これは第4章のバイアスとバリアンスの整理で言えば、バリアンスが大きい(過学習寄りの)状態です。反対にCを小さく設定すると、多少の誤分類やマージン侵害を許容してでも、広いマージンを優先するようになり、境界線は滑らかで単純な形状になります。これはバイアスが大きい(未学習寄りの)状態に近づきます。Cはkの選び方と同様、交差検証によって適切な値を探るべきハイパーパラメータです。

カーネルトリック、非線形の境界を作る仕組み

ここまでのSVMは、直線(線形)で境界を引くことを前提にしていました。しかし実際のデータには、直線では到底分離できない、入り組んだ形のクラス分布がしばしば現れます。例えば、一方のクラスが円の内側に、もう一方が円の外側に分布しているようなデータは、どれだけ工夫しても直線1本では分離できません。

この課題に対するSVMの答えが、カーネルトリックと呼ばれる仕組みです。基本的な発想は、元の特徴量空間ではうまく分離できないデータも、より高次元の特徴量空間に写像すれば、線形に分離できるようになる場合がある、というものです。例えば円状に分布するデータも、特徴量を2乗するなどして次元を1つ追加した空間に写像すると、平面で切り分けられるようになることがあります。カーネルトリックの巧妙な点は、この高次元空間への写像を実際に計算する必要がないところにあります。SVMの計算では、データ点同士の内積(類似度)さえ求まればよいという性質があり、カーネル関数と呼ばれる関数を使うことで、高次元空間での内積を、元の低次元の特徴量から直接計算できてしまいます。これにより、実際に高次元へ写像する計算コストを払うことなく、非線形な境界を扱えるようになります。

実務で最もよく使われるカーネルは、RBFカーネル(Radial Basis Function、ガウスカーネルとも呼ばれます)です。RBFカーネルは無限次元の特徴量空間への写像に相当し、事実上どのような形の境界も表現できる柔軟性を持ちます。RBFカーネルにはgammaというハイパーパラメータがあり、1つのデータ点が周囲にどこまで影響を及ぼすかを制御します。gammaを大きくすると、1つのデータ点の影響範囲が狭くなるため、個々の点に忠実な複雑で入り組んだ境界になり、過学習の危険が高まります。反対にgammaを小さくすると、1つのデータ点の影響範囲が広がるため、境界は滑らかで単純になり、線形に近い形状に近づいていきます。CとgammaはSVMの表現力を決める2つの主要なハイパーパラメータであり、実務ではグリッドサーチなどによって両者を組み合わせて調整します(この調整手法は第12章で扱います)。RBFカーネル以外にも、線形カーネル(実質的に線形SVMと同じ)、多項式カーネル、シグモイドカーネルなどが用意されていますが、まず試すべき非線形カーネルとしてはRBFが実務での標準的な選択です。

円状に分布する2クラスのデータ(左)と、それを高次元空間に写像することで平面で線形に分離できるようになる様子(右)を並べた概念図
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
from sklearn.svm import SVC

X, y = make_moons(n_samples=200, noise=0.3, random_state=0)

x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 300),
                      np.linspace(y_min, y_max, 300))

fig, axes = plt.subplots(2, 2, figsize=(9, 8))
params = [(0.1, 0.1), (0.1, 10), (100, 0.1), (100, 10)]

for ax, (C, gamma) in zip(axes.ravel(), params):
    clf = SVC(kernel='rbf', C=C, gamma=gamma).fit(X, y)
    Z = clf.decision_function(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
    ax.contourf(xx, yy, Z, levels=20, alpha=0.3, cmap='coolwarm')
    ax.contour(xx, yy, Z, levels=[0], colors='black', linewidths=1.5)
    ax.scatter(X[:, 0], X[:, 1], c=y, cmap='coolwarm', edgecolors='k', s=20)
    ax.set_title(f'C={C}, gamma={gamma}(正解率={clf.score(X, y):.3f})')

plt.tight_layout()
plt.show()

C=0.1・gamma=0.1という、正則化が強くマージンの許容範囲も広い組み合わせでは、境界線はほぼ滑らかな曲線にとどまり、訓練データへの適合度は控えめになります。C=100・gamma=10という、正則化が弱くgammaも大きい組み合わせにすると、境界線は個々のデータ点を縫うように複雑に入り組んだ形状になり、訓練データへの適合度は高くなりますが、過学習の懸念が強まります。Cとgammaのどちらか一方だけを動かしても似たような効果、Cを上げれば境界は複雑に、gammaを上げても境界は複雑になる、という傾向が見て取れます。この2つのパラメータは似た方向に効くようでいて、Cが「誤分類への厳しさ」を、gammaが「個々の点の影響範囲」を制御するという、異なる仕組みで境界の複雑さに影響を与えている点を理解しておくと、チューニングの見通しが立てやすくなります。

SVMの長所と短所

SVMは、データ件数が数千から数万件程度の中規模で、かつ特徴量の数が比較的多い(高次元の)データに対して、高い分類性能を発揮することで知られています。かつて文章分類の分野で、単語の出現頻度をベクトル化した数千次元規模の特徴量に対してSVMが好んで使われてきたのも、こうした特性によるものです。

一方で、SVMには実務上注意すべき弱点もあります。第1に、標準のSVC(サポートベクター分類器)は確率を直接出力しません。あくまで境界線からの距離(決定関数の値)によって分類を行う仕組みであるため、ロジスティック回帰のように「このクラスに属する確率は80%」といった値をそのまま得ることはできません。scikit-learnのSVCではprobability=Trueと指定することで確率の推定値を得られますが、これは決定関数の出力に対して別途ロジスティック回帰を当てはめる(プラット・スケーリングと呼ばれる)近似計算であり、追加の計算コストがかかるうえ、predict()が返すクラスとpredict_proba()が示す確率が僅かに矛盾する場合がある点には注意が必要です。近年のscikit-learnでは、SVMで確率が必要な場合はCalibratedClassifierCVでSVCを包んで確率を較正する方法が推奨されつつありますので、新規のコードではそちらを検討するとよいと思います。第2に、計算コストの問題があります。標準的なSVMの学習にかかる計算量は、データ件数の2乗から3乗程度に比例して増加するとされ、データ件数が数十万件を超えるような規模になると、学習に非現実的な時間がかかるようになります。大規模データに線形SVMを適用したい場合は、より効率的なアルゴリズムを採用したLinearSVCや、確率的勾配降下法を用いるSGDClassifierといった代替手段を検討する必要があります。第3に、kNNと同様、SVMも距離や内積に基づいて境界を計算するため、特徴量の標準化が実質的に必須です。スケールの異なる特徴量をそのまま入力すると、特定の特徴量が過大に評価され、正しい境界を学習できません。

実務での位置づけ

勾配ブースティング木(GBDT)が表形式データの分類・回帰において主流を占める現在、kNNやSVMが本番システムの主力モデルとして採用される場面は以前より限られてきています。それでも、この2つの手法を理解しておく価値は失われていません。

kNNは、仕組みが単純で解釈しやすいという性質から、新しい分析に取り組む際の最初のベースラインモデルとして扱いやすい手法です。また、あるデータ点がその周囲のデータからどれだけ「浮いている」かを距離で測るという発想は、第11章で扱う異常検知の考え方の土台にもなっています。SVMについても、データ件数が数百から数万件程度に収まり、かつ特徴量の数が多いデータに対しては、今なお有力な選択肢の1つです。加えて、正常データのみを使って分離境界を学習するOne-Class SVMという派生手法は、不正利用検知や設備の異常検知といった場面で活用されており、これも第11章で改めて取り上げます。GBDTを主力に据えつつ、データの性質や規模に応じてkNN・SVMを比較対象や補完的な選択肢として持っておくことが、現実的な使い分けの姿勢だと言えます。

kNNとSVMは、いずれも特徴量間の距離や内積を直接扱うモデルです。特徴量ごとの数値の尺度をそろえる標準化という前処理を怠ると、桁の大きい特徴量が結果を支配してしまい、モデル本来の性能を引き出せません。この2つの手法を使う際には、標準化を省略できない前提条件として扱う必要があります。

ロジスティック回帰・kNN・SVMの使い分け整理

ここまで見てきたロジスティック回帰(第6章)、kNN、SVMの3手法を、実務で使い分ける際の観点から整理します。

観点ロジスティック回帰kNNSVM
解釈性高い(係数の符号・大きさから要因を説明しやすい)中程度(判定根拠となった近傍事例を提示できる)低い(特に非線形カーネル使用時は境界の意味を説明しにくい)
学習の計算量データ量に対して軽いほぼゼロ(記憶するだけ)データ件数の2〜3乗程度に比例し重くなりやすい
予測の計算量軽い重い(訓練データ全件との距離計算が必要)サポートベクターの数に依存(件数が少なければ軽い)
前処理(標準化)の要否必須ではないが推奨必須必須
確率出力直接得られる近傍の投票割合から近似的に得られる直接は得られない(近似計算が必要)
得意な場面線形に近い関係、要因説明が求められる場面少量データ、局所的なパターンが強いデータ、ベースライン検証中規模かつ高次元のデータ、クラス間に明確な境界があるデータ

実務では、まずロジスティック回帰やGBDTのような扱いやすい手法でベースラインの精度を把握し、そのうえでデータの規模や性質(件数の多寡、次元数、線形分離が可能かどうか)に応じて、kNNやSVMを比較対象として試すという進め方が現実的です。次章では、GBDTを含むアンサンブル学習の基礎となる決定木を扱い、表形式データにおける現在の主流モデルへの理解を深めていきます。

この章を深めたい方への参考書籍

『サポートベクトルマシン』(竹内一郎・烏山昌幸、講談社 機械学習プロフェッショナルシリーズ):マージン最大化とソフトマージン、カーネル法の数理的背景を、導出を追いながら体系的に学べる専門書です。本章で直感的に説明したCやgammaの役割について、その背後にある最適化問題の定式化から理解を深めたい読者に向いており、SVMを実務で本格的にチューニングする際の土台となる1冊です。

第8章 テーブルデータ分析の定番、決定木からランダムフォレストと勾配ブースティングへ

前章では、距離に基づくkNNと、マージン最大化に基づくSVMという発想の異なる2つの分類アルゴリズムを見てきました。本章で扱う決定木とその発展形は、これらとはまったく異なる原理で動きます。「もし〜ならば」という条件分岐を積み重ねる、人間の判断プロセスに近い仕組みです。

単体の決定木は、仕組みが直感的で結果を可視化しやすい一方、癖の強いモデルでもあります。ところがこの決定木を大量に束ね、束ね方を工夫するというアイデアから生まれたランダムフォレストと勾配ブースティング決定木(GBDT)は、企業の顧客データや取引データ、センサーログといった行と列で構成される構造化データ、いわゆるテーブルデータの分析において、現在でも最も頼りにされている手法群です。画像や音声、自然言語の分野では深層学習が主役を担う一方、テーブルデータの実務案件ではGBDT系のアルゴリズムが依然として第一候補に挙がることが多い状況が続いています。

本章では、決定木の基本的な分割の仕組みから出発し、なぜ単体の決定木が過学習しやすいのかを確認したうえで、複数の木を組み合わせるアンサンブル学習の考え方、その2つの代表的な流派であるバギングとブースティング、そしてバギングの代表であるランダムフォレストとブースティングの代表であるGBDT、さらに実務標準となったXGBoostとLightGBMまでを順に整理していきます。

決定木の仕組み、if-thenの分岐でデータを切り分ける

決定木(Decision Tree)は、「特徴量Aが〇〇より大きいか」といった条件による分岐を繰り返し、データを次第に小さなグループへと分割していく手法です。分岐の一つひとつはノード(node)と呼ばれ、これ以上分岐しない末端のノードは葉(leaf)と呼ばれます。分類問題であれば、各葉に多く含まれるクラスがその葉の予測結果になります。

決定木の学習で最も重要なのは、「どの特徴量の、どの閾値で分岐すればデータが最もきれいに分かれるか」を決める分割基準です。分類木では代表的な基準として次の2つが使われます。

  • ジニ不純度(Gini impurity):あるノードに含まれるデータからランダムに2つを取り出したとき、クラスが一致しない確率。ノード内のクラスが1種類だけであれば0になり、クラスが均等に混ざっているほど大きくなります。scikit-learnの決定木は既定でこの基準を使います
  • 情報利得(information gain):分岐前後でエントロピー(データの乱雑さを表す指標)がどれだけ減少したかを表す量。エントロピーもジニ不純度と同様、クラスが1種類に偏っているほど小さく、混ざっているほど大きくなります

決定木の学習アルゴリズムは、あらゆる特徴量と閾値の組み合わせを試し、分岐後の子ノードの不純度が最も小さくなる組み合わせを選び、その分岐を繰り返し適用してツリーを深くしていきます。この手続きは目先の1回の分岐で最良の結果を選ぶ意味で貪欲法(greedy algorithm)と呼ばれ、木全体として最適な分割を保証するものではありませんが広く使われています。

決定木は分類だけでなく、数値を予測する回帰木(regression tree)としても使えます。回帰木では、不純度の代わりに各ノードに含まれるデータの平均値からの誤差の合計(二乗誤差など)を分割基準として用い、誤差ができるだけ小さくなるように分岐を選びます。葉に到達したデータの予測値は、その葉に含まれる訓練データの目的変数の平均値になります。

決定木がif-thenの条件分岐でデータを枝分かれさせていく様子

決定木の魅力と弱点

決定木が長く使われ続けている理由は、その解釈のしやすさにあります。学習済みの木をそのまま図として描き出せば、「年収が〇〇万円以上で、かつ勤続年数が〇年以上であれば承認」というように、モデルがどのような条件で判断を下しているかを専門知識のない人にもそのまま説明できます。線形回帰の係数を読み解くよりも直接的に判断の道筋を追える点は、決定木の最大の強みです。

前処理の負担が小さい点も実務上の利点です。決定木は特徴量の値そのものではなく大小関係(閾値を超えるか超えないか)だけを見て分岐するため、特徴量のスケールをそろえる標準化や正規化がそもそも不要です。外れ値が含まれるデータに対しても、線形モデルほど神経質に対処しなくても学習が進みます。ただし質的変数の扱いには注意が必要です。カテゴリを単に整数に置き換えただけの列は、scikit-learnの決定木では大小関係を持つ数値として解釈されてしまうため、ワンホットエンコーディングなどの変換を行うか、カテゴリ変数をそのまま扱えるLightGBMのような実装を使うのが安全です。

一方で、決定木には無視できない弱点もあります。

  • 過学習しやすい:分岐の深さに制約を設けなければ、訓練データの1件1件を正確に言い当てるまで枝分かれを続け、訓練データにしか通用しない複雑な木ができあがります
  • 不安定である:訓練データがわずかに変わっただけで最初の分岐の特徴量や閾値が変わり、そこから先の木の形が大きく変わることがあります。決定木が高い分散(バリアンス)を持つモデルであることを意味します

過学習を抑える基本的な対策は、木の成長に制約をかける剪定(pruning)です。代表的なパラメータには、木の深さの上限を定めるmax_depth、1つの葉に含まれる最小データ数を定めるmin_samples_leaf、分岐に必要な最小データ数を定めるmin_samples_splitなどがあります。値を小さく(浅く)しすぎると表現力が不足し、大きく(深く)しすぎると過学習が進むため、交差検証で適切な値を探る必要があります。

次のコード例では、scikit-learnで決定木を学習させ、木の構造をそのまま可視化します。

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, plot_tree
import matplotlib.pyplot as plt

data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, test_size=0.3, random_state=0, stratify=data.target
)

# 深さを制限しない木と、深さ3に制限した木を比べる
for max_depth in [None, 3]:
    tree = DecisionTreeClassifier(max_depth=max_depth, random_state=0)
    tree.fit(X_train, y_train)
    train_acc = tree.score(X_train, y_train)
    test_acc = tree.score(X_test, y_test)
    print(f"max_depth={str(max_depth):>4}: "
          f"訓練={train_acc:.3f} テスト={test_acc:.3f} 差={train_acc - test_acc:.3f}")

# 図に描くのは深さ3に制限した木
tree = DecisionTreeClassifier(max_depth=3, random_state=0)
tree.fit(X_train, y_train)

plt.figure(figsize=(16, 8))
plot_tree(
    tree,
    feature_names=data.feature_names,
    class_names=data.target_names,
    filled=True,
    fontsize=8,
)
plt.savefig("decision_tree.png", dpi=150, bbox_inches="tight")

max_depthを指定しない状態で学習させると、訓練データの精度は1.000に達する一方、テストデータの精度は0.906にとどまり、その差は0.094になります。訓練データを完全に覚え込んでいるにもかかわらず、未知のデータでは1割近く外している状態です。max_depth=3に制限すると訓練0.980・テスト0.901となり、差は0.079まで縮まります。ここで注意したいのは、テストデータの精度そのものは0.906から0.901へわずかに下がっている点です。深さの制限は「テスト精度を上げる魔法」ではなく、訓練データへの過剰な適合を抑えて、モデルの振る舞いを説明しやすく安定させるための手当てだと捉えるのが実態に近い理解です。

アンサンブル学習の発想、弱い学習器を束ねる

決定木が単体では不安定になりやすいという弱点を逆手に取って強みに変えるのがアンサンブル学習(ensemble learning)という考え方です。1本の高精度な木を目指すのではなく、それぞれは精度がそこそこの「弱い学習器」を大量に用意し、その予測を組み合わせることで単体よりも高い精度と安定性を実現しようとするアプローチです。

アンサンブル学習には大きく2つの流派があります。

  • バギング(bagging、bootstrap aggregating):訓練データから重複を許してランダムに抽出した複数のサブセットを使い、複数の木を並列に、互いに独立して学習させる方式。各木の予測を多数決(分類)や平均(回帰)で統合します。個々の木の分散(バリアンス)を打ち消し合うことで、全体としての予測を安定させることを狙います
  • ブースティング(boosting):木を1本ずつ順番に、直列に学習させる方式。既存の木の集団がまだ正しく予測できていない部分(誤りや残差)に焦点を当てて、次の木を学習させることを繰り返します。個々の木の偏り(バイアス)を段階的に減らしていくことを狙います

並列に多数決を取るか、直列に誤りを修正するか、という違いは、後述するランダムフォレスト(バギング)とGBDT(ブースティング)の性格の違いにそのまま表れます。バギング系は個々の木を独立に学習させるため計算を並列化しやすく、過学習にも比較的頑健です。ブースティング系は順番に学習させる必要があり時間がかかりやすい一方、少ない本数で高い予測精度に到達しやすい特徴を持ちます。

バギングは「独立に学習させた複数の木の多数決で、ばらつきを打ち消す」考え方であり、ブースティングは「前の木が残した誤りを次の木が順番に埋めていく」考え方です。どちらも決定木という同じ部品を使いながら束ね方の設計思想が異なる点が、ランダムフォレストとGBDTの違いを理解する軸になります。

バギング(並列に多数決)とブースティング(直列に誤りを修正)の違いを対比した図

ランダムフォレスト、バギングと特徴量のランダム選択

ランダムフォレスト(Random Forest)は、バギングの考え方にもう一段のランダム性を加えた手法で、次の2種類のランダム化を組み合わせています。

  • データのブートストラップサンプリング:訓練データから重複を許して同じ件数だけランダムに抽出したサブセットを木の本数分用意し、それぞれ別の木を学習させます
  • 特徴量のランダム選択:各ノードで分岐を決める際、すべての特徴量ではなく、そのつどランダムに選んだ一部の特徴量だけを候補にして最良の分岐を探します

特徴量までランダムに絞り込む理由は、木同士の相関を下げるためです。データだけをランダム化しても、非常に強い予測力を持つ特徴量が1つあれば多くの木がその特徴量を最初の分岐に選んでしまい、木同士が似通ってしまいます。木が似ていると、多数決を取っても個々の誤りが打ち消し合いにくくなります。分岐のたびに特徴量も絞ることで、多様性のある木の集団を作り出しています。

ランダムフォレストはこの多様性のおかげで、単体の決定木よりも過学習しにくく、パラメータの初期設定に対しても比較的頑健に動作します。木の本数を増やしても過学習が急激に進むことは少なく、初手として試しやすいモデルです。

ランダムフォレストには実務上便利な副産物が2つあります。

  • 特徴量重要度(feature importance):各特徴量が、森全体の中でどれだけ不純度の減少に貢献したかを集計した指標。どの特徴量が予測に効いているかを大まかに把握するのに使われます
  • OOB評価(out-of-bag evaluation):ブートストラップサンプリングでは、各木の学習に使われないデータ(平均でおよそ37%程度)が発生します。この使われなかったデータを、その木の簡易的な検証データとして使う評価方法がOOB評価です。別途テストデータを取り分けなくても、訓練の過程で汎化性能を見積もれる点が利点です

次のコード例では、ランダムフォレストを学習させ、特徴量重要度とOOBスコアを確認します。

import pandas as pd
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier

data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, test_size=0.3, random_state=0, stratify=data.target
)

rf = RandomForestClassifier(
    n_estimators=500,      # 木の本数
    max_features="sqrt",   # 候補特徴量数(分類は平方根が既定)
    oob_score=True,        # OOB評価
    random_state=0,
    n_jobs=-1,
)
rf.fit(X_train, y_train)

print(f"OOBスコア: {rf.oob_score_:.3f}")
print(f"テストデータ精度: {rf.score(X_test, y_test):.3f}")

# 特徴量重要度の上位を確認
importances = pd.Series(rf.feature_importances_, index=data.feature_names)
print(importances.sort_values(ascending=False).head(5))

OOBスコアとテストデータでの精度が近い値になっていれば、モデルが訓練データに過度に適合していない目安になります。特徴量重要度の上位に来る項目は、業務上の解釈と照らし合わせ、想定とずれていないかを確認する材料として活用できます。

ランダムフォレストの特徴量重要度を棒グラフで並べたイメージ

勾配ブースティング(GBDT)、残差を次の木が学ぶ仕組み

勾配ブースティング決定木(Gradient Boosting Decision Tree、GBDT)は、ブースティングの考え方を勾配降下法(第2章で扱った、損失関数を減らす方向にパラメータを更新する考え方)と組み合わせた手法です。

直感的な流れは次のとおりです。まず、ごく単純な最初の木(多くの場合、目的変数の平均値を予測するだけの木)を用意します。次に、その予測と実際の値との差、すなわち残差を計算し、2本目の木を元の目的変数ではなくこの残差を予測するように学習させます。1本目の予測に2本目の予測を足し合わせることで、全体の予測は少しだけ実際の値に近づきます。この手順を、指定した本数だけ繰り返していきます。

この仕組みには調整すべき重要なトレードオフが2つあります。

  • 学習率(learning rate):新しく追加する木の予測を、どれだけの割合で全体の予測に反映させるかを決める係数。小さく抑えるほど過学習しにくく細かい調整が利く一方、精度に到達するまでに多くの本数が必要になります
  • 木の本数(n_estimators):学習率を小さくするほど、それを補うために本数を増やす必要があります。学習率を下げて本数を増やす組み合わせのほうが高い精度に到達しやすいことが知られていますが、その分学習時間は長くなります

本数を増やしすぎると、ランダムフォレストとは異なりGBDTは訓練データの残差を追いかけ続けて過学習していきます。そのため、後述する早期停止(early stopping)によって検証データでの性能が改善しなくなった時点で木の追加を打ち切る運用が欠かせません。

XGBoost・LightGBM、実務標準となった理由

勾配ブースティングの考え方自体は以前から知られていましたが、実務での標準的な選択肢として広く定着したのは、XGBoostとLightGBMという2つの実装が登場して以降です。両者は素朴なGBDTの実装に対し、主に次のような改良を加えています。

  • 計算速度の最適化:ヒストグラムベースの分割探索(特徴量の値を事前にビン分割し候補閾値を絞り込む手法)などにより、大量データでも現実的な時間で学習できます
  • 欠損値の自動処理:欠損値を持つデータについて、分岐の際にどちら側の子ノードに送るのが最適かをデータから学習する仕組みを備え、事前の欠損値補完が必須ではありません
  • 正則化項の導入:XGBoostは損失関数に木の複雑さに応じたペナルティ項を組み込んでおり、過学習の抑制を仕組みとして持っています

XGBoostとLightGBMの代表的な違いは木の成長のさせ方にあります。XGBoostや従来型のGBDTの多くは階層ごとに木を横に広げるレベルワイズ(level-wise)成長を採用しているのに対し、LightGBMは不純度の減少が最も大きい葉から優先的に分岐するリーフワイズ(leaf-wise)成長を採用しています。同じ葉の数であれば損失を効率的に減らせるため学習が高速になりやすい一方、木が偏って深くなりやすく、データ件数が少ない場合は過学習に注意が必要です。LightGBMという名前どおり、大規模データでの学習速度の速さが大きな採用理由になっています。

scikit-learnにも、同種のヒストグラムベースGBDTとしてHistGradientBoostingClassifier/HistGradientBoostingRegressorが用意されています。XGBoostやLightGBMほど機能や周辺エコシステムは豊富ではありませんが、追加ライブラリを導入せずscikit-learnの枠組みで完結できる点、欠損値を自動的に扱える点は実務上の利点であり、環境構築を簡素化したい場面での選択肢になります。

次のコード例では、LightGBMのscikit-learn互換インタフェースを使い、検証データを使った早期停止つきで学習を行います。LightGBMの現行バージョンでは、早期停止はfitメソッドの引数ではなく、callbacks引数にlightgbm.early_stoppingを渡す形で指定します。

import lightgbm as lgb
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

data = load_breast_cancer()
X_train, X_valid, y_train, y_valid = train_test_split(
    data.data, data.target, test_size=0.2, random_state=0, stratify=data.target
)

model = lgb.LGBMClassifier(
    n_estimators=1000,     # 上限は多めに設定し早期停止に任せる
    learning_rate=0.05,
    num_leaves=31,         # 葉の数の上限(リーフワイズ成長)
    random_state=0,
)

model.fit(
    X_train,
    y_train,
    eval_set=[(X_valid, y_valid)],
    eval_metric="auc",
    callbacks=[
        lgb.early_stopping(stopping_rounds=50),  # 50回改善がなければ打ち切り
        lgb.log_evaluation(period=100),
    ],
)

print(f"実際に使われた木の本数: {model.best_iteration_}")
print(f"検証データでのベストスコア: {model.best_score_['valid_0']['auc']:.3f}")

上限の本数を1000本と多めに設定し、検証データでの評価指標(この例ではAUC)が50回連続で改善しなければ学習を打ち切る運用です。学習率や本数を手作業で細かく調整しなくても、過学習が進む手前で適切な本数を自動的に選ばせることができます。

なぜテーブルデータでは深層学習よりGBDTが強いことが多いのか

画像認識や自然言語処理の分野では深層学習が圧倒的な成果を上げている一方、テーブルデータを対象とした複数の比較研究では、適切にチューニングされたGBDT系のモデルが、同程度に調整された深層学習モデルと同等かそれ以上の精度を示す傾向が報告されています。理由として指摘されることが多いのは、テーブルデータの特徴量はしばしば互いに無関係な単位や意味を持ち、画像のピクセルのような滑らかな空間的構造を持たないため深層学習の強みが発揮されにくいという点、決定木ベースの分割は特徴量間のスケールの違いに影響を受けにくいという点、そしてテーブルデータでは深層学習が必要とする規模のデータを確保しにくい場面が多いという点です。ただし研究は現在も進行中であり、「テーブルデータには常にGBDTが優れる」と一律に断定できるものではなく、現時点での傾向として理解しておくのが適切です。

チューニングの主要パラメータ早見表

ランダムフォレストとGBDT系のモデルで実務上まず確認すべき主要なパラメータを整理します。

パラメータ役割実務上の目安
n_estimators木の本数ランダムフォレストは多いほど安定しやすく、数百本程度から試す。GBDTは学習率とセットで調整する
learning_rateGBDTで各木の寄与を抑える係数0.01〜0.1程度を起点に、小さくするほど本数を増やして補う
max_depth / num_leaves木1本あたりの複雑さの上限浅すぎれば表現力不足、深すぎれば過学習。交差検証で探索する
min_samples_leaf / min_child_samples1つの葉に必要な最小データ数データ件数が少ない、あるいはノイズが多い場合は大きめに設定して過学習を抑える
早期停止(early stopping)検証データの評価指標が改善しなくなった時点で学習を打ち切るGBDT系では、本数の上限は多めに取り、早期停止に本数の決定を委ねるのが定石

これらを手作業で総当たりに近い形で探索するのは非効率であるため、実務ではグリッドサーチやベイズ最適化といった探索手法と組み合わせて調整するのが一般的です。この点は第12章で改めて扱います。

実務での位置づけ、構造化データの第一候補

決定木単体は解釈のしやすさゆえに、承認・却下の判断基準を明示したい場面など、単純なルールベースの説明が求められる場面で今も使われます。しかし精度を優先する多くの実務案件では、ランダムフォレストかGBDT系(XGBoost、LightGBM、あるいはHistGradientBoostingClassifier)のいずれかが第一候補として検討の出発点に置かれることが多いといえます。

その一方で、多数の木を組み合わせたランダムフォレストやGBDTは、単体の決定木のように木の形をそのまま眺めて判断根拠を読み取ることはできません。特定の1件の予測について「なぜこの顧客は解約リスクが高いと判断されたのか」を説明する必要がある場合には、SHAPをはじめとする説明可能AI(XAI)の手法を組み合わせるのが実務上の定石です。この点については、別冊「AIはなぜその答えを出したのか、説明可能AI(XAI)の実践ガイド」で詳しく扱っています。

構造化データを扱う実務案件では、まずランダムフォレストかGBDT系のモデルで精度の基準を作り、その予測根拠をXAIの手法で補って説明責任を果たすという組み合わせが定着しつつあります。精度を担うモデルと説明を担う仕組みを分けて設計する発想は、モデル選定を検討するうえで押さえておきたい視点です。

本章では、決定木の分割の仕組みから、その弱点を補うアンサンブル学習の考え方、ランダムフォレストとGBDT、そして実務標準となったXGBoostとLightGBMまでを見てきました。次章では、大量の特徴量を持つデータをより少ない次元に圧縮する次元削減の手法を取り上げます。

この章を深めたい方への参考書籍

『Kaggleで勝つデータ分析の技術』(門脇大輔ほか、技術評論社):データ分析コンペティションの上位入賞者らによる実践書で、GBDT系モデルの特徴量エンジニアリングやパラメータ調整のノウハウが具体的に整理されています。本章で扱ったランダムフォレストやGBDTを、実データでどう使いこなすかという一歩先の実践知識を得たい場合に参考になります。

第9章 高次元データを圧縮する次元削減とPCA

次元の呪い:特徴量が増えるほどデータは疎になり、距離が意味を失う

第7章では、kNN(k近傍法)が「近くにあるデータは似ている」という距離の考え方を土台にしたアルゴリズムであることを見ました。この距離という発想は、特徴量の数(次元)が増えるにつれて、次第に機能しなくなっていきます。この現象は次元の呪いと呼ばれ、高次元データを扱う際に押さえておくべき前提になります。

直感的にイメージしにくい話ですが、次のように考えると掴みやすくなります。1次元の直線上にデータを均等に散らばらせるのと、2次元の正方形の中に均等に散らばらせるのと、3次元の立方体の中に均等に散らばらせるのとでは、同じ密度を保つために必要なデータ数が桁違いに増えていきます。特徴量が10個、100個と増えていくと、同じ密度を保つために必要なデータ量は指数関数的に膨らみ、現実のデータセットではとても追いつきません。結果として、高次元空間の中でデータは「点在」というよりも「ほとんど何もない空間にぽつぽつと存在する」疎な状態になります。

この疎な状態がkNNのようなアルゴリズムに与える影響は深刻です。次元が十分に高くなると、任意の2点間の距離の差(最も近い点と最も遠い点の距離の比)がどんどん小さくなり、「近い」と「遠い」の区別そのものが意味を失っていくことが知られています。すべての点がお互いにほぼ同じくらいの距離にあるように見えてしまえば、距離を根拠にした近傍探索は機能しません。特徴量を増やせば増やすほどモデルの精度が上がるとは限らず、むしろ無関係な特徴量を大量に混ぜることで、有益な特徴量が持つ情報がノイズに埋もれてしまう場合があります。

もう1つの制約は、人間の側の限界です。私たちが散布図やグラフとして直感的に眺められるのは、事実上3次元(それも実際には2次元の平面に奥行きを加えた疑似的な3次元)までです。4次元以上のデータを人間がそのまま「見て」構造を掴むことはできません。特徴量が数十、数百とある実務データを理解し、可視化し、扱いやすい形に整えるためには、情報をできる限り保ちながら次元を落とす技術が必要になります。これが本章で扱う次元削減というテーマです。

低次元では近くに集まって見えるデータ点が、次元を増やすごとに互いの距離がほぼ均一になり「疎」になっていく様子を段階的に示す図

次元削減の2つの目的:可視化と前処理

次元削減がビジネスの現場で使われる目的は、大きく2つに整理できます。

目的 典型的な使い方 主な手法
可視化 数十から数百次元のデータを2次元や3次元に落とし、散布図として人間の目で構造(クラスタの有無、外れ値、グループ間の位置関係)を把握する PCA、t-SNE、UMAP
前処理 後続の機械学習モデルに入力する前に、ノイズ除去・計算量の削減・多重共線性の緩和を目的として次元を落とす 主にPCA

前処理としての次元削減には、具体的に3つの効果があります。第一に、ノイズ除去です。データの分散のうち、ごく一部の方向にしか現れないような細かい変動は、しばしば測定誤差や偶然のノイズに由来することが多く、そうした方向を切り捨てることで本質的な信号だけを残せる場合があります。第二に、計算量の削減です。特徴量が数百から数千に及ぶデータ(センサーデータや画像データなど)をそのままモデルに入力すると学習に時間がかかりますが、情報をあまり損なわずに次元を数十程度まで圧縮できれば、学習と推論の両方が高速化されます。第三に、多重共線性の緩和です。重回帰分析やロジスティック回帰では、説明変数どうしの相関が強いと係数の推定が不安定になることが知られていますが、相関の強い変数群を互いに無相関な少数の主成分に置き換えることで、この問題を回避できる場合があります。

PCA(主成分分析)の直感:分散が最大の方向を探して情報をなるべく失わずに射影する

次元削減の代表的な手法がPCA(Principal Component Analysis、主成分分析)です。PCAの発想は「データが最もばらついている方向を見つけ、その方向を新しい軸として採用する」というものです。

例えば、身長と体重という2つの特徴量を持つデータを考えます。身長と体重には強い正の相関があるため、散布図に描くと右肩上がりの帯のような形に分布します。この帯の長い方向(身長が高い人ほど体重も重いという傾向が最も強く現れる方向)にまっすぐ新しい軸を引くと、その1本の軸だけでデータのばらつきの大部分を説明できることが分かります。この最初に見つかる、データが最も広がっている方向が第1主成分です。続いて、第1主成分と直交する(情報が重複しない)方向の中で、次に分散が大きい方向を第2主成分として選びます。この手続きを繰り返すことで、元の特徴量の数だけ主成分を作ることができますが、実務では分散の大きい上位のいくつかの主成分だけを採用し、残りを切り捨てることで次元を削減します。

ここで重要なのが、各主成分がデータ全体の分散のうちどれだけの割合を説明しているかを示す寄与率(explained variance ratio)と、上位からいくつかの主成分を足し合わせたときにどこまで説明できるかを示す累積寄与率です。何次元まで残すかは、この累積寄与率が実務上十分な水準(目安として80%から95%程度)に達する主成分数を採用する、という基準で決めることが一般的です。次のコードで、寄与率と累積寄与率を実際に確認してみます。ワインの化学成分13項目からなるデータセットを使います。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_wine
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

data = load_wine()
X = data.data  # 13種類の化学成分からなる特徴量

# PCAの前には必ず標準化を行う(スケールの大きい変数に主成分が引きずられるのを防ぐ)
X_scaled = StandardScaler().fit_transform(X)

pca = PCA().fit(X_scaled)
explained = pca.explained_variance_ratio_
cumulative = np.cumsum(explained)

fig, ax1 = plt.subplots(figsize=(7, 4))
ax1.bar(range(1, len(explained) + 1), explained, color="steelblue", label="寄与率")
ax1.set_xlabel("主成分の番号")
ax1.set_ylabel("寄与率")

ax2 = ax1.twinx()
ax2.plot(range(1, len(cumulative) + 1), cumulative, color="darkorange", marker="o", label="累積寄与率")
ax2.axhline(0.9, color="gray", linestyle="--", linewidth=1)
ax2.set_ylabel("累積寄与率")

plt.title("主成分ごとの寄与率と累積寄与率")
plt.tight_layout()
plt.savefig("pca_explained_variance.png")

このコードを実行すると、13次元あった特徴量のうち、上位5つの主成分だけで累積寄与率が80.2%に達することが確認できます(上位3つでは66.5%、90%を超えるには8つ必要です)。つまり元の情報量の大部分を、はるかに少ない次元数で近似的に表現できるということです。実務では、この累積寄与率のグラフ(スクリープロットと呼ばれます)を見ながら、グラフの傾きが緩やかになる地点(エルボー)や、累積寄与率が目標水準を超える地点を目安に、残す次元数を決めます。

主成分ごとの寄与率を棒グラフ、累積寄与率を折れ線グラフで重ねて示すスクリープロット

PCAの数理の概略:共分散行列と固有ベクトル、そして標準化の必須性

PCAの計算の中身に深入りしすぎる必要はありませんが、何が起きているのかを大づかみに理解しておくと、結果の解釈や落とし穴の回避に役立ちます。PCAは、各特徴量間の関係性を表す共分散行列(特徴量どうしがどれだけ一緒に変動するかを表す行列)を計算し、その共分散行列の固有ベクトルと固有値を求めることで主成分を導き出します。固有ベクトルの向きが主成分の方向(新しい軸)を表し、対応する固有値の大きさがその主成分が説明する分散の大きさ(寄与率のもとになる量)を表します。scikit-learnの内部では、この固有値問題を直接解く代わりに、特異値分解(SVD)という数値的に安定した計算方法を使って同じ結果を得ています。

ここで実務上決定的に重要なのが、PCAを実行する前に必ず標準化(各変数を平均0、標準偏差1に揃える処理)を行うという点です。PCAは分散が最大の方向を探すアルゴリズムであるため、標準化をせずに単位の異なる変数(例えば「年齢(単位:歳、数十程度の値)」と「年収(単位:円、数百万の値)」)をそのまま入力すると、単に数値の桁が大きいという理由だけで年収の分散が支配的になり、年齢という変数の情報がほとんど無視された主成分になってしまいます。これはデータが持つ本質的な構造を反映した結果ではなく、単位の取り方という偶然に左右された結果に過ぎません。特徴量の単位やスケールがそろっていることが明らかな特殊なケースを除き、PCAの前処理として標準化を行うことは、いわば必須の作法だと考えておく必要があります。

PCAは「データが最もばらついている方向」を新しい軸として順番に取り出す手法であり、上位のいくつかの主成分だけを残すことで、情報の大部分を保ったまま次元を削減できます。ただし変数間のスケールが揃っていないと分散の大きい変数に主成分が引きずられてしまうため、標準化は省略できない前提条件です。

主成分の解釈:負荷量から「軸の意味」を読み解く実務と、その限界

PCAで作られた主成分は、単なる数式上の産物ではなく、業務的な意味づけができる場合があります。この意味づけに使うのが負荷量(loading)と呼ばれる値です。負荷量は、各主成分が元のどの特徴量をどの程度の重みで組み合わせて作られているかを示す係数であり、各特徴量の負荷量を確認することで「この主成分は何を表しているらしいか」を推測する手がかりになります。

例えば、顧客満足度アンケートで「対応の丁寧さ」「価格の妥当性」「商品の品質」「配送の速さ」など数十項目を尋ねたとします。PCAを実行した結果、第1主成分にほぼすべての項目が同じ符号でまんべんなく強い負荷量を持っていた場合、この第1主成分は「総合的な満足度の高さ」を表す軸だと解釈できます。一方で第2主成分が「価格の妥当性」に正の負荷量、「商品の品質」に負の負荷量を持つような形になっていれば、この第2主成分は「価格を重視する顧客か、品質を重視する顧客か」というトレードオフの軸を表していると読み解くことができます。このように主成分に業務的なラベルを与えられると、数十項目のアンケートを2つか3つの軸で要約して報告できるようになり、経営層への説明も格段にしやすくなります。

ただし、すべての主成分が明快に解釈できるとは限らない点には注意が必要です。特に第3主成分より後ろの成分になるほど、複数の特徴量が入り乱れた、業務的な意味づけが難しい抽象的な軸になりがちです。無理に「この主成分はこういう意味だ」とこじつけるよりも、解釈できる主成分は積極的に活用し、解釈が難しい主成分は「数値としては効いているが意味づけは保留する」という扱いに留める姿勢の方が、実務上は健全だと考えられます。

PCAを前処理に使う:分類パイプラインと、教師あり学習でのリーク防止

PCAは可視化だけでなく、機械学習モデルの前処理としても頻繁に使われます。ここでは、8×8ピクセル(64次元)の手書き数字画像データセットを題材に、PCAで次元を圧縮してからロジスティック回帰(第6章)で分類するパイプラインを組んでみます。

この処理で必ず守らなければならないのが、PCAのfit(主成分の方向を学習する処理)は訓練データだけを使って行うという原則です。第3章で扱った過学習と汎化の議論と同じ理屈で、テストデータの情報を使ってPCAの軸を決めてしまうと、本来モデルが知らないはずのテストデータの分布情報が学習過程に紛れ込む、データリークが発生します。scikit-learnのPipelineを使えば、訓練データへのfitとテストデータへの変換の適用範囲を機械的に分離できるため、こうしたリークを構造的に防ぐことができます。

from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

digits = load_digits()
X, y = digits.data, digits.target  # 8×8ピクセルの手書き数字画像、64次元

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

pipeline = Pipeline([
    ("scaler", StandardScaler()),
    ("pca", PCA(n_components=0.90, random_state=42)),  # 累積寄与率90%になるまで次元を圧縮
    ("clf", LogisticRegression(max_iter=1000, random_state=42)),
])

pipeline.fit(X_train, y_train)  # fitは訓練データのみに適用し、テストデータの情報は使わない
y_pred = pipeline.predict(X_test)

n_components = pipeline.named_steps["pca"].n_components_
print(f"64次元 → {n_components}次元に圧縮")
print(f"テスト精度: {accuracy_score(y_test, y_pred):.3f}")

ここではPCA(n_components=0.90)のように、残す主成分の個数ではなく、達成したい累積寄与率を小数(0から1の値)で指定しています。scikit-learnはこの指定を受けて、累積寄与率が90%を超える最小の主成分数を自動的に選びます。実行すると、64次元だったデータが31次元へと半分以下に圧縮された上で、ロジスティック回帰にかけてもテスト精度0.947が維持されることが確認できます。Pipelineの内部では、fitが呼ばれた際に標準化・PCA・ロジスティック回帰のすべてが訓練データだけを使って学習され、predictの際にはその学習済みの変換がテストデータに適用される、という流れが自動的に保証されます。

非線形の次元削減:t-SNEとUMAP、可視化に強い手法とその注意点

PCAは、データの中に直線的な(線形の)構造を仮定して分散最大の方向を探す手法です。しかし現実のデータには、渦巻き状やドーナツ状といった、直線的には捉えきれない複雑な構造を持つものもあります。こうした非線形の構造をより忠実に低次元へ落とし込む手法として、t-SNEとUMAPという2つの手法がよく使われます。いずれも主に可視化を目的とした手法です。

  • t-SNE(t-distributed Stochastic Neighbor Embedding):高次元空間で近い点どうしを、低次元空間でも近くに配置するように学習する手法です。局所的な(近傍の)構造をきれいに可視化できる一方、計算に時間がかかりやすく、perplexity(近傍とみなす点の数の目安)というパラメータの値によって見た目の印象が大きく変わる点に注意が必要です。また、低次元空間上でのクラスタどうしの距離や、クラスタの大きさそのものには意味がないとされており、「クラスタが離れているから2つの集団は大きく異なる」といった距離の大小に基づく解釈は避けるべきとされています。
  • UMAP(Uniform Manifold Approximation and Projection):t-SNEと似た発想を持ちながら、計算が高速で、大域的な(データ全体の)構造もある程度保ちやすいとされる手法です。t-SNEと同様にn_neighbors(近傍数)やmin_dist(点どうしの最小距離)といったパラメータの設定によって見た目の印象が変わる点は共通していますが、新しいデータ点を既存の埋め込みに追加で変換する機能を備えている点がt-SNEとの実務上の違いです。

t-SNEとUMAPは、いずれもパラメータの設定によって図の見た目が大きく変わり得るため、1回の実行結果だけを見て「これがデータの真の構造だ」と断定するのは危険です。パラメータを変えて複数回試し、共通して現れる構造かどうかを確認する慎重さが求められます。次のコードで、先ほどと同じ手書き数字データをt-SNEで2次元に可視化してみます。

import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.manifold import TSNE

digits = load_digits()
X, y = digits.data, digits.target

tsne = TSNE(
    n_components=2,
    perplexity=30,
    max_iter=1000,  # 現行のscikit-learn APIではn_iterではなくmax_iterを指定する
    random_state=42,
)
X_embedded = tsne.fit_transform(X)

plt.figure(figsize=(7, 6))
scatter = plt.scatter(
    X_embedded[:, 0], X_embedded[:, 1], c=y, cmap="tab10", s=15, alpha=0.8
)
plt.legend(*scatter.legend_elements(), title="数字", loc="upper right", fontsize=8)
plt.title("t-SNEによる手書き数字データ(64次元)の2次元可視化")
plt.tight_layout()
plt.savefig("tsne_digits.png")

このコードを実行すると、0から9までの数字ごとにまとまったクラスタが2次元平面上に浮かび上がり、64次元のままでは把握しづらかった「どの数字とどの数字が形として紛らわしいか」といった構造を、目で確認できるようになります。

同じ高次元データをPCA・t-SNE・UMAPでそれぞれ2次元に落とした際の見た目の違いを3枚並べて比較する図

PCAと非線形手法の使い分けとしては、「PCAは前処理にも可視化にも使える汎用的な手法、t-SNEとUMAPは可視化に特化した手法」と整理しておくと実務上分かりやすくなります。t-SNEやUMAPで得られる低次元の座標は、点どうしの近さの見た目を重視して作られており、元の距離関係を歪めた上で成り立っている場合が多いため、そのまま後続の分類モデルなどの入力特徴量として使うことは一般的には推奨されません。次元削減してから予測モデルにかけたい場合はPCA、あるいはPCAで一定程度圧縮した上でUMAPをさらにかけるといった組み合わせ方が実務では取られることがあります。

ビジネス適用例:アンケート、センサーデータ、画像の圧縮

次元削減は、業種を問わず幅広い場面で活用されています。代表的な適用例を整理します。

  • 顧客アンケートの構造把握:数十項目に及ぶ満足度調査やブランドイメージ調査の回答を、PCAでいくつかの軸(総合満足度、価格重視度、機能重視度など)に要約し、顧客セグメントの違いを少数の軸で説明する
  • センサーデータの圧縮:工場設備や車両に取り付けられた多数のセンサーから得られる時系列データを、次元削減によって主要な変動成分に絞り込み、異常検知(第11章で扱います)やモニタリングの計算負荷を軽減する
  • 画像データの圧縮:画像を構成する多数のピクセル値を特徴量とみなし、PCAで主要な変動パターンだけを残すことで、画質の劣化を抑えながらデータサイズを圧縮する。実際の画像圧縮規格そのものではありませんが、「情報の大部分は少数の主要なパターンに集約できる」という直感を理解する題材としてよく使われます

これらの適用例に共通しているのは、次元削減が単なる技術的な処理ではなく、大量の変数を人間が理解し、意思決定に使える形に「要約する」ための手段だという点です。数十から数百におよぶ変数をそのまま眺めても構造は見えてきませんが、次元削減によって少数の軸に集約すれば、経営層への報告や、次の施策の検討に使える形の知見に変換できます。

次元削減は「情報を捨てる技術」ではなく「情報を要約する技術」です。数十項目のアンケートや数百チャンネルのセンサーデータであっても、分散の大部分を説明する少数の軸さえ見つけられれば、人間が理解でき、次のモデルにも渡しやすい形に変換できます。

この章を深めたい方への参考書籍

『はじめてのパターン認識』(平井有三、森北出版):PCAを含む次元削減や特徴抽出の考え方を、パターン認識という文脈の中で数理的な背景も含めて解説している定番書です。本章では深入りしなかった共分散行列や固有値の計算の詳細まで踏み込んで理解したい場合に適した1冊だと思います。

第10章 正解のないデータをグループ分けするクラスタリング

ここまでの章では、ロジスティック回帰やkNN、SVM、決定木とアンサンブル、次元削減といった手法を扱ってきました。これらの多くは「正解ラベル」を手がかりに学習する教師あり学習でした。本章から扱うクラスタリングは、正解ラベルが存在しないデータに構造を見出す教師なし学習の代表的な手法です。顧客データを何となく眺めているだけでは気づけない、似た者同士のまとまりをデータから浮かび上がらせる技術だといえます。

教師なし学習であるクラスタリングの難しさ

教師あり学習では、モデルの良し悪しを判定する基準がはっきりしています。予測値と正解ラベルとの誤差を測ればよく、第5章で扱った評価指標がそのまま使えます。ところがクラスタリングには、比較対象となる正解ラベルがそもそも存在しません。「この分け方が正しい」と断定できる基準がない中で、それでも「良い分け方」を判断しなければならないという点に、クラスタリング特有の難しさがあります。

例えば、あるECサイトの顧客1万人を、購買データをもとに何らかのグループに分けたいとします。3つのグループに分けても、7つのグループに分けても、アルゴリズムはそれぞれもっともらしい分割結果を返してきます。どちらが「正しい」かを機械的に判定する絶対的な指標は存在しません。あるとすれば、「そのグループ分けが人間にとって解釈可能で、実際の施策に使えるかどうか」という、極めて実務的な基準です。この点は本章の後半で改めて取り上げます。

クラスタリングを学ぶ意義は、大量のデータの中に潜む構造を、人手では発見しにくい形で提示してくれることにあります。一方で、その構造の「意味づけ」は最終的に人間が行う必要があるという性質を、最初に押さえておくことが重要です。

k-meansの直感:重心の更新を繰り返す

クラスタリング手法の中で最もよく使われるのがk-means法です。あらかじめ分けたいグループ数\( k \)を指定し、次の手順を繰り返すことでデータを\( k \)個のクラスタに分割します。

ステップ内容
1. 初期化\( k \)個の重心(セントロイド)の初期位置をランダムに、あるいはk-means++の方法で決める
2. 割り当て各データ点を、最も近い重心が属するクラスタに割り当てる
3. 更新各クラスタに割り当てられたデータ点の平均位置を計算し、重心をそこへ移動する
4. 収束判定クラスタの割り当てが変化しなくなるまで、ステップ2と3を繰り返す

やっていることは意外に単純です。「近い重心にとりあえず所属させる」「所属したメンバーの平均に重心を動かす」という2つの操作を、割り当てが変わらなくなるまで交互に繰り返しているだけです。この繰り返しのたびに、クラスタ内の分散(各点と所属する重心との距離の二乗の合計)は単調に減少していき、いずれ収束します。

k-means法には、実務で必ず押さえておくべき弱点が2つあります。1つ目は初期値依存です。ステップ1の重心の初期位置によって、最終的に得られるクラスタの分け方が変わってしまうことがあります。運が悪い初期値を選ぶと、本来自然に分かれるはずのグループがうまく分割されない、局所的に望ましくない解に収束することがあります。この弱点を緩和するために考案されたのがk-means++という初期化方法で、最初の重心をランダムに1つ選んだあと、既に選ばれた重心から離れているデータ点ほど次の重心として選ばれやすくする仕組みを持っています。重心同士が最初から適度に散らばって配置されるため、初期値依存による失敗が起きにくくなります。scikit-learnのKMeansクラスは、initパラメータの既定値が”k-means++”になっており、特別な理由がなければこの既定設定をそのまま使えば問題ありません。

2つ目の弱点はスケーリングの必要性です。k-means法は各データ点と重心とのユークリッド距離をもとにクラスタを決めるため、変数ごとの単位やスケールがそのまま距離の計算に影響します。例えば「年間購入金額(単位は円、数万円から数十万円の幅)」と「月間ログイン回数(単位は回、数回から数十回の幅)」を標準化せずにそのまま使うと、値の絶対的な大きさが桁違いに大きい購入金額がほぼ一方的に距離を決めてしまい、ログイン回数の違いがクラスタリングにほとんど反映されなくなります。そのため、k-means法を含む距離ベースのクラスタリングでは、事前に各変数を標準化(平均0、標準偏差1に変換)しておくことが実務上ほぼ必須の前処理です。

k-meansの重心が繰り返し更新されて収束していく様子

クラスタ数kの決め方:エルボー法、シルエット係数、運用上の制約

k-means法を使う際に必ず直面するのが、クラスタ数\( k \)をいくつに設定するかという問題です。\( k \)はアルゴリズムが自動的に決めてくれるものではなく、分析者があらかじめ指定する必要があります。この\( k \)の決め方には、統計的な目安と実務的な制約の両方を考慮する必要があります。

統計的な目安としてよく使われる1つ目の方法がエルボー法です。\( k \)の値を2、3、4と増やしながらクラスタ内の分散(inertia、各点と所属する重心との距離の二乗和)を計算し、横軸に\( k \)、縦軸にinertiaをとった折れ線グラフを描きます。\( k \)を増やせばinertiaは必ず単調に減少しますが、あるところから減少の勾配が緩やかになる「肘(エルボー)」のような折れ曲がりが現れることがあり、その手前の\( k \)を目安として採用する考え方です。

ただしエルボー法には限界があります。実際のデータでは、はっきりとした肘が現れず、なだらかなカーブが続くだけのグラフになることが少なくありません。この場合、どこを肘とみなすかは分析者の主観に委ねられてしまい、客観的な決定打にはなりにくいという弱点があります。

2つ目の目安がシルエット係数です。各データ点について、同じクラスタ内の他の点との平均距離(凝集度)と、最も近い別のクラスタの点との平均距離(分離度)を比較し、「自分のクラスタにどれだけしっくり収まっているか」を\( -1 \)から\( 1 \)の範囲の値で表します。値が1に近いほど、そのデータ点は自分のクラスタにうまく収まっており、他のクラスタとは明確に分離できていることを意味します。全データ点のシルエット係数の平均値(シルエットスコア)を\( k \)ごとに計算し、最も高くなる\( k \)を選ぶという使い方をします。エルボー法よりも定量的な判断がしやすい一方、シルエットスコアが最大になる\( k \)が必ずしも1つに定まらず、複数の\( k \)で近い値になることもあります。

ここで実務上重要になるのが、統計的な指標だけで\( k \)を決めてはいけないという点です。エルボー法やシルエット係数が\( k=8 \)を示唆したとしても、その8つのセグメントそれぞれに対して個別のマーケティング施策やコミュニケーション設計を用意できるかどうかは、まったく別の問題です。実務で顧客セグメンテーションを行う目的は、セグメントごとに異なる打ち手を講じることにあります。組織が実際に運用できる施策の本数には限りがあり、経験的には3から5程度のセグメント数に落ち着くことが多いように思います。統計的な指標はあくまで参考値の1つと位置づけ、最終的な\( k \)は「このセグメント数であれば、各セグメントに固有の施策を設計・実行できるか」という運用上の制約とあわせて決める姿勢が求められます。

クラスタ数\( k \)は、エルボー法やシルエット係数といった統計的な指標だけで機械的に決めるものではありません。分けたセグメントの数だけ、組織が実際に異なる施策を打てるかという運用上の制約を踏まえて決めることが、クラスタリングを実務で機能させる分かれ目になります。

階層クラスタリング:デンドログラムで構造を把握する

k-means法とは異なるアプローチを取るのが階層クラスタリングです。k-means法が最初に\( k \)を指定する必要があるのに対し、階層クラスタリングはまず全てのデータ点同士の近さをもとにクラスタを段階的に併合していき、その併合の過程を樹形図(デンドログラム)として可視化します。\( k \)を後から自由に選べる点が大きな特徴です。

代表的な手順は凝集型(下から積み上げる方式)で、最初は各データ点を1つずつ独立したクラスタとみなし、最も距離の近いクラスタ同士を1つに併合するという操作を、最終的に全データが1つのクラスタになるまで繰り返します。この併合の過程を記録したものがデンドログラムで、横軸(あるいは縦軸)にデータ点、もう一方の軸に併合が起きた距離をとった木構造の図として描かれます。

デンドログラムを読むときのポイントは、枝が結合する高さ(距離)に注目することです。低い位置で結合している点同士は互いに近い、つまり似た性質を持つグループであり、逆に高い位置でようやく結合する枝は、性質が大きく異なるグループだと解釈できます。任意の高さで水平に線を引き、その線と交差する枝の本数を数えることで、その高さを基準にした場合のクラスタ数を後から柔軟に決められます。

クラスタ同士の距離をどう定義するかにはいくつかの方式があり、代表的なものがウォード法です。ウォード法は、クラスタを併合した際にクラスタ内の分散がどれだけ増加するかを基準にし、分散の増加が最小になる組み合わせを選んで併合していく方式です。この考え方はk-means法がクラスタ内分散を最小化しようとする発想と近く、実務でも安定した結果が得られやすいため広く使われています。他にも、最も近いデータ点同士の距離を使う最短距離法(単連結法)、最も遠いデータ点同士の距離を使う最長距離法(完全連結法)、クラスタの重心間の距離を使う重心法などがありますが、まず試す選択肢としてはウォード法が定番です。

階層クラスタリングは、全データ点同士の距離を計算する必要があるため、データ件数が数万件を超えるような大規模データには計算負荷の面で向きません。むしろ真価を発揮するのは、数十から数百件程度の少数データに対して、データの内部構造そのものを視覚的に把握したい場面です。例えば数十店舗の売上構成比を比較して、どの店舗とどの店舗が似た傾向を持つのかを俯瞰したい、といった用途に適しています。

店舗データのデンドログラムで枝が結合していく様子

混合ガウスモデル(GMM)とEMアルゴリズム:所属を確率で表す

k-means法や階層クラスタリングは、いずれも1つのデータ点を1つのクラスタに確定的に割り当てます。これに対し、混合ガウスモデル(Gaussian Mixture Model、GMM)は、データ全体が複数の正規分布(ガウス分布)が混ざり合ってできていると仮定し、各データ点について「クラスタ1に所属する確率は70%、クラスタ2に所属する確率は30%」というように、所属の度合いを確率で柔らかく表現します。

GMMのパラメータ(各クラスタの正規分布の平均・分散・混合比率)は、EMアルゴリズム(Expectation-Maximization algorithm)という手続きで推定します。Eステップでは、現在のパラメータのもとで各データ点が各クラスタに所属する確率を計算し、Mステップでは、その所属確率を重みとして使いながら各クラスタの平均や分散を更新します。このEステップとMステップを交互に繰り返すことで、データ全体をよく説明する分布のパラメータに収束させていきます。

GMMとk-means法の関係を直感的に捉えるなら、GMMは「各クラスタの形(広がり方や向き)を柔軟に許容しながら、所属を確率で表現するk-means法の拡張版」だといえます。実際、各クラスタの分散が全て等しい球状であるという制約を置いたGMMは、k-means法とほぼ同じ挙動になることが知られています。GMMはこの制約を外し、クラスタごとに異なる形の広がり(楕円形など)を許容するぶん、k-means法よりも柔軟な分割が可能になります。ただし本ガイドでは、GMMの詳細な数式展開までは扱わず、「所属を確率的に扱う」という発想と、k-means法との関係性を押さえておく段階までにとどめます。

DBSCAN:密度に基づき外れ値検出も兼ねる手法

k-means法や階層クラスタリング、GMMは、いずれも事前にクラスタ数(あるいはそれに相当するパラメータ)を決める必要がありました。これに対しDBSCAN(Density-Based Spatial Clustering of Applications with Noise)は、データ点の密度に着目することで、クラスタ数を事前に指定せずにクラスタを発見できる手法です。

DBSCANの基本的な発想は単純です。あるデータ点の周囲、半径\( \varepsilon \)(eps)以内に、指定した個数(min_samples)以上の他のデータ点が存在すれば、そのデータ点は「密集している」とみなし、密集した点同士を連鎖的につなげてクラスタを形成します。どのクラスタの密集領域にも属さない、周囲にデータ点が少ない孤立した点は、クラスタに属さない「ノイズ(外れ値)」として扱われます。

この性質により、DBSCANはクラスタリングと外れ値検出を同時に行える手法だという点が実務上の利点になります。例えば異常な取引パターンや通常と異なる挙動を示すデータ点を、密度の低い孤立点として自動的に検出できます。一方で、epsとmin_samplesという2つのパラメータの設定に結果が敏感に左右される点、クラスタごとに密度が大きく異なるデータには不向きな点には注意が必要です。密度に基づく外れ値検出という考え方は、第11章で扱う異常検知の話題とも重なります。

クラスタリング結果の評価と解釈:正解がないため実務で使えるかで判断する

クラスタリングには正解ラベルがないため、教師あり学習のように予測精度で評価することはできません。シルエット係数のような指標は、クラスタの分かれ方の数学的な良さをある程度測ってはくれますが、それだけで「良いクラスタリングができた」と判断するのは早計です。最終的にものを言うのは、得られたクラスタが人間にとって解釈でき、実際の意思決定に使えるかどうかです。

この解釈のために実務で必ず行うべき手順が、クラスタごとのプロファイリングです。各クラスタに割り当てられたデータについて、クラスタリングに使った変数(購入金額、購入頻度など)や、クラスタリングには使わなかった他の変数(年齢、居住地域、利用チャネルなど)の平均値や構成比をクラスタ別に集計し、比較します。例えば「クラスタ0は購入頻度は高いが平均単価が低い」「クラスタ2は購入頻度は低いが1回あたりの単価が非常に高い」といった特徴が見えてくれば、それぞれのクラスタに「日常使い層」「高額単発購入層」といった名前を与えて意味づけできます。

逆に、各クラスタの平均値を並べても目立った違いが見えてこない、あるいは分けたクラスタの人数比率が極端に偏っていて実務上扱いにくい、といった場合は、変数の選び方や\( k \)の設定を見直す必要があるサインです。クラスタリングの評価は、統計的な指標の確認とプロファイリングによる解釈可能性の確認を、両輪として進めるものだと捉えておくとよいでしょう。

プロファイリングを行う際には、クラスタリングに使った変数だけでなく、あえて使わなかった変数もあわせて集計しておくことが役に立ちます。例えば購買行動の変数だけでクラスタを作った場合でも、できあがったクラスタごとに年齢層や居住地域、問い合わせ履歴の有無といった変数の分布を並べてみると、クラスタリングの段階では意図していなかった傾向が見つかることがあります。こうした副次的な発見が、施策の具体的な設計に役立つ手がかりになる場合も少なくありません。

ビジネス適用:顧客セグメンテーションと店舗のグループ分け

クラスタリングが実務で最もよく使われる場面の1つが、顧客セグメンテーションです。中でも代表的な特徴量の作り方がRFM分析です。RFMは、Recency(直近の購買からの経過日数)、Frequency(購買頻度)、Monetary(累計購買金額)という3つの指標の頭文字で、顧客の購買行動を要約する古典的な切り口として広く使われています。この3つの指標を変数として標準化し、k-means法などでクラスタリングにかけることで、「最近よく買う優良顧客層」「かつては頻繁に購入していたが最近離れつつある層」「単価は高いが購買頻度は低い層」といった顧客のまとまりを見出せます。

顧客セグメンテーション以外にも、店舗のグループ分けという適用先があります。多店舗展開する小売業やサービス業では、立地条件、客層構成、商品カテゴリ別の売上構成比といった変数をもとに店舗をクラスタリングすることで、「都市型で客単価が高い店舗群」「郊外型でファミリー層が中心の店舗群」といった店舗タイプを把握できます。同じタイプに分類された店舗同士であれば、片方の店舗で成功した販促施策をもう一方にも展開しやすくなる、といった実務上の利点があります。

ここで強調しておきたいのが、クラスタリングは分析の出発点であって終着点ではないという点です。顧客を5つのセグメントに分けたこと自体は、まだ何のビジネス成果も生んでいません。重要なのは、分けた各セグメントに対して、どのような施策(メールの訴求内容の出し分け、優良顧客向けの特典設計、離反しつつある層への再アプローチなど)を接続するかです。セグメントごとの特徴を整理したうえで、それぞれに合った具体的な打ち手を設計し、実行し、効果を追跡するところまでを一連のプロセスとして設計することで、クラスタリングは初めて経営上の意味を持ちます。

もう1つ実務上の注意点として、顧客の購買行動や店舗の商圏環境は時間とともに変化するため、一度作ったセグメントを固定的に扱い続けるのは望ましくありません。RFMのような時間依存の変数を使う場合は特に、一定の周期(四半期や半期など)でクラスタリングを再実行し、セグメントの構成や境界がどう変化したかを確認する運用を組み込んでおくと、施策の精度を長期的に保ちやすくなります。

RFM分析による顧客セグメントとそれぞれに対応する施策の対応関係を示す図

Pythonで実装する:k-means、階層クラスタリング、クラスタのプロファイリング

ここまでの内容を、Pythonのコード例で確認します。まず、顧客データを想定した特徴量を使い、k-means法でクラスタリングを行いながら、エルボー法とシルエット係数によって適切な\( k \)の目安を探る例です。

import pandas as pd
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score

# 顧客ごとの特徴量(月間購入金額、月間購入回数、会員継続月数)
# ファイル名・列名は自社データに置き換えて使うテンプレートです
df = pd.read_csv("customer_features.csv")
X = df[["monthly_amount", "monthly_freq", "tenure_months"]]

# 距離ベースの手法ではスケーリングがほぼ必須
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

inertia_list = []
silhouette_list = []
k_range = range(2, 9)

for k in k_range:
    # initの既定値は"k-means++"
    # n_init="auto"はinitに応じて試行回数が決まる(k-means++なら1回、randomなら10回)。
    # 初期値依存を抑えたい場合は回数を明示する
    km = KMeans(n_clusters=k, init="k-means++", n_init=10, random_state=42)
    labels = km.fit_predict(X_scaled)
    inertia_list.append(km.inertia_)
    silhouette_list.append(silhouette_score(X_scaled, labels))

for k, inertia, sil in zip(k_range, inertia_list, silhouette_list):
    print(f"k={k}: inertia={inertia:.1f}, silhouette={sil:.3f}")

# inertia_listを折れ線グラフにすればエルボー法の図になる
# silhouette_listが最大になるkを目安の1つとして確認する

inertia_listをグラフにすればエルボー法の折れ線が得られ、silhouette_listの最大値からシルエット係数による目安が得られます。ただし前述のとおり、最終的な\( k \)は、この統計的な目安と、実際に運用できる施策の本数とをあわせて決定します。

続いて、少数の店舗データを想定し、階層クラスタリングでデンドログラムを描く例です。

import pandas as pd
from scipy.cluster.hierarchy import dendrogram, linkage
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt

# 店舗ごとの特徴量(客単価、来店頻度、主要カテゴリの売上構成比など)
# ファイル名・列名は自社データに置き換えて使うテンプレートです
store_df = pd.read_csv("store_features.csv")
X_store = store_df.drop(columns=["store_name"])

scaler = StandardScaler()
X_store_scaled = scaler.fit_transform(X_store)

# ウォード法:クラスタ内分散の増加が最小になる組み合わせで併合する
Z = linkage(X_store_scaled, method="ward")

plt.figure(figsize=(10, 5))
dendrogram(Z, labels=store_df["store_name"].tolist())
plt.ylabel("距離(ウォード法)")
plt.title("店舗の階層クラスタリング")
plt.tight_layout()
plt.show()

# 任意の高さで水平に切ることで、後からクラスタ数を柔軟に決められる

最後に、k-means法などで得られたクラスタ番号をもとに、クラスタごとのプロファイリング(平均値の比較)を行う例です。この工程が、クラスタリング結果を意味づけし、施策につなげるための要となります。

import pandas as pd

# labelsは前段のKMeansなどで得たクラスタ番号の配列
df["cluster"] = labels

# クラスタ別に主要変数の平均値と人数を集計する
profile = df.groupby("cluster")[
    ["monthly_amount", "monthly_freq", "tenure_months"]
].mean()
profile["count"] = df.groupby("cluster").size()
profile["ratio"] = (profile["count"] / len(df) * 100).round(1)

print(profile.round(1))
# 各クラスタの平均値の高低を見比べ、
# 「高頻度・低単価」「低頻度・高単価」のような特徴を言語化していく

プロファイリングの結果は、クラスタ数を決める段階と施策を設計する段階の両方で繰り返し参照することになります。1回集計して終わりにするのではなく、変数の組み合わせを変えながら、各クラスタの特徴が具体的な打ち手に結びつくかどうかを確認する作業だと捉えておくとよいでしょう。

クラスタリングは「分けること」自体が目的ではありません。分けた結果を人間が解釈し、セグメントごとに異なる施策を設計・実行してはじめて、経営上の価値が生まれます。プロファイリングによる意味づけと、施策への接続までを一連の工程として設計することが実務での成否を分けます。

本章では、正解ラベルのないデータから構造を見出す教師なし学習として、k-means法、階層クラスタリング、混合ガウスモデル、DBSCANという4つの代表的なクラスタリング手法を確認しました。クラスタ数の決め方には統計的な目安がある一方、最終的には実務で使える数まで絞り込む判断が必要であること、そしてクラスタリングの評価は「解釈でき、使えるかどうか」というプロファイリングを通じた確認が欠かせないことも見てきました。次章では、クラスタリングの一部の手法とも関わりの深い、不均衡データの扱い方と異常検知の手法を取り上げます。

この章を深めたい方への参考書籍

『続・わかりやすいパターン認識 教師なし学習入門』(石井健一郎・上田修功、オーム社):k-means法や混合ガウスモデル、EMアルゴリズムといった教師なし学習の考え方を、数式の背景まで含めて体系的に解説した書籍です。本章で概念レベルにとどめたGMMとEMアルゴリズムの数理をより深く理解したい読者に適しています。

第11章 実務の壁を越える、不均衡データと異常検知

実務データは不均衡が普通であるという現実

ここまでの章では、分類問題の評価指標やモデルの選び方を、比較的クラスの比率が偏っていないデータを念頭に説明してきました。しかし実務で扱うデータの多くは、そのような穏やかな比率にはなっていません。クレジットカードの不正利用は全取引の0.1%程度、製造設備の故障は稼働記録の0.5%程度、通信サービスの解約は月間数%程度にとどまることが一般的です。このように「見つけたい事象」が全体のごく一部にしか存在しない状況を、不均衡データ(imbalanced data)と呼びます。

第5章でモデル評価指標を扱った際に、Accuracy(正解率)だけでモデルを評価する危うさに触れました。不均衡データはその危うさが最も顕著に現れる場面です。仮に不正利用の発生率が0.1%のデータに対して、常に「不正ではない」と予測し続けるだけのモデルを作ったとします。このモデルは不正を1件も検出できていないにもかかわらず、Accuracyは99.9%に達します。数字だけを見れば非常に優秀なモデルに見えますが、実際には検知システムとして何の役にも立っていません。これが、第5章でも触れた、不均衡データではAccuracyが指標として意味を失うという構造です。経営層への報告資料に精度99%という数字が並んでいても、その内実が「異常をほぼ検知できていない」という状態である可能性は、常に疑ってかかる必要があります。

不均衡データへの対処が難しいのは、単に評価指標を変えれば済む話ではなく、モデルの学習過程そのものが多数派クラスに引きずられてしまうためです。損失関数は多くの場合、全サンプルに対する誤差の合計や平均で定義されますが、少数派クラスのサンプル数が少なければ、それを正しく予測できてもできなくても、損失全体への影響はわずかです。結果として、学習アルゴリズムは「少数派を無視して多数派だけを当てにいく」ことが、数式の上では合理的な戦略になってしまいます。この構造を理解した上で、評価・データ・アルゴリズム・意思決定という複数の段階で手を打っていくことが、不均衡データに対処する際の基本方針になります。

不均衡データへの対処の全体像

不均衡データへの対処法は、大きく4つのアプローチに整理できます。実務では、これらを単独ではなく組み合わせて使うことが一般的です。

アプローチ 内容 特徴・注意点
評価指標の変更 Accuracyに代えて、適合率・再現率・PR-AUC・F値などクラスの偏りに影響されにくい指標でモデルを評価する モデルの学習ロジックそのものは変わらないため、他のアプローチと必ず併用する。まず着手すべき出発点
データレベルの対処(サンプリング) 学習データの段階でクラス比率を調整する。アンダーサンプリング(多数派を間引く)、オーバーサンプリング(少数派を増やす)、SMOTEなどの合成サンプリング 情報の損失や過学習のリスクを伴う。予測確率の解釈にも歪みが生じるため補正が必要になる場合がある
アルゴリズムレベルの対処(重み付け) 損失関数の段階で、少数派クラスのサンプルに大きな重みを与え、誤分類のペナルティを引き上げる。class_weightパラメータなどで指定する データを増減させないため情報の損失が起きにくく、多くのアルゴリズムで手軽に設定できる。実務上の第一手として有力
閾値の調整 モデルが出力する確率(スコア)を0か1かに分類する境目である閾値を、業務上のコストに合わせて調整する モデルの再学習が不要で即座に適用できる。閾値をどこに置くかの判断には、誤検知と見逃しそれぞれのコスト評価が必要
不均衡データへの4つの対処アプローチ(評価指標・データ・アルゴリズム・閾値)を一枚の図で整理したイメージ

評価指標を見直す、PR曲線とPR-AUC

第5章ではROC曲線とROC-AUCを扱いましたが、不均衡データにおいてはROC-AUCが実態より楽観的な数値を示しやすいという弱点があります。ROC曲線は偽陽性率(FPR)と真陽性率(再現率)の関係を描きますが、多数派クラスが圧倒的に多い状況では、偽陽性の絶対数が多少増えても、母数である多数派全体に対する割合(FPR)はほとんど動きません。そのため、実際には誤検知が大量に発生しているモデルでも、ROC-AUCの数値上は高いスコアが出てしまうことがあります。

この弱点を補うのが、適合率(Precision)と再現率(Recall)の関係を描くPR曲線(Precision-Recall Curve)と、その曲線下の面積であるPR-AUCです。適合率は「陽性と予測したもののうち、実際に陽性だった割合」であり、多数派クラスの母数に依存しません。少数派クラスの検出に関心がある不均衡データの評価では、ROC-AUCよりもPR-AUCの方が、モデルの実力をより厳しく、より実態に即して映し出します。

PR-AUCを見る際に欠かせないのが、「何もしないモデル」との比較です。ランダムに予測するだけのモデル、あるいは常に少数派の発生率と同じ確率で陽性と予測するモデルのPR-AUCは、おおよそデータ全体に占める少数派クラスの比率と一致します。つまり不正利用の発生率が0.1%であれば、何もしないモデルのPR-AUCはおよそ0.001です。自分が構築したモデルのPR-AUCが0.3であれば「悪くない」と感じるかもしれませんが、その評価は0.001という基準線と比較して初めて意味を持ちます。逆にROC-AUCは、何もしないランダムなモデルであっても常に0.5前後になるため、この基準線としての機能を持ちません。不均衡データを評価する際は、必ずPR-AUCと基準線をセットで確認する習慣が重要です。

アンダーサンプリングとオーバーサンプリング

データレベルの対処は、学習データそのものに手を加えて、モデルが学習しやすいクラス比率に近づける方法です。代表的な手法として、アンダーサンプリングとオーバーサンプリングがあります。

アンダーサンプリング(Undersampling)は、多数派クラスのサンプルを間引いて少数派クラスとの比率を近づける方法です。ランダムアンダーサンプリング(RandomUnderSampler)が最も基本的な実装で、多数派クラスから無作為にサンプルを取り除きます。学習に使うデータ全体の件数が減るため計算コストは下がりますが、間引いたサンプルに含まれていた情報が失われるという欠点があります。特に多数派クラスの中に存在する、少数派との判別に役立つ境界付近のパターンまで削ってしまうと、モデルの判別性能そのものが低下するリスクがあります。

オーバーサンプリング(Oversampling)は逆に、少数派クラスのサンプルを増やして比率を近づける方法です。最も単純な実装であるランダムオーバーサンプリング(RandomOverSampler)は、少数派クラスのサンプルを単純に複製します。しかし同じサンプルを何度も複製すると、モデルがその特定のサンプルの細部にまで過剰に適合してしまう過学習のリスクが高まります。

この過学習のリスクを緩和する目的で考案されたのが、SMOTE(Synthetic Minority Over-sampling Technique)です。SMOTEは少数派クラスのサンプルを単純に複製するのではなく、少数派クラスの中である1つのサンプルと、その近傍にある別の少数派サンプルとの間を特徴量空間上で線形に補間し、その中間地点に新しい合成サンプルを生成するという発想を取ります。単純な複製よりも多様性のある学習データが得られるため、過学習を抑えつつ少数派クラスの情報を増幅できる手法として広く使われてきました。

ただしSMOTEにも限界があります。近傍のサンプル間を直線的に補間するという発想は、特徴量空間上で少数派クラスがある程度まとまった塊を成していることを前提としています。少数派クラスのサンプルが特徴量空間の中でまばらに散らばっていたり、多数派クラスの領域と大きく重なり合っていたりする場合、生成された合成サンプルが本来存在しないはずの領域、あるいは実際には多数派クラスに近い境界のノイズ領域に生成されてしまうことがあります。またカテゴリ変数を多く含むデータでは、そもそも特徴量空間上の距離や補間という考え方がなじみにくく、SMOTENC(カテゴリ変数対応版)など専用の派生手法を検討する必要があります。次元数が非常に大きいデータでも、近傍の距離計算が意味を持ちにくくなる次元の呪いの影響を受けやすい点にも注意が必要です。

サンプリングを行う際にもう1つ見落とされがちな注意点が、サンプリング後にモデルが出力する予測確率の歪みです。オーバーサンプリングやアンダーサンプリングによって学習データ内のクラス比率を人為的に変えると、モデルはその人為的な比率を「世界の実態」として学習してしまいます。たとえば実際の不正利用率が0.1%であるにもかかわらず、学習データ上で1対1の比率になるようサンプリングした場合、モデルが出力する「不正である確率」は、実際の発生確率よりも大幅に高い値として出力されるようになります。分類の閾値を0.5で機械的に区切るだけであれば大きな問題にならないこともありますが、出力された確率の値そのものを金額換算やリスクスコアとしてそのまま業務に使う場合、この歪みを補正しないまま扱うと、誤った意思決定につながります。補正の考え方としては、学習時のクラス比率と実際の母集団におけるクラス比率の差を用いて事後的に確率を補正する方法や、プラット・スケーリング(モデルが出したスコアにロジスティック回帰を当てはめ直して確率に変換する手法)やアイソトニック回帰(予測確率と実際の陽性割合の対応を、大小関係だけを保った階段状の関数で引き直す手法)といったキャリブレーション手法を追加で適用する方法があります。サンプリングを行った場合は、確率の値そのものを鵜呑みにせず、必ずこの歪みの有無を確認する姿勢が求められます。

クラス重み、データを増減させずに損失の重みを調整する

データレベルの対処がサンプルの件数そのものを操作するのに対し、アルゴリズムレベルの対処は、学習データの件数を変えずに、損失関数が各サンプルに与える重みを調整します。その代表例が、scikit-learnの多くの分類器に用意されているclass_weightパラメータです。

class_weight=’balanced’を指定すると、各クラスの出現頻度に反比例するように重みが自動的に計算され、少数派クラスのサンプルを誤分類した際の損失が大きくなるよう調整されます。具体的には、あるクラスの重みは「全サンプル数を、クラス数とそのクラスのサンプル数の積で割った値」として計算されます。この仕組みにより、モデルは少数派クラスを軽視すると学習全体の損失が大きく増えてしまうため、多数派に偏った予測を避けるよう学習が進みます。

class_weightの利点は、データそのものを増減させないため情報の損失や人為的な重複が発生しないこと、そしてほとんどの実装で1行の設定を加えるだけで適用できる手軽さにあります。LogisticRegression、RandomForestClassifier、SVCなどscikit-learnの主要な分類器の多くがこのパラメータをサポートしており、勾配ブースティング系のライブラリでも、LightGBMではclass_weightやis_unbalanceパラメータ、XGBoostではscale_pos_weightパラメータという形で同様の考え方が実装されています。不均衡データに直面した際、まず試すべき「最初の一手」として推奨される理由がここにあります。

以下は、class_weightを指定した場合と指定しない場合とで、不均衡データに対する分類性能がどう変わるかを比較する例です。

import numpy as np
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import (
    average_precision_score,
    precision_score,
    recall_score,
)

# 少数派クラスが1%程度の不均衡データを生成
X, y = make_classification(
    n_samples=20000,
    n_features=10,
    weights=[0.99, 0.01],
    flip_y=0.01,
    random_state=0,
)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, stratify=y, random_state=0
)

for weight_setting in [None, "balanced"]:
    model = LogisticRegression(
        class_weight=weight_setting, max_iter=1000, random_state=0
    )
    model.fit(X_train, y_train)

    y_pred = model.predict(X_test)
    y_proba = model.predict_proba(X_test)[:, 1]

    print(f"class_weight={weight_setting}")
    print(f"  再現率(Recall)   : {recall_score(y_test, y_pred):.3f}")
    print(f"  適合率(Precision): {precision_score(y_test, y_pred):.3f}")
    print(f"  PR-AUC           : {average_precision_score(y_test, y_proba):.3f}")

このコードを実行すると、class_weightを指定しない既定の設定では、閾値0.5で予測した際の再現率が0.021と極端に低くなり、少数派クラスをほとんど検出できないことが分かります。一方でclass_weight=’balanced’を指定すると再現率は0.747まで大きく改善しますが、その代わりに適合率は1.000から0.038へと大きく低下します。

ここで見落としてはならないのが、PR-AUCが0.331から0.204へ下がっている点です。同じ条件でROC-AUCを計算すると0.792から0.789とほとんど変わらないため、モデルが持つ順位付けの能力そのものが失われたわけではありません。それでもPR-AUCが下がるということは、class_weightは「少数派の検出力を底上げする手段」ではなく、「閾値0.5における再現率と適合率の配分を再現率寄りに振り替える手段」だということです。重み付けを入れたから少数派をよく見分けられるようになった、と読むのは誤りで、実際には見逃しを減らす代わりに誤検知を大量に受け入れているにすぎません。どちらの誤りが業務上高くつくかを決めたうえで使う道具だと捉えてください。

閾値の調整、コストから最適点を決める

class_weightやサンプリングによってモデルの学習自体を調整する方法とは別に、学習済みモデルが出力する確率をそのまま使い、分類の境目となる閾値だけを調整するというアプローチもあります。第5章で触れた通り、多くの分類器は「陽性である確率」を出力し、既定では0.5を上回れば陽性、下回れば陰性と判定します。しかしこの0.5という基準に絶対的な根拠はなく、業務上のコストに応じて自由に動かすことができます。前節のclass_weightが再現率と適合率の配分を間接的に振り替えていたのに対し、閾値調整は同じ配分を、業務コストという明示的な根拠に基づいて直接動かす手段だと捉えると、両者の役割の違いがはっきりします。

閾値をどこに設定するのが最適かを判断する実務的な方法が、コスト行列(Cost Matrix)を用いた期待値アプローチです。第5章で扱った混同行列の4象限それぞれに、金額換算したコストを割り当てます。

  • 真陽性(TP): 不正利用を正しく検知できた場合の対応コスト(調査コストなど)
  • 偽陽性(FP): 正常な取引を誤って不正と判定してしまった場合のコスト(顧客への確認連絡や機会損失など)
  • 偽陰性(FN): 実際の不正利用を見逃してしまった場合のコスト(被害金額そのもの)
  • 真陰性(TN): 正常な取引を正しく正常と判定できた場合のコスト(通常は0)

この4種類のコストが判明していれば、ある閾値を採用した場合にテストデータ全体で発生する期待コストの合計を計算できます。閾値を少しずつ動かしながらこの期待コストを計算し、合計コストが最小になる点を選ぶことで、単なる統計的な精度指標ではなく、事業上の損得に基づいた最適閾値を導き出せます。一般に、偽陰性(見逃し)のコストが偽陽性(誤検知)のコストより大きい不正検知や故障予知のような業務では、最適な閾値は0.5よりもかなり低い位置に落ち着くことが多く、これは「多少の誤検知を許容してでも見逃しを減らす」という判断がコストの上でも合理的であることを意味します。

import numpy as np
from sklearn.linear_model import LogisticRegression

# 前節と同じデータで、class_weightを指定しない既定のモデルを学習させる
model = LogisticRegression(max_iter=1000, random_state=0).fit(X_train, y_train)
y_proba = model.predict_proba(X_test)[:, 1]

# 混同行列の各象限に割り当てたコスト(円)の例
# 偽陰性(見逃し)のコストが偽陽性(誤検知)より大きい設定
cost_tp = 500       # 不正検知時の調査コスト
cost_fp = 300        # 誤検知による顧客対応コスト
cost_fn = 50000     # 見逃した不正利用の被害額
cost_tn = 0

def expected_cost(y_true, y_proba, threshold):
    y_pred = (y_proba >= threshold).astype(int)
    tp = np.sum((y_true == 1) & (y_pred == 1))
    fp = np.sum((y_true == 0) & (y_pred == 1))
    fn = np.sum((y_true == 1) & (y_pred == 0))
    tn = np.sum((y_true == 0) & (y_pred == 0))
    return tp * cost_tp + fp * cost_fp + fn * cost_fn + tn * cost_tn

thresholds = np.linspace(0.01, 0.99, 99)
costs = [expected_cost(y_test, y_proba, t) for t in thresholds]

best_threshold = thresholds[int(np.argmin(costs))]
print(f"期待コスト最小の閾値: {best_threshold:.2f}")
print(f"そのときの期待コスト: {min(costs):,.0f}円")

この例では期待コストが最小になる閾値は0.01で、そのときの期待コストは約171万円でした。既定の0.5をそのまま使った場合の約465万円と比べると、モデルには一切手を触れず、判定の境目を動かすだけで期待コストが6割強減る計算になります。見逃しの被害額が誤検知の対応コストの100倍以上あるという設定のもとでは、疑わしいものを広く拾いにいくのが金額の上で合理的だということです。

閾値調整の大きな利点は、モデルを再学習する必要がなく、コスト構造が変化した際にも即座に運用ルールを見直せることです。一方で、コスト行列を正しく設定するには、業務側の関係者と協力して「見逃した場合に実際どれだけの損失が発生するか」「誤検知1件あたりの対応にどれだけの工数がかかるか」を具体的な金額に落とし込む作業が欠かせません。この金額の見積もりが粗いままだと、いくら精緻に最適閾値を計算しても、その根拠自体が脆弱になってしまいます。

不均衡データへの対処は、評価指標をPR-AUCに切り替える、class_weightで学習時の重みを補正する、コスト行列に基づいて閾値を調整するという3つの手を組み合わせるのが基本形です。サンプリングは有効な選択肢ですが、情報損失や過学習、確率出力の歪みという副作用を伴うため、まずはデータを増減させないclass_weightと閾値調整から着手することをおすすめします。

異常検知への転換、教師なしアプローチが必要になる場面

ここまでのアプローチは、いずれも「少数派クラスにもある程度のラベル付きサンプルが存在する」ことを前提としていました。しかし実務では、それすら成り立たない場面に遭遇します。新種のサイバー攻撃、これまで一度も発生したことのない設備の故障モード、突如として現れる新しい手口の不正利用など、「異常」の具体例がほぼ存在しない、あるいはそもそも「何が異常か」を事前に定義できないケースです。

このような場面では、教師あり学習による不均衡データ分類というアプローチ自体が成立しません。分類モデルは「過去に見たパターンの中から異常を見分ける」ことしかできず、過去に一度も観測されていない種類の異常には原理的に対応できないためです。ここで発想を転換し、異常検知(Anomaly Detection)と呼ばれる教師なし学習のアプローチに切り替える必要があります。

異常検知の基本的な考え方は、「異常とは何か」を直接学習するのではなく、「正常とは何か」を学習し、そこから外れる度合いが大きいデータを異常として検出するというものです。この発想の転換により、事前に想定していなかった未知のパターンの異常であっても、正常なデータの分布から大きく外れてさえいれば検出できる可能性が生まれます。不均衡分類と異常検知の使い分けの目安を整理すると、次のようになります。

観点 不均衡データ分類が向く場面 異常検知が向く場面
ラベルの有無 少数派クラスにも一定数のラベル付きサンプルが存在する 異常のラベルがほぼない、または「異常」の定義自体が事前に定まらない
異常のパターン 過去に発生した既知のパターンの延長線上で判別できる 過去に見たことのない未知のパターンにも対応する必要がある
学習の枠組み 教師あり学習(正常・異常の両方のラベルを使って学習) 教師なし学習(正常なデータの分布のみから学習し、外れ度合いをスコア化)

代表的な異常検知手法

異常検知には多様な手法がありますが、実務でよく使われる代表的な手法の直感と使いどころを整理します。

手法 直感 使いどころ
統計的手法(3シグマ法・ホテリング理論) 正常データが正規分布に従うと仮定し、平均から標準偏差の何倍離れているかで異常度を測る。多変量の場合はホテリングのT2統計量を用い、変数間の相関構造まで考慮した「分布の中心からの距離」を計算する センサー値など単純で解釈しやすい指標の監視、変数間の関係が比較的安定している設備モニタリングなど。前提となる正規分布から大きく外れたデータには弱い
Isolation Forest データをランダムに分割する木構造を繰り返し作り、あるサンプルが少ない分割回数で孤立(他のサンプルから切り離される)するほど異常度が高いとみなす。異常なサンプルは特徴量空間の中でまばらな領域に位置するため、少ない分割で孤立しやすいという性質を利用する 特徴量の数が多い高次元データ、正常データの分布形状を事前に仮定したくない場合。学習も予測も高速で、実務での適用例が多い
One-Class SVM 正常データのみを使い、特徴量空間上で正常データをできるだけ広く包み込む境界(超平面)を学習する。この境界の外側に位置するデータを異常と判定する 正常データの構造が比較的複雑で非線形な境界が必要な場合。ただしデータ件数が多いと計算コストが高くなりやすく、カーネルやハイパーパラメータの調整に手間がかかる
LOF(Local Outlier Factor) あるサンプルの周辺にあるデータの密度を、その近傍データがさらに持つ周辺密度と比較し、相対的に密度が低い(孤立している)サンプルほど異常度が高いとみなす。データ全体で一様な基準ではなく、局所的な密度の違いを考慮する 正常データの中に密度の異なる複数のクラスタが混在している場合。全体としては密集地帯にあるが、その中では相対的に浮いているという「局所的な異常」の検出に強い
Isolation Forest・One-Class SVM・LOFがそれぞれ異なる形の異常データをどう捉えるかを2次元の散布図で比較したイメージ

以下は、Isolation Forestを用いて異常検知を行う実装例です。正常データが大半を占めるデータセットに、意図的に外れ値を混ぜて検出できるかを確認します。

import numpy as np
from sklearn.ensemble import IsolationForest

rng = np.random.default_rng(0)

# 正常データ(2次元の正規分布に従うと仮定)を1000件生成
X_normal = rng.normal(loc=0, scale=1.0, size=(1000, 2))

# 一様分布から20件を混入させる(一部は正常データの分布と重なる)
X_anomaly = rng.uniform(low=-6, high=6, size=(20, 2))
X = np.vstack([X_normal, X_anomaly])

# 全体に占める異常の想定割合(contamination)を指定して学習
model = IsolationForest(
    n_estimators=200,
    contamination=0.02,
    random_state=0,
)
model.fit(X)

# 1: 正常, -1: 異常 として判定される
labels = model.predict(X)

# 異常スコア(値が小さいほど異常)
scores = model.decision_function(X)

n_detected = np.sum(labels[-20:] == -1)
print(f"混入させた20件のうち異常と判定された件数: {n_detected}")
print(f"データ全体での異常スコアの分布(最小値/最大値): "
      f"{scores.min():.3f} / {scores.max():.3f}")

このコードでは20件を混入させ、そのうち14件が異常と判定されます。残りの6件は一様分布から引いた際にたまたま正常データの密集地帯に落ちたもので、座標だけを見て正常と区別することは原理的にできません。正常領域から明確に離れた14件については、すべて検出できています。異常検知の評価では、このように「そもそも識別可能な異常だったのか」を切り分けて見る必要があります。

contaminationパラメータは、データ全体に占める異常データの想定割合を指定するもので、この値が予測結果の閾値に直接影響します。decision_functionが返すスコアは値が小さいほど異常度が高いことを示し、predictはこのスコアをcontaminationに応じた閾値で二値化した結果を返します。実務では、predictが返す二値の判定結果だけでなく、decision_functionの連続値のスコアを保持しておき、後述する運用設計の中で閾値を柔軟に調整できるようにしておくことが重要です。

異常検知の実務設計

異常検知の手法そのものを実装できることと、それを実務で機能させることの間には大きな距離があります。実務設計で押さえるべき論点を整理します。

第一に、異常検知モデルは連続値の異常スコアを出力し、そのスコアをどこで区切って「アラートを出すか」を決める運用上の閾値が必ず必要になります。この閾値は、統計的な基準だけで機械的に決めるのではなく、アラートを受け取って調査する現場の人員体制や、見逃した場合の被害の大きさとのバランスで決める必要があります。不均衡データ分類における閾値調整と同様に、ここでも誤報率(正常なのに異常と判定してしまう割合)と見逃し率のトレードオフを常に意識する必要があります。

第二に、誤報率とのトレードオフは、異常検知システムの実運用における最大の課題になりやすい点です。閾値を厳しく(異常と判定しやすく)設定すれば見逃しは減りますが、現場には大量のアラートが押し寄せ、本当に重要なアラートが埋もれてしまう「アラート疲れ」を招きます。逆に閾値を緩めれば誤報は減りますが、重大な異常を見逃すリスクが高まります。運用開始後も、アラートの的中率や現場の対応状況を継続的にモニタリングし、閾値を定期的に見直す仕組みを組み込んでおくことが望ましいといえます。

第三に、そして実務上もっとも重要な論点が、正常データの定義そのものです。異常検知モデルの性能は、アルゴリズムの選定以上に、学習に使う「正常データ」がどれだけ正しく正常な状態を代表しているかに左右されます。もし学習データの収集期間中に、記録されていない小さな異常や、季節性・稼働状況の変化による正常な変動が混在していれば、モデルはそれらを「正常」として誤って学習してしまい、本番運用で正しく機能しません。異常検知プロジェクトの初期段階では、モデル構築そのものよりも、「どの期間のどのデータを正常な基準とするか」を業務知識に基づいて丁寧に定義する作業に、時間と労力の大半を割く必要があります。

製造業における適用例としては、生産設備に取り付けたセンサーの振動・温度・電流値などの時系列データを学習し、通常の稼働パターンから外れる予兆を検知することで、突発的な故障が発生する前に保守作業を計画する取り組みが広がっています。金融分野では、口座の取引パターンを学習し、普段と異なる時間帯・金額・送金先への取引を異常スコアとして検出することで、不正利用や口座乗っ取りの疑いがある取引を早期に発見する仕組みに使われています。いずれの領域でも、異常検知システム単体で自動的に判断を完結させるのではなく、スコアが高い事例を専門知識を持つ担当者が確認し、最終的な判断を下すという運用体制とセットで設計されている点が共通しています。

  • 異常検知の連続スコアを保持し、閾値は業務側の運用体制やコストに応じて柔軟に調整できるようにする
  • 誤報率と見逃し率のトレードオフを定期的にモニタリングし、閾値を継続的に見直す
  • 「正常データ」の定義そのものに、モデル構築以上の時間と業務知識を投じる
  • 異常検知モデルの判定を最終決定とせず、専門知識を持つ担当者による確認プロセスと組み合わせる

この章を深めたい方への参考書籍

『異常検知と変化検知』(井手剛・杉山将、講談社機械学習プロフェッショナルシリーズ):ホテリング理論やベイズ的な異常検知、変化点検知までを数理的な背景とともに体系立てて解説した専門書です。本章で直感的に紹介した統計的手法の理論的な裏付けを深めたい読者、時系列データの変化点検知まで学習範囲を広げたい読者に適しています。

第12章 モデル選択とチューニング、パイプラインによる実践

前章では不均衡データと異常検知という、実務で頻繁に出会う課題を扱いました。ここまでの11章で、機械学習の考え方(第1章・第2章)、汎化とリーク(第3章)、バイアスとバリアンス、正則化(第4章)、評価指標(第5章)、分類・回帰の主要アルゴリズム(第6章から第8章)、次元削減とクラスタリング(第9章・第10章)、不均衡データと異常検知(第11章)と要素を積み上げてきました。最終章となる本章では、これらの要素を実際のプロジェクトの中でどう組み合わせ、どう運用に落とし込むかという「組み立て方」を扱います。個々のアルゴリズムを深く理解していても、正しい手順で組み合わせられなければ実務では機能しません。本章はその実践的な作法をまとめる、本ガイドを締めくくる章です。

モデル選択の全体像、ベースラインから始めて複雑化する定石

機械学習プロジェクトを進める際、最初から複雑で高性能とされるモデル(勾配ブースティングやニューラルネットワークなど)に飛びつきたくなる場面は少なくありません。しかし実務でモデルを選び抜く定石は、まず単純なベースラインを立て、必要に応じて段階的に複雑さを上げていく順序です。回帰であれば線形回帰、分類であれば第6章のロジスティック回帰が、最初に試すべきベースラインの代表格になります。

いきなり複雑なモデルから着手することを避けるべき理由は、性能面よりもプロセス管理の面にあります。単純なモデルは学習も予測も高速で、前処理やデータ読み込みに潜むバグ、目的変数の定義の誤りといった性能以前の問題を早い段階で発見しやすくなります。また、ベースラインの精度があってはじめて、複雑なモデルを導入した効果を定量的に評価できますし、線形回帰やロジスティック回帰は係数の解釈が容易で、業務担当者への説明やモデルの挙動検証もしやすいという利点があります。

ベースライン確立後は、決定木(第8章)のような非線形性や変数間の相互作用を扱えるモデル、次にランダムフォレストや勾配ブースティング木(XGBoost、LightGBMなど)といったアンサンブル(第8章)へと進むのが一般的な順序です。表形式データでは勾配ブースティング系がもっとも高い精度を出すことが多く、多くのプロジェクトはこの段階で着地します。画像・音声・自然言語のように構造の強いデータでは、ここでニューラルネットワークが選択肢に入ります。

段階代表的なモデル主な狙い
ベースライン線形回帰、ロジスティック回帰基準精度とパイプラインの健全性確認
第2段階決定木、kNN(第7章)非線形性の有無を確認
第3段階ランダムフォレスト、勾配ブースティング木実務での主力候補
第4段階ニューラルネットワーク画像・音声・言語への対応

この階段を必ず上まで上る必要はありません。ベースラインや第2段階の時点で業務要件を満たす精度が得られていれば、それ以上複雑なモデルに進む必要はなく、精度だけを絶対基準にせず、運用コストや説明責任とのバランスで着地点を決めることが実務上の判断になります。

前処理とモデルを一体化するPipeline、リークと再現性への備え

第3章では、標準化やエンコーディングの計算に検証データの情報が紛れ込むリーク(データ漏洩)を扱い、対策としてPipelineで前処理とモデルを1つにまとめる方法を紹介しました。ハイパーパラメータのチューニングを行う本章ではPipelineの役割がさらに重要になります。グリッドサーチやランダムサーチは内部で何十回、何百回と学習と評価を繰り返しますが、そのたびに前処理のパラメータが訓練データだけから計算し直されなければ、リークの影響が積み重なってしまうためです。

Pipelineにはリーク防止のほかに、再現性の確保と本番デプロイのしやすさという利点もあります。前処理とモデルを1つのオブジェクトにまとめておけば、学習済みのPipelineを保存し本番環境に読み込ませるだけで、学習時と同じ前処理を予測時にも再現できます。前処理とモデルのコードを別々に管理すると、両者の処理が食い違う事故が起こりやすく、これはモデル劣化の原因としても軽視できません。

実務のデータセットには数値とカテゴリの特徴量が混在していることがほとんどで、数値列には欠損値補完と標準化を、カテゴリ列には欠損値補完とワンホットエンコーディングを、というように列ごとに異なる前処理が必要です。これを一括して扱う仕組みがColumnTransformerで、どの列にどの前処理を適用するかを宣言的に指定し、Pipelineの1ステップとして組み込めます。

from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
# 以下、X_train(数値列+カテゴリ列のDataFrame)とy_trainは自社データに置き換えて使うテンプレートです
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import GridSearchCV, StratifiedKFold

numeric_features = ['age', 'income', 'tenure_months']
categorical_features = ['plan_type', 'region']

numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler()),
])
categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(handle_unknown='ignore')),
])

# 列ごとに異なる前処理をまとめて適用する
preprocessor = ColumnTransformer(transformers=[
    ('num', numeric_transformer, numeric_features),
    ('cat', categorical_transformer, categorical_features),
])

# 前処理とモデルを1つのPipelineに統合する
pipeline = Pipeline(steps=[
    ('preprocessor', preprocessor),
    ('clf', GradientBoostingClassifier(random_state=42)),
])

# ステップ名__パラメータ名の形でハイパーパラメータ空間を指定
param_grid = {
    'clf__n_estimators': [100, 200, 300],
    'clf__max_depth': [2, 3, 4],
    'clf__learning_rate': [0.01, 0.05, 0.1],
}

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
grid_search = GridSearchCV(
    pipeline, param_grid, cv=cv, scoring='roc_auc', n_jobs=-1,
)
grid_search.fit(X_train, y_train)

print(f'最良のパラメータ: {grid_search.best_params_}')
print(f'交差検証でのAUC: {grid_search.best_score_:.3f}')

この例のポイントは、GridSearchCVに渡しているのがモデル単体ではなく前処理まで含んだPipeline全体だという点です。交差検証の各フォールドで、欠損値補完・標準化・エンコーディングのすべてがその回の訓練データだけから計算し直されます。ハイパーパラメータの指定は「ステップ名__パラメータ名」の形式で行い、この例では’clf’に対してn_estimators(木の本数)、max_depth(深さ)、learning_rate(学習率)の3つを探索対象にしています。

数値列とカテゴリ列がColumnTransformerで分岐して別々の前処理を受け、その後Pipelineの中でモデルへと合流していく処理フロー図

グリッドサーチ、総当たりで探索空間を網羅する

ハイパーパラメータの候補をあらかじめ格子状(グリッド)に定義し、組み合わせをすべて試して交差検証で評価するのがGridSearchCVです。先ほどの例のようにn_estimatorsとmax_depth、learning_rateにそれぞれ3通りの候補を設定した場合、組み合わせは3×3×3で27通りとなり、5分割交差検証と組み合わせると学習は135回実行されます。

グリッドサーチの利点は、指定範囲内のすべての組み合わせを漏れなく試すため最良の組み合わせを確実に発見できる点です。弱点は、ハイパーパラメータの数や候補が増えるほど組み合わせ数が掛け算的に膨れ上がる点で、5個のハイパーパラメータに5通りずつ候補を設定すれば3,125通り、5分割交差検証では15,625回の学習が必要になり、計算資源とのトレードオフが無視できなくなります。まず粗い間隔で広く探索し、良い結果の出た近辺を細かい間隔で絞り込む二段階の探索が実務ではよく用いられます。

ランダムサーチ、無駄なく効率よく探索する

グリッドサーチの計算量の問題への実務上の解決策がRandomizedSearchCVによるランダムサーチです。あらかじめ格子状に組み合わせを定義するのではなく、各ハイパーパラメータの分布(範囲)を指定し、その中からランダムに組み合わせを抽出して指定回数だけ試行します。次のコードは、前掲のPipelineと交差検証の設定をそのまま引き継いで使います。

ランダムサーチがグリッドサーチより効率的なことが多い理由は、ハイパーパラメータには性能への影響がほぼないものと大きいものが混在しているという経験則にあります。グリッドサーチでは影響の小さい候補にも均等に計算資源が割かれますが、ランダムサーチでは影響の大きいハイパーパラメータの値をより広くまんべんなく試せるため、同じ試行回数でも良い組み合わせを発見しやすくなります。

from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, uniform

param_distributions = {
    'clf__n_estimators': randint(50, 500),
    'clf__max_depth': randint(2, 8),
    'clf__learning_rate': uniform(0.005, 0.2),
    'clf__subsample': uniform(0.6, 0.4),
}

random_search = RandomizedSearchCV(
    pipeline, param_distributions,
    n_iter=50,  # 試行回数(全組み合わせは試さない)
    cv=cv, scoring='roc_auc', random_state=42, n_jobs=-1,
)
random_search.fit(X_train, y_train)

print(f'最良のパラメータ: {random_search.best_params_}')
print(f'交差検証でのAUC: {random_search.best_score_:.3f}')

n_iterで試行回数を明示的に指定できる点も実務上の利点です。計算に使える時間や資源から逆算して試行回数を決められるため、探索空間が広い場合やハイパーパラメータが多い場合には、グリッドサーチよりランダムサーチを第一の選択肢とすることが多くなります。

ベイズ最適化、次に試すべき点を賢く選ぶ

グリッドサーチもランダムサーチも、それまでの試行結果を次の試行に反映させない探索方法です。これに対してベイズ最適化(Bayesian optimization)は、試したハイパーパラメータとその評価結果の関係を学習し、その結果をもとに「次に試すべき有望な点」を賢く選びながら探索を進める方法です。

ハイパーパラメータと評価指標の関係を表す代理モデル(サロゲートモデル)を構築し、まだ試していない組み合わせについても期待される性能とその不確実性を推定します。この推定結果をもとに獲得関数(acquisition function)という指標を使い、期待性能が高い点、あるいは不確実性が高く情報量の多い点を優先的に次の試行対象として選びます。この「試す、代理モデルを更新する、次の点を選ぶ」というサイクルを繰り返すことで、少ない試行回数で良いハイパーパラメータに到達できることが期待されます。

実務でベイズ最適化を使う際は、仕組みを自前で実装する必要はなく、Optunaのような専用ライブラリを利用するのが一般的です。Optunaは探索の途中経過の可視化や、見込みの薄い試行を早期に打ち切る仕組みも備え、scikit-learn以外にも勾配ブースティング系ライブラリやニューラルネットワークの探索にも広く使われています。探索するハイパーパラメータが多い場合や1回の学習に時間がかかり試行回数を抑えたい場合には、検討する価値があります。

早期停止と学習曲線、チューニングより先に確認すべきこと

チューニングに取りかかる前に、性能不足がチューニングで解決できる問題なのか、データの量そのものが不足しているのかを見極めることが重要です。この診断に使えるのが学習曲線(learning curve)です。

学習曲線は、訓練データの件数を少しずつ増やしながら学習させ、そのたびに訓練誤差とテスト誤差(または交差検証誤差)を記録してグラフ化したものです。データを増やしてもテスト誤差がほぼ下がらず両者の差が小さいまま横ばいなら、モデルの表現力が不足している学習不足(第3章)の状態であり、データ追加より複雑なモデルへの切り替えや特徴量追加を検討すべきです。逆に訓練誤差は低いままテスト誤差が高く差が大きい場合は過学習の兆候で、データを増やせば改善する可能性が高いといえます。

from sklearn.model_selection import learning_curve
import numpy as np

train_sizes, train_scores, valid_scores = learning_curve(
    pipeline, X_train, y_train,
    train_sizes=np.linspace(0.1, 1.0, 10),  # 訓練データの10%から100%まで
    cv=cv, scoring='roc_auc', n_jobs=-1,
)

train_mean = train_scores.mean(axis=1)
valid_mean = valid_scores.mean(axis=1)

for size, tr, va in zip(train_sizes, train_mean, valid_mean):
    print(f'訓練件数: {int(size):>6}  訓練AUC: {tr:.3f}  検証AUC: {va:.3f}')

learning_curve関数は、指定した訓練件数ごとに交差検証を実行し、訓練スコアと検証スコアの平均を返します。訓練件数を横軸にとってグラフに描けば、データ追加とモデル・パラメータ見直しのどちらに投資すべきかの判断材料になります。この診断を経ずにチューニングへ進むと、データ不足が原因の問題をハイパーパラメータ調整だけで解決しようとして時間を浪費することがあります。

学習曲線と近い発想を持つのが、勾配ブースティング木やニューラルネットワークで用いられる早期停止(early stopping)です。検証データに対する誤差を監視し、改善しなくなった時点で学習を打ち切る仕組みで、木の本数やエポック数を固定値で決め打ちせず自動的に適切な値へ収束させる、第4章の正則化の一種として位置づけられます。

AutoMLの現在地、自動化される部分とされない部分

AutoML(Automated Machine Learning)は、モデル選択やハイパーパラメータ探索、特徴量変換の一部までを自動化する技術・ツール群の総称です。Google Cloud、AWS、Microsoft Azureなど主要クラウドベンダーがサービスを提供するほか、AutoGluonやH2O AutoMLといったオープンソースライブラリも実務で使われています。

AutoMLが得意とするのは、用意された特徴量とラベルを入力として、複数のアルゴリズムを横断的に試し、ハイパーパラメータを探索し、複数モデルのアンサンブル(スタッキングなど)まで含めて精度の高いモデルを自動構築する部分です。本章で説明したPipeline、GridSearchCVやRandomizedSearchCV、ベイズ最適化に相当する探索の大部分は、AutoMLツールの内部で自動的に実行されます。

一方でAutoMLが自動化できない領域も明確に存在します。どのような業務課題をどのような予測問題として定式化するかという課題設計そのものは対象外です。第1章のとおり、「解約を減らしたい」を「解約確率を予測する二値分類問題」とするか「解約までの期間を予測する生存時間分析」(ある出来事が起きるまでの時間そのものを対象にする統計手法)とするかは、業務を理解した人間の判断です。目的変数が確定した後にしか値を持たない特徴量(リークの原因、第3章)が紛れ込んでいないかという業務的妥当性の検証も、AutoMLは代行しません。精度・適合率・再現率・AUCといった評価指標(第5章)のうちどれを最適化対象にすべきかの選定も、業務のコストと利益の構造を理解した人間の判断に委ねられています。

AutoMLは「与えられた課題設定の中で最良のモデルを探す」作業を自動化する技術であり、「どのような課題を解くべきか」を設計する作業までは代行してくれません。課題設計、特徴量の業務的な意味づけ、評価指標の選定という、本ガイドが第1章から重視してきた「設計」の部分は、AutoMLの時代になってもなお人間の仕事として残ります。

ネストした交差検証、チューニングと評価を分ける

第3章では、チューニングに使うデータと最終評価に使うデータを分けておく必要があると説明しました。GridSearchCVのbest_score_をモデルの最終性能としてそのまま報告すると、そのスコアには選択バイアスがかかり実際の汎化性能より高めに出る傾向があります。交差検証の各フォールドを、候補の中から「もっとも都合の良い結果を出した候補」を選ぶための基準として使ってしまっているためです。

この選択バイアスを避ける厳密な評価方法がネストした交差検証(nested cross validation)です。外側と内側の二重の交差検証を持ち、外側は最終評価のためだけに使います。各フォールドの訓練データに対して内側の交差検証(GridSearchCVそのもの)でハイパーパラメータを選定し、選定したモデルを外側のテストデータで評価する手順を外側の分割数だけ繰り返し、その平均を最終的な汎化性能の推定値とします。

計算コストは高く(外側の分割数×内側の試行回数の掛け算になるため)実務のすべての場面で使われるわけではありませんが、複数モデルをチューニング後の公平な条件で比較したい場合や、厳密な性能評価が求められる場面では有力な選択肢になります。

本番運用の備え、モデルは劣化するという前提を持つ

ここまでの説明はモデルを構築し評価する工程が中心でしたが、機械学習プロジェクトの価値はモデルを本番環境に投入し運用し続けてはじめて生まれます。そして本番運用で必ず直面するのが、モデルの性能は時間とともに劣化するという事実です。

この劣化の主な原因がデータドリフトです。学習時点のデータ分布と本番運用で入力されるデータ分布が、顧客行動や市場環境の変化、季節性などによって時間とともにずれていく現象を指します。入力分布は変わらなくても入力と目的変数の関係性が変化するコンセプトドリフトと呼ばれる現象もあり、こちらは入力の監視だけでは検出できないため、気づかれないまま精度低下が進みやすい点に注意が必要です。

対応としては、本番環境の入力データや予測結果の分布を継続的に監視し、学習時の分布からの乖離が基準を超えたらアラートを出す仕組みが基本になります。あわせて、どのタイミングでどのデータを使って再学習するかという設計(定期的な再学習か、ドリフト検知をトリガーとした再学習か)も欠かせません。こうしたモデルの監視、再学習、デプロイの自動化を扱う領域はMLOps(Machine Learning Operations)と呼ばれ、独立した専門領域として確立されつつあります。本ガイドではMLOpsの詳細には立ち入りませんが、劣化を前提とした継続的な監視と改善のサイクルを組織として設計しておくことが、プロジェクトを一過性のPoCで終わらせないための要点になります。

時間軸に沿ってモデルの精度が徐々に低下していく様子と、ドリフト検知によって再学習がトリガーされ精度が回復するサイクルを示す図

本章と本ガイド全体のまとめ

本章では、ベースラインから複雑なモデルへ段階的に進む定石、リークを防ぎ再現性を担保するPipeline、グリッドサーチ・ランダムサーチ・ベイズ最適化というハイパーパラメータ探索の広がり、チューニング以前にデータの充足を診断する学習曲線、AutoMLが自動化する範囲としない範囲、ネストした交差検証や本番運用における劣化への備えを扱いました。これらはいずれも、モデル単体の性能を高める技術というより、モデルを実務のプロセスの中に正しく組み込むための技術です。第1章の機械学習の定義から始まり、学習の仕組み、汎化とリーク、バイアスとバリアンス、評価指標、主要アルゴリズム、次元削減とクラスタリング、不均衡データと異常検知を経て本章に至るまでの12章を通じて一貫して伝えたかったのは、機械学習の技術は正しく設計された課題の上に乗せてはじめて価値を持つ、という考え方です。

どれほど交差検証を厳密に組んでも、どれほどハイパーパラメータを丹念にチューニングしても、解こうとしている課題の設定自体が業務の実態とずれていれば、モデルが生み出す価値は限定的です。逆に課題設定さえ正しければ、ベースラインのロジスティック回帰程度の技術水準であっても、業務に大きなインパクトをもたらすことは十分にあり得ます。

本ガイドで扱った12章分の技術は、いずれも「解くべき課題が正しく設計されている」という前提のもとで初めて価値を発揮します。モデルの精度をどれだけ高めても、課題設計そのものが誤っていれば、その努力は業務の成果には結びつきません。技術の引き出しを増やすことと同じくらい、何を、何のために予測・分類・検出するのかという課題設計に時間をかけることが、機械学習プロジェクトを成功に導く土台になります。

Anagraftでは、AIプロジェクトの構想・課題設計から、データ分析・機械学習モデルの開発、AI人材の育成まで一貫したご支援を行っています。ご相談は、以下よりお問い合わせください。

お問い合わせ

この章を深めたい方への参考書籍

『機械学習デザインパターン』(Valliappa Lakshmanan ほか、オライリー・ジャパン):データの準備からモデルの学習、評価、本番デプロイと運用まで、機械学習システムの構築で繰り返し現れる問題とその定石をパターン集として整理した1冊です。本章で扱ったPipelineの設計思想や、本番運用におけるモデルの劣化・再学習という論点を、より広い実務パターンの中に位置づけて学びたい読者に適しています。

終章 機械学習を成果につなげるために

12章で積み上げてきたこと

本ガイドでは、機械学習の考え方(データからパターンを学ぶとはどういうことか)から始まり、信頼できる評価の作法(過学習・交差検証・リーク・評価指標)、主要アルゴリズムの仕組みと使いどころ(線形モデルからアンサンブル、教師なし学習まで)、そして実務の壁(不均衡データ・異常検知)とモデル選択の総仕上げまでを一続きに見てきました。

全体を貫いていたメッセージは2つあります。ひとつは、「モデルの良し悪しは、見たことのないデータへの性能で測る」という汎化の原則です。訓練データでの精度に意味はなく、検証設計を誤れば数字はいくらでも良く見えてしまいます。この原則は、どんなに新しい手法が登場しても変わりません。もうひとつは、「アルゴリズムの優劣より、課題設計と評価設計が先」ということです。同じデータ、同じ手法でも、何を予測対象とし、どの指標で測り、その予測を業務のどこに組み込むかの設計次第で、生まれる価値は桁で変わります。

実務プロセスに落とし込む

本ガイドで学んだ内容を実際のプロジェクトで使う際の、標準的な進め方を整理しておきます。

  1. 課題とKPIの設計:何を予測できると、誰の行動が変わり、どの数字が動くのかを先に決めます。ここが曖昧なまま着手しないことが最大の分かれ目です
  2. ベースラインの構築:単純な集計ルールや線形モデルで最初の基準を作ります。複雑なモデルの価値は、この基準からの改善幅で測ります
  3. 検証設計の固定:データの分割方法、評価指標、判定基準を実験前に決めて文書化します(時系列なら時間で分割、など)
  4. モデルの改善サイクル:特徴量の追加とモデルの変更を、固定した検証設計の上で比較します。リークを常に疑います
  5. ビジネス評価と運用設計:技術指標を金額に翻訳し、導入判断を仰ぎます。導入後はデータの変化(ドリフト)を監視し、再学習の計画を持ちます

機械学習プロジェクトの失敗の多くは、モデルの精度不足ではなく、課題設計・検証設計・運用設計のどれかが抜けていたことに起因します。アルゴリズムの知識と同じ重みで、この3つの設計を大切にしていただければと思います。

次に学ぶこと

本ガイドの先には、次の領域が広がっています。本シリーズおよび既刊のコラムで順次カバーしていきます。

  • 回帰分析と統計モデリング編(本シリーズ続刊):予測ではなく「要因の説明」を目的とする場合の回帰の作法。第6章で触れたGLMの世界です
  • 時系列分析編(本シリーズ続刊):時間の順序が本質になるデータの専用手法。なぜ通常の回帰をそのまま使ってはいけないかから解説します
  • 深層学習編(計画中):画像・テキストなど非構造化データの主役。本ガイドの損失関数と勾配法の知識がそのまま土台になります
  • 既刊「説明可能AI(XAI)の実践ガイド」:本ガイドで作ったモデルの判断根拠を説明する技術(SHAP、Grad-CAMなど)を扱っています
  • コードの逆引き集:本ガイドの内容をコードから引きたい方に向けたレシピ集を別途ご用意しています

Anagraftでは、AIプロジェクトの構想・課題設計から、データ分析・機械学習モデルの開発、AI人材の育成まで一貫したご支援を行っています。ご相談は、以下よりお問い合わせください。

お問い合わせ

本コラムで紹介した書籍一覧

各章末でご紹介した参考書籍の一覧です。学習の段階に合わせてお選びください。

  • 『AI・データ分析プロジェクトのすべて』(大城信晃ほか、技術評論社):企画から運用まで、プロジェクトの全工程を実務目線で整理した一冊。第1章の「使いどころ」の視点を実務に広げられます
  • 『見て試してわかる機械学習アルゴリズムの仕組み 機械学習図鑑』(秋庭伸也ほか、翔泳社):代表的なアルゴリズムを図解と短いコードで1つずつ確認できる見取り図的な一冊。個別の手法の章に入る前後のリファレンスとして便利です
  • 『機械学習のエッセンス』(加藤公一、SBクリエイティブ):数学とアルゴリズムをPythonで実装しながら本質から理解できる定番書。第2章の最適化の中身を深掘りできます
  • 『Pythonではじめる機械学習』(Andreas C. Muller、Sarah Guido、オライリー・ジャパン):scikit-learnの実践的入門の定番。交差検証やパイプラインの実装を体系的に学べます
  • 『イラストで学ぶ機械学習』(杉山将、講談社):最小二乗法を軸に主要手法を図解で貫く入門書。第4章の正則化の理解を補強できます
  • 『評価指標入門』(高柳慎一・長田怜士、技術評論社):評価指標とビジネスの接続に正面から取り組んだ珍しい一冊。第5章の内容をより深く実務に落とせます
  • 『Pythonで動かして学ぶ!あたらしい機械学習の教科書』(伊藤真、翔泳社):数式とコードを一対一で対応させながら進む丁寧な教科書。ロジスティック回帰の仕組みを手を動かして確認できます
  • 『サポートベクトルマシン』(竹内一郎・烏山昌幸、講談社):SVMの理論を正面から学びたい方のための専門書です
  • 『Kaggleで勝つデータ分析の技術』(門脇大輔ほか、技術評論社):GBDTを軸にした特徴量エンジニアリングと検証設計の実践知が凝縮されています。第8章の先の実戦力を磨けます
  • 『はじめてのパターン認識』(平井有三、森北出版):識別・次元削減の理論を大学教科書レベルで固められる定番です
  • 『続・わかりやすいパターン認識 教師なし学習入門』(石井健一郎・上田修功、オーム社):クラスタリングとEMアルゴリズムを丁寧に解説した教師なし学習の良書です
  • 『異常検知と変化検知』(井手剛・杉山将、講談社):異常検知の主要手法を体系的に扱う日本語では貴重な専門書。第11章の先へ進めます
  • 『機械学習デザインパターン』(Valliappa Lakshmanan ほか、オライリー・ジャパン):本番運用を見据えた設計の定石集。第12章のPipelineの考え方を運用まで延長できます
  • 『仕事ではじめる機械学習』(有賀康顕ほか、オライリー・ジャパン):機械学習を業務システムに組み込む際の考え方を扱った実務家の定番。終章のプロセス設計と併せて読むと効果的です

参考情報・出典

本コラムに掲載したコードは、Python 3.14/scikit-learn 1.9.0/NumPy 2.4.3/SciPy 1.16.3/pandas 3.0.1/LightGBM 4.6.0/imbalanced-learn 0.14.2 で実行して出力を確認しています。本文に載せた数値は、この環境で実際に印字された値です。scikit-learnは版によって既定値や引数の扱いが変わることがあるため、手元で実行して結果が異なる場合は、お使いのバージョンの公式ドキュメントをあわせてご確認ください。