示例介绍

是一个用于 PyTorch 的表格深度学习扩展库。 现代数据以表格格式存储,具有异构列,每列都有自己的语义类型,例如,数值(如年龄或价格)、分类(如性别或产品类型)、时间、文本(如描述或评论)、图像等。 的目标是构建一个深度学习框架,以在此类复杂多样的数据上执行有效的机器学习。

许多最近的表格模型遵循FeatureEncoderTableConvDecoder的模块化设计。 旨在促进在此类模块化架构下创建、实现和评估用于表格数据的深度学习模型。 请参阅深度表格模型的模块化设计页面以获取更多信息。

在本文档中,我们通过自包含的示例介绍的基本概念。

其核心是, 提供了以下主要功能:

常见基准数据集

包含大量常见的基准数据集。所有数据集的列表可在 torch_frame.datasets 中找到。

中初始化数据集非常简单。 数据集的初始化将自动下载其原始文件并处理列。

在下面的例子中,我们将使用一个预加载的数据集,其中包含泰坦尼克号乘客的信息。 如果你想使用自己的数据集,请参考处理异构语义类型中的示例。

from torch_frame.datasets import Titanic

dataset = Titanic(root='/tmp/titanic')

len(dataset)
>>> 891

dataset.feat_cols
>>> ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']

dataset.materialize()
>>> Titanic()

dataset.df.head(5)
>>>
                Survived  Pclass                                            Name    Sex   Age   SibSp  Parch            Ticket     Fare Cabin Embarked
PassengerId
1                   0       3                            Braund, Mr. Owen Harris    male  22.0      1      0         A/5 21171   7.2500   NaN        S
2                   1       1  Cumings, Mrs. John Bradley (Florence Briggs Th...  female  38.0      1      0          PC 17599  71.2833   C85        C
3                   1       3                             Heikkinen, Miss. Laina  female  26.0      0      0  STON/O2. 3101282   7.9250   NaN        S
4                   1       1       Futrelle, Mrs. Jacques Heath (Lily May Peel)  female  35.0      1      0            113803  53.1000  C123        S
5                   0       3                           Allen, Mr. William Henry    male  35.0      0      0            373450   8.0500   NaN        S

也支持自定义数据集,因此你可以将 用于你自己的问题。 假设你准备了你的 pandas.DataFrame 作为 df,其中包含五列: cat1, cat2, num1, num2, 和 y。 创建 torch_frame.data.Dataset 对象非常简单:

import torch_frame
from torch_frame.data import Dataset

# Specify the stype of each column with a dictionary.
col_to_stype = {"cat1": torch_frame.categorical, "cat2": torch_frame.categorical,
                "num1": torch_frame.numerical, "num2": torch_frame.numerical,
                "y": torch_frame.categorical}

# Set "y" as the target column.
dataset = Dataset(df, col_to_stype=col_to_stype, target_col="y")

表格的数据处理

一个表包含不同数据类型的列。每种数据类型由一个语义类型描述,我们称之为stype。 目前支持以下stypes

中,一个表由TensorFrame的一个实例描述,默认情况下它包含以下属性:

注意

feat_dict 中的键集必须与 col_names_dict 中的键集完全匹配。 TensorFrame 在初始化时进行验证。

Dataset创建TensorFrame的过程被称为物化。 materialize()Dataset中的原始数据帧转换为Tensors并将其存储在TensorFrame中。 materialize()还提供了一个可选参数path来缓存TensorFramecol_stats。如果指定了path, 在物化过程中,将首先尝试加载保存的TensorFramecol_stats。如果没有找到该path的保存对象, 将物化数据集并将物化后的TensorFramecol_stats保存到path

注意

请注意,物化过程对原始特征的处理非常有限,例如,不进行归一化和缺失值处理。 PyTorch Frame 将分类 torch_frame.stype 中的缺失值转换为 -1,并将数值 torch_frame.stype 中的缺失值转换为 NaN。 我们期望 NaN/缺失值处理和归一化由模型端通过 torch_frame.nn.encoder.StypeEncoder 来处理。

TensorFrame 对象的核心是 Tensor;因此,它非常适合与 PyTorch 进行训练和推理。在 中,我们围绕 TensorFrame 构建数据加载器和模型,充分利用 PyTorch 的所有效率和灵活性。

from torch_frame import stype

dataset.materialize() # materialize the dataset

dataset.materialize(path='/tmp/titanic/data.pt') # materialize the dataset with caching

dataset.materialize(path='/tmp/titanic/data.pt') # next materialization will load the cache

tensor_frame = dataset.tensor_frame

tensor_frame.feat_dict.keys()
>>> dict_keys([<stype.categorical: 'categorical'>, <stype.numerical: 'numerical'>])

tensor_frame.feat_dict[stype.numerical]
>>> tensor([[22.0000,  1.0000,  0.0000,  7.2500],
            [38.0000,  1.0000,  0.0000, 71.2833],
            [26.0000,  0.0000,  0.0000,  7.9250],
            ...,
            [    nan,  1.0000,  2.0000, 23.4500],
            [26.0000,  0.0000,  0.0000, 30.0000],
            [32.0000,  0.0000,  0.0000,  7.7500]])

tensor_frame.feat_dict[stype.categorical]
>>> tensor([[0, 0, 0],
            [1, 1, 1],
            [0, 1, 0],
            ...,
            [0, 1, 0],
            [1, 0, 1],
            [0, 0, 2]])

tensor_frame.col_names_dict
>>> {<stype.categorical: 'categorical'>: ['Pclass', 'Sex', 'Embarked'], <stype.numerical: 'numerical'>: ['Age', 'SibSp', 'Parch', 'Fare']}

tensor_frame.y
>>> tensor([0, 1, 1,  ..., 0, 1, 0])

一个 TensorFrame 包含以下基本属性:

tensor_frame.stypes
>>> [<stype.numerical: 'numerical'>, <stype.categorical: 'categorical'>]

tensor_frame.num_cols
>>> 7

tensor_frame.num_rows
>>> 891

tensor_frame.device
>>> device(type='cpu')

我们支持将TensorFrame中的数据转移到支持的设备上。

tensor_frame.to("cpu")

tensor_frame.to("cuda")

一旦Dataset被实例化,我们就可以检索数据的列统计信息。 对于每个stype,都会计算一组不同的统计信息。

对于分类特征,

  • StatType.COUNT 包含一个由两个列表组成的元组,其中第一个列表包含有序的类别名称,第二个列表包含类别计数,按从高到低排序。

对于数值特征,

  • StatType.MEAN 表示数值特征的平均值,

  • StatType.STD 表示标准差,

  • StatType.QUANTILES 包含一个列表,该列表包含列的最小值、第一四分位数(第25百分位数)、中位数(第50百分位数)、第三四分位数(第75百分位数)和最大值。

dataset.col_to_stype
>>> {'Survived': <stype.categorical: 'categorical'>, 'Pclass': <stype.categorical: 'categorical'>, 'Sex': <stype.categorical: 'categorical'>, 'Age': <stype.numerical: 'numerical'>, 'SibSp': <stype.numerical: 'numerical'>, 'Parch': <stype.numerical: 'numerical'>, 'Fare': <stype.numerical: 'numerical'>, 'Embarked': <stype.categorical: 'categorical'>}

dataset.col_stats['Sex']
>>> {<StatType.COUNT: 'COUNT'>: (['male', 'female'], [577, 314])}

dataset.col_stats['Age']
>>> {<StatType.MEAN: 'MEAN'>: 29.69911764705882, <StatType.STD: 'STD'>: 14.516321150817316, <StatType.QUANTILES: 'QUANTILES'>: [0.42, 20.125, 28.0, 38.0, 80.0]}

现在假设你有一个新的 pandas.DataFrame 叫做 new_df,并且你想将其转换为相应的 TensorFrame 对象。你可以按照以下方式实现:

new_tf = dataset.convert_to_tensor_frame(new_df)

小批量

神经网络通常以迷你批次的方式进行训练。 包含其自己的 DataLoader,它可以以迷你批次加载 DatasetTensorFrame

from torch_frame.data import DataLoader

data_loader = DataLoader(tensor_frame, batch_size=32,
                        shuffle=True)

for batch in data_loader:
    batch
    >>> TensorFrame(
            num_cols=7,
            num_rows=32,
            categorical (3): ['Pclass', 'Sex', 'Embarked'],
            numerical (4): ['Age', 'SibSp', 'Parch', 'Fare'],
            has_target=True,
            device='cpu',
        )

表格数据的学习方法

在学习了中的数据操作、数据集和加载器之后,是时候实现我们的第一个模型了!

现在让我们实现一个名为ExampleTransformer的模型。它使用TabTransformerConv作为其卷积层。 初始化StypeWiseFeatureEncoder需要col_statscol_names_dict,我们可以直接从任何具体化的数据集中获取它们作为属性。

from typing import Any, Dict, List

from torch import Tensor
from torch.nn import Linear, Module, ModuleList

import torch_frame
from torch_frame import TensorFrame, stype
from torch_frame.data.stats import StatType
from torch_frame.nn.conv import TabTransformerConv
from torch_frame.nn.encoder import (
    EmbeddingEncoder,
    LinearEncoder,
    StypeWiseFeatureEncoder,
)


class ExampleTransformer(Module):
    def __init__(
        self,
        channels: int,
        out_channels: int,
        num_layers: int,
        num_heads: int,
        col_stats: Dict[str, Dict[StatType, Any]],
        col_names_dict: Dict[torch_frame.stype, List[str]],
    ):
        super().__init__()
        self.encoder = StypeWiseFeatureEncoder(
            out_channels=channels,
            col_stats=col_stats,
            col_names_dict=col_names_dict,
            stype_encoder_dict={
                stype.categorical: EmbeddingEncoder(),
                stype.numerical: LinearEncoder()
            },
        )
        self.tab_transformer_convs = ModuleList([
            TabTransformerConv(
                channels=channels,
                num_heads=num_heads,
            ) for _ in range(num_layers)
        ])
        self.decoder = Linear(channels, out_channels)

    def forward(self, tf: TensorFrame) -> Tensor:
        x, _ = self.encoder(tf)
        for tab_transformer_conv in self.tab_transformer_convs:
            x = tab_transformer_conv(x)
        out = self.decoder(x.mean(dim=1))
        return out

在上面的例子中,EmbeddingEncoder 用于编码分类特征, LinearEncoder 用于编码数值特征。 然后,嵌入被传递到 TabTransformerConv 的层中。 然后,输出被连接并输入到一个 torch.nn.Linear 解码器中。

让我们创建训练-测试分割并创建数据加载器。

from torch_frame.datasets import Yandex
from torch_frame.data import DataLoader

dataset = Yandex(root='/tmp/adult', name='adult')
dataset.materialize()
dataset.shuffle()
train_dataset, test_dataset = dataset[:0.8], dataset[0.80:]
train_loader = DataLoader(train_dataset.tensor_frame, batch_size=128,
                        shuffle=True)
test_loader = DataLoader(test_dataset.tensor_frame, batch_size=128)

让我们训练这个模型50个周期:

import torch
import torch.nn.functional as F

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = ExampleTransformer(
    channels=32,
    out_channels=dataset.num_classes,
    num_layers=2,
    num_heads=8,
    col_stats=train_dataset.col_stats,
    col_names_dict=train_dataset.tensor_frame.col_names_dict,
).to(device)

optimizer = torch.optim.Adam(model.parameters())

for epoch in range(50):
    for tf in train_loader:
        tf = tf.to(device)
        pred = model(tf)
        loss = F.cross_entropy(pred, tf.y)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

最后,我们可以在测试集上评估我们的模型:

model.eval()
correct = 0
for tf in test_loader:
    tf = tf.to(device)
    pred = model(tf)
    pred_class = pred.argmax(dim=-1)
    correct += (tf.y == pred_class).sum()
acc = int(correct) / len(test_dataset)
print(f'Accuracy: {acc:.4f}')
>>> Accuracy: 0.8447

这就是实现你的第一个深度表格网络所需的全部内容。 祝你编程愉快!