专栏首页数据の美推荐系统业界hello world
原创

推荐系统业界hello world

导语

笔者在当年上学刚刚接触物品推荐问题时,使用的数据集就是MovieLens,那时候的课本上,大多使用传统的协同过滤算法,基于相似用户、相似物品,来解决问题。时至今日,市面上涌现了大量的机器学习相关书籍,解决物品推荐问题的算法虽早已物是人非,然而MovieLens数据集,作为物品推荐问题里的“hello world”,却仍然是学习,或者检验一个推荐算法的不二之选。此为笔者个人拙见,仅供参考,敬请指正。

正文

MovieLens,创建于1997年,是一个推荐系统和虚拟社区网站,其主要功能为应用协同过滤技术和用户对电影的喜好,向用户推荐电影。MovieLens保存了用户对电影的评分,其按照用户、电影的数据量大小,提供了多个数据集,如MovieLens 100k、MovieLens 1M、MovieLens 10M等等。注意,本文仅仅使用打分数据,没有另外引入更多的信息,如用户属性、物品属性等,这样的限定有助于我们把物品推荐这个问题抽象化,同时也使得我们的注意力更加收敛。

本文以MovieLens 100k为例,数据集在文章开头附件处u.zip。MovieLens 100k数据集,共10万条样本数据,每个样本记录了用户id、电影id、用户对电影的打分、打分的时间戳(单位:秒),涉及用户943个,电影1682部。

user_id

item_id

rating

timestamp

1

1

5

874965758

2

1

4

888550871

5

1

4

875635748

6

1

4

883599478

10

1

4

877888877

13

1

3

882140487

15

1

1

879455635

16

1

5

877717833

17

1

4

885272579

18

1

5

880130802

20

1

3

879667963

21

1

5

874951244

23

1

5

874784615

25

1

5

885853415

26

1

3

891350625

38

1

5

892430636

41

1

4

890692860

42

1

5

881105633

43

1

5

875975579

……

……

……

……

我们使用更为直观的用户物品矩阵来展示这一份数据。

用户1

用户2

用户3

用户4

用户5

……

电影1

5

4

0

0

4

……

电影2

3

0

0

0

3

……

电影3

4

0

0

0

0

……

电影4

3

0

0

0

0

……

电影5

3

0

0

0

0

……

……

……

……

……

……

……

……

# -*- coding:utf-8 -*-

import pandas as pd
import numpy as np


def main():
    header = ['user_id', 'item_id', 'rating', 'timestamp']
    dataset = pd.read_csv('u.csv', sep=',', names=header)

    # 获取去重用户数
    users = dataset.user_id.unique().shape[0]

    # 获取去重电影数
    items = dataset.item_id.unique().shape[0]

    # 生成用户物品矩阵
    user_item_matrix = np.zeros((items, users))
    for line in dataset.itertuples():
        user_item_matrix[line[2] - 1, line[1] - 1] = line[3]

    # 持久化
    np.savetxt('user_item_matrix.csv', user_item_matrix, delimiter=',')


if __name__ == '__main__':
    main()

我们先来简单回顾一下,从前协同过滤算法是如何基于这个用户物品矩阵解决物品推荐问题的。协同过滤(英语:Collaborative Filtering),推荐场景的一个常用思维,"简单来说是利用某兴趣相投、拥有共同经验之群体的喜好来推荐用户感兴趣的信息"--引用自维基百科。

比如说,943个用户中,谁谁谁和用户1的兴趣最接近?显然就是那些个,和用户1看过相同电影并且给出差不多分数的用户,他们的兴趣相投。

又比如说,1682部电影中,哪部哪部和电影1的风格最接近?显然就是和电影1有相同受众的电影,例如小孩都喜欢看电影A和电影1,打高分;大人都不喜欢看电影A和电影1,打低分,那电影A和电影1都偏向低龄化,它们的风格接近。

协同过滤解决问题的核心思想就是,通过计算两两打分向量的距离,找到和用户1兴趣接近的用户并以他们的喜好作为用户1的电影推荐列表(基于用户的协同过滤),或者找到和电影1风格接近的电影并以他们的受众作为电影1的用户推荐列表(基于物品的协同过滤)。文章接下来基于物品的协同过滤展开探讨。

计算两个向量的距离,有挺多种计算方式,如最常见的夹角余弦(Cosine)和欧氏距离,其他如曼哈顿距离、切比雪夫距离、闵可夫斯基距离、巴氏距离等等,各种计算方式侧重点不一,需要结合实际业务进行选择,这里仅以夹角余弦(Cosine)为例。

欧氏距离
夹角余弦
# -*- coding:utf-8 -*-

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics.pairwise import pairwise_distances
from sklearn.metrics import mean_squared_error
from math import sqrt


def main():
    header = ['user_id', 'item_id', 'rating', 'timestamp']
    dataset = pd.read_csv('u.csv', sep=',', names=header)

    # 获取去重用户数,共943个用户
    users = dataset.user_id.unique().shape[0]

    # 获取去重电影数,共1682部电影
    items = dataset.item_id.unique().shape[0]

    # 以3:1的比例,划分数据集为训练集和测试集
    train_data, test_data = train_test_split(dataset, test_size=0.25)

    # 生成训练集的用户物品矩阵
    train_data_matrix = np.zeros((items, users))
    for line in train_data.itertuples():
        train_data_matrix[line[2] - 1, line[1] - 1] = line[3]

    # 生成测试集的用户物品矩阵
    test_data_matrix = np.zeros((items, users))
    for line in test_data.itertuples():
        test_data_matrix[line[2] - 1, line[1] - 1] = line[3]

    # 计算两两物品之间的相似度,item_similarity是一个1682*1682的矩阵
    item_similarity = pairwise_distances(train_data_matrix, metric='cosine')

    """
        矩阵相乘;
        其中item_similarity是一个1682*1682的矩阵,下标为(i,j)的元素表示第i部电影和第j部电影的相似度;
        train_data_matrix是一个1682*943的矩阵,下标为(i,j)的元素表示第j个用户对第i部电影的打分;
        forecast_data_matrix是一个1682*943的矩阵,下标为(i,j)的元素表示第j个用户对第i部电影的预测打分,其根据第i部电影与其他电影的相似度向量,以及第j个用户对其他电影的实际打分向量,经过向量点乘计算得到;
    """
    forecast_data_matrix = item_similarity.dot(train_data_matrix)

    # 计算每一部电影与其他电影的相似度的和,作为权重,用于调整forecast_data_matrix
    weights = np.array([item_similarity.sum(axis=1)])

    # 加权的forecast_data_matrix矩阵,使weighted_forecast_data_matrix的元素取值范围接近1-5
    weighted_forecast_data_matrix = forecast_data_matrix / weights.T

    '''
        基于测试集,评估预测效果;其中,评估指标使用均方差误差;
        test_data_matrix是1682*943的矩阵,一个共有25000个样本,评估预测效果时,只计算这部分样本,预测值和实际值的误差;
    '''

    predicted_value_list = []
    actual_value_list = []

    for i in range(1682):
        for j in range(943):
            actual_value = test_data_matrix[i][j]
            if actual_value != 0:
                actual_value_list.append(actual_value)
                predicted_value = weighted_forecast_data_matrix[i][j]
                predicted_value_list.append(predicted_value)

    MSE = sqrt(mean_squared_error(predicted_value_list, actual_value_list))

    print("MSE is {MSE}".format(MSE=MSE))


if __name__ == '__main__':
    # 输出:MSE is 3.4505954993448706
    main()

可以看到预测结果的均方误差是3.4505954993448706,用户的真实打分和预测打分约莫相差1.857577858218834(打分范围1-5)。传统的协同过滤算法,挖掘用户物品矩阵背后规律的思路是很明朗的,很经典的。我们想想刚才的实现方式中,哪些地方有比较大的改进空间。计算物品向量的距离,这部分属于数学范畴,几百年来牢不可破,我们能做的只有更换另外一个计算公式去衡量向量距离,可操作的空间很小。那物品向量呢,这部分变数很大,基于用户原始打分去构造,显然是比较粗糙的,似乎还有更好的方式去构造。

我们来分析一下这个用户物品矩阵,还有什么信息是可以挖取的?

第一,缺失值的填充不可马虎,能否改进?

试想一下,在实际业务中,我们较难获得像MovieLens这样的高质量数据集。目前市面上,物品推荐的产品设计或许是以下这样的:

1、基于feed流,侧重于推送,用户被动接受信息

2、信息爆炸,用户接触的物品仅是物品池里的一小部分

3、生活节奏很快,用户很忙,用户未必有时间给物品评分

种种原因导致,我们最后收集到的用户物品矩阵,是稀疏的。对待缺失值,除了选择忽略,或者简单替换,应该还有更好的方式去处理。

第二,原始数据里还有一个字段,打分的时间戳(单位:秒),这个信息在转化为用户物品矩阵时给丢掉了,我们能否带上?

第三,自从Mikolov在他2013年的论文“Efficient Estimation of Word Representation in Vector Space”提出词向量的概念后,embedding思想大行其道,万物皆向量,我们能否以一种更好的数据结构去组织这个用户物品矩阵?

我们在接下来的文章中继续讨论,欢迎大家关注我的专栏,您的支持,是我最大的动力。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 三门问题

    三门问题(Monty Hall problem)亦称为蒙提霍尔问题,是美国电视游戏节目Let's Make a Deal的主持人蒙提·霍尔(Mo...

    鸢尾花
  • C++拾取——Linux下实测布隆过滤器(Bloom filter)和unordered_multiset查询效率

            布隆过滤器是一种判定元素是否存在于集合中的方法。其基本原理是使用哈希方法将数据映射到一个很长的向量上。在维基百科上,它被称为“空间效率和查询时间...

    方亮
  • 你真的了解艾滋病吗?这个妹子做了款小程序,专门防制 AIDS | 晓组织 #6

    我是奶瓶湄紫,「艾查查」小程序的主体开发者,公共卫生执业医师,就职于上海市普陀区疾控中心,常年游走在互联网边缘。

    知晓君
  • 机器学习算法实现解析——libFM之libFM的训练过程概述

    本节主要介绍的是libFM源码分析的第四部分——libFM的训练。 FM模型的训练是FM模型的核心的部分。 4.1、libFM中训练过程的实现 在FM模型的训练...

    zhaozhiyong
  • 有理有据的胡说八道:由DB圈的一桩陈年公案看大数据时代的数据科学

    忽如一夜春风来,大数据之漫山遍野的开。如今的IT界,言必称大数据云计算,高级一点的还有机器学习人工智能。数据科学正在如火如荼的在IT公司里发展。数据科学家们出身...

    用户1564362
  • 批量创建用户并使用sudo和ACL来控制用户权限

    版权声明:本文为耕耘实录原创文章,各大自媒体平台同步更新。欢迎转载,转载请注明出处,谢谢。

    耕耘实录
  • 设计模式之module模式及其改进

    写在前面 编写易于维护的代码,其中最重要的方面就是能够找到代码中重复出现的主题并优化他们,这也是设计模式最有价值的地方 《head first设计模式》里有一篇...

    okaychen
  • SpringBoot学习笔记(二) 文件访问映射

    Kindear
  • 一日一技:Python 下面最简单的单例模式写法

    二十几种设计模式中,单例模式是最简单最常用的一种。在其他语言里面实现单例模式要写不少代码,但是在 Python 里面,有一种非常简单的单例模式写法。

    青南
  • matlab在64位的机子上不能运行notebook解决方法

    forrestlin

扫码关注云+社区

领取腾讯云代金券