3億會員、4億商品,深度學習在大型電商商品推薦的應用實踐!
電商行業(yè)中,對于用戶的商品推薦一直是一個非常熱門而且重要的話題,有很多比較成熟的方法,但是也各有利弊。
常見算法套路
電商品推薦中的常見算法大致如下:
基于商品相似度
比如食物 A 和食物 B,根據(jù)它們的價格、味道、保質(zhì)期、品牌等維度,可以計算它們的相似程度,可以想象,我買了包子,很有可能順路帶一盒水餃回家。
優(yōu)點:冷啟動,只要你有商品的數(shù)據(jù),在業(yè)務(wù)初期用戶數(shù)據(jù)不多的情況下,也可以做推薦。
缺點:預處理復雜,任何一件商品,維度可以說至少可以上百,如何選取合適的維度進行計算,涉及到工程經(jīng)驗,這些也是花錢買不到的。
典型:亞馬遜早期的推薦系統(tǒng)。
基于關(guān)聯(lián)規(guī)則
最常見的就是通過用戶購買的習慣,經(jīng)典的就是“啤酒尿布”的案例,但是實際運營中這種方法運用的是最少的。
首先要做關(guān)聯(lián)規(guī)則,數(shù)據(jù)量一定要充足,否則置信度太低,當數(shù)據(jù)量上升了,我們有更多優(yōu)秀的方法,可以說沒有什么亮點,業(yè)內(nèi)的算法有 apriori、ftgrow 之類的。
優(yōu)點:簡單易操作,上手速度快,部署起來也非常方便。
缺點:需要有較多的數(shù)據(jù),精度效果一般。
典型:早期運營商的套餐推薦。
基于物品的協(xié)同推薦
假設(shè)物品 A 被小張、小明、小董買過;物品 B 被小紅、小麗、小晨買過;物品 C 被小張、小明、小李買過。
直觀的來看,物品 A 和物品 C 的購買人群相似度更高(相對于物品 B),現(xiàn)在我們可以對小董推薦物品 C,小李推薦物品 A,這個推薦算法比較成熟,運用的公司也比較多。
優(yōu)點:相對精準,結(jié)果可解釋性強,副產(chǎn)物可以得出商品熱門排序。
缺點:計算復雜,數(shù)據(jù)存儲瓶頸,冷門物品推薦效果差。
典型:早期一號店商品推薦。
基于用戶的協(xié)同推薦
假設(shè)用戶 A 買過可樂、雪碧、火鍋底料;用戶 B 買過衛(wèi)生紙、衣服、鞋;用戶 C 買過火鍋、果汁、七喜。
直觀上來看,用戶 A 和用戶 C 相似度更高(相對于用戶 B),現(xiàn)在我們可以對用戶 A 推薦用戶 C 買過的其他東西,對用戶 C 推薦用戶 A 買過的其他東西,優(yōu)缺點與基于物品的協(xié)同推薦類似,不重復了。
基于模型的推薦
svd+、特征值分解等等,將用戶的購買行為的矩陣拆分成兩組權(quán)重矩陣的乘積,一組矩陣代表用戶的行為特征,一組矩陣代表商品的重要性,在用戶推薦過程中,計算該用戶在歷史訓練矩陣下的各商品的可能性進行推薦。
優(yōu)點:精準,對于冷門的商品也有很不錯的推薦效果。
缺點:計算量非常大,矩陣拆分的效能及能力瓶頸一直是受約束的。
典型:惠普的電腦推薦。
基于時序的推薦
這個比較特別,在電商運用的少,在 Twitter,F(xiàn)acebook,豆瓣運用的比較多,就是在只有贊同和反對的情況下,怎么進行評論排序。
基于深度學習的推薦
現(xiàn)在比較火的 CNN(卷積神經(jīng)網(wǎng)絡(luò))、RNN(循環(huán)神經(jīng)網(wǎng)絡(luò))、DNN(深度神經(jīng)網(wǎng)絡(luò))都有運用在上面推薦的例子,但是都還處于試驗階段。有個基于 word2vec 的方法已經(jīng)相對比較成熟,也是我們今天介紹的重點。
優(yōu)點:推薦效果非常精準,所需要的基礎(chǔ)存儲資源較少。
缺點:工程運用不成熟,模型訓練調(diào)參技巧難。
典型:蘇寧易購的會員商品推薦。
item2vec 工程引入
現(xiàn)在蘇寧的商品有約 4 億個,商品的類目有 10000 多組,大的品類也有近 40 個,如果通過傳統(tǒng)的協(xié)同推薦,實時計算的話,服務(wù)器成本,計算能力都是非常大的局限。
會員研發(fā)部門因為不是主要推薦的應用部門,所以在選擇上,我們期望的是更加高效高速且相對準確的簡約版模型方式,所以我們這邊基于 word2vec 的原始算法,仿造了 itemNvec 的方式。
首先,讓我們對 itemNvec 進行理論拆分:
01.part one:n-gram
目標商品的前后商品對目標商品的影響程度
這是兩個用戶 userA,userB 在易購上面的消費 time line,灰色方框內(nèi)為我們觀察對象,試問一下,如果換一下灰色方框內(nèi)的 userA、userB 的購買物品,直觀的可能性有多大?
直觀的體驗告訴我們,這是不可能出現(xiàn),或者絕對不是常出現(xiàn)的。所以,我們就有一個初始的假設(shè),對于某些用戶在特定的類目下,用戶的消費行為是連續(xù)影響的。
換句話說,就是我買了什么東西是依賴我之前買過什么東西。如何通過算法語言解釋上面說的這件事呢?大家回想一下,naive bayes 做垃圾郵件分類的時候是怎么做的?
假設(shè)“我公司可以提供發(fā)票、軍火出售、航母維修”這句話是不是垃圾郵件?
- P1(“垃圾郵件”|“我公司可以提供發(fā)票、軍火出售、航母維修”)
- =p(“垃圾郵件”)p(“我公司可以提供發(fā)票、軍火出售、航母維修”/“垃圾郵件”)/p(“我公司可以提供發(fā)票、軍火出售、航母維修”)
- =p(“垃圾郵件”)p(“發(fā)票”,“軍火”,“航母”/“垃圾郵件”)/p(“發(fā)票”,“軍火”,“航母”)
同理
- P2(“正常郵件”|“我公司可以提供發(fā)票、軍火出售、航母維修”)
- =p(“正常郵件”)p(“發(fā)票”,“軍火”,“航母”/“正常郵件”)/p(“發(fā)票”,“軍火”,“航母”)
我們只需要比較 p1 和 p2 的大小即可,在條件獨立的情況下可以直接寫成:
- P1(“垃圾郵件”|“我公司可以提供發(fā)票、軍火出售、航母維修”)
- =p(“垃圾郵件”)p(“發(fā)票”/“垃圾郵件”)p(“軍火”/“垃圾郵件”)p(“航母”/“垃圾郵件”)
- P2(“正常郵件”|“我公司可以提供發(fā)票、軍火出售、航母維修”)
- =p(“正常郵件”)p(“發(fā)票”/“正常郵件”)p(“軍火”/“正常郵件”)p(“航母”/“正常郵件”)
但是,我們看到,無論“我公司可以提供發(fā)票、軍火出售、航母維修”詞語的順序怎么變化,不影響它最后的結(jié)果判定,但是我們這邊的需求里面前面買的東西對后項的影響會更大。
冰箱=>洗衣機=>衣柜=>電視=>汽水,這樣的下單流程合理。
冰箱=>洗衣機=>汽水=>電視=>衣柜,這樣的下單流程相對來講可能性會更低。
但是對于 naive bayes,它們是一致的。所以,我們這邊考慮順序,還是上面那個垃圾郵件的問題。
- P1(“垃圾郵件”|“我公司可以提供發(fā)票、軍火出售、航母維修”)
- =p(“垃圾郵件”)p(“發(fā)票”)p(“軍火”/“發(fā)票”)p(“軍火”/“航母”)
- P1(“正常郵件”|“我公司可以提供發(fā)票、軍火出售、航母維修”)
- =p(“正常郵件”)p(“發(fā)票”)p(“軍火”/“發(fā)票”)p(“軍火”/“航母”)
這邊我們每個詞只依賴前一個詞,理論上講依賴 1-3 個詞通常都是可接受的。
以上考慮順序的 bayes 就是基于著名的馬爾科夫假設(shè)(Markov Assumption):下一個詞的出現(xiàn)僅依賴于它前面的一個或幾個詞下的聯(lián)合概率問題,相關(guān)詳細的理論數(shù)學公式就不給出了,這里涉及一個思想。
02.part two:Huffman Coding
更大的數(shù)據(jù)存儲形式
我們常用的 user 到 item 的映射是通過 one hot encoding 的形式去實現(xiàn)的,這有一個非常大的弊端就是數(shù)據(jù)存儲系數(shù)且維度災難可能性極大。
回到最初的那組數(shù)據(jù):現(xiàn)在蘇寧的商品有約 4 億個,商品的類目有 10000 多組,大的品類也有近 40 個,同時現(xiàn)在會員數(shù)目達到 3 億。
要是需要建造一個用戶商品對應的購買關(guān)系矩陣做基于用戶的協(xié)同推薦的話,我們需要做一個 4 億 X 6 億的 1/0 矩陣,這個是幾乎不可能的,Huffman 采取了一個近似二叉樹的形式進行存儲。
我們以易購商品購買量為例,講解一下如何以二叉樹的形式替換 one hot encoding 存儲方式:
假設(shè)
818 蘇寧大促期間,經(jīng)過統(tǒng)計,有冰箱=>洗衣機=>烘干機=>電視=>衣柜=>鉆石的用戶下單鏈條(及購買物品順序如上),其中冰箱總售出 15 萬臺,洗衣機總售出 8 萬臺,烘干機總售出 6 萬臺,電視總售出 5 萬臺,衣柜總售出 3 萬臺,鉆石總售出 1 萬顆。
Huffman 樹構(gòu)造過程:
- 給定{15,8,6,5,3,1}為二叉樹的節(jié)點,每個樹僅有一個節(jié)點,那就存在 6 顆單獨的樹。
- 選擇節(jié)點權(quán)重值最小的兩顆樹進行合并也就是{3}、{1},合并后計算新權(quán)重3+1=4。
- 將{3},{1}樹從節(jié)點列表刪除,將 3+1=4 的新組合樹放回原節(jié)點列表。
- 重新進行 2-3,直到只剩一棵樹為止。
針對每層每次分支過程,我們可以將所有權(quán)重大的節(jié)點看做是 1,權(quán)重小的節(jié)點看做是 0,相反亦可。
現(xiàn)在,我們比如需要知道鉆石的 code,就是 1000,也就是灰色方框的位置,洗衣機的 code 就是 111。
這樣的存儲利用了 0/1 的存儲方式,也同時考慮了組合位置的排列長度,節(jié)省了數(shù)據(jù)的存儲空間。
03.part three:node probility
最大化當前數(shù)據(jù)出現(xiàn)可能的概率密度函數(shù)
對于鉆石的位置而言,它的 Huffman code 是 1000,那就意味著在每一次二叉選擇的時候,它需要一次被分到 1,三次被分到 0。
而且每次分的過程中,只有 1/0 可以選擇,這是不是和 logistic regression 里面的 0/1 分類相似,所以這邊我們也直接使用了 lr 里面的交叉熵來作為 loss function。
其實對于很多機器學習的算法而言,都是按照先假定一個模型,再構(gòu)造一個損失函數(shù),通過數(shù)據(jù)來訓練損失函數(shù)求 argmin(損失函數(shù))的參數(shù),放回到原模型。
讓我們詳細的看這個鉆石的例子:
第一步:p(1|No.1 層未知參數(shù))=sigmoid(No.1 層未知參數(shù))
第二步:p(0|No.2 層未知參數(shù))=sigmoid(No.2 層未知參數(shù))
同理,第三、第四層:
- p(0|No.3 層未知參數(shù))=sigmoid(No.3 層未知參數(shù))
- p(0|No.4 層未知參數(shù))=sigmoid(No.4 層未知參數(shù))
然后求p(1|No.1 層未知參數(shù)) x p(0|No.2 層未知參數(shù)) x p(0|No.3 層未知參數(shù))x p(0|No.4 層未知參數(shù))最大下對應的每層的未知參數(shù)即可。
求解方式與 logistic 求解方式近似,未知參數(shù)分布偏導,后續(xù)采用梯度下降的方式。(極大、批量、牛頓按需使用)
04.part four:approximate nerual network
商品的相似度
剛才在 part three 里面有個 p(1|No.1 層未知參數(shù))這個邏輯,這個 NO.1 層未知參數(shù)里面有一個就是商品向量。
舉個例子:
存在 1000 萬個用戶有過:“啤酒=>西瓜=>剃須刀=>百事可樂”的商品購買順序。
10 萬個用戶有過:“啤酒=>蘋果=>剃須刀=>百事可樂”的商品購買順序,如果按照傳統(tǒng)的概率模型比如 navie bayes 或者 n-gram 來看。
P(啤酒=>西瓜=>剃須刀=>百事可樂)>>p(啤酒=>蘋果=>剃須刀=>百事可樂),但是實際上這兩者的人群應該是同一波人,他們的屬性特征一定會是一樣的才對。
我們這邊通過隨機初始化每個商品的特征向量,然后通過 part three 的概率模型去訓練,最后確定了詞向量的大小。除此之外,還可以通過神經(jīng)網(wǎng)絡(luò)算法去做這樣的事情。
Bengio 等人在 2001 年發(fā)表在 NIPS 上的文章《A Neural Probabilistic Language Model》介紹了詳細的方法。
我們這邊需要知道的就是,對于最小維度商品,我們以商品向量(0.8213,0.8232,0.6613,0.1234,...)的形式替代了0-1點(0,0,0,0,0,1,0,0,0,0...),單個的商品向量無意義。
但是成對的商品向量,我們就可以比較他們間的余弦相似度,比較類目的相似度,甚至品類的相似度。
Python代碼實現(xiàn)
01.數(shù)據(jù)讀取
- # -*- coding:utf-8 -*-
- import pandas as pd
- import numpy as np
- import matplotlib as mt
- from gensim.models import word2vec
- from sklearn.model_selection import train_test_split
- order_data = pd.read_table('C:/Users/17031877/Desktop/SuNing/cross_sell_data_tmp1.txt')
- dealed_data = order_data.drop('member_id', axis=1)
- dealed_data = pd.DataFrame(dealed_data).fillna(value='')
02.簡單的數(shù)據(jù)合并整理
- # 數(shù)據(jù)合并
- dealed_data = dealed_data['top10'] + [" "] + dealed_data['top9'] + [" "] + dealed_data['top8'] + [" "] + \
- dealed_data['top7'] + [" "] + dealed_data['top6'] + [" "] + dealed_data['top5'] + [" "] + dealed_data[
- 'top4'] + [" "] + dealed_data['top3'] + [" "] + dealed_data['top2'] + [" "] + dealed_data['top1']
- # 數(shù)據(jù)分列
- dealed_data = [s.encode('utf-8').split() for s in dealed_data]
- # 數(shù)據(jù)拆分
- train_data, test_data = train_test_split(dealed_data, test_size=0.3, random_state=42)
03.模型訓練
- # 原始數(shù)據(jù)訓練
- # sg=1,skipgram;sg=0,SBOW
- # hs=1:hierarchical softmax,huffmantree
- # nagative = 0 非負采樣
- model = word2vec.Word2Vec(train_data, sg=1, min_count=10, window=2, hs=1, negative=0)
接下來就是用 model 來訓練得到我們的推薦商品,這邊有三個思路,可以根據(jù)具體的業(yè)務(wù)需求和實際數(shù)據(jù)量來選擇:
相似商品映射表
- # 最后一次瀏覽商品最相似的商品組top3
- x = 1000
- result = []
- result = pd.DataFrame(result)
- for i in range(x):
- test_data_split = [s.encode('utf-8').split() for s in test_data[i]]
- k = len(test_data_split)
- last_one = test_data_split[k - 1]
- last_one_recommended = model.most_similar(last_one, topn=3)
- tmp = last_one_recommended[0] + last_one_recommended[1] + last_one_recommended[2]
- last_one_recommended = pd.concat([pd.DataFrame(last_one), pd.DataFrame(np.array(tmp))], axis=0)
- last_one_recommended = last_one_recommended.T
- result = pd.concat([pd.DataFrame(last_one_recommended), result], axis=0)
考慮用戶最后一次操作的關(guān)注物品 x,干掉那些已經(jīng)被用戶購買的商品,剩下的商品表示用戶依舊有興趣但是因為沒找到合適的或者便宜的商品,通過商品向量之間的相似度,可以直接計算出,與其高度相似的商品推薦給用戶。
最大可能購買商品
根據(jù)歷史上用戶依舊購買的商品順序,判斷根據(jù)當前這個目標用戶近期買的商品,接下來他最有可能買什么?
比如歷史數(shù)據(jù)告訴我們,購買了手機+電腦的用戶,后一周內(nèi)最大可能會購買背包,那我們就針對那些近期購買了電腦+手機的用戶去推送電腦包的商品給他,刺激他的潛在購物需求。
- # 向量庫
- rbind_data = pd.concat(
- [order_data['top1'], order_data['top2'], order_data['top3'], order_data['top4'], order_data['top5'],
- order_data['top6'], order_data['top7'], order_data['top8'], order_data['top9'], order_data['top10']], axis=0)
- x = 50
- start = []
- output = []
- score_final = []
- for i in range(x):
- score = np.array(-100000000000000)
- name = np.array(-100000000000000)
- newscore = np.array(-100000000000000)
- tmp = test_data[i]
- k = len(tmp)
- last_one = tmp[k - 2]
- tmp = tmp[0:(k - 1)]
- for j in range(number):
- tmp1 = tmp[:]
- target = rbind_data_level[j]
- tmp1.append(target)
- test_data_split = [tmp1]
- newscore = model.score(test_data_split)
- if newscore > score:
- score = newscore
- name = tmp1[len(tmp1) - 1]
- else:
- pass
- start.append(last_one)
- output.append(name)
- score_final.append(score)
聯(lián)想記憶推薦
在最大可能購買商品中,我們根據(jù)這個用戶近期的購買行為,從歷史已購用戶的購買行為數(shù)據(jù)發(fā)現(xiàn)規(guī)律,提供推薦的商品。
還有一個近似的邏輯,就是通過目標用戶最近一次的購買商品進行推測,參考的是歷史用戶的單次購買附近的數(shù)據(jù),詳細如下:
這個實現(xiàn)也非常的簡單,這邊代碼我自己也沒有寫,就不貼了,采用的還是 word2vec 里面的predict_output_word(context_words_list, topn=10),Report the probability distribution of the center word given the context words as input to the trained model。
上述這些詳細做起來還是比較復雜的,我這邊也是簡單的貼了一些思路供大家參考和實踐。