จะคำนวณค่าเฉลี่ยเคลื่อนที่โดยไม่เก็บจำนวนและยอดรวมข้อมูลได้อย่างไร


119

ฉันกำลังพยายามหาวิธีคำนวณค่าเฉลี่ยสะสมเคลื่อนที่โดยไม่เก็บจำนวนและข้อมูลทั้งหมดที่ได้รับจนถึงตอนนี้

ฉันมีสองอัลกอริทึม แต่ทั้งคู่จำเป็นต้องจัดเก็บการนับ:

  • ค่าเฉลี่ยใหม่ = ((จำนวนเก่า * ข้อมูลเก่า) + ข้อมูลถัดไป) / การนับถัดไป
  • ค่าเฉลี่ยใหม่ = ค่าเฉลี่ยเก่า + (ข้อมูลถัดไป - ค่าเฉลี่ยเก่า) / การนับถัดไป

ปัญหาของวิธีการเหล่านี้คือจำนวนที่มากขึ้นเรื่อย ๆ ส่งผลให้สูญเสียความแม่นยำในค่าเฉลี่ยที่ได้

วิธีแรกใช้การนับเก่าและการนับถัดไปซึ่งเห็นได้ชัดว่าห่างกัน 1 สิ่งนี้ทำให้ฉันคิดว่าอาจมีวิธีลบการนับ แต่น่าเสียดายที่ฉันยังไม่พบ มันทำให้ฉันได้ไกลขึ้นเล็กน้อยซึ่งส่งผลให้วิธีที่สอง แต่ยังคงมีการนับอยู่

เป็นไปได้หรือฉันแค่ค้นหาสิ่งที่เป็นไปไม่ได้


1
หมายเหตุตามตัวเลขการจัดเก็บจำนวนรวมปัจจุบันและจำนวนปัจจุบันเป็นวิธีที่เสถียรที่สุด มิฉะนั้นสำหรับการนับที่สูงขึ้นถัดไป / (การนับถัดไป) จะเริ่มน้อยลง ดังนั้นหากคุณกังวลเกี่ยวกับการสูญเสียความแม่นยำให้เก็บผลรวมไว้!
AlexR

คำตอบ:


91

คุณสามารถทำได้ง่ายๆ:

double approxRollingAverage (double avg, double new_sample) {

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

    return avg;
}

ที่ไหนNเป็นจำนวนตัวอย่างที่คุณต้องการไปกว่าค่าเฉลี่ย โปรดทราบว่าการประมาณนี้เทียบเท่ากับค่าเฉลี่ยเคลื่อนที่เลขชี้กำลัง ดู: คำนวณการหมุน / ค่าเฉลี่ยเคลื่อนที่ใน C ++


3
คุณไม่ต้องเพิ่ม 1 ใน N ก่อนบรรทัดนี้หรือไม่? เฉลี่ย + = new_sample / N;
Damian

20
สิ่งนี้ไม่ถูกต้องทั้งหมด สิ่งที่ @ Muis อธิบายคือค่าเฉลี่ยเคลื่อนที่แบบถ่วงน้ำหนักแบบทวีคูณซึ่งบางครั้งก็เหมาะสม แต่ไม่ตรงตามที่ OP ร้องขอ ตัวอย่างเช่นพิจารณาพฤติกรรมที่คุณคาดหวังเมื่อจุดส่วนใหญ่อยู่ในช่วง 2 ถึง 4 แต่ค่าหนึ่งสูงกว่าหนึ่งล้าน EWMA (ที่นี่) จะยึดร่องรอยของล้านนั้นไว้สักระยะหนึ่ง การชักแบบ จำกัด ตามที่ OP ระบุจะสูญเสียทันทีหลังจาก N ขั้นตอน มีข้อดีของการจัดเก็บคงที่
jma

9
นั่นไม่ใช่ค่าเฉลี่ยเคลื่อนที่ สิ่งที่คุณอธิบายคือตัวกรองขั้วเดียวที่สร้างการตอบสนองแบบเอ็กซ์โพเนนเชียลเพื่อกระโดดในสัญญาณ ค่าเฉลี่ยเคลื่อนที่สร้างการตอบสนองเชิงเส้นที่มีความยาว N
ruhig brauner

3
ระวังว่านี่ค่อนข้างห่างไกลจากคำจำกัดความทั่วไปของค่าเฉลี่ย หากคุณตั้งค่า N = 5 และป้อน 5 5ตัวอย่างค่าเฉลี่ยจะเท่ากับ 0.67
Dan Dascalescu

2
@DanDascalescu ในขณะที่คุณถูกต้องว่ามันไม่ใช่ค่าเฉลี่ยแบบหมุน แต่ค่าที่ระบุของคุณจะออกตามลำดับขนาด เมื่อavgเริ่มต้น0คุณจะได้3.36หลัง 5 5วินาทีและ4.46หลังจาก 10: cpp.sh/2ryqlสำหรับค่าเฉลี่ยระยะยาวนี่เป็นการประมาณที่มีประโยชน์อย่างแน่นอน
cincodenada

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

นี่คือสมมติว่าจำนวนเปลี่ยนแปลงเพียงค่าเดียว ในกรณีที่ค่า M เปลี่ยนแปลงแล้ว:

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

นี่คือสูตรทางคณิตศาสตร์ (ฉันเชื่อว่าเป็นสูตรที่มีประสิทธิภาพมากที่สุด) เชื่อว่าคุณสามารถสร้างรหัสเพิ่มเติมได้ด้วยตัวเอง


ผลรวมของมูลค่าใหม่คืออะไร? มันแตกต่างจาก "ค่าใหม่" ในสูตรเดิมของคุณหรือไม่
Mikhail

@Mikhail ในตัวอย่างที่สองมีmการนำค่าใหม่มารวมอยู่ในค่าเฉลี่ยใหม่ ฉันเชื่อว่าsum of new valueนี่คือผลรวมของmค่าใหม่ที่ใช้ในการคำนวณค่าเฉลี่ยใหม่
Patrick Goley

10
มีประสิทธิภาพมากกว่าเล็กน้อยสำหรับอันแรก: new_average = (old_average * (n-1) + new_value) / n- ลบหนึ่งในการหาร
Pixelstix

แล้วการรันค่าเฉลี่ย 3 องค์ประกอบกับ 6,0,0,9 ล่ะ?
Roshan Mehta

1
เมื่อฉันใช้สมการนี้ค่าหรือค่าเฉลี่ยที่ทำงานจะเพิ่มขึ้นอย่างช้าๆเสมอ มันไม่เคยลง - ขึ้นเท่านั้น
anon58192932

30

จากบล็อกในการทำงานการคำนวณค่าความแปรปรวนของกลุ่มตัวอย่างที่มีค่าเฉลี่ยนอกจากนี้ยังมีการคำนวณโดยใช้วิธีการของ Welford :

ใส่คำอธิบายภาพที่นี่

เสียดายที่เราไม่สามารถอัปโหลดภาพ SVG ได้


3
สิ่งนี้คล้ายกับสิ่งที่ Muis นำมาใช้ยกเว้นว่าการแบ่งนั้นใช้ปัจจัยร่วมกัน จึงมีเพียงกองเดียว
พลิก

จริงๆแล้วมันใกล้เคียงกับ @ Abdullah-Al-Ageel (คณิตศาสตร์สับเปลี่ยนเป็นหลัก) โดยที่ Muis ไม่ได้คำนึงถึงการเพิ่ม N; การอ้างอิงสูตรคัดลอกวาง: [Avg at n] = [Avg at n-1] + (x - [Avg at n-1]) / n
drzaus

2
@Flip & drwaus: โซลูชันของ Muis และ Abdullah Al-Ageel ไม่เหมือนกันใช่ไหม เป็นการคำนวณเหมือนกันเขียนต่างกัน สำหรับฉันคำตอบทั้ง 3 ข้อนั้นเป็นตัวบ่งชี้คำตอบนี้เป็นภาพที่ชัดเจนกว่า (แย่มากที่เราไม่สามารถใช้ MathJax ใน SO)
user276648

23

นี่เป็นอีกหนึ่งคำตอบที่นำเสนอความเห็นว่าคำตอบของMuis , Abdullah Al-AgeelและFlipเป็นอย่างไรในทางคณิตศาสตร์เหมือนกันยกเว้นเขียนแตกต่างกัน

แน่นอนว่าเรามีการวิเคราะห์ของJosé Manuel Ramosที่อธิบายว่าข้อผิดพลาดในการปัดเศษส่งผลต่อแต่ละข้อแตกต่างกันเล็กน้อย แต่การใช้งานนั้นขึ้นอยู่กับวิธีการใช้คำตอบแต่ละข้อกับโค้ด

อย่างไรก็ตามมีความแตกต่างที่ค่อนข้างใหญ่

มันอยู่ในMuis 's N, พลิก ' s kและอับดุลลาห์อัล Ageeln 's อับดุลลาห์อัล Ageelไม่ค่อยอธิบายสิ่งที่nควรจะเป็น แต่Nและkแตกต่างกันในที่Nนี้คือ " จำนวนตัวอย่างที่คุณต้องการมากกว่าค่าเฉลี่ย " ในขณะที่kเป็นนับค่าตัวอย่าง (แม้ว่าฉันจะสงสัยว่าการโทรN ไปที่จำนวนกลุ่มตัวอย่างนั้นถูกต้องหรือไม่)

และที่นี่เรามาถึงคำตอบด้านล่าง โดยพื้นฐานแล้วมันเป็นค่าเฉลี่ยเคลื่อนที่แบบถ่วงน้ำหนักเลขชี้กำลังแบบเดิม ๆเช่นเดียวกับค่าอื่น ๆ ดังนั้นหากคุณกำลังมองหาทางเลือกอื่นให้หยุดตรงนี้

ค่าเฉลี่ยเคลื่อนที่ถ่วงน้ำหนักเอกซ์โปเนนเชียล

ในขั้นต้น:

average = 0
counter = 0

สำหรับแต่ละค่า:

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

ความแตกต่างคือmin(counter, FACTOR)ส่วน min(Flip's k, Muis's N)นี้เป็นเช่นเดียวกับการพูดว่า

FACTORคือค่าคงที่ที่มีผลต่อการ "จับ" แนวโน้มล่าสุดโดยเฉลี่ย จำนวนน้อยยิ่งเร็ว ( 1มันไม่ใช่ค่าเฉลี่ยอีกต่อไปและกลายเป็นค่าล่าสุด)

counterคำตอบนี้ต้องทำงานเคาน์เตอร์ หากมีปัญหาmin(counter, FACTOR)สามารถแทนที่ด้วย just FACTORเปลี่ยนเป็นคำตอบของMuis ปัญหาในการทำเช่นนี้คือค่าเฉลี่ยเคลื่อนที่ได้รับผลกระทบจากสิ่งที่averageเริ่มต้น หากเริ่มต้น0เป็นศูนย์นั้นอาจใช้เวลานานในการหาค่าเฉลี่ย

มันจบลงอย่างไร

ค่าเฉลี่ยเคลื่อนที่เอกซ์โปเนนเชียล


3
อธิบายได้ดี ฉันพลาดค่าเฉลี่ยธรรมดาในกราฟของคุณเพราะสิ่งที่ OP ถาม
xmedeko

บางทีฉันหายไปบางอย่าง max(counter, FACTOR)แต่คุณโดยบังเอิญค่าเฉลี่ย min(counter, FACTOR)จะคืนค่า FACTOR เสมอใช่ไหม?
WebWanderer

1
ฉันเชื่อว่าประเด็นmin(counter, FACTOR)คือการอธิบายช่วงวอร์มอัพ หากไม่มีถ้า FACTOR ของคุณ (หรือ N หรือจำนวนตัวอย่างที่ต้องการ) คือ 1,000 คุณจะต้องมีตัวอย่างอย่างน้อย 1,000 ตัวอย่างก่อนที่จะได้ผลลัพธ์ที่ถูกต้องเนื่องจากการอัปเดตทั้งหมดก่อนหน้านั้นจะถือว่าคุณมีตัวอย่าง 1,000 ตัวอย่างเมื่อคุณทำได้เท่านั้น มี 20
rharter

จะเป็นการดีที่จะหยุดนับหลังจากถึงตัวประกอบอาจจะเร็วกว่านั้น
inf3rno

9

คำตอบของ Flip มีความสอดคล้องกันในเชิงคำนวณมากกว่า Muis

เมื่อใช้รูปแบบเลขคู่คุณจะเห็นปัญหาการปัดเศษในแนวทาง Muis:

แนวทาง Muis

เมื่อคุณหารและลบการปัดเศษจะปรากฏในค่าที่เก็บไว้ก่อนหน้าโดยเปลี่ยนค่านั้น

อย่างไรก็ตามวิธีการ Flip จะรักษาค่าที่จัดเก็บไว้และลดจำนวนหน่วยงานดังนั้นจึงลดการปัดเศษและลดข้อผิดพลาดที่แพร่กระจายไปยังค่าที่จัดเก็บไว้ให้น้อยที่สุด การเพิ่มจะทำให้เกิดการปัดเศษขึ้นหากมีบางสิ่งที่จะเพิ่ม (เมื่อ N มีขนาดใหญ่จะไม่มีอะไรให้เพิ่ม)

วิธีการพลิก

การเปลี่ยนแปลงเหล่านี้เป็นสิ่งที่น่าทึ่งเมื่อคุณกำหนดค่านิยมจำนวนมากมักมีค่าเฉลี่ยเป็นศูนย์

ฉันแสดงผลลัพธ์โดยใช้โปรแกรมสเปรดชีต:

ประการแรกผลที่ได้รับ: ผล

คอลัมน์ A และ B คือค่า n และ X_n ตามลำดับ

คอลัมน์ C คือวิธีการพลิกและ D หนึ่งคือแนวทาง Muis ซึ่งเป็นผลลัพธ์ที่เก็บไว้ในค่าเฉลี่ย คอลัมน์ E สอดคล้องกับค่ากลางที่ใช้ในการคำนวณ

กราฟที่แสดงค่าเฉลี่ยของค่าคู่คือกราฟถัดไป:

กราฟ

อย่างที่คุณเห็นมีความแตกต่างอย่างมากระหว่างทั้งสองแนวทาง


2
ไม่ใช่คำตอบ แต่เป็นข้อมูลที่มีประโยชน์ จะดียิ่งขึ้นถ้าคุณเพิ่มเส้นที่ 3 ลงในกราฟของคุณสำหรับค่าเฉลี่ยที่แท้จริงมากกว่าค่าที่ผ่านมาnค่าดังนั้นเราจะได้เห็นว่าแนวทางใดในสองแนวทางที่ใกล้เคียงที่สุด
jpaugh

2
@jpaugh: คอลัมน์ B สลับระหว่าง -1.00E + 15 และ 1.00E + 15 ดังนั้นเมื่อ N เท่ากันค่าเฉลี่ยที่แท้จริงควรเป็น 0 ชื่อของกราฟคือ "Even partial mean" ซึ่งหมายความว่าบรรทัดที่ 3 ที่คุณถามคือ f (x) = 0 กราฟแสดงให้เห็นว่าทั้งสองแนวทางแนะนำข้อผิดพลาดที่เกิดขึ้นเรื่อย ๆ
desowin

ถูกต้องกราฟจะแสดงข้อผิดพลาดที่แพร่กระจายโดยใช้ตัวเลขจำนวนมากที่เกี่ยวข้องกับการคำนวณโดยใช้ทั้งสองวิธี
José Manuel Ramos

คำอธิบายแผนภูมิของคุณมีสีผิด: Muis เป็นสีส้มส่วน Flip เป็นสีน้ำเงิน
xmedeko

6

ตัวอย่างการใช้จาวาสคริปต์เพื่อเปรียบเทียบ:

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

ใน Java8:

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

คุณยังมีIntSummaryStatistics, DoubleSummaryStatistics...


2
OP กำลังขออัลกอริทึมไม่ใช่สำหรับตัวชี้วิธีคำนวณใน Java
olq_plo

0

โซลูชัน Python ที่เป็นระเบียบตามคำตอบข้างต้น:

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)

การใช้งาน:

x = RunningAverage()
x(0)
x(2)
x(4)
print(x)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.