电影数据处理

    各数据对应关系如下:

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

    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进行索引。

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

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

    不同于用户数据,电影数据中包含文字数据,可是,神经网络模型是无法直接处理文本数据的,我们可以借助自然语言处理中word embedding的方式完成文本到数字向量之间的转换。按照word embedding的步骤,首先,需要将每个单词用数字代替,然后利用embedding的方法完成数字到映射向量之间的转换。此处数据处理中,我们只需要先完成文本到数字的转换。

    接下来,我们把电影名字的单词用数字代替。在读取电影数据的同时,统计不同的单词,从数字 1 开始对不同单词进行标号。

    1. 最大电影title长度是: 15
    2. 电影 ID: 1
    3. 电影 title: Toy Story
    4. ID1 的电影数据是: {'mov_id': 1, 'title': [1, 2], 'years': 1995}

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

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

    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. max_cat_length = max((max_cat_length, len(cats)))
    13. v_cat = item[2].split('|')
    14. # 3. 统计电影类别单词,并给每个单词一个序号,放在movie_cat中
    15. for cat in cats:
    16. if cat not in movie_cat:
    17. movie_cat[cat] = c_count
    18. c_count += 1
    19. # 保存电影ID数据和title数据到字典中
    20. movie_info[v_id] = {'mov_id': int(v_id),
    21. 'category': v_cat}
    22. print("电影类别数量最多是:", max_cat_length)
    23. ID = 1
    24. # 读取第一条数据,并打印
    25. item = data[0]
    26. item = item.strip().split("::")
    27. print("电影 ID:", item[0])
    28. print("电影种类 category:", item[2].split('|'))
    29. 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]}

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

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

    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. cats = item[2].split('|')
    18. v_year = item[1][-5:-1]
    19. titles = v_title.split()
    20. # 统计电影名字的单词,并给每个单词一个序号,放在movie_titles中
    21. if t not in movie_titles:
    22. movie_titles[t] = t_count
    23. t_count += 1
    24. # 统计电影类别单词,并给每个单词一个序号,放在movie_cat中
    25. for cat in cats:
    26. if cat not in movie_cat:
    27. movie_cat[cat] = c_count
    28. c_count += 1
    29. # 补0使电影名称对应的列表长度为15
    30. v_tit = [movie_titles[k] for k in titles]
    31. while len(v_tit)<15:
    32. v_tit.append(0)
    33. # 补0使电影种类对应的列表长度为6
    34. v_cat = [movie_cat[k] for k in cats]
    35. while len(v_cat)<6:
    36. v_cat.append(0)
    37. # 保存电影数据到movie_info中
    38. movie_info[v_id] = {'mov_id': int(v_id),
    39. 'title': v_tit,
    40. 'category': v_cat,
    41. 'years': int(v_year)}
    42. return movie_info, movie_cat, movie_titles
    43. movie_info_path = "./work/ml-1m/movies.dat"
    44. movie_info, movie_cat, movie_titles = get_movie_info(movie_info_path)
    45. print("电影数量:", len(movie_info))
    46. ID = 1
    47. print("原始的电影ID为 {} 的数据是:".format(ID), data[ID-1])
    48. print("电影ID为 {} 的转换后数据是:".format(ID), movie_info[str(ID)])
    49. print("电影种类对应序号:'Animation':{} 'Children's':{} 'Comedy':{}".format(movie_cat['Animation'],
    50. movie_cat["Children's"],
    51. movie_cat['Comedy']))
    52. 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

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