个性化推荐

本项目使用文本卷积神经网络,并使用MovieLens数据集完成电影推荐的任务。

推荐系统在日常的网络应用中无处不在,比如网上购物、网上买书、新闻app、社交网络、音乐网站、电影网站等等等等,有人的地方就有推荐。根据个人的喜好,相同喜好人群的习惯等信息进行个性化的内容推荐。比如打开新闻类的app,因为有了个性化的内容,每个人看到的新闻首页都是不一样的。

这当然是很有用的,在信息爆炸的今天,获取信息的途径和方式多种多样,人们花费时间最多的不再是去哪获取信息,而是要在众多的信息中寻找自己感兴趣的,这就是信息超载问题。为了解决这个问题,推荐系统应运而生。

协同过滤是推荐系统应用较广泛的技术,该方法搜集用户的历史记录、个人喜好等信息,计算与其他用户的相似度,利用相似用户的评价来预测目标用户对特定项目的喜好程度。优点是会给用户推荐未浏览过的项目,缺点呢,对于新用户来说,没有任何与商品的交互记录和个人喜好等信息,存在冷启动问题,导致模型无法找到相似的用户或商品。

为了解决冷启动的问题,通常的做法是对于刚注册的用户,要求用户先选择自己感兴趣的话题、群组、商品、性格、喜欢的音乐类型等信息,比如豆瓣FM:

下载数据集

运行下面代码把数据集下载下来

import pandas as pd
from sklearn.model_selection import train_test_split
import numpy as np
from collections import Counter
import tensorflow as tf

import os
import pickle
import re
from tensorflow.python.ops import math_ops
from urllib.request import urlretrieve
from os.path import isfile, isdir
import zipfile
import tqdm
import hashlib

先来看看数据

本项目使用的是MovieLens 1M 数据集,包含6000个用户在近4000部电影上的1亿条评论。

数据集分为三个文件:用户数据users.dat,电影数据movies.dat和评分数据ratings.dat。

用户数据

分别有用户ID、性别、年龄、职业ID和邮编等字段。

数据中的格式:UserID::Gender::Age::Occupation::Zip-code

  • Gender is denoted by a "M" for male and "F" for female
  • Age is chosen from the following ranges:
    • 1: "Under 18"
    • 18: "18-24"
    • 25: "25-34"
    • 35: "35-44"
    • 45: "45-49"
    • 50: "50-55"
    • 56: "56+"
  • Occupation is chosen from the following choices:
    • 0: "other" or not specified
    • 1: "academic/educator"
    • 2: "artist"
    • 3: "clerical/admin"
    • 4: "college/grad student"
    • 5: "customer service"
    • 6: "doctor/health care"
    • 7: "executive/managerial"
    • 8: "farmer"
    • 9: "homemaker"
    • 10: "K-12 student"
    • 11: "lawyer"
    • 12: "programmer"
    • 13: "retired"
    • 14: "sales/marketing"
    • 15: "scientist"
    • 16: "self-employed"
    • 17: "technician/engineer"
    • 18: "tradesman/craftsman"
    • 19: "unemployed"
    • 20: "writer"
users_title = ['UserID', 'Gender', 'Age', 'OccupationID', 'Zip-code']
users = pd.read_table('./ml-1m/users.dat', sep='::', header=None, names=users_title, engine = 'python')
users.head()
UserID Gender Age OccupationID Zip-code
0 1 F 1 10 48067
1 2 M 56 16 70072
2 3 M 25 15 55117
3 4 M 45 7 02460
4 5 M 25 20 55455

可以看出UserID、Gender、Age和Occupation都是类别字段,其中邮编字段是我们不使用的。

电影数据

分别有电影ID、电影名和电影风格等字段。

数据中的格式:MovieID::Title::Genres

  • Titles are identical to titles provided by the IMDB (including

year of release)

  • Genres are pipe-separated and are selected from the following genres:
    • Action
    • Adventure
    • Animation
    • Children's
    • Comedy
    • Crime
    • Documentary
    • Drama
    • Fantasy
    • Film-Noir
    • Horror
    • Musical
    • Mystery
    • Romance
    • Sci-Fi
    • Thriller
    • War
    • Western
movies_title = ['MovieID', 'Title', 'Genres']
movies = pd.read_table('./ml-1m/movies.dat', sep='::', header=None, names=movies_title, engine = 'python')
movies.head()
MovieID Title Genres
0 1 Toy Story (1995) Animation|Children's|Comedy
1 2 Jumanji (1995) Adventure|Children's|Fantasy
2 3 Grumpier Old Men (1995) Comedy|Romance
3 4 Waiting to Exhale (1995) Comedy|Drama
4 5 Father of the Bride Part II (1995) Comedy

MovieID是类别字段,Title是文本,Genres也是类别字段

评分数据

分别有用户ID、电影ID、评分和时间戳等字段。

数据中的格式:UserID::MovieID::Rating::Timestamp

  • UserIDs range between 1 and 6040
  • MovieIDs range between 1 and 3952
  • Ratings are made on a 5-star scale (whole-star ratings only)
  • Timestamp is represented in seconds since the epoch as returned by time(2)
  • Each user has at least 20 ratings
ratings_title = ['UserID','MovieID', 'Rating', 'timestamps']
ratings = pd.read_table('./ml-1m/ratings.dat', sep='::', header=None, names=ratings_title, engine = 'python')
ratings.head()
UserID MovieID Rating timestamps
0 1 1193 5 978300760
1 1 661 3 978302109
2 1 914 3 978301968
3 1 3408 4 978300275
4 1 2355 5 978824291

评分字段Rating就是我们要学习的targets,时间戳字段我们不使用。

来说说数据预处理

  • UserID、Occupation和MovieID不用变。
  • Gender字段:需要将‘F’和‘M’转换成0和1。
  • Age字段:要转成7个连续数字0~6。
  • Genres字段:是分类字段,要转成数字。首先将Genres中的类别转成字符串到数字的字典,然后再将每个电影的Genres字段转成数字列表,因为有些电影是多个Genres的组合。
  • Title字段:处理方式跟Genres字段一样,首先创建文本到数字的字典,然后将Title中的描述转成数字的列表。另外Title中的年份也需要去掉。
  • Genres和Title字段需要将长度统一,这样在神经网络中方便处理。空白部分用‘< PAD >’对应的数字填充。

实现数据预处理

def load_data():
    """
    Load Dataset from File
    """
    #读取User数据
    users_title = ['UserID', 'Gender', 'Age', 'JobID', 'Zip-code']
    users = pd.read_table('./ml-1m/users.dat', sep='::', header=None, names=users_title, engine = 'python')
    users = users.filter(regex='UserID|Gender|Age|JobID')
    users_orig = users.values
    #改变User数据中性别和年龄
    gender_map = {'F':0, 'M':1}
    users['Gender'] = users['Gender'].map(gender_map)

    age_map = {val:ii for ii,val in enumerate(set(users['Age']))}
    users['Age'] = users['Age'].map(age_map)

    #读取Movie数据集
    movies_title = ['MovieID', 'Title', 'Genres']
    movies = pd.read_table('./ml-1m/movies.dat', sep='::', header=None, names=movies_title, engine = 'python')
    movies_orig = movies.values
    #将Title中的年份去掉
    pattern = re.compile(r'^(.*)\((\d+)\)$')

    title_map = {val:pattern.match(val).group(1) for ii,val in enumerate(set(movies['Title']))}
    movies['Title'] = movies['Title'].map(title_map)

    #电影类型转数字字典
    genres_set = set()
    for val in movies['Genres'].str.split('|'):
        genres_set.update(val)

    genres_set.add('<PAD>')
    genres2int = {val:ii for ii, val in enumerate(genres_set)}

    #将电影类型转成等长数字列表,长度是18
    genres_map = {val:[genres2int[row] for row in val.split('|')] for ii,val in enumerate(set(movies['Genres']))}

    for key in genres_map:
        for cnt in range(max(genres2int.values()) - len(genres_map[key])):
            genres_map[key].insert(len(genres_map[key]) + cnt,genres2int['<PAD>'])
    
    movies['Genres'] = movies['Genres'].map(genres_map)

    #电影Title转数字字典
    title_set = set()
    for val in movies['Title'].str.split():
        title_set.update(val)
    
    title_set.add('<PAD>')
    title2int = {val:ii for ii, val in enumerate(title_set)}

    #将电影Title转成等长数字列表,长度是15
    title_count = 15
    title_map = {val:[title2int[row] for row in val.split()] for ii,val in enumerate(set(movies['Title']))}
    
    for key in title_map:
        for cnt in range(title_count - len(title_map[key])):
            title_map[key].insert(len(title_map[key]) + cnt,title2int['<PAD>'])
    
    movies['Title'] = movies['Title'].map(title_map)

    #读取评分数据集
    ratings_title = ['UserID','MovieID', 'ratings', 'timestamps']
    ratings = pd.read_table('./ml-1m/ratings.dat', sep='::', header=None, names=ratings_title, engine = 'python')
    ratings = ratings.filter(regex='UserID|MovieID|ratings')

    #合并三个表
    data = pd.merge(pd.merge(ratings, users), movies)
    
    #将数据分成X和y两张表
    target_fields = ['ratings']
    features_pd, targets_pd = data.drop(target_fields, axis=1), data[target_fields]
    
    features = features_pd.values
    targets_values = targets_pd.values
    
    return title_count, title_set, genres2int, features, targets_values, ratings, users, movies, data, movies_orig, users_orig

加载数据并保存到本地

  • title_count:Title字段的长度(15)
  • title_set:Title文本的集合
  • genres2int:电影类型转数字的字典
  • features:是输入X
  • targets_values:是学习目标y
  • ratings:评分数据集的Pandas对象
  • users:用户数据集的Pandas对象
  • movies:电影数据的Pandas对象
  • data:三个数据集组合在一起的Pandas对象
  • movies_orig:没有做数据处理的原始电影数据
  • users_orig:没有做数据处理的原始用户数据
title_count, title_set, genres2int, features, targets_values, ratings, users, movies, data, movies_orig, users_orig = load_data()

pickle.dump((title_count, title_set, genres2int, features, targets_values, ratings, users, movies, data, movies_orig, users_orig), open('preprocess.p', 'wb'))

预处理后的数据

users.head()
UserID Gender Age JobID
0 1 0 0 10
1 2 1 5 16
2 3 1 6 15
3 4 1 2 7
4 5 1 6 20
movies.head()
MovieID Title Genres
0 1 [34, 436, 1562, 1562, 1562, 1562, 1562, 1562, ... [5, 1, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
1 2 [4591, 1562, 1562, 1562, 1562, 1562, 1562, 156... [6, 1, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
2 3 [1568, 2642, 3628, 1562, 1562, 1562, 1562, 156... [10, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
3 4 [3299, 356, 580, 1562, 1562, 1562, 1562, 1562,... [10, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
4 5 [4471, 336, 1359, 1170, 420, 4949, 1562, 1562,... [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
movies.values[0]
array([1,
       list([34, 436, 1562, 1562, 1562, 1562, 1562, 1562, 1562, 1562, 1562, 1562, 1562, 1562, 1562]),
       list([5, 1, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])],
      dtype=object)

从本地读取数据

title_count, title_set, genres2int, features, targets_values, ratings, users, movies, data, movies_orig, users_orig = pickle.load(open('preprocess.p', mode='rb'))

模型设计

通过研究数据集中的字段类型,我们发现有一些是类别字段,通常的处理是将这些字段转成one hot编码,但是像UserID、MovieID这样的字段就会变成非常的稀疏,输入的维度急剧膨胀,这是我们不愿意见到的,毕竟我这小笔记本不像大厂动辄能处理数以亿计维度的输入:)

所以在预处理数据时将这些字段转成了数字,我们用这个数字当做嵌入矩阵的索引,在网络的第一层使用了嵌入层,维度是(N,32)和(N,16)。

电影类型的处理要多一步,有时一个电影有多个电影类型,这样从嵌入矩阵索引出来是一个(n,32)的矩阵,因为有多个类型嘛,我们要将这个矩阵求和,变成(1,32)的向量。

电影名的处理比较特殊,没有使用循环神经网络,而是用了文本卷积网络,下文会进行说明。

从嵌入层索引出特征以后,将各特征传入全连接层,将输出再次传入全连接层,最终分别得到(1,200)的用户特征和电影特征两个特征向量。

我们的目的就是要训练出用户特征和电影特征,在实现推荐功能时使用。得到这两个特征以后,就可以选择任意的方式来拟合评分了。我使用了两种方式,一个是上图中画出的将两个特征做向量乘法,将结果与真实评分做回归,采用MSE优化损失。因为本质上这是一个回归问题,另一种方式是,将两个特征作为输入,再次传入全连接层,输出一个值,将输出值回归到真实评分,采用MSE优化损失。

实际上第二个方式的MSE loss在0.8附近,第一个方式在1附近,5次迭代的结果。

文本卷积网络

网络看起来像下面这样
图片来自Kim Yoon的论文:Convolutional Neural Networks for Sentence Classification

将卷积神经网络用于文本的文章建议你阅读Understanding Convolutional Neural Networks for NLP

网络的第一层是词嵌入层,由每一个单词的嵌入向量组成的嵌入矩阵。下一层使用多个不同尺寸(窗口大小)的卷积核在嵌入矩阵上做卷积,窗口大小指的是每次卷积覆盖几个单词。这里跟对图像做卷积不太一样,图像的卷积通常用2x2、3x3、5x5之类的尺寸,而文本卷积要覆盖整个单词的嵌入向量,所以尺寸是(单词数,向量维度),比如每次滑动3个,4个或者5个单词。第三层网络是max pooling得到一个长向量,最后使用dropout做正则化,最终得到了电影Title的特征。

辅助函数

import tensorflow as tf
import os
import pickle

def save_params(params):
    """
    Save parameters to file
    """
    pickle.dump(params, open('params.p', 'wb'))


def load_params():
    """
    Load parameters from file
    """
    return pickle.load(open('params.p', mode='rb'))
#嵌入矩阵的维度
embed_dim = 32
#用户ID个数
uid_max = max(features.take(0,1)) + 1 # 6040
#性别个数
gender_max = max(features.take(2,1)) + 1 # 1 + 1 = 2
#年龄类别个数
age_max = max(features.take(3,1)) + 1 # 6 + 1 = 7
#职业个数
job_max = max(features.take(4,1)) + 1# 20 + 1 = 21

#电影ID个数
movie_id_max = max(features.take(1,1)) + 1 # 3952
#电影类型个数
movie_categories_max = max(genres2int.values()) + 1 # 18 + 1 = 19
#电影名单词个数
movie_title_max = len(title_set) # 5216

#对电影类型嵌入向量做加和操作的标志,考虑过使用mean做平均,但是没实现mean
combiner = "sum"

#电影名长度
sentences_size = title_count # = 15
#文本卷积滑动窗口,分别滑动2, 3, 4, 5个单词
window_sizes = {2, 3, 4, 5}
#文本卷积核数量
filter_num = 8

#电影ID转下标的字典,数据集中电影ID跟下标不一致,比如第5行的数据电影ID不一定是5
movieid2idx = {val[0]:i for i, val in enumerate(movies.values)}

超参

# Number of Epochs
num_epochs = 5
# Batch Size
batch_size = 256

dropout_keep = 0.5
# Learning Rate
learning_rate = 0.0001
# Show stats for every n number of batches
show_every_n_batches = 20

save_dir = './save'

编码实现

输入

定义输入的占位符

def get_inputs():
    uid = tf.placeholder(tf.int32, [None, 1], name="uid")
    movie_id = tf.placeholder(tf.int32, [None, 1], name="movie_id")
    movie_categories = tf.placeholder(tf.int32, [None, 18], name="movie_categories")
    movie_titles = tf.placeholder(tf.int32, [None, 15], name="movie_titles")
    targets = tf.placeholder(tf.int32, [None, 1], name="targets")
    LearningRate = tf.placeholder(tf.float32, name = "LearningRate")
    dropout_keep_prob = tf.placeholder(tf.float32, name = "dropout_keep_prob")
    return uid, movie_id, movie_categories, movie_titles, targets, LearningRate, dropout_keep_prob

构建神经网络

定义User的嵌入矩阵

def get_user_embedding(uid):
    with tf.name_scope("user_embedding"):
        uid_embed_matrix = tf.Variable(tf.random_uniform([uid_max, embed_dim], -1, 1), name = "uid_embed_matrix")
        uid_embed_layer = tf.nn.embedding_lookup(uid_embed_matrix, uid, name = "uid_embed_layer")
    return uid_embed_layer

将User的嵌入矩阵一起全连接生成User的特征

def get_user_feature_layer(uid_embed_layer):
    with tf.name_scope("user_fc"):
        #第一层全连接
        uid_fc_layer = tf.layers.dense(uid_embed_layer, embed_dim, name = "uid_fc_layer", activation=tf.nn.relu)
    
        #第二层全连接
        user_combine_layer = tf.contrib.layers.fully_connected(uid_fc_layer, 200, tf.tanh)  #(?, 1, 200)
    
        user_combine_layer_flat = tf.reshape(user_combine_layer, [-1, 200])
    return user_combine_layer, user_combine_layer_flat

定义Movie ID的嵌入矩阵

def get_movie_id_embed_layer(movie_id):
    with tf.name_scope("movie_embedding"):
        movie_id_embed_matrix = tf.Variable(tf.random_uniform([movie_id_max, embed_dim], -1, 1), name = "movie_id_embed_matrix")
        movie_id_embed_layer = tf.nn.embedding_lookup(movie_id_embed_matrix, movie_id, name = "movie_id_embed_layer")
    return movie_id_embed_layer

对电影类型的多个嵌入向量做加和

def get_movie_categories_layers(movie_categories):
    with tf.name_scope("movie_categories_layers"):
        movie_categories_embed_matrix = tf.Variable(tf.random_uniform([movie_categories_max, embed_dim], -1, 1), name = "movie_categories_embed_matrix")
        movie_categories_embed_layer = tf.nn.embedding_lookup(movie_categories_embed_matrix, movie_categories, name = "movie_categories_embed_layer")
        if combiner == "sum":
            movie_categories_embed_layer = tf.reduce_sum(movie_categories_embed_layer, axis=1, keep_dims=True)
    #     elif combiner == "mean":

    return movie_categories_embed_layer

Movie Title的文本卷积网络实现

def get_movie_cnn_layer(movie_titles):
    #从嵌入矩阵中得到电影名对应的各个单词的嵌入向量
    with tf.name_scope("movie_embedding"):
        movie_title_embed_matrix = tf.Variable(tf.random_uniform([movie_title_max, embed_dim], -1, 1), name = "movie_title_embed_matrix")
        movie_title_embed_layer = tf.nn.embedding_lookup(movie_title_embed_matrix, movie_titles, name = "movie_title_embed_layer")
        movie_title_embed_layer_expand = tf.expand_dims(movie_title_embed_layer, -1)
    
    #对文本嵌入层使用不同尺寸的卷积核做卷积和最大池化
    pool_layer_lst = []
    for window_size in window_sizes:
        with tf.name_scope("movie_txt_conv_maxpool_{}".format(window_size)):
            filter_weights = tf.Variable(tf.truncated_normal([window_size, embed_dim, 1, filter_num],stddev=0.1),name = "filter_weights")
            filter_bias = tf.Variable(tf.constant(0.1, shape=[filter_num]), name="filter_bias")
            
            conv_layer = tf.nn.conv2d(movie_title_embed_layer_expand, filter_weights, [1,1,1,1], padding="VALID", name="conv_layer")
            relu_layer = tf.nn.relu(tf.nn.bias_add(conv_layer,filter_bias), name ="relu_layer")
            
            maxpool_layer = tf.nn.max_pool(relu_layer, [1,sentences_size - window_size + 1 ,1,1], [1,1,1,1], padding="VALID", name="maxpool_layer")
            pool_layer_lst.append(maxpool_layer)

    #Dropout层
    with tf.name_scope("pool_dropout"):
        pool_layer = tf.concat(pool_layer_lst, 3, name ="pool_layer")
        max_num = len(window_sizes) * filter_num
        pool_layer_flat = tf.reshape(pool_layer , [-1, 1, max_num], name = "pool_layer_flat")
    
        dropout_layer = tf.nn.dropout(pool_layer_flat, dropout_keep_prob, name = "dropout_layer")
    return pool_layer_flat, dropout_layer

将Movie的各个层一起做全连接

def get_movie_feature_layer(movie_id_embed_layer):
    with tf.name_scope("movie_fc"):
        #第一层全连接
        movie_id_fc_layer = tf.layers.dense(movie_id_embed_layer, embed_dim, name = "movie_id_fc_layer", activation=tf.nn.relu)

        #第二层全连接
        movie_combine_layer = tf.contrib.layers.fully_connected(movie_id_fc_layer, 200, tf.tanh)  #(?, 1, 200)
    
        movie_combine_layer_flat = tf.reshape(movie_combine_layer, [-1, 200])
    return movie_combine_layer, movie_combine_layer_flat

构建计算图

tf.reset_default_graph()
train_graph = tf.Graph()
with train_graph.as_default():
    #获取输入占位符
    uid, movie_id, movie_categories, movie_titles, targets, lr, dropout_keep_prob = get_inputs()
    #获取User的4个嵌入向量
    uid_embed_layer = get_user_embedding(uid)
    #得到用户特征
    user_combine_layer, user_combine_layer_flat = get_user_feature_layer(uid_embed_layer)
    #获取电影ID的嵌入向量
    movie_id_embed_layer = get_movie_id_embed_layer(movie_id)
    #获取电影类型的嵌入向量
    #movie_categories_embed_layer = get_movie_categories_layers(movie_categories)
    #获取电影名的特征向量
    #pool_layer_flat, dropout_layer = get_movie_cnn_layer(movie_titles)
    #得到电影特征
    movie_combine_layer, movie_combine_layer_flat = get_movie_feature_layer(movie_id_embed_layer)
    #计算出评分,要注意两个不同的方案,inference的名字(name值)是不一样的,后面做推荐时要根据name取得tensor
    with tf.name_scope("inference"):
        #将用户特征和电影特征作为输入,经过全连接,输出一个值的方案
#         inference_layer = tf.concat([user_combine_layer_flat, movie_combine_layer_flat], 1)  #(?, 200)
#         inference = tf.layers.dense(inference_layer, 1,
#                                     kernel_initializer=tf.truncated_normal_initializer(stddev=0.01), 
#                                     kernel_regularizer=tf.nn.l2_loss, name="inference")
        #简单的将用户特征和电影特征做矩阵乘法得到一个预测评分
#        inference = tf.matmul(user_combine_layer_flat, tf.transpose(movie_combine_layer_flat))
        inference = tf.reduce_sum(user_combine_layer_flat * movie_combine_layer_flat, axis=1)
        inference = tf.expand_dims(inference, axis=1)

    with tf.name_scope("loss"):
        # MSE损失,将计算值回归到评分
        cost = tf.losses.mean_squared_error(targets, inference )
        loss = tf.reduce_mean(cost)
    # 优化损失 
#     train_op = tf.train.AdamOptimizer(lr).minimize(loss)  #cost
    global_step = tf.Variable(0, name="global_step", trainable=False)
    optimizer = tf.train.AdamOptimizer(lr)
    gradients = optimizer.compute_gradients(loss)  #cost
    train_op = optimizer.apply_gradients(gradients, global_step=global_step)
    
inference
<tf.Tensor 'inference/ExpandDims:0' shape=(?, 1) dtype=float32>

取得batch

def get_batches(Xs, ys, batch_size):
    for start in range(0, len(Xs), batch_size):
        end = min(start + batch_size, len(Xs))
        yield Xs[start:end], ys[start:end]

训练网络

%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import matplotlib.pyplot as plt
import time
import datetime

losses = {'train':[], 'test':[]}

with tf.Session(graph=train_graph) as sess:
    
    #搜集数据给tensorBoard用
    # Keep track of gradient values and sparsity
    grad_summaries = []
    for g, v in gradients:
        if g is not None:
            grad_hist_summary = tf.summary.histogram("{}/grad/hist".format(v.name.replace(':', '_')), g)
            sparsity_summary = tf.summary.scalar("{}/grad/sparsity".format(v.name.replace(':', '_')), tf.nn.zero_fraction(g))
            grad_summaries.append(grad_hist_summary)
            grad_summaries.append(sparsity_summary)
    grad_summaries_merged = tf.summary.merge(grad_summaries)
        
    # Output directory for models and summaries
    timestamp = str(int(time.time()))
    out_dir = os.path.abspath(os.path.join(os.path.curdir, "runs", timestamp))
    print("Writing to {}\n".format(out_dir))
     
    # Summaries for loss and accuracy
    loss_summary = tf.summary.scalar("loss", loss)

    # Train Summaries
    train_summary_op = tf.summary.merge([loss_summary, grad_summaries_merged])
    train_summary_dir = os.path.join(out_dir, "summaries", "train")
    train_summary_writer = tf.summary.FileWriter(train_summary_dir, sess.graph)

    # Inference summaries
    inference_summary_op = tf.summary.merge([loss_summary])
    inference_summary_dir = os.path.join(out_dir, "summaries", "inference")
    inference_summary_writer = tf.summary.FileWriter(inference_summary_dir, sess.graph)

    sess.run(tf.global_variables_initializer())
    saver = tf.train.Saver()
    for epoch_i in range(num_epochs):
        
        #将数据集分成训练集和测试集,随机种子不固定
        train_X,test_X, train_y, test_y = train_test_split(features,  
                                                           targets_values,  
                                                           test_size = 0.2,  
                                                           random_state = 0)  
        
        train_batches = get_batches(train_X, train_y, batch_size)
        test_batches = get_batches(test_X, test_y, batch_size)
    
        #训练的迭代,保存训练损失
        for batch_i in range(len(train_X) // batch_size):
            x, y = next(train_batches)

            categories = np.zeros([batch_size, 18])
            for i in range(batch_size):
                categories[i] = x.take(6,1)[i]

            titles = np.zeros([batch_size, sentences_size])
            for i in range(batch_size):
                titles[i] = x.take(5,1)[i]

            feed = {
                uid: np.reshape(x.take(0,1), [batch_size, 1]),
                movie_id: np.reshape(x.take(1,1), [batch_size, 1]),
                movie_categories: categories,  #x.take(6,1)
                movie_titles: titles,  #x.take(5,1)
                targets: np.reshape(y, [batch_size, 1]),
                dropout_keep_prob: dropout_keep, #dropout_keep
                lr: learning_rate}

            step, train_loss, summaries, _ = sess.run([global_step, loss, train_summary_op, train_op], feed)  #cost
            losses['train'].append(train_loss)
            train_summary_writer.add_summary(summaries, step)  #
            
            # Show every <show_every_n_batches> batches
            if (epoch_i * (len(train_X) // batch_size) + batch_i) % show_every_n_batches == 0:
                time_str = datetime.datetime.now().isoformat()
                print('{}: Epoch {:>3} Batch {:>4}/{}   train_loss = {:.3f}'.format(
                    time_str,
                    epoch_i,
                    batch_i,
                    (len(train_X) // batch_size),
                    train_loss))
                
        #使用测试数据的迭代
        for batch_i  in range(len(test_X) // batch_size):
            x, y = next(test_batches)
            
            categories = np.zeros([batch_size, 18])
            for i in range(batch_size):
                categories[i] = x.take(6,1)[i]

            titles = np.zeros([batch_size, sentences_size])
            for i in range(batch_size):
                titles[i] = x.take(5,1)[i]

            feed = {
                uid: np.reshape(x.take(0,1), [batch_size, 1]),
                movie_id: np.reshape(x.take(1,1), [batch_size, 1]),
                movie_categories: categories,  #x.take(6,1)
                movie_titles: titles,  #x.take(5,1)
                targets: np.reshape(y, [batch_size, 1]),
                dropout_keep_prob: 1,
                lr: learning_rate}
            
            step, test_loss, summaries = sess.run([global_step, loss, inference_summary_op], feed)  #cost

            #保存测试损失
            losses['test'].append(test_loss)
            inference_summary_writer.add_summary(summaries, step)  #

            time_str = datetime.datetime.now().isoformat()
            if (epoch_i * (len(test_X) // batch_size) + batch_i) % show_every_n_batches == 0:
                print('{}: Epoch {:>3} Batch {:>4}/{}   test_loss = {:.3f}'.format(
                    time_str,
                    epoch_i,
                    batch_i,
                    (len(test_X) // batch_size),
                    test_loss))

    # Save Model
    saver.save(sess, save_dir)  #, global_step=epoch_i
    print('Model Trained and Saved')
Writing to /home/hanxiaoyang/recommender_system/Student/student-f7/runs/1543734533

2018-12-02T15:08:54.630039: Epoch   0 Batch    0/3125   train_loss = 14.999
2018-12-02T15:08:54.926223: Epoch   0 Batch   20/3125   train_loss = 11.530
2018-12-02T15:08:55.196877: Epoch   0 Batch   40/3125   train_loss = 8.304
2018-12-02T15:08:55.476122: Epoch   0 Batch   60/3125   train_loss = 5.417
2018-12-02T15:08:55.765892: Epoch   0 Batch   80/3125   train_loss = 4.109
2018-12-02T15:08:56.058309: Epoch   0 Batch  100/3125   train_loss = 3.422
2018-12-02T15:08:56.350407: Epoch   0 Batch  120/3125   train_loss = 3.192
2018-12-02T15:08:56.630023: Epoch   0 Batch  140/3125   train_loss = 2.495
2018-12-02T15:08:56.908233: Epoch   0 Batch  160/3125   train_loss = 2.632
2018-12-02T15:08:57.193453: Epoch   0 Batch  180/3125   train_loss = 2.157
2018-12-02T15:08:57.473207: Epoch   0 Batch  200/3125   train_loss = 2.503
2018-12-02T15:08:57.775503: Epoch   0 Batch  220/3125   train_loss = 2.509
2018-12-02T15:08:58.082774: Epoch   0 Batch  240/3125   train_loss = 2.247
2018-12-02T15:08:58.368983: Epoch   0 Batch  260/3125   train_loss = 2.324
2018-12-02T15:08:58.671305: Epoch   0 Batch  280/3125   train_loss = 2.225
2018-12-02T15:08:58.967622: Epoch   0 Batch  300/3125   train_loss = 1.853
2018-12-02T15:08:59.266105: Epoch   0 Batch  320/3125   train_loss = 2.018
2018-12-02T15:08:59.584633: Epoch   0 Batch  340/3125   train_loss = 1.995
2018-12-02T15:08:59.892979: Epoch   0 Batch  360/3125   train_loss = 1.902
2018-12-02T15:09:00.178931: Epoch   0 Batch  380/3125   train_loss = 2.065
2018-12-02T15:09:00.489512: Epoch   0 Batch  400/3125   train_loss = 1.859
2018-12-02T15:09:00.790778: Epoch   0 Batch  420/3125   train_loss = 1.806
2018-12-02T15:09:01.098491: Epoch   0 Batch  440/3125   train_loss = 1.874
2018-12-02T15:09:01.413542: Epoch   0 Batch  460/3125   train_loss = 2.052
2018-12-02T15:09:01.734180: Epoch   0 Batch  480/3125   train_loss = 1.685
2018-12-02T15:09:02.079382: Epoch   0 Batch  500/3125   train_loss = 1.408
2018-12-02T15:09:02.372887: Epoch   0 Batch  520/3125   train_loss = 1.635
2018-12-02T15:09:02.684697: Epoch   0 Batch  540/3125   train_loss = 1.539
2018-12-02T15:09:03.023571: Epoch   0 Batch  560/3125   train_loss = 1.992
2018-12-02T15:09:03.352080: Epoch   0 Batch  580/3125   train_loss = 1.804
2018-12-02T15:09:03.640875: Epoch   0 Batch  600/3125   train_loss = 1.720
2018-12-02T15:09:03.922265: Epoch   0 Batch  620/3125   train_loss = 1.847
2018-12-02T15:09:04.201808: Epoch   0 Batch  640/3125   train_loss = 1.810
2018-12-02T15:09:04.517607: Epoch   0 Batch  660/3125   train_loss = 1.693
2018-12-02T15:09:04.818062: Epoch   0 Batch  680/3125   train_loss = 1.634
2018-12-02T15:09:05.130390: Epoch   0 Batch  700/3125   train_loss = 1.567
2018-12-02T15:09:05.443439: Epoch   0 Batch  720/3125   train_loss = 1.429
2018-12-02T15:09:05.745778: Epoch   0 Batch  740/3125   train_loss = 1.592
2018-12-02T15:09:06.035847: Epoch   0 Batch  760/3125   train_loss = 1.750
2018-12-02T15:09:06.342745: Epoch   0 Batch  780/3125   train_loss = 1.797
2018-12-02T15:09:06.636230: Epoch   0 Batch  800/3125   train_loss = 1.589
2018-12-02T15:09:06.929605: Epoch   0 Batch  820/3125   train_loss = 1.563
2018-12-02T15:09:07.210753: Epoch   0 Batch  840/3125   train_loss = 1.610
2018-12-02T15:09:07.527024: Epoch   0 Batch  860/3125   train_loss = 1.522
2018-12-02T15:09:07.835487: Epoch   0 Batch  880/3125   train_loss = 1.479
2018-12-02T15:09:08.139285: Epoch   0 Batch  900/3125   train_loss = 1.468
2018-12-02T15:09:08.449343: Epoch   0 Batch  920/3125   train_loss = 1.506
2018-12-02T15:09:08.760228: Epoch   0 Batch  940/3125   train_loss = 1.531
2018-12-02T15:09:09.060086: Epoch   0 Batch  960/3125   train_loss = 1.732
2018-12-02T15:09:09.358086: Epoch   0 Batch  980/3125   train_loss = 1.554
2018-12-02T15:09:09.664750: Epoch   0 Batch 1000/3125   train_loss = 1.663
2018-12-02T15:09:09.945375: Epoch   0 Batch 1020/3125   train_loss = 1.366
2018-12-02T15:09:10.229868: Epoch   0 Batch 1040/3125   train_loss = 1.393
2018-12-02T15:09:10.541320: Epoch   0 Batch 1060/3125   train_loss = 1.595
2018-12-02T15:09:10.832541: Epoch   0 Batch 1080/3125   train_loss = 1.365
2018-12-02T15:09:11.149264: Epoch   0 Batch 1100/3125   train_loss = 1.532
2018-12-02T15:09:11.443734: Epoch   0 Batch 1120/3125   train_loss = 1.518
2018-12-02T15:09:11.748989: Epoch   0 Batch 1140/3125   train_loss = 1.449
2018-12-02T15:09:12.082288: Epoch   0 Batch 1160/3125   train_loss = 1.487
2018-12-02T15:09:12.365410: Epoch   0 Batch 1180/3125   train_loss = 1.514
2018-12-02T15:09:12.663887: Epoch   0 Batch 1200/3125   train_loss = 1.484
2018-12-02T15:09:12.953294: Epoch   0 Batch 1220/3125   train_loss = 1.320
2018-12-02T15:09:13.244023: Epoch   0 Batch 1240/3125   train_loss = 1.372
2018-12-02T15:09:13.540576: Epoch   0 Batch 1260/3125   train_loss = 1.323
2018-12-02T15:09:13.840900: Epoch   0 Batch 1280/3125   train_loss = 1.330
2018-12-02T15:09:14.116659: Epoch   0 Batch 1300/3125   train_loss = 1.358
2018-12-02T15:09:14.402939: Epoch   0 Batch 1320/3125   train_loss = 1.342
2018-12-02T15:09:14.723077: Epoch   0 Batch 1340/3125   train_loss = 1.304
2018-12-02T15:09:15.023964: Epoch   0 Batch 1360/3125   train_loss = 1.330
2018-12-02T15:09:15.334055: Epoch   0 Batch 1380/3125   train_loss = 1.321
2018-12-02T15:09:15.644647: Epoch   0 Batch 1400/3125   train_loss = 1.300
2018-12-02T15:09:15.936032: Epoch   0 Batch 1420/3125   train_loss = 1.385
2018-12-02T15:09:16.235722: Epoch   0 Batch 1440/3125   train_loss = 1.111
2018-12-02T15:09:16.549303: Epoch   0 Batch 1460/3125   train_loss = 1.340
2018-12-02T15:09:16.827390: Epoch   0 Batch 1480/3125   train_loss = 1.395
2018-12-02T15:09:17.132810: Epoch   0 Batch 1500/3125   train_loss = 1.486
2018-12-02T15:09:17.415596: Epoch   0 Batch 1520/3125   train_loss = 1.308
2018-12-02T15:09:17.698310: Epoch   0 Batch 1540/3125   train_loss = 1.259
2018-12-02T15:09:17.963719: Epoch   0 Batch 1560/3125   train_loss = 1.267
2018-12-02T15:09:18.247582: Epoch   0 Batch 1580/3125   train_loss = 1.490
2018-12-02T15:09:18.528274: Epoch   0 Batch 1600/3125   train_loss = 1.309
2018-12-02T15:09:18.830563: Epoch   0 Batch 1620/3125   train_loss = 1.414
2018-12-02T15:09:19.127400: Epoch   0 Batch 1640/3125   train_loss = 1.272
2018-12-02T15:09:19.433637: Epoch   0 Batch 1660/3125   train_loss = 1.397
2018-12-02T15:09:19.739575: Epoch   0 Batch 1680/3125   train_loss = 1.296
2018-12-02T15:09:20.026200: Epoch   0 Batch 1700/3125   train_loss = 1.214
2018-12-02T15:09:20.345206: Epoch   0 Batch 1720/3125   train_loss = 1.348
2018-12-02T15:09:20.662695: Epoch   0 Batch 1740/3125   train_loss = 1.320
2018-12-02T15:09:20.971763: Epoch   0 Batch 1760/3125   train_loss = 1.389
2018-12-02T15:09:21.289330: Epoch   0 Batch 1780/3125   train_loss = 1.166
2018-12-02T15:09:21.579248: Epoch   0 Batch 1800/3125   train_loss = 1.228
2018-12-02T15:09:21.862921: Epoch   0 Batch 1820/3125   train_loss = 1.229
2018-12-02T15:09:22.164970: Epoch   0 Batch 1840/3125   train_loss = 1.292
2018-12-02T15:09:22.481697: Epoch   0 Batch 1860/3125   train_loss = 1.396
2018-12-02T15:09:22.768343: Epoch   0 Batch 1880/3125   train_loss = 1.368
2018-12-02T15:09:23.052474: Epoch   0 Batch 1900/3125   train_loss = 1.059
2018-12-02T15:09:23.339176: Epoch   0 Batch 1920/3125   train_loss = 1.261
2018-12-02T15:09:23.642719: Epoch   0 Batch 1940/3125   train_loss = 1.251
2018-12-02T15:09:23.913377: Epoch   0 Batch 1960/3125   train_loss = 1.171
2018-12-02T15:09:24.207502: Epoch   0 Batch 1980/3125   train_loss = 1.293
2018-12-02T15:09:24.514103: Epoch   0 Batch 2000/3125   train_loss = 1.437
2018-12-02T15:09:24.806367: Epoch   0 Batch 2020/3125   train_loss = 1.315
2018-12-02T15:09:25.082649: Epoch   0 Batch 2040/3125   train_loss = 1.225
2018-12-02T15:09:25.343345: Epoch   0 Batch 2060/3125   train_loss = 1.172
2018-12-02T15:09:25.630751: Epoch   0 Batch 2080/3125   train_loss = 1.289
2018-12-02T15:09:25.914602: Epoch   0 Batch 2100/3125   train_loss = 1.190
2018-12-02T15:09:26.231335: Epoch   0 Batch 2120/3125   train_loss = 1.074
2018-12-02T15:09:26.506390: Epoch   0 Batch 2140/3125   train_loss = 1.313
2018-12-02T15:09:26.783359: Epoch   0 Batch 2160/3125   train_loss = 1.198
2018-12-02T15:09:27.080022: Epoch   0 Batch 2180/3125   train_loss = 1.168
2018-12-02T15:09:27.375285: Epoch   0 Batch 2200/3125   train_loss = 1.196
2018-12-02T15:09:27.672720: Epoch   0 Batch 2220/3125   train_loss = 1.191
2018-12-02T15:09:27.957107: Epoch   0 Batch 2240/3125   train_loss = 1.048
2018-12-02T15:09:28.261160: Epoch   0 Batch 2260/3125   train_loss = 1.192
2018-12-02T15:09:28.562755: Epoch   0 Batch 2280/3125   train_loss = 1.210
2018-12-02T15:09:28.855218: Epoch   0 Batch 2300/3125   train_loss = 1.095
2018-12-02T15:09:29.153555: Epoch   0 Batch 2320/3125   train_loss = 1.253
2018-12-02T15:09:29.427748: Epoch   0 Batch 2340/3125   train_loss = 1.286
2018-12-02T15:09:29.712249: Epoch   0 Batch 2360/3125   train_loss = 1.184
2018-12-02T15:09:29.994927: Epoch   0 Batch 2380/3125   train_loss = 1.198
2018-12-02T15:09:30.277742: Epoch   0 Batch 2400/3125   train_loss = 1.255
2018-12-02T15:09:30.604134: Epoch   0 Batch 2420/3125   train_loss = 1.072
2018-12-02T15:09:30.896775: Epoch   0 Batch 2440/3125   train_loss = 1.226
2018-12-02T15:09:31.202511: Epoch   0 Batch 2460/3125   train_loss = 1.200
2018-12-02T15:09:31.515326: Epoch   0 Batch 2480/3125   train_loss = 1.171
2018-12-02T15:09:31.832978: Epoch   0 Batch 2500/3125   train_loss = 1.125
2018-12-02T15:09:32.139429: Epoch   0 Batch 2520/3125   train_loss = 1.185
2018-12-02T15:09:32.449646: Epoch   0 Batch 2540/3125   train_loss = 1.108
2018-12-02T15:09:32.732977: Epoch   0 Batch 2560/3125   train_loss = 1.016
2018-12-02T15:09:33.024127: Epoch   0 Batch 2580/3125   train_loss = 1.200
2018-12-02T15:09:33.288278: Epoch   0 Batch 2600/3125   train_loss = 1.098
2018-12-02T15:09:33.564636: Epoch   0 Batch 2620/3125   train_loss = 1.042
2018-12-02T15:09:33.845111: Epoch   0 Batch 2640/3125   train_loss = 1.132
2018-12-02T15:09:34.133573: Epoch   0 Batch 2660/3125   train_loss = 1.145
2018-12-02T15:09:34.415841: Epoch   0 Batch 2680/3125   train_loss = 1.077
2018-12-02T15:09:34.708671: Epoch   0 Batch 2700/3125   train_loss = 1.178
2018-12-02T15:09:34.994359: Epoch   0 Batch 2720/3125   train_loss = 1.181
2018-12-02T15:09:35.273277: Epoch   0 Batch 2740/3125   train_loss = 1.273
2018-12-02T15:09:35.543321: Epoch   0 Batch 2760/3125   train_loss = 1.145
2018-12-02T15:09:35.813954: Epoch   0 Batch 2780/3125   train_loss = 1.050
2018-12-02T15:09:36.105075: Epoch   0 Batch 2800/3125   train_loss = 1.386
2018-12-02T15:09:36.401395: Epoch   0 Batch 2820/3125   train_loss = 1.385
2018-12-02T15:09:36.695111: Epoch   0 Batch 2840/3125   train_loss = 1.209
2018-12-02T15:09:36.981984: Epoch   0 Batch 2860/3125   train_loss = 1.066
2018-12-02T15:09:37.260874: Epoch   0 Batch 2880/3125   train_loss = 1.244
2018-12-02T15:09:37.542966: Epoch   0 Batch 2900/3125   train_loss = 1.138
2018-12-02T15:09:37.810490: Epoch   0 Batch 2920/3125   train_loss = 1.078
2018-12-02T15:09:38.075726: Epoch   0 Batch 2940/3125   train_loss = 1.166
2018-12-02T15:09:38.352385: Epoch   0 Batch 2960/3125   train_loss = 1.071
2018-12-02T15:09:38.646493: Epoch   0 Batch 2980/3125   train_loss = 1.093
2018-12-02T15:09:38.967269: Epoch   0 Batch 3000/3125   train_loss = 1.068
2018-12-02T15:09:39.263935: Epoch   0 Batch 3020/3125   train_loss = 1.193
2018-12-02T15:09:39.542416: Epoch   0 Batch 3040/3125   train_loss = 1.170
2018-12-02T15:09:39.829399: Epoch   0 Batch 3060/3125   train_loss = 1.031
2018-12-02T15:09:40.126042: Epoch   0 Batch 3080/3125   train_loss = 1.229
2018-12-02T15:09:40.432716: Epoch   0 Batch 3100/3125   train_loss = 1.266
2018-12-02T15:09:40.737233: Epoch   0 Batch 3120/3125   train_loss = 1.030
2018-12-02T15:09:40.858704: Epoch   0 Batch    0/781   test_loss = 1.023
2018-12-02T15:09:41.016189: Epoch   0 Batch   20/781   test_loss = 1.100
2018-12-02T15:09:41.177747: Epoch   0 Batch   40/781   test_loss = 1.171
2018-12-02T15:09:41.340759: Epoch   0 Batch   60/781   test_loss = 1.290
2018-12-02T15:09:41.499710: Epoch   0 Batch   80/781   test_loss = 1.231
2018-12-02T15:09:41.663946: Epoch   0 Batch  100/781   test_loss = 1.271
2018-12-02T15:09:41.839625: Epoch   0 Batch  120/781   test_loss = 1.118
2018-12-02T15:09:42.003491: Epoch   0 Batch  140/781   test_loss = 1.185
2018-12-02T15:09:42.163677: Epoch   0 Batch  160/781   test_loss = 1.287
2018-12-02T15:09:42.324468: Epoch   0 Batch  180/781   test_loss = 1.217
2018-12-02T15:09:42.503528: Epoch   0 Batch  200/781   test_loss = 1.126
2018-12-02T15:09:42.665481: Epoch   0 Batch  220/781   test_loss = 1.060
2018-12-02T15:09:42.828787: Epoch   0 Batch  240/781   test_loss = 1.127
2018-12-02T15:09:43.006846: Epoch   0 Batch  260/781   test_loss = 1.240
2018-12-02T15:09:43.172602: Epoch   0 Batch  280/781   test_loss = 1.301
2018-12-02T15:09:43.352548: Epoch   0 Batch  300/781   test_loss = 1.171
2018-12-02T15:09:43.514435: Epoch   0 Batch  320/781   test_loss = 1.202
2018-12-02T15:09:43.683839: Epoch   0 Batch  340/781   test_loss = 0.906
2018-12-02T15:09:43.878163: Epoch   0 Batch  360/781   test_loss = 1.177
2018-12-02T15:09:44.035065: Epoch   0 Batch  380/781   test_loss = 1.131
2018-12-02T15:09:44.207635: Epoch   0 Batch  400/781   test_loss = 1.126
2018-12-02T15:09:44.383291: Epoch   0 Batch  420/781   test_loss = 1.057
2018-12-02T15:09:44.560787: Epoch   0 Batch  440/781   test_loss = 1.224
2018-12-02T15:09:44.715417: Epoch   0 Batch  460/781   test_loss = 1.081
2018-12-02T15:09:44.906609: Epoch   0 Batch  480/781   test_loss = 1.136
2018-12-02T15:09:45.100408: Epoch   0 Batch  500/781   test_loss = 0.860
2018-12-02T15:09:45.272540: Epoch   0 Batch  520/781   test_loss = 1.064
2018-12-02T15:09:45.439389: Epoch   0 Batch  540/781   test_loss = 1.006
2018-12-02T15:09:45.625014: Epoch   0 Batch  560/781   test_loss = 1.168
2018-12-02T15:09:45.799305: Epoch   0 Batch  580/781   test_loss = 1.199
2018-12-02T15:09:45.961687: Epoch   0 Batch  600/781   test_loss = 1.208
2018-12-02T15:09:46.158818: Epoch   0 Batch  620/781   test_loss = 1.286
2018-12-02T15:09:46.347092: Epoch   0 Batch  640/781   test_loss = 1.254
2018-12-02T15:09:46.515203: Epoch   0 Batch  660/781   test_loss = 1.159
2018-12-02T15:09:46.685761: Epoch   0 Batch  680/781   test_loss = 1.364
2018-12-02T15:09:46.874680: Epoch   0 Batch  700/781   test_loss = 1.130
2018-12-02T15:09:47.022934: Epoch   0 Batch  720/781   test_loss = 1.317
2018-12-02T15:09:47.185816: Epoch   0 Batch  740/781   test_loss = 1.121
2018-12-02T15:09:47.350576: Epoch   0 Batch  760/781   test_loss = 1.143
2018-12-02T15:09:47.523342: Epoch   0 Batch  780/781   test_loss = 1.169
2018-12-02T15:09:50.280822: Epoch   1 Batch   15/3125   train_loss = 1.226
2018-12-02T15:09:50.630653: Epoch   1 Batch   35/3125   train_loss = 1.171
2018-12-02T15:09:50.952671: Epoch   1 Batch   55/3125   train_loss = 1.401
2018-12-02T15:09:51.289542: Epoch   1 Batch   75/3125   train_loss = 1.062
2018-12-02T15:09:51.633478: Epoch   1 Batch   95/3125   train_loss = 0.953
2018-12-02T15:09:51.980294: Epoch   1 Batch  115/3125   train_loss = 1.163
2018-12-02T15:09:52.312598: Epoch   1 Batch  135/3125   train_loss = 1.035
2018-12-02T15:09:52.623288: Epoch   1 Batch  155/3125   train_loss = 1.105
2018-12-02T15:09:52.935451: Epoch   1 Batch  175/3125   train_loss = 1.134
2018-12-02T15:09:53.238495: Epoch   1 Batch  195/3125   train_loss = 1.172
2018-12-02T15:09:53.547494: Epoch   1 Batch  215/3125   train_loss = 1.139
2018-12-02T15:09:53.876052: Epoch   1 Batch  235/3125   train_loss = 1.109
2018-12-02T15:09:54.208161: Epoch   1 Batch  255/3125   train_loss = 1.176

在 TensorBoard 中查看可视化结果

tensorboard --logdir /PATH_TO_CODE/runs/1513402825/summaries/

保存参数

保存save_dir 在生成预测时使用。

save_params((save_dir))

load_dir = load_params()

显示训练Loss

plt.plot(losses['train'], label='Training loss')
plt.legend()
_ = plt.ylim()

png

显示测试Loss

迭代次数再增加一些,下降的趋势会明显一些

plt.plot(losses['test'], label='Test loss')
plt.legend()
_ = plt.ylim()

获取 Tensors

使用函数 get_tensor_by_name()loaded_graph 中获取tensors,后面的推荐功能要用到。

def get_tensors(loaded_graph):

    uid = loaded_graph.get_tensor_by_name("uid:0")
    movie_id = loaded_graph.get_tensor_by_name("movie_id:0")
    movie_categories = loaded_graph.get_tensor_by_name("movie_categories:0")
    movie_titles = loaded_graph.get_tensor_by_name("movie_titles:0")
    targets = loaded_graph.get_tensor_by_name("targets:0")
    dropout_keep_prob = loaded_graph.get_tensor_by_name("dropout_keep_prob:0")
    lr = loaded_graph.get_tensor_by_name("LearningRate:0")
    #两种不同计算预测评分的方案使用不同的name获取tensor inference
#     inference = loaded_graph.get_tensor_by_name("inference/inference/BiasAdd:0")
    inference = loaded_graph.get_tensor_by_name("inference/ExpandDims:0") # 之前是MatMul:0 因为inference代码修改了 这里也要修改 感谢网友 @清歌 指出问题
    movie_combine_layer_flat = loaded_graph.get_tensor_by_name("movie_fc/Reshape:0")
    user_combine_layer_flat = loaded_graph.get_tensor_by_name("user_fc/Reshape:0")
    return uid, movie_id, movie_categories, movie_titles, targets, lr, dropout_keep_prob, inference, movie_combine_layer_flat, user_combine_layer_flat

指定用户和电影进行评分

这部分就是对网络做正向传播,计算得到预测的评分

def rating_movie(user_id_val, movie_id_val):
    loaded_graph = tf.Graph()  #
    with tf.Session(graph=loaded_graph) as sess:  #
        # Load saved model
        loader = tf.train.import_meta_graph(load_dir + '.meta')
        loader.restore(sess, load_dir)
    
        # Get Tensors from loaded model
        uid, movie_id, movie_categories, movie_titles, targets, lr, dropout_keep_prob, inference,_, __ = get_tensors(loaded_graph)  #loaded_graph
    
        categories = np.zeros([1, 18])
        categories[0] = movies.values[movieid2idx[movie_id_val]][2]
    
        titles = np.zeros([1, sentences_size])
        titles[0] = movies.values[movieid2idx[movie_id_val]][1]
    

                    
        feed = {
              uid: np.reshape(users.values[user_id_val-1][0], [1, 1]),
              movie_id: np.reshape(movies.values[movieid2idx[movie_id_val]][0], [1, 1]),
              movie_categories: categories,  #x.take(6,1)
              movie_titles: titles,  #x.take(5,1)
              dropout_keep_prob: 1}
    
        # Get Prediction
        inference_val = sess.run([inference], feed)  
    
        return (inference_val)
rating_movie(234, 1401)
INFO:tensorflow:Restoring parameters from ./save





[array([[2.8522062]], dtype=float32)]

生成Movie特征矩阵

将训练好的电影特征组合成电影特征矩阵并保存到本地

loaded_graph = tf.Graph()  #
movie_matrics = []
with tf.Session(graph=loaded_graph) as sess:  #
    # Load saved model
    loader = tf.train.import_meta_graph(load_dir + '.meta')
    loader.restore(sess, load_dir)

    # Get Tensors from loaded model
    uid, movie_id, movie_categories, movie_titles, targets, lr, dropout_keep_prob, _, movie_combine_layer_flat, __ = get_tensors(loaded_graph)  #loaded_graph

    for item in movies.values:
        categories = np.zeros([1, 18])
        categories[0] = item.take(2)

        titles = np.zeros([1, sentences_size])
        titles[0] = item.take(1)

        feed = {
            movie_id: np.reshape(item.take(0), [1, 1]),
            movie_categories: categories,  #x.take(6,1)
            movie_titles: titles,  #x.take(5,1)
            dropout_keep_prob: 1}

        movie_combine_layer_flat_val = sess.run([movie_combine_layer_flat], feed)  
        movie_matrics.append(movie_combine_layer_flat_val)

pickle.dump((np.array(movie_matrics).reshape(-1, 200)), open('movie_matrics.p', 'wb'))
movie_matrics = pickle.load(open('movie_matrics.p', mode='rb'))
INFO:tensorflow:Restoring parameters from ./save
movie_matrics = pickle.load(open('movie_matrics.p', mode='rb'))

生成User特征矩阵

将训练好的用户特征组合成用户特征矩阵并保存到本地

loaded_graph = tf.Graph()  #
users_matrics = []
with tf.Session(graph=loaded_graph) as sess:  #
    # Load saved model
    loader = tf.train.import_meta_graph(load_dir + '.meta')
    loader.restore(sess, load_dir)

    # Get Tensors from loaded model
    uid, movie_id, movie_categories, movie_titles, targets, lr, dropout_keep_prob, _, __,user_combine_layer_flat = get_tensors(loaded_graph)  #loaded_graph

    for item in users.values:

        feed = {
            uid: np.reshape(item.take(0), [1, 1]),
            dropout_keep_prob: 1}

        user_combine_layer_flat_val = sess.run([user_combine_layer_flat], feed)  
        users_matrics.append(user_combine_layer_flat_val)

pickle.dump((np.array(users_matrics).reshape(-1, 200)), open('users_matrics.p', 'wb'))
users_matrics = pickle.load(open('users_matrics.p', mode='rb'))
INFO:tensorflow:Restoring parameters from ./save
users_matrics = pickle.load(open('users_matrics.p', mode='rb'))

开始推荐电影

使用生产的用户特征矩阵和电影特征矩阵做电影推荐

推荐同类型的电影

思路是计算当前看的电影特征向量与整个电影特征矩阵的余弦相似度,取相似度最大的top_k个,这里加了些随机选择在里面,保证每次的推荐稍稍有些不同。

def recommend_same_type_movie(movie_id_val, top_k = 20):
    
    loaded_graph = tf.Graph()  #
    with tf.Session(graph=loaded_graph) as sess:  #
        # Load saved model
        loader = tf.train.import_meta_graph(load_dir + '.meta')
        loader.restore(sess, load_dir)
        
        norm_movie_matrics = tf.sqrt(tf.reduce_sum(tf.square(movie_matrics), 1, keep_dims=True))
        normalized_movie_matrics = movie_matrics / norm_movie_matrics

        #推荐同类型的电影
        probs_embeddings = (movie_matrics[movieid2idx[movie_id_val]]).reshape([1, 200])
        probs_similarity = tf.matmul(probs_embeddings, tf.transpose(normalized_movie_matrics))
        sim = (probs_similarity.eval())
    #     results = (-sim[0]).argsort()[0:top_k]
    #     print(results)
        
        print("您看的电影是:{}".format(movies_orig[movieid2idx[movie_id_val]]))
        print("以下是给您的推荐:")
        p = np.squeeze(sim)
        p[np.argsort(p)[:-top_k]] = 0
        p = p / np.sum(p)
        results = set()
        while len(results) != 5:
            c = np.random.choice(3883, 1, p=p)[0]
            results.add(c)
        for val in (results):
            print(val)
            print(movies_orig[val])
        
        return results
recommend_same_type_movie(1401, 20)
INFO:tensorflow:Restoring parameters from ./save
WARNING:tensorflow:From <ipython-input-52-af7683641ea5>:9: calling reduce_sum (from tensorflow.python.ops.math_ops) with keep_dims is deprecated and will be removed in a future version.
Instructions for updating:
keep_dims is deprecated, use keepdims instead
您看的电影是:[1401 'Ghosts of Mississippi (1996)' 'Drama']
以下是给您的推荐:
1380
[1401 'Ghosts of Mississippi (1996)' 'Drama']
676
[683 "Eye of Vichy, The (Oeil de Vichy, L') (1993)" 'Documentary']
1604
[1650 'Washington Square (1997)' 'Drama']
3698
[3767 'Missing in Action 2: The Beginning (1985)' 'Action|War']
2491
[2560 'Ravenous (1999)' 'Drama|Horror']





{676, 1380, 1604, 2491, 3698}

推荐您喜欢的电影

思路是使用用户特征向量与电影特征矩阵计算所有电影的评分,取评分最高的top_k个,同样加了些随机选择部分。

def recommend_your_favorite_movie(user_id_val, top_k = 10):

    loaded_graph = tf.Graph()  #
    with tf.Session(graph=loaded_graph) as sess:  #
        # Load saved model
        loader = tf.train.import_meta_graph(load_dir + '.meta')
        loader.restore(sess, load_dir)

        #推荐您喜欢的电影
        probs_embeddings = (users_matrics[user_id_val-1]).reshape([1, 200])

        probs_similarity = tf.matmul(probs_embeddings, tf.transpose(movie_matrics))
        sim = (probs_similarity.eval())
    #     print(sim.shape)
    #     results = (-sim[0]).argsort()[0:top_k]
    #     print(results)
        
    #     sim_norm = probs_norm_similarity.eval()
    #     print((-sim_norm[0]).argsort()[0:top_k])
    
        print("以下是给您的推荐:")
        p = np.squeeze(sim)
        p[np.argsort(p)[:-top_k]] = 0
        p = p / np.sum(p)
        results = set()
        while len(results) != 5:
            c = np.random.choice(3883, 1, p=p)[0]
            results.add(c)
        for val in (results):
            print(val)
            print(movies_orig[val])

        return results
recommend_your_favorite_movie(234, 10)
INFO:tensorflow:Restoring parameters from ./save
以下是给您的推荐:
3047
[3116 'Miss Julie (1999)' 'Drama']
1288
[1308 'I Shot a Man in Vegas (1995)' 'Comedy']
107
[109 'Headless Body in Topless Bar (1995)' 'Comedy']
2865
[2934 'Love Bewitched, A (El Amor Brujo) (1986)' 'Musical']
628
[633 'Denise Calls Up (1995)' 'Comedy']





{107, 628, 1288, 2865, 3047}

看过这个电影的人还看了(喜欢)哪些电影

  • 首先选出喜欢某个电影的top_k个人,得到这几个人的用户特征向量。
  • 然后计算这几个人对所有电影的评分
  • 选择每个人评分最高的电影作为推荐
  • 同样加入了随机选择
import random

def recommend_other_favorite_movie(movie_id_val, top_k = 20):
    loaded_graph = tf.Graph()  #
    with tf.Session(graph=loaded_graph) as sess:  #
        # Load saved model
        loader = tf.train.import_meta_graph(load_dir + '.meta')
        loader.restore(sess, load_dir)

        probs_movie_embeddings = (movie_matrics[movieid2idx[movie_id_val]]).reshape([1, 200])
        probs_user_favorite_similarity = tf.matmul(probs_movie_embeddings, tf.transpose(users_matrics))
        favorite_user_id = np.argsort(probs_user_favorite_similarity.eval())[0][-top_k:]
    #     print(normalized_users_matrics.eval().shape)
    #     print(probs_user_favorite_similarity.eval()[0][favorite_user_id])
    #     print(favorite_user_id.shape)
    
        print("您看的电影是:{}".format(movies_orig[movieid2idx[movie_id_val]]))
        
        print("喜欢看这个电影的人是:{}".format(users_orig[favorite_user_id-1]))
        probs_users_embeddings = (users_matrics[favorite_user_id-1]).reshape([-1, 200])
        probs_similarity = tf.matmul(probs_users_embeddings, tf.transpose(movie_matrics))
        sim = (probs_similarity.eval())
    #     results = (-sim[0]).argsort()[0:top_k]
    #     print(results)
    
    #     print(sim.shape)
    #     print(np.argmax(sim, 1))
        p = np.argmax(sim, 1)
        print("喜欢看这个电影的人还喜欢看:")

        results = set()
        while len(results) != 5:
            c = p[random.randrange(top_k)]
            results.add(c)
        for val in (results):
            print(val)
            print(movies_orig[val])
        
        return results
recommend_other_favorite_movie(1401, 20)
INFO:tensorflow:Restoring parameters from ./save
您看的电影是:[1401 'Ghosts of Mississippi (1996)' 'Drama']
喜欢看这个电影的人是:[[2135 'M' 50 1]
 [371 'M' 18 4]
 [713 'M' 35 7]
 [2921 'M' 50 1]
 [5503 'F' 25 9]
 [282 'M' 25 17]
 [3833 'M' 25 1]
 [566 'M' 25 17]
 [1763 'M' 35 7]
 [581 'M' 50 14]
 [1701 'F' 25 4]
 [3557 'M' 18 5]
 [5102 'M' 25 12]
 [982 'F' 25 9]
 [1672 'M' 25 17]
 [3031 'M' 18 4]
 [883 'F' 35 14]
 [4085 'F' 25 6]
 [2338 'M' 45 17]
 [3901 'M' 18 14]]
喜欢看这个电影的人还喜欢看:

结论

以上就是实现的常用的推荐功能,将网络模型作为回归问题进行训练,得到训练好的用户特征矩阵和电影特征矩阵进行推荐。

扩展阅读

如果你对个性化推荐感兴趣,以下资料建议你看看:

今天的分享就到这里,请多指教!

Comments
Write a Comment
  • 显示训练Loss 图片挂了

  • Timyounfor reply

    你好,请问这个实验结果出得来么(这个你自己实现的么

  • 渣渣剑 reply

    牛逼。厉害。学习了。

  • chengstone reply

    哥们 转载请注明出处 原文地址如下:https://zhuanlan.zhihu.com/p/32078473