轉自:
博客
http://blog.csdn.net/google19890102/article/details/45532745/
github
https://github.com/zhaozhiyong19890102/Python-Machine-Learning-Algorithm/tree/master/Chapter_3%20Factorization%20Machine
一、因子分解機FM的模型
因子分解機(Factorization Machine, FM)是由Steffen Rendle提出的一種基於矩陣分解的機器學習算法。
1、因子分解機FM的優勢

對於因子分解機FM來說,最大的特點是對於稀疏的數據具有很好的學習能力。現實中稀疏的數據很多,例如作者所舉的推薦系統的例子便是一個很直觀的具有稀疏特點的例子。
2、因子分解機FM的模型
對於度為2的因子分解機FM的模型為:

其中,參數
,
,
。
表示的是兩個大小為
的向量
和向量
的點積:








其中,
表示的是系數矩陣
的第
維向量,且
,
稱為超參數。在因子分解機FM模型中,前面兩部分是傳統的線性模型,最后一部分將兩個互異特征分量之間的相互關系考慮進來。





因子分解機FM也可以推廣到高階的形式,即將更多互異特征分量之間的相互關系考慮進來。
二、因子分解機FM算法
因子分解機FM算法可以處理如下三類問題:
- 回歸問題(Regression)
- 二分類問題(Binary Classification)
- 排序(Ranking)
1、回歸問題(Regression)
在回歸問題中,直接使用
作為最終的預測結果。在回歸問題中使用最小均方誤差(the least square error)作為優化的標准,即


其中,
表示樣本的個數。

2、二分類問題(Binary Classification)
與Logistic回歸類似,通過階躍函數,如Sigmoid函數,將
映射成不同的類別。在二分類問題中使用logit loss作為優化的標准,即


其中,
表示的是階躍函數Sigmoid。具體形式為:


三、因子分解機FM算法的求解過程
1、交叉項系數
在基本線性回歸模型的基礎上引入交叉項,如下:
表示共有n個特征:

若是這種直接在交叉項
的前面加上交叉項系數
的方式在稀疏數據的情況下存在一個很大的缺陷,即在對於觀察樣本中未出現交互的特征分量,不能對相應的參數進行估計。


對每一個特征分量
引入輔助向量
,利用
對交叉項的系數
進行估計,即





令

則

這就對應了一種矩陣的分解。對
值的限定,對FM的表達能力有一定的影響。


2、模型的求解

這里要求出
,主要采用了如公式
求出交叉項。具體過程如下:



注:上式中:
,且,倒數第二行中,將 j 換成 i,原式不變,所以能得到倒數第一行的形式。
3、基於隨機梯度的方式求解

對於回歸問題:

對於二分類問題:
![\frac{\partial loss^C\left ( \hat{y},y \right )}{\partial \theta }=-\frac{1}{\sigma \left ( \hat{y}y \right )}\sigma \left ( \hat{y}y \right )\cdot \left [ 1-\sigma \left ( \hat{y}y \right ) \right ]\cdot y\cdot \frac{\partial \hat{y}}{\partial \theta }\\ =\left [ \sigma \left ( \hat{y}y \right )-1 \right ]\cdot y\cdot \frac{\partial \hat{y}}{\partial \theta }](/image/aHR0cDovL2xhdGV4LmNvZGVjb2dzLmNvbS9naWYubGF0ZXg_XGZyYWN7XHBhcnRpYWwmc3BhY2U7bG9zc15DXGxlZnQmc3BhY2U7KCZzcGFjZTtcaGF0e3l9LHkmc3BhY2U7XHJpZ2h0JnNwYWNlOyl9e1xwYXJ0aWFsJnNwYWNlO1x0aGV0YSZzcGFjZTt9PS1cZnJhY3sxfXtcc2lnbWEmc3BhY2U7XGxlZnQmc3BhY2U7KCZzcGFjZTtcaGF0e3l9eSZzcGFjZTtccmlnaHQmc3BhY2U7KX1cc2lnbWEmc3BhY2U7XGxlZnQmc3BhY2U7KCZzcGFjZTtcaGF0e3l9eSZzcGFjZTtccmlnaHQmc3BhY2U7KVxjZG90JnNwYWNlO1xsZWZ0JnNwYWNlO1smc3BhY2U7MS1cc2lnbWEmc3BhY2U7XGxlZnQmc3BhY2U7KCZzcGFjZTtcaGF0e3l9eSZzcGFjZTtccmlnaHQmc3BhY2U7KSZzcGFjZTtccmlnaHQmc3BhY2U7XVxjZG90JnNwYWNlO3lcY2RvdCZzcGFjZTtcZnJhY3tccGFydGlhbCZzcGFjZTtcaGF0e3l9fXtccGFydGlhbCZzcGFjZTtcdGhldGEmc3BhY2U7fVxcJnNwYWNlOz1cbGVmdCZzcGFjZTtbJnNwYWNlO1xzaWdtYSZzcGFjZTtcbGVmdCZzcGFjZTsoJnNwYWNlO1xoYXR7eX15JnNwYWNlO1xyaWdodCZzcGFjZTspLTEmc3BhY2U7XHJpZ2h0JnNwYWNlO11cY2RvdCZzcGFjZTt5XGNkb3Qmc3BhY2U7XGZyYWN7XHBhcnRpYWwmc3BhY2U7XGhhdHt5fX17XHBhcnRpYWwmc3BhY2U7XHRoZXRhJnNwYWNlO30=.png)
而

最終交叉項要估計的參數每一個是:V
i,f
有n個特征, 每個特征有k個分量,那交叉項的參數個數就是:n*k。
四、實驗(求解二分類問題)
1、實驗的代碼:
- #coding:UTF-8
- from __future__ import division
- from math import exp
- from numpy import *
- from random import normalvariate#正態分布
- from datetime import datetime
- trainData = 'E://data//diabetes_train.txt'
- testData = 'E://data//diabetes_test.txt'
- featureNum = 8
- def loadDataSet(data):
- dataMat = []
- labelMat = []
- fr = open(data)#打開文件
- for line in fr.readlines():
- currLine = line.strip().split()
- #lineArr = [1.0]
- lineArr = []
- for i in xrange(featureNum):
- lineArr.append(float(currLine[i + 1]))
- dataMat.append(lineArr)
- labelMat.append(float(currLine[0]) * 2 - 1)
- return dataMat, labelMat
- def sigmoid(inx):
- return 1.0 / (1 + exp(-inx))
- def stocGradAscent(dataMatrix, classLabels, k, iter):
- #dataMatrix用的是mat, classLabels是列表
- m, n = shape(dataMatrix)
- alpha = 0.01
- #初始化參數
- w = zeros((n, 1))#其中n是特征的個數
- w_0 = 0. #截距項
- v = normalvariate(0, 0.2) * ones((n, k)) #交叉項
- for it in xrange(iter):
- print it
- for x in xrange(m):#隨機優化,對每一個樣本而言的
- inter_1 = dataMatrix[x] * v
- inter_2 = multiply(dataMatrix[x], dataMatrix[x]) * multiply(v, v)#multiply對應元素相乘
- #完成交叉項
- interaction = sum(multiply(inter_1, inter_1) - inter_2) / 2.
- p = w_0 + dataMatrix[x] * w + interaction#計算預測的輸出
- loss = sigmoid(classLabels[x] * p[0, 0]) - 1
- print loss
- w_0 = w_0 - alpha * loss * classLabels[x]
- for i in xrange(n):
- if dataMatrix[x, i] != 0:
- w[i, 0] = w[i, 0] - alpha * loss * classLabels[x] * dataMatrix[x, i]
- for j in xrange(k):
- v[i, j] = v[i, j] - alpha * loss * classLabels[x] * (dataMatrix[x, i] * inter_1[0, j] - v[i, j] * dataMatrix[x, i] * dataMatrix[x, i])
- return w_0, w, v
- def getAccuracy(dataMatrix, classLabels, w_0, w, v):
- m, n = shape(dataMatrix)
- allItem = 0
- error = 0
- result = []
- for x in xrange(m):
- allItem += 1
- inter_1 = dataMatrix[x] * v
- inter_2 = multiply(dataMatrix[x], dataMatrix[x]) * multiply(v, v)#multiply對應元素相乘
- #完成交叉項
- interaction = sum(multiply(inter_1, inter_1) - inter_2) / 2.
- p = w_0 + dataMatrix[x] * w + interaction#計算預測的輸出
- pre = sigmoid(p[0, 0])
- result.append(pre)
- if pre < 0.5 and classLabels[x] == 1.0:
- error += 1
- elif pre >= 0.5 and classLabels[x] == -1.0:
- error += 1
- else:
- continue
- print result
- return float(error) / allItem
- if __name__ == '__main__':
- dataTrain, labelTrain = loadDataSet(trainData)
- dataTest, labelTest = loadDataSet(testData)
- date_startTrain = datetime.now()
- print "開始訓練"
- w_0, w, v = stocGradAscent(mat(dataTrain), labelTrain, 20, 200)
- print "訓練准確性為:%f" % (1 - getAccuracy(mat(dataTrain), labelTrain, w_0, w, v))
- date_endTrain = datetime.now()
- print "訓練時間為:%s" % (date_endTrain - date_startTrain)
- print "開始測試"
- print "測試准確性為:%f" % (1 - getAccuracy(mat(dataTest), labelTest, w_0, w, v))
2、實驗結果:

五、幾點疑問
在傳統的非稀疏數據集上,有時效果並不是很好。在實驗中,我有一點處理,即在求解Sigmoid函數的過程中,在有的數據集上使用了帶閾值的求法:
- def sigmoid(inx):
- #return 1.0 / (1 + exp(-inx))
- return 1. / (1. + exp(-max(min(inx, 15.), -15.)))
六 圖片