MMR(Maximal Marginal Relevance)算法在推荐系统中的应用
MMR(Maximal Marginal Relevance)多样性算法在推荐系统中的作用是平衡推荐结果的相关性和多样性。推荐系统通常会根据用户的兴趣和历史行为推荐最相关的内容,但这样做可能会导致推荐内容过于相似,缺乏多样性。
MMR 会在每一步选择相关性高且与已选项相似度低的内容,从而在确保推荐结果与用户兴趣相关的同时,增加推荐结果的多样性。这种方法能有效避免推荐结果中的重复性,提高用户体验,使得推荐内容更丰富多样。
1. 基础算法
精排给

将已经选择的内容集合记为
其中:
-
表示内容 和已选择的内容的相似度,可以理解为内容 的多样性分数,取负号是为了让相似度越小,分数越高。 -
是一个超参数,用来调节精排分数和多样性分数的权重。 越大,越偏向于选择精排分数高的内容, 越小,越偏向于选择多样性高的内容。
基础 Maximal Marginal Relevance(MMR)算法的步骤如下:
-
已选择的内容集合
,未选择的内容集合 -
选择精排分数最高的内容
,将其移动到 中 -
做
次循环:a. 计算集合
中每个内容 的 Marginal Relevance 分数b. 选择 Marginal Relevance 分数最高的内容
,将其移动到 中
算法执行完毕后,
下面是一个简单的 Python 代码实现,用 26 个大写字母代替待推荐的内容,最开始时随机为他们赋一个精排分数(相关性分数)。
import random
import string
# Mapping of characters to scores
score = {c: random.random() for c in string.ascii_uppercase}
# Print the ranked list
sorted_items = sorted(score.items(), key=lambda x: x[1], reverse=True)
for pos, item in enumerate(sorted_items, 1):
print(pos, item[0], item[1])
同时定义不同字母之间的相似度(多样性分数)为两个字母的 ASCII 值的差的绝对值除以 26。
def similarity(a: str, b: str) -> float:
"""
Calculate the similarity between two characters.
Args:
a (str): The first character.
b (str): The second character.
Returns:
float: The similarity between the two characters, ranging from 0 to 1.
"""
# Calculate the absolute difference between the ASCII values of the two characters
diff = abs(ord(a) - ord(b))
# Calculate the similarity by dividing the difference by the maximum possible difference
similarity_score = 1 - diff / 26
return similarity_score
则基础 MMR 算法可以实现如下(假设最终需要输出
def rank(theta=0.7):
"""
Rank items using a score-based algorithm.
Args:
theta (float): The weight for the item's score.
Returns:
list: The ranked list of items.
"""
selected_items = []
candidate_items = list(score.keys())
for pos in range(10):
if pos == 0:
# Select the item with the highest score as the first item
item = max(candidate_items, key=lambda x: score[x])
selected_items.append(item)
candidate_items.remove(item)
continue
# Calculate the margin relevance for each candidate item
margin_relevance = {
i: theta * score[i] - (1 - theta) * max(similarity(i, j) for j in selected_items)
for i in candidate_items
}
# Select the item with the highest margin relevance
item = max(candidate_items, key=lambda x: margin_relevance[x])
selected_items.append(item)
candidate_items.remove(item)
return selected_items
2. 滑动窗口
标准的 MMR 算法可以记作:
在实际操作中可能存在如下问题:
-
已选中的内容越多(即集合
越大),越难找到内容 ,使得 和 中的内容都不相似。 -
设
的取值范围是 。当 很大时,多样性分数 总是约等于 1,导致 MMR 算法失效。
解决方案: 设置一个滑动窗口

滑动窗口的 MMR 算法可以记作:
基于滑动窗口的 MMR 算法可以有效解决多样性评分失效的问题,提高推荐结果的多样性,下方是一个简单的 Python 代码实现:
def rank_sliding_window(theta=0.7, window=4):
"""
Rank items using a sliding window algorithm.
Args:
theta (float): The weight for the item's score.
window (int): The size of the sliding window.
Returns:
list: The ranked list of items.
"""
selected_items = []
candidate_items = list(score.keys())
for pos in range(10):
if pos == 0:
# Select the item with the highest score as the first item
item = max(candidate_items, key=lambda x: score[x])
selected_items.append(item)
candidate_items.remove(item)
continue
# Calculate the margin relevance for each candidate item
mr = {
i: theta * score[i]
- (1 - theta) * max(similarity(i, j) for j in selected_items[-window:])
for i in candidate_items
}
# Select the item with the highest margin relevance
item = max(candidate_items, key=lambda x: mr[x])
selected_items.append(item)
candidate_items.remove(item)
return selected_items
3. 结合业务规则
重排除了要考虑多样性之外,有时候需要同时满足一些业务规则。以下是在 UGC 内容社区推荐场景下的一些规则举例:
-
规则 1: 一次曝光中,最多同时出现
个同类型物料这里的物料“类型”指的是:图文物料 或 视频物料。例如当
时,最多连续出 5 个图文/视频物料。如果排在第 到 的都为视频物料,那么第 个物料必须为图文物料。 -
规则 2: 每
个物料最多出现 1 个运营推广物料运营推广物料的精排分会乘以大于 1 的系数(boost),帮助这类物料获得更多曝光。为了防止 boost 影响用户体验,限制每
个物料最多出现 1 个运营推广物料。如果排第 位的是运营推广物料,那么排 到 的不能是运营推广物料。 -
规则 3: 前
个物料最多出现 个带电商内容的物料排名前
的物料最容易被看到,对用户体验最重要(一般 top 4 为首屏)。例如,限制:前 个物料最多出现 个带电商内容的物料,前 个物料最多出现 个带电商内容的物料。
接下来介绍如何将业务规则融入到前面介绍的 MMR 算法中。
根据上文的介绍,MMR 算法每一轮都会从候选集

下方是一个简单的 Python 代码实现:
def rank_sliding_window_with_rule(theta=0.7, window=4):
"""
Rank items using a sliding window algorithm and a filtering rule.
Args:
theta (float): The weight for the item's score.
window (int): The size of the sliding window.
Returns:
list: The ranked list of items.
"""
selected_items = []
candidate_items = list(score.keys())
for pos in range(10):
# If it's the first position, select the item with the highest score
if pos == 0:
item = max(candidate_items, key=lambda x: score[x])
selected_items.append(item)
candidate_items.remove(item)
continue
# Apply the filtering rule to exclude items that do not meet the condition
new_R = [i for i in candidate_items if score[i] > 0.5]
# !NOTE: The new candidate set may be empty after filtering
if not new_R:
# TODO: Add downgraded items to the candidate set
...
# Calculate the margin relevance for each candidate item
mr = {
i: theta * score[i]
- (1 - theta) * max(similarity(i, j) for j in selected_items[-window:])
for i in new_R
}
# Select the item with the highest margin relevance
item = max(new_R, key=lambda x: mr[x])
selected_items.append(item)
candidate_items.remove(item)
return selected_items
4. 参考资料
- https://github.com/wangshusen/RecommenderSystem(本文图片均来自此repo)
Enjoy Reading This Article?
Here are some more articles you might like to read next: