Thuật toán trực tuyến cho độ lệch tuyệt đối trung bình và tập dữ liệu lớn


16

Tôi có một vấn đề nhỏ đang làm tôi bối rối. Tôi phải viết thủ tục cho một quy trình mua lại trực tuyến của một chuỗi thời gian đa biến. Ở mỗi khoảng thời gian (ví dụ 1 giây), tôi nhận được một mẫu mới, về cơ bản là một vectơ dấu phẩy động có kích thước N. Thao tác tôi cần thực hiện là một chút khó khăn:

  1. Đối với mỗi mẫu mới, tôi tính phần trăm cho mẫu đó (bằng cách chuẩn hóa vectơ để các phần tử sẽ tổng hợp thành 1).

  2. Tôi tính toán vectơ phần trăm trung bình theo cùng một cách, nhưng sử dụng các giá trị trong quá khứ.

  3. Đối với mỗi giá trị trong quá khứ, tôi tính độ lệch tuyệt đối của vectơ phần trăm liên quan đến mẫu đó với vectơ phần trăm trung bình toàn cầu được tính ở bước 2. Bằng cách này, độ lệch tuyệt đối luôn là một số trong khoảng 0 (khi vectơ bằng trung bình vectơ) và 2 (khi nó là tổng số khác nhau).

  4. Sử dụng trung bình độ lệch cho tất cả các mẫu trước đó, tôi tính độ lệch tuyệt đối trung bình, lại là một số trong khoảng từ 0 đến 2.

  5. Tôi sử dụng độ lệch tuyệt đối trung bình để phát hiện xem một mẫu mới có tương thích với các mẫu khác hay không (bằng cách so sánh độ lệch tuyệt đối của nó với độ lệch tuyệt đối trung bình của toàn bộ bộ được tính ở bước 4).

Vì mỗi khi một mẫu mới được thu thập các thay đổi trung bình toàn cầu (và do đó độ lệch tuyệt đối trung bình cũng thay đổi), có cách nào để tính giá trị này mà không quét toàn bộ dữ liệu nhiều lần không? (một lần để tính phần trăm trung bình toàn cầu và một lần để thu thập độ lệch tuyệt đối). Ok, tôi biết việc tính trung bình toàn cầu rất dễ dàng mà không cần quét toàn bộ, vì tôi chỉ phải sử dụng một vectơ tạm thời để lưu tổng của mỗi thứ nguyên, nhưng độ lệch tuyệt đối trung bình thì sao? Tính toán của nó bao gồm abs()toán tử, vì vậy tôi cần truy cập vào tất cả các dữ liệu trong quá khứ!

Cảm ơn bạn đã giúp đỡ.

Câu trả lời:


6

Nếu bạn có thể chấp nhận một số thiếu chính xác, vấn đề này có thể được giải quyết dễ dàng bằng cách di chuyển chuột đã đếm. Đó là, chọn một số số largeish (nói M = 1000 ), sau đó khởi tạo một số thùng nguyên B i , j cho i = 1 ... Mj = 1 ... N , nơi N là kích thước vector, như bằng không. Sau đó, khi bạn thấy quan sát thứ k của một vectơ phần trăm, hãy tăng B i , j nếu phần tử thứ j của vectơ này nằm giữa (MM=1000Bi,ji=1Mj=1NNkBi,jj i / M , lặp qua cácphần tử N của vectơ. (Tôi giả sử các vectơ đầu vào của bạn không âm, do đó khi bạn tính 'phần trăm' của mình, các vectơ nằm trong phạm vi [ 0 , 1 ] .)(i1)/Mi/MN[0,1]

Tại bất kỳ thời điểm nào, bạn có thể ước tính vectơ trung bình từ các thùng và độ lệch tuyệt đối trung bình. Sau khi quan sát vectơ như vậy, phần tử thứ j của giá trị trung bình được ước tính bằng ˉ X j = 1Kjjnguyên tố thứ của độ lệch tuyệt đối trung bình ước tính của1

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

chỉnh sửa : đây là một trường hợp cụ thể của một cách tiếp cận tổng quát hơn, nơi bạn đang xây dựng một ước tính mật độ thực nghiệm. Điều này có thể được thực hiện với đa thức, spline, v.v., nhưng cách tiếp cận binning là cách dễ nhất để mô tả và thực hiện.


Wow, cách tiếp cận thực sự thú vị. Tôi không biết về điều đó và tôi sẽ ghi nhớ nó. Thật không may, trong trường hợp này nó sẽ không hoạt động, vì tôi có các yêu cầu thực sự hạn chế từ quan điểm sử dụng bộ nhớ, vì vậy M nên rất nhỏ và tôi đoán sẽ có quá nhiều mất chính xác.
gianluca

@gianluca: có vẻ như bạn có 1. rất nhiều dữ liệu, 2. tài nguyên bộ nhớ hạn chế, 3. yêu cầu độ chính xác cao. Tôi có thể thấy lý do tại sao vấn đề này làm bạn hoảng sợ! Có lẽ, như được đề cập bởi @kwak, bạn có thể tính toán một số biện pháp lây lan khác: MAD, IQR, độ lệch chuẩn. Tất cả những cách tiếp cận có thể có tác dụng cho vấn đề của bạn.
shabbychef

gianluca:> Cung cấp cho chúng tôi thêm ý tưởng định lượng về kích thước của bộ nhớ, mảng và độ chính xác mà bạn muốn. Nó cũng có thể là câu hỏi của bạn sẽ được trả lời tốt nhất @ stackoverflow mặc dù.
user603

4

Trước đây, tôi đã sử dụng cách tiếp cận sau đây để tính toán độ lệch áp suất vừa phải một cách hiệu quả (lưu ý, đây là cách tiếp cận của lập trình viên, không phải là thống kê, do đó, có thể có những thủ thuật thông minh như shabbychef có thể hiệu quả hơn).

CẢNH BÁO: Đây không phải là một thuật toán trực tuyến. Nó đòi hỏi O(n)trí nhớ. Hơn nữa, nó có hiệu suất trường hợp xấu nhất O(n), đối với các bộ dữ liệu như [1, -2, 4, -8, 16, -32, ...](nghĩa là giống như tính toán lại đầy đủ). [1]

Tuy nhiên, vì nó vẫn hoạt động tốt trong nhiều trường hợp sử dụng nên có thể đáng để đăng ở đây. Ví dụ: để tính độ lệch tuyệt đối của 10000 số ngẫu nhiên trong khoảng từ 100 đến 100 khi mỗi mục đến, thuật toán của tôi mất chưa đến một giây, trong khi tính toán lại đầy đủ mất hơn 17 giây (trên máy của tôi, sẽ thay đổi theo từng máy và theo dữ liệu đầu vào). Tuy nhiên, bạn cần duy trì toàn bộ vectơ trong bộ nhớ, đây có thể là một hạn chế đối với một số mục đích sử dụng. Các phác thảo của thuật toán như sau:

  1. Thay vì có một vectơ duy nhất để lưu trữ các phép đo trong quá khứ, hãy sử dụng ba hàng đợi ưu tiên được sắp xếp (một cái gì đó như một đống tối thiểu / tối đa). Ba danh sách này phân vùng đầu vào thành ba: các mục lớn hơn giá trị trung bình, các mục nhỏ hơn giá trị trung bình và các mục bằng giá trị trung bình.
  2. (Hầu hết) mỗi khi bạn thêm một mục có nghĩa là thay đổi, vì vậy chúng tôi cần phân vùng lại. Điều cốt yếu là bản chất được sắp xếp của các phân vùng, điều đó có nghĩa là thay vì quét mọi mục trong danh sách để phân vùng lại, chúng ta chỉ cần đọc những mục chúng ta đang di chuyển. Mặc dù trong trường hợp xấu nhất, điều này vẫn sẽ yêu cầu các O(n)thao tác di chuyển, đối với nhiều trường hợp sử dụng thì không phải như vậy.
  3. Sử dụng một số sổ sách thông minh, chúng tôi có thể đảm bảo rằng độ lệch được tính toán chính xác tại mọi thời điểm, khi phân vùng lại và khi thêm các mục mới.

Một số mã mẫu, trong python, dưới đây. Lưu ý rằng nó chỉ cho phép các mục được thêm vào danh sách, không bị xóa. Điều này có thể dễ dàng được thêm vào, nhưng tại thời điểm tôi viết bài này, tôi không cần nó. Thay vì tự thực hiện các hàng đợi ưu tiên, tôi đã sử dụng danh sách được sắp xếp từ gói vỉ tuyệt vời của Daniel Stutzbach , sử dụng nội bộ B + Tree .

Xem xét mã này được cấp phép theo giấy phép MIT . Nó đã không được tối ưu hóa hoặc đánh bóng đáng kể, nhưng đã làm việc cho tôi trong quá khứ. Phiên bản mới sẽ có sẵn ở đây . Hãy cho tôi biết nếu bạn có bất kỳ câu hỏi, hoặc tìm thấy bất kỳ lỗi.

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] Nếu các triệu chứng vẫn tồn tại, hãy đi khám bác sĩ.


2
Tôi đang thiếu một cái gì đó: nếu bạn phải "duy trì toàn bộ vectơ trong bộ nhớ", làm thế nào để nó đủ điều kiện là thuật toán "trực tuyến" ??
whuber

@whuber Không, không thiếu thứ gì, tôi đoán đó không phải là thuật toán trực tuyến. Nó đòi hỏi O(n)bộ nhớ và trong trường hợp xấu nhất sẽ mất thời gian O (n) cho mỗi mục được thêm vào. Trong dữ liệu phân phối thông thường (và có lẽ các phân phối khác), nó hoạt động khá hiệu quả.
đánh dấu

3

XXXss2/π


Đó là một ý tưởng thú vị. Bạn có thể bổ sung nó, có lẽ, với việc phát hiện trực tuyến các ngoại lệ và sử dụng chúng để sửa đổi ước tính khi bạn thực hiện.
whuber

Bạn có thể có thể sử dụng phương pháp của Welford để tính độ lệch chuẩn trực tuyến mà tôi đã ghi lại trong câu trả lời thứ hai của mình.
đánh dấu

1
Tuy nhiên, người ta cần lưu ý rằng theo cách này, người ta có thể mất đi sự mạnh mẽ của các công cụ ước tính, chẳng hạn như MAD rõ ràng, đôi khi đang hướng đến sự lựa chọn của mình trước các lựa chọn đơn giản hơn.
Thạch anh

2

MAD (x) chỉ là hai phép tính trung vị đồng thời, mỗi phép tính có thể được thực hiện trực tuyến thông qua thuật toán binmedian .

Bạn có thể tìm thấy giấy liên quan cũng như mã C và FORTRAN trực tuyến tại đây .

(đây chỉ là việc sử dụng một mánh khóe thông minh trên đầu mánh thông minh của Shabbychef, để tiết kiệm bộ nhớ).

Phụ lục:

Tồn tại một loạt các phương pháp đa vượt qua cũ để tính toán lượng tử. Một cách tiếp cận phổ biến là duy trì / cập nhật một kho quan sát có kích thước xác định được chọn ngẫu nhiên từ luồng và tính toán đệ quy lượng tử (xem tổng quan này ) trên hồ chứa này. Cách tiếp cận này (và có liên quan) được thay thế bởi phương pháp được đề xuất ở trên.


Bạn có thể vui lòng nêu chi tiết hoặc tham khảo mối quan hệ giữa MAD và hai trung vị không?
Thạch anh

medi=1n|ximedi=1n| (hence two medians)
user603

Hehm, I actually meant if you can explain how is this relation allowing for the two medians to be concurrent; those seem dependent to me, since the inputs to the outer median may all change at each added sample to the inner calculation. How would you perform them in parallel?
Quartz

I have to go back to the binmedian paper for details...but given a computed value of the median (medi=1nxi) and a new value of xn+1 the algorithm could compute medi=1n+1xi much faster than O(n) by identifying the bin to which xn+1 belongs. I don't see how this insight could not be generalized to the outer median in the mad computation.
user603

1

The following provides an inaccurate approximation, although the inaccuracy will depend on the distribution of the input data. It is an online algorithm, but only approximates the absolute deviance. It is based on a well known algorithm for calculating variance online, described by Welford in the 1960s. His algorithm, translated into R, looks like:

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)
}

It performs very similarly to R's builtin variance function:

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 

Modifying the algorithm to calculate absolute deviation simply involves an additional sqrt call. However, the sqrt introduces inaccuracies that are reflected in the result:

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)
}

The errors, calculated as above, are much greater than for the variance calculation:

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

However, depending on your use case, this magnitude of error might be acceptable.

historgram of differences


This does not give the exact answer, for the following reason: ixiixi. You are computing the former, while the OP wants the latter.
shabbychef

I agree that the method is inexact. However, I disagree with your diagnosis of the inexactness. Welford's method for calculating variance, which does not even contain a sqrt, has a similar error. However, as n gets large, the error/n gets vanishingly small, suprisingly quickly.
fmark

Welford's method has no sqrt because it is computing the variance, not the standard deviation. By taking the sqrt, it seems like you are estimating the standard deviation, not the mean absolute deviation. am I missing something?
shabbychef

@shabbychef Each iteration of Welfords is calculating the contribution of the new datapoint to the absolute deviation, squared. So I take the square root of each contribution squared to get back to the absolute deviance. You might note, for example, that I take the square root of the delta before I add it to the deviance sum, rather than afterward as in the case of the standard deviation.
fmark

3
I see the problem; Welfords obscures the problem with this method: the online estimate of the mean is being used instead of the final estimate of the mean. While Welford's method is exact (up to roundoff) for variance, this method is not. The problem is not due to the sqrt imprecision. It is because it uses the running mean estimate. To see when this will break, try xs <- sort(rnorm(n.testitems)) When I try this with your code (after fixing it to return a.dev / n), I get relative errors on the order of 9%-16%. So this method is not permutation invariant, which could cause havoc...
shabbychef
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.