Deep Interest Evolution Network(DIEN)专题2:代码解析之样本数据处理

源代码git地址:

https://github.com/mouna99/dien

整个数据处理可以通过执行脚本prepare_data.sh来实现,代码如下:

export PATH="~/anaconda4/bin:$PATH"
wget http://snap.stanford.edu/data/amazon/productGraph/categoryFiles/reviews_Books.json.gz
wget http://snap.stanford.edu/data/amazon/productGraph/categoryFiles/meta_Books.json.gz
gunzip reviews_Books.json.gz
gunzip meta_Books.json.gz
python script/process_data.py meta_Books.json reviews_Books_5.json
python script/local_aggretor.py
python script/split_by_user.py
python script/generate_voc.py

两个json文件,用户评价、点击相关的文件reviews_Books.json和具体商品信息相关的文件:meta_Books.json,具体json字段可参考DIN数据处理中的介绍。下面依次介绍3个python文件的功能,部分代码作了修改(适合python3执行并且修改一些问题)。

process_data.py

import sys
import random
import time

def process_meta(file): # meta_Books.json
    fi = open(file, "r")
    fo = open("item-info", "w")
    for line in fi:
        obj = eval(line)
        cat = obj["categories"][0][-1]
        #print>>fo, obj["asin"] + "\t" + cat
        print(obj["asin"] + "\t" + cat, file=fo) 
        # (文件 item-info 保存字段): 商品item id, 商品分类 cat(某个名词,例如:Cables & Accessories)
    fo.close()

def process_reviews(file): # reviews_Books_5.json
    fi = open(file, "r")
    user_map = {}
    fo = open("reviews-info", "w")
    for line in fi:
        obj = eval(line)
        userID = obj["reviewerID"]
        itemID = obj["asin"]
        rating = obj["overall"]
        time = obj["unixReviewTime"]
        #print>>fo, userID + "\t" + itemID + "\t" + str(rating) + "\t" + str(time)
        print(userID + "\t" + itemID + "\t" + str(rating) + "\t" + str(time), file=fo)
        # (文件reviews-info保存字段):user id, 商品item id, rating of the product(商品等级,浮点数), 时间戳
    fo.close()

def manual_join():
    f_rev = open("reviews-info", "r")
    user_map = {}
    item_list = []
    for line in f_rev:
        line = line.strip()
        items = line.split("\t")
        #loctime = time.localtime(float(items[-1]))
        #items[-1] = time.strftime('%Y-%m-%d', loctime)
        if items[0] not in user_map:
            user_map[items[0]]= []
        user_map[items[0]].append(("\t".join(items), float(items[-1])))
        # user_map: key -> user id; list:每个元素为 ("user id \t 商品item id \t rating of the product(商品等级)\t 时间戳", 时间戳) 
        item_list.append(items[1]) # 所有点击商品id,按顺序存入 item_list
    f_meta = open("item-info", "r")
    meta_map = {}
    for line in f_meta:
        arr = line.strip().split("\t")
        if arr[0] not in meta_map:
            meta_map[arr[0]] = arr[1] # meta_map: key -> item id; item category 一一对应
            arr = line.strip().split("\t")
    fo = open("jointed-new", "w")
    for key in user_map:
        sorted_user_bh = sorted(user_map[key], key=lambda x:x[1]) # 一个用户点击过的商品按时间戳排序
        for line, t in sorted_user_bh:
            items = line.split("\t")
            asin = items[1]
            j = 0
            while True:
                asin_neg_index = random.randint(0, len(item_list) - 1)
                asin_neg = item_list[asin_neg_index]
                if asin_neg == asin:
                    continue 
                items[1] = asin_neg
                #print>>fo, "0" + "\t" + "\t".join(items) + "\t" + meta_map[asin_neg]
                print("0" + "\t" + "\t".join(items) + "\t" + meta_map[asin_neg], file=fo)
                # 负样本,字段为:0(label) \t \t user id \t item id (负样本item) \t rating of the product(商品等级)\t 时间戳 \t 真实item 分类
                j += 1
                if j == 1:             #negative sampling frequency
                    break
            if asin in meta_map:
                #print>>fo, "1" + "\t" + line + "\t" + meta_map[asin]
                print("1" + "\t" + line + "\t" + meta_map[asin], file=fo)
                # 正样本,字段为:1(label) \t \t user id \t item id (正样本item) \t rating of the product(商品等级)\t 时间戳 \t 真实item 分类
            else:
                #print>>fo, "1" + "\t" + line + "\t" + "default_cat"
                print("1" + "\t" + line + "\t" + "default_cat", file=fo)
                # 正样本,字段为:1(label) \t \t user id \t item id (正样本item) \t rating of the product(商品等级)\t 时间戳 \t 真实item 分类


def split_test():
    fi = open("jointed-new", "r")
    fo = open("jointed-new-split-info", "w")
    user_count = {}
    for line in fi:
        line = line.strip()
        user = line.split("\t")[1]
        if user not in user_count:
            user_count[user] = 0
        user_count[user] += 1
    fi.seek(0)
    i = 0
    last_user = "A26ZDKC53OP6JD"
    for line in fi:
        line = line.strip()
        user = line.split("\t")[1]
        if user == last_user:
            if i < user_count[user] - 2:  # 1 + negative samples
                #print>> fo, "20180118" + "\t" + line
                print("20180118" + "\t" + line, file=fo)
            else:
                #print>>fo, "20190119" + "\t" + line
                print("20190119" + "\t" + line, file=fo)
        else:
            last_user = user
            i = 0
            if i < user_count[user] - 2:
                #print>> fo, "20180118" + "\t" + line
                print("20180118" + "\t" + line, file=fo)
            else:
                #print>>fo, "20190119" + "\t" + line
                print("20190119" + "\t" + line, file=fo)
        i += 1
        #(一个用户的最后一对正负样本:20190119,除最后一个外的其他所有对正负样本:20180118 ) \t label \t \t user id \t item id \t rating of the product(商品等级)\t 时间戳 \t 真实item 分类
process_meta(sys.argv[1]) # meta_Books.json 
process_reviews(sys.argv[2]) # reviews_Books_5.json
manual_join()
split_test()

process_meta函数,读取meta_Books.json文件,保存为item-info文件,文件字段为:

商品item id, 商品分类 cat(某个名词,例如:Cables & Accessories)

process_reviews函数,读取reviews_Books_5.json文件,保存为reviews-info文件,文件字段为:

user id, 商品item id, rating of the product(商品等级,浮点数), 时间戳

manual_join函数,提取每一个点击行为的样本数据(正样本),且对于每一个正样本,都会对应随机选择一个其他的item商品作为负样本,保存为jointed-new文件,并且同一个用户多次点击的正负样本对会按照时间戳排序连续存储,文件字段为:

label \t \t user id \t item id \t rating of the product(商品等级)\t 时间戳 \t 真实item 分类

split_test函数,写入文件jointed-new-split-info,字段如下:

(一个用户的最后一对正负样本:20190119,除最后一个外的其他所有对正负样本:20180118 ) \t label \t \t user id \t item id \t rating of the product(商品等级)\t 时间戳 \t 真实item 分类

每个用户的样本分为正负样本对,且按时间顺序排序,依次存下来,且第一列加上一列前缀,20180118表示该用户的样本中最后一次之前点击的样本,20190119标识最后一次点击样本。

local_aggretor.py

import sys
import hashlib
import random

fin = open("jointed-new-split-info", "r")
ftrain = open("local_train", "w")
ftest = open("local_test", "w")

last_user = "0"
common_fea = ""
line_idx = 0
for line in fin:
    items = line.strip().split("\t")
    ds = items[0]
    clk = int(items[1]) # 是否有点击
    user = items[2] # 用户id
    movie_id = items[3] # 商品item id
    dt = items[5] # 时间戳
    cat1 = items[6] # item分类

    if ds=="20180118": # 如果不是最后一个正负样本对
        fo = ftrain
    else: # 如果是最后一个正负样本对
        fo = ftest
    if user != last_user: # 用户的第一个样本对 
        movie_id_list = []
        cate1_list = []
        #print >> fo, items[1] + "\t" + user + "\t" + movie_id + "\t" + cat1 +"\t" + "" + "\t" + "" 
    else:
        history_clk_num = len(movie_id_list)
        cat_str = ""
        mid_str = ""
        for c1 in cate1_list:
            cat_str += c1 + ""
        for mid in movie_id_list:
            mid_str += mid + ""
        if len(cat_str) > 0: cat_str = cat_str[:-1]
        if len(mid_str) > 0: mid_str = mid_str[:-1]
        if history_clk_num >= 1:    # 8 is the average length of user behavior
            #print >> fo, items[1] + "\t" + user + "\t" + movie_id + "\t" + cat1 +"\t" + mid_str + "\t" + cat_str
            print(items[1] + "\t" + user + "\t" + movie_id + "\t" + cat1 +"\t" + mid_str + "\t" + cat_str, file = fo)
    last_user = user
    if clk:
        movie_id_list.append(movie_id) #之前点过的商品id
        cate1_list.append(cat1) #之前点过的商品分类
    line_idx += 1
    #local test 存储内容:
    #每个用户两行数据:
    #第一行:label 0, 用户id, 商品id(未点击,负样本), 商品分类, 之前点击过所有商品id,之前点击过所有商品分类
    #第二行:label 1, 用户id, 商品id(点击过,正样本), 商品分类, 之前点击过所有商品id,之前点击过所有商品分类 

打开上一步写入的文件jointed-new-split-info,由上面分析可知,用户的每一次点击样本都会生成一个正负样本对,然后按照时间戳依次存储,在此文件的处理中,会生成所有在此次点击之前有点击行为的样本(非用户的第一次点击都会生成样本),并且会添加之前点击商品id之前商品分类这两个特征,同样按时间顺序保存正负样本对,根据上面文件中的前缀来区分是否是最后一次点击,最后一次点击的样本保存在local_test文件(每个用户只保存最后一次点击对应的一对样本),非最后一次点击保存在local_train文件(可能会有多对样本),保存字段如下:

#第一行:label 0, 用户id, 商品id(未点击,负样本), 商品分类, 之前点击过所有商品id,之前点击过所有商品分类
#第二行:label 1, 用户id, 商品id(点击过,正样本), 商品分类, 之前点击过所有商品id,之前点击过所有商品分类

split_by_user.py

代码如下:

import random

fi = open("local_test", "r")
#local test 存储内容:
#每个用户两行数据:
#第一行:label 0, 用户id, 商品id(未点击,负样本), 商品分类, 之前点击过所有商品id,之前点击过所有商品分类
#第二行:label 1, 用户id, 商品id(点击过,正样本), 商品分类, 之前点击过所有商品id,之前点击过所有商品分类 
ftrain = open("local_train_splitByUser", "w")
ftest = open("local_test_splitByUser", "w")
#格式相同:
#第一行:label 0, 用户id, 商品id(未点击,负样本), 商品分类, 之前点击过所有商品id,之前点击过所有商品分类
#第二行:label 1, 用户id, 商品id(点击过,正样本), 商品分类, 之前点击过所有商品id,之前点击过所有商品分类 

while True:
    rand_int = random.randint(1, 10)
    noclk_line = fi.readline().strip()
    clk_line = fi.readline().strip() # 点击和未点击 成对出现
    if noclk_line == "" or clk_line == "":
        break
    if rand_int == 2:
        #print >> ftest, noclk_line
        print(noclk_line, file = ftest)
        #print >> ftest, clk_line
        print(clk_line, file = ftest)
    else:
        #print >> ftrain, noclk_line
        print(noclk_line, file = ftrain)
        #print >> ftrain, clk_line
        print(clk_line, file = ftrain)

这里代码比较简单,就是将local_test即用户最后一次点击,按照1:9分成train和test,分别存入local_train_splitByUser文件和local_test_splitByUser文件,保存字段同上一步存储:

#第一行:label 0, 用户id, 商品id(未点击,负样本), 商品分类, 之前点击过所有商品id,之前点击过所有商品分类
#第二行:label 1, 用户id, 商品id(点击过,正样本), 商品分类, 之前点击过所有商品id,之前点击过所有商品分类

generate_voc.py

import _pickle as cPickle

f_train = open("local_test", "r")
#第一行:label 0, 用户id, 商品id(未点击,负样本), 商品分类, 之前点击过所有商品id,之前点击过所有商品分类
#第二行:label 1, 用户id, 商品id(点击过,正样本), 商品分类, 之前点击过所有商品id,之前点击过所有商品分类 
uid_dict = {}
mid_dict = {}
cat_dict = {}

iddd = 0
for line in f_train:
    arr = line.strip("\n").split("\t")
    clk = arr[0] # 是否有点击
    uid = arr[1] # 用户id
    mid = arr[2] # 商品item id
    cat = arr[3] # item分类
    mid_list = arr[4] #之前点过的item id
    cat_list = arr[5] #之前点过的item的分类
    if uid not in uid_dict:
        uid_dict[uid] = 0
    uid_dict[uid] += 1
    if mid not in mid_dict:
        mid_dict[mid] = 0
    mid_dict[mid] += 1
    if cat not in cat_dict:
        cat_dict[cat] = 0
    cat_dict[cat] += 1
    if len(mid_list) == 0:
        continue
    for m in mid_list.split(""):
        if m not in mid_dict:
            mid_dict[m] = 0
        mid_dict[m] += 1
    #print iddd
    iddd+=1
    for c in cat_list.split(""):
        if c not in cat_dict:
            cat_dict[c] = 0
        cat_dict[c] += 1

sorted_uid_dict = sorted(uid_dict.items(), key=lambda x:x[1], reverse=True) # items将 key和value转换成 组元(key, value),按照value排序,降序排序。每个user样本个数都为2.
sorted_mid_dict = sorted(mid_dict.items(), key=lambda x:x[1], reverse=True) # 每个商品item对应的被点击次数。 
sorted_cat_dict = sorted(cat_dict.items(), key=lambda x:x[1], reverse=True) # 每个商品分类对应的被点击次数。

uid_voc = {}
index = 0
for key, value in sorted_uid_dict:
    uid_voc[key] = index
    index += 1
# 所有 uid 依次编号为 0,1,2,3,4,5  ......
mid_voc = {}
mid_voc["default_mid"] = 0
index = 1
for key, value in sorted_mid_dict:
    mid_voc[key] = index
    index += 1
# 所有的 item id 依次编号为0,1,2,3,4,5 .......
cat_voc = {}
cat_voc["default_cat"] = 0
index = 1
for key, value in sorted_cat_dict:
    cat_voc[key] = index
    index += 1
# 所有的 cate id 依次编号为0,1,2,3,4,5 .......

cPickle.dump(uid_voc, open("uid_voc.pkl", "wb"))
cPickle.dump(mid_voc, open("mid_voc.pkl", "wb"))
cPickle.dump(cat_voc, open("cat_voc.pkl", "wb"))

这部分代码也比较简单,将所有用户id(uid)、商品item id(mid)和item分类id(cat)按照出现次数从大到小排序,并且每一个id都会编号(从0开始依次编号),uid_voc、mid_voc和cat_voc分别表示三类特征id映射的编号(0,1,2,3,4........)。

先讲到这里,下一节开始介绍具体模型的训练过程。

你可能感兴趣的:(推荐算法,json)