1.FM背景與簡介
FM主要是爲了解決稀疏數據下的特徵組合問題。2010年,日本大阪大學(Osaka University)的 Steffen Rendle 在矩陣分解(MF)、SVD++[2]、PITF[3]、FPMC[4] 等基礎之上,歸納出針對高維稀疏數據的因子機(Factorization Machine, FM)模型[13]。因子機模型可以將上述模型全部納入一個統一的框架進行分析[1]。目前,FM被廣泛的應用於廣告預估模型中,相比LR而言,效果強了不少。
1.1 稀疏數據
高維的稀疏矩陣是工程中常見的問題,比如下圖:
圖中每一個樣本對應多個屬性,分別是User,Movie,Other Movies rated,Time,Last Movie rated,由於這些屬性都是categorical類型,所以一般進行One-hot編碼轉換爲數值類型,但是由於每個屬性都有多個離散的取值,所以One-hot編碼之後樣本空間相比原來變大了許多,而特徵矩陣也會變得非常稀疏。假設有10000部電影,有10000個用戶,單看前兩條,每個樣本的二階交叉特徵維度就是一億維,但是每個特徵中只有兩維取值不爲0,非常稀疏。在CTR/CVR預測時,One-Hot編碼常會導致樣本的稀疏性,樣本的稀疏性是實際問題中不可避免的挑戰。
同時通過觀察大量的樣本數據可以發現,某些特徵經過關聯之後,與label之間的相關性就會提高。如:“USA”與“Thanksgiving”、“China”與“Chinese New Year”這樣的關聯特徵,對用戶的點擊有着正向的影響。換句話說,來自“China”的用戶很可能會在“Chinese New Year”有大量的瀏覽、購買行爲,而在“Thanksgiving”卻不會有特別的消費行爲。這種關聯特徵與label的正向相關性在實際問題中是普遍存在的,如“化妝品”類商品與“女”性,“球類運動配件”的商品與“男”性,“電影票”的商品與“電影”品類偏好等。[5]。
顯然,不同特徵之間是有關聯的,表示特徵之間的關聯,一種直接的方法是構建組合特徵[5]。那麼怎麼組合特徵呢?
1.2 FM模型
SVM就曾通過多項式核函數來實現特徵的交叉。實際上,多項式是構建組合特徵的一種非常直觀的模型。我們先看一下二階多項式的建模:
外層求和i實際上只取到了n-1,顯然組合特徵有n(n-1)/2個,如果特徵有萬級別,那麼參數就有億級別,這種計算量是很大的。
而且在稀疏性普遍存在的應用場景種,二次項參數的訓練非常困難。以爲每一個wij在訓練時都要要求xi,xj不爲零,否則梯度下降時梯度恆爲零,而實際上xi,xj同時非零的情況非常少見,這會導致大部分參數無法學習,模型的性能也可想而知。那麼如何解決二次項參數的訓練問題呢?
矩陣分解提供了思路,矩陣分解吧rating矩陣分爲user矩陣和item矩陣的乘積來進行低秩近似,這裏FM引入輔助向量V來表示W:
這樣有什麼好處呢?[6]中的解釋我覺得非常好:
2.FM模型求解
目前FM模型的求解方法主要有以下三種[12]:
隨機梯度下降法(Stochastic Gradient Descent, SGD)
交替最小二乘法(Alternating Least Square Method,ALS)
馬爾科夫鏈蒙特卡羅法(Markov Chain Monte Carlo,MCMC)
FM論文裏給出了SGD求解方法,這了直接把論文裏的求解粘貼過來,然後步步解讀:
第一步到第二步根據這個公式理解:
這樣理解起來不太直觀,可以把第一步的式子看成是對矩陣上三角元素的求和,結果就是二分之一矩陣所有元素減去對角線元素的和。
第二步到第三部是直接展開。第三步到第四步我寫了個詳細的過程:
其實就是利用了多重求和的一個基本公式:
引入輔助向量V之前,計算複雜度爲O(n2),引入V之後,爲O(kn2),而通過2.1中的交叉項求解,我們知道,計算複雜度爲O(kn)。一般來說k遠小於n,我們可以FM可以在線性時間對新樣本進行預測。
之後就可以用梯度下降法求解了,FM模型的梯度爲:
3. FM優缺點分析
跑完了代碼,有興趣再來看看優缺點吧。
優點
1)FM模型可以在非常稀疏的數據中進行參數估計;
2)FM模型擁有線性的時間複雜度,可以在億級別的大數據;
3)FM模型是一個通用模型,可以用在任何特徵值爲實值的情況下;
4)FM模型可以將上千萬維的特徵壓縮爲幾百維甚至幾十維,極大地減少了模型的參數,參數大大減少,模型的泛化能力也大大增強。
缺點
引用下知乎用戶“屈偉”的說法[11]:
4.期待已久的tensorflow實戰
數據處理
首先來看數據處理函數:
def vectorize_dic(dic, ix=None, p=None):
if(ix == None):
d = count(0)
ix = defaultdict(lambda:next(d))
n = len(list(dic.values())[0])
g = len(list(dic.keys()))
nz = n*g
col_ix = np.empty(nz, dtype=int)
i = 0
for k, lis in dic.items():
col_ix[i::g] = [ix[str(k)+str(el)] for el in lis]
i += 1
row_ix = np.repeat(np.arange(n), g)
data = np.ones(nz);print('data.shape ', data.shape)
if(p == None):
p = len(ix)
ixx = np.where(col_ix < p)
return csr.csr_matrix(
(data[ixx], (row_ix[ixx], col_ix[ixx])), shape=(n, p)), ix
這份代碼裏最難理解的就是這個函數,因爲裏面的小知識點較多。但是我們把握核心就行了,就是這個csr.csr_matrix。由於數據過於稀疏,只需要存儲有用的信息(非零值)即可。csr_matrix提供了兩種不錯的表示方法。
第一種:
csr_matrix((data, (row_ind, col_ind)), [shape=(M, N)])
其中row_ind和col_ind代表data中數據所在的行和列,就這麼簡單。這個也是程序中用到的方法。
第二種:
csr_matrix((data, indices, indptr), [shape=(M, N)])
indicies代表列,indptr的長度爲行數加1,其中的第i個元素代表前i-1行中的data數據個數,那麼第n+1個元素就是是len(data),即data中的所有數據。我們根據indptr可以確定data中每個元素所在的行數,根據indices確定每個元素的列數,那麼矩陣就可以唯一確定了。這種表示方法推薦看[10]中的解讀。
瞭解csr_matrix之後,我們就要關注row_ind和col_ix了。樣本數爲n,屬性數爲g,那麼row_ind表示爲(0,0,…0,1,1,…1,…,n-1,n-1,…,n-1)也就不難理解,每g個元素表示一個樣本的行。那麼col_ix也就很明顯了,就是樣本的各屬性取值再one-hot編碼向量中的位置。
這個函數我推薦大家把每個變量打印出來看看,就知道是什麼意思了。博主在原來代碼的基礎上,加了一些調試代碼(就是打印出關鍵變量)方便大家理解代碼。覺得這個函數不好理解的可以試試。
靜態圖定義
首先看一下各種變量定義:
n, p = x_train.shape
# number of latent factors
k = 10
# design features of users
X = tf.placeholder('float', shape=[None, p])
# target vector
Y = tf.placeholder('float', shape=[None, 1])
# bias and weights
w0 = tf.Variable(tf.zeros([1]))
W = tf.Variable(tf.zeros([p]))
# matrix factorization factors, randomly initialized
V = tf.Variable(tf.random_normal([k, p], stddev=0.01))
# estimation of y, initialized to 0
Y_hat = tf.Variable(tf.zeros([n, 1]))
W這裏的定義可以當成行向量來理解,看看Y_hat就知道列向量怎麼定義了。
然後定義損失函數和優化器:
# calculate output with FM equation
linear_terms = tf.add(w0, tf.reduce_sum(tf.multiply(W, X), 1, keepdims=True))
pair_interactions = tf.multiply(0.5,
tf.reduce_sum(
tf.subtract(
tf.pow(tf.matmul(X,tf.transpose(V)), 2),
tf.matmul(tf.pow(X, 2), tf.pow(tf.transpose(V), 2))),
1, keepdims=True))
Y_hat = tf.add(linear_terms, pair_interactions)
lambda_w = tf.constant(0.001, name='lambda_w')
lambda_v = tf.constant(0.001, name='lambda_v')
l2_norm = tf.reduce_sum(tf.multiply(lambda_w, tf.pow(W,2)))+tf.reduce_sum(tf.multiply(lambda_v, tf.pow(V,2)))
error = tf.reduce_mean(tf.square(tf.subtract(Y, Y_hat)))
loss = tf.add(error, l2_norm)
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01).minimize(loss)
原作者對l2_norm的處理我覺得有些問題,做了些改進。問題不大。
這裏對矩陣的處理推薦大家看着代碼自己嘗試一下,不然下次自己可能寫不出來。關鍵是要時刻注意各種變量的shape。
訓練和測試
# In[77]: train process
epoches = 2
batch_size = 100
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
for epoch in tqdm(range(epoches), unit='epoch'):
perm = np.random.permutation(x_train.shape[0])
for bx, by in batcher(x_train[perm], y_train[perm], batch_size):
sess.run(optimizer, feed_dict={
X:bx.reshape(-1, p),
Y:by.reshape(-1, 1)
})
# In[81]: test process
errors = []
for bx, by in batcher(x_test, y_test):
errors.append(sess.run(error, feed_dict={
X:bx.reshape(-1, p),
Y:by.reshape(-1, 1)
}))
RMSE = np.sqrt(np.array(errors).mean())
print(RMSE)
sess.close()
這裏使用tqdm可以再jupyter notebook上實時顯示訓練進度。
本文參考python2代碼:
https://github.com/babakx/fm_tensorflow/blob/master/fm_tensorflow.ipynb完整代碼網址:
https://github.com/wyl6/Recommender-Systems-Samples/tree/master/RecSys%20Traditional/MF/FM
參考
[1] https://tracholar.github.io/machine-learning/2017/03/10/factorization-machine.html
[2] S. Rendle and L. Schmidt-Thieme, “Pairwise interaction tensor factorization for personalized tag recommendation,” in WSDM ’10: Proceedings of the third ACM international conference on Web search and data mining. New York, NY, USA: ACM, 2010, pp. 81–90.
[3] S. Rendle, C. Freudenthaler, and L. Schmidt-Thieme, “Factorizing personalized markov chains for next-basket recommendation,” in WWW ’10: Proceedings of the 19th international conference on World wide web. New York, NY, USA: ACM, 2010, pp. 811–820.
[4]: A two-stage ensemble of diverse models for advertisement ranking in KDD Cup 2012[C]//KDDCup. 2012.
[5] https://plushunter.github.io/2017/07/13/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E7%AE%97%E6%B3%95%E7%B3%BB%E5%88%97%EF%BC%8826%EF%BC%89%EF%BC%9A%E5%9B%A0%E5%AD%90%E5%88%86%E8%A7%A3%E6%9C%BA%EF%BC%88FM%EF%BC%89%E4%B8%8E%E5%9C%BA%E6%84%9F%E7%9F%A5%E5%88%86%E8%A7%A3%E6%9C%BA%EF%BC%88FFM%EF%BC%89/
[6] https://zhuanlan.zhihu.com/p/37963267
[7] https://zhuanlan.zhihu.com/p/35753471
[8] https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csr_matrix.html
[9] https://blog.csdn.net/u012871493/article/details/51593451
[10] https://blog.csdn.net/u012871493/article/details/51593451
[11] https://www.zhihu.com/question/62109451
[12] https://zhuanlan.zhihu.com/p/61096338
[13] Rendle, S. (2010, December). Factorization machines. In 2010 IEEE International Conference on Data Mining (pp. 995-1000). IEEE.
公衆號
更多精彩內容請移步公衆號:推薦算法工程師
感覺公衆號內容不錯點個關注唄