简介
推荐系统的评估相关的知识比重在整个推荐系统的知识框架中占比不大,但是其重要程度不言而喻,因为采用的评价指标直接影响到了推荐系统的优化方向是否正确。评价指标主要用于评价推荐系统各方面的性能,按照应用场景可以分为离线评估和线上测试。其中离线评估的主要方法包括Holdout检验、交叉检验、留一验证、自助法等,评价指标主要包括用户满意度、预测准确度、召回率、覆盖率、多样性、新颖性、流行度、均方根误差、对数损失、P-R曲线、AUC、ROC曲线等等。线上测试的评估方法主要包括A/B测试、Interleaving方法等,评价指标主要包括点击率、转化率、留存率、平均点击个数等等。本文将着重介绍离线评估相关方法和指标,尤其是P-R曲线、AUC、ROC曲线等,这些评价指标是最常用的也是最基本的,出现在各类推荐相关的论文中,因此需要重点掌握。
离线评估方法和评价指标
在推荐系统的评估过程中,离线评估往往被当做最常用也是最基本的评估方法。顾名思义,离线评估是指在将模型部署于线上环境之前,在离线环境中进行的评估。由于不用部署到生产环境,离线评估没有线上部署的工程风险,也无须浪费宝贵的线上流量资源,而且具有测试时间短,同时进行多组并行测试、能够利用丰富的线下计算资源等诸多优点。
离线评估的主要方法
- Holdout检验
Holdout检验是基础的离线评估方法,它将原始的样本集合随机划分为训练集和测试集两部分。举例来说,对于一个推荐模型,可以把样本按照70%~30%的比例随机分成两部分,70%的样本用于模型的训练,30%的样本可以用于模型的评估。
在实际应用中,有很多方便的库可以帮助我们进行样本集合的划分,比如scikit-learn中提供的train_test_split函数,下面进行个简单展示:
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检验,则得到的结论存在较大的随机性。为了消除这种随机性,“交叉检验”的思想被提出。
- 交叉验证
k-fold交叉验证:先将全部的样本划分为k个大小相等的样本子集;依次遍历这k个子集,每次都把当前子集作为验证集,其余所有子集作为训练集,进行模型的训练和评估;最后将所有的k次的评估指标的平均值作为最终的评估指标。在实验中,k经常取10。同样,scikit-learn中提供了KFold函数可以使用,例子如下:
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个元素共有种可能,因此它的时间开销远超留一验证,故很少在实际工程中使用。
同样,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)
结果:
- 自助法
不管是Holdout检验还是交叉检验,都是基于划分训练集和测试集的方法进行模型评估的。然而,当样本规模比较小的时候,将样本集进行划分会让训练集进一步减小,这可能会影响到模型的训练效果。有没有能维持训练集样本规模的验证方法呢?“自助法”可以在一定程度上解决这个问题。
自助法是基于自助采样法的检验方法:对于总数为n的样本集合,进行n次有放回的随机抽样,得到大小为n的训练集。在n次采样过程中,有的样本会被重复采样,有的样本没有被抽出过,将这些没有被抽出的样本作为验证集进行模型验证,这就是自助法的验证过程。
给定包含个样本的数据集,有放回的采样次,得到采样集。数据集中样本可能在中出现多次,也可能不出现在。一个样本始终不在采样集中出现的概率是,因此中约有的样本出现在了中。将用作训练集, - 用作测试集。
离线评估的指标
-
均方根误差
同理,MAE采用绝对值计算预测误差,定义如下::
很多推荐网站都会提供一个让用户给物品打分的功能,如果知道了用户对物品的历史评分数据,就可以从中学习到用户的兴趣模型,并预测该用户在将来浏览到未曾见过的物品时,会给这个物品打多少分。评分预测可以看做是回归问题,评分预测的预测准确度一般是通过均方差误差(RMSE)和平均绝对误差(MAE)计算。对于测试集中的一个用户和物品,我们令代表用户对物品的实际评分,而代表我们的推荐算法给出的预测评分。
综上,可以得出RSME的定义为:
其中|T|代表测试集的大小。
一般情况下,RMSE能够很好滴反映回归模型预测值与真实值的偏离程度。但在实际应用时,如果存在个别偏离程度非常大的离群点,那么即使离群点数量非常少,也会让RSME指标变得很差。为了解决这个问题,可以使用鲁棒性更强的平均绝对百分比误差(Mean Absolute Percent Error,MAPE)进行类似的评估,MAPE的定义如下:
相比RSME,MAPE相当于把每个点的误差都进行了归一化,降低了个别离群点带来的绝对误差的影响。 -
覆盖率
覆盖率是描述一个推荐系统对物品长尾的发掘能力,即推荐系统做到了雨露均沾,对商城中的每一个物品都有涉及,而不是只推荐那些热门的商品。据此,覆盖率的一个简单定义为推荐系统能够推荐出来的物品占总物品集合的的比例。假设系统的用户集合,推荐系统给每个用户推荐了一个长度为的物品列表,那么覆盖率的计算公式为::
其中是商品的总数。 -
新颖度与平均流行度
我们使用推荐列表中全部物品的平均流行度衡量推荐结果的新颖度。如果推荐的物品都很热门,那么说明推荐的新颖度比较低。反之,说明推荐结果比较新颖。
流行度的定义如下:
其中是每个物品的流行度,可以通过该物品在测试集中出现的次数来简单计算,是推荐物品集合的总数。
这里在计算平均流行度的时候对每个物品的流行度取对数,这是因为物品的流行度分布满足长尾分布,取对数后,流行度的平均值更加稳定。 -
对数损失函数
对数损失函数(LogLoss)也是经常在离线评估中使用的指数,在一个二分类问题中,LogLoss的定义如下:
其中,为输入实例的真实类别,为预测输入实例是正样本的概率,是样本总数。
LogLoss就是逻辑回归的损失函数,而大量深度学习模型的输出层正式逻辑回归或者Softmax,因此采用LogLoss作为评估指标能够非常直观地反应模型损失函数的变化。
-
准确率
对于分类问题,比如CTR问题,准确率(Accuracy)是指分类正确的样本占总样本个数的比例,即:
其中,代表被正确分类的个数,代表总样本个数。准确率是分类任务中较为直观的评价指标,虽然具有较强的可解释性,但是也存在明显的缺陷:当不同类别的样本的比例非常不均衡时,占比大的类别往往成为影响准确率的最主要因素。例如,如果负样本占比99%,那么分类器将所有样本都预测为负样本,也可以取得99%的准确率。 -
精准率和召回率
从公式上看,它是把用户真实喜爱列表和推荐列表的交集的大小去除以推荐列表的大小,它的意义是计算在所预测的推荐列表中究竟有多少物品是用户感兴趣的。
精准率(Precision)是分类正确的正样本个数占分类器判定为正样本的样本个数的比例。召回率(Recall)是分类正确的正样本个数占真正的正样本个数的比例。
在排序模型中,通常没有一个确定的阈值把预测结果直接判定为正样本或负样本,而是采用TopN排序结果的精准率(Precision@N)和召回率(Recall@N)来衡量排序模型的性能,即认为模型排序的TopN的结果就是模型判定的正样本,然后分别计算Precision@N和Recall@N。
以TopN推荐为例,令代表模型根据用户在训练集上的行为给用户计算出的推荐列表,而代表用户在测试集上的真实喜爱列表。那么推荐结果的精准率的定义如下:
召回率的定义如下: 可以看到它与精准率的定义非常相似,唯一不同的是分母变成了用户真实喜爱列表大小。它的意义在于用户真实喜爱列表中的物品中有多少是被推荐算法预测出来的,即真实列表的召回率。
维基百科上的图片很好地展示了Precision和Recall的计算公式,方便记忆: 图自https://en.wikipedia.org/wiki/Precision_and_recall#/media/File:Precisionrecall.svg 上图中圆圈内的代表被选出的样本,用其中的正样本除以被选中的样本总数就是Precision,用其中的正样本除以所有的正样本数量就是Recall。
注意准确率(Accuracy)和精准率(Precision)的区别。
精准率和召回率是矛盾统一的两个指标:为了提高精准率,分类器需要尽量在“更有把握时”才把样本预测为正样本,即降低了精准率计算公式中的分母部分。但往往会因为过于保守而漏掉很多“没有把握”的正样本,导致召回率过低。
以挑选西瓜为例,若希望将好瓜尽可能多地挑选出来,则可通过增加选瓜的数量来实现,如果将所有的西瓜都选上,那么所有的好瓜也必然都被选上了,这样就会导致Precision很低,但是Recall就会相对较高。若希望选出的瓜中好瓜比例尽可能高,则可只挑选最有把握的瓜,但这样就难免会漏掉不少好瓜,使得Recall较低。
为了综合反映Precision和Recall的结果,可以使用F1-score,F1-score是精准率和召回率调和平均值,定义如下:
用一张图总结一下:
关于混淆矩阵,在下一节有详细介绍。
-
P-R曲线
P-R曲线 P-R图直观地显示出模型在样本总体上的Precision、Recall。在进行比较的时候,若一个模型的P-R曲线被另外一个模型的P-R曲线完全“包住”,则可断言后者的性能优于前者。如上图中模型A的性能就优于模型C;如果两个模型的P-R曲线出现了交叉,如上图汇总的A和B,则难以一般性地断言两者孰优孰劣,只能在具体的Precision和Recall条件下进行比较。
P-R曲线,顾名思义,其中P代表Precision,R代表Recall。P-R曲线就是根据精确率和召回率而绘制的曲线,一般横轴选择召回率,纵轴选择精确率。对于一个排序模型来说,其P-R曲线上的一个点代表“在某一阈值下,模型将大于该阈值的结果判定为正样本,将小于该阈值的结果判定为负样本时,排序结果对应的召回率和精确率”。整条P-R曲线是通过从高到低移动正样本的阈值生成的。如下图所示,其中包含了3个模型的P-R曲线,其中横轴0点附近代表阈值最大时模型的Precision和Recall。 -
ROC曲线
ROC曲线的全称是“the Receiver Operating Characteristic”曲线,中文译为“受试者工作特征曲线”。ROC曲线最早诞生于军事领域,而后在医学领域应用甚广,“受试者工作特征曲线”这一名称也正是来自于医学领域。
在正式介绍ROC曲线之前,我们先来彻底理解一下混淆矩阵的定义。混淆矩阵中有Positive、Negative、True、False等概念,意义如下: -
称预测类别为1的为Positive(阳性),预测类别为0的为Negative(阴性)
-
预测正确的为True(真),预测错误的为False(伪)
然后,由此引出True Positive Rate(真阳率TPR)、False Positive Rate(伪阳率FPR)两个概念,计算方式如下:
- TPR =
- FPR =
仔细观察上面的两个式子,发现两个式子的分子其实对应了混淆矩阵的第二行,即预测类别为1的那一行。另外可以发现TPR就是用TP除以TP所在的列,FPR就是用FP除以FP所在的列。二者的含义如下:
- TPR代表在所有真实类别为1的样本中,预测类别为1的比例
- FPR代表在所有真实类别为0的样本中,预测类别为1的比例
表示的意义是:对于不论真实类别是0还是1的样本,模型预测样本为1的概率都是相等的。
换句话说,模型对正例和负例毫无区分能力,做决策和抛硬币没啥区别。因此,我们认为AUC的最小值为0.5(当然也存在预测相反这种极端的情况,AUC小于0.5,这种情况相当于分类器总是把对的说成错的,错的认为是对的,那么只要把预测类别取反,便得到了一个AUC大于0.5的分类器)。
下面举一个小例子,以分类问题为例,预测类别为离散标签,假设8个样本的预测情况如下: 得到的混淆矩阵如下: 进而计算得到TPR=3/4,FPR=2/4,得到ROC曲线: 可以看到这实际上式两段直线组成的曲线,是因为我们只画出了一个关键点。
如果对于CTR任务,预测的结果是一个概率值,那应该如何画出ROC曲线呢?比如预测结果如下: 这时,需要设置阈值来得到混淆矩阵,不同的阈值会影响得到的TPR,FPR。如果阈值取0.5,小于0.5的为0,否则为1,那么我们就得到了与之前一样的混淆矩阵。其他的阈值就不再啰嗦了。依次使用所有预测值作为阈值,得到一系列TPR,FPR,然后画出关键点,再连线即可得到ROC曲线。
因此,ROC曲线跟P-R曲线一样,也是通过不断地移动模型正样本阈值来生成的。
-
AUC
AUC(Area Under Curve)的意思是曲线下的面积。它通常被定义为ROC曲线下与坐标轴围成的面积,显然这个面积的数值不会大于1(但是这个曲线也不一定是ROC,也可以是前面提及的P-R曲线)。又由于ROC曲线一般都处于y=x这条直线的上方,所以AUC的取值范围在0.5和1之间。AUC越接近1.0,检测方法真实性越高;等于0.5时,则真实性最低,无应用价值。我们往往使用AUC值作为模型的评价标准是因为很多时候ROC曲线并不能清晰的说明哪个分类器的效果更好,而作为一个数值,对应AUC更大的分类器效果更好。
综上,AUC是衡量二分类模型优劣的一种评价指标,表示预测的正例排在负例前面的概率。 -
mAP
平均精度均值(mean Average Precision, mAP)是另一个在推荐系统、信息领域中常用的评估指标。该指标其实是对平均精度(Average Precision,AP)的再次平均,因此在计算mAP之前,我们先了解一下什么是平均精度。
假设推荐系统对某一用户测试集的排序结果如下:
推荐序列 | 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测试无法被替代的原因主要有以下三点:
- 离线评估无法完全消除数据有偏现象的影响,因此得出的离线评估结果无法完全替代线上评估结果。
- 离线评估无法完全还原线上的工程环境。一般来说,离线评估往往不考虑线上的延迟、数据丢失、标签缺失等情况。因此,离线评估环境只能说是理想状态下的工程环境,得出的评估结果存在一定的失真现象。
- 线上系统的某些商业指标在离线评估中无法计算。离线评估一般针对模型本身进行评估,无法直接获得与模型相关的其他指标,特别是商业指标。也新的推荐模型为例,离线评估关注的往往是ROC曲线、PR曲线等的改进,而线上评估可以全面了解该推荐模型带来的用户点击率、留存时长、PV访问量等的变化。这些都需要由A/B测试进行全面评估。
线上A/B测试的评估指标
一般来讲,A/B测试都是模型上线前的最后一道测试,通过A/B测试检验的模型将直接服务于线上用户,完成公司的商业目标。因此,A/B测试的指标与线上业务的核心指标保持一致。
下表列出了电商类推荐模型、新闻类推荐模型、视频类推荐模型的线上A/B测试的主要评估指标:
推荐系统类别 | 线上A/B测试评估指标 |
---|---|
电商类推荐模型 | 点击率、转化率、客单价(用户平均消费金额) |
新闻类推荐模型 | 留存率(x日后仍活跃的用户数/x日前的用户数)、平均停留时长、平均点击个数 |
视频类推荐模型 | 播放完成率(播放时长/视频时长)、平均播放时长、播放总时长 |
线上A/B测试的指标与离线评估指标有较大差异。离线评估不具备直接计算业务核心指标的条件,因此退而求其次,选择了偏向于技术评估的模型相关指标。但在公司层面,更关心能够驱动业务发展的核心指标。因此,在具备线上测试环境时,利用A/B测试验证模型对业务核心指标的提升效果是有必要的。从这个意义上讲,线上A/B测试的作用是离线评估无法替代的。
参考
- 《机器学习》 -- 周志华
- 《推荐系统实战》-- 项亮
- 《深度学习推荐系统》-- 王喆
- https://www.jianshu.com/p/5df19746daf9
- https://www.zhihu.com/question/39840928
- https://baike.baidu.com/item/AUC/19282953?fr=aladdin
- https://en.wikipedia.org/wiki/Precision_and_recall
- https://blog.csdn.net/bqw18744018044/article/details/81024520
- https://scikit-learn.org/stable/modules/classes.html#module-sklearn.model_selection
- https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html
网友评论