本文档说明RecStudio中支持的几种数据集类型的数据格式和适用场景。

TripletDataset

1. 说明

TripletDataset 的名字取自于三元组(用户,物品,交互),它是 RecStudio 中最基础的、应用最广泛的数据集类型。

2. 解析前后的数据格式

以dataset_demo中的ml-100k数据集举例,展示TripletDataset类加载数据集文件前后,数据的组织形式:

解析前:

解析前各个文件的原始内容为(只列出前五行):

user_id age gender  occupation  zip_code
1   24  M   technician  85711
2   53  F   other   94043
4   24  M   technician  43537
5   33  F   other   15213
...
item_id movie_title release_year    class
1   Toy Story   1995    Animation Children's Comedy
2   GoldenEye   1995    Action Adventure Thriller
3   Four Rooms
4   Get Shorty  1995    Action Comedy Drama
...
user_id item_id rating  timestamp
196 242 3   881250949
186 302 3   891717742
22  377 1   878887116
244 51  2   880606923
...

ml-100k对应的配置文件为:

url: "recstudio:dataset_demo/ml-100k"
user_id_field: &u user_id:token
item_id_field: &i item_id:token
rating_field: &r rating:float
time_field: &t timestamp:float
time_format: ~

inter_feat_name: ml-100k.inter
inter_feat_field: [*u, *i, *r, *t]
inter_feat_header: 0

user_feat_name: [ml-100k.user]
user_feat_field: [[*u, age:token, gender:token, occupation:token, zip_code:token]]
user_feat_header: 0

item_feat_name: [ml-100k.item]
item_feat_field: [[*i, movie_title:token_seq:" ", release_year:token, class:token_seq:" "]]
item_feat_header: 0

field_separator: "\t"
min_user_inter: 0
min_item_inter: 0
field_max_len: ~
rating_threshold: ~
ranker_rating_threshold: 3
drop_low_rating: ~
max_seq_len: 20

save_cache: False # whether to save processed dataset to cache.

解析后:

下面以最终传入模型的train_data(TripletDataset类的实例)为例,展示三元组(用户,物品,交互)信息在TripletDataset类中的组织形式。 为了方便存储和运算,解析后的user和item属性值都进行了映射,记录映射信息的字典存放于train_data.field2token2idx:

{'user_id': {'[PAD]': 0, '196': 1, '186': 2, '22': 3, '244': 4, '166': 5, '298': 6, '115': 7, '253': 8, ...}, 
 'item_id': {'[PAD]': 0, '242': 1, '302': 2, '377': 3, '51': 4, '346': 5, '474': 6, '265': 7, '465': 8, ...}, 
 'age': {'[PAD]': 0, '24': 1, '53': 2, '33': 3, '42': 4, '57': 5, '36': 6, '29': 7, '39': 8, ...}, 
 'gender': {'[PAD]': 0, 'M': 1, 'F': 2}, 
 'occupation': {'[PAD]': 0, 'technician': 1, 'other': 2, 'executive': 3, 'administrator': 4, 'student': 5, 'lawyer': 6, 'educator': 7, 'scientist': 8, ...}, 
 'zip_code': {'[PAD]': 0, '85711': 1, '94043': 2, '43537': 3, '15213': 4, '98101': 5, '91344': 6, '05201': 7, '01002': 8, ...}, 
 'movie_title': {'[PAD]': 0, 'Toy': 1, 'Story': 2, 'GoldenEye': 3, 'Four': 4, 'Rooms': 5, 'Get': 6, 'Shorty': 7, 'Copycat': 8, ...}, 
 'release_year': {'1995': 1, '[PAD]': 0, '1994': 2, '1996': 3, '1976': 4, '1967': 5, '1977': 6, '1993': 7, '1965': 8, ...}, 
 'class': {'[PAD]': 0, 'Animation': 1, "Children's": 2, 'Comedy': 3, 'Action': 4, 'Adventure': 5, 'Thriller': 6, 'Drama': 7, 'Crime': 8, ...}}

映射后,三元组信息的存放格式如下。 用户特征以的形式存放于train_data.user_feat,可以通过其data属性来查看具体的值。

{'user_id': tensor([  0,   1,   ...942, 943]), 
 'age': tensor([ 0, 12,  8, ..., 14, 24]), 
 'gender': tensor([0, 1, 2, 1, ... 2, 2, 1]), 
 'occupation': tensor([ 0, 13,  3, ..., 14,  5]), 
 'zip_code': tensor([  0,  35, 17...777, 792])}

物品特征以的形式存放于train_data.item_feat,可以通过其data属性来查看具体的值。

{'item_id': tensor([   0,    1, ...81, 1682]), 
 'movie_title': tensor([[   0,    0,...0,    0]]), 
 'release_year': tensor([ 0,  3, 39, ...,  2,  1]), 
 'class': tensor([[ 0,  0,  0,...  0,  0]])}

交互特征以的形式存放于train_data.inter_feat,可以通过其data属性来查看具体的值。

{'user_id': tensor([  1,   1,   ...943, 943]), 
 'item_id': tensor([551, 650, 52... 13, 181]), 
 'rating': tensor([5., 4., 4., ..., 2., 5.]), 
 'timestamp': tensor([8.8125e+08, ...7505e+08])}

3. 数据集预处理流程

TripletDataset类并不是简单地加载并复制原始数据集文件。在组织好所有数据并交给模型之前,TripletDataset类对读入的原始数据进行了一系列的预处理。 预处理的具体过程不是固定的,具体的处理策略要根据config的设置来执行(修改config的方式视TripletDataset类的使用形式而定,在“快速开始”、“模块化模型设计”和“高级使用”中都有相应的介绍,这里不展开叙述)。本例使用的数据集同上,TripletDataset类的config为:

{'url': 'recstudio:dataset_demo/ml-100k',
'user_id_field': 'user_id:token',
'item_id_field': 'item_id:token',
'rating_field': 'rating:float',
'time_field': 'timestamp:float',
'time_format': None,
'inter_feat_name': 'ml-100k.inter',
'inter_feat_field': ['user_id:token', 'item_id:token', 'rating:float', 'timestamp:float'],
'inter_feat_header': 0,
'user_feat_name': ['ml-100k.user'],
'user_feat_field': [['user_id:token', 'age:token', 'gender:token', 'occupation:token', 'zip_code:token']],
'user_feat_header': 0,
'item_feat_name': ['ml-100k.item'],
'item_feat_field': [['item_id:token', 'movie_title:token_seq:" "', 'release_year:token', 'class:token_seq:" "']],
'item_feat_header': 0,
'field_separator': '\t',
'min_user_inter': 0,
'min_item_inter': 0,
'field_max_len': None,
'rating_threshold': None,
'ranker_rating_threshold': 3,
'drop_low_rating': None,
'max_seq_len': 20,
'save_cache': False}

下面介绍预处理的流程。

  1. 读入三元组信息:
  2. TripletDataset读入交互数据,并剔除特征不完整的样本,保存在self.inter_feat中。 如果原数据集中没有rating_field(评分特征),那么添加一列rating,所有值为1(隐式反馈)。
  3. TripletDataset分别读入用户、物品特征,然后填充空值,分别保存在self.user_feat和self.item_feat中。 float型特征会用该特征均值填充,token型特征会用'[PAD]'(如果该特征已经映射过则使用0)填充, float_seq和token_seq分别用对应数据类型的空numpy array来填充。
  4. 过滤三元组信息:
  5. 根据self.config['rating_threshold'],对评分进行处理。(具体的过滤方式上次讨论说要修改,还要加一个变量,等改完再写)
  6. 对于self.inter_feat(交互数据)中[self.fuid, self.fiid]重复的项(即同一用户对同一商品的交互),只保留第一项,去掉其余项。(该功能是否重复?好像出现了两次,在init和build中)
  7. 剔除交互很少的用户和物品。
    1. 从三元组中剔除交互物品数少于self.config['min_user_inter']的用户
    2. 再剔除交互用户数少于self.config['min_item_inter']的物品。
    3. 由于第 ii 步可能减少一些用户的交互数,导致又出现一些新的用户,其交互物品数少于阈值,所以循环执行前两步直到所有用户和物品的交互数都不低于阈值。
    4. (源代码这里有个备注,only delete users/items in inter_feat, users/items in user/item_feat should also be deleted. 但是我看后面好像已经有相应代码了,该备注是不是可以删去?)
  8. 把tokens映射到从0开始的index上(实际上是从1开始,因为'[PAD]'会被映射到0)。映射字典会保存在self.field2token2idx中,该字典的形式上文已展示过。
  9. 一些具体特征的处理:
    1. 对self.inter_feat的时间戳(如果有)的数据类型检验。特别地,如果为str型,检验其是否与self.config['time_format']匹配,并进行相应的解析;如果不匹配则报错。
    2. 对self.user_feat和self.item_feat,分别根据user_id和item_id从小到大的顺序进行重排。
  10. 根据模型的split_mode、ratio_or_num和shuffle参数进一步处理self.inter_feat(交互数据),并进行训练集/验证集/测试集划分:
  11. ratio_or_num用来划分训练集/验证集/测试集,为list型(其中的三个元素均为float型,分别代表训练集、验证集、测试集的比例)。(这里留一法应该是只能给seq dataset用,但是别的数据集用也不会报错,上次跟师兄讨论说之后可以绑定一下)
  12. 如果split_mode为'user_entry',那么先按用户分组,然后每个用户的交互信息(self.inter_feat)按ratio_or_num进行划分。如果shuffle为True,那么每个用户的交互信息在划分前会先打乱顺序。
  13. 如果split_mode为'user',那么按用户划分,即所有用户按ratio_or_num划分成三组,作为训练集/验证集/测试集。
  14. 如果split_mode为'entry',那么所有用户的交互信息(self.inter_feat)混在一起,按ratio_or_num进行划分。如果shuffle为True,那么所有的的交互信息在划分前会先打乱顺序。

4. 样本包含的特征

  • 对于FM类模型来说,train和validation/test阶段的数据形式是一样的,每个数据样本都对应一个用户、一个物品、一个评分。
  • 对于其他类模型来说,train阶段的每个数据样本对应一个用户、一个物品、一个评分,而validation/test阶段的每个数据样本对应一个用户、多个物品和对应的多个评分。

UserDataset

1. 说明

UserDataset 的名称源自于样本的划分方式,该数据集是按用户来划分样本的,每个样本包括了一个用户及其所有交互历史。

2. 解析前后的数据格式

以dataset_demo中的ml-100k数据集举例,展示UserDataset类加载数据集文件前后,数据的组织形式:

解析前:

解析前各个文件的原始内容为(只列出前五行):

user_id age gender  occupation  zip_code
1   24  M   technician  85711
2   53  F   other   94043
4   24  M   technician  43537
5   33  F   other   15213
...
item_id movie_title release_year    class
1   Toy Story   1995    Animation Children's Comedy
2   GoldenEye   1995    Action Adventure Thriller
3   Four Rooms
4   Get Shorty  1995    Action Comedy Drama
...
user_id item_id rating  timestamp
196 242 3   881250949
186 302 3   891717742
22  377 1   878887116
244 51  2   880606923
...

ml-100k对应的配置文件为:

url: "recstudio:dataset_demo/ml-100k"
user_id_field: &u user_id:token
item_id_field: &i item_id:token
rating_field: &r rating:float
time_field: &t timestamp:float
time_format: ~

inter_feat_name: ml-100k.inter
inter_feat_field: [*u, *i, *r, *t]
inter_feat_header: 0

user_feat_name: [ml-100k.user]
user_feat_field: [[*u, age:token, gender:token, occupation:token, zip_code:token]]
user_feat_header: 0

item_feat_name: [ml-100k.item]
item_feat_field: [[*i, movie_title:token_seq:" ", release_year:token, class:token_seq:" "]]
item_feat_header: 0

field_separator: "\t"
min_user_inter: 0
min_item_inter: 0
field_max_len: ~
rating_threshold: ~
ranker_rating_threshold: 3
drop_low_rating: ~
max_seq_len: 20

save_cache: False # whether to save processed dataset to cache.

解析后:

下面以最终传入模型的train_data(UserDataset类的实例)为例,展示样本信息在UserDataset类中的组织形式。 为了方便存储和运算,解析后的user和item属性值都进行了映射,记录映射信息的字典存放于train_data.field2token2idx:

{'user_id': {'[PAD]': 0, '196': 1, '186': 2, '22': 3, '244': 4, '166': 5, '298': 6, '115': 7, '253': 8, ...}, 
 'item_id': {'[PAD]': 0, '242': 1, '302': 2, '377': 3, '51': 4, '346': 5, '474': 6, '265': 7, '465': 8, ...}, 
 'age': {'[PAD]': 0, '24': 1, '53': 2, '33': 3, '42': 4, '57': 5, '36': 6, '29': 7, '39': 8, ...}, 
 'gender': {'[PAD]': 0, 'M': 1, 'F': 2}, 
 'occupation': {'[PAD]': 0, 'technician': 1, 'other': 2, 'executive': 3, 'administrator': 4, 'student': 5, 'lawyer': 6, 'educator': 7, 'scientist': 8, ...}, 
 'zip_code': {'[PAD]': 0, '85711': 1, '94043': 2, '43537': 3, '15213': 4, '98101': 5, '91344': 6, '05201': 7, '01002': 8, ...}, 
 'movie_title': {'[PAD]': 0, 'Toy': 1, 'Story': 2, 'GoldenEye': 3, 'Four': 4, 'Rooms': 5, 'Get': 6, 'Shorty': 7, 'Copycat': 8, ...}, 
 'release_year': {'1995': 1, '[PAD]': 0, '1994': 2, '1996': 3, '1976': 4, '1967': 5, '1977': 6, '1993': 7, '1965': 8, ...}, 
 'class': {'[PAD]': 0, 'Animation': 1, "Children's": 2, 'Comedy': 3, 'Action': 4, 'Adventure': 5, 'Thriller': 6, 'Drama': 7, 'Crime': 8, ...}}

映射后,样本信息的存放格式如下。 用户特征以的形式存放于train_data.user_feat,可以通过其data属性来查看具体的值。

{'user_id': tensor([  0,   1,   ...942, 943]), 
 'age': tensor([ 0, 12,  8, ..., 14, 24]), 
 'gender': tensor([0, 1, 2, 1, ... 2, 2, 1]), 
 'occupation': tensor([ 0, 13,  3, ..., 14,  5]), 
 'zip_code': tensor([  0,  35, 17...777, 792])}

物品特征以的形式存放于train_data.item_feat,可以通过其data属性来查看具体的值。

{'item_id': tensor([   0,    1, ...81, 1682]), 
 'movie_title': tensor([[   0,    0,...0,    0]]), 
 'release_year': tensor([ 0,  3, 39, ...,  2,  1]), 
 'class': tensor([[ 0,  0,  0,...  0,  0]])}

交互特征以的形式存放于train_data.inter_feat,可以通过其data属性来查看具体的值。

{'user_id': tensor([  1,   1,   ...943, 943]), 
 'item_id': tensor([551, 650, 52... 13, 181]), 
 'rating': tensor([5., 4., 4., ..., 2., 5.]), 
 'timestamp': tensor([8.8125e+08, ...7505e+08])}

3. 数据集预处理流程

UserDataset类并不是简单地加载并复制原始数据集文件。在组织好所有数据并交给模型之前,UserDataset类对读入的原始数据进行了一系列的预处理。 预处理的具体过程不是固定的,具体的处理策略要根据config的设置来执行(修改config的方式视UserDataset类的使用形式而定,在“快速开始”、“模块化模型设计”和“高级使用”中都有相应的介绍,这里不展开叙述)。本例使用的数据集同上,UserDataset类的config为:

{'url': 'recstudio:dataset_demo/ml-100k',
'user_id_field': 'user_id:token',
'item_id_field': 'item_id:token',
'rating_field': 'rating:float',
'time_field': 'timestamp:float',
'time_format': None,
'inter_feat_name': 'ml-100k.inter',
'inter_feat_field': ['user_id:token', 'item_id:token', 'rating:float', 'timestamp:float'],
'inter_feat_header': 0,
'user_feat_name': ['ml-100k.user'],
'user_feat_field': [['user_id:token', 'age:token', 'gender:token', 'occupation:token', 'zip_code:token']],
'user_feat_header': 0,
'item_feat_name': ['ml-100k.item'],
'item_feat_field': [['item_id:token', 'movie_title:token_seq:" "', 'release_year:token', 'class:token_seq:" "']],
'item_feat_header': 0,
'field_separator': '\t',
'min_user_inter': 0,
'min_item_inter': 0,
'field_max_len': None,
'rating_threshold': None,
'ranker_rating_threshold': 3,
'drop_low_rating': None,
'max_seq_len': 20,
'save_cache': False}

下面介绍预处理的流程。

  1. 读入样本信息:
  2. UserDataset读入交互数据,并剔除特征不完整的样本,保存在self.inter_feat中。 如果原数据集中没有rating_field(评分特征),那么添加一列rating,所有值为1(隐式反馈)。
  3. UserDataset分别读入用户、物品特征,然后填充空值,分别保存在self.user_feat和self.item_feat中。 float型特征会用该特征均值填充,token型特征会用'[PAD]'(如果该特征已经映射过则使用0)填充, float_seq和token_seq分别用对应数据类型的空numpy array来填充。
  4. 过滤样本信息:
  5. 根据self.config['rating_threshold'],对评分进行处理。(具体的过滤方式上次讨论说要修改,还要加一个变量,等改完再写)
  6. 对于self.inter_feat(交互数据)中[self.fuid, self.fiid]重复的项(即同一用户对同一商品的交互),只保留第一项,去掉其余项。
  7. 剔除交互很少的用户和物品。
    1. 从样本中剔除交互物品数少于self.config['min_user_inter']的用户
    2. 再剔除交互用户数少于self.config['min_item_inter']的物品。
    3. 由于第 ii 步可能减少一些用户的交互数,导致又出现一些新的用户,其交互物品数少于阈值,所以循环执行前两步直到所有用户和物品的交互数都不低于阈值。
  8. 把tokens映射到从0开始的index上(实际上是从1开始,因为'[PAD]'会被映射到0)。映射字典会保存在self.field2token2idx中,该字典的形式上文已展示过。
  9. 一些具体特征的处理:
    1. 对self.inter_feat的时间戳(如果有)的数据类型检验。特别地,如果为str型,检验其是否与self.config['time_format']匹配,并进行相应的解析;如果不匹配则报错。
    2. 对self.user_feat和self.item_feat,分别根据user_id和item_id从小到大的顺序进行重排。
  10. 根据模型的split_mode、ratio_or_num和shuffle参数进一步处理self.inter_feat(交互数据),并进行训练集/验证集/测试集划分:
  11. ratio_or_num用来划分训练集/验证集/测试集,为list型(其中的三个元素均为float型,分别代表训练集、验证集、测试集的比例)。
  12. 如果split_mode为'user_entry',那么先按用户分组,然后每个用户的交互信息(self.inter_feat)按ratio_or_num进行划分。如果shuffle为True,那么每个用户的交互信息在划分前会先打乱顺序。
  13. 如果split_mode为'user',那么按用户划分,即所有用户按ratio_or_num划分成三组,作为训练集/验证集/测试集。
  14. 如果split_mode为'entry',那么所有用户的交互信息(self.inter_feat)混在一起,按ratio_or_num进行划分。如果shuffle为True,那么所有的的交互信息在划分前会先打乱顺序。

4. 样本包含的特征

  • 训练阶段的每个数据样本对应一个用户、一个正例物品及其评分、所有历史交互物品及其评分,而验证/测试阶段的每个数据样本对应一个用户、多个正例物品及其对应的多个评分、该用户的所有历史交互物品及其评分。

SeqDataset

1. 说明

SeqDataset 的名称源自于样本的序列特征,该数据集的每个样本包括了一个用户和他的某段交互序列。

2. 解析前后的数据格式

以dataset_demo中的ml-100k数据集举例,展示SeqDataset类加载数据集文件前后,数据的组织形式:

解析前:

解析前各个文件的原始内容为(只列出前五行):

user_id age gender  occupation  zip_code
1   24  M   technician  85711
2   53  F   other   94043
4   24  M   technician  43537
5   33  F   other   15213
...
item_id movie_title release_year    class
1   Toy Story   1995    Animation Children's Comedy
2   GoldenEye   1995    Action Adventure Thriller
3   Four Rooms
4   Get Shorty  1995    Action Comedy Drama
...
user_id item_id rating  timestamp
196 242 3   881250949
186 302 3   891717742
22  377 1   878887116
244 51  2   880606923
...

ml-100k对应的配置文件为:

url: "recstudio:dataset_demo/ml-100k"
user_id_field: &u user_id:token
item_id_field: &i item_id:token
rating_field: &r rating:float
time_field: &t timestamp:float
time_format: ~

inter_feat_name: ml-100k.inter
inter_feat_field: [*u, *i, *r, *t]
inter_feat_header: 0

user_feat_name: [ml-100k.user]
user_feat_field: [[*u, age:token, gender:token, occupation:token, zip_code:token]]
user_feat_header: 0

item_feat_name: [ml-100k.item]
item_feat_field: [[*i, movie_title:token_seq:" ", release_year:token, class:token_seq:" "]]
item_feat_header: 0

field_separator: "\t"
min_user_inter: 0
min_item_inter: 0
field_max_len: ~
rating_threshold: ~
ranker_rating_threshold: 3
drop_low_rating: ~
max_seq_len: 20

save_cache: False # whether to save processed dataset to cache.

解析后:

下面以最终传入模型的train_data(SeqDataset类的实例)为例,展示样本信息在SeqDataset类中的组织形式。 为了方便存储和运算,解析后的user和item属性值都进行了映射,记录映射信息的字典存放于train_data.field2token2idx:

{'user_id': {'[PAD]': 0, '196': 1, '186': 2, '22': 3, '244': 4, '166': 5, '298': 6, '115': 7, '253': 8, ...}, 
 'item_id': {'[PAD]': 0, '242': 1, '302': 2, '377': 3, '51': 4, '346': 5, '474': 6, '265': 7, '465': 8, ...}, 
 'age': {'[PAD]': 0, '24': 1, '53': 2, '33': 3, '42': 4, '57': 5, '36': 6, '29': 7, '39': 8, ...}, 
 'gender': {'[PAD]': 0, 'M': 1, 'F': 2}, 
 'occupation': {'[PAD]': 0, 'technician': 1, 'other': 2, 'executive': 3, 'administrator': 4, 'student': 5, 'lawyer': 6, 'educator': 7, 'scientist': 8, ...}, 
 'zip_code': {'[PAD]': 0, '85711': 1, '94043': 2, '43537': 3, '15213': 4, '98101': 5, '91344': 6, '05201': 7, '01002': 8, ...}, 
 'movie_title': {'[PAD]': 0, 'Toy': 1, 'Story': 2, 'GoldenEye': 3, 'Four': 4, 'Rooms': 5, 'Get': 6, 'Shorty': 7, 'Copycat': 8, ...}, 
 'release_year': {'1995': 1, '[PAD]': 0, '1994': 2, '1996': 3, '1976': 4, '1967': 5, '1977': 6, '1993': 7, '1965': 8, ...}, 
 'class': {'[PAD]': 0, 'Animation': 1, "Children's": 2, 'Comedy': 3, 'Action': 4, 'Adventure': 5, 'Thriller': 6, 'Drama': 7, 'Crime': 8, ...}}

映射后,样本信息的存放格式如下。 用户特征以的形式存放于train_data.user_feat,可以通过其data属性来查看具体的值。

{'user_id': tensor([  0,   1,   ...942, 943]), 
 'age': tensor([ 0, 12,  8, ..., 14, 24]), 
 'gender': tensor([0, 1, 2, 1, ... 2, 2, 1]), 
 'occupation': tensor([ 0, 13,  3, ..., 14,  5]), 
 'zip_code': tensor([  0,  35, 17...777, 792])}

物品特征以的形式存放于train_data.item_feat,可以通过其data属性来查看具体的值。

{'item_id': tensor([   0,    1, ...81, 1682]), 
 'movie_title': tensor([[   0,    0,...0,    0]]), 
 'release_year': tensor([ 0,  3, 39, ...,  2,  1]), 
 'class': tensor([[ 0,  0,  0,...  0,  0]])}

交互特征以的形式存放于train_data.inter_feat,可以通过其data属性来查看具体的值。

{'user_id': tensor([  1,   1,   ...943, 943]), 
 'item_id': tensor([551, 650, 52... 13, 181]), 
 'rating': tensor([5., 4., 4., ..., 2., 5.]), 
 'timestamp': tensor([8.8125e+08, ...7505e+08])}

3. 数据集预处理流程

SeqDataset类并不是简单地加载并复制原始数据集文件。在组织好所有数据并交给模型之前,SeqDataset类对读入的原始数据进行了一系列的预处理。 预处理的具体过程不是固定的,具体的处理策略要根据config的设置来执行(修改config的方式视SeqDataset类的使用形式而定,在“快速开始”、“模块化模型设计”和“高级使用”中都有相应的介绍,这里不展开叙述)。本例使用的数据集同上,SeqDataset类的config为:

{'url': 'recstudio:dataset_demo/ml-100k',
'user_id_field': 'user_id:token',
'item_id_field': 'item_id:token',
'rating_field': 'rating:float',
'time_field': 'timestamp:float',
'time_format': None,
'inter_feat_name': 'ml-100k.inter',
'inter_feat_field': ['user_id:token', 'item_id:token', 'rating:float', 'timestamp:float'],
'inter_feat_header': 0,
'user_feat_name': ['ml-100k.user'],
'user_feat_field': [['user_id:token', 'age:token', 'gender:token', 'occupation:token', 'zip_code:token']],
'user_feat_header': 0,
'item_feat_name': ['ml-100k.item'],
'item_feat_field': [['item_id:token', 'movie_title:token_seq:" "', 'release_year:token', 'class:token_seq:" "']],
'item_feat_header': 0,
'field_separator': '\t',
'min_user_inter': 0,
'min_item_inter': 0,
'field_max_len': None,
'rating_threshold': None,
'ranker_rating_threshold': 3,
'drop_low_rating': None,
'max_seq_len': 20,
'save_cache': False}

下面介绍预处理的流程。

  1. 读入样本信息:
  2. SeqDataset读入交互数据,并剔除特征不完整的样本,保存在self.inter_feat中。 如果原数据集中没有rating_field(评分特征),那么添加一列rating,所有值为1(隐式反馈)。
  3. SeqDataset分别读入用户、物品特征,然后填充空值,分别保存在self.user_feat和self.item_feat中。 float型特征会用该特征均值填充,token型特征会用'[PAD]'(如果该特征已经映射过则使用0)填充, float_seq和token_seq分别用对应数据类型的空numpy array来填充。
  4. 过滤样本信息:
  5. 根据self.config['rating_threshold'],对评分进行处理。
  6. 对于self.inter_feat(交互数据)中[self.fuid, self.fiid]重复的项(即同一用户对同一商品的交互),只保留第一项,去掉其余项。
  7. 剔除交互很少的用户和物品。
    1. 从样本中剔除交互物品数少于self.config['min_user_inter']的用户
    2. 再剔除交互用户数少于self.config['min_item_inter']的物品。
    3. 由于第 ii 步可能减少一些用户的交互数,导致又出现一些新的用户,其交互物品数少于阈值,所以循环执行前两步直到所有用户和物品的交互数都不低于阈值。
  8. 把tokens映射到从0开始的index上(实际上是从1开始,因为'[PAD]'会被映射到0)。映射字典会保存在self.field2token2idx中,该字典的形式上文已展示过。
  9. 一些具体特征的处理:
    1. 对self.inter_feat的时间戳(如果有)的数据类型检验。特别地,如果为str型,检验其是否与self.config['time_format']匹配,并进行相应的解析;如果不匹配则报错。
    2. 对self.user_feat和self.item_feat,分别根据user_id和item_id从小到大的顺序进行重排。
  10. 根据模型的split_mode和shuffle参数进一步处理self.inter_feat(交互数据),并进行训练集/验证集/测试集划分:
  11. 该数据集默认使用留一法,即每个用户的最后一条交互数据放入测试集,倒数第二条(如果一共只有两条则还用倒数第一条)交互数据放入验证集,其余都放入训练集。
  12. 如果split_mode为'user_entry',那么先按用户分组,然后每个用户的交互信息(self.inter_feat)按ratio_or_num进行划分。如果shuffle为True,那么每个用户的交互信息在划分前会先打乱顺序。
  13. 如果split_mode为'user',那么按用户划分,即所有用户按ratio_or_num划分成三组,作为训练集/验证集/测试集。
  14. 如果split_mode为'entry',那么所有用户的交互信息(self.inter_feat)混在一起,按ratio_or_num进行划分。如果shuffle为True,那么所有的的交互信息在划分前会先打乱顺序。

4. 样本包含的特征

  • 该数据集默认使用留一法,那么train和validation/test阶段的数据形式是一样的,每个数据样本对应一个用户、一个正例物品及其评分、一段历史交互物品序列及其评分、该序列长度。

ALSDataset

1. 说明

ALSDataset 的名称是Alternating Least Squares(即最小交替二乘)的缩写。

2. 解析前后的数据格式

以dataset_demo中的ml-100k数据集举例,展示ALSDataset类加载数据集文件前后,数据的组织形式:

解析前:

解析前各个文件的原始内容为(只列出前五行):

user_id age gender  occupation  zip_code
1   24  M   technician  85711
2   53  F   other   94043
4   24  M   technician  43537
5   33  F   other   15213
...
item_id movie_title release_year    class
1   Toy Story   1995    Animation Children's Comedy
2   GoldenEye   1995    Action Adventure Thriller
3   Four Rooms
4   Get Shorty  1995    Action Comedy Drama
...
user_id item_id rating  timestamp
196 242 3   881250949
186 302 3   891717742
22  377 1   878887116
244 51  2   880606923
...

ml-100k对应的配置文件为:

url: "recstudio:dataset_demo/ml-100k"
user_id_field: &u user_id:token
item_id_field: &i item_id:token
rating_field: &r rating:float
time_field: &t timestamp:float
time_format: ~

inter_feat_name: ml-100k.inter
inter_feat_field: [*u, *i, *r, *t]
inter_feat_header: 0

user_feat_name: [ml-100k.user]
user_feat_field: [[*u, age:token, gender:token, occupation:token, zip_code:token]]
user_feat_header: 0

item_feat_name: [ml-100k.item]
item_feat_field: [[*i, movie_title:token_seq:" ", release_year:token, class:token_seq:" "]]
item_feat_header: 0

field_separator: "\t"
min_user_inter: 0
min_item_inter: 0
field_max_len: ~
rating_threshold: ~
ranker_rating_threshold: 3
drop_low_rating: ~
max_seq_len: 20

save_cache: False # whether to save processed dataset to cache.

解析后:

下面以最终传入模型的train_data(ALSDataset类的实例)为例,展示样本信息在UserDataset类中的组织形式。 为了方便存储和运算,解析后的user和item属性值都进行了映射,记录映射信息的字典存放于train_data.field2token2idx:

{'user_id': {'[PAD]': 0, '196': 1, '186': 2, '22': 3, '244': 4, '166': 5, '298': 6, '115': 7, '253': 8, ...}, 
 'item_id': {'[PAD]': 0, '242': 1, '302': 2, '377': 3, '51': 4, '346': 5, '474': 6, '265': 7, '465': 8, ...}, 
 'age': {'[PAD]': 0, '24': 1, '53': 2, '33': 3, '42': 4, '57': 5, '36': 6, '29': 7, '39': 8, ...}, 
 'gender': {'[PAD]': 0, 'M': 1, 'F': 2}, 
 'occupation': {'[PAD]': 0, 'technician': 1, 'other': 2, 'executive': 3, 'administrator': 4, 'student': 5, 'lawyer': 6, 'educator': 7, 'scientist': 8, ...}, 
 'zip_code': {'[PAD]': 0, '85711': 1, '94043': 2, '43537': 3, '15213': 4, '98101': 5, '91344': 6, '05201': 7, '01002': 8, ...}, 
 'movie_title': {'[PAD]': 0, 'Toy': 1, 'Story': 2, 'GoldenEye': 3, 'Four': 4, 'Rooms': 5, 'Get': 6, 'Shorty': 7, 'Copycat': 8, ...}, 
 'release_year': {'1995': 1, '[PAD]': 0, '1994': 2, '1996': 3, '1976': 4, '1967': 5, '1977': 6, '1993': 7, '1965': 8, ...}, 
 'class': {'[PAD]': 0, 'Animation': 1, "Children's": 2, 'Comedy': 3, 'Action': 4, 'Adventure': 5, 'Thriller': 6, 'Drama': 7, 'Crime': 8, ...}}

映射后,样本信息的存放格式如下。 用户特征以的形式存放于train_data.user_feat,可以通过其data属性来查看具体的值。

{'user_id': tensor([  0,   1,   ...942, 943]), 
 'age': tensor([ 0, 12,  8, ..., 14, 24]), 
 'gender': tensor([0, 1, 2, 1, ... 2, 2, 1]), 
 'occupation': tensor([ 0, 13,  3, ..., 14,  5]), 
 'zip_code': tensor([  0,  35, 17...777, 792])}

物品特征以的形式存放于train_data.item_feat,可以通过其data属性来查看具体的值。

{'item_id': tensor([   0,    1, ...81, 1682]), 
 'movie_title': tensor([[   0,    0,...0,    0]]), 
 'release_year': tensor([ 0,  3, 39, ...,  2,  1]), 
 'class': tensor([[ 0,  0,  0,...  0,  0]])}

交互特征以的形式存放于train_data.inter_feat,可以通过其data属性来查看具体的值。

{'user_id': tensor([  1,   1,   ...943, 943]), 
 'item_id': tensor([551, 650, 52... 13, 181]), 
 'rating': tensor([5., 4., 4., ..., 2., 5.]), 
 'timestamp': tensor([8.8125e+08, ...7505e+08])}

3. 数据集预处理流程

ALSDataset类并不是简单地加载并复制原始数据集文件。在组织好所有数据并交给模型之前,ALSDataset类对读入的原始数据进行了一系列的预处理。 预处理的具体过程不是固定的,具体的处理策略要根据config的设置来执行(修改config的方式视ALSDataset类的使用形式而定,在“快速开始”、“模块化模型设计”和“高级使用”中都有相应的介绍,这里不展开叙述)。本例使用的数据集同上,ALSDataset类的config为:

{'url': 'recstudio:dataset_demo/ml-100k',
'user_id_field': 'user_id:token',
'item_id_field': 'item_id:token',
'rating_field': 'rating:float',
'time_field': 'timestamp:float',
'time_format': None,
'inter_feat_name': 'ml-100k.inter',
'inter_feat_field': ['user_id:token', 'item_id:token', 'rating:float', 'timestamp:float'],
'inter_feat_header': 0,
'user_feat_name': ['ml-100k.user'],
'user_feat_field': [['user_id:token', 'age:token', 'gender:token', 'occupation:token', 'zip_code:token']],
'user_feat_header': 0,
'item_feat_name': ['ml-100k.item'],
'item_feat_field': [['item_id:token', 'movie_title:token_seq:" "', 'release_year:token', 'class:token_seq:" "']],
'item_feat_header': 0,
'field_separator': '\t',
'min_user_inter': 0,
'min_item_inter': 0,
'field_max_len': None,
'rating_threshold': None,
'ranker_rating_threshold': 3,
'drop_low_rating': None,
'max_seq_len': 20,
'save_cache': False}

下面介绍预处理的流程。

  1. 读入样本信息:
  2. ALSDataset读入交互数据,并剔除特征不完整的样本,保存在self.inter_feat中。 如果原数据集中没有rating_field(评分特征),那么添加一列rating,所有值为1(隐式反馈)。
  3. ALSDataset分别读入用户、物品特征,然后填充空值,分别保存在self.user_feat和self.item_feat中。 float型特征会用该特征均值填充,token型特征会用'[PAD]'(如果该特征已经映射过则使用0)填充, float_seq和token_seq分别用对应数据类型的空numpy array来填充。
  4. 过滤样本信息:
  5. 根据self.config['rating_threshold'],对评分进行处理。
  6. 对于self.inter_feat(交互数据)中[self.fuid, self.fiid]重复的项(即同一用户对同一商品的交互),只保留第一项,去掉其余项。
  7. 剔除交互很少的用户和物品。
    1. 从样本中剔除交互物品数少于self.config['min_user_inter']的用户
    2. 再剔除交互用户数少于self.config['min_item_inter']的物品。
    3. 由于第 ii 步可能减少一些用户的交互数,导致又出现一些新的用户,其交互物品数少于阈值,所以循环执行前两步直到所有用户和物品的交互数都不低于阈值。
  8. 把tokens映射到从0开始的index上(实际上是从1开始,因为'[PAD]'会被映射到0)。映射字典会保存在self.field2token2idx中,该字典的形式上文已展示过。
  9. 一些具体特征的处理:
    1. 对self.inter_feat的时间戳(如果有)的数据类型检验。特别地,如果为str型,检验其是否与self.config['time_format']匹配,并进行相应的解析;如果不匹配则报错。
    2. 对self.user_feat和self.item_feat,分别根据user_id和item_id从小到大的顺序进行重排。
  10. 根据模型的split_mode、ratio_or_num和shuffle参数进一步处理self.inter_feat(交互数据),并进行训练集/验证集/测试集划分:
  11. ratio_or_num用来划分训练集/验证集/测试集,为list型(其中的三个元素均为float型,分别代表训练集、验证集、测试集的比例)。
  12. 如果split_mode为'user_entry',那么先按用户分组,然后每个用户的交互信息(self.inter_feat)按ratio_or_num进行划分。如果shuffle为True,那么每个用户的交互信息在划分前会先打乱顺序。
  13. 如果split_mode为'user',那么按用户划分,即所有用户按ratio_or_num划分成三组,作为训练集/验证集/测试集。
  14. 如果split_mode为'entry',那么所有用户的交互信息(self.inter_feat)混在一起,按ratio_or_num进行划分。如果shuffle为True,那么所有的的交互信息在划分前会先打乱顺序。

4. 样本包含的特征

  • 训练阶段的每个数据样本对应一个用户、所有历史交互物品及其评分,而验证/测试阶段的每个数据样本对应一个用户、正负例物品各一个及其对应评分、该用户的所有历史交互物品。

适用场景

数据集类型 适用任务 数据形式 适用模型
TripletDataset General Recommendation 和 CTR 任务 (用户,物品,交互)三元组 MF类模型(例如BPRMF)和FM类模型(例如 DCN)
UserDataset AutoEncoder 相关任务 (用户,物品,交互,历史) AE类模型(例如MultiVAE)
SeqDataset Sequential Recommendation 任务 (用户,物品,交互,序列) Seq类模型(例如SASRec)
ALSDataset Alternating Least Squares 相关任务 $(u,I_u)$和$(i,U_i)$交替提供 MF类模型(例如CML)