推荐系统公平性:用GAN去除用户敏感信息
1 论文创新点
这篇博客我们介绍论文《Learning Fair Representations for Recommendation: A Graph-based Perspective》[1]。该论文有两个要点,其一是使用生成对抗网络(GAN)训练的滤波器对原始的用户-物品embeddings向量进行转换,以除去用户的敏感信息(该论文假定原始嵌入算法不可修改,只能在已经生成的embeddings向量上做转换);其二是在GAN的优化目标函数(被称为价值函数)中加入用户-物品二分图的信息,以充分利用用户和物品的关系。除此之外,该论文通篇都散发着表示学习(representation learning)[2] 思想的光辉。
2 关于GAN的背景知识
我们先根据Goodfellow提出GAN的原始论文《Generative Adversarial Nets》[3]来对GAN网络进行复习。
GAN的主要思想如下:给定样例数据\(\textbf{x}\),我们想学习得到数据的生成分布\(p_{data}(\textbf{x})\)。我们定义一个先验噪声变量\(p_{\textbf{z}}(\textbf{z})\),再定义生成器\(G(\textbf{z};\theta_{g})\)根据噪声数据\(z\)产生“伪造”样本,这里\(G\)可以是一个由多层感知机(MLP)表示的可微函数,参数为\(\theta_{g}\)。我们训练判别器\(D(x; \theta_{d})\)指示样本\(x\)是真实训练样本而不是“伪造”样本的概率。
我们训练判别器\(D\)极大化\(\log D(x)\)和\(log(1-D(G(\textbf{z})))\),以期求“存真去伪”;同时训练生成器\(G\)来极小化\(\log(1-D(G(\textbf{z})))\),以期求“以假乱真”。也就是说,\(D\)和\(G\)进行双人的极小极大(minimax)博弈,价值函数为:
接下来是训练部分,我们设置\(k\)轮迭代。设生成器产生的样本分布为\(p_{g}\),我们假定模型会逐渐收敛到全局最优点,即\(p_{g}=p_{data}\)。每轮迭代我们从噪声先验\(p_{\textbf{z}}(\textbf{z})\)采\(m\)个噪声样本{ \(\textbf{z}^{(1)},\ldots \textbf{z}^{(m)}\) },从数据生成分布\(p_{data}(\textbf{x})\)中采\(m\)个小批量(minibatch)样例\(\{\textbf{x}_{(1)},\ldots \textbf{x}_{(m)}\}\)。
然后我们按照随机梯度上升的方式更新判别器参数\(\theta_{d}\):
然后再次从噪声先验\(p_{\textbf{z}}(\textbf{z})\)采m个小批量噪声样本{ \(\textbf{z}^{(1)},\ldots \textbf{z}^{(m)}\) },并按照随机梯度下降的方式更新生成器的参数\(\theta_{f}\):
具体的基于梯度的参数更新法则不限,可以使用任何标准的基于梯度的学习法则,原始论文中用的momentum优化算法(实验中用的Adam优化算法)。
3 论文技术细节
论文采用的是Lastfm-360K数据集,该数据集包含用户-物品图结构的邻接矩阵。因为该算法主要用户和物品的embeddings向量上进行转换,故论文在实验中采用的是直接从网上下载经过图卷积网络得到的用户和物品的embeddings向量。用户和物品的embeddings向量都以.npy
矩阵格式存储,大小分别为1.5MB和1MB。
接下来我们来看属性滤波器(做为生成器)和判别器部分。属性过滤器论文直接采用多层感知机(MLP)进行实现。该MLP的输入维度和输出维度都是用户的embeddings向量的维度。其输出可以理解为属性滤波器力求“伪造”的不含敏感属性的用户embeddings,以“骗过”判别器。论文这里定义了两个隐藏层,每个隐藏层的激活函数采用LeakyReLU激活函数。LeakyReLU实在ReLU激活函数之上的改进,它在负输入值段的函数梯度\(\lambda\)是一个取自连续性均匀分布\(U(l, u)\)概率模型的随机变量,即
其中\(\lambda\sim U(l, u)\),\(l<u\),且\(l,u \in [0,1)\)。这样可以保证激活函数在负输入段也有梯度,可以有效避免梯度消失问题。属性滤波器的核心网络架构如下:
self.net = nn.Sequential(
nn.Linear(self.embed_dim, int(self.embed_dim*2), bias=True),
nn.LeakyReLU(0.2,inplace=True),
nn.Linear(int(self.embed_dim*2), self.embed_dim, bias=True),
nn.LeakyReLU(0.2,inplace=True),
nn.Linear(self.embed_dim, self.embed_dim , bias=True),
)
在判别器方面,由于需要训练出三个不同的属性滤波器,分别对用户的性别(gender)、年龄(age)、职业(occupation)这三种敏感属性进行滤波,所以论文分别定义了性别判别器、年龄判别器、职业判别器三种网络,以训练三种不同的属性滤波器。判别器也采用MLP实现,其输入维度维度为用户embeddings向量的维度,输出维度是一个softmax概率分布,指示样本属于属性空间中各类别的概率,据此来判定属性滤波器所生成滤波后样本的质量。
设输入的用户或物品的embeddins向量为\(\textbf{e}_{i}\),生成器为\(\mathcal{F}\),判别器为\(\mathcal{D}\),基于此定义第\(k\)个滤波器在第\(i\)个用户向量的输出是\(\mathcal{F}(\textbf{e}_{i})\)。\(\mathcal{F}(\textbf{e}_{i})\)由调用滤波器类的forward方法返回。判别器的核心网络架构如下,以用户性别属性判别器为例:(注:softmax()
函数在forward()
方法中调用)
self.net = nn.Sequential(
nn.Linear(self.embed_dim, int(self.embed_dim/4), bias=True),
nn.LeakyReLU(0.2,inplace=True),
nn.Linear(int(self.embed_dim/4), int(self.embed_dim/8), bias=True),
nn.LeakyReLU(0.2,inplace=True),
nn.Linear(int(self.embed_dim /8), self.out_dim , bias=True),
nn.LeakyReLU(0.2,inplace=True),
nn.Linear(self.out_dim, self.out_dim , bias=True)
)
在论文的实现代码中作者定义了InforMax
类做为GAN模型的总体架构,该类的主要目的是计算属性滤波器和判别器的价值函数。InforMax
类中包含做为属性滤波器的三个Filter(对于用户的性别、年龄、性格)和对应的三个做为判别器的Discriminator。最终对于嵌入向量\(e_{i}\),计算其滤波后的向量为\(f_{i}=\sum_{k=1}^{K}\mathcal{F}^{k}(e_{i})/K\)。然后再考虑到用户-物品图结构的信息,可以定义价值函数\(V_{R}\)表示为训练样本数据中rating(用户的评分等级)分布的对数似然,价值函数\(V_{G}\)表示成所预测的属性分布的对数似然,如下所示:
这个类的forward()
方法中同时返回GAN中的价值函数\(V_{G}\)和价值函数\(V_{R}\)。核心部分代码如下:
w_f=[1,2,1]
d_loss = (
d_mask[0]*d_loss1*w_f[0]+ d_mask[1]*d_loss2*w_f[1]
+ d_mask[2]*d_loss3*w_f[2])
d_loss_local = (
d_mask[0]*d_loss1_local*w_f[0]+ d_mask[1]*d_loss2_local*w_f[1]
+ d_mask[2]*d_loss3_local*w_f[2])
#L_R preference prediction loss.
user_person_f = user_f2_tmp
item_person_f = item_f2_tmp
user_b = F.embedding(user_batch,user_person_f)
item_b = F.embedding(item_batch,item_person_f)
prediction = (user_b * item_b).sum(dim=-1)
loss_part = self.mse_loss(prediction,rating_batch)
l2_regulization = 0.01*(user_b**2+item_b**2).sum(dim=-1)
loss_p_square=loss_part+l2_regulization.mean()
d_loss_all= 1*(d_loss+1*d_loss_local)
g_loss_all= 10*loss_p_square
g_d_loss_all = - 1*d_loss_all
#the loss needs to be returned.
d_g_loss = [d_loss_all,g_loss_all,g_d_loss_all]
最后我们来看模型训练模块。论文定义了生成器\(\mathcal{F}\)和判别器\(\mathcal{D}\)对价值函数\(V(\mathcal{F}, \mathcal{D})\)的极大极小化:
这里\(\lambda\)是一个平衡参数来平衡\(V_{R}\)和\(V_{G}\)这两个价值函数。如果\(\lambda\)等于0,那么我们公平性的需求就消失了。
代码实现中作者定义了两个Adam优化器d_optimizer
和f_optimizer
(分别用于极小和极大优化),并采用了两次遍历训练集的方式来完成该优化目标:
-
第一次遍历的每一轮迭代,调用
InforMax
模型的forward()
方法取得属性滤波器需要优化的价值函数部分,并调用backward()
方法方向传播计算梯度后,用Adam优化器分别对属性滤波器进行优化。 -
第二次遍历的每一轮迭代,调用
Informax
模型的forward()
方法取得判别器需要优化的价值函数部分,并调用backward()
方法反向传播计算梯度后,调用Adam优化器对判别器进行优化。
这样,就采取了两个优化器分别往正梯度和负梯度方向对判别器和属性滤波器进行优化。核心部分代码如下:
loss_current = [[],[],[],[]]
for user_batch, rating_batch, item_batch in train_loader:
user_batch = user_batch.cuda()
rating_batch = rating_batch.cuda()
item_batch = item_batch.cuda()
d_g_l_get = model(copy.deepcopy(pos_adj),user_batch,rating_batch,item_batch)
d_l,f_l,_ = d_g_l_get
loss_current[2].append(d_l.item())
d_optimizer.zero_grad()
d_l.backward()
d_optimizer.step()
for user_batch, rating_batch, item_batch in train_loader:
user_batch = user_batch.cuda()
rating_batch = rating_batch.cuda()
item_batch = item_batch.cuda()
d_g_l_get = model(copy.deepcopy(pos_adj),user_batch,rating_batch,item_batch)
_,f_l,d_l = d_g_l_get
loss_current[0].append(f_l.item())
f_optimizer.zero_grad()
f_l.backward()
f_optimizer.step()
4 实验结果
以下是论文的实验部分,我们分数据集描述、模型评估策略、超参数调整、测试结果记录四个部分来展开叙述:
数据集描述
MovieLens-1M 是一个推荐系统的基准数据集,这个数据集包括6040个用户对于近4000部电影的近1百万条评分信息。用户具有三个类别型属性,包括性别(2种类别),年龄(7种类别)和职位(21个类别)。效仿之前的基于公平性的推荐系统,论文在测试时按照9:1的比例划分训练集和测试集。
Lastfm-360K是一个音乐推荐数据集,包括了来自音乐网站Last.fm的用户对音乐制作人的评分。这个数据集包括大约36万个用户对29万个音乐制作人的1700万条评分信息。论文将用户播放音乐的次数做为用户的评分值。因为评分的值可能在一个大的范围内,需要先对进行log对数变换,然后将评分规范化到1和5的范围之间。用户有着自己的用户画像,其信息包括性别属性(2种类别)、年龄属性(7种类别)。和许多经典推荐系统的数据划分策略一样,论文将训练集、验证集、测试集划分为7:1:2。
模型评估策略
为了评估推荐系统的表现,论文使用均方根误差(RMSE)。为了有效度量该算法的公平性,论文计算了20%测试用户的公平性表现。
因为二值型特征(比如性别)在各数据集上都不平衡,有着70%的男性和30%的女性,论文使用AUC度量来度量二分类的表现。对于多值型属性,则使用micro-averaged F1来测量。
AUC或者F1能够被用于测量是否敏感的性别信息在表示学习的过程中被暴露。这个这个分类的度量值越小,则说明系统的公平性越好,泄露的敏感信息更少。
因为这篇论文中的模型是“上游模型不可知的(agnostic)”(也就是说为了增强其泛用性,将上游模型看做黑盒模型来处理),并且能够被应用于许多多属性的推荐场景,故论文按照不同的模型评价设定设计了许多测试。
首先,论文选择了目前最先进的(state-of-the-art)图卷积网络(GCN)推荐模型做为基准(baseline)模型。因为基于GCN的推荐模型最开始被设计成基于排名的损失函数,论文将其修改为基于评分的损失函数,并在图卷积的过程中添加了更详细的评分值来方便其设置。
超参数调整
在实际的模型实现中,论文选择多层感知机(MLP)做为每个过滤器和判别器具体的架构,过滤器的embedding size被设置为\(D=64\)。
对于MovieLens数据集,每个过滤器网络有3层,其中隐藏层的维度分别是128和64。判别器有4层,隐藏层的维度分别是16和8和4。
对于Lastfm-360K数据集,每个过滤器网络有4层,隐藏层维度分别为128,64,32。每个判别器
有4层,隐藏层维度分别是16,8和4。
论文使用LeakyReLU做为激活函数。在MovieLens数据集上将平衡参数\(\lambda\)设置为0.1,在Lastfm-360K数据集上将平衡参数设置为0.2。所有目标函数中的参数都是可微的。论文使用Adam优化器,将初始学习率调整到0.005。
测试结果记录
下面的两张表是模型的测试结果。在测试的过程中,为了简单起见,论文采取了简单的自中心图结构表示,并用一阶加权集成。根据模型的测试结果可以看出,GCN模型如果直接考虑敏感信息过滤器会将推荐系统的表现降低5%到10%,因为对于打分有用的隐向量维度可能会暴露敏感信息。
在MovieLens-1M数据集上的训练过程如下:
ga0--train-- 383.52958726882935
epoch:1 time:383.5 train_loss f:-192.2718 d:19.5616val_loss f:-192.8258 d:19.2919
val_rmse:0.9045 test_rmse:0.895
train data is end
ga0--train-- 360.9422023296356
epoch:2 time:360.9 train_loss f:-191.72 d:19.4652val_loss f:-200.0517 d:20.0125
val_rmse:0.7063 test_rmse:0.6894
train data is end
ga0--train-- 363.16574025154114
epoch:3 time:363.2 train_loss f:-200.8263 d:19.2499val_loss f:-203.8944 d:20.4799
val_rmse:2.8324 test_rmse:2.8068
train data is end
ga0--train-- 355.92360401153564
epoch:4 time:355.9 train_loss f:-189.3184 d:19.3741val_loss f:-180.7054 d:18.0778
ga0 clf_age/4
no model save path
val_rmse:0.7821 test_rmse:0.7787age f1:0.4683 0.4683 0.4683 0.4683
train data is end
ga0--train-- 356.7487156391144
epoch:5 time:356.7 train_loss f:-198.0661 d:19.8271val_loss f:-190.4692 d:19.0536
ga0 clf_age/5
no model save path
val_rmse:0.7407 test_rmse:0.7326age f1:0.469 0.469 0.469 0.469
对于不同的自中心结构的不同摘要网络在数据集MovieLens-1M上的表现如下所示:
敏感属性 | RMSE | AUC/F1 |
---|---|---|
性别 | 0.8553 | 0.8553 |
年龄 | 0.8553 | 0.3948 |
职业 | 0.8553 | 0.1556 |
在Lastfm-360K数据集上的训练过程如下:
ga0
--------training processing-------
train data is end
ga0--train-- 380.44703578948975
epoch:0 time:380.4 train_loss f:-200.3726 d:19.7304val_loss f:-193.2152 d:19.3319
val_rmse:0.9439 test_rmse:0.9304
train data is end
ga0--train-- 383.52958726882935
epoch:1 time:383.5 train_loss f:-192.2718 d:19.5616val_loss f:-192.8258 d:19.2919
val_rmse:0.9045 test_rmse:0.895
train data is end
ga0--train-- 360.9422023296356
epoch:2 time:360.9 train_loss f:-191.72 d:19.4652val_loss f:-200.0517 d:20.0125
val_rmse:0.7063 test_rmse:0.6894
train data is end
ga0--train-- 363.16574025154114
epoch:3 time:363.2 train_loss f:-200.8263 d:19.2499val_loss f:-203.8944 d:20.4799
val_rmse:2.8324 test_rmse:2.8068
train data is end
ga0--train-- 355.92360401153564
epoch:4 time:355.9 train_loss f:-189.3184 d:19.3741val_loss f:-180.7054 d:18.0778
ga0 clf_age/4
no model save path
val_rmse:0.7821 test_rmse:0.7787age f1:0.4683 0.4683 0.4683 0.4683
train data is end
以下是在Lastfm-360K上的表现:
敏感属性 | RMSE | AUC/F1 |
---|---|---|
性别 | 0.7358 | 0.5642 |
年龄 | 0.7358 | 0.4953 |
5 总结
在该论文的阅读过程中,有一个难点是搞清楚论文中每一个变量的含义。模型的关键部分,也就是价值函数部分,原始论文写得十分含糊,我也是反复来回咀嚼了几次原始论文,并在网上查询了其他人对论文的解读(这里又得感谢一下人大赵鑫老师AIBox实验室的博客文章),然后才充分理解了论文想表达的价值函数的定义。不过我相信我在这个地方花费的功夫是值得的,因为如果一个深度学习模型的优化函数都不对,那么显然它就无法完成我们的任务需求了,甚至南辕北辙。
在具体的代码实现部分,该篇论文的模型较为复杂,实现的工程量比较大。目前在代码阅读的过程中遇到的一大难点在于理清论文的逻辑思路和各模型组分之间的从属、先后关系,这要求我们充分把握整个模型的架构,然后再进行模块化的拆分理解。
参考文献
- [1] Wu L, Chen L, Shao P, et al. Learning Fair Representations for Recommendation: A Graph-based Perspective[C]//Proceedings of the Web Conference 2021. 2021: 2198-2208.
- [2] Goodfellow I, Bengio Y, Courville A. Deep learning[M]. MIT press, 2016.
- [3] Goodfellow I, Pouget-Abadie J, Mirza M, et al. Generative adversarial nets[J]. Advances in neural information processing systems, 2014, 27.