少し前の記事になりますが、 scikit-learnでK-分割交差検証 というのを書きました。
これは、分類のタスクでは目的変数の件数がクラスごとにある程度揃っていたり、データが十分に揃っていればうまく機能します。
しかし、一方で不均衡データなど、目的変数の値の割合が偏っていて特に、一部のクラスのデータが非常に少ないと困ったことになります。
試しに、いつものirisのデータを少し絞り込んで、元々種類ごとに50件ずつあるデータを
setosa: 50個
versicolor: 10個
virginica: 5個
にして試してみます。
(一番少ないクラスのデータ件数が5個なのに5分割するという極端な例ですが、
説明のためなのでご了承ください。)
from sklearn.model_selection import KFold
from sklearn.datasets import load_iris
import numpy as np
# データの読み込み
iris = load_iris()
X = iris.data
y = iris.target
# 実験のため対象を絞り込んで不均衡データにする
index = list(range(50)) + list(range(50, 60)) + list(range(100, 105))
X = X[index]
y = y[index]
for c in range(3):
print(f"{iris.target_names[c]}: {list(y).count(c)}個")
"""
setosa: 50個
versicolor: 10個
virginica: 5個
"""
# KFoldを用いてK-分割交差検証した時に各グループに含まれるラベル数
kf = KFold(5, shuffle=True)
i = 0
for train_index, test_index in kf.split(X):
i += 1
print(f"\n{i}グループの訓練データに含まれるラベル")
train_y = y[train_index]
for c in range(3):
print(f"{iris.target_names[c]}: {list(train_y).count(c)}個")
print(f"{i}グループのテストデータに含まれるラベル")
test_y = y[test_index]
for c in range(3):
print(f"{iris.target_names[c]}: {list(test_y).count(c)}個")
"""
1グループの訓練データに含まれるラベル
setosa: 41個
versicolor: 6個
virginica: 5個
1グループのテストデータに含まれるラベル
setosa: 9個
versicolor: 4個
virginica: 0個
2グループの訓練データに含まれるラベル
setosa: 42個
versicolor: 7個
virginica: 3個
2グループのテストデータに含まれるラベル
setosa: 8個
versicolor: 3個
virginica: 2個
3グループの訓練データに含まれるラベル
setosa: 39個
versicolor: 9個
virginica: 4個
3グループのテストデータに含まれるラベル
setosa: 11個
versicolor: 1個
virginica: 1個
4グループの訓練データに含まれるラベル
setosa: 40個
versicolor: 8個
virginica: 4個
4グループのテストデータに含まれるラベル
setosa: 10個
versicolor: 2個
virginica: 1個
5グループの訓練データに含まれるラベル
setosa: 38個
versicolor: 10個
virginica: 4個
5グループのテストデータに含まれるラベル
setosa: 12個
versicolor: 0個
virginica: 1個
"""
結果が長くなって恐縮ですが、1グループ目では、テストデータにvirginicaが含まれなくなっていますし、
5グループ目では versicolor がテストデータにありません。
逆に、訓練データでそれらのデータの割合が過剰に高くなっています。
これではモデルの学習もうまくいきませんし、評価も適切に行えません。
このような時、 train_test_splitであれば、stratify引数を使って、ラベルの割合を揃えられます。
参考: scikit-learnのtrain_test_splitで、訓練データとテストデータのラベルの割合を揃える
そして、KFoldには stratify がないのですが代わりに、
タイトルの 層化K分割交差検証(Stratified K-Folds cross-validator)という手法が知られており、それに対応する
StratifiedKFold というクラスが用意されています。
要は、ラベルの割合を揃えながらK分割交差検証する方法です。
使い方はKFoldととても似ていますが、splitするときに、labelも渡してあげる必要がある点だけ注意です。
from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(5, shuffle=True)
i = 0
for train_index, test_index in skf.split(X, y):
i += 1
print(f"\n{i}グループの訓練データに含まれるラベル")
train_y = y[train_index]
for c in range(3):
print(f"{iris.target_names[c]}: {list(train_y).count(c)}個")
print(f"{i}グループのテストデータに含まれるラベル")
test_y = y[test_index]
for c in range(3):
print(f"{iris.target_names[c]}: {list(test_y).count(c)}個")
"""
1グループの訓練データに含まれるラベル
setosa: 40個
versicolor: 8個
virginica: 4個
1グループのテストデータに含まれるラベル
setosa: 10個
versicolor: 2個
virginica: 1個
2グループの訓練データに含まれるラベル
setosa: 40個
versicolor: 8個
virginica: 4個
2グループのテストデータに含まれるラベル
setosa: 10個
versicolor: 2個
virginica: 1個
3グループの訓練データに含まれるラベル
setosa: 40個
versicolor: 8個
virginica: 4個
3グループのテストデータに含まれるラベル
setosa: 10個
versicolor: 2個
virginica: 1個
4グループの訓練データに含まれるラベル
setosa: 40個
versicolor: 8個
virginica: 4個
4グループのテストデータに含まれるラベル
setosa: 10個
versicolor: 2個
virginica: 1個
5グループの訓練データに含まれるラベル
setosa: 40個
versicolor: 8個
virginica: 4個
5グループのテストデータに含まれるラベル
setosa: 10個
versicolor: 2個
virginica: 1個
"""
全グループで、訓練データとテストデータの割合が揃っているのを確認できました。