1. 程式人生 > 其它 >GITCHAT系列2:個性化推薦

GITCHAT系列2:個性化推薦

大家好,我是來自PaddlePaddle開源社群的李釗(@livc),目前是一名大三學生。我曾經在手機百度實習,參與推薦演算法和反作弊的研發工作,目前是IDL的一名實習生。很開心作為PaddlePaddle Tutorials系列的作者之一參加 GitChat的分享。

在PaddlePaddle深度學習系列Chat的第一課中,官方開發組的張睿卿同學通過介紹一些深度學習的應用場景,帶領大家瞭解深度學習的基本原理和工作方式,我們先來簡單回顧下。

“人工智慧”並不是一個很新的概念,它其實已經有60歲了,它的發展經歷了三起三落,像極了數學史上的“三次危機”。作為燃料的大資料和硬體(GPU)騰興帶來的並行運算,促成了深度學習在2012年左右的大爆發。深度學習有很多有趣的應用,比如,搭載GoPro的小車的“自動駕駛”可以視為一個迴歸問題,普通的照片可以模仿出著名藝術家畫作的風格,在機器翻譯、序列生成等領域也有所突破。此外,深度學習並不“完美”,還有很多理論基礎問題等待我們去解決,比如說存在可解釋性的侷限:很多東西不能稱為“方法”,只能稱為“竅門”(trick),南大周志華教授將其比作“老中醫看病”。

從去年年底開始,PaddlePaddle社群將理論與實踐結合,開始撰寫一份深度學習教程,其中包括:新手入門、識別數字、影象分類、詞向量、情感分析、文字序列標註、機器翻譯、個性化推薦。這份教程的每一章都對應一個真實問題,從背景介紹到程式碼實踐,帶領大家完整地解決問題。

本次Chat的主題是個性化推薦。在系列教程個性化推薦一文中,我們介紹了推薦系統的背景和經典模型,並以電影推薦為例,使用MovieLens資料集和 PaddlePaddle訓練了一個神經網路模型。

什麼是推薦系統

隨著資訊科技和網際網路的發展,人們逐漸從資訊匱乏的時代走入了資訊過載(information overload)的時代。在這個時代,無論是資訊消費者還是資訊生產者都遇到了很大的挑戰:作為資訊消費者,如何從大量資訊中找到自己感興趣的資訊是一件非常困難的事情;作為資訊生產者,如何讓自己生產的資訊脫穎而出,受到廣大使用者的關注,也是一件非常困難的事情。推薦系統就是解決這一矛盾的重要工具。 —— 項亮 《推薦系統實踐》

喬布斯曾說,“消費者並不知道自己需要什麼,直到我們拿出自己的產品,他們就發現,這是我要的東西”。同樣,我們也可以說,資訊爆炸的時代,面對琳琅滿目的商品,使用者很可能不知道自己真正喜歡什麼,如果沒有推薦系統,使用者也許永遠不知道有更喜歡、更適合的商品沒有瀏覽到。

推薦系統和搜尋引擎是人們獲取資訊的兩種主要方法,與搜尋引擎相比,推薦系統並不需要使用者主動地尋找資訊或商品,也不需要使用者輸入難以用簡練文字描述的需求。但二者並不矛盾,在很多業務場景上推薦和搜尋是相互結合的,比如說,搜尋“周杰倫”時側欄會推薦《聽媽媽的話》。

從使用者的角度講,人們往往喜歡花2個小時看一部電影,卻不願意花20分鐘去挑選一部電影;從企業的角度看,Data Science Central編輯總監Bill Vorhies曾撰文[1]表示,“據估計,對亞馬遜和Netflix這樣的主要電商平臺來說,個性化推薦的使用者可能帶來多達10%到25%的增量收入”,這就是推薦系統的意義。

長尾效應

長尾(The Long Tail)最初由《連線》的總編輯克里斯·安德森(Chris Anderson)於2004年提出,用來描述諸如亞馬遜和Netflix之類的網站的商業和經濟模式,指那些不受到重視的銷量小但種類多的產品或服務,由於總量巨大,累積起來的總收益超過主流產品的現象。在網際網路領域,長尾效應尤為顯著[2]。

如下圖所示,圖中橫軸表示資料型別,縱軸表示頻率,大部分資料的頻率都很低,但都是大於零的(圖中右側黃色部分),這就是長尾。比如,人們生活中常用的漢字其實並不多,但因頻率較高,所以這些為數不多的漢字佔據了左側綠色區域,而絕大部分的漢字罕有使用,它們就屬於長尾。

一個優秀的推薦系統不僅能推薦全域性熱點,更應該能夠準確地理解“長尾”需求:通過挖掘某種使用者群體的小眾需求,將符合條件但並不熱門的商品或資訊推薦給使用者。由於並非每個人的偏好都與主流完全一致,長尾資料的成功挖掘將帶來遠遠高於平均的效益。

百度研究院的王益老師曾在《分散式機器學習系統》系列講座上分享過一個真實的case:使用者搜尋“紅酒木瓜湯”,如果推薦系統能夠理解出“豐胸”、“美容”、“減肥”等方面的語義,那點選(或購買)的機率將遠遠高於平均,推薦系統的任務就是將長尾需求和使用者偏好挖掘出來並匹配。亞馬遜高階副總裁Steve Kessel曾說“如果我有10萬種書,哪怕一次僅賣掉一本,10 年後加起來它們的銷售就會超過最新出版的《哈利·波特》!”說的其實也是這個道理。

傳統的推薦方法

傳統的推薦方法可以分為協同過濾推薦、基於內容過濾推薦和組合推薦,其中協同過濾的應用最為廣泛,我們的教程中有更詳細的介紹。

協同過濾推薦和基於內容過濾推薦各有優缺點,所以在工業界中往往採用模型的組合方式,克服各自的缺點,達到更好的效果。在剛剛結束不久的AAAI-17 大會上,1999年的一篇論文因發現了將協同過濾與基於內容過濾結合起來的有效方式,被評為經典論文提名獎(Honorable Mention)。

深度學習具有優秀的提取特徵的能力,能夠學習多層次的抽象特徵表示,並對異質或跨域的內容資訊進行學習,因此近年來在推薦系統上的應用和探索也漸漸增多。

基於深度學習的推薦系統

這一部分,我們會介紹Google提出的YouTube深度神經網路推薦模型和寬度&深度學習模型,以及我們使用PaddlePaddle實現的融合推薦模型。

YouTube的深度神經網路推薦系統

經常上YouTube看視訊的同學可能知道,它的首頁視訊幾乎全部是個性化的,足以見得推薦系統對這個世界上最大的視訊網站的重要性。

YouTube的推薦算法系統經歷過幾次改動,其團隊也釋出了很多相關的論文。在2016年9月的RecSys會議(推薦系統領域頂級會議)上,Google釋出了YouTube的深度神經網路推薦模型[3]。

這個模型由兩個神經網路組成:候選生成網路和排序網路。這樣劃分是一個常見的做法:為了節省計算資源,首先從大規模樣本中召回候選集,降低資料規模,然後進行更精細的運算,得到top k。

候選生成網路將推薦問題建模為一個類別數極大的多類分類問題(如下圖所示),它首先將使用者的歷史資訊(如觀看歷史、搜尋歷史)和其他特徵拼接成向量,輸入給非線形多層感知器(MLP)。

在訓練階段,將MLP的結果輸入Softmax進行多分類,預測時計算使用者的綜合特徵(MLP的輸出)與所有視訊的相似度,取得分較高的k個視訊輸入給排序網路。

這裡YouTube團隊介紹了“視訊釋出時間”(也可以稱作Example Age,樣本年齡)這一特徵,因為經過觀察,使用者更喜歡新發布的視訊,哪怕有點和自己不相關,對於這樣一個視訊數目龐大的網站,新視訊的推薦也是極其重要的。由於機器學習系統都是使用歷史的行為資料來訓練,這樣就對過去存在一個隱式的偏差(bias),因此把Example Age特徵加入模型後,可以發現模型結果和經驗上的分佈更相符。

候選生成網路結構[4]

排序網路模型結構與候選生成網路類似,它添加了許多用於描述使用者和視訊相關性的更精細的特徵,從而進行更細緻的打分,比如使用者很可能根據首頁視訊的縮圖去選擇。此外,排序網路頂部使用加權邏輯迴歸(weighted logistic regression)進行訓練,使用e^x作為測試階段的啟用函式。

Google的寬度&深度學習(Wide & Deep learning)

Google在2016年6月釋出了一篇關於“寬度&深度學習”的論文[5],業內一些公司也在紛紛學習。這裡的推薦場景是Google Play應用商店,但其實Wide & Deep的方法可以泛化應用在更廣義的推薦場景上。

簡單來說,人腦就是一個不斷記憶(memorization)並且歸納(generalization)的過程。比如說人們通過記憶“麻雀會飛”和“鴿子會飛”,歸納出“有翅膀的動物就會飛”的結論。由此獲得啟發,將寬線性模型(用於記憶,下圖左側)和深度神經網路模型(用於歸納,下圖右側)結合,汲取各自優勢形成了Wide & Deep模型用於推薦排序(下圖中間),這是一個非常有啟發的探索。

寬度&深度模型[6]

寬度模型的輸入是使用者安裝應用和為使用者展示(impression)的應用間的向量積(叉乘),模型通常訓練one-hot編碼後的二值特徵(比如安裝netflix app 並展示了pandora app是 1,沒有展示是0),這種操作不會歸納出訓練集中未出現的特徵對。

基於embedding的深度模型可以探索出過去從未或很少出現的新的特徵組合,提升了推薦商品的多樣性。它可以新增小顆粒特徵(比如安裝了視訊類應用,展示的是音樂類應用),同時也需要手動完成特徵工程。高維稀疏的類別特徵(如人口學特徵和裝置類別)對映為低緯稠密的向量後,與其他連續特徵(使用者年齡、應用安裝數等)拼接在一起,輸入MLP中,最後輸入邏輯輸出單元。

預測(服務)時,寬度&深度學習模型會將所有候選應用的分數從高到低排序後返回給使用者。

應用推薦中的寬度&深度模型[7]

儘管“寬度&深度學習”這一想法很簡單,但經過試驗,它顯著提高了Google Play商店中應用的下載率,同時滿足了訓練和測試階段的速度要求。值得一提的是,寬度&深度學習模型和整合(ensemble)學習並不是一回事,因為整合學習中的模型是分別獨立訓練的,互不干擾,只有在預測時才會聯絡在一起。

融合推薦模型

我們將使用PaddlePaddle實現電影推薦模型,資料集包含了6,000位使用者對4,000部電影的1,000,000條評價(評分範圍1~5分,均為整數),訓練完成後,通過輸入電影和使用者的ID,模型能夠預測出該使用者對該電影的評分,以代表喜好程度。這裡只介紹主要的網路配置,完整版請見教程。

  1. 設定 batch size、網路初始學習率,使用 RMSProp 優化方法。 settings( batch_size=1600, learning_rate=1e-3, learning_method=RMSPropOptimizer())
  2. 構造使用者、電影特徵(以使用者特徵為例) # 將使用者ID,性別,職業,年齡四個屬性分別對映到其特徵隱層。 user_id_emb = embedding_layer(input=user_id, size=embsize) user_id_hidden = fc_layer(input=user_id_emb, size=embsize) gender_emb = embedding_layer(input=gender, size=embsize) gender_hidden = fc_layer(input=gender_emb, size=embsize) age_emb = embedding_layer(input=age, size=embsize) age_hidden = fc_layer(input=age_emb, size=embsize) occup_emb = embedding_layer(input=occupation, size=embsize) occup_hidden = fc_layer(input=occup_emb, size=embsize) # 將這四個屬性分別全連線並相加形成使用者特徵的最終表示。 user_feature = fc_layer( input=[user_id_hidden, gender_hidden, age_hidden, occup_hidden], size=embsize)
  3. 計算餘弦相似度,定義損失函式和網路輸出。 similarity = cos_sim(a=movie_feature, b=user_feature, scale=2) # 訓練時,採用regression_cost作為損失函式計算迴歸誤差代價,並作為網路的輸出。 # 預測時,網路的輸出即為餘弦相似度。 if not is_predict: lbl=data_layer('rating', size=1) cost=regression_cost(input=similarity, label=lbl) outputs(cost)else: outputs(similarity)

訓練完成後,我們可以通過 ./evaluate.py log.txt 評估模型,找出效果最好的模型輪數。接下來,我們搭建一個簡單的ChatBot完成電影推薦的預測,作為融合推薦模型的應用。

融合推薦模型的ChatBot應用

近些年湧現出一大批聊天機器人和智慧家庭裝置,它們幾乎全部支援個性化,比如“識別不同的人”,“根據不同人的喜歡推薦不同的內容”。Facebook創始人扎克伯格使用多種AI技術為自己家裡構建了一個自動控制系統,命名為Jarvis,它能夠根據家庭成員的喜好播放不同風格的音樂。所以,基於聊天機器人的個性化服務是未來的趨勢。

Bot的開發非常簡單,我們藉助Telegram來完成這個任務。Telegram是一款開源的即時通訊軟體(類似微信、WhatsApp等),它的機器人平臺(Telegram Bot Platform)極大地豐富了生態,比如可以使用Bot SSH登入VPS 、接收 RSS訂閱新聞或部落格、下載YouTube視訊、接收微信訊息甚至是玩遊戲等等。由於Bot是面向API的,我們可以開發某個Workflow(比如IFTTT)完成一系列的任務,有人為其創造了一個新名詞,叫“r2r - robots 2 robots”。

由於其服務在中國的網路環境下並不容易訪問,這裡我推薦使用proxychains執行Python檔案或IPython互動環境,當然也可以直接搭建在國外的VPS上。基於融合推薦模型的ChatBot最終效果如圖:

1. 建立 Bot 並安裝依賴

首先需要找官方的機器人老爹@BotFather傳送/newbot命令申請建立,設定bot的基本資訊後會得到一串Token,幫助訪問HTTP API,這是Bot的唯一標識,不能洩露出去。

在GitHub上,很多熱心的開發者使用不同的語言封裝了原生API,讓開發變得更加容易。我們使用Python完成開發,因此首先安裝依賴pip install python-telegram-bot --upgrade

2. 接入PaddlePaddle預測檔案

變數MODEL_PATH是模型評估./evaluate.py log.txt的結果,函式cal_with_paddle實際就是教程中prediction.py的功能,輸入是電影和使用者的ID,輸出預測的評分。

PaddlePaddle社群即將釋出新版API,資料讀入、訓練、預測的過程將變得更加簡潔,因此你完全可以不關心這裡究竟做了什麼。

from py_paddle 
import swig_paddle, DataProviderConverter
from common_utils import *
from paddle.trainer.config_parser import parse_config
try:    
    import cPickle as pickle
except ImportError:    
    import pickle
# 模型路徑
MODEL_PATH = 'output/pass-00004/'
def cal_with_paddle(movie_id, user_id):
    # 載入引數
    swig_paddle.initPaddle('--use_gpu=0')
    conf = parse_config("trainer_config.py", "is_predict=1")
    network = swig_paddle.GradientMachine.createFromConfigProto(
        conf.model_config)    
   assert isinstance(network, swig_paddle.GradientMachine)
    network.loadParameters(MODEL_PATH)    # 讀入資料並預測
    with open('./data/meta.bin', 'rb') as f:
        meta = pickle.load(f)
        headers = [h[1] for h in meta_to_header(meta, 'movie')]
        headers.extend([h[1] for h in meta_to_header(meta, 'user')])
        cvt = DataProviderConverter(headers)
        movie_meta = meta['movie'][movie_id]
        user_meta = meta['user'][user_id]
        data = [movie_id - 1]
        data.extend(movie_meta)
        data.append(user_id - 1)
        data.extend(user_meta)        
       return '%.2f' % (network.forwardTest(cvt.convert([data]))[0]['value'][0][0] + 3)

3. 變數宣告與函式定義

替換TOKEN為實際申請的字串,定義按鍵介面,和互動函式。

from telegram import ReplyKeyboardMarkup
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters, RegexHandler,
                          ConversationHandler)
# 兩種互動方式,分別是按鍵選擇回覆和輸入文本回復
CHOOSING, TYPING_REPLY = range(2)
TOKEN ='123456789:AAG6xe24v748h4G6rUcxzxEZTFI932ECWaE'

# 選擇回覆介面的三個按鍵,分別是輸入電影ID、使用者ID和預測
reply_keyboard = [['Movie_ID', 'User_ID'],
                  ['Predict']]
markup = ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)

# 定義輸出格式
def facts_to_str(user_data):
    facts = list()    
    for key, value in user_data.items():
        facts.append('%s : %s' % (key, value))    
    return "n".join(facts).join(['n', 'n'])

# `/start` 命令
def start(bot, update):
    update.message.reply_text(        
        "Welcome to the movie recommender bot!",
        reply_markup=markup)    
    return CHOOSING

# 記錄按鍵(key)並要求使用者輸入對應文字(value)
def regular_choice(bot, update, user_data):
    text = update.message.text
    user_data['choice'] = text
    update.message.reply_text('Input %s please!' % text)    
    return TYPING_REPLY

# 記錄文字(value)
def received_information(bot, update, user_data):
    user_data[user_data['choice']] = update.message.text    
    del user_data['choice']
    update.message.reply_text("Neat! This is what you already told me:"
                              "%s"
                              % facts_to_str(user_data),
                              reply_markup=markup)    
    return CHOOSING

# 呼叫Paddle預測函式並輸出結果
def predict(bot, update, user_data):
    if 'choice' in user_data:        
       del user_data['choice']

    score = cal_with_paddle(int(user_data['Movie_ID']), int(user_data['User_ID']))
    update.message.reply_text("Predicting with Paddle!n"
                              "Prediction Score is %s" % score)
    user_data.clear()    
    return ConversationHandler.END

4. 開始執行

updater = Updater(TOKEN)
dp = updater.dispatcher

# 定義對話handler
conv_handler = ConversationHandler(    
    # 以`/start`命令作為入口
    entry_points=[CommandHandler('start', start)], 
    # 定義互動方式,支援正則匹配
    states={
        CHOOSING: [RegexHandler('^User_ID|Movie_ID$',
                                regular_choice,
                                pass_user_data=True),
                ],
        TYPING_REPLY: [MessageHandler(Filters.text,
                                    received_information,
                                    pass_user_data=True),
                    ],
    },    
    # 預測
    fallbacks=[RegexHandler('^Predict$', predict, pass_user_data=True)]
)
# 新增對話handler
dp.add_handler(conv_handler)
# 開始執行
updater.start_polling()

至此,我們已經完成了ChatBot推薦模型的基本功能。Bot中還有更豐富的功能值得探索,此外我們還可以接入雲服務的API,例如使用Google Cloud Speech API完成語音轉文字的功能。

總結

近些年來,深度學習已經極大地推進了影象處理、語音識別、NLP等領域的發展與進步,而在推薦系統上面的應用還處於早期階段,同時也意味著有很大的發展空間。此外,深度學習正在為醫學、生物資訊學、邏輯推理、量化投資甚至圍棋等領域帶來新的啟發與思考。我曾與學校的神經科學研究所合作,使用深度學習技術來分析食蟹猴基因特徵,預測microRNA的鹼基序列,獲得了不錯的效果,而最基本的神經網路結構也是從大腦的生物機理獲得的啟發,這形成了推動學科進步的良性迴圈。

2016年的最後一天,羅振宇在他的“跨年演講”中提到,“人工智慧不是人的延伸,它是人的替代”;英偉達CEO黃仁勳在《智慧工業革命》中認為:“繼蒸汽機(發明)、大規模生產以及自動化之後,AI技術將引發第四次工業革命”;周志華教授在採訪中說,“2017年,機器學習技術將在更多行業帶來更大價值”。各個行業的人們都在關注和見證著AI的發展,與此同時,很多工程師和社群(如PaddlePaddle)正在努力著降低學習和應用的門檻。

我們有幸親身經歷了這次發展的浪潮,但仍需清醒地意識到其實還有很漫長的路等待人們的探索,我們期待更多如GAN(生成對抗網路)一樣的新思想的爆發,這需要我們見素抱樸,不忘初心。

感謝

感謝訂閱本次Chat,個性化推薦這一章節的網路結構其實很簡單,更多的知識和內容,還請關注該系列的後續分享。

大家熟知的許多工,如:機器翻譯,看圖說話,為你寫詩,對話機器人,標題黨改寫等等,背後都有著共同的模型。下一課我們將會介紹這些任務背後的深度神經網路模型,一起進入自然語言處理任務中一個非常有意思的問題:自動文字生成。

我們將在下一課介紹自然語言處理任務中的重要積木:迴圈神經網路。圍繞迴圈神經網路,我們會一起討論,如何對抗梯度消失和梯度爆炸,為什麼需要深度迴圈神經網路,如何有效地訓練深度迴圈神經網路。在此基礎上,我們會繼續開發本課中的對話機器人,引入神經圖靈機的概念,介紹去年最火的技術之一:“注意力機制”,利用已有的積木,讓迴圈神經網路從資料中學習,自動生成回覆與使用者進行有趣地互動。最後,我們會一起討論現有技術面臨的挑戰,探討一些加速文字生成任務的技術,期待大家的參與。

PaddlePaddle不僅屬於百度,更屬於開源社群,我們希望對深度學習感興趣的研究人員、工程師和開源愛好者能夠加入PaddlePaddle Tech Writer,撰寫您所擅長的深度學習教程或設計有趣的示例,讓更多的人感受到深度學習的魅力。如果您在使用PaddlePaddle過程中遇到任何問題,都可以去GitHub發起Issue,社群的小夥伴們將在第一時間為您解答,希望PaddlePaddle與您共同成長。

參考資料

  1. http://www.datasciencecentral.com/profiles/blogs/understanding-and-selecting-recommenders-1
  2. https://zh.wikipedia.org/wiki/%E9%95%BF%E5%B0%BE
  3. https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/45530.pdf
  4. 引自論文[3]中圖3
  5. https://arxiv.org/abs/1606.07792
  6. 引自論文[5]中圖1
  7. 引自論文[5]中圖4