![6eca8007e105a894f38f9dfd7ff4f1a8.png](https://i-blog.csdnimg.cn/blog_migrate/e5be37abb0e9fa9ce0f70e1d4acff3e0.jpeg)
前面两篇文章回顾了我们团队做天池数据比赛《CIKM 2019 EComm AI:用户行为预测》的复赛方案,现在让我们的目光回到初赛。初赛数据组织和复赛一样,只是数据量差异较大。(初赛才2G数据,复赛28G啊。。。)
![25235dc36ee67780c8e3115d1c2360d3.png](https://i-blog.csdnimg.cn/blog_migrate/6305766f8ed95729cec67abfcca9d2f5.jpeg)
所以,初赛尝试的方案更多,这次就先说明基于矩阵分解的方案。
矩阵分解为什么能做推荐系统呢,昨天机器学习爱好者群里还有同学问过这个问题,这里借用中南大学卢博士的回答:
矩阵分解问题是通过少数的矩阵观测值,恢复矩阵中的其他未知元素。而这个问题是无法直接求解的,因此提出了低秩假设,即对原问题做了松弛近似-----人对电影的评分是由少数的用户特征和少数的电影特征决定的。原问题转换为低秩近似问题,而低秩近似问题是采用交替最小二乘(ALS)来求解的。
所以,采用ALS的方法,对初赛的用户-物品交互数据集做了相关分析,并得到推荐列表。关于ALS算法,网上有太多的资料可以学习,这里就不做赘述了,我们采用的是python中implicit库实现的ALS算法,可以跑在GPU上。(注意:只是训练可以,预测就不行,很难受)
from implicit.als import AlternatingLeastSquares
from implicit.approximate_als import FaissAlternatingLeastSquares
import pandas as pd
from tqdm import tqdm, trange
from scipy.sparse import coo_matrix, csr_matrix
import numpy as np
import deepdish as dd
import pickle
import os
from dask import dataframe
import pandas as pd
import gc
os.environ["MKL_NUM_THREADS"] = "1"
raw_data = pd.read_csv("/data/ECommAI/round2_a/3Tuple/raw_data.csv", names=["user_index", "item_index", "ratings"])
user_id = raw_data["user_index"].values
item_id = raw_data["item_index"].values
ratings = raw_data["ratings"].values
首先是加载数据并读取文件,交互数据的格式是这样的:
![4fc0f85a3ebdf730929ed0bb1a9ed3cd.png](https://i-blog.csdnimg.cn/blog_migrate/538929ddccce3c031db609980c125db9.png)
实际上会发现,这个数据格式和movielens的数据没什么区别,只是行为描述不再是评分,所以我们将行为字符串转换成评分,这里直接做的一一映射,如:
behavior_map = {"clk": 1.0 ,"collect":2.0 ,"cart" : 3.0 ,"buy" : 4.0}
这个操作实际上就是对一个列进行map操作,词典转换而已。(实际上对用户id和物品id也要做map_id的操作,毕竟要把数据放到矩阵里)
接着,我们将数据放入稀疏压缩矩阵csr_matrix中,然后调用ALS的模型训练函数fit,存储模型生成结果user和item的隐向量。
item_user_data = csr_matrix((ratings, (item_id, user_id)), shape=(max(item_id) + 1, max(user_id) + 1))
print("Done")
# initialize a model
model = AlternatingLeastSquares(factors=64, use_gpu=True, iterations=100, calculate_training_loss=True)
# train model
model.fit(item_user_data)
dd.io.save("/data/ECommAI/round1_b/user_factors.h5", model.user_factors, compression=False)
dd.io.save("/data/ECommAI/round1_b/item_factors.h5", model.item_factors, compression=False)
由于预测部分实在是太慢了,我们就把隐向量拿出来自己组织逻辑,完成推荐结果输出。
import deepdish as dd
import tensorflow as tf
import numpy as np
from tqdm import trange
tf.enable_eager_execution()
user_factors = dd.io.load("/data/ECommAI/round1_b/user_factors.h5")
item_factors = dd.io.load("/data/ECommAI/round1_b/item_factors.h5")
首先加载模型训练好的用户隐向量和物品隐向量。
# recommend_result = None
def make_recommendation(index, scale):
item_tensor = tf.convert_to_tensor(item_factors)
user_tensor = tf.convert_to_tensor(user_factors[index:index + scale, :])
idx0 = tf.shape(user_tensor)[0]
if idx0 != 0:
recommendation = tf.tensordot(user_tensor, item_tensor, axes=[1, 1])
recommendation_k = tf.math.top_k(recommendation, k=50)
return recommendation_k[1]
else:
return None
由于矩阵分解的预测阶段实际就是向量内积操作,通过tensorflow框架是可以并行化处理的,所以我们次啊用基于tensor的向量内积批量计算结果,并存储在list内。
scale = 200
recommend_result = make_recommendation(0, scale).numpy()
user_count = user_factors.shape[0]
for i in trange(1, user_count // scale + 1):
recommend_result = np.concatenate((recommend_result, make_recommendation(i * scale, scale).numpy()), axis=0)
dd.io.save("/data/ECommAI/round1_b/ALS_recommendations.h5", recommend_result, compression=None)
最终,我们对每200个用户进行批量内积计算结果,即用户
这个方案的线上评分在0.016左右,baseline的基于物品热度召回都有0.04,还是存在不少差距的。
这里有篇关于implicit库的文章可以参考:
使用python实战开发基于隐式反馈(点击,点赞,浏览,收藏,评论)的协同过滤的推荐系统(implicit库)blog.csdn.net