导入库¶
from pytorch_tabular import TabularModel
from pytorch_tabular.models import CategoryEmbeddingModelConfig
from pytorch_tabular.config import (
DataConfig,
OptimizerConfig,
TrainerConfig,
ExperimentConfig,
ModelConfig,
)
from pytorch_tabular.models import BaseModel
from pytorch_tabular.models.common.layers import Embedding1dLayer
from pytorch_tabular.models.common.heads import LinearHeadConfig
定义自定义模型¶
PyTorch Tabular 非常易于扩展且可无限自定义。在 PyTorch Tabular 中实现的所有模型都继承了一个抽象类 BaseModel
,它实际上是一个 PyTorchLightning 模型。
它处理所有主要功能,如解码配置参数和设置损失和指标。它还计算损失和指标,并将其反馈给进行反向传播的 PyTorch Lightning Trainer。
如果我们看一下 PyTorch Tabular 模型的结构,主要有三个组件:
- 嵌入层
- 主干
- 头部
嵌入层 从数据加载器中接收输入,该输入是一个包含 categorical
和 continuous
张量的字典。嵌入层将这个字典转换为一个单一的张量,正确处理分类张量。目前已经实现了两个嵌入层,EmbeddingLayer1d 和 EmbeddingLayer2d。
主干 是主要的模型架构。
头部 是线性层,它接受主干的输出并将其转换为我们所期望的输出。
为了封装和强制执行这种结构,BaseModel
要求我们定义三个 property
方法:
1. def embedding_layer(self)
2. def backbone(self)
3. def head(self)
还有另一个必须定义的方法 def _build_network(self)
。我们可以使用该方法定义嵌入、主干、头部及我们需要在模型中使用的其他层或组件。
对于标准的前馈层作为头部,我们还可以使用 BaseModel
中一个称为 _get_head_from_config
的便捷方法,该方法将使用您在 ModelConfig
中设置的 head
和 head_config
来自动为您初始化正确的头部。
使用它的一个示例:
对于标准流程,已经定义的 forward
方法就足够了。
def forward(self, x: Dict):
x = self.embed_input(x) # 嵌入输入字典并返回一个张量
x = self.compute_backbone(x) # 接收张量输入并进行表示学习
return self.compute_head(x) # 将主干输出转换为所需输出,如有必要应用目标范围,并将输出打包为所需格式。
虽然这是最基本的要求,但您可以重新定义或使用任何 PyTorch Lightning 标准方法来调整您的模型和训练。这个设置的真正美在于,您需要自定义的程度完全取决于您自己。对于标准工作流,您可以最小化更改。而对于非常不寻常的模型,您可以重新定义 BaseModel
中的任何方法(只要接口保持不变)。
除了模型,您还需要定义一个配置。配置是 Python 数据类,应该继承 ModelConfig
,并具有所有 ModelConfig 的默认参数。任何额外的参数应在数据类中定义。
要注意的关键事项:
- 所有不同配置中不同参数(如 TrainerConfig、OptimizerConfig 等)在调用
super()
之前都可在config
中获取,在调用super()
之后在self.hparams
中获取。 forward
方法中的输入批次是一个包含continuous
和categorical
键的字典。- 在
_build_network
方法中,保存每个您想在forward
方法中访问的组件到self
。
# 定义一个 Config 类,用于保存模型的超参数。
# 我们需要继承ModelConfig,以便使用默认参数,如学习率、head、head_config等。
# 存在于配置文件中
@dataclass
class MyAwesomeModelConfig(ModelConfig):
use_batch_norm: bool = True
# 将核心模型定义为一个纯 PyTorch 模型
# 前向方法接收一个张量并输出一个张量。
class MyAwesomeRegressionModel(nn.Module):
def __init__(self, hparams: DictConfig, **kwargs):
super().__init__()
self.hparams = (
hparams # 保存配置以便在模型的其他地方访问
)
self._build_network()
def _build_network(self):
# 连续和分类维度是预先计算并存储在配置(推断配置)中的
inp_dim = self.hparams.embedded_cat_dim + self.hparams.continuous_dim
self.linear_layer_1 = nn.Linear(inp_dim, 200)
self.linear_layer_2 = nn.Linear(inp_dim + 200, 70)
self.linear_layer_3 = nn.Linear(inp_dim + 70, 70)
self.input_batch_norm = nn.BatchNorm1d(self.hparams.continuous_dim)
if self.hparams.use_batch_norm:
self.batch_norm_2 = nn.BatchNorm1d(inp_dim + 200)
self.batch_norm_3 = nn.BatchNorm1d(inp_dim + 70)
self.dropout = nn.Dropout(0.3)
self.output_dim = 70
def forward(self, x: torch.Tensor):
inp = x
x = F.relu(self.linear_layer_1(x))
x = self.dropout(x)
x = torch.cat([x, inp], 1)
if self.hparams.use_batch_norm:
x = self.batch_norm_1(x)
x = F.relu(self.linear_layer_2(x))
x = self.dropout(x)
x = torch.cat([x, inp], 1)
if self.hparams.use_batch_norm:
x = self.batch_norm_3(x)
x = self.linear_layer_3(x)
return x
# 将嵌入方案与核心模型一同打包也是良好的实践。
# 这是为了让模型封装其关于如何嵌入输入的要求。
# 分类和连续张量的词典,以及如何将它们组合成单个张量。
# 我们可以使用 PyTorch Tabular 中的一个预定义嵌入层。
# 或者定义一个自定义的嵌入层。查看 Embedding1dLayer 的实现
# 关于如何实现嵌入层
def _build_embedding_layer(self):
return Embedding1dLayer(
continuous_dim=self.hparams.continuous_dim,
categorical_embedding_dims=self.hparams.embedding_dims,
embedding_dropout=self.hparams.embedding_dropout,
batch_norm_continuous_input=self.hparams.batch_norm_continuous_input,
)
# 通过继承 BaseModel 定义 PyTorch Tabular 模型
class MyAwesomeRegressionPTModel(BaseModel):
def __init__(self, config: DictConfig, **kwargs):
# 在调用super()之前,保存您在_build_network中需要的任何属性。
# 在调用 super() 之后,配置将被保存为 `hparams`
super().__init__(config, **kwargs)
def _build_network(self):
# 骨干网络 - 初始化我们之前定义的 PyTorch 模型
self._backbone = MyAwesomeRegressionModel(self.hparams)
# 使用我们在骨干网络中定义的方法初始化嵌入层
self._embedding_layer = self._backbone._build_embedding_layer()
# 头部 - 我们可以使用辅助方法来初始化标准线性头部
self.head = self._get_head_from_config()
# 现在定义BaseModel要求你重写的方法属性。
@property
def backbone(self):
return self._backbone
@property
def embedding_layer(self):
return self._embedding_layer
@property
def head(self):
return self._head
# 为了实现更多自定义功能,我们可以重写forward、compute_backbone、compute_head等方法。
定义配置¶
在使用配置创建TabularModel对象时,有一个偏离正常的地方。之前模型是根据配置推断并自动初始化的。但在这里,我们必须使用TabularModel的model_callable
参数,并传入模型类(而不是已经初始化的对象)。
data_config = DataConfig(
target=[
"target"
], # 目标应始终是一个列表。仅对回归任务支持多目标。多任务分类功能尚未实现。
continuous_cols=num_col_names,
categorical_cols=cat_col_names,
)
trainer_config = TrainerConfig(
auto_lr_find=True, # 运行LRFinder以自动推导学习率
batch_size=1024,
max_epochs=100,
accelerator="auto", # can be 'cpu','gpu', 'tpu', or 'ipu'
devices=-1, # -1 表示使用所有可用资源。
)
optimizer_config = OptimizerConfig()
head_config = LinearHeadConfig(
layers="32", # 每层节点数
activation="ReLU", # 各层之间的激活
dropout=0.1,
initialization="kaiming",
).__dict__ # 转换为字典以传递给模型配置(OmegaConf不接受对象)
model_config = MyAwesomeModelConfig(
task="regression",
use_batch_norm=False,
head="LinearHead", # 线性磁头
head_config=head_config, # 线性磁头配置
learning_rate=1e-3,
)
tabular_model = TabularModel(
data_config=data_config,
model_config=model_config,
optimizer_config=optimizer_config,
trainer_config=trainer_config,
model_callable=MyAwesomeRegressionPTModel, # 在使用自定义模型时,我们需要使用model_callable参数。
)
训练模型¶
其余的过程是照常进行