124

私は現在、C でローリング メディアン フィルター (ローリング ミーン フィルターに類似) を実装するアルゴリズムに取り組んでいます。文献を検索したところ、合理的に効率的な方法が 2 つあるようです。1 つ目は、値の最初のウィンドウを並べ替え、バイナリ検索を実行して新しい値を挿入し、各反復で既存の値を削除することです。

2 つ目 (Hardle と Steiger、1995 年、JRSS-C、アルゴリズム 296 から) は、一方の端に maxheap、もう一方の端に minheap、中央に中央値を持つ両端ヒープ構造を構築します。これにより、O(n log n) のアルゴリズムではなく、線形時間アルゴリズムが得られます。

これが私の問題です。前者を実装することは可能ですが、これを何百万もの時系列で実行する必要があるため、効率が非常に重要です。後者は、実装が非常に難しいことがわかっています。R の stats パッケージのコードの Trunmed.c ファイルにコードが見つかりましたが、かなり判読できません。

線形時間ローリング メディアン アルゴリズムの適切に作成された C 実装を知っている人はいますか?

編集: Trunmed.c コードへのリンクhttp://google.com/codesearch/p?hl=en&sa=N&cd=1&ct=rc#mYw3h_Lb_e0/R-2.2.0/src/library/stats/src/Trunmed.c

4

13 に答える 13

30

src/library/stats/src/Trunmed.cスタンドアロンの C++ クラス / C サブルーチンでも似たようなものが欲しかったので、Rを数回見ました。これは実際には 1 つの 2 つの実装であることに注意してください。src/library/stats/man/runmed.Rd(ヘルプ ファイルのソース) を参照してください。

\details{
  Apart from the end values, the result \code{y = runmed(x, k)} simply has
  \code{y[j] = median(x[(j-k2):(j+k2)])} (k = 2*k2+1), computed very
  efficiently.

  The two algorithms are internally entirely different:
  \describe{
    \item{"Turlach"}{is the Härdle-Steiger
      algorithm (see Ref.) as implemented by Berwin Turlach.
      A tree algorithm is used, ensuring performance \eqn{O(n \log
        k)}{O(n * log(k))} where \code{n <- length(x)} which is
      asymptotically optimal.}
    \item{"Stuetzle"}{is the (older) Stuetzle-Friedman implementation
      which makes use of median \emph{updating} when one observation
      enters and one leaves the smoothing window.  While this performs as
      \eqn{O(n \times k)}{O(n * k)} which is slower asymptotically, it is
      considerably faster for small \eqn{k} or \eqn{n}.}
  }
}

これがより独立した方法で再利用されるのを見るのは素晴らしいことです。ボランティアですか?Rビットのいくつかを手伝うことができます。

編集1:上記のTrunmed.cの古いバージョンへのリンクに加えて、現在のSVNコピーは次のとおりです

編集 2 : Ryan Tibshirani には、高速中央値ビニングに関する C および Fortran コードがいくつかあります。これは、ウィンドウ化されたアプローチの適切な出発点になる可能性があります。

于 2009-08-21T00:41:30.103 に答える
27

order-statistic を使用した C++ データ構造の最新の実装が見つからなかったため、MAK によって提案されたトップ コーダー リンクで両方のアイデアを実装することになりました ( Match Editorial : FloatingMedian までスクロールします)。

2 つのマルチセット

最初のアイデアでは、挿入/削除ごとに O(ln N) でデータを 2 つのデータ構造 (ヒープ、マルチセットなど) に分割しますが、分位数を大きなコストなしで動的に変更することはできません。つまり、ローリング メディアンまたはローリング 75% を設定できますが、両方を同時に設定することはできません。

セグメントツリー

2 番目のアイデアは、挿入/削除/クエリに O(ln N) であるセグメント ツリーを使用しますが、より柔軟です。何よりも、「N」はデータ範囲のサイズです。したがって、ローリング メディアンに 100 万アイテムのウィンドウがあり、データが 1..65536 から変化する場合、100 万のローリング ウィンドウの移動ごとに必要な操作は 16 回だけです!!

C++ コードは、Denis が上に投稿したものと似ています (「量子化されたデータの簡単なアルゴリズムをここに示します」)。

GNU 注文統計ツリー

あきらめる直前に、stdlibc++ に順序統計ツリーが含まれていることがわかりました!!!

これらには 2 つの重要な操作があります。

iter = tree.find_by_order(value)
order = tree.order_of_key(value)

libstdc++ のマニュアル policy_based_data_structures_testを参照してください(「分割と結合」を検索してください)。

c++0x/c++11 スタイルの部分的な typedef をサポートするコンパイラの便利なヘッダーで使用するために、ツリーをラップしました。

#if !defined(GNU_ORDER_STATISTIC_SET_H)
#define GNU_ORDER_STATISTIC_SET_H
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>

// A red-black tree table storing ints and their order
// statistics. Note that since the tree uses
// tree_order_statistics_node_update as its update policy, then it
// includes its methods by_order and order_of_key.
template <typename T>
using t_order_statistic_set = __gnu_pbds::tree<
                                  T,
                                  __gnu_pbds::null_type,
                                  std::less<T>,
                                  __gnu_pbds::rb_tree_tag,
                                  // This policy updates nodes'  metadata for order statistics.
                                  __gnu_pbds::tree_order_statistics_node_update>;

#endif //GNU_ORDER_STATISTIC_SET_H
于 2012-06-27T14:31:53.650 に答える
17

ここC の実装を行いました。この質問には、さらにいくつかの詳細があります: C-Turlach実装のローリング中央値

使用例:

int main(int argc, char* argv[])
{
   int i, v;
   Mediator* m = MediatorNew(15);
 
   for (i=0; i<30; i++) {
      v = rand() & 127;
      printf("Inserting %3d \n", v);
      MediatorInsert(m, v);
      v = MediatorMedian(m);
      printf("Median = %3d.\n\n", v);
      ShowTree(m);
   }
}
于 2011-05-11T22:15:01.977 に答える
14

この増分中央値推定量を使用します。

median += eta * sgn(sample - median)

これは、より一般的な平均推定量と同じ形式です。

mean += eta * (sample - mean)

ここで、 etaは小さな学習率パラメータ (例: 0.001) であり、sgn()のいずれかを返す符号関数です{-1, 0, 1}eta(データが非定常であり、時間の経過に伴う変化を追跡したい場合は、このような定数を使用します。それ以外の場合、定常ソースの場合は、これまでに見られたサンプルの数eta = 1 / nを収束させるために何かを使用します。)n

また、中央値推定量を修正して、任意の分位数で機能するようにしました。一般に、分位関数pは、データをとの 2 つの分数に分割する値を示します1 - p。以下は、この値を段階的に推定します。

quantile += eta * (sgn(sample - quantile) + 2.0 * p - 1.0)

pは 以内である必要があります[0, 1]sgn()これは基本的に、関数の対称出力{-1, 0, 1}を片側に傾けるようにシフトし、データ サンプルを 2 つの等しくないサイズのビンに分割します (データの分数pと分数1 - pは、それぞれ分位推定値より小さい/大きい)。p = 0.5の場合、これは推定量の中央値に還元されることに注意してください。

于 2011-09-20T13:13:06.447 に答える
9

量子化されたデータの簡単なアルゴリズムを次に示します (数か月後)。

""" median1.py: moving median 1d for quantized, e.g. 8-bit data

Method: cache the median, so that wider windows are faster.
    The code is simple -- no heaps, no trees.

Keywords: median filter, moving median, running median, numpy, scipy

See Perreault + Hebert, Median Filtering in Constant Time, 2007,
    http://nomis80.org/ctmf.html: nice 6-page paper and C code,
    mainly for 2d images

Example:
    y = medians( x, window=window, nlevel=nlevel )
    uses:
    med = Median1( nlevel, window, counts=np.bincount( x[0:window] ))
    med.addsub( +, - )  -- see the picture in Perreault
    m = med.median()  -- using cached m, summ

How it works:
    picture nlevel=8, window=3 -- 3 1s in an array of 8 counters:
        counts: . 1 . . 1 . 1 .
        sums:   0 1 1 1 2 2 3 3
                        ^ sums[3] < 2 <= sums[4] <=> median 4
        addsub( 0, 1 )  m, summ stay the same
        addsub( 5, 1 )  slide right
        addsub( 5, 6 )  slide left

Updating `counts` in an `addsub` is trivial, updating `sums` is not.
But we can cache the previous median `m` and the sum to m `summ`.
The less often the median changes, the faster;
so fewer levels or *wider* windows are faster.
(Like any cache, run time varies a lot, depending on the input.)

See also:
    scipy.signal.medfilt -- runtime roughly ~ window size
    http://stackoverflow.com/questions/1309263/rolling-median-algorithm-in-c

"""

from __future__ import division
import numpy as np  # bincount, pad0

__date__ = "2009-10-27 oct"
__author_email__ = "denis-bz-py at t-online dot de"


#...............................................................................
class Median1:
    """ moving median 1d for quantized, e.g. 8-bit data """

    def __init__( s, nlevel, window, counts ):
        s.nlevel = nlevel  # >= len(counts)
        s.window = window  # == sum(counts)
        s.half = (window // 2) + 1  # odd or even
        s.setcounts( counts )

    def median( s ):
        """ step up or down until sum cnt to m-1 < half <= sum to m """
        if s.summ - s.cnt[s.m] < s.half <= s.summ:
            return s.m
        j, sumj = s.m, s.summ
        if sumj <= s.half:
            while j < s.nlevel - 1:
                j += 1
                sumj += s.cnt[j]
                # print "j sumj:", j, sumj
                if sumj - s.cnt[j] < s.half <= sumj:  break
        else:
            while j > 0:
                sumj -= s.cnt[j]
                j -= 1
                # print "j sumj:", j, sumj
                if sumj - s.cnt[j] < s.half <= sumj:  break
        s.m, s.summ = j, sumj
        return s.m

    def addsub( s, add, sub ):
        s.cnt[add] += 1
        s.cnt[sub] -= 1
        assert s.cnt[sub] >= 0, (add, sub)
        if add <= s.m:
            s.summ += 1
        if sub <= s.m:
            s.summ -= 1

    def setcounts( s, counts ):
        assert len(counts) <= s.nlevel, (len(counts), s.nlevel)
        if len(counts) < s.nlevel:
            counts = pad0__( counts, s.nlevel )  # numpy array / list
        sumcounts = sum(counts)
        assert sumcounts == s.window, (sumcounts, s.window)
        s.cnt = counts
        s.slowmedian()

    def slowmedian( s ):
        j, sumj = -1, 0
        while sumj < s.half:
            j += 1
            sumj += s.cnt[j]
        s.m, s.summ = j, sumj

    def __str__( s ):
        return ("median %d: " % s.m) + \
            "".join([ (" ." if c == 0 else "%2d" % c) for c in s.cnt ])

#...............................................................................
def medianfilter( x, window, nlevel=256 ):
    """ moving medians, y[j] = median( x[j:j+window] )
        -> a shorter list, len(y) = len(x) - window + 1
    """
    assert len(x) >= window, (len(x), window)
    # np.clip( x, 0, nlevel-1, out=x )
        # cf http://scipy.org/Cookbook/Rebinning
    cnt = np.bincount( x[0:window] )
    med = Median1( nlevel=nlevel, window=window, counts=cnt )
    y = (len(x) - window + 1) * [0]
    y[0] = med.median()
    for j in xrange( len(x) - window ):
        med.addsub( x[j+window], x[j] )
        y[j+1] = med.median()
    return y  # list
    # return np.array( y )

def pad0__( x, tolen ):
    """ pad x with 0 s, numpy array or list """
    n = tolen - len(x)
    if n > 0:
        try:
            x = np.r_[ x, np.zeros( n, dtype=x[0].dtype )]
        except NameError:
            x += n * [0]
    return x

#...............................................................................
if __name__ == "__main__":
    Len = 10000
    window = 3
    nlevel = 256
    period = 100

    np.set_printoptions( 2, threshold=100, edgeitems=10 )
    # print medians( np.arange(3), 3 )

    sinwave = (np.sin( 2 * np.pi * np.arange(Len) / period )
        + 1) * (nlevel-1) / 2
    x = np.asarray( sinwave, int )
    print "x:", x
    for window in ( 3, 31, 63, 127, 255 ):
        if window > Len:  continue
        print "medianfilter: Len=%d window=%d nlevel=%d:" % (Len, window, nlevel)
            y = medianfilter( x, window=window, nlevel=nlevel )
        print np.array( y )

# end median1.py
于 2009-10-26T15:20:28.667 に答える
2

ストリーム内のすべての値が (比較的) 小さい定義された範囲内の整数である場合、単純な正確な解決策を持つ特別なケースがあることを指摘する価値があるかもしれません。たとえば、それらがすべて 0 から 1023 の間になければならないと仮定します。この場合、1024 要素の配列とカウントを定義し、これらの値をすべてクリアします。ストリーム内の値ごとに、対応するビンとカウントがインクリメントされます。ストリームが終了したら、count/2 の最大値を含むビンを見つけます。これは、0 から始まる連続するビンを追加することで簡単に実現できます。同じ方法を使用して、任意の順位の値を見つけることができます。(ビンの飽和を検出し、実行中にストレージ ビンのサイズをより大きなタイプに「アップグレード」する必要がある場合は、多少複雑になります。)

この特殊なケースは人為的に見えるかもしれませんが、実際には非常に一般的です。実数が範囲内にあり、「十分な」レベルの精度がわかっている場合は、実数の近似値として適用することもできます。これは、「実世界」のオブジェクトのグループに対するほとんどすべての測定セットに当てはまります。たとえば、人々のグループの身長や体重です。十分な大きさのセットではありませんか?地球上のすべての(個々の)バクテリアの長さや重量についても同様に機能します-誰かがデータを提供できると仮定して!

オリジナルを読み間違えたようです - 非常に長いストリームの中央値ではなく、スライディング ウィンドウの中央値が必要なようです。このアプローチはまだそのために機能します。最初のウィンドウの最初の N 個のストリーム値を読み込み、次に N+1 番目のストリーム値について、対応するビンをインクリメントし、0 番目のストリーム値に対応するビンをデクリメントします。この場合、デクリメントを可能にするために最後の N 値を保持する必要があります。これは、サイズ N の配列を循環的にアドレス指定することによって効率的に行うことができます。中央値の位置は -2、-1、0、1 だけ変化する可能性があるためです。 、2 スライディング ウィンドウの各ステップで、各ステップの中央値まですべてのビンを合計する必要はありません。変更された側のビンに応じて「中央値ポインター」を調整するだけです。例えば、新しい値と削除される値の両方が現在の中央値を下回る場合、値は変更されません (オフセット = 0)。N が大きくなりすぎてメモリに保持できなくなると、このメソッドは機能しなくなります。

于 2015-05-19T17:55:44.510 に答える
1

値を時点の関数として参照できる場合は、値を置換してサンプリングし、ブートストラップを適用して信頼区間内でブートストラップされた中央値を生成できます。これにより、入力値をデータ構造に常にソートするよりも効率的に近似中央値を計算できる場合があります。

于 2009-08-21T02:26:48.350 に答える
0

これは、正確な出力が重要でない場合 (表示目的など) に使用できるものです。totalcount と lastmedian に加えて、newvalue が必要です。

{
totalcount++;
newmedian=lastmedian+(newvalue>lastmedian?1:-1)*(lastmedian==0?newvalue: lastmedian/totalcount*2);
}

page_display_time のようなものに対して非常に正確な結果を生成します。

ルール: 入力ストリームは、ページ表示時間の順でスムーズで、カウントが大きく (>30 など)、中央値がゼロでない必要があります。

例: ページ読み込み時間、800 アイテム、10ms...3000ms、平均 90ms、実際の中央値:11ms

30 回の入力後、中央値エラーは通常 <=20% (9ms..12ms) であり、ますます少なくなります。800 回の入力後、エラーは +-2% です。

同様のソリューションを持つ別の思想家はここにあります: Median Filter 超効率的な実装

于 2013-04-24T07:06:41.200 に答える
0

Java で実行中の中央値が必要な場合は、PriorityQueue が役に立ちます。O(log N) の挿入、O(1) の現在の中央値、および O(N) の削除。データの分布がわかっている場合は、これよりもはるかにうまくいく可能性があります。

public class RunningMedian {
  // Two priority queues, one of reversed order.
  PriorityQueue<Integer> lower = new PriorityQueue<Integer>(10,
          new Comparator<Integer>() {
              public int compare(Integer arg0, Integer arg1) {
                  return (arg0 < arg1) ? 1 : arg0 == arg1 ? 0 : -1;
              }
          }), higher = new PriorityQueue<Integer>();

  public void insert(Integer n) {
      if (lower.isEmpty() && higher.isEmpty())
          lower.add(n);
      else {
          if (n <= lower.peek())
              lower.add(n);
          else
              higher.add(n);
          rebalance();
      }
  }

  void rebalance() {
      if (lower.size() < higher.size() - 1)
          lower.add(higher.remove());
      else if (higher.size() < lower.size() - 1)
          higher.add(lower.remove());
  }

  public Integer getMedian() {
      if (lower.isEmpty() && higher.isEmpty())
          return null;
      else if (lower.size() == higher.size())
          return (lower.peek() + higher.peek()) / 2;
      else
          return (lower.size() < higher.size()) ? higher.peek() : lower
                  .peek();
  }

  public void remove(Integer n) {
      if (lower.remove(n) || higher.remove(n))
          rebalance();
  }
}
于 2012-02-28T06:41:21.653 に答える
-1

これがJavaの実装です

package MedianOfIntegerStream;

import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;


public class MedianOfIntegerStream {

    public Set<Integer> rightMinSet;
    public Set<Integer> leftMaxSet;
    public int numOfElements;

    public MedianOfIntegerStream() {
        rightMinSet = new TreeSet<Integer>();
        leftMaxSet = new TreeSet<Integer>(new DescendingComparator());
        numOfElements = 0;
    }

    public void addNumberToStream(Integer num) {
        leftMaxSet.add(num);

        Iterator<Integer> iterMax = leftMaxSet.iterator();
        Iterator<Integer> iterMin = rightMinSet.iterator();
        int maxEl = iterMax.next();
        int minEl = 0;
        if (iterMin.hasNext()) {
            minEl = iterMin.next();
        }

        if (numOfElements % 2 == 0) {
            if (numOfElements == 0) {
                numOfElements++;
                return;
            } else if (maxEl > minEl) {
                iterMax.remove();

                if (minEl != 0) {
                    iterMin.remove();
                }
                leftMaxSet.add(minEl);
                rightMinSet.add(maxEl);
            }
        } else {

            if (maxEl != 0) {
                iterMax.remove();
            }

            rightMinSet.add(maxEl);
        }
        numOfElements++;
    }

    public Double getMedian() {
        if (numOfElements % 2 != 0)
            return new Double(leftMaxSet.iterator().next());
        else
            return (leftMaxSet.iterator().next() + rightMinSet.iterator().next()) / 2.0;
    }

    private class DescendingComparator implements Comparator<Integer> {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    }

    public static void main(String[] args) {
        MedianOfIntegerStream streamMedian = new MedianOfIntegerStream();

        streamMedian.addNumberToStream(1);
        System.out.println(streamMedian.getMedian()); // should be 1

        streamMedian.addNumberToStream(5);
        streamMedian.addNumberToStream(10);
        streamMedian.addNumberToStream(12);
        streamMedian.addNumberToStream(2);
        System.out.println(streamMedian.getMedian()); // should be 5

        streamMedian.addNumberToStream(3);
        streamMedian.addNumberToStream(8);
        streamMedian.addNumberToStream(9);
        System.out.println(streamMedian.getMedian()); // should be 6.5
    }
}
于 2016-08-07T06:15:13.993 に答える
-6

平滑化された平均が必要な場合は、最新の値に x を掛け、平均値に (1-x) を掛けて、それらを加算するのが手早く簡単な方法です。これが新しい平均になります。

編集: ユーザーが要求したものではなく、統計的に有効ではありませんが、多くの用途には十分です。
検索のために(反対票にもかかわらず)ここに残します!

于 2009-08-21T02:13:07.403 に答える