01-NAIS (item sim)
NAIS Neural Attentive Item Similarity Model for Recommendation-TKDE2018
論文鏈接:https://arxiv.org/pdf/1809.07053.pdf
論文代碼地址:https://github.com/AaronHeee/Neural-Attentive-Item-Similarity-Model
這篇論文運用attention機制的地方在計算項目相似度译秦,作者認為用戶的歷史商品對于推薦的目標(biāo)商品所產(chǎn)生的影響是不同的,所以在計算用戶對商品的預(yù)測評分時淮菠,根據(jù)用戶的歷史商品得到的用戶特征應(yīng)該根據(jù)影響因子作權(quán)重加和闭树。在使用傳統(tǒng)的softmax時發(fā)現(xiàn)效果不好蚌斩,因為要計算影響因子的數(shù)量太大霸奕,最后得出的概率分布并不好吭敢,所以在softmax的分母添加了指數(shù)衰減因子
Attention model: 源自人類的視覺注意力機制胚膊,在觀察一幅圖像時對信息量高的高質(zhì)量區(qū)域投入更多的注意力眷蜓。通俗地講就是通過對信息進行篩選分瘾,為質(zhì)量高的信息分配更多的注意力也就是權(quán)重。
user based CF:基于物品的協(xié)同過濾吁系,推薦系統(tǒng)的一種傳統(tǒng)算法芹敌,通過用戶行為計算物品相似度,為用戶推薦和用戶歷史商品相似的物品垮抗。
介紹
傳統(tǒng)的基于物品的協(xié)同過濾都是通過一些通用的相似度算法計算物品相似度氏捞,如cosine等。本文提出了一種定制的注意力框架來學(xué)習(xí)物品之間的相似度冒版。對于目標(biāo)商品液茎,根據(jù)用戶的歷史商品來預(yù)測用戶對目標(biāo)商品的評分時,用戶的歷史商品所貢獻的信息量是不同的,在傳統(tǒng)算法時捆等,預(yù)測公式采用相似度權(quán)重以及歷史商品評分的加權(quán)和滞造。本文對傳統(tǒng)的相似度權(quán)重進行創(chuàng)新,設(shè)計了一種利用注意力機制的網(wǎng)絡(luò)結(jié)構(gòu)栋烤,通過實驗證明可以更好的學(xué)習(xí)商品之間的相似度谒养,提高推薦準(zhǔn)確率。
1. 模型設(shè)計
預(yù)測模型
本文設(shè)計了兩種注意力模型明郭,第一種买窟,將歷史商品特征和目標(biāo)商品特征堆疊;第二種薯定,求歷史商品特征和目標(biāo)商品特征的點積始绍。注意力網(wǎng)絡(luò)采用MLP結(jié)構(gòu)。
損失函數(shù):
結(jié)構(gòu)圖
2. 實驗設(shè)計
參數(shù)設(shè)置大多數(shù)基本和代碼中的默認設(shè)置差不多话侄,實現(xiàn)過程中亏推,作者發(fā)現(xiàn)tensoflow在輸入數(shù)據(jù)的時候,用戶數(shù)據(jù)的歷史商品長度應(yīng)該是相同的年堆,所以作者設(shè)計了一種根據(jù)用戶來分組的mini-batch方法吞杭,也就是一個用戶的所有正樣本和負樣本構(gòu)成一個mini-batch,這樣用戶矩陣(b变丧,n)的維度n即為用戶的歷史商品數(shù)量芽狗,這樣設(shè)計有兩個好處,一個是不需要填充數(shù)據(jù)锄贷,二是免去了batch-size參數(shù)的考慮译蒂。
另一個需要注意的是曼月,作者采用的數(shù)據(jù)是另一篇論文已經(jīng)預(yù)處理好的數(shù)據(jù)谊却,具體論文地址,這些數(shù)據(jù)的測試集哑芹,每一個目標(biāo)用戶商品對炎辨,有99個負樣本。
數(shù)據(jù)集
實驗結(jié)果
參數(shù)分析
-
embedding size
作者解釋了在嵌入維度為8時聪姿,提出的算法效果要弱于基線算法的原因:
在嵌入維度過小時碴萧,非線性模型要比線性模型又更強的表示性。這個我不太理解末购?
-
weight_size(attention 網(wǎng)絡(luò)的隱藏層維度)
證明了在attention網(wǎng)絡(luò)嵌入層維度小的時候基于點乘的算法效果要好于基于concat的算法破喻,用
-
平滑因子
可以看到作者提出這個參數(shù)的原因,當(dāng)參數(shù)設(shè)置為1時,加入attention網(wǎng)絡(luò)的算法羽德,效果相當(dāng)糟糕几莽。作者認為是用戶歷史商品長度變化太大的原因,當(dāng)attention網(wǎng)絡(luò)用于文本或圖像時宅静,注意力因子長度十分固定章蚣,在計算softmax時不會出現(xiàn)這樣的問題。
3. 討論
-
實時個性化
本文設(shè)計的算法在支持實時更新用戶特征上有著出色的表現(xiàn)姨夹。對于實時個性化纤垂,需要實時監(jiān)控用戶行為,用戶在對某個商品交互后匀伏,實施推薦系統(tǒng)同時更新用戶的推薦列表洒忧。因為重新訓(xùn)練整個模型不現(xiàn)實,一般都選擇更新模型參數(shù)够颠,然而因為用戶行為可能并行發(fā)生熙侍,更新模型的固有參數(shù)會發(fā)生沖突,雖然可以通過分布式結(jié)構(gòu)來解決但是分布式往往需要更多的消耗履磨。本文的算法在實時問題上有更好的解決方式蛉抓,首先用戶的特征可以直接通過加法更新,時間消耗基本是常數(shù)級的剃诅。比如系統(tǒng)檢測到用戶u消耗了商品t巷送,這時用戶u對于商品i的預(yù)測為原預(yù)測值的基礎(chǔ)上加上,根本不需要重新計算模型矛辕。
-
兩種注意力模型結(jié)構(gòu)的選擇
從公式中可以看出笑跛,本文設(shè)計了兩種不同的注意力模型結(jié)構(gòu),一種是直接將和直接連接在一起聊品,組成不同shape的特征矩陣飞蹂,另一種則是計算和的點乘。前者保留了商品特征的原始結(jié)構(gòu)翻屈,但是因為矩陣的結(jié)構(gòu)發(fā)生變化可能導(dǎo)致網(wǎng)絡(luò)難以收斂陈哑。后者的矩陣結(jié)構(gòu)滿足學(xué)習(xí)的目標(biāo),但是丟失了學(xué)習(xí)的商品特征伸眶。兩種結(jié)構(gòu)各有利弊惊窖,也是作者設(shè)計兩種模型的原因。
4. 結(jié)論
本文出發(fā)點為用戶的歷史行為商品對目標(biāo)商品預(yù)測做出的貢獻是不同的厘贼,本文接下來的工作就是將NAIS結(jié)合更加先進的深度模型提高推薦準(zhǔn)確率界酒,以及考慮推薦系統(tǒng)的可解釋性。
相關(guān)工作
這部分作者介紹的很迷嘴秸,花了幾大段寫推薦系統(tǒng)的任務(wù)從顯示評分到隱式評分毁欣,分別采用不同的度量方式售担。介紹了一個最先進的排序方法,是對抗個性化排序署辉。然后介紹了深度學(xué)習(xí)在推薦系統(tǒng)的應(yīng)用族铆,分了兩個部分,一個是學(xué)習(xí)特征表示哭尝,另一個是學(xué)習(xí)scoring function哥攘。關(guān)于第二種介紹了三篇比較先進的論文。最后討論了另一個采用attention的論文材鹦,這篇論文是基于用戶的逝淹,并且強調(diào)了自己論文的亮點,想出了一個解決softmax計算大規(guī)模概率分布的方法桶唐。
代碼分析
定義參數(shù)
- path : 數(shù)據(jù)路徑
- dataset: 選擇的數(shù)據(jù)集栅葡,pinterest 還是movielens
- pretrain: 0: No pretrain, 1: Pretrain with updating FISM variables, 2:Pretrain with fixed FISM variables.
- verbose: Interval of evaluation
- batch_choice: user: generate batches by user, fixed:batch_size: generate batches by batch size
- epochs: 周期數(shù)
- weight_size: weight size
- embed_size: Embedding size
- data_alpha: Index of coefficient of embedding vector
- regs: Regularization for user and item embeddings.
- alpha: Index of coefficient of embedding vector
- train_loss: Caculate training loss or nor
- beta: Index of coefficient of sum of exp(A)
- num_neg: Number of negative instances to pair with a positive instance.
- lr: learning rate 學(xué)習(xí)速率
- activation: Activation for ReLU, sigmoid, tanh. 激活函數(shù)
- algorithm: 0 for NAIS_prod, 1 for NAIS_concat ,attention 網(wǎng)絡(luò)算法選擇
定義輸入接口
def _create_placeholders(self):
with tf.name_scope("input_data"):
self.user_input = tf.placeholder(tf.int32, shape=[None, None]) #the index of users
self.num_idx = tf.placeholder(tf.float32, shape=[None, 1]) #the number of items rated by users
self.item_input = tf.placeholder(tf.int32, shape=[None, 1]) #the index of items
self.labels = tf.placeholder(tf.float32, shape=[None,1]) #the ground truth
- user_input: 用戶的index尤泽, shape=[None, None]
- num_idx: 每個用戶評分的物品數(shù)量欣簇,shape=[None, 1],用來控制衰減參數(shù)
- item_input: 所有物品的index坯约, shape=[None, 1]
- labels: 標(biāo)簽熊咽, shape=[None,1]
創(chuàng)建變量
為了便于理解,簡化了參數(shù):
batch_size:b
embedding size: e
weight size: w attention 網(wǎng)絡(luò)嵌入
Q_:[N+1, e] 用來訓(xùn)練歷史商品的嵌入
Q:[N,e] 用來訓(xùn)練目標(biāo)商品的嵌入
attention 網(wǎng)絡(luò)的權(quán)重變量
b: [1, w]
h:[w,1]
dot product algo: W: [e,w]
concat product algo: W: [2e, w]
def _create_variables(self):
with tf.name_scope("embedding"): # The embedding initialization is unknown now
trainable_flag = (self.pretrain!=2)
self.c1 = tf.Variable(tf.truncated_normal(shape=[self.num_items, self.embedding_size], \
mean=0.0, stddev=0.01), \
name='c1', dtype=tf.float32, trainable=trainable_flag)
self.c2 = tf.constant(0.0, tf.float32, [1, self.embedding_size], name='c2')
self.embedding_Q_ = tf.concat([self.c1, self.c2], 0, name='embedding_Q_')
self.embedding_Q = tf.Variable(tf.truncated_normal(shape=[self.num_items, self.embedding_size], mean=0.0, stddev=0.01), \
name='embedding_Q', dtype=tf.float32,trainable=trainable_flag)
self.bias = tf.Variable(tf.zeros(self.num_items),name='bias',trainable=trainable_flag)
# Variables for attention
if self.algorithm == 0:
self.W = tf.Variable(tf.truncated_normal(shape=[self.embedding_size, self.weight_size], mean=0.0, \
stddev=tf.sqrt(tf.div(2.0, self.weight_size + self.embedding_size))),name='Weights_for_MLP', dtype=tf.float32, trainable=True)
else:
self.W = tf.Variable(tf.truncated_normal(shape=[2*self.embedding_size, self.weight_size], mean=0.0, \
stddev=tf.sqrt(tf.div(2.0, self.weight_size + (2*self.embedding_size)))),name='Weights_for_MLP', dtype=tf.float32, trainable=True)
self.b = tf.Variable(tf.truncated_normal(shape=[1, self.weight_size], mean=0.0, \
stddev=tf.sqrt(tf.div(2.0, self.weight_size + self.embedding_size))),name='Bias_for_MLP', dtype=tf.float32, trainable=True)
self.h = tf.Variable(tf.ones([self.weight_size, 1]), name='H_for_MLP', dtype=tf.float32)
計算inference
第一步:在計算輸出之前需要先定義兩個嵌入矩陣
embedding_q_
和embedding_q
分別是從嵌入變量矩陣embedding_Q_
和embedding_Q
中查找出來的闹丐,分別對應(yīng)著用戶的歷史商品嵌入和目標(biāo)商品嵌入横殴。
with tf.name_scope("inference"):
self.embedding_q_ = tf.nn.embedding_lookup(self.embedding_Q_, self.user_input) # (b, n, e)
self.embedding_q = tf.nn.embedding_lookup(self.embedding_Q, self.item_input) # (b, 1, e)
第二步:將這兩個嵌入矩陣輸入到attention網(wǎng)絡(luò)中,求歷史商品的加權(quán)和卿拴。
if self.algorithm == 0:
self.embedding_p = self._attention_MLP(self.embedding_q_ * self.embedding_q) #(b,e)
else:
n = tf.shape(self.user_input)[1]
self.embedding_p = self._attention_MLP(tf.concat([self.embedding_q_, \
tf.tile(self.embedding_q, tf.stack([1,n,1]))],2))
attention network
作者定義了一個attention函數(shù)衫仑,輸入是矩陣q_和q的concat或者點積。輸出矩陣每行結(jié)果為為
代碼中第一部分求
def _attention_MLP(self, q_):
with tf.name_scope("attention_MLP"):
b = tf.shape(q_)[0]
n = tf.shape(q_)[1]
r = (self.algorithm + 1)*self.embedding_size
MLP_output = tf.matmul(tf.reshape(q_,[-1,r]), self.W) + self.b #(b*n, e or 2*e) * (e or 2*e, w) + (1, w)
if self.activation == 0:
MLP_output = tf.nn.relu( MLP_output )
elif self.activation == 1:
MLP_output = tf.nn.sigmoid( MLP_output )
elif self.activation == 2:
MLP_output = tf.nn.tanh( MLP_output )
A_ = tf.reshape(tf.matmul(MLP_output, self.h),[b,n]) #(b*n, w) * (w, 1) => (None, 1) => (b, n)
# softmax for not mask features
exp_A_ = tf.exp(A_)
num_idx = tf.reduce_sum(self.num_idx, 1)
mask_mat = tf.sequence_mask(num_idx, maxlen = n, dtype = tf.float32) # (b, n)
exp_A_ = mask_mat * exp_A_
exp_sum = tf.reduce_sum(exp_A_, 1, keep_dims=True) # (b, 1)
exp_sum = tf.pow(exp_sum, tf.constant(self.beta, tf.float32, [1]))
A = tf.expand_dims(tf.div(exp_A_, exp_sum),2) # (b, n, 1)
return tf.reduce_sum(A * self.embedding_q_, 1)
其中在計算注意力softmax函數(shù)的分母的代碼有些復(fù)雜堕花,其中有個mask的運用
tf.sequence_mask( lengths, maxlen=None, dtype=tf.bool, name=None )
tf.sequence_mask([1, 3, 2], 5) # [[True, False, False, False, False],
# [True, True, True, False, False],
# [True, True, False, False, False]]
tf.sequence_mask([[1, 3],[2,0]]) # [[[True, False, False],
# [True, True, True]],
# [[True, True, False],
# [False, False, False]]]
第三步:求output
self.embedding_q = tf.reduce_sum(self.embedding_q, 1) #(b,e)
self.bias_i = tf.nn.embedding_lookup(self.bias, self.item_input)
self.coeff = tf.pow(self.num_idx, tf.constant(self.alpha, tf.float32, [1]))
self.output = tf.sigmoid(self.coeff *tf.expand_dims(tf.reduce_sum(self.embedding_p*self.embedding_q, 1),1) + self.bias_i)
科普:
tf.tile( input, multiples, name=None )
:將input按照維度乘以multiples文狱,其實就是按照維度擴展
tf.math.pow( x, y, name=None )
:指數(shù)冪
tf.stack( values, axis=0, name='stack' )
:按axis維度疊加
代碼舉例:
a = [[[1,1,1]]] #(1,1,3)
b = [[[1,1,1],[1,1,1]]] #(1,2,3)
c = [[[2],[2]]] #(1,2,1)
ta = tf.placeholder(shape=(1,1,3),dtype=tf.float32)
tb = tf.placeholder(shape=(1,2,3),dtype=tf.float32)
tc = tf.placeholder(shape=(1,2,1),dtype=tf.float32)
with tf.Session() as sess:
print(sess.run(tf.reduce_sum(tb,1), feed_dict={tb:b,tc:c}),'\n')
print(sess.run(tf.tile(ta, tf.stack([1,2,1])),feed_dict={ta:a,tb:b,tc:c}),'\n')
print(sess.run(tf.stack([1,2,1]), feed_dict={tb:b,tc:c}),'\n')
#results
[[2. 2. 2.]]
[[[1. 1. 1.]
[1. 1. 1.]]]
[1 2 1]
計算Loss
def _create_loss(self):
with tf.name_scope("loss"):
self.loss = tf.losses.log_loss(self.labels, self.output) + \
self.lambda_bilinear * tf.reduce_sum(tf.square(self.embedding_Q)) + \
self.gamma_bilinear * tf.reduce_sum(tf.square(self.embedding_Q_)) + \
self.eta_bilinear * tf.reduce_sum(tf.square(self.W))
構(gòu)建優(yōu)化器
def _create_optimizer(self):
with tf.name_scope("optimizer"):
self.optimizer = tf.train.AdagradOptimizer(learning_rate=self.learning_rate, \
initial_accumulator_value=1e-8).minimize(self.loss)