行銷報表最常見的切法是新客和舊客,但同樣是舊客,有人隔一陣子就回來、有人一次買很多就沒再出現、有人只試買過一次,這幾種人該收到的訊息、該花的廣告預算都不一樣,用新舊二分法全部混在一起看就看不出差別。
要把顧客分得更細,常見的做法是行銷人員自己訂規則,例如買兩次以上算忠誠客、客單價超過一千元算高價值客,規則好懂但門檻是人訂的,訂錯了也不會有人發現,今天換個方向,先把每位顧客的購買行為整理成一列數字,交給 BigQuery ML 的 K-means 讓資料自己分組,再回頭看每一組是什麼樣的人。
Day 05 的合成器在資料裡藏了四種顧客類型,今天分群時完全不看答案,等分完之後才揭曉,看 K-means 分出來的群和藏進去的類型對不對得上,今天的雲端費用都以 1 美元約 32 元換算成新台幣。
今日核心目標:
| 步驟 | 做什麼 | 產出 |
|---|---|---|
| 整理特徵 | 從 Day 07 的 fct_orders 把每位顧客的訂單彙總成一列 |
seg_features,2,461 人,其中觀察滿 30 天的 1,458 人 |
| 建模型 | 用觀察滿 30 天的人訓練 K-means,分成 4 群 | seg_kmeans_k4 |
| 分群 | 每位顧客依特徵分到最近的群 | mart_customer_segment,一位顧客一列 |
| 看輪廓 | 每一群的人數、訂單數、回購率、營收、品類占比 | 分群輪廓表 |
| 揭曉 | 分群結果和答案表對照 | 對照表 |
💡 核心工程理念:
martech_gt,分析與訓練只讀 martech_dw,只要看 SQL 裡有沒有出現 martech_gt 就知道有沒有偷看答案mart_customer_segment,之後的驗收和 AI 查資料庫都會用到K-means 只看數字,所以第一步是替每位顧客挑出能描述購買行為的欄位,機器學習裡稱為特徵,我挑了 10 個:
| 特徵 | 說明 |
|---|---|
| 訂單數、總件數、總營收 | 買了幾次、幾件、多少錢 |
| 首購件數 | 第一次下單買了幾件 |
| 襪子、浴巾、洗臉巾、組合四個品類的件數占比 | 買的都是什麼,四個加起來是 1 |
| 平均回購間隔 | 兩次下單之間平均隔幾天 |
| 距今天數 | 最後一次下單到資料最後一天隔了幾天 |
縣市和通路先不放,今天只看買了什麼、怎麼買,特徵的 SQL 在 segmentation/features.sql。
整理時有兩個細節會直接影響分群結果,第一個是只買過一次的人沒有回購間隔,這個欄位如果留空,BigQuery ML 會用全體平均值補上,沒回購的人就會看起來像平均隔一段時間就回來一次,所以我改填「首購到現在已經等了幾天」,意思是至少隔這麼久還沒回來。
第二個是各特徵的單位差很多,總營收從幾百到上千元、訂單數多半是 1 或 2、品類占比介於 0 到 1,K-means 用距離判斷誰跟誰比較像,不先處理的話營收差一百元的影響會遠大於訂單數差一次,BigQuery ML 預設會把每個特徵標準化,也就是換算成「高於或低於平均幾個標準差」,讓每個特徵放在同一個尺度上比較。
訓練只用首購後觀察滿 30 天的人,資料最後一天是 9/16,也就是首購在 8/17(含)以前的 1,458 人,這些人已經有足夠時間顯現會不會回購,群的樣子由他們決定,最近才第一次下單的人一樣會分到最近的群,但另外標記,3.4 會說明原因。
K-means 的做法是先放下幾個群中心,把每位顧客分給最近的中心,再把中心移到這群人的平均位置,一直重複到中心幾乎不再移動為止,BigQuery ML 裡只要一段 CREATE MODEL:
CREATE OR REPLACE MODEL martech_dw.seg_kmeans_k4
OPTIONS(
model_type = 'KMEANS',
num_clusters = 4,
kmeans_init_method = 'KMEANS++',
standardize_features = TRUE
) AS
SELECT order_count, total_qty, total_revenue, first_qty,
share_sock, share_bath, share_face, share_set,
avg_gap_days, recency_days
FROM martech_dw.seg_features
WHERE observed_30d;
kmeans_init_method 預設是隨機放中心,每次建模型結果可能差很多,改成 KMEANS++ 會讓一開始的中心彼此拉開,結果比較穩定但仍有隨機成分,這個模型重複了 3 輪中心就幾乎不動了。
分幾群要自己決定,我先定好分群的用途是「依顧客常買的品類推送不同商品訊息」,再建了 3、4、5 群三個模型,用 ML.EVALUATE 看 Davies-Bouldin 指標,這個指標衡量群內夠不夠緊、群和群之間夠不夠開,越小越好:
| 分幾群 | Davies-Bouldin |
|---|---|
| 3 群 | 1.480 |
| 4 群 | 1.058 |
| 5 群 | 1.365 |
4 群的指標最好,所以正式結果用 4 群,接著用 ML.PREDICT 把每位顧客分到最近的群,再算出每一群的輪廓,這張表還沒有看答案,是行銷人員替每一群取名字的依據,回購率是下單兩次以上的人占比,主要品類是群內平均的件數占比:
| 群 | 人數 | 平均訂單數 | 回購率 | 首購件數 | 平均營收 | 主要品類(件數占比) |
|---|---|---|---|---|---|---|
| 1 | 286 | 1.07 | 6.6% | 1.08 | 976 元 | 組合 97% |
| 2 | 100 | 1.12 | 11.0% | 1.30 | 408 元 | 洗臉巾 96% |
| 3 | 262 | 1.12 | 10.7% | 2.44 | 1,741 元 | 浴巾 97% |
| 4 | 810 | 1.90 | 54.9% | 1.49 | 675 元 | 襪子 96% |
光看這張表就能替四群取名字:組合客、洗臉巾客、浴巾客、襪子客,其中襪子客人數最多、回購率超過五成,浴巾客首購平均買 2.44 件、營收最高但很少回來,K-means 分出來的四群是依「買什麼」分的,10 個特徵裡有 4 個是品類占比,而且大多數人幾乎只買一種品類,品類就成了最明顯的分界,群的編號本身沒有意義,重建模型時可能對調,要看輪廓來認群。
發文前我照第 5 章的步驟重跑一次,同一份特徵重建 4 群模型,分出來的卻是另一種樣子,浴巾客一樣自成一群,但襪子客改依有沒有回購拆成兩群、組合客和洗臉巾客併成一群,Davies-Bouldin 是 1.235,比上面那次差,KMEANS++ 只是讓起點比較分散,仍然帶有隨機成分,而這份資料不只一種說得通的分法,每次重建都可能落在其中一種,所以本文以揭曉前決定的那一次為正式結果,讀者自己跑出來的群和文章不同是正常的,要看輪廓判斷每一群是什麼人。
合成器藏進去的四種顧客是高頻買襪客、浴巾大量客、新手組合客和沉睡客,沉睡客買完第一次就不會再回來,買什麼則是跟著當時的活動隨機決定,分群流程裡只有揭曉用的兩份 SQL 會讀答案表,下表只統計觀察滿 30 天的 1,458 人:
| 群 | 高頻買襪客 | 浴巾大量客 | 新手組合客 | 沉睡客 |
|---|---|---|---|---|
| 1 組合客 | 0 | 0 | 200 | 86 |
| 2 洗臉巾客 | 0 | 0 | 11 | 89 |
| 3 浴巾客 | 0 | 191 | 0 | 71 |
| 4 襪子客 | 474 | 0 | 64 | 272 |
reveal.sql 印出的欄位是英文,依序是 sock_regular、bath_bulk、starter_new、dormant。
浴巾大量客 191 人全部在群 3,高頻買襪客 474 人全部在群 4,這兩種人各自集中在一群,但那兩群都混進了沉睡客,群 3 有 71 人、群 4 有 272 人,群 4 的 810 人裡真正的高頻買襪客只占 58.5%,新手組合客有 200 人在群 1,另外 64 人在群 4、11 人在群 2,對得上的比例約七成三。
分不出來的是沉睡客,518 人散在四個群裡,而且每一群都有,沉睡客第一次買什麼是隨機的,買襪子的就和高頻買襪客一起進了群 4,買浴巾的就進了群 3,差別只在後來有沒有回來,但 4 群的分法是依品類分的,沉睡客就被併進各自買過的品類。
換成 5 群就不一樣了,同樣的特徵分 5 群時,有一群 436 人平均只下單 1 次、主要買襪子和洗臉巾,518 位沉睡客有 359 人在這一群,另外還有一群 210 人平均下單 3.24 次,其中 206 人是高頻買襪客,5 群比較接近藏進去的類型,但它的 Davies-Bouldin 是 1.365,比 4 群差。
也就是說指標最好的分法,不一定最接近真實的顧客類型,但我在揭曉前就決定用 4 群,正式結果不因為看到答案而改成 5 群,看完答案才換分法等於事後調整判準,分數當然會變好看,實際工作沒有答案表可以對,這種做法就沒有意義了,Day 13 會用同樣的原則替所有找回的訊號打分數。
資料最後 30 天才第一次下單的有 1,003 人,這些人很容易被分錯,今天試了兩種訓練方式結果都一樣,只用觀察滿 30 天的人訓練的 5 群模型,把其中 594 人分進平均下單 1.92 次、以襪子為主的那一群,裡面有 248 人是沉睡客,我另外用全部 2,461 人訓練了一個 4 群的模型當對照,也把其中 513 人分進「回購襪子」群,裡面 334 人確實是高頻買襪客,但有 176 人是沉睡客。
從特徵來看,這些人剛下單不久,距今天數很小,回購間隔補的是「已經等了幾天」,數字也很小,兩個數字加起來就像常常回來的人,其實只是還沒到看得出會不會回來的時間點。
換了訓練資料也沒有改善,所以最實際的做法是新顧客照樣分到最近的群,但另外標記「觀察未滿 30 天」,mart_customer_segment 保留了 observed_30d 欄位,看報表或下廣告名單時先把這些人排除,等觀察滿 30 天後重跑 features.sql 更新特徵,再用同一個模型跑 predict.sql 重新分群就好,不需要重新訓練。
ML.PREDICT、ML.EVALUATE、ML.CENTROIDS 和一般查詢一樣含在每月 1 TiB(約 1 兆位元組)的免費額度內,但 CREATE MODEL 不在免費額度裡,K-means 建模型每 TiB 312.5 美元,是一般查詢的 50 倍INFORMATION_SCHEMA.JOBS,確認每個模型只收一次 10 MB,今天建了 4 個模型共約新台幣 0.4 元特徵表實際只處理 0.2 MB,就算顧客多到 50 倍,建一個模型也還在最低 10 MB 的計費範圍內,先彙總成一人一列再訓練,是 BigQuery ML 最直接的省錢方法。
martech_dw 裡有 fct_orders
synthesizer/out/ground_truth/ 裡有答案檔,沒有的話用固定種子重跑一次會得到同一份gcloud auth list,帳號前面要有星號cd ~/ai-driven-martech-pipeline && git pull && bash segmentation/run.sh
畫面印出預估費用後輸入 yes,看到檢查項目全部通過、分群輪廓表與揭曉對照表就完成了,run.sh 只建正式的 4 群模型,想重現 3 群、5 群與全部顧客的對照,加上 --compare 會多建 3 個模型,約多花新台幣 0.3 元。
cd ~/ai-driven-martech-pipeline
git pull
bash scripts/load_ground_truth.sh
會建立資料集 martech_gt,載入顧客類型 2,461 列與訊號說明 7 列,載入不收費。
bq query --nouse_legacy_sql < segmentation/features.sql
最後會印出顧客數 2,461、觀察滿 30 天 1,458、空值 0。
bq query --nouse_legacy_sql < segmentation/model.sql
bq query --nouse_legacy_sql < segmentation/predict.sql
建模型要一到幾分鐘,大部分時間在排隊,完成後 mart_customer_segment 會有 2,461 列。
bq query --nouse_legacy_sql --format=pretty < segmentation/profile.sql
bq query --nouse_legacy_sql --format=pretty < segmentation/reveal.sql
先看輪廓替每一群取名字,再看揭曉表,群的編號甚至分法都可能和文章不同,對照輪廓就認得出來,揭曉表會分成觀察滿 30 天與未滿 30 天兩段,文章的對照表是前一段,3.2 的 Davies-Bouldin 比較要用 --compare 建出另外三個模型後,再執行 segmentation/evaluate.sql,5 群的揭曉表則用 segmentation/reveal_models.sql。
seg_features 2,461 列,觀察滿 30 天 1,458 人run.sh 的 8 項檢查全部通過,其中一項會確認特徵、模型與分群的 SQL 都沒有讀 martech_gt
bq rm -f -m martech_dw.seg_kmeans_k4
bq rm -f -m martech_dw.seg_kmeans_k3
bq rm -f -m martech_dw.seg_kmeans_k5
bq rm -f -m martech_dw.seg_kmeans_k4_all
後三行是用了 --compare 才需要,模型和表都很小,在每月 10 GiB 的免費儲存額度內,mart_customer_segment 之後的驗收會用到,建議保留,模型刪掉之後要重建才能替新顧客分群,重建一次約新台幣 0.1 元。
martech_dw,把答案當特徵分群,分數再高也沒有意義今天把 1,458 位觀察滿 30 天的顧客整理成 10 個購買行為特徵,用 BigQuery ML 的 K-means 分成 4 群,分出組合客、洗臉巾客、浴巾客和襪子客,揭曉之後浴巾大量客和高頻買襪客各自集中在一群,新手組合客約七成三對得上,但沉睡客散在四群裡,因為他們的第一筆訂單和同品類的顧客幾乎一樣。
回到篇名,資料自己分組確實能看出顧客只有幾種人,但 4 群分出來的是「買什麼」的差別,「會不會回來」要等觀察夠久才看得出來,這也是為什麼觀察不夠久的新顧客要另外標記,不能直接放進名單。
明日預告:Day 12《哪種客人值得多投廣告?讓機器學習預測顧客的長期價值》,今天的分群要等顧客回購才分得準,明天換個角度,只用第一筆訂單就知道的資訊,讓 BigQuery ML 預測一位新顧客接下來 30 天還會帶來多少營收,再算出哪個通路帶來的客人最值得多投廣告!