1塘娶、引言
信息檢索領(lǐng)域的一個(gè)重要任務(wù)就是針對用戶的一個(gè)請求query归斤,返回一組排好序的召回列表。
經(jīng)典的IR流派認(rèn)為query和document之間存在著一種生成過程刁岸,即q -> d 脏里。舉一個(gè)例子,搜索“哈登”虹曙,我們可以聯(lián)想到“保羅”迫横,“火箭”,“MVP”等等酝碳,每一個(gè)聯(lián)想出來的document有一個(gè)生成概率p(d|q)矾踱,然后根據(jù)這個(gè)生成概率進(jìn)行排序,這種模型被稱作生成模型疏哗。人們在研究生成模型的時(shí)候呛讲,設(shè)計(jì)了一系列基于query和document的特征,比方說TF-IDF,BM25贝搁。這些特征能非陈鹗希客觀的描述query和document的相關(guān)性,但沒有考慮document的質(zhì)量雷逆,用戶的反饋弦讽,pagerank等信息。
現(xiàn)代的IR流派則利用了機(jī)器學(xué)習(xí)膀哲,將query和document的特征放在一起往产,通過機(jī)器學(xué)習(xí)方法來計(jì)算query和document之間的匹配相關(guān)性: r=f(q,d)。舉個(gè)現(xiàn)實(shí)的例子某宪,我們知道“小白”更喜歡“吃雞”而不是“王者榮耀”捂齐,pointwise會(huì)優(yōu)化f(小白,吃雞)=1缩抡,f(小白奠宜,王者榮耀)=0;pairwise會(huì)優(yōu)化f(小白瞻想,吃雞)>f(小白压真,王者榮耀);listwise會(huì)考慮很多其他游戲蘑险,一起進(jìn)行優(yōu)化滴肿。機(jī)器學(xué)習(xí)的判別模型能夠很好地利用文本統(tǒng)計(jì)信息,用戶點(diǎn)擊信息等特征佃迄,但模型本身局限于標(biāo)注數(shù)據(jù)的質(zhì)量和大小泼差,模型常常會(huì)在訓(xùn)練數(shù)據(jù)上過擬合,或陷入某一個(gè)局部最優(yōu)解呵俏。
受到GAN的啟發(fā)堆缘,將生成模型和判別模型結(jié)合在一起,學(xué)者們便提出了IRGAN模型普碎。
2吼肥、IRGAN介紹
定義問題
假定我們又一些列的query{q1,...qN}并且有一系列的文檔document結(jié)合{d1,...dM},對于一個(gè)特定的query麻车,我們有一系列標(biāo)記的真實(shí)相關(guān)的文檔缀皱,但是這個(gè)數(shù)量是遠(yuǎn)遠(yuǎn)小于文檔總數(shù)量M的。query和document之間潛在的概率分布可以表示為條件概率分布ptrue(d|q,r)动猬。給定一堆從真實(shí)條件分布ptrue(d|q,r)觀察到的樣本啤斗, 我們可以定義兩種類型的IR model。
生成式檢索模型:該模型的目標(biāo)是學(xué)習(xí)pθ(d|q,r)赁咙,使其更接近于ptrue(d|q,r)钮莲。
判別式檢索模型:該模型的目標(biāo)是學(xué)習(xí)fΦ(q,d)免钻,即盡量能夠準(zhǔn)確的判別q和d的相關(guān)程度
因此,受到GAN的啟發(fā)臂痕,我們將上述的兩種IR模型結(jié)合起來做一個(gè)最大最小化的博弈:生成式模型的任務(wù)是盡可能的產(chǎn)生和query相關(guān)的document伯襟,以此來混淆判別式模型猿涨;判別式模型的任務(wù)是盡可能準(zhǔn)確區(qū)分真正相關(guān)的document和生成模型生成的document握童,因此,我們總體的目標(biāo)就是:
在上式中叛赚,生成式模型G為pθ(d|qn,r),生成式模型D對d是否與q相關(guān)進(jìn)行判定澡绩,通過下面的式子給出相關(guān)性得分:
優(yōu)化判別模型D
判別器的主要目標(biāo)是最大化我們的對數(shù)似然,即正確的區(qū)分真正相關(guān)的文檔和生成器生成的文檔俺附。最優(yōu)的參數(shù)通過下面的式子得到:
優(yōu)化生成模型G
生成器的主要目標(biāo)是產(chǎn)生能夠混淆判別器的document肥卡,判別器直接從給定的document池中選擇document。在固定判別器參數(shù)fΦ(q,d)的情況下事镣,生成器的學(xué)習(xí)目標(biāo)是(第一項(xiàng)不包含θ,因此可以省略):
我們把生成器的優(yōu)化目標(biāo)寫作JG(qn)步鉴。
由于生成的document是離散的,無法直接通過梯度下降法進(jìn)行優(yōu)化璃哟,一種通常的做法是使用強(qiáng)化學(xué)習(xí)中的策略梯度方法氛琢,我們將qn作為state,pθ(d|qn,r)作為對應(yīng)的策略随闪,而log(1+exp(fΦ(d阳似,qn))作為對應(yīng)的reward:
其中,第二步到第三步的變換利用了log函數(shù)求導(dǎo)的性質(zhì)铐伴,而在最后一步則基于采樣的document做了一個(gè)近似撮奏。
總體流程
IRGAN的整體訓(xùn)練流程如下:
Pair-wise的情況
在很多IR問題中,我們的數(shù)據(jù)是對一個(gè)query的一系列排序文檔對当宴,因?yàn)橄啾扰袛嘁粋€(gè)文檔的相關(guān)性畜吊,更容易判斷用戶對一對文檔的相對偏好(比如說通過點(diǎn)擊數(shù)據(jù),如果兩篇document同時(shí)展示給用戶户矢,用戶點(diǎn)擊了a而沒有點(diǎn)擊b定拟,則可以說明用戶對a的偏好大于對b的偏好),此外逗嫡,如果我們使用相關(guān)性進(jìn)行分級(jí)(用來表明不同文檔對同一個(gè)query的匹配程度)而不是使用是否相關(guān)青自,訓(xùn)練數(shù)據(jù)也可以自然的表示成有序的文檔對。
IRGAN在pairwise情況下是同樣適用的驱证,假設(shè)我有一堆帶標(biāo)記的document組合Rn = {<di延窜,dj>|di > dj}。生成器G的任務(wù)是盡量生成正確的排序組合來混淆判別器D抹锄,判別器D的任務(wù)是盡可能區(qū)分真正的排序組合和生成器生成的排序組合逆瑞≤伲基于下面的式子來進(jìn)行最大最小化博弈:
其中,o=<du,dv>,o'=<d'u,d'v>分別代表正確的組合和生成器生成的組合获高。而D(du,dv|q)計(jì)算公式如下:
接下來我們就來講一下生成器的生成策略哈肖。首先我們選擇一個(gè)正確的組合 <di,dj>,我們首先選取dj念秧,然后根據(jù)當(dāng)前的生成器G的策略pθ(d|q,r)淤井,選擇比dj生成概率大的dk,組成一組<dk,dj>摊趾。
有關(guān)更多的IRGAN的細(xì)節(jié)币狠,大家可以閱讀原論文,接下來砾层,我們來看一個(gè)簡單的Demo吧漩绵。
3、IRGAN的TF實(shí)現(xiàn)
本文的github代碼參考:
https://github.com/geek-ai/irgan/tree/master/item_recommendation
源代碼是python2.7版本的肛炮,修改為python3版本的代碼之后存放地址為:
https://github.com/princewen/tensorflow_practice/tree/master/recommendation/Basic-IRGAN-Demo
數(shù)據(jù)
先來說說數(shù)據(jù)吧止吐,數(shù)據(jù)用的是ml-100k的數(shù)據(jù),每一行的格式為“uid iid score"侨糟,我們把評(píng)分大于等于4分的電影作為用戶真正感興趣的電影碍扔。
Generator
對于訓(xùn)練Generator,我們需要輸入的有三部分:uid粟害,iid以及reward蕴忆,我們首先定義user和item的embedding,然后獲取uid和iid的item悲幅。同時(shí)套鹅,我們這里還給每個(gè)item定義了一個(gè)特征值:
self.user_embeddings = tf.Variable(tf.random_uniform([self.userNum,self.emb_dim],
minval=-initdelta,maxval=self.initdelta,
dtype =tf.float32))
self.item_embeddings = tf.Variable(tf.random_uniform([self.itemNum,self.emb_dim],
minval=-initdelta,maxval=self.initdelta,
dtype=tf.float32))
self.item_bias = tf.Variable(tf.zeros([self.itemNum]))
self.u = tf.placeholder(tf.int32)
self.i = tf.placeholder(tf.int32)
self.reward = tf.placeholder(tf.float32)
self.u_embedding = tf.nn.embedding_lookup(self.user_embeddings,self.u)
self.i_embedding = tf.nn.embedding_lookup(self.item_embeddings,self.i)
self.i_bias = tf.gather(self.item_bias,self.i)
接下來,我們需要計(jì)算傳入的user和item之間的相關(guān)性汰具,并通過傳入的reward來更新我們的策略:
self.all_logits = tf.reduce_sum(tf.multiply(self.u_embedding,self.item_embeddings),1) + self.item_bias
self.i_prob = tf.gather(
tf.reshape(tf.nn.softmax(tf.reshape(self.all_logits, [1, -1])), [-1]),
self.i)
self.gan_loss = -tf.reduce_mean(tf.log(self.i_prob) * self.reward) + self.lamda * (
tf.nn.l2_loss(self.u_embedding) + tf.nn.l2_loss(self.i_embedding) + tf.nn.l2_loss(self.i_bias)
)
g_opt = tf.train.GradientDescentOptimizer(self.learning_rate)
self.gan_updates = g_opt.minimize(self.gan_loss,var_list=self.g_params)
Discriminator
傳入D的同樣有三部分卓鹿,分別是uid,iid以及l(fā)abel值留荔,與G一樣吟孙,我們也首先得到embedding值:
self.user_embeddings = tf.Variable(tf.random_uniform([self.userNum,self.emb_dim],
minval=-self.initdelta,maxval=self.initdelta,
dtype=tf.float32))
self.item_embeddings = tf.Variable(tf.random_uniform([self.itemNum,self.emb_dim],
minval=-self.initdelta,maxval=self.initdelta,
dtype=tf.float32))
self.item_bias = tf.Variable(tf.zeros(self.itemNum))
self.u = tf.placeholder(tf.int32)
self.i = tf.placeholder(tf.int32)
self.label = tf.placeholder(tf.float32)
self.u_embedding = tf.nn.embedding_lookup(self.user_embeddings,self.u)
self.i_embedding = tf.nn.embedding_lookup(self.item_embeddings,self.i)
self.i_bias = tf.gather(self.item_bias,self.i)
隨后,我們通過對數(shù)損失函數(shù)來更新D:
self.pre_logits = tf.reduce_sum(tf.multiply(self.u_embedding, self.i_embedding), 1) + self.i_bias
self.pre_loss = tf.nn.sigmoid_cross_entropy_with_logits(labels = self.label,
logits = self.pre_logits) + self.lamda * (
tf.nn.l2_loss(self.u_embedding) + tf.nn.l2_loss(self.i_embedding) + tf.nn.l2_loss(self.i_bias)
)
d_opt = tf.train.GradientDescentOptimizer(self.learning_rate)
self.d_updates = d_opt.minimize(self.pre_loss,var_list=self.d_params)
D中還有很重要的一步就是聚蝶,計(jì)算reward:
self.reward_logits = tf.reduce_sum(tf.multiply(self.u_embedding,self.i_embedding),1) + self.i_bias
self.reward = 2 * (tf.sigmoid(self.reward_logits) - 0.5)
模型訓(xùn)練
我們的G和D是交叉訓(xùn)練的杰妓,D的訓(xùn)練過程如下,每隔5輪碘勉,我們就要調(diào)用generate_for_d函數(shù)產(chǎn)生一批新的訓(xùn)練樣本巷挥。
for d_epoch in range(100):
if d_epoch % 5 == 0:
generate_for_d(sess,generator,DIS_TRAIN_FILE)
train_size = ut.file_len(DIS_TRAIN_FILE)
index = 1
while True:
if index > train_size:
break
if index + BATCH_SIZE <= train_size + 1:
input_user,input_item,input_label = ut.get_batch_data(DIS_TRAIN_FILE,index,BATCH_SIZE)
else:
input_user,input_item,input_label = ut.get_batch_data(DIS_TRAIN_FILE,index,train_size-index+1)
index += BATCH_SIZE
_ = sess.run(discriminator.d_updates,feed_dict={
discriminator.u:input_user,discriminator.i:input_item,discriminator.label:input_label
})
generate_for_d函數(shù)形式如下,其根據(jù)G的策略验靡,生成一批樣本倍宾。
def generate_for_d(sess,model,filename):
data = []
for u in user_pos_train:
pos = user_pos_train[u]
rating = sess.run(model.all_rating,{model.u:[u]})
rating = np.array(rating[0]) / 0.2
exp_rating = np.exp(rating)
prob = exp_rating / np.sum(exp_rating)
neg = np.random.choice(np.arange(ITEM_NUM),size=len(pos),p=prob)
# 1:1 的正負(fù)樣本
for i in range(len(pos)):
data.append(str(u) + '\t' + str(pos[i]) + '\t' + str(neg[i]))
with open(filename,'w') as fout:
fout.write('\n'.join(data))
G的訓(xùn)練過程首先要通過D得到對應(yīng)的reward雏节,隨后更新自己的策略:
for g_epoch in range(50):
for u in user_pos_train:
sample_lambda = 0.2
pos = user_pos_train[u]
rating = sess.run(generator.all_logits,{generator.u:u})
exp_rating = np.exp(rating)
prob = exp_rating / np.sum(exp_rating)
pn = (1-sample_lambda) * prob
pn[pos] += sample_lambda * 1.0 / len(pos)
sample = np.random.choice(np.arange(ITEM_NUM), 2 * len(pos), p=pn)
reward = sess.run(discriminator.reward, {discriminator.u: u, discriminator.i: sample})
reward = reward * prob[sample] / pn[sample]
_ = sess.run(generator.gan_updates,
{generator.u: u, generator.i: sample, generator.reward: reward})
參考文獻(xiàn):
1、論文地址:https://arxiv.org/abs/1705.10513
2高职、https://github.com/geek-ai/irgan/tree/master/item_recommendation