电影数据处理
电影信息包含在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":
movie_info_path = "./work/ml-1m/movies.dat"
# 打开文件,编码方式选择ISO-8859-1,读取所有数据到data中
with open(movie_info_path, 'r', encoding="ISO-8859-1") as f:
data = f.readlines()
# 读取第一条数据,并打印
item = data[0]
print(item)
item = item.strip().split("::")
print("movie ID:", item[0])
print("movie title:", item[1][:-7])
print("movie year:", item[1][-5:-1])
print("movie genre:", item[2].split('|'))
- 1::Toy Story (1995)::Animation|Children's|Comedy
- movie ID: 1
- movie title: Toy Story
- movie year: 1995
- movie genre: ['Animation', "Children's", 'Comedy']
从上述代码,我们看出每条电影数据以
分隔,是字符串类型。类似处理用户数据的方式,需要将字符串类型的数据转换成数字类型,存储到字典中。 不同的是,在用户数据处理中,我们把性别数据M、F处理成0、1,而电影数据中Title和Genres都是长文本信息,为了便于后续神经网络计算,我们把其中每个单词都拆分出来,不同的单词用对应的数字序号指代。
所以,我们需要对这些数据进行如下处理:
- 统计电影ID信息。
- 统计电影名字的单词,并给每个单词一个数字序号。
- 统计电影类别单词,并给每个单词一个数字序号。
- 保存电影数据到字典中,方便根据电影ID进行索引。
实现方法如下:
1. 统计电影ID信息
将电影ID信息存到字典中,并获得电影ID的最大值。
movie_info_path = "./work/ml-1m/movies.dat"
# 打开文件,编码方式选择ISO-8859-1,读取所有数据到data中
with open(movie_info_path, 'r', encoding="ISO-8859-1") as f:
data = f.readlines()
movie_info = {}
for item in data:
item = item.strip().split("::")
# 获得电影的ID信息
v_id = item[0]
movie_info[v_id] = {'mov_id': int(v_id)}
max_id = max([movie_info[k]['mov_id'] for k in movie_info.keys()])
print("电影的最大ID是:", max_id)
- 电影的最大ID是: 3952
2. 统计电影名字的单词,并给每个单词一个数字序号
不同于用户数据,电影数据中包含文字数据,可是,神经网络模型是无法直接处理文本数据的,我们可以借助自然语言处理中word embedding的方式完成文本到数字向量之间的转换。按照word embedding的步骤,首先,需要将每个单词用数字代替,然后利用embedding的方法完成数字到映射向量之间的转换。此处数据处理中,我们只需要先完成文本到数字的转换。
接下来,我们把电影名字的单词用数字代替。在读取电影数据的同时,统计不同的单词,从数字 1 开始对不同单词进行标号。
# 用于记录电影title每个单词对应哪个序号
movie_titles = {}
#记录电影名字包含的单词最大数量
max_title_length = 0
# 对不同的单词从1 开始计数
t_count = 1
# 按行读取数据并处理
for item in data:
item = item.strip().split("::")
# 1. 获得电影的ID信息
v_id = item[0]
v_title = item[1][:-7] # 去掉title中年份数据
v_year = item[1][-5:-1]
titles = v_title.split()
# 获得title最大长度
max_title_length = max((max_title_length, len(titles)))
# 2. 统计电影名字的单词,并给每个单词一个序号,放在movie_titles中
for t in titles:
if t not in movie_titles:
movie_titles[t] = t_count
t_count += 1
v_tit = [movie_titles[k] for k in titles]
# 保存电影ID数据和title数据到字典中
movie_info[v_id] = {'mov_id': int(v_id),
'title': v_tit,
'years': int(v_year)}
print("最大电影title长度是:", max_title_length)
ID = 1
# 读取第一条数据,并打印
item = data[0]
item = item.strip().split("::")
print("电影 ID:", item[0])
print("电影 title:", item[1][:-7])
print("ID为1 的电影数据是:", movie_info['1'])
- 最大电影title长度是: 15
- 电影 ID: 1
- 电影 title: Toy Story
- ID为1 的电影数据是: {'mov_id': 1, 'title': [1, 2], 'years': 1995}
考虑到年份对衡量两个电影的相似度没有很大的影响,后续神经网络处理时,并不使用年份数据。
3. 统计电影类别的单词,并给每个单词一个数字序号
参考处理电影名字的方法处理电影类别,给不同类别的单词不同数字序号。
# 用于记录电影类别每个单词对应哪个序号
movie_titles, movie_cat = {}, {}
max_title_length = 0
max_cat_length = 0
t_count, c_count = 1, 1
# 按行读取数据并处理
for item in data:
item = item.strip().split("::")
# 1. 获得电影的ID信息
v_id = item[0]
cats = item[2].split('|')
# 获得电影类别数量的最大长度
max_cat_length = max((max_cat_length, len(cats)))
v_cat = item[2].split('|')
# 3. 统计电影类别单词,并给每个单词一个序号,放在movie_cat中
for cat in cats:
if cat not in movie_cat:
movie_cat[cat] = c_count
c_count += 1
v_cat = [movie_cat[k] for k in v_cat]
# 保存电影ID数据和title数据到字典中
movie_info[v_id] = {'mov_id': int(v_id),
'category': v_cat}
print("电影类别数量最多是:", max_cat_length)
ID = 1
# 读取第一条数据,并打印
item = data[0]
item = item.strip().split("::")
print("电影 ID:", item[0])
print("电影种类 category:", item[2].split('|'))
print("ID为1 的电影数据是:", movie_info['1'])
- 电影类别数量最多是: 6
- 电影 ID: 1
- 电影种类 category: ['Animation', "Children's", 'Comedy']
- ID为1 的电影数据是: {'mov_id': 1, 'category': [1, 2, 3]}
4. 电影类别和电影名称定长填充,并保存所有电影数据到字典中
在保存电影数据到字典前,值得注意的是,由于每个电影名字和类别的单词数量不一样,转换成数字表示时,还需要通过补0将其补全成固定数据长度。原因是这些数据作为神经网络的输入,其维度影响了第一层网络的权重维度初始化,这要求输入数据的维度是定长的,而不是变长的,所以通过补0使其变为定长输入。补0并不会影响神经网络运算的最终结果。
从上面两小节我们已知:最大电影名字长度是15,最大电影类别长度是6,15和6分别表示电影名字、种类包含的最大单词数量。因此我们通过补0使电影名字的列表长度为15,使电影种类的列表长度补齐为6。实现如下:
# 建立三个字典,分别存放电影ID、名字和类别
movie_info, movie_titles, movie_cat = {}, {}, {}
# 对电影名字、类别中不同的单词从 1 开始标号
t_count, c_count = 1, 1
count_tit = {}
# 按行读取数据并处理
for item in data:
item = item.strip().split("::")
# 1. 获得电影的ID信息
v_id = item[0]
v_title = item[1][:-7] # 去掉title中年份数据
cats = item[2].split('|')
v_year = item[1][-5:-1]
titles = v_title.split()
# 2. 统计电影名字的单词,并给每个单词一个序号,放在movie_titles中
for t in titles:
if t not in movie_titles:
movie_titles[t] = t_count
t_count += 1
# 3. 统计电影类别单词,并给每个单词一个序号,放在movie_cat中
for cat in cats:
if cat not in movie_cat:
movie_cat[cat] = c_count
c_count += 1
# 补0使电影名称对应的列表长度为15
v_tit = [movie_titles[k] for k in titles]
while len(v_tit)<15:
v_tit.append(0)
# 补0使电影种类对应的列表长度为6
v_cat = [movie_cat[k] for k in cats]
while len(v_cat)<6:
v_cat.append(0)
# 4. 保存电影数据到movie_info中
movie_info[v_id] = {'mov_id': int(v_id),
'title': v_tit,
'category': v_cat,
'years': int(v_year)}
print("电影数据数量:", len(movie_info))
ID = 2
print("原始的电影ID为 {} 的数据是:".format(ID), data[ID-1])
print("电影ID为 {} 的转换后数据是:".format(ID), movie_info[str(ID)])
- 电影数据数量: 3883
- 原始的电影ID为 2 的数据是: 2::Jumanji (1995)::Adventure|Children's|Fantasy
- 电影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}
完整的电影数据处理代码如下:
def get_movie_info(path):
# 打开文件,编码方式选择ISO-8859-1,读取所有数据到data中
with open(path, 'r', encoding="ISO-8859-1") as f:
data = f.readlines()
# 建立三个字典,分别用户存放电影所有信息,电影的名字信息、类别信息
movie_info, movie_titles, movie_cat = {}, {}, {}
# 对电影名字、类别中不同的单词计数
t_count, c_count = 1, 1
# 初始化电影名字和种类的列表
titles = []
cats = []
count_tit = {}
# 按行读取数据并处理
for item in data:
item = item.strip().split("::")
v_id = item[0]
v_title = item[1][:-7]
cats = item[2].split('|')
v_year = item[1][-5:-1]
titles = v_title.split()
# 统计电影名字的单词,并给每个单词一个序号,放在movie_titles中
for t in titles:
if t not in movie_titles:
movie_titles[t] = t_count
t_count += 1
# 统计电影类别单词,并给每个单词一个序号,放在movie_cat中
for cat in cats:
if cat not in movie_cat:
movie_cat[cat] = c_count
c_count += 1
# 补0使电影名称对应的列表长度为15
v_tit = [movie_titles[k] for k in titles]
while len(v_tit)<15:
v_tit.append(0)
# 补0使电影种类对应的列表长度为6
v_cat = [movie_cat[k] for k in cats]
while len(v_cat)<6:
v_cat.append(0)
# 保存电影数据到movie_info中
movie_info[v_id] = {'mov_id': int(v_id),
'title': v_tit,
'category': v_cat,
'years': int(v_year)}
return movie_info, movie_cat, movie_titles
movie_info_path = "./work/ml-1m/movies.dat"
movie_info, movie_cat, movie_titles = get_movie_info(movie_info_path)
print("电影数量:", len(movie_info))
ID = 1
print("原始的电影ID为 {} 的数据是:".format(ID), data[ID-1])
print("电影ID为 {} 的转换后数据是:".format(ID), movie_info[str(ID)])
print("电影种类对应序号:'Animation':{} 'Children's':{} 'Comedy':{}".format(movie_cat['Animation'],
movie_cat["Children's"],
movie_cat['Comedy']))
print("电影名称对应序号:'The':{} 'Story':{} ".format(movie_titles['The'], movie_titles['Story']))
- 电影数量: 3883
- 原始的电影ID为 1 的数据是: 1::Toy Story (1995)::Animation|Children's|Comedy
- 电影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}
- 电影种类对应序号:'Animation':1 'Children's':2 'Comedy':3
- 电影名称对应序号:'The':26 'Story':2
从上面的结果来看,ml-1m数据集中一共有3883个不同的电影,每个电影信息包含电影ID、电影名称、电影类别,均已处理成数字形式。