接上一節資料處理,本節將詳細介紹訓練和網路模型部分的代碼,為了配合python3的執行,部分代碼做了修改,先給出整個train.py的加注解代碼:
import numpy
from data_iterator import DataIterator
import tensorflow as tf
from model import *
import time
import random
import sys
from utils import *
EMBEDDING_DIM = 18
HIDDEN_SIZE = 18 * 2
ATTENTION_SIZE = 18 * 2
best_auc = 0.0
def prepare_data(input, target, maxlen = None, return_neg = False):
# x: a list of sentences
# input: N個訓練樣本,每一行格式如下:
# 用戶id[0], 商品id[1], 商品分類[2],之前點過商品(n個)[3],之前點個商品分類(n個)[4],沒點過商品(n*5個)[5],沒點過商品分類(n*5個)[6]
# label: 正樣本或者負樣本
lengths_x = [len(s[4]) for s in input] # N, 每個樣本之前點擊商品的個數
seqs_mid = [inp[3] for inp in input] # N*n, 之前點過商品序列
seqs_cat = [inp[4] for inp in input] # N * n, 之前點過商品分類序列
noclk_seqs_mid = [inp[5] for inp in input] # N * n * 5, 之前沒點過商品序列
noclk_seqs_cat = [inp[6] for inp in input] # N * n * 5, 之前沒點過商品分類
if maxlen is not None:
new_seqs_mid = []
new_seqs_cat = []
new_noclk_seqs_mid = []
new_noclk_seqs_cat = []
new_lengths_x = []
for l_x, inp in zip(lengths_x, input): # zip生成組元組成的list,長度與最小list長度一致
if l_x > maxlen:
new_seqs_mid.append(inp[3][l_x - maxlen:])
new_seqs_cat.append(inp[4][l_x - maxlen:])
new_noclk_seqs_mid.append(inp[5][l_x - maxlen:])
new_noclk_seqs_cat.append(inp[6][l_x - maxlen:])
new_lengths_x.append(maxlen)
else:
new_seqs_mid.append(inp[3])
new_seqs_cat.append(inp[4])
new_noclk_seqs_mid.append(inp[5])
new_noclk_seqs_cat.append(inp[6])
new_lengths_x.append(l_x)
lengths_x = new_lengths_x
seqs_mid = new_seqs_mid
seqs_cat = new_seqs_cat
noclk_seqs_mid = new_noclk_seqs_mid
noclk_seqs_cat = new_noclk_seqs_cat
if len(lengths_x) < 1:
return None, None, None, None
n_samples = len(seqs_mid) # 樣本數 N
maxlen_x = numpy.max(lengths_x) # 之前最多的點擊樣本個數;
if maxlen_x <= 1:
maxlen_x = 2
neg_samples = len(noclk_seqs_mid[0][0]) # 每一次之前點擊行為對應的負樣本個數
mid_his = numpy.zeros((n_samples, maxlen_x)).astype('int64') # N * maxLen_x 之前點擊item id 序列
cat_his = numpy.zeros((n_samples, maxlen_x)).astype('int64') # N * maxLen_x 之前點擊item 分類 序列
noclk_mid_his = numpy.zeros((n_samples, maxlen_x, neg_samples)).astype('int64') # N * maxLen_x * ngsample(5), 之前每次點擊對應負樣本
noclk_cat_his = numpy.zeros((n_samples, maxlen_x, neg_samples)).astype('int64') # N * maxLen_x * ngsample(5), 之前每次點擊對應負樣本分類
mid_mask = numpy.zeros((n_samples, maxlen_x)).astype('float32') # N * maxLen_x 實際之前點擊序列長度
for idx, [s_x, s_y, no_sx, no_sy] in enumerate(zip(seqs_mid, seqs_cat, noclk_seqs_mid, noclk_seqs_cat)):
mid_mask[idx, :lengths_x[idx]] = 1. # 第idx個樣本,前lengths_x[idx]置為1,即有點擊的位置置為1.
mid_his[idx, :lengths_x[idx]] = s_x # 第idx個樣本,之前點過的商品id序列
cat_his[idx, :lengths_x[idx]] = s_y # 第idx個樣本,之前點過的商品分類序列
noclk_mid_his[idx, :lengths_x[idx], :] = no_sx # 第idx個樣本,沒點過負樣本id
noclk_cat_his[idx, :lengths_x[idx], :] = no_sy # 第idx個樣本,沒點過負樣本分類
uids = numpy.array([inp[0] for inp in input]) # N,用戶id
mids = numpy.array([inp[1] for inp in input]) # N,商品id
cats = numpy.array([inp[2] for inp in input]) # N,商品分類
if return_neg:
return uids, mids, cats, mid_his, cat_his, mid_mask, numpy.array(target), numpy.array(lengths_x), noclk_mid_his, noclk_cat_his
# uids: N, 用戶id
# mids: N, 商品 item id
# cats: N, 商品分類
# mid_his: N * maxLen_x 之前點擊item id 序列
# cat_his: N * maxLen_x 之前點擊item 分類 序列
# mid_mask: N * maxLen_x 實際之前點擊序列長度
# numpy.array(target): N * 2, label 正樣本 [1,0] or 負樣本 [0,1]
# numpy.array(lengths_x):N, 實際之前點擊樣本序列長度
# noclk_mid_his:N * maxLen_x * ngsample(5), 之前每次點擊對應負樣本
# noclk_cat_his:N * maxLen_x * ngsample(5), 之前每次點擊對應負樣本分類
else:
return uids, mids, cats, mid_his, cat_his, mid_mask, numpy.array(target), numpy.array(lengths_x)
def eval(sess, test_data, model, model_path):
loss_sum = 0.
accuracy_sum = 0.
aux_loss_sum = 0.
nums = 0
stored_arr = []
for src, tgt in test_data:
nums += 1
uids, mids, cats, mid_his, cat_his, mid_mask, target, sl, noclk_mids, noclk_cats = prepare_data(src, tgt, return_neg=True)
# uids: N, 用戶id
# mids: N, 商品 item id
# cats: N, 商品分類
# mid_his: N * maxLen_x 之前點擊item id 序列
# cat_his: N * maxLen_x 之前點擊item 分類 序列
# mid_mask: N * maxLen_x 實際之前點擊序列長度
# target: N * 2, label 正樣本 [1,0] or 負樣本 [0,1]
# sl:N, 實際之前點擊樣本序列長度
# noclk_mids: N * maxLen_x * ngsample(5), 之前每次點擊對應負樣本
# noclk_cats: N * maxLen_x * ngsample(5), 之前每次點擊對應負樣本分類
prob, loss, acc, aux_loss = model.calculate(sess, [uids, mids, cats, mid_his, cat_his, mid_mask, target, sl, noclk_mids, noclk_cats])
loss_sum += loss
aux_loss_sum = aux_loss
accuracy_sum += acc
prob_1 = prob[:, 0].tolist()
target_1 = target[:, 0].tolist()
for p ,t in zip(prob_1, target_1):
stored_arr.append([p, t])
test_auc = calc_auc(stored_arr)
accuracy_sum = accuracy_sum / nums
loss_sum = loss_sum / nums
aux_loss_sum / nums
global best_auc
if best_auc < test_auc:
best_auc = test_auc
model.save(sess, model_path)
return test_auc, loss_sum, accuracy_sum, aux_loss_sum
def train(
train_file = "local_train_splitByUser",
test_file = "local_test_splitByUser",
#第一行:label 0, 用戶id, 商品id(未點擊,負樣本), 商品分類, 之前點擊過所有商品id,之前點擊過所有商品分類
#第二行:label 1, 用戶id, 商品id(點擊過,正樣本), 商品分類, 之前點擊過所有商品id,之前點擊過所有商品分類
uid_voc = "uid_voc.pkl",
mid_voc = "mid_voc.pkl",
cat_voc = "cat_voc.pkl",
batch_size = 128,
maxlen = 100,
test_iter = 100,
save_iter = 100,
model_type = 'DNN',
seed = 2,
):
model_path = "dnn_save_path/ckpt_noshuff" + model_type + str(seed)
best_model_path = "dnn_best_model/ckpt_noshuff" + model_type + str(seed)
gpu_options = tf.GPUOptions(allow_growth=True)
with tf.Session(config=tf.ConfigProto(gpu_options=gpu_options)) as sess:
train_data = DataIterator(train_file, uid_voc, mid_voc, cat_voc, batch_size, maxlen, shuffle_each_epoch=False)
test_data = DataIterator(test_file, uid_voc, mid_voc, cat_voc, batch_size, maxlen)
n_uid, n_mid, n_cat = train_data.get_n() # 用戶數、商品數和分類數
if model_type == 'DNN':
model = Model_DNN(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
elif model_type == 'PNN':
model = Model_PNN(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
elif model_type == 'Wide':
model = Model_WideDeep(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
elif model_type == 'DIN':
model = Model_DIN(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
elif model_type == 'DIN-V2-gru-att-gru':
model = Model_DIN_V2_Gru_att_Gru(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
elif model_type == 'DIN-V2-gru-gru-att':
model = Model_DIN_V2_Gru_Gru_att(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
elif model_type == 'DIN-V2-gru-qa-attGru':
model = Model_DIN_V2_Gru_QA_attGru(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
elif model_type == 'DIN-V2-gru-vec-attGru':
model = Model_DIN_V2_Gru_Vec_attGru(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
elif model_type == 'DIEN':
model = Model_DIN_V2_Gru_Vec_attGru_Neg(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
else:
print ("Invalid model_type : %s", model_type)
return
# model = Model_DNN(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
sess.run(tf.global_variables_initializer())
sess.run(tf.local_variables_initializer())
sys.stdout.flush()
print('test_auc: %.4f ---- test_loss: %.4f ---- test_accuracy: %.4f ---- test_aux_loss: %.4f' % eval(sess, test_data, model, best_model_path))
sys.stdout.flush()
start_time = time.time()
iter = 0
lr = 0.001
for itr in range(3):
loss_sum = 0.0
accuracy_sum = 0.
aux_loss_sum = 0.
for src, tgt in train_data:
# src : 用戶id, 商品id, 商品分類,之前點過商品(n個),之前點個商品分類(n個),沒點過商品(n*5個),沒點過商品分類(n*5個)
# label: 正樣本或者負樣本
uids, mids, cats, mid_his, cat_his, mid_mask, target, sl, noclk_mids, noclk_cats = prepare_data(src, tgt, maxlen, return_neg=True)
loss, acc, aux_loss = model.train(sess, [uids, mids, cats, mid_his, cat_his, mid_mask, target, sl, lr, noclk_mids, noclk_cats])
loss_sum += loss
accuracy_sum += acc
aux_loss_sum += aux_loss
iter += 1
sys.stdout.flush()
if (iter % test_iter) == 0:
print('iter: %d ----> train_loss: %.4f ---- train_accuracy: %.4f ---- tran_aux_loss: %.4f' % \
(iter, loss_sum / test_iter, accuracy_sum / test_iter, aux_loss_sum / test_iter))
print(' test_auc: %.4f ----test_loss: %.4f ---- test_accuracy: %.4f ---- test_aux_loss: %.4f' % eval(sess, test_data, model, best_model_path))
loss_sum = 0.0
accuracy_sum = 0.0
aux_loss_sum = 0.0
if (iter % save_iter) == 0:
print('save model iter: %d' %(iter))
model.save(sess, model_path+"--"+str(iter))
lr *= 0.5
def test(
train_file = "local_train_splitByUser",
test_file = "local_test_splitByUser",
uid_voc = "uid_voc.pkl",
mid_voc = "mid_voc.pkl",
cat_voc = "cat_voc.pkl",
batch_size = 128,
maxlen = 100,
model_type = 'DNN',
seed = 2
):
model_path = "dnn_best_model/ckpt_noshuff" + model_type + str(seed)
gpu_options = tf.GPUOptions(allow_growth=True)
with tf.Session(config=tf.ConfigProto(gpu_options=gpu_options)) as sess:
train_data = DataIterator(train_file, uid_voc, mid_voc, cat_voc, batch_size, maxlen)
test_data = DataIterator(test_file, uid_voc, mid_voc, cat_voc, batch_size, maxlen)
n_uid, n_mid, n_cat = train_data.get_n()
if model_type == 'DNN':
model = Model_DNN(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
elif model_type == 'PNN':
model = Model_PNN(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
elif model_type == 'Wide':
model = Model_WideDeep(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
elif model_type == 'DIN':
model = Model_DIN(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
elif model_type == 'DIN-V2-gru-att-gru':
model = Model_DIN_V2_Gru_att_Gru(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
elif model_type == 'DIN-V2-gru-gru-att':
model = Model_DIN_V2_Gru_Gru_att(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
elif model_type == 'DIN-V2-gru-qa-attGru':
model = Model_DIN_V2_Gru_QA_attGru(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
elif model_type == 'DIN-V2-gru-vec-attGru':
model = Model_DIN_V2_Gru_Vec_attGru(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
elif model_type == 'DIEN':
model = Model_DIN_V2_Gru_Vec_attGru_Neg(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
else:
print ("Invalid model_type : %s", model_type)
return
model.restore(sess, model_path)
print('test_auc: %.4f ----test_loss: %.4f ---- test_accuracy: %.4f ---- test_aux_loss: %.4f' % eval(sess, test_data, model, model_path))
if __name__ == '__main__':
if len(sys.argv) == 4:
SEED = int(sys.argv[3])
else:
SEED = 3
tf.set_random_seed(SEED)
numpy.random.seed(SEED)
random.seed(SEED)
if sys.argv[1] == 'train':
train(model_type=sys.argv[2], seed=SEED)
elif sys.argv[1] == 'test':
test(model_type=sys.argv[2], seed=SEED)
else:
print('do nothing...')
訓練資料獲取
首先介紹下如何讀取之前生成的訓練資料并迭代獲取這些資料生成最終的訓練資料,
樣本資料獲取和迭代
訓練和測驗樣本資料獲取代碼:
train_data = DataIterator(train_file, uid_voc, mid_voc, cat_voc, batch_size, maxlen, shuffle_each_epoch=False)
test_data = DataIterator(test_file, uid_voc, mid_voc, cat_voc, batch_size, maxlen)
這里通過DataIterator類來獲取訓練和測驗樣本資料,DataIterator的定義在data_iterator.py,詳細注解代碼如下:
import numpy
import json
import _pickle as pkl
import random
import gzip
import shuffle
def unicode_to_utf8(d):
return dict((key.encode("UTF-8"), value) for (key,value) in d.items())
def load_dict(filename):
try:
with open(filename, 'rb') as f:
return unicode_to_utf8(json.load(f))
except:
with open(filename, 'rb') as f:
#return unicode_to_utf8(pkl.load(f))
return pkl.load(f)
def fopen(filename, mode='r'):
if filename.endswith('.gz'):
return gzip.open(filename, mode)
return open(filename, mode)
class DataIterator:
def __init__(self, source, # local_train_splitByUser
#第一行:label 0, 用戶id, 商品id(未點擊,負樣本), 商品分類, 之前點擊過所有商品id,之前點擊過所有商品分類
#第二行:label 1, 用戶id, 商品id(點擊過,正樣本), 商品分類, 之前點擊過所有商品id,之前點擊過所有商品分類
uid_voc, # 用戶 id 編號,uid_voc.pkl
mid_voc, # item id 編號,mid_voc.pkl
cat_voc, # cat id 編號,cat_voc.pkl
batch_size=128,
maxlen=100,
skip_empty=False,
shuffle_each_epoch=False,
sort_by_length=True,
max_batch_size=20,
minlen=None):
if shuffle_each_epoch:
self.source_orig = source
self.source = shuffle.main(self.source_orig, temporary=True)
else:
self.source = fopen(source, 'r')
self.source_dicts = []
for source_dict in [uid_voc, mid_voc, cat_voc]:
self.source_dicts.append(load_dict(source_dict)) # uid_voc, mid_voc 和 cat_voc;
f_meta = open("item-info", "r")
# (檔案 item-info 保存欄位): 商品item id, 商品分類 cat(某個名詞,例如:Cables & Accessories)
meta_map = {} # item id 和 商品分類 cat的映射
for line in f_meta:
arr = line.strip().split("\t")
if arr[0] not in meta_map:
meta_map[arr[0]] = arr[1]
self.meta_id_map ={}
for key in meta_map:
val = meta_map[key] # item id 對應的 item cate
if key in self.source_dicts[1]:
mid_idx = self.source_dicts[1][key] # item id 對應的編號
else:
mid_idx = 0
if val in self.source_dicts[2]:
cat_idx = self.source_dicts[2][val] # cate id 對應的編號
else:
cat_idx = 0
self.meta_id_map[mid_idx] = cat_idx #item id 編號 和 cat id 編號對應
f_review = open("reviews-info", "r")
#(檔案reviews-info保存欄位):user id, 商品item id, rating of the product(商品等級,浮點數), 時間戳
self.mid_list_for_random = []
for line in f_review:
arr = line.strip().split("\t")
tmp_idx = 0
if arr[1] in self.source_dicts[1]: # mid_voc
tmp_idx = self.source_dicts[1][arr[1]]
self.mid_list_for_random.append(tmp_idx) # item id 的編號
self.batch_size = batch_size
self.maxlen = maxlen
self.minlen = minlen
self.skip_empty = skip_empty
self.n_uid = len(self.source_dicts[0]) # 用戶數
self.n_mid = len(self.source_dicts[1]) # 商品 item數
self.n_cat = len(self.source_dicts[2]) # 商品分類數
self.shuffle = shuffle_each_epoch
self.sort_by_length = sort_by_length
self.source_buffer = []
self.k = batch_size * max_batch_size
self.end_of_data = False
def get_n(self):
return self.n_uid, self.n_mid, self.n_cat
def __iter__(self):
return self
def reset(self):
if self.shuffle:
self.source= shuffle.main(self.source_orig, temporary=True)
else:
self.source.seek(0)
def __next__(self):
if self.end_of_data:
self.end_of_data = False
self.reset()
raise StopIteration
source = []
target = []
if len(self.source_buffer) == 0:
for k_ in range(self.k):
ss = self.source.readline()
#第一行:label 0, 用戶id, 商品id(未點擊,負樣本), 商品分類, 之前點擊過所有商品id,之前點擊過所有商品分類
#第二行:label 1, 用戶id, 商品id(點擊過,正樣本), 商品分類, 之前點擊過所有商品id,之前點擊過所有商品分類
if ss == "":
break
self.source_buffer.append(ss.strip("\n").split("\t"))
# list: label, 用戶id, 商品id, 商品分類, 之前點擊過所有商品id, 之前點擊過所有商品分類
# sort by history behavior length
if self.sort_by_length: # true
his_length = numpy.array([len(s[4].split("")) for s in self.source_buffer])
tidx = his_length.argsort()
_sbuf = [self.source_buffer[i] for i in tidx]
self.source_buffer = _sbuf # 按照之前點擊商品個數排序
else:
self.source_buffer.reverse()
if len(self.source_buffer) == 0:
self.end_of_data = False
self.reset()
raise StopIteration
try:
# actual work here
while True:
# read from source file and map to word index
try:
ss = self.source_buffer.pop() # label, 用戶id, 商品id, 商品分類, 之前點擊過所有商品id, 之前點擊過所有商品分類
except IndexError:
break
uid = self.source_dicts[0][ss[1]] if ss[1] in self.source_dicts[0] else 0 # 用戶id編號
mid = self.source_dicts[1][ss[2]] if ss[2] in self.source_dicts[1] else 0 # 產品id編號
cat = self.source_dicts[2][ss[3]] if ss[3] in self.source_dicts[2] else 0 # 分類編號
tmp = []
for fea in ss[4].split(""):
m = self.source_dicts[1][fea] if fea in self.source_dicts[1] else 0
tmp.append(m)
mid_list = tmp # 所有點擊過的產品id編號
tmp1 = []
for fea in ss[5].split(""):
c = self.source_dicts[2][fea] if fea in self.source_dicts[2] else 0
tmp1.append(c)
cat_list = tmp1 # 所有點擊過的產品分類編號
# read from source file and map to word index
#if len(mid_list) > self.maxlen:
# continue
if self.minlen != None:
if len(mid_list) <= self.minlen:
continue
if self.skip_empty and (not mid_list):
continue
noclk_mid_list = []
noclk_cat_list = []
for pos_mid in mid_list:
noclk_tmp_mid = []
noclk_tmp_cat = []
noclk_index = 0
while True:
noclk_mid_indx = random.randint(0, len(self.mid_list_for_random)-1)
noclk_mid = self.mid_list_for_random[noclk_mid_indx]
if noclk_mid == pos_mid:
continue
noclk_tmp_mid.append(noclk_mid)
noclk_tmp_cat.append(self.meta_id_map[noclk_mid])
noclk_index += 1
if noclk_index >= 5:
break
noclk_mid_list.append(noclk_tmp_mid)
noclk_cat_list.append(noclk_tmp_cat)
source.append([uid, mid, cat, mid_list, cat_list, noclk_mid_list, noclk_cat_list])
#用戶id, 商品id, 商品分類,之前點過商品(n個),之前點個商品分類(n個),沒點過商品(n*5個),沒點過商品分類(n*5個)
target.append([float(ss[0]), 1-float(ss[0])])
#label, 正樣本或者負樣本
if len(source) >= self.batch_size or len(target) >= self.batch_size:
break
except IOError:
self.end_of_data = True
# all sentence pairs in maxibatch filtered out because of length
if len(source) == 0 or len(target) == 0:
source, target = self.next()
return source, target
#source: N個樣本,對于每個樣本有如下欄位:
# 用戶id, 商品id, 商品分類,之前點過商品(n個),之前點個商品分類(n個),沒點過商品(n*5個),沒點過商品分類(n*5個)
#label: N個樣本的label: [0,1] 或者 [1,0]
分別打開訓練檔案local_train_splitByUser和測驗檔案local_test_splitByUser,之前的資料處理部分已經給出了說明,這兩個檔案的格式是每兩行對應一個用戶點擊行為的正負樣本對,格式如下:
#第一行:label 0, 用戶id, 商品id(未點擊,負樣本), 商品分類, 之前點擊過所有商品id,之前點擊過所有商品分類
#第二行:label 1, 用戶id, 商品id(點擊過,正樣本), 商品分類, 之前點擊過所有商品id,之前點擊過所有商品分類
uid_voc、mid_voc和cat_voc分別為用戶id、商品item id 和商品分類的編號,__next__函式實作了for回圈取樣本的功能,具體處理流程已經給出了詳細的代碼注釋,最侄訓傳source和target兩個變數,存盤內容如下:
#回傳變數1,source: N個樣本,對于每個樣本有如下欄位:
# 用戶id, 商品id, 商品分類,之前點過商品(n個),之前點個商品分類(n個),沒點過商品(n*5個),沒點過商品分類(n*5個)
#回傳變數2,label: N個樣本的label: [0,1] 或者 [1,0]
資料準備
在獲取訓練和測驗樣本資料后,還要進一步對資料做準備和處理,準備處理部分代碼如下:
for src, tgt in train_data:
# src : 用戶id, 商品id, 商品分類,之前點過商品(n個),之前點個商品分類(n個),沒點過商品(n*5個),沒點過商品分類(n*5個)
# label: 正樣本或者負樣本
uids, mids, cats, mid_his, cat_his, mid_mask, target, sl, noclk_mids, noclk_cats = prepare_data(src, tgt, maxlen, return_neg=True)
主要通過prepare_data函式實作:
def prepare_data(input, target, maxlen = None, return_neg = False):
# x: a list of sentences
# input: N個訓練樣本,每一行格式如下:
# 用戶id[0], 商品id[1], 商品分類[2],之前點過商品(n個)[3],之前點個商品分類(n個)[4],沒點過商品(n*5個)[5],沒點過商品分類(n*5個)[6]
# label: 正樣本或者負樣本
lengths_x = [len(s[4]) for s in input] # N, 每個樣本之前點擊商品的個數
seqs_mid = [inp[3] for inp in input] # N*n, 之前點過商品序列
seqs_cat = [inp[4] for inp in input] # N * n, 之前點過商品分類序列
noclk_seqs_mid = [inp[5] for inp in input] # N * n * 5, 之前沒點過商品序列
noclk_seqs_cat = [inp[6] for inp in input] # N * n * 5, 之前沒點過商品分類
if maxlen is not None:
new_seqs_mid = []
new_seqs_cat = []
new_noclk_seqs_mid = []
new_noclk_seqs_cat = []
new_lengths_x = []
for l_x, inp in zip(lengths_x, input): # zip生成組元組成的list,長度與最小list長度一致
if l_x > maxlen:
new_seqs_mid.append(inp[3][l_x - maxlen:])
new_seqs_cat.append(inp[4][l_x - maxlen:])
new_noclk_seqs_mid.append(inp[5][l_x - maxlen:])
new_noclk_seqs_cat.append(inp[6][l_x - maxlen:])
new_lengths_x.append(maxlen)
else:
new_seqs_mid.append(inp[3])
new_seqs_cat.append(inp[4])
new_noclk_seqs_mid.append(inp[5])
new_noclk_seqs_cat.append(inp[6])
new_lengths_x.append(l_x)
lengths_x = new_lengths_x
seqs_mid = new_seqs_mid
seqs_cat = new_seqs_cat
noclk_seqs_mid = new_noclk_seqs_mid
noclk_seqs_cat = new_noclk_seqs_cat
if len(lengths_x) < 1:
return None, None, None, None
n_samples = len(seqs_mid) # 樣本數 N
maxlen_x = numpy.max(lengths_x) # 之前最多的點擊樣本個數;
if maxlen_x <= 1:
maxlen_x = 2
neg_samples = len(noclk_seqs_mid[0][0]) # 每一次之前點擊行為對應的負樣本個數
mid_his = numpy.zeros((n_samples, maxlen_x)).astype('int64') # N * maxLen_x 之前點擊item id 序列
cat_his = numpy.zeros((n_samples, maxlen_x)).astype('int64') # N * maxLen_x 之前點擊item 分類 序列
noclk_mid_his = numpy.zeros((n_samples, maxlen_x, neg_samples)).astype('int64') # N * maxLen_x * ngsample(5), 之前每次點擊對應負樣本
noclk_cat_his = numpy.zeros((n_samples, maxlen_x, neg_samples)).astype('int64') # N * maxLen_x * ngsample(5), 之前每次點擊對應負樣本分類
mid_mask = numpy.zeros((n_samples, maxlen_x)).astype('float32') # N * maxLen_x 實際之前點擊序列長度
for idx, [s_x, s_y, no_sx, no_sy] in enumerate(zip(seqs_mid, seqs_cat, noclk_seqs_mid, noclk_seqs_cat)):
mid_mask[idx, :lengths_x[idx]] = 1. # 第idx個樣本,前lengths_x[idx]置為1,即有點擊的位置置為1.
mid_his[idx, :lengths_x[idx]] = s_x # 第idx個樣本,之前點過的商品id序列
cat_his[idx, :lengths_x[idx]] = s_y # 第idx個樣本,之前點過的商品分類序列
noclk_mid_his[idx, :lengths_x[idx], :] = no_sx # 第idx個樣本,沒點過負樣本id
noclk_cat_his[idx, :lengths_x[idx], :] = no_sy # 第idx個樣本,沒點過負樣本分類
uids = numpy.array([inp[0] for inp in input]) # N,用戶id
mids = numpy.array([inp[1] for inp in input]) # N,商品id
cats = numpy.array([inp[2] for inp in input]) # N,商品分類
if return_neg:
return uids, mids, cats, mid_his, cat_his, mid_mask, numpy.array(target), numpy.array(lengths_x), noclk_mid_his, noclk_cat_his
# uids: N, 用戶id
# mids: N, 商品 item id
# cats: N, 商品分類
# mid_his: N * maxLen_x 之前點擊item id 序列
# cat_his: N * maxLen_x 之前點擊item 分類 序列
# mid_mask: N * maxLen_x 實際之前點擊序列長度
# numpy.array(target): N * 2, label 正樣本 [1,0] or 負樣本 [0,1]
# numpy.array(lengths_x):N, 實際之前點擊樣本序列長度
# noclk_mid_his:N * maxLen_x * ngsample(5), 之前每次點擊對應負樣本
# noclk_cat_his:N * maxLen_x * ngsample(5), 之前每次點擊對應負樣本分類
else:
return uids, mids, cats, mid_his, cat_his, mid_mask, numpy.array(target), numpy.array(lengths_x)
max_len = 100,表示用戶歷史點擊商品的截斷長度為100,即最長歷史擊樣本序列長度為100,代碼同樣給出了詳細注釋,最侄訓傳變數:uids, mids, cats, mid_his, cat_his, mid_mask, target, sl, noclk_mids, noclk_cats,定義如下:
# uids: N, 用戶id
# mids: N, 商品 item id
# cats: N, 商品分類
# mid_his: N * maxLen_x 之前點擊item id 序列
# cat_his: N * maxLen_x 之前點擊item 分類 序列
# mid_mask: N * maxLen_x 實際之前點擊序列長度
# target: N * 2, label 正樣本 [1,0] or 負樣本 [0,1]
# sl:N, 實際之前點擊樣本序列長度
# noclk_mids: N * maxLen_x * ngsample(5), 之前每次點擊對應負樣本
# noclk_cats: N * maxLen_x * ngsample(5), 之前每次點擊對應負樣本分類
這是模型訓練需要的全部資料,
模型結構
模型定義代碼如下:
model = Model_DIN_V2_Gru_Vec_attGru_Neg(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE)
基礎網路結構
網路結構類Model_DIN_V2_Gru_Vec_attGru_Neg定義在model.py檔案:
class Model_DIN_V2_Gru_Vec_attGru_Neg(Model):
def __init__(self, n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE, use_negsampling=True):
# 用戶id數,商品id數,商品分類數,18,18 * 2,18 * 2
super(Model_DIN_V2_Gru_Vec_attGru_Neg, self).__init__(n_uid, n_mid, n_cat,
EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE,
use_negsampling)
# RNN layer(-s)
with tf.name_scope('rnn_1'):
# item_his_eb: 之前點過商品embedding 和 分類embedding拼接在一起,[batch_size, n, EMBEDDING_DIM * 2]
# [batch_size,], 實際之前點擊樣本序列長度
# HIDDEN_SIZE 32
rnn_outputs, _ = dynamic_rnn(GRUCell(HIDDEN_SIZE), inputs=self.item_his_eb,
sequence_length=self.seq_len_ph, dtype=tf.float32,
scope="gru1")
# [batch_size, n, HIDDEN_SIZE]
tf.summary.histogram('GRU_outputs', rnn_outputs)
# rnn_outputs[:, :-1, :]:上一時刻embedding特征,[batch_size, n - 1, HIDDEN_SIZE]
# item_his_eb[:, 1:, :]:當前時刻的embedding特征,[batch_size, n - 1, EMBEDDING_DIM * 2]
# noclk_item_his_eb[:, 1:, :]:每次點擊行為取第0個為負樣本,[batch_size, n - 1, EMBEDDING_DIM * 2]
# 每一個樣本,有效的點擊序列個數:[batch_size, n - 1]
aux_loss_1 = self.auxiliary_loss(rnn_outputs[:, :-1, :], self.item_his_eb[:, 1:, :],
self.noclk_item_his_eb[:, 1:, :],
self.mask[:, 1:], stag="gru")
self.aux_loss = aux_loss_1
# Attention layer
with tf.name_scope('Attention_layer_1'):
# item_eb:mid embedding 和 cat embedding拼接在一起,[batch_size, EMBEDDING_DIM * 2]
# rnn_outputs:GRU1輸出的用戶興趣狀態,[batch_size, n, HIDDEN_SIZE]
att_outputs, alphas = din_fcn_attention(self.item_eb, rnn_outputs, ATTENTION_SIZE, self.mask,
softmax_stag=1, stag='1_1', mode='LIST', return_alphas=True)
# 輸出:ouput: [batch_size, n, HIDDEN_SIZE]
# 每個樣本用戶之前每一個行為興趣特征和當前item的權重,即注意力分數:scores: [batch_size, n]
tf.summary.histogram('alpha_outputs', alphas)
with tf.name_scope('rnn_2'):
rnn_outputs2, final_state2 = dynamic_rnn(VecAttGRUCell(HIDDEN_SIZE), inputs=rnn_outputs,
att_scores = tf.expand_dims(alphas, -1),
sequence_length=self.seq_len_ph, dtype=tf.float32,
scope="gru2")
# 實作AUGRU,輸出:[batch_size, HIDDEN_SIZE]
tf.summary.histogram('GRU2_Final_State', final_state2)
inp = tf.concat([self.uid_batch_embedded, self.item_eb, self.item_his_eb_sum, self.item_eb * self.item_his_eb_sum, final_state2], 1)
# uid_batch_embedded: [batch_size, EMBEDDING_DIM]用戶特征embedding
# item_eb: mid embedding 和 cat embedding拼接在一起,[batch_size, EMBEDDING_DIM * 2]
# item_his_eb_sum:之前行為embedding求和[batch_size, EMBEDDING_DIM * 2]
# final_state2:attention興趣層提提權求和
# 所有特征拼接的一起,送入全連接網路
self.build_fcn_net(inp, use_dice=True)
其父類為model類,定義同樣在model.py:
class Model(object):
def __init__(self, n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE, use_negsampling = False):
with tf.name_scope('Inputs'):
self.mid_his_batch_ph = tf.placeholder(tf.int32, [None, None], name='mid_his_batch_ph')
self.cat_his_batch_ph = tf.placeholder(tf.int32, [None, None], name='cat_his_batch_ph')
self.uid_batch_ph = tf.placeholder(tf.int32, [None, ], name='uid_batch_ph')
self.mid_batch_ph = tf.placeholder(tf.int32, [None, ], name='mid_batch_ph')
self.cat_batch_ph = tf.placeholder(tf.int32, [None, ], name='cat_batch_ph')
self.mask = tf.placeholder(tf.float32, [None, None], name='mask')
self.seq_len_ph = tf.placeholder(tf.int32, [None], name='seq_len_ph')
self.target_ph = tf.placeholder(tf.float32, [None, None], name='target_ph')
self.lr = tf.placeholder(tf.float64, [])
self.use_negsampling =use_negsampling
if use_negsampling:
self.noclk_mid_batch_ph = tf.placeholder(tf.int32, [None, None, None], name='noclk_mid_batch_ph') #generate 3 item IDs from negative sampling.
self.noclk_cat_batch_ph = tf.placeholder(tf.int32, [None, None, None], name='noclk_cat_batch_ph')
# Embedding layer
with tf.name_scope('Embedding_layer'):
# uid embedding 層
self.uid_embeddings_var = tf.get_variable("uid_embedding_var", [n_uid, EMBEDDING_DIM])
tf.summary.histogram('uid_embeddings_var', self.uid_embeddings_var)
self.uid_batch_embedded = tf.nn.embedding_lookup(self.uid_embeddings_var, self.uid_batch_ph)
#mid embedding 層
self.mid_embeddings_var = tf.get_variable("mid_embedding_var", [n_mid, EMBEDDING_DIM])
tf.summary.histogram('mid_embeddings_var', self.mid_embeddings_var)
self.mid_batch_embedded = tf.nn.embedding_lookup(self.mid_embeddings_var, self.mid_batch_ph)
self.mid_his_batch_embedded = tf.nn.embedding_lookup(self.mid_embeddings_var, self.mid_his_batch_ph)
if self.use_negsampling:
self.noclk_mid_his_batch_embedded = tf.nn.embedding_lookup(self.mid_embeddings_var, self.noclk_mid_batch_ph)
# [batch_size, n, 5, EMBEDDING_DIM]
# cat embedding 層
self.cat_embeddings_var = tf.get_variable("cat_embedding_var", [n_cat, EMBEDDING_DIM])
tf.summary.histogram('cat_embeddings_var', self.cat_embeddings_var)
self.cat_batch_embedded = tf.nn.embedding_lookup(self.cat_embeddings_var, self.cat_batch_ph)
self.cat_his_batch_embedded = tf.nn.embedding_lookup(self.cat_embeddings_var, self.cat_his_batch_ph)
if self.use_negsampling:
self.noclk_cat_his_batch_embedded = tf.nn.embedding_lookup(self.cat_embeddings_var, self.noclk_cat_batch_ph)
# [batch_size, n, 5, EMBEDDING_DIM]
self.item_eb = tf.concat([self.mid_batch_embedded, self.cat_batch_embedded], 1)
# mid embedding 和 cat embedding拼接在一起,[batch_size, EMBEDDING_DIM * 2]
self.item_his_eb = tf.concat([self.mid_his_batch_embedded, self.cat_his_batch_embedded], 2)
# 之前點過商品embedding 和 分類embedding拼接在一起,[batch_size, n, EMBEDDING_DIM * 2]
self.item_his_eb_sum = tf.reduce_sum(self.item_his_eb, 1) # 之前行為embedding求和[batch_size, EMBEDDING_DIM * 2]
if self.use_negsampling:
self.noclk_item_his_eb = tf.concat(
[self.noclk_mid_his_batch_embedded[:, :, 0, :], self.noclk_cat_his_batch_embedded[:, :, 0, :]], -1)# 0 means only using the first negative item ID. 3 item IDs are inputed in the line 24.
# 每次點擊行為取第0個為負樣本,[batch_size, n, EMBEDDING_DIM * 2]
self.noclk_item_his_eb = tf.reshape(self.noclk_item_his_eb,
[-1, tf.shape(self.noclk_mid_his_batch_embedded)[1], 36])# cat embedding 18 concate item embedding 18.
self.noclk_his_eb = tf.concat([self.noclk_mid_his_batch_embedded, self.noclk_cat_his_batch_embedded], -1)
# [batch_size, n, 5, EMBEDDING_DIM * 2]
self.noclk_his_eb_sum_1 = tf.reduce_sum(self.noclk_his_eb, 2)
# [batch_size, n, EMBEDDING_DIM * 2]
self.noclk_his_eb_sum = tf.reduce_sum(self.noclk_his_eb_sum_1, 1)
# [batch_size, EMBEDDING_DIM * 2]
def build_fcn_net(self, inp, use_dice = False):
bn1 = tf.layers.batch_normalization(inputs=inp, name='bn1')
dnn1 = tf.layers.dense(bn1, 200, activation=None, name='f1')
if use_dice:
dnn1 = dice(dnn1, name='dice_1')
else:
dnn1 = prelu(dnn1, 'prelu1')
dnn2 = tf.layers.dense(dnn1, 80, activation=None, name='f2')
if use_dice:
dnn2 = dice(dnn2, name='dice_2')
else:
dnn2 = prelu(dnn2, 'prelu2')
dnn3 = tf.layers.dense(dnn2, 2, activation=None, name='f3')
self.y_hat = tf.nn.softmax(dnn3) + 0.00000001
with tf.name_scope('Metrics'):
# Cross-entropy loss and optimizer initialization
ctr_loss = - tf.reduce_mean(tf.log(self.y_hat) * self.target_ph)
self.loss = ctr_loss
if self.use_negsampling:
self.loss += self.aux_loss
tf.summary.scalar('loss', self.loss)
self.optimizer = tf.train.AdamOptimizer(learning_rate=self.lr).minimize(self.loss)
# Accuracy metric
self.accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.round(self.y_hat), self.target_ph), tf.float32))
tf.summary.scalar('accuracy', self.accuracy)
self.merged = tf.summary.merge_all()
def auxiliary_loss(self, h_states, click_seq, noclick_seq, mask, stag = None):
# h_states:上一時刻embedding特征,[batch_size, n - 1, HIDDEN_SIZE]
# click_seq: 當前時刻的embedding特征,[batch_size, n - 1, HIDDEN_SIZE]
# noclick_seq: 每次點擊行為取第0個為負樣本,[batch_size, n - 1, EMBEDDING_DIM * 2]
# mask: 每一個樣本,有效的點擊序列個數,[batch_size, n - 1]
mask = tf.cast(mask, tf.float32)
click_input_ = tf.concat([h_states, click_seq], -1) # [batch_size, n - 1, HIDDEN_SIZE * 2]
noclick_input_ = tf.concat([h_states, noclick_seq], -1) # [batch_size, n - 1, HIDDEN_SIZE * 2]
click_prop_ = self.auxiliary_net(click_input_, stag = stag)[:, :, 0] # [batch_size, n - 1]
noclick_prop_ = self.auxiliary_net(noclick_input_, stag = stag)[:, :, 0] # [batch_size, n - 1]
click_loss_ = - tf.reshape(tf.log(click_prop_), [-1, tf.shape(click_seq)[1]]) * mask
noclick_loss_ = - tf.reshape(tf.log(1.0 - noclick_prop_), [-1, tf.shape(noclick_seq)[1]]) * mask
loss_ = tf.reduce_mean(click_loss_ + noclick_loss_)
return loss_
def auxiliary_net(self, in_, stag='auxiliary_net'):
# [batch_size, n - 1, HIDDEN_SIZE * 2]
bn1 = tf.layers.batch_normalization(inputs=in_, name='bn1' + stag, reuse=tf.AUTO_REUSE)
dnn1 = tf.layers.dense(bn1, 100, activation=None, name='f1' + stag, reuse=tf.AUTO_REUSE)
dnn1 = tf.nn.sigmoid(dnn1)
dnn2 = tf.layers.dense(dnn1, 50, activation=None, name='f2' + stag, reuse=tf.AUTO_REUSE)
dnn2 = tf.nn.sigmoid(dnn2)
dnn3 = tf.layers.dense(dnn2, 2, activation=None, name='f3' + stag, reuse=tf.AUTO_REUSE)
y_hat = tf.nn.softmax(dnn3) + 0.00000001
return y_hat
def train(self, sess, inps):
if self.use_negsampling:
loss, accuracy, aux_loss, _ = sess.run([self.loss, self.accuracy, self.aux_loss, self.optimizer], feed_dict={
self.uid_batch_ph: inps[0],
self.mid_batch_ph: inps[1],
self.cat_batch_ph: inps[2],
self.mid_his_batch_ph: inps[3],
self.cat_his_batch_ph: inps[4],
self.mask: inps[5],
self.target_ph: inps[6],
self.seq_len_ph: inps[7],
self.lr: inps[8],
self.noclk_mid_batch_ph: inps[9],
self.noclk_cat_batch_ph: inps[10],
})
return loss, accuracy, aux_loss
else:
loss, accuracy, _ = sess.run([self.loss, self.accuracy, self.optimizer], feed_dict={
self.uid_batch_ph: inps[0],
self.mid_batch_ph: inps[1],
self.cat_batch_ph: inps[2],
self.mid_his_batch_ph: inps[3],
self.cat_his_batch_ph: inps[4],
self.mask: inps[5],
self.target_ph: inps[6],
self.seq_len_ph: inps[7],
self.lr: inps[8],
})
return loss, accuracy, 0
def calculate(self, sess, inps):
if self.use_negsampling:
probs, loss, accuracy, aux_loss = sess.run([self.y_hat, self.loss, self.accuracy, self.aux_loss], feed_dict={
self.uid_batch_ph: inps[0], # [uids[0], mids[1], cats[2], mid_his[3], cat_his[4], mid_mask[5], target[6], sl, noclk_mids, noclk_cats]
self.mid_batch_ph: inps[1],
self.cat_batch_ph: inps[2],
self.mid_his_batch_ph: inps[3],
self.cat_his_batch_ph: inps[4],
self.mask: inps[5],
self.target_ph: inps[6],
self.seq_len_ph: inps[7],
self.noclk_mid_batch_ph: inps[8],
self.noclk_cat_batch_ph: inps[9],
})
return probs, loss, accuracy, aux_loss
else:
probs, loss, accuracy = sess.run([self.y_hat, self.loss, self.accuracy], feed_dict={
self.uid_batch_ph: inps[0],
self.mid_batch_ph: inps[1],
self.cat_batch_ph: inps[2],
self.mid_his_batch_ph: inps[3],
self.cat_his_batch_ph: inps[4],
self.mask: inps[5],
self.target_ph: inps[6],
self.seq_len_ph: inps[7]
})
return probs, loss, accuracy, 0
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)
print('model restored from %s' % path)
興趣提取層(Interest Extractor Layer)
興趣提取層部分代碼實作如下:
# RNN layer(-s)
with tf.name_scope('rnn_1'):
# item_his_eb: 之前點過商品embedding 和 分類embedding拼接在一起,[batch_size, n, EMBEDDING_DIM * 2]
# [batch_size,], 實際之前點擊樣本序列長度
# HIDDEN_SIZE 32
rnn_outputs, _ = dynamic_rnn(GRUCell(HIDDEN_SIZE), inputs=self.item_his_eb,
sequence_length=self.seq_len_ph, dtype=tf.float32,
scope="gru1")
# [batch_size, n, HIDDEN_SIZE]
tf.summary.histogram('GRU_outputs', rnn_outputs)
# rnn_outputs[:, :-1, :]:上一時刻embedding特征,[batch_size, n - 1, HIDDEN_SIZE]
# item_his_eb[:, 1:, :]:當前時刻的embedding特征,[batch_size, n - 1, EMBEDDING_DIM * 2]
# noclk_item_his_eb[:, 1:, :]:每次點擊行為取第0個為負樣本,[batch_size, n - 1, EMBEDDING_DIM * 2]
# 每一個樣本,有效的點擊序列個數:[batch_size, n - 1]
aux_loss_1 = self.auxiliary_loss(rnn_outputs[:, :-1, :], self.item_his_eb[:, 1:, :],
self.noclk_item_his_eb[:, 1:, :],
self.mask[:, 1:], stag="gru")
self.aux_loss = aux_loss_1
可以看到,首先將用戶之前點擊item 的embedding送入以GRU為核的rnn結構中,獲取每一步的興趣狀態向量rnn_outputs,
接著將0到n-2時刻的rnn_outputs[:, :-1, :],1到n-1時刻(早一個時刻)的點擊商品embedding向量item_his_eb[:, 1:, :]對應負樣本點擊商品embedding向量noclk_item_his_eb[:, 1:, :]以及標識歷史點擊序列長度mask[:, 1:]送入auxiliary loss函式,auxiliary loss的實作代碼如下:
def auxiliary_loss(self, h_states, click_seq, noclick_seq, mask, stag = None):
# h_states:上一時刻embedding特征,[batch_size, n - 1, HIDDEN_SIZE]
# click_seq: 當前時刻的embedding特征,[batch_size, n - 1, HIDDEN_SIZE]
# noclick_seq: 每次點擊行為取第0個為負樣本,[batch_size, n - 1, EMBEDDING_DIM * 2]
# mask: 每一個樣本,有效的點擊序列個數,[batch_size, n - 1]
mask = tf.cast(mask, tf.float32)
click_input_ = tf.concat([h_states, click_seq], -1) # [batch_size, n - 1, HIDDEN_SIZE * 2]
noclick_input_ = tf.concat([h_states, noclick_seq], -1) # [batch_size, n - 1, HIDDEN_SIZE * 2]
click_prop_ = self.auxiliary_net(click_input_, stag = stag)[:, :, 0] # [batch_size, n - 1]
noclick_prop_ = self.auxiliary_net(noclick_input_, stag = stag)[:, :, 0] # [batch_size, n - 1]
click_loss_ = - tf.reshape(tf.log(click_prop_), [-1, tf.shape(click_seq)[1]]) * mask
noclick_loss_ = - tf.reshape(tf.log(1.0 - noclick_prop_), [-1, tf.shape(noclick_seq)[1]]) * mask
loss_ = tf.reduce_mean(click_loss_ + noclick_loss_)
return loss_
第一層rnn回傳的狀態序列rnn_outputs為模型化興趣變化程序的興趣序列,
興趣進化層 (Interest Evolving Layer)實作
興趣進化層 (Interest Evolving Layer)實作代碼如下:
# Attention layer
with tf.name_scope('Attention_layer_1'):
# item_eb:mid embedding 和 cat embedding拼接在一起,[batch_size, EMBEDDING_DIM * 2]
# rnn_outputs:GRU1輸出的用戶興趣狀態,[batch_size, n, HIDDEN_SIZE]
att_outputs, alphas = din_fcn_attention(self.item_eb, rnn_outputs, ATTENTION_SIZE, self.mask,
softmax_stag=1, stag='1_1', mode='LIST', return_alphas=True)
# 輸出:ouput: [batch_size, n, HIDDEN_SIZE]
# 每個樣本用戶之前每一個行為興趣特征和當前item的權重,即注意力分數:scores: [batch_size, n]
tf.summary.histogram('alpha_outputs', alphas)
with tf.name_scope('rnn_2'):
rnn_outputs2, final_state2 = dynamic_rnn(VecAttGRUCell(HIDDEN_SIZE), inputs=rnn_outputs,
att_scores = tf.expand_dims(alphas, -1),
sequence_length=self.seq_len_ph, dtype=tf.float32,
scope="gru2")
# 實作AUGRU,輸出:[batch_size, HIDDEN_SIZE]
tf.summary.histogram('GRU2_Final_State', final_state2)
inp = tf.concat([self.uid_batch_embedded, self.item_eb, self.item_his_eb_sum, self.item_eb * self.item_his_eb_sum, final_state2], 1)
# uid_batch_embedded: [batch_size, EMBEDDING_DIM]用戶特征embedding
# item_eb: mid embedding 和 cat embedding拼接在一起,[batch_size, EMBEDDING_DIM * 2]
# item_his_eb_sum:之前行為embedding求和[batch_size, EMBEDDING_DIM * 2]
# final_state2:attention興趣層提提權求和
# 所有特征拼接的一起,送入全連接網路
self.build_fcn_net(inp, use_dice=True)
attention層,輸入每個樣本用戶的興趣變化序列rnn_outputs和當前item的embedding item_eb,利用attention機制獲取每個樣本用戶之前每一個行為興趣特征和當前item的權重,回傳即注意力分數:scores[batch_size, n],attention層代碼實作如下:
def din_fcn_attention(query, facts, attention_size, mask, stag='null', mode='SUM', softmax_stag=1, time_major=False, return_alphas=False, forCnn=False):
# query:mid embedding 和 cat embedding拼接在一起,[batch_size, EMBEDDING_DIM * 2]
# facts:GRU1輸出的用戶興趣狀態,[batch_size, n, HIDDEN_SIZE]
# attention_size:36
# mask:每一個樣本,有效的點擊序列個數,[batch_size, n]
if isinstance(facts, tuple):
# In case of Bi-RNN, concatenate the forward and the backward RNN outputs.
facts = tf.concat(facts, 2)
if len(facts.get_shape().as_list()) == 2:
facts = tf.expand_dims(facts, 1)
if time_major:
# (T,B,D) => (B,T,D)
facts = tf.array_ops.transpose(facts, [1, 0, 2])
# Trainable parameters
mask = tf.equal(mask, tf.ones_like(mask))
facts_size = facts.get_shape().as_list()[-1] # D value - hidden size of the RNN layer
# 上一層GRU(GRU1)的輸出狀態,即此層GRU(GRU2)的輸入的維度大小:HIDDEN_SIZE,
querry_size = query.get_shape().as_list()[-1]
# 推薦商品 item id的embedding維度:EMBEDDING_DIM * 2,
query = tf.layers.dense(query, facts_size, activation=None, name='f1' + stag)
# 全連接,輸入:[batch_size, EMBEDDING_DIM * 2] 輸出:[batch_size, HIDDEN_SIZE]
query = prelu(query)
# prelu 非線性變換函式
queries = tf.tile(query, [1, tf.shape(facts)[1]])
# queries的1維不變,2維擴展為之前的n倍,即維度變為:[batch_size, HIDDEN_SIZE * n],,對于一個推薦商品,其會生成n個重復的相同一個推薦商品的embedding向量
queries = tf.reshape(queries, tf.shape(facts))
# 維度進一步變為:[batch_size, n, HIDDEN_SIZE]
din_all = tf.concat([queries, facts, queries-facts, queries*facts], axis=-1)
# # 最后一個維度拼接到一起,拼接后變為 [batch_size, n, 4 * HIDDEN_SIZE]
d_layer_1_all = tf.layers.dense(din_all, 80, activation=tf.nn.sigmoid, name='f1_att' + stag)
# 第一層網路,輸出[batch_size, n, 80]
d_layer_2_all = tf.layers.dense(d_layer_1_all, 40, activation=tf.nn.sigmoid, name='f2_att' + stag)
# 第二層網路,輸出[batch_size, n, 40]
d_layer_3_all = tf.layers.dense(d_layer_2_all, 1, activation=None, name='f3_att' + stag)
# 第三層網路,輸出[batch_size, n, 1]
d_layer_3_all = tf.reshape(d_layer_3_all, [-1, 1, tf.shape(facts)[1]])
scores = d_layer_3_all
# 最后的輸出為[batch_size, 1, n]
# Mask
# key_masks = tf.sequence_mask(facts_length, tf.shape(facts)[1]) # [B, T]
key_masks = tf.expand_dims(mask, 1) # [B, 1, T]
# 標識矩陣B * T個點位,哪些是true (存在之前點擊過的商品)哪些是false(不存在之前點擊過的商品)
#例如:tf.sequence_mask([1, 3, 2], 5),回傳值為:
# [[True, False, False, False, False],
# [True, True, True, False, False],
# [True, True, False, False, False]]
paddings = tf.ones_like(scores) * (-2 ** 32 + 1)
if not forCnn:
scores = tf.where(key_masks, scores, paddings) # [B, 1, T]
# Scale
# scores = scores / (facts.get_shape().as_list()[-1] ** 0.5)
# Activation
if softmax_stag:
scores = tf.nn.softmax(scores) # [B, 1, T]
# [batch_size, 1, n]
# Weighted sum
if mode == 'SUM':
output = tf.matmul(scores, facts) # [B, 1, H]
# output = tf.reshape(output, [-1, tf.shape(facts)[-1]])
else:
scores = tf.reshape(scores, [-1, tf.shape(facts)[1]])
# 緯度變為: [batch_size, n], 表示沒個樣本每個行為的權重
output = facts * tf.expand_dims(scores, -1)
# [batch_size, n, HIDDEN_SIZE] * [batch_size, n, 1]
output = tf.reshape(output, tf.shape(facts))
# [batch_size, n, HIDDEN_SIZE]
if return_alphas:
return output, scores
return output
ouput為每個歷史點擊商品embedding取attention打分加權后的結果,scores為這個batch的用戶之前點過商品embedding的注意力分數,這里的代碼和之前介紹的DIN代碼實作中attention層代碼是類似的,
AUGRU層實作,AUGRU層具體原理可以參考專題1,代碼如下:
with tf.name_scope('rnn_2'):
rnn_outputs2, final_state2 = dynamic_rnn(VecAttGRUCell(HIDDEN_SIZE), inputs=rnn_outputs,
att_scores = tf.expand_dims(alphas, -1),
sequence_length=self.seq_len_ph, dtype=tf.float32,
scope="gru2")
# 實作AUGRU,輸出:[batch_size, HIDDEN_SIZE]
tf.summary.histogram('GRU2_Final_State', final_state2)
inp = tf.concat([self.uid_batch_embedded, self.item_eb, self.item_his_eb_sum, self.item_eb * self.item_his_eb_sum, final_state2], 1)
# uid_batch_embedded: [batch_size, EMBEDDING_DIM]用戶特征embedding
# item_eb: mid embedding 和 cat embedding拼接在一起,[batch_size, EMBEDDING_DIM * 2]
# item_his_eb_sum:之前行為embedding求和[batch_size, EMBEDDING_DIM * 2]
# final_state2:attention興趣層提提權求和
# 所有特征拼接的一起,送入全連接網路
dynamic_rnn的代碼實作是在rnn.py,這里實作了AUGRU的核心功能,回傳最終attention興趣提權求和的狀態特征final_state2,最后將所有特征拼到一起送入全連接層:
def build_fcn_net(self, inp, use_dice = False):
bn1 = tf.layers.batch_normalization(inputs=inp, name='bn1')
dnn1 = tf.layers.dense(bn1, 200, activation=None, name='f1')
if use_dice:
dnn1 = dice(dnn1, name='dice_1')
else:
dnn1 = prelu(dnn1, 'prelu1')
dnn2 = tf.layers.dense(dnn1, 80, activation=None, name='f2')
if use_dice:
dnn2 = dice(dnn2, name='dice_2')
else:
dnn2 = prelu(dnn2, 'prelu2')
dnn3 = tf.layers.dense(dnn2, 2, activation=None, name='f3')
self.y_hat = tf.nn.softmax(dnn3) + 0.00000001
with tf.name_scope('Metrics'):
# Cross-entropy loss and optimizer initialization
ctr_loss = - tf.reduce_mean(tf.log(self.y_hat) * self.target_ph)
self.loss = ctr_loss
if self.use_negsampling:
self.loss += self.aux_loss
tf.summary.scalar('loss', self.loss)
self.optimizer = tf.train.AdamOptimizer(learning_rate=self.lr).minimize(self.loss)
# Accuracy metric
self.accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.round(self.y_hat), self.target_ph), tf.float32))
tf.summary.scalar('accuracy', self.accuracy)
self.merged = tf.summary.merge_all()
ctr_loss和aux_loss求和得到最終的loss函式,通過優化器來優化即可完成訓練,
訓練模型
執行:
train.py train DIEN
可以實作DIEN的訓練,成功后會列印如下結果:

當然其他網路結構也有實作代碼,這個不在本節介紹的范圍,會在其他文章中介紹,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/426493.html
標籤:AI
