github地址:点我
交叉验证
我们知道用训练集fit模型,用测试集验证泛化能力的套路。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from sklearn.datasets import make_blobs from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split # 创建一个模拟数据集 X, y = make_blobs(random_state=0) # 将数据和标签划分为训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0) # 将模型实例化,并用它来拟合训练集 logreg = LogisticRegression().fit(X_train, y_train) # 在测试集上评估该模型 print("Test set score: {:.2f}".format(logreg.score(X_test, y_test))) |
1 |
Test set score: 0.88 |
单次划分数据集并不稳定和全面,因此我们需要对数据集进行多次划分,训练多个模型进行综合评估,这叫”交叉验证”。
K折交叉
最常见的就是”K折交叉验证”,将数据均匀划分成K份,每次用1份做测试集,剩余做训练集。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from sklearn.datasets import load_iris from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score # 数据集 iris = load_iris() # 模型 logreg = LogisticRegression() # K折交叉验证 scores = cross_val_score(logreg, iris.data, iris.target, cv=3) print("Cross-validation scores: {}".format(scores)) print("Average cross-validation score: {:.2f}".format(scores.mean())) |
1 2 |
Cross-validation scores: [0.96078431 0.92156863 0.95833333] Average cross-validation score: 0.95 |
上述数据集分成3折,训练了3次模型,得到了3个精度,以及平均精度。
分层K折交叉
K折交叉划分数据的方式是从头开始均分成K份,如果样本数据的分类分布不均匀,那么就会导致K折交叉策略失效,比如:
1 2 3 4 5 6 7 |
from sklearn.datasets import load_iris from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score # 数据集 iris = load_iris() print("Iris labels:\n{}".format(iris.target)) |
1 2 3 |
Iris labels: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 000000000000111111111111111111111111 1 111111111111111111111111122222222222 2 222222222222222222222222222222222222 2 2] |
显然样本数据的分类没有打散,可能测试集全是分类2,而训练集里压根没出现过分类2.
sklearn会根据模型是回归还是分类决定使用标准K折还是分层K折,不需我们关心,只需要了解。
其他策略
K折还有其他策略,通过cv参数控制即可,比如:留1验证。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from sklearn.datasets import load_iris from sklearn.linear_model import LogisticRegression from sklearn.model_selection import ShuffleSplit,cross_val_score # 数据集 iris = load_iris() # 模型 logreg = LogisticRegression() # 打乱划分交叉 shuffle_split = ShuffleSplit(test_size=.5, train_size=.5, n_splits=10) scores = cross_val_score(logreg, iris.data, iris.target, cv=shuffle_split) print("Cross-validation scores: {}".format(scores)) print("Average cross-validation score: {:.2f}".format(scores.mean())) |
1 2 3 |
Cross-validation scores: [0.90666667 0.92 0.96 0.98666667 0.98666667 0.96 0.92 0.89333333 0.81333333 0.89333333] Average cross-validation score: 0.92 |
通过cv参数控制策略,ShuffleSplit是运行10次,每次随机抽取50%的为训练集,50%的为测试集。
网格搜索
利用网格搜索,实现模型的自动化调参,找到最佳泛化性能的参数。
带交叉验证的网格搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
from sklearn.datasets import load_iris from sklearn.linear_model import LogisticRegression from sklearn.model_selection import GridSearchCV, train_test_split from sklearn.svm import SVC # 数据集 iris = load_iris() # 切分数据 X_train, X_test, y_train, y_test = train_test_split( iris.data, iris.target, random_state=0) # 2种网格 param_grid = [ # 第1个网格 {'kernel': ['rbf'], 'C': [0.001, 0.01, 0.1, 1, 10, 100], 'gamma': [0.001, 0.01, 0.1, 1, 10, 100]}, # 第2个网格 {'kernel': ['linear'],'C': [0.001, 0.01, 0.1, 1, 10, 100]} ] # 在2个网格中, 找到SVC模型的最佳参数, 这里cv=5表示每一种参数组合进行5折交叉验证计算得分 grid_search = GridSearchCV(SVC(), param_grid, cv=5) # fit找到最佳泛化的参数 grid_search.fit(X_train, y_train) # 查看精度 print("泛化精度:", grid_search.score(X_test, y_test)) # 打印最佳参数 print("Best parameters: {}".format(grid_search.best_params_)) |
1 2 |
泛化精度: 0.9736842105263158 Best parameters: {'C': 100, 'gamma': 0.01, 'kernel': 'rbf'} |
实际上,X_train数据会被网格进一步切分成2部分,一部分是训练集、一部分是验证集。
最终,我们还是基于测试集X_test来看模型对于未知数据的泛化能力。
交叉验证+网格搜索的嵌套
可以采用先交叉划分数据集,再进行K折网格搜索,这就是嵌套的意思。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
from sklearn.datasets import load_iris from sklearn.linear_model import LogisticRegression from sklearn.model_selection import GridSearchCV, cross_val_score, train_test_split from sklearn.svm import SVC # 数据集 iris = load_iris() # 2种网格 param_grid = [ # 第1个网格 {'kernel': ['rbf'], 'C': [0.001, 0.01, 0.1, 1, 10, 100], 'gamma': [0.001, 0.01, 0.1, 1, 10, 100]}, # 第2个网格 {'kernel': ['linear'],'C': [0.001, 0.01, 0.1, 1, 10, 100]} ] # 在2个网格中, 找到SVC模型的最佳参数, 每一组参数进行5折评估 grid_search = GridSearchCV(SVC(), param_grid, cv=5) # 外层K折 scores = cross_val_score(grid_search, iris.data, iris.target, cv=5) # 打印精度 print(scores) |
主要用来评估一下网格搜索到的最佳参数可以达到的最好泛化精度:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
from sklearn.datasets import load_iris from sklearn.linear_model import LogisticRegression from sklearn.model_selection import GridSearchCV, cross_val_score, train_test_split from sklearn.svm import SVC # 数据集 iris = load_iris() # 2种网格 param_grid = [ # 第1个网格 {'kernel': ['rbf'], 'C': [0.001, 0.01, 0.1, 1, 10, 100], 'gamma': [0.001, 0.01, 0.1, 1, 10, 100]}, # 第2个网格 {'kernel': ['linear'],'C': [0.001, 0.01, 0.1, 1, 10, 100]} ] # 在2个网格中, 找到SVC模型的最佳参数, 每一组参数进行5折评估 grid_search = GridSearchCV(SVC(), param_grid, cv=5) # 外层K折 scores = cross_val_score(grid_search, iris.data, iris.target, cv=5) # 打印精度 print(scores) |
1 |
[0.96666667 1. 0.9 0.96666667 1. ] |
评估指标与评分
牢记最终目标
精度不是唯一目标,我们需要考虑商业指标,对商业的影响。
二分类指标
2分类一共有2种类型,一种称为正类(positive),一种称为反类(negative)。
根据样本分类与模型预测分类,可以产生4种组合:
- TP:预测是正类,样本是正类
- FP:预测是正类,样本是反类
- TN:预测是反类,样本是反类
- FN:预测是反类,样本是正类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import confusion_matrix # 数据集 digits = load_digits() # 转换成2分类, 即目标数字是否等于9 y = digits.target == 9 # 切分数据集 X_train, X_test, y_train, y_test = train_test_split( digits.data, y, random_state=0) # 模型 lr = LogisticRegression() # 训练 lr.fit(X_train, y_train) # 预测 y_pred = lr.predict(X_test) # 混淆矩阵 print(confusion_matrix(y_test, y_pred)) |
1 2 |
[[399 4] [ 7 40]] |
混淆矩阵根据预测结果以及样本,统计出了TP,FP,TN,FN。
其分布如下:
1 2 3 |
反类 TN FP 正类 FN TP 预测为反类 预测为正类 |
score()精度就是预测的正确率:
1 |
Accuracy = (TN+TP) / (TN+TP+FN+FP) |
精确率是预测为正类中有多少真的是正类:
1 |
Precision = TP / (TP + FP) |
精确率可以避免浪费,比如正类表示项目值得投资100万美元,如果把太多反类的预测为正类,那么就会让很多钱投资失败。
精确率的商业目标就是限制假正例的数量,可能因为假正例会带来很严重的影响。
召回率是有多少正类样本被预测为正类:
1 |
Recall = TP / (TP + FN) |
精确率和召回率是矛盾的,如果模型预测所有都是正类,那么召回率就是100%;此时,就会出现很多假正类,精确率就很差。
因此需要综合2个指标进行折衷,就是f1-分数:
1 |
F = 2 * precision * recall / (precision + recall) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import confusion_matrix,f1_score # 数据集 digits = load_digits() # 转换成2分类, 即目标数字是否等于9 y = digits.target == 9 # 切分数据集 X_train, X_test, y_train, y_test = train_test_split( digits.data, y, random_state=0) # 模型 lr = LogisticRegression() # 训练 lr.fit(X_train, y_train) # 预测 y_pred = lr.predict(X_test) # 混淆矩阵 print(confusion_matrix(y_test, y_pred)) # 打印f1-score print(f1_score(y_test, y_pred)) |
1 2 3 |
[[399 4] [ 7 40]] 0.8791208791208791 |
精确度、召回率、f1-score比精度更具备商业指导价值。
我们可以打印出所有评估指标:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import confusion_matrix,f1_score from sklearn.metrics import classification_report # 数据集 digits = load_digits() # 转换成2分类, 即目标数字是否等于9 y = digits.target == 9 # 切分数据集 X_train, X_test, y_train, y_test = train_test_split( digits.data, y, random_state=0) # 模型 lr = LogisticRegression() # 训练 lr.fit(X_train, y_train) # 预测 y_pred = lr.predict(X_test) # 混淆矩阵 print(confusion_matrix(y_test, y_pred)) # 打印f1-score print(classification_report(y_test, y_pred)) |
1 2 3 4 5 6 7 8 9 10 11 |
[[399 4] [ 7 40]] precision recall f1-score support False 0.98 0.99 0.99 403 True 0.91 0.85 0.88 47 micro avg 0.98 0.98 0.98 450 macro avg 0.95 0.92 0.93 450 weighted avg 0.98 0.98 0.98 450 |
分别打印了反类和正类的准确率、召回率、f1-score指标,根据我们期望的商业指标,可以看出模型对正类与反类的表现如何。
接下来书中也提到,分类器大多提供predict_proba方法,分类器实际是通过每种分类的概率决定分类的,我们可以调整阈值来影响模型结果,进而影响到不同分类的指标,可以通过模型绘制出不同阈值下模型的精确率、召回率表现,感觉有点晦涩就不展开了。
多分类指标
用分类报告来观察各个分类的指标就很不错:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
from sklearn.metrics import accuracy_score from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report # 数据集 digits = load_digits() # 切分 X_train, X_test, y_train, y_test = train_test_split( digits.data, digits.target, random_state=0) # 训练 lr = LogisticRegression().fit(X_train, y_train) # 预测 pred = lr.predict(X_test) # 精度 print("Accuracy: {:.3f}".format(lr.score(X_test, y_test))) # 精确度、召回率、f1 指标 print(classification_report(pred, y_test)) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Accuracy: 0.953 precision recall f1-score support 0 1.00 1.00 1.00 37 1 0.91 0.89 0.90 44 2 0.93 0.95 0.94 43 3 0.96 0.90 0.92 48 4 1.00 0.97 0.99 39 5 0.98 0.98 0.98 48 6 1.00 0.96 0.98 54 7 0.94 1.00 0.97 45 8 0.90 0.93 0.91 46 9 0.94 0.96 0.95 46 micro avg 0.95 0.95 0.95 450 macro avg 0.95 0.95 0.95 450 weighted avg 0.95 0.95 0.95 450 |
每个分类都可以看到其精确率、召回率、f1-score。
回归指标
对于回归问题来说,使用score方法评估即可,因为他没有分类的正反问题。
score底层使用的是R^2,它是评估回归模型的很好的指标。
模型选择中使用其他评估指标
网格搜索评估最佳模型参数默认是基于精度评判的,我们可以指定基于其他指标(精确度、召回率、f1)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
from sklearn.datasets import load_digits from sklearn.model_selection import GridSearchCV, cross_val_score, train_test_split from sklearn.svm import SVC from sklearn.metrics import classification_report # 数据集 digits = load_digits() # 切分成2分类问题, 数字是否等于9 X_train, X_test, y_train, y_test = train_test_split( digits.data, digits.target == 9, random_state=0) # 2种网格 param_grid = {'gamma': [0.001, 0.01, 0.1, 1, 10, 100]} # 在2个网格中, 找到SVC模型的最佳参数, 每一组参数进行3折评估, 使用f1-score作为评估依据 grid_search = GridSearchCV(SVC(), param_grid, cv=3, scoring='f1') # 搜索最佳参数 grid_search.fit(X_train, y_train) # 打印最佳参数 print(grid_search.best_params_) # 打印最佳参数的f1-score print(grid_search.best_score_) # 打印在测试集上的各种指标 print(classification_report(grid_search.predict(X_test), y_test)) |
1 2 3 4 5 6 7 8 9 10 |
{'gamma': 0.001} 0.9771729298313027 precision recall f1-score support False 1.00 0.99 1.00 405 True 0.94 0.98 0.96 45 micro avg 0.99 0.99 0.99 450 macro avg 0.97 0.99 0.98 450 weighted avg 0.99 0.99 0.99 450 |
网格搜索以f1-score为依据找到了最佳参数,并且我们看到模型在测试集上的f1-score表现的确非常好。
K折交叉验证也是一样的,可以指定K折输出的评估指标,默认是精度。
总结
本章涉及:
- 交叉验证
- 网格搜索
- 评估指标
在网格搜索中,数据分成3份:
- 训练集:生成模型
- 验证集:搜索参数
- 测试集:模型评估
在实施网格搜索时,先切分train和test,再将train交给网格搜索进行参数搜索,网格搜索会在内部切分数据为训练集和验证集。
机器学习的目标一般不是构建一个高精度的模型,而是评估商业指标,例如:精确率与召回率。
如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~
