在機器學習模型中,比如監督學習中,我們設計模型,我們重要的的工作是如何求解這個模型的最優值,通常是如何求救損失函數的最小值。比如logistic regression 中我們求解的是的loss function就是負log 最大似然函數。logistic regression 被廣泛應用與互聯網應用中,比如反欺詐,廣告ctr。logistic regression是廣義線性模型,優點是簡單,實現容易,線上能很快響應。當數據不是呈現線性關係的時候,如果我們想應用logistic regression就得擴大特徵空間,比如做非線性變換,特徵組合來達到非線性模型的效果。對於非線性模型比如GDBT, Random Forest,SVM的RBF核,相對就不需要做這些特徵變換,因爲模型本身已經已經做了非線性工作。我認爲,GBDT, Randomn Forest 這種他的非線性方法一個重要的工作就是做特徵的組合,而SVM的RBF核只是單一特徵變換,做了升維工作,讓數據在更高維空間能被劃分。在lr模型中特徵過多,或者非線性模型中,極容易出現過擬合,爲了儘量避免過擬合,同樣的做法就是加正則方法。通常的正則方法爲L1和L2。L1相對L2有個好處就是,他不僅可以避免過擬合問題,還可以起到特徵選擇的作用。當loss function 加L1的正則的時候,最優解會使很多不重要的特徵收斂到0值,而L2只會把這些特徵收斂到一個很小的值,但不是0。 我們來看下一個通用的加上L1的損失函數:
f(x) = l(x) + c||x||, 其中l(x) 是原來的可導損失函數。
現在的問題是如何求解f(x) 的最小值點。從f(x) 上來看,應爲加了L1,導致在x=0點不可導,所以以往直接算梯度的方法就不可取了。Microsoft Research的人員在ICML2007提出了一種基於L-BFGS的OWL_QN算法來求解因爲L1加入帶來的不可導問題,具體參考(Andrew G, Gao J. Scalable training of L 1-regularized log-linear models[C]//Proceedings of the 24th international conference on Machine learning. ACM, 2007: 33-40.)。
下面是文獻中把求解函數進行泰特展開,如下:
從上面看到,主要涉及到的就是一個一階梯度,和一個Hessian矩陣。求解hessian矩陣就是這裏的挑戰。L-BFGS採用有限的空間,犧牲少許精度的方法來求救hession。主要涉及幾個一維的向量,具體算法可參考wiki上的。
下面是wiki上關於L-BFGS的算法。
附上這塊的部分實現代碼:
while (gnorm > gtol) and (k < maxiter):
# find search direction (Nocedal & Wright 2006, p.178, Algorithm 7.4)
q = numpy.array(gfk, dtype=gfk.dtype)
size = len(sList)
aList = [None]*size
if size > 0:
for i in xrange(size-1,-1,-1):
aList[i] = rhoList[i] * numpy.dot(sList[i],q)
q -= aList[i] * yList[i]
# modify to ensure a well-scaled search direction (N&W 2006, eq. 7.20)
q *= (rhoList[-1] * numpy.dot(yList[-1],yList[-1]))**(-1)
for i in xrange(size):
b = rhoList[i] * numpy.dot(yList[i],q)
q += sList[i] * (aList[i] - b)
pk = -q
# fix non-descent components
non_descent = numpy.where(pk*gfk>=0)[0]
pk[non_descent] = 0
OWL- QN 算法是L-BFGS算法的一種變種,求解L1上不可導的問題。OWL-QN相對L-BFGS,其實大多都是一樣的,要是按照從代碼上來看,也許就是30行左右代碼不一樣而已。OWL-QL 相對L-BFGS不一樣的地方:
(1)每次選取的下一步最有點Xk+1的的象限進行了限制,不允許跨象限,比如之前Xk< 0 , Xk+1是不允許大於0,這種情況只能把Xk+1設爲0;
(2)最原始損失函數的梯度了,做了個次梯度修正(加上L1的修正)
這兩點,我們從wiki上分先的OWL- QN的python代碼上能看到:
(1)
def simple_line_search_owlqn(f, old_fval, xk, pk, gfk, k, Cvec):
"""Backtracking line search for fmin_owlqn. A simple line search works reasonably
well because the search direction has been rescaled so that the Wolfe conditions
will usually be satisfied for alpha=1 (see Nocedal & Wright, 2006, Numerical
Optimization, p. 178). NB: To improve efficiency this routine checks only one of
the Wolfe conditions. This is appropriate only for convex objectives. If the
objective is not convex it may lead to non-positive-definite Hessian approximations
and non-descent search directions. (see Nocedal & Wright 2006, chapters 3 and 6.)
"""
dirDeriv = numpy.dot(pk,gfk)
if dirDeriv >= 0:
sys.stderr.write("Warning: Non-descent direction. Check your gradient.\n")
return None, None, None
alpha = 1.0
backoff = 0.5
if k == 0:
alpha = 1.0 / (numpy.dot(pk,pk))**(0.5)
backoff = 0.1
c1 = 1e-4
new_fval = None
while True:
new_x = xk + alpha * pk
crossed_discont = numpy.where(numpy.logical_and(Cvec>0, xk*new_x<0))[0]
new_x[crossed_discont] = 0
new_fval = f(new_x) + numpy.dot(Cvec,numpy.absolute(new_x))
if new_fval <= old_fval + c1 * dirDeriv * alpha:
break
alpha *= backoff
if alpha <= 1e-4:
return None, None, None
return alpha, new_fval, new_x
(2)
gfkp1 = myfprime(xkp1) # raw loss function gradient;
# find penalized subgradients
gfkp1 = subgrad(xkp1,gfkp1,Cvec)
# subgrad:
def subgrad(x, gf, Cvec):
"""Subgradient computation for fmin_owlqn."""
for i in numpy.where(Cvec>0)[0]:
if x[i] < 0:
gf[i] -= Cvec[i]
elif x[i] > 0:
gf[i] += Cvec[i]
else:
if gf[i] < -Cvec[i]:
gf[i] += Cvec[i]
elif gf[i] > Cvec[i]:
gf[i] -= Cvec[i]
else:
gf[i] = 0
return gf
參考文獻:
[1] Andrew G, Gao J. Scalable training of L 1-regularized log-linear models[C]//Proceedings of the 24th international conference on Machine learning. ACM, 2007: 33-40.
[2] http://en.wikipedia.org/wiki/Limited-memory_BFGS
[3] http://www.umiacs.umd.edu/~msubotin/owlqn.py . Python implementation by Michael Subotin, intended for use with SciPy