使用评级系统减少投票者的影响

数据挖掘 统计数据 正常化
2021-09-26 14:45:54

我有一个网站,用户可以在其中对 1-5 星系统中的事物进行评分。一旦一个项目到达排行榜的顶部,一些用户往往会开始给它打 1 星,即使它获得了 4-5 星的大多数才能到达它的位置。这并不猖獗,我会说 10-20% 的新选票是 1。很明显,他们正试图操纵评级系统,我想阻止这种情况。

我目前这样做的方式是对我认为合法的投票设置一个“合理的窗口”。

对于少于 10 票的项目;我目前什么都不做,只取平均值作为它的评级。

一旦一个项目开始获得超过 10 票,我就会将它们与他们的平均值窗口联系起来。该窗口定义为

Window = 4.5 - Log(TotalVotes, 10);

所以一个合理的投票范围是 (Mean - Window) thru (Mean + Window)

一旦找到合理的投票范围,“评分”只是所有合理投票(那些落在合理范围内的人)的平均值。

这意味着实际平均值为 4.2 且获得 100 票的项目的窗口为4.5-Log(100,10) = 2.5,因此如果该项目获得 1 星投票,它将在评级中被忽略。但是,1 星仍会影响基础平均值。

这总体上效果很好,但问题是当一个项目的Mean - Window分数刚刚接近 1.0 时,一旦它低于 1.0,现在每 1 星投票都包含在评级中,并且评级显着下降,即使在 5 月之前和之后的差异也是如此刚刚多了一个 1 星评级。

我需要一个更好的系统/方法来过滤掉这些 1 星评级,而不仅仅是他们,还要处理有人可能让他们的朋友投票给一个项目 10 票和所有 5 星的情况,它的真实评级可能更多3星。

寻找有关如何处理用户驱动的评级系统和规范异常投票的任何建议。

4个回答

您应该查看其他位置估计器。

你想要的是一个健壮的估计器,具有很高的分解点

极端的方法是中位数。

但是您可能会通过修剪均值得到更多有趣的数值结果

您定义一个阈值,例如 2%。然后删除前 2% 的选票和后 2% 的选票,并仅取剩余条目的平均值。98% 5 星的应用仍将获得 5.0

但为了防止操纵,我会研究其他信号。例如,来自单个区域的集群投票。

我喜欢@Anony-Mousse 的回答。使用稳健的估计器是好的。

我想添加一个不同的方向来解决这个问题。似乎有一些“恶意”用户投了这些反对票,因此您可能想要识别它们。

创建用户数据集并使用“对领先项目投不合理的反对票”作为标签。您可以使用“对领先项目投反对票”作为默认值,然后手动修改它们并使规则更加微妙,例如“在项目到达顶部图表后对领先项目投了两次以上的反对票”我猜这些功能像低票数,领先项目的低票数等将很有用。

现在你处于一个有监督的学习框架中。一旦您识别出恶意用户,请忽略他们的投票并避免操纵。

为了使您的估计器更加稳健,您可以将您的评级建模为高斯混合模型 (GMM),它是两个高斯 rv 的混合:1) 真实评级,2) 等于 1 的垃圾评级。Scikit-learn 已经有一个罐装 GMM 分类器:http ://scikit-learn.org/stable/auto_examples/mixture/plot_gmm_classifier.html#example-mixture-plot-gmm-classifier-py

再深入一点,一个简单的方法是让 scikit-learn 将您的评级划分为两个高斯。如果其中一个分区的平均值接近 1,那么我们可以丢弃这些评级。或者,更优雅地,我们可以将另一个非近一高斯的均值作为真实评级均值。

这是执行此操作的 ipython 笔记本的一些代码:

from sklearn.mixture import GMM
import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt
import collections

def make_ratings(mean,std,rating_cnt):
    rating_sample = np.random.randn(rating_cnt)*std + mean
    return np.clip(rating_sample,1,5).astype(int)

def make_collection(true_mean,true_std,true_cnt,junk_count):
    true_ratings = make_ratings(true_mean,true_std,true_cnt)
    junk_ratings = make_ratings(1,0,junk_count)
    return np.hstack([true_ratings,junk_ratings])[:,np.newaxis]

def robust_mean(X, th = 2.5, agg_th=2.5, default_agg=np.mean):
    classifier = GMM(n_components=2)
    classifier.fit(X)
    if np.min(classifier.means_) > th or default_agg(X)<agg_th:
        return default_agg(X)
    else:
        return np.max(classifier.means_)

r_mean = 4.2
X = make_collection(r_mean,2,40,10)
plt.hist(X,5)
classifier = GMM(n_components=2)
classifier.fit(X)
plt.show()
print "vars =",classifier.covars_.flatten()
print "means = ",classifier.means_.flatten()
print "mean = ",np.mean(X)
print "median = ",np.median(X)
print "robust mean = ", robust_mean(X)
print "true mean = ", r_mean
print "prob(rating=1|class) = ",classifier.predict_proba(1).flatten()
print "prob(rating=true_mean|class) = ",classifier.predict_proba(r_mean).flatten()
print "prediction: ", classifier.predict(X)

一次运行的输出如下所示:

vars = [ 0.22386589  0.56931527]
means =  [ 1.32310978  4.00603523]
mean =  2.9
median =  3.0
robust mean =  4.00603523034
true mean =  4.2
prob(rating=1|class) =  [  9.99596493e-01   4.03507425e-04]
prob(rating=true_mean|class) =  [  1.08366762e-08   9.99999989e-01]
prediction:  [1 0 1 0 1 1 1 0 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0 1 1 0 1 1 1 0 1 0 1 0 1 1 0
 1 1 1 0 0 0 0 0 0 0 0 0 0]

我们可以通过一些蒙特卡罗试验来模拟这将如何工作:

true_means = np.arange(1.5,4.5,.2)
true_ratings = 40
junk_ratings = 10
true_std = 1
m_out = []
m_in = []
m_reg = []
runs = 40
for m in true_means:
    Xs = [make_collection(m,true_std,true_ratings,junk_ratings) for x in range(runs)]
    m_in.append([[m]*runs])
    m_out.append([[robust_mean(X, th = 2.5, agg_th=2,default_agg=np.mean) for X in Xs]])
    m_reg.append([[np.mean(X) for X in Xs]])

m_in = np.array(m_in).T[:,0,:]
m_out = np.array(m_out).T[:,0,:]
m_reg = np.array(m_reg).T[:,0,:]

plt.plot(m_in,m_out,'b.',alpha=.25)
plt.plot(m_in,m_reg,'r.',alpha=.25)
plt.plot(np.arange(0,5,.1),np.arange(0,5,.1),'k.')
plt.xlim([0,5])
plt.ylim([0,5])
plt.xlabel('true mean')
plt.ylabel('predicted mean')
plt.title("true_ratings=" + str(true_ratings)
          + "; junk_ratings=" + str(junk_ratings)
         + "; std="+str(true_std))

输出粘贴在下面。红色是平均评级,蓝色是建议评级。您可以调整参数以获得略有不同的行为。 在此处输入图像描述

记录所有投票

1 票在首页上的比例与不在首页上的比例

仅在第一页上应用一小部分数字 1
基本上根据第 1 页的偏见作为一个整体消除第 1 页的偏见

1 票申请 = 1 票项目在第一页 *(1 票第二页总票数 / 1 票总票数)