Онлайн-алгоритм середнього абсолютного відхилення та великого набору даних


16

У мене є невелика проблема, яка змушує мене лякатися. Я маю написати процедуру онлайн-процесу придбання багатовимірного часового ряду. Кожен проміжок часу (наприклад, 1 секунда) я отримую новий зразок, який в основному є вектором з плаваючою точкою розміром N. Операція, яку я повинен зробити, є трохи хитрою:

  1. Для кожного нового зразка я обчислюю відсотки для цього зразка (нормалізуючи вектор так, щоб елементи дорівнювали 1).

  2. Я обчислюю середній відсоток вектор таким же чином, але використовуючи минулі значення.

  3. Для кожного минулого значення я обчислюю абсолютне відхилення вектора відсотків, пов'язаного з цим зразком, із загальносвітовим вектором відсотків, обчисленим на кроці 2. Таким чином, абсолютне відхилення завжди є числом між 0 (коли вектор дорівнює середньому вектор) і 2 (коли це зовсім інше).

  4. Використовуючи середнє значення відхилень для всіх попередніх вибірок, я обчислюю середнє абсолютне відхилення, яке знову є числом між 0 і 2.

  5. Я використовую середнє абсолютне відхилення, щоб виявити, чи сумісний новий зразок з іншими зразками (шляхом порівняння його абсолютного відхилення із середнім абсолютним відхиленням усього набору, обчисленого на етапі 4).

Оскільки кожного разу, коли збирається новий зразок, середнє значення в глобальному масштабі (а значить, змінюється і середнє абсолютне відхилення), чи існує спосіб обчислити це значення без багаторазового сканування всього набору даних? (один раз для обчислення середньосвітових відсотків, і один раз для збору абсолютних відхилень). Гаразд, я знаю, що обчислити середні значення середнього рівня без сканування всього набору абсолютно просто, оскільки я просто повинен використовувати тимчасовий вектор для зберігання суми кожного виміру, але як бути середнє абсолютне відхилення? Його розрахунок включає abs()оператора, тому мені потрібно отримати доступ до всіх минулих даних!

Спасибі за вашу допомогу.

Відповіді:


6

Якщо ви можете прийняти деякі неточності, ця проблема може бути легко вирішена біннінговимі підрахунками. Тобто, виберіть деяке велике число (скажімо, M = 1000 ), потім ініціалізуйте кілька цілих бункерів B i , j для i = 1 M і j = 1 N , де N - розмір вектора, як нуль. Тоді, коли ви бачите k- е спостереження відсоткового вектора, приріст B i , j, якщо j- й елемент цього вектора знаходиться між (MM=1000Bi,ji=1Mj=1NNkBi,jj і i / M , петлюючи над N елементами вектора. (Я припускаю, що ваші вхідні вектори є негативними, так що при обчисленні ваших "відсотків" вектори знаходяться в діапазоні [ 0 , 1 ] .)(i1)/Mi/MN[0,1]

У будь-який момент часу можна оцінити середній вектор від бункерів і середнє абсолютне відхилення. Після дотримання таких векторів j- й елемент середнього значення оцінюється ˉ X j = 1KjіJй елемент середнього абсолютного відхилення оцінюється1

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

У минулому я використовував такий підхід, щоб обчислити відхилення абсолютизації помірно ефективно (зауважте, це підхід програмістів, а не статистик, тому неминуче можуть бути хитрі хитрощі, як шаббчеф, які можуть бути ефективнішими).

УВАГА: Це не онлайн-алгоритм. Для цього потрібна O(n)пам'ять. Крім того, він має найгірший показник O(n)для таких наборів даних [1, -2, 4, -8, 16, -32, ...](тобто такий самий, як і повний перерахунок). [1]

Однак, оскільки вона все ще працює в багатьох випадках використання, можливо, варто опублікувати тут. Наприклад, для того, щоб обчислити абсолютне відхилення 10000 випадкових чисел між -100 і 100 по мірі надходження кожного елемента, мій алгоритм займає менше однієї секунди, тоді як повний перерахунок займає більше 17 секунд (на моїй машині буде змінюватися в залежності від машини і за вхідними даними). Однак вам потрібно підтримувати весь вектор в пам'яті, що може бути обмеженням для деяких застосувань. Контур алгоритму такий:

  1. Замість того, щоб мати один вектор для зберігання минулих вимірів, використовуйте три відсортовані черги пріоритету (щось на зразок min / max купи). Ці три списки розділяють вхід на три: елементи, що перевищують середнє, елементи, менші від середнього, і елементи, рівні середньому.
  2. (Майже) щоразу, коли ви додаєте елемент, середнє значення змінюється, тому нам потрібно переділити. Найважливішим є відсортований характер розділів, а це означає, що замість того, щоб сканувати кожен елемент у списку до переділу, нам потрібно лише прочитати ті елементи, які ми переміщуємо. Хоча в гіршому випадку це все-таки знадобитьсяO(n) переміщення операцій, для багатьох випадків використання це не так.
  3. Використовуючи деякі розумні бухгалтерії, ми можемо переконатися, що відхилення правильно підраховано у всі часи, при перерозподілі та при додаванні нових елементів.

Деякі зразки коду, в python, нижче. Зауважте, що він дозволяє лише додавати елементи до списку, а не видаляти їх. Це можна легко додати, але в той час, коли я писав це, у мене цього не було потреби. Замість того, щоб реалізувати черги пріоритетів самостійно, я використав сортований список із чудового пакету blist Daniel Daniel Stutzbach , який використовує 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

XXXss2/π


Це цікава ідея. Ви можете доповнити це, можливо, онлайн-виявленням людей, що вижили, і використовувати їх для зміни кошторису під час подальшої роботи.
whuber

Можливо, ви могли використовувати метод Велфорда для обчислення стандартного відхилення в Інтернеті, що я задокументував у своїй другій відповіді.
fmark

1
Однак слід зазначити, що таким чином можна втратити надійність оцінювачів, таких як явний MAD, які іноді призводять до його вибору проти більш простих альтернатив.
Кварц

2

MAD (x) - це лише два паралельних середніх обчислення, кожне з яких можна здійснити в режимі он-лайн через бімедіану алгоритму .

Ви можете знайти відповідний папір, а також код C та FORTRAN в Інтернеті тут .

(це лише використання хитромудрої хитрості на основі розумного трюку Шаббічефа, щоб заощадити на пам’яті).

Додаток:

Існує безліч старих багатопрохідних методів обчислення квантилів. Популярний підхід - це підтримка / оновлення резервуара детермінованих розмірів спостережень, випадковим чином обраних із потоку, та рекурсивно обчислювати кванти (див. Цей огляд) на цьому водоймі. Цей (та пов'язаний з ним) підхід витісняється із запропонованого вище.


Чи можете ви детально розказати чи посилатися на зв’язок між MAD та двома медіанами?
Кварц

medi=1n|ximedi=1n|

Гем, я насправді мав на увазі, якщо ви можете пояснити, як це відношення дозволяє одночасно співпрацювати двом медіанам; вони здаються мені залежними, оскільки вхідні дані до зовнішньої медіани можуть змінюватися при кожному доданому вибірці до внутрішнього обчислення. Як би ви виконували їх паралельно?
Кварц

medi=1nxixn+1medi=1n+1xiO(n)xn+1
user603

1

Далі наведено неточне наближення, хоча неточність залежатиме від розподілу вхідних даних. Це онлайн-алгоритм, але лише приблизний до абсолютного відхилення. В його основі лежить відомий алгоритм обчислення дисперсії в Інтернеті, описаний Велфордом у 1960-х роках. Його алгоритм, перекладений на 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 

Однак, залежно від випадку використання, ця величина помилок може бути прийнятною.

historgram of differences


Це не дає точної відповіді з наступної причини: iхiiхi. Ви обчислюєте перше, тоді як ОП хоче останнє.
shabbychef

Я згоден, що метод неточний. Однак я не згоден з вашим діагнозом про неточність. Метод Велфорда для обчислення дисперсії, який навіть не містить sqrt, має аналогічну помилку. Однак, як nстає великим, то error/nстає зникаючим малим, надихається швидко.
fmark

Метод Велфорда не має sqrt, оскільки він обчислює дисперсію, а не стандартне відхилення. Беручи sqrt, здається, ви оцінюєте стандартне відхилення, а не середнє абсолютне відхилення. я щось пропускаю?
shabbychef

@shabbychef Кожна ітерація Welfords обчислює внесок нової точки даних в абсолютне відхилення в квадраті. Тому я беру квадратний корінь кожного внеску в квадрат, щоб повернутися до абсолютного відхилення. Ви можете зауважити, наприклад, що я беру квадратний корінь дельти, перш ніж додати його до суми відхилення, а не після цього, як у випадку зі стандартним відхиленням.
fmark

3
Я бачу проблему; Welfords затьмарює проблему за допомогою цього методу: замість остаточної оцінки середнього використовується онлайн-оцінка середнього значення. Хоча метод Велфорда є точним (аж до заокруглення) для дисперсії, цей метод не є. Проблема полягає не в sqrtнеточності. Це тому, що він використовує поточну оцінку середнього рівня. Щоб побачити, коли це порушиться, спробуйте xs <- sort(rnorm(n.testitems)) Коли я спробую це з вашим кодом (після виправлення його повернення a.dev / n), я отримую відносні помилки на порядку 9% -16%. Тож цей метод не є інваріантним перестановкою, що може спричинити хаос ...
shabbychef
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.