Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
524 lines (377 sloc) 14.7 KB

交叉验证

我们知道用训练集fit模型,用测试集验证泛化能力的套路。

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)))
Test set score: 0.88

单次划分数据集并不稳定和全面,因此我们需要对数据集进行多次划分,训练多个模型进行综合评估,这叫"交叉验证"。

K折交叉

最常见的就是"K折交叉验证",将数据均匀划分成K份,每次用1份做测试集,剩余做训练集。

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()))
Cross-validation scores: [0.96078431 0.92156863 0.95833333]
Average cross-validation score: 0.95

上述数据集分成3折,训练了3次模型,得到了3个精度,以及平均精度。

分层K折交叉

K折交叉划分数据的方式是从头开始均分成K份,如果样本数据的分类分布不均匀,那么就会导致K折交叉策略失效,比如:

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))
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验证。

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()))
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%的为测试集。

网格搜索

利用网格搜索,实现模型的自动化调参,找到最佳泛化性能的参数。

�带交叉验证的网格搜索

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_))
泛化精度: 0.9736842105263158
Best parameters: {'C': 100, 'gamma': 0.01, 'kernel': 'rbf'}

实际上,X_train数据会被网格进一步切分成2部分,一部分是训练集、一部分是验证集。

最终,我们还是基于测试集X_test来看模型对于未知数据的泛化能力。

交叉验证+网格搜索的嵌套

可以采用先交叉划分数据集,再进行K折网格搜索,这就是嵌套的意思。

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)

主要用来评估一下网格搜索到的最佳参数可以达到的最好泛化精度:

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)
[0.96666667 1.         0.9        0.96666667 1.        ]

评估指标与评分

牢记最终目标

精度不是唯一目标,我们需要考虑商业指标,对商业的影响。

二分类指标

2分类一共有2种类型,一种称为正类(positive),一种称为反类(negative)。

根据样本分类与模型预测分类,可以产生4种组合:

  • TP:预测是正类,样本是正类
  • FP:预测是正类,样本是反类
  • TN:预测是反类,样本是反类
  • FN:预测是反类,样本是正类
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))
[[399   4]
 [  7  40]]

混淆矩阵根据预测结果以及样本,统计出了TP,FP,TN,FN。

其分布如下:

反类 		TN 				FP
正类 		FN 				TP
    预测为反类			预测为正类

score()精度就是预测的正确率:

Accuracy = (TN+TP) / (TN+TP+FN+FP)

精确率是预测为正类中有多少真的是正类:

Precision = TP / (TP + FP)

精确率可以避免浪费,比如正类表示项目值得投资100万美元,如果把太多反类的预测为正类,那么就会让很多钱投资失败。

精确率的商业目标就是限制假正例的数量,可能因为假正例会带来很严重的影响。

召回率是有多少正类样本被预测为正类:

Recall = TP / (TP + FN)

精确率和召回率是矛盾的,如果模型预测所有都是正类,那么召回率就是100%;此时,就会出现很多假正类,精确率就很差。

因此需要综合2个指标进行折衷,就是f1-分数:

F = 2 * precision * recall / (precision + recall)
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))
[[399   4]
 [  7  40]]
0.8791208791208791

精确度、召回率、f1-score比精度更具备商业指导价值。

我们可以打印出所有评估指标:

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))
[[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方法,分类器实际是通过每种分类的概率决定分类的,我们可以调整阈值来影响模型结果,进而影响到不同分类的指标,可以通过模型绘制出不同阈值下模型的精确率、召回率表现,感觉有点晦涩就不展开了。

多分类指标

用分类报告来观察各个分类的指标就很不错:

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))



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)。

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))

{'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交给网格搜索进行参数搜索,网格搜索会在内部切分数据为训练集和验证集。

机器学习的目标一般不是构建一个高精度的模型,而是评估商业指标,例如:精确率与召回率。

You can’t perform that action at this time.