电影数据处理

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

电影数据处理 - 图1

各数据对应关系如下:

数据类别 数据说明 数据示例
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. 1::Toy Story (1995)::Animation|Children's|Comedy
  2.  
  3. movie ID: 1
  4. movie title: Toy Story
  5. movie year: 1995
  6. movie genre: ['Animation', "Children's", 'Comedy']

从上述代码,我们看出每条电影数据以

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

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

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

实现方法如下:

1. 统计电影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. 电影的最大ID是: 3952

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

不同于用户数据,电影数据中包含文字数据,可是,神经网络模型是无法直接处理文本数据的,我们可以借助自然语言处理中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. 最大电影title长度是: 15
  2. 电影 ID: 1
  3. 电影 title: Toy Story
  4. ID1 的电影数据是: {'mov_id': 1, 'title': [1, 2], 'years': 1995}

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

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

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

  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. 电影类别数量最多是: 6
  2. 电影 ID: 1
  3. 电影种类 category: ['Animation', "Children's", 'Comedy']
  4. ID1 的电影数据是: {'mov_id': 1, 'category': [1, 2, 3]}

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

在保存电影数据到字典前,值得注意的是,由于每个电影名字和类别的单词数量不一样,转换成数字表示时,还需要通过补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. 电影数据数量: 3883
  2. 原始的电影ID 2 的数据是: 2::Jumanji (1995)::Adventure|Children's|Fantasy
  3.  
  4. 电影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}

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

  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. 电影数量: 3883
  2. 原始的电影ID 1 的数据是: 1::Toy Story (1995)::Animation|Children's|Comedy
  3.  
  4. 电影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}
  5. 电影种类对应序号:'Animation':1 'Children's':2 'Comedy':3
  6. 电影名称对应序号:'The':26 'Story':2

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