평균 절대 편차 및 큰 데이터 세트를위한 온라인 알고리즘


16

나는 나를 놀리 게 만드는 약간의 문제가 있습니다. 다변량 시계열의 온라인 획득 프로세스에 대한 절차를 작성해야합니다. 매 시간 간격 (예 : 1 초)마다 기본적으로 N 크기의 부동 소수점 벡터 인 새 샘플을 얻습니다. 수행해야하는 작업은 약간 까다 롭습니다.

  1. 각각의 새 샘플에 대해 요소를 1로 합치도록 벡터를 정규화하여 해당 샘플의 백분율을 계산합니다.

  2. 평균 퍼센트 벡터는 같은 방식으로 계산하지만 과거 값을 사용합니다.

  3. 각 과거 값에 대해 2 단계에서 계산 된 전역 평균 백분위 벡터로 해당 샘플과 관련된 백분위 벡터의 절대 편차를 계산합니다. 이렇게하면 절대 편차는 항상 0 사이의 숫자입니다 (벡터가 평균과 같은 경우) 벡터) 및 2 (완전히 다른 경우).

  4. 이전의 모든 샘플에 대한 평균 편차를 사용하여 평균 절대 편차를 계산하는데, 이는 다시 0과 2 사이의 숫자입니다.

  5. 평균 절대 편차를 사용하여 새 샘플이 다른 샘플과 호환되는지 감지합니다 (절대 편차를 단계 4에서 계산 된 전체 세트의 평균 절대 편차와 비교).

새로운 샘플이 수집 될 때마다 전체 평균 변화 (따라서 평균 절대 편차도 변경됨) 때문에 전체 데이터 세트를 여러 번 스캔하지 않고이 값을 계산할 수 있습니까? (한 번은 전체 평균 백분율을 계산하고 한 번은 절대 편차를 수집합니다.) 좋아, 나는 전체 세트를 스캔하지 않고 전체 평균을 계산하는 것이 절대적으로 쉽다는 것을 알고있다. 왜냐하면 각 차원의 합을 저장하기 위해 임시 벡터를 사용해야하기 때문에 평균 절대 편차는 어떻습니까? 계산에는 abs()연산자가 포함되어 있으므로 모든 과거 데이터에 액세스해야합니다!

당신의 도움을 주셔서 감사합니다.

답변:


6

부정확 한 부분을 수용 할 수있는 경우이 횟수는 비닝 횟수를 통해 쉽게 해결할 수 있습니다 . 즉, 일부 largeish 번호 선택 (예를 들어 M = 1000 , 그 다음 약간 정수 빈들 초기화) B ~ I , J 에 대한 = 1 ... M을 하고 J = 1미디엄미디엄=1000나는,제이i=1M. 여기서 N 은 벡터 크기입니다 (0). 그런 다음 볼 때 K에게 percentual 벡터 번째 관찰 증분 B I , J를 는 IF J 이 벡터의 번째 요소 사이 (j=1NNkBi,jj i / M으로 벡터의 N 요소를반복합니다. (입력 벡터가 음이 아니라고 가정하므로 '백분율'을 계산할 때 벡터의 범위는 [ 0 , 1 ] 범위에있습니다.)(i1)/Mi/MN[0,1]

언제든지 빈에서 평균 벡터와 평균 절대 편차를 추정 할 수 있습니다. 와 같은 벡터를 관찰 한 후 , 평균 의 j 번째 요소는 ˉ X j = 1로 추정됩니다. Kj상기J의평균 절대 편차의 번째 요소에 의해 추정

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

편집 : 이것은 경험적 밀도 추정을 작성하는보다 일반적인 접근 방식의 특정 경우입니다. 이것은 다항식, 스플라인 등으로 수행 할 수 있지만 비닝 방식은 가장 쉽게 설명하고 구현할 수 있습니다.


와우, 정말 흥미로운 접근법. 나는 그것에 대해 몰랐고, 나는 그것을 명심할 것이다. 불행히도이 경우 메모리 사용 관점에서 실제로 제한적인 요구 사항이 있기 때문에 M이 실제로 작아야하므로 정밀도 손실이 너무 클 것으로 예상 되므로이 경우 작동하지 않습니다.
gianluca

@ gianluca : 그것은 당신이 1. 많은 양의 데이터, 2. 제한된 메모리 자원, 3. 높은 정밀도 요구 사항이있는 것처럼 들립니다. 이 문제가 왜 당신을 놀라게하는 지 알 수 있습니다! @kwak에서 언급했듯이 MAD, IQR, 표준 편차와 같은 다른 스프레드 측정을 계산할 수 있습니다. 이들 모두 문제에 도움이 될 수있는 접근 방식이 있습니다.
shabbychef

gianluca :> 원하는 메모리, 배열 및 정확도에 대한 더 많은 양의 아이디어를 제공하십시오. 그래도 귀하의 질문에 @ stackoverflow가 가장 잘 대답 될 것입니다.
user603

4

나는 과거에 다음과 같은 접근 방식을 사용하여 절제 편차를 적당히 효율적으로 계산했습니다. (이 방법은 통계학자가 아닌 프로그래머 접근 방식이므로, 더 효율적일 수있는 shabbychef 와 같은 영리한 트릭이있을 수 있습니다 ).

경고 : 이것은 온라인 알고리즘이 아닙니다. O(n)메모리 가 필요 합니다. 또한 O(n)데이터 집합 의 경우 [1, -2, 4, -8, 16, -32, ...](예 : 전체 재계 산과 동일) 최악의 성능 을가집니다. [1]

그러나 많은 유스 케이스에서 여전히 잘 수행되므로 여기에 게시하는 것이 좋습니다. 예를 들어, 각 항목이 도착할 때 -100에서 100 사이의 난수 10000의 절대 이탈을 계산하려면 알고리즘이 1 초 미만이 걸리고 전체 재계 산은 17 초가 넘습니다 (내 기계에서는 기계마다 다름) 입력 데이터에 따라). 그러나 전체 벡터를 메모리에 유지해야하는데 일부 사용에는 제약이 될 수 있습니다. 알고리즘의 개요는 다음과 같습니다.

  1. 과거 측정 값을 저장하기 위해 단일 벡터를 사용하는 대신 3 개의 정렬 된 우선 순위 대기열 (최소 / 최대 힙과 같은 것)을 사용하십시오. 이 세 가지 목록은 입력을 세 개로 나눕니다 : 평균보다 큰 항목, 평균보다 작은 항목 및 평균과 같은 항목.
  2. (거의) 항목을 추가 할 때마다 평균이 변경되므로 다시 분할해야합니다. 중요한 것은 파티션의 정렬 특성입니다. 즉, 목록의 모든 항목을 다시 분할하기 위해 스캔하는 대신 이동중인 항목 만 읽어야합니다. 최악의 경우 여전히 O(n)이동 작업 이 필요하지만 많은 유스 케이스의 경우에는 그렇지 않습니다.
  3. 영리한 부기를 사용하여 파티션을 다시 나누거나 새 항목을 추가 할 때 항상 이탈이 정확하게 계산되도록 할 수 있습니다.

파이썬에서 일부 샘플 코드는 다음과 같습니다. 항목을 목록에만 추가 할 수 있으며 제거 할 수는 없습니다. 이것은 쉽게 추가 될 수 있지만, 나는 이것을 쓸 당시에는 필요가 없었습니다. 우선 순위 대기열을 직접 구현하는 대신 Daniel Stutzbach의 우수한 blist 패키지 에서 정렬목록 을 사용했습니다.이 패키지 는 내부적으로 B + Tree 를 사용 합니다.

이 코드가 MIT 라이센스에 따라 라이센스 된 것으로 간주하십시오 . 그것은 크게 최적화되거나 연마되지는 않았지만 과거에는 저에게 효과적이었습니다. 새 버전은 여기에서 사용할 수 있습니다 . 궁금한 점이 있으면 알려주거나 버그를 찾으십시오.

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] 증상이 지속되면 주치의에게 문의하십시오.


2
나는 무언가를 놓치고있다 : 만약 당신이 "전체 벡터를 메모리에 유지해야"한다면, 이것이 어떻게 "온라인"알고리즘으로서 자격이 되는가?
whuber

@ whuber 아니요, 빠진 것이 없습니다. 온라인 알고리즘이 아닌 것 같습니다. O(n)메모리 가 필요 하며 최악의 경우 추가 된 각 항목에 대해 O (n) 시간이 걸립니다. 정규 분포 데이터 (아마도 다른 분포)에서는 상당히 효율적으로 작동합니다.
fmark

3

엑스엑스엑스에스에스2/π


흥미로운 생각입니다. 이상 값을 온라인으로 감지하여 보충하고이를 사용하여 추정치를 수정할 수 있습니다.
whuber

Welford의 방법을 사용하여 두 번째 답변에서 문서화 한 표준 편차를 온라인으로 계산할 수 있습니다.
fmark

1
그러나 이런 식으로 명시적인 MAD와 같은 추정기의 견고성을 잃을 수도 있는데, 이는 때로는 더 간단한 대안에 대한 선택으로 나아가고 있습니다.
Quartz

2

MAD (x) 관통 할 수 온라인 각각 두 동시 중앙값 연산이다 binmedian 알고리즘.

C 및 FORTRAN 코드는 물론 관련 용지를 온라인에서 찾을 수 있습니다. 에서 .

(이것은 메모리를 절약하기 위해 Shabbychef의 영리한 트릭 위에 영리한 트릭을 사용하는 것입니다).

추가:

Quantile을 계산하기위한 오래된 멀티 패스 방법이 많이 있습니다. 널리 사용되는 접근 방식은 스트림에서 무작위로 선택된 관측치의 결정적 저수지를 유지 / 업데이트 하고이 저수지에서 Quantile을 재귀 적으로 계산하는 것입니다 ( 리뷰 참조 ). 이 (및 관련) 접근 방식은 위에서 제안한 방법으로 대체됩니다.


MAD와 두 중앙값 사이의 관계를 자세히 설명하거나 참조 해 주시겠습니까?
Quartz

그것은 실제로 MAD의 공식입니다. 메드나는=1|엑스나는메드나는=1|(따라서 2 개의 중앙값)
user603

흠, 저는 실제로이 관계가 어떻게 두 중앙값을 동시에 사용할 수 있는지 설명 할 수 있다면 의미했습니다. 외부 중앙값에 대한 입력은 각각의 추가 된 샘플에서 내부 계산으로 변경 될 수 있기 때문에 저에게 의존적 인 것 같습니다. 그것들을 어떻게 병렬로 수행하겠습니까?
Quartz

자세한 내용은 binmedian 용지로 돌아 가야하지만 중앙값의 계산 된 값이 주어집니다 (미디엄이자형나는=1엑스나는)의 새로운 가치 엑스+1 알고리즘은 계산할 수 있습니다 미디엄이자형나는=1+1엑스나는 보다 빠르게 영형() 빈을 식별하여 엑스+1속한다. 이 계산이 미친 계산에서 어떻게 외부 중앙값으로 일반화 될 수 없는지 알 수 없습니다.
user603

1

부정확 한 입력 데이터의 분포에 따라 달라 지지만 다음은 부정확 한 근사값을 제공합니다. 온라인 알고리즘이지만 절대 이탈도에 근접합니다. 이는 1960 년대 웰 포드 (Welford) 에 의해 기술 된 온라인 분산 계산 을위한 잘 알려진 알고리즘 을 기반으로합니다 . 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의 내장 분산 함수와 매우 유사하게 수행됩니다.

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 

절대 편차를 계산하도록 알고리즘을 수정하면 추가 sqrt호출이 필요합니다. 그러나 sqrt결과는 결과에 반영된 부정확성을 나타냅니다.

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

위와 같이 계산 된 오차는 분산 계산보다 훨씬 큽니다.

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

그러나 사용 사례에 따라이 크기의 오류가 허용 될 수 있습니다.

차이의 히스토그램


다음과 같은 이유로 정확한 답변을 제공하지는 않습니다. 나는엑스나는나는엑스나는. 당신은 전자를 계산하는 반면, OP는 후자를 원합니다.
shabbychef

이 방법이 정확하지 않다는 데 동의합니다. 그러나 귀하의 부정확성 진단에 동의하지 않습니다. sqrt를 포함하지 않는 분산을 계산하는 Welford의 방법에도 유사한 오류가 있습니다. 그러나 n크기가 커질 error/n수록 놀랍도록 빠르게 작아집니다.
fmark

Welford의 방법은 표준 편차가 아닌 분산을 계산하기 때문에 sqrt가 없습니다. sqrt를 취하면 평균 절대 편차가 아닌 표준 편차를 추정하는 것처럼 보입니다. 뭔가 빠졌습니까?
shabbychef

@shabbychef Welfords의 각 반복은 절대 편차에 대한 새 데이터 포인트의 기여도를 제곱으로 계산합니다. 그래서 저는 각 기여의 제곱근을 제곱하여 절대 이탈로 돌아갑니다. 예를 들어 표준 편차의 경우와 같이 이후에 이탈 수를 합산하기 전에 델타의 제곱근을 취한다는 점에 유의할 수 있습니다.
fmark

3
나는 문제를 본다. Welfords는이 방법의 문제점을 모호하게합니다. 평균의 최종 추정치 대신 평균의 온라인 추정치가 사용되고 있습니다. Welford의 방법은 분산에 대해 정확하지만 반올림 할 수 있지만이 방법은 아닙니다. 문제는 부정확성 으로 인한 것이 아닙니다sqrt . 실행 평균 추정값을 사용하기 때문입니다. 이 문제가 발생하는 시점을 확인하려면 xs <- sort(rnorm(n.testitems)) 코드를 사용하여이를 시도 하면 (반환하도록 수정 한 후 a.dev / n) 9 % -16 % 정도의 상대 오류가 발생합니다. 따라서이 방법은 순열 변형이 아니며 혼란을 유발할 수 있습니다.
shabbychef
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.