
譯者 | 核子可樂
審校 | 重樓
作為眾多行業(yè)中不可或缺的組成部分,產(chǎn)品推薦系統(tǒng)對(duì)于提供商和消費(fèi)者而言至關(guān)重要,堪稱消費(fèi)體驗(yàn)與銷售額提升的助推器。
企業(yè)會(huì)收集并分析大量使用情況與行為數(shù)據(jù),借此優(yōu)化購買推薦與用戶滿意度。而一旦推薦不準(zhǔn)或者不及時(shí),則可能引發(fā)銷售損失并拉低消費(fèi)者體驗(yàn)。
本文將介紹一套基于用戶及產(chǎn)品向量嵌入的實(shí)時(shí)推薦系統(tǒng),由BigQuery(Google Cloud完全托管的PB級(jí)數(shù)據(jù)倉庫)及Spanner(Coogle Cloud完全托管、適合關(guān)鍵任務(wù)的全局規(guī)模數(shù)據(jù)庫)提供支持。

向量嵌入
向量嵌入在生成推薦中扮演著關(guān)鍵角色,它基于用戶與產(chǎn)品或服務(wù)間的交互來捕捉用戶行為、偏好和意圖,進(jìn)而表示產(chǎn)品或服務(wù)的特征及屬性。向量嵌入會(huì)將用戶和產(chǎn)品表示為高維數(shù)值向量,并通過計(jì)算各向量間的距離以衡量產(chǎn)品間、用戶間以及產(chǎn)品與用戶間的相似性。
以下圖為例,其中一張是棒球與球棍,另一張則是釣魚器具。將這些圖片輸入Gemini,并要求大模型“為兩張圖片生成一個(gè)64維幾量嵌入,使兩個(gè)嵌入的維度保持一致”,即可為其生成JSOn數(shù)組,且維度與下圖中維度標(biāo)簽的描述相同。

這時(shí)假定用戶首先與棒球圖片交互,我們可以將棒球嵌入與用戶現(xiàn)有嵌入進(jìn)行聚合以更新用戶嵌入(假設(shè)用戶嵌入具有相同維度)。在此示例中,我們使用簡單的平均值進(jìn)行聚合(大家可選擇更符合自身業(yè)務(wù)需求的方式)。在與漁具圖像交互后,系統(tǒng)會(huì)取兩個(gè)嵌入向量的平均值,并將其應(yīng)用于用戶嵌入。更新后的向量如下所示:

使用BigQuery進(jìn)行批處理
網(wǎng)站或應(yīng)用上的管線數(shù)據(jù)量可能非常巨大。根據(jù)用戶交互情況,大多數(shù)用戶可能并不需要立即獲取產(chǎn)品推薦。我們使用目標(biāo)嵌入(如圖片、橫幅、按鈕等網(wǎng)站及應(yīng)用元素)在BigQuery中收集并批量處理這些高容量、高速度交互數(shù)據(jù)。之后,我們使用先前計(jì)算的用戶嵌入執(zhí)行滾動(dòng)聚合,以更新最終用戶嵌入。這些用戶嵌入隨后會(huì)被推送至Spanner(通過反向ETL機(jī)制),以針對(duì)特定用戶ID進(jìn)行實(shí)時(shí)產(chǎn)品推薦。

批處理步驟如下:
- 在特定批處理時(shí)長內(nèi)獲取不同用戶ID,這能減少后續(xù)步驟從用戶表掃描的數(shù)據(jù)量。
- 在給定批處理時(shí)長內(nèi),將事件表與目標(biāo)表對(duì)接起來,以將目標(biāo)嵌入映射至各用戶-目標(biāo)交互。
- 將所有映射嵌入與用戶表中相應(yīng)的用戶ID進(jìn)行合并。
- 計(jì)算各用戶在每個(gè)維度上的嵌入的滾動(dòng)平均值。
- 將更新后的嵌入添加至用戶表內(nèi)。
---------------------------------------------------------------------------------------------
-- BQ Schema :
---------------------------------------------------------------------------------------------
-- dataset.events
-- user_id String,
-- target_id String,
-- ts Timestamp
-- dataset.targets
-- target_id String,
-- target_emb String
-- dataset.users
-- user_id String,
-- emb String,
-- last_updated_ts Timestamp
---------------------------------------------------------------------------------------------
WITH
-- STEP 1
dist_user_ids AS (
SELECT
DISTINCT events.user_id,
FROM
dataset.events
WHERE
events.ts >= $curr_batch_ts),
-- STEP 2
user_target_emb AS (
SELECT
events.user_id,
1 AS target_count,
targets.target_emb AS emb,
FROM
dataset.events events
JOIN
dataset.targets targets
ON
events.target_id = targets.target_id
WHERE
events.ts >= $curr_batch_ts
-- STEP 3
UNION ALL
SELECT
usres.user_id,
users.target_count,
users.emb,
FROM
dataset.users users
JOIN
dist_user_ids
ON
users.user_id = dist_user_ids.user_id),
-- STEP 4
emb_average AS (
SELECT
user_target_emb.user_id,
idx,
SUM(user_target_emb.target_count) AS target_count,
SUM(user_target_emb.target_count * emb_val)/SUM(user_target_emb.target_count) new_emb_val
FROM
user_target_emb,
UNNEST(user_target_emb.emb) emb_val
WITH
OFFSET
AS idx
GROUP BY
1,
2),
updated_user_embeddings AS (
SELECT
user_id,
ANY_VALUE(target_count) AS target_count,
ARRAY_AGG(new_emb_val
ORDER BY
idx) AS new_emb
FROM
emb_average
GROUP BY
1 )
-- STEP 5
SELECT
user_id,
target_count,
new_emb AS emb,
CURRENT_TIMESTAMP() AS last_updated_ts
FROM
updated_user_embeddings;用Spanner實(shí)現(xiàn)實(shí)時(shí)推薦
最新批次的更新用戶嵌入將通過反向ETL推送至相應(yīng)Spanner表內(nèi)。目標(biāo)嵌入的對(duì)應(yīng)表也在Spanner中維護(hù)。
Spanner中僅當(dāng)時(shí)間戳比BigQuery中正在處理的當(dāng)前批次更晚(更新)時(shí),才需要讀取事件流數(shù)據(jù)。我們可以設(shè)置作業(yè)或分配TTL標(biāo)記以定期清理此表。
除事件、用戶及目標(biāo)表之外,我們還須維護(hù)資產(chǎn)表,其中包含用于個(gè)性化推薦的預(yù)測資產(chǎn)。這些資產(chǎn)擁有自己的嵌入,且與用戶及目標(biāo)嵌入的維度相匹配。

當(dāng)前端針對(duì)給定用戶發(fā)出預(yù)測調(diào)用時(shí):
- 該用戶的全部最新事件將與目標(biāo)表對(duì)接,以將目標(biāo)嵌入映射至各用戶-目標(biāo)交互。
- 將所有映射的嵌入與用戶表內(nèi)相應(yīng)的用戶ID進(jìn)行合并。
- 計(jì)算給定用戶在各維度上的嵌入的最終滾動(dòng)平均值。
- 之后使用最終用戶嵌入計(jì)算與資產(chǎn)間的距離。
- 返回n個(gè)最接近的資產(chǎn),作為個(gè)性化推測預(yù)測的內(nèi)容。
---------------------------------------------------------------------------------------------
-- Spanner Schema :
---------------------------------------------------------------------------------------------
-- events
-- user_id String,
-- target_id String,
-- ts Timestamp
-- targets
-- target_id String,
-- target_emb String
-- users
-- user_id String,
-- emb String,
-- last_updated_ts Timestamp
-- assets
-- asset_id String,
-- asset_emb String,
---------------------------------------------------------------------------------------------
WITH
-- STEP 1
user_target_emb AS (
SELECT
events.user_id,
1 AS target_count,
targets.target_emb AS emb,
FROM
events
JOIN
targets
ON
events.target_id = targets.target_id
WHERE
events.user_id = "$user_id"
-- STEP 2
UNION ALL
SELECT
usres.user_id,
users.target_count,
users.emb,
FROM
users
WHERE
users.user_id = "$user_id" ),
-- STEP 3
emb_average AS (
SELECT
idx,
SUM(user_target_emb.target_count * emb_val)/SUM(user_target_emb.target_count) new_emb_val
FROM
user_target_emb,
UNNEST(user_target_emb.emb) emb_val
WITH
OFFSET
AS idx
GROUP BY
1),
updated_user_embeddings AS (
SELECT
ARRAY_AGG(new_emb_val
ORDER BY
idx) AS new_emb
FROM
emb_average ),
-- STEP 4
distances AS (
SELECT
asset_id,
EUCLIDEAN_DISTANCE((
SELECT
new_emb
FROM
updated_user_embeddings),
assets.asset_emb) AS distance,
FROM
assets)
-- STEP 5
SELECT
asset_id,
distance
FROM
distances
ORDER BY
2 DESC
LIMIT
$n總結(jié)
根據(jù)業(yè)務(wù)需求及規(guī)則條款,前端系統(tǒng)將決定向用戶展示哪些產(chǎn)品作為最終推薦。具體方式可以只考慮最短距離,也可以用更復(fù)雜的方式對(duì)預(yù)測結(jié)果進(jìn)行重新排序。
綜合流程圖如下:

如上所示,通過實(shí)時(shí)與批處理流程相結(jié)合,即可覆蓋用戶的每一次交互,并根據(jù)用戶當(dāng)前的“空間與時(shí)間”背景推薦與其喜好相匹配的產(chǎn)品和服務(wù)。
更重要的是,這套架構(gòu)亦具有彈性,可根據(jù)用戶流量及應(yīng)用需求進(jìn)行靈活擴(kuò)展。
原文標(biāo)題:Real-Time Recommendations Powered by Spanner, BigQuery, and Vector Embeddings,作者:Yogesh Tewari


























