主頁 >  其他 > Deep Interest Evolution Network(DIEN)專題3:代碼決議之模型訓練和模型結構

Deep Interest Evolution Network(DIEN)專題3:代碼決議之模型訓練和模型結構

2022-02-18 07:56:45 其他

接上一節資料處理,本節將詳細介紹訓練和網路模型部分的代碼,為了配合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

上一篇:知乎高贊:什么能力很重要,但大多數人都沒有?

下一篇:基于BP神經網路使用開盤價、最高價、最低價預測收盤價

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more