- 协同过滤推荐(Collaborative Filtering Recommendation):该算法的核心是分析用户的兴趣和行为,利用共同行为习惯的群体有相似喜好的原则,推荐用户感兴趣的信息。
- 基于用户的协同过滤:找到和用户购买喜好相同的一批人,把相同爱好人群喜好的商品推荐给小王。
- 基于物品的协同过滤:找到物品类似的几个物品,如果用户喜欢其中某个物品,则将和该物品类似的物品推荐给用户。
下面通过一个例子,来理解使用协同过滤来实现物品推荐的简单思路。
1. 数据构建
首先构建一个用户对物品的评分数据,数据中会包含一些 np.nan 表示的缺失值。这些缺失分数,我们可以使用 CF 来进行估计。
import pandas as pd import numpy as np def test(): # 数据表示 5 个用户,对 8 个物品的评分 np.random.seed(6) hisrory = np.random.randint(0, 8, size=[5, 8]).astype(object) # 随机在分数中构造缺失值 size = 4 i = np.random.randint(0, hisrory.shape[0], size=size) j = np.random.randint(0, hisrory.shape[1], size=size) hisrory[i, j] = np.nan # 转换类型 index = ['user' + str(i + 1) for i in range(hisrory.shape[0])] colum = ['item' + str(i + 1) for i in range(hisrory.shape[1])] hisrory = pd.DataFrame(hisrory, index=index, columns=colum) # 数据存储 hisrory.to_csv('data.csv') if __name__ == '__main__': test()
程序生成的数据内容如下:

2. User Based CF 评分估计
我们这里根据用户之间,或者商品之间的相关程度来估计出缺失值。首先计算出用户之间的相关程度。从相关程度最高的所有用户中选择前 2 个用户,结合这两个用户对该物品评分来估计出缺失分数,计算公式如下:

- U 与当前 user 最相似的多个用户,u 表示其中某一个用户;
- sim(u, user) 表示 u 与 user 的相似程度;
- \(r_{u, item}\) 表示 u 用户对 item 的评分
基本的思想就是,我虽然不知道 user1 的 item4 的评分多少,但是与我相似的用户对 item4 有评分,我就结合他们的分数,再使用上面的公式估计出未知的评分。
注意:这里的相关性用的是皮尔逊相关系数。相关系数的实现代码:
import pandas as pd import numpy as np # 物品相似度: 皮尔逊相关系数 def test01(): history = pd.read_csv('data.csv', index_col=0) correlation = history.corr(method="pearson") print(correlation) correlation.to_csv('item.csv') # 用户相似度: 皮尔逊相关系数 def test02(): history = pd.read_csv('data.csv', index_col=0) correlation = history.transpose().corr(method="pearson") print(correlation) correlation.to_csv('user.csv') if __name__ == '__main__': test01() print('-' * 80) test02()
接下来,根据相关系数基于用户 CF 来估计缺失值:
def test01(): data = pd.read_csv('data.csv', index_col=0) print(data) user = pd.read_csv('user.csv', index_col=0) print(user) for i in range(data.shape[0]): user_item = data.iloc[i] user_mask = pd.isna(user_item) # 判断是否存在缺失值 if np.any(user_mask): # 将所有用户根据相关性进行降序排列 sorted_users = user.iloc[i].drop(user_item.name).sort_values(ascending=False)[:2] # 获得相关性最强的用户 close_users = sorted_users.index # 获得对应的相关性值 close_score = sorted_users.values # 从 data 中获得相关性最强用户的对应缺失值列的分数 close_item = data.loc[close_users, user_mask].values # 根据公式计算分子 numerator = np.sum(close_item * close_score[None].transpose(), axis=0) # 计算根据公式分母 denominator = np.sum(close_score) # 计算得到估计的缺失分数 estimate_score = np.round(numerator / denominator, decimals=2) # 将估计分数填充到 data 数据中 data.loc[user_item.name, user_mask] = estimate_score print(data) data.to_csv('user_data.csv')
得到的结果是:

3. Item Based CF 评分估计
我们这里根据物品之间的相关程度来估计出缺失值。首先计算出物品之间的相关程度。从相关程度最高的所有物品中选择前 2 个物品,结合这结合用户对这两个相似物品的评分来估计出未知物品的分数,计算公式如下:

- I 与当前 item 最相似的多个物品,i 表示其中某一个物品;
- sim(i, item) 表示 i 与 item 的相关程度;
- \(r_{user, i}\) 表示用户 user 对物品 i 的评分。
注意:具体在实现时,由于原数据中某一列值全部相同的话,会导致皮尔逊相关系数为 NaN。对于这种情况,我们就使用相同的值来填充缺失值。例如:user5 的 item8 由于该列值都是 2,这里也就用 2 来填充缺失值。
def test02(): data = pd.read_csv('data.csv', index_col=0) print(data) print('-' * 80) item = pd.read_csv('item.csv', index_col=0) print(item) print('-' * 80) for i in range(data.shape[0]): user_item = data.iloc[i] user_mask = pd.isna(user_item) # 判断是否存在缺失值 if np.any(user_mask): # 获得包含缺失值的物品 item_nans = user_item[user_item.isna()].index for item_nan in item_nans: # 获得该物品的所有相似度并排序 similarity = item.loc[item_nan].drop(item_nan).sort_values(ascending=False) # 获得相似的前两个物品 similarity = similarity[:2] # 判断相似度是否全为 NaN if np.all(pd.isna(similarity)): data.loc[user_item.name, item_nan] = data[item_nan].mean() continue # 获得最相似两个物品名称 simi_names = similarity.index # 获得最相似两个物品相似度 simi_value = similarity.values # 获得当前用户对相似产品的评分 simi_score = data.loc[user_item.name, simi_names] # 根据公式计算分子 numerator = np.sum(simi_score * simi_value) # 计算根据公式分母 denominator = np.sum(simi_value) # 估计分数 estimate_score = np.round(numerator / denominator, decimals=2) data.loc[user_item.name, item_nan] = estimate_score print(data) data.to_csv('item_data.csv')
得到的结果是:

4. 协同过滤推荐
协同过滤推荐时,无论是基于用户的协同过滤,还是基于商品的协同过滤都需要计算用户之间,或者商品之间的相似程度,计算方法则有很多,比如:欧式距离、余弦相似度、皮尔逊相关系数、杰卡德距离等等。
我们这里使用皮尔逊相关系数。