我们知道,存在许多可让用户给特定类别的项目(如图书、电影)投票或者打分的网站,例如豆瓣IMDBGoodreads。这些网站通常会基于用户的投票给每个项目计算一个平均分,最简单的算法就是算术平均分了。但是,我们会发现,如果要给项目排序,使用算术平均分会显得不是太合理。

假设我们有两个项目 A 和 B。A 的投票数量是 1000,每张票的分数从 0 到 10 不等,其算术平均分是 9.0;B 的投票数量是 10,其算术平均分是 9.5。如果我们依据算术平均分对它们排序,则结果是 B 比 A 更值得阅读。但是,其实这种比较是不公平的,因为 A 和 B 的票数相差太悬殊了;A 是一个热门项目,而 B 是一个冷门项目。

贝叶斯平均就是用来尽可能消除这种冷热悬殊的。它的核心思想是假定我们预先给每个项目投了一定数量(记为 \(C\))的票,每张票的分数是 \(m\),然后再加上实际的投票做一次算术平均。它的公式如下:

\[ \bar{x} = \frac{Cm + \sum_{i=1}^{n}x_i}{C + n} \]

其中 \(x_i\) 为该项目一张投票的分数;\(n\) 是该项目的投票数量。

这样做的目的是让每个项目都站在同一起跑线上。如果 \(C\) 是一个相对较大的数,\(m\) 又是一个相对较平均的数,则实际的投票只是在这个起跑线上造成不至于太极端的扰动。就这样实现了尽可能的公平。

我们不可能先验地设定 \(C\) 和 \(m\),它们只能根据后验的统计结果计算它们,因此它们是动态的,会根据投票结果不断地修正。这种后验影响先验、先验又反作用于后验的循环逻辑,受激发于贝叶斯推断。一个简单的算法是,令 \(C\) 等于每个项目的算术平均票数,\(m\) 等于所有票面分数的算术平均。

现在设我们有 \(N\) 个项目,每个项目的票数为 \(n_j\),每个项目的算数平均分为 \(m_j\),则

\[ \begin{align} C &= \frac{\sum_{j=1}^{N}n_j}{N} \\
m &= \frac{\sum_{j=1}^{N}n_j m_j}{\sum_{j=1}^{N}n_j} \end{align} \]

我们可以改写一下原来的贝叶斯平均公式。设 \(B_j\) 为第 \(j\) 个项目的贝叶斯平均分,则有

\[ \begin{align} B_j &= \frac{Cm + n_j m_j}{C+n_j} \\
&= \frac{\frac{\sum_{j=1}^{N}n_j m_j}{N} + n_j m_j}{\frac{\sum_{j=1}^{N}n_j}{N} + n_j} \\
&= \frac{\sum_{j=1}^{N}n_j m_j + N n_j m_j}{\sum_{j=1}^{N}n_j + N n_j} \end{align} \]

对豆瓣图书搜索结果进行排序

因为豆瓣官网对图书的搜索结果并不提供按评分排序的功能,所以我建了一个网站去提供这个功能,它是贝叶斯平均的一个应用。

这个网站通过豆瓣开放 API 获取数据,然后给每一本书计算一个贝叶斯平均分,再对结果按贝叶斯平均分从大到小进行排序。网站的代码可以在这个仓库找到。