器→工具, 工具软件, 数据, 术→技巧

Scikit-Learn学习之交叉验证

钱魏Way · · 372 次浏览
!文章内容如有错误或排版问题,请提交反馈,非常感谢!

交叉验证方法盘点

在机器学习中,常见有的交叉验证方法有留出法(Holdout cross validation)和k折交叉验证(k-fold cross validation)等,除此之外还有留一法(Leave-One-Out,LOO)、留P法(Leave-P-Out,LPO)等,抽时间做了一些简单的整理。

留出法(Holdout cross validation)

留出法是将原始数据随机分为两个互斥的集合,一个作为训练集,另一个作为测试集。在对训练集进行训练后,用测试集来评估模型的性能。

优点:

  • 简单易用,计算量较小。
  • 对于大规模数据集,可以快速构建并验证模型。

缺点:

  • 训练集和测试集的划分可能不够随机或不够充分,导致结果偏差。
  • 只进行一次划分,对于数据量较小时可能无法保证模型稳定性。
  • 数据集划分后会浪费部分数据,尤其是当数据集非常小的时候。

有些场景下,留出法还会额外增加一个验证集,具体的功能划分为:

  • 训练集:训练模型
  • 验证集:调参
  • 测试集:评估模型的泛化能力

Scikit-Learn中的数据集拆分train_test_split()方法可以用于留出法。由于先前已经介绍过这里就不再做重复介绍了。

k折交叉验证(k-fold cross validation)

k折交叉验证首先将原始数据随机分成k个互斥的子集,然后利用其中的k-1个子集作为训练集,剩下的1个子集作为验证集。对于这k种划分情况,分别进行k次训练和测试,最终返回平均测试结果。

优点:

  • 数据集的每个子集都有机会作为验证集和训练集,增加了模型的稳定性和精度。
  • 能够更好地利用数据,相对于留出法和留一法更加可靠。

缺点:

  • 计算量相对于留出法和留一法略大。
  • 在某些特殊情况(如数据分布不均衡、数据中存在噪声等)下,结果可能不太可靠。

Scikit-learn中的k折交叉验证

KFold

from sklearn.datasets import load_iris
from sklearn.model_selection import KFold
from sklearn.linear_model import LogisticRegression

# 加载鸢尾花数据集
iris = load_iris()
X, y = iris.data, iris.target

# 定义5折交叉验证器
kf = KFold(n_splits=5, shuffle=True, random_state=42)

# 创建逻辑回归模型
lr = LogisticRegression()

# 对于每个训练集和测试集进行训练和评估
for train_index, test_index in kf.split(X):
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]

    # 训练模型
    lr.fit(X_train, y_train)

    # 评估模型
    score = lr.score(X_test, y_test)
    print("Accuracy:", score)

GroupKFold

GroupKFold是一种交叉验证的策略,它将数据集按照分组信息进行划分,保证同一组中的样本要么全部在训练集中,要么全部在测试集中。

具体来说,GroupKFold会将数据集分为K份,其中每份保持同一组中的样本比例相同。然后,依次选取其中一份作为测试集,其余的K-1份作为训练集,进行模型训练和测试。这个过程会重复K次,每次选取不同的测试集和训练集,最终得到K个模型性能评估结果的平均值。

GroupKFold的主要参数包括:

  • n_splits:表示将数据集拆分成几份,默认为5。

GroupKFold的优点是能够确保同一组中的样本要么全部在训练集中,要么全部在测试集中,从而更好地反映模型的实际表现。缺点是对于某些数据集和模型,可能需要更多的训练时间和计算资源。

下面是一个使用GroupKFold进行交叉验证的示例代码:

from sklearn.datasets import make_classification
from sklearn.model_selection import GroupKFold
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# 生成分类数据集和分组信息
X, y = make_classification(n_samples=1000, n_features=10, random_state=42)
groups = [i % 3 for i in range(len(X))]

# 定义GroupKFold对象
gkf = GroupKFold(n_splits=3)

# 定义模型
model = LogisticRegression()

# 用GroupKFold进行交叉验证
scores = []
for train_index, test_index in gkf.split(X, y, groups):
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    score = accuracy_score(y_test, y_pred)
    scores.append(score)

# 输出结果
print("Accuracy: {:.3f} (+/- {:.3f})".format(np.mean(scores), np.std(scores)))

在这个例子中,我们首先生成了一个分类数据集和分组信息,然后定义了一个GroupKFold对象,将数据集拆分为3份,保持同一组中的样本比例相同。接着定义了一个LogisticRegression模型,并用GroupKFold进行交叉验证,最后输出了模型的平均准确率和标准差。

RepeatedKFold

RepeatedKFold是一种交叉验证的策略,它将数据集随机拆分为训练集和测试集,可以用于评估模型的性能和选择最优参数。与KFold不同的是,RepeatedKFold会重复运行K次,每次生成不同的随机拆分结果。

具体来说,RepeatedKFold会将数据集分为K份,然后依次选取其中一份作为测试集,其余的K-1份作为训练集,进行模型训练和测试。这个过程会重复R次,每次选取不同的随机拆分结果,最终得到R*K个模型性能评估结果。

RepeatedKFold的主要参数包括:

  • n_splits:表示将数据集拆分成几份,默认为5。
  • n_repeats:表示重复运行几次交叉验证,默认为10。

RepeatedKFold的优点是能够生成多个随机拆分结果,从而减小误差和提高可靠性。缺点是由于每次随机拆分都需要重新生成,因此计算代价较大,适合数据量较小的场景。

下面是一个使用RepeatedKFold进行交叉验证的示例代码:

from sklearn.datasets import load_iris
from sklearn.model_selection import RepeatedKFold
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

# 加载鸢尾花数据集
X, y = load_iris(return_X_y=True)

# 定义RepeatedKFold对象
rkf = RepeatedKFold(n_splits=5, n_repeats=3, random_state=42)

# 定义模型
model = DecisionTreeClassifier()

# 用RepeatedKFold进行交叉验证
scores = []
for train_index, test_index in rkf.split(X):
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    score = accuracy_score(y_test, y_pred)
    scores.append(score)

# 输出结果
print("Accuracy: {:.3f} (+/- {:.3f})".format(np.mean(scores), np.std(scores)))

在这个例子中,我们首先加载了鸢尾花数据集,然后定义了一个RepeatedKFold对象,将数据集拆分为5份,重复运行3次。接着定义了一个DecisionTreeClassifier模型,并用RepeatedKFold进行交叉验证,最后输出了模型的平均准确率和标准差。

StratifiedKFold

StratifiedKFold 是一种交叉验证的策略,它将数据集按照类别信息进行分层划分,可以确保每个类别在训练集和测试集中的比例相同。

具体来说,StratifiedKFold 会将数据集分为 K 份,其中每份保持不同类别的样本比例相同。然后,依次选取其中一份作为测试集,其余的 K-1 份作为训练集,进行模型训练和测试。这个过程会重复 K 次,每次选取不同的测试集和训练集,最终得到 K 个模型性能评估结果的平均值。

StratifiedKFold 的主要参数包括:

  • n_splits:表示将数据集拆分成几份,默认为 5。

StratifiedKFold 的优点是能够确保每个类别在训练集和测试集中的比例相同,从而更好地反映模型的实际表现。缺点是对于某些数据集和模型,可能需要更多的训练时间和计算资源。

下面是一个使用 StratifiedKFold 进行交叉验证的示例代码:

from sklearn.datasets import load_iris
from sklearn.model_selection import StratifiedKFold
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

# 加载鸢尾花数据集
X, y = load_iris(return_X_y=True)

# 定义 StratifiedKFold 对象
skf = StratifiedKFold(n_splits=5)

# 定义模型
model = DecisionTreeClassifier()

# 用 StratifiedKFold 进行交叉验证
scores = []
for train_index, test_index in skf.split(X, y):
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    score = accuracy_score(y_test, y_pred)
    scores.append(score)

# 输出结果
print("Accuracy: {:.3f} (+/- {:.3f})".format(np.mean(scores), np.std(scores)))

在这个例子中,我们首先加载了鸢尾花数据集,然后定义了一个 StratifiedKFold 对象,将数据集拆分为 5 份,保持不同类别的样本比例相同。接着定义了一个 DecisionTreeClassifier 模型,并用 StratifiedKFold 进行交叉验证,最后输出了模型的平均准确率和标准差。

RepeatedStratifiedKFold

RepeatedStratifiedKFold 是一种交叉验证的策略,它将数据集按照类别信息进行分层划分,可以确保每个类别在训练集和测试集中的比例相同。与 StratifiedKFold 不同的是,RepeatedStratifiedKFold 会重复运行 K 次,每次生成不同的随机拆分结果。

具体来说,RepeatedStratifiedKFold 会将数据集分为 K 份,其中每份保持不同类别的样本比例相同。然后,依次选取其中一份作为测试集,其余的 K-1 份作为训练集,进行模型训练和测试。这个过程会重复 R 次,每次选取不同的随机拆分结果,最终得到 R*K 个模型性能评估结果。

RepeatedStratifiedKFold 的主要参数包括:

  • n_splits:表示将数据集拆分成几份,默认为 5。
  • n_repeats:表示重复运行几次交叉验证,默认为 10。

RepeatedStratifiedKFold 的优点是能够确保每个类别在训练集和测试集中的比例相同,从而更好地反映模型的实际表现。缺点是由于每次随机拆分都需要重新生成,因此计算代价较大,适合数据量较小的场景。

下面是一个使用 RepeatedStratifiedKFold 进行交叉验证的示例代码:

from sklearn.datasets import load_digits
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# 加载手写数字数据集
X, y = load_digits(return_X_y=True)

# 定义 RepeatedStratifiedKFold 对象
rskf = RepeatedStratifedKFold(n_splits=5, n_repeats=3, random_state=42)

# 定义模型
model = LogisticRegression()

# 用 RepeatedStratifedKFold 进行交叉验证
scores = []
for train_index, test_index in rskf.split(X, y):
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    score = accuracy_score(y_test, y_pred)
    scores.append(score)

# 输出结果
print("Accuracy: {:.3f} (+/- {:.3f})".format(np.mean(scores), np.std(scores)))

在这个例子中,我们首先加载了手写数字数据集,然后定义了一个 RepeatedStratifiedKFold 对象,将数据集拆分为 5 份,重复运行 3 次。接着定义了一个 LogisticRegression 模型,并用 RepeatedStratifiedKFold 进行交叉验证,最后输出了模型的平均准确率和标准差。

留一法(Leave one out cross validation)

留一法是 k 折交叉验证的特例。在留一法中,每个样本依次被作为测试集,其余所有样本作为训练集,进行模型训练和测试。

优点:

  • 将原始数据集的全部信息都用于训练模型,从而得到最准确的模型评估结果。
  • 对于小数据集比较适用。

缺点:

  • 运算开销较大,需要训练 n 个模型,其中 n 为数据集大小。
  • 对于大数据集,由于需要进行大量的重复实验,留一法计算成本很高。

Scikit-learn 中可以使用 LeaveOneOut 类来进行留一法交叉验证(Leave-One-Out Cross Validation)。留一法交叉验证是将每个样本都作为测试数据,其余样本作为训练数据,这样得到 n 个分类器的评估结果,最后计算平均值。对于小规模数据集(如当样本数量小于 20 时),留一法交叉验证是一种很好的验证方法。

下面是一个使用留一法交叉验证进行模型评估的范例:

from sklearn.datasets import load_iris
from sklearn.model_selection import LeaveOneOut
from sklearn.linear_model import LogisticRegression

# 加载鸢尾花数据集
iris = load_iris()
X, y = iris.data, iris.target

# 创建逻辑回归模型
lr = LogisticRegression()

# 定义留一法交叉验证器
loo = LeaveOneOut()

# 初始化正确分类数和总样本数
correct = 0
total = 0

# 对于每个训练集和测试集进行训练和评估
for train_index, test_index in loo.split(X):
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]

    # 训练模型
    lr.fit(X_train, y_train)

    # 预测测试集并计算准确率
    y_pred = lr.predict(X_test)
    if y_pred == y_test:
        correct += 1
    total += 1

# 输出模型在所有测试集上的准确率
print("Accuracy:", correct/total)

在上述代码中,我们首先加载了鸢尾花数据集,然后创建了一个逻辑回归模型,并定义了一个留一法交叉验证器。接着,我们对于每个训练集和测试集进行训练和评估,并记录了模型在所有测试集上的正确分类数和总样本数。最后,我们输出了模型在所有测试集上的准确率。

留P法(Leave-P-Out,LPO)交叉验证

留P法交叉验证(Leave-P-Out Cross Validation,简称LPO)是一种K折交叉验证的特例。在LPO中,每次将P个样本作为测试集,其余的样本作为训练集,重复这个过程直到所有的组合都被尝试一遍,即共有nCp种情况。

相对于其他的交叉验证方法,LPO更加严格和耗时,但由于考虑了全部的nCp种情况,因此能给出非常准确的模型评估结果,而且可以充分利用小数据集中的所有样本。因此,在样本数量较少或使用机器学习算法比较困难的情况下,LPO可以作为一种有效的交叉验证策略。

Scikit-learn中可以使用LeavePOut类来实现LPO交叉验证。下面是一个使用留P法交叉验证进行模型评估的示例:

from sklearn.datasets import load_iris
from sklearn.model_selection import LeavePOut
from sklearn.linear_model import LogisticRegression

# 加载鸢尾花数据集
iris = load_iris()
X, y = iris.data, iris.target

# 创建逻辑回归模型
lr = LogisticRegression()

# 定义留P法交叉验证器
lpo = LeavePOut(p=2)

# 初始化正确分类数和总样本数
correct = 0
total = 0

# 对于每个训练集和测试集进行训练和评估
for train_index, test_index in lpo.split(X):
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]

    # 训练模型
    lr.fit(X_train, y_train)

    # 预测测试集并计算准确率
    y_pred = lr.predict(X_test)
    if y_pred == y_test:
        correct += 1
    total += 1

# 输出模型在所有测试集上的准确率
print("Accuracy:", correct/total)

在上述代码中,我们首先加载了鸢尾花数据集,然后创建了一个逻辑回归模型,并定义了一个留P法交叉验证器。接着,我们对于每个训练集和测试集进行训练和评估,并记录了模型在所有测试集上的正确分类数和总样本数。最后,我们输出了模型在所有测试集上的准确率。

总的来说,留P法交叉验证是一种严格和耗时的验证方法,但对于小规模数据集来说是一种很好的验证方法。在样本数量较少或机器学习算法比较困难的情况下,可以使用LPO来进行模型评估。

随机排列交叉验证ShuffleSplit

ShuffleSplit是一种交叉验证的策略,它将数据随机拆分为训练集和测试集,可以用于评估模型的性能和选择最优参数。

具体来说,ShuffleSplit会对整个数据集进行多次随机划分,每次划分都会按照指定比例生成训练集和测试集。这些划分是互相独立的,可以用来对同一个模型进行多次评估,从而减小误差和提高可靠性。

ShuffleSplit的主要参数包括:

  • n_splits:表示拆分成几份,默认为10。
  • test_size:表示每次拆分中测试集的大小,可以是一个浮点数(表示占总数据量的比例)或整数(表示具体数量),默认为1。
  • train_size:表示每次拆分中训练集的大小,如果不设置,则默认为1-test_size。
  • random_state:表示随机数种子,用于确保每次拆分结果的可重复性。

ShuffleSplit的优点是可以利用全部数据对模型进行训练和测试,并且每次划分的结果都是随机的,能够更好地反映模型的实际表现。缺点是由于每次随机划分都需要重新生成,因此计算代价较大,适合数据量较小的场景。

下面是一个使用ShuffleSplit进行交叉验证的示例代码:

from sklearn.datasets import load_digits
from sklearn.model_selection import ShuffleSplit
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# 加载手写数字数据集
X, y = load_digits(return_X_y=True)

# 定义ShuffleSplit对象
ss = ShuffleSplit(n_splits=5, test_size=0.2, random_state=42)

# 定义模型
model = LogisticRegression()

# 用ShuffleSplit进行交叉验证
scores = []
for train_index, test_index in ss.split(X):
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    score = accuracy_score(y_test, y_pred)
    scores.append(score)

# 输出结果
print("Accuracy: {:.3f} (+/-{:.3f})".format(np.mean(scores), np.std(scores)))

在这个例子中,我们首先加载了手写数字数据集,然后定义了一个ShuffleSplit对象,将数据集拆分为5份,每次测试集占比20%。接着定义了一个LogisticRegression模型,并用ShuffleSplit进行交叉验证,最后输出了模型的平均准确率和标准差。

需要注意的是,由于ShuffleSplit划分结果是随机的,因此每次运行代码得到的结果可能会略有不同。

分层K折交叉验证StratifiedKFold

分层K折交叉验证(StratifiedKFold)是一种交叉验证的策略,它可以确保每个类别在训练集和测试集中的比例相同。这种交叉验证方法适用于有不同类别或标签的分类问题。

具体来说,StratifiedKFold会将数据集分为K份,其中每份保持原始数据集中各类别样本的比例。然后,依次选取其中一份作为测试集,其余的K-1份作为训练集,进行模型训练和测试。这个过程会重复K次,每次选取不同的测试集和训练集,最终得到K个模型性能评估结果的平均值。

StratifiedKFold的主要参数包括:

  • n_splits:表示将数据集拆分成几份,默认为5。
  • shuffle:表示是否对数据集进行随机打乱,默认为True。
  • random_state:表示随机数种子,用于确保每次拆分结果的可重复性。

StratifiedKFold的优点是能够确保不同类别的样本在训练集和测试集中的比例相同,从而更好地反映模型的实际表现。缺点是对于某些数据集和模型,可能需要更多的训练时间和计算资源。

下面是一个使用StratifiedKFold进行交叉验证的示例代码:

from sklearn.datasets import load_digits
from sklearn.model_selection import StratifiedKFold
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# 加载手写数字数据集
X, y = load_digits(return_X_y=True)

# 定义StratifiedKFold对象
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

# 定义模型
model = LogisticRegression()

# 用StratifiedKFold进行交叉验证
scores = []
for train_index, test_index in skf.split(X, y):
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    score = accuracy_score(y_test, y_pred)
    scores.append(score)

# 输出结果
print("Accuracy: {:.3f} (+/-{:.3f})".format(np.mean(scores), np.std(scores)))

在这个例子中,我们首先加载了手写数字数据集,然后定义了一个StratifiedKFold对象,将数据集拆分为5份,并对每份进行随机打乱。接着定义了一个LogisticRegression模型,并用StratifiedKFold进行交叉验证,最后输出了模型的平均准确率和标准差。

分层随机排列交叉验证StratifiedShuffleSplit

分层随机排列交叉验证(StratifiedShuffleSplit)是一种交叉验证的策略,它将数据集随机拆分为训练集和测试集,同时保持每个类别在训练集和测试集中的比例相同。

具体来说,StratifiedShuffleSplit会对整个数据集进行多次随机划分,每次划分都会按照指定比例生成训练集和测试集,并且保持每个类别在训练集和测试集中的比例相同。这些划分是互相独立的,可以用来对同一个模型进行多次评估,从而减小误差和提高可靠性。

StratifiedShuffleSplit的主要参数包括:

  • n_splits:表示拆分成几份,默认为10。
  • test_size:表示每次拆分中测试集的大小,可以是一个浮点数(表示占总数据量的比例)或整数(表示具体数量),默认为1。
  • train_size:表示每次拆分中训练集的大小,如果不设置,则默认为1-test_size。
  • random_state:表示随机数种子,用于确保每次拆分结果的可重复性。

StratifiedShuffleSplit的优点是能够确保不同类别的样本在训练集和测试集中的比例相同,从而更好地反映模型的实际表现。缺点是由于每次随机划分都需要重新生成,因此计算代价较大,适合数据量较小的场景。

下面是一个使用StratifiedShuffleSplit进行交叉验证的示例代码:

from sklearn.datasets import load_digits
from sklearn.model_selection import StratifiedShuffleSplit
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# 加载手写数字数据集
X, y = load_digits(return_X_y=True)

# 定义StratifiedShuffleSplit对象
sss = StratifiedShuffleSplit(n_splits=5, test_size=0.2, random_state=42)

# 定义模型
model = LogisticRegression()

# 用StratifiedShuffleSplit进行交叉验证
scores = []
for train_index, test_index in sss.split(X, y):
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    score = accuracy_score(y_test, y_pred)
    scores.append(score)

# 输出结果
print("Accuracy: {:.3f} (+/-{:.3f})".format(np.mean(scores), np.std(scores)))

在这个例子中,我们首先加载了手写数字数据集,然后定义了一个StratifiedShuffleSplit对象,将数据集拆分为5份,每次测试集占比20%。接着定义了一个LogisticRegression模型,并用StratifiedShuffleSplit进行交叉验证,最后输出了模型的平均准确率和标准差。

时间序列分割法TimeSeriesSplit

时间序列分割法(TimeSeriesSplit)是一种交叉验证的策略,它将数据集按照时间顺序进行划分,保证训练集和测试集之间不存在时间上的重叠。

具体来说,TimeSeriesSplit会将时间序列数据集划分为K份,其中每份都包含相同数量的连续时间窗口。然后,依次选取其中一份作为测试集,其余的K-1份作为训练集,进行模型训练和测试。这个过程会重复K次,每次选取不同的测试集和训练集,最终得到K个模型性能评估结果的平均值。

TimeSeriesSplit的主要参数包括:

  • n_splits:表示将时间序列数据集拆分成几份,默认为5。
  • TimeSeriesSplit的优点是能够确保训练集和测试集之间不存在时间上的重叠,从而更好地反映模型在未来的实际表现。缺点是对于某些数据集和模型,可能需要更多的训练时间和计算资源。

下面是一个使用TimeSeriesSplit进行交叉验证的示例代码:

from sklearn.datasets import load_boston
from sklearn.model_selection import TimeSeriesSplit
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

# 加载波士顿房价数据集
X, y = load_boston(return_X_y=True)

# 定义TimeSeriesSplit对象
tss = TimeSeriesSplit(n_splits=5)

# 定义模型
model = LinearRegression()

# 用TimeSeriesSplit进行交叉验证
scores = []
for train_index, test_index in tss.split(X):
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    score = mean_squared_error(y_test, y_pred)
    scores.append(score)

# 输出结果
print("MSE: {:.3f} (+/-{:.3f})".format(np.mean(scores), np.std(scores)))

在这个例子中,我们首先加载了波士顿房价数据集,然后定义了一个TimeSeriesSplit对象,将数据集划分为5份。接着定义了一个LinearRegression模型,并用TimeSeriesSplit进行交叉验证,最后输出了模型的平均MSE和标准差。

快速交叉验证工具

Scikit-learn中的cross_val_score 和 cross_validate 是2个快速交叉验证工具。

核心功能对比

特性 cross_val_score cross_validate
返回值 单一指标的测试集得分数组 包含测试集得分、训练时间等的字典
多指标支持 仅支持单个评估指标 支持同时计算多个评估指标
训练时间 不记录 记录每折训练和预测时间
返回训练集得分 不支持 可选返回训练集得分(return_train_score=True)
灵活性 简单快速 高(支持复杂需求)

参数详解

共同参数

  • estimator: 待评估的模型(如RandomForestClassifier())。
  • X,y: 输入数据和标签。
  • cv: 交叉验证策略(默认5折):可传入整数(折数)、KFold对象或自定义划分器。
  • n_jobs: 并行计算的核心数(-1表示使用所有核心)。
  • verbose: 输出日志详细程度(0为静默,值越大输出越多)。

cross_val_score 特有参数

  • scoring: 单一评估指标(如’accuracy’、’f1’、’neg_mean_squared_error’)。

cross_validate 特有参数

  • scoring: 支持多指标(传入列表或字典,如[‘accuracy’, ‘f1’])。
  • return_train_score: 是否返回训练集得分(默认False)。
  • return_estimator: 是否返回每折训练的模型(默认False)。

使用场景与选择建议

选择 cross_val_score 的情况

  • 只需快速获取模型在单一指标 上的验证结果。
  • 无需额外信息(如训练时间、训练集得分)。

选择 cross_validate 的情况

  • 需要同时评估多个指标(如准确率和F1分数)。
  • 需分析训练/测试时间过拟合程度(通过训练集和测试集得分对比)。
  • 需保存每折训练的模型(用于后续分析或集成)。

代码示例

cross_val_score 基础用法(分类问题)

from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier

# 加载数据
X, y = load_iris(return_X_y=True)
model = RandomForestClassifier(n_estimators=100)

# 5折交叉验证,计算准确率
scores = cross_val_score(
    estimator=model,
    X=X, y=y,
    cv=5,
    scoring='accuracy',
    n_jobs=-1
)

print("测试集准确率:", scores)           # 例如 [0.97, 0.96, 0.98, 0.95, 0.97]
print("平均准确率:", scores.mean())      # 0.966

cross_validate 多指标与时间记录(回归问题)

from sklearn.model_selection import cross_validate
from sklearn.datasets import load_diabetes
from sklearn.linear_model import Ridge

# 加载数据
X, y = load_diabetes(return_X_y=True)
model = Ridge(alpha=0.5)

# 定义多指标:R²和负均方误差(MSE)
scoring = ['r2', 'neg_mean_squared_error']

# 交叉验证,返回训练时间和训练集得分
results = cross_validate(
    estimator=model,
    X=X, y=y,
    cv=5,
    scoring=scoring,
    return_train_score=True,
    n_jobs=-1
)

# 输出结果
print("测试集R²:", results['test_r2'])             
print("训练集MSE:", -results['train_neg_mean_squared_error'])  # 注意负号转换
print("每折拟合时间:", results['fit_time'])

输出结果解析(cross_validate 返回的字典)

  • test_score/ train_score: 测试集/训练集的得分数组(多指标时为字典,如 test_r2)。
  • fit_time: 每折模型训练时间(秒)。
  • score_time: 每折模型预测时间(秒)。
  • estimator(若return_estimator=True): 各折训练好的模型对象。

高级技巧与注意事项

评估指标的选择

  • 分类任务常用指标:
    • ‘accuracy’(准确率)、’f1’(二分类)、’f1_macro’(多分类)。
    • ‘roc_auc’(需要概率预测,需设置predict_proba())。
  • 回归任务常用指标:
    • ‘r2’(R平方)、’neg_mean_squared_error’(负均方误差,需取负号得MSE)。

自定义指标:

from sklearn.metrics import make_scorer

def custom_loss(y_true, y_pred):
    return ...  # 自定义计算逻辑

scorer = make_scorer(custom_loss, greater_is_better=False)

处理交叉验证中的随机性

若模型或数据划分包含随机性(如 ShuffleSplit、随机森林),需固定随机种子:

cv = KFold(n_splits=5, shuffle=True, random_state=42)
model = RandomForestClassifier(random_state=42)

性能优化

  • 设置n_jobs=-1 利用多核加速(但对大型模型可能内存不足)。
  • 对计算密集型模型(如深度学习),减少cv 折数(如3折)。

常见问题与解决方案

Q1:如何获取每折的预测结果?

使用 cross_val_predict(但需注意数据泄露风险):

from sklearn.model_selection import cross_val_predict
y_pred = cross_val_predict(model, X, y, cv=5)

Q2:如何对比训练集和测试集得分以检测过拟合?

在 cross_validate 中设置 return_train_score=True:

results = cross_validate(model, X, y, return_train_score=True)
print("训练集得分:", results['train_score'])
print("测试集得分:", results['test_score'])

Q3:如何自定义数据划分策略?

传入自定义的交叉验证对象,例如时间序列的 TimeSeriesSplit:

from sklearn.model_selection import TimeSeriesSplit
cv = TimeSeriesSplit(n_splits=5)
scores = cross_val_score(model, X, y, cv=cv)

交叉验证与超参数调优

GridSearchCV 核心原理

  • 目标:通过遍历所有预定义的超参数组合,结合交叉验证评估每组参数的性能,最终选择最优参数组合。
  • 工作流程
    • 定义参数网格:指定需要搜索的超参数及其候选值。
    • 交叉验证评估:对每个参数组合,使用交叉验证计算平均得分。
    • 选择最优参数:根据得分选择最佳参数,并自动用全量训练数据重新训练最终模型(若refit=True)。

核心参数解析

from sklearn.model_selection import GridSearchCV

grid_search = GridSearchCV(
    estimator=model,          # 基模型(如随机森林、SVM)
    param_grid=param_grid,    # 参数网格(字典或列表)
    scoring='accuracy',       # 评估指标(支持多指标)
    cv=5,                     # 交叉验证折数(默认5折)
    n_jobs=-1,                # 并行计算核心数(-1为全核心)
    verbose=1,                # 输出日志详细程度(0为静默)
    refit=True,               # 是否用最优参数重新训练全量数据
    return_train_score=False  # 是否返回训练集得分(用于过拟合分析)
)

完整代码示例(分类任务)

数据准备与模型定义

from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split

# 加载数据
X, y = load_iris(return_X_y=True)

# 划分训练集和测试集(避免数据泄露)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 定义基模型
model = RandomForestClassifier(random_state=42)

定义参数网格

param_grid = {
    'n_estimators': [50, 100, 200],       # 树的数量
    'max_depth': [None, 5, 10],           # 树的最大深度
    'min_samples_split': [2, 5],          # 节点分裂最小样本数
    'criterion': ['gini', 'entropy']      # 分裂标准
}

初始化并运行网格搜索

grid_search = GridSearchCV(
    estimator=model,
    param_grid=param_grid,
    scoring='accuracy',
    cv=5,
    n_jobs=-1,
    verbose=1
)

# 在训练集上执行搜索
grid_search.fit(X_train, y_train)

输出最优结果

print("最优参数组合:", grid_search.best_params_)
print("最优模型交叉验证得分:", grid_search.best_score_)

# 使用最优模型在测试集上评估
best_model = grid_search.best_estimator_
test_accuracy = best_model.score(X_test, y_test)
print("测试集准确率:", test_accuracy)

结果分析与高级操作

查看所有参数组合的得分

import pandas as pd

# 将搜索结果转为DataFrame
results = pd.DataFrame(grid_search.cv_results_)

# 筛选关键列(参数组合、平均得分、标准差)
results = results[['params', 'mean_test_score', 'std_test_score']]

# 按得分排序
results.sort_values(by='mean_test_score', ascending=False, inplace=True)
print(results.head())

可视化参数影响(以n_estimators为例)

import matplotlib.pyplot as plt

# 提取n_estimators的得分
n_estimators_scores = []
for params, score in zip(results['params'], results['mean_test_score']):
    if params['max_depth'] == 10 and params['min_samples_split'] == 2:
        n_estimators_scores.append(score)

plt.plot([50, 100, 200], n_estimators_scores, marker='o')
plt.xlabel('n_estimators')
plt.ylabel('Accuracy')
plt.title('Impact of n_estimators on Model Performance')
plt.show()

参数网格设计策略

分阶段搜索

  • 粗搜索:大范围覆盖可能值(如n_estimators: [50, 100, 200, 500])。
  • 精搜索:在较优范围内细化(如n_estimators: [80, 90, 100, 110, 120])。

参数依赖处理

  • 条件参数:例如,当使用SVM 的 poly 核时,需调整 degree 参数。
param_grid = [
    {'kernel': ['linear'], 'C': [0.1, 1, 10]},
    {'kernel': ['poly'], 'degree': [2, 3], 'C': [0.1, 1]},
    {'kernel': ['rbf'], 'gamma': ['scale', 'auto'], 'C': [1, 10]}
]

避免常见陷阱

数据泄露

  • 严格划分数据:确保测试集不参与参数搜索和交叉验证。
  • 预处理整合:将标准化等步骤放入Pipeline:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('model', RandomForestClassifier())
])

param_grid = {'model__n_estimators': [100, 200]}
grid_search = GridSearchCV(pipeline, param_grid, cv=5)

计算资源优化

  • 并行化:设置n_jobs=-1 加速,但注意内存限制。
  • 随机搜索替代:使用RandomizedSearchCV 减少计算时间:
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint

param_dist = {
    'n_estimators': randint(50, 200),
    'max_depth': [None, 5, 10],
    'min_samples_split': randint(2, 6)
}

random_search = RandomizedSearchCV(
    model, param_dist, n_iter=20, cv=5, n_jobs=-1
)

超参数调优与模型泛化

过拟合验证曲线

训练集 vs 验证集得分:若训练集得分远高于验证集,可能过拟合。

results = cross_validate(
    grid_search.best_estimator_, 
    X_train, y_train, 
    return_train_score=True
)
print("训练集得分:", results['train_score'].mean())
print("验证集得分:", results['test_score'].mean())

最终模型评估

独立测试集:仅在调优完成后使用测试集评估,避免多次使用导致数据泄露。

交叉验证高级技巧

自定义交叉验证策略

通过生成器函数定义任意划分规则:

def custom_cv():
    # 自定义索引划分逻辑(例如按数据分组)
    groups = np.array([1,1,2,2,3,3])  # 假设每组数据不可分割
    unique_groups = np.unique(groups)
    for group in unique_groups:
        val_mask = (groups == group)
        train_mask = ~val_mask
        yield np.where(train_mask)[0], np.where(val_mask)[0]

model = RandomForestClassifier()
scores = cross_val_score(model, X, y, cv=custom_cv())

交叉验证中的预处理

使用 Pipeline 防止数据泄露(如标准化应在训练集上拟合):

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('model', SVC())
])

scores = cross_val_score(pipeline, X, y, cv=5)

交叉验证的最佳实践

数据预处理与划分

避免数据泄露

  • 预处理必须在交叉验证循环内进行(如标准化、缺失值填充),确保每折训练集的信息不污染验证集。
  • 正确做法:使用Pipeline 封装预处理步骤和模型。
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC

pipeline = Pipeline([
    ('scaler', StandardScaler()),  # 标准化
    ('classifier', SVC())           # 模型
])

# 交叉验证时自动在每折训练集上拟合scaler
scores = cross_val_score(pipeline, X, y, cv=5)

严格分割训练集和测试集

先分训练集和测试集,再在训练集上做交叉验证,测试集仅用于最终评估。

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
grid_search.fit(X_train, y_train)  # 在训练集上交叉验证调参
final_score = grid_search.score(X_test, y_test)  # 测试集评估

选择合适的交叉验证策略

分类问题:使用分层交叉验证

保持每折中类别比例与原数据一致(避免某些折中类别缺失)。

from sklearn.model_selection import StratifiedKFold

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=cv)

时间序列:用时间序列分割

按时间顺序划分,防止未来信息泄漏。

from sklearn.model_selection import TimeSeriesSplit

cv = TimeSeriesSplit(n_splits=5)
scores = cross_val_score(model, X, y, cv=cv)

小样本数据:留一法或重复交叉验证

样本量极小时(如<100),使用 LeaveOneOut 或重复交叉验证。

from sklearn.model_selection import LeaveOneOut

cv = LeaveOneOut()
scores = cross_val_score(model, X, y, cv=cv)

调参与模型选择

在训练集上使用交叉验证调参

调参时仅在训练集上运行网格搜索,测试集留作最终验证。

param_grid = {'C': [0.1, 1, 10]}
grid_search = GridSearchCV(SVC(), param_grid, cv=5)
grid_search.fit(X_train, y_train)  # 训练集调参

对比不同模型时保持测试集独立

使用交叉验证分数比较模型,但最终用独立测试集确认性能。

models = [RandomForestClassifier(), SVC(), LogisticRegression()]
for model in models:
    scores = cross_val_score(model, X_train, y_train, cv=5)
    print(f"{model.__class__.__name__} 平均得分: {scores.mean():.3f}")

结果分析与验证

关注验证分数均值和方差

  • 高均值 + 低方差:模型稳定且性能好。
  • 低均值 + 高方差:可能欠拟合或数据划分敏感。
print(f"平均准确率: {scores.mean():.3f} ± {scores.std():.3f}")

检查过拟合

通过 cross_validate 返回训练集和验证集得分:

results = cross_validate(model, X, y, cv=5, return_train_score=True)
print("训练集得分:", results['train_score'].mean())
print("验证集得分:", results['test_score'].mean())
  • 训练集 >> 验证集:过拟合(需简化模型或增加正则化)。
  • 训练集 ≈ 验证集:欠拟合(需增加模型复杂度)。

计算资源管理

并行加速

设置 n_jobs=-1 使用所有CPU核心。

scores = cross_val_score(model, X, y, cv=5, n_jobs=-1)

权衡折数和计算成本

  • 大数据集:用较少折数(如3折)减少计算时间。
  • 小数据集:增加折数(如10折)提高评估稳定性。

随机搜索替代网格搜索

参数空间大时,用 RandomizedSearchCV 减少搜索次数。

from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import loguniform

param_dist = {
    'C': loguniform(1e-3, 100),  # 对数均匀分布采样
    'gamma': loguniform(1e-4, 1)
}
search = RandomizedSearchCV(SVC(), param_dist, n_iter=50, cv=5)

其他关键实践

固定随机种子

确保结果可复现,尤其是在交叉验证和模型训练中。

model = RandomForestClassifier(random_state=42)
cv = KFold(n_splits=5, shuffle=True, random_state=42)

处理不平衡数据

使用分层交叉验证或调整类别权重。

cv = StratifiedKFold(n_splits=5)
model = RandomForestClassifier(class_weight='balanced')

多指标评估

同时监控多个指标(如准确率、F1、AUC)。

scoring = ['accuracy', 'f1_macro', 'roc_auc']
results = cross_validate(model, X, y, cv=5, scoring=scoring)

参考链接:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注