拒絕調包俠,不需要高階演算法和資料結構技巧
前言
大多數工科學生或者剛剛入門近年來比較火的“人工智慧”相關演算法的同學,在選擇語言的時候,都會選擇MATLAB、Python、R等等這些高階語言,對自己所學的演算法進行實現和除錯。這些高階語言中,包含了實現複雜演算法的基礎數學演算法、基本統計演算法、基礎資料結構的實現,比如均值(mean)、方差(std)、向量(Vector)等等。藉助於這些高階語言的Built-in Function,我們的一些想法會在較短時間內實現。並且,解釋型的程式設計方式,也方便了大家去除錯程式碼、執行程式碼。但是使用這些語言和它們的函式,會帶來一些效率降低的問題。大多數人首先想到的解決方式,可能是去選擇底層語言來實現演算法,比如C/C++, JAVA等。但其實,我們在運用高階語言進行編碼時,也有大量需要進行優化的內容。我們應當從“調包”和利用Built-in Function的習慣中解放出來。
問題
最近在用C++實現CUSUM時,我參考該演算法的MATLAB的程式碼直接翻譯成了C++的程式碼。本想到演算法應該會非常迅速,但是它的表現的確讓我大失所望。在優化掉輸出(ostream
)帶來的時間損耗後,演算法的速度依然沒有達到期望的要求。於是,觀察程式碼細節時發現,在迭代過程中,我們對一段隨迭代次數其長度線性增長的陣列片段(slice
)求取均值、方差時,使用了mean()
和std()
函式。
那麼,每一次新的資料新增進一個數組(Array或者Vector),就去呼叫上述這類函式,真的有必要嘛?我們是不是引入了太多的重複計算?
解決方案
我們先來看一下CUSUM演算法的MATLAB實現的一個片段:
%% Global Loop
% w = waitbar(0,'Calculating Cumulative Sums, please wait...');
while k < length(x)
% current sample
k = k+1;
% mean and variance estimation (from initial to current sample)
m(k) = mean(x(k0:k));
v(k) = var(x(k0:k));
複製程式碼
上述程式碼片段裡,在while
迴圈中,我們呼叫了length(x)
次函式mean
和std
,這其中包含了大量的重複計算,帶來了大量的計算開銷(計算均值,肯定有大量的加和操作)。假設我們已經計算了實數陣列
同理,我們可以得到計算新方差的公式:
新的編碼方案就變成:
// Global Loop
while (k < len - 1) {
k++;
prev_delta = X[k] - m[k - 1]; // online average
m.emplace_back(m[k - 1] + prev_delta / (k - k0 + 1));
post_delta = X[k] - m[k]; // online s.t.d
v.emplace_back(std::sqrt(v[k - 1]*v[k - 1] + prev_delta*post_delta));
複製程式碼
這樣計算速度就快很多了。
思考
如上所述的這種從左至右計算統計量的過程,在很多演算法中出現過,比如著名的決策樹演算法。決策樹在某個節點確定分裂特徵和分裂點的計算過程中,是如何進行計算統計量的呢?著名的決策樹開源框架,如XGBoost中,又是如何編碼,對樣本梯度進行統計的呢?這些留給大家去思考和發現。