Skip to content

Commit

Permalink
Merge pull request #18 from RUCAIBox/master
Browse files Browse the repository at this point in the history
Update
  • Loading branch information
hyp1231 authored Jul 21, 2020
2 parents 0665ebd + 7d20177 commit 0563cd3
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 141 deletions.
219 changes: 109 additions & 110 deletions documents/development_manual/README.md
Original file line number Diff line number Diff line change
@@ -1,110 +1,109 @@
# 内部开发手册

## 如何开发一个新模型?

### 1.确定要开发的模型

在下表中登记模型信息,包括模型名称,类别,模型状态,以及现负责人。

模型名称:该模型常用的简称或全称

模型类别:general (传统top-k推荐),context-aware (FM类等利用较多特征信息方法),
sequential(序列推荐方法,包括next-item,next-session)。如有新的模型类别,请加入到此处。

模型状态:按照开发程度由低到高分为:未认领(没有人认领),开发中(正在编写代码,模型没有办法在现有框架下跑通),
调试中(编码基本完成,也可以顺利跑通,但未经过最后的测试),已上线(编写模型通过最后测试,上线成功)

现负责人:保证每一个模型都有专门的负责人,负责开源之后的模型维护工作。当不处于这个项目后,需要把自己负责的模型进行转交,更新最新的负责人。

| 序号 | 模型名称 | 模型类别 | 模型状态 | 现负责人 |
| ---- | ---- | ---- | ---- | ---- |
| 1 | Popularity | general | 开发中 | 林子涵 |
| 2 | ItemKNN | general | 开发中 | 林子涵 |
| 3 | BPRMF | general | 测试中 | 牟善磊 |
| 4 | NCF | general | 测试中 | 牟善磊 |
| 5 | NGCF | general | 开发中 | 林子涵 |
| 6 | LightGCN | general | 未认领 | |
| 7 | FM | context-aware | 开发中 | 牟善磊 |
| 8 | DeepFM | context-aware | 开发中 | 牟善磊 |
| 9 | NFM | context-aware | 开发中 | 林子涵 |
| 10 | Wide&Deep | context-aware | 开发中 | 牟善磊 |
| 11 | GRU4Rec | sequential | 开发中 | 牟善磊 |
| 12 | SASRec | sequential | 未认领 | |
| 13 | FPMC | sequential | 未认领 | |

### 2.代码编写

1) 在 model/模型类别/ 目录下新建py模型文件,若不存在相对应的模型类别目录,需要新建目录。
2) 模型需要继承 AbstractRecommender 这个类,这个类要求实现 forward(), calculate_loss(), predict()三个方法。
forward() 为模型前向传播逻辑,calculate_loss() 为训练时调用的方法,输入为PyTorch常见训练类型数据,输出为模型Loss。
predict() 为评测时用到的方法,输出要为score或者其他任务适配的用来评测的内容。
3) 编写模型文件前,可以查看一下是否有与待实现模型输入输出以及结构相似的模型,如果有可以参考进行编写,事半功倍。
4) 在写模型结构时,包括各种layers和loss,如果为常见的layers和loss可以查看model/layers.py和model/loss.py看有没有已经实现的相应模块,
如果有可以直接调用。如果没有且该模块非常常用,可以添加到layers.py或loss.py文件中。
5) 模型相关的超参数,需要写入到配置文件中,配置文件目录:properties/model/
6) 代码规范请参考 Python PEP8编码规范

### 3.测试上线

1) 首先需要保证模型能够顺利运行,可选取ml-100k这个数据集进行测试。简单测试方式:调整好数据集配置文件,模型配置文件,overall配置文件,
运行run_test.py,或直接运行`python run_test.py --model='model_name' --dataset=ml-100k`,检查是否报错。
2) 保证模型顺利运行后,需要逐字检查模型文件,看是否有逻辑错误或其他错误。
3) 在上线测试数据集上,进行训练评测。一种方式(推荐):利用RecBox自动调参工具,按照模型类别找到相应的数据集及评测方式,设置好相应的配置文件,
保证要测试的设置在run_test.py中能够无误跑通,随后在hyper.test 中按照要求设置要调整的超参范围,调整好后执行`run_hyper.py --max_evals 'eval_nums'`
`max_evals`控制搜索次数,返回得到最优的超参和测试集结果,将结果填入对应模型的配置文件中。另一种方式:自行调参。
4) 检查3)得到的结果是否异常(是否与其他模型相差过大以及其他判断方式),正常无误模型可以顺利上线,结果异常需进一步检查代码,若还未发现问题,请及时与同学和老师进行沟通。

## **测试使用的数据集及评价指标**

### **general类 模型**

使用数据集: ml-1m、yelp、Amazon-game (所有数据集均过滤评分小于3的数据项)

评测方式:8:1:1 ,全排序

评价指标 :Recall@20、NGCG@20、MRR

| | ml-1m | ml-1m | ml-1m |
| ----------- | --------- | ------- | :---- |
| Method | Recall@20 | NDCG@20 | MRR |
| **Pop** | | | |
| **ItemKNN** | | | |
| **BPRMF** | | | |
| **NCF** | | | |
| **NGCF** | | | |

| | yelp | yelp | yelp |
| ----------- | --------- | ------- | :--- |
| Method | Recall@20 | NDCG@20 | MRR |
| **Pop** | | | |
| **ItemKNN** | | | |
| **BPRMF** | | | |
| **NCF** | | | |
| **NGCF** | | | |

| | Amazon-game | Amazon-game | Amazon-game |
| ----------- | ----------- | ----------- | :---------- |
| Method | Recall@20 | NDCG@20 | MRR |
| **Pop** | | | |
| **ItemKNN** | | | |
| **BPRMF** | | | |
| **NCF** | | | |
| **NGCF** | | | |

### **context-aware类模型**

使用数据集:ml-1m(评分小于3的数据项为正样本,大于等于3的数据项目为负样本)、

评测方式:8:1:1

评价指标:AUC、Logloss

| | ml-1m | ml-1m |
| ------------- | ----- | ------- |
| Method | AUC | Logloss |
| **FM** | | |
| **DeepFM** | | |
| **NFM** | | |
| **AFM** | | |
| **Wide&Deep** | | |

# 内部开发手册

## 如何开发一个新模型?

### 1.确定要开发的模型

在下表中登记模型信息,包括模型名称,类别,模型状态,以及现负责人。

模型名称:该模型常用的简称或全称

模型类别:general (传统top-k推荐),context-aware (FM类等利用较多特征信息方法),
sequential(序列推荐方法,包括next-item,next-session)。如有新的模型类别,请加入到此处。

模型状态:按照开发程度由低到高分为:未认领(没有人认领),开发中(正在编写代码,模型没有办法在现有框架下跑通),
调试中(编码基本完成,也可以顺利跑通,但未经过最后的测试),已上线(编写模型通过最后测试,上线成功)

现负责人:保证每一个模型都有专门的负责人,负责开源之后的模型维护工作。当不处于这个项目后,需要把自己负责的模型进行转交,更新最新的负责人。

| 序号 | 模型名称 | 模型类别 | 模型状态 | 现负责人 |
| ---- | ---- | ---- | ---- | ---- |
| 1 | Popularity | general | 开发中 | 林子涵 |
| 2 | ItemKNN | general | 开发中 | 林子涵 |
| 3 | BPRMF | general | 测试中 | 林子涵 |
| 4 | NCF | general | 测试中 | 林子涵 |
| 5 | NGCF | general | 开发中 | 林子涵 |
| 6 | LightGCN | general | 未认领 | |
| 7 | FM | context-aware | 开发中 | 牟善磊 |
| 8 | DeepFM | context-aware | 开发中 | 牟善磊 |
| 9 | NFM | context-aware | 开发中 | 林子涵 |
| 10 | Wide&Deep | context-aware | 开发中 | 牟善磊 |
| 11 | GRU4Rec | sequential | 开发中 | 牟善磊 |
| 12 | SASRec | sequential | 未认领 | |
| 13 | FPMC | sequential | 未认领 | |

### 2.代码编写

1) 在 model/模型类别/ 目录下新建py模型文件,若不存在相对应的模型类别目录,需要新建目录。
2) 模型需要继承 AbstractRecommender 这个类,这个类要求实现 forward(), calculate_loss(), predict()三个方法。
forward() 为模型前向传播逻辑,calculate_loss() 为训练时调用的方法,输入为PyTorch常见训练类型数据,输出为模型Loss。
predict() 为评测时用到的方法,输出要为score或者其他任务适配的用来评测的内容。
3) 编写模型文件前,可以查看一下是否有与待实现模型输入输出以及结构相似的模型,如果有可以参考进行编写,事半功倍。
4) 在写模型结构时,包括各种layers和loss,如果为常见的layers和loss可以查看model/layers.py和model/loss.py看有没有已经实现的相应模块,
如果有可以直接调用。如果没有且该模块非常常用,可以添加到layers.py或loss.py文件中。
5) 模型相关的超参数,需要写入到配置文件中,配置文件目录:properties/model/
6) 代码规范请参考 Python PEP8编码规范

### 3.测试上线

1) 首先需要保证模型能够顺利运行,可选取ml-100k这个数据集进行测试。简单测试方式:调整好数据集配置文件,模型配置文件,overall配置文件,
运行run_test.py,或直接运行`python run_test.py --model='model_name' --dataset=ml-100k`,检查是否报错。
2) 保证模型顺利运行后,需要逐字检查模型文件,看是否有逻辑错误或其他错误。
3) 在上线测试数据集上,进行训练评测。一种方式(推荐):利用RecBox自动调参工具,按照模型类别找到相应的数据集及评测方式,设置好相应的配置文件,
保证要测试的设置在run_test.py中能够无误跑通,随后在hyper.test 中按照要求设置要调整的超参范围,调整好后执行`run_hyper.py --max_evals 'eval_nums'`
`max_evals`控制搜索次数,返回得到最优的超参和测试集结果,将结果填入对应模型的配置文件中。另一种方式:自行调参。
4) 检查3)得到的结果是否异常(是否与其他模型相差过大以及其他判断方式),正常无误模型可以顺利上线,结果异常需进一步检查代码,若还未发现问题,请及时与同学和老师进行沟通。

## **测试使用的数据集及评价指标**

### **general类 模型**

使用数据集: ml-1m、yelp、Amazon-game (所有数据集均过滤评分小于3的数据项)

评测方式:8:1:1 ,全排序

评价指标 :Recall@20、NGCG@20、MRR

| | ml-1m | ml-1m | ml-1m |
| ----------- | --------- | ------- | :---- |
| Method | Recall@20 | NDCG@20 | MRR |
| **Pop** | | | |
| **ItemKNN** | | | |
| **BPRMF** | | | |
| **NCF** | | | |
| **NGCF** | | | |

| | yelp | yelp | yelp |
| ----------- | --------- | ------- | :--- |
| Method | Recall@20 | NDCG@20 | MRR |
| **Pop** | | | |
| **ItemKNN** | | | |
| **BPRMF** | | | |
| **NCF** | | | |
| **NGCF** | | | |

| | Amazon-game | Amazon-game | Amazon-game |
| ----------- | ----------- | ----------- | :---------- |
| Method | Recall@20 | NDCG@20 | MRR |
| **Pop** | | | |
| **ItemKNN** | | | |
| **BPRMF** | | | |
| **NCF** | | | |
| **NGCF** | | | |

### **context-aware类模型**

使用数据集:ml-1m(评分小于3的数据项为正样本,大于等于3的数据项目为负样本)、

评测方式:8:1:1

评价指标:AUC、Logloss

| | ml-1m | ml-1m |
| ------------- | ----- | ------- |
| Method | AUC | Logloss |
| **FM** | | |
| **DeepFM** | | |
| **NFM** | | |
| **AFM** | | |
| **Wide&Deep** | | |
128 changes: 99 additions & 29 deletions documents/user_manual/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,108 @@
### Data部分

### Model部分
1) model = AbstractRecommender(config:Config, dataset:Dataset).to(device)
将 AbstarctRecommender替换成想使用的model名称,初始化这个model
参数:config(Config) -- config信息
dataset(Dataset) -- dataset信息
返回:
1) `model = AbstractRecommender(config:Config, dataset:Dataset)`

`AbstarctRecommender`替换成想使用的model名称,初始化这个model

参数:
- `config(Config)` config对象
- `dataset(Dataset)` dataset对象

返回:

### Trainer部分
1) trainer = Trainer(config:Config, model:AbstractRecommender, logger:Logger)
声明一个trainer,控制模型训练和测试
参数: config(Config) -- config信息
model(AbstractRecommender) -- model
logger(Logger) -- logger
返回:

2) trainer.fit(train_data:DataLoader, valid_data:DataLoader=None)
输入训练数据和验证数据对模型开始训练,按照trainer接收的config信息进行训练,若输入valid_data, 会在验证集上最优时停止训练,
若valid_data为空,按预先设定的epochs停止训练。
参数:train_data(DataLoader) -- 训练数据
valid_data(DataLoader) -- 验证数据
返回:best_valid_score(float) -- 用于验证的最佳验证分数
best_valid_result(dict) -- 用于验证的最佳验证结果
1) `trainer = Trainer(config:Config, model:AbstractRecommender, logger:Logger)`

声明一个`trainer`,控制模型训练和测试

参数:
- `config(Config)` config对象
- `model(AbstractRecommender)` model对象
- `logger(Logger)` logger对象

返回:

2) `trainer.fit(train_data:DataLoader, valid_data:DataLoader=None)`

输入训练数据和验证数据对模型开始训练,按照`trainer`接收的`config`信息进行训练,若输入`valid_data`, 会在验证集上最优时停止训练,
`valid_data`为空,按预先设定的`epochs`停止训练。

参数:
- `train_data(DataLoader)` 训练数据
- `valid_data(DataLoader)` 验证数据

返回:
- `best_valid_score(float)` 用于验证的最佳验证分数
- `best_valid_result(dict)` 用于验证的最佳验证结果

3) trainer.evaluate(eval_data:DataLoader, load_best_model:bool=True, model_file:file=None)
对输入的eval_data进行评测
参数:train_data(DataLoader) -- 待验证数据
load_best_model(bool) -- Trainer执行完fit方法后,当前model参数并不是最优,可以选择是否读取训练过程中最优的模型,默认True
model_file(file) -- 若已经训练好模型,希望只调用Trainer的evaluate方法,输入含有模型参数的文件,读取其中的参数信息,此项优先级最高。
返回:result(dict) -- 评测结果
3) `trainer.evaluate(eval_data:DataLoader, load_best_model:bool=True, model_file:str=None)`

对输入的`eval_data`进行评测

参数:
- `train_data(DataLoader)` 待验证数据
- `load_best_model(bool)` Trainer执行完fit方法后,当前model参数并不是最优,可以选择是否读取训练过程中最优的模型,默认True
- `model_file(str)` 若已经训练好模型,希望只调用Trainer的evaluate方法,输入含有模型参数的文件,读取其中的参数信息,此项优先级最高。

返回:
- `result(dict)` 评测结果

4) `trainer.resume_checkpoint(resume_file:str)`

加载文件中的模型信息及参数信息。trainer可以加载已经训练了一段时间但没训练完全的模型继续完成训练。

参数:
- `resume_file(str)` 模型文件

返回:

5) `hp = HyperTuning(procedure_file:str, space:dict=None, params_file:str=None, interpreter:str='python',
algo:hyperopt.tpe=hyperopt.tpe.suggest, max_evals:int=100, bigger:bool=True)`

实例化`HyperTuning`的一个对象hp,hp用于控制输入的python文件,自动调整超参数寻找最优

参数:
- `procedure_file(str)` hp调用的程序文件 `*.py` ,该程序文件需要包含完整的训练过程,保证可以正确单独运行完成训练,
并将best_valid_score(验证集合上的最佳分数)打印出来。参考文件`run_test.py`
- `space(dict)` 要调整的超参数字典,其中key为超参数名称,value为hyperopt类型的参数范围,由于本模块为基于hyperopt的进一步封装,
`space`具体可以参考hyperopt这个库:https://github.com/hyperopt/hyperopt

一个例子:

space = {
'train_batch_size': hyperopt.hp.choice('train_batch_size',[512, 1024, 2048])
'learning_rate': hp.loguniform('learning_rate', -8, 0)
}

- `params_file(str)` 记录要调整的超参数信息的文件,为了那些无需过多复杂超参数设置的用户或想便捷使用超参调整模块的用户准备。
将要调整的超参简单写入文本,我们将其转换为`space`变量,如果`space`变量不为空,这个参数将没有意义,
用户需要保证`space``params_file`至少一个不为空。

`params_file`的格式要求如下:

一行对应一个参数,具体包括参数名称,搜索类型,搜索范围,中间用' '隔开。

由于是简化版,变化类别只能支持四类,但足够覆盖正常的模型超参数,若想使用更复杂的参数变化类别,请设置`space`参数

搜索类型:choice, 搜索范围:options(list),说明:从options中的元素搜索
搜索类型:uniform,搜索范围:low(int),high(int),说明:从(low,high)的均匀分布中搜索
搜索类型:loguniform, 搜索范围:low(int),high(int),说明:从exp(uniform(low,high))的均匀分布中搜索
搜索类型:quniform,搜索范围:low(int),high(int),q(int),说明:从round(uniform(low, high) / q) * q的均匀分布搜索

一个例子:`'hyper.test'`
了解更多信息请参考:https://github.com/hyperopt/hyperopt/wiki/FMin#2-defining-a-search-space

- `interpreter(str)` 执行`procedure_file`所用的解释器,默认`'python'`
- `algo(hyperopt.tpe)` 优化算法,默认`hyperopt.tpe.suggest`
- `max_evals(int)` 最大搜索次数,默认100次
- `bigger(bool)` `procedure_file`中的`best_valid_score`是否是越大越好,默认True

返回:

6) `hp.run()`

开始自动调整超参数

4) trainer.resume_checkpoint(resume_file:file)
加载文件中的模型信息及参数信息。trainer可以加载已经训练了一段时间但没训练完全的模型继续完成训练。
参数:resume_file(file) -- 模型文件
返回:

### Evaluate部分
4 changes: 2 additions & 2 deletions trainer/hyper_tuning.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@


class HyperTuning(object):
def __init__(self, procudure_filename, params_file=None, space=None, interpreter='python', algo=tpe.suggest, max_evals=100, bigger=True):
self.filename = procudure_filename
def __init__(self, procedure_file, space=None, params_file=None, interpreter='python', algo=tpe.suggest, max_evals=100, bigger=True):
self.filename = procedure_file
self.interpreter = interpreter
self.algo = algo
self.max_evals = max_evals
Expand Down

0 comments on commit 0563cd3

Please sign in to comment.