0%

内容平台的个性化推荐是怎么做的

引入

cover

每天,互联网上都会产生大量的内容信息,如何做好这些信息内容的分类、整理与分发是每一个内容平台需要不断思考的问题。内容平台除了鼓励内容创作者产出更优质的内容,更需要通过某种方式持续不断地抓住内容消费者的注意力,从而让内容创作者、内容消费者和平台形成一个有机的生态结构。那么,该怎样持续不断地吸引内容消费者的眼球呢?这几年来各种内容平台最佳内容分发实践,便是做好用户的个性化推荐。

那么,这些个性化推荐到底是怎么做的呢?我们先来看一个具体的交互实例。

一个交互实例

如果你在一个相关内容平台刚刚注册为新用户后,平台可能会让你选择一些感兴趣的领域,并自动为你关注相关领域的优质创作者。这些内容成为平台对你个性的第一次试探。

在此之后,平台会继续基于你的浏览、点赞及评论等记录,逐渐修正对你个性的认识。随着你与平台互动的次数越来越多,平台就可以做到非常精准的推荐,并且这些内容持续不断,让你“刷得停不下来”。

源源不断的内容资讯流,加上个性化推荐,极大地提高了用户对各个内容平台的粘性,并间接地为内容平台创造了商业价值。那么,这里的个性化推荐到底该怎么做,才能达到这种效果呢?

我觉得,一个较好的个性推荐系统要做好以下两点:

  1. 内容创作者、内容、内容消费者三者的特征提取
  2. 基于上述特征的相似推荐

特征提取

特征提取,简单来说,就是从某一事物中找到一组能够较为完整的描述其特征的属性组。这些特征,简单一点,可以由你与平台之间的互动数据来量化。这些数据包括但不限于你对某一方面的内容的浏览量、点赞次数、评论次数。

此外,还需要给这些属性组贴上符合其特质的标签。除了平台手动或者自动地给其中的内容和用户贴上标签,平台也鼓励用户自己给自己贴上标签。上面的例子中,平台在让你选择感兴趣的方面时,其实就是初步地给你打上略带个性但又普适的标签。

相似推荐

那么,有了这些属性组和其对应的标签后,怎么去利用它们呢?可以定义属性组之间的相似度,给一组新的完全未知的属性组贴上可能的标签,再依据标签做推荐。

那么,依据打上的标签,如何做推荐呢?

如果一个内容和某一内容消费者的标签相似度十分高,那么就可以把该内容推荐给他;如果一个内容创作者内容消费者的标签相似度十分高,那么就可以把这个内容创作者推荐给该内容消费者关注;如果一个内容消费者(假设为 小明 )和另一个内容消费者(假设为 小刚 )的标签相似度十分高,那么 小明 点赞过、喜欢过的内容、内容创作者可以推荐给 小刚 ,同样的,基于 小刚 和平台、内容、内容作者的互动数据也可以给 小明 做推荐。

上面提到,对于特征提取,我们可以利用一些较为简单的方法得到大量的属性组和对应的标签数据。这块内容我暂时不做过深的讨论。

而相似推荐则需要一些算法支撑。仔细思考相似推荐这个流程,其实这个过程就是基于属性组之间的相似度,将一些事物分到不同的类别当中,再根据类别做推荐。这里的分类,是机器学习中一个典型的任务。分类任务中有很多算法,其中一种较为简单的算法便是下面要介绍的 K 近邻算法

K近邻(K-nearest neighborsKNN)算法

算法描述

K 近邻的算法思想很简单。对于一个含有若干属性组的未知的事物,通过计算它和其他所有已贴上标签的属性组之间的相似度,然后找到相似度最大的 k 个,从中找到次数出现最多的标签,作为未知的事物的类别。

对应的,K 近邻的算法伪码描述如下1

1
2
3
4
5
6
7
8
9
10
11
12
//P.S.我修改了其中一部分

kNearest Neighbor
Classify ( X, Y, x ) // X: training data, Y: class labels of X, x unknown sample

for 𝑖 = 1 to m do
Compute similarity s( Xi, x )
end for

Compute set I containing indices for the k largest similarities s( Xi, x ).

return majority label for { Yi where i ∈ I }

相似度定义

由上面的伪码描述可知,这个算法的关键便是对两个属性组之间的相似度的定义。

两个属性组可以被量化为两个向量。对于向量之间的相似度,可以用他们之间的距离来表示。

较为常见的计算向量之间的距离公式有以下两种:

  1. 欧氏距离
  2. 余弦距离

欧氏距离利用类似勾股定理公式计算两个向量之间的距离。例如,有两个向量\(\vec{a}=(x_1,x_2,\cdots,x_n) ,\vec{b}=(y_1,y_2,\cdots,y_n)\),他们之间的欧氏距离定义为

\[ distance_{Euclidean} = \sqrt{\overset{n}{\underset{i=1}{\sum}}{(x_{i} - y_{i})^{2}}} \]

两个向量之间的欧氏距离越大,两个向量之间的相似度越低;反之越高。

余弦距离就是计算两个向量之间夹角的余弦值,套用上述用于计算欧氏距离的两个向量,可以定义他们之间的余弦距离如下:

\[ distance_{Cosine} = \frac{\overset{n}{\underset{i=1}{\sum}}x_{i}y_{i}}{\sqrt{\overset{n}{\underset{i=1}{\sum}}{x_{i}^{2}}}\cdot\sqrt{\overset{n}{\underset{i=1}{\sum}}{y_{i}^{2}}}} \] 我们知道,当余弦值等于 1 时,两个向量之间的夹角为 0 °,这表示它们之间的相似度最高;当余弦值等于 0 时,两个向量之间的夹角为 90 °,这表示它们之间没什么相关性;如果余弦值等于 -1 ,此时两个向量的方向完全相反,这表示他们的相似度最低。

算法实现

基于上面的伪码和相似度定义公式,可以很容易给出如下的 Python 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import math


def distance_euclidean(a, b):
"""
计算两个向量之间的欧氏距离
:param a: 向量1
:param b: 向量2
:return: 两个向量之间的欧氏距离
"""
assert len(a) == len(b)
return math.sqrt(sum([math.pow((x - y), 2) for [x, y] in list(zip(a, b))]))


def distance_cosine(a, b):
"""
计算两个向量之间的余弦距离
:param a: 向量1
:param b: 向量2
:return: 两个向量之间的余弦距离
"""
assert len(a) == len(a)
return (sum([x * y for [x, y] in list(zip(a, b))])) / (
(math.sqrt(sum([x * x for x in a]))) * (math.sqrt(sum([y * y for y in b]))))


def knn_classifier(X, Y, x, k, distance):
"""
利用K近邻算法给未知类别的属性组x分类
:param X: 已知类别属性组
:param Y: X所对应的标签
:param x: 未知类别属性组
:param k: 超参数k
:param distance: 距离定义函数
:return: x 可能所处的类别
"""
s = {} # 存储相似度(或者距离)
m = len(X) # 已知数据组数
reverse = False # 存储决定距离集合是升序还是降序。默认为False(升序)
if distance is distance_cosine: # 如果距离计算函数是余弦距离,那么要降序排列,置 reverse 为 True
reverse = True
# 计算距离
for i in range(m):
s[i] = distance(X[i], x)
# 排序(升序还是降序由距离函数所决定)
res_lst = sorted(s.items(), key=lambda x: x[1], reverse=reverse)

# 在 k 个相似度最大的邻居中给各个标签出现的次数计数
k_neighbors = {}
count = 0
for i, _ in res_lst:
if Y[i] in k_neighbors.keys():
k_neighbors[Y[i]] += 1
else:
k_neighbors[Y[i]] = 1
count += 1
if count == k:
break
# 给上述计数的标签排序(降序)
label_count_lst = sorted(k_neighbors.items(), key=lambda x: x[1], reverse=True)

# 返回结果
return label_count_lst[0][0]


if __name__ == "__main__":
group = [[0.1, 0], [1.0, 1.1], [1.0, 1.0], [1.1, 1.9], [0.3, 0.1]]
labels = ['A', 'B', 'B', 'B', 'A']
unknown = [1, 2]
print(knn_classifier(group, labels, unknown, 5, distance_euclidean))
# print(knn_classifier(group,labels,x,5,distance_cosine))

总结

今天我和你一起了解到个性化推荐的一般套路,并由此引出机器学习中的分类任务里一个非常简单的算法——K近邻算法。最后,我将本文的要点总结如下:

  1. 一个较好的个性推荐系统应该处理好两个关键问题:一是特征提取、二是相似推荐
  2. 做特征提取,一方面深入挖掘用户的行为数据,获取用户特征;另一方面鼓励用户给自己贴上标签,获取与特征对应的标签信息。这样就获得大量的(特征属性组,标签)数据积累,为下一步更精准的推荐打下基础
  3. 在积累了大量的用户行为数据后和一些符合用户个性的标签后,可以利用K近邻算法给新的用户分类别,做类别的相似推荐

  1. https://www.researchgate.net/figure/260397165_Pseudocode-for-KNN-classification↩︎