当前位置:   article > 正文

DS实习Week1 ---推荐系统学习(4)实例:电影推荐系统①_电影推荐系统数据集

电影推荐系统数据集

一、数据集介绍

个性化推荐算法的数据大多是文本和图像。比如网易云音乐推荐中,数据是音乐的名字、歌手、音乐类型等文本数据;抖音视频推荐中,数据是视频或图像数据;也有可能同时使用图像和文本数据,比如YouTube的视频推荐算法中,会同时考虑用户信息和视频类别、视频内容信息。

本次实践我们采用ml-1m电影推荐数据集,它是GroupLens Research从MovieLens网站上收集并提供的电影评分数据集。包含了6000多位用户对近3900个电影的共100万条评分数据,评分均为1~5的整数,其中每个电影的评分数据至少有20条。该数据集包含三个数据文件,分别是:

  • users.dat:存储用户属性信息的文本格式文件。
  • movies.dat:存储电影属性信息的文本格式文件。
  • ratings.dat:存储电影评分信息的文本格式文件。

另外,为了验证电影推荐的影响因素,我们还从网上获取到了部分电影的海报图像。现实生活中,相似风格的电影在海报设计上也有一定的相似性,比如暗黑系列和喜剧系列的电影海报风格是迥异的。所以在进行推荐时,可以验证一下加入海报后,对推荐结果的影响。 电影海报图像在posters文件夹下,海报图像的名字以"mov_id" + 电影ID + ".png"的方式命名。由于这里的电影海报图像有缺失,我们整理了一个新的评分数据文件,新的文件中包含的电影均是有海报数据的,因此,本次实践使用的数据集在ml-1m基础上增加了两份数据:

  • posters:包含电影海报图像。
  • new_rating.txt:存储包含海报图像的新评分数据文件。

用户信息、电影信息和评分信息包含的内容如下表所示。

用户信息UserIDGenderAgeOccupation
样例1F【M/F】110
电影信息MovieIDTitleGenresPosterID
样例1Toy StoryAnimation| Children’s|Comedy1
评分信息UserIDMovieIDRating
样例111935【1~5】

其中部分数据并不具有真实的含义,而是编号。年龄编号和部分职业编号的含义如下表所示。

年龄编号职业编号
  • 1: “Under 18”
  • 18: “18-24”
  • 25: “25-34”
  • 35: “35-44”
  • 45: “45-49”
  • 50: “50-55”
  • 56: “56+”
  • 0: “other” or not specified
  • 1: “academic/educator”
  • 2: “artist”
  • 3: “clerical/admin”
  • 4: “college/grad student”
  • 5: “customer service”
  • 6: “doctor/health care”
  • 7: “executive/managerial”

海报对应着尺寸大约为180*270的图片,每张图片尺寸稍有差别。

从样例的特征数据中,我们可以分析出特征一共有四类:

  1. ID类特征:UserID、MovieID、Gender、Age、Occupation,内容为ID值,前两个ID映射到具体用户和电影,后三个ID会映射到具体分档。
  2. 列表类特征:Genres,每个电影有多个类别标签。如果将电影类别编号,使用数字ID替换原始类别,特征内容是对应几个ID值的列表。
  3. 图像类特征:Poster,内容是一张180×270的图片。
  4. 文本类特征:Title,内容是一段英文文本。

因为特征数据有四种不同类型,所以构建模型网络的输入层预计也会有四种子结构。


二、推荐系统思路

如果能将用户A的原始特征转变成一种代表用户A喜好的特征向量,将电影1的原始特征转变成一种代表电影1特性的特征向量。那么,我们计算两个向量的相似度,就可以代表用户A对电影1的喜欢程度。据此,推荐系统可以如此构建:

假如给用户A推荐,计算电影库中“每一个电影的特征向量”与“用户A的特征向量”的余弦相似度,根据相似度排序电影库,取 Top k的电影推荐给A。

图2:推荐系统设计

 

这样设计的核心是两个特征向量的有效性,它们会决定推荐的效果。

1.如何获得有效特征

如何获取两种有效代表用户和电影的特征向量?首先,需要明确什么是“有效”?

对于用户评分较高的电影,电影的特征向量和用户的特征向量应该高度相似,反之则相异。

我们已经获得大量评分样本,因此可以构建一个训练模型如下图所示,根据用户对电影的评分样本,学习出用户特征向量和电影特征向量的计算方案(灰色箭头)。

图2:训练模型

  1. 第一层结构:特征变换,原始特征集合变换为两个特征向量。

  2. 第二层结构:计算向量相似度。为确保结果与电影评分可比较,两个特征向量的相似度从【0~1】缩放5倍到【0~5】。

  3. 第三层结构:计算Loss,计算缩放后的相似度与用户对电影的真实评分的“均方误差”。

以在训练样本上的Loss最小化为目标,即可学习出模型的网络参数,这些网络参数本质上就是从原始特征集合到特征向量的计算方法,如灰色箭头所示。根据训练好的网络,我们可以计算任意用户和电影向量的相似度,进一步完成推荐。

2.从原始特征到特征向量之间的网络如何设计?

基于上面的分析,推荐模型的网络结构初步设想如下。

图3:推荐模型的网络结构设想

 

将每个原始特征转变成Embedding表示,再合并成一个用户特征向量和一个电影特征向量。计算两个特征向量的相似度后,再与训练样本(已知的用户对电影的评分)做损失计算。

但不同类型的原始特征应该如何变换?有哪些网络设计细节需要考虑?将在后续结合代码实现逐一探讨,包括四个小节:

  1. 数据处理,将MovieLens的数据处理成神经网络理解的形式。
  2. 模型设计,设计神经网络模型,将离散的文字数据映射为向量。
  3. 配置训练参数并完成训练,提取并保存训练后的数据特征。
  4. 利用保存的特征构建相似度矩阵完成推荐。

三、数据处理流程

数据处理就是将人类容易理解的图像文本数据,转换为机器容易理解的数字形式,把离散的数据转为连续的数据。在推荐算法中,这些数据处理方法也是通用的。本次实验中,数据处理一共包含如下六步,流程如图4所示:

  1. 读取用户数据,存储到字典
  2. 读取电影数据,存储到字典
  3. 读取评分数据,存储到字典
  4. 读取海报数据,存储到字典
  5. 将各个字典中的数据拼接,形成数据读取器。
  6. 划分训练集和验证集生成迭代器,每次提供一个批次的数据

图4:数据处理流程图

1.用户数据处理

用户数据文件user.dat中的数据格式为:UserID::Gender::Age::Occupation::Zip-code,存储形式如下图所示:

上图中,每一行表示一个用户的数据,以::::隔开,第一列到最后一列分别表示UserID、Gender、Age、Occupation、Zip-code,各数据对应关系如下:

数据类别数据说明数据示例
UserID每个用户的数字代号1、2、3等序号
GenderF表示女性,M表示男性F或M
Age用数字表示各个年龄段
  • 1: “Under 18”                      
  • 18: “18-24”
  • 25: “25-34”
  • 35: “35-44”
  • 45: “45-49”
  • 50: “50-55”
  • 56: “56+”
Occupation用数字表示不同职业
  • 0: “other” or not specified
  • 1: “academic/educator”
  • 2: “artist”
  • 3: “clerical/admin”
  • 4: “college/grad student”
  • 5: “customer service”
  • 6: “doctor/health care”
  • 7: “executive/managerial”
  • 8: “farmer”
  • 9: “homemaker”
  • 10: “K-12 student”
  • 11: “lawyer”
  • 12: “programmer”
  • 13: “retired”
  • 14: “sales/marketing”
  • 15: “scientist”
  • 16: “self-employed”
  • 17: “technician/engineer”
  • 18: “tradesman/craftsman”
  • 19: “unemployed”
  • 20: “writer”
zip-code邮政编码,与用户所处的地理位置有关。
在本次实验中,不使用这个数据。
48067

首先,读取用户信息文件中的数据:

表 1 unzip 命令常用选项及含义
选项含义
-d 目录名将压缩文件解压到指定目录下。
-n解压时并不覆盖已经存在的文件。
-o解压时覆盖已经存在的文件,并且无需用户确认。
-v查看压缩文件的详细信息,包括压缩文件中包含的文件大小、文件名以及压缩比等,但并不做解压操作。
-t测试压缩文件有无损坏,但并不解压。
-x 文件列表解压文件,但不包含文件列表中指定的文件。
# 解压数据集 #使用 -d 选项手动指定解压缩位置 !unzip -o -q -d ~/work/ ~/data/data19736/ml-1m.zip
  1. import numpy as np
  2. usr_file = "./work/ml-1m/users.dat"
  3. # 打开文件,读取所有行到data
  4. with open(usr_file, 'r') as f:
  5. data = f.readlines()
  6. # 打印data的数据长度、第一条数据、数据类型
  7. print("data 数据长度是:",len(data))
  8. print("第一条数据是:", data[0])
  9. print("数据类型:", type(data[0]))
  1. <span style="color:#24292e"><span style="background-color:#ffffff"><span style="background-color:#f7f7f7">data 数据长度是: 6040
  2. 第一条数据是: 1::F::1::10::48067
  3. 数据类型: <class 'str'>
  4. </span></span></span>

观察以上结果,用户数据一共有6040条,以 :::: 分隔,是字符串类型。为了方便后续数据读取,区分用户的ID、年龄、职业等数据,一个简单的方式是将数据存储到字典中。

文本数据无法直接输入到神经网络中进行计算,所以需要将字符串类型的数据转换成数字类型。 另外,用户的性别F、M是字母数据,这里需要转换成数字表示。

我们定义如下函数实现字母转数字,将性别M、F转成数字0、1表示。

  1. def gender2num(gender):
  2. return 1 if gender == 'F' else 0
  3. print("性别M用数字 {} 表示".format(gender2num('M')))
  4. print("性别F用数字 {} 表示".format(gender2num('F')))
  1. <span style="color:#24292e"><span style="background-color:#ffffff"><span style="background-color:#f7f7f7">性别M用数字 0 表示
  2. 性别F用数字 1 表示
  3. </span></span></span>

接下来把用户数据的字符串类型的数据转成数字类型,并存储到字典中,实现如下:

  1. usr_info = {}
  2. max_usr_id = 0
  3. #按行索引数据
  4. for item in data:
  5. # 去除每一行中和数据无关的部分
  6. item = item.strip().split("::")
  7. usr_id = item[0]
  8. # 将字符数据转成数字并保存在字典中
  9. usr_info[usr_id] = {'usr_id': int(usr_id),
  10. 'gender': gender2num(item[1]),
  11. 'age': int(item[2]),
  12. 'job': int(item[3])}
  13. max_usr_id = max(max_usr_id, int(usr_id))
  14. print("用户ID为3的用户数据是:", usr_info['3'])
  1. <span style="color:#24292e"><span style="background-color:#ffffff"><span style="background-color:#f7f7f7">用户ID为3的用户数据是: {'usr_id': 3, 'gender': 0, 'age': 25, 'job': 15}
  2. </span></span></span>

至此,我们完成了用户数据的处理,完整的代码如下:

  1. import numpy as np
  2. def get_usr_info(path):
  3. # 性别转换函数,M-0, F-1
  4. def gender2num(gender):
  5. return 1 if gender == 'F' else 0
  6. # 打开文件,读取所有行到data
  7. with open(path, 'r') as f:
  8. data = f.readlines()
  9. # 建立用户信息的字典
  10. use_info = {}
  11. max_usr_id = 0
  12. #按行索引数据
  13. for item in data:
  14. #data是每一行组成的数组,item是每一行   #去除每一行中和数据无关的部分
  15. item = item.strip().split("::")
  16. usr_id = item[0]
  17. # 将字符数据转成数字并保存在字典中
  18. use_info[usr_id] = {'usr_id': int(usr_id),
  19. 'gender': gender2num(item[1]),
  20. 'age': int(item[2]),
  21. 'job': int(item[3])}
  22. max_usr_id = max(max_usr_id, int(usr_id))
  23. return use_info, max_usr_id
  24. usr_file = "./work/ml-1m/users.dat"
  25. usr_info, max_usr_id = get_usr_info(usr_file)
  26. print("用户数量:", len(usr_info))
  27. print("最大用户ID:", max_usr_id)
  28. print("第1个用户的信息是:", usr_info['1'])
  1. <span style="color:#24292e"><span style="background-color:#ffffff"><span style="background-color:#f7f7f7">用户数量: 6040
  2. 最大用户ID: 6040
  3. 第1个用户的信息是: {'usr_id': 1, 'gender': 1, 'age': 1, 'job': 10}
  4. </span></span></span>

从上面的结果可以得出,一共有6040个用户,其中ID为1的用户信息是{‘usr_id’: [1], ‘gender’: [1], ‘age’: [1], ‘job’: [10]},表示用户的性别序号是1(女),年龄序号是1(Under 18),职业序号是10(K-12 student),都已处理成数字类型。

2.电影数据处理

电影信息包含在movies.dat中,数据格式为:MovieID::Title::Genres,保存的格式与用户数据相同,每一行表示一条电影数据信息。

各数据对应关系如下:

数据类别数据说明数据示例
MovieID每个电影的数字代号1、2、3等序号
Title每个电影的名字和首映时间比如:Toy Story (1995)
Genres电影的种类,每个电影不止一个类别,不同类别以 | 隔开比如:Animation| Children’s|Comedy
包含的类别有:【Action,Adventure,Animation,Children’s,Comedy,Crime,Documentary,Drama,Fantasy,Film-Noir,Horror,Musical,Mystery,Romance,Sci-Fi,Thriller,War,Western】

首先,读取电影信息文件里的数据。需要注意的是,电影数据的存储方式和用户数据不同,在读取电影数据时,需要指定编码方式为"ISO-8859-1":

  1. movie_info_path = "./work/ml-1m/movies.dat"
  2. # 打开文件,编码方式选择ISO-8859-1,读取所有数据到data
  3. with open(movie_info_path, 'r', encoding="ISO-8859-1") as f:
  4. data = f.readlines()
  5. # 读取第一条数据,并打印
  6. item = data[0]
  7. print(item)
  8. item = item.strip().split("::")
  9. print("movie ID:", item[0])
  10. print("movie title:", item[1][:-7])
  11. print("movie year:", item[1][-5:-1])
  12. print("movie genre:", item[2].split('|'))
  1. <span style="color:#24292e"><span style="background-color:#ffffff"><span style="background-color:#f7f7f7">1::Toy Story (1995)::Animation|Children's|Comedy
  2. movie ID: 1
  3. movie title: Toy Story
  4. movie year: 1995
  5. movie genre: ['Animation', "Children's", 'Comedy']
  6. </span></span></span>

从上述代码,我们看出每条电影数据以 :::: 分隔,是字符串类型。类似处理用户数据的方式,需要将字符串类型的数据转换成数字类型,存储到字典中。 不同的是,在用户数据处理中,我们把性别数据M、F处理成0、1,而电影数据中Title和Genres都是长文本信息,为了便于后续神经网络计算,我们把其中每个单词都拆分出来,不同的单词用对应的数字序号指代。

所以,我们需要对这些数据进行如下处理:

  1. 统计电影ID信息。
  2. 统计电影名字的单词,并给每个单词一个数字序号。
  3. 统计电影类别单词,并给每个单词一个数字序号。
  4. 保存电影数据到字典中,方便根据电影ID进行索引。

实现方法如下:

① 统计电影ID信息

将电影ID信息存到字典中,并获得电影ID的最大值。

  1. movie_info_path = "./work/ml-1m/movies.dat"
  2. # 打开文件,编码方式选择ISO-8859-1,读取所有数据到data
  3. with open(movie_info_path, 'r', encoding="ISO-8859-1") as f:
  4. data = f.readlines()
  5. movie_info = {}
  6. for item in data:
  7. item = item.strip().split("::")
  8. # 获得电影的ID信息
  9. v_id = item[0]
  10. movie_info[v_id] = {'mov_id': int(v_id)}
  11. max_id = max([movie_info[k]['mov_id'] for k in movie_info.keys()])
  12. print("电影的最大ID是:", max_id)
  1. <span style="color:#24292e"><span style="background-color:#ffffff"><span style="background-color:#f7f7f7">电影的最大ID是: 3952
  2. </span></span></span>

②统计电影名字的单词,并给每个单词一个数字序号

不同于用户数据,电影数据中包含文字数据,但神经网络模型无法直接处理文本数据。我们可以借助自然语言处理中word embedding的方式完成文本到数字向量之间的转换。按照word embedding的步骤,需要先将每个单词用数字代替,然后利用embedding的方法完成数字到映射向量之间的转换。此处,我们只需要先完成文本到数字的转换,即把电影名称的单词用数字代替。在读取电影数据的同时,统计出现过的单词,从数字 1 开始对不同单词进行编码。

  1. # 用于记录电影title每个单词对应哪个序号
  2. movie_titles = {}
  3. #记录电影名字包含的单词最大数量
  4. max_title_length = 0
  5. # 对不同的单词从1 开始计数
  6. t_count = 1
  7. # 按行读取数据并处理
  8. for item in data:
  9. item = item.strip().split("::")
  10. # 1. 获得电影的ID信息
  11. v_id = item[0]
  12. v_title = item[1][:-7] # 去掉title中年份数据
  13. v_year = item[1][-5:-1]
  14. titles = v_title.split()
  15. # 获得title最大长度
  16. max_title_length = max((max_title_length, len(titles)))
  17. # 2. 统计电影名字的单词,并给每个单词一个序号,放在movie_titles中
  18. for t in titles:
  19. if t not in movie_titles:
  20. movie_titles[t] = t_count
  21. t_count += 1
  22. v_tit = [movie_titles[k] for k in titles]
  23. # 保存电影ID数据和title数据到字典中
  24. movie_info[v_id] = {'mov_id': int(v_id),
  25. 'title': v_tit,
  26. 'years': int(v_year)}
  27. print("最大电影title长度是:", max_title_length)
  28. ID = 1
  29. # 读取第一条数据,并打印
  30. item = data[0]
  31. item = item.strip().split("::")
  32. print("电影 ID:", item[0])
  33. print("电影 title:", item[1][:-7])
  34. print("ID为1 的电影数据是:", movie_info['1'])
  1. <span style="color:#24292e"><span style="background-color:#ffffff"><span style="background-color:#f7f7f7">最大电影title长度是: 15
  2. 电影 ID: 1
  3. 电影 title: Toy Story
  4. ID为1 的电影数据是: {'mov_id': 1, 'title': [1, 2], 'years': 1995}
  5. </span></span></span>

考虑到年份对衡量两个电影的相似度没有很大的影响,后续神经网络处理时,并不使用年份数据。

③统计电影类别的单词,并给每个单词一个数字序号

参考处理电影名字的方法处理电影类别,给不同类别的单词不同数字序号。

  1. # 用于记录电影类别每个单词对应哪个序号
  2. movie_titles, movie_cat = {}, {}
  3. max_title_length = 0
  4. max_cat_length = 0
  5. t_count, c_count = 1, 1
  6. # 按行读取数据并处理
  7. for item in data:
  8. item = item.strip().split("::")
  9. # 1. 获得电影的ID信息
  10. v_id = item[0]
  11. cats = item[2].split('|')
  12. # 获得电影类别数量的最大长度
  13. max_cat_length = max((max_cat_length, len(cats)))
  14. v_cat = item[2].split('|')
  15. # 3. 统计电影类别单词,并给每个单词一个序号,放在movie_cat中
  16. for cat in cats:
  17. if cat not in movie_cat:
  18. movie_cat[cat] = c_count
  19. c_count += 1
  20. v_cat = [movie_cat[k] for k in v_cat]
  21. # 保存电影ID数据和title数据到字典中
  22. movie_info[v_id] = {'mov_id': int(v_id),
  23. 'category': v_cat}
  24. print("电影类别数量最多是:", max_cat_length)
  25. ID = 1
  26. # 读取第一条数据,并打印
  27. item = data[0]
  28. item = item.strip().split("::")
  29. print("电影 ID:", item[0])
  30. print("电影种类 category:", item[2].split('|'))
  31. print("ID为1 的电影数据是:", movie_info['1'])
  1. <span style="color:#24292e"><span style="background-color:#ffffff"><span style="background-color:#f7f7f7">电影类别数量最多是: 6
  2. 电影 ID: 1
  3. 电影种类 category: ['Animation', "Children's", 'Comedy']
  4. ID为1 的电影数据是: {'mov_id': 1, 'category': [1, 2, 3]}
  5. </span></span></span>

④电影类别和电影名称定长填充,并保存所有电影数据到字典中

在保存电影数据到字典前,值得注意的是,由于每个电影名字和类别的单词数量不一样,转换成数字表示时,还需要通过补0将其补全成固定数据长度。原因是这些数据作为神经网络的输入,其维度影响了第一层网络的权重维度初始化,这要求输入数据的维度是定长的,而不是变长的,所以通过补0使其变为定长输入。补0并不会影响神经网络运算的最终结果

从上面两小节我们已知:最大电影名字长度是15,最大电影类别长度是6,15和6分别表示电影名字、种类包含的最多单词数量。因此我们通过补0使电影名字的列表长度为15,使电影种类的列表长度补齐为6。实现如下:

  1. # 建立三个字典,分别存放电影ID、名字和类别
  2. movie_info, movie_titles, movie_cat = {}, {}, {}
  3. # 对电影名字、类别中不同的单词从 1 开始标号
  4. t_count, c_count = 1, 1
  5. count_tit = {}
  6. # 按行读取数据并处理
  7. for item in data:
  8. item = item.strip().split("::")
  9. # 1. 获得电影的ID信息
  10. v_id = item[0]
  11. v_title = item[1][:-7] # 去掉title中年份数据
  12. cats = item[2].split('|')
  13. v_year = item[1][-5:-1]
  14. titles = v_title.split()
  15. # 2. 统计电影名字的单词,并给每个单词一个序号,放在movie_titles中
  16. for t in titles:
  17. if t not in movie_titles:
  18. movie_titles[t] = t_count
  19. t_count += 1
  20. # 3. 统计电影类别单词,并给每个单词一个序号,放在movie_cat中
  21. for cat in cats:
  22. if cat not in movie_cat:
  23. movie_cat[cat] = c_count
  24. c_count += 1
  25. # 补0使电影名称对应的列表长度为15
  26. v_tit = [movie_titles[k] for k in titles]
  27. while len(v_tit)<15:
  28. v_tit.append(0)
  29. # 补0使电影种类对应的列表长度为6
  30. v_cat = [movie_cat[k] for k in cats]
  31. while len(v_cat)<6:
  32. v_cat.append(0)
  33. # 4. 保存电影数据到movie_info中
  34. movie_info[v_id] = {'mov_id': int(v_id),
  35. 'title': v_tit,
  36. 'category': v_cat,
  37. 'years': int(v_year)}
  38. print("电影数据数量:", len(movie_info))
  39. ID = 2
  40. print("原始的电影ID为 {} 的数据是:".format(ID), data[ID-1])
  41. print("电影ID为 {} 的转换后数据是:".format(ID), movie_info[str(ID)])
  1. <span style="color:#24292e"><span style="background-color:#ffffff"><span style="background-color:#f7f7f7">电影数据数量: 3883
  2. 原始的电影ID为 2 的数据是: 2::Jumanji (1995)::Adventure|Children's|Fantasy
  3. 电影ID为 2 的转换后数据是: {'mov_id': 2, 'title': [3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'category': [4, 2, 5, 0, 0, 0], 'years': 1995}
  4. </span></span></span>

完整的电影数据处理代码如下:

  1. def get_movie_info(path):
  2. # 打开文件,编码方式选择ISO-8859-1,读取所有数据到data
  3. with open(path, 'r', encoding="ISO-8859-1") as f:
  4. data = f.readlines()
  5. # 建立三个字典,分别用户存放电影所有信息,电影的名字信息、类别信息
  6. movie_info, movie_titles, movie_cat = {}, {}, {}
  7. # 对电影名字、类别中不同的单词计数
  8. t_count, c_count = 1, 1
  9. # 初始化电影名字和种类的列表
  10. titles = []
  11. cats = []
  12. count_tit = {}
  13. # 按行读取数据并处理
  14. for item in data:
  15. item = item.strip().split("::")
  16. v_id = item[0]
  17. v_title = item[1][:-7]
  18. cats = item[2].split('|')
  19. v_year = item[1][-5:-1]
  20. titles = v_title.split()
  21. # 统计电影名字的单词,并给每个单词一个序号,放在movie_titles中
  22. for t in titles:
  23. if t not in movie_titles:
  24. movie_titles[t] = t_count
  25. t_count += 1
  26. # 统计电影类别单词,并给每个单词一个序号,放在movie_cat中
  27. for cat in cats:
  28. if cat not in movie_cat:
  29. movie_cat[cat] = c_count
  30. c_count += 1
  31. # 补0使电影名称对应的列表长度为15
  32. v_tit = [movie_titles[k] for k in titles]
  33. while len(v_tit)<15:
  34. v_tit.append(0)
  35. # 补0使电影种类对应的列表长度为6
  36. v_cat = [movie_cat[k] for k in cats]
  37. while len(v_cat)<6:
  38. v_cat.append(0)
  39. # 保存电影数据到movie_info中
  40. movie_info[v_id] = {'mov_id': int(v_id),
  41. 'title': v_tit,
  42. 'category': v_cat,
  43. 'years': int(v_year)}
  44. return movie_info, movie_cat, movie_titles
  45. movie_info_path = "./work/ml-1m/movies.dat"
  46. movie_info, movie_cat, movie_titles = get_movie_info(movie_info_path)
  47. print("电影数量:", len(movie_info))
  48. ID = 1
  49. print("原始的电影ID为 {} 的数据是:".format(ID), data[ID-1])
  50. print("电影ID为 {} 的转换后数据是:".format(ID), movie_info[str(ID)])
  51. print("电影种类对应序号:'Animation':{} 'Children's':{} 'Comedy':{}".format(movie_cat['Animation'],
  52. movie_cat["Children's"],
  53. movie_cat['Comedy']))
  54. print("电影名称对应序号:'The':{} 'Story':{} ".format(movie_titles['The'], movie_titles['Story']))
  1. <span style="color:#24292e"><span style="background-color:#ffffff"><span style="background-color:#f7f7f7">电影数量: 3883
  2. 原始的电影ID为 1 的数据是: 1::Toy Story (1995)::Animation|Children's|Comedy
  3. 电影ID为 1 的转换后数据是: {'mov_id': 1, 'title': [1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'category': [1, 2, 3, 0, 0, 0], 'years': 1995}
  4. 电影种类对应序号:'Animation':1 'Children's':2 'Comedy':3
  5. 电影名称对应序号:'The':26 'Story':2
  6. </span></span></span>

从上面的结果来看,ml-1m数据集中一共有3883个不同的电影,每个电影信息包含电影ID、电影名称、电影类别,均已处理成数字形式。

3.评分数据处理

有了用户数据和电影数据后,还需要获得用户对电影的评分数据,ml-1m数据集的评分数据在ratings.dat文件中。评分数据格式为UserID::MovieID::Rating::Timestamp,如下图。

这份数据很容易理解,如1::1193::5::978300760 表示ID为1的用户对电影ID为1193的评分是5。

978300760表示Timestamp数据,是标注数据时记录的时间信息,对当前任务来说是没有作用的数据,可以忽略这部分信息。

接下来,读取评分文件里的数据:

  1. use_poster = False
  2. if use_poster:
  3. rating_path = "./work/ml-1m/new_rating.txt"
  4. else:
  5. rating_path = "./work/ml-1m/ratings.dat"
  6. # 打开文件,读取所有行到data
  7. with open(rating_path, 'r') as f:
  8. data = f.readlines()
  9. # 打印data的数据长度,以及第一条数据中的用户ID、电影ID和评分信息
  10. item = data[0]
  11. print(item)
  12. item = item.strip().split("::")
  13. usr_id,movie_id,score = item[0],item[1],item[2]
  14. print("评分数据条数:", len(data))
  15. print("用户ID:", usr_id)
  16. print("电影ID:", movie_id)
  17. print("用户对电影的评分:", score)
  1. <span style="color:#24292e"><span style="background-color:#ffffff"><span style="background-color:#f7f7f7">1::1193::5::978300760
  2. 评分数据条数: 1000209
  3. 用户ID: 1
  4. 电影ID: 1193
  5. 用户对电影的评分: 5
  6. </span></span></span>

从以上统计结果来看,一共有1000209条评分数据。电影评分数据不包含文本信息,可以将数据直接存到字典中。

下面我们将评分数据封装到get_rating_info()函数中,并返回评分数据的信息。

  1. def get_rating_info(path):
  2. # 打开文件,读取所有行到data
  3. with open(path, 'r') as f:
  4. data = f.readlines()
  5. # 创建一个字典
  6. rating_info = {}
  7. for item in data:
  8. item = item.strip().split("::")
  9. # 处理每行数据,分别得到用户ID,电影ID,和评分
  10. usr_id,movie_id,score = item[0],item[1],item[2]
  11. if usr_id not in rating_info.keys():
  12. rating_info[usr_id] = {movie_id:float(score)}
  13. else:
  14. rating_info[usr_id][movie_id] = float(score)
  15. return rating_info
  16. # 获得评分数据
  17. #rating_path = "./work/ml-1m/ratings.dat"
  18. rating_info = get_rating_info(rating_path)
  19. print("ID为1的用户一共评价了{}个电影".format(len(rating_info['1'])))
  1. <span style="color:#24292e"><span style="background-color:#ffffff"><span style="background-color:#f7f7f7">ID为1的用户一共评价了53个电影
  2. </span></span></span>

4.海报图像读取

在电影海报的数据文件夹下,海报的图片文件以"mov_id" + 电影ID + ".jpg"的方式命名。因此,我们可以用电影ID去索引对应的海报图像。

我们可以从新的评分数据文件 new_rating.txt(注意部分原rating数据中的电影由于获取不到海报图片,所以进行了过滤处理)中获取到电影ID,进而索引到图像,实现如下:

  1. from PIL import Image
  2. import matplotlib.pyplot as plt
  3. # 使用海报图像和不使用海报图像的文件路径不同,处理方式相同
  4. use_poster = True
  5. if use_poster:
  6. rating_path = "./work/ml-1m/new_rating.txt"
  7. else:
  8. rating_path = "./work/ml-1m/ratings.dat"
  9. with open(rating_path, 'r') as f:
  10. data = f.readlines()
  11. # 从新的rating文件中收集所有的电影ID
  12. mov_id_collect = []
  13. for item in data:
  14. item = item.strip().split("::")
  15. usr_id,movie_id,score = item[0],item[1],item[2]
  16. mov_id_collect.append(movie_id)
  17. # 根据电影ID读取图像
  18. poster_path = "./work/ml-1m/posters/"
  19. # 显示mov_id_collect中第几个电影ID的图像
  20. idx = 1
  21. poster = Image.open(poster_path+'mov_id{}.jpg'.format(str(mov_id_collect[idx])))
  22. plt.figure("Image") # 图像窗口名称
  23. plt.imshow(poster)
  24. plt.axis('on') # 关掉坐标轴为 off
  25. plt.title("poster with ID {}".format(mov_id_collect[idx])) # 图像题目
  26. plt.show()
<span style="color:#24292e"><span style="background-color:#ffffff"><span style="background-color:#f7f7f7"><Figure size 640x480 with 1 Axes></span></span></span>

5.构建数据读取器

至此我们已经分别处理了用户、电影和评分数据,接下来我们要利用这些处理好的数据,构建一个数据读取器,方便在训练神经网络时直接调用。

首先,构造一个函数,把读取并处理后的数据整合到一起,即在rating数据中补齐用户和电影的所有特征字段。

  1. def get_dataset(usr_info, rating_info, movie_info):
  2. trainset = []
  3. # 按照评分数据的key值索引数据
  4. for usr_id in rating_info.keys():
  5. usr_ratings = rating_info[usr_id]
  6. for movie_id in usr_ratings:
  7. trainset.append({'usr_info': usr_info[usr_id],
  8. 'mov_info': movie_info[movie_id],
  9. 'scores': usr_ratings[movie_id]})
  10. return trainset
  11. dataset = get_dataset(usr_info, rating_info, movie_info)
  12. print("数据集总数据数:", len(dataset))
  1. <span style="color:#24292e"><span style="background-color:#ffffff"><span style="background-color:#f7f7f7">数据集总数据数: 1000209
  2. </span></span></span>

接下来构建数据读取器函数load_data(),先看一下整体结构:

  1. import random
  2. def load_data(dataset=None, mode='train'):
  3. """定义一些超参数等等"""
  4. # 定义数据迭代加载器
  5. def data_generator():
  6. """ 定义数据的处理过程"""
  7. data = None
  8. yield data
  9. # 返回数据迭代加载器
  10. return data_generator

我们来看一下完整的数据读取器函数实现,核心是将多个样本数据合并到一个列表(batch),当该列表达到batchsize后,以yield的方式返回(Python数据迭代器)

在进行批次数据拼合的同时,完成数据格式和数据尺寸的转换:

  • 由于飞桨框架的网络接入层要求将数据先转换成np.array的类型,再转换成框架内置变量tensor的类型。所以在数据返回前,需将所有数据均转换成np.array的类型,方便后续处理。
  • 每个特征字段的尺寸也需要根据网络输入层的设计进行调整。根据之前的分析,用户和电影的所有原始特征可以分为四类,ID类(用户ID,电影ID,性别,年龄,职业)、列表类(电影类别)、文本类(电影名称)和图像类(电影海报)。因为每种特征后续接入的网络层方案不同,所以要求他们的数据尺寸也不同。这里我们先初步的了解即可,待后续阅读了模型设计章节后,将对输入输出尺寸有更好的理解。

数据尺寸的说明:

  1. ID类(用户ID,电影ID,性别,年龄,职业)处理成(256)的尺寸,以便后续接入Embedding层,数值256是batchsize。
  2. 列表类(电影类别)处理成(256,6)的尺寸,数值6是电影最多的类别个数,以便后续接入全连接层。
  3. 文本类(电影名称)处理成(256,1,15)的尺寸,15是电影名称的最多单词数,以便接入2D卷积层。2D卷积层要求输入数据为四维,对应到图像处理的场景,各个维度的含义是【批次大小,通道数、图像的长、图像的宽】,其中RGB的彩色图像是3通道,灰度图像是单通道。在此处理文本的场景,我们使用2D卷积层需要将输入处理成其所需要的维度数量。因为embedding函数会在输入Tensor shape的最后一维后面添加embedding_dim的维度做为输出的Shape,即当输入为(256,1,15),嵌入向量大小为32时,embedding函数会输出(256,1,15,32),这正好是2D卷积层所需要维度数量。
  4. 图像类(电影海报)处理成(256,3,64,64)的尺寸, 以便接入2D卷积层。图像的原始尺寸是180*270彩色图像,使用resize函数压缩成64*64的尺寸,减少网络计算。
  1. import random
  2. use_poster = False
  3. def load_data(dataset=None, mode='train'):
  4. # 定义数据迭代Batch大小
  5. BATCHSIZE = 256
  6. data_length = len(dataset)
  7. index_list = list(range(data_length))
  8. # 定义数据迭代加载器
  9. def data_generator():
  10. # 训练模式下,打乱训练数据
  11. if mode == 'train':
  12. random.shuffle(index_list)
  13. # 声明每个特征的列表
  14. usr_id_list,usr_gender_list,usr_age_list,usr_job_list = [], [], [], []
  15. mov_id_list,mov_tit_list,mov_cat_list,mov_poster_list = [], [], [], []
  16. score_list = []
  17. # 索引遍历输入数据集
  18. for idx, i in enumerate(index_list):
  19. # 获得特征数据保存到对应特征列表中
  20. usr_id_list.append(dataset[i]['usr_info']['usr_id'])
  21. usr_gender_list.append(dataset[i]['usr_info']['gender'])
  22. usr_age_list.append(dataset[i]['usr_info']['age'])
  23. usr_job_list.append(dataset[i]['usr_info']['job'])
  24. mov_id_list.append(dataset[i]['mov_info']['mov_id'])
  25. mov_tit_list.append(dataset[i]['mov_info']['title'])
  26. mov_cat_list.append(dataset[i]['mov_info']['category'])
  27. mov_id = dataset[i]['mov_info']['mov_id']
  28. if use_poster:
  29. # 不使用图像特征时,不读取图像数据,加快数据读取速度
  30. poster = Image.open(poster_path+'mov_id{}.jpg'.format(str(mov_id)))
  31. poster = poster.resize([64, 64])
  32. if len(poster.size) <= 2:
  33. poster = poster.convert("RGB")
  34. mov_poster_list.append(np.array(poster))
  35. score_list.append(int(dataset[i]['scores']))
  36. # 如果读取的数据量达到当前的batch大小,就返回当前批次
  37. if len(usr_id_list)==BATCHSIZE:
  38. # 转换列表数据为数组形式,reshape到固定形状
  39. usr_id_arr = np.array(usr_id_list)
  40. usr_gender_arr = np.array(usr_gender_list)
  41. usr_age_arr = np.array(usr_age_list)
  42. usr_job_arr = np.array(usr_job_list)
  43. mov_id_arr = np.array(mov_id_list)
  44. mov_cat_arr = np.reshape(np.array(mov_cat_list), [BATCHSIZE, 6]).astype(np.int64)
  45. mov_tit_arr = np.reshape(np.array(mov_tit_list), [BATCHSIZE, 1, 15]).astype(np.int64)
  46. if use_poster:
  47. mov_poster_arr = np.reshape(np.array(mov_poster_list)/127.5 - 1, [BATCHSIZE, 3, 64, 64]).astype(np.float32)
  48. else:
  49. mov_poster_arr = np.array([0.])
  50. scores_arr = np.reshape(np.array(score_list), [-1, 1]).astype(np.float32)
  51. # 返回当前批次数据
  52. yield [usr_id_arr, usr_gender_arr, usr_age_arr, usr_job_arr], \
  53. [mov_id_arr, mov_cat_arr, mov_tit_arr, mov_poster_arr], scores_arr
  54. # 清空数据
  55. usr_id_list, usr_gender_list, usr_age_list, usr_job_list = [], [], [], []
  56. mov_id_list, mov_tit_list, mov_cat_list, score_list = [], [], [], []
  57. mov_poster_list = []
  58. return data_generator

load_data()函数通过输入的数据集,处理数据并返回一个数据迭代器。

我们将数据集按照8:2的比例划分训练集和验证集,可以分别得到训练数据迭代器和验证数据迭代器。

  1. dataset = get_dataset(usr_info, rating_info, movie_info)
  2. print("数据集总数量:", len(dataset))
  3. trainset = dataset[:int(0.8*len(dataset))]
  4. train_loader = load_data(trainset, mode="train")
  5. print("训练集数量:", len(trainset))
  6. validset = dataset[int(0.8*len(dataset)):]
  7. valid_loader = load_data(validset, mode='valid')
  8. print("验证集数量:", len(validset))
  1. <span style="color:#24292e"><span style="background-color:#ffffff"><span style="background-color:#f7f7f7">数据集总数量: 1000209
  2. 训练集数量: 800167
  3. 验证集数量: 200042
  4. </span></span></span>

数据迭代器的使用方式如下:

  1. for idx, data in enumerate(train_loader()):
  2. usr_data, mov_data, score = data
  3. usr_id_arr, usr_gender_arr, usr_age_arr, usr_job_arr = usr_data
  4. mov_id_arr, mov_cat_arr, mov_tit_arr, mov_poster_arr = mov_data
  5. print("用户ID数据尺寸", usr_id_arr.shape)
  6. print("电影ID数据尺寸", mov_id_arr.shape, ", 电影类别genres数据的尺寸", mov_cat_arr.shape, ", 电影名字title的尺寸", mov_tit_arr.shape)
  7. break
  1. <span style="color:#24292e"><span style="background-color:#ffffff"><span style="background-color:#f7f7f7">用户ID数据尺寸 (256,)
  2. 电影ID数据尺寸 (256,) , 电影类别genres数据的尺寸 (256, 6) , 电影名字title的尺寸 (256, 1, 15)
  3. </span></span></span>

6.数据处理完整代码

到这里,我们已完成了ml-1m数据读取和处理,接下来,我们将数据处理的代码封装到一个Python类中,完整实现如下:

  1. import random
  2. import numpy as np
  3. from PIL import Image
  4. class MovieLen(object):
  5. def __init__(self, use_poster):
  6. self.use_poster = use_poster
  7. # 声明每个数据文件的路径
  8. usr_info_path = "./work/ml-1m/users.dat"
  9. if use_poster:
  10. rating_path = "./work/ml-1m/new_rating.txt"
  11. else:
  12. rating_path = "./work/ml-1m/ratings.dat"
  13. movie_info_path = "./work/ml-1m/movies.dat"
  14. self.poster_path = "./work/ml-1m/posters/"
  15. # 得到电影数据
  16. self.movie_info, self.movie_cat, self.movie_title = self.get_movie_info(movie_info_path)
  17. # 记录电影的最大ID
  18. self.max_mov_cat = np.max([self.movie_cat[k] for k in self.movie_cat])
  19. self.max_mov_tit = np.max([self.movie_title[k] for k in self.movie_title])
  20. self.max_mov_id = np.max(list(map(int, self.movie_info.keys())))
  21. # 记录用户数据的最大ID
  22. self.max_usr_id = 0
  23. self.max_usr_age = 0
  24. self.max_usr_job = 0
  25. # 得到用户数据
  26. self.usr_info = self.get_usr_info(usr_info_path)
  27. # 得到评分数据
  28. self.rating_info = self.get_rating_info(rating_path)
  29. # 构建数据集
  30. self.dataset = self.get_dataset(usr_info=self.usr_info,
  31. rating_info=self.rating_info,
  32. movie_info=self.movie_info)
  33. # 划分数据集,获得数据加载器
  34. self.train_dataset = self.dataset[:int(len(self.dataset)*0.9)]
  35. self.valid_dataset = self.dataset[int(len(self.dataset)*0.9):]
  36. print("##Total dataset instances: ", len(self.dataset))
  37. print("##MovieLens dataset information: \nusr num: {}\n"
  38. "movies num: {}".format(len(self.usr_info),len(self.movie_info)))
  39. # 得到电影数据
  40. def get_movie_info(self, path):
  41. # 打开文件,编码方式选择ISO-8859-1,读取所有数据到data
  42. with open(path, 'r', encoding="ISO-8859-1") as f:
  43. data = f.readlines()
  44. # 建立三个字典,分别用户存放电影所有信息,电影的名字信息、类别信息
  45. movie_info, movie_titles, movie_cat = {}, {}, {}
  46. # 对电影名字、类别中不同的单词计数
  47. t_count, c_count = 1, 1
  48. count_tit = {}
  49. # 按行读取数据并处理
  50. for item in data:
  51. item = item.strip().split("::")
  52. v_id = item[0]
  53. v_title = item[1][:-7]
  54. cats = item[2].split('|')
  55. v_year = item[1][-5:-1]
  56. titles = v_title.split()
  57. # 统计电影名字的单词,并给每个单词一个序号,放在movie_titles中
  58. for t in titles:
  59. if t not in movie_titles:
  60. movie_titles[t] = t_count
  61. t_count += 1
  62. # 统计电影类别单词,并给每个单词一个序号,放在movie_cat中
  63. for cat in cats:
  64. if cat not in movie_cat:
  65. movie_cat[cat] = c_count
  66. c_count += 1
  67. # 补0使电影名称对应的列表长度为15
  68. v_tit = [movie_titles[k] for k in titles]
  69. while len(v_tit)<15:
  70. v_tit.append(0)
  71. # 补0使电影种类对应的列表长度为6
  72. v_cat = [movie_cat[k] for k in cats]
  73. while len(v_cat)<6:
  74. v_cat.append(0)
  75. # 保存电影数据到movie_info中
  76. movie_info[v_id] = {'mov_id': int(v_id),
  77. 'title': v_tit,
  78. 'category': v_cat,
  79. 'years': int(v_year)}
  80. return movie_info, movie_cat, movie_titles
  81. def get_usr_info(self, path):
  82. # 性别转换函数,M-0, F-1
  83. def gender2num(gender):
  84. return 1 if gender == 'F' else 0
  85. # 打开文件,读取所有行到data
  86. with open(path, 'r') as f:
  87. data = f.readlines()
  88. # 建立用户信息的字典
  89. use_info = {}
  90. max_usr_id = 0
  91. #按行索引数据
  92. for item in data:
  93. # 去除每一行中和数据无关的部分
  94. item = item.strip().split("::")
  95. usr_id = item[0]
  96. # 将字符数据转成数字并保存在字典中
  97. use_info[usr_id] = {'usr_id': int(usr_id),
  98. 'gender': gender2num(item[1]),
  99. 'age': int(item[2]),
  100. 'job': int(item[3])}
  101. self.max_usr_id = max(self.max_usr_id, int(usr_id))
  102. self.max_usr_age = max(self.max_usr_age, int(item[2]))
  103. self.max_usr_job = max(self.max_usr_job, int(item[3]))
  104. return use_info
  105. # 得到评分数据
  106. def get_rating_info(self, path):
  107. # 读取文件里的数据
  108. with open(path, 'r') as f:
  109. data = f.readlines()
  110. # 将数据保存在字典中并返回
  111. rating_info = {}
  112. for item in data:
  113. item = item.strip().split("::")
  114. usr_id,movie_id,score = item[0],item[1],item[2]
  115. if usr_id not in rating_info.keys():
  116. rating_info[usr_id] = {movie_id:float(score)}
  117. else:
  118. rating_info[usr_id][movie_id] = float(score)
  119. return rating_info
  120. # 构建数据集
  121. def get_dataset(self, usr_info, rating_info, movie_info):
  122. trainset = []
  123. for usr_id in rating_info.keys():
  124. usr_ratings = rating_info[usr_id]
  125. for movie_id in usr_ratings:
  126. trainset.append({'usr_info': usr_info[usr_id],
  127. 'mov_info': movie_info[movie_id],
  128. 'scores': usr_ratings[movie_id]})
  129. return trainset
  130. def load_data(self, dataset=None, mode='train'):
  131. use_poster = False
  132. # 定义数据迭代Batch大小
  133. BATCHSIZE = 256
  134. data_length = len(dataset)
  135. index_list = list(range(data_length))
  136. # 定义数据迭代加载器
  137. def data_generator():
  138. # 训练模式下,打乱训练数据
  139. if mode == 'train':
  140. random.shuffle(index_list)
  141. # 声明每个特征的列表
  142. usr_id_list,usr_gender_list,usr_age_list,usr_job_list = [], [], [], []
  143. mov_id_list,mov_tit_list,mov_cat_list,mov_poster_list = [], [], [], []
  144. score_list = []
  145. # 索引遍历输入数据集
  146. for idx, i in enumerate(index_list):
  147. # 获得特征数据保存到对应特征列表中
  148. usr_id_list.append(dataset[i]['usr_info']['usr_id'])
  149. usr_gender_list.append(dataset[i]['usr_info']['gender'])
  150. usr_age_list.append(dataset[i]['usr_info']['age'])
  151. usr_job_list.append(dataset[i]['usr_info']['job'])
  152. mov_id_list.append(dataset[i]['mov_info']['mov_id'])
  153. mov_tit_list.append(dataset[i]['mov_info']['title'])
  154. mov_cat_list.append(dataset[i]['mov_info']['category'])
  155. mov_id = dataset[i]['mov_info']['mov_id']
  156. if use_poster:
  157. # 不使用图像特征时,不读取图像数据,加快数据读取速度
  158. poster = Image.open(self.poster_path+'mov_id{}.jpg'.format(str(mov_id[0])))
  159. poster = poster.resize([64, 64])
  160. if len(poster.size) <= 2:
  161. poster = poster.convert("RGB")
  162. mov_poster_list.append(np.array(poster))
  163. score_list.append(int(dataset[i]['scores']))
  164. # 如果读取的数据量达到当前的batch大小,就返回当前批次
  165. if len(usr_id_list)==BATCHSIZE:
  166. # 转换列表数据为数组形式,reshape到固定形状
  167. usr_id_arr = np.array(usr_id_list)
  168. usr_gender_arr = np.array(usr_gender_list)
  169. usr_age_arr = np.array(usr_age_list)
  170. usr_job_arr = np.array(usr_job_list)
  171. mov_id_arr = np.array(mov_id_list)
  172. mov_cat_arr = np.reshape(np.array(mov_cat_list), [BATCHSIZE, 6]).astype(np.int64)
  173. mov_tit_arr = np.reshape(np.array(mov_tit_list), [BATCHSIZE, 1, 15]).astype(np.int64)
  174. if use_poster:
  175. mov_poster_arr = np.reshape(np.array(mov_poster_list)/127.5 - 1, [BATCHSIZE, 3, 64, 64]).astype(np.float32)
  176. else:
  177. mov_poster_arr = np.array([0.])
  178. scores_arr = np.reshape(np.array(score_list), [-1, 1]).astype(np.float32)
  179. # 放回当前批次数据
  180. yield [usr_id_arr, usr_gender_arr, usr_age_arr, usr_job_arr], \
  181. [mov_id_arr, mov_cat_arr, mov_tit_arr, mov_poster_arr], scores_arr
  182. # 清空数据
  183. usr_id_list, usr_gender_list, usr_age_list, usr_job_list = [], [], [], []
  184. mov_id_list, mov_tit_list, mov_cat_list, score_list = [], [], [], []
  185. mov_poster_list = []
  186. return data_generator
  187. # 声明数据读取类
  188. dataset = MovieLen(False)
  189. # 定义数据读取器
  190. train_loader = dataset.load_data(dataset=dataset.train_dataset, mode='train')
  191. # 迭代的读取数据, Batchsize = 256
  192. for idx, data in enumerate(train_loader()):
  193. usr, mov, score = data
  194. print("打印用户ID,性别,年龄,职业数据的维度:")
  195. for v in usr:
  196. print(v.shape)
  197. print("打印电影ID,名字,类别数据的维度:")
  198. for v in mov:
  199. print(v.shape)
  200. break
  1. <span style="color:#24292e"><span style="background-color:#ffffff"><span style="background-color:#f7f7f7">##Total dataset instances: 1000209
  2. ##MovieLens dataset information:
  3. usr num: 6040
  4. movies num: 3883
  5. 打印用户ID,性别,年龄,职业数据的维度:
  6. (256,)
  7. (256,)
  8. (256,)
  9. (256,)
  10. 打印电影ID,名字,类别数据的维度:
  11. (256,)
  12. (256, 6)
  13. (256, 1, 15)
  14. (1,)
  15. </span></span></span>

各数据处理前后格式如下:

数据分类输入数据样例输出数据样例
用户数据UserID::Gender::Age::Occupation
1::F::1::10
{‘usr_id’: 1, ‘gender’: 1, ‘age’: 1, ‘job’: 10}
电影数据MovieID::Title::Genres
2::Jumanji (1995)::Adventure|Children’s|Fantasy
{‘mov_id’: 2, ‘title’: [3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ‘category’: [4, 2, 5, 0, 0, 0]}
评分数据UserID::MovieID::Rating
1::1193::5
{‘usr_id’: 1, ‘mov_id’: 1193, ‘score’: 5}
海报数据“mov_id” + MovieID+".jpg"格式的图片64*64*3的像素矩阵

虽然我们将文本的数据转换成了数字表示形式,但是这些数据依然是离散的,不适合直接输入到神经网络中,还需要对其进行Embedding操作,将其映射为固定长度的向量。

接下来我们开始个性化电影推荐的第二个部分:模型设计。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/531478
推荐阅读
相关标签
  

闽ICP备14008679号