【推荐算法代码实现】Deep Interest Network for Click-Through Rate Prediction代码实现和解读

本文主要是介绍【推荐算法代码实现】Deep Interest Network for Click-Through Rate Prediction代码实现和解读,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

论文解读见【推荐算法】深度学习推荐算法综述 Deep Learning based Recommender System: A Survey and New Perspectives第6.2小节。

一、数据处理

1.1 基础数据

论文中用的是Amazon Product Data数据,包含两个文件:reviews_Electronics_5.json, meta_Electronics.json

下载并解压:

wget -c http://snap.stanford.edu/data/amazon/productGraph/categoryFiles/reviews_Electronics_5.json.gz
gzip -d reviews_Electronics_5.json.gz
wget -c http://snap.stanford.edu/data/amazon/productGraph/categoryFiles/meta_Electronics.json.gz
gzip -d meta_Electronics.json.gz

其中reviews_Electronics_5.json主要是用户买了相关商品产生的上下文信息,包括商品id, 时间,评论等;
meta_Electronics文件是关于商品本身的信息,包括商品id, 名称,类别,买了还买等信息。

reviews某单个样本如下:

{"reviewerID": "A2SUAM1J3GNN3B","asin": "0000013714","reviewerName": "J. McDonald","helpful": [2, 3],"reviewText": "I bought this for my husband who plays the piano.  He is having a wonderful time playing these old hymns.  The music  is at times hard to read because we think the book was published for singing from more than playing from.  Great purchase though!","overall": 5.0,"summary": "Heavenly Highway Hymns","unixReviewTime": 1252800000,"reviewTime": "09 13, 2009"
}

各字段分别为:

reviewerID:用户ID;
asin: 物品ID;
reviewerName:用户姓名;
helpful :评论帮助程度,例如上述为2/3;
reviewText :文本信息;
overall :物品评分;
summary:评论总结
unixReviewTime :时间戳
reviewTime :时间

meta某样本如下:

{"asin": "0000031852","title": "Girls Ballet Tutu Zebra Hot Pink","price": 3.17,"imUrl": "http://ecx.images-amazon.com/images/I/51fAmVkTbyL._SY300_.jpg","related":{"also_bought": ["B00JHONN1S", "B002BZX8Z6", "B00D2K1M3O", "0000031909", "B00613WDTQ", "B00D0WDS9A", "B00D0GCI8S", "0000031895", "B003AVKOP2", "B003AVEU6G", "B003IEDM9Q", "B002R0FA24", "B00D23MC6W", "B00D2K0PA0", "B00538F5OK", "B00CEV86I6", "B002R0FABA", "B00D10CLVW", "B003AVNY6I", "B002GZGI4E", "B001T9NUFS", "B002R0F7FE", "B00E1YRI4C", "B008UBQZKU", "B00D103F8U", "B007R2RM8W"],"also_viewed": ["B002BZX8Z6", "B00JHONN1S", "B008F0SU0Y", "B00D23MC6W", "B00AFDOPDA", "B00E1YRI4C", "B002GZGI4E", "B003AVKOP2", "B00D9C1WBM", "B00CEV8366", "B00CEUX0D8", "B0079ME3KU", "B00CEUWY8K", "B004FOEEHC", "0000031895", "B00BC4GY9Y", "B003XRKA7A", "B00K18LKX2", "B00EM7KAG6", "B00AMQ17JA", "B00D9C32NI", "B002C3Y6WG", "B00JLL4L5Y", "B003AVNY6I", "B008UBQZKU", "B00D0WDS9A", "B00613WDTQ", "B00538F5OK", "B005C4Y4F6", "B004LHZ1NY", "B00CPHX76U", "B00CEUWUZC", "B00IJVASUE", "B00GOR07RE", "B00J2GTM0W", "B00JHNSNSM", "B003IEDM9Q", "B00CYBU84G", "B008VV8NSQ", "B00CYBULSO", "B00I2UHSZA", "B005F50FXC", "B007LCQI3S", "B00DP68AVW", "B009RXWNSI", "B003AVEU6G", "B00HSOJB9M", "B00EHAGZNA", "B0046W9T8C", "B00E79VW6Q", "B00D10CLVW", "B00B0AVO54", "B00E95LC8Q", "B00GOR92SO", "B007ZN5Y56", "B00AL2569W", "B00B608000", "B008F0SMUC", "B00BFXLZ8M"],"bought_together": ["B002BZX8Z6"]},"salesRank": {"Toys & Games": 211836},"brand": "Coxlures","categories": [["Sports & Outdoors", "Other Sports", "Dance"]]
}

各字段分别为:

asin :物品ID;
title :物品名称;
price :物品价格;
imUrl :物品图片的URL;
related :相关产品(也买,也看,一起买,看后再买);
salesRank: 销售排名信息;
brand :品牌名称;
categories :该物品属于的种类列表;

1.2 数据处理

1.2.1 utils/1_convert_pd.py
import pickle
import pandas as pddef to_df(file_path):"""转化为DataFrame结构:param file_path: 文件路径:return:"""with open(file_path, 'r') as fin:df = {}i = 0for line in fin:df[i] = eval(line)i += 1df = pd.DataFrame.from_dict(df, orient='index')return dfreviews_df = to_df('../raw_data/reviews_Electronics_5.json')# 可以直接调用pandas的read_json方法,但会改变列的顺序
# reviews2_df = pd.read_json('../raw_data/reviews_Electronics_5.json', lines=True)# 序列化保存
with open('../raw_data/reviews.pkl', 'wb') as f:pickle.dump(reviews_df, f, pickle.HIGHEST_PROTOCOL)meta_df = to_df('../raw_data/meta_Electronics.json')
# 只保留review_df出现过的广告
meta_df = meta_df[meta_df['asin'].isin(reviews_df['asin'].unique())]
meta_df = meta_df.reset_index(drop=True)with open('../raw_data/meta.pkl', 'wb') as f:pickle.dump(meta_df, f, pickle.HIGHEST_PROTOCOL)

该程序:

  1. 将reviews_Electronics_5.json转换成dataframe,列分别为reviewID ,asin, reviewerName等;
  2. 将meta_Electronics.json转成dataframe,并且只保留在reviewes文件中出现过的商品,去重;
  3. 转换完的文件保存成pkl格式。
1.2.2 utils/2_remap_id.py
import random
import pickle
import numpy as nprandom.seed(1234)with open('../raw_data/reviews.pkl', 'rb') as f:reviews_df = pickle.load(f)reviews_df = reviews_df[['reviewerID', 'asin', 'unixReviewTime']]
with open('../raw_data/meta.pkl', 'rb') as f:meta_df = pickle.load(f)meta_df = meta_df[['asin', 'categories']]meta_df['categories'] = meta_df['categories'].map(lambda x: x[-1][-1])def build_map(df, col_name):"""制作一个映射,键为列名,值为序列数字:param df: reviews_df / meta_df:param col_name: 列名:return: 字典,键"""key = sorted(df[col_name].unique().tolist())m = dict(zip(key, range(len(key))))df[col_name] = df[col_name].map(lambda x: m[x])return m, key# reviews
reviews_df = pd.read_pickle('../raw_data/reviews.pkl')
reviews_df = reviews_df[['reviewerID', 'asin', 'unixReviewTime']]# meta
meta_df = pd.read_pickle('../raw_data/meta.pkl')
meta_df = meta_df[['asin', 'categories']]
# 类别只保留最后一个
meta_df['categories'] = meta_df['categories'].map(lambda x: x[-1][-1])# meta_df文件的物品ID映射
asin_map, asin_key = build_map(meta_df, 'asin')
# meta_df文件物品种类映射
cate_map, cate_key = build_map(meta_df, 'categories')
# reviews_df文件的用户ID映射
revi_map, revi_key = build_map(reviews_df, 'reviewerID')# user_count: 192403 item_count: 63001 cate_count: 801 example_count: 1689188
user_count, item_count, cate_count, example_count = \len(revi_map), len(asin_map), len(cate_map), reviews_df.shape[0]
# print('user_count: %d\titem_count: %d\tcate_count: %d\texample_count: %d' %
#       (user_count, item_count, cate_count, example_count))# 按物品id排序,并重置索引
meta_df = meta_df.sort_values('asin')
meta_df = meta_df.reset_index(drop=True)# reviews_df文件物品id进行映射,并按照用户id、浏览时间进行排序,重置索引
reviews_df['asin'] = reviews_df['asin'].map(lambda x: asin_map[x])
reviews_df = reviews_df.sort_values(['reviewerID', 'unixReviewTime'])
reviews_df = reviews_df.reset_index(drop=True)
reviews_df = reviews_df[['reviewerID', 'asin', 'unixReviewTime']]# 各个物品对应的类别
cate_list = np.array(meta_df['categories'], dtype='int32')# 保存所需数据为pkl文件
with open('../raw_data/remap.pkl', 'wb') as f:pickle.dump(reviews_df, f, pickle.HIGHEST_PROTOCOL)pickle.dump(cate_list, f, pickle.HIGHEST_PROTOCOL)pickle.dump((user_count, item_count, cate_count, example_count),f, pickle.HIGHEST_PROTOCOL)pickle.dump((asin_key, cate_key, revi_key), f, pickle.HIGHEST_PROTOCOL)

该程序:

  1. 将reviews_df只保留reviewerID, asin, unixReviewTime三列;
  2. 将meta_df保留asin, categories列,并且类别列只保留三级类目;(至此,用到的数据只设计5列,(reviewerID, asin, unixReviewTime),(asin, categories));
  3. 用asin,categories,reviewerID分别生产三个map(asin_map, cate_map, revi_map),key为对应的原始信息,value为按key排序后的index(从0开始顺序排序),然后将原数据的对应列原始数据转换成key对应的index;各个map的示意图如下: 请添加图片描述
  4. 将meta_df按asin对应的index进行排序,如图: 请添加图片描述
  5. 将reiviews_df中的asin转换成asin_map中asin对应的value值,并且按照reviewerID和时间排序。如图: 请添加图片描述
  6. 生成cate_list, 就是把meta_df的’categories’列取出来。
    请添加图片描述

1.3 生成训练集和测试集

din/build_dataset.py

import random
import picklerandom.seed(1234)with open('raw_data/remap.pkl', 'rb') as f:reviews_df = pickle.load(f)cate_list = pickle.load(f)user_count, item_count, cate_count, example_count = pickle.load(f)train_set, test_set = [], []# 最大的序列长度
max_sl = 0"""
生成训练集、测试集,每个用户所有浏览的物品(共n个)前n-1个为训练集(正样本),并生成相应的负样本,每个用户
共有n-2个训练集(第1个无浏览历史),第n个作为测试集。
"""
for reviewerID, hist in reviews_df.groupby('reviewerID'):# 每个用户浏览过的物品,即为正样本pos_list = hist['asin'].tolist()max_sl = max(max_sl, len(pos_list))# 生成负样本def gen_neg():neg = pos_list[0]while neg in pos_list:neg = random.randint(0, item_count - 1)return neg# 正负样本比例11neg_list = [gen_neg() for i in range(len(pos_list))]for i in range(1, len(pos_list)):# 生成每一次的历史记录,即之前的浏览历史hist = pos_list[:i]sl = len(hist)if i != len(pos_list) - 1:# 保存正负样本,格式:用户ID,正/负物品id,浏览历史,浏览历史长度,标签(1/0)train_set.append((reviewerID, pos_list[i], hist, sl, 1))train_set.append((reviewerID, neg_list[i], hist, sl, 0))else:# 最后一次保存为测试集test_set.append((reviewerID, pos_list[i], hist, sl, 1))test_set.append((reviewerID, neg_list[i], hist, sl, 0))# 打乱顺序
random.shuffle(train_set)
random.shuffle(test_set)assert len(test_set) == user_count# 写入dataset.pkl文件
with open('dataset/dataset.pkl', 'wb') as f:pickle.dump(train_set, f, pickle.HIGHEST_PROTOCOL)pickle.dump(test_set, f, pickle.HIGHEST_PROTOCOL)pickle.dump(cate_list, f, pickle.HIGHEST_PROTOCOL)pickle.dump((user_count, item_count, cate_count, max_sl), f, pickle.HIGHEST_PROTOCOL)

该代码:

  1. 将reviews_df按reviewerID进行聚合
    在这里插入图片描述
  2. 将hist的asin列作为每个reviewerID(也就是用户)的正样本列表(pos_list),注意这里的asin存的已经不是原始的item_id了,而是通过asin_map转换过来的index。负样本列表(neg_list)为在item_count范围内产生不在pos_list中的随机数列表。
  3. 得到pos_list后就开始构造训练集和测试集了。

训练集的构造方法:
如上图,例如对于reviewerID=0的用户,他的pos_list为[13179, 17993, 28326, 29247, 62275], 生成的训练集格式为(reviewerID, hist, pos_item, 1), (reviewerID, hist, neg_item, 0),这里需要注意hist并不包含pos_item, hist只包含在pos_item之前点击过的item,因为DIN采用类似attention的机制,只有历史的行为的attention才对后续的有影响,所以hist只包含pos_item之前点击的item才有意义。例如,对于reviewerID=0的用户,构造的训练集为:
请添加图片描述
测试集的构造方法:
对于每个pos_list和neg_list的最后一个item,用做生成测试集,测试集的格式为(reviewerID, hist, (pos_item, neg_item))
请添加图片描述

二、模型构建

2.1 DIN模型简介

相比于之前很多 “学术风” 的深度学习模型,阿里巴巴提出的 DIN 模型显然更具业务气息。它的应用场景是阿里巴巴的电商广告推荐,因此在计算一个用户是否点击一个广告 a 时,模型的输入特征自然分为两大部分:一部分是用户 u 的特征组,另一部分是候选广告 a 的特征组。无论是用户还是广告,都含有两个非常重要的特征:商品 id (good_id) 和商铺 id (shop_id)。用户特征里的商品 id 是一个序列,代表用户曾经点击过的商品集合,商铺 id 同理;而广告特征里的商品 id 和商铺 id 就是广告对应的商品 id 和商铺 id。

在原来的基础模型中,用户特征组中的商品序列和商铺序列经过简单的平均池化操作后就进入上层神经网络进行下一步训练,序列中的商品既没有区分重要程度,也和广告特征中的商品 id 没有关系。
请添加图片描述
然而事实上,广告特征和用户特征的关联程度是非常强的。假设广告中的商品是键盘,用户的点击商品序列中有几个不同的商品 id 分别是鼠标、T 恤和洗面奶。从常识出发,“鼠标” 这个历史商品 id 对预测 “键盘” 广告的点击率的重要程度应大于后两者。从模型的角度来说,在建模过程中投给不同特征的 “注意力” 理应有所不同,而且 “注意力得分” 的计算理应与广告特征有相关性。

将上述注意力的思想反映到模型中也是直观的,利用候选商品和历史行为商品之间的相关性计算出一个权重,这个权重就到表了注意力的强弱:
请添加图片描述
请添加图片描述

2.2 DIN代码结构概述

DIN代码是从train.py开始,在train.py中:

  1. 获取 训练数据 和 测试数据,这两个都是数据迭代器,用于数据的不断输入,定义在input.py中
  2. 生成相应的model,定义在model.py中

2.3 input.py

构建数据迭代器,用于训练数据和测试数据的不断输入

import numpy as npclass DataInput:def __init__(self, data, batch_size):self.batch_size = batch_sizeself.data = dataself.epoch_size = len(self.data) // self.batch_sizeif self.epoch_size * self.batch_size < len(self.data):self.epoch_size += 1self.i = 0def __iter__(self):return selfdef next(self):if self.i == self.epoch_size:raise StopIterationts = self.data[self.i * self.batch_size : min((self.i+1) * self.batch_size,len(self.data))]self.i += 1u, i, y, sl = [], [], [], []for t in ts:u.append(t[0])i.append(t[2])y.append(t[3])sl.append(len(t[1]))max_sl = max(sl)hist_i = np.zeros([len(ts), max_sl], np.int64)k = 0for t in ts:for l in range(len(t[1])):hist_i[k][l] = t[1][l]k += 1# u 是user_id, map后的# i 是item_id, map后的# y 是label, 1代表postive example, 0代表negtive example# hist_i 是用户的购买序列, 统一被填充成了max_sl(行为序列的最大长度)的长度# sl 是该用户的行为序列的长度return self.i, (u, i, y, hist_i, sl)class DataInputTest:def __init__(self, data, batch_size):self.batch_size = batch_sizeself.data = dataself.epoch_size = len(self.data) // self.batch_sizeif self.epoch_size * self.batch_size < len(self.data):self.epoch_size += 1self.i = 0def __iter__(self):return selfdef next(self):if self.i == self.epoch_size:raise StopIterationts = self.data[self.i * self.batch_size : min((self.i+1) * self.batch_size,len(self.data))]self.i += 1u, i, j, sl = [], [], [], []for t in ts:u.append(t[0])i.append(t[2][0])j.append(t[2][1])sl.append(len(t[1]))max_sl = max(sl)hist_i = np.zeros([len(ts), max_sl], np.int64)k = 0for t in ts:for l in range(len(t[1])):hist_i[k][l] = t[1][l]k += 1# u 是user_id, map后的# i 是positive item, map后的# j 是negtive item, map后的# hist_i 是用户的购买序列, 统一被填充成了max_sl(行为序列的最大长度)的长度# sl 是该用户的行为序列的长度return self.i, (u, i, j, hist_i, sl)

2.4 model.py

模型结构为:
在这里插入图片描述

import tensorflow as tf
from Dice import diceclass Model(object):def __init__(self,user_count,item_count,cate_count,cate_list):# shape: [B],  user id。 (B:batch size)self.u = tf.placeholder(tf.int32, [None, ])# shape: [B]  i: 正样本的itemself.i = tf.placeholder(tf.int32, [None, ])# shape: [B]  j: 负样本的itemself.j = tf.placeholder(tf.int32, [None, ])# shape: [B], y: labelself.y = tf.placeholder(tf.float32, [None, ])# shape: [B, T] #用户行为特征(User Behavior)中的item序列。T为序列长度self.hist_i = tf.placeholder(tf.int32, [None, None])# shape: [B]; sl:sequence length,User Behavior中序列的真实序列长度(?)self.sl = tf.placeholder(tf.int32, [None, ])#learning rateself.lr = tf.placeholder(tf.float64, [])hidden_units = 128# shape: [U, H], user_id的embedding weight. U是user_id的hash bucket size# tf.get_variable函数的作用是创建新的tensorflow变量user_emb_w = tf.get_variable("user_emb_w", [user_count, hidden_units])# shape: [I, H//2], item_id的embedding weight. I是item_id的hash bucket sizeitem_emb_w = tf.get_variable("item_emb_w", [item_count, hidden_units // 2])  # [I, H//2]# shape: [I], biasitem_b = tf.get_variable("item_b", [item_count],initializer=tf.constant_initializer(0.0))# shape: [C, H//2], cate_id的embedding weight.cate_emb_w = tf.get_variable("cate_emb_w", [cate_count, hidden_units // 2])# shape: [C, H//2]cate_list = tf.convert_to_tensor(cate_list, dtype=tf.int64)# 从cate_list中取出正样本的cate# tf.gather是从params的axis维根据indices的参数值获取切片ic = tf.gather(cate_list, self.i)# 正样本的embedding,embedding由item_id和cate_id的embedding concat而成i_emb = tf.concat(values=[tf.nn.embedding_lookup(item_emb_w, self.i),tf.nn.embedding_lookup(cate_emb_w, ic),], axis=1)# 偏置bi_b = tf.gather(item_b, self.i)# 从cate_list中取出负样本的catejc = tf.gather(cate_list, self.j)# 负样本的embedding,embedding由item_id和cate_id的embedding concat而成j_emb = tf.concat([tf.nn.embedding_lookup(item_emb_w, self.j),tf.nn.embedding_lookup(cate_emb_w, jc),], axis=1)# 偏置bj_b = tf.gather(item_b, self.j)# 用户行为序列(User Behavior)中的cate序列hc = tf.gather(cate_list, self.hist_i)# 用户行为序列(User Behavior)的embedding,包括item序列和cate序列h_emb = tf.concat([tf.nn.embedding_lookup(item_emb_w, self.hist_i),tf.nn.embedding_lookup(cate_emb_w, hc),], axis=2)# attention操作hist_i = attention(i_emb, h_emb, self.sl)  # -- attention end ---hist = tf.layers.batch_normalization(inputs=hist) hist = tf.reshape(hist,[-1,hidden_units])# (B, 1, H) -> (B, H)#添加一层全连接层,hist为输入,hidden_units为输出维数hist = tf.layers.dense(hist,hidden_units)u_emb = hist#下面两个全连接用来计算y',i为正样本,j为负样本# fcn begindin_i = tf.concat([u_emb, i_emb], axis=-1)din_i = tf.layers.batch_normalization(inputs=din_i, name='b1')d_layer_1_i = tf.layers.dense(din_i, 80, activation=None, name='f1')d_layer_1_i = dice(d_layer_1_i, name='dice_1_i')d_layer_2_i = tf.layers.dense(d_layer_1_i, 40, activation=None, name='f2')d_layer_2_i = dice(d_layer_2_i, name='dice_2_i')d_layer_3_i = tf.layers.dense(d_layer_2_i, 1, activation=None, name='f3')# (B, 1)din_j = tf.concat([u_emb, j_emb], axis=-1)din_j = tf.layers.batch_normalization(inputs=din_j, name='b1', reuse=True)d_layer_1_j = tf.layers.dense(din_j, 80, activation=None, name='f1', reuse=True)d_layer_1_j = dice(d_layer_1_j, name='dice_1_j')d_layer_2_j = tf.layers.dense(d_layer_1_j, 40, activation=None, name='f2', reuse=True)d_layer_2_j = dice(d_layer_2_j, name='dice_2_j')d_layer_3_j = tf.layers.dense(d_layer_2_j, 1, activation=None, name='f3', reuse=True)d_layer_3_i = tf.reshape(d_layer_3_i, [-1])d_layer_3_j = tf.reshape(d_layer_3_j, [-1])# (B,1) -> (B)#预测的(y正-y负)x = i_b - j_b + d_layer_3_i - d_layer_3_j  # [B]#预测的(y正)self.logits = i_b + d_layer_3_i # (B)# i_b是偏置# train end# logits for all item:u_emb_all = tf.expand_dims(u_emb, 1)u_emb_all = tf.tile(u_emb_all, [1, item_count, 1])#将所有的除u_emb_all外的embedding,concat到一起all_emb = tf.concat([item_emb_w,tf.nn.embedding_lookup(cate_emb_w, cate_list)], axis=1)all_emb = tf.expand_dims(all_emb, 0)all_emb = tf.tile(all_emb, [512, 1, 1])# 将所有的embedding,concat到一起din_all = tf.concat([u_emb_all, all_emb], axis=-1)din_all = tf.layers.batch_normalization(inputs=din_all, name='b1', reuse=True)d_layer_1_all = tf.layers.dense(din_all, 80, activation=None, name='f1', reuse=True)d_layer_1_all = dice(d_layer_1_all, name='dice_1_all')d_layer_2_all = tf.layers.dense(d_layer_1_all, 40, activation=None, name='f2', reuse=True)d_layer_2_all = dice(d_layer_2_all, name='dice_2_all')d_layer_3_all = tf.layers.dense(d_layer_2_all, 1, activation=None, name='f3', reuse=True)d_layer_3_all = tf.reshape(d_layer_3_all, [-1, item_count])self.logits_all = tf.sigmoid(item_b + d_layer_3_all)# -- fcn end -------self.mf_auc = tf.reduce_mean(tf.to_float(x > 0))self.score_i = tf.sigmoid(i_b + d_layer_3_i)self.score_j = tf.sigmoid(j_b + d_layer_3_j)self.score_i = tf.reshape(self.score_i, [-1, 1])self.score_j = tf.reshape(self.score_j, [-1, 1])self.p_and_n = tf.concat([self.score_i, self.score_j], axis=-1)# Step variableself.global_step = tf.Variable(0, trainable=False, name='global_step')self.global_epoch_step = tf.Variable(0, trainable=False, name='global_epoch_step')self.global_epoch_step_op = tf.assign(self.global_epoch_step, self.global_epoch_step + 1)# loss and trainself.loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=self.logits,labels=self.y))trainable_params = tf.trainable_variables()self.train_op = tf.train.GradientDescentOptimizer(learning_rate=self.lr).minimize(self.loss)def train(self,sess,uij,l):loss,_ = sess.run([self.loss,self.train_op],feed_dict={#self.u : uij[0],self.i : uij[1],self.y : uij[2],self.hist_i : uij[3],self.sl : uij[4],self.lr : l})return lossdef eval(self, sess, uij):u_auc, socre_p_and_n = sess.run([self.mf_auc, self.p_and_n], feed_dict={#self.u: uij[0],self.i: uij[1],#正样本self.j: uij[2],#负样本self.hist_i: uij[3],self.sl: uij[4],})return u_auc, socre_p_and_ndef test(self, sess, uid, hist_i, sl):return sess.run(self.logits_all, feed_dict={self.u: uid,self.hist_i: hist_i,self.sl: sl,})def save(self, sess, path):saver = tf.train.Saver()saver.save(sess, save_path=path)def restore(self, sess, path):saver = tf.train.Saver()saver.restore(sess, save_path=path)def extract_axis_1(data, ind):batch_range = tf.range(tf.shape(data)[0])indices = tf.stack([batch_range, ind], axis=1)res = tf.gather_nd(data, indices)return res#item_embedding,history_behivior_embedding,sequence_length
def attention(queries,keys,keys_length):'''queries:     [B, H]    [batch_size,embedding_size]keys:        [B, T, H]   [batch_size,T,embedding_size]keys_length: [B]        [batch_size]#T为历史行为序列长度'''#(?,32)->(None,32)->32# tile()函数是用来对张量(Tensor)进行扩展的,其特点是对当前张量内的数据进行一定规则的复制。最终的输出张量维度不变# tf.shape(keys)[1]==T# 对queries的维度进行reshape# (?,T,32)这里是为了让queries和keys的维度相同而做的操作# (?,T,128)把u和v以及u v的element wise差值向量合并起来作为输入,# 然后喂给全连接层,最后得出两个item embedding,比如u和v的权重,即g(Vi,Va)queries_hidden_units = queries.get_shape().as_list()[-1]queries = tf.tile(queries,[1,tf.shape(keys)[1]])queries = tf.reshape(queries,[-1,tf.shape(keys)[1],queries_hidden_units])din_all = tf.concat([queries,keys,queries-keys,queries * keys],axis=-1) # B*T*4H# 三层全链接(d_layer_3_all为训练出来的atteneion权重)d_layer_1_all = tf.layers.dense(din_all, 80, activation=tf.nn.sigmoid, name='f1_att')d_layer_2_all = tf.layers.dense(d_layer_1_all, 40, activation=tf.nn.sigmoid, name='f2_att')d_layer_3_all = tf.layers.dense(d_layer_2_all, 1, activation=None, name='f3_att') #B*T*1#为了让outputs维度和keys的维度一致outputs = tf.reshape(d_layer_3_all,[-1,1,tf.shape(keys)[1]]) #B*1*T#  bool类型 tf.shape(keys)[1]为历史行为序列的最大长度,keys_length为人为设定的参数,#  如tf.sequence_mask(53)  即为array[True,True,True,False,False]#  函数的作用是为了后面补齐行为序列,获取等长的行为序列做铺垫key_masks = tf.sequence_mask(keys_length,tf.shape(keys)[1])#在第二维增加一维,也就是由B*T变成B*1*Tkey_masks = tf.expand_dims(key_masks,1) # B*1*T#tf.ones_like新建一个与output类型大小一致的tensor,设置填充值为一个很小的值,而不是0,padding的mask后补一个很小的负数,这样softmax之后就会接近0paddings = tf.ones_like(outputs) * (-2 ** 32 + 1)#填充,获取等长的行为序列# tf.where(condition, x, y),condition是bool型值,True/False,返回值是对应元素,condition中元素为True的元素替换为x中的元素,为False的元素替换为y中对应元素#由于是替换,返回值的维度,和condition,x , y都是相等的。outputs = tf.where(key_masks,outputs,paddings) # B * 1 * T# Scale(缩放)outputs = outputs / (keys.get_shape().as_list()[-1] ** 0.5)# Activationoutputs = tf.nn.softmax(outputs) # B * 1 * T# Weighted Sum outputs=g(Vi,Va)   keys=Vi#这步为公式中的g(Vi*Va)*Vioutputs = tf.matmul(outputs,keys) # B * 1 * H 三维矩阵相乘,相乘发生在后两维,即 B * (( 1 * T ) * ( T * H ))return outputs

2.5 train.py

import os
import time
import pickle
import random
import numpy as np
import tensorflow as tf
import sys
from input import DataInput, DataInputTest
from model import Modelos.environ['CUDA_VISIBLE_DEVICES'] = '1'
random.seed(1234)
np.random.seed(1234)
tf.set_random_seed(1234)train_batch_size = 32
test_batch_size = 512
predict_batch_size = 32
predict_users_num = 1000
predict_ads_num = 100with open('dataset.pkl', 'rb') as f:train_set = pickle.load(f)test_set = pickle.load(f)cate_list = pickle.load(f)user_count, item_count, cate_count = pickle.load(f)best_auc = 0.0
def calc_auc(raw_arr):"""SummaryArgs:raw_arr (TYPE): DescriptionReturns:TYPE: Description"""# sort by pred value, from small to bigarr = sorted(raw_arr, key=lambda d:d[2])auc = 0.0fp1, tp1, fp2, tp2 = 0.0, 0.0, 0.0, 0.0for record in arr:fp2 += record[0] # noclicktp2 += record[1] # clickauc += (fp2 - fp1) * (tp2 + tp1)fp1, tp1 = fp2, tp2# if all nonclick or click, disgardthreshold = len(arr) - 1e-3if tp2 > threshold or fp2 > threshold:return -0.5if tp2 * fp2 > 0.0:  # normal aucreturn (1.0 - auc / (2.0 * tp2 * fp2))else:return Nonedef _auc_arr(score):score_p = score[:,0]score_n = score[:,1]#print "============== p ============="#print score_p#print "============== n ============="#print score_nscore_arr = []for s in score_p.tolist():score_arr.append([0, 1, s])for s in score_n.tolist():score_arr.append([1, 0, s])return score_arr
def _eval(sess, model):auc_sum = 0.0score_arr = []for _, uij in DataInputTest(test_set, test_batch_size):auc_, score_ = model.eval(sess, uij)score_arr += _auc_arr(score_)auc_sum += auc_ * len(uij[0])test_gauc = auc_sum / len(test_set)Auc = calc_auc(score_arr)global best_aucif best_auc < test_gauc:best_auc = test_gaucmodel.save(sess, 'save_path/ckpt')return test_gauc, Aucdef _test(sess, model):auc_sum = 0.0score_arr = []predicted_users_num = 0print "test sub items"for _, uij in DataInputTest(test_set, predict_batch_size):if predicted_users_num >= predict_users_num:breakscore_ = model.test(sess, uij)score_arr.append(score_)predicted_users_num += predict_batch_sizereturn score_[0]gpu_options = tf.GPUOptions(allow_growth=True)
with tf.Session(config=tf.ConfigProto(gpu_options=gpu_options)) as sess:model = Model(user_count, item_count, cate_count, cate_list, predict_batch_size, predict_ads_num)sess.run(tf.global_variables_initializer())sess.run(tf.local_variables_initializer())print('test_gauc: %.4f\t test_auc: %.4f' % _eval(sess, model))sys.stdout.flush()lr = 1.0start_time = time.time()for _ in range(50):random.shuffle(train_set)epoch_size = round(len(train_set) / train_batch_size)loss_sum = 0.0for _, uij in DataInput(train_set, train_batch_size):# 获取 训练数据 ,这是一个数据迭代器,用于数据的不断输入# uij是一个五元组 (u, i, y, hist_i, sl)# u 是user_id, map后的# i 是item_id, map后的# y 是label, 1代表postive example, 0代表negtive example# hist_i 是用户的购买序列, 统一被填充成了max_sl(行为序列的最大长度)的长度# sl 是该用户的行为序列的长度loss = model.train(sess, uij, lr)loss_sum += lossif model.global_step.eval() % 1000 == 0:test_gauc, Auc = _eval(sess, model)print('Epoch %d Global_step %d\tTrain_loss: %.4f\tEval_GAUC: %.4f\tEval_AUC: %.4f' %(model.global_epoch_step.eval(), model.global_step.eval(),loss_sum / 1000, test_gauc, Auc))sys.stdout.flush()loss_sum = 0.0if model.global_step.eval() % 336000 == 0:lr = 0.1print('Epoch %d DONE\tCost time: %.2f' %(model.global_epoch_step.eval(), time.time()-start_time))sys.stdout.flush()model.global_epoch_step_op.eval()print('best test_gauc:', best_auc)sys.stdout.flush()

参考资料

  1. DIN论文官方实现解析
  2. 广告点击率预估模型—DIN的Tensorflow2.X代码分析
  3. 推荐系统之DIN代码详解
  4. DIN算法代码详细解读

这篇关于【推荐算法代码实现】Deep Interest Network for Click-Through Rate Prediction代码实现和解读的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/359793

相关文章

基于Python Playwright进行前端性能测试的脚本实现

《基于PythonPlaywright进行前端性能测试的脚本实现》在当今Web应用开发中,性能优化是提升用户体验的关键因素之一,本文将介绍如何使用Playwright构建一个自动化性能测试工具,希望... 目录引言工具概述整体架构核心实现解析1. 浏览器初始化2. 性能数据收集3. 资源分析4. 关键性能指

使用Redis快速实现共享Session登录的详细步骤

《使用Redis快速实现共享Session登录的详细步骤》在Web开发中,Session通常用于存储用户的会话信息,允许用户在多个页面之间保持登录状态,Redis是一个开源的高性能键值数据库,广泛用于... 目录前言实现原理:步骤:使用Redis实现共享Session登录1. 引入Redis依赖2. 配置R

SpringBoot实现RSA+AES自动接口解密的实战指南

《SpringBoot实现RSA+AES自动接口解密的实战指南》在当今数据泄露频发的网络环境中,接口安全已成为开发者不可忽视的核心议题,RSA+AES混合加密方案因其安全性高、性能优越而被广泛采用,本... 目录一、项目依赖与环境准备1.1 Maven依赖配置1.2 密钥生成与配置二、加密工具类实现2.1

在Java中实现线程之间的数据共享的几种方式总结

《在Java中实现线程之间的数据共享的几种方式总结》在Java中实现线程间数据共享是并发编程的核心需求,但需要谨慎处理同步问题以避免竞态条件,本文通过代码示例给大家介绍了几种主要实现方式及其最佳实践,... 目录1. 共享变量与同步机制2. 轻量级通信机制3. 线程安全容器4. 线程局部变量(ThreadL

Python标准库datetime模块日期和时间数据类型解读

《Python标准库datetime模块日期和时间数据类型解读》文章介绍Python中datetime模块的date、time、datetime类,用于处理日期、时间及日期时间结合体,通过属性获取时间... 目录Datetime常用类日期date类型使用时间 time 类型使用日期和时间的结合体–日期时间(

python使用Akshare与Streamlit实现股票估值分析教程(图文代码)

《python使用Akshare与Streamlit实现股票估值分析教程(图文代码)》入职测试中的一道题,要求:从Akshare下载某一个股票近十年的财务报表包括,资产负债表,利润表,现金流量表,保存... 目录一、前言二、核心知识点梳理1、Akshare数据获取2、Pandas数据处理3、Matplotl

Django开发时如何避免频繁发送短信验证码(python图文代码)

《Django开发时如何避免频繁发送短信验证码(python图文代码)》Django开发时,为防止频繁发送验证码,后端需用Redis限制请求频率,结合管道技术提升效率,通过生产者消费者模式解耦业务逻辑... 目录避免频繁发送 验证码1. www.chinasem.cn避免频繁发送 验证码逻辑分析2. 避免频繁

分布式锁在Spring Boot应用中的实现过程

《分布式锁在SpringBoot应用中的实现过程》文章介绍在SpringBoot中通过自定义Lock注解、LockAspect切面和RedisLockUtils工具类实现分布式锁,确保多实例并发操作... 目录Lock注解LockASPect切面RedisLockUtils工具类总结在现代微服务架构中,分布

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

精选20个好玩又实用的的Python实战项目(有图文代码)

《精选20个好玩又实用的的Python实战项目(有图文代码)》文章介绍了20个实用Python项目,涵盖游戏开发、工具应用、图像处理、机器学习等,使用Tkinter、PIL、OpenCV、Kivy等库... 目录① 猜字游戏② 闹钟③ 骰子模拟器④ 二维码⑤ 语言检测⑥ 加密和解密⑦ URL缩短⑧ 音乐播放