在產(chǎn)品呈現(xiàn)方面存在瑕疵 其中一個呈現(xiàn)興趣產(chǎn)品列表的常見方法就是簡單地隨機(jī)生成結(jié)果。但是隨機(jī)性存在兩個問題:首先,需要相當(dāng)大量的隨機(jī)性以挖掘出淹沒在目錄底端的產(chǎn)品。其次,它會破壞推薦列表的設(shè)計(jì),降低消費(fèi)者的信任度。 這里所說的“設(shè)計(jì)”是什么意思呢?讓我們來看看Yahoo上的一個著名案例。 布蘭尼·斯皮爾斯效應(yīng) 假設(shè)你正在瀏覽關(guān)于即將到來的NFL比賽的內(nèi)容。在文章下面會有一組由算法向你額外推薦的文章。不可否認(rèn)在21世紀(jì)初,幾乎所有人都會閱讀關(guān)于布蘭妮·斯皮爾斯的文章。 所以,在超級碗比賽前瞻底部會有一個“猜你喜歡”,這一欄中會顯示一篇關(guān)于布蘭妮和凱文·費(fèi)德林的文章。讀者會感覺自己被算法侮辱了,雅虎居然覺得我想要閱讀關(guān)于布蘭妮·斯皮爾斯的文章?? 其它讀過本篇的人也曾瀏覽… 但是取而代之,如果這里說的是“讀過本篇的人也曾閱讀”,現(xiàn)在……嗯,好吧,點(diǎn)開看看。這樣的設(shè)計(jì)能夠說服讀者去點(diǎn)擊。這些東西很重要! 就像一個優(yōu)秀的接球手能夠當(dāng)著裁判的面投出一個擦邊球一樣,在網(wǎng)頁中適當(dāng)?shù)靥峁┊a(chǎn)品推薦能夠讓客戶心安理得地購買或點(diǎn)擊。 “為你推薦”——呃,所以這個網(wǎng)站覺得它了解我,是嗎?如果這樣寫呢: “與您類似的家庭經(jīng)常購買” 現(xiàn)在,我知道了上下文,我知道這并不是零售商在推銷產(chǎn)品,而是像我一樣的其它顧客覺得有用的產(chǎn)品,且受到了廣泛的認(rèn)可。 尋找合理的偶然性 在與投資人之一,來自Bullpen Capital的保羅·馬蒂諾(Paul Martino)深入的討論后,我們提出了一個想法,叫做熱度產(chǎn)品算法。我們將記錄每天被加入購物車的內(nèi)容,尋找出熱度上升的產(chǎn)品。是的,有時候這只能反應(yīng)出銷售部門的活動(例如在電子郵件中推廣產(chǎn)品能夠使產(chǎn)品熱度上升),但是通過適當(dāng)?shù)臉?biāo)準(zhǔn)化,它應(yīng)當(dāng)也能夠篩選出最新熱點(diǎn)、熱門搜索和其它偶然導(dǎo)致產(chǎn)品流行的原因。這也更容易讓一些冷門產(chǎn)品瞬間打響知名度,使之從成堆的產(chǎn)品中脫穎而出。 編寫熱度產(chǎn)品引擎 首先需要從數(shù)據(jù)庫獲得加入購物車的數(shù)據(jù),這一步相對簡單。利用SQL追蹤提取每個項(xiàng)目加入購物車的時間。這里我們將提取最近20天的購物車數(shù)據(jù),以便觀察熱度數(shù)據(jù)(盡管實(shí)際只需要幾天的數(shù)據(jù)就可以得出熱度): SELECT v.product_id, -(CURRENT_DATE - si.created_at::date) 'age', COUNT(si.id) FROM product_variant v INNER JOIN schedule_shipmentitem si ON si.variant_id = v.id WHERE si.created_at >= (now() - INTERVAL '20 DAYS') AND si.created_at < CURRENT_DATE GROUP BY 1, 2 我們稍微簡化了上述代碼(在正式版中,還包括活躍產(chǎn)品、購買客戶、購物車添加詳情等細(xì)微差別),但是輸出結(jié)果卻十分簡單: id age count 14 -20 22 14 -19 158 14 -18 94 14 -17 52 14 -16 56 14 -15 56 14 -14 52 14 -13 100 14 -12 109 14 -11 151 14 -10 124 14 -9 123 14 -8 58 14 -7 64 14 -6 114 14 -5 93 14 -4 112 14 -3 87 14 -2 81 14 -1 19 15 -20 16 ... 15 -1 30 16 -20 403 ... 16 -1 842 每一行都代表了特定產(chǎn)品在過去20天中的某一天被加入購物車的次數(shù)。這里將‘a(chǎn)ge’設(shè)為-20(20天前)到-1(昨天),數(shù)據(jù)就會直觀地按照從左至右,從過去到現(xiàn)在排列。 這里有從數(shù)據(jù)庫中提取的100個樣本數(shù)據(jù)(http://blog./files/sample-cart-add-data.csv)。產(chǎn)品ID和購物車添加都是匿名的,這樣當(dāng)標(biāo)準(zhǔn)化時,數(shù)據(jù)結(jié)果完全真實(shí),但是單個數(shù)據(jù)點(diǎn)并不代表我們的實(shí)際業(yè)務(wù)狀況。 基本方法 在接觸代碼之前,我們先用數(shù)據(jù)可視化概述基本方法。所有中間步驟和可視化內(nèi)容都將在稍后闡釋。 這里展示的是樣本數(shù)據(jù)集中542號產(chǎn)品加入購物車的情況: 第一步是添加一個低通過濾(光滑函數(shù)smooth function)來減少日常波動。 隨后將Y軸標(biāo)準(zhǔn)化,這樣能夠展現(xiàn)出熱門產(chǎn)品與一般產(chǎn)品的差異。注意Y軸中的數(shù)值變化。 最后,計(jì)算平滑趨勢的每個線段的斜率。 算法會對每個產(chǎn)品執(zhí)行這些步驟(當(dāng)然是在內(nèi)存中而非可視化),然后返回前一天斜率最高的產(chǎn)品,例如紅線在t=-1時的最大值。 代碼 讓我們開始吧!本文中所有代碼都可以通過Python 2 Jupyter Notebook或者Yhat’s Python 集成開發(fā)環(huán)境Rodeo運(yùn)行。 生成第一張圖表(簡單熱度可視化)的代碼如下。與創(chuàng)建圖表一樣,我們將用這個代碼創(chuàng)建出最終算法。 import matplotlib.pyplot as plt import pandas as pd import numpy as np # Read the data into a Pandas dataframe df = pd.read_csv('sample-cart-add-data.csv') # Group by ID & Age cart_adds = pd.pivot_table(df, values='count', index=['id', 'age']) ID = 542 trend = np.array(cart_adds[ID]) x = np.arange(-len(trend),0) plt.plot(x, trend, label='Cart Adds') plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.) plt.title(str(ID)) plt.show() 沒有比這更簡單的了。我們使用pandas透視表(pivot_table)函數(shù)創(chuàng)建產(chǎn)品ID和‘a(chǎn)ge’的索引,方便稍后選取數(shù)據(jù)。 光滑化 現(xiàn)在編寫光滑函數(shù)(smooth function),并將其添加至圖表: def smooth(series, window_size, window): # Generate data points 'outside' of x on either side to ensure # the smoothing window can operate everywhere ext = np.r_[2 * series[0] - series[window_size-1::-1], series, 2 * series[-1] - series[-1:-window_size:-1]] weights = window(window_size) weights[0:window_size/2] = np.zeros(window_size/2) smoothed = np.convolve(weights / weights.sum(), ext, mode='same') return smoothed[window_size:-window_size+1] # trim away the excess data smoothed = smooth( trend, 7, np.hamming) plt.plot(x, smoothed, label='Smoothed') 該函數(shù)需要解釋一下:首先,它主要源自SciPy Cookbook,但是經(jīng)過修改后,變得不那么奇怪。 在這里,光滑函數(shù)使用了由Hamming窗口定義的權(quán)重“窗口”,并在原始數(shù)據(jù)上移動,根據(jù)窗口權(quán)重對相鄰數(shù)據(jù)點(diǎn)進(jìn)行加權(quán)。 Numpy提供了一系列窗口(Hamming、Hanning、Blackman等),你可以在命令行中找到他們: >>> print np.hamming(7) [ 0.08 0.31 0.77 1. 0.77 0.31 0.08] 這個窗口將在數(shù)據(jù)集(卷積)上移動,創(chuàng)建一個新的光滑的數(shù)據(jù)集。這就是一個非常簡單的低通過濾。 第5-7行將原始級數(shù)中的最前和最后幾個數(shù)據(jù)點(diǎn)進(jìn)行翻轉(zhuǎn)和鏡像,這樣甚至是在數(shù)據(jù)點(diǎn)的邊緣,窗口仍然‘適合’。這看起來可能有點(diǎn)奇怪,因?yàn)槊刻旖Y(jié)束時,我們只會關(guān)心決定熱門產(chǎn)品的最終數(shù)據(jù)點(diǎn)。你會覺得最好用一個只會處理歷史數(shù)據(jù)的光滑函數(shù),但是因?yàn)椴钪抵粫诟櫱岸藭r鏡像后面的數(shù)據(jù),對結(jié)果根本沒有任何影響。 標(biāo)準(zhǔn)化 我們需要將平均每天加入10次購物車的產(chǎn)品與每天被添加成百上千次的產(chǎn)品進(jìn)行對比。利用四分位差(IQR)對數(shù)據(jù)進(jìn)行標(biāo)準(zhǔn)化區(qū)分可以解決這個問題: def standardize(series): iqr = np.percentile(series, 75) - np.percentile(series, 25) return (series - np.median(series)) / iqr smoothed_std = standardize(smoothed) plt.plot(x, smoothed_std) 同時還在結(jié)果中減去了中位數(shù),這樣級數(shù)便會以0為中心,而不是1。注意這是標(biāo)準(zhǔn)化,不是歸一化。二者的區(qū)別在于歸一化會將級數(shù)的值嚴(yán)格限定在一個已知范圍內(nèi)(通常為0和1),而標(biāo)準(zhǔn)化只是將所有數(shù)據(jù)置入相同的尺度中。 標(biāo)準(zhǔn)化數(shù)據(jù)的方法有很多種,這里展示的是其中一個十分直接且容易執(zhí)行的方法。 斜率 真簡單!計(jì)算圓滑的標(biāo)準(zhǔn)化級數(shù)上每一點(diǎn)的斜率非常簡單,只需要將級數(shù)位移一個單位,再與原級數(shù)相減。一些樣本數(shù)據(jù)的圖像化表示如下: 代碼如下: slopes = smoothed_std[1:]-smoothed_std[:-1]) plt.plot(x, slopes) 叮!就是這么簡單! 整合所有內(nèi)容 現(xiàn)在,只需要為每一個產(chǎn)品重復(fù)以上步驟,尋找最近一段時間中斜率值最高的產(chǎn)品。 最終代碼如下: import pandas as pdimport numpy as npimport operator SMOOTHING_WINDOW_FUNCTION = np.hamming SMOOTHING_WINDOW_SIZE = 7 def train(): df = pd.read_csv('sample-cart-add-data.csv') df.sort_values(by=['id', 'age'], inplace=True) trends = pd.pivot_table(df, values='count', index=['id', 'age']) trend_snap = {} for i in np.unique(df['id']): trend = np.array(trends[i]) smoothed = smooth(trend, SMOOTHING_WINDOW_SIZE, SMOOTHING_WINDOW_FUNCTION) nsmoothed = standardize(smoothed) slopes = nsmoothed[1:] - nsmoothed[:-1] # I blend in the previous slope as well, to stabalize things a bit and # give a boost to things that have been trending for more than 1 day if len(slopes) > 1: trend_snap[i] = slopes[-1] + slopes[-2] * 0.5 return sorted(trend_snap.items(), key=operator.itemgetter(1), reverse=True) def smooth(series, window_size, window): ext = np.r_[2 * series[0] - series[window_size-1::-1], series, 2 * series[-1] - series[-1:-window_size:-1]] weights = window(window_size) smoothed = np.convolve(weights / weights.sum(), ext, mode='same') return smoothed[window_size:-window_size+1] def standardize(series): iqr = np.percentile(series, 75) - np.percentile(series, 25) return (series - np.median(series)) / iqr trending = train()print 'Top 5 trending products:'for i, s in trending[:5]: print 'Product %s (score: %2.2f)' % (i, s) 結(jié)果如下: Top 5 trending products:Product 103 (score: 1.31)Product 573 (score: 1.25)Product 442 (score: 1.01)Product 753 (score: 0.78)Product 738 (score: 0.66) 這是算法的核心。目前它正在編寫中,與目前的現(xiàn)行的算法相比它表現(xiàn)良好。 我們正在加入幾個額外的內(nèi)容來進(jìn)一步增強(qiáng)它的性能: 1.去除非常不受歡迎的產(chǎn)品結(jié)果。否則只需要加入購物車10次以上,那些原本只有1-5次加入購物車的產(chǎn)品就會出現(xiàn)在熱門產(chǎn)品結(jié)果中。 2.對產(chǎn)品進(jìn)行加權(quán),這樣從每天500次增加到每天600次的產(chǎn)品就可以與從20次上升到40次的產(chǎn)品一起顯示在熱門中。 關(guān)于熱門產(chǎn)品算法的資料少得出奇。然而很有可能其它人擁有更加精良的技術(shù),能夠獲得更好的結(jié)果。 但是對于Grove來說,這已經(jīng)能夠達(dá)成所有的目標(biāo):易說明、具有偶然性,并且相較于呈獻(xiàn)給消費(fèi)者的任何其它產(chǎn)品來說,它的點(diǎn)擊量更高。 |
|