zoukankan      html  css  js  c++  java
  • 如何通过用户输入对产品进行排序

    原文来自William's Blog

    现有情况

    我们假定你已经有足够多的信息,你的用户能够投票(译者:投票指的是用户对产品进行打分)表示他们有多喜爱这个产品.如果你想在这个基础上对你的产品打分,并且根据产品的好坏进行排序,你应该怎么做呢?

    你可以在网上找到不少相关的例子. Amazon对产品进行排序, Hacker News对链接进行排序, Yelp对商家进行排序, 这些排序都是通过用户的评论和投票来做的.

    这些网站都采用同样的方法来进行排序:他们通过用户的投票对产品计算一个得分,然后根据这个得分进行排序.所以怎么打分就觉得了怎么排序.

    那么这个分数是如何生成的呢?最简单的方法是使用用户投票的平均值.实际上,这也是这些网站计算得分的方法(Hacker news同时考虑了由于时间带来了新鲜度变化).

    不幸的是,使用平均值会有一个很大的问题:如果一个物品只有很少的投票,那么这个平均值会非常极端.比如一个只有一次投票的物品,平均值就是这次投票的值(如果根本就没有投票,那么我们应该怎么办呢?).这个方法导致那些获得很少投票的物品很有可能排名很高或者很低.

    如果你上Amazon之后选择用户用户平均评价排序,那么就会碰到这个问题(译者:Amazon现在已经解决了这个问题了).排在最顶端的物品很多都是只有一个投票的物品,你需要跳过他们才能找到真正好的东西.

    那么怎么才是一个更好的排序呢?我们可能希望看到一个有50个五星的物品排在只有一个五星的物品的前面.简单来说,投票越多,那我们越能确定一个物品是否有用,所以我们应该更加关注高得分,并且投票数目多的物品.

    同时,虽然我们不大关注排在列表底端的东西,但是这个例子也适合这类物品:有50个一星的物品比有一个一星的物品更差.

    由此我们可以得出结论,最好的排序是得票低的物品应该排在中间,而得票多的物品更有可能排在顶端或者底端.

    置信区间的问题

    上述问题可以通过在平均值周围计算一个置信区间来解决,并且使用置信区间的下界来进行排序.置信区间有着如下性质,如果投票的数量越大,那么置信区间的范围会越窄,如果投票的数量越小,置信区间的范围会越宽.

    使用置信区间的下界进行排序对于得票多的物品来说没有问题,因为这些物品的置信区间下界和平均值相差不大,而对于得票少的物品来说,置信区间的下界会非常低.这样就导致了新物品由于得票比较少而排在列表的底部,和那些得了很多票的差物品的得分一样低.换句话说,如果使用置信区间的下界进行排序,那么一个没有得票的物品会和一个得了1000的差评的物品排的位置差不多.

    如果使用这个方法,每一个新物品开始都会排在列表的最低端.(译者:关于置信区间,可以围观wiki了解更多)

    使用贝叶斯统计

    是否有更好的方法呢?是的,这种数据稀疏的问题刚好可以使用贝叶斯统计来解决.

    贝叶斯提供了一个新的建模的框架,这个建模的方法不只是使用投票本身,还使用了我们在看到所有投票之前认为这个物品怎样的先验信念. 我们可以使用这个先验值来做一些平滑处理.这个平滑处理会使得如果物体得票数比较少,那么分数会比较接近先验值,如果物体得票数比较多,那么分数会比较接近得票平均值.也就是说,投票得到的分数可以覆盖该物品的先验值.

    计算先验值可以带来两个改进.其一是我们可以计算一个没有投票的物品的得分.我们不再以内与置信区间,从而可以决定怎么来对待一个得票少的物品.第二,这是的我们可以很方便把其他信息加入到得分的计算中去.额外的信息可以通过先验信念来表示.

    我们有着怎么样的额外信息呢?也就是说我们应该怎么计算先验值呢?我们可以认为,如果一个人看了科恩兄弟的某部电影,那么他可能会考虑科恩兄弟的其他电影,如果一个人买了一件索尼的产品,那么他很有可能还会考虑索尼的其他产品.

    总的来说,如果你不知道这个物品的信息,那么你可以考虑像是物品的信息,或者来源相同的物品信息等等.我们可以基于同样的方法来计算先验值.

    接下来我们看我们怎么通过贝叶斯统计来做平滑处理.

    方案一: 贝叶斯平均(TBA,Ture Bayesian Average)

    第一个方法,也是IMDB使用的方法,叫做贝叶斯平均(虽然这个技术并不真的是从统计学里来的).一下是使用TBA来计算分数的公式:

    s=Rv+Cmv+m

    R 是物品得票的平均值, v是得票数C是先验值, m是用来控制分数随得票数变化速度的参数. (了解更多可以围观 the Bayesian interpretation of the ‘true Bayesian average’.)

    这个公式有一个更加通俗的解释:我们假设已经有m张投票,每张投票的分值都是C.这些投票已经被自动加到物品上了,然后我们计算这个假投票和真投票合并之后的平均值.

    那么我们应该怎么选择C呢?最简单的方法是我们把C设置成所有物品获得的所有选票的平均值(或者某一类物品所获得的所有选票的平均值.).这样我们就可以获得我们想要的排序了,低得票的物品排序在中间,随着得票的增长,物品会的排序会上升或者下降. (想了解更严格参数定义,可以围观 why using the global average is a good target for smoothing.)(译者:计算先验值还可以使用协同过滤)

    TBA实现起来非常简单,而且很容易通过现有投票系统来改造.

    但是我们可以做的更好.

    贝叶斯平均的问题

    贝叶斯平均的主要问题是假设所有的用户投票是正态分布的.

    这个假设是有两个问题,第一,我们获得的投票是离散的,而不是连续的.第二,我们不能期待投票是正态分布的.当然,在实际应用中,这两个都不是什么大问题.我们在建模时总数提出一些合理假设来简化问题.

    更大的问题是真正的打分可能是多样性的,一些Amazon的产品有着大量的五星和一星的评价.一些电影有的人很喜欢,有的人很讨厌.这个时候正态分布的模型就无法处理这样的情况了.

    总的来说这个方法要把我们知道的所有信息计算成一个分数,然后根据这个分数来进行打分.但是这个方法并不是一种明确可控的方法.

    所以我们需要思考一个新的打分方法,这个方法把打分分成三个步骤.

    1. 我们对一个物体的先验信念
    2. 用户对这个物品的投票
    3. 一个投票直方图和分数的映射

    这个系统可以在解决数据稀疏问题的同时提供对物品排序的精确控制.

    方案二: 狄利克雷先验以及先验值函数

    为了达到这个目标,我们必须多项式里的正态分布.一个多项式模型会展示每个物品所获得的选票的完整直方图.比如,对于亚马逊产品,一个多项式模型将会记录每个产品有多少一星,多少两星等等.如果用户可以给一个产品的打分有n个不同的分值.我们可以把一个多项式对应成一个n维向量.

    (在实现的时候我们同时只关注直方图中每个值相对比例而不是实际数量.)

    为了在贝叶斯网络中使用这个多项式,我们将使用狄利克雷分布来表示先验信念.就像一个多项式分布表示一个n维向量一样,一个狄利克雷分布是一个n维向量上的概率分布.实际上,这个分布包含了所有的直方图可能.

    我们使用狄利克雷是应为这是一个多项式的共轭先验.这意味着当哦我们使用贝叶斯法则来合并狄利克雷和多项式的结果还是狄利克雷分布.这样我们就可以简单的进行迭代了,最开始是狄利克雷分布,计算了一组新的数据之后还是狄利克雷分布.

    狄利克雷分布是一个复杂的分布,幸运的是,他使用起来很简单.

    首先,狄利克雷分布像多项式一样参数化一个直方图.如果我们使用D来表示狄利克雷分布而使用M表示一个多项式,M(7,2,4,1,0)表示这样一个直方图,这个物品有7个一星,3个两星...D(7,3,4,1,0) 则表示一个狄利克雷分布.当然,狄利克雷的含义和多项式我拿起不一样,不过我们在这篇文章中不讨论这个问题.这里只是说明使用狄利克雷分布来表示一个投票直方图是很简单的.

    另外,当我们使用贝叶斯法则合并狄利克雷和一个多项式的时候,结果是一个很容易制定两个输入分布的狄利克雷,我们回想一下贝叶斯法则:

    P(Y|X)=P(X|Y)P(Y)/Z

    X表示观察到的投票,Y表示这个物品的概率模型.这我们的例子中,P(Y)是先验信念,P(X|Y)是我们实际的投票情况, P(Y|X)是更新的投票模型, Z是归一化参数.

    如果我们把D(α1,α2,)作为先验信念,把M(β1,β2,)作为条件概率, 那么贝叶斯法则将会变成如下形式:

    P(Y|X)=P(X|Y)P(Y)/Z
    =M(β1,β2,⋯)D(α1,α2,⋯)/Z
    =D(α1+β1,α2+β2,⋯)

    换句话说,为了计算后验分布,我们只需要添加两个输入直方图.

    所以我们现在有方法通过先验信息和投票信息计算出结果分布了.结果分布也是一个物品的投票直方图,就好象TBA的例子一样,会从先验分布向着投票分布移动.(实际上,我们还可以使用那个假设投票来解释,αi是假设的投票,而βi是真正的投票,然后我们把这两个加到一起)

    最后一步是将这个分布转换成一个单一的分值.最好的方法是使用一个函数来计算得分,这个函数的是如是一个直方图,输出是所有的得分可能以及每个得分的概率.

    为了结算结果,你需要计算一个复杂的积分,幸运的是,我们可以使用狄利克雷的一个特性来计算,这就是如果狄利克雷表达式是D(α1,α2,αn), 那么投票是分类i的比例是:

    E[θi]=αi/jαj

    换句话说,第i个桶里的投票比例就是参数αi在所有的参数和中所占的比例.

    如果我们需要一个线性的打分函数,我们也可以使用多项式的简单方法来计算.

    比如,对于我们的Amazon例子来说,我们可以使用如下公式:

    s(θ1,θ2,⋯,θ5)=sum(i*θi for i in range(1,5))

    我们将每个得分的值加起来计算出一个分数,因为这是线性的,所以计算起来也很简单:

    E[s(θ1,θ2,⋯,θ5)]=E[sum(i*αi for i in range(1,5))]
                    =sum(i*E[θi] for i in range(1,5))
                    =sum(i*αi for in in range(1,5)) /sum(αj)

    当然,这个简单的方法会有一些问题.比如,这些权重是我们时间中真的想要的么?它们意味着一个五星比一个一星要重要五倍,可能实际情况并非这样.而且这并没有对多样性的物品做特殊的处理.

    但是,这个框架允许你加入你所需要的任何函数,虽然使用一些非线性函数可能导致复杂的积分运算.

    现在我们达成了我们之前定下的三个目标.我们使用了先验信念,任意数量的投票和一个自定义得分函数,并且使用他们来计算出排名.

    最后的问题是我们应该使用什么样的先验信念,日过我们使用平均投票直方图的话,我们可以达到我们第一节所描述的效果,就是得票低的物品排在中间. (如果想要证明,可以围观 related blog post.)

    最后,一些代码

    下面是实现的Ruby代码.

    ## assumes 5 possible vote categories, but easily adaptable
    
    DEFAULT_PRIOR = [2, 2, 2, 2, 2]
    
    ## input is a five-element array of integers
    ## output is a score between 1.0 and 5.0
    def score votes, prior=DEFAULT_PRIOR
      posterior = votes.zip(prior).map { |a, b| a + b }
      sum = posterior.inject { |a, b| a + b }
      posterior.
        map.with_index { |v, i| (i + 1) * v }.
        inject { |a, b| a + b }.
        to_f / sum
    end

    如果你使用如上代码,你可以看到

    • 如果没有投票,将会有一个中间的得分: 3.0.
    • 添加高分投票之后,得分会增加,添加低分投票之后,得分会减少.
    • 如果得分是4,那么更多的4分投票不会影响得分
    • 如果先验值变大,那么需要更多的投票才能是得分偏离3.0

     

  • 相关阅读:
    学习java annotation
    自己模拟实现spring IOC原理
    ubuntu16.04~qt 5.8无法输入中文
    尔雅小助手
    ubuntu16.04 python3 安装selenium及环境配置
    A flash of Joy
    数据库大作业--由python+flask
    flask+html selected 根据后台数据设定默认值
    mysql--sqlalchemy.exc.IntegrityError: (IntegrityError) (1215, 'Cannot add foreign key constraint'
    SQL Server 2014连接不到服务器解决方法
  • 原文地址:https://www.cnblogs.com/triStoneL/p/2645307.html
Copyright © 2011-2022 走看看