「猜你喜歡」?摸清套路,本身也來 DIY 個推薦系統

專屬圖1.png

儘管還沒進入六月,可各大電商的暑期大促早已如火如荼地開始了。優惠、滿減、秒殺、返券……固然,在你成功清空購物車後,還少不了人民羣衆們喜聞樂見的「猜你喜歡」環節。常用就會發現,這類推薦內容一般都會很合你本身的口味,電商究竟是怎麼作到的?這就涉及到「推薦系統」這個概念了。html

商品推薦系統,究竟是怎樣的原理?

推薦系統,是利用電子商務網站向客戶提供商品信息和建議,幫助用戶決定應該購買什麼產品,並模擬銷售人員幫助客戶完成購買過程。個性化推薦則是根據用戶興趣特色和購買行爲,向用戶推薦用戶感興趣的信息和商品。
最先的推薦算法一般是用關聯規則創建的,如著名的啤酒尿不溼的故事,就是利用關聯規則推薦商品促進成交的經典案例。爲之而生的算法,如 Apriori算法就是亞馬遜所發明的。python

當咱們在 Amazon 上購買圖書時,會常常看到下面兩個提示:git

  1. 這些書會被消費者一塊兒購買,而且價格上有必定的折扣;
  2. 購買了這本書的人,也會購買其餘書。

Amazon 會對平臺中海量用戶記錄進行挖掘,發現這些規律,而後將這些規律應用於實際銷售工做當中。而結果也顯示,使用這種算法優化,對於當時亞馬遜的業績提高起到了很大做用。github

今天,隨着電子商務規模的不斷擴大,商品種類快速增加,顧客須要花費大量的時間才能找到本身想買的商品。這種瀏覽大量無關信息和產品的過程無疑會使淹沒在信息過載問題中的消費者不斷流失。爲解決這些問題,個性化推薦系統應運而生。算法

個性化推薦系統是創建在海量數據挖掘基礎上的一種高級商務智能平臺,可幫助電子商務網站爲其顧客購物提供徹底個性化的決策支持和信息服務。區別於傳統的規則推薦,個性化推薦算法一般使用機器學習甚至深度學習算法,對於用戶信息與其行爲信息充分挖掘,進而進行有效的推薦。
經常使用的推薦算法有不少,其中最爲經典的就是基於 Matrix Factorization(矩陣分解)的推薦。矩陣分解的思想簡單來講就是:每一個用戶和每一個物品都會有本身的一些特性,用矩陣分解的方法能夠從評分矩陣中分解出用戶—特性矩陣,以及特性—物品矩陣等,這樣作的好處是獲得了用戶的偏好和每件物品的特性。矩陣分解的思想也被擴展和泛化到深度學習及 Embedding 中,這樣構建模型可以加強模型的準確率及靈活易用性。apache

試試看,使用 Amazon SageMaker 構建基於 Gluon 的推薦系統

下文介紹的方法將會用到 Amazon SageMaker,它能夠幫助開發人員和數據科學家構建、訓練並部署ML模型。Amazon SageMaker 是一項徹底託管的服務,涵蓋了ML的整個工做流,能夠標記和準備數據、選擇算法、訓練模型、調整和優化模型以便部署、預測和執行操做。json

同時本方案基於 Gluon API,Gluon 是微軟聯合亞馬遜推出的一個開源深度學習庫,這是一個清晰、簡潔、簡單但功能強大的深度學習API,該規範能夠提高開發人員學習深度學習的速度,而無需關心所選擇的深度學習框架。Gluon API提供了靈活的接口來簡化深度學習原型設計、建立、訓練以及部署,並且不會犧牲數據訓練的速度。後端

下文將介紹如何使用 Amazon SageMaker 的自定義腳本(Bring Your Own Script,簡稱 BYOS)方式來運行 Gluon 程序(MXNet 後端)的訓練任務,而且進行部署調用。api

首先一塊兒看看如何在 Amazon SageMaker Notebook 上運行這個項目,在本地運行訓練任務,而後再進行部署,直接利用 Amazon SageMaker 的相關接口調用。網絡

解決方案概覽

在此示例中,咱們將使用 Amazon SageMaker 執行如下操做:

  1. 環境準備
  2. 使用 Jupyter Notebook 下載數據集並將其進行數據預處理
  3. 使用本地機器訓練
  4. 使用 Amazon SageMaker BYOS 進行模型訓練
  5. 託管部署及推理測試
1.環境準備

首先要建立一個 Amazon SageMaker Notebook,筆記本實例類型最好選擇 ml.p3.2xlarge,由於本例中用到了本地機器訓練的部分用來測試咱們的代碼,卷大小建議改爲10GB 或更大,由於運行該項目須要下載一些額外數據。

using-amazon-sagemaker-to-build-a-recommendation-system-based-on-gluon1.png

筆記本啓動後,打開頁面上的終端並執行下列命令下載代碼;或者也能夠經過 Sagemaker Examples 找到 Introduction to Applying Machine Learning/gluon_recommender_system.ipynb,點擊 Use 運行代碼。

cd ~/SageMaker
git clone https://github.com/awslabs/amazon-sagemaker-examples/tree/master/introduction_to_applying_machine_learning/gluon_recommender_system
2.使用 Jupyter Notebook 下載數據集並將其進行數據預處理

本文使用了亞馬遜官方開源數據集,其中包含2000位亞馬遜電商用戶對160k個視頻的評論打分,打分分數爲1-5,您能夠訪問該數據集主頁查看完整的數聽說明並下載。因爲本數據集很大,咱們將使用臨時目錄進行存儲。

!mkdir /tmp/recsys/
!aws s3 cp s3://amazon-reviews-pds/tsv/amazon_reviews_us_Digital_Video_Download_v1_00.tsv.gz /tmp/recsys/
3.數據預處理

下載好數據後,能夠經過Python的Pandas庫進行數據的讀取,瀏覽和預處理。

首先,運行以下代碼加載數據:

df=pd.read_csv('/tmp/recsys/amazon_reviews_us_Digital_Video_Download_v1_00.tsv.gz', delimiter='\t',error_bad_lines=False)
df.head()

隨後能夠看到以下結果,由於列比較多,這裏截圖顯示的並不完整:

using-amazon-sagemaker-to-build-a-recommendation-system-based-on-gluon2.png

咱們能夠看到,該數據集包含了不少特徵(列),其中每列具體的含義以下:

  • marketplace:兩位數的國家編碼,此處都是「US」
  • customer_id:一個表明發表評論用戶的隨機編碼,對於每一個用戶惟一
  • review_id:對於評論的惟一編碼
  • product_id:亞馬遜通用的產品編碼
  • product_parent:母產品編碼,不少產品有同屬於一個母產品
  • product_title:產品的描述
  • product_category:產品品類
  • star_rating:評論星數,從1到5
  • helpful_votes:有用評論數
  • total_votes:總評論數
  • vine:是否爲 vine 項目中的評論
  • verified_purchase:該評論是否來源於已購買該產品的客戶
  • review_headline:評論標題
  • review_body:評論內容
  • review_date:評論時間

在這個例子中,咱們只准備使用 custermor_id、product_id 和 star_rating 三列構建模型。這也是咱們構建一個推薦系統所須要最少的三列數據。其他特徵列若是在構建模型時添加,能夠有效提升模型的準確率,但本文不會包括這部份內容。同時咱們會保留 product_title 列用於結果驗證。

df = df[['customer_id', 'product_id', 'star_rating', 'product_title']]

同時,由於大部分的視頻對大部分人而言都沒有看過,因此咱們的數據是很稀疏的。通常來講,推薦系統的模型對於稀疏數據能夠很好地處理,但這通常須要大規模的數據來訓練模型。爲了實驗示例運行更加順暢,這裏咱們將把這種數據稀疏的場景進行驗證,而且進行清洗,並使用一個較爲稠密的reduced_df進行模型的訓練。

隨後能夠經過以下代碼進行稀疏(「長尾效應」)的驗證

customers = df['customer_id'].value_counts()
products = df['product_id'].value_counts()
quantiles = [0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.96, 0.97, 0.98, 0.99, 1]
print('customers\n', customers.quantile(quantiles))
print('products\n', products.quantile(quantiles))

能夠看到,只有5%的客戶評論了5個及以上的視頻,同時只有25%的視頻被超過10個用戶評論。

接下來咱們將把數據進行過濾,去掉長尾用戶和產品:

customers = customers[customers >= 5]
products = products[products >= 10]
 
reduced_df = df.merge(pd.DataFrame({'customer_id': customers.index})).merge(pd.DataFrame({'product_id': products.index}))

隨後對用戶與產品進行從新編碼:

customer_index = pd.DataFrame({'customer_id': customers.index, 'user': np.arange(customers.shape[0])})
product_index = pd.DataFrame({'product_id': products.index, 
                              'item': np.arange(products.shape[0])})
 
reduced_df = reduced_df.merge(customer_index).merge(product_index)

接下來,咱們將準備好的數據集切割爲訓練集和驗證集。其中驗證集將做爲模型效果的驗證使用,並不會在訓練中使用:

test_df = reduced_df.groupby('customer_id').last().reset_index()
 
train_df = reduced_df.merge(test_df[['customer_id', 'product_id']], 
                            on=['customer_id', 'product_id'], 
                            how='outer', 
                            indicator=True)
train_df = train_df[(train_df['_merge'] == 'left_only')]

最後,咱們將數據集由 Pandas DataFrame 轉換爲 MXNet NDArray,這是由於即將使用基於 MXNe 的 Gluon 接口進行模型訓練:
b

atch_size = 1024
 
train = gluon.data.ArrayDataset(nd.array(train_df['user'].values, dtype=np.float32),
                                nd.array(train_df['item'].values, dtype=np.float32),
                                nd.array(train_df['star_rating'].values, dtype=np.float32))
test  = gluon.data.ArrayDataset(nd.array(test_df['user'].values, dtype=np.float32),
                                nd.array(test_df['item'].values, dtype=np.float32),
                                nd.array(test_df['star_rating'].values, dtype=np.float32))
 
train_iter = gluon.data.DataLoader(train, shuffle=True, num_workers=4, batch_size=batch_size, last_batch='rollover')
test_iter = gluon.data.DataLoader(train, shuffle=True, num_workers=4, batch_size=batch_size, last_batch='rollover')
4.使用本地機器訓練

首先經過本身定義一個簡單的網絡結構在本地進行訓練,這裏咱們會繼承 Gluon 接口構建 MFBlock 類,須要設置以下的主要網絡結構。更多關於如何使用 Gluon 構建自定義網絡模型的信息,能夠參考動手深度學習。簡單說明以下:

  • embeddings 將輸入的稠密向量轉換爲固定長度。這裏咱們選取「64」,可調節
  • dense layers 全連接層,使用 ReLU 激活函數
  • dropout layer 用於防止過擬合
class MFBlock(gluon.HybridBlock):
    def __init__(self, max_users, max_items, num_emb, dropout_p=0.5):
        super(MFBlock, self).__init__()
        
        self.max_users = max_users
        self.max_items = max_items
        self.dropout_p = dropout_p
        self.num_emb = num_emb
        
        with self.name_scope():
            self.user_embeddings = gluon.nn.Embedding(max_users, num_emb)
            self.item_embeddings = gluon.nn.Embedding(max_items, num_emb)
            
            self.dropout_user = gluon.nn.Dropout(dropout_p)
            self.dropout_item = gluon.nn.Dropout(dropout_p)
 
            self.dense_user   = gluon.nn.Dense(num_emb, activation='relu')
            self.dense_item = gluon.nn.Dense(num_emb, activation='relu')
            
    def hybrid_forward(self, F, users, items):
        a = self.user_embeddings(users)
        a = self.dense_user(a)
        
        b = self.item_embeddings(items)
        b = self.dense_item(b)
 
        predictions = self.dropout_user(a) * self.dropout_item(b)     
        predictions = F.sum(predictions, axis=1)
        return predictions
##set up network
num_embeddings = 64
net = MFBlock(max_users=customer_index.shape[0], 
              max_items=product_index.shape[0],
              num_emb=num_embeddings,
              dropout_p=0.5)
# Initialize network parameters
ctx = mx.gpu()
net.collect_params().initialize(mx.init.Xavier(magnitude=60),
                                ctx=ctx,
                                force_reinit=True)
net.hybridize()
 
# Set optimization parameters
opt = 'sgd'
lr = 0.02
momentum = 0.9
wd = 0.
 
trainer = gluon.Trainer(net.collect_params(),
                        opt,
                        {'learning_rate': lr,
                         'wd': wd,
                         'momentum': momentum})

同時咱們還須要構建一個評估函數用來評價模型。這裏將使用 MSE:

def eval_net(data, net, ctx, loss_function):
    acc = MSE()
    for i, (user, item, label) in enumerate(data):
        
            user = user.as_in_context(ctx)
            item = item.as_in_context(ctx)
            label = label.as_in_context(ctx)
            predictions = net(user, item).reshape((batch_size, 1))
            acc.update(preds=[predictions], labels=[label])
   
return acc.get()[1]

接下來定義訓練的代碼而且示例一下進行幾個輪次的訓練:

def execute(train_iter, test_iter, net, epochs, ctx):
    
    loss_function = gluon.loss.L2Loss()
    for e in range(epochs):
        
        print("epoch: {}".format(e))
        
        for i, (user, item, label) in enumerate(train_iter):
                user = user.as_in_context(ctx)
                item = item.as_in_context(ctx)
                label = label.as_in_context(ctx)
                
                with mx.autograd.record():
                    output = net(user, item)               
                    loss = loss_function(output, label)
                    
                loss.backward()
                trainer.step(batch_size)
 
        print("EPOCH {}: MSE ON TRAINING and TEST: {}. {}".format(e,
                                                                   eval_net(train_iter, net, ctx, loss_function),
                                                                   eval_net(test_iter, net, ctx, loss_function)))
    print("end of training")
    return net
%%time
epochs = 3
trained_net = execute(train_iter, test_iter, net, epochs, ctx)

能夠看到,打印出來的訓練日誌以下圖所示,顯示訓練成功進行,而且 Loss 隨着迭代次數的增長在降低:

using-amazon-sagemaker-to-build-a-recommendation-system-based-on-gluon3.png

5.使用 Amazon SageMaker BYOS 進行模型訓練

在上文範例中,咱們使用本地環境一步步訓練了一個較小的模型,驗證了咱們的代碼。如今,咱們須要把代碼進行整理,在 Amazon SageMaker 上進行可擴展至分佈式的託管訓練任務。

首先要將上文的代碼整理至一個 Python 腳本,而後使用 SageMaker 上預配置的 MXNet 容器,咱們能夠經過不少靈活的使用方式來使用該容器,詳情可參考 mxnet-sagemaker-estimators。

接下來將執行以下幾步操做:

  • 將全部數據預處理的工做封裝至一個函數,本文中爲prepare_train_data
  • 將全部訓練相關的代碼(函數或類)複製粘貼
  • 定義一個名爲 Train 的函數,用於:
    ➢ 添加一段 Sagemaker 訓練集羣讀取數據的代碼
    ➢ 定義超參數爲一個字典做爲入參,在以前的例子咱們是全局定義的
    ➢ 建立網絡而且執行訓練

咱們能夠在下載的代碼目錄中看到 Recommender.py腳本,是編輯後的範例。

如今,咱們須要將數據從本地上傳至 S3,這樣 Amazon SageMaker 後臺運行時能夠直接讀取。這種方式一般對於大數據量的場景和生產環境的實踐十分常見。

boto3.client('s3').copy({'Bucket': 'amazon-reviews-pds', 
                         'Key': 'tsv/amazon_reviews_us_Digital_Video_Download_v1_00.tsv.gz'},
                        bucket,
                        prefix + '/train/amazon_reviews_us_Digital_Video_Download_v1_00.tsv.gz')

最後,咱們能夠經過 SageMaker Python SDK 建立一個 MXNet estimator,須要傳入如下設置:

  • 訓練的實例類型和實例個數,SageMaker 提供的 MXNet 容器支持單機訓練也支持多 GPU 訓練,只須要指定訓練機型個數便可切換
  • 模型存儲的 S3路徑及其對應的權限設置
  • 模型對應的超參數,這裏咱們將 Embedding 的個數提高,後面能夠看到這個結果會優於以前的結果,這裏的超參數配置能夠進一步進行調優,從而獲得更爲準確的模型

完成以上配置後,能夠用.fit () 來開啓訓練任務,這會建立一個 SageMaker 訓練任務加載數據和程序,運行咱們 Recommender.py 腳本中的 Train 函數,將模型結果保存至傳入的 S3路徑。

m = MXNet('recommender.py', 
          py_version='py3',
          role=role, 
          train_instance_count=1, 
          train_instance_type="ml.p2.xlarge",
          output_path='s3://{}/{}/output'.format(bucket, prefix),
          hyperparameters={'num_embeddings': 512, 
                           'opt': opt, 
                           'lr': lr, 
                           'momentum': momentum, 
                           'wd': wd,
                           'epochs': 10},
         framework_version='1.1')
 
m.fit({'train': 's3://{}/{}/train/'.format(bucket, prefix)})

訓練啓動後,咱們能夠在 Amazon SageMaker 控制檯看到這個訓練任務,點進詳情能夠看到訓練的日誌輸出,以及監控機器的 GPU、CPU、內存等使用率等狀況,以確認程序能夠正常工做。

using-amazon-sagemaker-to-build-a-recommendation-system-based-on-gluon4.png

using-amazon-sagemaker-to-build-a-recommendation-system-based-on-gluon5.png

6.託管部署及推理測試

在本地完成訓練後,便可輕鬆地將上述模型部署成一個實時可在生產環境中調用的端口:

predictor = m.deploy(initial_instance_count=1, 
                     instance_type='ml.m4.xlarge')
predictor.serializer = None
<!-- /\* Font Definitions \*/ @font-face {font-family:"Cambria Math"; panose-1:2 4 5 3 5 4 6 3 2 4; mso-font-charset:0; mso-generic-font-family:roman; mso-font-pitch:variable; mso-font-signature:3 0 0 0 1 0;} @font-face {font-family:"Segoe UI"; panose-1:2 11 5 2 4 2 4 2 2 3; mso-font-charset:0; mso-generic-font-family:swiss; mso-font-pitch:variable; mso-font-signature:-469750017 -1073683329 9 0 511 0;} @font-face {font-family:微軟雅黑; panose-1:2 11 5 3 2 2 4 2 2 4; mso-font-charset:134; mso-generic-font-family:swiss; mso-font-pitch:variable; mso-font-signature:-2147483001 718224464 22 0 262175 0;} @font-face {font-family:Consolas; panose-1:2 11 6 9 2 2 4 3 2 4; mso-font-charset:0; mso-generic-font-family:modern; mso-font-pitch:fixed; mso-font-signature:-536869121 64767 1 0 415 0;} @font-face {font-family:"\\@微軟雅黑"; mso-font-charset:134; mso-generic-font-family:swiss; mso-font-pitch:variable; mso-font-signature:-2147483001 718224464 22 0 262175 0;} /\* Style Definitions \*/ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-unhide:no; mso-style-qformat:yes; mso-style-parent:""; margin-top:2.5pt; margin-right:0cm; margin-bottom:2.5pt; margin-left:0cm; mso-para-margin-top:.5gd; mso-para-margin-right:0cm; mso-para-margin-bottom:.5gd; mso-para-margin-left:0cm; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; layout-grid-mode:char; word-break:break-all; font-size:10.5pt; mso-bidi-font-size:11.0pt; font-family:"Segoe UI",sans-serif; mso-fareast-font-family:微軟雅黑; mso-bidi-font-family:"Times New Roman"; mso-bidi-theme-font:minor-bidi; mso-font-kerning:1.0pt; layout-grid-mode:line;} p.a, li.a, div.a {mso-style-name:代碼; mso-style-update:auto; mso-style-unhide:no; mso-style-qformat:yes; mso-style-link:"代碼 字符"; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; layout-grid-mode:char; background:#BFBFBF; mso-background-themecolor:background1; mso-background-themeshade:191; word-break:break-all; font-size:10.5pt; mso-bidi-font-size:11.0pt; font-family:Consolas; mso-fareast-font-family:微軟雅黑; mso-bidi-font-family:"Times New Roman"; mso-bidi-theme-font:minor-bidi; mso-font-kerning:1.0pt; layout-grid-mode:line;} span.a0 {mso-style-name:"代碼 字符"; mso-style-unhide:no; mso-style-locked:yes; mso-style-link:代碼; font-family:Consolas; mso-ascii-font-family:Consolas; mso-fareast-font-family:微軟雅黑; mso-hansi-font-family:Consolas; background:#BFBFBF; mso-shading-themecolor:background1; mso-shading-themeshade:191; layout-grid-mode:both;} .MsoChpDefault {mso-style-type:export-only; mso-default-props:yes; font-family:等線; mso-bidi-font-family:"Times New Roman"; mso-bidi-theme-font:minor-bidi;} /\* Page Definitions \*/ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page WordSection1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:36.0pt; mso-footer-margin:36.0pt; mso-paper-source:0;} div.WordSection1 {page:WordSection1;} -->

上述命令運行後,咱們會在控制面板中看到相應的終結點配置。當成功建立後,能夠測試一下,爲此能夠發出一個 HTTP Post 請求,也能夠簡單地調用 SDK 的.predict () 直接調用,隨後會獲得返回的預測結果:[5.446407794952393, 1.6258208751678467]

predictor.predict(json.dumps({'customer\_id': customer\_index\[customer\_index\['user'\] == 6\]\['customer\_id'\].values.tolist(),

 'product\_id': \['B00KH1O9HW', 'B00M5KODWO'\]}))

在此基礎上,咱們還能夠計算模型在測試集上的偏差,結果爲1.27。這個結果優於咱們以前本地 Embedding 設置爲64時的1.65,這也體現了經過調節網絡結構,咱們能夠不斷優化模型。

test_preds = []
for array in np.array_split(test_df[['customer_id', 'product_id']].values, 40):
    test_preds += predictor.predict(json.dumps({'customer_id': array[:, 0].tolist(), 
                                                'product_id': array[:, 1].tolist()}))
 
test_preds = np.array(test_preds)
print('MSE:', np.mean((test_df['star_rating'] - test_preds) ** 2))

總結
本文介紹瞭如何利用Amazon SageMaker基於Gluon構建一個簡單的推薦系統,而且將它進行部署調用。這能夠是你們入手推薦系統的很好的入門教程。但值得注意的是:本文做爲基礎示例,並無包含超參數調優,網絡結構的優化,多特徵的引入等工做,這都是後續提高準確率構建一個完備推薦系統所必需的工做。若是須要使用更復雜深刻的推薦系統模型,或是基於 Gluon 構建其餘應用,請關於咱們後續發佈地相關內容,或參閱 Amazon SageMaker 官方文檔。

參考資料

底圖2.png

相關文章
相關標籤/搜索