การกำหนดค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานแบบเรียลไทม์


31

อะไรจะเป็นวิธีที่เหมาะในการค้นหาค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานของสัญญาณสำหรับแอปพลิเคชันตามเวลาจริง ฉันต้องการที่จะทริกเกอร์คอนโทรลเลอร์เมื่อสัญญาณมีค่าเบี่ยงเบนมาตรฐานมากกว่า 3 จากค่าเฉลี่ยในระยะเวลาหนึ่ง

ฉันสมมติว่า DSP โดยเฉพาะจะทำสิ่งนี้ได้อย่างง่ายดาย แต่มี "ทางลัด" ใด ๆ ที่อาจไม่ต้องการอะไรที่ซับซ้อนขนาดนี้หรือไม่?


คุณรู้อะไรเกี่ยวกับสัญญาณหรือไม่ มันหยุดนิ่งหรือไม่?

@Tim สมมติว่าเป็นเครื่องเขียน สำหรับความอยากรู้อยากเห็นของฉันเองสิ่งที่จะเป็นประโยชน์ของสัญญาณที่ไม่หยุดนิ่ง?
jonsca

3
หากเป็นเครื่องเขียนคุณสามารถคำนวณค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานได้ สิ่งต่าง ๆ จะซับซ้อนกว่านี้ถ้าค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานแปรผันตามเวลา

5
มีความเกี่ยวข้องมาก: en.wikipedia.org/wiki/…
ดร. เบลิซาเรียส

คำตอบ:


36

มีข้อบกพร่องในคำตอบของ Jason R ซึ่งถูกกล่าวถึงในบท "Art of Computer Programming" ของ Knuth 2. ปัญหาเกิดขึ้นหากคุณมีค่าเบี่ยงเบนมาตรฐานซึ่งเป็นส่วนเล็ก ๆ ของค่าเฉลี่ย: การคำนวณ E (x ^ 2) - (E (x) ^ 2) ได้รับความทุกข์ทรมานจากความไวที่รุนแรงถึงข้อผิดพลาดในการปัดเศษทศนิยม

คุณสามารถลองด้วยตัวคุณเองในสคริปต์ Python:

ofs = 1e9
A = [ofs+x for x in [1,-1,2,3,0,4.02,5]] 
A2 = [x*x for x in A]
(sum(A2)/len(A))-(sum(A)/len(A))**2

ฉันได้รับ -128.0 เป็นคำตอบซึ่งเห็นได้ชัดว่าไม่ถูกต้องในการคำนวณเนื่องจากคณิตศาสตร์ทำนายว่าผลลัพธ์ควรไม่เป็นค่าลบ

Knuth อ้างอิงแนวทาง (ฉันจำชื่อผู้ประดิษฐ์ไม่ได้) สำหรับการคำนวณค่าเฉลี่ยการทำงานและส่วนเบี่ยงเบนมาตรฐานซึ่งมีลักษณะดังนี้:

 initialize:
    m = 0;
    S = 0;
    n = 0;

 for each incoming sample x:
    prev_mean = m;
    n = n + 1;
    m = m + (x-m)/n;
    S = S + (x-m)*(x-prev_mean);

และหลังจากแต่ละขั้นตอนแล้วค่าของmคือค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานสามารถคำนวณตามsqrt(S/n)หรือsqrt(S/n-1)ขึ้นอยู่กับคำจำกัดความที่คุณชื่นชอบของส่วนเบี่ยงเบนมาตรฐาน

สมการที่ฉันเขียนด้านบนนั้นแตกต่างจากที่ Knuth เล็กน้อย แต่มันเทียบเท่ากับการคำนวณ

เมื่อฉันมีเวลาอีกไม่กี่นาทีฉันจะเขียนสูตรสูตรข้างต้นใน Python และแสดงว่าคุณจะได้รับคำตอบที่ไม่จำเป็น (หวังว่าจะใกล้เคียงกับค่าที่ถูกต้อง)


update: นี่มันคือ

test1.py:

import math

def stats(x):
  n = 0
  S = 0.0
  m = 0.0
  for x_i in x:
    n = n + 1
    m_prev = m
    m = m + (x_i - m) / n
    S = S + (x_i - m) * (x_i - m_prev)
  return {'mean': m, 'variance': S/n}

def naive_stats(x):
  S1 = sum(x)
  n = len(x)
  S2 = sum([x_i**2 for x_i in x])
  return {'mean': S1/n, 'variance': (S2/n - (S1/n)**2) }

x1 = [1,-1,2,3,0,4.02,5] 
x2 = [x+1e9 for x in x1]

print "naive_stats:"
print naive_stats(x1)
print naive_stats(x2)

print "stats:"
print stats(x1)
print stats(x2)

ผล:

naive_stats:
{'variance': 4.0114775510204073, 'mean': 2.0028571428571427}
{'variance': -128.0, 'mean': 1000000002.0028572}
stats:
{'variance': 4.0114775510204073, 'mean': 2.0028571428571431}
{'variance': 4.0114775868357446, 'mean': 1000000002.0028571}

คุณจะได้ทราบว่ายังคงมีข้อผิดพลาดบางปัดเศษ แต่มันก็ไม่ได้เลวร้ายในขณะที่naive_statsเพียง pukes


แก้ไข: เพิ่งสังเกตเห็นความคิดเห็นของเบลิซาเรียสอ้างถึงวิกิพีเดียซึ่งพูดถึงอัลกอริธึมของ Knuth


1
+1 สำหรับคำตอบโดยละเอียดพร้อมโค้ดตัวอย่าง วิธีนี้ดีกว่าวิธีที่ระบุไว้ในคำตอบของฉันเมื่อจำเป็นต้องมีการใช้จุดลอยตัว
Jason R

1
หนึ่งอาจตรวจสอบนี้สำหรับการใช้งาน C ++: johndcook.com/standard_deviation.html
Rui Marques

1
ใช่นั่นแหล่ะ เขาใช้สมการที่ Knuth ใช้แน่นอน คุณสามารถปรับให้เหมาะสมบ้างและหลีกเลี่ยงการตรวจสอบการวนซ้ำเริ่มต้นกับการวนซ้ำตามมาหากคุณใช้วิธีการของฉัน
Jason S

"นูอ้างอิงวิธีการ (ผมจำไม่ได้ว่าชื่อของนักประดิษฐ์) สำหรับการคำนวณค่าเฉลี่ยการทำงาน" - มันเป็นวิธีการของ Welfordโดยวิธีการ
Jason S

ฉันได้โพสต์คำถามที่เกี่ยวข้องกับเรื่องนี้ถ้าใครสามารถช่วยได้: dsp.stackexchange.com/questions/31812/…
Jonathan

13

อะไรจะเป็นวิธีที่เหมาะในการค้นหาค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานของสัญญาณสำหรับแอปพลิเคชันตามเวลาจริง ฉันต้องการที่จะทริกเกอร์คอนโทรลเลอร์เมื่อสัญญาณมีค่าเบี่ยงเบนมาตรฐานมากกว่า 3 จากค่าเฉลี่ยในระยะเวลาหนึ่ง

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

ในโดเมนความถี่ "ค่าเฉลี่ยถ่วงน้ำหนักการทำงานแบบเอ็กซ์โปเนนเชียล" เป็นเพียงเสาจริง มันง่ายที่จะใช้ในโดเมนเวลา

การใช้โดเมนเวลา

อนุญาตmeanและmeansqเป็นค่าประมาณปัจจุบันของค่าเฉลี่ยและค่าเฉลี่ยของกำลังสองของสัญญาณ ทุกรอบให้อัปเดตค่าประมาณเหล่านี้ด้วยตัวอย่างใหม่x:

% update the estimate of the mean and the mean square:
mean = (1-a)*mean + a*x
meansq = (1-a)*meansq + a*(x^2)

% calculate the estimate of the variance:
var = meansq - mean^2;

% and, if you want standard deviation:
std = sqrt(var);

ที่นี่เป็นค่าคงที่ที่กำหนดความยาวที่มีประสิทธิภาพของค่าเฉลี่ยการวิ่ง วิธีเลือกอธิบายไว้ด้านล่างใน "การวิเคราะห์"0<a<1a

สิ่งที่แสดงไว้ข้างต้นในฐานะโปรแกรมที่จำเป็นอาจถูกอธิบายเป็นไดอะแกรมสัญญาณการไหล:

ป้อนคำอธิบายรูปภาพที่นี่

การวิเคราะห์

อัลกอริทึมด้านบนคำนวณโดยที่คืออินพุตที่ตัวอย่างและคือเอาต์พุต (ประมาณค่าเฉลี่ยของค่าเฉลี่ย) นี่เป็นตัวกรอง IIR แบบเสาเดี่ยวที่เรียบง่าย การเปลี่ยนเราพบฟังก์ชันถ่ายโอน1}}yi=axi+(1a)yi1xiiyiz

H(z)=a1(1a)z1

กลั่นตัวกรอง IIR ลงในบล็อกของตัวเองไดอะแกรมจะมีลักษณะดังนี้:

ป้อนคำอธิบายรูปภาพที่นี่

หากต้องการไปยังโดเมนต่อเนื่องเราทำการแทนที่โดยที่คือเวลาตัวอย่างและคืออัตราตัวอย่าง การแก้เราจะพบว่าระบบอย่างต่อเนื่องมีเสาที่(1-A) z=esTTfs=1/T1(1a)esT=0s=1Tlog(1a)

เลือก:a

a=1exp{2πTτ}

อ้างอิง


1
คุณช่วยอธิบายวิธีกำหนดความยาวของค่าเฉลี่ยการวิ่งได้ไหม? และสิ่งที่มีค่าของควรใช้? สเปคเป็นไปไม่ได้ที่จะตอบสนอง aa0 > a > 1
Dilip Sarwate

นี่คล้ายกับแนวทางของ Jason R วิธีนี้จะแม่นยำน้อยลง แต่เร็วขึ้นและลดลงในหน่วยความจำ วิธีนี้จบลงด้วยการใช้หน้าต่างเอ็กซ์โปเนนเชียล
schnarf

woops! 0 < a < 1แน่นอนผมหมายถึง หากระบบของคุณได้สุ่มตัวอย่าง tmie Tและคุณต้องการคงที่เวลาเฉลี่ยแล้วเลือกtau a = 1 - exp (2*pi*T/tau)
nibot

ฉันคิดว่าอาจจะมีข้อผิดพลาดในที่นี่ ตัวกรองแบบขั้วเดี่ยวไม่มี 0 dB เกนที่ DC และเนื่องจากคุณใช้ตัวกรองหนึ่งตัวในโดเมนเชิงเส้นและอีกหนึ่งตัวในโดเมนกำลังสองข้อผิดพลาดเกนต่างกันสำหรับ E <x> และ E <x ^ 2> ฉันจะอธิบายเพิ่มเติมในคำตอบของฉัน
Hilmar

มันจะได้รับ 0 dB ที่ DC เปลี่ยนตัวz=1(DC) เข้าH(z) = a/(1-(1-a)/z)และคุณจะได้รับ 1.
nibot

5

วิธีที่ฉันใช้ก่อนหน้านี้ในแอปพลิเคชันการประมวลผลแบบฝังตัวคือการรักษาการสะสมของผลรวมและผลรวมของกำลังสองของสัญญาณที่น่าสนใจ:

Ax,i=k=0ix[k]=Ax,i1+x[i],Ax,1=0

Ax2,i=k=0ix2[k]=Ax2,i1+x2[i],Ax2,1=0

นอกจากนี้ติดตามการทันทีเวลาปัจจุบันในสมการข้างต้น (นั่นคือทราบจำนวนตัวอย่างที่คุณเพิ่มเข้าไปสะสมที่) จากนั้นค่าเฉลี่ยตัวอย่างและค่าเบี่ยงเบนมาตรฐาน ณ เวลาที่เป็น:ii

μ~=Axii+1

σ~=Axi2i+1μ~2

หรือคุณสามารถใช้:

σ~=Axi2iμ~2

ซึ่งขึ้นอยู่กับวิธีการประมาณค่าส่วนเบี่ยงเบนมาตรฐานที่คุณต้องการ สมการเหล่านี้ตั้งอยู่บนพื้นฐานของนิยามของความแปรปรวน :

σ2=E(X2)(E(X))2

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

แก้ไข:นอกเหนือจากความคิดเห็นข้างต้นเกี่ยวกับการโอเวอร์โฟลว์แล้วมันควรจะสังเกตว่านี่ไม่ใช่อัลกอริธึมเชิงตัวเลขเมื่อนำมาใช้ในการคำนวณเลขทศนิยมซึ่งอาจทำให้เกิดข้อผิดพลาดขนาดใหญ่ในสถิติโดยประมาณ ดูคำตอบของ Jason S สำหรับวิธีการที่ดีกว่าในกรณีนั้น


1
อาจเขียนใหม่เป็นจะทำให้ชัดเจนว่าคุณต้องเพิ่มตัวเลขเพียงสองตัวที่ แต่ละขั้นตอนมีคนเกรงว่าจะใช้มันเป็นข้อสรุปทั้งหมดองค์ประกอบของในแต่ละขั้นตอน i xAx,i=x[i]+Ax,i1, Ax,0=x[0]ix
Lorem Ipsum

ใช่ดีกว่า ฉันพยายามเขียนซ้ำเพื่อให้การใช้งานแบบเรียกซ้ำนั้นชัดเจนยิ่งขึ้น
Jason R

2
-1 เมื่อฉันมีตัวแทนเพียงพอที่จะทำเช่นนี้มีปัญหาเชิงตัวเลข ดู Knuth vol. 2
Jason S

ดูเหมือนจะมีการพิมพ์ผิดที่นี่สองสาม ทำไมเฉลี่ยถูกหักออกภายใต้เครื่องหมายรากที่สองสำหรับ ? มันควรเป็นเพื่อให้ตรงกับสมการที่แสดงใช่ไหม? นอกจากนี้แม้ว่าฉันจะไม่โหวตคำตอบนี้ แต่ฉันเห็นด้วยกับ Jason S ว่าอาจมีปัญหาเชิงตัวเลขในวิธีการนี้ μ 2 σ 2 = E ( X 2 ) - ( E ( X ) ) 2σμ2σ2=E(X2)(E(X))2
Dilip Sarwate

2
@ JasonS: ฉันจะไม่เห็นด้วยกับเทคนิคที่มีข้อบกพร่องโดยเนื้อแท้แม้ว่าฉันจะเห็นด้วยกับจุดของคุณว่ามันไม่ได้เป็นวิธีที่แข็งแกร่งเชิงตัวเลขเมื่อดำเนินการในจุดลอย ฉันควรจะได้รับความชัดเจนมากขึ้นว่าผมเคยใช้นี้ประสบความสำเร็จในโปรแกรมที่ใช้จำนวนเต็มคณิตศาสตร์ เลขจำนวนเต็ม (หรือการใช้จุดคงที่ของตัวเลขเศษส่วน) ไม่ได้ประสบปัญหาที่คุณชี้ให้เห็นว่าทำให้สูญเสียความแม่นยำ ในบริบทนั้นมันเป็นวิธีการที่เหมาะสมที่ต้องการการดำเนินงานน้อยลงต่อตัวอย่าง
Jason R

3

เช่นเดียวกับคำตอบที่ต้องการข้างต้น (Jason S. ) และยังได้มาจากสูตรที่นำมาจาก Knut (เล่ม 2, หน้า 232) เรายังสามารถหาสูตรเพื่อแทนที่ค่าเช่นลบและเพิ่มค่าในขั้นตอนเดียว . ตามการทดสอบของฉันแทนที่ให้ความแม่นยำที่ดีกว่าการลบ / เพิ่มสองขั้นตอน

รหัสด้านล่างนี้อยู่ใน Java meanและsรับการปรับปรุง (ตัวแปรสมาชิก "global") เหมือนกับmและsข้างบนในโพสต์ของ Jason ค่าหมายถึงขนาดของหน้าต่างcountn

/**
 * Replaces the value {@code x} currently present in this sample with the
 * new value {@code y}. In a sliding window, {@code x} is the value that
 * drops out and {@code y} is the new value entering the window. The sample
 * count remains constant with this operation.
 * 
 * @param x
 *            the value to remove
 * @param y
 *            the value to add
 */
public void replace(double x, double y) {
    final double deltaYX = y - x;
    final double deltaX = x - mean;
    final double deltaY = y - mean;
    mean = mean + deltaYX / count;
    final double deltaYp = y - mean;
    final double countMinus1 = count - 1;
    s = s - count / countMinus1 * (deltaX * deltaX - deltaY * deltaYp) - deltaYX * deltaYp / countMinus1;
}

3

คำตอบของ Jason และ Nibot นั้นแตกต่างกันในด้านที่สำคัญอย่างหนึ่ง: วิธีการของ Jason คำนวณ std dev และค่าเฉลี่ยของสัญญาณทั้งหมด (ตั้งแต่ y = 0) ในขณะที่ Nibot นั้นเป็นการคำนวณ "กำลังทำงาน" นั่นคือน้ำหนักตัวอย่างล่าสุดที่แข็งแกร่งกว่าตัวอย่างจาก อดีตอันไกลโพ้น

เนื่องจากแอปพลิเคชันดูเหมือนจะต้องการ std dev และหมายความว่าเป็นฟังก์ชันของเวลาวิธีการของ Nibot อาจเป็นวิธีที่เหมาะสมกว่า (สำหรับแอปพลิเคชันเฉพาะนี้) อย่างไรก็ตามส่วนที่ยากจริงจะได้รับส่วนถ่วงเวลาที่เหมาะสม ตัวอย่างของ Nibot ใช้ตัวกรองขั้วเดี่ยวอย่างง่าย

วิธีการที่เหมาะสมในการอธิบายนี้คือการที่เราได้รับการประมาณการของโดยการกรองและประมาณการสำหรับโดยการกรอง 2 โดยทั่วไปตัวกรองการประมาณจะเป็นตัวกรองความถี่ต่ำ ตัวกรองเหล่านี้ควรถูกปรับสัดส่วนเพื่อให้ได้รับ 0dB ที่ 0 Hz มิฉะนั้นจะมีข้อผิดพลาดเกนคงที่x [ n ] E [ x 2 ] x [ n ] 2E[x]x[n]E[x2]x[n]2

ตัวเลือกของตัวกรอง lowpass สามารถนำโดยสิ่งที่คุณรู้เกี่ยวกับสัญญาณและความละเอียดของเวลาที่คุณต้องการสำหรับการประเมินของคุณ ความถี่ตัดต่ำและลำดับสูงขึ้นจะส่งผลให้ความแม่นยำดีขึ้น แต่เวลาตอบสนองช้าลง

ในการทำให้สิ่งต่าง ๆ มีความซับซ้อนยิ่งขึ้นตัวกรองหนึ่งตัวจะถูกนำไปใช้ในโดเมนเชิงเส้น Squaring จะเปลี่ยนเนื้อหาสเปกตรัมของสัญญาณอย่างมีนัยสำคัญดังนั้นคุณอาจต้องการใช้ตัวกรองอื่นในโดเมนกำลังสอง

นี่คือตัวอย่างเกี่ยวกับวิธีการประมาณค่าเฉลี่ย rms และ std dev เป็นฟังก์ชันของเวลา

%% example
fs = 44100; n = fs; % 44.1 kHz sample rate, 1 second
% signal: white noise plus a low frequency drift at 5 Hz)
x = randn(n,1) + sin(2*pi*(0:n-1)'*5/fs);
% mean estimation filter: since we are looking for effects in the 5 Hz range we use maybe a
% 25 Hz filter, 2nd order so it's not too sluggish
[b,a] = butter(2,25*2/fs);
xmeanEst = filter(b,a,x);
% now we estimate x^2, since most frequency double we use twice the bandwidth
[b,a] = butter(2,50*2/fs);
x2Est = filter(b,a,x.^2);
% std deviation estimate
xstd = sqrt(x2Est)-xmeanEst;
% and plot it
h = plot([x, xmeanEst sqrt(x2Est) xstd]);
grid on;
legend('x','E<x>','sqrt(E<x^2>)','Std dev');
set(h(2:4),'Linewidth',2);

1
y1 = filter(a,[1 (1-a)],x);ตัวกรองในคำตอบของฉันสอดคล้องกับ
nibot

1
จุดที่ดีในความแตกต่างระหว่างสถิติการทำงานและสถิติของกลุ่มตัวอย่างโดยรวม การนำไปใช้ของฉันสามารถแก้ไขเพื่อคำนวณสถิติการทำงานโดยการสะสมผ่านหน้าต่างที่กำลังเคลื่อนที่ซึ่งสามารถทำได้อย่างมีประสิทธิภาพเช่นกัน (ในแต่ละขั้นตอนให้ลบตัวอย่างเวลาที่เพิ่งเลื่อนออกจากหน้าต่างจากตัวสะสมแต่ละตัว)
Jason R

ขอโทษนะคุณพูดถูกและฉันผิด ฉันจะแก้ไขให้ถูกต้องทันที
ฮิล

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