RUCAIBox / RecBole

A unified, comprehensive and efficient recommendation library
https://recbole.io/
MIT License
3.48k stars 615 forks source link

[💡QUESTION] 上下文信息怎么加入,比如预测用户 每次 购买的TopK 商品,用户下单的时点信息,位置信息 #891

Closed transposition closed 2 years ago

transposition commented 3 years ago

请问,上下文信息怎么加入,比如预测用户每一次购买的商品TopK ,用户下单的时点信息(比如星期几),位置信息(城市) 之前没搞过这个。。。请见谅

我用Wide&Deep 模型的时候,尝试过放在.inter 里面,但是在eval 的时候,这些字段没有带进来。eval的时候,interaction 只有.user的字段,缺少了上下文信息,就会导致找不到field 报错。看了下源码,取出来的interaction只有user的字段

def _full_sort_batch_eval(self, batched_data):
    interaction, history_index, swap_row, swap_col_after, swap_col_before = batched_data
    try:
        # Note: interaction without item ids
        scores = self.model.full_sort_predict(interaction.to(self.device))
    except NotImplementedError:
        new_inter = interaction.to(self.device).repeat_interleave(self.tot_item_num) # 就一行
        batch_size = len(new_inter)
        new_inter.update(self.item_tensor[:batch_size]) # 扩充了item的列
        if batch_size <= self.test_batch_size:
            scores = self.model.predict(new_inter)
        else:
            scores = self._spilt_predict(new_inter, batch_size)
# model config
embedding_size: 32
# dataset config
field_separator: "\t"  #指定数据集field的分隔符
seq_separator: "-"   #指定数据集中token_seq或者float_seq域里的分隔符
USER_ID_FIELD: user_id #指定用户id域
ITEM_ID_FIELD: poi_id #指定物品id域
RATING_FIELD: ~   #指定打分rating域
TIME_FIELD: order_timestamp  #指定时间域
NEG_PREFIX: neg_   #指定负采样前缀
LABEL_FIELD: buy

rm_dup_inter: ~ #

unused_col:
  inter: [order_timestamp]
#指定从什么文件里读什么列,这里就是从ml-1m.inter里面读取user_id, item_id, rating, timestamp这四列
load_col:
  inter: [user_id,poi_id,dt-day,dt-week,geohash,order_timestamp]
  item: [poi_id,avg_price]
  user: [user_id,age,gender]

# training settings
epochs: 50  #训练的最大轮数
train_batch_size: 2048 #训练的batch_size
learner: adam #使用的pytorch内置优化器
learning_rate: 0.001 #学习率
training_neg_sample_num: 3 #每一个正样本,采样的负样本数目
eval_step: 1 #每训练x次后做evalaution的次数
stopping_step: 10 #控制训练收敛的步骤数,在该步骤数内若选取的评测标准没有什么变化,就可以提前停止了

# evalution settings
#eval_setting: RO_RS,full #对数据随机重排,设置按比例划分数据集,且使用全排序
eval_setting: TO_RS,full #对数据根据TIME_FIELD重排,设置按比例划分数据集,且使用全排序,TO_LO 留N out,N 为 leave_one_num

group_by_user: True #是否将一个user的记录划到一个组里,当eval_setting使用RO_RS的时候该项必须是True
split_ratio: [0.8,0.1,0.1] #切分比例
metrics: ["Recall", "MRR","NDCG","Hit","Precision"] #评测标准
topk: [5] #评测标准使用topk,设置成10评测标准就是["Recall@10", "MRR@10", "NDCG@10", "Hit@10", "Precision@10"]
valid_metric: Hit@5 #选取哪个评测标准作为作为提前停止训练的标准
eval_batch_size: 4096 #评测的batch_size
2017pxy commented 3 years ago

@transposition 抱歉,我们目前的context-aware模型在进行全排序的时候,只能使用.user.item文件中的上下文信息,对于你说的这种交互时的上下文信息,我们目前不支持。主要原因是在构造负例时,设置负例的context信息比较麻烦,我们也会在之后考虑支持这个功能。

transposition commented 3 years ago

@transposition 抱歉,我们目前的context-aware模型在进行全排序的时候,只能使用.user.item文件中的上下文信息,对于你说的这种交互时的上下文信息,我们目前不支持。主要原因是在构造负例时,设置负例的context信息比较麻烦,我们也会在之后考虑支持这个功能。

不好意思哈,我还是小白,对很多概念可能有理解偏差。。。

先再描述下我的场景哈,差别是contex 信息,既不属于user,也不属于item

我现在的做法: 我现在是把contex信息放到.inter, 这样 train okay,但valid阶段报错 ,原因是valid 阶段用的是ContextFullDataLoader,他在生成batch样本的时候,是用 .user 为主表,而不是 .inter 导致 只取到了 user,iterm的信息

我理解需的流程: train阶段,有(user, contex,item_pos),要做负采样,将(user, contex,item_pos)中的item_pos 替换成负采样的item_neg,再增加label valid阶段,有(user, contex,),要将所有tiem信息补全,(user, contex,item_all),再增加label。如果是全排序,似乎是不需要负采样

我在想是不是改一下 ContextFullDataLoader部分data的生成就可以了?

    def _next_batch_data(self):
       #  user_df = self.user_df[self.pr:self.pr + self.step] -- 不从user 取
       -- step 1
        user_df = self.inter[self.pr:self.pr + self.step] -- 改成 从inter取数,得到(user,contex)
       # cur_data = self._neg_sampling(user_df) --这里一直没看明白,eval 阶段不是全排序么,为什么也要负采样,看完代码也还是懵逼。。。
       -- step 2       
         (user, contex,)复制 item_num次
          加入所有item   (user, contex,) -> (user, contex,item)
       -- step 3
      (user, contex,item)-> (user, contex,item,label)
        self.pr += self.step
        return cur_data

求指正

transposition commented 3 years ago

@transposition 抱歉,我们目前的context-aware模型在进行全排序的时候,只能使用.user.item文件中的上下文信息,对于你说的这种交互时的上下文信息,我们目前不支持。主要原因是在构造负例时,设置负例的context信息比较麻烦,我们也会在之后考虑支持这个功能。

不好意思哈,我还是小白,对很多概念可能有理解偏差。。。

先再描述下我的场景哈,差别是contex 信息,既不属于user,也不属于item

我现在的做法: 我现在是把contex信息放到.inter, 这样 train okay,但valid阶段报错 ,原因是valid 阶段用的是ContextFullDataLoader,他在生成batch样本的时候,是用 .user 为主表,而不是 .inter 导致 只取到了 user,iterm的信息

我理解需的流程: train阶段,有(user, contex,item_pos),要做负采样,将(user, contex,item_pos)中的item_pos 替换成负采样的item_neg,再增加label valid阶段,有(user, contex,),要将所有tiem信息补全,(user, contex,item_all),再增加label。如果是全排序,似乎是不需要负采样

我在想是不是改一下 ContextFullDataLoader部分data的生成就可以了?

    def _next_batch_data(self):
       #  user_df = self.user_df[self.pr:self.pr + self.step] -- 不从user 取
       -- step 1
        user_df = self.inter[self.pr:self.pr + self.step] -- 改成 从inter取数,得到(user,contex)
       # cur_data = self._neg_sampling(user_df) --这里一直没看明白,eval 阶段不是全排序么,为什么也要负采样,看完代码也还是懵逼。。。
       -- step 2       
         (user, contex,)复制 item_num次
          加入所有item   (user, contex,) -> (user, contex,item)
       -- step 3
      (user, contex,item)-> (user, contex,item,label)
        self.pr += self.step
        return cur_data

求指正

2017pxy commented 3 years ago

@transposition 你好,可能这里对于全排序来说,“负采样”这一说法并不准确,这里其实想强调的是负例的构造。在你所描述的这个场景下,负例的context信息构造存在一个问题,就是我如何生成负例的交互时的context信息。

以你所说的位置信息为例,它是个token类型,那么如果用户在测试集有两个正例交互,第一个在“北京”,第二个在“上海”;那么在评测时,对于其他未交互的商品(负例),我们如何设置它的这个位置信息呢?这里就会有很多做法,比如随机设置(从“北京,上海”中随机选),比如永远和第一个正例保持一致(都设置成“北京”),又或者可以设置成特殊的token等等。就目前对于这个问题,我们团队有过很多次讨论,但没有得出一个较为统一的结论,因此我们暂时搁置了这个功能的支持。

你的写法我看了,依然没有解决这个问题(或者说你的策略是context信息永远和第一个交互保持一致)。当然,如果你可以选择好自己的负例构造策略(比如都设置成“北京”),那么你可以按照你的思路自行修改这块的代码,来实现你的想法。但对于一个通用性较强的工具包来说,我们是不能写死这里的逻辑的;

希望我的回答能够解释你的问题,谢谢

transposition commented 3 years ago

@transposition 你好,可能这里对于全排序来说,“负采样”这一说法并不准确,这里其实想强调的是负例的构造。在你所描述的这个场景下,负例的context信息构造存在一个问题,就是我如何生成负例的交互时的context信息。

以你所说的位置信息为例,它是个token类型,那么如果用户在测试集有两个正例交互,第一个在“北京”,第二个在“上海”;那么在评测时,对于其他未交互的商品(负例),我们如何设置它的这个位置信息呢?这里就会有很多做法,比如随机设置(从“北京,上海”中随机选),比如永远和第一个正例保持一致(都设置成“北京”),又或者可以设置成特殊的token等等。就目前对于这个问题,我们团队有过很多次讨论,但没有得出一个较为统一的结论,因此我们暂时搁置了这个功能的支持。

你的写法我看了,依然没有解决这个问题(或者说你的策略是context信息永远和第一个交互保持一致)。当然,如果你可以选择好自己的负例构造策略(比如都设置成“北京”),那么你可以按照你的思路自行修改这块的代码,来实现你的想法。但对于一个通用性较强的工具包来说,我们是不能写死这里的逻辑的;

希望我的回答能够解释你的问题,谢谢

非常感谢你耐心、详尽又及时的回复,不好意思,最后能再追问一下,没有形成较为统一的结论争论的点具体是什么?

首先,我的策略,确实是context信息永远和第一个交互保持一致。比如,(user、晚上、上海) -> 买了 KFC,那(user、晚上、上海,其它商家)就都是负样本。 我理解实际场景中,context是可以获取的,所以test数据集,contex也是已知的。所以,目标就是要预测用户,用户在当前context的条件下购买各个item 的 P( buy |user,context)。

那么

对于方案1:永远和第一个正例保持一致

对于没有交互的,可能是没有浏览,也可能是浏览了没有购买,我理解可以都当做负样本,都是用户在当前context条件下,没有购买。 问题:负样本可能不为负,但影响不大 对于没有浏的item,会有样本有偏的问题。假设用户当前购买item a ,但如果用户浏览的时候看到更便宜的b后,可能会放弃当前购买的item a,转而购买b,但这种样本在item库中占比可能特别少,所以即使label 都赋为0,label错误比例也很小。所以把没有交互的都当做负样本我觉得问题不大。

对于方案2:随机设置(从“北京,上海”中随机选)

我理解这种做法好像不太能理解,我们只知道用户在当前context条件下,买了啥,但不知道用户在context条件变化后,会不会买其他的item,根本无法推断label。甚至contex维度多了之后,会随机采样出用户事实上都不会发生的context(比如一个奉行995的员工,时间:周末,地点:公司),那这个样本好像都没啥意义。当然也可以理解成一种样本增强。

对于方案3:设置成特殊的token

在实际线上预测的时候,context都是已知的,所以设置成特殊的token,也会导致这个样本,在线上实际不存在,那对于训练来说,也是没有帮助的。

总结下,我觉得第1种方案,虽有label会有问题,但至少有实际意义,后2种的方案,不仅无法推断label,而且生成的负样本好像都没有实际意义。所以我觉得方案1是合理的策略,毕竟评估的时候也是看在当前context条件下的召回。

周末愉快!

2017pxy commented 3 years ago

@transposition 你好,我们没有确定好策略的主要原因是:目前没有找到有相关的文献(已发表的会议论文)来描述这一环节的策略(当然,也可能是我们没有调研完全),我刚刚提出的这几种策略,其实都是“拍脑袋”想到的方法。作为一款用于科研的工具包,我们希望我们设计的所有策略都是有依据可循的(有相关的论文支撑或者实际的工业应用支撑)。如果你的方案有相关的文献或者工业界真实应用的支撑,可以在本issue下进行回复你的reference,我们会仔细阅读并考虑。

关于方案一和方案二,其实方案一也有随机性的问题,这个随机性体现在第一个出现的交互是什么?还用刚刚的例子,如果第一个出现的是“北京”,那么你的负例可能都是“北京”,但如果第一个出现的是“上海”,那么负例就全部都是“上海”了。这和方案二不是有同样的问题吗?所以我个人认为,这个策略的设置有很多不确定的因素,无论哪种方法,可能都会有bias。

transposition commented 3 years ago

@transposition 你好,我们没有确定好策略的主要原因是:目前没有找到有相关的文献(已发表的会议论文)来描述这一环节的策略(当然,也可能是我们没有调研完全),我刚刚提出的这几种策略,其实都是“拍脑袋”想到的方法。作为一款用于科研的工具包,我们希望我们设计的所有策略都是有依据可循的(有相关的论文支撑或者实际的工业应用支撑)。如果你的方案有相关的文献或者工业界真实应用的支撑,可以在本issue下进行回复你的reference,我们会仔细阅读并考虑。

关于方案一和方案二,其实方案一也有随机性的问题,这个随机性体现在第一个出现的交互是什么?还用刚刚的例子,如果第一个出现的是“北京”,那么你的负例可能都是“北京”,但如果第一个出现的是“上海”,那么负例就全部都是“上海”了。这和方案二不是有同样的问题吗?所以我个人认为,这个策略的设置有很多不确定的因素,无论哪种方法,可能都会有bias。

原来如此,那等我找到了reference再回复哈。我先用方案一实现下,因为我这个场景,每个用户的contex其实不太随机,有点像用户的习惯吧。

再次感谢!

jianshen92 commented 3 years ago

@transposition 我也遇到类似的问题,也是需要把 context加入至 inter之中,比如说 day-of-week, hour-of-day,之类,还有像 user_past_3_days_purchase 这种算是user feature,可是是会随着时间而改变的feature.

发现recbole处理不了这种情况,结果就需要自己改编。

文献的话,这里附上腾讯的 https://arxiv.org/pdf/1706.06978.pdf deep interest network, 里面可以看到他们也引用了context feature Screenshot 2021-07-22 at 5 23 18 PM

希望recbole团队可以考虑列入这个需求。

transposition commented 3 years ago

我试着改了下,也挺容易的,放到.inter里面,改了下 GeneralFullDataLoader,你可以先应急,同期待官方

 def _next_batch_data(self):
        # user_df = self.user_df[self.pr:self.pr + self.step]
        # cur_data = self._neg_sampling(user_df)
        # self.pr += self.step
        # return cur_data

        inter_df = self.dataset.inter_feat[self.pr:self.pr + self.step]
        # input_inter = self.dataset.join(inter_df)  # join user feature
        inter_df.update(self.dataset.user_feat[inter_df[self.dataset.uid_field]])  # join user feature

        cur_data = self._neg_sampling(inter_df)
        self.pr += self.step
        return cur_data
2017pxy commented 3 years ago

@jianshen92 你好,我们会仔细阅读你提供的文献并尽快考虑添加这个功能,谢谢!

2017pxy commented 3 years ago

@jianshen92 你好,这篇DIN的论文我读过了,文中似乎并没有明确说明实验测试时,对于负样本的context信息的构造方法,而文中实验的测评方式也似乎不是全排序评测,因此目前我无法通过这篇文献来解决本issue的问题。如果我有阅读遗漏的地方,欢迎指出!

sxhysj commented 3 years ago

@transposition 你好,我们没有确定好策略的主要原因是:目前没有找到有相关的文献(已发表的会议论文)来描述这一环节的策略(当然,也可能是我们没有调研完全),我刚刚提出的这几种策略,其实都是“拍脑袋”想到的方法。作为一款用于科研的工具包,我们希望我们设计的所有策略都是有依据可循的(有相关的论文支撑或者实际的工业应用支撑)。如果你的方案有相关的文献或者工业界真实应用的支撑,可以在本issue下进行回复你的reference,我们会仔细阅读并考虑。

关于方案一和方案二,其实方案一也有随机性的问题,这个随机性体现在第一个出现的交互是什么?还用刚刚的例子,如果第一个出现的是“北京”,那么你的负例可能都是“北京”,但如果第一个出现的是“上海”,那么负例就全部都是“上海”了。这和方案二不是有同样的问题吗?所以我个人认为,这个策略的设置有很多不确定的因素,无论哪种方法,可能都会有bias。

为什么不去找最接近的Context呢?一般在Industry就是这么做

Jia-py commented 2 years ago

我试着改了下,也挺容易的,放到.inter里面,改了下 GeneralFullDataLoader,你可以先应急,同期待官方

 def _next_batch_data(self):
        # user_df = self.user_df[self.pr:self.pr + self.step]
        # cur_data = self._neg_sampling(user_df)
        # self.pr += self.step
        # return cur_data

        inter_df = self.dataset.inter_feat[self.pr:self.pr + self.step]
        # input_inter = self.dataset.join(inter_df)  # join user feature
        inter_df.update(self.dataset.user_feat[inter_df[self.dataset.uid_field]])  # join user feature

        cur_data = self._neg_sampling(inter_df)
        self.pr += self.step
        return cur_data

请问.iter文件除user_id与item_id外的特征如何引入模型训练呢?感谢。