DIN算法代码详细解读

[复制链接]
查看3968 | 回复9 | 2022-4-9 15:24:15 | 显示全部楼层 |阅读模式
首先给出论文的地址:Deep Interest Network for Click-Through Rate Prediction4 j7 |/ i. g- t
然后给出两篇对论文进行了详细介绍的文章:
1 _0 n+ b9 ^% d) [3 F' o! b! Q王喆:推荐系统中的注意力机制——阿里深度兴趣网络(DIN)
8 _- P0 x; p( _  E. Y2 Z梁勇:推荐系统遇上深度学习(十八)-探秘阿里深度兴趣网络浅析及实现7 T$ }9 j2 t4 c1 C
建议先读完上面两篇文章,对模型有大概的了解之后再来读本篇文章。本文主要从代码的层面出发,逐行去分析理解论文中思想的具体实现。* O2 M+ P+ H$ v9 S9 x
本文使用的代码地址为:zhougr1993/DeepInterestNetwork
# O8 A. n* G" V5 F% ?开始!1 G2 H8 }  j( K: l$ v9 {3 f
源码介绍4 _/ w$ N( p) G2 o
; d" A5 `' S$ b! O* W/ O* Q  M3 _% `% q

# u  ]/ _0 _8 m5 P1 E0 r" x- r DIN算法代码详细解读-1.jpg 4 i! X1 I8 S1 i/ h
论 文 中 用 的 是 Amazon Product Data 数 据 , 包 含 两 个 文 件 reviews_Electronics_5.json和 meta_Electronics.json。具体数据形式在本文最后有展 示。文件格式链接中有说明,其中 reviews 主要是用户买了相关商品产生的上下 文信息,包括商品 id, 时间,评论等。meta 文件是关于商品本身的信息,包括商 品 id, 名称,类别,买了还买等信息。其中 meta 部分为商品信息,reviewer 为点 击者信息,转换为 dataframe 格式之后的具体字段如下:
( H$ c- `9 F# x7 {meta_df: ['asin','imUrl','description','categories','title','price','salesRank','related','brand']
" I! r) i' v& p& Mreviews_df: ['reviewerID', 'asin', 'reviewerName', 'helpful', 'reviewText','overall', 'summary', 'unixReviewTime','reviewTime']( Z3 y1 W: u; X3 S8 b) \
源码解析/ u: K& M/ Z( b' j2 c

/ [- Z& Z5 I! C9 M8 V一、数据预处理* d9 o- J, s5 d6 b% p% d$ T- Q

4 n3 G9 A0 s* ^# f, H9 ?$ f DIN算法代码详细解读-2.jpg
3 c; c$ y. _9 @& _: B: q! z二、生成正负样本
1 @% v; M/ H0 N" b5 H2 L9 l: E3 q0 o5 A- F4 b' |) p' {
DIN算法代码详细解读-3.jpg & h, d! B3 A' D5 x1 I1 l; k
build_dataset.py代码详解如下:
. s: l1 }+ Y0 d9 |0 p1 himport random- F9 ?9 P) d7 O# A" T
import pickle: f6 N/ f% Y, i- F
random.seed(1234)
8 `* b1 n* g2 N, R2 n+ \withopen('C:/Users/Administrator/Desktop/raw_data/remap.pkl','rb')asf:3 X6 J' ^7 h4 ]! J- c
    reviews_df=pickle.load(f)
+ ]/ W9 w2 y8 b! _  J% `2 }    cate_list=pickle.load(f)
9 H0 O$ l2 k2 }; q; Y    user_count,item_count,cate_count,example_count=pickle.load(f)$ |7 I4 N5 @% Q$ ]) ^
''' 6 M" J7 h# b+ {9 B
pos_list(每个点击者点击的商品 ID 组成的 list)例如: [8] [9,6,4,5] [3] [8] 2 ~( K* V. Y9 Z5 f( O& x
''' 1 K$ Y: D, m/ v; i) G
train_set=[] / f1 J, F: I% \, O+ }7 ]# p
test_set=[]
5 D' L5 W1 }! Q+ r; Z; h9 gfor reviewerID,histinreviews_df.groupby('reviewerID'): 4 K: I+ X8 b5 N5 n5 t
    pos_list=hist['asin'].tolist() - [$ q" x& a/ [% f
    defgen_neg(): ; O) s- f2 n  i& z! m
        #取每个用户点击列表的第一个商品 / R: c% s- L0 q$ B! \4 f
        neg=pos_list[0]
" r: h- F# T' x: N+ g        while neg in pos_list: & I. y/ P, s: f
            #随机初始化,即给点击者随机初始化一个商品,item_count-1 为商品数
; K  M( _! g' o            neg=random.randint(0,item_count-1)
6 J# p) w; T- s: T        return neg
! o4 P* e  B2 a$ U/ C    neg_list=[gen_neg() for i in range(len(pos_list))] + c, \; G4 L' E5 x

3 z; h' }7 k" N#如果用户点击的商品数大于 1,则循环
' m0 O) K* t0 n" D! ]& S8 yfor i in range(1,len(pos_list)):
; e" X8 V9 F4 m+ }# m% O3 u    hist=pos_list[:i] 9 r; C  o! h- f1 k7 V
    #print(hist) & U3 {' ~$ b4 ?" E+ b( b% v
    '''
1 o7 e) U* ?/ b: ?* g+ {% I    #下面的 if 语句控制正负样本的个数和格式),例如某用户点击过 abcd 四 个商品,
7 L0 f" r( y+ R- y# {5 N, R    则最终生成的样本为:(其中 X 为随机初始化的某商品 ID) 6 r5 W8 t( Q# d) J  D0 u
    ((user_id,a,(b,1)) (user_id,a,(X,0)) (user_id,(a,b),(c,1))
* v5 w2 N7 O: ]- G    user_id,(a,b),(X0)) (user_id,(a,b,c),(d,1)) (user_id,(a,b,c),(X,0))
& e; }# c0 b- }    '''3 f4 P) s1 b# R1 y- V
    if i != len(pos_list) - 1: ( O6 t% D" H( A
        train_set.append((reviewerID,hist,pos_list,1))
6 A. j9 U0 ?7 q( A8 B- x  L        print(train_set)
( n4 B( R& |- R9 z; t        train_set.append((reviewerID,hist,neg_list,0)) . w# X% _5 b: Y6 i: ~& x
        print(train_set) + n# N3 y8 F1 t$ g; g
    #验证集格式(user_id,a,(b,X))
  m' y2 b" b5 R0 G0 G    else:
" M, X( ]( E, o8 M: x        label=(pos_list,neg_list)
, [! c4 c* P3 h* ~0 m        print(label) 1 d7 ?- Q  V! J
        test_set.append((reviewerID,hist,label))
5 a' y+ i3 Y& r$ K5 W% m; k        print(test_set)
( K2 p1 w! o2 U" m, i0 c5 E
( b7 S; |! I+ u+ u1 P. S#最终的数据集里点击商品数小于 1 的数据删除掉了
9 I# L+ Y% @# D" _random.shuffle(train_set) , G) S! `8 U) N& T9 z- R
random.shuffle(test_set)9 L" m+ j* ^9 b% S! r
assertlen(test_set)==user_count 5 C2 h  c8 h4 [& S& i. E; t
- A6 i# H4 b5 @' _. }% U# M
with open('dataset.pkl','wb') as f:
3 t! b! L+ q" N9 Y4 d6 ^" gpickle.dump(train_set,f,pickle.HIGHEST_PROTOCOL) : x6 a( k6 U, Q, Y; I; h9 A
pickle.dump(test_set,f,pickle.HIGHEST_PROTOCOL) ) \  d& l1 g- s+ a
pickle.dump(cate_list,f,pickle.HIGHEST_PROTOCOL) / U9 W& L, v9 e, a
pickle.dump((user_count, item_count, cate_count), f, pickle.HIGHEST_PROTOCOL)三、模型部分(包括attention机制实现)
8 U! R/ `/ A8 ]
5 J, v% T! `7 J下面介绍本算法的核心model.py文件,每行关键的代码都给出了注释。
$ m0 C1 {; K; e, S2 J0 f9 B+ Pimport tensorflow as tf
* ^; V3 _" W, nfrom Dice import dice8 I1 V3 q9 X. q  w$ D3 T+ I: P

+ E9 I$ m2 ^, B9 aclass Model(object):" t+ O# _+ e* s4 n  W# O% {% z/ L
    def __init__(self,user_count,item_count,cate_count,cate_list):/ Q# s3 o( c' G; |6 N: Q) R/ i7 n
        # shape: [B],  user id。 (B:batch size)
- G, A- ~* V) }4 Y& f9 N        self.u = tf.placeholder(tf.int32, [None, ])
% }  X0 k+ r3 i6 S, S        # shape: [B]  i: 正样本的item
( v6 M9 G8 a: L+ ~        self.i = tf.placeholder(tf.int32, [None, ])
/ z( p; w6 V4 ^' u+ K        # shape: [B]  j: 负样本的item
9 u* m5 t. a* D( z) T, C        self.j = tf.placeholder(tf.int32, [None, ])
- u& |- [+ K; L, b0 v0 T        # shape: [B], y: label
5 N' t$ p' |6 a' Z* R3 V- Q7 F        self.y = tf.placeholder(tf.float32, [None, ])
+ I6 r* }; p7 M' _2 `1 }        # shape: [B, T] #用户行为特征(User Behavior)中的item序列。T为序列长度
/ f. W, E# n7 g        self.hist_i = tf.placeholder(tf.int32, [None, None])- B* F' v( q9 f
        # shape: [B]; sl:sequence length,User Behavior中序列的真实序列长度(?)
. a4 B- }: ^4 _        self.sl = tf.placeholder(tf.int32, [None, ])7 B' t& H* g+ ~: g. L
        #learning rate
; U6 k# t% L) S8 V        self.lr = tf.placeholder(tf.float64, [])
9 Z3 c1 F  ?- A, U( k( ^        ) k) M. g# u3 |1 p2 |
        hidden_units = 128
2 q' \& R# }5 _2 b6 s8 b        # shape: [U, H], user_id的embedding weight. U是user_id的hash bucket size0 ^' S0 g- Z. \$ `9 T
        user_emb_w = tf.get_variable("user_emb_w", [user_count, hidden_units])/ b- o5 W$ R: A; Z! ~2 P
( C4 L$ X; `! J% u
        # shape: [I, H//2], item_id的embedding weight. I是item_id的hash bucket size
. x2 h( O- l6 q5 g; P        item_emb_w = tf.get_variable("item_emb_w", [item_count, hidden_units // 2])  # [I, H//2]
* `  ]; U5 t6 t" T
' t$ j0 C5 |. J5 z2 B- t$ U        # shape: [I], bias
, U* W: E. D7 u& z+ f1 V; p, D. b        item_b = tf.get_variable("item_b", [item_count],initializer=tf.constant_initializer(0.0))
2 i+ w  e, G9 m2 V0 \+ V$ _        # shape: [C, H//2], cate_id的embedding weight.
/ D3 L( J  v) j1 W- e        cate_emb_w = tf.get_variable("cate_emb_w", [cate_count, hidden_units // 2])3 ~. v! p& w- {. p3 V' T
3 D4 {( g+ u. W7 Z
        # shape: [C, H//2]- w2 k! M2 }; E( D% s- ]
        cate_list = tf.convert_to_tensor(cate_list, dtype=tf.int64)  v$ T3 m* I- P" }7 @

, B: r  B) r/ w        # 从cate_list中取出正样本的cate
3 k% R, k8 ]% m! L9 u! ?        ic = tf.gather(cate_list, self.i)' d5 k3 `& _! v" \! E+ X; p+ t2 F
        # 正样本的embedding,正样本包括item和cate
0 w! e# j- G" B, E* R) _0 ?7 M        i_emb = tf.concat(values=[tf.nn.embedding_lookup(item_emb_w, self.i),tf.nn.embedding_lookup(cate_emb_w, ic),], axis=1)
& K$ m- C% n' T9 _3 {6 V; [        # 偏置b
" }* T& D( n) f9 V6 S0 s        i_b = tf.gather(item_b, self.i)
3 F9 g8 K, S+ Y/ s        ( I) J! ?, Z! B1 T% H  L
        # 从cate_list中取出负样本的cate. H; r- B7 l/ s7 j+ n' K
        jc = tf.gather(cate_list, self.j)9 S3 I# }( V# n4 ^! v
        # 负样本的embedding,负样本包括item和cate4 T+ B0 T; E6 h3 u
        j_emb = tf.concat([tf.nn.embedding_lookup(item_emb_w, self.j),tf.nn.embedding_lookup(cate_emb_w, jc),], axis=1)
! W. V) b* Q/ g# r) F        # 偏置b; z! h5 i2 W1 e8 L2 ?- p
        j_b = tf.gather(item_b, self.j)9 ]6 h0 J$ n% w' A$ ]+ V9 d
0 x0 V: B+ r" H' ]8 V% k
        # 用户行为序列(User Behavior)中的cate序列. K/ @  @8 ~1 w) p4 H- z
        hc = tf.gather(cate_list, self.hist_i)
5 X! H( w+ x' ]
# F) U% O! c( h: o2 r8 {! f( K# X* M        # 用户行为序列(User Behavior)的embedding,包括item序列和cate序列+ D8 o: V0 u6 \2 t  K  ^) ^' N
        h_emb = tf.concat([tf.nn.embedding_lookup(item_emb_w, self.hist_i),tf.nn.embedding_lookup(cate_emb_w, hc),], axis=2)
7 {+ u) |7 L+ i% }( _0 m: K        # attention操作
6 f$ M' U7 R, X& F, y+ X        hist_i = attention(i_emb, h_emb, self.sl)  
4 x0 u& W9 D! s, o( d. W) j        # -- attention end ---
5 Z6 }  S2 z8 {5 e* x" {/ M+ e8 {0 I$ Y' P
        hist = tf.layers.batch_normalization(inputs=hist)
* N( R& `: T6 e9 i        hist = tf.reshape(hist,[-1,hidden_units])
/ {+ v9 ?' a' D2 Q# ?        #添加一层全连接层,hist为输入,hidden_units为输出维数
4 Y2 A8 K! Z3 x$ ?7 u: T! }        hist = tf.layers.dense(hist,hidden_units); q# {( O8 z4 {7 _8 V- S
8 O& {9 a! Y# i, P
        u_emb = hist/ b; l- a3 J. O

0 N; A- w" X$ y8 }# C0 d% m        #下面两个全连接用来计算y',i为正样本,j为负样本
& X3 V1 q: x5 |# O' ^0 K: e        # fcn begin: p' ?# f/ q4 ?, F, R6 X+ K4 l3 Y( J  k
        din_i = tf.concat([u_emb, i_emb], axis=-1)
1 m: G3 ~# M9 Y; E        din_i = tf.layers.batch_normalization(inputs=din_i, name='b1')# |# q5 z$ j( N5 c* p8 }; t* _
        d_layer_1_i = tf.layers.dense(din_i, 80, activation=None, name='f1'), O1 S% J) ]4 x+ d9 q5 B
        d_layer_1_i = dice(d_layer_1_i, name='dice_1_i')& l3 M. O3 @! x3 `% O# j9 J( w
        d_layer_2_i = tf.layers.dense(d_layer_1_i, 40, activation=None, name='f2')* P- M6 i5 }& `9 j( k4 O
        d_layer_2_i = dice(d_layer_2_i, name='dice_2_i')( Y4 E. ^- z1 A* a
        d_layer_3_i = tf.layers.dense(d_layer_2_i, 1, activation=None, name='f3')6 A! G5 U, c8 o6 S' ^& O

; `& P$ r/ \. ^7 y; [7 w9 J        din_j = tf.concat([u_emb, j_emb], axis=-1)
1 O# Y* u0 w6 a        din_j = tf.layers.batch_normalization(inputs=din_j, name='b1', reuse=True)8 p; j; G: u5 |. _3 G
        d_layer_1_j = tf.layers.dense(din_j, 80, activation=None, name='f1', reuse=True)
. y4 Q- b, H. s! }7 @9 Q+ ^; C0 @6 M        d_layer_1_j = dice(d_layer_1_j, name='dice_1_j')
( c! O) A5 L0 i# A" v        d_layer_2_j = tf.layers.dense(d_layer_1_j, 40, activation=None, name='f2', reuse=True)
5 f5 |: d+ }! G; t6 b# D! A        d_layer_2_j = dice(d_layer_2_j, name='dice_2_j')
% @8 c' P9 ^+ o1 {4 |        d_layer_3_j = tf.layers.dense(d_layer_2_j, 1, activation=None, name='f3', reuse=True)  y& X* _4 L9 b6 v
1 H  q/ C- H& j" H
        d_layer_3_i = tf.reshape(d_layer_3_i, [-1])
" ], @6 i2 _* S; ^- N! j        d_layer_3_j = tf.reshape(d_layer_3_j, [-1])
; w# ~% r1 g& @) c
, F6 C8 S4 o8 H3 B2 h2 U9 i# D        #预测的(y正-y负)
3 g5 ]/ G4 I9 J5 _+ M  n        x = i_b - j_b + d_layer_3_i - d_layer_3_j  # [B]7 |1 r: n1 e9 Z) {" q! L
        #预测的(y正)
+ B  t. k- |* M) f        self.logits = i_b + d_layer_3_i0 j$ V5 w; O2 S0 V$ r

% ^7 K/ Y, R  Z
* }; b- h: H/ W$ v* L+ L        # logits for all item:, W1 k7 b; C' j3 x8 ]4 U8 }% l( t. L
        u_emb_all = tf.expand_dims(u_emb, 1)
. @( Y. A  }$ c        u_emb_all = tf.tile(u_emb_all, [1, item_count, 1])3 ]& u& h0 D* w4 D8 ^' C9 y
        #将所有的除u_emb_all外的embedding,concat到一起  n/ J7 g" o  f1 K* q; F$ @( \
        all_emb = tf.concat([item_emb_w,tf.nn.embedding_lookup(cate_emb_w, cate_list)], axis=1)
) n" e: i) t  h9 ]% q& @        all_emb = tf.expand_dims(all_emb, 0)
) ?7 g. i9 ~2 F5 r        all_emb = tf.tile(all_emb, [512, 1, 1])
: g5 f( W/ K9 L/ z, ^' u+ @        # 将所有的embedding,concat到一起
4 x* b( S7 z, \( t        din_all = tf.concat([u_emb_all, all_emb], axis=-1). m0 O9 T- R3 x( H. b- V2 u
        din_all = tf.layers.batch_normalization(inputs=din_all, name='b1', reuse=True)# k& O, Z# i1 P% R+ s, m: q3 T
        d_layer_1_all = tf.layers.dense(din_all, 80, activation=None, name='f1', reuse=True)& S6 ?7 Q, `- h, W, ^1 W3 R
        d_layer_1_all = dice(d_layer_1_all, name='dice_1_all')
7 U& Q% C. O# P: F5 S9 X        d_layer_2_all = tf.layers.dense(d_layer_1_all, 40, activation=None, name='f2', reuse=True)
- p( }! F( ~5 u8 ?* a+ `) ^5 r. @        d_layer_2_all = dice(d_layer_2_all, name='dice_2_all')
1 b; O- r8 T1 |# h1 `0 u        d_layer_3_all = tf.layers.dense(d_layer_2_all, 1, activation=None, name='f3', reuse=True)1 q/ @% E  V! l9 B6 j$ L! F- `! Y
        d_layer_3_all = tf.reshape(d_layer_3_all, [-1, item_count])
+ z* ]2 y5 u& n1 g% i+ S" S) ~  A0 Z
        self.logits_all = tf.sigmoid(item_b + d_layer_3_all)
* T% r0 r7 v3 }0 V) P        # -- fcn end -------9 z/ t# T' ~7 M. R$ X. U+ X4 _1 q& P
6 @& X5 ?( P3 A+ Y4 k4 n) O
        self.mf_auc = tf.reduce_mean(tf.to_float(x > 0))( J3 x5 R9 z, F4 R# J
        self.score_i = tf.sigmoid(i_b + d_layer_3_i)" ^, @: F/ o3 ?7 Q" d9 C5 K3 G
        self.score_j = tf.sigmoid(j_b + d_layer_3_j)# ^+ [# m" s9 p* T
        self.score_i = tf.reshape(self.score_i, [-1, 1])
1 b4 M# u2 w, O; N* a        self.score_j = tf.reshape(self.score_j, [-1, 1])
1 j. ^/ @! W) w6 G6 W' j) `        self.p_and_n = tf.concat([self.score_i, self.score_j], axis=-1)
# A2 v$ P" M# L4 Z  x, |3 m) G1 S# T9 e4 [* H) b" P$ X# V  Y) ~
        # Step variable* F# s1 e/ [/ u8 V
        self.global_step = tf.Variable(0, trainable=False, name='global_step')
) F/ @( F3 v% u9 Q        self.global_epoch_step = tf.Variable(0, trainable=False, name='global_epoch_step')
' e1 Z7 D1 }( \2 m) o" R        self.global_epoch_step_op = tf.assign(self.global_epoch_step, self.global_epoch_step + 1)
  O& @! G; B. z* Z9 M' V
2 n* d$ {8 \: T        # loss and train
( r) |4 x; S3 v        self.loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=self.logits,labels=self.y)): H! c  @' {0 S& r1 f6 _0 D: a
        trainable_params = tf.trainable_variables()* m/ m* k; F. y7 b! I
        self.train_op = tf.train.GradientDescentOptimizer(learning_rate=self.lr).minimize(self.loss)6 Y* a. B$ p1 H3 q3 V

7 X7 p6 S: x. i2 H7 _# r    def train(self,sess,uij,l):
- H1 Q2 u  N7 `6 R! t        loss,_ = sess.run([self.loss,self.train_op],feed_dict={
# F3 ]7 |  [/ o8 l2 l% {            #self.u : uij[0],
' w0 f2 x8 ^+ r$ X: o# R- y: H2 t            self.i : uij[1],6 A. D( u) D5 Y% Y3 p4 D; N
            self.y : uij[2],5 b" s2 Z! x1 d* S
            self.hist_i : uij[3],( l$ g' O7 a% ?9 {. T5 Q
            self.sl : uij[4]," b- m- o" u8 P4 _5 H2 a0 L( N# U
            self.lr : l
7 \) A+ v: I  ^( _! g        })
$ t. @" F( R0 G: b+ c9 n        return loss2 e0 o. v8 L, ]7 G8 V( u

% h* V. v$ }7 v4 ^4 n) d- M    def eval(self, sess, uij):
1 W, f) N6 y6 Z& Y) H2 |, J        u_auc, socre_p_and_n = sess.run([self.mf_auc, self.p_and_n], feed_dict={. o. X% D4 u5 l+ s, W$ I
            #self.u: uij[0],. O, z; f1 D4 r' A+ x& U
            self.i: uij[1],#正样本
0 d* I: h" B; \( S( L$ t            self.j: uij[2],#负样本1 |/ p9 P. \+ {* e, E) u" ^
            self.hist_i: uij[3],' W/ i! M! F6 A8 i2 q# _$ u# W
            self.sl: uij[4],
+ i; y( R) m: O6 h9 ~        }); @; M" a5 y' r+ l( Z' _' x- f
        return u_auc, socre_p_and_n( }% z& Y$ H8 Y; c
) K5 Z) L1 V" Q; o7 Q* p& \
    def test(self, sess, uid, hist_i, sl):4 w# @% A" W$ o) Y
        return sess.run(self.logits_all, feed_dict={9 _5 W+ _1 S; T. |' f3 V/ M8 N
            self.u: uid,' r. \$ i5 G  K8 U- L) d
            self.hist_i: hist_i,) R2 e! ^& i0 V' V
            self.sl: sl,
9 T) \8 Z$ D4 d$ z3 K" p1 }        })2 @* ]1 _) u! A* {5 D4 U

% L- h, o+ V+ ^. ]8 y/ F( U    def save(self, sess, path):
& x9 Z' ?1 T/ o2 m$ U' {        saver = tf.train.Saver()
5 o6 J( a. k, A3 _& j1 Q( B7 b        saver.save(sess, save_path=path)6 J/ M5 P. b- A/ E2 P0 L
1 m& d' [1 a+ T0 g. Y8 ?0 I" h
    def restore(self, sess, path):9 v: U6 r7 i9 D2 @8 X' f' P
        saver = tf.train.Saver()
* L5 e: N3 i, M2 w8 p7 ^, w        saver.restore(sess, save_path=path)& h! ?* U  g3 U; L5 j0 t
* ]) m8 U/ g2 o; {
def extract_axis_1(data, ind):  T* z* ?* \2 x
    batch_range = tf.range(tf.shape(data)[0])# n& W; a+ v# n" M6 q% g5 K
    indices = tf.stack([batch_range, ind], axis=1)8 O6 P& k% t% [5 r  J
    res = tf.gather_nd(data, indices)
7 V/ x( e3 e- B% m    return res, l7 K: L* [+ L8 f

9 ?5 n! U  _2 F3 R#item_embedding,history_behivior_embedding,sequence_length
" l* Y* H# l6 Z  a6 G2 sdef attention(queries,keys,keys_length):
9 \+ c/ g. N7 _6 [    '''
0 w: }$ K7 Q' ?        queries:     [B, H]    [batch_size,embedding_size]
3 o8 J7 L+ L5 r, v7 u  S        keys:        [B, T, H]   [batch_size,T,embedding_size]2 l3 l5 s2 \$ k6 p9 Y/ b3 D
        keys_length: [B]        [batch_size]
# X9 B4 i7 u2 p        #T为历史行为序列长度
! a+ E0 L; C9 N% {& ?    '''
* U; d3 X" l0 f6 o- `+ A5 u, |! `& S% ^1 _
    #(?,32)->(None,32)->32
2 O  a4 W/ c. P    # tile()函数是用来对张量(Tensor)进行扩展的,其特点是对当前张量内的数据进行一定规则的复制。最终的输出张量维度不变
$ O- S8 z+ U2 w% d) ~    # tf.shape(keys)[1]==T+ }6 r4 O# {: h9 I  p7 H
    # 对queries的维度进行reshape
% |) I$ b" M2 O0 g7 |    # (?,T,32)这里是为了让queries和keys的维度相同而做的操作/ k. c9 B. r7 r2 F
    # (?,T,128)把u和v以及u v的element wise差值向量合并起来作为输入,
9 d+ m. F) G! J1 H9 F4 R  d    # 然后喂给全连接层,最后得出两个item embedding,比如u和v的权重,即g(Vi,Va)
4 [+ p2 R1 m" ^2 B$ i3 u- H$ F6 H, a* I8 D+ m
    queries_hidden_units = queries.get_shape().as_list()[-1]
0 ]( L7 U; L, X    queries = tf.tile(queries,[1,tf.shape(keys)[1]])
6 H, i' O, U" O: l9 w3 G# u( b    queries = tf.reshape(queries,[-1,tf.shape(keys)[1],queries_hidden_units])4 B, A% m+ w4 G% Q# r
    din_all = tf.concat([queries,keys,queries-keys,queries * keys],axis=-1) # B*T*4H
- g: b. I! v7 @, h' @
7 z* U9 D8 d) S/ U5 g    # 三层全链接(d_layer_3_all为训练出来的atteneion权重)- g- N' O; u! i
    d_layer_1_all = tf.layers.dense(din_all, 80, activation=tf.nn.sigmoid, name='f1_att')
# }8 N& I+ [, w; d+ g9 |$ V    d_layer_2_all = tf.layers.dense(d_layer_1_all, 40, activation=tf.nn.sigmoid, name='f2_att')
1 ^/ N- j' W8 w3 B6 a5 k. L    d_layer_3_all = tf.layers.dense(d_layer_2_all, 1, activation=None, name='f3_att') #B*T*1
8 k, r4 B# m9 H
( N  @) O4 `/ ]0 x! n: S    #为了让outputs维度和keys的维度一致
& h- N4 a2 `, n4 U7 F* o    outputs = tf.reshape(d_layer_3_all,[-1,1,tf.shape(keys)[1]]) #B*1*T0 I% P& T' b4 ^& W! t

! k4 T/ S0 A# t: z" w3 K+ c5 [" S2 f    #  bool类型 tf.shape(keys)[1]为历史行为序列的最大长度,keys_length为人为设定的参数,
  o: E5 Q" j7 a' v. Y( I    #  如tf.sequence_mask(5,3)  即为array[True,True,True,False,False]
* p7 Q6 J- d7 u) V4 c0 V    #  函数的作用是为了后面补齐行为序列,获取等长的行为序列做铺垫
; E5 t6 l( R$ L  j  Q    key_masks = tf.sequence_mask(keys_length,tf.shape(keys)[1])) ?; O+ O' \3 @2 {% y" V1 m9 O

' [, u6 ]; W, T8 |1 F, @    #在第二维增加一维,也就是由B*T变成B*1*T8 {" I. J6 q! p
    key_masks = tf.expand_dims(key_masks,1) # B*1*T& N! V. S5 p5 D0 j* K/ J( Q

- l% Z  {1 A6 `& A: R$ d    #tf.ones_like新建一个与output类型大小一致的tensor,设置填充值为一个很小的值,而不是0,padding的mask后补一个很小的负数,这样softmax之后就会接近0& Y: A2 R" E" I
    paddings = tf.ones_like(outputs) * (-2 ** 32 + 1)3 z9 B8 a. m/ i# s' ?( f1 u
' I9 G8 D0 `7 T7 h2 M
    #填充,获取等长的行为序列
$ u0 m/ `$ e$ s9 G# m# C1 x; \    # tf.where(condition, x, y),condition是bool型值,True/False,返回值是对应元素,condition中元素为True的元素替换为x中的元素,为False的元素替换为y中对应元素
& w  N# T& G/ l" X2 p7 ]* I    #由于是替换,返回值的维度,和condition,x , y都是相等的。
5 z2 h3 M9 N( i0 j: p" O- z: E6 [    outputs = tf.where(key_masks,outputs,paddings) # B * 1 * T
, \8 s4 K3 O$ I4 d* Y0 C) K9 T4 ^! ?2 A8 O
    # Scale(缩放)
5 L# W/ f. c+ h0 n, N0 @    outputs = outputs / (keys.get_shape().as_list()[-1] ** 0.5)
4 F9 v/ K4 @* T' K& }0 E. e    # Activation6 H! {! p% a5 P/ y% S
    outputs = tf.nn.softmax(outputs) # B * 1 * T
! [# m. z  d) {* X1 S1 M, N    # Weighted Sum outputs=g(Vi,Va)   keys=Vi" @" K; v/ r, f3 `
    #这步为公式中的g(Vi*Va)*Vi
0 o4 K/ d1 X) D' ?    outputs = tf.matmul(outputs,keys) # B * 1 * H 三维矩阵相乘,相乘发生在后两维,即 B * (( 1 * T ) * ( T * H )), _6 c4 ?! y5 x' G, o2 `
) L; ]" y5 {9 R
    return outputs% o' P" C" x( a
下一篇文章中将详细讲述在实际的业务中,如何应用DIN算法,敬请期待。
逝水流年年i | 2022-4-10 01:51:41 | 显示全部楼层
请问一下,model里面正负样本怎么理解?谢谢
柽茵我 | 2022-4-10 13:21:28 | 显示全部楼层
用户购买了商品为正样本,没购买过的为负样本。
上床不是罪上f | 2022-4-11 00:54:34 | 显示全部楼层
请作者尽快更新,我正在认真膜拜你的大作。对我很有帮助。
星梦飞儿哉 | 2022-4-11 06:09:49 | 显示全部楼层
请教两个问题:& h( J' h) O4 O  ?
1 Scale(缩放)是除以根号H,这样的原理是什么呢?. ~9 j" e3 G  C6 s6 S/ {/ k
2 outputs = tf.nn.softmax(outputs)
* C5 L8 ]8 G! m文章里说不做softmax,以保持attention强度,代码里为啥有呢?而且计算复杂度很高。
: E$ z5 ~: o9 |) b8 B) `谢谢
ivywyw2017 | 2022-4-11 13:24:59 | 显示全部楼层
没有实现自适应正则损失函数呀
七悠假 | 2022-4-11 17:18:17 | 显示全部楼层
不用softmax,是合理的
命运守护神判 | 2022-4-11 23:45:59 | 显示全部楼层
你好,din中为啥这里没有emb行向量的weighted sum,这在代码里体现了了吗?我看了源代码和您解释的这个,并没发现。
恋夫连很 | 2022-4-12 07:19:15 | 显示全部楼层
亲,有没有找到自适应正则如何实现的code?可以发我下吗?
hchknfdrg | 2022-4-12 13:18:46 | 显示全部楼层
1,代码中:u_emb = hist,即u_emb被历史行为代替了,但是文章的图中,却说user_emb最后输入到MLP中了,这里您怎么看?
& u' r6 y* p/ N: E- Q2,cate_emb_w是候选集,还是类别?
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

211

金钱

0

收听

0

听众
性别

新手上路

金钱
211 元