現在、Cでローリングメジアンフィルター(ローリング平均フィルターに類似)を実装するアルゴリズムに取り組んでいます。文献の調査から、2つの合理的に効率的な方法があります。最初の方法は、値の初期ウィンドウをソートしてから、バイナリ検索を実行して新しい値を挿入し、各反復で既存の値を削除することです。
2番目(Hardle and Steiger、1995、JRSS-C、アルゴリズム296から)は、片端に最大ヒープ、もう片端に最小ヒープ、中央に中央値を持つ両端ヒープ構造を構築します。これにより、O(n log n)のアルゴリズムの代わりに線形時間アルゴリズムが生成されます。
ここに私の問題があります:前者を実装することは実行可能ですが、何百万もの時系列でこれを実行する必要があるため、効率が非常に重要です。後者は実装が非常に難しいことが証明されています。 Rの統計パッケージのコードの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
Rのsrc/library/stats/src/Trunmed.c
スタンドアローンのC++クラス/ Cサブルーチンでも似たようなものが欲しかったので数回。これは実際には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ビットの一部を手伝うことができます。
Edit 1:上記の古いバージョンのTrunmed.cへのリンクのほかに、現在のSVNコピーがあります
Edit 2:Ryan Tibshiraniには 高速中央値ビニング にCおよびFortranコードがあります。アプローチ。
順序統計を使用したc ++データ構造の最新の実装を見つけることができなかったため、MAKが推奨するトップコーダーリンクで両方のアイデアを実装することになりました( Match Editorial :FloatingMedianまでスクロールします)。
最初のアイデアは、挿入/削除ごとにO(ln N)を使用してデータを2つのデータ構造(ヒープ、マルチセットなど)に分割するため、大きなコストをかけずに変位値を動的に変更することはできません。つまりローリング中央値、またはローリング75%を使用できますが、両方を同時に使用することはできません。
2番目のアイデアでは、挿入/削除/クエリにO(ln N)のセグメントツリーを使用しますが、より柔軟です。何よりも「N」は、データ範囲のサイズです。したがって、ローリング中央値に100万アイテムのウィンドウがあり、データが1..65536から変動する場合、100万ローリングウィンドウの移動ごとに必要な操作は16だけです!!
C++コードは、Denisが上記に投稿したものと似ています(「量子化されたデータの簡単なアルゴリズムです」)
あきらめる直前に、stdlibc ++には順序統計ツリーが含まれていることがわかりました!!!
これらには2つの重要な操作があります。
iter = tree.find_by_order(value)
order = tree.order_of_key(value)
libstdc ++ manual 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
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);
}
}
この増分中央値推定器を使用します。
_median += eta * sgn(sample - median)
_
より一般的な平均推定量と同じ形式を持ちます:
_mean += eta * (sample - mean)
_
ここでetaは小さな学習率パラメーター(例えば_0.001
_)であり、sgn()
は1を返すsignum関数です。 _{-1, 0, 1}
_の。 (データが非定常であり、経時的な変化を追跡する場合は、このような定数eta
を使用します。そうでない場合、定常ソースの場合は_eta = 1 / n
_などを使用して収束します。ここで、n
はこれまでに見られたサンプルの数。)
また、中央値推定器を変更して、任意の分位数で機能するようにしました。一般的に、 分位関数 は、データを2つの小数に分割する値を示します:p
および_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
_の場合、これは中央値推定器に減少することに注意してください。
以下は、量子化されたデータの簡単なアルゴリズムです(数か月後):
""" 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 __== "__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
ローリング中央値は、数値の2つのパーティションを維持することで見つけることができます。
パーティションを維持するには、最小ヒープと最大ヒープを使用します。
最大ヒープには、中央値以下の数値が含まれます。
最小ヒープには、中央値以上の数値が含まれます。
Balancing Constraint:要素の総数が偶数の場合、両方のヒープに同じ要素が必要です。
要素の総数が奇数の場合、最大ヒープには最小ヒープよりも要素が1つ多くなります。
中央値要素:両方のパーティションの要素数が等しい場合、中央値は最初のパーティションの最大要素と2番目のパーティションの最小要素の合計の半分になります。
それ以外の場合、中央値は最初のパーティションの最大要素になります。
アルゴリズム- 1- 2つのヒープ(1最小ヒープと1最大ヒープ)を取る 最大ヒープには要素の前半数が含まれます 最小ヒープには2番目の要素が含まれます要素の半数 2-ストリームからの新しい数値を最大ヒープの最上部と比較し、 より小さいか等しい場合は、その数値を最大ヒープに追加します。 それ以外の場合、最小ヒープに数値を追加します。 3-最小ヒープに最大ヒープよりも多くの要素がある場合 次に、最小ヒープの最上位要素を削除して、最大ヒープに追加します。 最大ヒープに最小ヒープ よりも複数の要素がある場合、最大ヒープの最上位要素を削除して最小ヒープに追加します。 4- If Both heapsの要素数が等しい場合、 medianはMax Heapの最大要素とMin Heapの最小要素の合計の半分になります。 それ以外の場合、中央値は最初のパーティションの最大要素になります。
public class Solution {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
RunningMedianHeaps s = new RunningMedianHeaps();
int n = in.nextInt();
for(int a_i=0; a_i < n; a_i++){
printMedian(s,in.nextInt());
}
in.close();
}
public static void printMedian(RunningMedianHeaps s, int nextNum){
s.addNumberInHeap(nextNum);
System.out.printf("%.1f\n",s.getMedian());
}
}
class RunningMedianHeaps{
PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>();
PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(Comparator.reverseOrder());
public double getMedian() {
int size = minHeap.size() + maxHeap.size();
if(size % 2 == 0)
return (maxHeap.peek()+minHeap.peek())/2.0;
return maxHeap.peek()*1.0;
}
private void balanceHeaps() {
if(maxHeap.size() < minHeap.size())
{
maxHeap.add(minHeap.poll());
}
else if(maxHeap.size() > 1+minHeap.size())
{
minHeap.add(maxHeap.poll());
}
}
public void addNumberInHeap(int num) {
if(maxHeap.size()==0 || num <= maxHeap.peek())
{
maxHeap.add(num);
}
else
{
minHeap.add(num);
}
balanceHeaps();
}
}
ある時点の関数として値を参照する機能がある場合、置換で値をサンプリングし、 bootstrapping を適用して、信頼区間内でブートストラップされた中央値を生成できます。これにより、入力値をデータ構造に絶えずソートするよりも効率的に近似中央値を計算できます。
ストリームのすべての値が(比較的)小さい定義された範囲内の整数である場合、単純で正確な解決策がある特別なケースがあることを指摘する価値があるかもしれません。たとえば、それらはすべて0〜1023の間にある必要があると仮定します。この場合、1024個の要素の配列とカウントを定義し、これらの値をすべてクリアします。ストリームの値ごとに、対応するビンとカウントを増やします。ストリームが終了した後、count/2の最高値を含むビンを見つけます。0から始まる連続するビンを追加することで簡単に達成できます。同じ方法を使用して、任意のランク順の値を見つけることができます。 (実行中にビンの飽和を検出し、棚番のサイズをより大きなタイプに「アップグレード」する必要がある場合は、若干の複雑さがあります。)
この特殊なケースは人為的に見えるかもしれませんが、実際には非常に一般的です。実数が範囲内にあり、「十分な」精度レベルがわかっている場合、実数の近似として適用することもできます。これは、「現実世界」のオブジェクトのグループに関するほとんどすべての測定セットに当てはまります。たとえば、人々のグループの身長または体重。十分な大きさではありませんか?地球上のすべての(個々の)バクテリアの長さや重量に対しても同様に機能します-誰かがデータを提供できると仮定して!
私はオリジナルを読み違えているように見えます-非常に長いストリームの中央値だけでなく、スライディングウィンドウの中央値が必要なようです。このアプローチはそれでも有効です。最初のウィンドウの最初のN個のストリーム値を読み込み、次にN + 1番目のストリーム値で対応するビンをインクリメントし、0番目のストリーム値に対応するビンをデクリメントします。この場合、最後のN個の値を保持して減分できるようにする必要があります。これは、サイズNの配列を周期的にアドレス指定することで効率的に実行できます。中央値の位置は-2、-1,0,1 、2スライディングウィンドウの各ステップでは、各ステップの中央値までのすべてのビンを合計する必要はなく、どちらのビンが変更されたかに応じて「中央値ポインタ」を調整するだけです。たとえば、新しい値と削除される値の両方が現在の中央値を下回る場合、値は変化しません(オフセット= 0)。 Nが大きくなりすぎてメモリに保持できない場合、メソッドは失敗します。
Javaで実行中の中央値が必要な場合は、PriorityQueueが最適です。 O(log N)insert、O(1)現在の中央値、およびO(N) remove。データの分布がわかっている場合は、実行できます。これよりもずっといい。
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();
}
}
正確な出力が重要でない場合(表示などのため)に使用できるのは、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%未満(9ミリ秒から12ミリ秒)であり、ますます少なくなります。 800入力後、エラーは+ -2%です。
同様の解決策を持つ別の思想家はここにあります: メディアンフィルター超効率的な実装