根据用户和创作者数据设计推荐系统
离线实验步骤
这次的推荐算法和对其的评测主要依靠离线实验,所以首先回顾下:
读取数据集内容,并划分为训练集和测试集
声明
这次任务的数据集是QQ音乐部分用户的下载记录。dat文件中每一行包括用户ID和下载歌曲所对应的艺术家(一个用户ID在多行中出现)。我选择将测试集和训练集定义为字典类型,value为列表类型。
读取
我用的是 with open语句代开文件,并把数据放入列表,其中还包括去除换行符等特殊字符。
如何解决收集dat文件中数据后的非英文字符?
打开文件时,参数encoding=‘UTF-8’,和’rb’读取的内容不同。
使用codecs模块可以指定一个编码进行文件处理
理解:
划分
用户行为数据集按照均匀分布随机分成M份,挑选一份作为测试集,将剩下的M-1份作为训练集。进行M次实验就可以得到M个不同的训练集和测试集。如果数据集够大,模型足够简单,为了快速通过离线实验初步地选择算法,也可以只进行一次实验。
期间我用print输出测试集和训练集来检查,内容很多时console貌似不能显示全部内容
此外,为了方便,我将训练集和测试集储存为pkl格式,从而利用数据包。
基于用户的协同过滤算法
算法公式(可通过矩阵实现,我用的是字典矩阵):
- 相似度:
经改进后:
- 兴趣度:
过程记录
注意在字典键不存在的情况下赋值,应先dictdefault 或 setdefault,不确定是否存在用get。
dictdefault的效率应该高于setdefault。
由于建立矩阵的时间接近2分钟,可以考虑pickle。
因为计算准确率、召回率和覆盖率都要有遍历过程,最好将它们的计算过程整合,提高效率。
不止于此,如程序能先有更合理的结构,再在细节上处理(如一些第三方库提供的功能可能更快捷),可有效提升效率。
对数据集dat文件encoding='UTF-8’后(与’rb’读取的内容不同),再用算法推荐的结果:
小修小改:
基于物品的协同过滤算法(与基于用户有共通之处)
算法公式
基于物品的协同过滤算法主要分为两步。
(1) 计算物品之间的相似度。
(2) 根据物品的相似度和用户的历史行为给用户生成推荐列表。
-
相似度:
也是要在计算相似度前,先建立倒排表,但这次是用户->艺术家的对应关系。
-
兴趣度:
改进版: -
相似度归一:
过程记录(主要是运算快?)
因为我的测试集和训练集字典是用户对应艺术家,所以不用建立倒排表了
可以使相似度归一再得到最终的相似矩阵
程序运行速度比UserCF快了太多
一比矩阵文件大小吓一跳,可能我的UserCF算法还有缺陷
覆盖率一般不会过高。因为相似度归一 除的是小于1的数,所以数值上看兴趣值比UserCF大
小修小改:
笔记
2.Python中代码换行,还有三引号方法
3.*在进行文件处理时,发现函数传入参数和返回对象不是同者,即使同名也如此。
4.operator模块提供的itemgetter函数用于获取对象的哪些维的数据,参数为一些序号。要注意,operator.itemgetter函数获取的不是值,而是定义了一个函数,通过该函数作用到对象上才能获取值。
from operator import itemgetter
通过公共键对字典列表排序:itemgetter
如itemgetter(0)根据字典keys排序,1则是value
5.Python可以构造多维字典,而numpy库包括数组、矩阵等功能,pandas 是基于 Numpy 构建的。
Python二维字典的几个小例子
Python numpy中矩阵的用法总结
6.numpy 和 pandas:
Numpy:是数值计算的扩展包,它能高效处理N维数组,复杂函数,线性代数
Panadas:是做数据处理。市python的一个数据分析包
numpy对象最重要特点向量化运算,pandas对象最重要特点是字典和列表混合
7.字典的get、setdefault、from collections import defaultdict,defaultdict()
Python中通过Key访问字典,当Key不存在时,会引发‘KeyError’异常。为了避免这种情况的发生,可以使用collections类中的defaultdict()方法来为字典提供默认值。(或者用get,setdefault等方法)
from collections import defaultdict
# defaultdict接受一个工厂函数作为参数
dict1 = defaultdict(int)
dict2 = defaultdict(set)
dict3 = defaultdict(str)
dict4 = defaultdict(list)
dict1[2] ='two'
print(dict1[1])
print(dict2[1])
print(dict3[1])
print(dict4[1])
# 输出:
0
set()
[]
#
>>> d= defaultdict(dict())
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
d= defaultdict(dict())
TypeError: first argument must be callable or None
>>> d= defaultdict(dict)
>>> d
defaultdict(<class 'dict'>, {})
setdefault 和 defaultdict的一个重要区别:
(对代码分析结构发现,影响挺大)
#defaultdict
>>> from collections import defaultdict
>>> d=defaultdict(str)
>>> d['0']
''
>>> d=defaultdict('abc')
Traceback (most recent call last):
File "<pyshell#17>", line 1, in <module>
d=defaultdict('abc')
TypeError: first argument must be callable or None
>>>print(d)
defaultdict(<class 'str'>, {'0': ''})
#setdefault
>>> d={}
>>> for i in range(0,10):
d.setdefault(str(i),'abc')
>>> d
{'i': 1, '0': 1, '10': 1, '1': 'abc', '2': 'abc', '3': 'abc', '4': 'abc', '5': 'abc', '6': 'abc', '7': 'abc', '8': 'abc', '9': 'abc'}
通过defaultdict,使用list作第一个参数,再用上setdefault,可以很容易将有对应关系的列表转化为字典,再用list将键-值对序列转换为列表字典。
8.调用函数时,仅接收函数返回值,也会执行其中的输出语句。
9.format的一点笔记
>>> precision = 0.52333
>>> recall=0.2326
>>> coverage=0.9456
>>> print('{:.2%} {:.2%}'.format(precision))
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
print('{:.2%} {:.2%}'.format(precision))
IndexError: tuple index out of range
>>> print('{0:.2%} {0:.2%}'.format(precision))
52.33% 52.33%
>>> print('准确率: {1:.2%}\n召回率: {2:.2%}\n覆盖率: {3:.2%}'.format(precision, recall, coverage))
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module> # 必须指定从0开始!!!,或不设位置!!
IndexError: tuple index out of range
>>> print('准确率: {0:.2%}\n召回率: {1:.2%}\n覆盖率: {2:.2%}'.format(precision, recall, coverage))
准确率: 52.33%
召回率: 23.26%
覆盖率: 94.56%
10 .items()返回列表,列表里用元组储存每一键值对。