MindSpore 首发:隐私保护的 Bandit 算法,实现电影推荐
MindSpore 首发:隐私保护的 Bandit 算法,实现电影推荐

老虎机(Bandit)问题是强化学习中一类重要的问题,由于它定义简洁且有大量的理论分析,因此被广泛应用于新闻推荐,医学试验等实际场景中。随着人类进入大数据时代,用户对自身数据的隐私性日益重视,这对机器学习算法的设计提出了新的挑战。为了在保护隐私的情况下解决 Bandit 这一经典问题,北京大学和华为诺亚方舟实验室联合提出了基于本地差分隐私的 Bandit 算法,论文已被 NeurIPS 2020 录用,代码已基于 MindSpore 开源首发。
本文将先简单介绍 Bandit 问题和本地差分隐私的相关背景,然后介绍基于本地差分隐私的 Bandit 算法,最后通过一个简单的电影推荐场景来验证 LDP LinUCB 算法。

大家都有过这样的经历,在我们刷微博或是读新闻的时候,经常会看到一些系统推荐的内容,这些推荐的内容是根据用户对过往推荐内容的点击情况以及阅读时长等反馈来产生的。在这个过程里,系统事先不知道用户对各种内容的偏好,通过不断地与用户进行交互(推荐内容 — 得到反馈),来慢慢学习到用户的偏好特征,不断提高推荐的精准性,从而最大化用户的价值,这就是一个典型的 Bandit 问题。
Bandit 问题有 context-free 和 contextual 两种常见的设定,下面给出它们具体的数学定义。
【Context-Free Bandit】
假设给定一个动作集合
,玩家跟环境的交互过程按轮进行。在每一轮
,玩家基于之前所有的观测结果选择一个动作
去执行,然后从环境观测到一个损失值
,如此往复(有些文献中定义成 reward,那么 reward 的负数就对应此处的损失值)。我们定义累积 regret 函数为
,问题目标是设计一个算法使得累积 regret 最小。其中
既可以是 adversarial 也可以是 stochastic(只需让
,
是独立同分布的零均值噪声)。
【Contextual Bandit】
顾名思义,Contextual Bandit 这类算法在做决策时考虑了上下文的信息,因而更加适合实际的个性化推荐场景。形式化地说,在每一轮
,系统观测到当前用户
和每一个候选物品的联合特征的集合
(即上下文信息),然后基于之前所有的观测结果选择一个候选物品
(由于联合特征
和候选物品一一对应,故此处用
代替)去推荐,并观测到一个奖励值
。通常,
可以建模成
,其中
是待学习的真实参数,
是零均值噪声,
是某个形式已知的函数。同样地,我们可以定义累积 regret 函数为
,其中
,问题目标是设计一个算法使得累积 regret 最小。

传统的差分隐私技术(Differential Privacy,DP)是将用户数据集中到一个可信的数据中心,在数据中心对用户数据进行匿名化使其符合隐私保护的要求后,再分发给下游使用,我们将其称之为中心化差分隐私。但是,一个绝对可信的数据中心很难找到,因此人们提出了本地差分隐私技术(Local Differential Privacy,LDP),它直接在客户端进行数据的隐私化处理后再提交给数据中心,彻底杜绝了数据中心泄露用户隐私的可能。

本地差分隐私的定义:假设
是正实数,算法
被称为满足
-LDP,如果对任意两个数据
和任意子集
,
。特别地,如果
满足
-LDP,我们简称为
-LDP。
可以看到,当
和
越小,说明
和
相似性越高,隐私保护程度也越好。 通常来说,对数据加噪声可以满足 LDP,两种常用的加噪声的方法:高斯噪声和拉普拉斯噪声。 给定一个函数
,
高斯机制定义为
,其中

拉普拉斯机制定义为
,其中
。
可以证明,高斯机制能满足
-LDP 性质,拉普拉斯机制能满足
-LDP 性质。因此,下文主要考虑
-LDP 性质,将算法中的高斯机制替换成拉普拉斯机制可以得到对应的
-LDP 性质。

Context-Free Bandit
假定我们有一个非隐私保护的 Bandit 算法
,根据高斯机制,如果直接在每一轮回传的损失值
上注入噪声,那么该算法就可以满足
-LDP 性质。假设
是有界的,即
,那么满足
-LDP 的 Bandit 算法可以写成如下形式:

我们可以证明,上述算法有如下的性能:
【定理】 假设非隐私保护的 Bandit 算法
的 regret 上界是
,那么算法 1 有如下理论保证:
,有
根据上述定理,我们只需将任一非隐私保护的算法按照算法 1 进行改造,就立即可以得到对应的隐私保护版本的算法,且它的累积 regret 的理论上界和非隐私算法只相差一个
因子,因此算法 1 具有很强的通用性。我们将损失函数满足不同凸性和光滑性条件下的 regret 简单罗列如下:

上述算法和结论可以扩展到每一轮能观测多个动作损失值的情况,感兴趣的可以参见论文(https://arxiv.org/abs/2006.00701)。
Contextual Bandit
这里我们只介绍一类最简单的线性的情况:
函数是恒等变换,即
。LinUCB 是一个解决这种 linear contextual bandit 的经典算法。在 LinUCB 算法中,每一轮需要传输的是更新量是
和
,我们通过给这些变量加高斯噪声就可以保证
-LDP,我们称之为 LDP LinUCB 算法,具体过程如下:

【定理】 依照至少为
的概率,LDP LinUCB 算法的 regret 满足如

上述算法和结论可以扩展到 gg 不是恒等变换的情况,感兴趣的可以参见论文(https://arxiv.org/abs/2006.00701)。

MovieLens 是一个包含多个用户对多部电影评分的公开数据集,我们可以用它来模拟电影推荐。我们通过src/dataset.py 来构建环境:我们从数据集中抽取一部分有电影评分数据的用户,然后将评分矩阵通过 SVD 分解来补全评分数据,并将分数归一化到[−1,+1]。在每次交互的时候,系统随机抽取一个用户,推荐算法获得特征,并选择一部电影进行推荐,MovieLensEnv会在打分矩阵中查询该用户对电影对评分并返回,从而模拟用户给电影打分。
class MovieLensEnv:
def observation(self):
"""random select a user and return its feature."""
sampled_user = random.randint(0, self._data_matrix.shape[0] - 1)
self._current_user = sampled_user
return Tensor(self._feature[sampled_user])
def current_rewards(self):
"""rewards for current user."""
return Tensor(self._approx_ratings_matrix[self._current_user])
LDP LinUCB 的算法位于src/linucb.py,参数如下,分别对应算法中的
:
import mindspore.nn as nn
class LinUCB(nn.Cell):
def __init__(self, context_dim, epsilon=100, delta=0.1, alpha=0.1, T=1e5):
...
# Parameters
self._V = Tensor(np.zeros((context_dim, context_dim), dtype=np.float32))
self._u = Tensor(np.zeros((context_dim,), dtype=np.float32))
self._theta = Tensor(np.zeros((context_dim,), dtype=np.float32))
每来一个用户,LDP LinUCB 算法根据用户和电影的联合特征x基于当前的模型来选择最优的电影a_max做推荐,并传输带噪声的更新量:
(算法中的
)
import mindspore.nn as nn
class LinUCB(nn.Cell):
...
def construct(self, x, rewards):
"""compute the perturbed gradients for parameters."""
# Choose optimal action
x_transpose = self.transpose(x, (1, 0))
scores_a = self.squeeze(self.matmul(x, self.expand_dims(self._theta, 1)))
scores_b = x_transpose * self.matmul(self._Vc_inv, x_transpose)
scores_b = self.reduce_sum(scores_b, 0)
scores = scores_a + self._beta * scores_b
max_a = self.argmax(scores)
xa = x[max_a]
xaxat = self.matmul(self.expand_dims(xa, -1), self.expand_dims(xa, 0))
y = rewards[max_a]
y_max = self.reduce_max(rewards)
y_diff = y_max - y
self._current_regret = float(y_diff.asnumpy())
self._regret += self._current_regret
# Prepare noise
B = np.random.normal(0, self._sigma, size=xaxat.shape)
B = np.triu(B)
B += B.transpose() - np.diag(B.diagonal())
B = Tensor(B.astype(np.float32))
Xi = np.random.normal(0, self._sigma, size=xa.shape)
Xi = Tensor(Xi.astype(np.float32))
# Add noise and update parameters
return xaxat + B, xa * y + Xi, max_a
系统收到更新量之后,更新模型参数如下:
import mindspore.nn as nn
class LinUCB(nn.Cell):
...
def server_update(self, xaxat, xay):
"""update parameters with perturbed gradients."""
self._V += xaxat
self._u += xay
self.inverse_matrix()
theta = self.matmul(self._Vc_inv, self.expand_dims(self._u, 1))
self._theta = self.squeeze(theta)

我们测试不同的 \varepsilonε 对累积 regret 对影响:
- x 轴:交互轮数
- y 轴:累积 regret

可以看到,当固定隐私变量
的时候,累积 regret 随着时间增加得越来越缓慢,意味着推荐的电影和用户最喜欢的电影越来越接近,即推荐变得越来越精准。
同时,随着
的减小,隐私保护程度越好,但性能也会有所下降。由于测试用的数据量较小,因此此处
设定的比较大。在真实商用场景中的数据量会远远大于此处模拟用的数据量,届时可以把
定到 10以下。
接着我们测试了 LDP LinUCB 和非隐私保护 LinUCB 的累积 regret 的比较(LinUCB 的累积 regret 是
):
- x 轴:交互轮数
- y 轴:累积 regret 除以


可以看到 LDP LinUCB 的累积 regret 增长速度和近似,说明 LDP LinUCB 近乎是最优的算法。这也提示我们,论文中给出的 LDP LinUCB 的理论上界
也许可以进一步改进到
。
相关模型代码已上线 MindSpore Model Zoo:
https://gitee.com/mindspore/mindspore/tree/master
感兴趣的可自行体验。

1. Kai Zheng, Tianle Cai, Weiran Huang, Zhenguo Li, Liwei Wang. "Locally Differentially Private (Contextual) Bandits Learning." Advances in Neural Information Processing Systems. 2020.
2. LDP LinUCB 代码:
https://gitee.com/mindspore/mindspore/tree/master
MindSpore两日集训营火热报名中!
本期特邀6位MindSpore资深工程师为大家直播授课,参加集训的同学可通过使用MindSpore 实现一键部署在线推理服务、了解华为智慧终端的黑科技是什么、学习高效低功耗的单节点数据缓存模块、掌握快速定位模型精度问题的方法,讲解过后给大家留出时间消化内容、做作业,最后进行直播答疑,授课期间的任何疑问均能得到及时反馈!
扫码报名↓

*记得备注:集训
MindSpore官方资料
GitHub : https://github.com/mindspore-ai/mindspore
Gitee:https : //gitee.com/mindspore/mindspore
官方QQ群 : 871543426
扫描下方二维码加入MindSpore项目
