譯者 | 朱先忠
審校 | 重樓
本文將探討直接在關系數據庫上執行機器學習的新方法——關系型深度學習。
本文示例項目數據集的關系模式(作者提供圖片)
在本文中,我們將深入探討一種有趣的深度學習(DL)新方法,稱為關系型深度學習(RDL)。我們還將通過在一家電子商務公司的真實數據庫(不是數據集?。┥献鲆恍㏑DL來獲得一些實踐經驗。
簡介
在現實世界中,我們通常有一個關系數據庫,我們想在這個數據庫上運行一些機器學習任務。但是,有時候數據庫需要高度規范化;這意味著,大量耗時的特征工程和粒度損失,因為我們必須進行大量的聚合操作。更重要的是,我們可以構建無數種可能的特征組合,每種組合都可能產生良好的性能(【文獻2】)。這意味著,我們可能會在數據庫表格中留下一些與ML任務相關的信息。
這類似于計算機視覺的早期,在深度神經網絡出現之前,特征工程任務是基于像素值形式手工完成的。如今,模型直接使用原始像素,而不再依賴于這個中間環節。
關系型深度學習
關系型深度學習(RDL)承諾用表格形式學習實現同樣的事情。也就是說,它消除了通過直接在關系數據庫上學習來構建特征矩陣的額外步驟。RDL通過將數據庫及其關系轉換為圖來實現這一點;其中,表中的一行成為節點,表之間的關系成為邊,行值作為節點特征存儲在節點內。
在本文中,我們將使用Kaggle的電子商務數據集,該數據集包含有關星形模式中電子商務平臺的交易數據,其中包含一個核心事實表(交易)和一些維度表。完整的代碼可以在鏈接處的筆記本文件中找到。
在本文中,我們將使用relbench庫來執行RDL。在relbench中,我們必須做的第一件事是指定關系數據庫的模式。下面給出一個示例,說明我們如何對數據庫中的“事務”表執行此操作。我們將表作為pandas數據幀給出,并指定主鍵和時間戳列。主鍵列用于唯一標識實體。時間戳確保我們只能在預測未來交易時從過去的交易中學習。在這種構圖中,這意味著信息只能從時間戳較低的節點(即過去)流向時間戳較高的節點。此外,我們指定關系中存在的外鍵。在這種情況下,事務表具有列“customer_key”,該列是指向“customer_dim”表的外鍵。
tables['transactions'] = Table(
df=pd.DataFrame(t),
pkey_col='t_id',
fkey_col_to_pkey_table={
'customer_key': 'customers',
'item_key': 'products',
'store_key': 'stores'
},
time_col='date'
)
其余的表需要以相同的方式定義。請注意,如果你已經有了數據庫模式,這也可以通過自動化的方式實現。由于數據集來自Kaggle,所以我需要手動創建模式。我們還需要將日期列轉換為實際的pandas日期時間對象,并刪除任何NaN值。
class EcommerceDataBase(Dataset):
#創建你自己的數據集的示例:https://github.com/snap-stanford/relbench/blob/main/tutorials/custom_dataset.ipynb
val_timestamp = pd.Timestamp(year=2018, month=1, day=1)
test_timestamp = pd.Timestamp(year=2020, month=1, day=1)
def make_db(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 = load_csv_to_db(BASE_DIR + '/store_dim.csv').drop(columns=['upazila'])
products = load_csv_to_db(BASE_DIR + '/item_dim.csv')
transactions = load_csv_to_db(BASE_DIR + '/fact_table.csv').rename(columns={'coustomer_key': 'customer_key'})
times = load_csv_to_db(BASE_DIR + '/time_dim.csv')
t = transactions.merge(times[['time_key', 'date']], on='time_key').drop(columns=['payment_key', 'time_key', 'unit'])
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)
print(t.isna().sum(axis=0))
print(products.isna().sum(axis=0))
print(stores.isna().sum(axis=0))
print(customers.isna().sum(axis=0))
tables['products'] = Table(
df=pd.DataFrame(products),
pkey_col='item_key',
fkey_col_to_pkey_table={},
time_col=None
)
tables['customers'] = Table(
df=pd.DataFrame(customers),
pkey_col='customer_key',
fkey_col_to_pkey_table={},
time_col=None
)
tables['transactions'] = Table(
df=pd.DataFrame(t),
pkey_col='t_id',
fkey_col_to_pkey_table={
'customer_key': 'customers',
'item_key': 'products',
'store_key': 'stores'
},
time_col='date'
)
tables['stores'] = Table(
df=pd.DataFrame(stores),
pkey_col='store_key',
fkey_col_to_pkey_table={}
)
return Database(tables)
至關重要的是,作者引入了訓練表的概念。這個訓練表基本上定義了ML任務。這里的想法是,我們想預測數據庫中某個實體的未來狀態(即未來值)。我們通過指定一個表來實現這一點,其中每一行都有一個時間戳、實體的標識符和我們想要預測的一些值。id用于指定實體,時間戳指定我們需要預測實體的時間點。這也將限制可用于推斷此實體值的數據(即僅過去的數據)。值本身就是我們想要預測的(即真實數據值)。
就我們而言,我們有一個與客戶互動的在線平臺。我們希望預測客戶在未來30天內的收入。我們可以使用DuckDB執行的SQL語句創建訓練表。這是RDL的一大優勢,因為我們可以僅使用SQL創建任何類型的ML任務。例如,我們可以定義一個查詢來選擇未來30天內買家的購買數量,以進行流失預測。
df = duckdb.sql(f"""
select
timestamp,
customer_key,
sum(total_price) as revenue
from
timestamp_df t
left join
transactions ta
on
ta.date <= t.timestamp + INTERVAL '{self.timedelta}'
and ta.date > t.timestamp
group by timestamp, customer_key
""").df().dropna()
結果將是一個數據庫表格,其中seller_id是我們想要預測的實體的關鍵字,收入是目標,時間戳是我們需要進行預測的時間(即我們只能使用到目前為止的數據進行預測)。
訓練表(作者提供圖片)
下面是創建“customer_venue”任務的完整代碼。
class CustomerRevenueTask(EntityTask):
# 自定義任務示例:https://github.com/snap-stanford/relbench/blob/main/tutorials/custom_task.ipynb
task_type = TaskType.REGRESSION
entity_col = "customer_key"
entity_table = "customers"
time_col = "timestamp"
target_col = "revenue"
timedelta = pd.Timedelta(days=30) # 我們想要預測未來的收入。
metrics = [r2, mae]
num_eval_timestamps = 40
def make_table(self, db: Database, timestamps: "pd.Series[pd.Timestamp]") -> Table:
timestamp_df = pd.DataFrame({"timestamp": timestamps})
transactions = db.table_dict["transactions"].df
df = duckdb.sql(f"""
select
timestamp,
customer_key,
sum(total_price) as revenue
from
timestamp_df t
left join
transactions ta
on
ta.date <= t.timestamp + INTERVAL '{self.timedelta}'
and ta.date > t.timestamp
group by timestamp, customer_key
""").df().dropna()
print(df)
return Table(
df=df,
fkey_col_to_pkey_table={self.entity_col: self.entity_table},
pkey_col=None,
time_col=self.time_col,
)
至此,我們已經完成了大部分工作。其余的工作流程都是類似的,獨立于機器學習任務。我能夠從relbench提供的示例筆記本文件中復制大部分代碼。
例如,我們需要對節點特征進行編碼。在這里,我們可以使用GloVe嵌入(【譯者注】個別網文中翻譯為“手套嵌入”)來編碼所有文本特征,如產品描述和產品名稱。
from typing import List, Optional
from sentence_transformers import SentenceTransformer
from torch import Tensor
class GloveTextEmbedding:
def __init__(self, device: Optional[torch.device
] = None):
self.model = SentenceTransformer(
"sentence-transformers/average_word_embeddings_glove.6B.300d",
device=device,
)
def __call__(self, sentences: List[str]) -> Tensor:
return torch.from_numpy(self.model.encode(sentences))
之后,我們可以將這些轉換應用于我們的數據并構建圖表。
from torch_frame.config.text_embedder import TextEmbedderConfig
from relbench.modeling.graph import make_pkey_fkey_graph
text_embedder_cfg = TextEmbedderConfig(
text_embedder=GloveTextEmbedding(device=device), batch_size=256
)
data, col_stats_dict = make_pkey_fkey_graph(
db,
col_to_stype_dict=col_to_stype_dict, # speficied column types
text_embedder_cfg=text_embedder_cfg, # our chosen text encoder
cache_dir=os.path.join(
root_dir, f"rel-ecomm_materialized_cache"
), # store materialized graph for convenience
)
其余的代碼將從標準層構建GNN(圖神經網絡),對循環訓練進行編碼,并進行一些評估。為了簡單起見,我將把這段代碼從本文中刪除,因為它非常標準,在各個任務中都是一樣的。你可以在鏈接https://github.com/LaurinBrechter/GraphTheory/tree/main/rdl處查看對應的筆記本文件。
訓練結果(作者提供圖片)
因此,我們可以訓練這個GNN,使其r2達到0.3左右,MAE達到500。這意味著,它預測賣家在未來30天的收入,平均誤差為+-500美元。當然,我們不知道這是好是壞,也許通過經典機器學習和特征工程的結合,我們可以得到80%的r2。
結論
關系型深度學習是一種有趣的機器學習新方法,特別是當我們有一個復雜的關系模式時,手動特征工程太費力了。它使我們能夠僅使用SQL定義ML任務,這對于那些不深入研究數據科學但僅了解一些SQL的人來說尤其有用。這也意味著,我們可以快速迭代,并對不同的任務進行大量實驗。
同時,這種方法也存在自己的問題,例如訓練GNN和從關系模式構建圖存在不少困難。此外,還有一個問題是,RDL在性能方面能在多大程度上與經典ML模型競爭。過去,我們已經看到,在表格預測問題上,XGboost等模型已被證明比神經網絡更好。
參考文獻
【1】Robinson,Joshua等人,《RelBench:關系數據庫深度學習的基準》,arXiv,2024,https://arxiv.org/abs/2407.20060。
【2】Fey、Matthias等人,《關系深度學習:關系數據庫上的圖表示學習》,arXiv預印本arXiv:2312.04615(2023)。
【3】Schlichtkrull,Michael等人?!队脠D卷積網絡建模關系數據》,語義網:第15屆國際會議,2018年ESWC,希臘克里特島伊拉克利翁,2018年6月3日至7日,會議記錄#15。施普林格國際出版社,2018年。
譯者介紹
朱先忠,51CTO社區編輯,51CTO專家博客、講師,濰坊一所高校計算機教師,自由編程界老兵一枚。
原文標題:Self-Service ML with Relational Deep Learning,作者:Laurin Brechter