ぱと隊長日誌

ブログ運用もエンジニアとしての生き方も模索中

機械学習における CV と DCV の違いと内部挙動

はじめに

機械学習における CV (Cross-Validation) と DCV (Double Cross-Validation) の違いを説明した記事は数多くありますが、概念やライブラリの使い方を中心としたものが多いようです。

本記事では、CV と DCV の内部で、どのデータを用いて何を行っているのかに踏み込んで説明します。また、説明の根拠となる文献の記述をできるだけ本文中に示します。

なお、DCV は Nested Cross-Validation または Nested Resampling と呼ばれることもあります。文献によって用語の使い方が異なる場合がありますが、本記事では「外側 CV の各訓練データの中で、内側 CV によるモデル選択を繰り返す二重構造」を DCV と呼ぶことにします。

なぜ CV が必要なのか

機械学習において、ハイパーパラメータはモデルの性能を左右する重要な要素です。

適切なハイパーパラメータは、使用する機械学習手法やデータセットによって異なるため、多くの場合、実際のデータを用いて選択します。

ここで問題になるのが、何を基準に良いハイパーパラメータを選ぶかです。

機械学習の目的は、学習に使用したデータではなく、未知のデータに対して良い予測を行うことです。しかし、学習に使用したデータで同じモデルを評価すると、未知データに対する性能を過度に良く見積もる可能性があります。

Using the same data to train and test a model is a bad strategy as it would lead to an overly optimistic performance estimate.
[3, 3 Evaluation and Benchmarking]

学習に使用したデータに対する誤差は、学習済みモデルがそのデータにどの程度適合したかを示すものであり、未知データに対する性能を直接示すものではありません。

そこで、手持ちのデータを訓練用と評価用に分けながら繰り返し評価する CV を使用します。

k-fold CV の基本的な動作

k-fold CV では、データセットを k 個の部分集合、すなわち fold に分割します。

基本的な手順は次のとおりです。

1. データセットを k 個の fold に分割する。
2. 1 個の fold を検証データとし、残りの k−1 個を訓練データとする。
3. 訓練データでモデルを学習する。
4. 検証データに対する予測誤差を計算する。
5. 検証データとする fold を順番に変えながら、手順 2~4 を k 回繰り返す。
6. k 回の評価結果を集約する。

[2] の資料では、手順 6 について、「k 個の予測誤差の平均を計算します」としています。また、その平均値について、「CV スコアは汎化誤差(未知データに対する誤差)の推定値」と説明しています。
[2, スライド24]

したがって、各 fold の大きさが同じで、評価指標の集約方法にも問題がない場合は、k 個の予測誤差の平均を CV スコアとして使用します。

ただし、fold の大きさが異なる場合や、RMSE のような非線形な評価指標を使用する場合には注意が必要です。

例えば、次の二つは必ずしも一致しません。

  • fold ごとに RMSE を計算してから平均する
  • すべての out-of-fold 予測をまとめて一つの RMSE を計算する

そのため、実際には使用するライブラリが評価値をどのように集約しているかも確認する必要があります。

CV で「学習手順全体」を評価する

Varma と Simon は、CV によって性能を適切に評価するための前提について、次のように説明しています。

CV methods are proven to be unbiased only if all the various aspects of classifier training takes place inside the CV loop. This means that all aspects of training a classifier e.g. feature selection, classifier type selection and classifier parameter tuning takes place on the data not left out during each CV loop.
[1, Background]

すなわち、特徴量選択、機械学習手法の選択、ハイパーパラメータ調整など、データに基づいてモデルを決めるすべての処理を、各 CV ループの訓練データだけで行う必要があります。

k-fold CV によるハイパーパラメータ選択

「予測誤差が小さいほど良い」という評価指標を使用するものとします。

k-fold CV を用いてハイパーパラメータを選択する場合、一般には次の手順になります。

1. データセットを k 個の fold に分割する。
2. 評価するハイパーパラメータ候補を一つ選ぶ。
3. 1 個の fold を検証データとし、残りの fold でモデルを学習する。
4. 検証データに対する予測誤差を計算する。
5. 検証データとする fold を順番に変えながら、手順 3~4 を繰り返す。
6. k 個の予測誤差を集約し、評価対象ハイパーパラメータの CV 誤差とする。
7. 別のハイパーパラメータ候補について、手順 3~6 を繰り返す。
8. CV 誤差が最も小さいハイパーパラメータを選択する。
9. 選択したハイパーパラメータを用いて、データセット全体でモデルを学習する。

ここで重要なのは、CV 誤差には二つの異なる用途があることです。
第一の用途は、複数の候補からハイパーパラメータを選ぶことです。
第二の用途は、モデルやモデル構築手順の未知データに対する性能を推定することです。

同じCV結果を、これら二つの用途にそのまま使用してよいとは限りません。

最小の CV 誤差を性能評価値として使えるか

複数のハイパーパラメータ候補について CV 誤差を計算し、その中から最も小さい値を選んだとします。

各候補の CV 誤差には、サンプルのばらつきや fold 分割による偶然の変動が含まれます。その中から最小値を選ぶと、本当に性能が良い候補だけでなく、偶然 CV 誤差が小さくなった候補も選ばれやすくなります。

したがって、ハイパーパラメータ選択に使用した最小 CV 誤差を、そのまま最終モデルの性能推定値として使用すると、未知データに対する性能を楽観的に評価する可能性があります。

ここで問題になっているのは、CV をハイパーパラメータ選択に使用すること自体ではありません。次の二つを同じ CV 結果で行うことが問題です。

  • CV 誤差を用いて最適なハイパーパラメータを選ぶ
  • 選択に使った最小 CV 誤差を、選択後のモデルの性能として報告する

mlr3book では、このことを次のように説明しています。

HPO requires additional resampling to reduce bias when estimating the performance of a model. If the same data is used for determining the optimal configuration and the evaluation of the resulting model itself, the actual performance estimate might be biased (Simon 2007).
[4, 4.3 Nested Resampling]

つまり、ハイパーパラメータ選択を含めたモデル構築と、構築されたモデルの評価は分けて行う必要があります。

DCV によるモデル選択と性能評価の分離

この問題に対処するため、DCV では次の二つを別の CV ループで行います。

  • 内側CV:モデル構成の選択
  • 外側CV:モデル構成の選択を含む構築手順の性能評価

mlr3book では、DCV の役割を次のように説明しています。

Nested resampling separates model optimization from the process of estimating the performance of the tuned model by adding an additional resampling
[4, 4.3 Nested Resampling]

つまり、モデルの最適化と、最適化済みモデルの性能推定を分離するために、もう一つ外側のリサンプリングを追加します。

内側 CV では、ハイパーパラメータを選択します。複数の機械学習手法も候補として比較する場合は、手法の選択も内側 CV に含めます。

外側 CV では、その選択処理を含むモデル構築手順を、内側 CV で一度も使用していないデータによって評価します。

DCV の具体的な手順

外側 CV を m-fold、内側 CV を n-fold とします。

1. データセットを m 個の外側 fold に分割する。
2. 1 個の fold を外側テストデータとし、残りを外側訓練データとする。
3. 外側訓練データだけを n 個に分割し、内側 CV を行う。
4. 内側 CV によって、ハイパーパラメータやモデル構成を選択する。
5. 選択した構成を用いて、外側訓練データ全体でモデルを学習する。
6. 学習したモデルを、外側テストデータで評価する。
7. 外側テストデータとする fold を変えながら、手順2~6を m 回繰り返す。
8. m 個の外側評価値を集約する。

処理の構造を簡略化すると、次のようになります。

外側 CV
    外側訓練データと外側テストデータに分割する

    外側訓練データだけを用いて内側 CV を行う
        各ハイパーパラメータ候補を評価する
        最適な候補を選択する

    選択したハイパーパラメータ候補を用いて外側訓練データ全体で学習する
    外側テストデータで予測誤差を計算する

外側 fold で得られた予測誤差を集約する

重要なのは、外側テストデータがハイパーパラメータ選択を含めたモデルの構築に使用されないことです。外側テストデータは、モデル構築処理が完了した後の評価にだけ使用します。

外側 CV の集約値は何を表すのか

外側 CV で得られた m 個の誤差の集約値は、特定の一組のハイパーパラメータを持つ一つのモデルの性能を表すものではありません。外側 fold ごとに訓練データが異なるため、内側 CV によって選ばれるハイパーパラメータも異なる可能性があります。

mlr3book では、DCV の性能評価が次のものに基づくと説明されています。

Nested resampling is a method to compare models and to estimate the generalization performance of a tuned model, however, this is the performance based on multiple different configurations (one from each outer fold) and not performance based on a single configuration.
[4, 4.3 Nested Resampling]

例えば、外側 CV が 5-fold の場合、内側 CV によって 5 つの異なるハイパーパラメータ構成が選ばれる可能性があります。

したがって、DCV が直接評価しているのは、「特定のハイパーパラメータを設定した一つのモデル」ではなく、「与えられた訓練データの中で内側 CV を行い、モデル構成を選択し、その構成でモデルを学習する手順」です。

より正確には、外側 CV の集約値は、内側 CV によるモデル選択を含む一連のモデル構築手順を、未知データに適用したときに期待される性能を推定するものです。

なお、DCV によって、あらゆるバイアスが完全になくなるわけではありません。外側 CV の各モデルは、データセット全体より少ないデータで学習されます。そのため、データセット全体で学習する最終モデルよりも、外側 CV の誤差がやや大きくなる場合があります。

したがって、DCV については「完全に不偏な誤差推定値が得られる」と断定するよりも、「モデル選択に使用した最小 CV 誤差をそのまま報告する方法より、モデル選択による楽観的バイアスを抑えた性能推定ができる」と表現する方が適切です。

DCV は最終モデルを決める手法ではない

DCV は最終的な一組のハイパーパラメータを直接決める手法ではありません。

外側 fold ごとに内側 CV が行われるため、複数の異なるハイパーパラメータ構成が得られます。これらのうち、どれか一つをそのまま最終モデルの構成として採用することが、 DCV の目的ではありません。

mlr3book でもこのように述べています。

A common mistake is to think of nested resampling as a method to select optimal model configurations.
[4, 4.3 Nested Resampling]

DCV によって、モデル構築手順の期待される予測性能が実用上十分であることを確認した後は、次の手順で最終モデルを作成します。

1. データセット全体を対象に、DCV の内側で使用したものと同じモデル選択手順を実行する。
2. モデル選択手順の結果を踏まえ、最終的なモデル構成を選択する。
3. 選択した構成を用いて、データセット全体で最終モデルを学習する。

このとき選択される最終的なモデル構成は、DCV の各外側 fold で選択されたものと一致するとは限りません。

DCV の外側評価値は、この最終モデルそのものを直接評価した値ではありません。同じモデル選択手順を新しいデータに適用した場合に期待される性能の推定値として解釈します。

複数のモデル構築手順を比較する場合

DCV は複数のモデル構築手順の比較にも使用できます。

例えば、次の二つを比較できます。

  • 内側 CV でハイパーパラメータを調整する SVR
  • 内側 CV でハイパーパラメータを調整する Random Forest

このとき、両者に同じ外側 fold を使用することで、同じデータ条件における性能を比較できます。

The result from the outer resampling can be used for comparison to other models trained and tested on the same outer folds.
[4, 4.3 Nested Resampling]

また、内側 CV の候補に複数の機械学習手法を含め、機械学習手法とハイパーパラメータを同時に選択することもできます。

その場合、外側 CV が評価するのは、「複数の機械学習手法とハイパーパラメータ候補の中から、内側CVによって最適な組合せを選択する手順」です。

ただし、外側 CV の結果を用いて多数のモデル構築手順から最良の一つを選び、その同じ外側CVスコアを最終性能として報告すると、比較段階における新たな選択バイアスが生じる可能性があります。

複数の構築手順から一つを選んだ後にも、完全に独立した最終性能評価が必要であれば、別のテストデータを用意するか、さらに外側の評価設計を検討する必要があります。

まとめ

通常の CV と DCV の違いは、単に CV を一回行うか、二重に行うかという点だけではありません。

通常の CV によるハイパーパラメータ調整では、CV 誤差を用いて最適な候補を選択します。しかし、選択に使用した最小 CV 誤差をそのまま性能評価値として報告すると、未知データに対する性能を楽観的に評価する可能性があります。

DCVでは、内側 CV でハイパーパラメータなどのモデル構成を選択し、外側 CV でその選択処理を含むモデル構築手順全体を評価します。機械学習手法もデータに基づいて選択する場合は、その選択も内側 CV に含めます。

したがって、外側 CV の誤差が表しているのは、特定の一つのハイパーパラメータ構成の性能ではありません。内側 CV による選択を含む、モデル構築手順全体に期待される性能です。

DCV による評価が完了した後は、データセット全体に対して同じモデル選択手順を適用し、最終的なハイパーパラメータを決定したうえで、データセット全体を用いて最終モデルを学習します。

参考文献

[1] Sudhir Varma and Richard Simon (2006): Bias in error estimation when using cross-validation for model selection

[2] 二反田篤史(2021): 「機械学習の基礎と展望」
https://www.mi.u-tokyo.ac.jp/pdf/3-3_ml_basic_and_future.pdf

[3] Applied Machine Learning Using mlr3 in R, Chapter 3.
https://mlr3book.mlr-org.com/chapters/chapter3/evaluation_and_benchmarking.html

[4] Applied Machine Learning Using mlr3 in R, Chapter 4.
https://mlr3book.mlr-org.com/chapters/chapter4/hyperparameter_optimization.html

本記事におけるAIの活用について

以下の編集プロセスを経ました。
1. 筆者が初稿を書き起こす。
2. ChatGPT で推敲(正確性や読みやすさの改善)を実行する。
3. 筆者が全体的な見直しを行う。
4. ChatGPT で改めて推敲を実行する。
5. 筆者が最終チェックを行う。

よって、文責は筆者にあります。

「化学のためのPythonによるデータ解析・機械学習入門(改訂2版)」略語集

化学のためのPythonによるデータ解析・機械学習入門(改訂2版)」は略語が頻出します。そこで、繰り返し登場する略語について一覧にまとめました。スムーズに読み進めるためのお役に立てば幸いです。

なお、本記事の内容は(改訂2版)を基準としていますが、初版でも活用できると思われます。

用語の説明は可能な限り同書を参考にしましたが、まとめが難しかったケースではAIで生成しています。その場合、"(AI)"と記載しています。

並び順はページ数を基準としています。

略称  英語  日本語  説明 ページ
PCA  Principal Component Analysis  主成分分析  多数の特徴量を、主成分軸上に投影したデータの分散が最大になるように主成分軸の方向を順に決め、少数の互いに直交する軸へ変換する手法です。これにより、情報の損失を抑えながらデータを低次元化し、サンプル間の関係を可視化できます。(AI)  26
t-SNE t-distributed Stochastic Neighbor Embedding    t-SNEとは、高次元空間におけるサンプル間の類似度を同時確率分布で表し、その分布と低次元空間における類似度の分布との差が小さくなるようにサンプルを配置する次元削減手法です。低次元空間ではスチューデントの t 分布を用いることで、近いサンプル同士を近く、異なる集団を離して可視化しやすくします。(AI)  43
OLS  (MLR) Ordinary Least Squares  (Multiple Linear Regression)  最小二乗法による線形重回帰分析  目的変数を複数の説明変数の線形結合で表し、実測値と推定値との差である残差の二乗和が最小となるように各説明変数の回帰係数を決定する手法です。(AI)  53
MAE Mean Absolute Error    誤差の絶対値の平均値。  58
RMSE Root-Mean-Squared Error    誤差を2乗して平均値を計算し1/2乗したもの。  58
PCR Principal Component Regression  主成分回帰  説明変数を選択せずに、PCAで説明変数を互いに無相関な主成分に変換してから、主成分と目的変数との間でOLSを行うこと。  61
CV Cross Validation  クロスバリデーション  データを複数のグループに分け、一部を学習用、残りを検証用として交代で用いることで、未知のデータに対するモデルの予測性能を評価する方法です。複数回の評価結果をまとめることで、特定のデータ分割に左右されにくい性能評価ができます。(AI)  62
PLS Partial Least Squares  部分的最小二乗法  主成分間の無相関性を維持しながら目的変数の情報を考慮して主成分を計算し、主成分と目的変数との間で回帰モデルを構築する手法の1つ。目的変数と主成分との間の共分散が最大になるように主成分軸の方向が決められます。  64
SVR Support Vector Regression  サポートベクター回帰  SVMを回帰分析に応用した手法で、基本的にはOLS,PCR,PLSのような線形の回帰分析手法ですが、カーネルトリックという手法により非線形手法に拡張できます。  70
DCV Double Cross-Validation  ダブルクロスバリデーション  外側のCVで分けたトレーニングデータに対して内側のCVを行い、ハイパーパラメータを最適化した後、その条件で構築したモデルを外側のテストデータに適用することで、モデルの予測性能をより適切に評価する方法です。(AI)  80
k-NN k-Nearst Neighbors algorithm  k近傍法  あるサンプルのクラスを推定するときに、クラスがすでにわかっているサンプルの中から、クラスを推定したいサンプルに最も近いk個のサンプルを選び、それらのクラスで多数決をとってクラスを決定する方法です。  82, 102 
SVM Support Vector Machine  サポートベクターマシン  SVRの基になった手法であり、2つのクラスを分類します。  87
DT Decision Tree  決定木  説明変数に対する条件でサンプルを順に分割し、各ノードに含まれるサンプルのクラスができるだけ偏るように分岐条件を決め、終端ノードに到達したサンプルのクラスを推定する分類手法です。(AI)  93
RF Random Forests  ランダムフォレスト  DTモデルをたくさんつくり、新しいサンプルにおけるクラスの推定結果を、各DTモデルの推定結果を多数決することで決めます。  96
AD Applicability Domain  モデルの適用範囲  回帰モデルやクラス分類モデルが本来の推定性能、つまりモデルを構築するときに用いたデータセットに対して示す性能、を発揮できる説明変数のデータ領域のこと。  99
OCSVM One-Class Support Vector Machine    主に正常とみなされるトレーニングサンプルの分布を学習し、カーネル関数を用いてサンプルが密集する領域を囲む境界を求める手法です。新しいサンプルがその領域の内側にあれば既知のデータ分布に近いサンプル、外側にあれば外れサンプルとして判定できるため、異常検知やモデルの適用範囲(AD)の評価に用いられます。  103

PostgreSQL の track_io_timing とパフォーマンス影響

概要

PostgreSQL の track_io_timing はデータベースによる I/O 待機の記録を有効にし、各種統計(pg_stat_* 系)や EXPLAIN などで参照できるようにする設定です。ですが、プラットフォームによっては深刻な負荷の原因になるとし、デフォルトでは無効となっています。

pgsql-hackers でもデフォルトで有効にすることが議論されましたが、プラットフォーム依存のオーバーヘッド懸念が指摘され、少なくとも当該スレッドではデフォルト変更に至っていません。
PostgreSQL: track_io_timing default setting

そこで、track_io_timing がパフォーマンスにどれほど影響を与えるのか、Hyper-V 上の Rocky Linux + PostgreSQL 18.2 で測定した一例を示します。

検証環境

Windows 11 マシンの Hyper-V 環境で検証しました。

ホスト

プロセッサ Intel Core i5-14500
メモリー 32 GB
OS Windows 11 Pro 25H2

Hyper-V

プロセッサ 10個の仮想プロセッサ
メモリー 8 GB
OS Rocky Linux release 9.7
DB PostgreSQL 18.2

検証

検証方針

track_io_timing パラメータの説明を抜粋します。

データベースによるI/O待機の記録を有効にします。 このパラメータはデフォルトで無効になっています。その理由は、現時点の時刻をオペレーティングシステムに繰り返し問い合わせるので、プラットフォームによっては深刻な負荷の原因になるからです。使用しているシステムにおける記録の負荷を計測するためpg_test_timingツールが使用できます。

19.9. 実行時統計情報

また、pg_test_timing の説明では、時間計測のオーバーヘッドやクロックソースによる影響が示されています。
pg_test_timing

これらを踏まえ、以下のように検証を進めることにします。

まずは利用可能なクロックソース毎に pg_test_timing で時間計測のオーバーヘッドを測定します。そして、最良と思われるクロックソースを選択します。

次に track_io_timing パラメータのパフォーマンス影響を計測します。ベンチマークとして pgbench の組み込みスクリプトである tpcb-like, select-only を用います。

クロックソース評価

# cat /sys/devices/system/clocksource/clocksource0/available_clocksource
tsc hyperv_clocksource_tsc_page hyperv_clocksource_msr acpi_pm

-- クロックソースを tsc に変更する場合
# echo tsc > /sys/devices/system/clocksource/clocksource0/current_clocksource

# taskset -c 0 /usr/pgsql-18/bin/pg_test_timing -d 10

「オーバーヘッド込みのループ時間毎」の結果をクロックソース毎に示します。なお、ヒストグラムは割愛していますが、妥当なバラツキと判断しました。

クロックソース ループ時間
tsc 15.29 ns
hyperv_clocksource_tsc_page 16.91 ns
hyperv_clocksource_msr 519.59 ns
acpi_pm 505.39 ns

この結果を受けて、今回は "tsc" を採用することにしました。OSインストール直後のデフォルト設定であること、かつ測定結果が良好であったことを重視しました。

track_io_timing とベンチマーク

$ createdb bench

$ pgbench -i -s 1000 bench

-- track_io_timing 変更後はデータベースを再起動した
# systemctl restart postgresql-18

-- select-only
$ pgbench -S -c 8 -j 8 -T 300 bench

-- tpcb-like
$ pgbench -c 8 -j 8 -T 300 bench

各条件で 3 回計測し、"latency average" の中央値を採用します。結果を以下に示します。

track_io_timing select-only tpcb-like
off 0.385 ms 3.958 ms
on 0.402 ms 3.913 ms

select-only では track_io_timing の有効化で latency が 4.4 % 悪化しました。

一方、tpcb-like は 1.1 % 改善しましたが、差は小さいため、今回の条件では「有意差」と断定せず参考値として扱います。

まとめ

今回の検証シナリオだけ見る限り、良い性能のクロックソースであれば、track_io_timing 有効化の影響は限定的といえそうです。有効化によるパフォーマンス影響を否定できず、通常時の適用は難しくとも、検証のために限定的に利用することは検討の余地があります。

ですが、track_io_timing を有効化したい場面は I/O 負荷を疑っている状況と推測され、調査のための有効化は状況をさらに悪化させる可能性があります。有効化の前に最低限でも pg_test_timing でクロックソースを評価すること。そして、可能であれば事前検証とリカバリー計画を用意した上で適用するのが望ましいと考えます。