Bootstrap

pyspark实现基于协同过滤的电影推荐系统

最近在学一门大数据的课,课程要求很开放,任意做一个大数据相关的项目即可,不知道为什么我就想到推荐算法,一直到着手要做之前还没有新的更好的来代替,那就这个吧。

推荐算法

推荐算法的发展由来已久,但和机器学习一样,没有大量的算力和数据作支撑达不到好的效果,直到如今高速网络和大数据时代才发展得如火如荼。

推荐算法具体有几种:协同过滤,基于内容,基于知识,和混合过滤,具体的知识可以看该文章,简单总结如下:

协同过滤:基于分类的推荐,把用户或物品按类划分,在同类中互相推荐对方的喜好,比如两个都喜欢看动作电影的用户,其中一个又喜欢看爱情片,就把爱情片也推给另一个同样喜欢看动作电影的用户。
该方法根据划分的对象不同,又可以分为基于用户和基于物品两种,后面会对协同过滤算法作稍微详细的介绍。

基于内容:该方案逻辑更为严谨,可以通过用户画像,设定的大致喜好来推荐,该算法需要对物品的特征进行计算,找到最符合用户喜好的几个物品进行推荐。

基于知识:这单独分类就有点牵强了,基于知识的推荐其实就是更严谨的协同过滤和内容推荐,该算法会构造用户的需求列表,根据该列表与物品的相似程度进行推荐,比如高档耐用品用户在满足后就没有了需求,像房子,汽车等,当用户拥有以后就没必要再继续推送。

混合过滤:顾名思义,就是结合多种方法的推荐算法,比如把前面三种方法输入到一个神经网络,最后拟合出一个结果来,希望结合各自的优点,克服他们的缺点。

协同过滤

该算法的核心思想是将被推荐者的同类找出来,基于用户和基于物品的区别,举个例子就是分别找到和你一样喜欢看动作电影的人,把他的其他喜欢推荐给你;和找到和动作电影比较类似的其他电影,把这些电影推荐给喜欢看动作电影的人,这两步殊途同归,而同时都有一个重点——如何衡量相似

相似是我们日常形容的一种感觉,而对计算机来说是可以用数学量化计算的,如余弦相似度,欧几里得距离,皮尔森系数等,其他详细计算方法可见该文章

相似度计算

本项目中以皮尔斯相关系数作为相似度的衡量标准,表示两个矩阵协方差与标准差的商,计算公式如图:
皮尔斯相关系数
具体计算方法可见该文章,我们编程实现时更多关注其运算规则和返回值即可,该方法返回一个矩阵,对角元素表示其与自身的相似度—为1[0,1][1,0]位置为两矩阵的相似度,该计算方法在np库中有现成的使用方法,示例如下:

    # 计算两个用户的皮尔逊相关系数,越靠近1,表示越相关
    # 返回相关系数矩阵2*2的矩阵,对角线表示与自身的相关系数,[0, 1](或[1, 0],它们是相等的)则是matrix1和matrix2之间的相关系数
    pearson=np.corrcoef(matrix1, matrix2)[0, 1]

相似度取值为[-1,1]越大表示越相关,计算出相似度后即相当于找到类似的群体,再推荐该群体的喜好给用户即可。

但在大数据时代中,在动辄几十上百万条的数据里逐条记录计算相似度并推送不那么优雅也不那么准确,并且矩阵中大部分数据都为0,是一个稀疏矩阵,因为很少有人能评价数据库中的所有物品,故数学家研究能不能把这么多记录的大数据矩阵拆成更小的矩阵呢?

SVD奇异值矩阵分解

奇异值矩阵分解可以实现将m×n的矩阵拆解为m×m正交阵、m×n对角阵,多的行列全为0、n×n正交阵三个矩阵,我们可能回想,这拆完其实比之前还大,拆解还有什么意义?
其中对角阵对角线的值我们称为奇异值,奇异值从大到小排列,所以通常我们使用前几位的奇异值组成对角阵,就已经可以大差不差地构造出原有矩阵来,具体原理见该文章。即我们可以将该矩阵压缩为m×rr×rr×n三个矩阵,此时的r我们可以自由选取,这样一来就可以大大压缩矩阵的空间了,实现代码展示如下:

arr1=np.random.randint(0,100,(5,5))
print(arr1)
# svd 分解得到矩阵
a,b,c=np.linalg.svd(arr1)
# 为了节省内存,对角阵使用一维数组保存,故需将其展开
b=np.diag(b)
# 一维数组值到对角线,其余填充0

分别输出后展示如下:
svd分解矩阵展示
随后我们尝试对该矩阵进行压缩,计算后得到的矩阵分别取前k位,方法代码如下:

def get_svd(matrix,k):
    U, sigma, VT = np.linalg.svd(matrix)
    # 主对角线数组值,其余均为0,展开
    sigma=np.diag(sigma)
    # 截取前k个奇异值
    U_k=U[:, :k]
    sigma_k=sigma[:k, :k]
    VT_k=VT[:k, ]
    return U_k, sigma_k, VT_k

k取3,即5维矩阵保留前3维奇异值,执行展示如下:
svd压缩
可以看到损失变化并不很大,此时因为我们的矩阵并不稀疏,且压缩率为百分之二十,所以可以说压缩效果相当好。

隐向量

原本纵坐标代表用户,横坐标代表物品,压缩后矩阵不完整,取而代之的是r,那这个r代表什么呢?我在视频中看到如下例子(原视频链接
原始矩阵
隐向量矩阵
这是一个音乐推荐系统,原有矩阵经过分解,压缩的r取3,用户和歌曲被一个3×3的矩阵联系起来,我们感性上可以把他认为是歌曲种类,但这并不准确,因为r的值具体取多少并不固定,这类似于机器学习中的网络,网络把我们的特征计算得到一个结果,这个结果并没有什么实在意义,但计算机能计算并拟合就足够了,我们将其理解称为计算机做得一个分类也就可以了,这个分类在矩阵分解中被称为隐向量

矩阵分解的应用

说到这我们还没明白,拆解了矩阵以后又怎么样呢?我们从大矩阵得到了小矩阵,并且能利用该小矩阵近似恢复出大矩阵,我们在推荐的时候到底该怎么用这个矩阵呢?

在协同过滤中,使用矩阵分解方法时,我们实际上是在根据用户向量来生成推荐物品矩阵的预测,即获得了r×n的矩阵后,由用户1×r的输入特征,即可获得1×n的用户关于所有物品喜好程度的预测矩阵,我们推荐时只需根据该矩阵即可。
我们由以往基于用户或物品计算相似,粗糙地选择相似者喜好推荐的方法,转变为生成一张近似但量化的,关于用户对所有物品喜好的特征向量来进行推荐,相比较矩阵分解方法更为优雅和准确。

但到这里我想了一个问题,这种方式还叫协同过滤吗?,并没有直接和其他用户和物品协同,这种纯数学的方法是否还符合协同过滤的定义呢?有关问题我问了文心一言,他的解答我直接放在下面以供参考
矩阵分解和协同过滤的关系

数据处理

数据集来自该链接,目录如下:
电影数据集目录
不同文件分别为:影评、电影信息、演员信息、评分信息和用户信息,其中电影又包含电影长度、海报等等信息,具体介绍可以见其中的README
该数据集可以说十分详细,但稍微有些旧,是2019年的,另外是实在太大了,我要构建矩阵时pycharm提示需要153GB的内存。
内存报错信息
以目前想当然来看,应该可以是分批多次读取再组合的,但是因为本次只是一个尝试,就不研究真实大数据条件下的操作了,重点侧重与算法,与项目的完成上,故首先要对数据进行缩减。

先从电影入手,把不包含海报链接的去掉,再把没有豆瓣评分和演员信息等的去掉,最终只保留下两千条数据,再使用这两千条电影的id,在评分文件ratings过滤记录,代码如下:

import pandas as pd

file_source='E:\\software\\DataBase\\Data\\movie_data\\new_movies.csv'
old_source='E:\\software\\DataBase\\Data\\movie\\ratings.csv'
save=pd.read_csv(file_source)
data=pd.read_csv(old_source)
# 获取指定列的值
value_tosave=save["MOVIE_ID"]

# 过滤后的数据
filtered_data = data[data['MOVIE_ID'].isin(value_tosave)]

# 保存过滤后的数据
filtered_data.to_csv('E:\\software\\DataBase\\Data\\movie\\new_ratings.csv', index=False)

大概步骤为:
先获取电影文件中的电影ID
在评分文件中匹配该ID,符合的保存在变量中;
最后将数据写入csv文件。

同理需要使用排名文件对用户文件进行过滤,此时注意,因为用户名包含中文信息,故在保存csv文件时需要指定编码为utf-8,故保存代码修改为filtered_data.to_csv('E:\\software\\DataBase\\Data\\movie\\new_users.csv', index=False,encoding='utf-8-sig')

过滤前后文件大小对比如下:
文件大小对比
电影数目由十四万降至两千八,打分数目由一百四十万降至十九万,用户数量由六十四万降至八万,此时的数据量计算机大概可以处理了。

但裁后计算svd发现,还是需要50个G的内存,看来分布式系统是不可不用了。
裁后矩阵

Spark系统

大数据框架

之前总是专注算法,对大数据还是一直停留在直观印象上,觉得无非就是数据大一些,记录多一些,但突然想到,这些表象最直接地反映在编程思想上:以往的程序单机运行即可,速度慢的时候只要换一台新的服务器就能得到很好的效果,但到了大数据时代,单个服务器再强也是不可能满足运行要求,故在编程中就要采用分治思想,使用分布式进行计算

目前主流的大数据框架有Hadoopspark,详细介绍和对比可见该文章,二者都是Apache的框架,目前将其理解对比如下:
Hadoop:2012年提出的第一款大数据框架,主要理念为map/reduce,其中map负责数据集的拆解,reduce负责对拆解后的数据集进行运算,其中隐含层为shuffle负责对拆解的数据进行排序归并等操作,总的来说是将大数据采用总分总的形式运行,借助分布式文件系统HDFS实现,如果有计算集群则可以大幅度提升运行效率。
Spark:2014年提出的新一代大数据框架,改进了原有map/reduce,转而使用RAM进行数据暂存,相比更快,并用弹性数据集RDD取代原本HDFS,并且支持Hadoop,其详细教程可看该视频,和极客教程。但具体来说其仅为分布式计算框架,解决了Hadoop原有基于磁盘的效率问题,用内存操作代替了频繁读写磁盘,序列化等步骤,数据在执行完毕前不落地,加快了计算效率,而存储结构仍使用原有的分布式文件系统HDFS

安装部署

本次实验在Windows上进行,但因为没有服务器集群,故部署为单机,大致部署思路如下:
1,配置Java环境,spark是基于Java编写的,必须要有相应的Java环境。
2,安装spark包,官网下载解压到目录即可。
3,配置环境变量
4,bin文件替换,spark是基于Linux系统,若要在Windows中使用需要替换相关文件

具体操作基本可以参考该文章,本地部署无需使用hadoop的可以将相关操作全部指向spark文件即可,配环境确实十分困难,最后我也不知道怎么配好的,甚至没法复现也不敢再重新来过,目前的教训就是一定注意版本对应

RDD

spark的数据操作基于弹性数据集RDD完成,具体操作可见该文章,一次简单读取文件并输出的示例如下:

from pyspark import SparkContext,SparkConf
# 根据对象名和部署模式获取sparkcontext对象
conf=SparkConf().setAppName("test").setMaster("local")
sc=pyspark.SparkContext(conf=conf)
path="file:///E:\\software\\DataBase\\Data\\movie_data\\new_movies.csv"
# 对象读取文件,获得RDD
data=sc.textFile(path)
# 或者使用parallelize生成对象集合
data=sc.parallelize(List(1,2,3,4))
# 遍历输出
data.foreach(print)

操作函数分为转换和动作两种,其中转换类有

filter(func) 		# 过滤,满足func的元素返回新的数据集
map(func)			# 映射,元素传入func中,计算返回数据集
flatMap(func)		# 与map相似,但输入元素可映射0到多个结果
groupByKey()		# 将键值对的数据集按键分组
reduceByKey(func)	# 分组后用func进行计算

动作类函数有:

count()			# 返回元素个数
collect() 		# 以数组形式返回元素
reduce(func)	# 通过func聚合元素
foreach(func)	# 每个元素传递到func中运行
first()			# 返回第一个元素
take(n)			# 返回前n个元素

spark在编程使用时的确具有很多不同特性,作为了解记录如下:
1,惰性机制。spark在执行中采取有向无环图的形式,每个转换操作如map只是记录动作,只有遇到动作reduce才开始执行。
2,RDD只读。RDD在执行生成后不可修改,我们操作的新变量实质是其拷贝版本,这些不同的拷贝构成了有向无环图的节点,这给我们恢复系统状态提供了方便,同时因为计算不结束不落地,减少磁盘开销,提高了效率。
3,粗颗粒度。只能整体转换,不能只修改其中一条。

DataFrame

RDD只给了数据按行操作的能力,有时我们要对数据进行按列的操作甚至更细比如按标签操作时就无法实现了,以及非关系型数据难以用于机器学习的困境,基于此SparkSQL提供了类似pandas的DataFrame操作,其大概原理是将sql语句转为spark,该方法给spark处理大规模结构化数据的能力,同时也比RDD更简单易用,且计算效率更高。

大概使用方法如下:
from pyspark.sql import SparkSession导入包
spark=SparkSession.builder.config(conf=SparkConf()).getOrCreate()创建实例对象

df=spark.read.csv(path)从文件读入,或使用spark_df = spark.createDataFrame(pandas_df)pandas数据中读入创建DataFrame
df.show()展示数据
df.select(列名)实现按列选取数据
df.write.csv(new_path)保存数据

部分操作函数如下:
df.printSchema()展示结构信息
df.filter()条件如df["age"]>10实现按条件过滤
df.groupby("列名")实现分组
df.sort(df['age'].desc())实现按年龄降序排序

工程实践

其实该算法最关键的就是根据已有的打分矩阵计算svd分解并压缩后的矩阵VV矩阵与用户输入矩阵做矩阵乘法就可以生成一张用户关于每部电影的预测矩阵,后续可以根据该矩阵给用户推荐,关于spark使用svd官方教程在此,目前打算构建成一个前后端交互项目,前端将用户打分数据传到后台,后台推荐算法把推荐的电影信息给前端展示,流程大概如下:
推荐系统流程图
其中后端推荐算法主要是将用户特征向量与V矩阵做矩阵乘法获得预测矩阵,根据该矩阵给用户推荐电影,流程图如下:
推荐系统流程图
文件代码结构如下:
代码结构
其中internet文件负责处理网络信息,main为主函数,matrix_calculate负责把大打分矩阵拆解压缩出V矩阵,recommend实现给用户推荐的具体算法。

主函数

因为本项目为前后端交互,故采用socket模式,死循环内监听端口,接受处理信息,并发送信息给客户端,主函数代码如下:

import recommend
import pandas as pd
import socket
import internet
score_matrix=pd.read_csv('"E:\\software\\DataBase\\Data\\movie_data\\ratings_matrix.csv',
                           header=0,index_col=0)
predict_matrix=pd.read_csv('"E:\\software\\DataBase\\Data\\movie_data\\predict_matrix.csv',
                            header=0,index_col=0)
if __name__ == '__main__':
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 实例化对象
    sock.bind(('ip', 1111))  # 绑定IP端口
    sock.listen(2)  # 监听端口,允许排队的个数为n
    conn, addr = sock.accept()  # 获取连接对象和地址
    while True:
        try:
            print("等待客户端发送数据...")
            data = conn.recv(1024)  # 接收文件数据
            if not data:
                break
            print("收到:", data.decode())
            # 电影id,评分
            movie_list,score_list, = internet.parse(data.decode())

            # 信息更新到数据库
            internet.receive(movie_list, score_list)
            # 建立用户向量,指定user用户分解矩阵
            user_vector=recommend.zip_user_vector(score_matrix,"user")
            # 获得k个预测结果
            movie_id=recommend.predict_movie_id(user_vector,predict_matrix,20)
            # 在电影表中查找信息
            movie_list=recommend.predict_movie(movie_id)
            # 电影信息发送给前端
            internet.send_recommendation(conn,movie_list)
        except:
            print("连接断开")

预测矩阵spark计算

该节是最体现大数据工作的地方,也是本次项目不得不用大数据框架之处,2k电影×8万用户的打分矩阵计算svd矩阵分解在个人电脑上会直接内存溢出,如果用原始数据想必服务器也遭不住,所以该方法一定需要分布式计算来负载压力。

同时该部分是我卡的最久的地方,首先spark语法不清楚,内部使用的数据结构不了解,每步执行都处在为什么报错和为什么能执行的疑惑中,如今结束了再次整理思路:
1,svd分解pyspark有现成的库,调库computeSVD即可实现,其中参数k表示取前k个奇异值
2,computeSVDRowMatrix的一个方法,故执行前要先将数据转为RowMatrix,但该结构又是一个分布式矩阵,在本地使用前要先收集一下,相关介绍如下:

RowMatrix是ApacheSparkMLlib库中的一个类,它表示一个分布式行矩阵。
它主要用于对大规模数据集进行线性代数操作,如奇异值分解(SVD)、主成分分析(PCA)等。

RowMatrix的每行都是一个向量,并且这些向量可以分布式地存储在集群的不同节点上。
这使得它特别适合处理大型数据集,因为它允许在分布式环境中进行线性代数操作,而不需要将所有数据加载到单个节点上。

3,分解矩阵u,s,v分别是三种不同类型的矩阵,要分别处理保存,相关介绍如下:

    U:左奇异矩阵,类型为RowMatrix。
    它是一个分布式行矩阵,其中包含了SVD分解后的左奇异向量。
    由于它是一个分布式矩阵,因此无法直接像本地矩阵那样进行操作,但可以通过其方法(如rows)来访问其行数据。

    s:奇异值向量,类型为Vector。
    它是一个包含所有奇异值的向量,这些奇异值按照从大到小的顺序排列。
    这个向量是一个本地向量,可以直接在驱动程序中进行操作。

    V:右奇异矩阵,类型为Matrix。它是一个本地矩阵,包含了SVD分解后的右奇异向量。
    这个矩阵可以直接在驱动程序中进行操作,例如进行矩阵乘法或提取特定的列向量。

4,该过程计算量大,耗时长,执行前无比谨慎,别因为一点小问题白费一个小时的等待。

计算过程直接展示代码:

def caculate_svd_v(csv_path):
    start_time = time.time()
    print("正在加载spark...")
    conf = SparkConf().setAppName("svd").setMaster("local")
    sc = SparkContext(conf=conf)
    print("正在加载数据...")

    # 直接使用textFile的方式读取数据,然后再进行map操作
    raw_data = sc.textFile(csv_path)
    raw_data = raw_data.map(lambda x: x.split(","))
    matrix = RowMatrix(raw_data)
    print('矩阵构建完成')
    print('开始计算SVD...')
    # 是否计算U矩阵,压缩阶数k为15
    svd = matrix.computeSVD(15, computeU = True)
    cost_time=time.time()-start_time
    print("计算完成,耗时:",cost_time)

    print("正在转化矩阵为pandas...")
    df_u = row_to_df(svd.U)
    df_s=pd.DataFrame(svd.s.toArray())
    df_V=vector_to_df(svd.V)
    print("正在保存结果...")
    df_u.to_csv("E:\\software\\DataBase\\Data\\movie_data\\u.csv")
    df_s.to_csv("E:\\software\\DataBase\\Data\\movie_data\\s.csv")
    df_V.to_csv("E:\\software\\DataBase\\Data\\movie_data\\v.csv")

其中转化pandas过程中有两个函数需要自行编写,分别处理RowMatrixVector,代码展示如下:

def row_to_df(vector):
    # collect 方法将分布式向量转换为本地向量,然后将其转换为pandas数据框
    print("正在转化u矩阵...")
    # 返回一个列表
    u=vector.rows.collect()
    print("计算U矩阵规格为:",len(u),"*",len(u[0]),"=",len(u)*len(u[0]))
    return pd.DataFrame(u)
def vector_to_df(dense_matrix):
    # 用于将dense矩阵转换为pandas数据框
    print("获取到dense的值为:",dense_matrix)
    print("正在转化V矩阵...")
    # 获取行列数
    rows = dense_matrix.numRows
    cols = dense_matrix.numCols
    print("计算V矩阵规格为:", rows, "*", cols,
          "=", rows * cols)
    value=dense_matrix.toArray()
    # 获得np值
    nplist=list(value)
    v_list=np.array(nplist).reshape(rows,cols)
    # 根据np值转为pandas数据框
    df=pd.DataFrame(v_list)
    print("获取到的pandas值为:",df)
    return df

这部分开始一直报错也是因为不了解其中的结构,要对矩阵s,u,v分别输出时发现总也不行,仔细看看会发现其实是类型问题,此时再根据类型去查解决方案即可,计算机中遇到的每个问题都有前人解决过,只要把问题问得够具体够详细,就没有解决不了的困难。

但该步仍有疑问没能解决:
1,单机如何实现性能扩展
spark负责分布式计算的资源调度和负载均衡,可以用集群的力量解决一个大问题,但本次实验中,spark采用单机部署,并没有其他计算机可供spark调度,它是如何实现大矩阵运算而不内存溢出呢?

2,文件读入方式不同导致崩溃
上述代码几乎重构过三次,第二次时我为了借助pandas库对标签过滤的方便,选择将数据先读入pandas,再由pandas传给RDD,到此都一切顺利,而等到计算svd时又会出现内存溢出错误。当时观察了RDD输出都和spark直接读取文件无异,其背后原因到目前尚不得解。

推荐流程

该部分实现四个函数:
1,计算svd的k阶压缩。spark加载计算较慢,处理后的V矩阵和用户矩阵可以用本机环境直接计算,故需自行编写该函数。
2,计算用户的特征向量。计算V矩阵时将隐向量设置为15,大意为15种电影类型,用户的标记也应该压缩到对15种电影的标记,该步使用svd分解压缩法实现。
3,计算预测矩阵并返回其中前k个最大值的电影id。V矩阵与特征向量做点积生成预测矩阵,因为只针对一个用户预测,故该矩阵其实是一个向量,在向量中选取前k个索引,到电影表中查找对应的电影id。(为了免去对indexcolumns的过滤处理,我单独设置了一张不含标签纯打分信息的表nolabel,该过程增加了磁盘开销,但减少了我的大脑开销)
4,根据电影id在电影表中查找电影信息。该步就比较简单了,用pd.loc索引查找电影id,相关电影信息加入列表即可。
四个函数的代码展示如下:

def get_svd(matrix,k):
    # 根据矩阵计算svd,并取前k个奇异值
    U, sigma, VT = np.linalg.svd(matrix,full_matrices=False)
    # 主对角线数组值,其余均为0
    sigma=np.diag(sigma)
    # 截取前k个奇异值
    U_k=U[:, :k]
    sigma_k=sigma[:k, :k]
    VT_k=VT[:k, ]
    return U_k, sigma_k, VT_k

def zip_user_vector(file,user_id):
    # 负责将用户特征向量分解成3个矩阵,分别是U,S,VT,只用U作为用户的特征向量
    print("正在计算用户特征向量...")
    record=file.loc[user_id].values
    print(record)
    print(record.shape)
    print("正在分解用户矩阵")
    record=record.reshape(5, -1)
    # 隐向量压缩为15
    u,s,v=get_svd(record,3)
    u=u.reshape(1,-1)
    print("分解后的矩阵格式为:", u.shape)
    print("分解后的矩阵为:", u)
    return u

def predict_movie_id(user_matrix,predict_matrix,k):
    # 给出k部预测的电影id
    print("正在预测用户评分...")
    matrix=np.dot(user_matrix,predict_matrix)
    # 将数组降序排列,返回数组的索引
    print("正在选择电影")
    sort_list=np.argsort(matrix)[::-1]
    # 选前k个最大值的索引
    index_list=sort_list[0][0:k]
    print("获得电影索引为:",index_list)
    # 根据索引查找电影id
    movie = pd.read_csv("E:\\software\\DataBase\\Data\\movie_data\\blank_ratings_matrix.csv"
                       , header=0, index_col=0)
    label = movie.columns
    movie_id=[]
    for i in index_list:
        movie_id.append(label[i])
    print("预测结果为:",movie_id)
    return movie_id

def predict_movie(movie_id):
    # 根据电影id在表中查找相关电影信息
    df=pd.read_csv("E:\\software\\DataBase\\Data\\movie_data\\new_movies.csv",
                   header=0,index_col=0)
    movie_list=[]
    for i in movie_id:
        # csv文件中id为整型
        i=int(i)
        movie_list.append(df.loc[i])
    return movie_list

综合使用如下代码进行推荐:

# 获得用户向量
user_vector=zip_user_vector(pd.read_csv('E:\\software\\DataBase\\Data\\movie_data\\ratings_matrix.csv',
                           header=0,index_col=0),"user")
# 获得预测电影id
movie_id=predict_movie_id(user_vector,pd.read_csv('E:\\software\\DataBase\\Data\\movie_data\\predict_matrix.csv',
                            header=0,index_col=0),3)
# 根据id查找电影信息
print(predict_movie(movie_id))

输出结果如下:
推荐电影信息
目前可以实现推荐功能,但因为数据集问题,其中记录有重复的,因为算法本身依赖数据集格式,时间精力有限,故暂时不作调整。

报文解析

该部分就两个功能:
1,将前端传来的数据解析成电影id和打分情况,写入打分表文件。
2,将后端生成的电影信息发送到前端。

直接展示代码:

def receive(movie_list,score_list):
    # 接受电影id和打分情况并写入文件
    file=pd.read_csv("E:\\software\\DataBase\\Data\\movie_data\\ratings_matrix.csv",
                     header=0,index_col=0)
    for i in range(len(movie_list)):
        file.loc["user",movie_list[i]]=score_list[i]
    file.to_csv("E:\\software\\DataBase\\Data\\movie_data\\ratings_matrix.csv")

def send_recommendation(conn,movie_list):
    for i in movie_list:
        conn.send(i)

总结

要说推荐系统也从属于机器学习?我是有点doubt的,它可一点没学全是我教的。不过就到现在的学习经历来说,计算机内的定义和区分并不十分严谨和界限分明,流传的很多说法甚至根本不是它的真正意思,只是因为它看起来是这样而一直被这么叫,所以实现之前不管对方是大佬还是小白,多问一嘴也是给自己省事。

该项目实现了简单的推荐系统,浅尝了一次推荐算法,再次搞了前后端的交互,要说读了研就是不一样,这项目含金量比起本科不知道高了多少,可跟实际情况比起来还是差很多,但毕竟只是个作业,了解到这一步我觉得已经极限了,对于刚入学两个月的我,做到这我认为已经极限了,对不一定当作主要方向,纯粹爱好驱动的尝试,也已经极限了。

总结与展望:
1,真正的大数据。不说上亿条数据,起码把整个数据集跑起来吧,这期间需要应该要学一些性能优化的方法。
2,改进的推荐算法,算法在实际应用中可能仍然不够实际,相关算法也有改进算法可以使用,甚至直接自己改进算法也不是不可能的。
3,优化安全的前后端。到目前前后端通信框架从来没使用过,安全性健壮性压根没考虑,只是单纯从实现角度完成,看现在行情都是要全栈,开发岗应该对整个流程都是要花点心思。

写在后面

前面的总结都是写于算法原理的初步探索阶段,在求知欲得到满足的快乐时期,真正实现起来,暂时想不到比一步一个坑更雅的说法。当时的很多小想法到现在都被磨平了,现在只觉得尽快实现比什么都重要,可以看到在我的代码中不乏简单粗暴和力大飞砖——反正电脑累总比我累强;另一方面也是期末时间紧任务重,不太有精力和心境去慢慢打磨作品了,再加上可能人知道的越多就知道自己有更多不知道的,到现在结束阶段只觉得项目实在粗糙,甚至像小孩过家家,不过还是不能站在当下嘲笑过去幼稚的自己,最近的话怎么说:那不是小丑,那是我的来时路,写csdn一方面算是做笔记,另一方面也算记录自己的成长之路。

;