淺談推薦系統基礎

這篇文章的技術難度會低一些,主要是對推薦系統所涉及到的各部分內容進行介紹,以及給出一些推薦系統的常用算法,比起技術,產品色彩會強不少。參考了《長尾理論》、《推薦系統實踐》以及大量相關博客內容。

什么是推薦系統

我之前寫過一篇《長尾理論》精讀,里面有這樣的觀點:

推動市場由熱門經濟學向長尾經濟學轉變有三種力量:第一種是生產普及的力量(生產者),第二種是傳播普及的力量(集合器),第三種是供需相連的力量(過濾器)。

生產普及的力量指,當下大眾制作內容(圖像、音視頻、文字等)的門檻大大降低,人們有能力制作并有意愿分享自己產生的內容。使得可供展示的內容量大大增加。

傳播普及的力量指,相當一部分內容由原子存在變為比特存在,不再需要占據物理世界中的『貨架』,而是存儲在硬盤之中,存儲成本的降低使得大量非熱門的長尾內容可以被擺上虛擬世界中的『貨架』,真的有了對外展示的機會。

而供需相連的力量,就是指推薦系統。

既然存在大量的長尾內容,那如何供需相連?推薦系統要做的,就是聯系用戶和內容,一方面幫助用戶發現對自己有價值的內容;另一方面讓內容能夠展現在對它感興趣的用戶面前,從而實現內容消費者和內容生產者的雙贏。

為了聯系用戶和內容,其實過去也有很優秀的解決方案,有代表性的比如分類目錄搜索引擎

隨著互聯網規模的不斷擴大,分類目錄網站也只能覆蓋少量的熱門網站,越來越不能滿足用戶的需求,因此搜索引擎誕生了。搜索引擎可以讓用戶搜索關鍵詞來找到自己所需要的信息,但是,搜索的前提就是用戶要主動提供準確的關鍵詞,但是如果用戶無法準確的描述自己需求的關鍵詞時,搜索引擎就無能為力了。

而推薦系統不同,它不需要用戶提供明確的需求,甚至連用戶主動提出需求都不需要。推薦系統通過分析用戶的歷史行為給用戶的興趣建模,從而主動給用戶推薦能夠滿足它們興趣和需求的內容。

什么是好的推薦系統?

先總體來說,一個完整的推薦系統一般存在三個參與方:用戶內容提供者提供推薦系統的網站

首先,推薦系統要滿足用戶的需求,給用戶推薦那些讓他們感興趣的內容;其次,推薦系統要讓內容提供者的內容都能被推薦給對其感興趣的用戶;最后,好的推薦系統設計,能夠讓推薦系統本身收集到高質量的用戶反饋,不斷提高推薦的質量,提高推薦系統的效益。

推薦系統實驗方法

評價推薦系統效果的實驗方法主要有三種,分別是離線實驗用戶調查在線實驗

離線實驗一般是:

  • 通過日志系統獲得用戶行為數據,并按照一定格式生成一個標準的數據集
  • 將數據集按一定規則分成訓練集和測試集
  • 在訓練集上訓練用戶興趣模型,在測試集上進行預測
  • 通過事先定義的離線指標評測算法在測試集上的預測結果

離線實驗在數據集上完成,不需要真實用戶參與,可以快速的計算出來。主要缺點是離線指標往往不包含很多商業上關注的指標,比如點擊率、轉化率。

用戶調查是理論上最有效的方法,因為高預測準確率不等于高用戶滿意度,還是要從用戶中來,到用戶中去。

用戶調查需要有一些真實的用戶,讓他們在需要測試的推薦系統上完成一些任務,同時我們觀察和記錄他們的行為,并讓他們回答一些問題,最后通過分析他們的行為和答案了解測試系統的性能。

但是用戶調查成本很高,而且測試用戶也需要精心挑選,太麻煩了。

在線實驗一般在離線實驗和必要的用戶調查之后,一般是將推薦系統上線做AB測試,將它和舊的算法進行比較。

AB測試是一種很常用的在線評測算法的實驗方法,不僅是算法,對產品設計的改動也可以采用這種方法。它通過一定的規則將用戶隨機分成幾組,并對不同組的用戶采用不同的算法,然后通過統計不同組的各種不同的評測指標比較不同的算法性能,比如點擊率。

AB測試的缺點是周期較長,影響較大,我們通常只用它測試那些在離線實驗和用戶調查中表現很好的算法。

一般而言,我們需要證明新的推薦算法在很多離線指標上優于現有算法,而且用戶滿意度不低于現有的算法,最后在線上AB測試后,發現在我們關心的指標上也優于現有的算法。這樣新的推薦系統才能最終上線發布。

推薦系統評測指標

用戶滿意度

用戶滿意度是推薦系統最重要的指標,但是用戶滿意度沒法離線計算,只能通過用戶調查和在線實驗獲得。

用戶調查前面講了,是找一些真實的用戶去試用,然后統計行為以及詢問一些問題。

在線實驗一般是對一些線上用戶的行為進行統計來得到用戶滿意度,比如在電子商務網站中,用戶如果購買了推薦的商品,就表示他們在一定程度上滿意;或者也可以設計一些用戶反饋頁面收集用戶滿意度。更一般的,我們可以統計點擊率、用戶停留時間和轉化率等指標。

預測準確度

在過去,很多人將推薦準確度作為推薦系統唯一追求的指標,比如一個推薦系統預測一個用戶將來會購買一本書,而最后用戶買了,這樣推薦的準確度很高。

但是,如果用戶本來就準備買這本書,無論推薦與否,都會購買,那這個推薦系統,實際上并沒有讓用戶購買更多的書。

沒有幫助用戶找到新的感興趣的內容,沒有幫內容生產者找到新用戶,也沒增加推薦系統的總成交量(姑且叫成交量)。

所以,預測準確度當然很重要,但推薦系統也要能擴展用戶的視野,幫助他們發現那些他們可能會感興趣,但卻不那么容易發現的東西。同時,推薦系統還要能夠把那些埋沒在長尾中的內容推薦給可能會對它們感興趣的用戶。

預測準確度在不同研究方向中表現形式也不同,比如評分預測中,就是需要預測,該用戶在將來看到一個他沒有評過分的物品時,會給這個物品評多少分。

在評分預測中,預測準確度一般通過均方根誤差(RMSE)和平均絕對誤差(MAE)計算,如下式:


均方根誤差
平均絕對誤差

式子都很好理解,主要思想就是誤差累加,RMSE因為使用了平方項的懲罰,對系統的評測更加苛刻。

除了評分預測,還有TopN推薦,TopN推薦就是給用戶一個個性化的推薦列表。而TopN推薦的預測準確度一般通過準確率和召回率度量,如下式:

召回率

準確率

至于準確率和召回率,我在《淺談機器學習基礎》中有特別詳細的講解,還專門畫了個圖。而且在《淺談自然語言處理基礎》中,我還講了F1測度,F1測度等于(2*準確率*召回率)/(準確率+召回率),F1測度越高越好,這樣就給出了判定兩個在準確度和召回率各有優勢算法優劣的簡單方法。除了F1測度,《淺談機器學習基礎》還提到了ROC曲線,用于協助決策。

其實TopN推薦要比評分預測更有價值,因為判斷用戶對內容是否感興趣要比預測用戶對內容的評價更有意義,而且現在新的產品,也已經很少用評分來收集用戶反饋了。TopN更直接也更有效。

覆蓋率

覆蓋率描述一個推薦系統對長尾內容的發掘能力,也就是著力于達成前面的『推薦系統要讓內容提供者的內容都能被推薦給對其感興趣的用戶』

先給一個最簡單的覆蓋率定義,就是對所有用戶的推薦列表取并集,看看其是否覆蓋率所有的內容,覆蓋比例是多少。

但是上面的定義過于粗糙,為了更細致的描述推薦系統對長尾內容的發掘能力,我們選擇統計所有用戶的推薦列表中,不同物品出現次數的分布。

如果所有的物品都出現在推薦列表中,并且出現的次數差不多,那么推薦系統發掘長尾的能力就很好,那么如何度量這種定義下的覆蓋率呢?

前面的文章不止一次的講過熵,熵指混亂程度,熵最大的分布,就是各種物品出現的概率均勻,熵越小,就代表分布越集中,混亂程度越小。所以我們可以計算物品分布的熵值,并希望熵越大越好,熵越大指分布越平均,推薦系統也就更接近全覆蓋,而不是只集中在少數熱門的物品。熵的計算公式這里不給了,到處都是。

第二個指標是基尼系數,先給出計算公式:


p函數是流行度從小到大的排序,ij是按照流行度從大到小排序物品列表中的第j個物品。

公式不好理解,這里給張圖:


這張圖怎么解釋呢,黑色曲線表示最不熱門的x%物品的總流行度的流行度占系統的比例y%,為什么相交在(0, 0)和(1, 1)呢,(0, 0)是說,空物品的流行度之和占總物品流行度之和的0%,(1, 1)是說,所有物品的流行度之和占總物品流行度之和的100%,這個很好理解。

然后為什么肯定在y=x之下,考慮這樣一個情況,最均勻的情況,所有物品的流行度都相同,那么每增加固定百分比的物品,那增加的流行度在總流行度中占的比例也是固定的,而且也是相同的。看起來很繞,實際上很容易直觀的感覺出來。

實際上,所有物品的流行度不會是相同的,有熱門物品也有冷門物品,因為是從最不熱門的物品開始計算的,所以剛開始可能很高百分比的冷門物品的流行度也不多,所以這條線就會在y=x下面,增加的非常緩慢;后面到了熱門物品,很少的熱門物品就能增加很多的流行度,所以這條曲線增加的速度開始越來越快,最后到達(1, 1)。

然后基尼系數就是SA/(SA+SB)了,基尼系數越小,就越接近y=x,最理想情況下,基尼系數為0,流行度完全均勻分布。

社會學中有種現象叫做馬太效應,強者愈強,弱者愈弱。這樣,越熱門的物品會越熱門,越冷門的物品越會無人問津,推薦系統就希望盡量消除這種馬太效應,讓冷門物品也能找到對自己感興趣的用戶,讓用戶也不必只看排行榜,自己的興趣和需求也能得到更好的滿足。所以我們先根據初始用戶行為(根據用戶行為定義的熱門/冷門)計算物品的基尼系數,然后再根據推薦系統行為(根據推薦系統的推薦次數定義的熱門/冷門)計算物品的基尼系數,如果后者的基尼系數反而大了,那說明推薦算法加劇了馬太效應。

稍微解釋一下,如果推薦系統只瘋狂推薦某一種物品,其他物品都不推薦,這樣的馬太效應就反而更甚于初始的情況了,又會進一步加劇整個生態的馬太效應。只有推薦系統對物品均勻的推薦,初始的熱門/冷門物品的推薦次數都差不多,才能讓初始的冷門產品熱起來。

多樣性

多樣性描述了推薦列表中物品兩兩之間的不相似性,推薦系統的整體多樣性可以定義為所有用戶推薦列表多樣性的平均值。

相似性或者不相似性的度量方式有多種,比如用物品的內容相似度,我們就可以得到內容多樣性函數;如果用協同過濾的相似度函數描述物品之間的相似度,就可以得到協同過濾的多樣性函數。

其實提高覆蓋率也能在側面對提高多樣性起到積極作用。

新穎性

新穎的推薦是指給用戶推薦那些他們之前沒聽說過的物品,最簡單的方式當然是,把那些用戶之前在系統中有過行為的物品從推薦列表中過濾掉。

還有種方式是讓推薦結果中物品的平均熱門程度較低,這樣就更可能有較高的新穎性。犧牲精度提高新穎性是很容易的,困難的是不犧牲精度,同時提高新穎性。

驚喜度

驚喜度是,如果推薦結果和用戶的歷史興趣不相似,但卻讓用戶覺得滿意。提高推薦驚喜度需要提高用戶滿意度,同時降低推薦結果和用戶歷史興趣的相似度。

新穎度和驚喜度的區別在,新穎度說的是沒聽過的物品,沒聽過的物品是有可能與用戶的歷史興趣相似的,就沒有了驚喜度。驚喜度可以說是新穎度的升級,因為沒聽過的物品中包含與歷史興趣相似的和不相似的物品。也許驚喜度的核心在于讓用戶無法理解推薦原因。

信任度

信任度是指用戶對于推薦系統的信任程度。我們可以通過提供給用戶推薦理由以及利用用戶的好友/信任的人的信息給用戶做推薦來提高信任度。

但是其實很多情況下,對于一些很容易直觀感受到推薦結果好壞的物品,比如音樂,信任度就不那么重要了。

實時性

在很多網站中,因為物品具有很強的實時性,比如新聞、微博等,所以推薦系統的實時性就顯得至關重要。

推薦系統的實時性包含兩部分,一部分是推薦系統需要實時地更新推薦列表來滿足用戶新的需求;另一部分是推薦系統需要能夠將新加入系統的物品推薦給用戶。

實時性對完成推薦系統的冷啟動也有著重要作用。

健壯性

健壯性指一個推薦系統防止作弊的能力。

設計推薦系統時,應盡量使用代價比較高的用戶行為。在使用數據前,可以進行攻擊檢測,從而對數據進行清理。

商業目標

推薦系統也需要滿足自身商業目標的需求。

總結

在上面提到的指標里,預測準確度、覆蓋率、多樣性、新穎性是可以離線計算的。實際評測算法時,我們一般采用預測準確度的正確率和召回率,覆蓋率,還有推薦商品的平均流行度。

綜合一下上面的指標,我們前面說了三個目標,分別是讓用戶滿意、讓物品提供者滿意、讓推薦系統滿意。用戶滿意度對應第一個目標,覆蓋率對應第二個目標,商業目標對應第三個目標。因為用戶滿意度不容易獲得,所以實際上預測準確度替代用戶滿意度成為了最重要的指標。然后我們回到推薦列表上,將其與物品類型結合,物品種類多就是多樣性;將其與用戶認知結合,用戶沒聽過就是新穎性驚喜度是新穎性的升級。然后是整個推薦系統,推薦系統需要實時性健壯性,來穩定保證好的推薦結果。而且有的場景的推薦系統還要考慮到用戶對推薦系統的信任度的問題。

這樣就把這十個指標串起來了,也更方便記憶。

當然我們在采用以上指標進行評測時,也要考慮到評測的用戶維度、物品維度、時間維度,也就是涉及評測的用戶群,物品的種類屬性和評測的季節、時間等。這可以讓我們發現不同算法在不同場景下的優缺點。

利用用戶行為數據

實現個性化推薦最理想的情況,是用戶告訴我們他喜歡什么,但這種方法有三個缺點:

  • 第一個是,現在的自然語言處理技術還很難理解用戶用來描述興趣的自然語言;
  • 第二個是,用戶的興趣是不斷變化的;
  • 第三個是,用戶也不知道自己喜歡什么,或者說,用戶也很難用語言描述自己喜歡什么。

這里考慮代入HMM的思想,用戶的需求會不斷變化,就是狀態序列。而且這個狀態序列是隱藏的,也就是我們無法直接獲知用戶的興趣,不管是因為用戶自己沒意識到還是無法表達。我們需要通過觀察序列,也就是用戶的行為數據去做推測,去根據EM算法估計這個HMM的參數,然后再用其來得到用戶的需求序列,也就是隱狀態序列。

基于用戶行為分析的算法是個性化推薦系統的重要算法,學術界一般將這種算法稱為協同過濾算法

我們能拿到的用戶行為一般分為兩種,顯性反饋行為隱性反饋行為,顯性反饋行為就是點擊喜歡不喜歡,或者評5分1分。隱性反饋行為指的是那些不能明確反應用戶喜好的行為。最具代表性的隱性反饋行為就是頁面瀏覽行為,雖然不明確,但數據量更大。而且隱性反饋只有正反饋,沒有負反饋。

即便是反饋也分為有無上下文,實際上就是是否記錄了用戶反饋行為的時間以及前后行為,這里先只考慮無上下文的隱性反饋數據集。

用戶行為分析

用戶活躍度和物品流行度的分布

互聯網上的很多數據其實都滿足長尾分布,也叫PowerLaw分布,我在《淺談自然語言處理基礎》中還提到過,就是講平滑方法,古德圖靈估計法那里。里面提到了Zipf定律,也即,如果將英文單詞出現的頻率按照由高到低排列,則每個單詞出現的頻率和它在熱門排行榜中排名的常數次冪成反比。也可以這么說,如果x1x2x3是三個熱門排名相鄰的三類單詞,x1最靠前,那么出現的頻率x2/x1 < x2/x3,也就是最開始下降的最快,然后下降速度越來越慢。

我們發現,用戶活躍度和物品流行度都滿足長尾分布。

用戶活躍度和物品流行度的關系

我們認為,新用戶傾向于瀏覽熱門的物品,老用戶會逐漸開始瀏覽冷門的物品。用戶越活躍,越傾向于瀏覽冷門的物品。

僅僅基于用戶數據設計的推薦算法一般稱為協同過濾算法,協同過濾算法也分為不同種類,比如基于鄰域的方法隱語義模型基于圖的隨機游走算法等。其中應用的最廣的是基于鄰域的方法,而基于鄰域的方法主要包括以下兩種:

  • 基于用戶的協同過濾算法:給用戶推薦和他興趣相似的用戶喜歡的物品
  • 基于物品的協同過濾算法:給用戶推薦和他之前喜歡的物品相似的物品

簡便起見,我們通常使用準確率、召回率、覆蓋率和新穎度來對算法進行離線實驗,覆蓋率就用最簡單的覆蓋率定義,新穎度用推薦物品的平均流行度代替。

基于鄰域的算法

基于用戶的協同過濾算法

基于用戶的協同過濾算法主要包括兩個步驟:

  • 找到和目標用戶興趣相似的用戶集合
  • 找到這個集合中的用戶喜歡的,且目標用戶沒有聽說過的物品推薦給目標用戶

第一步的關鍵就是找到和目標用戶興趣相似的用戶,我們可以用兩個用戶興趣的交集比上興趣的并集來求得相似度(Jaccard相似度),或者利用余弦相似度計算。

如果用余弦相似度:


分子是兩個用戶興趣交集的模,分母是兩個用戶興趣的模的乘積的平方根。

要注意的是,有很多用戶之間根本就沒有興趣的交集,所以就不需要浪費時間在這種情況的計算上。

得到用戶之間的興趣相似度之后,UserCF算法會推薦給用戶和他興趣最相似的K個用戶最喜歡的若干個物品

判斷該用戶u對某一件物品i的感興趣程度時的公式如下:

也即用K個和他興趣最相似用戶的平均興趣代表這個用戶的興趣。w代表兩個用戶興趣之間的相似程度,r指感興趣程度的大小,這里統一為1。Σ下面的意思是,K個和u興趣最相似的用戶,而且同時要對物品i有過行為。可以這么理解,如果這K個用戶都沒有對某個物品有過行為,那基本就可以認為他們對該物品都不感興趣,就不應該加到式子中。

換句話說,這K個用戶,與用戶u的相似度決定了他們的話語權,他們表決的方式就是自己是否對該物品有過正面行為。

最后我們只需要取感興趣程度TopN的物品出來推薦給用戶就好了,當然還要去掉該用戶已經有過行為的物品。

K是UserCF算法的一個重要參數。K的選取會影響UserCF算法的結果。

一般進行算法評測時,我們會有兩個標準算法,分別是MostPopular和Random算法,一個是按最高流行度來,一個是完全隨機,都只是簡單的去掉用戶有過行為的物品。

UserCF算法的平均性能要遠好于以上兩個算法。

當然UserCF算法也有改進的空間,比如在計算用戶相似度的時候,大家同樣購買了熱門物品其實沒有什么說服力,并不能以此說明兩個用戶就相似了,所以我們需要對熱門物品進行降權,如下式:

該公式與原公式相比,懲罰了用戶u和用戶v共同興趣列表中熱門物品對他們相似度的影響。這里先提一下TF-IDF,后面還要提,《淺談機器學習基礎》中講K-means的時候就講過TF-IDF,TF-IDF里的這個IDF,就是對出現在幾乎所有文檔中的熱門詞進行降權懲罰。

基于物品的協同過濾算法

基于物品的協同過濾算法是目前業界應用最多的算法。

如果網站的用戶數目增加較快,計算用戶興趣的相似度矩陣就越來越難。而ItemCF算法不計算用戶興趣的相似度矩陣,而是計算物品之間的相似度。還有,我們前面說過基于鄰域的這兩個算法都是協同過濾算法,協同過濾算法的定義就是只使用用戶行為數據,所以這里所定義的物品的相似度,不利用物品本身的內容信息去計算,而是主要通過分析用戶的行為記錄計算物品之間的相似度。

如果喜歡A的用戶大多都喜歡B,那么A和B可以講擁有一定的相似性。或者說,就算不相似,那我們把B推薦給喜歡A的用戶也是沒錯的。

基于物品的協同過濾算法主要分為兩步:

  • 計算物品之間的相似度
  • 根據物品的相似度和用戶的歷史行為給用戶生成推薦列表

我們可以用下面的公式定義物品之間的相似度:


意思就是,買了i的用戶有多少也買了j。如果兩者的用戶群重合比例越大,那么認為ij就更相似。

但是還有個問題,就是如果按照上面的公式算,所有的物品都和熱門商品相似,如果j是大熱門商品的話,基本上喜歡i的全都喜歡j,這樣就有問題,為了提高覆蓋率,我們要對熱門物品進行懲罰:

上面的式子就對熱門物品的權重進行了懲罰。

得到物品的相似度之后,ItemCF通過如下公式計算用戶u對物品i的興趣:

與UserCF對比著來說,UserCF是用K個和用戶u興趣最相似用戶的平均興趣代表這個用戶u的興趣;ItemCF就是用K個和物品j最相似的物品來代表這個物品j。UserCF是,這K個用戶,與用戶u的相似度決定了他們的話語權,他們表決的方式就是自己是否對該物品有過正面行為;ItemCF是,這K個物品,與物品j的相似度決定了他們的話語權,他們表決的方式就是自己是否被該用戶有過正面行為。

然后我們再回到物品相似度,雖然上面已經給熱門物品降了權,但是我們還要考慮到熱門用戶的問題。我們認為,一個活躍用戶可能會喜歡很多種類的物品,他對物品相似度的貢獻應該小于不活躍的用戶,因為不活躍的用戶往往喜歡比較專一,在衡量物品相似度上更有價值,這叫IUF(Inverse User Frequence)。如下式:


又進一步對活躍用戶進行了降權

另外,在有物品分類的情況下,我們需要對類內物品相似度進行歸一化,因為通常熱門類別類內相似度也較高。如果一個用戶同時喜歡了熱門類別和非熱門類別的物品,如果純按照相似度推薦,那就會都推薦給用戶熱門類別中的物品,會降低覆蓋度、多樣性。所以我們利用類內最大的相似度,對類內所有的相似度進行歸一化。

UserCF和ItemCF的綜合比較

主要從兩個方面來講,第一個,UserCF的推薦結果著重于反應和用戶興趣相似的小群體的熱點,著重于維系用戶的歷史興趣,因為就是根據歷史興趣計算出來的相似用戶,進而計算出來的推薦商品。而ItemCF的推薦更加個性化,反映用戶自己的興趣傳承,因為一旦用戶的興趣有了更新,喜歡了新物品,那么與該物品相關的物品在參與ItemCF進行計算時,就會馬上有權重提高,被推薦出來。

這么說,UserCF幫你找了一些用戶來代表你,他們的興趣是不可能統一的發生大幅改變的,所以你得到的推薦結果都是這一類的東西;而ItemCF,一旦你興趣列表變了,那接著就認為你興趣變了,喜歡你這個新興趣的人喜歡的物品就會被推薦給你。

UserCF認為喜歡同樣物品的人相似,ItemCF認為被同樣人喜歡的物品相似。UserCF對用戶聚類,整體對待他們的喜好,ItemCF對物品聚類,喜歡一個就是喜歡一堆。

對于UserCF和ItemCF,再舉一下典型的例子,首先是新聞網站,新聞網站必然要用UserCF,相似用戶的興趣基本相同,沒問題;如果用了ItemCF,難道要推薦和這篇新聞相似的舊新聞?當然這兩種方法也不是一定要絕對分開。

比如音樂網站,網易云音樂的推薦算法,就更接近ItemCF,你喜歡了一種新風格,這一風格的歌就會被推薦給你,而不是認為你一輩子只喜歡聽一種類型的音樂,把你和與過去的你相似的人綁在一起。

第二個是從技術角度想,物品和用戶表,哪個穩定就用哪個建模。物品迅速增加那就建立用戶相似度表,用戶迅速增加就建立物品相似度表。

隱語義模型

隱語義模型(latent factor model,LFM)是最近幾年推薦系統最為熱門的研究話題,它的核心思想是通過隱含特征聯系用戶興趣和物品。

前面已經詳細的介紹了UserCF和ItemCF,這里說一下LFM的主要思想,首先回憶一下SVD,SVD將矩陣拆解為三部分的乘積。《淺談機器學習基礎》中這樣講過:

SVD的第二個用途是在自然語言處理中,我在《數學之美》這本書上讀到。我們用A矩陣來描述成千上萬篇文章和幾十上百萬個詞的關聯性,A里面每一列是一篇文章,每一行代表一個詞,對應位置上是這個詞的加權詞頻(比如TF-IDF值),然后我們對A進行奇異值分解,分成這樣:A=XBY,這里和前面的:A=XY的關聯性在于,兩式的X相同,第二式的Y等于第一式中的BY,X是M*K,B是K*K,Y是K*N。

第一個矩陣X是對詞分類的結果,它的每一行表示一個詞,每一列表示一個同義詞類,對應位置的值表示該詞和該同義詞類的相關性大小。

第三個矩陣Y是對文章分類的結果,它的每一列對應一篇文章,每一行表示一個主題,對應位置的值表示該文章和該主題的相關性大小。

第二個矩陣則展示了不同同義詞類和不同文章主題的相關性大小。

推薦系統這里也是同理,如果將原數據按照SVD分解成三個矩陣的話,所得到的就是對用戶興趣的分類、對物品的分類以及用戶興趣類別與物品類別之間的關系。當然我們也知道SVD不僅能分解成三個矩陣的形式,也能分解為兩矩陣的形式,意義是用戶興趣與某隱類的關系和該隱類與物品的關系。SVD的詳細講解可以參考前面的《淺談機器學習基礎》,其實下面要講的LFM方法,也就是《淺談機器學習基礎》所講的,SVD在推薦系統中的應用。

當然對用戶興趣和物品進行分類這件事情人工也是可以做的,但成本較大,而且效果也并不太好,所以這里就不詳細說了。

隱含語義分析技術其實有很多著名的模型和方法,其中和該技術相關的有pLSA、LDA、隱含類別模型、隱含主題模型、矩陣分解等。這些方法在本質上是相通的。這里主要講解LFM。

LFM通過如下公式計算用戶u對物品i的興趣:

累加式子中的p代表用戶u的興趣和第k個隱類之間的關系,q代表第k個隱類和物品i之間的關系。對所有隱類求和的結果就是總的興趣程度。

這其實是種機器學習方法,模型就是這個模型,然后我們可以用平方誤差來做損失函數,就是給定訓練集下,度量用戶感興趣與否的實際情況與預測結果是否相符,再用梯度下降最小化損失函數,減小模型預測結果與實際情況的誤差,最終收斂就可以了。我們還可以在損失函數中添加正則項來防止過擬合。這些都是《淺談機器學習基礎》里面反復講過的東西。

而且為了應對隱性反饋數據集只有正樣本的情況,我們傾向于從用戶沒有行為的熱門物品中選取適量(與正樣本數平衡)的負樣本。適量就不用說了,選擇熱門物品的原因在于,物品熱門而用戶對其無正面反饋,比冷門物品更能說明用戶對其不感興趣,而不是因為也許根本就沒有發現。

LFM還有個問題,就是它很難實現實時的推薦,因為經典的LFM模型每次訓練時都要掃描所有的用戶行為記錄,不是分分鐘就能訓練好就能更新用戶隱類向量p和物品隱類向量q的。如果要將LFM應用在新聞網站這種內容實時更新的系統中,那是肯定無法滿足需求的。

雅虎為了解決傳統LFM不能實時化的問題,提出了一個解決方案,公式如下:


后面那部分就是原先的用戶隱類向量和物品隱類向量,幾個小時更新一次。實時性體現在前面的式子上,x是根據用戶歷史行為特別訓練的用戶向量,y是根據物品的內容(關鍵詞、屬性、種類)去生成的物品內容特征向量。這樣兩者的乘積就能實時的估計出用戶對該物品的興趣,幾小時后,通過傳統的LFM就能得到更精確的數據。

就像上面說的,LFM與基于鄰域的這兩種方法UserCF和ItemCF相比,LFM不能在線實時推薦,需要提前訓練好模型,而ItemCF可以,至于UserCF,只要和他相似的用戶喜歡了新的物品,也可以做到實時推薦。

基于圖的方法較麻煩,而且效果也比不上LFM,這里就不詳細說了。

推薦系統冷啟動問題

前面我們講過如何使用用戶行為數據去設計一個推薦系統,但是推薦系統該如何完成冷啟動?

冷啟動問題主要分為三種,一種是用戶冷啟動,對于一個新用戶,我們沒有他的歷史行為數據,該怎么為其做個性化推薦;第二種是物品冷啟動,就是如何將新的物品推薦給可能對它感興趣的人;第三種是系統冷啟動,也就是整個系統沒有用戶,只有一些物品的信息,該怎么辦。

利用專家做初始標注

我們可以利用專家在若干個維度上對物品完成初始標記,后面再利用機器學習算法去計算相似度。這里不詳細說了。

利用用戶注冊信息

比如我們可以利用用戶的人口統計學信息、用戶興趣描述(很少)、從其他網站導入的用戶站外行為數據。

我們可以計算擁有每種特征的人對各個物品的喜好程度,比如可以簡單的定義為喜歡某種物品的人在擁有這種特征的人中所占的比例,而且我們還要注意要對熱門物品降權,免得給所有特征的人都推薦熱門物品。

選擇合適的物品啟動用戶的興趣

比如我們可以在用戶注冊后給用戶提供一些物品,讓用戶反饋他們對這些物品的興趣。

那啟動物品集合該怎么選?該怎么設置題目給新用戶做才最有效果?

回想一下《淺談機器學習基礎》里講的決策樹算法,這也就是一個對用戶的分類問題,決策樹算法里面,我們的思想是依次選擇讓整個數據集熵減小最大的特征對用戶進行劃分。如果我們已經擁有對用戶興趣的劃分,也即可以方便的計算熵,那直接用決策樹算法是最好的,但是如果我們沒有,那也可以選擇一種近似的決策樹算法。

不過與決策樹的思想相同,仍然要去選擇區分度最大的物品對用戶進行分類,我們可以用用戶對物品評分的方差來度量,方差越大說明意見分歧越大,越有區分度。我們先選擇最有區分度的物品對用戶分類,然后再對不同類別的用戶選擇對該類別下的用戶最有區分度的物品進行分類,不斷迭代。在決策樹算法中,我們用熵減,或者叫信息增益定義物品的區分度,而這里我們用的是評分方差。

利用社交網絡

我們可以導入用戶在其他系統中的社會化關系,然后按照UserCF算法的思想,把與用戶有好友關系的用戶臨時當做相似用戶,熟悉度替代相似度來使用UserCF算法進行推薦。

如果推薦系統是直接用來起到推薦好友的作用,那要考慮到網站的類型,如果用戶的目的是為了獲取內容,那盡量為其推薦與他愛好相似的用戶;如果用戶的目的是認識熟人,那根據社交關系鏈推薦會更有效果,比如推薦給他朋友的朋友,利用手機通訊錄也是很好的選擇。

還有一種是信息流推薦,這里也一并講了,Facebook的EdgeRank是很流行的信息流推薦算法,該算法綜合考慮了每個會話的時間、長度和與用戶興趣的相似度。

比如這樣定義一條對話的權重:


u指產生行為的用戶和當前用戶的熟悉度,熟悉度可以用共同好友數量來衡量;w指行為的權重,比如原創、評論、點贊、轉發等,不同的行為應該有不同的權重;d指時間權重,越早的行為權重越低。

除了上面這些社會化因素之外,我們還可以進一步考慮用戶本身對會話內容的偏好,比如會話的長度、會話話題與用戶興趣之間的相關性,這樣再結合前面的社會化屬性,就會比較全面了。

利用物品的內容信息

其實UserCF算法對物品冷啟動并不敏感,新加入的物品,如果有推薦系統之外的方式能讓用戶看到,只要一個用戶群中有一個人喜歡了,那這個物品就會擴散開來,然后又帶動了進一步擴散。離線訓練的用戶相似度表是不需要動的。

但是ItemCF算法就不行了,對于新的物品,我們根本不知道它跟哪些物品相似,推薦系統就推薦不出來它,這涉及到物品相似度表,解決方案只能是頻繁的更新相關表了,比如半小時更新一次。

我們還可以利用物品的內容信息來解決基于ItemCF的推薦系統的冷啟動問題,我們可以將物品通過向量空間模型表示,維度可以是一些導演、演員之類的實體,如果內容是文本的,可以利用NLP技術抽取一些關鍵詞,也就是《淺談自然語言處理基礎》里面提過的命名實體識別。

這樣我們就得到了物品的一個關鍵詞向量,里面的維度是較能夠體現物品特征的關鍵詞,然后再回想一下TF-IDF算法,我們就把一個個物品當成一篇篇文章,一個個關鍵詞當做文章里的詞。如果物品真的是文本,那就可以直接用TF-IDF算法,把每個維度替換成相應的TF-IDF值;如果不是文本,可以根據知識,人工的賦予TF權重,當然還可以計算出相應的IDF值來使模型更為精確。

然后我們的關鍵詞向量就可以利用余弦相似度去參與計算物品的內容相似度了。對物品歸類的話,可以直接用KNN,這樣就得到了內容過濾算法ContentItemKNN。

內容過濾算法忽視了用戶行為,從而也忽視了物品的流行度以及用戶行為中所包含的規律,所以它的精度比較低,但新穎度卻比較高。

為了優化內容過濾算法,這里提出主題模型LDA(Latent Dirichlet Allocation),先說一下LDA和LFM的關系,它們最相似的部分都是發現內容背后的隱含主題,但其它的關系真是不大,有人講它們是雷鋒和雷峰塔,Java和Javasript的關系。

LFM用的是矩陣分解的思想,然后梯度下降去學習,與SVD的思想相似。

而LDA由pLSA、LSA這兩種主題模型演化而來,這里詳細講解一下,參考了主題模型-LDA淺析,不過我覺得我講的好理解得多_(:з」∠)_

LDA模型對的基本思想是,一篇文章的每個詞都是通過以一定概率選擇了某個主題,并從這個主題中以一定概率選擇某個詞語來得到的。

我們先引入一個問題,如何生成M篇文章,每篇文章包含N個單詞。

先說第一種最簡單的方法:


我們先搞一批訓練語料,學出單詞w的概率分布p(w),然后把這個分布用N次,得到一篇N個單詞的文章,然后把這個方法用M次,得到M篇N個單詞的文章。實際上也就是連著用p(w)這個分布M*N次。

這個方法的缺點就是單詞沒有主題,沒有聯系,亂七八糟。

然后是第二種方法:


這種方法增加了主題z,主題z也有主題的分布p(z)

先只看圖,這個z在方框N外面,說明一篇N個詞的文章只有一個主題;其次,這個z在方框M里面,M篇不同的文章有不同的主題z

這樣,這M篇文章,我們為每一篇文章都先根據p(z)生成一個z,然后在這篇文章內,再使用N次條件概率p(w|z)生成N個單詞,由此得到M篇N個單詞的文章。一個任務里面有M篇不同主題的文章,每篇文章的單詞都是根據自己的主題生成的。

這個方法的缺點在于,每篇文章里只能有一個主題。

然后就是LDA方法了:


LDA一下子多了三個參數α、β和θ,我們先只看圖,我們發現主題z放在了方框N里面,說明N個詞,每個詞都有自己的主題了,一個詞的分布就成了p(w|z)*p(z)。然后我們看到θ,θ是一個主題向量,決定了p(z),θ在方框N外面,說明每篇文章的N個詞都有一個相同的θ,用于決定這篇文章內所有N個詞的p(z)。θ在方框M里面,說明M篇文章,每篇文章都有一個不同的θ,而p(θ)也就被需要了,p(θ)是θ的分布,具體為Dirichlet分布,決定每篇文章的所有N個詞都對應哪一個θ。然后再外面是α,α在方框M外面,也就說對于一個任務的這M篇文章,都是同一個α,而這個α決定了p(θ)。此外還有一個β,這個β是希望,詞的分布概率不只被z決定,也即詞的分布不是p(w|z)*p(z)而是p(w|z,β)*p(z)

上面扯了這么多,都是為了方便理解,實際上就是這個公式:


LDA聯合概率分布

這么說,對于一個任務,我們先給定α、β(一個任務一個α、β),這個α決定了M篇文章都分別對應哪個主題向量θ(一篇文章一個θ),然后每篇文章的主題向量θ決定了這篇文章的主題分布p(z),也就是這篇文章每個詞都分別對應哪個主題z(一個詞一個z)。然后每個詞是由這個詞的z和β共同決定的。

再精簡一點,一個任務一個α、β,一篇文章一個主題向量θ,一個詞一個主題z,α決定主題向量θ,主題向量θ決定主題z,主題z和β一塊決定詞w

傳統判斷兩個文檔相似性的方法是通過查看兩個文檔共同出現的單詞的多少,如TF-IDF等,這種方法沒有考慮到文字背后的語義關聯,可能在兩個文檔共同出現的單詞很少甚至沒有,而LDA是主題模型,可以通過隱含主題發現沒有重復單詞的文檔的相似性。LDA在個性化推薦、社交網絡、廣告預測等各個領域的應用都非常廣泛。

然后LDA的訓練與HMM相似,采用的也是EM算法,最后會收斂到一個合理的分布上去。

我再嘗試回答幾個更本質的問題。

為什么一個重復單詞都沒有,還能判定文章相似?
用的單詞雖然不重復,但都語義上相似。

怎么判斷單詞語義上相似?
出現在了相似文章中。

那這不是個雞生蛋蛋生雞的問題嗎?
EM算法就是解決這種雞蛋問題的,回憶《淺談自然語言處理》里面對EM算法的講解即可。

LDA可以合理的將單詞歸類到不同的隱含主題之中。而且如果文檔的主題向量θ,也即主題z的分布較為相似,那我們就可以認為兩篇文檔具有較高的相似度,計算分布的相似度可以用KL散度,也就是相對熵。

與上下文信息結合

之前提到的推薦算法主要研究了如何聯系用戶興趣和物品,將最符合用戶興趣的物品推薦給用戶,但卻都沒有考慮到上下文。

比如舉幾個例子,不能因為用戶在夏天喜歡過某件T恤,就在冬天也給該用戶推薦類似的T恤;用戶在中關村打開一個美食推薦系統時,不能給他推薦河北省的餐館;用戶在上班時和下班后的興趣會有區別,在平時和周末的興趣會有區別,甚至上廁所時和在辦公桌旁閱讀的喜好也是不同的。

時間上下文信息

一般認為,時間對用戶興趣的影響表現在用戶的興趣是變化的物品也是有生命周期的季節\節日效應

推薦系統需要擁有實時性來滿足用戶變化的興趣,比如用戶一旦產生了新的行為,推薦系統就應該有恰當的反應。而且還有一點需要注意的是,推薦系統需要有時間多樣性,也就是,即便是用戶實際上沒有進行任何操作,但我們也不應該每天給用戶推薦相同的內容。

比如我們可以在生成推薦結果時加入一定的隨機性,或者記錄用戶每天看到的推薦結果,對這些推薦結果進行適當的降權,又或者每天給用戶使用不同的推薦算法。

這里我們主要考慮,時間上下文信息對我們經典的基于鄰域的兩個算法ItemCF和UserCF能夠起到什么優化作用。

對于ItemCF,考慮第一點,用戶在相隔很短的時間內喜歡的物品具有更高的相似度;然后是第二點,用戶近期行為比用戶很久之前的行為,更能體現用戶現在的興趣。

對于UserCF,考慮第一點,如果兩個用戶同時喜歡相同的物品,那么這兩個用戶應該有更大的興趣相似度;然后是第二點,與當前用戶最相似的這一組用戶最近的興趣,應該比這組用戶很久之前的興趣更加接近當前用戶今天的興趣。

畢竟ItemCF和UserCF都各有兩個過程,只要將兩個過程分別與時間結合起來,很容易就能知道該往哪個方向優化。

地點上下文信息

地點上下文與用戶興趣也有一定的關系,比如不同城市/國家的人的興趣愛好會有不同,這叫興趣本地化,還有用戶往往在附近地區活動,一般不會因為要吃個飯坐高鐵去別的地方,這叫活動本地化

所以我們在分析用戶行為數據時,可以考慮到用戶位置和物品位置,當然這是一些實體化的服務提供者需要考慮的問題,如果講網購,用戶和物品位置對喜好的影響就小多了,但也并不是完全消失。

推薦系統實例

這里主要是講好四張圖,首先是第一張,推薦系統和其他系統之間的關系:


推薦系統和其他系統之間的關系

我們通過用戶行為以及其他數據設計推薦系統,推薦系統通過前臺頁面與用戶產生交互,所得到的數據又被日志系統記錄,處理后又回到用戶行為數據庫中,被用來設計更好的推薦系統。

然后是第二張,基于特征的推薦系統架構思路:


基于特征的推薦系統架構思路

其實推薦系統做的就是文章最開頭長尾理論里面講的供需相連,就是連接用戶與物品,那么用戶與物品通過什么相連呢,我們統一的定義其為『特征』。

比如ItemCF,用戶喜歡了一個物品,就相當于是有了一個特征,我們根據這個特征找到相似物品推薦給用戶。

比如UserCF,用戶和某K個用戶最相似,這就也是一個特征,我們根據這個特征找到這K個用戶最喜歡的物品推薦給用戶。

至于LFM,那就與本質更接近了,它的隱含主題/語義就是特征。

還有LDA,LDA與ItemCF其實同理,用戶喜歡了一篇文檔,就相當于是有了一個特征,那根據主題向量θ找到相似的文檔推薦給用戶即可。

然后是第三張,推薦系統的架構圖:


推薦系統的架構圖

我們可以看到推薦系統可以有不止一個推薦引擎,有了多個推薦引擎,我們可以統籌兼顧,方便的配置不同特征和任務的權重,推薦系統只負責將多個推薦引擎的結果按照一定權重或者優先級合并、排序然后返回。

然后是第四張,推薦引擎的架構圖:


推薦引擎的架構圖

推薦引擎架構主要包括三部分:

  • 部分A負責從數據庫或緩存中拿到用戶行為數據,通過分析不同行為,生成當前用戶的特征向量,如果使用非行為特征,就不需要行為提取和分析模塊了,該模塊的輸出就是用戶特征向量。
  • 部分B負責將用戶的特征向量通過特征-物品相關矩陣轉化為該推薦引擎的初始推薦物品列表。
  • 部分C負責對初始的推薦列表進行過濾、排名等處理,從而生成該引擎的最終推薦結果。

部分A和部分B都和算法的選擇有關,這里主要說一下部分C,首先是過濾模塊,我們通常要過濾掉用戶已經產生過行為的物品、過濾掉候選物品以外的物品、過濾掉某些質量很差的商品。

過濾掉候選物品以外的物品有些難理解,意思是,比如說,有產品需求,是要求推薦這個種類的產品,或者用戶自主設置了篩選條件,比如一定的價格區間或者限定了SPU等。

然后是排名模塊,這個各個算法都有考慮,不過這里還是統一的說一下,對于各種推薦算法,我們往往都需要對熱門物品進行降權,排名模塊這里往往也需要一個對熱門物品進行降權的子模塊,來再一次提高新穎性。而且還可以考慮這樣一個問題,與用戶喜歡的物品相似的熱門物品,用戶更有可能已經知道了,可以在對熱門物品降權時著重照顧一下這部分物品。

說完了新穎性,這里提一下多樣性,如果僅按相似度去計算,很可能推薦出的物品都屬于同一個類別。我們可以將原始推薦結果按某種內容屬性分為幾類,然后推薦每類前幾名的物品。就像星際爭霸比賽,雖然說是要看實力,但是也總是要分賽區的,每個賽區多少個名額,要是純按實力,可能所有的名額都是韓國人的了。盡量讓推薦結果來自不同的特征。

還有時間多樣性,前面也提過了,即便是用戶不操作,也盡量不讓用戶每天看到相同的推薦內容。可以引入隨機、記錄用戶看過的推薦結果進行降權或者直接每天用不同的推薦算法。

排名模塊最重要的部分就是用戶反饋模塊,用戶反饋模塊主要是通過分析用戶之前和推薦結果的交互日志,預測用戶會對什么樣的推薦結果比較感興趣,然后根據用戶的興趣進一步優化推薦結果。

比如推薦系統的目標是提高用戶對于推薦結果的點擊率,那么可以利用點擊模型預測用戶是否會點擊推薦結果。比如搜索結果的點擊預測、搜索廣告的點擊預測、上下文廣告的點擊預測。

構建這個預測模型首先需要提取特征,比如:

  • 用戶相關的特征:年齡、性別、活躍度
  • 物品相關的特征:流行度、內容屬性、評分
  • 物品在推薦列表中的位置
  • 用戶之前是否點擊過和推薦物品有同樣推薦解釋的其他推薦結果
  • 用戶之前是否點擊過和推薦物品來自同樣推薦引擎的其他推薦結果

本篇文章的推薦算法基本以推薦物品的推薦算法為主,上面的架構也更傾向于去解決物品推薦問題,不太適合解決社會化推薦問題。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,011評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,263評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,543評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,323評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,874評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,095評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,605評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,551評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,720評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,961評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,358評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,612評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,330評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,690評論 2 370

推薦閱讀更多精彩內容