推荐系统(1)—协同过滤基本思想及其实现
前言:由于近期项目上在开发一个销售管理系统,里面涉及到一个基于用户的产品给推荐算法,之前也对推荐系统有比较系统地了解,因此本文及接下来的几篇文章将详细推荐系统的思想及其多中实现方法,本篇将主要介绍基于系统过滤的推荐系统及其Python实现。
1、协同过滤思想
协同过滤(collabrotive filtering)是商品销售(尤其是网店)常用的推荐方法,分为基于用户(user-based)和基于商品(item-based)两种情况,整体思想是在已经有销售记录的数据库中,从用户购买的历史商品数据中找到用户或产品的相似度,然后对用户做推荐。如果选择基于用户的系统过滤,则找到用户的相似度,对一个特定的用户,可以选择将他最相似的K个用户所购买的且该用户未购买的N中商品推荐给他;若是基于商品的推荐,则找到商品之间的相似度,然后对购买了该商品的用户,推荐其未购买的相似商品中的其他商品,协同过滤的主要思想是聚类(无论是基于用户还是基于商品)。
2、相似度度量方法
上面提到要计算用户/商品之间的相似度,那么怎么度量两个用户/商品之间的相似度呢?在转载的文章《漫谈:机器学习中距离和相似性度量方法》中,谈到了多种度量距离和相似性的方法,在机器学习中,距离和相似性是比较相关的概念,一般来说距离越大,相似性与低,反之相似性越高。在推荐系统的相似度度量中,常常用到以下三种相似度度量:
1)闵可夫斯基距离(Minkowski/欧式距离):$$distance(X,Y)=\left ( \sum {i=1}^{n}\left | x-y_{i}^{} \right | ^{p}\right ){{p}}}$$
2)杰卡德相似系数(Jaccard):$$J(A,B)=\frac{\left | A\bigcap B \right |}{\left | A\bigcup B \right |}$$
3)余弦相似度:$$\cos \left ( \theta \right )=\frac{a^{T}b}{\left | a \right |\left | b \right |}$$
4)Pearson相关系数:$$\rho_{XY}=\frac{cov\left ( X,Y \right )}{\rho _{X}\rho {Y}}$$
5)相对熵(K-L距离):$$D\left ( p||q \right )=\sum {x}p\left ( x \right )\log \frac{p\left ( x \right )}{q\left ( x \right )}=E\log \frac{p\left ( x \right )}{q\left ( x \right )}$$
6)Hellinger距离:$$D\left ( p||q \right )=\frac{2}{1-\alpha ^{2}}\left ( 1-\int p\left ( x \right )^{\frac{1+\alpha }{2}}q\left ( x \right )^{\frac{1-\alpha }{2}}d \right )$$
3、几种相似度的python实现
上面列举了六种相似度度量(并未列举完所有的相似度度量),各种适合的场景不太一致,但是这些相似度之间又有一定意义上的相关性(如果敢兴趣可以推导一下),在这六种相似度中,闵可夫斯基距离、杰卡德相似度、余弦相似度用的频率较高,下面是这几种相似度的简单实现:
import numpy as np
def euclidea_sim(x,y):
assert len(x) == len(y)
dis = np.linalg.norm(x-y)
sim = 1/(1+dis)
return sim
def jaccard_sim(x,y):
assert len(x) == len(y)
x,y = np.array(x).astype(bool),np,array(y).astype(bool)
return sum(x*y)/sum(x+y)
def cosine_sim(x,y):
assert len(x) == len(y)
sum_x_y = np.dot(x,y)
return sum_x_y/np.linalg.norm(x)/np.linalg.norm(y)
# 以上用python简单地实现了三种相似度,供下面提供推荐使用
上面用python实现了常用的三种相似度度量方法。注意,这里的余弦相似度的取值范围为[-1,1],可以通过简单变化将各种相似度都转换在[0,1]之间(比如余弦相似度乘以0.5+0.5处理),这里不做过多处理。下面将从销售中的记录数据(如下表),完整地实现一个简单的推荐系统(之所以说简单,是因为没有过多地考虑业务场景,而且仅仅用最基本的系统过滤方法)。
客户ID | 购买日期 | 产品类别 | 产品ID | 购买数量 |
客户46 | 2017/2/15 | 类别18 | 产品17 | 3 |
客户118 | 2017/4/7 | 类别15 | 产品7 | 1 |
客户9 | 2017/4/12 | 类别17 | 产品1 | 15 |
客户74 | 2016/11/27 | 类别28 | 产品13 | 17 |
客户248 | 2017/4/24 | 类别19 | 产品15 | 1 |
客户271 | 2017/4/25 | 类别6 | 产品20 | 8 |
客户220 | 2017/4/1 | 类别20 | 产品10 | 5 |
客户94 | 2017/3/23 | 类别32 | 产品11 | 14 |
import numpy as np
import pandas as pd
#pandas库清理数据特别方便,因此本次用到该库
from pandas import DataFrame
# 加载数据,数据放在本地磁盘
self_file = "F:\\python_jupyter\\sell_record.csv"
sell_record = pd.read_csv(self_file, sep=',',header=0,encoding='gbk')
# 客户可能多次购买同一种产品,因此把客户对产品汇总
sell_pivot = sell_record.pivot_table(values='购买数量',index='客户ID',columns='产品ID',aggfunc=sum,fill_value=0)
#先计算相似度矩阵
def sim_mat(sell_group,sim=euclidea_sim):
# 定义一个全零相似度矩阵
#
sim_matrix = np.zeros((sell_group.shape[0],sell_group.shape[0]),dtype=float)
sim_matrix = DataFrame(sim_matrix,index=sell_group.index,columns=sell_group.index)
for index in sell_group.index:
for column in sell_group.index:
sim_matrix.ix[index,column] = sim(sell_group.ix[index,:],sell_group.ix[column,:])
return sim_matrix
def recommendation(sim_mat,customer,n_sim_customer,n_product,sell_record):
'''
paramer:
sim_mat:相似度矩阵
customer:需要推荐的客户
n_sim_customer:相似客户数
n_product:推荐的产品数
sell_record:客户购买的产品列表,每条数据为一个行,每列数据为一个产品统计的客户产品购买量
'''
try:
k_similar = sim_mat.sort_values(customer,axis=0,ascending=False)[:n_sim_customer]
except:
print('该用户还未购买过我公司产品,可为他推荐热门产品')
return None
# 找到k个相似用户购买的所有产品
recom_product = sell_record.ix[k_similar.index,:].astype(bool).sum(axis=0)
recom_product = recom_product[recom_product>0].sort_values(axis=0,ascending=False).index
count_ = 0
recom_list = []
# while count_ < n_product:
for i in recom_product:
if sell_record[i][customer] > 0:
continue
else:
recom_list.append(i)
count_ += 1
if count_ >= n_product:
return recom_list
return recom_list
# sim_mat[customer].
上面代码就是简单的基于客户(user-based)推荐系统简单实现,可以对上面代码进行简单地修改就能直接应用于基于商品(item-based)的推荐。该代码未考虑具体业务场景(购买产品度量、相似度选择、生成推荐商品候选列表对各个商品与该用户的相似度乘积求和)及执行效率,具体应用时可以对这些方面加以改进。下面是对客户10做推荐
sim = sim_mat(sell_pivot)
recommendation(sim,"客户10",20,5,sell_pivot)
['产品11', '产品9', '产品3', '产品4', '产品16']