Скользящий медианный алгоритм в C - PullRequest
108 голосов
/ 21 августа 2009

В настоящее время я работаю над алгоритмом реализации скользящего медианного фильтра (аналога скользящего среднего фильтра) в C. Из моего поиска литературы, похоже, есть два достаточно эффективных способа сделать это. Первый - отсортировать начальное окно значений, затем выполнить двоичный поиск, чтобы вставить новое значение и удалить существующее на каждой итерации.

Второй (от Hardle and Steiger, 1995, JRSS-C, Algorithm 296) строит структуру кучи с двумя концами, с maxheap на одном конце, minheap на другом и медианой в середине. Это дает алгоритм с линейным временем вместо алгоритма O (n log n).

Вот моя проблема: реализация первого выполнима, но мне нужно выполнить это на миллионах временных рядов, поэтому эффективность очень важна. Последнее оказывается очень сложным для реализации. Я нашел код в файле Trunmed.c кода для пакета статистики R, но он довольно не поддается расшифровке.

Кто-нибудь знает хорошо написанную реализацию 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

Ответы [ 12 ]

25 голосов
/ 21 августа 2009

Я смотрел на R src/library/stats/src/Trunmed.c несколько раз, так как хотел что-то похожее и в отдельной подпрограмме класса C ++ / C. Обратите внимание, что на самом деле это две реализации в одной, см. 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

  • Srunmed.c (для версии Stuetzle)
  • Trunmed.c (для версии Turlach)
  • runmed.R для функции R, вызывающей эти

Редактировать 2 : у Райана Тибширани есть несколько кодов C и Fortran для быстрого медианного биннинга , что может быть подходящей отправной точкой для оконного подхода.

16 голосов
/ 27 июня 2012

Я не смог найти современную реализацию структуры данных c ++ с упорядоченной статистикой, поэтому в итоге реализовал обе идеи в ссылке top coders, предложенной MAK ( Соответствие редакции : прокрутите вниз до FloatingMedian).

Два мультимножества

Первая идея разбивает данные на две структуры данных (кучи, мультимножества и т. Д.) С O (ln N) на вставку / удаление не позволяет динамически изменять квантиль без больших затрат. То есть у нас может быть скользящий медиан или 75%, но не оба одновременно.

Сегментное дерево

Вторая идея использует дерево сегментов, которое O (ln N) для вставки / удаления / запросов, но является более гибким. Лучше всего "N" - размер вашего диапазона данных. Таким образом, если у вашей скользящей медианы есть окно из миллиона элементов, но ваши данные варьируются от 1..65536, то для каждого движения скользящего окна в 1 миллион требуется только 16 операций !!

Код на С ++ похож на тот, который Денис выложил выше («Вот простой алгоритм для квантованных данных»)

Деревья статистики заказов GNU

Непосредственно перед тем, как сдаться, я обнаружил, что stdlibc ++ содержит деревья статистики заказов !!!

У них есть две критические операции:

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

См. руководство по libstdc ++ policy_based_data_structures_test (поиск "разбить и объединить").

Я обернул дерево для использования в вспомогательном заголовке для компиляторов, поддерживающих частичные определения типов в стиле c ++ 0x / c ++ 11:

#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
14 голосов
/ 12 мая 2011

Я сделал 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);
   }
}
8 голосов
/ 20 сентября 2011

Я использую эту инкрементальную срединную оценку:

median += eta * sgn(sample - median)

, которая имеет ту же форму, что и более распространенная средняя оценка:

mean += eta * (sample - mean)

Здесь eta - это параметр небольшой скорости обучения (например, 0.001), а sgn() - это функция signum, которая возвращает один из {-1, 0, 1}. (Используйте константу eta, например, если данные нестационарны, и вы хотите отслеживать изменения во времени; в противном случае для стационарных источников используйте что-то вроде eta = 1 / n для конвергенции, где n - это количество выборок, видимых так, далеко.)

Кроме того, я изменил срединную оценку, чтобы она работала для произвольных квантилей. Как правило, квантильная функция сообщает вам значение, которое делит данные на две фракции: p и 1 - p. Следующие оценки это значение постепенно:

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

Значение p должно быть в пределах [0, 1]. Это существенно смещает симметричный выход sgn() функции {-1, 0, 1}, чтобы наклониться к одной стороне, разбивая выборки данных на две ячейки неравного размера (доли p и 1 - p данных меньше / больше, чем квантильная оценка соответственно). Обратите внимание, что для p = 0.5 это сводится к медианной оценке.

8 голосов
/ 26 октября 2009

Вот простой алгоритм для квантованных данных (месяцы спустя):

""" 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
    /919120/skolzyaschii-mediannyi-algoritm-v-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
3 голосов
/ 19 февраля 2017

Скользящее медиану можно найти, поддерживая два разбиения чисел.

Для поддержки разделов используйте Min Heap и Max Heap.

Max Heap будет содержать числа, меньшие, чем равные медиане.

Min Heap будет содержать числа больше, чем равно медиане.

Ограничение баланса: если общее количество элементов четное, то обе кучи должны иметь одинаковые элементы.

если общее количество элементов нечетное, то в Max Heap будет на один элемент больше, чем в Min Heap.

Медианный элемент: Если в обоих разделах одинаковое количество элементов, медиана будет равна половине суммы максимального элемента из первого раздела и минимального элемента из второго раздела.

В противном случае медиана будет максимальным элементом из первого раздела.

Algorithm-
1- Take two Heap(1 Min Heap and 1 Max Heap)
   Max Heap will contain first half number of elements
   Min Heap will contain second half number of elements

2- Compare new number from stream with top of Max Heap, 
   if it is smaller or equal add that number in max heap. 
   Otherwise add number in Min Heap.

3- if min Heap has more elements than Max Heap 
   then remove top element of Min Heap and add in Max Heap.
   if max Heap has more than one element than in Min Heap 
   then remove top element of Max Heap and add in Min Heap.

4- If Both heaps has equal number of elements then
   median will be half of sum of max element from Max Heap and min element from Min Heap.
   Otherwise median will be max element from the first partition.
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();
    }
}
1 голос
/ 19 мая 2015

Возможно, стоит указать, что существует особый случай, который имеет простое точное решение: когда все значения в потоке являются целыми числами в (относительно) небольшом определенном диапазоне. Например, предположим, что все они должны находиться в диапазоне от 0 до 1023. В этом случае просто определите массив из 1024 элементов и счетчик и очистите все эти значения. Для каждого значения в потоке увеличивается значение соответствующего бина и счетчика. После окончания потока найдите ячейку, которая содержит наибольшее значение count / 2 - это легко сделать, добавив последовательные ячейки, начиная с 0. Используя тот же метод, можно найти значение произвольного рангового порядка. (Существует небольшая сложность, если потребуется определить насыщенность бункера и «обновить» размер бункеров до большего типа во время выполнения.)

Этот особый случай может показаться искусственным, но на практике он очень распространен. Это также может быть применено в качестве приближения для действительных чисел, если они лежат в пределах диапазона, и известен «достаточно хороший» уровень точности. Это будет справедливо для практически любого набора измерений группы объектов «реального мира». Например, высоты или веса группы людей. Не достаточно большой набор? Это сработало бы точно так же для длины или веса всех (отдельных) бактерий на планете - при условии, что кто-то может предоставить данные!

Похоже, я неправильно истолковал оригинал - кажется, что он хочет получить медиану скользящего окна вместо медианы очень длинного потока. Этот подход все еще работает для этого. Загрузите первые N потоковых значений для начального окна, затем для N + 1-го значения потока увеличьте соответствующий бин при уменьшении бина, соответствующего 0-му значению потока. В этом случае необходимо сохранить последние N значений, чтобы разрешить уменьшение, что может быть эффективно выполнено путем циклического обращения к массиву размера N. Поскольку положение медианы может изменяться только на -2, -1,0,1 , 2 на каждом шаге скользящего окна, нет необходимости суммировать все ячейки до медианы на каждом шаге, просто отрегулируйте «указатель медианы» в зависимости от того, какие ячейки боковой стороны были изменены. Например, если и новое значение, и удаляемое значение упадут ниже текущей медианы, оно не изменится (смещение = 0). Метод ломается, когда N становится слишком большим, чтобы удобно держать его в памяти.

1 голос
/ 28 февраля 2012

Для тех, кому нужна работающая медиана в 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();
  }
}
1 голос
/ 21 августа 2009

Если у вас есть возможность ссылаться на значения как функцию от точек во времени, вы можете выбрать значения с заменой, применив bootstrapping , чтобы сгенерировать медиану с начальной загрузкой в ​​пределах доверительных интервалов. Это может позволить вам вычислить приблизительную медиану с большей эффективностью, чем постоянная сортировка входящих значений в структуру данных.

0 голосов
/ 07 августа 2016

Вот реализация 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
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...