示例介绍
PyTorch Frame 是一个用于 PyTorch 的表格深度学习扩展库。 现代数据以表格格式存储,具有异构列,每列都有自己的语义类型,例如,数值(如年龄或价格)、分类(如性别或产品类型)、时间、文本(如描述或评论)、图像等。 PyTorch Frame 的目标是构建一个深度学习框架,以在此类复杂多样的数据上执行有效的机器学习。
许多最近的表格模型遵循FeatureEncoder
、TableConv
和Decoder
的模块化设计。
PyTorch Frame旨在促进在此类模块化架构下创建、实现和评估用于表格数据的深度学习模型。
请参阅深度表格模型的模块化设计页面以获取更多信息。
在本文档中,我们通过自包含的示例介绍PyTorch Frame的基本概念。
其核心是,PyTorch Frame 提供了以下主要功能:
常见基准数据集
PyTorch Frame 包含大量常见的基准数据集。所有数据集的列表可在 torch_frame.datasets 中找到。
在PyTorch Frame中初始化数据集非常简单。 数据集的初始化将自动下载其原始文件并处理列。
在下面的例子中,我们将使用一个预加载的数据集,其中包含泰坦尼克号乘客的信息。 如果你想使用自己的数据集,请参考处理异构语义类型中的示例。
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
PyTorch Frame 也支持自定义数据集,因此你可以将 PyTorch Frame 用于你自己的问题。
假设你准备了你的 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
。
目前PyTorch Frame支持以下stypes
:
stype.categorical
表示分类列。stype.numerical
表示数值列。stype.multicategorical
表示多分类列。stype.text_embedded
表示通过某些文本编码器预先嵌入的文本列。
在PyTorch Frame中,一个表由TensorFrame
的一个实例描述,默认情况下它包含以下属性:
col_names_dict
: 一个字典,保存每个stype
的列名。feat_dict
: 一个包含不同stypes
的Tensor
的字典。 对于stype.numerical
和stype.categorical
,Tensor
的形状是[num_rows, num_cols],而对于stype.text_embedded
,形状是[num_rows, num_cols, emb_dim]。y
(可选): 一个包含预测目标值的张量。
注意
feat_dict
中的键集必须与 col_names_dict
中的键集完全匹配。
TensorFrame
在初始化时进行验证。
从Dataset
创建TensorFrame
的过程被称为物化。
materialize()
将Dataset
中的原始数据帧转换为Tensors
并将其存储在TensorFrame
中。
materialize()
还提供了一个可选参数path来缓存TensorFrame
和col_stats。如果指定了path,
在物化过程中,PyTorch Frame将首先尝试加载保存的TensorFrame
和col_stats。如果没有找到该path的保存对象,PyTorch Frame
将物化数据集并将物化后的TensorFrame
和col_stats保存到path。
注意
请注意,物化过程对原始特征的处理非常有限,例如,不进行归一化和缺失值处理。
PyTorch Frame 将分类 torch_frame.stype
中的缺失值转换为 -1,并将数值 torch_frame.stype
中的缺失值转换为 NaN。
我们期望 NaN/缺失值处理和归一化由模型端通过 torch_frame.nn.encoder.StypeEncoder
来处理。
TensorFrame
对象的核心是 Tensor
;因此,它非常适合与 PyTorch 进行训练和推理。在 PyTorch Frame 中,我们围绕 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
中的数据转移到PyTorch支持的设备上。
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)
小批量
神经网络通常以迷你批次的方式进行训练。PyTorch Frame 包含其自己的 DataLoader
,它可以以迷你批次加载 Dataset
或 TensorFrame
。
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',
)
表格数据的学习方法
在学习了PyTorch Frame中的数据操作、数据集和加载器之后,是时候实现我们的第一个模型了!
现在让我们实现一个名为ExampleTransformer
的模型。它使用TabTransformerConv
作为其卷积层。
初始化StypeWiseFeatureEncoder
需要col_stats
和col_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
这就是实现你的第一个深度表格网络所需的全部内容。 祝你编程愉快!