Ortalama mutlak sapma ve büyük veri seti için çevrimiçi algoritma


16

Beni çıldırtan küçük bir problemim var. Çok değişkenli bir zaman serisinin çevrimiçi bir edinim süreci için prosedür yazmam gerekiyor. Her zaman aralığında (örneğin 1 saniye), temelde N boyutunda kayan nokta vektörü olan yeni bir örnek alıyorum. Yapmam gereken işlem biraz zor:

  1. Her yeni örnek için, o örnek için yüzdelik değerleri hesaplarım (vektörü normalleştirerek elemanların 1'e çıkmasını sağlayarak).

  2. Ortalama yüzdelikleri vektörü aynı şekilde, ancak geçmiş değerleri kullanarak hesaplıyorum.

  3. Geçmişteki her bir değer için, o örnekle ilgili yüzdelik vektörünün 2. adımda hesaplanan küresel ortalama yüzdelik vektörü ile mutlak sapmasını hesaplıyorum. Bu şekilde, mutlak sapma her zaman 0 arasında bir sayıdır (vektör ortalamaya eşit olduğunda) vektör) ve 2 (tamamen farklı olduğunda).

  4. Önceki tüm numuneler için sapmaların ortalamasını kullanarak, ortalama 0 ve 2 arasındaki bir sayı olan ortalama mutlak sapmayı hesaplıyorum.

  5. Yeni bir örneğin diğer örneklerle uyumlu olup olmadığını saptamak için ortalama mutlak sapmayı kullanıyorum (mutlak sapmasını adım 4'te hesaplanan tüm setin ortalama mutlak sapması ile karşılaştırarak).

Her yeni örnek toplandığında küresel ortalama değişimler (ve böylece ortalama mutlak sapma da değişiyor), tüm veri kümesini birden çok kez taramadan bu değeri hesaplamanın bir yolu var mı? (bir kez küresel ortalama yüzdelikleri hesaplamak için bir kez ve mutlak sapmaları toplamak için bir kez). Tamam, biliyorum ki tüm seti taramadan küresel ortalamaları hesaplamak kesinlikle kolaydır, çünkü sadece her bir boyutun toplamını saklamak için geçici bir vektör kullanmak zorundayım, ama ortalama mutlak sapma ne olacak? Hesaplaması abs()operatörü içerir , bu yüzden tüm geçmiş verilere erişmem gerekiyor!

Yardımın için teşekkürler.

Yanıtlar:


6

Bazı yanlışlıkları kabul edebiliyorsanız, bu sayı binning sayıları ile kolayca çözülebilir . Yani, bir miktar büyük numarası seçin ( m = 1000 diyelim ), sonra i = 1 M ve j = 1 N için B i , j bazı tamsayı bölmelerini başlatın , burada N sıfır olarak vektör boyutudur. Görmek Sonra k bir Percentual vektörün inci gözlem, artım B i , j ise j Bu vektörün inci elemanı arasındaMM=1000Bi,ji=1Mj=1NNkBi,jj ve i / M ,vektörün N elemanlarıüzerinde ilmek. (Giriş vektörlerinizin negatif olmadığını varsayıyorum, böylece 'yüzdelik değerlerinizi' hesapladığınızda, vektörler [ 0 , 1 ] aralığındadır.)(i1)/Mi/MN[0,1]

Herhangi bir zamanda, kutulardaki ortalama vektörü ve ortalama mutlak sapmayı tahmin edebilirsiniz. İnceledikten sonra tür vektörler, J ortalama elemanı inci tahmin edilir ˉ x j = 1Kjvej,ortalama mutlak sapma inci elemanı ile tahmin edilir1

X¯j=1Kii1/2MBi,j,
j
1Ki|Xj¯i1/2M|Bi,j

edit : Bu, ampirik yoğunluk tahmini oluşturduğunuz daha genel bir yaklaşımın özel bir örneğidir. Bu polinomlar, kamalar, vb.


Vay, gerçekten ilginç bir yaklaşım. Bunu bilmiyordum ve bunu aklımda tutacağım. Ne yazık ki, bu durumda çalışmaz, çünkü bellek kullanımı açısından gerçekten kısıtlayıcı gereksinimlerim var, bu yüzden M gerçekten küçük olmalı ve çok fazla hassasiyet kaybı olacağını tahmin ediyorum.
gianluca

@gianluca: 1. çok fazla veri, 2. sınırlı bellek kaynakları, 3. yüksek hassasiyet gereksinimleri gibi geliyor. Bu sorunun neden sizi korkuttuğunu görebiliyorum! Belki de @kwak tarafından belirtildiği gibi, başka bir yayılma ölçüsü hesaplayabilirsiniz: MAD, IQR, standart sapma. Bunların hepsinde sorununuz için işe yarayacak yaklaşımlar var.
shabbychef

gianluca:> Bize istediğiniz bellek boyutu, diziler ve doğruluk hakkında daha nicel fikir verin. Sorunuz en iyi şekilde @ stackoverflow olsa da cevaplanmış olabilir.
user603

4

Geçmişte abartı sapmasını orta derecede verimli bir şekilde hesaplamak için aşağıdaki yaklaşımı kullandım (not, bu bir programcı yaklaşımı, bir istatistikçi değil, bu yüzden şüphesiz shabbychef'ler gibi akıllı hileler olabilir daha etkili olabilecek olabilir).

UYARI: Bu çevrimiçi bir algoritma değildir. O(n)Bellek gerektirir . Ayrıca, en kötü durum performansına sahiptir.O(n) gibi veri kümeleri için[1, -2, 4, -8, 16, -32, ...] (yani tam yeniden hesaplama ile aynı). [1]

Bununla birlikte, birçok kullanım durumunda hala iyi performans gösterdiğinden, burada göndermeye değer olabilir. Örneğin, 10000 rasgele mutlak sapmayı hesaplamak için her öğe geldiğinde -100 ile 100 arasında sayının algoritmam bir saniyeden az sürer, tam yeniden hesaplama 17 saniyeyi alır (makinemde makine ve giriş verilerine göre). Bununla birlikte, tüm vektörleri bellekte tutmanız gerekir; bu, bazı kullanımlar için bir kısıtlama olabilir. Algoritmanın ana hatları aşağıdaki gibidir:

  1. Geçmiş ölçümleri saklamak için tek bir vektöre sahip olmak yerine, üç sıralı öncelik sırası kullanın (min / maks öbek gibi bir şey). Bu üç liste girdiyi üçe ayırır: ortalamadan büyük öğeler, ortalamadan küçük öğeler ve ortalamaya eşit öğeler.
  2. (Neredeyse) her ürün eklediğinizde ortalama değişiklikler olur, bu yüzden bölümlere ayırmamız gerekir. Önemli olan bölümlerin sıralı doğasıdır, yani listedeki her öğeyi yeniden parçalanmaya taramak yerine, yalnızca taşıdığımız öğeleri okumamız gerekir. En kötü durumda bu yine de gerektirirO(n) hareket operasyonları , ancak birçok kullanım durumunda bu böyle değildir.
  3. Bazı akıllı defter tutma yöntemlerini kullanarak, yeniden bölümleme yaparken ve yeni öğeler eklerken sapmanın her zaman doğru hesaplandığından emin olabiliriz.

Python'da bazı örnek kodlar aşağıdadır. Yalnızca öğelerin listeye eklenmesine izin verildiğini, kaldırılmadığını unutmayın. Bu kolayca eklenebilir, ancak bunu yazdığım zaman buna ihtiyacım yoktu. Aksine öncelikli kuyruklar kendim uygulamak yerine, ben kullandım SortedList Daniel Stutzbach mükemmel dan blist paketin kullanmak, B + Ağacı içten s.

MIT lisansı altında lisanslanan bu kodu göz önünde bulundurun . Önemli ölçüde optimize edilmemiş veya cilalanmamış, ancak geçmişte benim için çalıştı. Yeni sürümler burada mevcut olacak . Herhangi bir sorunuz varsa bize bildirin veya herhangi bir hata bulun.

from blist import sortedlist
import operator

class deviance_list:
    def __init__(self):
        self.mean =  0.0
        self._old_mean = 0.0
        self._sum =  0L
        self._n =  0  #n items
        # items greater than the mean
        self._toplist =  sortedlist()
        # items less than the mean
        self._bottomlist = sortedlist(key = operator.neg)
        # Since all items in the "eq list" have the same value (self.mean) we don't need
        # to maintain an eq list, only a count
        self._eqlistlen = 0

        self._top_deviance =  0
        self._bottom_deviance =  0

    @property
    def absolute_deviance(self):
        return self._top_deviance + self._bottom_deviance

    def append(self,  n):
        # Update summary stats
        self._sum += n
        self._n +=  1
        self._old_mean =  self.mean
        self.mean =  self._sum /  float(self._n)

        # Move existing things around
        going_up = self.mean > self._old_mean
        self._rebalance(going_up)

        # Add new item to appropriate list
        if n >  self.mean:
            self._toplist.add(n)
            self._top_deviance +=  n -  self.mean
        elif n == self.mean: 
            self._eqlistlen += 1
        else:
            self._bottomlist.add(n)
            self._bottom_deviance += self.mean -  n


    def _move_eqs(self,  going_up):
        if going_up:
            self._bottomlist.update([self._old_mean] *  self._eqlistlen)
            self._bottom_deviance += (self.mean - self._old_mean) * self._eqlistlen
            self._eqlistlen = 0
        else:
            self._toplist.update([self._old_mean] *  self._eqlistlen)
            self._top_deviance += (self._old_mean - self.mean) * self._eqlistlen
            self._eqlistlen = 0


    def _rebalance(self, going_up):
        move_count,  eq_move_count = 0, 0
        if going_up:
            # increase the bottom deviance of the items already in the bottomlist
            if self.mean !=  self._old_mean:
                self._bottom_deviance += len(self._bottomlist) *  (self.mean -  self._old_mean)
                self._move_eqs(going_up)


            # transfer items from top to bottom (or eq) list, and change the deviances
            for n in iter(self._toplist):
                if n < self.mean:
                    self._top_deviance -= n -  self._old_mean
                    self._bottom_deviance += (self.mean -  n)
                    # we increment movecount and move them after the list
                    # has finished iterating so we don't modify the list during iteration
                    move_count +=  1
                elif n == self.mean:
                    self._top_deviance -= n -  self._old_mean
                    self._eqlistlen += 1
                    eq_move_count +=  1
                else:
                    break
            for _ in xrange(0,  move_count):
                self._bottomlist.add(self._toplist.pop(0))
            for _ in xrange(0,  eq_move_count):
                self._toplist.pop(0)

            # decrease the top deviance of the items remain in the toplist
            self._top_deviance -= len(self._toplist) *  (self.mean -  self._old_mean)
        else:
            if self.mean !=  self._old_mean:
                self._top_deviance += len(self._toplist) *  (self._old_mean -  self.mean)
                self._move_eqs(going_up)
            for n in iter(self._bottomlist): 
                if n > self.mean:
                    self._bottom_deviance -= self._old_mean -  n
                    self._top_deviance += n -  self.mean
                    move_count += 1
                elif n == self.mean:
                    self._bottom_deviance -= self._old_mean -  n
                    self._eqlistlen += 1
                    eq_move_count +=  1
                else:
                    break
            for _ in xrange(0,  move_count):
                    self._toplist.add(self._bottomlist.pop(0))
            for _ in xrange(0,  eq_move_count):
                self._bottomlist.pop(0)

            # decrease the bottom deviance of the items remain in the bottomlist
            self._bottom_deviance -= len(self._bottomlist) *  (self._old_mean -  self.mean)


if __name__ ==  "__main__":
    import random
    dv =  deviance_list()
    # Test against some random data,  and calculate result manually (nb. slowly) to ensure correctness
    rands = [random.randint(-100,  100) for _ in range(0,  1000)]
    ns = []
    for n in rands: 
        dv.append(n)
        ns.append(n)
        print("added:%4d,  mean:%3.2f,  oldmean:%3.2f,  mean ad:%3.2f" %
              (n, dv.mean,  dv._old_mean,  dv.absolute_deviance / dv.mean))
        assert sum(ns) == dv._sum,  "Sums not equal!"
        assert len(ns) == dv._n,  "Counts not equal!"
        m = sum(ns) / float(len(ns))
        assert m == dv.mean,  "Means not equal!"
        real_abs_dev = sum([abs(m - x) for x in ns])
        # Due to floating point imprecision, we check if the difference between the
        # two ways of calculating the asb. dev. is small rather than checking equality
        assert abs(real_abs_dev - dv.absolute_deviance) < 0.01, (
            "Absolute deviances not equal. Real:%.2f,  calc:%.2f" %  (real_abs_dev,  dv.absolute_deviance))

[1] Semptomlar devam ederse, doktorunuza başvurun.


2
Bir şey eksik: Eğer "tüm vektörü bellekte tutmak" gerekiyorsa, bu nasıl bir "çevrimiçi" algoritma olarak nitelendirilir ??
whuber

@whuber Hayır, hiçbir şeyi kaçırmamak, sanırım çevrimiçi bir algoritma değil. O(n)Bellek gerektirir ve en kötü durumda eklenen her öğe için O (n) zaman alır. Normal olarak dağıtılan verilerde (ve muhtemelen diğer dağıtımlarda) oldukça verimli çalışır.
fmark

3

XXXss2/π


Bu ilginç bir fikir. Belki de, aykırı değerlerin çevrimiçi tespiti ile bunu tamamlayabilir ve tahminleri ilerlerken değiştirmek için kullanabilirsiniz.
whuber

İkinci cevabımda belgelediğim standart sapmayı çevrimiçi olarak hesaplamak için muhtemelen Welford'un yöntemini kullanabilirsiniz.
fmark

1
Bununla birlikte, bu yolla, bazen daha basit alternatiflere karşı seçimine yön veren açık MAD gibi tahmin edicilerin sağlamlığını kaybedebileceği unutulmamalıdır.
Kuvars

2

MAD (x) , her biri binmed algoritması ile çevrimiçi yapılabilen sadece iki eşzamanlı medyan hesaplamadır .

İlişkili makalenin yanı sıra C ve FORTRAN kodlarını çevrimiçi olarak burada bulabilirsiniz .

(bu sadece Shabbychef'in akıllı numarasının üstünde, hafızadan tasarruf etmek için akıllı bir numara kullanılmasıdır).

Zeyilname:

Kuantilleri hesaplamak için çok sayıda eski çok geçişli yöntem vardır. Popüler bir yaklaşım, akıştan rastgele seçilen deterministik olarak boyutlandırılmış bir gözlem rezervuarını korumak / güncellemek ve tekrar eden miktarları hesaplamaktır (bkz. bu rezervuar üzerinde bu incelemeye ) . Bu (ve ilgili) yaklaşımın yerini yukarıda önerilen yaklaşım alır.


MAD ve iki medyan arasındaki ilişkiyi detaylandırır veya referans verir misiniz?
Kuvars

medi=1n|ximedi=1n|

Hehm, aslında bu ilişkinin iki medyanın eşzamanlı olmasına nasıl izin verdiğini açıklayabilir misiniz? dış medyan girdilerin hepsi iç hesaplamaya eklenen her bir örnekte değişebileceğinden, bunlar bana bağımlı görünmektedir. Bunları paralel olarak nasıl yaparsınız?
Kuvars

medi=1nxixn+1medben=1n+1xbenÖ(n)xn+1 ait . Bu içgörünün çılgın hesaplamada dış medyanla nasıl genelleştirilemediğini görmüyorum.
user603 15:13

1

Doğru olmayan giriş verilerinin dağılımına bağlı olsa da, aşağıdakiler yanlış bir yaklaşım sağlar. Çevrimiçi bir algoritmadır, ancak yalnızca mutlak sapmaya yaklaşır. 1960'larda Welford tarafından açıklanan çevrimiçi varyansı hesaplamak için iyi bilinen bir algoritmaya dayanmaktadır . R'ye çevrilmiş algoritması şöyle görünür:

M2 <- 0
mean <- 0
n <- 0

var.online <- function(x){
    n <<- n + 1
    diff <- x - mean
    mean <<- mean + diff / n
    M2 <<- M2 + diff * (x - mean)
    variance <- M2 / (n - 1)
    return(variance)
}

R'nin yerleşik varyans fonksiyonuna çok benzer şekilde çalışır:

set.seed(2099)
n.testitems <- 1000
n.tests <- 100
differences <- rep(NA, n.tests)
for (i in 1:n.tests){
        # Reset counters
        M2 <- 0
        mean <- 0
        n <- 0

        xs <- rnorm(n.testitems)
        for (j in 1:n.testitems){
                v <- var.online(xs[j])
        }

        differences[i] <- abs(v - var(xs))

}
summary(differences)
     Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
0.000e+00 2.220e-16 4.996e-16 6.595e-16 9.992e-16 1.887e-15 

Mutlak sapmayı hesaplamak için algoritmayı değiştirmek ek bir sqrtçağrı gerektirir . Bununla birlikte, sqrtsonuca yansıyan yanlışlıklar ortaya çıkar:

absolute.deviance.online <- function(x){
    n <<- n + 1
    diff <- x - mean
    mean <<- mean + diff / n
    a.dev <<- a.dev + sqrt(diff * (x - mean))
    return(a.dev)
}

Yukarıdaki gibi hesaplanan hatalar, varyans hesaplamasından çok daha büyüktür:

    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
0.005126 0.364600 0.808000 0.958800 1.360000 3.312000 

Ancak, kullanım durumunuza bağlı olarak bu hata büyüklüğü kabul edilebilir.

farklılıkların tarihçesi


Bu, aşağıdaki nedenden dolayı kesin bir cevap vermez: ΣbenxbenΣbenxben. İlkini hesaplarken OP ikincisini istiyor.
shabbychef

Yöntemin hatalı olduğunu kabul ediyorum. Ancak, yanlışlık teşhisine katılmıyorum. Welford'un, bir sqrt bile içermeyen varyansı hesaplama yönteminde benzer bir hata vardır. Bununla birlikte, nbüyüdükçe error/n, şaşırtıcı derecede küçük, şaşırtıcı derecede hızlı bir şekilde küçülür.
fmark

Welford'un yönteminde sqrt yoktur, çünkü standart sapmayı değil varyansı hesaplar. Metreyi alarak, ortalama mutlak sapmayı değil, standart sapmayı tahmin ediyor gibi görünüyorsunuz. bir şey mi kaçırıyorum?
shabbychef

@shabbychef Welfords'ın her tekrarı, yeni veri noktasının kare içindeki mutlak sapmaya katkısını hesaplamaktadır. Bu yüzden mutlak sapmaya geri dönmek için her katkının kare kökünü alıyorum. Örneğin deltanın kare kökünü sapma toplamına eklemeden önce, standart sapma durumunda olduğu gibi sonradan değil, not edebilirsiniz.
fmark

3
Sorunu görüyorum; Welfords sorunu bu yöntemle gizliyor: ortalamanın nihai tahmini yerine ortalamanın çevrimiçi tahmini kullanılıyor. Welford yöntemi varyans için kesin (yuvarlamaya kadar) olsa da, bu yöntem değildir. Sorun değil nedeniyle sqrtbelirsizlik. Bunun nedeni, çalışan ortalama tahminini kullanmasıdır. Bunun ne zaman kırılacağını görmek xs <- sort(rnorm(n.testitems)) için bunu kodunuzla denediğimde (geri dönmek için düzelttikten sonra a.dev / n),% 9-16 oranında göreceli hatalar alıyorum. Yani bu yöntem, tahribat neden olabilecek permütasyon değişmez değil ...
shabbychef
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.