机器学习02
三、模型选择与调优
1 交叉验证
(1) 保留交叉验证HoldOut
HoldOut Cross-validation(Train-Test Split)
优点
1. 简单高效
- 操作简便:这种方法的实现非常直接,只需要将原始数据集按照一定比例(常见的如 70:30、80:20 等)随机划分为训练集和测试集。例如,在 Python 的
scikit - learn
库中,使用train_test_split
函数可以轻松完成划分,代码简洁易懂。
- 计算成本低:由于只进行一次模型训练和评估,相较于其他复杂的交叉验证方法(如 k 折交叉验证、留一交叉验证),它所需的计算资源和时间较少。在处理大规模数据集或者模型训练时间较长的情况下,这种简单性和高效性尤为突出。
2. 模拟真实应用场景
- 反映泛化能力:通过将一部分数据作为测试集,模拟了模型在未见过的数据上的表现,能够直观地反映模型的泛化能力。测试集的数据与训练集相互独立,模型在测试集上的性能可以近似看作其在实际应用中的性能。
3. 灵活性高
- 可调整划分比例:可以根据具体需求灵活调整训练集和测试集的划分比例。如果数据集较小,可以适当减少测试集的比例,以确保有足够的数据用于模型训练;如果数据集较大,则可以增加测试集的比例,以更准确地评估模型性能。
缺点
1. 评估结果不稳定
- 划分随机性影响:训练集和测试集的随机划分可能会导致评估结果存在较大的波动。不同的划分方式可能会得到不同的训练集和测试集分布,从而使模型在测试集上的性能表现有所差异。例如,在一个类别分布不均衡的数据集上,如果随机划分使得测试集中某一类别的样本过少,可能会导致评估结果不准确。
- 缺乏全面性:只进行一次划分和评估,不能充分考虑数据的多样性和模型在不同数据子集上的表现。因此,评估结果可能无法准确代表模型的真实性能。
2. 数据利用不充分
- 部分数据未用于训练:测试集的数据在模型训练过程中没有被使用,这意味着部分数据信息被浪费。特别是在数据集较小的情况下,这种数据利用不充分的问题会更加明显,可能导致模型无法充分学习到数据的特征,影响模型的性能。
3. 难以选择最优模型和参数
- 信息有限:由于只进行一次评估,难以全面比较不同模型或同一模型不同参数组合的性能。在实际应用中,需要尝试多种模型和参数设置来找到最优方案,而 HoldOut 方法提供的信息有限,可能无法准确判断哪种模型或参数组合是最优的。
(2) K-折交叉验证(K-fold)
(K-fold Cross Validation,记为K-CV或K-fold)
K-Fold交叉验证技术中,整个数据集被划分为K个大小相同的部分。每个分区被称为 一个”Fold”。所以我们有K个部分,我们称之为K-Fold。一个Fold被用作验证集,其余的K-1个Fold被用作训练集。
该技术重复K次,直到每个Fold都被用作验证集,其余的作为训练集。
模型的最终准确度是通过取k个模型验证数据的平均准确度来计算。
优点
更准确的模型评估
- 降低评估偏差:相较于简单的训练 - 测试集划分(HoldOut),K - 折交叉验证通过多次使用不同的数据子集进行验证,减少了因数据划分的随机性而导致的评估偏差。每次验证时,模型都在不同的数据组合上进行训练和测试,使得评估结果更能反映模型在整个数据集上的真实性能。例如,在一个包含 1000 个样本的数据集上,使用 5 折交叉验证,每个样本都有机会在验证集中出现一次,这样得到的评估结果会更加稳定和可靠。
- 充分考虑数据多样性:K 次验证过程让模型接触到了数据的不同方面,考虑了数据的多样性。这有助于发现模型在不同数据分布下的表现,避免模型对特定数据子集过拟合,从而提高模型的泛化能力。
有效利用数据
- 提高数据利用率:K - 折交叉验证将整个数据集都用于训练和验证,充分利用了所有样本的信息。与 HoldOut 方法中测试集数据不参与训练相比,K - 折交叉验证能够更充分地挖掘数据的价值,尤其是在样本数量有限的情况下,这种优势更为明显。
- 减少数据浪费:由于每个样本都在训练和验证中被使用,减少了数据的浪费,使得模型能够从更多的数据中学习到有用的特征,进而提升性能。
适用于模型选择和参数调优
- 比较不同模型:可以使用 K - 折交叉验证来比较不同的机器学习模型在同一数据集上的性能。通过对每个模型进行 K 折交叉验证并比较它们的平均评估结果,能够选择出最适合该数据集的模型。例如,在一个分类问题中,可以使用 10 折交叉验证来比较决策树、支持向量机和神经网络等不同模型的性能,从而确定最优模型。
- 优化模型参数:对于一个特定的模型,通常有多个参数需要调整。K - 折交叉验证可以帮助我们在不同的参数组合下评估模型的性能,从而选择出最优的参数组合。例如,在使用支持向量机时,可以通过 5 折交叉验证来选择最优的核函数和惩罚参数 C。
缺点
计算成本高
- 时间开销大:K - 折交叉验证需要进行 K 次模型训练和验证,计算量随着 K 的增大而增加。尤其是在数据集较大或者模型训练时间较长的情况下,计算成本会显著增加。例如,当 K = 10 且数据集非常大时,模型需要进行 10 次完整的训练和验证过程,这会消耗大量的时间和计算资源。
- 内存消耗大:多次训练模型需要存储不同的模型参数和中间结果,这会增加内存的消耗。在资源有限的情况下,可能会导致内存不足的问题,影响程序的正常运行。
对 K 值选择敏感
- K 值选择困难:K 值的选择对评估结果有重要影响。如果 K 值过小,可能无法充分考虑数据的多样性,导致评估结果不稳定;如果 K 值过大,虽然能更充分地利用数据,但计算成本会大幅增加,而且当 K 接近样本数量时,K - 折交叉验证就近似于留一交叉验证,计算效率会变得很低。因此,选择合适的 K 值需要根据数据集的大小和特点进行权衡。
数据分布问题
- 类别不平衡影响:如果数据集中不同类别的样本数量不均衡,K - 折交叉验证可能会导致某些类别在验证集中出现的次数过少,从而影响评估结果的准确性。例如,在一个二分类问题中,正样本和负样本的比例为 1:10,如果进行 5 折交叉验证,可能会出现某些验证集中正样本数量非常少的情况,导致模型在这些验证集上的性能评估不准确。
(3) 分层k-折交叉验证Stratified k-fold
Stratified k-fold cross validation
分层 k - 折交叉验证(Stratified k - fold cross validation)是对普通 k - 折交叉验证的改进,在划分数据集时会考虑样本的类别分布,确保每个折(子集)中各类别样本的比例与原始数据集中的比例一致。
优点
1. 更准确的模型评估
- 平衡类别分布:在处理类别不平衡的数据集时,分层 k - 折交叉验证能保证每个折中的类别比例与总体数据集一致。例如,在一个医学诊断数据集中,患病样本和健康样本的比例可能是 1:9,如果使用普通 k - 折交叉验证,可能会出现某个折中几乎没有患病样本的情况,导致模型在该折上的评估结果不能真实反映其在实际应用中的性能。而分层 k - 折交叉验证可以避免这种情况,使得每个折都包含各类别的合理样本,从而更准确地评估模型。
- 减少评估偏差:由于每个折的类别分布相似,模型在每个折上的训练和验证环境更具代表性,减少了因类别分布不均而导致的评估偏差。这使得最终得到的平均评估指标(如准确率、召回率等)更能反映模型在整个数据集上的真实性能。
2. 提高模型泛化能力评估的可靠性
- 模拟真实场景:在实际应用中,数据往往是类别不平衡的。分层 k - 折交叉验证模拟了这种真实场景,让模型在具有代表性的类别分布下进行训练和验证,从而更可靠地评估模型的泛化能力,即模型在未见过的数据上的表现能力。
- 避免过拟合特定类别:如果不考虑类别分布进行交叉验证,模型可能会在某些折上过度拟合某个占比大的类别,而忽略其他类别。分层 k - 折交叉验证可以防止这种情况发生,促使模型学习到各类别的特征,提高模型对不同类别样本的识别能力。
3. 适用于多种分类问题
- 广泛应用:无论是二分类问题还是多分类问题,分层 k - 折交叉验证都能有效应用。它可以确保在各种分类任务中,每个折都能包含各类别的样本,为模型评估提供更合理的环境。
缺点
1. 计算成本较高
- 多次训练和验证:和普通 k - 折交叉验证一样,分层 k - 折交叉验证需要进行 k 次模型训练和验证过程。随着数据集的增大和模型复杂度的提高,计算时间和资源消耗会显著增加。例如,在处理大规模图像数据集时,每次训练模型都需要大量的计算资源和时间,k 次训练和验证会使计算成本变得很高。
2. 对数据特征的依赖
- 仅考虑类别信息:分层 k - 折交叉验证主要关注样本的类别分布,而忽略了其他数据特征。如果数据集中存在其他重要的特征结构(如时间序列、空间信息等),分层 k - 折交叉验证可能无法充分利用这些信息,导致评估结果不够全面。例如,在时间序列数据中,数据的顺序和趋势对模型性能有重要影响,而分层 k - 折交叉验证可能会破坏这种时间顺序,影响评估的准确性。
3. 对异常值和噪声的敏感性
- 异常值影响:由于分层 k - 折交叉验证是基于类别进行划分的,如果数据集中存在异常值或噪声,这些异常样本可能会被分散到各个折中,影响模型在每个折上的训练和验证效果,进而影响最终的评估结果。例如,在一个信用评分数据集中,存在一些异常高或异常低的评分样本,这些异常样本可能会干扰模型的学习过程,导致评估结果不准确。
留一交叉验证(Leave-One-Out Cross-Validation, LOOCV)
原理
留一交叉验证是一种特殊的 k 折交叉验证,其中 k 等于样本数量 。具体做法是每次只留一个样本作为验证集,其余 个样本作为训练集,重复 次,最终得到 个模型评估结果,取平均值作为最终的评估指标。
优点
- 数据利用率高:每个样本都有机会作为验证集,所有样本都被用于模型的训练和验证,充分利用了有限的数据,能得到较为准确的模型评估。
- 偏差较小:由于几乎使用了所有数据进行训练,模型的偏差相对较小,评估结果更接近模型在整个数据集上的真实性能。
缺点
- 计算成本高:需要进行 次模型训练和验证,当样本数量 很大时,计算量会非常大,时间成本极高。
- 对异常值敏感:单个样本作为验证集时,如果该样本是异常值,可能会对评估结果产生较大影响。
- 方差较大:每次验证只基于一个样本,评估结果的波动可能较大,导致方差较大。
适用场景
- 样本数量较少的情况,此时可以充分利用有限的数据进行评估。
- 对模型评估准确性要求较高,且计算资源充足的场景。
蒙特卡罗交叉验证(Monte Carlo Cross-Validation)
原理
蒙特卡罗交叉验证也称为随机子抽样验证,它随机地将数据集划分为训练集和验证集,重复多次(例如 次),每次划分的比例可以自定义。每次划分后训练模型并评估,最终得到 个评估结果,取平均值作为最终的评估指标。
优点
- 灵活性高:可以灵活控制训练集和验证集的比例,以及重复划分的次数,适应不同的需求。
- 计算效率相对较高:相比于留一交叉验证,蒙特卡罗交叉验证的计算量通常较小,尤其是在样本数量较大时。
缺点
- 评估结果不稳定:由于每次划分是随机的,不同的划分可能导致不同的训练集和验证集分布,使得评估结果的波动较大,稳定性较差。
- 部分样本可能未被充分利用:存在部分样本在多次划分中都未被用作验证集的可能性,导致数据利用不够充分。
适用场景
- 数据集较大,希望在较短时间内得到大致的模型评估结果。
- 对计算资源有限制,无法进行复杂交叉验证的情况。
时间序列交叉验证(Time Series Cross-Validation)
原理
考虑到时间序列数据的顺序性和相关性,时间序列交叉验证按照时间顺序依次划分训练集和验证集。例如,前 个时间点的数据作为训练集,接下来的 个时间点的数据作为验证集,逐步向前移动,直到覆盖整个时间序列。
优点
- 符合时间序列特性:充分考虑了时间序列数据的顺序性和相关性,能够更准确地评估模型在实际时间序列预测中的性能。
- 避免信息泄露:确保验证集的数据不会在训练集中出现,避免了模型利用未来信息进行训练,使评估结果更具现实意义。
缺点
- 数据划分受时间限制:由于要按照时间顺序划分,不能像其他交叉验证方法那样随机划分,可能会导致某些时间段的数据在验证集中出现的次数较少,影响评估的全面性。
- 模型适应性问题:随着时间的推移,数据的分布和特征可能发生变化,模型在不同时间段的性能可能不稳定,需要不断调整模型。
适用场景
- 处理时间序列数据,如股票价格预测、气象数据预测、销售数据预测等场景。
示例
from sklearn.datasets import load_iris
from sklearn.model_selection import StratifiedKFold
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
# 加载数据
iris = load_iris()
X = iris.data
y = iris.target
# 初始化分层k-折交叉验证器
#n_splits划分为几个折叠
#shuffle是否在拆分之前被打乱(随机化),False则按照顺序拆分
#random_state随机因子
strat_k_fold = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
# 创建一个K近邻分类器实例
knn = KNeighborsClassifier(n_neighbors=7)
# 进行交叉验证
accuracies = []
for train_index, test_index in strat_k_fold.split(X, y):
print(train_index, test_index)
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
# 数据预处理(标准化)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 使用K近邻分类器进行训练
knn.fit(X_train_scaled, y_train)
# 输出每次折叠的准确性得分
score = knn.score(X_test,y_test)
print(score)
accuracies.append(score)#把分数添加到外面列表中
print(sum(accuracies)/len(accuracies))#平均得分
#使用StratifiedKFold来创建5个折叠,每个折叠中鸢尾花数据集的类别分布与整体数据集的分布一致。然后我们对每个折叠进行了训练和测试,计算了分类器的准确性
2 超参数搜索
超参数搜索也叫网格搜索(Grid Search)
比如在KNN算法中,k是一个可以人为设置的参数,所以就是一个超参数。网格搜索能自动的帮助我们找到最好的超参数值。
class sklearn.model_selection.GridSearchCV(estimator, param_grid)
说明:
同时进行交叉验证(CV)、和网格搜索(GridSearch),GridSearchCV实际上也是一个估计器(estimator),同时它有几个重要属性:
best_params_ 最佳参数
best_score_ 在训练集中的准确率
best_estimator_ 最佳估计器
cv_results_ 交叉验证过程描述
best_index_最佳k在列表中的下标
参数:
estimator: scikit-learn估计器实例
param_grid:以参数名称(str)作为键,将参数设置列表尝试作为值的字典
示例: {"n_neighbors": [1, 3, 5, 7, 9, 11]}
cv: 确定交叉验证切分策略,值为:
(1)None 默认5折
(2)integer 设置多少折
如果估计器是分类器,使用"分层k-折交叉验证(StratifiedKFold)"。在所有其他情况下,使用KFold。
示例-鸢尾花分类
用KNN算法对鸢尾花进行分类,添加网格搜索和交叉验证
# 用KNN算法对鸢尾花进行分类,添加网格搜索和交叉验证
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
def knn_iris_gscv():
# 1)获取数据
iris = load_iris()
# 2)划分数据集
x_train, x_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=22)
# 3)特征工程:标准化
transfer = StandardScaler()
x_train = transfer.fit_transform(x_train)
x_test = transfer.transform(x_test)
# 4)KNN算法预估器, 这里就不传参数n_neighbors了,交给GridSearchCV来传递
estimator = KNeighborsClassifier()
# 加入网格搜索与交叉验证, GridSearchCV会让k分别等于1,2,5,7,9,11进行网格搜索偿试。cv=10表示进行10次交叉验证
estimator = GridSearchCV(estimator, param_grid={"n_neighbors": [1, 3, 5, 7, 9, 11]}, cv=10)
estimator.fit(x_train, y_train)
# 5)模型评估
# 方法1:直接比对真实值和预测值
y_predict = estimator.predict(x_test)
print("y_predict:\n", y_predict)
print("直接比对真实值和预测值:\n", y_test == y_predict)
# 方法2:计算准确率
score = estimator.score(x_test, y_test)
print("在测试集中的准确率为:\n", score) #0.9736842105263158
# 最佳参数:best_params_
print("最佳参数:\n", estimator.best_params_) #{'n_neighbors': 3}, 说明k=3时最好
# 最佳结果:best_score_
print("在训练集中的准确率:\n", estimator.best_score_) #0.9553030303030303
# 最佳估计器:best_estimator_
print("最佳估计器:\n", estimator.best_estimator_) # KNeighborsClassifier(n_neighbors=3)
# 交叉验证结果:cv_results_
print("交叉验证过程描述:\n", estimator.cv_results_)
#最佳参数组合的索引:最佳k在列表中的下标
print("最佳参数组合的索引:\n",estimator.best_index_)
#通常情况下,直接使用best_params_更为方便
return None
knn_iris_gscv()