如今,推薦引擎無處不在,人們希望數(shù)據(jù)科學(xué)家知道如何構(gòu)建一個推薦引擎
Word2vec是一個非常流行的詞嵌入,用于執(zhí)行各種NLP任務(wù)
我們將使用word2vec來構(gòu)建我們自己的推薦系統(tǒng)。就讓我們來看看NLP和推薦引擎是如何結(jié)合的吧!
完整的代碼可以從這里下載:
https://github.com/prateekjoshi565/recommendation_system/blob/master/recommender_2.ipynb
老實(shí)說,你在亞馬遜上有注意到網(wǎng)站為你推薦的內(nèi)容嗎(Recommended for you部分)? 自從幾年前我發(fā)現(xiàn)機(jī)器學(xué)習(xí)可以增強(qiáng)這部分內(nèi)容以來,我就迷上了它。每次登錄Amazon時,我都會密切關(guān)注該部分。
Netflix、谷歌、亞馬遜、Flipkart等公司花費(fèi)數(shù)百萬美元完善他們的推薦引擎是有原因的,因?yàn)檫@是一個強(qiáng)大的信息獲取渠道并且提高了消費(fèi)者的體驗(yàn)。
讓我用一個最近的例子來說明這種作用。我去了一個很受歡迎的網(wǎng)上市場購買一把躺椅,那里有各種各樣的躺椅,我喜歡其中的大多數(shù)并點(diǎn)擊了查看了一把人造革手動躺椅。
請注意頁面上顯示的不同類型的信息,圖片的左半部分包含了不同角度的商品圖片。右半部分包含有關(guān)商品的一些詳細(xì)信息和部分類似的商品。
而這是我最喜歡的部分,該網(wǎng)站正在向我推薦類似的商品,這為我節(jié)省了手動瀏覽類似躺椅的時間。
在本文中,我們將構(gòu)建自己的推薦系統(tǒng)。但是我們將從一個獨(dú)特的視角來處理這個問題。我們將使用一個NLP概念--Word2vec,向用戶推薦商品。如果你覺得這個教程讓你有點(diǎn)小期待,那就讓我們開始吧!
在文中,我會提及一些概念。我建議可以看一下以下這兩篇文章來快速復(fù)習(xí)一下
理解神經(jīng)網(wǎng)絡(luò): https://www.analyticsvidhya.com/blog/2017/05/neural-network-from-scratch-in-python-and-r/?utm_source=blog& utm_medium=how-to-build-recommendation-system-word2vec-python
構(gòu)建推薦引擎的綜合指南:
https://www.analyticsvidhya.com/blog/2018/06/comprehensive-guide-recommendation-engine-python/?utm_source=blog&utm_medium=how-to-build-recommendation-system-word2vec-python
我們知道機(jī)器很難處理原始文本數(shù)據(jù)。事實(shí)上,除了數(shù)值型數(shù)據(jù),機(jī)器幾乎不可能處理其他類型的數(shù)據(jù)。因此,以向量的形式表示文本幾乎一直是所有NLP任務(wù)中最重要的步驟。
在這個方向上,最重要的步驟之一就是使用 word2vec embeddings,它是在2013年引入NLP社區(qū)的并徹底改變了NLP的整個發(fā)展。
事實(shí)證明,這些 embeddings在單詞類比和單詞相似性等任務(wù)中是最先進(jìn)的。word2vec embeddings還能夠?qū)崿F(xiàn)像 King - man +woman ~= Queen
之類的任務(wù),這是一個非常神奇的結(jié)果。
有兩種?word2vec模型——Continuous Bag of Words模型和Skip-Gram模型。在本文中,我們將使用Skip-Gram模型。
首先讓我們了解word2vec向量或者說embeddings是怎么計(jì)算的。
word2vec模型是一個簡單的神經(jīng)網(wǎng)絡(luò)模型,其只有一個隱含層,該模型的任務(wù)是預(yù)測句子中每個詞的近義詞。然而,我們的目標(biāo)與這項(xiàng)任務(wù)無關(guān)。我們想要的是一旦模型被訓(xùn)練好,通過模型的隱含層學(xué)習(xí)到的權(quán)重。然后可以將這些權(quán)重用作單詞的embeddings。
讓我舉個例子來說明word2vec模型是如何工作的。請看下面這句話:
假設(shè)單詞“teleport”(用黃色高亮顯示)是我們的輸入單詞。它有一個大小為2的上下文窗口。這意味著我們只考慮輸入單詞兩邊相鄰的兩個單詞作為鄰近的單詞。
注意:上下文窗口的大小不是固定的,可以根據(jù)我們的需要進(jìn)行更改。
現(xiàn)在,任務(wù)是逐個選擇鄰近的單詞(上下文窗口中的單詞),并給出詞匯表中每個單詞成為選中的鄰近單詞的概率。這聽起來應(yīng)該挺直觀的吧?
讓我們再舉一個例子來詳細(xì)了解整個過程。
我們需要一個標(biāo)記數(shù)據(jù)集來訓(xùn)練神經(jīng)網(wǎng)絡(luò)模型。這意味著數(shù)據(jù)集應(yīng)該有一組輸入和對應(yīng)輸入的輸出。在這一點(diǎn)上,你可能有一些問題,像:
在哪里可以找到這樣的數(shù)據(jù)集?
這個數(shù)據(jù)集包含什么?
這個數(shù)據(jù)有多大?
等等。
然而我要告訴你的是:我們可以輕松地創(chuàng)建自己的標(biāo)記數(shù)據(jù)來訓(xùn)練word2vec模型。下面我將演示如何從任何文本生成此數(shù)據(jù)集。讓我們使用一個句子并從中創(chuàng)建訓(xùn)練數(shù)據(jù)。
第一步: 黃色高亮顯示的單詞將作為輸入,綠色高亮顯示的單詞將作為輸出單詞。我們將使用2個單詞的窗口大小。讓我們從第一個單詞作為輸入單詞開始。
所以,關(guān)于這個輸入詞的訓(xùn)練樣本如下:
第二步: 接下來,我們將第二個單詞作為輸入單詞。上下文窗口也會隨之移動?,F(xiàn)在,鄰近的單詞是“we”、“become”和“what”。
新的訓(xùn)練樣本將會被添加到之前的訓(xùn)練樣本中,如下所示:
我們將重復(fù)這些步驟,直到最后一個單詞。最后,這句話的完整訓(xùn)練數(shù)據(jù)如下:
我們從一個句子中抽取了27個訓(xùn)練樣本,這是我喜歡處理非結(jié)構(gòu)化數(shù)據(jù)的許多方面之一——憑空創(chuàng)建了一個標(biāo)記數(shù)據(jù)集。
現(xiàn)在,假設(shè)我們有一堆句子,我們用同樣的方法從這些句子中提取訓(xùn)練樣本。我們最終將獲得相當(dāng)大的訓(xùn)練數(shù)據(jù)。
假設(shè)這個數(shù)據(jù)集中有5000個惟一的單詞,我們希望為每個單詞創(chuàng)建大小為100維的向量。然后,對于下面給出的word2vec架構(gòu):
V = 5000(詞匯量)
N = 100(隱藏單元數(shù)量或單詞embeddings長度)
輸入將是一個熱編碼向量,而輸出層將給出詞匯表中每個單詞都在其附近的概率。
一旦對該模型進(jìn)行訓(xùn)練,我們就可以很容易地提取學(xué)習(xí)到的權(quán)值矩陣
正如你在上面看到的,權(quán)重矩陣的形狀為5000 x 100。這個矩陣的第一行對應(yīng)于詞匯表中的第一個單詞,第二個對應(yīng)于第二個單詞,以此類推。
這就是我們?nèi)绾瓮ㄟ^word2vec得到固定大小的詞向量或embeddings。這個數(shù)據(jù)集中相似的單詞會有相似的向量,即指向相同方向的向量。例如,單詞“car”和“jeep”有類似的向量:
這是對word2vec如何在NLP中使用的高級概述。
在我們開始構(gòu)建推薦系統(tǒng)之前,讓我問你一個問題。如何將word2vec用于非nlp任務(wù),如商品推薦?我相信自從你讀了這篇文章的標(biāo)題后,你就一直在想這個問題。讓我們一起解出這個謎題。
你能猜到word2vec用來創(chuàng)建文本向量表示的自然語言的基本特性嗎?
是文本的順序性。每個句子或短語都有一個單詞序列。如果沒有這個順序,我們將很難理解文本。試著解釋下面這句話:
“these most been languages deciphered written of have already”
這個句子沒有順序,我們很難理解它,這就是為什么在任何自然語言中,單詞的順序是如此重要。正是這個特性讓我想到了其他不像文本具有順序性質(zhì)的數(shù)據(jù)。
其中一類數(shù)據(jù)是消費(fèi)者在電子商務(wù)網(wǎng)站的購買行為。大多數(shù)時候,消費(fèi)者的購買行為都有一個模式,例如,一個從事體育相關(guān)活動的人可能有一個類似的在線購買模式:
如果我們可以用向量表示每一個商品,那么我們可以很容易地找到相似的商品。因此,如果用戶在網(wǎng)上查看一個商品,那么我們可以通過使用商品之間的向量相似性評分輕松地推薦類似商品。
但是我們?nèi)绾蔚玫竭@些商品的向量表示呢?我們可以用word2vec模型來得到這些向量嗎?
答案當(dāng)然是可以的! 把消費(fèi)者的購買歷史想象成一句話,而把商品想象成這句話的單詞:
更進(jìn)一步,讓我們研究在線零售數(shù)據(jù),并使用word2vec構(gòu)建一個推薦系統(tǒng)。
現(xiàn)在讓我們再一次確定我們的問題和需求:
我們被要求創(chuàng)建一個系統(tǒng),根據(jù)消費(fèi)者過去的購買行為,自動向電子商務(wù)網(wǎng)站的消費(fèi)者推薦一定數(shù)量的商品。
我們將使用一個在線零售數(shù)據(jù)集,你可以從這個鏈接下載:
https://archive.ics.uci.edu/ml/machine-learning-databases/00352/
讓我們啟動Jupyter Notebook,快速導(dǎo)入所需的庫并加載數(shù)據(jù)集。
import pandas as pd
import numpy as np
import random
from tqdm import tqdm
from gensim.models import Word2Vec
import matplotlib.pyplot as plt
%matplotlib inline
import warnings;
warnings.filterwarnings('ignore')
df = pd.read_excel('Online Retail.xlsx')
df.head()
以下是該數(shù)據(jù)集中字段的描述:
InvoiceNo: 賬單號,分配給每筆交易的唯一編號
StockCode: 商品的代碼。分配給每個不同商品的唯一編號
Description: 商品描述
Quantity: 每筆交易每種商品的數(shù)量
InvoiceDate: 每筆交易日期和時間
CustomerID: 消費(fèi)者編號,分配給每個消費(fèi)者的唯一編號
df.shape
Output: (541909, 8)
數(shù)據(jù)集包含541,909個記錄,這對于我們建立模型來說相當(dāng)不錯。
# 檢查缺失值數(shù)據(jù)
df.isnull().sum()
由于我們有足夠的數(shù)據(jù),我們將刪除所有缺少值的行。
# 刪除缺失值所在行
df.dropna(inplace=True)
讓我們將StockCode轉(zhuǎn)換為string數(shù)據(jù)類型:
df['StockCode']= df['StockCode'].astype(str)
讓我們來看看我們的數(shù)據(jù)集中消費(fèi)者的數(shù)量:
customers = df['CustomerID'].unique().tolist()
len(customers)
Output: 4372
在我們的數(shù)據(jù)集中有4,372個消費(fèi)者,對于這些消費(fèi)者,我們將提取他們的購買歷史。換句話說,我們可以有4372個購買序列。
留出數(shù)據(jù)集的一小部分用于驗(yàn)證是一個很好的方法。因此,我將使用90%消費(fèi)者的數(shù)據(jù)來創(chuàng)建word2vec embeddings。讓我們開始分割數(shù)據(jù)。
# 打亂消費(fèi)者id
random.shuffle(customers)
# 提取90%的消費(fèi)者
customers_train = [customers[i] for i in range(round(0.9*len(customers)))]
# 分為訓(xùn)練集和驗(yàn)證集
train_df = df[df['CustomerID'].isin(customers_train)]
validation_df = df[~df['CustomerID'].isin(customers_train)]
我們將在數(shù)據(jù)集中為訓(xùn)練集和驗(yàn)證集創(chuàng)建消費(fèi)者購買的序列。
# 存儲消費(fèi)者的購買歷史
purchases_train = []
# 用商品代碼填充列表
for i in tqdm(customers_train):
temp = train_df[train_df['CustomerID'] == i]['StockCode'].tolist()
purchases_train.append(temp)
# 存儲消費(fèi)者的購買歷史
purchases_val = []
# 用商品代碼填充列表
for i in tqdm(validation_df['CustomerID'].unique()):
temp = validation_df[validation_df['CustomerID'] == i]['StockCode'].tolist()
purchases_val.append(temp)
# 訓(xùn)練word2vec模型
model = Word2Vec(window = 10, sg = 1, hs = 0,
negative = 10, # for negative sampling
alpha=0.03, min_alpha=0.0007,
seed = 14)
model.build_vocab(purchases_train, progress_per=200)
model.train(purchases_train, total_examples = model.corpus_count,
epochs=10, report_delay=1)
因?yàn)槲覀儾淮蛩氵M(jìn)一步訓(xùn)練模型,所以我們在這里調(diào)用init_sims()
。這將使模型的內(nèi)存效率更高:
model.init_sims(replace=True)
讓我們來看看“model”的相關(guān)參數(shù):
print(model)
Output: Word2Vec(vocab=3151, size=100, alpha=0.03)
我們的模型有3151個唯一的單詞,每個單詞的向量大小為100維。接下來,我們將提取詞匯表中所有單詞的向量,并將其存儲在一個地方,以便于訪問。
# 提取向量
X = model[model.wv.vocab]
X.shape
Output: (3151, 100)
可視化你所創(chuàng)建的embeddings是很有幫助的。在這里,我們有100維的Embeddings。我們甚至無法可視化4維空間,更不用說100維了,那么我們怎么做呢?
我們將使用UMAP算法將商品Embeddings的維數(shù)從100降到2,UMAP算法通常用于降維。
import umap
cluster_embedding = umap.UMAP(n_neighbors=30, min_dist=0.0,
n_components=2, random_state=42).fit_transform(X)
plt.figure(figsize=(10,9))
plt.scatter(cluster_embedding[:, 0], cluster_embedding[:, 1], s=3, cmap='Spectral')
這個圖中的每個點(diǎn)都是一個商品。如你所見,這些數(shù)據(jù)點(diǎn)有幾個很小的集群。這些是相似商品的組。
恭喜你!我們終于準(zhǔn)備好我們的在線零售數(shù)據(jù)集中每個商品的word2vec embeddings ?,F(xiàn)在,我們的下一步是為某個商品或某個商品的向量推薦類似的商品。
讓我們首先創(chuàng)建一個商品id和商品描述的字典,以便輕松地將商品的描述映射到其id,反之亦然。
products = train_df[['StockCode', 'Description']]
# 去重
products.drop_duplicates(inplace=True, subset='StockCode', keep='last')
# 創(chuàng)建一個商品id和商品描述的字典
products_dict = products.groupby('StockCode')['Description'].apply(list).to_dict()
# 字典測試
products_dict['84029E']
Output: [‘RED WOOLLY HOTTIE WHITE HEART.’]
我定義了下面的函數(shù)。將一個商品的向量(n)作為輸入,返回前6個相似的商品:
def similar_products(v, n = 6):
# 為輸入向量提取最相似的商品
ms = model.similar_by_vector(v, topn= n+1)[1:]
# 提取相似產(chǎn)品的名稱和相似度評分
new_ms = []
for j in ms:
pair = (products_dict[j[0]][0], j[1])
new_ms.append(pair)
return new_ms
讓我們通過傳遞商品編號為'90019A' (‘SILVER M.O.P ORBIT BRACELET’)的商品:
similar_products(model['90019A'])
Output:
[(‘SILVER M.O.P ORBIT DROP EARRINGS’, 0.766798734664917),
(‘PINK HEART OF GLASS BRACELET’, 0.7607438564300537),
(‘AMBER DROP EARRINGS W LONG BEADS’, 0.7573930025100708),
(‘GOLD/M.O.P PENDANT ORBIT NECKLACE’, 0.7413625121116638),
(‘ANT COPPER RED BOUDICCA BRACELET’, 0.7289256453514099),
(‘WHITE VINT ART DECO CRYSTAL NECKLAC’, 0.7265784740447998)]
太酷了!結(jié)果還是非常相關(guān),并且與輸入商品匹配得很好。然而,這個輸出僅基于單個商品的向量。如果我們想根據(jù)他或她過去的多次購買來推薦商品呢?
一個簡單的解決方案是取用戶迄今為止購買的所有商品的向量的平均值,并使用這個結(jié)果向量找到類似的商品。我們將使用下面的函數(shù),它接收一個商品id列表,并返回一個100維的向量,它是輸入列表中商品的向量的平均值:
def aggregate_vectors(products):
product_vec = []
for i in products:
try:
product_vec.append(model[i])
except KeyError:
continue
return np.mean(product_vec, axis=0)
回想一下,為了驗(yàn)證目的,我們已經(jīng)創(chuàng)建了一個單獨(dú)的購買序列列表?,F(xiàn)在剛好可以利用它。
len(purchases_val[0])
Output: 314
用戶購買的第一個商品列表的長度為314。我們將把這個驗(yàn)證集的商品序列傳遞給aggregate_vectors函數(shù)。
aggregate_vectors(purchases_val[0]).shape
Output: (100, )
函數(shù)返回了一個100維的數(shù)組。這意味著函數(shù)運(yùn)行正常。現(xiàn)在我們可以用這個結(jié)果得到最相似的商品:
similar_products(aggregate_vectors(purchases_val[0]))
Output:
[(‘PARTY BUNTING’, 0.661663293838501),
(‘ALARM CLOCK BAKELIKE RED ‘, 0.640213131904602),
(‘ALARM CLOCK BAKELIKE IVORY’, 0.6287959814071655),
(‘ROSES REGENCY TEACUP AND SAUCER ‘, 0.6286610960960388),
(‘SPOTTY BUNTING’, 0.6270893216133118),
(‘GREEN REGENCY TEACUP AND SAUCER’, 0.6261675357818604)]
結(jié)果,我們的系統(tǒng)根據(jù)用戶的整個購買歷史推薦了6款商品。此外,你也可以根據(jù)最近幾次購買情況來進(jìn)行商品推薦。
下面我只提供了最近購買的10種商品作為輸入:
similar_products(aggregate_vectors(purchases_val[0][-10:]))
Output:
[(‘PARISIENNE KEY CABINET ‘, 0.6296610832214355),
(‘FRENCH ENAMEL CANDLEHOLDER’, 0.6204789876937866),
(‘VINTAGE ZINC WATERING CAN’, 0.5855435729026794),
(‘CREAM HANGING HEART T-LIGHT HOLDER’, 0.5839680433273315),
(‘ENAMEL FLOWER JUG CREAM’, 0.5806118845939636)]
你可以隨意修改這段代碼,并嘗試從驗(yàn)證集中的更多商品序列進(jìn)行商品推薦。也可以進(jìn)一步優(yōu)化這段代碼或使其更好。
最后,你可以嘗試在類似的非文本序列數(shù)據(jù)上實(shí)現(xiàn)此代碼。例如,音樂推薦就是一個很好的用例。