作者:Samy Baladram
机器每天都会做出数百万次预测——从检测照片中的物体到帮助医生发现疾病。但在相信这些预测之前,我们需要知道它们是否有效。毕竟,没有人愿意使用一台大多数时候都会出错的机器!
这就是验证的用武之地。验证方法测试机器预测以衡量其可靠性。虽然这听起来很简单,但存在不同的验证方法,每种方法都旨在应对机器学习中的特定挑战。
在这里,我以树形结构组织了这些验证技术(全部 12 种),展示了它们如何从基本概念演变为更专业的概念。当然,我们将使用清晰的视觉效果和一致的数据集来展示每种方法的不同之处以及方法选择的重要性。
模型验证是测试机器学习模型如何处理训练期间未见过或使用的数据的过程。基本上,我们使用现有数据来检查模型的性能,而不是使用新数据。这有助于我们在部署模型进行实际使用之前识别问题。
有多种验证方法,每种方法都有特定的优点并解决不同的验证挑战:
以下是显示这些验证方法如何相互关联的树形图:
接下来,我们将通过准确展示每种验证方法的工作原理来更仔细地研究它们。为了使一切更容易理解,我们将通过清晰的示例来展示这些方法如何处理真实数据。
我们将始终使用相同的示例来帮助您理解每种测试方法。虽然该数据集可能不适合某些验证方法,但出于教育目的,使用这个示例可以更轻松地比较不同的方法并了解每种方法的工作原理。
我们将使用此数据集来根据天气条件预测某人是否会打高尔夫球。
将 pandas 导入为 pd
将 numpy 导入为 np# 加载数据集
数据集_字典 = {
'展望': ['晴', '晴', '阴', '雨', '雨', '雨', '阴',
'晴', '晴', '雨', '晴', '阴', '阴', '雨',
'晴天', '阴天', '下雨', '晴天', '晴天', '下雨', '阴天',
'下雨', '晴天', '阴天', '晴天', '阴天', '下雨', '阴天'],
‘温度’: [85.0, 80.0, 83.0, 70.0, 68.0, 65.0, 64.0, 72.0, 69.0, 75.0, 75.0,
72.0、81.0、71.0、81.0、74.0、76.0、78.0、82.0、67.0、85.0、73.0、
88.0, 77.0, 79.0, 80.0, 66.0, 84.0],
‘湿度’: [85.0, 90.0, 78.0, 96.0, 80.0, 70.0, 65.0, 95.0, 70.0, 80.0, 70.0,
90.0、75.0、80.0、88.0、92.0、85.0、75.0、92.0、90.0、85.0、88.0、
65.0, 70.0, 60.0, 95.0, 70.0, 78.0],
'风':[假,真,假,假,假,真,真,假,假,假,真,
真,假,真,真,假,假,真,假,真,真,假,
真,假,假,真,假,假],
'播放': ['否', '否', '是', '是', '是', '否', '是', '否', '是', '是', '是',
'是','是','否','否','是','是','否','否','否','是','是',
'是','是','是','是','否','是']
}
df = pd.DataFrame(dataset_dict)
# 数据预处理
df = pd.DataFrame(dataset_dict)
df = pd.get_dummies(df, columns=['Outlook'], prefix='', prefix_sep='', dtype=int)
df['风'] = df['风'].astype(int)
# 设置标签
X, y = df.drop('播放', axis=1), df['播放']
我们将使用一个决策树分类器对于我们所有的测试。我们选择这个模型是因为我们可以轻松地将结果模型绘制为树结构,每个分支显示不同的决策。为了使事情简单并专注于我们如何测试模型,我们将使用默认值scikit学习
参数固定随机状态
。
让我们明确一下我们将使用的这两个术语:决策树分类器是我们的学习算法– 它是在我们的数据中发现模式的方法。当我们将数据输入该算法时,它会创建一个模型(在本例中,一棵树具有清晰的分支,显示不同的决策)。这个模型是我们实际用来进行预测的模型。
从 sklearn.tree 导入 DecisionTreeClassifier,plot_tree
将 matplotlib.pyplot 导入为 pltdt = 决策树分类器(random_state=42)
每次我们以不同的方式分割数据进行验证时,我们都会得到具有不同决策规则的不同模型。一旦我们的验证表明我们的算法可靠,我们将使用所有数据创建一个最终模型。我们将实际使用这个最终模型来预测某人是否会打高尔夫球。
准备好此设置后,我们现在可以专注于了解每种验证方法的工作原理以及它如何帮助我们根据天气条件更好地预测高尔夫比赛。让我们一次检查每种验证方法。
保留方法是检查模型效果的最基本方法。在这些方法中,我们基本上保存一些数据只是为了测试。
这个方法很简单:我们将数据分成两部分。我们使用一部分来训练我们的模型,另一部分来测试它。在分割数据之前,我们将其随机混合,以便原始数据的顺序不会影响我们的结果。
训练和测试数据集的大小都取决于我们的总数据集大小,通常用它们的比率表示。要确定它们的大小,您可以遵循以下准则:
从 sklearn.model_selection 导入 train_test_split### 简单的训练测试分割###
# 分割数据
X_train, X_test, y_train, y_test = train_test_split(
X、y、test_size=0.2、random_state=42
)
# 训练和评估
dt.fit(X_train, y_train)
test_accuracy = dt.score(X_test, y_test)
# 阴谋
plt.figure(figsize=(5, 5), dpi=300)
plot_tree(dt,feature_names = X.columns,填充= True,rounded = True)
plt.title(f'训练-测试分割(测试精度:{test_accuracy:.3f})')
plt.tight_layout()
这种方法很容易使用,但有一些局限性——结果可能会根据我们随机分割数据的方式而发生很大变化。这就是为什么我们总是需要尝试不同的随机状态
以确保结果一致。此外,如果我们没有足够的数据来开始,我们可能没有足够的数据来正确训练或测试我们的模型。
该方法将我们的数据分为三个部分。中间部分称为验证数据,用于调整模型的参数,我们的目标是尽量减少其中的错误。
由于在此调整过程中多次考虑验证结果,因此我们的模型可能会开始在此验证数据上做得太好(这正是我们想要的)。这就是我们制作单独测试集的原因。我们只在最后测试一次——它让我们了解模型的运行情况。
以下是分割数据的典型方法:
### 训练-验证-测试分割###
# 第一次分割:单独的测试集
X_temp,X_test,y_temp,y_test = train_test_split(
X、y、test_size=0.2、random_state=42
)# 第二次分割:单独的验证集
X_train, X_val, y_train, y_val = train_test_split(
X_temp、y_temp、test_size=0.25、random_state=42
)
# 训练和评估
dt.fit(X_train, y_train)
val_accuracy = dt.score(X_val, y_val)
test_accuracy = dt.score(X_test, y_test)
# 阴谋
plt.figure(figsize=(5, 5), dpi=300)
plot_tree(dt,feature_names = X.columns,填充= True,rounded = True)
plt.title(f'Train-Val-Test Split\n验证精度: {val_accuracy:.3f}'
f'\n测试精度:{test_accuracy:.3f}')
plt.tight_layout()
根据您拥有的数据量,保留方法的工作方式有所不同。当您有大量数据(> 100,000)时,它们非常有效。但是当您的数据较少(< 1,000)时,此方法并不是最好的。对于较小的数据集,您可能需要使用更高级的验证方法来更好地了解模型的实际效果。
我们刚刚了解到,保留方法可能不适用于小型数据集。这正是我们当前面临的挑战——我们只有 28 天的数据。遵循保留原则,我们将单独保留 14 天的数据以供最终测试使用。这给我们留下了 14 天的时间来尝试其他验证方法。
# 初始训练-测试分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, shuffle=False)
在下一部分中,我们将了解交叉验证方法如何花费这 14 天并以不同的方式将其分割多次。即使数据如此有限,这也能让我们更好地了解模型的实际运行情况。
交叉验证改变了我们测试模型的方式。我们不是使用一次数据分割来测试我们的模型一次,而是使用相同数据的不同分割对其进行多次测试。这有助于我们更好地了解我们的模型的实际效果如何。
交叉验证的主要思想是多次测试我们的模型,每次训练和测试数据集都来自数据的不同部分。这有助于防止因数据的一个非常好的(或非常差的)分割而产生偏差。
这就是为什么这很重要:假设当我们以一种方式测试时,我们的模型获得了 95% 的准确率,但当我们使用相同的数据以另一种方式测试时,只有 75% 的准确率。哪个数字表明我们的模型到底有多好?交叉验证通过为我们提供许多而不是一个测试结果来帮助我们回答这个问题。这让我们更清楚地了解模型的实际表现。
基本 K 折交叉验证K
-fold 交叉验证解决了基本分割的一个大问题:过度依赖一种分割数据的方式。而不是一次分割数据,K-fold 将数据分成K等份。然后它多次测试模型,每次使用不同的部分进行测试,同时使用所有其他部分进行训练。
我们选择的号码K改变我们测试模型的方式。大多数人使用 5 或 10K,但这可能会根据我们拥有的数据量以及项目所需的数据而改变。假设我们使用K= 3。这意味着我们将数据分成三个相等的部分。然后我们对模型进行三次不同的训练和测试。每次,2/3 的数据用于训练,1/3 用于测试,但我们轮换哪一部分用于测试。这样,每条数据都可以用于训练和测试。
从 sklearn.model_selection 导入 KFold,cross_val_score# 交叉验证策略
cv = KFold(n_splits=3, shuffle=True, random_state=42)
# 计算交叉验证分数
分数 = cross_val_score(dt, X_train, y_train, cv=cv)
print(f"验证准确度:{scores.mean():.3f} ± {scores.std():.3f}")
# 为每个分割绘制树
plt.figure(figsize=(4, 3.5*cv.get_n_splits(X_train)))
对于 i,枚举(cv.split(X_train,y_train))中的(train_idx,val_idx):
# 训练并可视化本次分割的树
dt.fit(X_train.iloc[train_idx], y_train.iloc[train_idx])
plt.subplot(cv.get_n_splits(X_train), 1, i+1)
plot_tree(dt,feature_names = X_train.columns,杂质= False,填充= True,rounded = True)
plt.title(f'Split {i+1} (验证精度: {scores[i]:.3f})\n训练索引: {train_idx}\n验证索引: {val_idx}')
plt.tight_layout()
验证精度:0.433±0.047
当我们完成所有回合后,我们计算所有回合的平均表现K测试。这个平均值让我们能够更可靠地衡量模型的运行情况。我们还可以通过查看不同轮次测试之间结果的变化程度来了解模型的稳定性。
分层K折
基本的 K 折交叉验证通常效果很好,但当我们的数据不平衡时,它可能会遇到问题 - 这意味着我们比其他类型拥有更多的一种类型。例如,如果我们有 100 个数据点,其中 90 个为 A 型,而只有 10 个为 B 型,则随机分割这些数据可能会导致我们没有足够的 B 型数据来正确测试。
分层 K 折通过确保每个分割具有与原始数据相同的组合来解决此问题。如果我们的完整数据集包含 10% 的 B 型数据,则每次分割也将包含大约 10% 的 B 型数据。这使得我们的测试更加可靠,尤其是当某些类型的数据比其他数据类型稀有时。
从 sklearn.model_selection 导入 StratifiedKFold,cross_val_score# 交叉验证策略
cv = StratifiedKFold(n_splits=3, shuffle=True, random_state=42)
# 计算交叉验证分数
分数 = cross_val_score(dt, X_train, y_train, cv=cv)
print(f"验证准确度:{scores.mean():.3f} ± {scores.std():.3f}")
# 为每个分割绘制树
plt.figure(figsize=(5, 4*cv.get_n_splits(X_train)))
对于 i,枚举(cv.split(X_train,y_train))中的(train_idx,val_idx):
# 训练并可视化本次分割的树
dt.fit(X_train.iloc[train_idx], y_train.iloc[train_idx])
plt.subplot(cv.get_n_splits(X_train), 1, i+1)
plot_tree(dt,feature_names = X_train.columns,杂质= False,填充= True,rounded = True)
plt.title(f'Split {i+1} (验证精度: {scores[i]:.3f})\n训练索引: {train_idx}\n验证索引: {val_idx}')
plt.tight_layout()
验证精度:0.650±0.071
保持这种平衡有两个好处。首先,它确保每个分割正确地代表我们的数据。其次,它为我们提供了更加一致的测试结果。这意味着如果我们多次测试我们的模型,我们很可能每次都会得到相似的结果。
重复K折
有时,即使我们使用 K 折验证,我们的测试结果也会在不同的随机分割之间发生很大变化。重复 K 折叠通过多次运行整个 K 折叠过程并每次使用不同的随机分割来解决这个问题。
例如,假设我们运行 3 次 5 折交叉验证。这意味着我们的模型总共经过了 15 次训练和测试。通过多次测试,我们可以更好地判断结果的哪些差异来自随机机会,哪些表明我们的模型的实际性能如何。缺点是所有这些额外的测试需要更多时间才能完成。
从 sklearn.model_selection 导入 RepeatedKFold# 交叉验证策略
n_splits = 3
cv = RepeatedKFold(n_splits=n_splits, n_repeats=2, random_state=42)
# 计算交叉验证分数
分数 = cross_val_score(dt, X_train, y_train, cv=cv)
print(f"验证准确度:{scores.mean():.3f} ± {scores.std():.3f}")
# 为每个分割绘制树
Total_splits = cv.get_n_splits(X_train) # 将为 6(3 次折叠 × 2 次重复)
plt.figure(figsize=(5, 4*total_splits))
对于 i,枚举(cv.split(X_train,y_train))中的(train_idx,val_idx):
# 训练并可视化本次分割的树
dt.fit(X_train.iloc[train_idx], y_train.iloc[train_idx])
# 计算重复次数和折叠次数
重复,fold = i // n_splits + 1, i % n_splits + 1
plt.subplot(total_splits, 1, i+1)
plot_tree(dt,feature_names = X_train.columns,杂质= False,填充= True,rounded = True)
plt.title(f'Split {repetition}.{fold} (验证准确度: {scores[i]:.3f})\n'
f'训练索引:{list(train_idx)}\n'
f'验证索引:{list(val_idx)}')
plt.tight_layout()
验证精度:0.425±0.107
当我们查看重复的 K 重结果时,由于我们有很多组测试结果,因此我们不仅可以计算平均值,还可以计算出我们对结果的信心程度。这让我们更好地了解我们的模型到底有多可靠。
重复分层 K 折
这种方法结合了我们刚刚学到的两件事:保持班级平衡(分层)和运行多轮测试(重复)。它在多次测试的同时保持不同类型数据的正确组合。当我们有一个不均匀的小数据集时,这种方法尤其有效,其中一种类型的数据比其他类型的数据多得多。
从 sklearn.model_selection 导入 RepeatedStratifiedKFold# 交叉验证策略
n_splits = 3
cv = RepeatedStratifiedKFold(n_splits=n_splits, n_repeats=2, random_state=42)
# 计算交叉验证分数
分数 = cross_val_score(dt, X_train, y_train, cv=cv)
print(f"验证准确度:{scores.mean():.3f} ± {scores.std():.3f}")
# 为每个分割绘制树
Total_splits = cv.get_n_splits(X_train) # 将为 6(3 次折叠 × 2 次重复)
plt.figure(figsize=(5, 4*total_splits))
对于 i,枚举(cv.split(X_train,y_train))中的(train_idx,val_idx):
# 训练并可视化本次分割的树
dt.fit(X_train.iloc[train_idx], y_train.iloc[train_idx])
# 计算重复次数和折叠次数
重复,fold = i // n_splits + 1, i % n_splits + 1
plt.subplot(total_splits, 1, i+1)
plot_tree(dt,feature_names = X_train.columns,杂质= False,填充= True,rounded = True)
plt.title(f'Split {repetition}.{fold} (验证准确度: {scores[i]:.3f})\n'
f'训练索引:{list(train_idx)}\n'
f'验证索引:{list(val_idx)}')
plt.tight_layout()
验证精度:0.542±0.167
然而,有一个权衡:这种方法需要更多的时间让我们的计算机运行。每次我们重复整个过程,训练模型所需的时间就会成倍增加。在决定是否使用这种方法时,我们需要考虑是否值得花费额外的时间来运行所有这些测试,以获得更可靠的结果。
K 组折叠
有时,我们的数据自然会出现在应该保持在一起的组中。想想高尔夫数据,我们全年对同一个高尔夫球场进行多次测量。如果我们将一个高尔夫球场的一些测量值放入训练数据中,将其他测量值放入测试数据中,就会产生一个问题:我们的模型将在训练期间间接了解测试数据,因为它看到了同一球场的其他测量值。
当我们分割数据时,K 组折叠通过将同一组的所有数据(例如来自一个高尔夫球场的所有测量值)保留在同一部分来解决此问题。这可以防止我们的模型意外地看到它不应该看到的信息,这可能会让我们认为它的表现比实际情况更好。
# 创建群组
groups = ['组 1', '组 4', '组 5', '组 3', '组 1', '组 2', '组 4',
“组 2”、“组 6”、“组 3”、“组 6”、“组 5”、“组 1”、“组 4”、
“组 4”、“组 3”、“组 1”、“组 5”、“组 6”、“组 2”、“组 4”、
‘第 5 组’、‘第 1 组’、‘第 4 组’、‘第 5 组’、‘第 5 组’、‘第 2 组’、‘第 6 组’]# 简单的训练-测试分割
X_train, X_test, y_train, y_test, groups_train, groups_test = train_test_split(
X、y、组、test_size=0.5、shuffle=False
)
# 交叉验证策略
cv = GroupKFold(n_splits=3)
# 计算交叉验证分数
分数 = cross_val_score(dt, X_train, y_train, cv=cv.split(X_train, y_train, groups=groups_train))
print(f"验证准确度:{scores.mean():.3f} ± {scores.std():.3f}")
# 为每个分割绘制树
plt.figure(figsize=(4, 3.5*cv.get_n_splits(X_train)))
对于 i,枚举(cv.split(X_train,y_train,groups=groups_train))中的(train_idx,val_idx):
# 获取本次分组的分组
train_groups = 排序(设置(np.array(groups_train)[train_idx]))
val_groups = 排序(设置(np.array(groups_train)[val_idx]))
# 训练并可视化本次分割的树
dt.fit(X_train.iloc[train_idx], y_train.iloc[train_idx])
plt.subplot(cv.get_n_splits(X_train), 1, i+1)
plot_tree(dt,feature_names = X_train.columns,杂质= False,填充= True,rounded = True)
plt.title(f'Split {i+1} (验证准确度: {scores[i]:.3f})\n'
f'训练索引:{train_idx} ({", ".join(train_groups)})\n'
f'验证索引:{val_idx} ({", ".join(val_groups)})')
plt.tight_layout()
验证精度:0.417±0.143
当处理自然成组的数据时,这种方法可能很重要,例如来自同一高尔夫球场的多个天气读数或随着时间的推移从同一位置收集的数据。
时间序列分割
当我们以规则的 K 倍随机分割数据时,我们假设每条数据不会影响其他数据。但这不适用于随时间变化的数据,因为之前发生的事情会影响接下来发生的事情。时间序列拆分更改了 K 倍,以更好地处理这种按时间排序的数据。
时间序列分割不是随机分割数据,而是按从过去到未来的顺序使用数据。训练数据仅包含测试数据之前时间的信息。这符合我们在现实生活中使用模型的方式,即我们使用过去的数据来预测接下来会发生什么。
从 sklearn.model_selection 导入 TimeSeriesSplit,cross_val_score# 交叉验证策略
cv = TimeSeriesSplit(n_splits=3)
# 计算交叉验证分数
分数 = cross_val_score(dt, X_train, y_train, cv=cv)
print(f"验证准确度:{scores.mean():.3f} ± {scores.std():.3f}")
# 为每个分割绘制树
plt.figure(figsize=(4, 3.5*cv.get_n_splits(X_train)))
对于 i,枚举(cv.split(X_train,y_train))中的(train_idx,val_idx):
# 训练并可视化本次分割的树
dt.fit(X_train.iloc[train_idx], y_train.iloc[train_idx])
plt.subplot(cv.get_n_splits(X_train), 1, i+1)
plot_tree(dt,feature_names = X_train.columns,杂质= False,填充= True,rounded = True)
plt.title(f'Split {i+1} (验证准确度: {scores[i]:.3f})\n'
f'训练索引:{train_idx}\n'
f'验证索引:{val_idx}')
plt.tight_layout()
验证精度:0.556±0.157
例如,与K=3 和我们的高尔夫数据,我们可以使用一月和二月的天气数据进行训练,以预测三月的高尔夫比赛模式。然后我们使用一月到三月进行训练来预测四月,依此类推。通过仅向前推进,这种方法可以让我们更现实地了解我们的模型在根据天气预测未来高尔夫比赛模式时的效果。
留一交叉验证 (LOOCV)
留一交叉验证 (LOOCV) 是最彻底的验证方法。它仅使用一用于测试的样本和用于训练的所有其他样本。重复验证,直到每一条数据都用于测试。
假设我们有 100 天的高尔夫天气数据。LOOCV 将对模型进行 100 次训练和测试。每次训练用时 99 天,测试用时 1 天。此方法消除了测试中的任何随机性 - 如果您对相同数据多次运行 LOOCV,您将始终得到相同的结果。
然而,LOOCV需要大量的计算时间。如果你有氮数据块,您需要训练您的模型氮次。对于大型数据集或复杂模型,这可能需要很长时间才能实现。一些更简单的模型(例如线性模型)具有使 LOOCV 更快的快捷方式,但并非所有模型都如此。
从 sklearn.model_selection 导入 LeaveOneOut# 交叉验证策略
CV = LeaveOneOut()
# 计算交叉验证分数
分数 = cross_val_score(dt, X_train, y_train, cv=cv)
print(f"验证准确度:{scores.mean():.3f} ± {scores.std():.3f}")
# 为每个分割绘制树
plt.figure(figsize=(4, 3.5*cv.get_n_splits(X_train)))
对于 i,枚举(cv.split(X_train,y_train))中的(train_idx,val_idx):
# 训练并可视化本次分割的树
dt.fit(X_train.iloc[train_idx], y_train.iloc[train_idx])
plt.subplot(cv.get_n_splits(X_train), 1, i+1)
plot_tree(dt,feature_names = X_train.columns,杂质= False,填充= True,rounded = True)
plt.title(f'Split {i+1} (验证准确度: {scores[i]:.3f})\n'
f'训练索引:{train_idx}\n'
f'验证索引:{val_idx}')
plt.tight_layout()
验证精度:0.429±0.495
当我们没有太多数据并且需要充分利用我们拥有的每一部分时,LOOCV 非常有效。由于结果取决于每个数据,因此如果我们的数据中有噪声或异常值,结果可能会发生很大变化。
保留 P 交叉验证
Leave-P-Out 建立在 Leave-One-Out 的思想之上,但它不是仅使用一项数据进行测试,而是一次测试 P 项数据。这在留一法和 K 折验证之间建立了平衡。我们为 P 选择的数字会改变我们测试模型的方式以及所需的时间。
Leave-P-Out 的主要问题是可能的测试组合数量增长的速度有多快。例如,如果我们有 100 天的高尔夫天气数据,并且希望一次测试 5 天 (P=5),则有数百万种不同的可能方法来选择这 5 天。当我们有大量数据或使用较大的 P 数时,测试所有这些组合会花费太多时间。
从 sklearn.model_selection 导入 LeavePOut,cross_val_score# 交叉验证策略
CV = LeavePOut(p=3)
# 计算交叉验证分数(使用所有分割来提高准确性)
分数 = cross_val_score(dt, X_train, y_train, cv=cv)
print(f"验证准确度:{scores.mean():.3f} ± {scores.std():.3f}")
# 绘制前 15 棵树
n_树 = 15
plt.figure(figsize=(4, 3.5*n_trees))
对于 i,枚举(cv.split(X_train,y_train))中的(train_idx,val_idx):
如果 i >= n_trees:
休息
# 训练并可视化本次分割的树
dt.fit(X_train.iloc[train_idx], y_train.iloc[train_idx])
plt.subplot(n_trees, 1, i+1)
plot_tree(dt,feature_names = X_train.columns,杂质= False,填充= True,rounded = True)
plt.title(f'Split {i+1} (验证准确度: {scores[i]:.3f})\n'
f'训练索引:{train_idx}\n'
f'验证索引:{val_idx}')
plt.tight_layout()
验证精度:0.441±0.254
由于这些实际限制,Leave-P-Out 主要用于特殊情况,在这些情况下,我们需要非常彻底的测试,并且有足够小的数据集才能使其发挥作用。它在研究项目中特别有用,在这些项目中,获得最准确的测试结果比测试所需的时间更重要。
ShuffleSplit 交叉验证
ShuffleSplit 的工作方式与其他验证方法不同,它使用完全随机的分割。ShuffleSplit 不是像 K-fold 这样以有组织的方式分割数据,也不是像 Leave-P-Out 那样测试每种可能的组合,而是每次都会创建随机的训练和测试分割。
ShuffleSplit 与 K-fold 的不同之处在于,拆分不遵循任何模式。在 K 折中,每条数据都只使用一次进行测试。但在ShuffleSplit中,一天的高尔夫天气数据可能会用于多次测试,或者可能根本不用于测试。这种随机性为我们提供了一种不同的方式来了解我们的模型的表现如何。
ShuffleSplit 尤其适用于大型数据集,其中 K-fold 可能需要很长时间才能运行。无论我们有多少数据,我们都可以选择要测试的次数。我们还可以控制每个分割的大小。这使我们能够在彻底的测试和运行时间之间找到良好的平衡。
从 sklearn.model_selection 导入 ShuffleSplit,train_test_split# 交叉验证策略
cv = ShuffleSplit(n_splits=3, test_size=0.2, random_state=41)
# 计算交叉验证分数
分数 = cross_val_score(dt, X_train, y_train, cv=cv)
print(f"验证准确度:{scores.mean():.3f} ± {scores.std():.3f}")
# 为每个分割绘制树
plt.figure(figsize=(4, 3.5*cv.get_n_splits(X_train)))
对于 i,枚举(cv.split(X_train,y_train))中的(train_idx,val_idx):
# 训练并可视化本次分割的树
dt.fit(X_train.iloc[train_idx], y_train.iloc[train_idx])
plt.subplot(cv.get_n_splits(X_train), 1, i+1)
plot_tree(dt,feature_names = X_train.columns,杂质= False,填充= True,rounded = True)
plt.title(f'Split {i+1} (验证准确度: {scores[i]:.3f})\n'
f'训练索引:{train_idx}\n'
f'验证索引:{val_idx}')
plt.tight_layout()
验证精度:0.333±0.272
由于 ShuffleSplit 可以根据需要创建任意数量的随机分割,因此当我们想要了解模型的性能如何随着不同的随机分割而变化时,或者当我们需要更多测试来对结果充满信心时,它非常有用。
分层洗牌分割
分层 ShuffleSplit 将随机拆分与保持不同类型数据的正确组合相结合。与分层 K 折一样,它确保每个分割的每种类型数据的百分比与完整数据集大致相同。
这种方法为我们提供了两全其美的最好:随机分裂的自由和保持数据平衡的公平性。例如,如果我们的高尔夫数据集有70%的日子,而打高尔夫球的日子为30%,则每次随机分开都将尝试保持相同的70届30组合。当我们拥有不平衡的数据时,这一点特别有用,其中随机分裂可能会意外创建未很好地代表我们数据的测试集。
来自sklearn.model_selection导入stratifiedshufflesplit,train_test_split#交叉验证策略
cv = stratifiedshufflesplit(n_splits = 3,test_size = 0.2,andural_state = 41)
#计算交叉验证得分
scores = cross_val_score(dt,x_train,y_train,cv = cv)
打印(f“验证精度:{scores.mean():。3f} –±{scores.std():。3f}”)
#每个拆分的树木
plt.figure(figsize =(4,3.5*cv.get_n_splits(x_train)))))
对于i(train_idx,val_idx)
#训练和可视化这棵树的裂缝
dt.fit(x_train.iloc [train_idx],y_train.iloc [train_idx])
plt.subplot(cv.get_n_splits(x_train),1,i+1)
plot_tree(dt,feature_names = x_train.columns,impurity = false,填充= true,folded = true)
plt.title(f'split {i+1}(验证精度:{scores [i]:。3f})\ n'
f'train索引:{train_idx} \ n'
f'validation索引:{val_idx}')
plt.tight_layout()
验证精度:0.556±0.157
但是,尝试保持分裂的随机性质和数据类型的正确组合可能很棘手。该方法有时必须在完全随机和保持理想的比例之间做出小小的妥协。实际使用时,这些小的权衡很少引起问题,并且具有平衡的测试集通常比完全随机的分裂更重要。
总而言之,模型验证方法分为两个主要类别:持有方法和交叉验证方法:
保持方法
·火车测试拆分:最简单的方法,将数据分为两个部分
·“火车验证测试拆分:三向拆分,用于更复杂的模型开发
交叉验证方法
交叉验证方法可以通过多回合验证更好地利用可用数据:
k折
这些方法不是单个拆分,而是将数据划分为k部分:
·基本k折:通过不同的测试集旋转
·分层k折:维持跨分割的班级平衡
·组K折:保存数据分组
·时间序列分开:尊重时间顺序
·重复的k倍
·重复分层k折
放出方法
这些方法将验证到极端:
·离开p:一次对P数据点进行测试
·留下:对单个数据点进行测试
随机方法
这些引入了受控的随机性:
•shufflesplit:反复创建随机分裂
·分层弹置:与平衡类的随机分裂
将 pandas 导入为 pd
将 numpy 导入为 np
从 sklearn.tree 导入 DecisionTreeClassifier
来自sklearn.model_selection导入(
#保持方法
train_test_split,
#K折
kfold,#基本k折
stratifiedkfold,#维持班级余额
GroupKfold,#用于分组数据
Limeseriessplit,#时间数据
repoyedkfold,#多次运行
重复StratratifiedKfold,#具有类平衡的多个运行
#离开方法
离开,#单个测试点
离开,#P测试点
#随机方法
Shufflesplit,#随机火车测试拆分
stratifiedshufflesplit,#随机分割与班级平衡
cross_val_score#计算验证分数
)# 加载数据集
dataset_dict = {
'utlook':['sunny','sunny','overcast',dainy',dainy',''
“阳光明媚”,“阳光”,“多雨”,“阳光”,“阴天”,“阴天”,“多雨”,
“阳光明媚”,“阴天”,“多雨”,“阳光”,“阳光”,“多雨”,“阴天”,
“多雨”,“阳光”,“阴天”,“阳光”,“阴天”,“多雨”,“ overcast”],
'温度':[85.0,80.0,83.0,70.0,68.0,65.0,64.0,64.0,72.0,69.0,69.0,75.0,75.0,
72.0,81.0,71.0,81.0,74.0,76.0,78.0,82.0,67.0,67.0,85.0,73.0,
88.0,77.0,79.0,80.0,66.0,84.0],
“湿度”:[85.0,90.0,78.0,96.0,80.0,70.0,65.0,65.0,95.0,70.0,80.0,80.0,70.0,
90.0、75.0、80.0、88.0、92.0、85.0、75.0、92.0、90.0、90.0、85.0、88.0,
65.0、70.0、60.0、95.0、70.0、78.0],
“风”:[false,true,false,false,False,true,true,false,false,false,true,
是,错误,true,true,false,false,true,false,true,True,false,
是的,false,false,true,false,fals],
'play':['no','','yes','yes','','','','','','','','','','','','','','','','','','','','','','','','','',
'是','yes','','','','','','','','','','','','','','','',是',
'是','是','是','是','','yes']
}
df = pd.dataframe(dataset_dict)
#数据预处理
df = pd.dataframe(dataset_dict)
df = pd.get_dummies(df,columns = ['outlook'],prefix ='',prefix_sep ='',dtype = int)
df ['wind'] = df ['wind']。astype(int)
#设置标签
x,y = df.drop('play',轴= 1),df ['play']
##简单火车测试拆分
x_train,x_test,y_train,y_test = train_test_split(
x,y,test_size = 0.5,shuffle = false,
)
##火车测试验证拆分
#第一个拆分:单独的测试集
#x_temp,x_test,y_temp,y_test = train_test_split(
#x,y,test_size = 0.2,Random_State = 42
#)
#第二拆分:单独的验证集
#x_train,x_val,y_train,y_val = train_test_split(
#x_temp,y_temp,test_size = 0.25,Random_State = 42
#)
#创建模型
dt = deciessTreeClaleFier(Random_State = 42)
#选择验证方法
#cv = kfold(n_splits = 3,shuffle = true,andury_state = 42)
#cv = stratifiedkfold(n_splits = 3,shuffle = true,andury_state = 42)
#cv = groupkfold(n_splits = 3)#需要组参数
#cv = timeseriessplit(n_splits = 3)
#cv = repeatedkfold(n_splits = 3,n_repeats = 2,andury_state = 42)
#cv = repotedstratifiedkfold(n_splits = 3,n_repeats = 2,andural_state = 42)
cv = weaveOneOut()
#cv = wellpout(p = 3)
#cv = shufflesplit(n_splits = 3,test_size = 0.2,Random_State = 42)
#cv = stratifiedshufflesplit(n_splits = 3,test_size = 0.3,andural_state = 42)
#计算和打印分数
scores = cross_val_score(dt,x_train,y_train,cv = cv)
打印(f“验证精度:{scores.mean():。3f} –±{scores.std():。3f}”)
#最终拟合和测试
dt.fit(x_train,y_train)
test_accuracy = dt.score(x_test,y_test)
打印(f“测试精度:{test_accuracy:.3f}”)
验证精度:0.429±0.495
测试准确性:0.714
评论上述结果:验证和测试准确性之间的巨大差距以及验证分数的高标准偏差表明我们的模型性能是不稳定的。这种不一致可能来自在我们的小型天气数据集中使用剩余验证的单个数据点测试,从而导致性能变化很大。使用较大验证集的不同验证方法可能会给我们带来更可靠的结果。
选择如何验证模型是简单的不同情况,需要不同的方法。了解使用哪种方法可能意味着获得可靠或误导性结果之间的区别。选择验证方法时应考虑以下一些方面:
数据集的大小强烈影响哪种验证方法最有效。让我们看一下不同的尺寸:
大数据集(超过100,000个样本)
当您拥有大数据集时,测试时间成为主要考虑因素之一。简单的保持验证(将数据分解为培训和测试)通常可以很好地工作,因为您有足够的数据进行可靠的测试。如果您需要使用交叉验证,则仅使用3倍或使用较少回合的Shufflesplit可以提供良好的结果而无需花费太长时间。
中型数据集(1,000至100,000个样本)
对于中型数据集,常规的K折交叉验证最有效。使用5或10倍可以在可靠的结果和合理的计算时间之间取得良好的平衡。这一数量的数据通常足以创建代表性的分裂,但不能太长时间。
小数据集(少于1,000个样本)
小型数据集,例如我们的28天高尔夫记录示例,需要进行更仔细的测试。在这种情况下,保留对一的交叉验证或重复的k折实际上可以很好地工作。即使这些方法需要更长的时间才能运行,它们在没有太多数据可以使用的情况下帮助我们获得最可靠的结果。
选择验证方法时,我们需要考虑我们的计算资源。数据集大小之间的三向平衡,我们的模型的复杂程度以及我们使用的验证方法:
快速训练模型
诸如决策树,逻辑回归和线性SVM之类的简单模型可以使用更彻底的验证方法,例如保留的交叉验证或重复的分层K折,因为它们会迅速训练。由于每个训练回合仅需几秒钟或几分钟,因此我们可以负担得起许多验证迭代。即使在其N训练回合中运行LOOCV对于这些算法也可能是实用的。
资源丰富的模型
深度神经网络,带有许多树木的随机森林或梯度增强模型需要更长的时间来训练。当使用这些模型时,更深入的验证方法(例如重复的K折叠或离开P-out)可能是不切实际的。我们可能需要选择更简单的方法,例如基本的K折或ShufflesPlit,以使测试时间合理。
内存注意事项
一些方法之类的方法需要一次跟踪多个数据分裂。ShufflesPlit可以帮助您解决内存限制,因为它一次处理一个随机拆分。对于具有复杂模型的大型数据集(例如需要大量内存的深神经网络),可能需要更简单的固定方法。如果我们仍然需要使用有限的内存彻底验证,则可以使用时间序列拆分,因为它自然会按顺序处理数据,而不是一次需要记忆中的所有分裂。
当资源受到限制时,使用可以正确运行的更简单的验证方法(例如基本的k折)比尝试运行更复杂的方法(例如离开p-out)更好。
阶级失衡会严重影响我们如何验证模型。对于数据不平衡,分层验证方法变得至关重要。分层k折和分层的方法等方法确保每个测试拆分的类别与我们的完整数据集的类别差不多。如果不使用这些分层方法,某些测试集可能根本没有特定的类别,从而无法正确测试我们的模型对预测的能力。
在使用随着时间变化的数据时,我们需要特殊的验证方法。定期随机分裂方法不奏效,因为时间顺序很重要。 使用时间序列数据,我们必须使用诸如尊重时间顺序的时间序列分开之类的方法。
许多数据集都包含自然的相关数据组。当我们验证模型时,我们数据中的这些连接需要特殊处理。当数据点相关时,我们需要使用诸如K折的方法,以防止我们的模型意外学习的东西。
该流程图将帮助您为数据选择最合适的验证方法。假设您有足够的计算资源,下面的步骤概述了选择最佳验证方法的明确过程。
模型验证对于构建可靠的机器学习模型至关重要。在探索了许多验证方法之后,从简单的火车测试拆分到复杂的交叉验证方法,我们了解到,对于您拥有的任何数据,总是有一种合适的验证方法。
当机器学习通过新的方法和工具不断变化时,这些基本验证规则保持不变。当您很好地了解这些原则时,我相信您将建立人们可以信任和依靠的模型。