ceres入门学习
轉(zhuǎn)載自https://www.jianshu.com/p/e5b03cf22c80
Ceres solver 是谷歌開發(fā)的一款用于非線性優(yōu)化的庫,在谷歌的開源激光雷達(dá)slam項目cartographer中被大量使用。
Ceres簡易例程
使用Ceres求解非線性優(yōu)化問題,一共分為三個部分:
1、 第一部分:構(gòu)建cost fuction,即代價函數(shù),也就是尋優(yōu)的目標(biāo)式。這個部分需要使用仿函數(shù)(functor)這一技巧來實現(xiàn),做法是定義一個cost function的結(jié)構(gòu)體,在結(jié)構(gòu)體內(nèi)重載()運算符,具體實現(xiàn)方法后續(xù)介紹。
2、 第二部分:通過代價函數(shù)構(gòu)建待求解的優(yōu)化問題。
3、 第三部分:配置求解器參數(shù)并求解問題,這個步驟就是設(shè)置方程怎么求解、求解過程是否輸出等,然后調(diào)用一下Solve方法。
好了,此時你應(yīng)該對ceres的大概使用流程有了一個基本的認(rèn)識。下面我就基于ceres官網(wǎng)上的教程中的一個例程來詳細(xì)介紹一下ceres的用法。
Ceres官網(wǎng)教程給出的例程中,求解的問題是求x使得1/2*(10-x)^2取到最小值。(很容易心算出x的解應(yīng)該是10)
好,來看代碼:
第一部分:構(gòu)造代價函數(shù)結(jié)構(gòu)體
這里的使用了仿函數(shù)的技巧,即在CostFunction結(jié)構(gòu)體內(nèi),對()進(jìn)行重載,這樣的話,該結(jié)構(gòu)體的一個實例就能具有類似一個函數(shù)的性質(zhì),在代碼編寫過程中就能當(dāng)做一個函數(shù)一樣來使用。
關(guān)于仿函數(shù),這里再多說幾句,對結(jié)構(gòu)體、類的一個實例,比如Myclass類的一個實例Obj1,如果Myclass里對()進(jìn)行了重載,那Obj1被創(chuàng)建之后,就可以將Obj1這個實例當(dāng)做函數(shù)來用,比如Obj(x)這樣,為了方便讀者理解,下面隨便編一段簡單的示例代碼,湊活看看吧。
在我隨便寫的示教代碼中,可以看到我將Myclass的()符號的功能定義成了將括號內(nèi)的數(shù)n乘以隱藏參數(shù)x倍,其中x是Obj1對象的一個私有成員變量,是是在構(gòu)造Obj1時候賦予的。因為重載了()符號,所以在主函數(shù)中Obj1這個對象就可以當(dāng)做一個函數(shù)來使用,使用方法為Obj1(n),如果Obj1的內(nèi)部成員變量_x是5,則此函數(shù)功能就是將輸入?yún)?shù)擴(kuò)大5倍,如果這個成員變量是50,Obj1()函數(shù)的功能就是將輸入n擴(kuò)大50倍,這也是仿函數(shù)技巧的一個優(yōu)點,它能利用對象的成員變量來儲存更多的函數(shù)內(nèi)部參數(shù)。
了解了仿函數(shù)技巧的使用方法后,再回過頭來看看ceres使用中構(gòu)造CostFuction 的具體方法:
CostFunction結(jié)構(gòu)體中,對括號符號重載的函數(shù)中,傳入?yún)?shù)有兩個,一個是待優(yōu)化的變量x,另一個是殘差residual,也就是代價函數(shù)的輸出。
重載了()符號之后,CostFunction就可以傳入AutoDiffCostFunction方法來構(gòu)建尋優(yōu)問題了。
第二部分:通過代價函數(shù)構(gòu)建待求解的優(yōu)化問題
Problem problem; CostFunction* cost_function = new AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor); problem.AddResidualBlock(cost_function, NULL, &x); 這一部分就是待求解的優(yōu)化問題的構(gòu)建過程,使用之前結(jié)構(gòu)體創(chuàng)建一個實例,由于使用了仿函數(shù)技巧,該實例在使用上可以當(dāng)做一個函數(shù)。基于該實例new了一個CostFunction結(jié)構(gòu)體,這里使用的自動求導(dǎo),將之前的代價函數(shù)結(jié)構(gòu)體傳入,第一個1是輸出維度,即殘差的維度,第二個1是輸入維度,即待尋優(yōu)參數(shù)x的維度。分別對應(yīng)之前結(jié)構(gòu)體中的residual和x。向問題中添加誤差項,本問題比較簡單,添加一次就行(有的問題要不斷多次添加ResidualBlock以構(gòu)建最小二乘求解問題)。這里的參數(shù)NULL是指不使用核函數(shù),&x表示x是待尋優(yōu)參數(shù)。
第三部分:配置問題并求解問題
Solver::Options options; options.linear_solver_type = ceres::DENSE_QR; options.minimizer_progress_to_stdout = true; Solver::Summary summary; Solve(options, &problem, &summary); std::cout << summary.BriefReport() << "\n"; std::cout << "x : " << initial_x << " -> " << x << "\n";這一部分很好理解,創(chuàng)建一個Option,配置一下求解器的配置,創(chuàng)建一個Summary。最后調(diào)用Solve方法,求解。
最后輸出結(jié)果:
讀者們看到這里相信已經(jīng)對Ceres庫的使用已經(jīng)有了一個大概的認(rèn)識,現(xiàn)在可以試著將代碼實際運行一下來感受一下,加深一下理解。
博主的使用環(huán)境為Ubuntu 16.04,所以在此附上CMakeList.txt
附:CMakeLists.txt代碼:
cmake_minimum_required(VERSION 2.8) project(ceres)find_package(Ceres REQUIRED) include_directories( ${CERES_INCLUDE_DIRS} )add_executable(use_ceres l_2.cpp) target_link_libraries(use_ceres ${CERES_LIBRARIES})進(jìn)階-更多的求導(dǎo)法
在上面的例子中,使用的是自動求導(dǎo)法(AutoDiffCostFunction),Ceres庫中其實還有更多的求導(dǎo)方法可供選擇(雖然自動求導(dǎo)的確是最省心的,而且一般情況下也是最快的。。。)。這里就簡要介紹一下其他的求導(dǎo)方法:
數(shù)值求導(dǎo)法(一般比自動求導(dǎo)法收斂更慢,且更容易出現(xiàn)數(shù)值錯誤):
數(shù)值求導(dǎo)法的代價函數(shù)結(jié)構(gòu)體構(gòu)建和自動求導(dǎo)中的沒有區(qū)別,只是在第二部分的構(gòu)建求解問題中稍有區(qū)別,下面是官網(wǎng)給出的數(shù)值求導(dǎo)法的問題構(gòu)建部分代碼:
problem.AddResidualBlock(cost_function, NULL, &x); 乍一看和自動求導(dǎo)法中的代碼沒區(qū)別,除了代價函數(shù)結(jié)構(gòu)體的名字定義得稍有不同,使用的是NumericDiffCostFunction而非AutoDiffCostFunction,改動的地方只有在模板參數(shù)設(shè)置輸入輸出維度前面加了一個模板參數(shù)ceres::CENTRAL,表明使用的是數(shù)值求導(dǎo)法。
還有其他一些更多更復(fù)雜的求導(dǎo)法,不詳述。
再進(jìn)階-曲線擬合
趁熱打鐵,閱讀到這里想必讀者們應(yīng)該已經(jīng)對Ceres庫的使用已經(jīng)比較了解了(如果前面認(rèn)真看了的話),現(xiàn)在就來嘗試解決一個更加復(fù)雜的問題來檢驗一下成果,順便進(jìn)階一下。
問題:
擬合非線性函數(shù)的曲線(和官網(wǎng)上的例子不一樣,稍微復(fù)雜一丟丟):
y=e{3x{2}+2x+1}
依然,先上代碼:
代碼之前先啰嗦幾句,整個代碼的思路還是先構(gòu)建代價函數(shù)結(jié)構(gòu)體,然后在[0,1]之間均勻生成待擬合曲線的1000個數(shù)據(jù)點,加上方差為1的白噪聲,數(shù)據(jù)點用兩個vector儲存(x_data和y_data),然后構(gòu)建待求解優(yōu)化問題,最后求解,擬合曲線參數(shù)。
(PS. 本段代碼中使用OpenCV的隨機(jī)數(shù)產(chǎn)生器,要跑代碼的同學(xué)可能要先裝一下OpenCV)
對應(yīng)的CMakeLists.txt
cmake_minimum_required(VERSION 2.8) project(ceres)find_package(Ceres REQUIRED) include_directories( ${CERES_INCLUDE_DIRS} ) find_package(OpenCV REQUIRED) include_directories( ${OpenCV_INCLUDE_DIRS} ) add_executable(curve curve.cpp) target_link_libraries(curve ${CERES_LIBRARIES} ${OpenCV_LIBS})代碼解讀:
代碼的整體流程還是之前的流程,四個部分大致相同。比之前稍微復(fù)雜一點的地方就在于計算單個點的殘差時需要輸入該點的x,y坐標(biāo),而且需要反復(fù)多次累計單點的殘差以構(gòu)建總體的優(yōu)化目標(biāo)。
先看代價函數(shù)結(jié)構(gòu)體的構(gòu)建:
這里依然使用仿函數(shù)技巧,與之前不同的是結(jié)構(gòu)體內(nèi)部有_x,_y成員變量,用于儲存散點的坐標(biāo)。
再看優(yōu)化問題的構(gòu)建:
這里與前例不同的是需要輸入散點的坐標(biāo)x,y,由于_x,_y是結(jié)構(gòu)體成員變量,所以可以通過構(gòu)造函數(shù)直接對這兩個值賦值。本代碼里也是這么用的。
最終的運行結(jié)果是: iter cost cost_change |gradient| |step| tr_ratio tr_radius ls_iter iter_time total_time0 5.277388e+06 0.00e+00 5.58e+04 0.00e+00 0.00e+00 1.00e+04 0 1.68e-02 1.71e-021 4.287886e+238 -4.29e+238 0.00e+00 7.39e+02 -8.79e+231 5.00e+03 1 4.92e-04 1.77e-022 1.094203e+238 -1.09e+238 0.00e+00 7.32e+02 -2.24e+231 1.25e+03 1 3.86e-04 1.82e-023 5.129910e+234 -5.13e+234 0.00e+00 6.96e+02 -1.05e+228 1.56e+02 1 3.69e-04 1.86e-024 1.420558e+215 -1.42e+215 0.00e+00 4.91e+02 -2.97e+208 9.77e+00 1 3.58e-04 1.89e-025 9.607928e+166 -9.61e+166 0.00e+00 1.85e+02 -2.23e+160 3.05e-01 1 3.59e-04 1.93e-026 7.192680e+60 -7.19e+60 0.00e+00 4.59e+01 -2.94e+54 4.77e-03 1 3.70e-04 1.97e-027 5.061060e+06 2.16e+05 2.68e+05 1.21e+00 2.52e+00 1.43e-02 1 1.82e-02 3.79e-028 4.342234e+06 7.19e+05 9.34e+05 8.84e-01 2.08e+00 4.29e-02 1 1.58e-02 5.38e-029 2.876001e+06 1.47e+06 2.06e+06 6.42e-01 1.66e+00 1.29e-01 1 1.12e-02 6.50e-0210 1.018645e+06 1.86e+06 2.58e+06 4.76e-01 1.38e+00 3.86e-01 1 1.95e-02 8.46e-0211 1.357731e+05 8.83e+05 1.30e+06 2.56e-01 1.13e+00 1.16e+00 1 9.16e-03 9.38e-0212 2.142986e+04 1.14e+05 2.71e+05 8.60e-02 1.03e+00 3.48e+00 1 7.91e-03 1.02e-0113 1.636436e+04 5.07e+03 5.94e+04 3.01e-02 1.01e+00 1.04e+01 1 7.07e-03 1.09e-0114 1.270381e+04 3.66e+03 3.96e+04 6.21e-02 9.96e-01 3.13e+01 1 5.77e-03 1.15e-0115 6.723500e+03 5.98e+03 2.68e+04 1.30e-01 9.89e-01 9.39e+01 1 5.15e-03 1.20e-0116 1.900795e+03 4.82e+03 1.24e+04 1.76e-01 9.90e-01 2.82e+02 1 5.10e-03 1.25e-0117 5.933860e+02 1.31e+03 3.45e+03 1.23e-01 9.96e-01 8.45e+02 1 5.34e-03 1.30e-0118 5.089437e+02 8.44e+01 3.46e+02 3.77e-02 1.00e+00 2.53e+03 1 5.44e-03 1.36e-0119 5.071157e+02 1.83e+00 4.47e+01 1.63e-02 1.00e+00 7.60e+03 1 5.43e-03 1.41e-0120 5.056467e+02 1.47e+00 3.03e+01 3.13e-02 1.00e+00 2.28e+04 1 5.37e-03 1.47e-0121 5.046313e+02 1.02e+00 1.23e+01 3.82e-02 1.00e+00 6.84e+04 1 5.48e-03 1.52e-0122 5.044403e+02 1.91e-01 2.23e+00 2.11e-02 9.99e-01 2.05e+05 1 5.60e-03 1.58e-0123 5.044338e+02 6.48e-03 1.38e-01 4.35e-03 9.98e-01 6.16e+05 1 6.58e-03 1.65e-01 a = 3.01325 b = 1.97599 c = 1.01113
可以看到,最終的擬合結(jié)果與真實值非常接近。
再再進(jìn)階-魯棒曲線擬合
求解優(yōu)化問題中(比如擬合曲線),數(shù)據(jù)中往往會有離群點、錯誤值什么的,最終得到的尋優(yōu)結(jié)果很容易受到影響,此時就可以使用一些損失核函數(shù)來對離群點的影響加以消除。要使用核函數(shù),只需要把上述代碼中的NULL或nullptr換成損失核函數(shù)結(jié)構(gòu)體的實例。
Ceres庫中提供的核函數(shù)主要有:TrivialLoss 、HuberLoss、 SoftLOneLoss 、 CauchyLoss。
比如此時要使用CauchyLoss,只需要將nullptr換成new CauchyLoss(0.5)就行(0.5為參數(shù))。
下面兩圖別為Ceres官網(wǎng)上的例程的結(jié)果,可以明顯看出使用損失核函數(shù)之后的曲線收離群點的影響更小。
不使用魯棒核函數(shù)的擬合 使用魯棒核函數(shù)的擬合
參考資料:
[1]. http://www.ceres-solver.org/
[2].《視覺slam十四講》 高翔
[3]. http://www.cnblogs.com/xiaowangba/p/6313933.html
轉(zhuǎn)載于:https://www.cnblogs.com/gary-guo/p/9767777.html
總結(jié)
- 上一篇: Keras Theano 输出中间层结
- 下一篇: ResDepot CRC码