1. 程式人生 > >基於深度神經網路的embeddding來構建推薦系統

基於深度神經網路的embeddding來構建推薦系統

在之前的部落格中,我主要介紹了embedding用於處理類別特徵的應用,其實,在學術界和工業界上,embedding的應用還有很多,比如在推薦系統中的應用。本篇部落格就介紹瞭如何利用embedding來構建一個圖書的推薦系統。

本文主要譯自《Building a Recommendation System Using Neural Network Embeddings》,完整詳細的程式碼見官方GitHub

目錄

一、背景&資料集讀取

1.1 神經網路嵌入(Neural Network Embeddings)

1.2 資料集:來自維基百科

1.3 資料集清洗

二、監督學習

2.1 定義機器學習的任務

2.2 關於訓練集及測試集的劃分

2.3 嵌入模型

2.4 生成訓練示例

2.5 訓練模型

三、構建推薦系統

四、嵌入視覺化

五、總結

參考文獻


一、背景&資料集讀取

深度學習應用甚廣,在諸多方面的表現,如影象分割、時序預測和自然語言處理,都優於其他機器學習方法。以前,你只能在學術論文或者大型商業公司中看到它的身影,但如今,我們已能利用自己的電腦進行深度學習計算。本文將利用深度學習和維基百科構建圖書推薦系統。

該推薦系統基於假設:連結到相似的維基百科頁面的書籍彼此相似(注:必須要理解本文所用的資料集才能這句話的深層含義。稍後會講解資料集的含義)

1.1 神經網路嵌入(Neural Network Embeddings)

嵌入(embedding),即用連續向量表示離散變數的方法。與獨熱編碼不同的是,神經網路嵌入維度較低,並能令相似實體在嵌入空間中相鄰

神經網路嵌入的主要用途有三種:

  1. 在嵌入空間中找到最近鄰。
  2. 作為有監督的機器學習模型的輸入。
  3. 挖掘變數間的關係。

1.2 資料集:來自維基百科

與以往的資料科學專案一樣,我們需要從資料集入手。點選此處,檢視如何下載和處理維基百科上的每一篇文章,以及搜尋書籍頁面。在本文的資料集中,我們儲存了圖書標題、基本資訊、圖書頁面上指向其它維基百科頁面的連結(wikilinks)和外部網站的連結。為了建立一個推薦系統,我們僅僅使用圖書標題

wikilinks

(注:我們在這裡仔細解釋下wikilinks:所謂的wikilinks就是在圖書的維基百科頁面上的介紹該書的一些片語,比如說《戰爭與和平》,那麼它的wikilinks可能就是列夫托爾斯泰,俄國,俄語等。因為這些詞語在介紹《戰爭與和平》時肯定會出現,而有的讀者可能對這些詞語感興趣,因此,在《戰爭與和平》的頁面上會有跳往這些詞語的連結,這就是所謂的wikilinks。

因此,該推薦系統基於假設:連結到相似的維基百科頁面的書籍彼此相似。這句話的意義就很明顯了,如果2本書籍的wikilinks都指向了列夫托爾斯泰,俄國,俄語等,那麼這2本書可能就是很相似。比如說《戰爭與和平》《安娜·卡列尼娜》。)

這是一本書和它的wikilinks:

1.3 資料集清洗

資料下載完成後,我們需要對其進行探索和清洗,此時你可能會發現一些原始資料之間的關係。如下圖,展示了與維基百科圖書中的頁面關聯性最強的wikilinks:

從上圖可看出,排名前四的都是常用link,對構建推薦系統沒有任何幫助。就像書籍的裝訂版本,是平裝(paperback)還是精裝(hardcover)對我們瞭解圖書的內容沒有任何作用,並且神經網路無法根據這個特徵判別書籍是否相似。因此,可以選擇過濾掉這些無用的特徵。

(注:我們指的相似性是內容的相似性)

仔細思考哪些資料對構建推薦系統是有幫助的,哪些是無用的,有用的保留,無用的過濾,這樣的資料清洗工作才算到位。

完成資料清洗後,我們的資料集中剩餘41758條wikilinks以及37020本圖書。接下來,我們需要引入有監督的機器學習方法。

二、監督學習

2.1 定義機器學習的任務

監督學習就是最常見的分類問題,即通過已有的訓練樣本去訓練得到一個最優模型,再利用這個模型將所有的輸入對映為相應的輸出,對輸出進行簡單的判斷從而實現分類的目的。基於我們預先給定的假設:類似的書籍會連結到類似的維基百科頁面,我們可將監督學習的任務定義為:給定(book title,wikilink)對,確定wikilink是否出現在書籍的維基百科頁面中

我們將提供數十萬個由書籍名稱,wikilink以及標籤組成的訓練示例,同時給神經網路提供一些正確的訓練示例,即資料集中包含的,以及一些錯誤的示例,以促使神經網路學會區分wikilink是否出現在書籍的維基百科頁面中。

嵌入是為特定的任務而學習的,並且只與該問題有關。如果我們的任務是想要確定哪些書籍由Jane Austen撰寫,嵌入會根據該任務將Austen所寫的書對映在嵌入空間中更相鄰的地方。或者我們希望通過訓練來判斷書籍的頁面中是否有指定的wikilink頁面,此時神經網路會根據內容使相似書籍在嵌入空間中相鄰。

一旦我們定義了學習任務,接下來便可開始編寫程式碼進行實現。由於神經網路只能接受整數輸入,我們會將書籍分別對映為整數:

# Mapping of books to index and index to books
book_index = {book[0]: idx for idx, book in enumerate(books)}

book_index['Anna Karenina']
22494

注:本文的程式碼是Git中的部分程式碼,完整程式碼的獲取請參看GitHub)

對連結我們也進行同樣的對映,並建立一個訓練集。對所有書籍進行遍歷,並記錄頁面上記錄出現的wikilink,列出所有的(book,wikilink)對:

pairs = []

# Iterate through each book
for book in books:
    
    title = book[0]
    book_links = book[2]
    # Iterate through wikilinks in book article
    for link in book_links:
        # Add index of book and index of link to pairs
        pairs.extend((book_index[title],                
                      link_index[link]))

最終有772798個示例用於模型訓練。接下來,隨機選擇連結索引和book索引,如果它們不在(book,wikilink)對中,那麼它們就是能用於增強模型的學習能力false examples

2.2 關於訓練集及測試集的劃分

雖然在有監督的機器學習任務中需要劃分驗證集(validation set)以及測試集,但本文的目的不在於得到精確的模型,只是想訓練神經網路模型完成預測任務。訓練結束後,我們也不需要在新的資料集中測試我們的模型,所以並不需要評估模型的效能或者使用驗證集以防止過擬合。為了更好的學習嵌入,我們將所有的示例都用於訓練

2.3 嵌入模型

神經網路嵌入雖然聽上去十分複雜,但使用Keras深度學習框架實現它們卻相對容易。

嵌入模型分為5層:

  1. Input:並行輸入書籍和連結
  2. Embedding:設定代表book和link兩個類別的向量長度為50
  3. Dot:進行點積運算
  4. Reshape:把點積reshape成一個一維向量
  5. Dense:一個帶sigmod啟用函式的輸出神經元

嵌入神經網路中,能夠通過訓練權重最小化損失函式。神經網路將一本書和一個連結作為輸入,輸出一個0到1之間的預測值,並與真實值進行比較,模型採用Adam優化器

模型程式碼如下:

from keras.layers import Input, Embedding, Dot, Reshape, Dense
from keras.models import Model

def book_embedding_model(embedding_size = 50, classification = False):
    """Model to embed books and wikilinks using the Keras functional API.
       Trained to discern if a link is present in on a book's page"""
    
    # Both inputs are 1-dimensional
    book = Input(name = 'book', shape = [1])
    link = Input(name = 'link', shape = [1])
    
    # Embedding the book (shape will be (None, 1, 50))
    book_embedding = Embedding(name = 'book_embedding',
                               input_dim = len(book_index),
                               output_dim = embedding_size)(book)
    
    # Embedding the link (shape will be (None, 1, 50))
    link_embedding = Embedding(name = 'link_embedding',
                               input_dim = len(link_index),
                               output_dim = embedding_size)(link)
    
    # Merge the layers with a dot product along the second axis 
    # (shape will be (None, 1, 1))
    merged = Dot(name = 'dot_product', normalize = True, 
                 axes = 2)([book_embedding, link_embedding])
    
    # Reshape to be a single number (shape will be (None, 1))
    merged = Reshape(target_shape = [1])(merged)
    
    # Squash outputs for classification
    out = Dense(1, activation = 'sigmoid')(merged)
    model = Model(inputs = [book, link], outputs = out)
    
    # Compile using specified optimizer and loss 
    model.compile(optimizer = 'Adam', loss = 'binary_crossentropy', 
                  metrics = ['accuracy'])
    
    return model

這個框架可以擴充套件至各類嵌入模型。並且,我們並不關心模型是否精準,只想獲取嵌入。在嵌入模型中,權重才是目標,預測只是學習嵌入的手段。

(注:這句話不明白的話,可以參看我之前介紹embedding的部落格

本模型約含400萬個權重,如下所示:

__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
book (InputLayer)               (None, 1)            0                                            
__________________________________________________________________________________________________
link (InputLayer)               (None, 1)            0                                            
__________________________________________________________________________________________________
book_embedding (Embedding)      (None, 1, 50)        1851000     book[0][0]                       
__________________________________________________________________________________________________
link_embedding (Embedding)      (None, 1, 50)        2087900     link[0][0]                       
__________________________________________________________________________________________________
dot_product (Dot)               (None, 1, 1)         0           book_embedding[0][0]             
                                                                 link_embedding[0][0]             
__________________________________________________________________________________________________
reshape_1 (Reshape)             (None, 1)            0           dot_product[0][0]                
==================================================================================================
Total params: 3,938,900
Trainable params: 3,938,900
Non-trainable params: 0

利用上述方法,我們不僅可以得到書籍的嵌入,還可以得到連結的嵌入。

2.4 生成訓練示例

神經網路是batch learners,因為它們是基於一小批樣本進行訓練的,對所有的資料批次都進行了一次迭代稱為epochs。常用的神經網路訓練方法是使用生成器,它能產生批量樣本函式,優點是不需要將所有的訓練集都載入到記憶體中。

下面的程式碼完整地顯示了生成器:

import numpy as np
import random
random.seed(100)

def generate_batch(pairs, n_positive = 50, negative_ratio = 1.0):
    """Generate batches of samples for training. 
       Random select positive samples
       from pairs and randomly select negatives."""
    
    # Create empty array to hold batch
    batch_size = n_positive * (1 + negative_ratio)
    batch = np.zeros((batch_size, 3))
    
    # Continue to yield samples
    while True:
        # Randomly choose positive examples
        for idx, (book_id, link_id) in enumerate(random.sample(pairs, n_positive)):
            batch[idx, :] = (book_id, link_id, 1)
        idx += 1
        
        # Add negative examples until reach batch size
        while idx < batch_size:
            
            # Random selection
            random_book = random.randrange(len(books))
            random_link = random.randrange(len(links))
            
            # Check to make sure this is not a positive example
            if (random_book, random_link) not in pairs_set:
                
                # Add to batch and increment index
                batch[idx, :] = (random_book, random_link, neg_label)
                idx += 1
                
        # Make sure to shuffle order
        np.random.shuffle(batch)
        yield {'book': batch[:, 0], 'link': batch[:, 1]}, batch[:, 2]

其中n_positive表示每個batch中正例樣本的數量,negative_ration表示每個batch中負例樣本與正例樣本的比率。

在有監督的學習任務、生成器、嵌入模型都準備完畢的情況下,我們正式進入圖書推薦系統的構建。

2.5 訓練模型

有一些訓練引數是可以調節的,如每個批次中正例樣本的數量。通常,我會從一小批量開始嘗試,直到效能開始下降。同樣,我們需要通過嘗試調整負例樣本與正例樣本的比率。

n_positive = 1024

gen = generate_batch(pairs, n_positive, negative_ratio = 2)

# Train
h = model.fit_generator(gen, epochs = 15, 
                        steps_per_epoch = len(pairs) // n_positive)

一旦神經網路開始訓練,我們就能獲取權重:

# Extract embeddings
book_layer = model.get_layer('book_embedding')
book_weights = book_layer.get_weights()[0]

三、構建推薦系統

嵌入本身不那麼有趣,無非是50維向量。

 

然而我們可以利用這些向量做些有趣的事,例如構建圖書推薦系統。為了在嵌入空間中找到與所查詢書籍最接近的書,我們取那本書的向量,並計算它與所有其他書的向量的點積。如果我們的嵌入是標準化的,那麼向量之間的範圍會從-1,最不相似,到+1,最相似。

(注:在程式碼裡先進行了l2範數的標準化,因此,2個向量點積之後就是餘弦相似度,範圍從-1到1)

以查詢《戰爭與和平》為例,相似書籍如下:

除了對書籍進行嵌入,我們也對wikilink也做了嵌入,以此查詢與wikilink最為相似的連結:

再比如,目前,我正在閱讀Stephen Jay Gould的經典著作《Bully for Brontosaurus》,將其輸入構建的推薦系統便可以知道接下來應該讀什麼:

 

(注:通過上述的介紹,我們已經看到了如何利用神經網路的embeddding來構建推薦系統。本質上,這仍然是一個協調過濾的思想,即根據相似性來尋找某本書籍的最近鄰居,然後把最近鄰居推薦給喜歡某本書籍的人。但是,這裡與傳統的協調過濾方法明顯的不同的地方有:

①基於embedding的推薦方法並不要求書籍向量的”同一化“,即某本書不需要再像以前那樣,必須由長度完全相等的向量來表示,在現在世界中,構造這樣的資料集很困難,即便構造出來,資料也很稀疏。

②基於embedding的推薦方法可以通過”關係“來捕捉相似性,以此來保證embedding後的向量仍然可以保證這種相似性。我們在這裡理解維基百科的資料,可以認為書名到wikilinks存在著某種關係。那麼給我們的啟發是什麼呢?比如,我們想求得成人奶粉這個類目下所有sku的相似性,我們就可以類似文中構造訓練集的方法那樣來構造關於成人奶粉sku的資料集。)

四、嵌入視覺化

嵌入的優點是可以將所學到的嵌入進行視覺化處理,以顯示哪些類別是相似的。首先需要將這些權重的維度降低為2-D或3-D。然後,在散點圖上視覺化這些點,以檢視它們在空間中的分離情況。目前最流行的降維方法是——t-Distributed Stochastic Neighbor Embedding (TSNE)

我們將37000多維的圖書通過神經網路嵌入對映為50維,接著使用TSNE將維數將至為2。

似乎有一些明顯的團塊。然而,由於我們沒有以任何方式區分書籍,因此很難從上圖中得出有任何意義的東西。

我們需要其它的一些資訊讓我們看到我們對圖書做的embedding是有效的。

在資料集裡,有個欄位是圖書的類別genre。

讓我們用genre畫出embeddings,它包含在每本書的Infobox模板資料中。我們將其限制為10種最流行的genres。

我們可以看到,經過embedding後的圖書,原來類別很相似的,降維後仍然相似。

更多視覺化的探索可以參看原始碼。

五、總結

神經網路嵌入能夠將離散的資料表示為連續的低維向量,克服了傳統編碼方法的侷限性,能查詢最近鄰,作為另一個模型的輸入以及進行視覺化,是處理離散變數的有效工具,也是深度學習的有效應用。在本文中,我們基於連結到相似頁面間彼此相似的假設,利用神經網路嵌入構建了圖書推薦系統。

構建基於神經網路嵌入的推薦系統的步驟總結如下:

  1. 收集資料
  2. 制定一個有監督的學習任務
  3. 訓練嵌入神經網路模型
  4. 進行推薦實戰及視覺化

參考文獻

【1】基於神經網路嵌入的推薦系統:利用深度學習和維基百科構建圖書推薦系統

【2】Wikipedia Data Science: Working with the World’s Largest Encyclopedia

【3】 Neural Network Embeddings Explained

【4】How to Use t-SNE Effectively

【5】詮釋資料降維演算法:一文講盡t-分佈鄰域嵌入演算法(t-SNE)如何有效利用