Day 04 把 Live Demo 站接上 GA4 與綠界測試金流,軌道 A 的真實事件已經開始流進 BigQuery,但一個剛上線的小店一天只有零星幾筆點擊,撐不起多觸點歸因、顧客分群或素材成效分析,所以雙軌架構的另一條軌道 B 要用合成資料補上歷史規模。
很多教學做合成資料的方式是用亂數產生幾十萬筆點擊與訂單,這樣做有三個問題:
這三個問題的解法其實出自同一個核心概念:合成資料不該是隨機產生的亂數,而是一份預先寫好解答的考卷,只要先設定好要植入哪些商業訊號,後續的每一篇分析就能有標準答案可供對照,例如答案表記載 ROAS 異常從 8/12 開始,Day 09 就能驗證 Gemini 有沒有找出這一天,答案表記載有人物的圖片點擊率比較高,Day 17 就能驗證 AI 有沒有看出這個規律,AI 是否分析正確,翻開答案表便一目瞭然。
今日核心目標:
原本預計一天完成的合成器改拆為兩天處理:今天專注於設計好契約與訊號並執行小樣本測試,明天 Day 06 再量產 90 天的資料並匯入 BigQuery,這份契約一旦確立,後續十幾篇內容都會以此為基礎,因此經過思索後,決定必須多花一天時間先行奠定良好的基礎。
💡 核心工程理念:
products.json,事件欄位沿用 GA4 匯出到 BigQuery 的命名,兩條軌道合併後用 data_source 欄位區分ground_truth.json,合成器讀它來產生資料,分析篇讀它來對答案,兩邊永遠不會不一致三份設定檔各管一件事:
| 檔案 | 負責什麼 | 誰會用到 |
|---|---|---|
live-demo/products.json |
5 款商品的 ID、定價、尺寸與 2 檔活動 | Live Demo 與合成器共用 |
synthesizer/creatives.json |
30 則廣告素材的通路、活動、廣告群組與設計屬性 | 合成器、Day 16 視覺分析 |
synthesizer/ground_truth.json |
七個植入訊號的參數 | 合成器、各分析篇對答案 |
合成器讀進這三份設定之後產出五張原始表:
| 原始表 | 一列代表 | 90 天筆數 |
|---|---|---|
raw_creatives |
一則素材 | 30 |
raw_ad_daily |
一則素材的一天成效(曝光、點擊、花費) | 1,700 |
raw_events |
一個網站事件 | 約 52 萬 |
raw_orders |
一筆訂單 | 約 3,300 |
raw_customers |
一位顧客(含假個資) | 約 2,500 |
這五張表刻意維持「原始」的樣子,就像從廣告平台、GA4 與金流後台各自匯出來的資料,彼此之間要靠 creative_id、transaction_id、customer_id 串起來,Day 07 的星狀綱要要做的正是把這些散落的原始資料整理成事實表與維度表,完整欄位定義放在儲存庫的 synthesizer/README.md。
Day 04 的明日預告寫過合成資料要沿用同一組商品 ID 與事件欄位,具體來說對齊了四件事:
products.json,素材裡引用的商品只要不在清單上就直接丟出錯誤並停止,不會悄悄產生對不上的資料event_date、event_timestamp、user_pseudo_id 照 GA4 匯出表的命名與格式,event_date 用台北時間的日期,event_timestamp 用 UTC 微秒(organic)、(direct) 標記,訂單編號用 SY 開頭,和 Live Demo 的 DM 開頭分開有一點要事先說明,真實的 GA4 匯出表是巢狀結構,ga_session_id 放在 event_params 裡、商品資料放在 items 陣列裡,raw_events 則是把這些欄位攤平後的樣子,一列一個事件、一格一個值,Day 04 提過的 UNNEST 會在 Day 07 用到,把真實事件攤成同樣的形狀再和合成資料合併。
合成資料的期間設在 6/19 到 9/16 共 90 天,之後交給 Live Demo 的真實事件,在 BigQuery 裡把兩邊疊在一起看,就是一條從歷史延伸到今天的完整時間軸。
合成資料最常見的破綻是數字太整齊,這次的做法是只設定少數幾個基準值,對齊企劃設定的點擊率 1.5–3.5% 與轉換率 1.8–2.5%,其他數字讓它從模擬過程中自然長出來:
| 指標 | 設定方式 | 90 天結果 |
|---|---|---|
| 點擊率 | 各通路基準值再乘上素材屬性效果與每日波動 | google 搜尋 2.98%、meta 2.03%、line 2.10% |
| 轉換率 | 各通路基準值乘上依造訪次數遞增的倍數,第一次來的人很少買 | 以工作階段計 2.29% |
| 客單價 | 不另外抽分佈,由定價乘上數量自然形成 | 平均 622 元、中位數 360 元 |
客單價特別值得一提,常見做法是抽一個常態分佈當訂單金額,但這樣會出現 537.28 元這種不存在的價格甚至抽到負數,這裡的金額一律是「商品定價乘上數量」,所以每一筆都能在 Live Demo 上真的買到,分佈自然是右偏的:最常見的訂單是一雙 180 元的襪子,少數人一次買三、四條浴巾。
造訪行為也照真實網站的樣子設計:午休與晚上是造訪高峰,訪客來過之後十四天內都可能再回來,每個人的造訪一律依時間先後處理,其餘細節放在 README。
這是整個合成器的核心,每個訊號都對應一種行銷人實際會遇到的狀況,也對應後面某幾篇要驗證的分析方法:
| 代號 | 植入的訊號 | 對應的實務狀況 | 在哪幾天找回來 |
|---|---|---|---|
| S1 | meta 重訓襪專案開發新客廣告群組 8/12 起 CPC 翻倍、轉換不變 | 競價變貴但沒人發現,ROAS 理論上腰斬 | Day 09、24、28 |
| S2 | 8/27 整天沒有 purchase 事件,但訂單照常成立 | 網站改版弄壞追蹤碼 | Day 09、24 |
| S3 | 某一則 meta 素材點擊率每週衰退約 8% | 素材疲乏 | Day 09、17 |
| S4 | 圖片有人物 ×1.25、CTA 在右下 ×1.10、暖色系 ×1.10 | 素材設計影響點擊 | Day 17、18、20 |
| S5 | 四種顧客:高頻買襪、浴巾大量一次性、組合包新客、沉睡客 | 顧客價值差異 | Day 11、12 |
| S6 | meta 多出現在購買路徑開頭、google 搜尋多在最後一步 | 最後點擊歸因低估社群廣告 | Day 08 |
| S7 | 9/1 起秋日專案期間專案商品占比上升 | 檔期活動成效 | Day 08、09 |
S2 是 Day 04 前言提過的「事件根本沒送出去」,這個訊號特別放在 GA4 事件表和訂單表之間,只看事件的人會以為那天營收歸零,對照訂單表才會發現是追蹤出問題,這正是 Day 09 要讓 Gemini 學會分辨的狀況。
S6 是 Day 08 歸因篇要驗證的現象,社群廣告負責讓人第一次認識品牌,搜尋廣告負責促成最後一次成交,如果只看最後一次點擊,meta 的功勞幾乎會被抹掉,合成器用「各通路帶來新訪客的比例」與「造訪次數越多越可能成交」兩個設定讓這個現象自然出現,而不是直接指定每筆訂單的歸因。
訊號的強度也刻意調整過,強到用正確的方法一定找得到,但又不至於強到隨便看一張報表就一眼看穿,例如 S4 的人物效果只有 1.25 倍,混在每則素材本身的個體差異與每日波動裡,要把 24 則圖片素材放在一起比較才看得出來,Day 13 會用答案表逐一檢查七個訊號是不是都被前面幾篇找回來了。
S4 要成立有一個前提:每則圖片素材的設計屬性要先決定好,而且各屬性之間不能綁在一起,如果有人物的圖剛好都是暖色系,分析時就分不清點擊率高是因為人物還是因為顏色。
所以 creatives.json 裡的 24 則圖片素材,四個屬性(有無人物、CTA 位置、主色、文字密度)在開發新客與再行銷兩種受眾裡都各自平均分配,彼此之間也盡量不相關,因為再行銷的對象本來就來過網站,點擊率天生比較高,比較素材時要在同一種受眾裡比,每一則還附上生圖用的提示詞,實際的圖片會在 Day 15 之前依這份規格產生,這份規格表同時也是 Day 16 讓 Gemini 抽取視覺特徵時的標準答案,Gemini 說這張圖有人物、CTA 在右下,我們可以直接對照規格表算出它答對幾成,Day 20 的模型評測就會用到這個準確率。
一樣提供兩條路線,兩條路線的結果完全相同,可以依需求選擇:
cd ~/ai-driven-martech-pipeline && git pull && cd synthesizer && python3 synthetic_pipeline.py --days 7 --out ./sample
cd ~/ai-driven-martech-pipeline
git pull
cd synthesizer
ls
會看到 synthetic_pipeline.py、creatives.json、ground_truth.json、validate.py、README.md 與 tests/,合成器會從上一層的 live-demo/products.json 讀商品資料。
python3 synthetic_pipeline.py --days 7 --out ./sample
執行完會印出摘要,第一週只有常態廣告在跑,所以不會有 view_promotion 事件:
{
"period": "2026-06-19 ~ 2026-06-25",
"rows": {
"raw_ad_daily": 70,
"raw_events": 26458,
"raw_orders": 138,
"raw_customers": 138
},
"ctr_by_channel": {
"meta": 0.0217,
"line": 0.0207,
"google_cpc": 0.0311
},
"sessions": 7345,
"cvr_session": 0.0188,
"aov": 708.6,
"event_names": {
"first_visit": 3845,
"session_start": 7345,
"view_item_list": 7343,
"select_item": 3753,
"view_item": 3753,
"begin_checkout": 281,
"purchase": 138
}
}
head -3 sample/raw_ad_daily.csv
head -3 sample/raw_orders.csv
tail -n +2 sample/raw_events.csv | cut -d, -f3 | sort | uniq -c | sort -rn
最後一行會列出每種事件的筆數,除了代表新訪客的 first_visit 之外,可以看到從 session_start 到 purchase 大致遞減的轉換漏斗。
python3 synthetic_pipeline.py --days 7 --out ./sample2
md5sum sample/raw_events.csv sample2/raw_events.csv
兩個檔案的雜湊值會完全相同,換一個 --seed 則會得到另一份資料,但統計特性與植入的訊號都一樣。
python3 -m unittest discover -s tests -v
測試會用兩個種子各跑一次完整 90 天資料,檢查商品 ID、金額、每位訪客的第一個事件是否為 first_visit,以及七個植入的訊號是不是都找得到,約需 1 分鐘,過程中會印出幾段摘要與幾行 usage 錯誤訊息,那是測試刻意觸發的,最後看到 OK 就是全部通過。
raw_orders.csv 的 item_id 只會出現 Live Demo 上的 5 款商品raw_customers.csv 的 email 全部是 example.com 結尾rm -rf ~/ai-driven-martech-pipeline/synthesizer/sample ~/ai-driven-martech-pipeline/synthesizer/sample2
今天沒有建立任何雲端資源,刪掉樣本資料夾就清乾淨了。
event_date 用的是 GA4 資源的報表時區(台北),event_timestamp 則是 UTC 微秒,自己產生資料時兩個欄位要分開處理,不然跨午夜的事件會被算到錯的日期example.com 這個保留網域,確保就算有人拿去寄信也不會寄到真人信箱ground_truth.json 和 customer_segments.csv 只拿來對答案,分析篇的查詢與 prompt 都不能引用它們,不然就會變成球員兼裁判今天把合成器的資料契約定下來了:三份設定檔決定商品、素材與訊號,五張原始表模擬廣告平台、GA4 與金流後台各自的匯出資料,七個植入的訊號讓後面每一篇分析都有標準答案可以對,而且和 Day 04 的真實事件用的是同一套商品 ID 與欄位命名。
回頭看前言的三個問題,分析不出東西是因為資料裡沒有規律,所以我們主動植入規律,無法驗證是因為沒有答案,所以答案表和設定檔是同一份,對不上真實資料是因為兩邊各做各的,所以合成器直接讀 Live Demo 的商品檔。
明日預告:Day 06《合成器量產:90 天 50 萬筆灌入 BigQuery 與分佈驗證》,我們將把今天的合成器放大到完整 90 天,用批次載入工作灌進 BigQuery,並逐項驗證統計分佈與七個訊號是否都完整保留下來!