《kaggle竞赛攻顶秘笈》 | 任务种类 | 任务评价指标 | 评价指标与目标函数 | 评价指标最佳化
文章目录
- 一、数据分析竞赛中的任务种类
- 二、数据分析竞赛的资料集
- 三、任务评价指标
- 1.回归任务的评价指标
- 1.1 均方根误差(RMSE)
- 1.2 均方根对数误差(RMSLE)
- 1.3 平均绝对值误差(MAE)
- 1.4 决定系数( R 2 R^2 R2)
- 2.二元分类任务的评价指标——标签预测
- 2.1 混淆矩阵(confusion matrix)
- 2.2 准确率(accuracy)和错误率(error rate)
- 2.3 精确率(precision)和召回率(recall)
- 2.4 F1-score和Fβ-score
- 2.5 MCC
- 3.二元分类任务的评价指标——概率预测
- 3.1 交叉熵(logloss)
- 3.2 AUC
- 4.多元分类任务的评价指标
- 4.1 multi-class accuracy
- 4.2 multi-class logloss
- 4.3 mean-F1、macro-F1和micro-F1
- 4.4 quadratic weighted kappa
- 4.5 推荐任务的评价指标:MAP@K
- 四、评价指标与目标函数
- 1.评价指标与目标函数的差异
- 2.自定义评价指标与目标函数
- 五、评价指标的最佳化
- 1.最佳化评价指标的方法
- 2.最佳化阈值
- 3.是否使用out-of-fold来最佳化阈值?
- 4.针对预测概率的调整
一、数据分析竞赛中的任务种类
- 回归任务
- 分类任务
- 二分类任务
可依输出的预测值分为标签预测和概率预测 - 多分类任务
- 多元分类:一个资料属于一个类别
- 多标签分类:一个资料同时属于多个类别
- 二分类任务
- 推荐任务
预测消费者可能会购买的商品或是消费者可能对哪些广告有兴趣 - 图像任务
- 目标识别任务
- 图像分割任务
二、数据分析竞赛的资料集
- 表格资料:结构化数据
- 外部资料
大部分允许使用外部资料的竞赛都规定参赛者必须在Discussion中的专用讨论群组内分享自己已使用的外部资料。 - 时间序列资料:以时间的推移进行测量的资料
- 图像或自然语言资料
三、任务评价指标
评价模型的性能及预测值的好坏的指标就是评价指标。
1.回归任务的评价指标
1.1 均方根误差(RMSE)
计算方式是:先将每个资料的实际值与预测值相减得到差值,再求得差值的平方,然后求平均并开根号,就可以计算出RMSE
。
R
M
S
E
=
1
N
∑
i
=
1
N
(
y
i
−
y
i
^
)
2
RMSE=\sqrt{\frac{1}{N}\sum_{i=1}^{N}(y_i-\hat{y_i})^2}
RMSE=N1i=1∑N(yi−yi^)2
注意:RMSE
较容易受到极端值的影响,因此,必须事先排除极端值,否则,最后建立的模型很有可能会过于偏向于极端值。
例子:
import numpy as np
from sklearn.metrics import mean_squared_error
# y_true为真实值、y_pred为预测值
y_true = [1.0, 1.5, 2.0, 1.2, 1.8]
y_pred = [0.8, 1.5, 1.8, 1.3, 3.0]
# 计算均方误差(MSE)
mse = mean_squared_error(y_true,y_pred)
# 对MSE进行平方根得到RMSE
rmse = np.sqrt(mse)
print(rmse)
# 0.5531726674375732
1.2 均方根对数误差(RMSLE)
计算方式是计算实际值与预测值各自加1的对数,并将两个对数的差平方后,取平均并开根号。
R
M
S
L
E
=
1
N
∑
i
=
1
N
(
l
o
g
(
1
+
y
i
)
−
l
o
g
(
1
+
y
i
^
)
)
2
RMSLE=\sqrt{\frac{1}{N}\sum_{i=1}^{N}(log(1+y_i)-log(1+\hat{y_i}))^2}
RMSLE=N1i=1∑N(log(1+yi)−log(1+yi^))2
注意:
RMSLE
与RMSE
的关系为:将每个资料的实际值进行对数转换后,再使用RMSE
来进行评估- 当标签呈现重尾分布,先做对数运算再取RMSE,可以避免受到少数较大值的影响
例子:
from sklearn.metrics import mean_squared_log_error
# y_true为真实值、y_pred为预测值
y_true = [1.0, 1.5, 2.0, 1.2, 1.8]
y_pred = [0.8, 1.5, 1.8, 1.3, 3.0]
# mean_squared_log_error(y_true,y_pred)等价于np.mean(np.square(np.log1p(y_true)-np.log1p(y_pred)))
rmlse = np.sqrt(mean_squared_log_error(y_true,y_pred))
print(rmlse)
# 0.02901076588100996
1.3 平均绝对值误差(MAE)
计算方式为取实际值与绝对值的差值,计算其绝对值的平均。
M
A
E
=
1
N
∑
i
=
1
N
∣
y
i
−
y
i
^
∣
MAE=\frac{1}{N}\sum_{i=1}^{N}|y_i-\hat{y_i}|
MAE=N1i=1∑N∣yi−yi^∣
注意:
MAE
不易受到极端值的影响MAE
在微分时有一些较难处理的特性
例子:
from sklearn.metrics import mean_absolute_error
# y_true为真实值、y_pred为预测值
y_true = [1.0, 1.5, 2.0, 1.2, 1.8]
y_pred = [0.8, 1.5, 1.8, 1.3, 3.0]
mae = mean_absolute_error(y_true,y_pred)
print(mae)
# 0.33999999999999997
1.4 决定系数( R 2 R^2 R2)
决定系数的计算公式为:
R
2
=
1
−
∑
i
=
1
N
(
y
i
−
y
i
^
)
2
∑
i
=
1
N
(
y
i
−
y
i
‾
)
2
R^2=1-\frac{\sum_{i=1}^{N}(y_i-\hat{y_i})^2}{\sum_{i=1}^{N}(y_i-\overline{y_i})^2}
R2=1−∑i=1N(yi−yi)2∑i=1N(yi−yi^)2
注意:
- 让决定系数最大化就等于让
RMSE
最小化 - 决定系数最大值为1,越接近于1表示预测越准确
例子:
from sklearn.metrics import r2_score
# y_true为真实值、y_pred为预测值
y_true = [1.0, 1.5, 2.0, 1.2, 1.8]
y_pred = [0.8, 1.5, 1.8, 1.3, 1.8]
r2 = r2_score(y_true,y_pred)
print(r2)
# 0.8676470588235294
2.二元分类任务的评价指标——标签预测
2.1 混淆矩阵(confusion matrix)
根据预测值是正例或负例,以及预测正确或错误可分为以下情况:
TP
:真阳性,预测值为正例,且预测正确的情况TN
:真阴性,预测值为负例,且预测正确的情况FP
:伪阳性,预测值为正例,但预测错误的情况(实际值为负例)FN
:伪阴性,预测值为负例,但预测错误的情况(实际值为正例)
将以上四种情况作为矩阵图中的元素来显示预测的结果就可以成为混淆矩阵。
例子:
from sklearn.metrics import confusion_matrix
# 以 0,1 来表示二元分类的正例与负例
y_true = [1, 0, 1, 1, 0, 1, 1, 0]
y_pred = [0, 0, 1, 1, 0, 0, 1, 1]
tp = np.sum((np.array(y_true) == 1) & (np.array(y_pred) == 1))
tn = np.sum((np.array(y_true) == 0) & (np.array(y_pred) == 0))
fp = np.sum((np.array(y_true) == 0) & (np.array(y_pred) == 1))
fn = np.sum((np.array(y_true) == 1) & (np.array(y_pred) == 0))
confusion_matrix1 = np.array([[tp, fp],
[fn, tn]])
print(confusion_matrix1)
# array([[3, 1],
# [2, 2]])
# 也可以使用 scikit-learn 的 metrics 套件所提供的 confusion_matrix() 函数来制作,
# 但要注意两种方法在混淆矩阵元素的配置有所不同。
confusion_matrix2 = confusion_matrix(y_true, y_pred)
print(confusion_matrix2)
# array([[2, 1], tn, fp
# [2, 3]]) fn, tp 先负例再正例
2.2 准确率(accuracy)和错误率(error rate)
准确率表示预测正确的数据占所有数据的比率,错误率表示预测错误的数据占所有数据的比率。
a
c
c
=
T
P
+
T
N
T
P
+
T
N
+
F
P
+
F
N
acc = \frac{TP+TN}{TP+TN+FP+FN}
acc=TP+TN+FP+FNTP+TN
e
r
r
=
1
−
a
c
c
err = 1-acc
err=1−acc
注意:
- 在数据不平衡(标签类别比例不一致)时,较难以此评价指标来评价模型的性能。
这是因为,当我们预测数据为正例或者是负例时,会先预测各个数据属于正例的概率,再判定比阈值概率大的为正例,小的为负例。使用accuracy的话,我们一般会假定阈值为50%。因此,accuracy的判断能力仅止于判断某个数据的正例概率在50%以上或者以下。
例子:
from sklearn.metrics import accuracy_score
# 使用 0 和 1 来表示二元分类的正例与负例
y_true = [1, 0, 1, 1, 0, 1, 1, 0]
y_pred = [0, 0, 1, 1, 0, 0, 1, 1]
accuracy = accuracy_score(y_true, y_pred)
print(accuracy)
# 0.625
2.3 精确率(precision)和召回率(recall)
精确率指的是预测结果为正例的资料中,实际值也为正例的概率。
p
r
e
c
i
s
i
o
n
=
T
P
T
P
+
F
P
precision=\frac{TP}{TP+FP}
precision=TP+FPTP
召回率指的是实际值为正例的资料中,有多少数据被预测为正例。
r
e
c
a
l
l
=
T
P
T
P
+
F
N
recall=\frac{TP}{TP+FN}
recall=TP+FNTP
注意:
- 精确率(precision)和召回率(recall)是相互权衡的关系,也就是说,若有一方的数值较高,则另一方的数值则会较低,若完全忽略其中一种数值,则另一种就会得到接近1的结果。比如:模型中的所有资料都预测成正例,则会有很高的recall,等于1,但是precision会很低。
例子:
from sklearn.metrics import precision_score,recall_score
# 使用 0 和 1 来表示二元分类的正例与负例
y_true = [1, 0, 1, 1, 0, 1, 1, 0]
y_pred = [0, 0, 1, 1, 0, 0, 1, 1]
precision = precision_score(y_true,y_pred)
recall = recall_score(y_true,y_pred)
print(precision)
# 0.75
print(recall)
# 0.6
2.4 F1-score和Fβ-score
F1-score使用的是precision和recall的调和平均。Fβ-score指的是在F1-score的基础上,使用系数
β
\beta
β来调整recall的权重。
F
1
=
2
1
r
e
c
a
l
l
+
1
p
r
e
c
i
s
i
o
n
=
2
⋅
r
e
c
a
l
l
⋅
p
r
e
c
i
s
i
o
n
r
e
c
a
l
l
+
p
r
e
c
i
s
i
o
n
=
2
T
P
2
T
P
+
F
P
+
F
N
F_1=\frac{2}{\frac{1}{recall}+\frac{1}{precision}}=\frac{2·recall·precision}{recall+precision}=\frac{2TP}{2TP+FP+FN}
F1=recall1+precision12=recall+precision2⋅recall⋅precision=2TP+FP+FN2TP
F
β
=
(
1
+
β
)
2
β
2
r
e
c
a
l
l
+
1
p
r
e
c
i
s
i
o
n
=
(
1
+
β
)
2
)
⋅
r
e
c
a
l
l
⋅
p
r
e
c
i
s
i
o
n
r
e
c
a
l
l
+
β
2
p
r
e
c
i
s
i
o
n
F_{\beta}=\frac{(1+\beta)^2}{\frac{\beta^2}{recall}+\frac{1}{precision}}=\frac{(1+\beta)^2)·recall·precision}{recall+\beta^2precision}
Fβ=recallβ2+precision1(1+β)2=recall+β2precision(1+β)2)⋅recall⋅precision
注意:
- 从F1-score的公式中其分子只有TP可以得知,这个指标处理正例与负例并不对称。因此,将实际值与预测值的正例和负例对调时,分数、结果都会不一样。
例子:
from sklearn.metrics import f1_score,fbeta_score
# 使用 0 和 1 来表示二元分类的正例与负例
y_true = [1, 0, 1, 1, 0, 1, 1, 0]
y_pred = [0, 0, 1, 1, 0, 0, 1, 1]
f1 = f1_score(y_true,y_pred)
print(f1)
f_beta = fbeta_score(y_true,y_pred,beta=1)
print(f_beta)
#0.6666666666666665
#0.6666666666666665
2.5 MCC
虽然使用MCC作为评价指标并不常见,但是这个指标可以很容易的评估模型处理不均衡资料的性能。
M
C
C
=
T
P
×
T
N
−
F
P
×
F
N
(
T
P
+
F
P
)
(
T
P
+
F
N
)
(
T
N
+
F
P
)
(
T
N
+
F
N
)
MCC=\frac{TP×TN-FP×FN}{\sqrt{(TP+FP)(TP+FN)(TN+FP)(TN+FN)}}
MCC=(TP+FP)(TP+FN)(TN+FP)(TN+FN)TP×TN−FP×FN
注意:
- 该指标的结果介于-1到+1之间,当结果为+1时,表示预测准确;若结果为0时,则表示模型的预测能力如同随机预测;若结果为-1时,则表示预测值与实际值完全相反。
- 这个指标以对称的方式处理正例和负例,因此,就算将实际值和预测值的正例和负例对调也会得到相同的分数。
例子:
from sklearn.metrics import matthews_corrcoef
# 使用 0 和 1 来表示二元分类的正例与负例
y_true = [1, 0, 1, 1, 0, 1, 1, 0]
y_pred = [0, 0, 1, 1, 0, 0, 1, 1]
mcc = matthews_corrcoef(y_true,y_pred)
print(mcc)
# 0.2581988897471611
3.二元分类任务的评价指标——概率预测
3.1 交叉熵(logloss)
logloss是分类任务中代表性的评价指标。
l
o
g
l
o
s
s
=
−
1
N
∑
i
=
1
N
(
y
i
l
o
g
p
i
+
(
1
−
y
i
l
o
g
(
1
−
p
i
)
)
)
=
−
1
N
∑
i
=
1
N
l
o
g
p
i
′
logloss=-\frac{1}{N}\sum_{i=1}^{N}(y_ilogp_i+(1-y_ilog(1-p_i)))=-\frac{1}{N}\sum_{i=1}^{N}logp_{i}^{'}
logloss=−N1i=1∑N(yilogpi+(1−yilog(1−pi)))=−N1i=1∑Nlogpi′
其中,
y
i
y_i
yi代表第
i
i
i个资料是否为正例;
p
i
p_i
pi代表模型预测第
i
i
i个资料为正例的概率;
p
i
′
p_{i}^{'}
pi′代表模型预测第
i
i
i个资料为实际值的概率。
注意:
- logloss越低表示指标数值越好
- 当模型正确预测出实际值时,logloss的值会最小
例子:
from sklearn.metrics import log_loss
# 以 0 和 1 表示二元分類的負例和正例
y_true = [1, 0, 1, 1, 0, 1]
y_prob = [0.1, 0.2, 0.8, 0.8, 0.1, 0.3]
logloss = log_loss(y_true, y_prob)
print(logloss)
# 0.7135581778200728
3.2 AUC
我们会依据ROC曲线来计算AUC,AUC就是ROC曲线的面积。ROC曲线的X轴为伪阳率(FPR),Y轴为真阳率(TPR)。
- 伪阳率:错将负例预测为正例占所有负例的比率
F P R = F P F P + T N FPR=\frac{FP}{FP+TN} FPR=FP+TNFP - 真阳率:正确预测正例占所有正例的比率
T P R = T P T P + F N TPR=\frac{TP}{TP+FN} TPR=TP+FNTP
ROC曲线的绘制可以理解为:将模型预测每一个资料为正例的概率(阈值),由大至小排列,计算在不同阈值下,FPR和TPR的结果,用坐标点(FPR,TPR)来绘制图形
注意:
- 当预测值反转(正例与负例互换),AUC就是1-原始AUC
- AUC只会受资料预测值彼此之间大小关系的影响
- 当资料的正例非常少,属于不平衡资料时,正例能否有较高的预测概率对ACU有很大影响
例子:
from sklearn.metrics import roc_auc_score
# 以 0 和 1 表示二元分類的負例和正例
y_true = [1, 0, 1, 1, 0, 1]
y_prob = [0.1, 0.2, 0.8, 0.8, 0.1, 0.3]
auc = roc_auc_score(y_true,y_prob)
print(auc)
# 0.8125
4.多元分类任务的评价指标
多元分类任务的评价指标大多有二元分类任务延伸而来。
4.1 multi-class accuracy
将二元分类的accuracy多加几类就成为multi-class accuracy指标,也同样使用sklearn的metrics所提供的accuracy_score()函数来计算。
4.2 multi-class logloss
首先产生各类别的预测概率,接着求得资料所属类别的预测概率的对数并反转符号,最后加总取平均所得的结果就是此评价指标的分数。
m
u
l
t
i
−
c
l
a
s
s
l
o
g
l
o
s
s
=
1
N
∑
i
=
1
N
∑
m
=
1
M
y
i
,
m
l
o
g
p
i
,
m
multi-class logloss=\frac{1}{N}\sum_{i=1}^{N}\sum_{m=1}^{M}y_{i,m}logp_{i,m}
multi−classlogloss=N1i=1∑Nm=1∑Myi,mlogpi,m
其中,
M
M
M表示类别数;
y
i
,
m
y_{i,m}
yi,m表示资料是否属于
m
m
m类别;
p
i
,
m
p_{i,m}
pi,m表示资料属于
m
m
m的预测概率。
例子:
from sklearn.metrics import log_loss
# 3 类别分类实际值与预测值
y_true = np.array([0, 2, 1, 2, 2])
y_pred = np.array([[0.68, 0.32, 0.00],
[0.00, 0.00, 1.00],
[0.60, 0.40, 0.00],
[0.00, 0.00, 1.00],
[0.28, 0.12, 0.60]])
logloss = log_loss(y_true, y_pred)
print(logloss)
# 0.3625557672904274
4.3 mean-F1、macro-F1和micro-F1
将F1-score进行各种延伸运算后,就成为多分类的评价指标,比如:mean-F1、macro-F1和micro-F1。这类指标主要使用与多分类任务中的多标签分类任务。
以一个资料为单位来计算F1-score,其平均值就是mean-F1。
例子:
from sklearn.metrics import f1_score
# 在计算多标签分类的评价指标时,将实际值与预测值以k-hot 编码的形式来表示
# 实际值:[[1,2], [1], [1,2,3], [2,3], [3]]
y_true = np.array([[1, 1, 0],
[1, 0, 0],
[1, 1, 1],
[0, 1, 1],
[0, 0, 1]])
# 预测值:[[1,3], [2], [1,3], [3], [3]]
y_pred = np.array([[1, 0, 1],
[0, 1, 0],
[1, 0, 1],
[0, 0, 1],
[0, 0, 1]])
# 计算 mean-f1评价指标时,先以资料为单位计算 F1-score,再取其平均
mean_f1 = np.mean([f1_score(y_true[i, :], y_pred[i, :]) for i in range(len(y_true))])
print(mean_f1)
# 0.5933333333333334
以一种类别为单位来计算F1-score,其平均值就是macro-F1评价指标的分数。
例子:
from sklearn.metrics import f1_score
# 在计算多标签分类的评价指标时,将实际值与预测值以k-hot 编码的形式来表示
# 实际值:[[1,2], [1], [1,2,3], [2,3], [3]]
y_true = np.array([[1, 1, 0],
[1, 0, 0],
[1, 1, 1],
[0, 1, 1],
[0, 0, 1]])
# 预测值:[[1,3], [2], [1,3], [3], [3]]
y_pred = np.array([[1, 0, 1],
[0, 1, 0],
[1, 0, 1],
[0, 0, 1],
[0, 0, 1]])
# 计算 macro-f 评价指标时,先以分类为单位计算 F1-score,再取其平均
n_class = 3
macro_f1 = np.mean([f1_score(y_true[:, c], y_pred[:, c]) for c in range(n_class)])
print(macro_f1)
# 0.5523809523809523
将资料×类别配对后,归纳出TP、TN、FP、FN的结果,再以此混淆阵列所计算出的F值就是micro-F1的评价指标分数。
例子:
from sklearn.metrics import f1_score
# 在计算多标签分类的评价指标时,将实际值与预测值以k-hot 编码的形式来表示
# 实际值:[[1,2], [1], [1,2,3], [2,3], [3]]
y_true = np.array([[1, 1, 0],
[1, 0, 0],
[1, 1, 1],
[0, 1, 1],
[0, 0, 1]])
# 预测值:[[1,3], [2], [1,3], [3], [3]]
y_pred = np.array([[1, 0, 1],
[0, 1, 0],
[1, 0, 1],
[0, 0, 1],
[0, 0, 1]])
# 将资料×类别配对后,归纳出TP、TN、FP、FN的结果,再以此混淆阵列所计算出的F值就是micro-F1的评价指标分数。
micro_f1 = f1_score(y_true.reshape(-1), y_pred.reshape(-1))
print(micro_f1)
# 0.6250000000000001
上述部分也有如下写法,在 f1_score 函式中加上 average 参数来计算。
# 也可以直接在 f1_score 函式中加上 average 参数来计算
mean_f1 = f1_score(y_true, y_pred, average='samples')
macro_f1 = f1_score(y_true, y_pred, average='macro')
micro_f1 = f1_score(y_true, y_pred, average='micro')
4.4 quadratic weighted kappa
quadratic weighted kappa为多分类的评价指标,主要在各类别之间有次序关系时使用(如使用数字1-5表示电影排名),提交的预测为每个资料属于哪个类别。
k
=
1
−
∑
i
,
j
w
i
,
j
O
i
,
j
∑
i
,
j
w
i
,
j
E
i
,
j
k=1-\frac{\sum_{i,j}w_{i,j}O_{i,j}}{\sum_{i,j}w_{i,j}E_{i,j}}
k=1−∑i,jwi,jEi,j∑i,jwi,jOi,j
其中,
- O i , j O_{i,j} Oi,j是一个多分类的混淆矩阵
- E i , j E_{i,j} Ei,j是混淆矩阵的期望值
- w i , j w_{i,j} wi,j为实际值与预测值之差的平方 ( i − j ) 2 (i-j)^2 (i−j)2
例子:
from sklearn.metrics import confusion_matrix, cohen_kappa_score
# 建立用来计算quadratic weighted kappa 的函数
def quadratic_weighted_kappa(c_matrix):
numer = 0.0
denom = 0.0
for i in range(c_matrix.shape[0]):
for j in range(c_matrix.shape[1]):
n = c_matrix.shape[0]
wij = ((i - j) ** 2.0)
oij = c_matrix[i, j]
eij = c_matrix[i, :].sum() * c_matrix[:, j].sum() / c_matrix.sum()
numer += wij * oij
denom += wij * eij
return 1.0 - numer / denom
# y_true 实际类别的list list、y_pred 为预测值的list
y_true = [1, 2, 3, 4, 3]
y_pred = [2, 2, 4, 4, 5]
# 计算多分类的混淆矩阵
c_matrix = confusion_matrix(y_true, y_pred, labels=[1, 2, 3, 4, 5])
print(c_matrix)
# 计算 quadratic weighted kappa
kappa = quadratic_weighted_kappa(c_matrix)
print(kappa)
# 0.6153846153846154
# 也也可以调用函数cohen_kappa_score来直接计算 quadratic weighted kappa,不用先算混淆矩阵
kappa = cohen_kappa_score(y_true, y_pred, weights='quadratic')
print(kappa)
# 0.6153846153846154
4.5 推荐任务的评价指标:MAP@K
当资料属于一个或者多个分类时,将预测到最有可能隶属的分类依可能性高低排列,最后将前K个可能性较高的类别作为预测值。K可以是5或者10等任何一个数值。
MAP@K的计算公式如下:
M
A
P
@
K
=
1
N
∑
i
=
1
N
(
1
m
i
n
(
m
i
,
k
)
∑
k
=
1
K
P
i
(
k
)
)
MAP@K=\frac{1}{N}\sum_{i=1}^{N}(\frac{1}{min(m_i,k)}\sum_{k=1}^{K}P_i(k))
MAP@K=N1i=1∑N(min(mi,k)1k=1∑KPi(k))
- m i m_i mi是资料 i i i实际值所隶属的类别个数
- P i ( k ) P_i(k) Pi(k)为精确率。此精确率是由资料 i i i隶属于第1到第k个类别的预测结果计算而来。
一般来说,每个资料所作出的预测值的数量会等于K。另外,当K个预测值内含有的正解相同时,预测到的正解名词越后面,分数会越低。
例子:
# MAP@K
# K=3,资料数为5个,总类别为4个
K = 3
# 每个资料的实际值
y_true = [[1, 2], [1, 2], [4], [1, 2, 3, 4], [3, 4]]
# 每个资料的预测值。
y_pred = [[1, 2, 4], [4, 1, 2], [1, 4, 3], [1, 2, 3], [1, 2, 4]]
# 建立函数计算每个数据的平均精确率
def apk(y_i_true, y_i_pred):
# y_pred的长度必须在K 以下,且元素不能重叠
assert (len(y_i_pred) <= K)
assert (len(np.unique(y_i_pred)) == len(y_i_pred))
sum_precision = 0.0
num_hits = 0.0
for i, p in enumerate(y_i_pred):
if p in y_i_true:
num_hits += 1
precision = num_hits / (i + 1)
sum_precision += precision
return sum_precision / min(len(y_i_true), K)
# 建立计算 MAP@K的函数
def mapk(y_true, y_pred):
return np.mean([apk(y_i_true, y_i_pred) for y_i_true, y_i_pred in zip(y_true, y_pred)])
# 求得 MAP@K
print(mapk(y_true, y_pred))
# 0.65
# 预测值的排名也会影响分数
print(apk(y_true[0], y_pred[0]))
print(apk(y_true[1], y_pred[1]))
# 1.0, 0.5833
四、评价指标与目标函数
1.评价指标与目标函数的差异
目标函数指的是在训练模型时能帮助求得最佳结果的函数。为了让训练能够顺利进行,会要求目标函数必须可被微分。评价指标只要能够由实际值与预测值计算出来就行,并没有其他的限制。若竞赛指定的评价指标与使用于训练模型的目标函数一致,我们可以很清楚地知道该模型可以针对评价指标提交最合适的预测值。但若两者不一致,就需要用其他方法进行辅助。
2.自定义评价指标与目标函数
当套件中未提供自己想使用的评价指标或目标函数时,我们可以自行定义。
例子:
import numpy as np
import pandas as pd
# 读取数据
train = pd.read_csv('../input/sample-data/train_preprocessed.csv')
train_x = train.drop(['target'], axis=1)
train_y = train['target']
test_x = pd.read_csv('../input/sample-data/test_preprocessed.csv')
from sklearn.model_selection import KFold
kf = KFold(n_splits=4, shuffle=True, random_state=71)
tr_idx, va_idx = list(kf.split(train_x))[0]
# 分割为训练集与验证集
tr_x, va_x = train_x.iloc[tr_idx], train_x.iloc[va_idx]
tr_y, va_y = train_y.iloc[tr_idx], train_y.iloc[va_idx]
# -----------------------------------
# 在xgboost中使用自己定义评价指标以及目标函数
# (参考)https://github.com/dmlc/xgboost/blob/master/demo/guide-python/custom_objective.py
# -----------------------------------
import xgboost as xgb
from sklearn.metrics import log_loss
# 通过xgb.Dmatrix() 可將特征与标签转换成适合于 xgboost 模型的资料结构。
dtrain = xgb.DMatrix(tr_x, label=tr_y)
dvalid = xgb.DMatrix(va_x, label=va_y)
# 自定义目标函数
def logregobj(preds, dtrain):
labels = dtrain.get_label() # 取得实际值标签
# 使用自定义目标函数的模型在进行预测值所输出的值并非如同目标函数的输出,需要Sigmoid 函数进行转换
preds = 1.0 / (1.0 + np.exp(-preds)) # Sigmoid 函数
grad = preds - labels # 斜率
hess = preds * (1.0 - preds) # 二阶导数值
return grad, hess
# 自定义评价指标(错误率)
def evalerror(preds, dtrain):
labels = dtrain.get_label() # 取得实际值标签
return 'custom-error', float(sum(labels != (preds > 0.0))) / len(labels)
# 设定超参数
params = {'silent': 1, 'random_state': 71}
num_round = 50
watchlist = [(dtrain, 'train'), (dvalid, 'eval')]
# 开始对模型进行训练
bst = xgb.train(params, dtrain, num_round, watchlist, obj=logregobj, feval=evalerror)
pred_val = bst.predict(dvalid)
pred = 1.0 / (1.0 + np.exp(-pred_val))
logloss = log_loss(va_y, pred)
print(logloss)
# 0.22561101998374797
# 使用一般训练方法时使用,指定 binary:logistic 为目标函数
params = {'silent': 1, 'random_state': 71, 'objective': 'binary:logistic'}
bst = xgb.train(params, dtrain, num_round, watchlist)
pred = bst.predict(dvalid)
logloss = log_loss(va_y, pred)
print(logloss)
# 0.22226432055911283
五、评价指标的最佳化
1.最佳化评价指标的方法
- 直接最佳化模型
若评价指标为RMSE
或logloss
,可以将模型的目标函数指定为与评价指标一致。 - 配合不同评价指标做训练资料的预处理
若当评价指标为RMSLE
时,可以先将训练资料做对数转换,将目标函数设定为RMSE
来训练,最后再使用指数函数还原。 - 针对不同的评价指标预测值进行后处理
这种方法指的是在模型训练完成后,依据评价指标的性质,对输出的预测值另行处理或是使用最佳化演算法来求得最佳化阈值。 - 使用自定义目标函数
若比赛的评价指标为MAE,由于MAE不能做目标函数,因此,需要考虑使用与MAE相似的函数做目标函数,比如:Fair函数与Psuedo-Huber函数 - 使用早停(Early Stopping)来最佳化不同的评价指标
Early Stopping
可以让模型在评价指标达到最佳化时就停止学习。
2.最佳化阈值
若评价指标所评估的预测值为标签而非概率时,一般来说,会由模型生成预测概率后,将大于一定阈值的概率值设定为正例。
在使用F1-score的情况下,阈值会影响正例的比例,所以我们必须找出最佳化阈值来让F1-score分数最高。
- 尝试所有可能的阈值
以0.01为单位尝试所有可能的阈值,从而找到使得F1-score得到最高分的阈值 - 使用最佳化演算法
利用scipy.optimize
套件所提供的一些最佳化演算法来找出最佳的阈值,下面尝试使用最佳演算法Nelder-Mead
,使用此方法时目标函数(在最佳化算法中作为最佳化对象的函数)不一定要可以被微分。
例子:import numpy as np import pandas as pd from sklearn.metrics import f1_score from scipy.optimize import minimize # 生成 10000个样本资料 (概率值) rand = np.random.RandomState(seed=71) train_y_prob = np.linspace(0, 1.0, 10000) # 随机产生 10000 个资料,每一个都和 train_y_prob 的概率值比较,小于是负例、大于则为正例,以此作为实际值的标签 train_y = pd.Series(rand.uniform(0.0, 1.0, train_y_prob.size) < train_y_prob) # 从 train_y_prob 的概率值生成常态分布的随机数列,并控制数列范围在 0 和 1 之间,以此作为输出的概率值 train_pred_prob = np.clip(train_y_prob * np.exp(rand.standard_normal(train_y_prob.shape) * 0.3), 0.0, 1.0) # 当初始阈值为 0.5 时,F1 为 0.722 init_threshold = 0.5 init_score = f1_score(train_y, train_pred_prob >= init_threshold) print(init_threshold, init_score) # 0.5 0.7224831529507862 # 建立想要进行最佳化的目标函数 def f1_opt(x): return -f1_score(train_y, train_pred_prob >= x) # 在 scipy.optimize 套件提供的 minimize() 中指定 'Nelder-Mead' 演算法來求得最佳阈值 # 在最佳阈值下计算 F1、求得 0.756 result = minimize(f1_opt, x0=np.array([0.5]), method='Nelder-Mead') best_threshold = result['x'].item() best_score = f1_score(train_y, train_pred_prob >= best_threshold) print(best_threshold, best_score) # 0.32324218749999983 0.7557317703844165
3.是否使用out-of-fold来最佳化阈值?
上面我们使用了所有训练资料的实际值与预测概率来寻找最佳F1-score的阈值。但这样很容易过拟合,因此,考虑是否在最佳化阈值时,使用out-of-fold来交叉验证。
例子:
import numpy as np
import pandas as pd
from scipy.optimize import minimize
from sklearn.metrics import f1_score
from sklearn.model_selection import KFold
# 产生样本资料
rand = np.random.RandomState(seed=71)
train_y_prob = np.linspace(0, 1.0, 10000)
# 实际值和预测值分別为 train_y, train_pred_prob
train_y = pd.Series(rand.uniform(0.0, 1.0, train_y_prob.size) < train_y_prob)
train_pred_prob = np.clip(train_y_prob * np.exp(rand.standard_normal(train_y_prob.shape) * 0.3), 0.0, 1.0)
# 在交叉验证范围内求得阈值
thresholds = []
scores_tr = []
scores_va = []
kf = KFold(n_splits=4, random_state=71, shuffle=True)
for i, (tr_idx, va_idx) in enumerate(kf.split(train_pred_prob)):
tr_pred_prob, va_pred_prob = train_pred_prob[tr_idx], train_pred_prob[va_idx]
tr_y, va_y = train_y.iloc[tr_idx], train_y.iloc[va_idx]
# 设定最佳化目标函数
def f1_opt(x):
return -f1_score(tr_y, tr_pred_prob >= x)
# 在训练资料中进行阈值的最佳化,使用验证资料來进行评价
result = minimize(f1_opt, x0=np.array([0.5]), method='Nelder-Mead')
threshold = result['x'].item()
score_tr = f1_score(tr_y, tr_pred_prob >= threshold)
score_va = f1_score(va_y, va_pred_prob >= threshold)
print(threshold, score_tr, score_va)
thresholds.append(threshold)
scores_tr.append(score_tr)
scores_va.append(score_va)
# 將每个 fold 的最佳化阈值平均
threshold_test = np.mean(thresholds)
print(threshold_test)
# 0.33166503906249983
4.针对预测概率的调整
若模型有下列情况时,输出的预测概率可能会有所偏差
- 资料不够完整
- 在资料特别稀少时,会难以预测出接近0或者1的概率
- 模型的训练方法没有针对预测概率进行最佳化
以下为调整预测概率的方法:
- 取预测值的n次方(n为0.9~1.1)
- 防止接近极端值0或1的概率
- stacking(堆叠)
- Calibrated Classifier CV
参考:
- 《kaggle竞赛攻顶秘笈》