运行消融研究

你想找出哪种损失函数和训练方法最适合你的交互模型(模型架构)?那么进行消融研究就是最佳选择!

通常,消融研究是一组实验,其中机器学习系统的组件被移除/替换,以衡量这些组件对系统性能的影响。在知识图谱嵌入模型的背景下,典型的消融研究涉及调查不同的损失函数、训练方法、负采样器以及逆关系的显式建模。对于基于这些组件的特定模型组合,需要确定最佳的超参数值集,例如嵌入维度、学习率、批量大小、损失函数特定的超参数(如边际排名损失中的边际值)。这是通过一个称为超参数优化的过程来实现的。已经提出了不同的方法,其中随机搜索和网格搜索非常流行。

在PyKEEN中,我们可以在自己的程序中或通过命令行界面使用配置文件(file_name.json)来定义和执行消融研究。

首先,我们展示如何在程序中运行消融研究。为此,我们提供了函数 pykeen.ablation.ablation_pipeline(),该函数需要datasetsmodelslossesoptimizerstraining_loopsdirectory参数来定义数据集、模型、损失函数、 优化器(例如Adam)、消融研究的训练方法以及实验产物应保存的输出目录。接下来,我们为pykeen.models.ComplExpykeen.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,
... )

注意

modelsdatasetslossestraining_loopsoptimizers不同, create_inverse_triples有一个默认值,即False

如果modelsdatasetslossestraining_loopsoptimizerscreate_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_trialstimeout秒后终止,取决于哪个先发生),定义要优化的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范围。因为scalepower_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_loopspykeen.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配置文件的路径作为参数。 配置文件由一个包含子字典ablationoptuna的字典组成,其中定义了 消融研究和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

在本教程中,我们展示了如何在程序中定义和启动消融研究,以及如何从命令行界面执行它。此外,我们还展示了如何使用您自己的数据定义消融研究。