譯者 | 朱先忠
審校 | 重樓
本文將探討直接在關(guān)系數(shù)據(jù)庫(kù)上執(zhí)行機(jī)器學(xué)習(xí)的新方法——關(guān)系型深度學(xué)習(xí)。
本文示例項(xiàng)目數(shù)據(jù)集的關(guān)系模式(作者提供圖片)
在本文中,我們將深入探討一種有趣的深度學(xué)習(xí)(DL)新方法,稱為關(guān)系型深度學(xué)習(xí)(RDL)。我們還將通過(guò)在一家電子商務(wù)公司的真實(shí)數(shù)據(jù)庫(kù)(不是數(shù)據(jù)集?。┥献鲆恍㏑DL來(lái)獲得一些實(shí)踐經(jīng)驗(yàn)。
簡(jiǎn)介
在現(xiàn)實(shí)世界中,我們通常有一個(gè)關(guān)系數(shù)據(jù)庫(kù),我們想在這個(gè)數(shù)據(jù)庫(kù)上運(yùn)行一些機(jī)器學(xué)習(xí)任務(wù)。但是,有時(shí)候數(shù)據(jù)庫(kù)需要高度規(guī)范化;這意味著,大量耗時(shí)的特征工程和粒度損失,因?yàn)槲覀儽仨氝M(jìn)行大量的聚合操作。更重要的是,我們可以構(gòu)建無(wú)數(shù)種可能的特征組合,每種組合都可能產(chǎn)生良好的性能(【文獻(xiàn)2】)。這意味著,我們可能會(huì)在數(shù)據(jù)庫(kù)表格中留下一些與ML任務(wù)相關(guān)的信息。
這類似于計(jì)算機(jī)視覺的早期,在深度神經(jīng)網(wǎng)絡(luò)出現(xiàn)之前,特征工程任務(wù)是基于像素值形式手工完成的。如今,模型直接使用原始像素,而不再依賴于這個(gè)中間環(huán)節(jié)。
關(guān)系型深度學(xué)習(xí)
關(guān)系型深度學(xué)習(xí)(RDL)承諾用表格形式學(xué)習(xí)實(shí)現(xiàn)同樣的事情。也就是說(shuō),它消除了通過(guò)直接在關(guān)系數(shù)據(jù)庫(kù)上學(xué)習(xí)來(lái)構(gòu)建特征矩陣的額外步驟。RDL通過(guò)將數(shù)據(jù)庫(kù)及其關(guān)系轉(zhuǎn)換為圖來(lái)實(shí)現(xiàn)這一點(diǎn);其中,表中的一行成為節(jié)點(diǎn),表之間的關(guān)系成為邊,行值作為節(jié)點(diǎn)特征存儲(chǔ)在節(jié)點(diǎn)內(nèi)。
在本文中,我們將使用Kaggle的電子商務(wù)數(shù)據(jù)集,該數(shù)據(jù)集包含有關(guān)星形模式中電子商務(wù)平臺(tái)的交易數(shù)據(jù),其中包含一個(gè)核心事實(shí)表(交易)和一些維度表。完整的代碼可以在鏈接處的筆記本文件中找到。
在本文中,我們將使用relbench庫(kù)來(lái)執(zhí)行RDL。在relbench中,我們必須做的第一件事是指定關(guān)系數(shù)據(jù)庫(kù)的模式。下面給出一個(gè)示例,說(shuō)明我們?nèi)绾螌?duì)數(shù)據(jù)庫(kù)中的“事務(wù)”表執(zhí)行此操作。我們將表作為pandas數(shù)據(jù)幀給出,并指定主鍵和時(shí)間戳列。主鍵列用于唯一標(biāo)識(shí)實(shí)體。時(shí)間戳確保我們只能在預(yù)測(cè)未來(lái)交易時(shí)從過(guò)去的交易中學(xué)習(xí)。在這種構(gòu)圖中,這意味著信息只能從時(shí)間戳較低的節(jié)點(diǎn)(即過(guò)去)流向時(shí)間戳較高的節(jié)點(diǎn)。此外,我們指定關(guān)系中存在的外鍵。在這種情況下,事務(wù)表具有列“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'
)
其余的表需要以相同的方式定義。請(qǐng)注意,如果你已經(jīng)有了數(shù)據(jù)庫(kù)模式,這也可以通過(guò)自動(dòng)化的方式實(shí)現(xiàn)。由于數(shù)據(jù)集來(lái)自Kaggle,所以我需要手動(dòng)創(chuàng)建模式。我們還需要將日期列轉(zhuǎn)換為實(shí)際的pandas日期時(shí)間對(duì)象,并刪除任何NaN值。
class EcommerceDataBase(Dataset):
#創(chuàng)建你自己的數(shù)據(jù)集的示例: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)
至關(guān)重要的是,作者引入了訓(xùn)練表的概念。這個(gè)訓(xùn)練表基本上定義了ML任務(wù)。這里的想法是,我們想預(yù)測(cè)數(shù)據(jù)庫(kù)中某個(gè)實(shí)體的未來(lái)狀態(tài)(即未來(lái)值)。我們通過(guò)指定一個(gè)表來(lái)實(shí)現(xiàn)這一點(diǎn),其中每一行都有一個(gè)時(shí)間戳、實(shí)體的標(biāo)識(shí)符和我們想要預(yù)測(cè)的一些值。id用于指定實(shí)體,時(shí)間戳指定我們需要預(yù)測(cè)實(shí)體的時(shí)間點(diǎn)。這也將限制可用于推斷此實(shí)體值的數(shù)據(jù)(即僅過(guò)去的數(shù)據(jù))。值本身就是我們想要預(yù)測(cè)的(即真實(shí)數(shù)據(jù)值)。
就我們而言,我們有一個(gè)與客戶互動(dòng)的在線平臺(tái)。我們希望預(yù)測(cè)客戶在未來(lái)30天內(nèi)的收入。我們可以使用DuckDB執(zhí)行的SQL語(yǔ)句創(chuàng)建訓(xùn)練表。這是RDL的一大優(yōu)勢(shì),因?yàn)槲覀兛梢詢H使用SQL創(chuàng)建任何類型的ML任務(wù)。例如,我們可以定義一個(gè)查詢來(lái)選擇未來(lái)30天內(nèi)買家的購(gòu)買數(shù)量,以進(jìn)行流失預(yù)測(cè)。
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()
結(jié)果將是一個(gè)數(shù)據(jù)庫(kù)表格,其中seller_id是我們想要預(yù)測(cè)的實(shí)體的關(guān)鍵字,收入是目標(biāo),時(shí)間戳是我們需要進(jìn)行預(yù)測(cè)的時(shí)間(即我們只能使用到目前為止的數(shù)據(jù)進(jìn)行預(yù)測(cè))。
訓(xùn)練表(作者提供圖片)
下面是創(chuàng)建“customer_venue”任務(wù)的完整代碼。
class CustomerRevenueTask(EntityTask):
# 自定義任務(wù)示例: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) # 我們想要預(yù)測(cè)未來(lái)的收入。
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,
)
至此,我們已經(jīng)完成了大部分工作。其余的工作流程都是類似的,獨(dú)立于機(jī)器學(xué)習(xí)任務(wù)。我能夠從relbench提供的示例筆記本文件中復(fù)制大部分代碼。
例如,我們需要對(duì)節(jié)點(diǎn)特征進(jìn)行編碼。在這里,我們可以使用GloVe嵌入(【譯者注】個(gè)別網(wǎng)文中翻譯為“手套嵌入”)來(lái)編碼所有文本特征,如產(chǎn)品描述和產(chǎn)品名稱。
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))
之后,我們可以將這些轉(zhuǎn)換應(yīng)用于我們的數(shù)據(jù)并構(gòu)建圖表。
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
)
其余的代碼將從標(biāo)準(zhǔn)層構(gòu)建GNN(圖神經(jīng)網(wǎng)絡(luò)),對(duì)循環(huán)訓(xùn)練進(jìn)行編碼,并進(jìn)行一些評(píng)估。為了簡(jiǎn)單起見,我將把這段代碼從本文中刪除,因?yàn)樗浅?biāo)準(zhǔn),在各個(gè)任務(wù)中都是一樣的。你可以在鏈接https://github.com/LaurinBrechter/GraphTheory/tree/main/rdl處查看對(duì)應(yīng)的筆記本文件。
訓(xùn)練結(jié)果(作者提供圖片)
因此,我們可以訓(xùn)練這個(gè)GNN,使其r2達(dá)到0.3左右,MAE達(dá)到500。這意味著,它預(yù)測(cè)賣家在未來(lái)30天的收入,平均誤差為+-500美元。當(dāng)然,我們不知道這是好是壞,也許通過(guò)經(jīng)典機(jī)器學(xué)習(xí)和特征工程的結(jié)合,我們可以得到80%的r2。
結(jié)論
關(guān)系型深度學(xué)習(xí)是一種有趣的機(jī)器學(xué)習(xí)新方法,特別是當(dāng)我們有一個(gè)復(fù)雜的關(guān)系模式時(shí),手動(dòng)特征工程太費(fèi)力了。它使我們能夠僅使用SQL定義ML任務(wù),這對(duì)于那些不深入研究數(shù)據(jù)科學(xué)但僅了解一些SQL的人來(lái)說(shuō)尤其有用。這也意味著,我們可以快速迭代,并對(duì)不同的任務(wù)進(jìn)行大量實(shí)驗(yàn)。
同時(shí),這種方法也存在自己的問(wèn)題,例如訓(xùn)練GNN和從關(guān)系模式構(gòu)建圖存在不少困難。此外,還有一個(gè)問(wèn)題是,RDL在性能方面能在多大程度上與經(jīng)典ML模型競(jìng)爭(zhēng)。過(guò)去,我們已經(jīng)看到,在表格預(yù)測(cè)問(wèn)題上,XGboost等模型已被證明比神經(jīng)網(wǎng)絡(luò)更好。
參考文獻(xiàn)
【1】Robinson,Joshua等人,《RelBench:關(guān)系數(shù)據(jù)庫(kù)深度學(xué)習(xí)的基準(zhǔn)》,arXiv,2024,https://arxiv.org/abs/2407.20060。
【2】Fey、Matthias等人,《關(guān)系深度學(xué)習(xí):關(guān)系數(shù)據(jù)庫(kù)上的圖表示學(xué)習(xí)》,arXiv預(yù)印本arXiv:2312.04615(2023)。
【3】Schlichtkrull,Michael等人?!队脠D卷積網(wǎng)絡(luò)建模關(guān)系數(shù)據(jù)》,語(yǔ)義網(wǎng):第15屆國(guó)際會(huì)議,2018年ESWC,希臘克里特島伊拉克利翁,2018年6月3日至7日,會(huì)議記錄#15。施普林格國(guó)際出版社,2018年。
譯者介紹
朱先忠,51CTO社區(qū)編輯,51CTO專家博客、講師,濰坊一所高校計(jì)算機(jī)教師,自由編程界老兵一枚。
原文標(biāo)題:Self-Service ML with Relational Deep Learning,作者:Laurin Brechter