作者的代碼可見:https://github.com/shenweichen/DeepCTR
本文分析的數(shù)據(jù)全部借用文章中的數(shù)據(jù)常摧。見
PART1 main函數(shù)
step1:讀取數(shù)據(jù),代碼中使用的是pandas的read_csv文件的讀取方法牡整。
step2:將離線型特征、連續(xù)型特征的特征名嘲叔、標(biāo)簽名分別放到不同的list中。
????????為了后續(xù)畫圖方便贵白,沒有將所有特征用到抚芦,只用了前兩個(gè)連續(xù)特征和前兩個(gè)離散特征
這樣實(shí)際上我們得到了三個(gè)list倍谜。如果我們用該代碼迈螟,使用的特征名和他給的特征名不一致的,我們可以直接顯示給定特征名尔崔。例如
sparse_features = ['C1', 'C2']
dense_features = ['I1', 'I2']
target = ['label']
step3:基礎(chǔ)特征工程
????????這也是在一般的機(jī)器學(xué)習(xí)中我們也會做的一個(gè)操作就是特征工程答毫,只不過深度學(xué)習(xí)的特征工程較機(jī)器學(xué)習(xí)簡單,一般我們不去人工挖掘一些交叉的特征您旁,而是交給embedding層去做處理烙常。
????????一般有哪些特征工程的方法,可以參考:https://www.zhihu.com/question/29316149
該論文中只做了兩種處理鹤盒,一是對離線型特征做LabelEncoder蚕脏,該方法是將特征中的文本信息轉(zhuǎn)成數(shù)字。例如:
from pandas import DataFrame
from sklearn.preprocessing import LabelEncoder
import pandas as pd
data = {'水果':['蘋果','梨','草莓'],
'數(shù)量':[3,2,5],
'價(jià)格':[10,9,8]}
#根據(jù)dict創(chuàng)建dataframe
df = DataFrame(data)
lbe = LabelEncoder()
df['水果'] = lbe.fit_transform(data['水果'])
????????可以看到蘋果被重新編碼為1侦锯,梨重新編碼為0驼鞭,草莓重新編碼為2。若使用的時(shí)候尺碰,自己的數(shù)據(jù)集中的數(shù)據(jù)本身就是數(shù)字型的挣棕,其實(shí)這步就可以省略。
????????對于連續(xù)型特征亲桥,由于涉及到線性運(yùn)算洛心,若某一維度的值特別大后,就會導(dǎo)致該特征對模型整體影響偏高题篷。所以為了消除這種影響词身,一般會進(jìn)行歸一化處理。本文采用的min-max歸一化番枚。
mms = MinMaxScaler(feature_range=(0, 1))
data[dense_features] = mms.fit_transform(data[dense_features])
將每列的特征壓縮在0-1范圍之類法严。當(dāng)前還有0均值1方差的歸一化處理方式。
step4:數(shù)據(jù)處理(格式轉(zhuǎn)換)
sparseFeat = [SparseFeat(feat, vocabulary_size=data[feat].nunique(), embedding_dim=4) for feat in sparse_features]
denseFeat = [DenseFeat(feat, 1,) for feat in dense_features]
#其中vocabulary_size表示的是詞典的詞大小葫笼,即離散變量它的取值有多少個(gè)深啤。
#例如對于性別這個(gè)離散特征,它的取值有'female', 'male'和未識別路星。則有三種可取值溯街。
????????其中nunique()可以求dataframe的某列中不同元素的個(gè)數(shù)。用該方法就可以求出離散特征可取值的個(gè)數(shù)奥额。還是對之前的蘋果和梨的例子苫幢。可以看到
print(df['水果'].nunique()) #結(jié)果為3
其實(shí)sparseFeat 和denseFeat 只是將之前的特征名封裝成了一個(gè)類垫挨。對于離散型特征C1韩肝,要指定該特征可取值的個(gè)數(shù),即vocabulary_size九榔,還有embedding_dim即經(jīng)過embedding后希望得到的維度哀峻。對于embedding_dim其實(shí)有個(gè)疑問:
if embedding_dim == "auto":
embedding_dim = 6 * int(pow(vocabulary_size, 0.25))
????????對于embedding后的維度涡相,用戶可以通過顯式指定embedding_dim,也可以設(shè)置為auto剩蟀,這樣會根據(jù)上述的公式得到維度催蝗。也就是詞典的大小開兩次根號后的六倍。但是為什么這么處理呢育特?
PART2 DeepFM方法
????????為了直觀地看出代碼中的模型整個(gè)框架丙号,先用C1、C2缰冤、I1犬缨、I2兩個(gè)連續(xù)型特征和兩個(gè)離散型特征。使用如下代碼
plot_model(model, to_file='model_deepfm.png', show_shapes=True,show_layer_names=True)
畫出模型框架圖棉浸,結(jié)果如下:
????????可以看出怀薛,整個(gè)框架分為三個(gè)部分,F(xiàn)M迷郑,DNN枝恋,Linear三個(gè)部分。為了更好了理解每一個(gè)部分嗡害,我們拆分來理解焚碌。我們先看一下DeepFM的輸入
????????首先理解FM模型,該模型可以理解為是在LR模型基礎(chǔ)上的一種改進(jìn)吧霸妹。因?yàn)榫€性模型僅考慮特征的線性組合呐能,但是有時(shí)候特征與特征之間也存在一定的關(guān)系,由此應(yīng)運(yùn)出該模型抑堡。首先推導(dǎo)出模型表達(dá)式
這樣就推導(dǎo)出了FM的計(jì)算公式,簡記為和平方-平方和朗徊。接著我們看代碼中對應(yīng)FM模型的部分
fm_logit = add_func([FM()(concat_func(v, axis=1))
for k, v in group_embedding_dict.items() if k in fm_group])
首先得理解group_embedding_dict到底是什么首妖?其對應(yīng)的代碼如下:
def input_from_feature_columns(features, feature_columns, l2_reg, init_std, seed, prefix='', seq_mask_zero=True,
support_dense=True, support_group=False):
sparse_feature_columns = list(
filter(lambda x: isinstance(x, SparseFeat), feature_columns)) if feature_columns else []
varlen_sparse_feature_columns = list(
filter(lambda x: isinstance(x, VarLenSparseFeat), feature_columns)) if feature_columns else []
embedding_matrix_dict = create_embedding_matrix(feature_columns, l2_reg, init_std, seed, prefix=prefix,
seq_mask_zero=seq_mask_zero)
group_sparse_embedding_dict = embedding_lookup(embedding_matrix_dict, features, sparse_feature_columns)
dense_value_list = get_dense_input(features, feature_columns)
if not support_dense and len(dense_value_list) > 0:
raise ValueError("DenseFeat is not supported in dnn_feature_columns")
sequence_embed_dict = varlen_embedding_lookup(embedding_matrix_dict, features, varlen_sparse_feature_columns)
group_varlen_sparse_embedding_dict = get_varlen_pooling_list(sequence_embed_dict, features,
varlen_sparse_feature_columns)
group_embedding_dict = mergeDict(group_sparse_embedding_dict, group_varlen_sparse_embedding_dict)
if not support_group:
group_embedding_dict = list(chain.from_iterable(group_embedding_dict.values()))
return group_embedding_dict, dense_value_list