模型评估
性能度量
错误率与精度
- 错误率(error rate):分类错误的样本占样本总数的比例;
- 精度(accuracy):分类正确的样本占样本总数的比例;
精度 = 1 - 错误率
查准率、召回率、F1得分
错误率和精度虽然常用,但是并不能满足所有的任务需求。比如:在一次疾病检测中,我们关注以下问题:
1) 检测出感染的个体中有多少是真正的病毒携带者?
2)所有真正病毒携带者中有多少被检测出来?
此时错误率/精度将无法反映出以上信息。实际上,类似的问题中,‘查准率’(precision)与‘召回率’(recall)是更为适合的度量标准。
- 对于二分类问题,我们根据真实类别、预测类别得到:
- 真正例(true positive)
- 真反例(true negative)
- 假正例(false positive)
- 假反例(false negative)
- 样本总数:TP + TN + FP + FN
- 查准率(precision):TP / (TP + FP)
- 召回率(recall): TP / (TP + FN)
- F1得分:
f 1 = 2 ∗ p r e c i s i o n ∗ r e c a l l p r e c i s i o n + r e c a l l f_1 = \frac {2*precision*recall}{precision+recall} f1=precision+recall2∗precision∗recall
一般来说,查准率高时,召回率往往偏低;召回率高时,查准率往往偏低。例如:病毒感染检测中,若要提高查准率,需要采用更加严格的检测标准,但是这样更容易漏检部分感染者,于是召回率偏低;反之,放松检测标准,更多的人将被检测为感染者,这样查准率经偏低。通常只在一些简单的任务中才可以同时获得很高的查准率和召回率。 - 商品推荐中,为了尽可能少打扰客户,希望推荐的内容为用户感兴趣的,则更关心推荐的准与不准,即关注查准率;
- 逃犯检索中,为了使得更少的逃犯漏网,即使多抓两个人也不愿意漏抓一个人,即关注召回率。
混淆矩阵
混淆矩阵也称为误差矩阵,是表示精度的一种标准格式,使用 M a t r i x n ∗ n Matrix_{n*n} Matrixn∗n表示。 s u m ( r o w i ) sum(row_i) sum(rowi)表示第i个真实的样本, s u m ( c o l i ) sum(col_i) sum(coli)表示第i个预测类别的样本。
- 预测结果准确的混淆矩阵
A类别 | B类别 | C类别 | |
---|---|---|---|
A类别 | 5 | 0 | 0 |
B类别 | 0 | 6 | 0 |
C类别 | 0 | 0 | 7 |
上述混淆矩阵表示:A类别实际有5个样本,B类别实际6个样本,C类别实际7个样本;预测结果中A类5个,B类6个,C类7个;
- 预测结果不准确的混淆矩阵
A类别 | B类别 | C类别 | |
---|---|---|---|
A类别 | 3 | 1 | 1 |
B类别 | 0 | 4 | 2 |
C类别 | 0 | 0 | 7 |
上述混淆矩阵表示:A类别实际有5个样本,B类别实际6个样本,C类别实际7个样本;预测结果中A类别3个预测正确,B类和C类各有一个样本错误预测为A类别,其他分析类似。
根据混淆矩阵:
查准率 = 主对角线上的值 / 该值所在列的和
召回率 = 主对角线上的值 / 该值所在行的和
演示
import numpy as np
import sklearn.metrics as sm
import sklearn.model_selection as ms
import sklearn.naive_bayes as nb
x, y = [], []
with open('D:/python/data/multiple1.txt', 'r') as f:
for line in f.readlines():
data = [float(substr) for substr in line.split(',')]
x.append(data[:-1])
y.append(data[-1])
x = np.array(x)
y = np.array(y)
train_x, test_x, train_y, test_y = ms.train_test_split(x, y, test_size=0.25, random_state=7)
# 创建高斯贝叶斯分类器对象
model = nb.GaussianNB()
# 模型训练
model.fit(train_x, train_y)
# 预测
pred_test_y = model.predict(test_x)
print('precision: ', sm.precision_score(test_y, pred_test_y,
average='macro' # 计算平均值,不考虑样本权重
))
# precision: 0.9903846153846154
print('recall: ', sm.recall_score(test_y, pred_test_y,
average='macro' # 计算平均值,不考虑样本权重
))
# recall: 0.9910714285714286
print('F1_score: ', sm.f1_score(test_y, pred_test_y,
average='macro'
))
# F1_score: 0.9905525846702318
print('\nConfusion Matrix:\n', sm.confusion_matrix(test_y, pred_test_y))
"""
Confusion Matrix:
[[22 0 0 0]
[ 0 27 1 0]
[ 0 0 25 0]
[ 0 0 0 25]]
"""
训练集和测试集
通常情况下,评估一个模型性能好坏,将样本划分为两部分,一部分专门用于对模型进行训练,称为‘训练集’;一部分用于对模型进行测试,称为‘测试集’。训练集和测试集一般不存在重叠部分。常用的训练集和测试集比例有:9:1, 8:2, 7:3等。训练集和测试集的划分应该尽量保持均衡、随机,不能集中于少量类别。
交叉验证法
- 什么是交叉验证
在样本数量较少的情况下,如果将样本划分为训练集、测试集,可能导致单个集合样本数量更少,这样可以采用交叉验证法训练和测试模型。 - 执行步骤
‘交叉验证法’(cross validation)先将数据集划分为k个大小相同/相似、互补相交的子集,每一个子集称为一个‘折叠’(fold),每次训练,轮流使用其中的一个作为测试集,其他作为训练集。由此,我们就相当于获得了k组训练集、测试集,最终的预测结果为k个测试结果的平均值。
- 实现(sklearn)
import numpy as np
import sklearn.metrics as sm
import sklearn.model_selection as ms
import sklearn.naive_bayes as nb
x, y = [], []
with open('D:/python/data/multiple1.txt', 'r') as f:
for line in f.readlines():
data = [float(substr) for substr in line.split(',')]
x.append(data[:-1])
y.append(data[-1])
x = np.array(x)
y = np.array(y)
train_x, test_x, train_y, test_y = ms.train_test_split(x, y, test_size=0.25, random_state=7)
# 创建高斯贝叶斯分类器对象
model = nb.GaussianNB()
pws = ms.cross_val_score(model, train_x, train_y,
cv=5, # 折叠数量
scoring='precision_weighted' # 查准率
)
print('precision: ', pws.mean())
# precision: 0.996875
rws = ms.cross_val_score(model, train_x, train_y,
cv=5, # 折叠数量
scoring='recall_weighted' # 召回率
)
print('recall: ', rws.mean())
# recall: 0.9966666666666667
f1s = ms.cross_val_score(model, train_x, train_y,
cv=5, # 折叠数量
scoring='f1_weighted' # 召回率
)
print('f1_score: ', f1s.mean())
# f1_score: 0.996662958843159
acc = ms.cross_val_score(model, train_x, train_y,
cv=5, # 折叠数量
scoring='accuracy' # 召回率
)
print('accuracy: ', acc.mean())
# accuracy: 0.9966666666666667
模型优化
验证曲线
验证曲线是根据不同的评估参数对模型的优劣进行评估。比如:构建随机森林,树的数量不同,模型预测准确度有何不同?
import numpy as np
import sklearn.preprocessing as sp
import sklearn.ensemble as se
import sklearn.model_selection as ms
import matplotlib.pyplot as plt
raw_samples = [] # 保存样本数据
with open('D:\python\data\car.txt', 'r') as f:
for line in f.readlines():
raw_samples.append(line.replace('\n', '').split(','))
data = np.array(raw_samples).transpose()
print(data.shape)
encoders = [] # 记录标签编码器
train_x = [] # 编码后的数据
# 对样本进行标签编码
for row in range(len(data)):
encoder = sp.LabelEncoder() # 创建标签编码器
encoders.append(encoder)
if row < len(data) - 1: # 不是最后一行,因此为样本特征
coder = encoder.fit_transform(data[row]) # 编码
train_x.append(coder)
else: # 最后一行为样本标签
train_y = encoder.fit_transform(data[row])
train_x = np.array(train_x).transpose()
train_y = np.array(train_y)
print(train_x.shape)
print(train_y.shape)
# 创建随机森林分类器
model = se.RandomForestClassifier(max_depth=8, # 最大树高
random_state=1) # 随机种子
n_estimators = np.arange(50, 550, 50) # 待验证参数表
# 调用validation_curve,返回训练集及测试集得分矩阵
train_scores, test_scores = ms.validation_curve(model,
train_x, train_y,
'n_estimators',
n_estimators, # 代预测模型参数
cv=5
)
train_mean = train_scores.mean(axis=1)
print('train_mean:\n', train_mean)
test_mean = test_scores.mean(axis=1)
print('test_mean:\n', test_mean)
print('--------可视化--------')
plt.figure('validation_curve', facecolor='lightgray')
plt.title('validation_curve', fontsize=16)
plt.xlabel('n_estimators', fontsize=16)
plt.ylabel('f1_score', fontsize=16)
plt.tick_params(labelsize=10)
plt.grid(linestyle=':')
plt.plot(n_estimators, test_mean, 'o-', c='blue', label='Testing')
plt.legend()
plt.show()
学习曲线
学习曲线是用来评估不同大小下训练集下模型的优劣程度,如果预测结果随着训练集样本的增大变化不大,则说明增加样本数量不会对模型产生明显优化作用。
超参数优化
超参数是在开始学习之前设置的参数,并不需要通过训练得到的参数数据。超参数的设置只要依赖于经验、实验或经过比较的优选值。比如,以下超参数:
- 学习率;
- 决策树模型树的最大深度;
- 随机森林模型树的数量;
- 交叉验证中折叠的数量;
- 训练集/测试集的比例
超参数选择主要有随机搜索、网格搜索等方法
网格搜索
网格搜索f啊指将主要参数以及这些参数的主要取值,通过穷举法产生不同组合,计算并比较预测结,以此寻找这些参数的最有组合

- 网格搜索法演示
import numpy as np
import sklearn.model_selection as ms
import sklearn.svm as svm
import sklearn.metrics as sm
import matplotlib.pyplot as plt
x, y = [], []
with open('D:/python/data/multiple2.txt', 'r') as f:
for line in f.readlines():
data = [float(substr) for substr in line.split(',')]
x.append(data[:-1])
y.append(data[-1])
x = np.array(x)
y = np.array(y)
# 确定网格搜索字典
params = [
{
'kernel':['linear'],
'C':[1, 10, 100, 1000]
},
{
'kernel':['poly'],
'C':[1],
'degree':[2,3]
},
{
'kernel':['rbf'],
'C':[1, 10, 100, 1000],
'gamma':[1, 0.1, 0.01, 0.001]
},
]
# 创建网格搜索对象
model = ms.GridSearchCV(svm.SVC(probability=True), # 启用概率估计
params, cv=5
)
# 训练
model.fit(x, y)
print('best_score_', model.best_score_)
print('best_params_', model.best_params_)
l, r, h = x[:,0].min()-1, x[:,0].max()+1, 0.005
b, u, v = x[:,1].min()-1, x[:,1].max()+1, 0.005
grid_x = np.meshgrid(np.arange(l,r,h), np.arange(b,u,v))
flat_x = np.c_[grid_x[0].ravel(), grid_x[1].ravel()]
pred_flat_y = model.predict(flat_x)
pred_flat_y = pred_flat_y.reshape(grid_x[0].shape)
plt.figure('Grid Search', facecolor='lightgray')
plt.title('Grid Searb', fontsize=18)
plt.xlabel('x', fontsize=12)
plt.ylabel('y', fontsize=12)
plt.tick_params(labelsize=10)
plt.pcolormesh(grid_x[0], grid_x[1], pred_flat_y, cmap='gray')
C0, C1 = y==0, y==1
plt.scatter(x[C0][:, 0], x[C0][:, 1], c='red', s=60)
plt.scatter(x[C1][:, 0], x[C1][:, 1], c='blue', s=60)
plt.show()
- 随机搜索
随机搜索的思想于网络搜索比较相似,只是不再测试上界与下界之间的所有值,而是在搜索范围内随机选取样本点。理论依据是如果样本足够大,那么通过随机采样的方法也可以大概率找到全局最优参数或者近似最优参数。因为是随机采样,所以速度较网格搜索(穷举法)更快,然而搜索结果没有办法保证,即无法保证一定会找到最优参数组合。