推荐系统之评估方法和评价指标PR、ROC、AUC

2020-11-16  本文已影响0人  HaloZhang

简介

推荐系统的评估相关的知识比重在整个推荐系统的知识框架中占比不大,但是其重要程度不言而喻,因为采用的评价指标直接影响到了推荐系统的优化方向是否正确。评价指标主要用于评价推荐系统各方面的性能,按照应用场景可以分为离线评估和线上测试。其中离线评估的主要方法包括Holdout检验、交叉检验、留一验证、自助法等,评价指标主要包括用户满意度、预测准确度、召回率、覆盖率、多样性、新颖性、流行度、均方根误差、对数损失、P-R曲线、AUC、ROC曲线等等。线上测试的评估方法主要包括A/B测试、Interleaving方法等,评价指标主要包括点击率、转化率、留存率、平均点击个数等等。本文将着重介绍离线评估相关方法和指标,尤其是P-R曲线、AUC、ROC曲线等,这些评价指标是最常用的也是最基本的,出现在各类推荐相关的论文中,因此需要重点掌握。


离线评估方法和评价指标

在推荐系统的评估过程中,离线评估往往被当做最常用也是最基本的评估方法。顾名思义,离线评估是指在将模型部署于线上环境之前,在离线环境中进行的评估。由于不用部署到生产环境,离线评估没有线上部署的工程风险,也无须浪费宝贵的线上流量资源,而且具有测试时间短,同时进行多组并行测试、能够利用丰富的线下计算资源等诸多优点。

离线评估的主要方法

import numpy as np
from sklearn.model_selection import train_test_split

x,y = np.arange(10).reshape((5,2)), range(5)
print("data: \n", x)
print("labels: ", list(y))

# 对数据集进行划分,设置测试集占比30%,训练集占比70%
X_train, X_test,Y_train,Y_test = train_test_split(x, y, test_size=0.3, random_state=100)
print("Train Data: ", X_train, Y_train)
print("Test Data: ", X_test, Y_test)
输出:

Holdout检验的缺点也很明显,即在验证集上计算出来的评估指标与训练集合验证集的划分有直接关系,如果仅仅进行少量Holdout检验,则得到的结论存在较大的随机性。为了消除这种随机性,“交叉检验”的思想被提出。

from sklearn.model_selection import KFold

X = np.array([[1, 2], [3, 4], [1, 2], [3, 4]])
y = np.array([1, 2, 3, 4])

kf = KFold(n_splits=4)
kf.get_n_splits(X)

for train_index, test_index in kf.split(X):
    print("TRAIN:", train_index, "TEST:", test_index)
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]

结果:


  留一验证:每次留下1个样本作为验证集,其余所有样本作为测试集。样本总数为n,依次遍历所有n个样本,进行n次验证,在将评估指标求平均得到最终指标,在样本总数较多的情况下,留一验证法的时间开销极大。事实上,留一验证是留p验证的特例。留p验证是指每次留下p个样本作为验证集,而从n个元素中选取p个元素共有C_n^p种可能,因此它的时间开销远超留一验证,故很少在实际工程中使用。
同样,scikit-learn中提供了LeaveOneOut方法可使用,例子如下:
import numpy as np

from sklearn.model_selection import LeaveOneOut
X = np.array([[1, 2], [3, 4], [5,6]])
y = np.array([1, 2, 3])

loo = LeaveOneOut()
loo.get_n_splits(X)

for train_index, test_index in loo.split(X):
    print("TRAIN:", train_index, "TEST:", test_index)
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]
    print(X_train, X_test, y_train, y_test)
结果:

给定包含N个样本的数据集T,有放回的采样N次,得到采样集T_s。数据集T中样本可能在T_s中出现多次,也可能不出现在T_s。一个样本始终不在采样集中出现的概率是lim_{N \to \infty} (1 - \frac {1}{N})^N = \frac {1}{e} = 0.368,因此T中约有63.2\%的样本出现在了T_s中。将T_s用作训练集,T - T_s用作测试集。

离线评估的指标

LogLoss就是逻辑回归的损失函数,而大量深度学习模型的输出层正式逻辑回归或者Softmax,因此采用LogLoss作为评估指标能够非常直观地反应模型损失函数的变化。

注意准确率(Accuracy)和精准率(Precision)的区别。

精准率和召回率是矛盾统一的两个指标:为了提高精准率,分类器需要尽量在“更有把握时”才把样本预测为正样本,即降低了精准率计算公式中的分母部分。但往往会因为过于保守而漏掉很多“没有把握”的正样本,导致召回率过低。
以挑选西瓜为例,若希望将好瓜尽可能多地挑选出来,则可通过增加选瓜的数量来实现,如果将所有的西瓜都选上,那么所有的好瓜也必然都被选上了,这样就会导致Precision很低,但是Recall就会相对较高。若希望选出的瓜中好瓜比例尽可能高,则可只挑选最有把握的瓜,但这样就难免会漏掉不少好瓜,使得Recall较低。
为了综合反映Precision和Recall的结果,可以使用F1-score,F1-score是精准率和召回率调和平均值,定义如下:
F1 = \frac {2 \cdot Precison \cdot Recall}{ Precision + Recall }
用一张图总结一下:

上图左边是混淆矩阵,右边分别是精准率、召回率、F1-score、准确率的计算公式。

关于混淆矩阵,在下一节有详细介绍。

对上述概念进行组合,就产生了如下的混淆矩阵: 混淆矩阵

然后,由此引出True Positive Rate(真阳率TPR)、False Positive Rate(伪阳率FPR)两个概念,计算方式如下:

仔细观察上面的两个式子,发现两个式子的分子其实对应了混淆矩阵的第二行,即预测类别为1的那一行。另外可以发现TPR就是用TP除以TP所在的列,FPR就是用FP除以FP所在的列。二者的含义如下:

如果我们计算出了TPR和FPR,那么ROC曲线的绘制就很简单了,ROC曲线的横轴是FPR、纵轴是TPR,当二者相等时,绘制出的曲线是一条直线,如下:

表示的意义是:对于不论真实类别是0还是1的样本,模型预测样本为1的概率都是相等的。
换句话说,模型对正例和负例毫无区分能力,做决策和抛硬币没啥区别。因此,我们认为AUC的最小值为0.5(当然也存在预测相反这种极端的情况,AUC小于0.5,这种情况相当于分类器总是把对的说成错的,错的认为是对的,那么只要把预测类别取反,便得到了一个AUC大于0.5的分类器)。

而我们希望模型达到的效果是:对于真实类别为1的样本,模型预测为1的概率(即TPR),要大于真实类别为0而预测类别为1的概率(即FPR),即y>x,因此大部分的ROC曲线长成下面这个样子: 最理想的情况下,既没有真实类别为1而错分为0的样本——TPR一直为1,也没有真实类别为0而错分为1的样本——FPR一直为0,AUC为1,这便是AUC的极大值。
下面举一个小例子,以分类问题为例,预测类别为离散标签,假设8个样本的预测情况如下: 得到的混淆矩阵如下: 进而计算得到TPR=3/4,FPR=2/4,得到ROC曲线: 可以看到这实际上式两段直线组成的曲线,是因为我们只画出了一个关键点。
如果对于CTR任务,预测的结果是一个概率值,那应该如何画出ROC曲线呢?比如预测结果如下: 这时,需要设置阈值来得到混淆矩阵,不同的阈值会影响得到的TPR,FPR。如果阈值取0.5,小于0.5的为0,否则为1,那么我们就得到了与之前一样的混淆矩阵。其他的阈值就不再啰嗦了。依次使用所有预测值作为阈值,得到一系列TPR,FPR,然后画出关键点,再连线即可得到ROC曲线。
因此,ROC曲线跟P-R曲线一样,也是通过不断地移动模型正样本阈值来生成的。
推荐序列 N=1 N=2 N=3 N=4 N=5 N=6
真实标签 1 0 0 1 1 1

其中,1代表正样本,0代表负样本。我们来计算下它们的Precision。如下表所示:

推荐序列 N=1 N=2 N=3 N=4 N=5 N=6
真实标签 1 0 0 1 1 1
Precision@N 1/1 1/2 1/3 2/4 3/5 4/6

AP的计算只取正样本处的Precision进行平均,即AP = (1/1+2/4+3/5+4/6)/4=0.6917。如果推荐系统对测试集中每个用户都进行样本排序,那么每个用户都会计算出一个AP值,再对所有用户的AP值进行平均,就得到了mAP。也就是说,mAP是对精确度平均的平均。
值得注意的是,mAP的计算方法和P-R曲线、ROC曲线的计算方式完全不同,因为mAP需要对每个用户的样本进行分用户排序,而P-R曲线和ROC曲线均是对全量测试样本进行排序。

实例

下面以一个经典的莺尾花分类的例子来展示各种指标的计算。
导入莺尾花数据,使用Holdout检验,将数据集随机划分成训练集和测试集:

from sklearn import svm, datasets
from sklearn.model_selection import train_test_split
import numpy as np

iris = datasets.load_iris()
X = iris.data
y = iris.target

# Add noisy features
random_state = np.random.RandomState(0)
n_samples, n_features = X.shape
X = np.c_[X, random_state.randn(n_samples, 200 * n_features)]

# Limit to the two first classes, and split into training and test
X_train, X_test, y_train, y_test = train_test_split(X[y < 2], y[y < 2],
                                                    test_size=.5,
                                                    random_state=random_state)

创建一个线性SVM分类器,计算测试数据到决策平面的距离以及对测试数据进行预测:

# Create a simple classifier
classifier = svm.LinearSVC(random_state=random_state)
classifier.fit(X_train, y_train)
y_score = classifier.decision_function(X_test)
y_predict = classifier.predict(X_test)

计算准确率:

from sklearn.metrics import accuracy_score
accuracy = accuracy_score(y_test, y_predict)
print("Accuracy: ", accuracy)
准确率

计算精准率:

from sklearn.metrics import precision_score
precision = precision_score(y_test, y_predict)
print("Precision: ", precision)
精准率

计算召回率:

from sklearn.metrics import recall_score
recall = recall_score(y_test, y_predict)
print("Recall: ", recall)
Recall

计算F1-Score:

from sklearn.metrics import f1_score
F1_score = f1_score(y_test, y_predict)
print("F1-score: ", F1_score)
F1 Score

计算精确率均值AP:

from sklearn.metrics import average_precision_score
average_precision = average_precision_score(y_test, y_score)
print('Average precision: {0:0.2f}'.format(average_precision))
Average Precision

计算混淆矩阵:

from sklearn.metrics import confusion_matrix
confusion_matrix = confusion_matrix(y_test, y_predict)
print("Confusion Matrix: \n", confusion_matrix)
Confusion Matrix

绘制P-R曲线,并且计算AUC:

from sklearn.metrics import precision_recall_curve, auc
from sklearn.metrics import plot_precision_recall_curve
import matplotlib.pyplot as plt

disp = plot_precision_recall_curve(classifier, X_test, y_test)
disp.ax_.set_title('P-R Example')

precision, recall, _thresholds = precision_recall_curve(y_test, y_predict)
auc = auc(recall, precision)
print("AUC: ", auc)
P-R曲线 AUC

绘制ROC曲线并且计算AUC:

from sklearn.metrics import roc_auc_score, auc, roc_curve
import matplotlib.pyplot as plt

fpr, tpr, thresholds = roc_curve(y_test, y_score)
roc_auc = auc(fpr, tpr)  #auc为Roc曲线下的面积

#开始画ROC曲线
plt.plot(fpr, tpr, 'b',label='AUC = %0.2f'% roc_auc)
plt.legend(loc='lower right')
plt.plot([0,1],[0,1],'r--')
plt.xlim([-0.1,1.1])
plt.ylim([-0.1,1.1])
plt.xlabel('FPR') #横坐标是fpr
plt.ylabel('TPR')  #纵坐标是tpr
plt.title('ROC Example')
plt.show()
ROC曲线

A/B测试与线上评估指标

无论离线评估如何仿真线上环境,终究无法完全还原线上的所有变量。对几乎所有的互联网公司来说,线上A/B测试都是验证新模块、新功能、新产品是否有效的主要测试方法。

A/B测试

A/B测试又称为“分流测试”或“分桶测试”,是一个随机实验,通常被分为实验组和对照组。在利用控制变量法保持单一变量的前提下,将A、B两组数据进行对比,得出实验结论。具体到互联网场景下的算法测试中,可以将用户随机分成实验组和对照组,对实验组的用户施以新模型,对对照组的用户施以旧模型,比较实验组和对照组在各线上评估指标上的差异。可以由下图来展示: A/B测试示意图

上图中用户被随机均分成两组,橘色和绿色代表被控制的变量,最右侧是转化率。通过这种方式可以看到,系统中单个变量对系统产生的整体影响。
相对离线评估而言,线上A/B测试无法被替代的原因主要有以下三点:

线上A/B测试的评估指标

一般来讲,A/B测试都是模型上线前的最后一道测试,通过A/B测试检验的模型将直接服务于线上用户,完成公司的商业目标。因此,A/B测试的指标与线上业务的核心指标保持一致。
下表列出了电商类推荐模型、新闻类推荐模型、视频类推荐模型的线上A/B测试的主要评估指标:

推荐系统类别 线上A/B测试评估指标
电商类推荐模型 点击率、转化率、客单价(用户平均消费金额)
新闻类推荐模型 留存率(x日后仍活跃的用户数/x日前的用户数)、平均停留时长、平均点击个数
视频类推荐模型 播放完成率(播放时长/视频时长)、平均播放时长、播放总时长

线上A/B测试的指标与离线评估指标有较大差异。离线评估不具备直接计算业务核心指标的条件,因此退而求其次,选择了偏向于技术评估的模型相关指标。但在公司层面,更关心能够驱动业务发展的核心指标。因此,在具备线上测试环境时,利用A/B测试验证模型对业务核心指标的提升效果是有必要的。从这个意义上讲,线上A/B测试的作用是离线评估无法替代的。

参考

上一篇下一篇

猜你喜欢

热点阅读