沒錯,這次登場的是FFM。各大比賽中的「種子」算法,中國臺灣大學Yu-Chin Juan榮譽出品,美團技術團隊背書,Michael Jahrer的論文的field概念靈魂升華,土豪公司鑑別神器。通過引入field的概念,FFM把相同性質的特徵歸於同一個field,相當於把FM中已經細分的feature再次拆分,可不可怕,厲不厲害?好,讓我們來看看怎麼一個厲害法。
網上已經說爛了的美團技術團隊給出的那張圖:
針對Country這個變量,
FM的做法是one-hot-encoding,生成country_USA,country_China兩個稀疏的變量,再進行embedding向量化。
FFM的做法是cross-one-hot-encoding,生成country_USA_Day_26/11/15,country_USA_Ad_type_Movie...M個變量,再進行embedding向量化。
就和上圖一樣,fm做出來的latent factor是二維的,就是給每個特徵一個embedding結果;而暴力的ffm做出的latent factor是三維的,出來給特徵embedding還考慮不同維度特徵給不同的embedding結果,也是FFM中「field-aware」的由來。
同時從公式中看,對於xi這個特徵為什麼embedding的latent factor向量的V是Vifj,其實就是因為xi乘以的是xj,所以latent factor向量的信息提取才是field j,也就是fj。多說一句,網上很多給出的現成的代碼,這邊都是寫錯了的,都寫著寫著變成了vifi,可能是寫的順手。
都說到這裡了,我再多說一句,為什麼說ffm是土豪公司鑑別神器呢?我們看下僅僅是二次項,ffm需要計算的參數有 nfk 個,遠多於FM模型的 nk個,而且由於每次計算都依賴於乘以的xj的field,所以,無法用fm的那個計算技巧(ab = 1/2(a+b)2-a2-b^2),所以計算複雜度是 O(kn^2)。這種情況下,沒有GPU就不要想了,有GPU的特徵多於50個,而且又很離散的,沒有個三位數的GPU也算了。之前我看美團說他們在用,我想再去看看他們的實際用的過程的時候,發現文章被刪了,真的是可惜,我其實一直也想知道如何減輕這個變態的計算量的方法。
給個實例給大家看下以上的這些的應用:
依舊來自美團技術研發團隊中給出的案例,有用戶數據如下:
這條記錄可以編碼成5個特徵,其中「Genre=Comedy」和「Genre=Drama」屬於同一個field,「Price」是數值型,不用One-Hot編碼轉換。為了方便說明FFM的樣本格式,我們將所有的特徵和對應的field映射成整數編號。
紅色部分對應的是field,來自於原始特徵的個數;藍色部分對應的是feature,來自於原始特徵onehot之後的個數。
對於特徵Feature:User=YuChin而言,有Movie=3Idiots、Genre=Comedy、Genre=Drama、Price四項要交互:
User=YuChin與Movie=3Idiots交互是<V1,2,V2,1>·1·1,也就是第一項,為什麼是V1,2呢?因為User=YuChin是Featureindex=1,而交互的Movie=3Idiots是Fieldindex=2,同理V2,1也是這樣的,以此類推,那麼,FFM的組合特徵有10項,如下圖所示:
這就是一個案例的實際操作過程。
為什麼要把這個單拎出來說呢?我看了網上不少的對於特徵的處理過程,版本實在是太多了,而且差異化也蠻大,這邊就和大家一起梳理一下:
1.feature index * feature value
這個就是上面我這個實際案例的方式,對於分類變量採取onehot,對於連續變量之間進行值的點積,不做處理。優點是快速簡單,不需要預處理,但是缺點也很明顯,離群點影響,值的波動大等。
2.連續值離散化
這個方法借鑑了Cart裡面對連續值的處理方式,就是把所有的連續值都當成一個分類變量處理。舉例,現在有一個年齡age的連續變量[10,19,20,22,22,34],這種方法就生成了age_10,age_19,age_20,age_22,age_34這些變量,如果連續值一多,這個方法帶來的計算量就直線上升。
3.分箱下的連續值離散化
這種方法優化了第二種方法,舉例解釋,現在有一個年齡age的連續變量[10,19,20,22,22,34],我們先建立一個map,[0,10):0,[10,20):1,[20,30):2,[30,100):3。原始的數據就變成了[1,1,2,2,2,3],再進行2的連續值離散化方法,生成了age_1,age_2,age_3這幾個變量,優化了計算量,而且使得結果更具有解釋性。
這個是官方指定的方法,是-1/1做二分類的時候常用的loss計算方法:
這邊需要注意的是,在做的時候,需要把label拆分成-1/1而不是0/1,當我們預測正確的時候,predlabel>0且越大正確的程度越高,相應的log項是越小的,整體loss越小;相反,如果我們預測的越離譜,predlabel<0且越小離譜的程度越高,相應的log項是越大的,整體loss越大。
我看到很多人的實現依舊用了tf.nn.softmax_cross_entropy_with_logits,其實就是多分類中的損失函數,和大家平時的圖像分類、商品推薦召回一模一樣:
這邊需要注意的是,在做的時候,需要把label拆分成[1,0]和[0,1]進行計算。不得不說,大家真的是為了省事很機智(喪心病狂)啊!
我這邊只給一些關鍵地方的代碼,更多的去GitHub裡面看吧。
1self.v = tf.get_variable('v', shape=[self.p, self.f, self.k], dtype='float32',initializer=tf.truncated_normal_initializer(mean=0, stddev=0.01))
看到了,這邊生成的v就是上面Vffm的形式。
1for i in range(self.p):
2 # 尋找沒有match過的特徵,也就是論文中的j = i+1開始
3 for j in range(i + 1, self.p):
4 print('i:%s,j:%s' % (i, j))
5 # vifj
6 vifj = self.v[i, self.feature2field[j]]
7 # vjfi
8 vjfi = self.v[j, self.feature2field[I]]
9 # vi · vj
10 vivj = tf.reduce_sum(tf.multiply(vifj, vjfi))
11 # xi · xj
12 xixj = tf.multiply(self.X[:, i], self.X[:, j])
13 self.field_cross_interaction += tf.multiply(vivj, xixj)
我這邊強行拆開了寫,這樣看起來更清晰一點,注意這邊的vifj和vjfi的生成,這邊也可以看到找對於的field的方法是用了filed這個字典,這就是為什麼不能用fm的點擊技巧。
1# -1/1情況下的logistic loss
2self.loss = tf.reduce_mean(tf.log(1 + tf.exp(-self.y * self.y_out)))
這邊記得論文中的負號,如果有batch的情況下記得求個平均再進行bp過程。
原始的ffm論文中給出了一些結論,我們在實際使用中值得參考:
11. Split the data set into a training set and a validation set.
22. At the end of each epoch, use the validation set to calcu-
3late the loss.
43. If the loss goes up, record the number of epochs. Stop or
5go to step 4.
64. If needed, use the full data set to re-train a model with
7the number of epochs obtained in step 3.
FFM是一個細化隱向量非常好的方法,雖然很簡單,但還是有很多細節之處值得考慮,比如如何線上應用,如何可解釋,如何求稀疏解等等。在部署實現FFM之前,我還是建議大家先上線FM,當效果真的走投無路的時候再考慮FFM,FFM在工業界的影響著實不如學術界那麼強大,偷偷說一句,太慢了,真的是太慢了,慢死了,我寧可去用deepfm。
最後,給出代碼實現的Github地址FFM,這邊是我自己寫的,理解理解算法可以,但是實際用的時候建議參考FFM的實現比較好的項目比如libffm,最近比較火的xlearn。
原文連結:https://www.jianshu.com/p/8b57473e385a
想要了解更多資訊,請掃描下方二維碼,關注機器學習研究會
轉自: 人工智慧LeadAI