Làm thế nào để tính toán trung bình động mà không cần giữ tổng số và dữ liệu?


118

Tôi đang cố gắng tìm cách tính giá trị trung bình tích lũy mà không cần lưu trữ dữ liệu đếm và tổng số nhận được cho đến nay.

Tôi đã nghĩ ra hai thuật toán nhưng cả hai đều cần lưu trữ số lượng:

  • trung bình mới = ((số cũ * dữ liệu cũ) + dữ liệu tiếp theo) / số lần tiếp theo
  • trung bình mới = trung bình cũ + (dữ liệu tiếp theo - trung bình cũ) / số lần tiếp theo

Vấn đề với các phương pháp này là số lượng ngày càng lớn hơn dẫn đến mất độ chính xác trong kết quả trung bình.

Phương pháp đầu tiên sử dụng số cũ và số tiếp theo rõ ràng là cách nhau 1. Điều này khiến tôi nghĩ rằng có lẽ có một cách để loại bỏ số đếm nhưng tiếc là tôi vẫn chưa tìm thấy nó. Tuy nhiên, nó đã đưa tôi đi xa hơn một chút, dẫn đến phương pháp thứ hai nhưng vẫn có số lượng.

Liệu nó có thể, hay tôi chỉ đang tìm kiếm những điều không thể?


1
NB bằng số, lưu trữ tổng hiện tại và số hiện tại là cách ổn định nhất. Nếu không, đối với số lượng cao hơn, tiếp theo / (số tiếp theo) sẽ bắt đầu bị thiếu. Vì vậy, nếu bạn thực sự lo lắng về việc mất độ chính xác, hãy giữ lại các tổng!
AlexR

Câu trả lời:


91

Bạn chỉ cần làm:

double approxRollingAverage (double avg, double new_sample) {

    avg -= avg / N;
    avg += new_sample / N;

    return avg;
}

Trong trường hợp Nlà số lượng mẫu mà bạn muốn trên mức trung bình. Lưu ý rằng giá trị gần đúng này tương đương với đường trung bình động theo cấp số nhân. Xem: Tính toán trung bình lăn / di chuyển trong C ++


3
Bạn không phải thêm 1 vào N trước dòng này? avg + = new_sample / N;
Damian

20
Đây không phải là hoàn toàn chính xác. Những gì @Muis mô tả là một khoảng không di chuyển có trọng số theo cấp số nhân, đôi khi phù hợp nhưng không chính xác là những gì OP yêu cầu. Ví dụ: hãy xem xét hành vi mà bạn mong đợi khi hầu hết các điểm đều nằm trong khoảng từ 2 đến 4 nhưng một giá trị lên tới một triệu. Một EWMA (ở đây) sẽ lưu giữ dấu vết của hàng triệu đô la đó trong một thời gian khá dài. Một tích chập hữu hạn, như được chỉ ra bởi OP, sẽ mất nó ngay lập tức sau N bước. Nó có lợi thế là lưu trữ liên tục.
jma

9
Đó không phải là một đường trung bình động. Những gì bạn mô tả là một bộ lọc một cực tạo ra các phản hồi theo cấp số nhân để nhảy tín hiệu. Một đường trung bình động tạo ra một phản ứng tuyến tính với chiều dài N.
Brauner ruhig

3
Lưu ý rằng điều này khá xa so với định nghĩa phổ biến về trung bình. Nếu bạn đặt N = 5 và nhập 5 5mẫu, giá trị trung bình sẽ là 0,67.
Dan Dascalescu

2
@DanDascalescu Mặc dù bạn đã chính xác rằng nó không thực sự là giá trị trung bình luân phiên, nhưng giá trị đã nêu của bạn bị lệch theo thứ tự độ lớn. Với avgkhởi tạo thành 0, bạn kết thúc bằng 3.36sau 5 5giây và 4.46sau 10: cpp.sh/2ryql Đối với mức trung bình dài, đây chắc chắn là một ước tính hữu ích.
cincodenada

80
New average = old average * (n-1)/n + new value /n

Điều này giả sử số lượng chỉ thay đổi bởi một giá trị. Trong trường hợp nó được thay đổi bởi M giá trị thì:

new average = old average * (n-len(M))/n + (sum of values in M)/n).

Đây là công thức toán học (tôi tin là công thức hiệu quả nhất), tin rằng bạn có thể tự viết mã thêm


Tổng giá trị mới là gì? điều đó bằng cách nào đó khác với "giá trị mới" trong công thức ban đầu của bạn?
Mikhail

@Mikhail trong ví dụ thứ hai, có mcác giá trị mới được tính vào giá trị trung bình mới. Tôi tin rằng sum of new valueở đây có nghĩa là tổng các mgiá trị mới được sử dụng để tính giá trị trung bình mới.
Patrick Goley

9
Hiệu quả hơn một chút cho cái đầu tiên: new_average = (old_average * (n-1) + new_value) / n- Loại bỏ một trong các khoảng chia.
Pixelstix

Làm thế nào về hoạt động trung bình của 3 yếu tố với 6,0,0,9?
Roshan Mehta

1
Khi tôi thực hiện phương trình này, giá trị hoặc giá trị trung bình luôn tăng từ từ. Nó không bao giờ đi xuống - chỉ tăng lên.
anon58192932 Ngày

30

Từ một blog về chạy tính toán phương sai mẫu, nơi giá trị trung bình cũng được tính bằng phương pháp của Welford :

nhập mô tả hình ảnh ở đây

Thật tệ là chúng tôi không thể tải lên hình ảnh SVG.


3
Điều này tương tự như những gì Muis đã thực hiện, ngoại trừ việc phân chia được sử dụng một hệ số chung. Như vậy chỉ có một bộ phận.
Lật

Nó thực sự gần với @ Abdullah-Al-Ageel (về cơ bản là toán học giao hoán) ở chỗ Muis không tính đến việc tăng N; tham chiếu công thức sao chép-dán: [Trung bình tại n] = [Trung bình tại n-1] + (x - [Trung bình tại n-1]) / n
drzaus

2
@Flip & drwaus: Không phải giải pháp của Muis và Abdullah Al-Ageel hoàn toàn giống nhau sao? Đó là cùng một phép tính, chỉ được viết khác nhau. Đối với tôi 3 câu trả lời đó là giống nhau, câu trả lời này trực quan hơn (quá tệ là chúng tôi không thể sử dụng MathJax trên SO).
user276648

21

Đây là một câu trả lời khác đưa ra bình luận về cách mà câu trả lời của Muis , Abdullah Al-AgeelFlip đều giống nhau về mặt toán học ngoại trừ cách viết khác nhau.

Chắc chắn, chúng tôi có José Manuel Ramos phân tích của giải thích cách các lỗi làm tròn ảnh hưởng đến mỗi lỗi hơi khác nhau, nhưng điều đó phụ thuộc vào việc triển khai và sẽ thay đổi dựa trên cách mỗi câu trả lời được áp dụng cho mã.

Tuy nhiên, có một sự khác biệt khá lớn

Nó có trong Muis 's N, Flip ' s kAbdullah Al-Ageel 's n. Abdullah Al-Ageel không hoàn toàn giải thích những gì nnên có, nhưng Nkkhác biệt ở chỗ Nlà " số lượng mẫu mà bạn muốn lấy trung bình " trong khi klà số lượng các giá trị được lấy mẫu. (Mặc dù tôi có nghi ngờ về việc gọi N số lượng mẫu có chính xác không.)

Và đây chúng ta cùng đến với câu trả lời dưới đây. Về cơ bản nó giống như đường trung bình động có trọng số theo cấp số nhân như những đường khác, vì vậy nếu bạn đang tìm kiếm một giải pháp thay thế, hãy dừng lại ngay tại đây.

Đường trung bình động có trọng số theo cấp số nhân

Ban đầu:

average = 0
counter = 0

Đối với mỗi giá trị:

counter += 1
average = average + (value - average) / min(counter, FACTOR)

Sự khác biệt là min(counter, FACTOR)một phần. Điều này cũng giống như nóimin(Flip's k, Muis's N) .

FACTORlà một hằng số ảnh hưởng đến tốc độ trung bình "bắt kịp" xu hướng mới nhất. Số lượng càng nhỏ càng nhanh. ( 1Nó không còn là giá trị trung bình nữa và chỉ trở thành giá trị mới nhất.)

Câu trả lời này yêu cầu bộ đếm đang chạy counter. Nếu có vấn đề, min(counter, FACTOR)có thể được thay thế bằng chỉ FACTOR, biến nó thành câu trả lời của Muis . Vấn đề khi thực hiện điều này là đường trung bình bị ảnh hưởng bởi bất cứ thứ gì averageđược khởi tạo. Nếu nó được khởi tạo thành0 , số 0 đó có thể mất nhiều thời gian để vượt ra khỏi mức trung bình.

Nó kết thúc như thế nào

Đường trung bình động hàm mũ


3
Giải thích tốt. Tôi chỉ bỏ lỡ một mức trung bình rõ ràng trong biểu đồ của bạn, vì đó là những gì OP đã yêu cầu.
xmedeko 14/09/18

Có lẽ tôi đang thiếu một cái gì đó, nhưng bạn có tình cờ không max(counter, FACTOR). min(counter, FACTOR)sẽ luôn trả về FACTOR, phải không?
WebWanderer

1
Tôi tin rằng quan điểm của min(counter, FACTOR)nó là tính đến thời kỳ khởi động. Không có nó, nếu FACTOR của bạn (hoặc N, hoặc số lượng mẫu mong muốn) là 1000, thì bạn sẽ cần ít nhất 1000 mẫu trước khi nhận được kết quả chính xác, vì tất cả các bản cập nhật trước đó sẽ giả sử bạn có 1000 mẫu, khi bạn chỉ có thể có 20.
rharter

Sẽ rất tuyệt nếu bạn dừng đếm sau khi đạt đến yếu tố, có lẽ nó sẽ nhanh hơn theo cách đó.
inf3rno

8

Câu trả lời của Flip phù hợp hơn về mặt tính toán so với câu trả lời của Muis.

Sử dụng định dạng số kép, bạn có thể thấy vấn đề vòng tròn trong cách tiếp cận Muis:

Phương pháp Muis

Khi bạn chia và trừ, một dấu tròn sẽ xuất hiện trong giá trị được lưu trữ trước đó, thay đổi nó.

Tuy nhiên, phương pháp Flip bảo toàn giá trị được lưu trữ và giảm số lần phân chia, do đó, giảm thời gian làm tròn và giảm thiểu lỗi được truyền đến giá trị được lưu trữ. Chỉ thêm sẽ làm xuất hiện vòng tròn nếu có thứ gì đó cần thêm (khi N lớn, không có gì để thêm)

Cách tiếp cận Flip

Những thay đổi đó rất đáng chú ý khi bạn tạo ra một giá trị trung bình có xu hướng trung bình của chúng bằng không.

Tôi cho bạn thấy kết quả bằng chương trình bảng tính:

Thứ nhất, kết quả thu được: Các kết quả

Các cột A và B lần lượt là giá trị n và X_n.

Cột C là phương pháp Flip, và cột D là phương pháp Muis, kết quả được lưu trong giá trị trung bình. Cột E tương ứng với giá trị trung bình được sử dụng trong tính toán.

Biểu đồ hiển thị giá trị trung bình của các giá trị chẵn là biểu đồ tiếp theo:

Đồ thị

Như bạn có thể thấy, có sự khác biệt lớn giữa cả hai cách tiếp cận.


2
Không thực sự là một câu trả lời, nhưng thông tin hữu ích. Sẽ tốt hơn nữa nếu bạn thêm dòng thứ 3 vào biểu đồ của mình, cho giá trị trung bình thực trên n giá trị trong quá khứ, vì vậy chúng tôi có thể thấy cách tiếp cận nào trong hai cách tiếp cận gần nhất.
jpaugh

2
@jpaugh: Cột B xen kẽ giữa -1,00E + 15 và 1,00E + 15, vì vậy khi N là số chẵn, giá trị trung bình thực tế phải là 0. Tiêu đề của biểu đồ là "Giá trị một phần chẵn". Điều này có nghĩa là dòng thứ 3 mà bạn hỏi chỉ đơn giản là f (x) = 0. Biểu đồ cho thấy rằng cả hai cách tiếp cận đều đưa ra các lỗi liên tục tăng lên.
desowin 19/02/18

Đúng vậy, biểu đồ hiển thị chính xác lỗi được truyền bằng cách sử dụng các số lớn liên quan đến tính toán bằng cả hai cách tiếp cận.
José Manuel Ramos

Chú giải của biểu đồ của bạn có sai màu: Muis's màu cam, Flip's blue.
xmedeko 14/09/18

6

Một ví dụ sử dụng javascript, để so sánh:

https://jsfiddle.net/drzaus/Lxsa4rpz/

function calcNormalAvg(list) {
    // sum(list) / len(list)
    return list.reduce(function(a, b) { return a + b; }) / list.length;
}
function calcRunningAvg(previousAverage, currentNumber, index) {
    // [ avg' * (n-1) + x ] / n
    return ( previousAverage * (index - 1) + currentNumber ) / index;
}


1

Trong Java8:

LongSummaryStatistics movingAverage = new LongSummaryStatistics();
movingAverage.accept(new data);
...
average = movingAverage.getAverage();

bạn cũng có IntSummaryStatistics, DoubleSummaryStatistics...


2
OP đang yêu cầu một thuật toán, không phải cho một con trỏ làm thế nào để tính toán điều này trong Java.
olq_plo

0

Một giải pháp Python gọn gàng dựa trên các câu trả lời trên:

class RunningAverage():
    def __init__(self):
        self.average = 0
        self.n = 0
        
    def __call__(self, new_value):
        self.n += 1
        self.average = (self.average * (self.n-1) + new_value) / self.n 
        
    def __float__(self):
        return self.average
    
    def __repr__(self):
        return "average: " + str(self.average)

sử dụng:

x = RunningAverage()
x(0)
x(2)
x(4)
print(x)
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.