廣告後台最常看的是取得一位新客要花多少錢,以及這筆訂單賺回多少,但同樣花幾百元拿到的新客,有人一個月後又回來買、有人買完一次就不再出現,如果只用第一筆訂單的金額決定出價,會把預算投給首單大但不會回來的客人,錯過首單小但會一直回購的客人。
比較好的做法是看顧客的長期價值(Customer Lifetime Value,常簡稱 LTV),也就是這位顧客未來還會帶來多少營收,但新客剛下單時還看不出來他會不會回購,等看得出來又太晚了,所以今天要做的是只用第一筆訂單當下就知道的資訊,讓 BigQuery ML 預測他接下來 30 天還會帶來多少營收。
Day 11 的分群要等顧客回購才分得準,今天換個方向,同樣在最後揭曉 Day 05 合成器藏進去的四種顧客類型,看預測值和真實的回購節奏對不對得上,今天的雲端費用都以 1 美元約 32 元換算成新台幣。
今日核心目標:
| 步驟 | 做什麼 | 產出 |
|---|---|---|
| 整理特徵 | 從 Day 07 的 fct_orders 取每位顧客的第一筆訂單,加上之後 30 天的回購營收 |
ltv_features,2,461 人 |
| 基準線 | 全體平均、依首購商品平均,不用機器學習 | 驗證集誤差 |
| 建模型 | 用首購在 8/8 以前的 1,183 人訓練線性迴歸 | ltv_linreg |
| 評估 | 用首購在 8/9–8/17 的 275 人驗證,和基準線比誤差 | 誤差比較表 |
| 預測 | 驗證集與首購在 8/18 之後的 1,003 人 | mart_customer_ltv |
| 算價值 | 新客的首單金額加預測回購,除以同期取得成本 | 通路與活動價值表 |
| 揭曉 | 預測值和答案表對照 | 對照表 |
💡 核心工程理念:
mart_customer_ltv,Day 13 的驗收與之後的 AI 查詢都會用到機器學習的題目要先想清楚「在什麼時間點、用什麼資訊、預測什麼」,今天的時間點是顧客剛完成第一筆訂單,這時候知道的只有他買了哪項商品、買幾件、花多少錢,以及從哪個來源、哪個媒介、哪個活動進來,要預測的是首購後 30 天內的回購營收,第一筆訂單本身不算在內。
最容易犯的錯是把 Day 11 的分群當成特徵,分群用了訂單數、回購間隔、距今天數,這些都是首購之後才發生的事,拿來預測未來回購等於先偷看答案,驗證時分數會很漂亮,真的拿去預測新客時卻用不了,因為新客還沒有這些紀錄,機器學習稱這種情況為資料洩漏(Data Leakage)。
第二個要決定的是觀察多久,資料最後一天是 9/16,我原本想看 60 天,但首購後已經滿 60 天的人只剩 628 位,而且重訓襪專案 7/15 才開始,60 天窗裡幾乎沒有這個活動的顧客,改成 30 天後有 1,458 人可以用,代價是回購間隔比 30 天長的顧客,30 天內還看不到他們回來,Day 11 的輪廓裡浴巾客首購買得多、之後很少回來,就是可能被低估的一群,3.4 用預測做決策時要記得這一點。
還沒滿 30 天的人不能拿來訓練,例如 9/10 才首購的人,到 9/16 只觀察了 6 天,回購營收是 0 不代表他不會回來,只是還沒到時間,把這些人當成「不會回購」訓練,模型就會學到錯的東西,所以 ltv_features 用 label_complete 標出已經觀察滿 30 天的人,只有他們可以用來訓練和驗證。
訓練和驗證的切法也要照時間,已滿 30 天的 1,458 人依首購日期排序,最早的約八成(首購在 8/8 以前的 1,183 人)訓練,最晚的約兩成(8/9–8/17 的 275 人)驗證,同一天首購的人不拆開,隨機切的話訓練資料會混進比驗證顧客更晚首購的人,等於用未來預測過去,實際使用時永遠是用過去預測未來,驗證也要照這個方向,特徵的 SQL 在 ltv/features.sql。
整理完後,已滿 30 天的 1,458 人平均 30 天回購營收是 92.8 元,其中 25.2% 的人在 30 天內有回購,大部分人是 0,少數人回購好幾次,這種分布本身就不好預測。
在訓練模型之前,我先算兩個不用機器學習的猜法,第一個是全體平均,每個人都猜訓練集的平均 96.4 元,第二個是依首購商品平均,買同一項商品的人猜同一個數字,這兩條基準線是模型的及格線,模型的誤差要比它們小,才代表機器學習真的有幫上忙。
模型用 BigQuery ML 的線性迴歸,也就是把每個特徵乘上一個權重再加總,字串欄位像首購商品、來源、媒介,BigQuery ML 會自動拆成是或否的欄位,data_split_method = 'CUSTOM' 讓模型照 is_eval 欄位切訓練與驗證,而不是隨機切:
CREATE OR REPLACE MODEL martech_dw.ltv_linreg
OPTIONS(
model_type = 'LINEAR_REG',
input_label_cols = ['future_revenue_30d'],
data_split_method = 'CUSTOM',
data_split_col = 'is_eval'
) AS
SELECT
first_item_id, first_qty, first_revenue,
first_source, first_medium,
future_revenue_30d, is_eval
FROM martech_dw.ltv_features
WHERE label_complete;
活動代號沒有放進來,原因在第 6 章說明,三種方法在驗證集 275 人上的誤差如下,MAE 是平均絕對誤差,也就是平均猜錯多少元,RMSE 會放大猜錯很多的情況:
| 方法 | MAE | RMSE | 平均預測 | 平均實際 |
|---|---|---|---|---|
| ① 全體平均 | 131.0 | 171.8 | 96.4 | 77.4 |
| ② 依首購商品平均 | 111.4 | 161.8 | 104.8 | 77.4 |
| ③ 線性迴歸 | 110.9 | 160.8 | 102.4 | 77.4 |
線性迴歸比全體平均好很多,但只比「依首購商品平均」少猜錯 0.5 元,也就是說加上來源、媒介、件數、金額之後,準確度幾乎沒有進步,模型能用的資訊幾乎都來自「買什麼」,ML.EVALUATE 回傳的 R²(模型解釋了多少變異,1 代表完全解釋)只有 0.113,看起來很低,但只看 R² 會誤以為模型沒用,和基準線比才知道它比全體平均好,只是幾乎沒有比最簡單的分組平均好。
三種方法的平均預測都比實際高,驗證期這批顧客實際的回購營收平均 77.4 元,比訓練期的 96.4 元低,模型用過去的平均水準去猜,碰到回購比較少的一批人就會整體高估,這也是依時間切才看得到的問題,隨機切的話訓練和驗證混在一起,平均水準差不多,這個落差就被藏起來了。
合成器藏進去的四種顧客是高頻買襪客、浴巾大量客、新手組合客和沉睡客,答案表裡寫的回購節奏是高頻買襪客每次約 75% 的機率回購、平均隔 25 天,新手組合客 35%、隔 40 天,浴巾大量客只有 10%、隔 60 天,沉睡客買完第一次就不會再回來,Day 12 只有揭曉用的 reveal.sql 和檢查第 9 項的 check.sql 會讀答案表,下表是驗證集 275 人:
| 真實類型 | 人數 | 平均預測 | 平均實際 | 30 天內回購比例 |
|---|---|---|---|---|
| 高頻買襪客 | 84 | 169.9 | 244.8 | 67.9% |
| 沉睡客 | 116 | 103.4 | 0 | 0% |
| 新手組合客 | 44 | 17.1 | 16.4 | 9.1% |
| 浴巾大量客 | 31 | 39.6 | 0 | 0% |
表中四種類型在答案表(reveal.sql 的輸出)裡的英文分別是 sock_regular、dormant、starter_new、bath_bulk,reveal.sql 第一段另外還會印出新客 1,003 人的平均預測。
高頻買襪客的平均預測最高,是其他三種顧客合計平均(73.2 元)的 2.3 倍,方向是對的,但實際值比預測還高。
浴巾大量客在驗證集裡 30 天內都沒有回購,他們回購的機率本來就低,就算回購也要隔 60 天左右,30 天的標籤看不到,這是 3.1 說的觀察期代價,和模型準不準是兩回事。
最大的問題在沉睡客,實際是 0,模型卻平均猜了 103.4 元,拆開首購商品來看就知道原因,下表包含驗證集與新客共 1,278 人:
| 首購商品 | 真實類型 | 人數 | 平均預測 |
|---|---|---|---|
| 日常中筒襪 | 高頻買襪客 | 282 | 178.5 |
| 日常中筒襪 | 沉睡客 | 131 | 173.6 |
| 厚底毛巾訓練襪 | 高頻買襪客 | 136 | 157.9 |
| 厚底毛巾訓練襪 | 沉睡客 | 114 | 149.6 |
| 純棉大浴巾 | 浴巾大量客 | 164 | 38.3 |
| 純棉大浴巾 | 沉睡客 | 49 | 33.3 |
| 日常入門組合 | 新手組合客 | 266 | 16.7 |
| 日常入門組合 | 沉睡客 | 66 | 18.9 |
| 純棉洗臉毛巾 | 沉睡客 | 70 | 4.3 |
同樣第一次買日常中筒襪,高頻買襪客和沉睡客拿到的預測只差 5 元,因為兩種人的第一筆訂單幾乎一樣,高頻買襪客只是平均多買一點件數,這個差別太小,模型只看得到第一筆訂單,幾乎只能給他們同一個平均值,這和 Day 11 分不出沉睡客是同一件事,差別只在後來有沒有回來,而首購當下還沒有後來。
所以這個預測值應該讀成「模型估計買中筒襪進來的新客,平均 30 天會再帶來約 177 元」,而不是「這位顧客會再花 177 元」,拿來比較一群人的價值沒問題,拿來判斷單一顧客就不準。
有了每位新客的預測,就可以回答哪種客人值得多投廣告,我取首購在 8/18–9/16 的新客,依首購訂單的通路與活動分組,預期 30 天價值是首單金額加上模型預測的回購營收,取得成本是同期這個通路這個活動的廣告花費除以新客數,兩個相除就是每花 1 元能在 30 天內換回多少營收:
| 通路 | 活動 | 新客數 | 取得成本 | 首單 | 預測回購 | 價值 ÷ 成本 |
|---|---|---|---|---|---|---|
| Google 搜尋 | 常態 | 141 | 354 元 | 817 元 | 88 元 | 2.56 |
| Google 搜尋 | 秋日棉織 | 71 | 348 元 | 797 元 | 57 元 | 2.45 |
| Google 搜尋 | 重訓襪 | 124 | 316 元 | 598 元 | 121 元 | 2.27 |
| LINE | 重訓襪 | 57 | 783 元 | 576 元 | 138 元 | 0.91 |
| Meta | 常態 | 59 | 951 元 | 775 元 | 82 元 | 0.90 |
| LINE | 秋日棉織 | 27 | 941 元 | 766 元 | 70 元 | 0.89 |
| LINE | 常態 | 45 | 1,038 元 | 681 元 | 89 元 | 0.74 |
| Meta | 秋日棉織 | 34 | 1,114 元 | 741 元 | 65 元 | 0.72 |
| Meta | 重訓襪 | 79 | 1,651 元 | 559 元 | 128 元 | 0.42 |
活動欄對應的 utm_campaign 是 evergreen、autumn-cotton、training-socks,重訓襪是「重訓襪專案」,秋日棉織是「秋日棉織專案」。
重訓襪專案帶進來的新客,在三個通路的預測回購都是最高的(121–138 元),模型沒有用活動當特徵,這個差距主要來自首購商品,重訓襪專案的新客有 73.8% 首購襪子,答案表裡重訓襪專案確實把高頻買襪客的權重調成兩倍,此外這個活動的商品只有兩款襪子,被活動帶進來的沉睡客首購的也都是襪子,模型一樣給他們高分。
重訓襪專案的新客首單最低、預測回購最高,兩者抵銷了一部分,但除了 LINE 以外,每花 1 元換回的營收仍然是同通路裡最低,LINE 裡則從只看首單時的第二名升到第一名,和秋日棉織專案只差 0.02,人數也少,還不足以下結論。
表上看起來最划算的是 Google 搜尋,每花 1 元換回 2 元以上,最不划算的是 Meta 的重訓襪專案,取得一位新客要 1,651 元,30 天只換回 687 元,但這張表有四個限制,讀的時候要一起考慮:
所以這張表比較適合回答「同一個通路裡,哪個活動帶來的客人比較好」,同一個通路內最後接觸和再行銷的影響差不多,但只算 30 天的偏差還在,Meta 重訓襪專案還要扣掉競價變貴的影響,回購間隔長的活動要打個折扣看,跨通路的比較則要搭配 Day 08 的歸因一起看。
ML.EVALUATE、ML.PREDICT 和一般查詢一樣含在每月 1 TiB(約 1 兆位元組)的免費額度內,CREATE MODEL 不在免費額度裡,線性迴歸和 K-means 一樣是 BigQuery 內建模型,每 TiB 312.5 美元3.2 已經看到線性迴歸只比依首購商品平均好一點點,換成更複雜的提升樹,也很難從「買什麼、從哪來」這幾個欄位擠出更多資訊,資訊不夠時換成要另付訓練費的模型通常只是多花錢,先用基準線確認還有沒有進步空間,再決定要不要升級模型。
martech_dw 裡有 fct_orders 和 fct_ad_daily
martech_gt:Day 11 載入過就可以直接用,沒有的話 Cloud Shell 裡要有 Day 05 合成器的答案檔 synthesizer/out/ground_truth/,run.sh 會自動載入gcloud auth list,帳號前面要有星號cd ~/ai-driven-martech-pipeline && git pull && bash ltv/run.sh
畫面先印出特徵摘要與預估費用,輸入 yes 後依序會看到模型評估、基準線比較、10 項檢查、通路與活動價值表和揭曉對照表,想重現第 6 章「沒看過的活動」加上 --unseen,會多建一個對照模型,約多花新台幣 0.1 元,最後多印一張兩個模型的對照表。
cd ~/ai-driven-martech-pipeline
git pull
bash scripts/load_ground_truth.sh
bq query --nouse_legacy_sql < ltv/features.sql
第三行是載入答案表,Day 11 做過的話可以略過,features.sql 最後會印出已滿 30 天 1,458 人、驗證集 275 人、待預測 1,003 人、訓練到 8/8、平均 30 天回購營收 92.8。
bq query --nouse_legacy_sql < ltv/model.sql
bq query --nouse_legacy_sql --format=pretty < ltv/evaluate.sql
bq query --nouse_legacy_sql --format=pretty < ltv/baseline.sql
建模型不到一分鐘,evaluate.sql 印出 MAE 110.9、R² 0.113,baseline.sql 印出 3.2 的三種方法比較。
bq query --nouse_legacy_sql < ltv/predict.sql
bq query --nouse_legacy_sql --format=pretty < ltv/value.sql
mart_customer_ltv 會有 1,278 列,其中驗證集 275 列、新客 1,003 列,預測是負數的會截成 0。
bq query --nouse_legacy_sql --format=pretty < ltv/reveal.sql
第一段是依真實類型的平均預測與實際,第二段是首購商品 × 真實類型。
ltv_features 2,461 列,已滿 30 天 1,458 人,驗證集 275 人,訓練集的首購日期都早於驗證集run.sh 的 10 項檢查全部通過,其中一項會確認特徵、模型、預測與價值表的 SQL 都沒有讀 martech_gt
bq rm -f -m martech_dw.ltv_linreg
bq rm -f -m martech_dw.ltv_linreg_campaign
第二行是用了 --unseen 才需要,模型和表都很小,在每月 10 GiB 的免費儲存額度內,mart_customer_ltv 明天的驗收會用到,建議保留,模型刪掉之後要重建才能替新顧客預測,重建一次約新台幣 0.1 元。
bash ltv/run.sh --unseen 可以重現這個對照今天只用第一筆訂單當下就知道的資訊,用 BigQuery ML 線性迴歸預測新客接下來 30 天的回購營收,模型比全體平均好很多,但幾乎只學到「買什麼」,和依首購商品平均差不多,揭曉之後高頻買襪客的平均預測最高,方向正確,但買襪子的沉睡客和高頻買襪客首購幾乎一樣,模型分不出來。
回到篇名,把預測值換算成每花 1 元換回多少營收之後,重訓襪專案的新客首單最低、預測回購卻最高,只看首單金額會低估他們,但這張表同時帶著最後接觸、粗算取得成本、只看 30 天三個偏差,也還沒扣毛利,最適合用在同一個通路內比較活動,跨通路的預算分配要搭配 Day 08 的歸因一起看。
明日預告:Day 13《先把答案藏好再來考 AI,看它能找回幾個》,Day 08 到今天,我們一路從合成資料裡找回 Day 05 藏進去的訊號,明天把這些訊號當成考卷,先把判準寫死再看成績,整理出一張成績單,再做一次不給任何提示的 AI 盲測,看它自己能找回幾個!