Spark MLlib實現求解惑?
">初用spark mllib,請教幾位大牛解惑,不勝感激,場景是:我用mllib里的 lr+lbfgs/gd + l1/l2正則,想要解決一個轉化率預估的回歸問題,由於特徵維度較大,需要使用sparse vector,於是在使用中發現以下問題:
1、以lbfgs為例,核心優化使用的是breeze里的lbfgs實現,原本的實現是支持:dense vector和sparse vector(vector buiilder),而mllib里也封裝了sparse vector,但lbfgs的默認實現雖然介面里傳的是vector(支持dense和sparse),但在實現時使用dense vector初始化了BreezeLBFGS,包括cost function也一樣,最後其實整個優化只支持dense vector,不知道這麼實現有無什麼玄機?2、以GD為例,為何GradientDescent是private[mllib]的,導致我後面想要自己封裝一個使用L1/L2正則的lr時無法直接使用,還得把mllib里的源碼再複製一遍到新類中,類似聲明為[mllib]的還有不少,自己去擴展這些實現時比較不方便,不知道這麼設計的初衷是什麼哩?3、L1正則使用soft-thresholding方法,但我發現無法將權重稀疏化,倒是owlqn里使用虛梯度的方法可以達到目的,想問下是否我的使用方法有問題(ps:我在updater初始化時傳入一個L1Updater)?
4、我想引入breeze里的owlqn實現到mllib里,不知社區是否有相關計劃?5、我想利用神經網路做多模型融合,類似這種融合在提高模型泛化能力上比較明顯,另外對imbalanced比較嚴重的數據學習也有幫助,不知道社區是否有相關計劃,還是需要自己動手豐衣足食哩(之前有幾個朋友都反映,自己動手實現了一個東西,覺得好牛逼的說,但是發現沒過多久社區就發了一個更更牛逼的)?再次感謝各位牛人!
第一次在zhihu上回答問題,還有點小緊張,:-)
回答一下前兩個問題,後面的問題跟社區有關,可以發郵件到Spark users郵件列表裡詢問。
1. 這句話 「實現時使用dense vector初始化了BreezeLBFGS,包括cost function也一樣,最後其實整個優化只支持dense vector」 中的dense vector指的只是initWeights。initWeights就是一個vector,即使維度很高,用dense vector佔用空間也不大。「只支持dense vector」這句話不對哦,data(也就是訓練樣本)中的feature可以是Dense vector也可以是Sparse vector,而且整個計算過程中不會將data的sparse vector轉換為dense vector。比如,在計算gradient的時候會調用 val margin = -1.0 * dot (data, weights),這裡的data就是每個樣例的feature,如果feature是sparse vector,這裡計算margin就直接使用BLAS.dot (feature: SparseVector, weights: DenseVector),很高效滴。
2. 「以GD為例,為何GradientDescent是private[mllib]的,導致我後面想要自己封裝一個使用L1/L2正則的lr時無法直接使用,還得把mllib里的源碼再複製一遍到新類中,類似聲明為[mllib]的還有不少,自己去擴展這些實現時比較不方便,不知道這麼設計的初衷是什麼哩?」
其實你錯怪MLlib了,:-)
因為很多演算法都通過GD求解,所以將GradientDesent做成一個general的類,它主要包含Gradient和Updater兩個參數,第一個參數是Gradient的求解方法,由不同的演算法定製(比如LR使用LogisticGradient,SVM使用HingeGradient),第二個參數是parameter的更新策略,比如SimpleUpdater直接對weights進行更新,不使用任何正則化方法,而L1Updater更新weights時使用L1正則化。GradientDesent雖然是private[mllib]的,但你可以通過 .optimizer訪問到,比如val lr = new LogisticRegressionWithSGD(); val gradientDescent = lr.optimizer。然後呢,就可以設置GradientDesent裡面的Gradient和Updater,比如要換成L1正則化的LR,就可以接著寫gradientDescent.setUpdater(new L1Updater)。還有,Gradient和Updater都不是private[mllib],所以你可以自由繼承這兩個類,然後.optimizer.setGradient(new MyGradient),.optimizer.setUpdater(new MyUpdater)就可以了。
P.S. MLlib面向使用現有ML演算法進行數據處理的工程師,所以會盡量精簡API,不暴露內部實現。類似的問題很多,比如Breeze本身支持向量間的加、減、點乘等運算,MLlib就沒有直接暴露這些API。我來回答一下第3、4個問題吧。
3、L1正則使用soft-thresholding方法,但我發現無法將權重稀疏化,倒是owlqn里使用虛梯度的方法可以達到目的,想問下是否我的使用方法有問題(ps:我在updater初始化時傳入一個L1Updater)?
Soft thresholding方法實際上是在每一次梯度下降之後,用步長對權重再做一次shrink。因此只能用於SGD/BGD,而無法被用於LBFGS這樣的擬牛頓方法的。
另外從spark的源碼上看,LBFGS只是用updater計算了一下正則項的loss和gradient(其實是一種偷懶行為~),而L1Updater顯然不能計算正則項的gradient,因此簡單地傳入L1Updater是沒有用的。
4、我想引入breeze里的owlqn實現到mllib里,不知社區是否有相關計劃?
目前github上面已經有人提交了實現owlqn的代碼,估計下一個版本的spark就能用到。
[SPARK-5253] [ML] LinearRegression with L1/L2 (ElasticNet) using OWLQN · apache/spark@6a827d5 · GitHub其實自己動手,用spark調用breeze的owlqn也是很方便的。最簡單的方法是繼承LBFGS新寫一個OWLQN類,將BreezeLBFGS替換成BreezeOWLQN。然後重寫一下CostFun、axpy等幾個private的類和方法。不過這樣的實現只能支持dense vector的權重,要支持sparse vector的權重需要重構的就多了~
mllib實現中即使是sparsevector的實現是一個拉鏈式hash table,也是需要事先預分配所有維度的內存的,不適合做轉化率預估這種高維稀疏場景。mllib中的GD演算法以及L1和L2正則化都比較初級,在廣告和推薦這種場景都不適用。所以,最好的辦法就是自己在spark之上實現一套演算法
最近在看MLlib,想問下MLlib訓練處的模型怎麼查看權重係數?
數學的都不要邀請我了,真不會,哪裡出錯了,老邀我
最近在做一個類似的問題。不耐煩讀MLlib源代碼,不太會面向對象編程,準備直接定義breeze的DiffFunction然後調用它的LBFGS或OWLQN。這個鏈接里的回答個人覺得很好,解釋的很清楚。optimizer implementation-stackoverflow question
推薦閱讀: