Как уменьшить вычисление среднего числа к подмножествам общим способом?

Править: Так как кажется, что никто не читает исходный вопрос, с которым это связывается, позвольте мне ввести резюме его здесь.

Исходная проблема, как спрошено кем-то еще, состояла в том, что, учитывая большое количество значений, где сумма превысит что тип данных Double содержал бы, как можно вычислить среднее число тех значений.

Было несколько ответов, в которых было сказано, чтобы вычислить в наборах, как взятие 50 и 50 чисел и вычисление среднего числа в тех наборах, и затем наконец взять среднее число всех тех наборов и объединить их для получения заключительного среднего значения.

Мое положение было то, что, если Вы не можете гарантировать, что все те значения могут быть разделены на многие одинаково размерные наборы, Вы не можете использовать этот подход. Кто-то смел меня задавать вопрос здесь для предоставления ответа, таким образом, здесь это.

В основном, учитывая произвольное число значений, где:

  • Я знаю количество значений заранее (но снова, как Ваш ответ изменился бы, если бы Вы не сделали?')
  • Я не могу собрать все числа, и при этом я не могу суммировать их (сумма будет слишком большой для нормального типа данных на Вашем языке программирования),

как я могу вычислить среднее число?

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

Обратите внимание, что я знаю отлично, что достаточно математики знает это в математических условиях теории, вычисляя сумму A[1..N]/N даст мне среднее число, давайте предположим, что существуют причины, что это не столь просто, и я должен разделить рабочую нагрузку, и что количество значений не обязательно будет передаваемым по наследству 3, 7, 50, 1000 или что бы то ни было.

Другими словами, решение, которое я после, должно будет быть общим.


От этого вопроса:

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


Править: Исходный вопрос был о верхнем пределе, который конкретный тип данных мог содержать, и так как он подводил итог большого количества чисел (количество, которое было дано, как пример был 10^9), тип данных не мог содержать сумму. Так как это было проблемой в исходном решении, я принимаю (и это - предпосылка для моего вопроса, жаль о пропавших без вести этого), что числа являются слишком большими для предоставления любых значимых ответов.

Так, деление на общее количество значений непосредственно отсутствует. Исходная причина того, почему нормальное решение для СУММЫ/КОЛИЧЕСТВА отсутствовало, состояла в том, что СУММА переполнится, но давайте примем для этого вопроса, который SET-SET/SET-SIZE недостаточно заполнит, или что бы то ни было.

Важная часть - то, что я не могу просто суммировать, я не могу просто разделиться на количество итоговых значений. Если я не могу сделать этого, мой подход будет работать, или нет, и что я могу сделать для фиксации его?


Позвольте мне обрисовать в общих чертах проблему.

Давайте предположим, что Вы собираетесь вычислить среднее число номеров 1 - 6, но Вы не можете (по любой причине), делают так путем подведения итогов чисел, подсчета чисел и затем деления суммы на количество. Другими словами, Вы не можете просто сделать (1+2+3+4+5+6)/6.

Другими словами, SUM(1..6)/COUNT(1..6) отсутствует. Мы не рассматриваем ПУСТОЙ УКАЗАТЕЛЬ (как в ПУСТОМ УКАЗАТЕЛЕ базы данных) здесь.

Несколько из ответов на тот вопрос сослались на способность разделить числа, усредняемые на наборы, сказать 3 или 50 или 1 000 чисел, затем вычислив некоторое число для что, и затем наконец комбинирующий те значения для получения заключительного среднего числа.

Мое положение - то, что это не возможно в общем случае, так как это сделает некоторые числа, те появляющиеся в заключительном наборе, более или менее ценном, чем все те в предыдущих наборах, если Вы не сможете разделить все числа на одинаково размерные наборы.

Например, для вычисления среднего числа 1-6 можно разделить его на наборы 3 чисел как это:

/ 1   2   3 \   / 4   5   6 \
| - + - + - | + | - + - + - |
\ 3   3   3 /   \ 3   3   3 /  <-- 3 because 3 numbers in the set
 ----------      -----------
      2               2        <-- 2 because 2 equally sized groups

Который дает Вам это:

      2               5
      -       +       - = 3.5
      2               2

(примечание: (1+2+3+4+5+6)/6 = 3.5, таким образом, это корректно здесь),

Однако моя точка - то, что, после того как количество значений не может быть разделено на многие одинаково размерные наборы, этот метод разваливается. Например, что относительно последовательности 1-7, который содержит простое число значений.

Может аналогичный подход, который не будет суммировать все значения и считать все значения, сразу, работу?

Так, есть ли такой подход? Как делают я вычисляю среднее число произвольного числа значений, в которых сохраняется следующее:

  1. Я не могу сделать нормального подхода суммы/количества по любой причине
  2. Я знаю количество значений заранее (что, если я не делаю, который изменит ответ?)

5
задан Community 23 May 2017 в 12:08
поделиться

8 ответов

Итак, предположим, вы сложили три числа и разделили их на три, а затем сложили два числа и разделили их на два. Можете ли вы получить среднее из них?

x = (a + b + c) / 3
y = (d + e) / 2
z = (f + g) / 2

И вы хотите

r = (a + b + c + d + e + f + g) / 7

Это равно

r = (3 * (a + b + c) / 3 + 2 * (d + e) / 2 + 2 * (f + g) / 2) / 7
r = (3 * x + 2 * y + 2 * z) / 7

Обе строки выше, конечно, переполнение, но поскольку деление является распределительным, мы делаем

r = (3.0 / 7.0) * x + (2.0 / 7.0) * y + (2.0 / 7.0) * z

Что гарантирует, что вы не будете переполнение, поскольку я умножаю x, y и z на доли меньше единицы.

Это фундаментальный момент здесь. Я не делю все числа заранее на общее количество, и я никогда не превышаю переполнение.

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

И нет, если вы не знаете значения заранее, это не так. t изменять что-либо (при условии, что вы можете считать их при суммировании).

Вот функция Scala, которая это делает. Это не идиоматический Scala, чтобы его было легче понять:

def avg(input: List[Double]): Double = {
  var partialAverages: List[(Double, Int)] = Nil
  var inputLength = 0
  var currentSum = 0.0
  var currentCount = 0
  var numbers = input

  while (numbers.nonEmpty) {
    val number = numbers.head
    val rest = numbers.tail
    if (number > 0 && currentSum > 0 && Double.MaxValue - currentSum < number) {
      partialAverages = (currentSum / currentCount, currentCount) :: partialAverages
      currentSum = 0
      currentCount = 0
    } else if (number < 0 && currentSum < 0 && Double.MinValue - currentSum > number) {
      partialAverages = (currentSum / currentCount, currentCount) :: partialAverages
      currentSum = 0
      currentCount = 0
    }
    currentSum += number
    currentCount += 1
    inputLength += 1
    numbers = rest
  }
  partialAverages = (currentSum / currentCount, currentCount) :: partialAverages

  var result = 0.0
  while (partialAverages.nonEmpty) {
    val ((partialSum, partialCount) :: rest) = partialAverages
    result += partialSum * (partialCount.toDouble / inputLength)
    partialAverages = rest
  }

  result
}

EDIT: Разве умножение на 2 и 3 не вернет меня обратно в диапазон "не поддерживает тип данных?"

Нет. Если бы вы ныряли на 7 к концу, безусловно. Но здесь вы на каждом шаге делите сумму. Даже в вашем реальном случае веса ( 2/7 и 3/7 ) будут в диапазоне управляемых чисел (например, 1/10 ~ ] 1/10000 ), который не будет иметь большого значения по сравнению с вашим весом (например, 1 ).

PS: Интересно, почему я работаю над этим ответом вместо того, чтобы писать свой где я могу заработать репутацию: -)

7
ответ дан 18 December 2019 в 11:57
поделиться

Пусть X будет вашим набором образцов. Разделите его на два набора A и B любым способом. Определите delta = m_B - m_A , где m_S обозначает среднее значение набора S . Тогда

m_X = m_A + delta * |B| / |X|

, где | S | обозначает мощность набора S . Теперь вы можете повторно применить это к разделу и вычислить среднее значение.

Почему это правда? Пусть s = 1 / | A | и t = 1 / | B | и u = 1 / | X | (для удобства обозначений) и пусть aSigma и bSigma обозначают сумму элементов в A и B соответственно, так что:

  m_A + delta * |B| / |X|
= s * aSigma + u * |B| * (t * bSigma - s * aSigma)
= s * aSigma + u * (bSigma - |B| * s * aSigma)
= s * aSigma + u * bSigma - u * |B| * s * aSigma
= s * aSigma * (1 - u * |B|) + u * bSigma
= s * aSigma * (u * |X| - u * |B|) + u * bSigma
= s * u * aSigma * (|X| - |B|) + u * bSigma
= s * u * aSigma * |A| + u * bSigma
= u * aSigma + u * bSigma
= u * (aSigma + bSigma)
= u * (xSigma)
= xSigma / |X|
= m_X

Доказательство завершено.

Отсюда очевидно, как использовать это либо для рекурсивного вычисления среднего (скажем, путем многократного разделения набора пополам), либо как использовать это для распараллеливания вычисления среднего значения набора.

Известный онлайн-алгоритм вычисления среднего - лишь частный случай этого. Это алгоритм, который, если m является средним значением {x_1, x_2, ..., x_n} , то среднее значение {x_1, x_2, ..., x_n, x_ (n + 1)} равно m + ((x_ (n + 1) - m)) / (n + 1) . Таким образом, с X = {x_1, x_2, ..., x_ (n + 1)} , A = {x_ (n + 1)} и B = {x_1, x_2, ..., x_n} мы восстанавливаем онлайн-алгоритм.

Хорошо известный онлайн-алгоритм вычисления среднего - лишь частный случай этого. Это алгоритм, который, если m является средним значением {x_1, x_2, ..., x_n} , то среднее значение {x_1, x_2, ..., x_n, x_ (n + 1)} равно m + ((x_ (n + 1) - m)) / (n + 1) . Таким образом, с X = {x_1, x_2, ..., x_ (n + 1)} , A = {x_ (n + 1)} и B = {x_1, x_2, ..., x_n} мы восстанавливаем онлайн-алгоритм.

Хорошо известный онлайн-алгоритм вычисления среднего - лишь частный случай этого. Это алгоритм, который, если m является средним значением {x_1, x_2, ..., x_n} , то среднее значение {x_1, x_2, ..., x_n, x_ (n + 1)} равно m + ((x_ (n + 1) - m)) / (n + 1) . Таким образом, с X = {x_1, x_2, ..., x_ (n + 1)} , A = {x_ (n + 1)} и B = {x_1, x_2, ..., x_n} мы восстанавливаем онлайн-алгоритм.

3
ответ дан 18 December 2019 в 11:57
поделиться

Если вы заранее знаете количество значений (скажем, N ), вы просто добавляете 1 / N + 2 / N + 3 / N и т.д., предположим, что у вас есть значения 1, 2, 3 . Вы можете разбить это на столько вычислений, сколько захотите, и просто сложить свои результаты. Это может привести к небольшой потере точности, но это не должно быть проблемой, если вам также не нужен сверхточный результат.

Если вы заранее не знаете количество элементов, вам, возможно, придется более творческий. Но вы, опять же, можете делать это постепенно. Скажем, список 1, 2, 3, 4 . Начните с среднего = 1 . Тогда среднее = среднее * (1/2) + 2 * (1/2) . Тогда среднее = среднее * (2/3) + 3 * (1/3) . Тогда среднее значение = среднее значение * (3/4) + 4 * (1/4) и т. Д. Это легко обобщить,

4
ответ дан 18 December 2019 в 11:57
поделиться

Нестандартное мышление: Используйте вместо этого медиану. Это намного проще вычислить - существует множество алгоритмов (например, с использованием очередей), вы часто можете найти хорошие аргументы в пользу того, почему это более значимо для наборов данных (меньше подвержено влиянию экстремальных значений и т. Д.), И у вас не будет проблем с числовая точность. Это будет быстро и эффективно. Кроме того, для больших наборов данных (которые, похоже, у вас есть), если распределения действительно не странные, значения для среднего и медианы будут одинаковыми.

1
ответ дан 18 December 2019 в 11:57
поделиться

Когда вы разбиваете числа на наборы, вы просто делите их на общее число, или мне что-то не хватает?

Вы написали это как

/ 1   2   3 \   / 4   5   6 \
| - + - + - | + | - + - + - |
\ 3   3   3 /   \ 3   3   3 /
 ----------      -----------
      2               2

, но это всего лишь

/ 1   2   3 \   / 4   5   6 \
| - + - + - | + | - + - + - |
\ 6   6   6 /   \ 6   6   6 /

, поэтому для чисел от 1 до 7 одна возможная группировка - это просто

/ 1   2   3 \   / 4   5   6 \   / 7 \
| - + - + - | + | - + - + - | + | - |
\ 7   7   7 /   \ 7   7   7 /   \ 7 /
0
ответ дан 18 December 2019 в 11:57
поделиться

Average of x_1 .. x_N
    = (Sum(i=1,N,x_i)) / N
    = (Sum(i=1,M,x_i) + Sum(i=M+1,N,x_i)) / N
    = (Sum(i=1,M,x_i)) / N + (Sum(i=M+1,N,x_i)) / N

Это можно применять неоднократно, и это верно независимо от того, имеют ли суммы одинакового размера. Итак:

  • Продолжайте добавлять члены до тех пор, пока оба:
    • добавление еще одного приведет к переполнению (или иначе потеряет точность)
    • деление на N не приведет к потере значимости
  • Разделите сумму на N
  • Добавьте результат к среднее-до сих пор

Есть один очевидный неудобный случай, заключающийся в том, что в конце последовательности есть несколько очень маленьких членов, так что у вас заканчиваются значения, прежде чем вы удовлетворяете условию «деление на N не приведет к потере значимости» . В этом случае просто отбросьте эти значения - если их вклад в среднее значение не может быть представлен в вашем плавающем типе, то он, в частности, меньше, чем точность вашего среднего. Поэтому для результата не имеет значения, включаете вы эти термины или нет.

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

10^100, 1, -10^100

Математика говорит, что это 1, но арифметика с плавающей запятой говорит, что это зависит от того, в каком порядке вы складываете члены, и в 4 из 6 возможных вариантов это 0, потому что (10 ^ 100) + 1 = 10 ^ 100. Но я думаю, что некоммутативность арифметики с плавающей запятой - это другая и более общая проблема, чем этот вопрос. Если о сортировке ввода не может быть и речи, я думаю, что есть вещи, которые вы можете сделать, если вы будете поддерживать множество аккумуляторов разной величины и добавлять каждое новое значение к тому, какое из них даст наилучшую точность. Но я точно не знаю.

s среднее значение:

10^100, 1, -10^100

Математика говорит, что это 1, но арифметика с плавающей запятой говорит, что это зависит от того, в каком порядке вы складываете члены, и в 4 из 6 возможных вариантов это 0, потому что (10 ^ 100) + 1 = 10 ^ 100. Но я думаю, что некоммутативность арифметики с плавающей запятой - это другая и более общая проблема, чем этот вопрос. Если о сортировке ввода не может быть и речи, я думаю, что есть вещи, которые вы можете сделать, если вы будете поддерживать множество аккумуляторов разной величины и добавлять каждое новое значение к тому, какое из них даст наилучшую точность. Но я точно не знаю.

s среднее значение:

10^100, 1, -10^100

Математика говорит, что это 1, но арифметика с плавающей запятой говорит, что это зависит от того, в каком порядке вы складываете члены, и в 4 из 6 возможных вариантов это 0, потому что (10 ^ 100) + 1 = 10 ^ 100. Но я думаю, что некоммутативность арифметики с плавающей запятой - это другая и более общая проблема, чем этот вопрос. Если о сортировке ввода не может быть и речи, я думаю, что есть вещи, которые вы можете сделать, если вы будете поддерживать множество аккумуляторов разной величины и добавлять каждое новое значение к тому, какое из них даст наилучшую точность. Но я точно не знаю.

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

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

0
ответ дан 18 December 2019 в 11:57
поделиться

Некоторые математические решения здесь очень хороши. Вот простое техническое решение.

Используйте больший тип данных. Это делится на две возможности:

  1. Использовать высокоточную библиотеку с плавающей запятой. Тот, кто сталкивается с необходимостью усреднить миллиард чисел, вероятно, имеет ресурсы для покупки или способность писать 128-битную (или более длинную) библиотеку с плавающей запятой.

    Я понимаю здесь недостатки. Это определенно будет медленнее, чем использование внутренних типов. Вы все равно можете переполниться / опуститься, если количество значений станет слишком большим. Yada yada.

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

    Если кто-то более мотивированный, чем я в данный момент, захочет проверить правильность кода и исправить любые проблемы, которые могут возникнуть, пожалуйста, будьте моим гостем.


    Я ' Мы проверили этот код и внесли несколько небольших исправлений (отсутствующая пара круглых скобок в вызове конструктора List и неправильный делитель в последнем делении DivideBy ] функция).

    Я проверил его, сначала прогнав через 1000 наборов случайной длины (от 1 до 1000), заполненных случайными целыми числами (от 0 до 2 32 - 1). Это были наборы, точность которых я мог легко и быстро проверить, также используя для них каноническое среднее.

    Затем я протестировал 100 * больших серий со случайной длиной от 10 5 ] и 10 9 . Нижняя и верхняя границы этих серий также были выбраны случайным образом, с ограничениями, чтобы серия соответствовала диапазону 32-битного целого числа. Для любой серии результаты легко проверить как (нижняя граница + верхняя граница) / 2 .

    * Хорошо, это небольшая невинная ложь. Я прервал тест большой серии примерно после 20 или 30 успешных запусков. Серия длиной 10 9 занимает чуть менее полутора минут на моей машине, так что полчаса или около того тестирования этой процедуры было достаточно на мой вкус.

    Для тех, кто заинтересован, мой тестовый код приведен ниже:

    static IEnumerable<uint> GetSeries(uint lowerbound, uint upperbound){
        for(uint i = lowerbound; i <= upperbound; i++)
            yield return i;
    }
    
    static void Test(){
        Console.BufferHeight = 1200;
        Random rnd = new Random();
    
        for(int i = 0; i < 1000; i++){
            uint[] numbers = new uint[rnd.Next(1, 1000)];
            for(int j = 0; j < numbers.Length; j++)
                numbers[j] = (uint)rnd.Next();
    
            double sum = 0;
            foreach(uint n in numbers)
                sum += n;
    
            double avg = sum / numbers.Length;
            double ans = new BigMeanSet().GetAverage(numbers);
    
            Console.WriteLine("{0}: {1} - {2} = {3}", numbers.Length, avg, ans, avg - ans);
    
            if(avg != ans)
                Debugger.Break();
        }
    
        for(int i = 0; i < 100; i++){
            uint length     = (uint)rnd.Next(100000, 1000000001);
            uint lowerbound = (uint)rnd.Next(int.MaxValue - (int)length);
            uint upperbound = lowerbound + length;
    
            double avg = ((double)lowerbound + upperbound) / 2;
            double ans = new BigMeanSet().GetAverage(GetSeries(lowerbound, upperbound));
    
            Console.WriteLine("{0}: {1} - {2} = {3}", length, avg, ans, avg - ans);
    
            if(avg != ans)
                Debugger.Break();
        }
    }
    
0
ответ дан 18 December 2019 в 11:57
поделиться

Вот еще один подход. Вы "получаете" числа один за другим из какого-то источника, но можете отслеживать среднее на каждом шаге.

Сначала я выпишу формулу среднего на шаге n+1:

mean[n+1] = mean[n] - (mean[n] - x[n+1]) / (n+1)

с исходным условием:

mean[0] = x[0]

(индекс начинается с нуля).

Первое уравнение можно упростить до:

mean[n+1] = n * mean[n] / (n+1) + x[n+1]/(n+1)

Идея состоит в том, что Вы отслеживаете среднее, и когда Вы "получаете" следующее значение в Вашей последовательности, Вы вычисляете его смещение от текущего среднего, и делите его поровну между n+1 примерами, рассмотренными до сих пор, и соответствующим образом корректируете свое среднее значение. Если Ваши числа не имеют большой дисперсии, Ваше бегущее среднее должно быть очень слегка скорректировано с новыми числами, так как n становится большим.

Очевидно, что этот метод работает, даже если Вы не знаете общего количества значений при старте. Дополнительным преимуществом является то, что Вы всегда знаете значение текущего среднего. Один из недостатков, о котором я могу подумать, заключается в том, что он, вероятно, придает больше "веса" числам, видимым в начале (не в строгом математическом смысле, а из-за представления с плавающей точкой)

Наконец, все подобные вычисления, если не быть достаточно внимательным, неизбежно приведут к "ошибкам" с плавающей точкой. Смотрите мой ответ на другой вопрос о некоторых проблемах с вычислениями с плавающей точкой и о том, как проверить на наличие потенциальных проблем.

В качестве теста я сгенерировал N=100000 нормально распределенных случайных чисел со средним нулем и дисперсией 1. Затем я вычислил их среднее значение тремя методами.

  1. sum(numbers) / N, назовем его m1,
  2. мой метод выше, назовем его m2,
  3. сортируем числа, а затем используем мой метод выше, назовем его m3.

Вот что я нашел: m1 - m2 ∼ -4. 6×10-17, m1 - m3 ∼ -3×10-15, m2 - m3 ∼ -3×10-15. Таким образом, если ваши номера отсортированы, то ошибка может быть недостаточно мала для вас. (Обратите внимание, однако, что даже самая худшая ошибка - это 10-15 частей в 1 для 100000 чисел, так что в любом случае она может быть достаточно хорошей)

.
0
ответ дан 18 December 2019 в 11:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: