推荐系统
歌曲序列建模
NLP场景
我是中国人 => 我 是 中国 人
我 => [1,0,0,0] 1500
是 => [0,1,0,0] 1500
…
1*4 vector one-hot encoding
word => vector
distance similarity
从word2vec到song2vec
我们把歌曲的id序列取出来,类比于分完词后的句子,送到word2vec中去学习一下
#coding: utf-8
import multiprocessing
import gensim
import sys
from random import shuffle
def parse_playlist_get_sequence(in_line, playlist_sequence):
song_sequence = []
contents = in_line.strip().split("\t")
# 解析歌单序列
for song in contents[1:]:
try:
song_id, song_name, artist, popularity = song.split(":::")
song_sequence.append(song_id)
except:
print "song format error"
print song+"\n"
for i in range(len(song_sequence)):
shuffle(song_sequence)
playlist_sequence.append(song_sequence)
def train_song2vec(in_file, out_file):
#所有歌单序列
playlist_sequence = []
#遍历所有歌单
for line in open(in_file):
parse_playlist_get_sequence(line, playlist_sequence)
#使用word2vec训练
cores = multiprocessing.cpu_count()
print "using all "+str(cores)+" cores"
print "Training word2vec model..."
model = gensim.models.Word2Vec(sentences=playlist_sequence, size=150, min_count=3, window=7, workers=cores)
print "Saving model..."
model.save(out_file)
song_sequence_file = "./popular.playlist"
model_file = "./song2vec.model"
train_song2vec(song_sequence_file, model_file)
进一步思考
所以我们用word2vec学会了哪些歌曲和哪些歌曲最接近。
我们来思考一些很现实同时又很难解决的问题。比如:
冷启动问题
我们经常会遇到冷启动的问题,比如没有任何信息的歌曲,我们如何对它做推荐呢?
如果是歌手发行的新歌曲,我们怎么进行推荐呢?
如果我听完(并收藏)了一首很冷门的歌,怎么进行推荐呢?
我们知道新歌(或者小众的歌)是非常难和其他的歌关联上的,我们有的信息太少了(很少有用户在它上面发生行为)。
1.1 一种解决办法当然是推荐热门的歌曲,但是其实没从个人兴趣出发,我们知道这并不是最好的办法,并没有太大的卵用。
1.2 我们把问题的粒度放粗一点,用同样的思路,比如一个可考虑的解决方案是,我们把歌曲的粒度上升到对应的歌手,把刚才的song_list替换成artist_list,重新用word2vec建模,这样我们可以得到和一个歌手最相关(接近)的歌手,再推荐这个歌手最热门的歌曲,相对1.1的方法针对性强一些。
商品 => 品类
品类list => 送到word2vec里面去学习
[上衣,上衣,上衣,牛仔裤,牛仔裤,连衣裙…]
用户兴趣预测问题
我们刚才完成的功能,类似酷狗音乐和网易音乐里针对一首歌的“相似音乐”,那么问题又来了,如果我们现在要对一个user用这套song2vec的方式推荐,我们怎么做呢?
每个人的兴趣都是有时效性的,这意味着说,3年前我喜欢王菲的歌,去年我喜欢五月天的歌,而今年我可能就改摇滚路线,喜欢汪峰的歌了。
每一首歌的热度也是不一样的,有一些热门的歌,如果用户能喜欢,当然是首选
那么,我们来做一个粗暴一点点的处理,把这2个维度拉进来,一起来针对一个用户做推荐。
**把每个用户喜欢(收藏)过的歌,沿着时间轴排好,同时由近到远给不同的衰减因子(比如最近一首歌是1,前一首是0.98,再前一首是0.98^2,以此类推…),同时我们针对不同的歌曲热度,给定不同的推荐因子(比如热度100的是1,热度80的是0.9…),每一首歌都可以拿回一个song2vec的推荐列表和对应的相似度,对相似度以时间衰减因子和热度权重进行加权,最后的结果排序后,展示给用户。
矩阵分解
LFM:把用户在item上打分的行为,看作是有内部依据的,认为和k个factor有关系
每一个user i会有一个用户的向量(k维),每一个item会有一个item的向量(k维)
SVD是矩阵分解的一种方式
推荐系统学习(TensorFlow)
获取数据
以movielens为例,数据格式为user item rating timestamp
数据处理部分
tensorflow搭建的模型,训练方式通常是一个batch一个batch训练的
from __future__ import absolute_import, division, print_function
import numpy as np
import pandas as pd
def read_data_and_process(filname, sep="\t"):
col_names = ["user", "item", "rate", "st"]
df = pd.read_csv(filname, sep=sep, header=None, names=col_names, engine='python')
df["user"] -= 1
df["item"] -= 1
for col in ("user", "item"):
df[col] = df[col].astype(np.int32)
df["rate"] = df["rate"].astype(np.float32)
return df
class ShuffleDataIterator(object):
"""
随机生成一个batch一个batch数据
"""
#初始化
def __init__(self, inputs, batch_size=10):
self.inputs = inputs
self.batch_size = batch_size
self.num_cols = len(self.inputs)
self.len = len(self.inputs[0])
self.inputs = np.transpose(np.vstack([np.array(self.inputs[i]) for i in range(self.num_cols)]))
#总样本量
def __len__(self):
return self.len
def __iter__(self):
return self
#取出下一个batch
def __next__(self):
return self.next()
#随机生成batch_size个下标,取出对应的样本
def next(self):
ids = np.random.randint(0, self.len, (self.batch_size,))
out = self.inputs[ids, :]
return [out[:, i] for i in range(self.num_cols)]
class OneEpochDataIterator(ShuffleDataIterator):
"""
顺序产出一个epoch的数据,在测试中可能会用到
"""
def __init__(self, inputs, batch_size=10):
super(OneEpochDataIterator, self).__init__(inputs, batch_size=batch_size)
if batch_size > 0:
self.idx_group = np.array_split(np.arange(self.len), np.ceil(self.len / batch_size))
else:
self.idx_group = [np.arange(self.len)]
self.group_id = 0
def next(self):
if self.group_id >= len(self.idx_group):
self.group_id = 0
raise StopIteration
out = self.inputs[self.idx_group[self.group_id], :]
self.group_id += 1
return [out[:, i] for i in range(self.num_cols)]
模型搭建
import tensorflow as tf
# 使用矩阵分解搭建的网络结构
def inference_svd(user_batch, item_batch, user_num, item_num, dim=5, device="/cpu:0"):
#使用CPU
with tf.device("/cpu:0"):
# 初始化几个bias项
global_bias = tf.get_variable("global_bias", shape=[])
w_bias_user = tf.get_variable("embd_bias_user", shape=[user_num])
w_bias_item = tf.get_variable("embd_bias_item", shape=[item_num])
# bias向量
bias_user = tf.nn.embedding_lookup(w_bias_user, user_batch, name="bias_user")
bias_item = tf.nn.embedding_lookup(w_bias_item, item_batch, name="bias_item")
w_user = tf.get_variable("embd_user", shape=[user_num, dim],
initializer=tf.truncated_normal_initializer(stddev=0.02))
w_item = tf.get_variable("embd_item", shape=[item_num, dim],
initializer=tf.truncated_normal_initializer(stddev=0.02))
# user向量与item向量
embd_user = tf.nn.embedding_lookup(w_user, user_batch, name="embedding_user")
embd_item = tf.nn.embedding_lookup(w_item, item_batch, name="embedding_item")
with tf.device(device):
# 按照实际公式进行计算
# 先对user向量和item向量求内积
infer = tf.reduce_sum(tf.multiply