2019-03-28

Wide & Deep Learning for Recommender Systems(Google&Facebook推薦)

1、背景

文章提出的Wide&Deep模型,旨在使得訓練得到的模型能夠同時獲得記憶(memorization)和泛化(generization)能力:

  • 記憶(體現(xiàn)準確性):即從歷史數(shù)據(jù)中發(fā)現(xiàn)item或者特征之間的相關性萨西;
  • 泛化(體現(xiàn)新穎性):即相關性的傳遞承璃,發(fā)現(xiàn)在歷史數(shù)據(jù)中很少或者沒有出現(xiàn)的新的特征組合。

2、Wide&Deep模型

2.1丑罪、模型結構

Wide&Deep模型結構圖

在Wide&Deep模型中包括兩個部分传黄,分別為Wide和Deep部分杰扫,Wide部分如上圖中的左圖所示,Deep部分如上圖中的右圖所示膘掰。

2.2章姓、Wide模型

Memorization主要是學習特征的共性或者說相關性,產(chǎn)生的推薦是和已經(jīng)有用戶行為的物品直接相關的物品识埋。

用的模型是邏輯回歸(logistic regression, LR)凡伊,LR 的優(yōu)點就是簡單(simple)、容易規(guī)闹现郏化(scalable)系忙、可解釋性強(interpretable)。LR 的特征往往是二值且稀疏的(binary and sparse)惠豺,這里同樣采用 one-hot 編碼银还,如 “user_installed_app=netflix”,如果用戶安裝了 Netflix洁墙,這個特征的值為 1蛹疯,否則為 0。

為了達到 Memorization扫俺,我們對稀疏的特征采取 cross-product transformation苍苞,比如說 AND(user_installed_app=netflix, impression_app=pandora”) 這個特征,只有 Netflix 和 Pandora 兩個條件都達到了,值才為 1羹呵,這類 feature 解釋了 co-occurrence 和 target label 之間的關系骂际。一個 cross-product transformation 的局限在于,對于在訓練集里沒有出現(xiàn)過的 query-item pair冈欢,它不能進行泛化(Generalization)

Wide模型的輸入是用戶安裝應用(installation)和為用戶展示(impression)的應用間的向量積(叉乘)歉铝,模型通常訓練one-hot編碼后的二值特征,這種操作不會歸納出訓練集中未出現(xiàn)的特征對凑耻。

實際上太示,Wide模型是一個廣義線性模型:y = w^{T}x+b,其中特征x=[x_1,...,x_d]是一個d維特征向量香浩,特征包括原始輸入特征以及cross-product transformation特征类缤。交叉積轉換為\phi_k(x)=\prod_{i=1}^d x_{i}^{c_{ki}}
c_{ki}為布爾變量,若第i個特征是第k個變換\phi_k的一部分邻吭,值為1餐弱,否則為0。w=[w_1,...,w_d]為模型參數(shù)囱晴。最終在y的基礎上增加Sigmoid函數(shù)作為最終的輸出膏蚓。

2.3、Deep模型

Generalization可以理解為相關性的傳遞(transitivity)畸写,會學習新的特征組合驮瞧,來提高推薦物品的多樣性,或者說提供泛化能力(Generalization)枯芬。

泛化往往是通過學習 low-dimensional dense embeddings 來探索過去從未或很少出現(xiàn)的新的特征組合來實現(xiàn)的论笔,通常的 embedding-based model 有 Factorization Machines(FM) 和 Deep Neural Networks(DNN)。特殊興趣或者小眾愛好的用戶破停,query-item matrix 非常稀疏翅楼,很難學習,然而 dense embedding 的方法還是可以得到對所有 query-item pair 非零的預測真慢,這就會導致 over-generalize毅臊,推薦不怎么相關的物品。這點和 LR 正好互補黑界,因為 LR 只能記住很少的特征組合管嬉。

為了達到 Generalization,文章引入新的小顆粒特征朗鸠,如類別特征(安裝了視頻類應用蚯撩,展示的是音樂類應用,等等)AND(user_installed_category=video, impression_category=music)烛占,這些高維稀疏的類別特征(如人口學特征和設備類別)映射為低緯稠密的向量后胎挎,與其他連續(xù)特征(用戶年齡沟启、應用安裝數(shù)等)拼接在一起,輸入 MLP 中犹菇,最后輸入邏輯輸出單元德迹。

Deep模型是一個前饋神經(jīng)網(wǎng)絡。深度神經(jīng)網(wǎng)絡模型通過需要的輸入是連續(xù)的稠密特征揭芍,對于稀疏胳搞、高維的類別特征,通常首先將其轉換為低維的向量称杨,這個過程也稱為embedding肌毅。

在訓練的時候首先隨機初始化embedding向量,并在模型的訓練過程中通過最小化損失函數(shù)來優(yōu)化模型姑原,逐漸修改該向量的值悬而,即將向量作為參數(shù)參與模型的訓練。

隱層的計算方法為:
a^{(l+1)}=f(W^{(l)}a^{(l)}+b^{(l)})
其中页衙,f為激活函數(shù)摊滔,如ReLus。
基于embedding的深度模型的輸入是類別特征(生成embedding)+連續(xù)特征店乐。

2.4、Wide&Deep模型聯(lián)合訓練

聯(lián)合訓練是指同時訓練Wide模型和Deep模型呻袭,并將兩個模型的輸出算log odds ratio然后加權求和作為最終的預測結果:
P(Y=1|x)=\sigma (w_{wide}^T[x,\phi(x)]+ w_{deep}^Ta^{(lf)}+b )

聯(lián)合訓練和embedding方法是不同的眨八。embedding把不同的部分分開訓練,這個過程中不同的模型相互之間不知道彼此的存在左电,也不會互相影響廉侧。但在聯(lián)合訓練中,整個網(wǎng)絡同時被訓練篓足,梯度的反向傳播同時影響整個模型的所有部分段誊,使用mini-batch SGD來訓練模型。

訓練方法:

3、apps的推薦系統(tǒng)

將上述Wide&Deep模型應用在Google play的apps推薦中檀夹。

3.1筋粗、 推薦系統(tǒng)

推薦系統(tǒng)的一般結構如下所示:


overview of RS

當一個用戶訪問App Store時,會產(chǎn)生一個請求炸渡,請求到達推薦系統(tǒng)后娜亿,推薦系統(tǒng)為該用戶返回推薦的apps列表。
在實際的推薦系統(tǒng)中蚌堵,通常將推薦的過程分為兩個部分买决,檢索系統(tǒng)(retrieval)和排序系統(tǒng)(ranking),retrieval從數(shù)據(jù)庫中檢索出與用戶相關的最匹配query的一些apps吼畏,這里的檢索通常會結合采用機器學習模型和人工定義規(guī)則兩種方法督赤。從大規(guī)模樣本中召回最佳候選集后,ranking負責對這些檢索出的apps打分泻蚊、排序躲舌,最終按照分數(shù)的高低返回相應的列表給用戶。分數(shù)P(y|x)性雄,y是用戶采取的行動(例如下載)没卸,x是特征包括:

  • user features:eg, country, language, demographics
  • contextual features: eg, device, hour of the day, day of the week
  • impression features: eg, app age, historical statistics of an app

WDL就是用在排序系統(tǒng)中。

3.2秒旋、apps推薦的特征

模型的訓練之前约计,最重要的工作是訓練數(shù)據(jù)的準備以及特征的選擇,在apps推薦中迁筛,可使用到的數(shù)據(jù)包括用戶數(shù)據(jù)和曝光數(shù)據(jù)煤蚌,因此,每一條樣本對應了一條曝光數(shù)據(jù)瑰煎。樣本點標簽為1表示安裝铺然,0表示未安裝。
對于類別特征酒甸,通過詞典(vocabularies)將其映射成向量魄健;對于連續(xù)的實值特征,將其歸一化到區(qū)間[0,1]插勤。


Wide&Deep 模型 for apps recommendation

3.3沽瘦、度量的標準

度量的指標有兩個革骨,分別針對在線的度量和離線的度量,在線時通過A/B test析恋,最終利用安裝率(acquisition)良哲;離線則使用AUC作為評價模型的指標。

4助隧、代碼介紹

數(shù)據(jù)源:UCI開源數(shù)據(jù)集Adult
針對美國某區(qū)域的一次人口普查結果筑凫,共32561條數(shù)據(jù),具體字段如下:


列名解釋
from __future__ import print_function

import tensorflow as tf
import tempfile
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings("ignore")

# Categorical base columns.構建低維離散特征
gender = tf.contrib.layers.sparse_column_with_keys(column_name="gender", keys=["Female", "Male"])
race = tf.contrib.layers.sparse_column_with_keys(column_name="race", keys=["Amer-Indian-Eskimo", "Asian-Pac-Islander", "Black", "Other", "White"])
education = tf.contrib.layers.sparse_column_with_hash_bucket("education", hash_bucket_size=1000)
relationship = tf.contrib.layers.sparse_column_with_hash_bucket("relationship", hash_bucket_size=100)
workclass = tf.contrib.layers.sparse_column_with_hash_bucket("workclass", hash_bucket_size=100)
occupation = tf.contrib.layers.sparse_column_with_hash_bucket("occupation", hash_bucket_size=1000)
native_country = tf.contrib.layers.sparse_column_with_hash_bucket("native_country", hash_bucket_size=1000)

# Continuous base columns.
age = tf.contrib.layers.real_valued_column("age")#構建連續(xù)型實數(shù)特征
age_buckets = tf.contrib.layers.bucketized_column(age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])
education_num = tf.contrib.layers.real_valued_column("education_num")
capital_gain = tf.contrib.layers.real_valued_column("capital_gain")
capital_loss = tf.contrib.layers.real_valued_column("capital_loss")
hours_per_week = tf.contrib.layers.real_valued_column("hours_per_week")

wide_columns = [
  gender, native_country, education, occupation, workclass, relationship, age_buckets,
#構建離散組合特征
  tf.contrib.layers.crossed_column([education, occupation], hash_bucket_size=int(1e4)),
  tf.contrib.layers.crossed_column([native_country, occupation], hash_bucket_size=int(1e4)),
  tf.contrib.layers.crossed_column([age_buckets, education, occupation], hash_bucket_size=int(1e6))]

deep_columns = [
#構建embedding特征
  tf.contrib.layers.embedding_column(workclass, dimension=8),
  tf.contrib.layers.embedding_column(education, dimension=8),
  tf.contrib.layers.embedding_column(gender, dimension=8),
  tf.contrib.layers.embedding_column(relationship, dimension=8),
  tf.contrib.layers.embedding_column(native_country, dimension=8),
  tf.contrib.layers.embedding_column(occupation, dimension=8),
  age, education_num, capital_gain, capital_loss, hours_per_week]

model_dir = tempfile.mkdtemp()
'''
定義分類模型
n_classes// 分類數(shù)目并村,默認是二分類巍实,>2進行多分類 ;weight_column_name // 訓練實例的權重哩牍;
linear_optimizer = tf.train.FtrlOptimizer( ...) // 線性模型權重更新的optimizer棚潦;
dnn_optimizer=tf.train.AdagradOptimizer( ...) // DNN模型權重更新的optimizer
'''
m = tf.contrib.learn.DNNLinearCombinedClassifier(
    model_dir=model_dir,#模型目錄
    linear_feature_columns=wide_columns,#輸入線性模型的feature columns
    dnn_feature_columns=deep_columns,#輸入DNN模型的feature columns
    dnn_hidden_units=[100, 50])#DNN模型隱層單元數(shù)

# Define the column names for the data sets.
COLUMNS = ["age", "workclass", "fnlwgt", "education", "education_num",
  "marital_status", "occupation", "relationship", "race", "gender",
  "capital_gain", "capital_loss", "hours_per_week", "native_country", "income_bracket"]
LABEL_COLUMN = 'label'
CATEGORICAL_COLUMNS = ["workclass", "education", "marital_status", "occupation",
                       "relationship", "race", "gender", "native_country"]
CONTINUOUS_COLUMNS = ["age", "education_num", "capital_gain", "capital_loss",
                      "hours_per_week"]

# Download the training and test data to temporary files.
# Alternatively, you can download them yourself and change train_file and
# test_file to your own paths.
#train_file = tempfile.NamedTemporaryFile()
#test_file = tempfile.NamedTemporaryFile()
#urllib.request.urlretrieve("http://mlr.cs.umass.edu/ml/machine-learning-databases/adult/adult.data", train_file.name)
#urllib.request.urlretrieve("http://mlr.cs.umass.edu/ml/machine-learning-databases/adult/adult.test", test_file.name)

# Read the training and test data sets into Pandas dataframe.
df_train = pd.read_csv('F:/method codes/adult.data.csv', names=COLUMNS, skipinitialspace=True)
df_test = pd.read_csv('F:/method codes/adult.test.csv', names=COLUMNS, skipinitialspace=True, skiprows=1)
df_train[LABEL_COLUMN] = (df_train['income_bracket'].apply(lambda x: '>50K' in x)).astype(int)
df_test[LABEL_COLUMN] = (df_test['income_bracket'].apply(lambda x: '>50K' in x)).astype(int)

def input_fn(df):#定義如何從輸入的dataframe構建特征和標記:
  # Creates a dictionary mapping from each continuous feature column name (k) to
  # the values of that column stored in a constant Tensor.
'''
tf.constant構建constant tensor,df[k].values是對應feature column的值構成的list
'''
  continuous_cols = {k: tf.constant(df[k].values)
                     for k in CONTINUOUS_COLUMNS}
  # Creates a dictionary mapping from each categorical feature column name (k)
  # to the values of that column stored in a tf.SparseTensor.
#tf.SparseTensor構建sparse tensor膝昆,SparseTensor由indices,values, dense_shape三個dense tensor構成丸边,
#indices中記錄非零元素在sparse tensor的位置,values是indices中每個位置的元素的值荚孵,dense_shape指定
#sparse tensor中每個維度的大小妹窖。
'''
以下代碼為每個category column構建一個[df[k].size,1]的二維的SparseTensor处窥。
'''
  categorical_cols = {k: tf.SparseTensor(
      indices=[[i, 0] for i in range(df[k].size)],
      values=df[k].values,
      dense_shape=[df[k].size, 1])
                      for k in CATEGORICAL_COLUMNS}
  # Merges the two dictionaries into one.
'''
用以下示意圖來表示以上代碼構建的sparse tensor label是一個 constant tensor嘱吗,記錄每個實例的 label
'''
  feature_cols = dict(list(continuous_cols.items()) + list(categorical_cols.items()))
# features是continuous_cols和categorical_cols的union構成的dict 
# dict中每個entry的key是feature column的name,value是feature column值的tensor
  # Converts the label column into a constant Tensor.
  label = tf.constant(df[LABEL_COLUMN].values)
  # Returns the feature columns and the label.
  return feature_cols, label

def train_input_fn():
  return input_fn(df_train)

def eval_input_fn():
  return input_fn(df_test)

print('df_train shape:',np.array(df_train).shape)
print('df_test shape:',np.array(df_test).shape)

m.fit(input_fn=train_input_fn, steps=200)#訓練模型
results = m.evaluate(input_fn=eval_input_fn, steps=1)#模型評測
for key in sorted(results):
    print("%s: %s" % (key, results[key]))
#https://blog.csdn.net/a819825294/article/details/71080472
#https://www.sohu.com/a/190148302_115128

參考資料:http://www.reibang.com/p/28a1849f6707
https://blog.csdn.net/google19890102/article/details/78171283

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末滔驾,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子俄讹,更是在濱河造成了極大的恐慌哆致,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件患膛,死亡現(xiàn)場離奇詭異摊阀,居然都是意外死亡,警方通過查閱死者的電腦和手機踪蹬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門胞此,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人跃捣,你說我怎么就攤上這事漱牵。” “怎么了疚漆?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵酣胀,是天一觀的道長刁赦。 經(jīng)常有香客問我,道長闻镶,這世上最難降的妖魔是什么甚脉? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮铆农,結果婚禮上牺氨,老公的妹妹穿的比我還像新娘。我一直安慰自己墩剖,他們只是感情好猴凹,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涛碑,像睡著了一般精堕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蒲障,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天歹篓,我揣著相機與錄音,去河邊找鬼揉阎。 笑死庄撮,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的毙籽。 我是一名探鬼主播洞斯,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼坑赡!你這毒婦竟也來了烙如?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤毅否,失蹤者是張志新(化名)和其女友劉穎亚铁,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體螟加,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡徘溢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了捆探。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片然爆。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖黍图,靈堂內(nèi)的尸體忽然破棺而出曾雕,到底是詐尸還是另有隱情,我是刑警寧澤雌隅,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布翻默,位于F島的核電站缸沃,受9級特大地震影響,放射性物質發(fā)生泄漏修械。R本人自食惡果不足惜趾牧,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肯污。 院中可真熱鬧翘单,春花似錦、人聲如沸蹦渣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柬唯。三九已至认臊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锄奢,已是汗流浹背失晴。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拘央,地道東北人涂屁。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像灰伟,于是被迫代替她去往敵國和親拆又。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容