拒絕調包俠,不須要高級算法和數據結構技巧

前言

大多數工科學生或者剛剛入門近年來比較火的「人工智能」相關算法的同窗,在選擇語言的時候,都會選擇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)次函數meanstd,這其中包含了大量的重複計算,帶來了大量的計算開銷(計算均值,確定有大量的加和操做)。假設咱們已經計算了實數數組X_n=[x_1, x_2, \ldots, x_n]的均值,記爲\bar{X}_{n}。當一個新的數據x_{n+1}被採樣到,並加入X_n中,造成X_{n+1}=[x_1, x_2, \ldots, x_n, x_{n+1}]。在計算均值\bar{X}_{n+1}時,能夠利用如下公式:框架

\begin{equation}
\bar{X}_{n+1}=\bar{X}_{n}+\frac{x_{n+1} - \bar{X}_n}{n+1} \tag{1}
\end{equation}

同理,咱們能夠獲得計算新方差的公式:函數

\begin{equation}
\mathrm{Var}(X_{n+1})=\mathrm{Var}(X_n)+(x_{n+1} - \bar{X}_n)(x_{n+1} - \bar{X}_{n+1}) \tag{2}
\end{equation}

新的編碼方案就變成:oop

// 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));
複製代碼

這樣計算速度就快不少了。post

思考

如上所述的這種從左至右計算統計量的過程,在不少算法中出現過,好比著名的決策樹算法。決策樹在某個節點肯定分裂特徵和分裂點的計算過程當中,是如何進行計算統計量的呢?著名的決策樹開源框架,如XGBoost中,又是如何編碼,對樣本梯度進行統計的呢?這些留給你們去思考和發現。優化

相關文章
相關標籤/搜索