15 机器学习 - CF协同过滤推荐(电影推荐案例)

简介: 15 机器学习 - CF协同过滤推荐(电影推荐案例)

1. 需求

根据一个用户对电影评分的数据集来实现基于用户相似度的协同过滤算法推荐,相似度的算法采用皮尔逊相关系数

数据样例如下:

用户ID:电影ID:评分:时间
1::1193::5::978300760
1::661::3::978302109
1::914::3::978301968
1::3408::4::978300275
1::2355::5::978824291
1::1197::3::978302268
1::1287::5::978302039
1::2804::5::978300719
1::594::4::978302268
1::919::4::978301368

2. 算法实现

本案例使用的数据分析包为pandas,Numpy和matplotlib

2.1 数据规整

首先将评分数据从ratings.dat中读出到一个DataFrame 里:

>>> import pandas as pd
>>> from pandas import Series,DataFrame
>>> rnames = ['user_id','movie_id','rating','timestamp']
>>> ratings = pd.read_table(r'ratings.dat',sep='::',header=None,names=rnames)
>>> ratings[:3]
   user_id  movie_id  rating  timestamp
0        1      1193       5  978300760
1        1       661       3  978302109
2        1       914       3  978301968
[3 rows x 4 columns]

ratings 表中对我们有用的仅是 user_id、movie_id 和 rating 这三列,因此我们将这三列取出,放到一个以 user 为行,movie 为列,rating 为值的表 data 里面。

>>> data = ratings.pivot(index='user_id',columns='movie_id',values='rating')  #形成一个透视表
>>> data[:5]

可以看到这个表相当得稀疏,填充率大约只有 5%,接下来要实现推荐的第一步是计算 user 之间的相关系数

2.2 相关度测算

DataFrame对象有一个很亲切的方法:

corr(method='pearson', min_periods=1) 方法,可以对所有列互相计算相关系数。

其中:

  • method默认为皮尔逊相关系数,
  • min_periods参数,这个参数的作用是设定计算相关系数时的最小样本量,低于此值的一对列将不进行运算。这个值的取舍关系到相关系数计算的准确性,因此有必要先来确定一下这个参数。

2.3 min_periods 参数测定

测定这样一个参数的基本方法:

  • 统计在 min_periods 取不同值时,相关系数的标准差大小,越小越好;
    但同时又要考虑到,我们的样本空间十分稀疏,min_periods 定得太高会导致出来的结果集太小,所以只能选定一个折中的值。

这里我们测定评分系统标准差的方法为:

  • 在 data中挑选一对重叠评分最多的用户,用他们之间的相关系数的标准差去对整体标准差做点估计。

在此前提下对这一对用户在不同样本量下的相关系数进行统计,观察其标准差变化。

首先,要找出重叠评分最多的一对用户。我们新建一个以 user 为行列的方阵 foo,然后挨个填充不同用户间重叠评分的个数:

>>> foo = DataFrame(np.empty((len(data.index),len(data.index)),dtype=int),index=data.index,columns=data.index)
#print(empt.shape): (6040, 6040)
>>> for i in foo.index:
        for j in foo.columns:
            foo.ix[i,j] = data.ix[i][data.ix[j].notnull()].dropna().count()

这段代码特别费时间,因为最后一行语句要执行 4000*4000 = 1600万遍;

找到的最大值所对应的行列分别为 424 和 4169,这两位用户之间的重叠评分数为 998:

>>> for i in foo.index:
        foo.ix[i,i]=0   #先把对角线的值设为 0
>>> ser = Series(np.zeros(len(foo.index)))
>>> for i in foo.index:
        ser[i]=foo[i].max()   #计算每行中的最大值
>>> ser.idxmax()   #返回ser的最大值所在的行号
4169
>>> ser[4169]    #取得最大值
998
>>> foo[foo==998][4169].dropna()   #取得另一个 user_id
424     4169
Name: user_id, dtype: float64

把 424 和 4169 的评分数据单独拿出来,放到一个名为 test 的表里,另外计算了一下这两个用户之间的相关系数为 0.456,还算不错,另外通过柱状图了解一下他俩的评分分布情况:

>>> data.ix[4169].corr(data.ix[424])
0.45663851303413217
>>> test = data.reindex([424,4169],columns=data.ix[4169][data.ix[424].notnull()].dropna().index)
>>> test
movie_id  2   6   10  11  12  17 ...
424        4   4   4   4   1   5 ...
4169       3   4   4   4   2   5 ...
>>> test.ix[424].value_counts(sort=False).plot(kind='bar')
>>> test.ix[4169].value_counts(sort=False).plot(kind='bar')

对这俩用户的相关系数统计,我们分别随机抽取 20、50、100、200、500 和 998 个样本值,各抽 20 次。并统计结果:

>>> periods_test = DataFrame(np.zeros((20,7)),columns=[10,20,50,100,200,500,998])
>>> for i in periods_test.index:
        for j in periods_test.columns:
            sample = test.reindex(columns=np.random.permutation(test.columns)[:j])
            periods_test.ix[i,j] = sample.iloc[0].corr(sample.iloc[1])
>>> periods_test[:5]
        10        20        50        100       200       500       998
0 -0.306719  0.709073  0.504374  0.376921  0.477140  0.426938  0.456639
1  0.386658  0.607569  0.434761  0.471930  0.437222  0.430765  0.456639
2  0.507415  0.585808  0.440619  0.634782  0.490574  0.436799  0.456639
3  0.628112  0.628281  0.452331  0.380073  0.472045  0.444222  0.456639
4  0.792533  0.641503  0.444989  0.499253  0.426420  0.441292  0.456639
[5 rows x 7 columns]
>>> periods_test.describe()
             10         20         50         100        200        500  #998略
count  20.000000  20.000000  20.000000  20.000000  20.000000  20.000000  
mean    0.346810   0.464726   0.458866   0.450155   0.467559   0.452448  
std     0.398553   0.181743   0.103820   0.093663   0.036439   0.029758  
min    -0.444302   0.087370   0.192391   0.242112   0.412291   0.399875  
25%     0.174531   0.320941   0.434744   0.375643   0.439228   0.435290  
50%     0.487157   0.525217   0.476653   0.468850   0.472562   0.443772  
75%     0.638685   0.616643   0.519827   0.500825   0.487389   0.465787  
max     0.850963   0.709073   0.592040   0.634782   0.546001   0.513486  
[8 rows x 7 columns]

从 std 这一行来看,理想的 min_periods 参数值应当为 200 左右(标准差和均值、极值最接近)。

2.4 算法检验

为了确认在 min_periods=200 下本推荐算法的靠谱程度,最好还是先做个检验。

具体方法为:在评价数大于 200 的用户中随机抽取 1000 位用户,每人随机提取一个评价另存到一个数组里,并在数据表中删除这个评价。然后基于阉割过的数据表计算被提取出的 1000 个评分的期望值,最后与真实评价数组进行相关性比较,看结果如何。

>>> check_size = 1000
>>> check = {}
>>> check_data = data.copy()   #复制一份 data 用于检验,以免篡改原数据
>>> check_data = check_data.ix[check_data.count(axis=1)>200]  #滤除评价数小于200的用户
>>> for user in np.random.permutation(check_data.index):
        movie = np.random.permutation(check_data.ix[user].dropna().index)[0]
        check[(user,movie)] = check_data.ix[user,movie]
        check_data.ix[user,movie] = np.nan
        check_size -= 1
        if not check_size:
            break
>>> corr = check_data.T.corr(min_periods=200)
>>> corr_clean = corr.dropna(how='all')
>>> corr_clean = corr_clean.dropna(axis=1,how='all')  #删除全空的行和列
>>> check_ser = Series(check)   #这里是被提取出来的 1000 个真实评分
>>> check_ser[:5]
(15, 593)     4
(23, 555)     3
(33, 3363)    4
(36, 2355)    5
(53, 3605)    4
dtype: float64

接下来要基于corr_clean 给 check_ser 中的 1000 个用户-影片对计算评分期望。

计算方法为:对与用户相关系数大于 0.1 的其他用户评分进行加权平均,权值为相关系数

>>> result = Series(np.nan,index=check_ser.index)
>>> for user,movie in result.index:   #这个循环看着很乱,实际内容就是加权平均而已
        prediction = []
        if user in corr_clean.index:
            corr_set = corr_clean[user][corr_clean[user]>0.1].dropna()   #仅限大于 0.1 的用户
        else:continue
        for other in corr_set.index:
            if  not np.isnan(data.ix[other,movie]) and other != user:#注意bool(np.nan)==True
                prediction.append((data.ix[other,movie],corr_set[other]))
        if prediction:
            result[(user,movie)] = sum([value*weight for value,weight in prediction])/sum([pair[1] for pair in prediction])
>>> result.dropna(inplace=True)
>>> len(result)#随机抽取的 1000 个用户中也有被 min_periods=200 刷掉的
862
>>> result[:5]
(23, 555)     3.967617
(33, 3363)    4.073205
(36, 2355)    3.903497
(53, 3605)    2.948003
(62, 1488)    2.606582
dtype: float64
>>> result.corr(check_ser.reindex(result.index))
0.436227437429696
>>> (result-check_ser.reindex(result.index)).abs().describe()#推荐期望与实际评价之差的绝对值
count    862.000000
mean       0.785337
std        0.605865
min        0.000000
25%        0.290384
50%        0.686033
75%        1.132256
max        3.629720
dtype: float64

862 的样本量能达到 0.436 的相关系数,应该说结果还不错。如果一开始没有滤掉评价数小于 200 的用户的话,那么首先在计算 corr 时会明显感觉时间变长,其次 result 中的样本量会很小,大约 200+个。但因为样本量变小的缘故,相关系数可以提升到 0.5~0.6 。

另外从期望与实际评价的差的绝对值的统计量上看,数据也比较理想。

2.5 实现推荐

在上面的检验,尤其是平均加权的部分做完后,推荐的实现就没有什么新东西了。

首先在原始未阉割的 data 数据上重做一份 corr 表:

>>> corr = data.T.corr(min_periods=200)
>>> corr_clean = corr.dropna(how='all')
>>> corr_clean = corr_clean.dropna(axis=1,how='all')

我们在 corr_clean 中随机挑选一位用户为他做一个推荐列表:

>>> lucky = np.random.permutation(corr_clean.index)[0]
>>> gift = data.ix[lucky]
>>> gift = gift[gift.isnull()]    #现在 gift 是一个全空的序列

最后的任务就是填充这个 gift:

>>> corr_lucky = corr_clean[lucky].drop(lucky)#lucky 与其他用户的相关系数 Series,不包含 lucky 自身
>>> corr_lucky = corr_lucky[corr_lucky>0.1].dropna()   #筛选相关系数大于 0.1 的用户
>>> for movie in gift.index:   #遍历所有lucky没看过的电影
        prediction = []
        for other in corr_lucky.index:  #遍历所有与lucky 相关系数大于 0.1 的用户
            if not np.isnan(data.ix[other,movie]):
                prediction.append((data.ix[other,movie],corr_clean[lucky][other]))
        if prediction:
            gift[movie] = sum([value*weight for value,weight in prediction])/sum([pair[1] for pair in prediction])
>>> gift.dropna().order(ascending=False)   #将 gift 的非空元素按降序排列
movie_id
3245        5.000000
2930        5.000000
2830        5.000000
2569        5.000000
1795        5.000000
981         5.000000
696         5.000000
682         5.000000
666         5.000000
572         5.000000
1420        5.000000
3338        4.845331
669         4.660464
214         4.655798
3410        4.624088
...
2833        1
2777        1
2039        1
1773        1
1720        1
1692        1
1538        1
1430        1
1311        1
1164        1
843         1
660         1
634         1
591         1
56          1
Name: 3945, Length: 2991, dtype: float64
目录
相关文章
|
12天前
|
机器学习/深度学习 传感器 运维
使用机器学习技术进行时间序列缺失数据填充:基础方法与入门案例
本文探讨了时间序列分析中数据缺失的问题,并通过实际案例展示了如何利用机器学习技术进行缺失值补充。文章构建了一个模拟的能源生产数据集,采用线性回归和决策树回归两种方法进行缺失值补充,并从统计特征、自相关性、趋势和季节性等多个维度进行了详细评估。结果显示,决策树方法在处理复杂非线性模式和保持数据局部特征方面表现更佳,而线性回归方法则适用于简单的线性趋势数据。文章最后总结了两种方法的优劣,并给出了实际应用建议。
42 7
使用机器学习技术进行时间序列缺失数据填充:基础方法与入门案例
|
1月前
|
机器学习/深度学习 数据采集 搜索推荐
利用Python和机器学习构建电影推荐系统
利用Python和机器学习构建电影推荐系统
87 1
|
6月前
|
机器学习/深度学习 算法 数据挖掘
机器学习之sklearn基础——一个小案例,sklearn初体验
机器学习之sklearn基础——一个小案例,sklearn初体验
159 6
|
6月前
|
机器学习/深度学习 算法
【阿旭机器学习实战】【30】二手车价格预估--KNN回归案例
【阿旭机器学习实战】【30】二手车价格预估--KNN回归案例
|
7月前
|
Java Python 开发者
Python 学习之路 01基础入门---【Python安装,Python程序基本组成】
线程池详解与异步任务编排使用案例-xian-cheng-chi-xiang-jie-yu-yi-bu-ren-wu-bian-pai-shi-yong-an-li
512 3
Python 学习之路 01基础入门---【Python安装,Python程序基本组成】
|
4月前
|
机器学习/深度学习 人工智能 数据处理
【人工智能】项目实践与案例分析:利用机器学习探测外太空中的系外行星
探测外太空中的系外行星是天文学和天体物理学的重要研究领域。随着望远镜观测技术的进步和大数据的积累,科学家们已经能够观测到大量恒星的光度变化,并尝试从中识别出由行星凌日(行星经过恒星前方时遮挡部分光线)引起的微小亮度变化。然而,由于数据量巨大且信号微弱,传统方法难以高效准确地识别所有行星信号。因此,本项目旨在利用机器学习技术,特别是深度学习,从海量的天文观测数据中自动识别和分类系外行星的信号。这要求设计一套高效的数据处理流程、构建适合的机器学习模型,并实现自动化的预测和验证系统。
90 1
【人工智能】项目实践与案例分析:利用机器学习探测外太空中的系外行星
|
3月前
|
机器学习/深度学习 人工智能 算法
利用机器学习预测股市趋势:一个实战案例
【9月更文挑战第5天】在这篇文章中,我们将探索如何使用机器学习技术来预测股市趋势。我们将通过一个简单的Python代码示例来演示如何实现这一目标。请注意,这只是一个入门级的示例,实际应用中可能需要更复杂的模型和更多的数据。
|
7月前
|
Python
Python学习之路 02 之分支结构
Python学习之路 02 之分支结构
498 0
Python学习之路 02 之分支结构
|
7月前
|
Java
线程池详解与异步任务编排使用案例-xian-cheng-chi-xiang-jie-yu-yi-bu-ren-wu-bian-pai-shi-yong-an-li
线程池详解与异步任务编排使用案例-xian-cheng-chi-xiang-jie-yu-yi-bu-ren-wu-bian-pai-shi-yong-an-li
93 0
|
4月前
|
机器学习/深度学习 存储 分布式计算
Hadoop与机器学习的融合:案例研究
【8月更文第28天】随着大数据技术的发展,Hadoop已经成为处理大规模数据集的重要工具。同时,机器学习作为一种数据分析方法,在各个领域都有着广泛的应用。本文将介绍如何利用Hadoop处理大规模数据集,并结合机器学习算法来挖掘有价值的信息。我们将通过一个具体的案例研究——基于用户行为数据预测用户留存率——来展开讨论。
333 0

热门文章

最新文章