运行消融研究
你想找出哪种损失函数和训练方法最适合你的交互模型(模型架构)?那么进行消融研究就是最佳选择!
通常,消融研究是一组实验,其中机器学习系统的组件被移除/替换,以衡量这些组件对系统性能的影响。在知识图谱嵌入模型的背景下,典型的消融研究涉及调查不同的损失函数、训练方法、负采样器以及逆关系的显式建模。对于基于这些组件的特定模型组合,需要确定最佳的超参数值集,例如嵌入维度、学习率、批量大小、损失函数特定的超参数(如边际排名损失中的边际值)。这是通过一个称为超参数优化的过程来实现的。已经提出了不同的方法,其中随机搜索和网格搜索非常流行。
在PyKEEN中,我们可以在自己的程序中或通过命令行界面使用配置文件(file_name.json)来定义和执行消融研究。
首先,我们展示如何在程序中运行消融研究。为此,我们提供了函数
pykeen.ablation.ablation_pipeline(),该函数需要datasets、models、losses、optimizers、
training_loops和directory参数来定义数据集、模型、损失函数、
优化器(例如Adam)、消融研究的训练方法以及实验产物应保存的输出目录。接下来,我们为pykeen.models.ComplEx在
pykeen.datasets.Nations数据集上定义了一个消融研究,以评估不同损失函数(在我们的例子中,
二元交叉熵损失和边际排序损失)以及显式建模逆关系的影响。
现在,让我们从定义最小需求开始,即数据集、交互模型、损失函数、训练方法和优化器,以便进行消融研究。
>>> from pykeen.ablation import ablation_pipeline
>>> directory = "doctests/ablation/ex01_minimal"
>>> ablation_pipeline(
... directory=directory,
... models=["ComplEx"],
... datasets=["Nations"],
... losses=["BCEAfterSigmoidLoss", "MarginRankingLoss"],
... training_loops=["LCWA"],
... optimizers=["Adam"],
... # The following are not part of minimal configuration, but are necessary
... # for demonstration/doctests. You should make these numbers bigger when
... # you're using PyKEEN's ablation framework
... epochs=1,
... n_trials=1,
... )
我们可以使用metadata关键字提供关于我们研究的任意附加信息。一些键,如title是特殊的,被PyKEEN和optuna使用。
>>> from pykeen.ablation import ablation_pipeline
>>> directory = "doctests/ablation/ex02_metadata"
>>> ablation_pipeline(
... directory=directory,
... models=["ComplEx"],
... datasets=["Nations"],
... losses=["BCEAfterSigmoidLoss", "MarginRankingLoss"],
... training_loops=["LCWA"],
... optimizers=["Adam"],
... # Add metadata with:
... metadata=dict(
... title="Ablation Study Over Nations for ComplEx.",
... ),
... # Fast testing configuration, make bigger in prod
... epochs=1,
... n_trials=1,
... )
如上所述,我们还希望测量显式建模逆关系对模型性能的影响。因此,我们通过包含create_inverse_triples参数来扩展消融研究:
>>> from pykeen.ablation import ablation_pipeline
>>> directory = "doctests/ablation/ex03_inverse"
>>> ablation_pipeline(
... directory=directory,
... models=["ComplEx"],
... datasets=["Nations"],
... losses=["BCEAfterSigmoidLoss"],
... training_loops=["LCWA"],
... optimizers=["Adam"],
... # Add inverse triples with
... create_inverse_triples=[True, False],
... # Fast testing configuration, make bigger in prod
... epochs=1,
... n_trials=1,
... )
注意
与models、datasets、losses、training_loops和optimizers不同,
create_inverse_triples有一个默认值,即False。
如果models、datasets、losses、training_loops、optimizers或create_inverse_triples参数中只有一个值,则可以将其作为单个值给出,而不是列表。
>>> from pykeen.ablation import ablation_pipeline
>>> directory = "doctests/ablation/ex04_terse_kwargs"
>>> ablation_pipeline(
... directory=directory,
... models="ComplEx",
... datasets="Nations",
... losses=["BCEAfterSigmoidLoss", "MarginRankingLoss"],
... training_loops="LCWA",
... optimizers="Adam",
... create_inverse_triples=[True, False],
... # Fast testing configuration, make bigger in prod
... epochs=1,
... n_trials=1,
... )
注意
如果所有这些值都是固定的,那么进行消融研究就没有意义。
对于知识图谱嵌入模型(KGEM)中每个需要超参数的组件,即交互模型、损失函数和训练方法,我们在PyKEEN中提供了默认的超参数优化(HPO)范围。因此,我们的消融研究定义在此阶段已经完成。由于超参数范围依赖于数据集,用户可以/应该定义自己的HPO范围。我们稍后将展示如何实现这一点。 为了完成消融研究,我们建议为您的消融研究定义早期停止,具体操作如下:
>>> from pykeen.ablation import ablation_pipeline
>>> directory = "doctests/ablation/ex05_stopper"
>>> ablation_pipeline(
... directory=directory,
... models=["ComplEx"],
... datasets=["Nations"],
... losses=["BCEAfterSigmoidLoss", "MarginRankingLoss"],
... training_loops=["LCWA"],
... optimizers=["Adam"],
... stopper = "early",
... stopper_kwargs = {
... "frequency": 5,
... "patience": 20,
... "relative_delta": 0.002,
... "metric": "hits@10",
... },
... # Fast testing configuration, make bigger in prod
... epochs=1,
... n_trials=1,
... )
我们使用参数stopper定义早停器,并通过stopper_kwargs提供早停器的实例化参数。我们定义早停器应每5个周期评估一次,并在验证集上具有20个周期的耐心。为了继续训练,我们期望模型在Hits@10中获得大于0.2%的改进。
在定义了消融研究之后,我们需要为消融研究中的每个实验定义HPO设置。请记住,对于每个消融实验,我们都会执行HPO以确定当前研究模型的最佳超参数。在PyKEEN中,我们使用Optuna作为HPO框架。再次,我们为Optuna相关的参数提供了默认值。然而,它们定义了一个非常有限的HPO搜索,仅用于测试目的。因此,我们自己定义了Optuna所需的参数:
>>> from pykeen.ablation import ablation_pipeline
>>> directory = "doctests/ablation/ex06_optuna_kwargs"
>>> ablation_pipeline(
... directory=directory,
... models="ComplEx",
... datasets="Nations",
... losses=["BCEAfterSigmoidLoss", "MarginRankingLoss"],
... training_loops="LCWA",
... optimizers="Adam",
... # Fast testing configuration, make bigger in prod
... epochs=1,
... # Optuna-related arguments
... n_trials=2,
... timeout=300,
... metric="hits@10",
... direction="maximize",
... sampler="random",
... pruner= "nop",
... )
我们使用参数n_trials将每个实验的HPO迭代次数设置为2,设置timeout为300秒(HPO将在n_trials或timeout秒后终止,取决于哪个先发生),定义要优化的metric,使用参数direction定义指标是最大化还是最小化,使用参数sampler定义随机搜索作为HPO算法,最后定义我们不使用剪枝器来剪枝无希望的试验(注意我们使用早期停止代替)。
为了衡量性能的差异,我们可以额外定义使用参数best_replicates来重新训练和重新评估每个消融实验的最佳模型的频率:
>>> from pykeen.ablation import ablation_pipeline
>>> directory = "doctests/ablation/ex5"
>>> ablation_pipeline(
... directory=directory,
... models=["ComplEx"],
... datasets=["Nations"],
... losses=["BCEAfterSigmoidLoss", "MarginRankingLoss"],
... training_loops=["LCWA"],
... optimizers=["Adam"],
... create_inverse_triples=[True, False],
... stopper="early",
... stopper_kwargs={
... "frequency": 5,
... "patience": 20,
... "relative_delta": 0.002,
... "metric": "hits@10",
... },
... # Fast testing configuration, make bigger in prod
... epochs=1,
... # Optuna-related arguments
... n_trials=2,
... timeout=300,
... metric="hits@10",
... direction="maximize",
... sampler="random",
... pruner= "nop",
... best_replicates=5,
... )
渴望查看结果吗?然后导航到您的输出目录 path/to/output/directory。
在您的输出目录中,您将找到子目录,例如 0000_nations_complex,其中包含定义消融研究中一个特定消融实验的所有实验工件。最相关的子目录是 best_pipeline,它包含表现最佳实验的工件,包括其在 pipeline_config.json 中的定义、获得的结果以及在子目录 replicates 中训练的模型。replicates 中的副本数量对应于通过参数 -r 提供的数量。此外,您还可以在根目录中找到有关消融研究的更多信息:study.json 描述了消融实验,hpo_config.json 描述了消融实验的 HPO 设置,trials.tsv 提供了每个 HPO 实验的概览。
定义您自己的HPO范围
如上所述,我们为每个超参数提供了默认的超参数/超参数范围。然而,这些默认值/范围并不能保证良好的性能。因此,现在是时候定义你自己的范围了,我们将向你展示如何做到这一点!对于超参数值/范围的定义,两个字典是必不可少的,kwargs用于分配超参数的固定值,而kwargs_ranges用于定义从中采样的值范围。
让我们从为属于交互模型的超参数分配HPO范围开始。这可以通过使用字典 model_to_model_kwargs_ranges 来实现:
...
# Define HPO ranges
>>> model_to_model_kwargs_ranges = {
... "ComplEx": {
... "embedding_dim": {
... "type": "int",
... "low": 4,
... "high": 6,
... "scale": "power_two"
... }
... }
... }
...
我们为嵌入维度定义了一个HPO范围。因为scale是power_two,所以下限(low)等于4,上限high等于6,嵌入维度从集合\(\{2^4,2^5, 2^6\}\)中采样。
接下来,我们使用参数 model_to_training_loop_to_training_kwargs 将训练周期数固定为 50,并使用 model_to_training_loop_to_training_kwargs_ranges 定义批量大小的范围。我们使用这两个字典是因为定义的超参数是训练函数的超参数(即 training_loop 的函数):
...
>>> model_to_model_kwargs_ranges = {
... "ComplEx": {
... "embedding_dim": {
... "type": "int",
... "low": 4,
... "high": 6,
... "scale": "power_two"
... }
... }
... }
>>> model_to_training_loop_to_training_kwargs = {
... "ComplEx": {
... "lcwa": {
... "num_epochs": 50
... }
... }
... }
>>> model_to_training_loop_to_training_kwargs_ranges= {
... "ComplEx": {
... "lcwa": {
... "label_smoothing": {
... "type": "float",
... "low": 0.001,
... "high": 1.0,
... "scale": "log"
... },
... "batch_size": {
... "type": "int",
... "low": 7,
... "high": 9,
... "scale": "power_two"
... }
... }
... }
... }
...
最后,我们为学习率定义一个范围,这是优化器的一个超参数:
...
>>> model_to_model_kwargs_ranges = {
... "ComplEx": {
... "embedding_dim": {
... "type": "int",
... "low": 4,
... "high": 6,
... "scale": "power_two"
... }
... }
... }
>>> model_to_training_loop_to_training_kwargs = {
... "ComplEx": {
... "lcwa": {
... "num_epochs": 50
... }
... }
... }
>>> model_to_training_loop_to_training_kwargs_ranges= {
... "ComplEx": {
... "lcwa": {
... "label_smoothing": {
... "type": "float",
... "low": 0.001,
... "high": 1.0,
... "scale": "log"
... },
... "batch_size": {
... "type": "int",
... "low": 7,
... "high": 9,
... "scale": "power_two"
... }
... }
... }
... }
>>> model_to_optimizer_to_optimizer_kwargs_ranges= {
... "ComplEx": {
... "adam": {
... "lr": {
... "type": "float",
... "low": 0.001,
... "high": 0.1,
... "scale": "log"
... }
... }
... }
... }
...
我们决定使用Adam作为优化器,并为学习率定义了一个log scale,即学习率从区间\([0.001, 0.1)\)中采样。
既然我们已经定义了自己的超参数值/范围,让我们来看看整体配置:
>>> from pykeen.ablation import ablation_pipeline
>>> metadata = dict(title="Ablation Study Over Nations for ComplEx.")
>>> models = ["ComplEx"]
>>> datasets = ["Nations"]
>>> losses = ["BCEAfterSigmoidLoss"]
>>> training_loops = ["lcwa"]
>>> optimizers = ["adam"]
>>> create_inverse_triples= [True, False]
>>> stopper = "early"
>>> stopper_kwargs = {
... "frequency": 5,
... "patience": 20,
... "relative_delta": 0.002,
... "metric": "hits@10",
... }
# Define HPO ranges
>>> model_to_model_kwargs_ranges = {
... "ComplEx": {
... "embedding_dim": {
... "type": "int",
... "low": 4,
... "high": 6,
... "scale": "power_two"
... }
... }
... }
>>> model_to_training_loop_to_training_kwargs = {
... "ComplEx": {
... "lcwa": {
... "num_epochs": 50
... }
... }
... }
>>> model_to_training_loop_to_training_kwargs_ranges= {
... "ComplEx": {
... "lcwa": {
... "label_smoothing": {
... "type": "float",
... "low": 0.001,
... "high": 1.0,
... "scale": "log"
... },
... "batch_size": {
... "type": "int",
... "low": 7,
... "high": 9,
... "scale": "power_two"
... }
... }
... }
... }
>>> model_to_optimizer_to_optimizer_kwargs_ranges= {
... "ComplEx": {
... "adam": {
... "lr": {
... "type": "float",
... "low": 0.001,
... "high": 0.1,
... "scale": "log"
... }
... }
... }
... }
# Run ablation experiment
>>> ablation_pipeline(
... models=models,
... datasets=datasets,
... losses=losses,
... training_loops=training_loops,
... optimizers=optimizers,
... model_to_model_kwargs_ranges=model_to_model_kwargs_ranges,
... model_to_training_loop_to_training_kwargs=model_to_training_loop_to_training_kwargs,
... model_to_optimizer_to_optimizer_kwargs_ranges=model_to_optimizer_to_optimizer_kwargs_ranges,
... directory="doctests/ablation/ex6",
... best_replicates=5,
... n_trials=2,
... timeout=300,
... metric="hits@10",
... direction="maximize",
... sampler="random",
... pruner="nop",
... )
我们需要提供参数 datasets, models, losses, optimizers, 和
training_loops 给 pykeen.ablation.ablation_pipeline()。对于所有其他组件和超参数,PyKEEN
提供了默认值/范围。然而,为了达到最佳性能,我们应该自己仔细定义
超参数的值/范围,如上所述。请注意,还有许多范围需要配置,例如
损失函数或负采样器的超参数。查看 tests/resources/hpo_complex_nations.json` 中提供的示例,了解如何定义其他组件的范围。
使用您自己的数据运行消融研究
我们展示了如何使用PyKEEN集成数据集运行消融研究。现在你可能在问自己,是否可以使用自己的数据运行消融研究?是的,你可以! 与之前的配置相比,只需要进行最小的更改:
>>> datasets = [
... {
... "training": "/path/to/your/train.txt",
... "validation": "/path/to/your/validation.txt",
... "testing": "/path/to/your/test.txt"
... }
... ]
在数据集字段中,您不提供数据集名称的列表,而是包含训练-验证-测试分割路径的字典。
从命令行界面运行消融研究
如果你想从命令行界面开始一个消融研究,我们提供了函数
pykeen.experiments.cli.ablation(),它期望一个JSON配置文件的路径作为参数。
配置文件由一个包含子字典ablation和optuna的字典组成,其中定义了
消融研究和Optuna相关的配置。此外,类似于程序化接口,可以
提供metadata字典。我们之前在程序中定义的消融研究对应的配置文件
将如下所示:
{
"metadata": {
"title": "Ablation Study Over Nations for ComplEx."
},
"ablation": {
"datasets": ["nations"],
"models": ["ComplEx"],
"losses": ["BCEAfterSigmoidLoss", "CrossEntropyLoss"]
"training_loops": ["lcwa"],
"optimizers": ["adam"],
"create_inverse_triples": [true,false],
"stopper": "early"
"stopper_kwargs": {
"frequency": 5,
"patience": 20,
"relative_delta": 0.002,
"metric": "hits@10"
},
"model_to_model_kwargs_ranges":{
"ComplEx": {
"embedding_dim": {
"type": "int",
"low": 4,
"high": 6,
"scale": "power_two"
}
}
},
"model_to_training_loop_to_training_kwargs": {
"ComplEx": {
"lcwa": {
"num_epochs": 50
}
}
},
"model_to_training_loop_to_training_kwargs_ranges": {
"ComplEx": {
"lcwa": {
"label_smoothing": {
"type": "float",
"low": 0.001,
"high": 1.0,
"scale": "log"
},
"batch_size": {
"type": "int",
"low": 7,
"high": 9,
"scale": "power_two"
}
}
}
},
"model_to_optimizer_to_optimizer_kwargs_ranges": {
"ComplEx": {
"adam": {
"lr": {
"type": "float",
"low": 0.001,
"high": 0.1,
"scale": "log"
}
}
}
}
"optuna": {
"n_trials": 2,
"timeout": 300,
"metric": "hits@10",
"direction": "maximize",
"sampler": "random",
"pruner": "nop"
}
}
}
消融研究可以按如下方式开始:
$ pykeen experiments ablation path/to/complex_nation.json -d path/to/output/directory
为了衡量性能的方差,应该使用选项 -r/--best-replicates 来重新训练和重新评估每个消融实验的最佳模型 n 次:
$ pykeen experiments ablation path/to/complex_nation.json -d path/to/output/directory -r 5
在本教程中,我们展示了如何在程序中定义和启动消融研究,以及如何从命令行界面执行它。此外,我们还展示了如何使用您自己的数据定义消融研究。