Python基于机器学习方法实现的电影推荐系统实例详解


Posted in Python onJune 25, 2019

推荐算法在互联网行业的应用非常广泛,今日头条、美团点评等都有个性化推荐,推荐算法抽象来讲,是一种对于内容满意度的拟合函数,涉及到用户特征和内容特征,作为模型训练所需维度的两大来源,而点击率,页面停留时间,评论或下单等都可以作为一个量化的 Y 值,这样就可以进行特征工程,构建出一个数据集,然后选择一个合适的监督学习算法进行训练,得到模型后,为客户推荐偏好的内容,如头条的话,就是咨询和文章,美团的就是生活服务内容。

可选择的模型很多,如协同过滤,逻辑斯蒂回归,基于DNN的模型,FM等。我们使用的方式是,基于内容相似度计算进行召回,之后通过FM模型和逻辑斯蒂回归模型进行精排推荐,下面就分别说一下,我们做这个电影推荐系统过程中,从数据准备,特征工程,到模型训练和应用的整个过程。

我们实现的这个电影推荐系统,爬取的数据实际上维度是相对少的,特别是用户这一侧的维度,正常推荐系统涉及的维度,诸如页面停留时间,点击频次,收藏等这些维度都是没有的,以及用户本身的维度也相对要少,没有地址、年龄、性别等这些基本的维度,这样我们爬取的数据只有打分和评论这些信息,所以之后我们又从这些信息里再拿出一些统计维度来用。我们爬取的电影数据(除电影详情和图片信息外)是如下这样的形式:

Python基于机器学习方法实现的电影推荐系统实例详解

这里的数据是有冗余的,又通过如下的代码,对数据进行按维度合并,去除冗余数据条目:

# 处理主函数,负责将多个冗余数据合并为一条电影数据,将地区,导演,主演,类型,特色等维度数据合并
def mainfunc():
 try:
  unable_list = []
  with connection.cursor() as cursor:
   sql='select id,name from movie'
   cout=cursor.execute(sql)
   print("数量: "+str(cout))

   for row in cursor.fetchall():
    #print(row[1])
    movieinfo = df[df['电影名'] == row[1]]
    if movieinfo.shape[0] == 0:
     disable_movie(row[0])
     print('disable movie ' + str(row[1]))
    else:
     g = lambda x:movieinfo[x].iloc[0]
     types = movieinfo['类型'].tolist()
     types = reduce(lambda x,y:x+'|'+y,list(set(types)))
     traits = movieinfo['特色'].tolist()
     traits = reduce(lambda x,y:x+'|'+y,list(set(traits)))
     update_one_movie_info(type_=types, actors=g('主演'), region=g('地区'), director=g('导演'), trait=traits, rat=g('评分'), id_=row[0])
       
  connection.commit()
 finally:
  connection.close()

之后开始准备用户数据,我们从用户打分的数据中,统计出每一个用户的打分的最大值,最小值,中位数值和平均值等,从而作为用户的一个附加属性,存储于userproex表中:

'insert into userproex(userid, rmax, rmin, ravg, rcount, rsum, rmedian) values(\'%s\', %s, %s, %s, %s, %s, %s)' % (userid, rmax, rmin, ravg, rcount, rsum, rmedium)
'update userproex set rmax=%s, rmin=%s, ravg=%s, rmedian=%s, rcount=%s, rsum=%s where userid=\'%s\'' % (rmax, rmin, ravg, rmedium, rcount, rsum, userid)

以上两个SQL是最终插入表的时候用到的,代表准备用户数据的最终步骤,其余细节可以参考文末的github仓库,不在此赘述,数据处理还用到了一些SQL,以及其他处理细节。

系统上线运行时,第一次是全量的数据处理,之后会是增量处理过程,这个后面还会提到。

我们目前把用户数据和电影的数据的原始数据算是准备好了,下一步开始特征工程。做特征工程的思路是,对type, actors, director, trait四个类型数据分别构建一个频度统计字典,用于之后的one-hot编码,代码如下:

def get_dim_dict(df, dim_name):
 type_list = list(map(lambda x:x.split('|') ,df[dim_name]))
 type_list = [x for l in type_list for x in l]
 def reduce_func(x, y):
 for i in x:
  if i[0] == y[0][0]:
  x.remove(i)
  x.append(((i[0],i[1] + 1)))
  return x
 x.append(y[0])
 return x
 l = filter(lambda x:x != None, map(lambda x:[(x, 1)], type_list))
 type_zip = reduce(reduce_func, list(l))
 type_dict = {}
 for i in type_zip:
 type_dict[i[0]] = i[1]
 return type_dict

涉及到的冗余数据也要删除

df_ = df.drop(['ADD_TIME', 'enable', 'rat', 'id', 'name'], axis=1)

将电影数据转换为字典列表,由于演员和导演均过万维,实际计算时过于稀疏,当演员或导演只出现一次时,标记为冷门演员或导演

movie_dict_list = []
for i in df_.index:
 movie_dict = {}
 #type
 for s_type in df_.iloc[i]['type'].split('|'):
 movie_dict[s_type] = 1
 #actors
 for s_actor in df_.iloc[i]['actors'].split('|'):
 if actors_dict[s_actor] < 2:
  movie_dict['other_actor'] = 1
 else:
  movie_dict[s_actor] = 1
 #regios
 movie_dict[df_.iloc[i]['region']] = 1
 #director
 for s_director in df_.iloc[i]['director'].split('|'):
 if director_dict[s_director] < 2:
  movie_dict['other_director'] = 1
 else:
  movie_dict[s_director] = 1
 #trait
 for s_trait in df_.iloc[i]['trait'].split('|'):
 movie_dict[s_trait] = 1
 movie_dict_list.append(movie_dict)

使用DictVectorizer进行向量化,做One-hot编码

v = DictVectorizer()
X = v.fit_transform(movie_dict_list)

这样的数据,下面做余弦相似度已经可以了,这是特征工程的基本的一个处理,模型所使用的数据,需要将电影,评分,用户做一个数据拼接,构建训练样本,并保存CSV,注意这个CSV不用每次全量构建,而是除第一次外都是增量构建,通过mqlog中类型为'c'的消息,增量构建以comment(评分)为主的训练样本,拼接之后的形式如下:

USERID cf2349f9c01f9a5cd4050aebd30ab74f
movieid 10533913
type 剧情|奇幻|冒险|喜剧
actors 艾米·波勒|菲利丝·史密斯|理查德·坎德|比尔·哈德尔|刘易斯·布莱克
region 美国
director 彼特·道格特|罗纳尔多·德尔·卡门
trait 感人|经典|励志
rat 8.7
rmax 5
rmin 2
ravg 3.85714
rcount 7
rmedian 4
TIME_DIS 15

这个数据的actors等字段和上面的处理是一样的,为了之后libfm的使用,在这里需要转换为libsvm的数据格式

dump_svmlight_file(train_X_scaling, train_y_, train_file)

模型使用上遵循先召回,后精排的策略,先通过余弦相似度计算一个相似度矩阵,然后根据这个矩阵,为用户推荐相似的M个电影,在通过训练好的FM,LR模型,对这个M个电影做偏好预估,FM会预估一个用户打分,LR会预估一个点击概率,综合结果推送给用户作为推荐电影。

模块列表

  • recsys_ui: 前端技术(html5+JavaScript+jquery+ajax)
  • recsys_web: 后端技术(Java+SpringBoot+mysql)
  • recsys_spider: 网络爬虫(python+BeautifulSoup)
  • recsys_sql: 使用SQL数据处理
  • recsys_model: pandas, libFM, sklearn. pandas数据分析和数据清洗,使用libFM,sklearn对模型初步搭建
  • recsys_core: 使用pandas, libFM, sklearn完整的数据处理和模型构建、训练、预测、更新的程序
  • recsys_etl:ETL 处理爬虫增量数据时使用kettle ETL便捷处理数据

为了能够输出一个可感受的系统,我们采购了阿里云服务器作为数据库服务器和应用服务器,在线上搭建了电影推荐系统的第一版,地址是:

www.technologyx.cn

可以注册,也可以使用已有用户:

用户名 密码
gavin 123
gavin2 123
wuenda 123

欢迎登录使用感受一下。

Python基于机器学习方法实现的电影推荐系统实例详解

设计思路

Python基于机器学习方法实现的电影推荐系统实例详解

用简单地方式表述一下设计思路,

1.后端服务recsys_web依赖于系统数据库的推荐表‘recmovie'展示给用户推荐内容

2.用户对电影打分后(暂时没有对点击动作进行响应),后台应用会向mqlog表插入一条数据(消息)。

3.新用户注册,系统会插入mqlog中一条新用户注册消息

4.新电影添加,系统会插入mqlog中一条新电影添加消息

5.推荐模块recsys_core会拉取用户的打分消息,并且并行的做以下操作:

a.增量的更新训练样本
b.快速(因服务器比较卡,目前设定了延时)对用户行为进行基于内容推荐的召回
c.训练样本更新模型
d.使用FM,LR模型对Item based所召回的数据进行精排
e.处理新用户注册消息,监听到用户注册消息后,对该用户的属性初始化(统计值)。
f.处理新电影添加消息,更新基于内容相似度而生成的相似度矩阵

注:

由于线上资源匮乏,也不想使系统增加复杂度,所以没有直接使用MQ组件,而是以数据库表作为代替。
项目源码地址: https://github.com/GavinHacker/recsys_core

模型相关的模块介绍

增量的处理用户comment,即增量处理评分模块

这个模块负责监听来自mqlog的消息,如果消息类型是用户的新的comment,则对消息进行拉取,并相应的把新的comment合并到总的训练样本集合,并保存到一个临时目录

然后更新数据库的config表,把最新的样本集合(csv格式)的路径更新上去

运行截图

Python基于机器学习方法实现的电影推荐系统实例详解

消息队列的截图

Python基于机器学习方法实现的电影推荐系统实例详解

把csv处理为libsvm数据

这个模块负责把最新的csv文件,异步的处理成libSVM格式的数据,以供libFM和LR模型使用,根据系统的性能确定任务的间隔时间

运行截图

Python基于机器学习方法实现的电影推荐系统实例详解

基于内容相似度推荐

当监听到用户有新的comment时,该模块将进行基于内容相似度的推荐,并按照电影评分推荐

运行截图

Python基于机器学习方法实现的电影推荐系统实例详解

libFM预测

http://www.libfm.org/

对已有的基于内容推荐召回的电影进行模型预测打分,呈现时按照打分排序

如下图为打分更新

Python基于机器学习方法实现的电影推荐系统实例详解

逻辑回归预测

对样本集中的打分做0,1处理,根据正负样本平衡,> 3分为喜欢 即1, <=3 为0 即不喜欢,这样使用逻辑回归做是否喜欢的点击概率预估,根据概率排序

Python基于机器学习方法实现的电影推荐系统实例详解

项目源码地址: https://github.com/GavinHacker/recsys_core

Python 相关文章推荐
Python读取ini文件、操作mysql、发送邮件实例
Jan 01 Python
python使用Flask框架获取用户IP地址的方法
Mar 21 Python
python操作sqlite的CRUD实例分析
May 08 Python
python 实现上传图片并预览的3种方法(推荐)
Jul 14 Python
Python安装pycurl失败的解决方法
Oct 15 Python
Centos部署django服务nginx+uwsgi的方法
Jan 02 Python
简单了解python的break、continue、pass
Jul 08 Python
python3.x提取中文的正则表达式示例代码
Jul 23 Python
python能做什么 python的含义
Oct 12 Python
python 生成任意形状的凸包图代码
Apr 16 Python
Python如何自动获取目标网站最新通知
Jun 18 Python
python中opencv实现图片文本倾斜校正
Jun 11 Python
Python 中的参数传递、返回值、浅拷贝、深拷贝
Jun 25 #Python
pyqt5 删除layout中的所有widget方法
Jun 25 #Python
在Python中表示一个对象的方法
Jun 25 #Python
Python设置matplotlib.plot的坐标轴刻度间隔以及刻度范围
Jun 25 #Python
浅谈PyQt5 的帮助文档查找方法,可以查看每个类的方法
Jun 25 #Python
PyQt5根据控件Id获取控件对象的方法
Jun 25 #Python
PyQt5组件读取参数的实例
Jun 25 #Python
You might like
杏林同学录(九)
2006/10/09 PHP
15种PHP Encoder的比较
2007/04/17 PHP
php常见的魔术方法详解
2014/12/25 PHP
PHP下载远程文件到本地存储的方法
2015/03/24 PHP
关于Laravel Route重定向的一个注意点
2017/01/16 PHP
php实现姓名根据首字母排序的类与方法(实例代码)
2018/05/16 PHP
JavaScript中Math对象使用说明
2008/01/16 Javascript
JS提交并解析后台返回的XML的代码
2008/11/03 Javascript
Extjs学习笔记之六 面版
2010/01/08 Javascript
window.dialogArguments 使用说明
2011/04/11 Javascript
为什么要在引入的css或者js文件后面加参数的详细讲解
2013/05/03 Javascript
TypeScript具有的几个不同特质
2015/04/07 Javascript
JavaScript三元运算符的多种使用技巧
2015/04/16 Javascript
JavaScript根据CSS的Media Queries来判断浏览设备的方法
2016/05/10 Javascript
JS 通过系统时间限定动态添加 select option的实例代码
2016/06/09 Javascript
什么是JavaScript中的结果值?
2016/10/08 Javascript
基于js实现的限制文本框只可以输入数字
2016/12/05 Javascript
jQuery实现元素的插入
2017/02/27 Javascript
AngularJS的脏检查深入分析
2017/04/22 Javascript
深入理解vue中的slot与slot-scope
2019/04/22 Javascript
Vue Echarts实现可视化世界地图代码实例
2019/05/07 Javascript
jQuery+ajax实现批量删除功能完整示例
2019/06/06 jQuery
如何基于JS截获动态代码
2019/12/25 Javascript
用Python实现协同过滤的教程
2015/04/08 Python
Python扩展内置类型详解
2018/03/26 Python
对Python subprocess.Popen子进程管道阻塞详解
2018/10/29 Python
Django发送邮件功能实例详解
2019/09/02 Python
Python基于pip实现离线打包过程详解
2020/05/15 Python
Python实现EM算法实例代码
2020/10/04 Python
Net-A-Porter美国官网:全球时尚奢侈品名站
2017/02/11 全球购物
JRE、JDK、JVM之间的关系怎样
2012/05/16 面试题
某某同志考察材料
2014/05/28 职场文书
社保转移委托书范本
2014/10/08 职场文书
刑事附带民事起诉状
2015/05/19 职场文书
九年级化学教学反思
2016/02/22 职场文书
python中subplot大小的设置步骤
2021/06/28 Python