作者:Laurin Brechter
在这篇博客文章中,我们将探讨一种有趣的新深度学习(DL)方法,称为关系深度学习(RDL)。我们还将通过在一个电子商务公司的实际数据库(而不是数据集!)上进行一些RDL来获得一些实践经验。
在现实世界中,我们通常有一个关系型数据库,希望在其上运行一些机器学习任务。但特别是当数据库高度规范化时,这将意味着需要进行大量耗时的特征工程,并且由于必须执行许多聚合操作而导致粒度损失。更重要的是,我们可以构建无数种可能的特征组合,每个组合都可能会产生良好的性能[2]。这意味着我们很可能会忽略与机器学习任务相关的一些信息。
这类似于计算机视觉早期的时代,在深度神经网络出现之前,特征是由人工从像素值中手工设计出来的。如今,模型直接处理原始像素,而不是依赖于这一中间层。
RDL承诺为表格学习提供同样的便利。也就是说,它通过直接在您的关系数据库之上进行学习来消除构造特征矩阵的额外步骤。它是通过将具有关系的数据库转换成一个图来实现这一点的,在这个图中,表中的每一行变成一个节点,而表之间的关系则成为边。行值被存储在节点内部作为节点特征。
在这篇博客文章中,我们将使用这个电子商务数据集来自Kaggle的数据,包含了一个以星型模式组织的电子商务平台交易数据,其中心是事实表(交易),还有一些维度表。完整代码可以在此找到:笔记本电脑.
在本文中,我们将使用relbench库来做RDL在relbench中,我们首先要指定关系数据库的模式。以下是我们在数据库中为“transactions”表进行这样操作的一个示例。我们将表格作为pandas数据框提供,并指定主键和时间戳列。主键列用于唯一标识实体。时间戳确保当我们想要预测未来交易时,只能从过去的交易中学习。在图中,这意味着信息只能从具有较低时间戳的节点(即在过去)流向具有较高时间戳的节点。此外,我们还指定了关系中存在的外键。在这种情况下,“transactions”表包含“customer_key”列,这是一个指向“customer_dim”表的外键。
tables['transactions'] = Table(
df=pd.DataFrame(t),
pkey_col='t_id',
fkey_col_to_pkey_table={
'客户密钥': '客户'
'item_key': '产品'
'store_key': 'stores'
},
time_col='日期'
)
其余的表格需要以同样的方式定义。注意,如果你已经有一个数据库模式,这一步也可以自动化。由于数据集来自Kaggle,我需要手动创建模式。我们还需要将日期列转换为实际的pandas日期时间对象,并移除任何NaN值。
类 EcommerceDataBase(Dataset):
创建您自己的数据集示例:https://github.com/snap-stanford/relbench/blob/main/tutorials/custom_dataset.ipynbval_timestamp = pd.Timestamp(year=2018, month=1, day=1)
test_timestamp = pd.Timestamp(year=2020, month=1, day=1)
def 创建数据库(self) -> Database:
tables = {}
customers = load_csv_to_db(BASE_DIR + '/customer_dim.csv').drop(columns=['contact_no', 'nid']).rename(columns={'coustomer_key': 'customer_key'})
stores = 加载并读取CSV到数据库(BASE_DIR + '/store_dim.csv')。drop(columns=['upazila'])
products = 加载csv到数据库(BASE_DIR + '/item_dim.csv')
transactions = load_csv_to_db(BASE_DIR + '/事实表.csv').rename(columns={'coustomer_key': 'customer_key'})
times = 加载csv到数据库(BASE_DIR + '/time_dim.csv')
注意:通常来说,“load_csv_to_db”是一个函数名,在翻译时一般保留原样,这里假设整个表达式需要直译,所以“load_csv_to_db”保持不变。如果要更自然地表述,可以改为:“times = 将时间维度的CSV文件加载到数据库中(BASE_DIR + '/time_dim.csv')”。但根据指示,只输出确切翻译结果如下:
times = load_csv_to_db(BASE_DIR + '/time_dim.csv')
t = transactions.merge(times[['时间键', '日期']], on='时间键').drop(columns=['支付键', '时间键', '单位'])
t['date'] = pd.to_datetime(t.date)
t = t.reset_index().rename(columns={'index': 't_id'})
t['quantity'] = t.quantity.astype('int')
t['unit_price'] = t.unit_price.astype(float)
products['unit_price'] = products.unit_price.astype('float')
t['total_price'] = t.total_price.astype('float')
打印(t.isna().sum(axis=0))
打印(products.isna().sum(axis=0))
打印(stores.isna().sum(axis=0))
打印 customers 缺失值的数量
# 翻译说明:原句意在打印数据框customers中每一列的缺失值数量,isna()生成一个布尔型数据框表示哪些位置是缺失值(NaN),sum(axis=0)则将每个列上的True值相加(True视作1,False视作0),得到每列缺失值的数量。
tables['products'] = Table(
df=pd.DataFrame(products),
pkey_col='商品键'
fkey_col_to_pkey_table={},
time_col=None
)
tables['customers'] = Table(
df=pd.DataFrame(customers),
pkey_col='客户键',
fkey_col_to_pkey_table={},
time_col=None
)
tables['transactions'] = Table(
df=pd.DataFrame(t),
pkey_col='t_id',
fkey_col_to_pkey_table={
'客户键': '客户'
'item_key': '产品'
'store_key': 'stores'
},
time_col='日期'
)
tables['stores'] = Table(
df=pd.DataFrame(stores),
pkey_col='store_key',
fkey_col_to_pkey_table={}
)
返回 Database(tables)
关键在于,作者们提出了训练表的概念。这个训练表本质上定义了机器学习任务。这里的思路是,我们想要预测数据库中某个实体的未来状态(即未来的值)。通过指定一个表格来实现这一目标,其中每一行包含时间戳、实体标识符以及我们要预测的值。该标识符用于指定具体实体,时间戳则指定了我们需要在何时何地预测该实体的状态。这也将限制可用于推断此实体价值的数据范围(即仅使用过去的数据)。这个值本身是我们想要预测的内容(即真实值)。
在我们的情况下,我们有一个在线平台和客户。我们想预测客户在未来30天内的收入。我们可以使用DuckDB执行SQL语句来创建训练表。这是RDL的一个巨大优势,因为我们可以用仅仅SQL来创建任何类型的机器学习任务。例如,我们可以定义一个查询来选择买家在未来30天内的购买次数以进行流失预测。
df = duckdb.sql(f"""
选择
时间戳,
客户密钥,
SUM(total_price) AS revenue
来自
timestamp_df t
左连接
交易事务 ta
在...之上(根据上下文可能有所不同,此处给出最通用的翻译)原文如果只是"on"而没有具体的对象或动作,那么直接翻译为“在...之上”可能不够具体。如需更准确翻译,请提供完整句子或更多上下文信息。
如果没有具体内容需要翻译,则输出:
on
ta.date <= t.timestamp + INTERVAL '{self.timedelta}'
并且 ta.date > t.timestamp
按时间戳和客户键分组
"".df().dropna()
结果将是一个表格,其中seller_id作为我们要预测的实体的关键字段,revenue作为目标变量,timestamp表示我们需要在此时间点进行预测(即我们只能使用截至此时的数据来进行预测)。
以下是创建“customer_revenue”任务的完整代码。
类CustomerRevenueTask(EntityTask):
自定义任务示例:https://github.com/snap-stanford/relbench/blob/main/tutorials/custom_task.ipynbtask_type = 任务类型.REgression
实体列 = "customer_key"
实体表 = "客户"
time_col = "时间戳"
target_col = "收入"
timedelta = pd.Timedelta(days=30) # 我们想要预测未来多长时间内的收入。
metrics = [r2, mae]
num_eval_timestamps = 40
def 创建表格(self, db: Database, timestamps: "pd.Series[pd.Timestamp]") -> Table:
timestamp_df = pd.DataFrame({"时间戳": timestamps})
transactions = db.table_dict["transactions"].df
df = duckdb.sql(f"""
选择
时间戳,
客户密钥,
总价格之和作为收入
来自
timestamp_df t
左连接
交易事务 ta
在…的时候(此短语不完整,缺少上下文,无法提供具体翻译)
ta.date <= t.timestamp + INTERVAL '{self.timedelta}'
并且 ta.date > t.timestamp
按时间戳和客户键分组
"".df().dropna()
打印(df)
返回 Table(
df=df,
fkey_col_to_pkey_table={self.entity_col: self.entity_table},
pkey_col=None,
time_col=self.time_col,
)
这样一来,我们已经完成了主要的工作。剩下的工作流程将会类似,与机器学习任务无关。我能够从之前的代码中复制大部分内容。示例笔记本relbench提供的。
例如,我们需要编码节点特征。在这里,我们可以使用glove嵌入来编码所有文本特征,如产品描述和产品名称。
从 typing 导入 List, Optional
从sentence_transformers导入SentenceTransformer
从torch导入Tensor类GloveTextEmbedding:
def __init__(self, device: Optional[torch.device]
None):
self.model = SentenceTransformer(
" sentence-transformers/平均词嵌入_glove.6B.300d"
device=device,
)
def __call__(self, sentences: List[str]) -> Tensor:
返回 torch.from_numpy(self.model.encode(sentences))
之后,我们可以将这些转换应用于我们的数据并构建图形。
从torch_frame.config.text_embedder导入TextEmbedderConfig
从relbench modeling.graph导入make_pkey_fkey_graphtext_embedder_cfg = TextEmbedderConfig(
text_embedder=GloveTextEmbedding(device=device), batch_size=256
)
数据, col_stats_dict = make_pkey_fkey_graph()
db,
col_to_stype_dict=col_to_stype_dict, # 指定的列类型
text_embedder_cfg=text_embedder_cfg, # 我们选择的文本编码器
cache_dir=os.path.join(
根目录, f"rel-ecomm_materialized_cache"
),# 为了方便存储材料化图
)
剩下的代码将从标准层构建GNN,编写训练循环,并进行一些评估。由于这部分代码在各个任务中都是标准且相同的,为了简洁起见,我将在本博客文章中省略这些代码。您可以查看笔记本中的内容这里.
因此,我们可以训练这个GNN达到约0.3的r2值和500的MAE。这意味着它预测卖家在未来30天内的收入误差平均为±500美元。当然,我们无法确定这是否是好的结果,也许通过结合传统机器学习和特征工程可以得到80%的r2值。
关系深度学习是一种有趣的新型机器学习方法,特别是在我们有一个复杂的关联模式时,手动特征工程会变得非常繁琐。它使我们能够仅使用SQL定义一个机器学习任务,这对于那些不深入数据科学但熟悉一些SQL的人来说特别有用。这也意味着我们可以快速迭代并针对不同的任务进行大量实验。
同时,这种方法也存在诸如训练GNNs的难度和根据关系模式构建图的困难等问题。此外,问题在于RDL在性能上能多大程度上与经典机器学习模型竞争。在过去,我们看到像XGboost这样的模型已经证明在表格预测问题上比神经网络表现更好。