《IRGAN》阅读笔记

IRGAN 论文阅读笔记

介绍

本文中,作者将IR领域的研究分为两类:

第一类是经典思维流派,它的主要思想是描述用户的 query 和 document 之间的关联分布,并通过建立统计学习模型得到综合打分,给出合适的检索结果。用符号表示就是 $p \to d$.

第二类是现代思维流派,它的主要思想是将IR看作一个分类(判别)问题,通过同时考虑 query 和 document 的feature,然后预测它们的评分(关联度),最后进行排序,相当于一个Top-N问题。用符号表示就是 $q + d \to r$.

模型

由于本人是做推荐的,接下来的解释,都会从推荐系统的角度进行解读。

整个模型的目标函数

Overall-formula

判别模型

它的作用就是尽可能的区别已观测样本和未观测样本。所以优化的目标就是最大化已观测样本和生成样本之间的差距。当然这些未观测样本中可能有正样本,可能有负样本。

discriminator

对于判别模型来说,我们在训练它的时候,会将正例、负例,一起喂给模型,多次学习之后。理论上,模型会对于我们选择的正例、负例有一个很好的区分度。

举个例子,训练的数据格式,如下所示:

1
2
3
4
5
users = [1, 1, 1, 1] # 表示用户1
items = [34, 33, 75, 53] #表示各种物品
labels = [1, 0, 0, 1] # 表示该用户对这个物品的喜好程度。
socre = discriminator(users, items)
binary_cross_entropy_with_logits(socre, labels)

最后,判别模型的 pytorch 表示为:

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
# Discriminator
# 先根据 IRGAN 原始论文,依旧使用 矩阵分解格式
class Discriminator(nn.Module):
def __init__(self, itemNum, userNum, emb_dim, alpha=0.1):
super(Discriminator, self).__init__()
self.itemNum = itemNum
self.userNum = userNum
self.emb_dim = emb_dim
self.alpha = alpha
# embedding
self.u_embeds = nn.Embedding(userNum, emb_dim)
self.i_embeds = nn.Embedding(itemNum, emb_dim)
self.i_bias = Parameter(torch.Tensor(itemNum))

self.reset_parameters()

def forward(self, user, item, label):
item_v = self.i_embeds(item) # b x e
user_v = self.u_embeds(user) # b x e
ibias = self.i_bias[item]
# IRGAN中用的是 elements wise product, 我这里尝试使用 inner product
pre_logits = (torch.sum(torch.mm(user_v, item_v.t()), 1) + ibias) # bxe * exb -> b
# 直接在模型中把 loss 计算得了...
loss = F.binary_cross_entropy_with_logits(pre_logits, label) + self.alpha * (torch.norm(item_v) + torch.norm(user_v) + torch.norm(ibias))
return loss

# 计算 reward
def getReward(self, user, item):
# 获得reward的过程。
base_line = 0.5
reward_logits = torch.mm(self.u_embeds(user), self.i_embeds(item).t()) + self.i_bias[item] # 1xe * exb -> 1xb

reward = 2 * (torch.sigmoid(reward_logits) - base_line)
reward = reward.view(-1, 1)
return reward

def reset_parameters(self):
self.i_bias.data.uniform_(-1, 1)

生成模型

跟普通的GAN不同,在IRGAN中,我们会建立一个候选池,然后,生成模型所生成的 items 就是从候选池中挑选得到的。

生成模型的作用是对于给定的 user ,我们尝试从候选池中,选择最接近已观测样本分布的未观测样本。这里我们用 $P_\theta (d | u, i)$ 表示生成模型,一句话总结它的作用就是尽可能的使得 $P_\theta (d | u, i) \approx p_{true} (d | u, i)$。

从推荐系统的角度来解释,我们会先假设 user 消费过的 itme 存在某种分布$P(\theta)$,然后生成模型会帮我们选择那些 user 未消费过的,但是可能服从$P(\theta)$的item。

generator

训练生成模型的时候,由于 generator 的 target function 为 $J^G (q_n)$,我们对其求导,结果如下图所示。

policy gradient

在上述 policy gradient 公式中, $p_{\theta} (d_k | q_n, r)$ 表示的是生成模型, $f_{\phi} (d_k, q_n)$ 表示的是判别模型。

其中 $log(1 + exp(f_{\phi} (d_k, q_n)))$ 被看作 reward, 用来 update 生成模型。

[todolist: 写一篇关于 Policy gradient 的 blog]

另外,在生成模型中,还有一个 trick 是论文中没有提到的,那就是 重要性采样, 我们会通过重要性采样,来获取generator模型产生的item。

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
# Generator
class Generator(nn.Module):
def __init__(self, itemNum, userNum, emb_dim, user_pos_train):
super(Generator, self).__init__()
self.itemNum = itemNum
self.userNum = userNum
self.emb_dim = emb_dim

# user-item 对
self.user_pos_train = user_pos_train

# embedding
self.u_embeds = nn.Embedding(userNum, emb_dim)
self.i_embeds = nn.Embedding(itemNum, emb_dim)
self.i_bias = = Parameter(torch.Tensor(out_features))

self.reset_parameters()

def forward(self, user, dis):
sample_lambda = 0.2

# 获取 postive 样本的数量
pos = user_pos_train[user]

# 获取该user 对所有 item 的 rating
rating = torch.mm(self.u_embeds(user), self.i_embeds.weight.t()) + self.i_bias # 1xe * exitem -> 1xitem
exp_rating = torch.exp(rating)
prob = exp_rating / torch.sum(exp_rating) # 我们把这个看作是 generator 的 distribution
prob = prob.view(-1, 1)

# 接下来是重要性采样
pn = (1 - sample_lambda) * prob
pn[pos] += sample_lambda * 1.0 / len(pos)
sample = np.random.choice(np.arange(self.itemNum), 2 * len(pos), p=pn.data.numpy().reshape(-1))

# 调用 discrimitor 的 getReward 获取 reward
reward = dis.getReward(user, Variable(torch.from_numpy(sample).type(torch.LongTensor)))
reward = reward * prob[torch.from_numpy(sample).type(torch.LongTensor)] / pn[torch.from_numpy(sample).type(torch.LongTensor)]

# 跟discriminator一样,我们在这里面根据 loss 来 update generator

# loss of generator
i_prob = prob[torch.from_numpy(sample).type(torch.LongTensor)]
gen_loss = -torch.mean(torch.log(i_prob) * reward)

return gen_loss

def reset_parameters(self):
self.i_bias.data.uniform_(-1, 1)

[同样的,在论文提供的Movielens数据集上跑出来的结果如下所示]

个人的一些看法

将GAN的思想引入了 IR 领域,为这一领域引入了新的思想,确实是一个开山之作,但是个人感觉不足的地方在于,整个论文的没有解决一个核心问题 —— 引入GAN之后,可以解决推荐领域的什么问题。

不过新的思想的引入,也确实很牛逼,而且可以预见, GAN + RS 怕是马上就要开始了。