构建数据读取器
至此我们已经分别处理了用户、电影和评分数据,接下来我们要利用这些处理好的数据,构建一个数据读取器,方便在训练神经网络时直接调用。
首先,构造一个函数,把读取并处理后的数据整合到一起,即在rating数据中补齐用户和电影的所有特征字段。
def get_dataset(usr_info, rating_info, movie_info):
trainset = []
# 按照评分数据的key值索引数据
for usr_id in rating_info.keys():
usr_ratings = rating_info[usr_id]
for movie_id in usr_ratings:
trainset.append({'usr_info': usr_info[usr_id],
'mov_info': movie_info[movie_id],
'scores': usr_ratings[movie_id]})
return trainset
dataset = get_dataset(usr_info, rating_info, movie_info)
print("数据集总数据数:", len(dataset))
- 数据集总数据数: 1000209
接下来构建数据读取器函数load_data(),先看一下整体结构:
import random
def load_data(dataset=None, mode='train'):
"""定义一些超参数等等"""
# 定义数据迭代加载器
def data_generator():
""" 定义数据的处理过程"""
data = None
yield data
# 返回数据迭代加载器
return data_generator
我们来看一下完整的数据读取器函数实现,核心是将多个样本数据合并到一个列表(batch),当该列表达到batchsize后,以yield的方式返回(Python数据迭代器)。
在进行批次数据拼合的同时,完成数据格式和数据尺寸的转换:
- 由于飞桨框架的网络接入层要求将数据先转换成np.array的类型,再转换成框架内置变量variable的类型。所以在数据返回前,需将所有数据均转换成np.array的类型,方便后续处理。
- 每个特征字段的尺寸也需要根据网络输入层的设计进行调整。根据之前的分析,用户和电影的所有原始特征可以分为四类,ID类(用户ID,电影ID,性别,年龄,职业)、列表类(电影类别)、文本类(电影名称)和图像类(电影海报)。因为每种特征后续接入的网络层方案不同,所以要求他们的数据尺寸也不同。这里我们先初步的了解即可,待后续阅读了模型设计章节后,将对输入输出尺寸有更好的理解。
数据尺寸的说明:
- ID类(用户ID,电影ID,性别,年龄,职业)处理成(256,1)的尺寸,以便后续接入Embedding层。第一个维度256是batchsize,第二个维度是1,因为Embedding层要求输入数据的最后一维为1。
- 列表类(电影类别)处理成(256,6,1)的尺寸,6是电影最多的类比个数,以便后续接入全连接层。
- 文本类(电影名称)处理成(256,1,15,1)的尺寸,15是电影名称的最大单词数,以便接入2D卷积层。2D卷积层要求输入数据为四维,对应图像数据是【批次大小,通道数、图像的长、图像的宽】,其中RGB的彩色图像是3通道,灰度图像是单通道。
- 图像类(电影海报)处理成(256,3,64,64)的尺寸, 以便接入2D卷积层。图像的原始尺寸是180270彩色图像,使用resize函数压缩成6464的尺寸,减少网络计算。
import random
use_poster = False
def load_data(dataset=None, mode='train'):
# 定义数据迭代Batch大小
BATCHSIZE = 256
data_length = len(dataset)
index_list = list(range(data_length))
# 定义数据迭代加载器
def data_generator():
# 训练模式下,打乱训练数据
if mode == 'train':
random.shuffle(index_list)
# 声明每个特征的列表
usr_id_list,usr_gender_list,usr_age_list,usr_job_list = [], [], [], []
mov_id_list,mov_tit_list,mov_cat_list,mov_poster_list = [], [], [], []
score_list = []
# 索引遍历输入数据集
for idx, i in enumerate(index_list):
# 获得特征数据保存到对应特征列表中
usr_id_list.append(dataset[i]['usr_info']['usr_id'])
usr_gender_list.append(dataset[i]['usr_info']['gender'])
usr_age_list.append(dataset[i]['usr_info']['age'])
usr_job_list.append(dataset[i]['usr_info']['job'])
mov_id_list.append(dataset[i]['mov_info']['mov_id'])
mov_tit_list.append(dataset[i]['mov_info']['title'])
mov_cat_list.append(dataset[i]['mov_info']['category'])
mov_id = dataset[i]['mov_info']['mov_id']
if use_poster:
# 不使用图像特征时,不读取图像数据,加快数据读取速度
poster = Image.open(poster_path+'mov_id{}.jpg'.format(str(mov_id)))
poster = poster.resize([64, 64])
if len(poster.size) <= 2:
poster = poster.convert("RGB")
mov_poster_list.append(np.array(poster))
score_list.append(int(dataset[i]['scores']))
# 如果读取的数据量达到当前的batch大小,就返回当前批次
if len(usr_id_list)==BATCHSIZE:
# 转换列表数据为数组形式,reshape到固定形状
usr_id_arr = np.array(usr_id_list)
usr_gender_arr = np.array(usr_gender_list)
usr_age_arr = np.array(usr_age_list)
usr_job_arr = np.array(usr_job_list)
mov_id_arr = np.array(mov_id_list)
mov_cat_arr = np.reshape(np.array(mov_cat_list), [BATCHSIZE, 6]).astype(np.int64)
mov_tit_arr = np.reshape(np.array(mov_tit_list), [BATCHSIZE, 1, 15]).astype(np.int64)
if use_poster:
mov_poster_arr = np.reshape(np.array(mov_poster_list)/127.5 - 1, [BATCHSIZE, 3, 64, 64]).astype(np.float32)
else:
mov_poster_arr = np.array([0.])
scores_arr = np.reshape(np.array(score_list), [-1, 1]).astype(np.float32)
# 返回当前批次数据
yield [usr_id_arr, usr_gender_arr, usr_age_arr, usr_job_arr], \
[mov_id_arr, mov_cat_arr, mov_tit_arr, mov_poster_arr], scores_arr
# 清空数据
usr_id_list, usr_gender_list, usr_age_list, usr_job_list = [], [], [], []
mov_id_list, mov_tit_list, mov_cat_list, score_list = [], [], [], []
mov_poster_list = []
return data_generator
load_data()函数通过输入的数据集,处理数据并返回一个数据迭代器。
我们将数据集按照8:2的比例划分训练集和验证集,可以分别得到训练数据迭代器和验证数据迭代器。
dataset = get_dataset(usr_info, rating_info, movie_info)
print("数据集总数量:", len(dataset))
trainset = dataset[:int(0.8*len(dataset))]
train_loader = load_data(trainset, mode="train")
print("训练集数量:", len(trainset))
validset = dataset[int(0.8*len(dataset)):]
valid_loader = load_data(validset, mode='valid')
print("验证集数量:", len(validset))
- 数据集总数量: 1000209
- 训练集数量: 800167
- 验证集数量: 200042
数据迭代器的使用方式如下:
for idx, data in enumerate(train_loader()):
usr_data, mov_data, score = data
usr_id_arr, usr_gender_arr, usr_age_arr, usr_job_arr = usr_data
mov_id_arr, mov_cat_arr, mov_tit_arr, mov_poster_arr = mov_data
print("用户ID数据尺寸", usr_id_arr.shape)
print("电影ID数据尺寸", mov_id_arr.shape, ", 电影类别genres数据的尺寸", mov_cat_arr.shape, ", 电影名字title的尺寸", mov_tit_arr.shape)
break
- 用户ID数据尺寸 (256, 1)
- 电影ID数据尺寸 (256, 1) , 电影类别genres数据的尺寸 (256, 6, 1) , 电影名字title的尺寸 (256, 1, 15, 1)