1奈籽、FM回顧
1.1 引言
FM是一種監(jiān)督學習痛悯,在線性單特征模型中加入了二階特征交互鸵鸥,但是兩兩特征交互有個明顯的缺點就是所有交互特征的權重都是相同的嵌巷,顯而易見萄凤,不同特征的重要性是不同的,比如女性-籃球和男性-籃球搪哪,其交互的權重肯定是不一樣的靡努,顯然FM模型并沒有考慮到這一點。
因此在AFM模型中引入了交叉項的權重晓折,不同的特征交叉項惑朦,由于影響的效果不同,所以這個權重也不同漓概,文章中把這個權重稱為注意力權重漾月。但是AFM有個很大的不足就是參數過于多,計算量非常大胃珍。
1.2 FM公式
其二階second-order項簡化公式為:
由公式可以看出梁肿,若embedding_size也就是最外層的求和不考慮的話,那二階交叉項得到的就是一個k維的矩陣觅彰,這個k維的矩陣輸入DNN中就可以進行模型的變換吩蔑,這個矩陣下邊就記為[k]。
模型 | 結構 | 與DNN結合 |
---|---|---|
DeepFM | 并行結構 | [k]直接輸入DNN中填抬,與FM公式進行concat烛芬,相當于FM二階后再加入高階特征 |
FNN | 串行結構 | 非end-to-end模型,把一次項二次項權重w,v預訓練好赘娄,而后輸入DNN中 |
FNM | 串行結構 | [k]直接輸入DNN中仆潮,替換FM公式中的二階交互項 |
AFM | 串行結構 | 在二次項交互權重之上再加入了注意力權重系數,替換原始的二次項 |
2 AFM結構
2.1 AFM原理
因為二階交互項的重要性不同,因此在交互項之前加入了一個注意力因子擅憔,這個注意力因子就表示交互項的重要程度鸵闪。這個就是Attention機制
FM的二階項表達:
AFM的二次項表達
2.2 AFM結構
上圖就是AFM二次交叉項的結構圖,其忽略了一次項和偏置的部分暑诸,前三部分就是FM的二次項部分蚌讼,后邊加入的注意力網絡attention-based pooling才是本文的創(chuàng)新和核心,文中加入一個attention net生成注意力權重參數个榕,加入二次項交叉項之前篡石,而且直接對二次項特征維度進行累加,生成加權和西采。
2.2.1
attention net網絡的表示公式凰萨,求得其交互項的注意力權重,因為權重系數表示的是交叉項的相關程度械馆,因此a的系數表示越大表示其相關程度越高胖眷,a系數的綜合為1,因此設計一個注意力機制網絡霹崎,而后通過softmax函數珊搀,求出系數的值。
得到注意力權重后尾菇, 對FM的表達式進行改寫為:
其中權重,,,境析,k為embedding后的向量的維度,t為attention network的隱藏維度派诬。
由公式可以看出劳淆,AFM在FM的基礎上只是加入了注意力機制,并沒有使用DNN的網絡結構進行非線性的學習其他高階特征默赂。
3沛鸵、實驗代碼
實驗過程中還是使用了自己的數據集,特征數量feature_size將近3w缆八,field_size為69谒臼,因此其交互項為69*68/2=2346個。根據之前NFM的代碼進行改寫耀里,loss函數為MSE,使用Adam作為優(yōu)化拾氓,同樣采用了RMSE進行評分預測:
3.1數據預處理
def get_feature_dict(df,num_col):
'''
特征向量字典冯挎,其格式為{field:{特征:編號}}
:param df:
:return: {field:{特征:編號}}
'''
feature_dict={}
total_feature=0
df.drop('rate',axis=1,inplace=True)
for col in df.columns:
if col in num_col:
feature_dict[col]=total_feature
total_feature += 1
else:
unique_feature = df[col].unique()
feature_dict[col]=dict(zip(unique_feature,range(total_feature,total_feature+len(unique_feature))))
total_feature += len(unique_feature)
return feature_dict,total_feature
def get_data(df,feature_dict):
'''
:param df:
:return:
'''
y = df[['rate']].values
dd = df.drop('rate',axis=1)
df_index = dd.copy()
df_value = dd.copy()
for col in df_index.columns:
if col in num_col:
df_index[col] = feature_dict[col]
else:
df_index[col] = df_index[col].map(feature_dict[col])
df_value[col] = 1.0
xi=df_index.values.tolist()
xv=df_value.values.tolist()
return xi,xv,y
3.2 AFM模型
3.2.1 設置權重初始化
權重的設置分為FM部分權重和attention部分的權重
#FM權重
w_0 = tf.Variable(tf.constant(0.1),name='bias')
w = tf.Variable(tf.random.normal([feature_size, 1], mean=0, stddev=0.01),name='first_weight')
v = tf.Variable(tf.random.normal([feature_size, embedding_size], mean=0, stddev=0.01),name='second_weight')
3.2.2 embedding層和placeholder的設置
feat_index = tf.compat.v1.placeholder(tf.int32,[None,None],name='feat_index')
feat_value = tf.compat.v1.placeholder(tf.float32,[None,None],name='feat_value')
label = tf.compat.v1.placeholder(tf.float32,shape=[None,1],name='label')
embedding_first =tf.nn.embedding_lookup(w,feat_index) #None*F *1 F是field_size大小,也就是不同域的個數
embedding = tf.nn.embedding_lookup(v,feat_index) #None * F * embedding_size
feat_val = tf.reshape(feat_value,[-1,field_size,1])
#vx的計算
embedding_vx = tf.multiply(embedding_first,feat_val)
3.2.2 模型的設置
1)線性部分
y_first_order= tf.reduce_sum(embedding_vx,2) # None*F
y_first_order_num = tf.reduce_sum(y_first_order,1,keepdims=True) # None*1
liner = tf.add(y_first_order_num, w_0) # None*1
2)attention部分
(a)attention權重初始化(K為embedding_size,A為attention_size)
公式中:
W的權重維度為 K * A
b的權重維度為 1* K
h的權重維度為 1* K
p的權重維度為 1* K
#Attention部分的權重
weights={}
glorot = np.sqrt(2.0 / (attention_size + embedding_size))
weights['attention_w'] = tf.Variable(
np.random.normal(loc=0, scale=glorot, size=(embedding_size,attention_size)), dtype=np.float32
)
weights['attention_b'] = tf.Variable(
np.random.normal(loc=0, scale=glorot, size=(attention_size,)), dtype=np.float32
)
weights['attention_h'] = tf.Variable(
np.random.normal(loc=0, scale=glorot, size=(attention_size,)), dtype=np.float32
)
weights['attention_p'] = tf.Variable(
np.random.normal(loc=0, scale=glorot, size=(embedding_size,)), dtype=np.float32
)
(b) Attention Net
按照公式來計算注意力權重
為了得到每一個兩兩交叉項的向量,也就是求得
交叉項的embedding房官,我們寫了個循環(huán)趾徽,如下邊代碼,通過stack使之變成一個張量翰守,而后通過第一維和第二維的轉置得到一個None* F(F-1)/2* K維的張量孵奶。 F(F-1)/2* K表示一個樣本所有交互項的交互矩陣。
# 交互項的矩陣 #None*F(F-1)/2*k
attention_weight_list=[]
for i in range(field_size):
for j in range(i+1,field_size):
attention_weight_list.append(tf.multiply(embedding_vx[:,i,:],embedding_vx[:,j,:]))
attention_cross = tf.stack(attention_weight_list)
attention_cross = tf.transpose(attention_cross,perm=[1,0,2],name='transpose') #None*F(F-1)/2*k
得到了attention_cross交互矩陣后蜡峰,根據公式:
計算權重系數了袁,使用softmax把權重系數總和限制為1,其最后輸出矩陣大小為None* F(F-1)/2* 1
# 可以帶入權重attention
attention_weight = tf.add(tf.matmul(attention_cross,weights['attention_w']),weights['attention_b']) #None*F(F-1)/2*A
attention_weight_a = tf.reduce_sum(tf.multiply(tf.nn.relu(attention_weight),weights['attention_h']),axis=2) #None*F(F-1)/2
attention_weight_out = tf.reshape(softmax(attention_weight_a),[-1,int(field_size*(field_size-1)/2),1]) #None*F(F-1)/2*1
(softmax的函數)
def softmax(x):
if len(x.shape) > 1:
fenzi_exp= tf.exp(x - tf.reduce_max(x))
fenmu = 1.0/tf.reduce_sum(fenzi_exp,axis=1,keepdims=True)
result = fenzi_exp * fenmu
else:
result = tf.exp(x - tf.reduce_max(x))/tf.reduce_sum(tf.exp(x - tf.reduce_max(x)))
return result
(c)二次項
#二階second_order的輸出
#所有交叉項相加
atten_add = tf.reduce_sum(tf.multiply(attention_cross,attention_weight_out),axis=1) #N*K
second_order = tf.reduce_sum(tf.multiply(atten_add,weights['attention_p']),axis=1,keepdims=True) #N*1
3)輸出和優(yōu)化
out = tf.add(liner,second_order) #N*1
#loss
loss = tf.nn.l2_loss(tf.subtract(out,label))
optimizer = tf.compat.v1.train.AdamOptimizer(lr,beta1=0.9,beta2=0.999,epsilon=1e-8).minimize(loss)
完整代碼見:https://github.com/garfieldsun/recsys/tree/master/AFM
參考博客:
1湿颅、論文
2载绿、http://www.reibang.com/p/83d3b2a1e55d