การตรวจจับสัญญาณสูงสุดในข้อมูลไทม์เรียลไทม์


242

ปรับปรุง:ขั้นตอนวิธีการปฏิบัติที่ดีที่สุดเพื่อให้ห่างไกล เป็นหนึ่งในนี้


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

พิจารณาชุดข้อมูลต่อไปนี้:

p = [1 1 1.1 1 0.9 1 1 1.1 1 0.9 1 1.1 1 1 0.9 1 1 1.1 1 1 1 1 1.1 0.9 1 1.1 1 1 0.9 1, ...
     1.1 1 1 1.1 1 0.8 0.9 1 1.2 0.9 1 1 1.1 1.2 1 1.5 1 3 2 5 3 2 1 1 1 0.9 1 1 3, ... 
     2.6 4 3 3.2 2 1 1 0.8 4 4 2 2.5 1 1 1];

(รูปแบบ Matlab แต่ไม่เกี่ยวกับภาษา แต่เกี่ยวกับอัลกอริทึม)

พล็อตของข้อมูล

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

  1. มีเสียงรบกวนพื้นฐานพร้อมค่าเฉลี่ยทั่วไป
  2. มี ' จุดสูงสุด ' หรือ ' จุดข้อมูลขนาดใหญ่' ที่มีขนาดใหญ่ซึ่งเบี่ยงเบนจากเสียงรบกวนอย่างมีนัยสำคัญ

ให้เราสมมติสิ่งต่อไปนี้:

  • ไม่สามารถกำหนดความกว้างของยอดเขาล่วงหน้าได้
  • ความสูงของยอดเขาอย่างชัดเจนและแตกต่างอย่างมีนัยสำคัญจากค่าอื่น ๆ
  • อัลกอริทึมที่ใช้จะต้องคำนวณเรียลไทม์ (ดังนั้นเปลี่ยนกับดาต้าพอยน์ใหม่แต่ละอัน)

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


คำถามของฉัน: อัลกอริทึมที่ดีในการคำนวณเกณฑ์ดังกล่าวแบบเรียลไทม์คืออะไร? มีอัลกอริทึมเฉพาะสำหรับสถานการณ์เช่นนี้หรือไม่? อัลกอริทึมที่รู้จักกันดีที่สุดคืออะไร


อัลกอริทึมที่แข็งแกร่งหรือข้อมูลเชิงลึกที่เป็นประโยชน์ล้วนเป็นที่นิยมอย่างสูง (สามารถตอบได้ทุกภาษา: มันเกี่ยวกับอัลกอริธึม)


5
มีต้องมีบางความต้องการสูงแน่นอนสำหรับการเป็นยอดเขาที่นอกเหนือไปจากความต้องการที่คุณได้รับอยู่แล้ว มิฉะนั้นยอดสูงสุด ณ เวลา 13 ควรได้รับการพิจารณาว่าเป็นจุดสูงสุด (เทียบเท่า: หากในอนาคตยอดเขาสูงถึง 1,000 หรือมากกว่านั้นทั้งสองยอดที่ 25 และ 35 ไม่ควรพิจารณายอดเขา)
j_random_hacker

ฉันเห็นด้วย. สมมติว่ายอดเขาเหล่านี้เป็นสิ่งที่เราต้องพิจารณาเท่านั้น
Jean-Paul

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

2
ฉันเคยทำสิ่งนี้เพื่อตรวจจับการเปลี่ยนแปลงความเข้มของแสงอย่างกะทันหันบน photosensor ฉันทำสิ่งนี้ด้วยค่าเฉลี่ยเคลื่อนที่และละเว้นจุดข้อมูลใด ๆ ที่ใหญ่กว่าเกณฑ์ โปรดทราบว่าเกณฑ์นี้แตกต่างจากเกณฑ์ที่กำหนดจุดสูงสุด ดังนั้นสมมติว่าคุณรวมเฉพาะจุดข้อมูลที่อยู่ภายในหนึ่ง stddev กับค่าเฉลี่ยเคลื่อนที่ของคุณและพิจารณา datapoints เหล่านั้นที่มีมากกว่าสาม stddev เป็นยอด อัลกอริทึมนี้ทำได้ดีมากสำหรับบริบทของการใช้งานของเราในเวลานั้น
justhalf

1
อ่าฉันเข้าใจแล้ว ฉันไม่ได้คาดหวังในรูปแบบรหัส ถ้าฉันเคยเห็นคำถามนี้มาก่อนคุณอาจจะได้คำตอบนั้นเร็วกว่า = D อย่างไรก็ตามแอปพลิเคชันของฉันในเวลานั้นเพื่อตรวจสอบว่าตัวรับแสงถูกบดบังจากแหล่งกำเนิดแสงโดยรอบหรือไม่ (นี่คือเหตุผลที่เราต้องการค่าเฉลี่ยเคลื่อนที่เนื่องจากแหล่งกำเนิดแสงโดยรอบอาจเปลี่ยนไปเรื่อย ๆ ) เราสร้างสิ่งนี้เป็นเกมที่คุณควรวางมือเหนือเซ็นเซอร์ตามรูปแบบเฉพาะ = D
justhalf

คำตอบ:


334

อัลกอริทึมการตรวจจับสูงสุดที่ทนทาน (ใช้คะแนน z)

ฉันคิดอัลกอริธึมที่ทำงานได้ดีสำหรับชุดข้อมูลประเภทนี้ มันขึ้นอยู่กับหลักการของการกระจายตัว : ถ้าดาต้าพอยน์ใหม่เป็นจำนวน x ที่กำหนดจากค่าเบี่ยงเบนมาตรฐานจากค่าเฉลี่ยเคลื่อนที่บางสัญญาณอัลกอริทึมจะส่งสัญญาณ (หรือที่เรียกว่าคะแนน z ) อัลกอริทึมนั้นแข็งแกร่งมากเพราะมันสร้างค่าเฉลี่ยเคลื่อนที่และการเบี่ยงเบนที่แยกจากกันสัญญาณดังกล่าวจะไม่ทำให้ธรณีประตูเสียหาย สัญญาณในอนาคตจะถูกระบุด้วยความแม่นยำเดียวกันโดยประมาณโดยไม่คำนึงถึงปริมาณของสัญญาณก่อนหน้า ขั้นตอนวิธีการใช้เวลา 3 ปัจจัยการผลิต: lag = the lag of the moving window, และthreshold = the z-score at which the algorithm signals influence = the influence (between 0 and 1) of new signals on the mean and standard deviationตัวอย่างเช่นหนึ่งlagใน 5 จะใช้การสังเกต 5 ครั้งสุดท้ายเพื่อทำให้ข้อมูลราบรื่น threshold3.5 จะส่งสัญญาณถ้าดาต้าพอยน์ 3.5 เบี่ยงเบนมาตรฐานจากค่าเฉลี่ยเคลื่อนที่ และinfluenceจาก 0.5 ให้สัญญาณครึ่งหนึ่งของอิทธิพลที่ datapoints ปกติมี เช่นเดียวกับinfluence0 จะละเว้นสัญญาณทั้งหมดสำหรับการคำนวณขีด จำกัด ใหม่ อิทธิพลของ 0 จึงเป็นตัวเลือกที่แข็งแกร่งที่สุด (แต่ถือว่าคงที่ ) การกำหนดตัวเลือกอิทธิพลที่ 1 นั้นแข็งแกร่งน้อยที่สุด สำหรับข้อมูลที่ไม่อยู่นิ่งตัวเลือกอิทธิพลจึงควรอยู่ระหว่าง 0 ถึง 1

มันทำงานได้ดังต่อไปนี้:

pseudocode

# Let y be a vector of timeseries data of at least length lag+2
# Let mean() be a function that calculates the mean
# Let std() be a function that calculates the standard deviaton
# Let absolute() be the absolute value function

# Settings (the ones below are examples: choose what is best for your data)
set lag to 5;          # lag 5 for the smoothing functions
set threshold to 3.5;  # 3.5 standard deviations for signal
set influence to 0.5;  # between 0 and 1, where 1 is normal influence, 0.5 is half

# Initialize variables
set signals to vector 0,...,0 of length of y;   # Initialize signal results
set filteredY to y(1),...,y(lag)                # Initialize filtered series
set avgFilter to null;                          # Initialize average filter
set stdFilter to null;                          # Initialize std. filter
set avgFilter(lag) to mean(y(1),...,y(lag));    # Initialize first value
set stdFilter(lag) to std(y(1),...,y(lag));     # Initialize first value

for i=lag+1,...,t do
  if absolute(y(i) - avgFilter(i-1)) > threshold*stdFilter(i-1) then
    if y(i) > avgFilter(i-1) then
      set signals(i) to +1;                     # Positive signal
    else
      set signals(i) to -1;                     # Negative signal
    end
    # Reduce influence of signal
    set filteredY(i) to influence*y(i) + (1-influence)*filteredY(i-1);
  else
    set signals(i) to 0;                        # No signal
    set filteredY(i) to y(i);
  end
  # Adjust the filters
  set avgFilter(i) to mean(filteredY(i-lag),...,filteredY(i));
  set stdFilter(i) to std(filteredY(i-lag),...,filteredY(i));
end

กฎง่ายๆสำหรับการเลือกพารามิเตอร์ที่ดีสำหรับข้อมูลของคุณสามารถดูได้ที่ด้านล่าง


การสาธิต

การสาธิตการอัลกอริธึมที่กำหนดเกณฑ์ได้สูง

รหัส Matlab สำหรับการสาธิตนี้สามารถพบได้ที่นี่ หากต้องการใช้การสาธิตเพียงใช้งานและสร้างอนุกรมเวลาด้วยตัวคุณเองโดยคลิกที่แผนภูมิด้านบน อัลกอริทึมเริ่มทำงานหลังจากวาดlagจำนวนการสังเกต


ผลลัพธ์

สำหรับคำถามเดิมอัลกอริทึมนี้จะให้ผลลัพธ์ต่อไปนี้เมื่อใช้การตั้งค่าต่อไปนี้lag = 30, threshold = 5, influence = 0::

ตัวอย่างอัลกอริทึม Thresholding


การใช้งานในภาษาการเขียนโปรแกรมที่แตกต่างกัน:


กฎง่ายๆสำหรับการกำหนดค่าอัลกอริทึม

lag: พารามิเตอร์ lag กำหนดจำนวนข้อมูลของคุณที่จะทำให้ราบรื่นและวิธีปรับเปลี่ยนอัลกอริทึมคือการเปลี่ยนแปลงของข้อมูลเฉลี่ยในระยะยาว ยิ่งนิ่งข้อมูลของคุณคือล่าช้ามากกว่าที่คุณควรจะรวมถึง (นี้ควรปรับปรุงความทนทานของอัลกอริทึม) หากข้อมูลของคุณมีแนวโน้มที่แปรผันตามเวลาคุณควรพิจารณาว่าคุณต้องการให้อัลกอริทึมปรับตัวเข้ากับแนวโน้มเหล่านี้อย่างรวดเร็วเพียงใด นั่นคือถ้าคุณใส่lagที่ 10 จะใช้เวลา 10 'ระยะเวลา' ก่อนที่ treshold ของอัลกอริทึมจะถูกปรับเป็นการเปลี่ยนแปลงที่เป็นระบบในค่าเฉลี่ยระยะยาว ดังนั้นเลือกlagพารามิเตอร์ตามพฤติกรรมที่ได้รับความนิยมของข้อมูลของคุณและวิธีการปรับตัวที่คุณต้องการให้อัลกอริทึมเป็นอย่างไร

influence: พารามิเตอร์นี้พิจารณาอิทธิพลของสัญญาณที่มีต่อเกณฑ์การตรวจจับของอัลกอริทึม หากใส่ที่ 0 สัญญาณจะไม่มีผลกับขีด จำกัด เช่นว่าสัญญาณในอนาคตจะถูกตรวจพบตามเกณฑ์ที่คำนวณด้วยค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานที่ไม่ได้รับอิทธิพลจากสัญญาณที่ผ่านมา อีกวิธีที่จะคิดเกี่ยวกับเรื่องนี้ก็คือถ้าคุณใส่อิทธิพลที่ 0 คุณก็จะถือว่ามีความคงที่โดยปริยาย (นั่นคือไม่ว่าจะมีสัญญาณมากแค่ไหนก็ตาม หากไม่เป็นเช่นนั้นคุณควรวางพารามิเตอร์อิทธิพลไว้ระหว่าง 0 ถึง 1 โดยขึ้นอยู่กับขอบเขตที่สัญญาณจะมีผลต่อแนวโน้มของข้อมูลที่แปรผันตามเวลา เช่นถ้าสัญญาณนำไปสู่การแตกของโครงสร้าง ของค่าเฉลี่ยระยะยาวของอนุกรมเวลาพารามิเตอร์อิทธิพลควรมีค่าสูง (ใกล้เคียงกับ 1) เพื่อให้เกณฑ์สามารถปรับการเปลี่ยนแปลงเหล่านี้ได้อย่างรวดเร็ว

threshold: พารามิเตอร์ threshold คือจำนวนส่วนเบี่ยงเบนมาตรฐานจากค่าเฉลี่ยเคลื่อนที่ข้างต้นซึ่งอัลกอริทึมจะจัดประเภทดาต้าพอยน์ใหม่เป็นสัญญาณ ตัวอย่างเช่นถ้าดาต้าพอยน์ใหม่คือ 4.0 ส่วนเบี่ยงเบนมาตรฐานเหนือค่าเฉลี่ยเคลื่อนที่และพารามิเตอร์ขีด จำกัด ถูกตั้งค่าเป็น 3.5 อัลกอริทึมจะระบุดาต้าพอยน์เป็นสัญญาณ ควรตั้งค่าพารามิเตอร์นี้ตามจำนวนสัญญาณที่คุณคาดหวัง ตัวอย่างเช่นหากข้อมูลของคุณกระจายตามปกติเกณฑ์ (หรือ: คะแนน z) 3.5 สอดคล้องกับความน่าจะเป็นสัญญาณ 0.00047 (จากตารางนี้) ซึ่งหมายความว่าคุณคาดว่าสัญญาณหนึ่งครั้งทุก 2128 datapoints (1 / 0.00047) เกณฑ์จึงส่งผลโดยตรงต่อความอ่อนไหวของอัลกอริธึมและความถี่สัญญาณอัลกอริธึม ตรวจสอบข้อมูลของคุณเองและกำหนดเกณฑ์ที่เหมาะสมที่ทำให้สัญญาณอัลกอริทึมเมื่อคุณต้องการ (อาจจำเป็นต้องใช้การทดลองและข้อผิดพลาดบางอย่างที่นี่เพื่อรับเกณฑ์ที่ดีสำหรับวัตถุประสงค์ของคุณ)


คำเตือน: โค้ดด้านบนจะวนซ้ำตามดาต้าพอยน์ทุกครั้งที่รัน เมื่อใช้รหัสนี้ตรวจสอบให้แน่ใจว่าได้แบ่งการคำนวณสัญญาณออกเป็นฟังก์ชันแยกต่างหาก จากนั้นเมื่อ DataPoint ใหม่มาถึง, อัพเดทfilteredY, avgFilterและstdFilterครั้งเดียว อย่าคำนวณสัญญาณสำหรับข้อมูลทุกครั้งที่มีดาต้าพอยน์ใหม่ (เช่นในตัวอย่างด้านบน) ซึ่งจะไม่มีประสิทธิภาพและช้ามาก!

วิธีอื่น ๆ ในการปรับเปลี่ยนอัลกอริทึม (สำหรับการปรับปรุงที่เป็นไปได้) คือ:

  1. ใช้ค่ามัธยฐานแทนค่าเฉลี่ย
  2. ใช้การวัดขนาดที่แข็งแกร่งเช่น MAD แทนค่าเบี่ยงเบนมาตรฐาน
  3. ใช้ระยะการส่งสัญญาณดังนั้นสัญญาณไม่สลับบ่อยเกินไป
  4. เปลี่ยนวิธีการทำงานของพารามิเตอร์ที่มีอิทธิพล
  5. รักษาสัญญาณขึ้นและลงแตกต่างกัน (การรักษาแบบอสมมาตร)
  6. สร้างinfluenceพารามิเตอร์ที่แยกต่างหากสำหรับค่าเฉลี่ยและ std ( ดังที่ทำในการแปล Swift นี้ )

(เป็นที่รู้จัก) การอ้างอิงทางวิชาการของคำตอบ StackOverflow นี้:

งานอื่น ๆ ที่ใช้อัลกอริทึม

แอปพลิเคชันอื่นของอัลกอริทึมนี้

ลิงก์ไปยังอัลกอริธึมการตรวจจับจุดสูงสุดอื่น ๆ


หากคุณใช้ฟังก์ชั่นนี้ที่อื่นโปรดให้เครดิตฉันหรือคำตอบนี้ หากคุณมีคำถามใด ๆ เกี่ยวกับขั้นตอนวิธีการนี้โพสต์ไว้ในความคิดเห็นด้านล่างหรือเอื้อมมือออกไปฉันในLinkedIn



ลิงก์ไปยัง movingstd เสีย แต่คุณสามารถหาคำอธิบายได้ที่นี่
Phylliida

@reasra เปิดใช้งานฟังก์ชั่นไม่จำเป็นต้องมีค่าเบี่ยงเบนมาตรฐานเคลื่อนที่หลังจากเขียนใหม่ ตอนนี้สามารถใช้งานได้กับฟังก์ชั่น Matlab แบบเรียบง่ายในตัว :)
Jean-Paul

1
ฉันกำลังลองใช้รหัส Matlab สำหรับข้อมูล accelerometer แต่ด้วยเหตุผลบางอย่างthresholdกราฟก็กลายเป็นเส้นสีเขียวแบนหลังจากที่มีขนาดใหญ่มากถึง 20 ในข้อมูลและมันก็คงเป็นเช่นนั้นสำหรับกราฟที่เหลือ ... ฉันลบ sike มันไม่ได้เกิดขึ้นดังนั้นดูเหมือนว่าจะเกิดจากการขัดขวางในข้อมูล ความคิดใด ๆ ที่อาจเกิดขึ้น? ผมเป็นมือใหม่ใน Matlab ดังนั้นฉันไม่สามารถคิดออก ...
แมกนัส W

@BadCash คุณช่วยยกตัวอย่าง (พร้อมข้อมูล) ได้ไหม? อาจถามคำถามของคุณเองที่นี่เพื่อ SO และบอกลิงค์
Jean-Paul

2
มีหลายวิธีในการปรับปรุง algo นี้ดังนั้นควรมีความคิดสร้างสรรค์ (การรักษาที่แตกต่างกันขึ้น / ลง, มัธยฐานแทนค่าเฉลี่ย, std ที่แข็งแกร่ง, การเขียนโค้ดเป็นฟังก์ชั่นหน่วยความจำที่มีประสิทธิภาพ; .)
Jean-Paul

41

นี่คือPython/ numpyการนำไปใช้ของอัลกอริทึม z-score ที่ราบรื่น (ดูคำตอบด้านบน ) คุณสามารถค้นหาส่วนสำคัญที่นี่

#!/usr/bin/env python
# Implementation of algorithm from https://stackoverflow.com/a/22640362/6029703
import numpy as np
import pylab

def thresholding_algo(y, lag, threshold, influence):
    signals = np.zeros(len(y))
    filteredY = np.array(y)
    avgFilter = [0]*len(y)
    stdFilter = [0]*len(y)
    avgFilter[lag - 1] = np.mean(y[0:lag])
    stdFilter[lag - 1] = np.std(y[0:lag])
    for i in range(lag, len(y)):
        if abs(y[i] - avgFilter[i-1]) > threshold * stdFilter [i-1]:
            if y[i] > avgFilter[i-1]:
                signals[i] = 1
            else:
                signals[i] = -1

            filteredY[i] = influence * y[i] + (1 - influence) * filteredY[i-1]
            avgFilter[i] = np.mean(filteredY[(i-lag+1):i+1])
            stdFilter[i] = np.std(filteredY[(i-lag+1):i+1])
        else:
            signals[i] = 0
            filteredY[i] = y[i]
            avgFilter[i] = np.mean(filteredY[(i-lag+1):i+1])
            stdFilter[i] = np.std(filteredY[(i-lag+1):i+1])

    return dict(signals = np.asarray(signals),
                avgFilter = np.asarray(avgFilter),
                stdFilter = np.asarray(stdFilter))

ด้านล่างคือการทดสอบในชุดข้อมูลเดียวกันกับที่ให้พล็อตเดียวกันกับในคำตอบดั้งเดิมสำหรับR/Matlab

# Data
y = np.array([1,1,1.1,1,0.9,1,1,1.1,1,0.9,1,1.1,1,1,0.9,1,1,1.1,1,1,1,1,1.1,0.9,1,1.1,1,1,0.9,
       1,1.1,1,1,1.1,1,0.8,0.9,1,1.2,0.9,1,1,1.1,1.2,1,1.5,1,3,2,5,3,2,1,1,1,0.9,1,1,3,
       2.6,4,3,3.2,2,1,1,0.8,4,4,2,2.5,1,1,1])

# Settings: lag = 30, threshold = 5, influence = 0
lag = 30
threshold = 5
influence = 0

# Run algo with settings from above
result = thresholding_algo(y, lag=lag, threshold=threshold, influence=influence)

# Plot result
pylab.subplot(211)
pylab.plot(np.arange(1, len(y)+1), y)

pylab.plot(np.arange(1, len(y)+1),
           result["avgFilter"], color="cyan", lw=2)

pylab.plot(np.arange(1, len(y)+1),
           result["avgFilter"] + threshold * result["stdFilter"], color="green", lw=2)

pylab.plot(np.arange(1, len(y)+1),
           result["avgFilter"] - threshold * result["stdFilter"], color="green", lw=2)

pylab.subplot(212)
pylab.step(np.arange(1, len(y)+1), result["signals"], color="red", lw=2)
pylab.ylim(-1.5, 1.5)
pylab.show()

ตรงนี้ 'y' เป็นจริงสัญญาณและ 'สัญญาณ' เป็นชุดของจุดข้อมูลฉันถูกต้องในการทำความเข้าใจหรือไม่?
TheTank

1
@TheTank yเป็นอาเรย์ข้อมูลที่คุณส่งผ่านsignalsเป็นอาเรย์+1หรือ-1เอาต์พุตที่ระบุสำหรับแต่ละดาต้าพอยน์y[i]ว่าดาต้าพอยน์นั้นเป็น "จุดสูงสุดที่สำคัญ" หรือไม่เมื่อตั้งค่าที่คุณใช้
Jean-Paul

23

วิธีหนึ่งคือตรวจหาจุดสูงสุดตามการสังเกตต่อไปนี้:

  • เวลา t คือค่าสูงสุดหาก (y (t)> y (t-1)) && (y (t)> y (t + 1))

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

  • ยอดเขาถ้า (y (t) - y (t-dt)> m) && (y (t) - y (t + dt)> m)

โดยที่dtและmเป็นพารามิเตอร์ในการควบคุมความไวเทียบกับการหน่วงเวลา

นี่คือสิ่งที่คุณจะได้รับจากอัลกอริทึมที่กล่าวถึง: ป้อนคำอธิบายรูปภาพที่นี่

นี่คือรหัสในการทำซ้ำพล็อตในไพ ธ อน:

import numpy as np
import matplotlib.pyplot as plt
input = np.array([ 1. ,  1. ,  1. ,  1. ,  1. ,  1. ,  1. ,  1.1,  1. ,  0.8,  0.9,
    1. ,  1.2,  0.9,  1. ,  1. ,  1.1,  1.2,  1. ,  1.5,  1. ,  3. ,
    2. ,  5. ,  3. ,  2. ,  1. ,  1. ,  1. ,  0.9,  1. ,  1. ,  3. ,
    2.6,  4. ,  3. ,  3.2,  2. ,  1. ,  1. ,  1. ,  1. ,  1. ])
signal = (input > np.roll(input,1)) & (input > np.roll(input,-1))
plt.plot(input)
plt.plot(signal.nonzero()[0], input[signal], 'ro')
plt.show()

เมื่อตั้งค่าm = 0.5คุณจะได้รับสัญญาณที่สะอาดยิ่งขึ้นโดยมีผลบวกปลอมเพียงตัวเดียว: ป้อนคำอธิบายรูปภาพที่นี่


เร็วกว่า = ดีกว่าเพื่อให้จุดสูงสุดทั้งหมดมีนัยสำคัญ ขอบคุณ! เจ๋งมาก!
Jean-Paul

ฉันจะเปลี่ยนความไวได้อย่างไร
Jean-Paul

ฉันสามารถคิดถึงวิธีการสองวิธี: 1: ตั้งค่าmเป็นค่าที่มากขึ้นเพื่อให้ตรวจพบยอดเขาที่ใหญ่กว่าเท่านั้น 2: แทนที่จะคำนวณ y (t) - y (t-dt) (และ y (t) - y (t + dt)) คุณรวมจาก t-dt ถึง t (และ t ถึง t + dt)
aha

2
คุณจะปฏิเสธเกณฑ์อะไรอีก 7 ยอดเขา
hotpaw2

4
มีปัญหาเกี่ยวกับยอดแบนคือเนื่องจากสิ่งที่คุณทำคือการตรวจหาขอบพื้น 1-D (ชอบ convoluting สัญญาณด้วย [1 0 -1])
เบน

18

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


1
คำตอบของคุณให้ฉันไปที่บทความนี้และคำตอบนี้ซึ่งจะช่วยฉันสร้างอัลกอริทึมที่ดีสำหรับการใช้ ขอบคุณ!
Jean-Paul

@cklin คุณช่วยอธิบายวิธีคำนวณเวฟเล็ตโคฟได้อย่างไรเนื่องจากมันไม่ได้อยู่ในช่วงเวลาเดียวกันกับอนุกรมเวลาดั้งเดิม การอ้างอิงใด ๆ เกี่ยวกับการใช้งานนี้?
horaceT

11

เราได้พยายามใช้อัลกอริธึม z-smoothed บนชุดข้อมูลของเราซึ่งส่งผลให้เกิดอาการโอเวอร์เกินหรือต่ำกว่า (ขึ้นอยู่กับวิธีการปรับพารามิเตอร์) ด้วยพื้นกลางเล็กน้อย ในสัญญาณการเข้าชมเว็บไซต์ของเราเราพบว่ามีความถี่ต่ำซึ่งหมายถึงวัฏจักรรายวันและถึงแม้จะมีพารามิเตอร์ที่ดีที่สุด (ดังแสดงด้านล่าง) ก็ยังคงหายไปโดยเฉพาะในวันที่ 4 เพราะจุดข้อมูลส่วนใหญ่ได้รับการยอมรับว่าผิดปกติ .

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

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


เจ๋งมากที่เห็นว่าอัลกอริทึมเป็นจุดเริ่มต้นสำหรับเวอร์ชันขั้นสูงของคุณ ข้อมูลของคุณมีรูปแบบที่เฉพาะเจาะจงมากดังนั้นจึงควรใช้รูปแบบอื่นในการลบรูปแบบโดยใช้เทคนิคอื่นก่อน หรือคุณอาจต้องการใช้กึ่งกลางแทนหน้าต่างปกคลุมด้วยวัตถุฉนวนเพื่อคำนวณค่าเฉลี่ย / st.dev ความคิดเห็นอื่น: โซลูชันของคุณย้ายจากขวาไปซ้ายเพื่อระบุ spikes แต่ไม่สามารถทำได้ในแอปพลิเคชันแบบเรียลไทม์ (นั่นคือเหตุผลที่ว่าทำไม algo ดั้งเดิมนั้นง่ายมากเพราะข้อมูลในอนาคตไม่สามารถเข้าถึงได้)
Jean-Paul

10

ในโทโพโลยีการคำนวณความคิดของ homology แบบถาวรจะนำไปสู่การแก้ปัญหาที่มีประสิทธิภาพ - รวดเร็วเหมือนการเรียงลำดับตัวเลข - มันไม่เพียง แต่ตรวจจับจุดสูงสุดเท่านั้น แต่ยังตรวจสอบยอด "สำคัญ" ของยอดเขาด้วยวิธีธรรมชาติที่ช่วยให้คุณสามารถเลือกยอดเขาที่มีความสำคัญสำหรับคุณ

สรุปอัลกอริทึม ในการตั้งค่า 1 มิติ (อนุกรมเวลาสัญญาณมูลค่าจริง) อัลกอริทึมสามารถอธิบายได้อย่างง่ายดายโดยรูปต่อไปนี้:

ยอดเขาที่ตกค้างมากที่สุด

คิดว่ากราฟฟังก์ชั่น (หรือชุดระดับย่อย) เป็นแนวนอนและพิจารณาระดับน้ำที่ลดลงโดยเริ่มที่ระดับอินฟินิตี้ (หรือ 1.8 ในภาพนี้) ในขณะที่ระดับลดลงที่หมู่เกาะ maxima ท้องถิ่นปรากฏขึ้น ในท้องถิ่นน้อยที่สุดเกาะเหล่านี้รวมเข้าด้วยกัน รายละเอียดหนึ่งในแนวคิดนี้คือเกาะที่ปรากฏในเวลาต่อมาถูกรวมเข้ากับเกาะที่มีอายุมากกว่า "ความคงอยู่" ของเกาะคือเวลาเกิดของมันลบด้วยเวลาตายของมัน ความยาวของแท่งสีน้ำเงินแสดงถึงการคงอยู่ซึ่งเป็น "ความสำคัญ" ของจุดสูงสุดที่กล่าวถึงข้างต้น

อย่างมีประสิทธิภาพ ไม่ยากเกินไปที่จะค้นหาการนำไปใช้งานที่ทำงานในเวลาเชิงเส้น - ในความเป็นจริงมันเป็นลูปเดียวที่ง่าย - หลังจากที่ค่าฟังก์ชันถูกเรียงลำดับแล้ว ดังนั้นการดำเนินการนี้ควรรวดเร็วในทางปฏิบัติและนำไปปฏิบัติได้ง่ายเช่นกัน

อ้างอิง บทความทั้งหมดของเรื่องและการอ้างอิงถึงแรงบันดาลใจจาก homology ถาวร (สาขาใน topology เกี่ยวกับพีชคณิตเชิงคอมพิวเตอร์) สามารถพบได้ที่นี่: https://www.sthu.org/blog/13-perstopology-peakdetection/index.html


อัลกอริทึมนี้เร็วกว่าและแม่นยำกว่าตัวอย่างเช่น scipy.signal.find_peaks สำหรับอนุกรมเวลา "ของจริง" ที่มีจุดข้อมูล 1053896 จะตรวจพบจุดสูงสุด 137516 (13%) คำสั่งของจุดสูงสุด (สำคัญที่สุดก่อน) ช่วยให้สามารถแยกยอดที่สำคัญที่สุดได้ มันมีจุดเริ่มต้นสูงสุดและจุดสิ้นสุดของแต่ละจุดสูงสุด ทำงานได้ดีกับข้อมูลที่มีเสียงดัง
vinh

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

9

พบอัลกอริทึมอื่นโดย GH Palshikar ในอัลกอริทึมแบบง่ายสำหรับการตรวจจับสูงสุดในซีรีย์เวลา

อัลกอริทึมเป็นดังนี้:

algorithm peak1 // one peak detection algorithms that uses peak function S1 

input T = x1, x2, …, xN, N // input time-series of N points 
input k // window size around the peak 
input h // typically 1 <= h <= 3 
output O // set of peaks detected in T 

begin 
O = empty set // initially empty 

    for (i = 1; i < n; i++) do
        // compute peak function value for each of the N points in T 
        a[i] = S1(k,i,xi,T); 
    end for 

    Compute the mean m' and standard deviation s' of all positive values in array a; 

    for (i = 1; i < n; i++) do // remove local peaks which are “small” in global context 
        if (a[i] > 0 && (a[i] – m') >( h * s')) then O = O + {xi}; 
        end if 
    end for 

    Order peaks in O in terms of increasing index in T 

    // retain only one peak out of any set of peaks within distance k of each other 

    for every adjacent pair of peaks xi and xj in O do 
        if |j – i| <= k then remove the smaller value of {xi, xj} from O 
        end if 
    end for 
end

ข้อดี

  • กระดาษมี5อัลกอริทึมที่แตกต่างกันสำหรับการตรวจจับสูงสุด
  • อัลกอริธึมทำงานกับข้อมูลอนุกรมเวลาแบบดิบ (ไม่จำเป็นต้องปรับให้เรียบ)

ข้อเสีย

  • ยากที่จะกำหนดkและhล่วงหน้า
  • Peaks ต้องไม่แบน (เช่นจุดสูงสุดที่สามในข้อมูลการทดสอบของฉัน)

ตัวอย่าง:

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


กระดาษที่น่าสนใจจริง ๆ S4 ดูเหมือนจะเป็นฟังก์ชั่นที่ดีกว่าที่จะใช้ในความคิดของเขา แต่ที่สำคัญกว่านั้นคือการชี้แจงเมื่อ k <i <Nk ไม่เป็นความจริง วิธีหนึ่งจะกำหนดฟังก์ชั่น S1 (S2, .. ) สำหรับ i = 0 ฉันไม่ได้หารด้วย 2 และละเว้นตัวถูกดำเนินการแรกและสำหรับคนอื่น ๆ ที่ฉันรวมทั้งตัวถูกดำเนินการทั้งสอง แต่สำหรับฉัน <= k มีตัวถูกดำเนินการน้อยกว่า ทางด้านขวา
daniels_pa

8

นี่คือการใช้อัลกอริทึม z-score Smoothed (ด้านบน) ใน Golang มันถือว่าชิ้น[]int16(ตัวอย่าง PCM 16 บิต) คุณสามารถหาเค้าที่นี่

/*
Settings (the ones below are examples: choose what is best for your data)
set lag to 5;          # lag 5 for the smoothing functions
set threshold to 3.5;  # 3.5 standard deviations for signal
set influence to 0.5;  # between 0 and 1, where 1 is normal influence, 0.5 is half
*/

// ZScore on 16bit WAV samples
func ZScore(samples []int16, lag int, threshold float64, influence float64) (signals []int16) {
    //lag := 20
    //threshold := 3.5
    //influence := 0.5

    signals = make([]int16, len(samples))
    filteredY := make([]int16, len(samples))
    for i, sample := range samples[0:lag] {
        filteredY[i] = sample
    }
    avgFilter := make([]int16, len(samples))
    stdFilter := make([]int16, len(samples))

    avgFilter[lag] = Average(samples[0:lag])
    stdFilter[lag] = Std(samples[0:lag])

    for i := lag + 1; i < len(samples); i++ {

        f := float64(samples[i])

        if float64(Abs(samples[i]-avgFilter[i-1])) > threshold*float64(stdFilter[i-1]) {
            if samples[i] > avgFilter[i-1] {
                signals[i] = 1
            } else {
                signals[i] = -1
            }
            filteredY[i] = int16(influence*f + (1-influence)*float64(filteredY[i-1]))
            avgFilter[i] = Average(filteredY[(i - lag):i])
            stdFilter[i] = Std(filteredY[(i - lag):i])
        } else {
            signals[i] = 0
            filteredY[i] = samples[i]
            avgFilter[i] = Average(filteredY[(i - lag):i])
            stdFilter[i] = Std(filteredY[(i - lag):i])
        }
    }

    return
}

// Average a chunk of values
func Average(chunk []int16) (avg int16) {
    var sum int64
    for _, sample := range chunk {
        if sample < 0 {
            sample *= -1
        }
        sum += int64(sample)
    }
    return int16(sum / int64(len(chunk)))
}

@ Jean-Paul ฉันไม่แน่ใจว่าทุกอย่างถูกต้องดังนั้นอาจมีข้อบกพร่อง
Xeoncross

1
คุณได้ลองจำลองตัวอย่างผลลัพธ์จาก Matlab / R แล้วหรือยัง? นั่นควรเป็นการยืนยันคุณภาพที่ดี
Jean-Paul

7

นี่คือการใช้ C ++ ของอัลกอริธึม z-smoothed จากคำตอบนี้

std::vector<int> smoothedZScore(std::vector<float> input)
{   
    //lag 5 for the smoothing functions
    int lag = 5;
    //3.5 standard deviations for signal
    float threshold = 3.5;
    //between 0 and 1, where 1 is normal influence, 0.5 is half
    float influence = .5;

    if (input.size() <= lag + 2)
    {
        std::vector<int> emptyVec;
        return emptyVec;
    }

    //Initialise variables
    std::vector<int> signals(input.size(), 0.0);
    std::vector<float> filteredY(input.size(), 0.0);
    std::vector<float> avgFilter(input.size(), 0.0);
    std::vector<float> stdFilter(input.size(), 0.0);
    std::vector<float> subVecStart(input.begin(), input.begin() + lag);
    avgFilter[lag] = mean(subVecStart);
    stdFilter[lag] = stdDev(subVecStart);

    for (size_t i = lag + 1; i < input.size(); i++)
    {
        if (std::abs(input[i] - avgFilter[i - 1]) > threshold * stdFilter[i - 1])
        {
            if (input[i] > avgFilter[i - 1])
            {
                signals[i] = 1; //# Positive signal
            }
            else
            {
                signals[i] = -1; //# Negative signal
            }
            //Make influence lower
            filteredY[i] = influence* input[i] + (1 - influence) * filteredY[i - 1];
        }
        else
        {
            signals[i] = 0; //# No signal
            filteredY[i] = input[i];
        }
        //Adjust the filters
        std::vector<float> subVec(filteredY.begin() + i - lag, filteredY.begin() + i);
        avgFilter[i] = mean(subVec);
        stdFilter[i] = stdDev(subVec);
    }
    return signals;
}

2
Caveat: การใช้งานนี้ไม่ได้ให้วิธีการคำนวณค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐาน สำหรับ C ++ 11 สามารถหาวิธีง่าย ๆ ได้ที่นี่: stackoverflow.com/a/12405793/3250829
rayryeng

6

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


ตัวกรองคาลมานนั้นน่าสนใจ แต่ฉันไม่สามารถหาอัลกอริทึมที่ใช้งานได้เพื่อจุดประสงค์ของฉัน ฉันขอขอบคุณคำตอบอย่างสูงและฉันจะตรวจสอบเอกสารการตรวจจับสูงสุดบางอย่างเช่นนี้เพื่อดูว่าฉันสามารถเรียนรู้จากอัลกอริทึมใด ๆ ขอบคุณ!
Jean-Paul

6

การใช้ C ++

#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>
#include <cmath>
#include <iterator>
#include <numeric>

using namespace std;

typedef long double ld;
typedef unsigned int uint;
typedef std::vector<ld>::iterator vec_iter_ld;

/**
 * Overriding the ostream operator for pretty printing vectors.
 */
template<typename T>
std::ostream &operator<<(std::ostream &os, std::vector<T> vec) {
    os << "[";
    if (vec.size() != 0) {
        std::copy(vec.begin(), vec.end() - 1, std::ostream_iterator<T>(os, " "));
        os << vec.back();
    }
    os << "]";
    return os;
}

/**
 * This class calculates mean and standard deviation of a subvector.
 * This is basically stats computation of a subvector of a window size qual to "lag".
 */
class VectorStats {
public:
    /**
     * Constructor for VectorStats class.
     *
     * @param start - This is the iterator position of the start of the window,
     * @param end   - This is the iterator position of the end of the window,
     */
    VectorStats(vec_iter_ld start, vec_iter_ld end) {
        this->start = start;
        this->end = end;
        this->compute();
    }

    /**
     * This method calculates the mean and standard deviation using STL function.
     * This is the Two-Pass implementation of the Mean & Variance calculation.
     */
    void compute() {
        ld sum = std::accumulate(start, end, 0.0);
        uint slice_size = std::distance(start, end);
        ld mean = sum / slice_size;
        std::vector<ld> diff(slice_size);
        std::transform(start, end, diff.begin(), [mean](ld x) { return x - mean; });
        ld sq_sum = std::inner_product(diff.begin(), diff.end(), diff.begin(), 0.0);
        ld std_dev = std::sqrt(sq_sum / slice_size);

        this->m1 = mean;
        this->m2 = std_dev;
    }

    ld mean() {
        return m1;
    }

    ld standard_deviation() {
        return m2;
    }

private:
    vec_iter_ld start;
    vec_iter_ld end;
    ld m1;
    ld m2;
};

/**
 * This is the implementation of the Smoothed Z-Score Algorithm.
 * This is direction translation of https://stackoverflow.com/a/22640362/1461896.
 *
 * @param input - input signal
 * @param lag - the lag of the moving window
 * @param threshold - the z-score at which the algorithm signals
 * @param influence - the influence (between 0 and 1) of new signals on the mean and standard deviation
 * @return a hashmap containing the filtered signal and corresponding mean and standard deviation.
 */
unordered_map<string, vector<ld>> z_score_thresholding(vector<ld> input, int lag, ld threshold, ld influence) {
    unordered_map<string, vector<ld>> output;

    uint n = (uint) input.size();
    vector<ld> signals(input.size());
    vector<ld> filtered_input(input.begin(), input.end());
    vector<ld> filtered_mean(input.size());
    vector<ld> filtered_stddev(input.size());

    VectorStats lag_subvector_stats(input.begin(), input.begin() + lag);
    filtered_mean[lag - 1] = lag_subvector_stats.mean();
    filtered_stddev[lag - 1] = lag_subvector_stats.standard_deviation();

    for (int i = lag; i < n; i++) {
        if (abs(input[i] - filtered_mean[i - 1]) > threshold * filtered_stddev[i - 1]) {
            signals[i] = (input[i] > filtered_mean[i - 1]) ? 1.0 : -1.0;
            filtered_input[i] = influence * input[i] + (1 - influence) * filtered_input[i - 1];
        } else {
            signals[i] = 0.0;
            filtered_input[i] = input[i];
        }
        VectorStats lag_subvector_stats(filtered_input.begin() + (i - lag), filtered_input.begin() + i);
        filtered_mean[i] = lag_subvector_stats.mean();
        filtered_stddev[i] = lag_subvector_stats.standard_deviation();
    }

    output["signals"] = signals;
    output["filtered_mean"] = filtered_mean;
    output["filtered_stddev"] = filtered_stddev;

    return output;
};

int main() {
    vector<ld> input = {1.0, 1.0, 1.1, 1.0, 0.9, 1.0, 1.0, 1.1, 1.0, 0.9, 1.0, 1.1, 1.0, 1.0, 0.9, 1.0, 1.0, 1.1, 1.0,
                        1.0, 1.0, 1.0, 1.1, 0.9, 1.0, 1.1, 1.0, 1.0, 0.9, 1.0, 1.1, 1.0, 1.0, 1.1, 1.0, 0.8, 0.9, 1.0,
                        1.2, 0.9, 1.0, 1.0, 1.1, 1.2, 1.0, 1.5, 1.0, 3.0, 2.0, 5.0, 3.0, 2.0, 1.0, 1.0, 1.0, 0.9, 1.0,
                        1.0, 3.0, 2.6, 4.0, 3.0, 3.2, 2.0, 1.0, 1.0, 0.8, 4.0, 4.0, 2.0, 2.5, 1.0, 1.0, 1.0};

    int lag = 30;
    ld threshold = 5.0;
    ld influence = 0.0;
    unordered_map<string, vector<ld>> output = z_score_thresholding(input, lag, threshold, influence);
    cout << output["signals"] << endl;
}

6

จากการแก้ปัญหาที่เสนอโดย @ Jean-Paul ฉันได้ใช้อัลกอริทึมของเขาใน C #

public class ZScoreOutput
{
    public List<double> input;
    public List<int> signals;
    public List<double> avgFilter;
    public List<double> filtered_stddev;
}

public static class ZScore
{
    public static ZScoreOutput StartAlgo(List<double> input, int lag, double threshold, double influence)
    {
        // init variables!
        int[] signals = new int[input.Count];
        double[] filteredY = new List<double>(input).ToArray();
        double[] avgFilter = new double[input.Count];
        double[] stdFilter = new double[input.Count];

        var initialWindow = new List<double>(filteredY).Skip(0).Take(lag).ToList();

        avgFilter[lag - 1] = Mean(initialWindow);
        stdFilter[lag - 1] = StdDev(initialWindow);

        for (int i = lag; i < input.Count; i++)
        {
            if (Math.Abs(input[i] - avgFilter[i - 1]) > threshold * stdFilter[i - 1])
            {
                signals[i] = (input[i] > avgFilter[i - 1]) ? 1 : -1;
                filteredY[i] = influence * input[i] + (1 - influence) * filteredY[i - 1];
            }
            else
            {
                signals[i] = 0;
                filteredY[i] = input[i];
            }

            // Update rolling average and deviation
            var slidingWindow = new List<double>(filteredY).Skip(i - lag).Take(lag+1).ToList();

            var tmpMean = Mean(slidingWindow);
            var tmpStdDev = StdDev(slidingWindow);

            avgFilter[i] = Mean(slidingWindow);
            stdFilter[i] = StdDev(slidingWindow);
        }

        // Copy to convenience class 
        var result = new ZScoreOutput();
        result.input = input;
        result.avgFilter       = new List<double>(avgFilter);
        result.signals         = new List<int>(signals);
        result.filtered_stddev = new List<double>(stdFilter);

        return result;
    }

    private static double Mean(List<double> list)
    {
        // Simple helper function! 
        return list.Average();
    }

    private static double StdDev(List<double> values)
    {
        double ret = 0;
        if (values.Count() > 0)
        {
            double avg = values.Average();
            double sum = values.Sum(d => Math.Pow(d - avg, 2));
            ret = Math.Sqrt((sum) / (values.Count() - 1));
        }
        return ret;
    }
}

ตัวอย่างการใช้งาน:

var input = new List<double> {1.0, 1.0, 1.1, 1.0, 0.9, 1.0, 1.0, 1.1, 1.0, 0.9, 1.0,
    1.1, 1.0, 1.0, 0.9, 1.0, 1.0, 1.1, 1.0, 1.0, 1.0, 1.0, 1.1, 0.9, 1.0, 1.1, 1.0, 1.0, 0.9,
    1.0, 1.1, 1.0, 1.0, 1.1, 1.0, 0.8, 0.9, 1.0, 1.2, 0.9, 1.0, 1.0, 1.1, 1.2, 1.0, 1.5, 1.0,
    3.0, 2.0, 5.0, 3.0, 2.0, 1.0, 1.0, 1.0, 0.9, 1.0, 1.0, 3.0, 2.6, 4.0, 3.0, 3.2, 2.0, 1.0,
    1.0, 0.8, 4.0, 4.0, 2.0, 2.5, 1.0, 1.0, 1.0};

int lag = 30;
double threshold = 5.0;
double influence = 0.0;

var output = ZScore.StartAlgo(input, lag, threshold, influence);

1
เฮ้ @ Jean-Paul ไชโย ใช่ฉันได้ทดสอบผลลัพธ์กับรุ่น R ของคุณเพื่อให้แน่ใจว่าตรงกับ ขอขอบคุณอีกครั้งสำหรับวิธีแก้ปัญหาของคุณ
Ocean Airdrop

สวัสดีฉันคิดว่ามีข้อผิดพลาดในรหัสนั้นในวิธี StdDev คุณใช้ values.Count () - 1 ควรใช้ -1 หรือไม่ ฉันคิดว่าคุณต้องการจำนวนรายการและนั่นคือสิ่งที่คุณได้รับจากค่าจำนวน ()
Viktor

1
อืม .. จุดที่ดี แม้ว่าตอนแรกฉันจะส่งอัลกอริทึมไปที่ C # แต่ฉันไม่เคยใช้มันเลย ฉันอาจจะแทนที่ฟังก์ชั่นทั้งหมดนั้นด้วยการเรียกไปยังห้องสมุด nuget MathNet "Install-Package MathNet.Numerics" มันมีฟังก์ชั่นที่สร้างไว้ล่วงหน้าสำหรับ PopulationStandardDeviation () และ StandardDeviation (); เช่น. var populationStdDev = รายการใหม่ <double> (1,2,3,4) .PopulationStandardDeviation (); var sampleStdDev = รายการใหม่ <double> (1,2,3,4) .StandardDeviation ();
Ocean Airdrop

6

นี่คือการดำเนินการ C ของคะแนน Z Smoothed ของ@ Jean-Paulสำหรับไมโครคอนโทรลเลอร์ Arduino ที่ใช้ในการอ่านมาตรความเร่งและตัดสินใจว่าทิศทางของผลกระทบนั้นมาจากทางซ้ายหรือทางขวา สิ่งนี้ทำงานได้ดีมากเนื่องจากอุปกรณ์นี้ส่งคืนสัญญาณที่ถูกตีกลับ นี่คืออินพุตสำหรับอัลกอริทึมการตรวจจับสูงสุดนี้จากอุปกรณ์ - แสดงผลกระทบจากด้านขวาตามด้วยและผลกระทบจากด้านซ้าย คุณสามารถเห็นเข็มเริ่มต้นจากนั้นความผันผวนของเซ็นเซอร์

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

#include <stdio.h>
#include <math.h>
#include <string.h>


#define SAMPLE_LENGTH 1000

float stddev(float data[], int len);
float mean(float data[], int len);
void thresholding(float y[], int signals[], int lag, float threshold, float influence);


void thresholding(float y[], int signals[], int lag, float threshold, float influence) {
    memset(signals, 0, sizeof(float) * SAMPLE_LENGTH);
    float filteredY[SAMPLE_LENGTH];
    memcpy(filteredY, y, sizeof(float) * SAMPLE_LENGTH);
    float avgFilter[SAMPLE_LENGTH];
    float stdFilter[SAMPLE_LENGTH];

    avgFilter[lag - 1] = mean(y, lag);
    stdFilter[lag - 1] = stddev(y, lag);

    for (int i = lag; i < SAMPLE_LENGTH; i++) {
        if (fabsf(y[i] - avgFilter[i-1]) > threshold * stdFilter[i-1]) {
            if (y[i] > avgFilter[i-1]) {
                signals[i] = 1;
            } else {
                signals[i] = -1;
            }
            filteredY[i] = influence * y[i] + (1 - influence) * filteredY[i-1];
        } else {
            signals[i] = 0;
        }
        avgFilter[i] = mean(filteredY + i-lag, lag);
        stdFilter[i] = stddev(filteredY + i-lag, lag);
    }
}

float mean(float data[], int len) {
    float sum = 0.0, mean = 0.0;

    int i;
    for(i=0; i<len; ++i) {
        sum += data[i];
    }

    mean = sum/len;
    return mean;


}

float stddev(float data[], int len) {
    float the_mean = mean(data, len);
    float standardDeviation = 0.0;

    int i;
    for(i=0; i<len; ++i) {
        standardDeviation += pow(data[i] - the_mean, 2);
    }

    return sqrt(standardDeviation/len);
}

int main() {
    printf("Hello, World!\n");
    int lag = 100;
    float threshold = 5;
    float influence = 0;
    float y[]=  {1,1,1.1,1,0.9,1,1,1.1,1,0.9,1,1.1,1,1,0.9,1,1,1.1,1,1,1,1,1.1,0.9,1,1.1,1,1,0.9,
  ....
1,1.1,1,1,1.1,1,0.8,0.9,1,1.2,0.9,1,1,1.1,1.2,1,1.5,1,3,2,5,3,2,1,1,1,0.9,1,1,3,       2.6,4,3,3.2,2,1,1,0.8,4,4,2,2.5,1,1,1,1.2,1,1.5,1,3,2,5,3,2,1,1,1,0.9,1,1,3,
       2.6,4,3,3.2,2,1,1,0.8,4,4,2,2.5,1,1,1}

    int signal[SAMPLE_LENGTH];

    thresholding(y, signal,  lag, threshold, influence);

    return 0;
}

ผลลัพธ์ของเธอที่มีอิทธิพล = 0

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

ไม่ดี แต่มีอิทธิพล = 1

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

ซึ่งดีมาก


5

นี่คือการนำ Java ไปใช้จริงตามคำตอบ Groovy ที่โพสต์ไว้ก่อนหน้านี้ (ฉันรู้ว่ามีการติดตั้ง Groovy และ Kotlin ไว้แล้ว แต่สำหรับคนอย่างฉันที่เพิ่งทำจาวามันเป็นเรื่องยากที่จะเข้าใจวิธีการแปลงระหว่างภาษาอื่นและ Java)

(ผลลัพธ์ตรงกับกราฟของคนอื่น)

การใช้อัลกอริทึม

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import org.apache.commons.math3.stat.descriptive.SummaryStatistics;

public class SignalDetector {

    public HashMap<String, List> analyzeDataForSignals(List<Double> data, int lag, Double threshold, Double influence) {

        // init stats instance
        SummaryStatistics stats = new SummaryStatistics();

        // the results (peaks, 1 or -1) of our algorithm
        List<Integer> signals = new ArrayList<Integer>(Collections.nCopies(data.size(), 0));

        // filter out the signals (peaks) from our original list (using influence arg)
        List<Double> filteredData = new ArrayList<Double>(data);

        // the current average of the rolling window
        List<Double> avgFilter = new ArrayList<Double>(Collections.nCopies(data.size(), 0.0d));

        // the current standard deviation of the rolling window
        List<Double> stdFilter = new ArrayList<Double>(Collections.nCopies(data.size(), 0.0d));

        // init avgFilter and stdFilter
        for (int i = 0; i < lag; i++) {
            stats.addValue(data.get(i));
        }
        avgFilter.set(lag - 1, stats.getMean());
        stdFilter.set(lag - 1, Math.sqrt(stats.getPopulationVariance())); // getStandardDeviation() uses sample variance
        stats.clear();

        // loop input starting at end of rolling window
        for (int i = lag; i < data.size(); i++) {

            // if the distance between the current value and average is enough standard deviations (threshold) away
            if (Math.abs((data.get(i) - avgFilter.get(i - 1))) > threshold * stdFilter.get(i - 1)) {

                // this is a signal (i.e. peak), determine if it is a positive or negative signal
                if (data.get(i) > avgFilter.get(i - 1)) {
                    signals.set(i, 1);
                } else {
                    signals.set(i, -1);
                }

                // filter this signal out using influence
                filteredData.set(i, (influence * data.get(i)) + ((1 - influence) * filteredData.get(i - 1)));
            } else {
                // ensure this signal remains a zero
                signals.set(i, 0);
                // ensure this value is not filtered
                filteredData.set(i, data.get(i));
            }

            // update rolling average and deviation
            for (int j = i - lag; j < i; j++) {
                stats.addValue(filteredData.get(j));
            }
            avgFilter.set(i, stats.getMean());
            stdFilter.set(i, Math.sqrt(stats.getPopulationVariance()));
            stats.clear();
        }

        HashMap<String, List> returnMap = new HashMap<String, List>();
        returnMap.put("signals", signals);
        returnMap.put("filteredData", filteredData);
        returnMap.put("avgFilter", avgFilter);
        returnMap.put("stdFilter", stdFilter);

        return returnMap;

    } // end
}

วิธีการหลัก

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

public class Main {

    public static void main(String[] args) throws Exception {
        DecimalFormat df = new DecimalFormat("#0.000");

        ArrayList<Double> data = new ArrayList<Double>(Arrays.asList(1d, 1d, 1.1d, 1d, 0.9d, 1d, 1d, 1.1d, 1d, 0.9d, 1d,
                1.1d, 1d, 1d, 0.9d, 1d, 1d, 1.1d, 1d, 1d, 1d, 1d, 1.1d, 0.9d, 1d, 1.1d, 1d, 1d, 0.9d, 1d, 1.1d, 1d, 1d,
                1.1d, 1d, 0.8d, 0.9d, 1d, 1.2d, 0.9d, 1d, 1d, 1.1d, 1.2d, 1d, 1.5d, 1d, 3d, 2d, 5d, 3d, 2d, 1d, 1d, 1d,
                0.9d, 1d, 1d, 3d, 2.6d, 4d, 3d, 3.2d, 2d, 1d, 1d, 0.8d, 4d, 4d, 2d, 2.5d, 1d, 1d, 1d));

        SignalDetector signalDetector = new SignalDetector();
        int lag = 30;
        double threshold = 5;
        double influence = 0;

        HashMap<String, List> resultsMap = signalDetector.analyzeDataForSignals(data, lag, threshold, influence);
        // print algorithm params
        System.out.println("lag: " + lag + "\t\tthreshold: " + threshold + "\t\tinfluence: " + influence);

        System.out.println("Data size: " + data.size());
        System.out.println("Signals size: " + resultsMap.get("signals").size());

        // print data
        System.out.print("Data:\t\t");
        for (double d : data) {
            System.out.print(df.format(d) + "\t");
        }
        System.out.println();

        // print signals
        System.out.print("Signals:\t");
        List<Integer> signalsList = resultsMap.get("signals");
        for (int i : signalsList) {
            System.out.print(df.format(i) + "\t");
        }
        System.out.println();

        // print filtered data
        System.out.print("Filtered Data:\t");
        List<Double> filteredDataList = resultsMap.get("filteredData");
        for (double d : filteredDataList) {
            System.out.print(df.format(d) + "\t");
        }
        System.out.println();

        // print running average
        System.out.print("Avg Filter:\t");
        List<Double> avgFilterList = resultsMap.get("avgFilter");
        for (double d : avgFilterList) {
            System.out.print(df.format(d) + "\t");
        }
        System.out.println();

        // print running std
        System.out.print("Std filter:\t");
        List<Double> stdFilterList = resultsMap.get("stdFilter");
        for (double d : stdFilterList) {
            System.out.print(df.format(d) + "\t");
        }
        System.out.println();

        System.out.println();
        for (int i = 0; i < signalsList.size(); i++) {
            if (signalsList.get(i) != 0) {
                System.out.println("Point " + i + " gave signal " + signalsList.get(i));
            }
        }
    }
}

ผล

lag: 30     threshold: 5.0      influence: 0.0
Data size: 74
Signals size: 74
Data:           1.000   1.000   1.100   1.000   0.900   1.000   1.000   1.100   1.000   0.900   1.000   1.100   1.000   1.000   0.900   1.000   1.000   1.100   1.000   1.000   1.000   1.000   1.100   0.900   1.000   1.100   1.000   1.000   0.900   1.000   1.100   1.000   1.000   1.100   1.000   0.800   0.900   1.000   1.200   0.900   1.000   1.000   1.100   1.200   1.000   1.500   1.000   3.000   2.000   5.000   3.000   2.000   1.000   1.000   1.000   0.900   1.000   1.000   3.000   2.600   4.000   3.000   3.200   2.000   1.000   1.000   0.800   4.000   4.000   2.000   2.500   1.000   1.000   1.000   
Signals:        0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   1.000   0.000   1.000   1.000   1.000   1.000   1.000   0.000   0.000   0.000   0.000   0.000   0.000   1.000   1.000   1.000   1.000   1.000   1.000   0.000   0.000   0.000   1.000   1.000   1.000   1.000   0.000   0.000   0.000   
Filtered Data:  1.000   1.000   1.100   1.000   0.900   1.000   1.000   1.100   1.000   0.900   1.000   1.100   1.000   1.000   0.900   1.000   1.000   1.100   1.000   1.000   1.000   1.000   1.100   0.900   1.000   1.100   1.000   1.000   0.900   1.000   1.100   1.000   1.000   1.100   1.000   0.800   0.900   1.000   1.200   0.900   1.000   1.000   1.100   1.200   1.000   1.000   1.000   1.000   1.000   1.000   1.000   1.000   1.000   1.000   1.000   0.900   1.000   1.000   1.000   1.000   1.000   1.000   1.000   1.000   1.000   1.000   0.800   0.800   0.800   0.800   0.800   1.000   1.000   1.000   
Avg Filter:     0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   1.003   1.003   1.007   1.007   1.003   1.007   1.010   1.003   1.000   0.997   1.003   1.003   1.003   1.000   1.003   1.010   1.013   1.013   1.013   1.010   1.010   1.010   1.010   1.010   1.007   1.010   1.010   1.003   1.003   1.003   1.007   1.007   1.003   1.003   1.003   1.000   1.000   1.007   1.003   0.997   0.983   0.980   0.973   0.973   0.970   
Std filter:     0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.060   0.060   0.063   0.063   0.060   0.063   0.060   0.071   0.073   0.071   0.080   0.080   0.080   0.077   0.080   0.087   0.085   0.085   0.085   0.083   0.083   0.083   0.083   0.083   0.081   0.079   0.079   0.080   0.080   0.080   0.077   0.077   0.075   0.075   0.075   0.073   0.073   0.063   0.071   0.080   0.078   0.083   0.089   0.089   0.086   

Point 45 gave signal 1
Point 47 gave signal 1
Point 48 gave signal 1
Point 49 gave signal 1
Point 50 gave signal 1
Point 51 gave signal 1
Point 58 gave signal 1
Point 59 gave signal 1
Point 60 gave signal 1
Point 61 gave signal 1
Point 62 gave signal 1
Point 63 gave signal 1
Point 67 gave signal 1
Point 68 gave signal 1
Point 69 gave signal 1
Point 70 gave signal 1

กราฟแสดงข้อมูลและผลลัพธ์ของการเรียกใช้จาวา


5

ภาคผนวก 1 ถึงคำตอบดั้งเดิม: MatlabและการRแปล

รหัส Matlab

function [signals,avgFilter,stdFilter] = ThresholdingAlgo(y,lag,threshold,influence)
% Initialise signal results
signals = zeros(length(y),1);
% Initialise filtered series
filteredY = y(1:lag+1);
% Initialise filters
avgFilter(lag+1,1) = mean(y(1:lag+1));
stdFilter(lag+1,1) = std(y(1:lag+1));
% Loop over all datapoints y(lag+2),...,y(t)
for i=lag+2:length(y)
    % If new value is a specified number of deviations away
    if abs(y(i)-avgFilter(i-1)) > threshold*stdFilter(i-1)
        if y(i) > avgFilter(i-1)
            % Positive signal
            signals(i) = 1;
        else
            % Negative signal
            signals(i) = -1;
        end
        % Make influence lower
        filteredY(i) = influence*y(i)+(1-influence)*filteredY(i-1);
    else
        % No signal
        signals(i) = 0;
        filteredY(i) = y(i);
    end
    % Adjust the filters
    avgFilter(i) = mean(filteredY(i-lag:i));
    stdFilter(i) = std(filteredY(i-lag:i));
end
% Done, now return results
end

ตัวอย่าง:

% Data
y = [1 1 1.1 1 0.9 1 1 1.1 1 0.9 1 1.1 1 1 0.9 1 1 1.1 1 1,...
    1 1 1.1 0.9 1 1.1 1 1 0.9 1 1.1 1 1 1.1 1 0.8 0.9 1 1.2 0.9 1,...
    1 1.1 1.2 1 1.5 1 3 2 5 3 2 1 1 1 0.9 1,...
    1 3 2.6 4 3 3.2 2 1 1 0.8 4 4 2 2.5 1 1 1];

% Settings
lag = 30;
threshold = 5;
influence = 0;

% Get results
[signals,avg,dev] = ThresholdingAlgo(y,lag,threshold,influence);

figure; subplot(2,1,1); hold on;
x = 1:length(y); ix = lag+1:length(y);
area(x(ix),avg(ix)+threshold*dev(ix),'FaceColor',[0.9 0.9 0.9],'EdgeColor','none');
area(x(ix),avg(ix)-threshold*dev(ix),'FaceColor',[1 1 1],'EdgeColor','none');
plot(x(ix),avg(ix),'LineWidth',1,'Color','cyan','LineWidth',1.5);
plot(x(ix),avg(ix)+threshold*dev(ix),'LineWidth',1,'Color','green','LineWidth',1.5);
plot(x(ix),avg(ix)-threshold*dev(ix),'LineWidth',1,'Color','green','LineWidth',1.5);
plot(1:length(y),y,'b');
subplot(2,1,2);
stairs(signals,'r','LineWidth',1.5); ylim([-1.5 1.5]);

รหัส R

ThresholdingAlgo <- function(y,lag,threshold,influence) {
  signals <- rep(0,length(y))
  filteredY <- y[0:lag]
  avgFilter <- NULL
  stdFilter <- NULL
  avgFilter[lag] <- mean(y[0:lag], na.rm=TRUE)
  stdFilter[lag] <- sd(y[0:lag], na.rm=TRUE)
  for (i in (lag+1):length(y)){
    if (abs(y[i]-avgFilter[i-1]) > threshold*stdFilter[i-1]) {
      if (y[i] > avgFilter[i-1]) {
        signals[i] <- 1;
      } else {
        signals[i] <- -1;
      }
      filteredY[i] <- influence*y[i]+(1-influence)*filteredY[i-1]
    } else {
      signals[i] <- 0
      filteredY[i] <- y[i]
    }
    avgFilter[i] <- mean(filteredY[(i-lag):i], na.rm=TRUE)
    stdFilter[i] <- sd(filteredY[(i-lag):i], na.rm=TRUE)
  }
  return(list("signals"=signals,"avgFilter"=avgFilter,"stdFilter"=stdFilter))
}

ตัวอย่าง:

# Data
y <- c(1,1,1.1,1,0.9,1,1,1.1,1,0.9,1,1.1,1,1,0.9,1,1,1.1,1,1,1,1,1.1,0.9,1,1.1,1,1,0.9,
       1,1.1,1,1,1.1,1,0.8,0.9,1,1.2,0.9,1,1,1.1,1.2,1,1.5,1,3,2,5,3,2,1,1,1,0.9,1,1,3,
       2.6,4,3,3.2,2,1,1,0.8,4,4,2,2.5,1,1,1)

lag       <- 30
threshold <- 5
influence <- 0

# Run algo with lag = 30, threshold = 5, influence = 0
result <- ThresholdingAlgo(y,lag,threshold,influence)

# Plot result
par(mfrow = c(2,1),oma = c(2,2,0,0) + 0.1,mar = c(0,0,2,1) + 0.2)
plot(1:length(y),y,type="l",ylab="",xlab="") 
lines(1:length(y),result$avgFilter,type="l",col="cyan",lwd=2)
lines(1:length(y),result$avgFilter+threshold*result$stdFilter,type="l",col="green",lwd=2)
lines(1:length(y),result$avgFilter-threshold*result$stdFilter,type="l",col="green",lwd=2)
plot(result$signals,type="S",col="red",ylab="",xlab="",ylim=c(-1.5,1.5),lwd=2)

รหัสนี้ (ทั้งสองภาษา) จะให้ผลลัพธ์ต่อไปนี้สำหรับข้อมูลของคำถามเดิม:

ตัวอย่างใหม่จากรหัส Matlab


ภาคผนวก 2 กับคำตอบเดิม: Matlabรหัสสาธิต

(คลิกเพื่อสร้างข้อมูล)

การสาธิต Matlab

function [] = RobustThresholdingDemo()

%% SPECIFICATIONS
lag         = 5;       % lag for the smoothing
threshold   = 3.5;     % number of st.dev. away from the mean to signal
influence   = 0.3;     % when signal: how much influence for new data? (between 0 and 1)
                       % 1 is normal influence, 0.5 is half      
%% START DEMO
DemoScreen(30,lag,threshold,influence);

end

function [signals,avgFilter,stdFilter] = ThresholdingAlgo(y,lag,threshold,influence)
signals = zeros(length(y),1);
filteredY = y(1:lag+1);
avgFilter(lag+1,1) = mean(y(1:lag+1));
stdFilter(lag+1,1) = std(y(1:lag+1));
for i=lag+2:length(y)
    if abs(y(i)-avgFilter(i-1)) > threshold*stdFilter(i-1)
        if y(i) > avgFilter(i-1)
            signals(i) = 1;
        else
            signals(i) = -1;
        end
        filteredY(i) = influence*y(i)+(1-influence)*filteredY(i-1);
    else
        signals(i) = 0;
        filteredY(i) = y(i);
    end
    avgFilter(i) = mean(filteredY(i-lag:i));
    stdFilter(i) = std(filteredY(i-lag:i));
end
end

% Demo screen function
function [] = DemoScreen(n,lag,threshold,influence)
figure('Position',[200 100,1000,500]);
subplot(2,1,1);
title(sprintf(['Draw data points (%.0f max)      [settings: lag = %.0f, '...
    'threshold = %.2f, influence = %.2f]'],n,lag,threshold,influence));
ylim([0 5]); xlim([0 50]);
H = gca; subplot(2,1,1);
set(H, 'YLimMode', 'manual'); set(H, 'XLimMode', 'manual');
set(H, 'YLim', get(H,'YLim')); set(H, 'XLim', get(H,'XLim'));
xg = []; yg = [];
for i=1:n
    try
        [xi,yi] = ginput(1);
    catch
        return;
    end
    xg = [xg xi]; yg = [yg yi];
    if i == 1
        subplot(2,1,1); hold on;
        plot(H, xg(i),yg(i),'r.'); 
        text(xg(i),yg(i),num2str(i),'FontSize',7);
    end
    if length(xg) > lag
        [signals,avg,dev] = ...
            ThresholdingAlgo(yg,lag,threshold,influence);
        area(xg(lag+1:end),avg(lag+1:end)+threshold*dev(lag+1:end),...
            'FaceColor',[0.9 0.9 0.9],'EdgeColor','none');
        area(xg(lag+1:end),avg(lag+1:end)-threshold*dev(lag+1:end),...
            'FaceColor',[1 1 1],'EdgeColor','none');
        plot(xg(lag+1:end),avg(lag+1:end),'LineWidth',1,'Color','cyan');
        plot(xg(lag+1:end),avg(lag+1:end)+threshold*dev(lag+1:end),...
            'LineWidth',1,'Color','green');
        plot(xg(lag+1:end),avg(lag+1:end)-threshold*dev(lag+1:end),...
            'LineWidth',1,'Color','green');
        subplot(2,1,2); hold on; title('Signal output');
        stairs(xg(lag+1:end),signals(lag+1:end),'LineWidth',2,'Color','blue');
        ylim([-2 2]); xlim([0 50]); hold off;
    end
    subplot(2,1,1); hold on;
    for j=2:i
        plot(xg([j-1:j]),yg([j-1:j]),'r'); plot(H,xg(j),yg(j),'r.');
        text(xg(j),yg(j),num2str(j),'FontSize',7);
    end
end
end


4

นี่คือความพยายามของฉันในการสร้างโซลูชัน Ruby สำหรับ "Smoothed z-score algo" จากคำตอบที่ยอมรับ:

module ThresholdingAlgoMixin
  def mean(array)
    array.reduce(&:+) / array.size.to_f
  end

  def stddev(array)
    array_mean = mean(array)
    Math.sqrt(array.reduce(0.0) { |a, b| a.to_f + ((b.to_f - array_mean) ** 2) } / array.size.to_f)
  end

  def thresholding_algo(lag: 5, threshold: 3.5, influence: 0.5)
    return nil if size < lag * 2
    Array.new(size, 0).tap do |signals|
      filtered = Array.new(self)

      initial_slice = take(lag)
      avg_filter = Array.new(lag - 1, 0.0) + [mean(initial_slice)]
      std_filter = Array.new(lag - 1, 0.0) + [stddev(initial_slice)]
      (lag..size-1).each do |idx|
        prev = idx - 1
        if (fetch(idx) - avg_filter[prev]).abs > threshold * std_filter[prev]
          signals[idx] = fetch(idx) > avg_filter[prev] ? 1 : -1
          filtered[idx] = (influence * fetch(idx)) + ((1-influence) * filtered[prev])
        end

        filtered_slice = filtered[idx-lag..prev]
        avg_filter[idx] = mean(filtered_slice)
        std_filter[idx] = stddev(filtered_slice)
      end
    end
  end
end

และตัวอย่างการใช้งาน:

test_data = [
  1, 1, 1.1, 1, 0.9, 1, 1, 1.1, 1, 0.9, 1, 1.1, 1, 1, 0.9, 1,
  1, 1.1, 1, 1, 1, 1, 1.1, 0.9, 1, 1.1, 1, 1, 0.9, 1, 1.1, 1,
  1, 1.1, 1, 0.8, 0.9, 1, 1.2, 0.9, 1, 1, 1.1, 1.2, 1, 1.5,
  1, 3, 2, 5, 3, 2, 1, 1, 1, 0.9, 1, 1, 3, 2.6, 4, 3, 3.2, 2,
  1, 1, 0.8, 4, 4, 2, 2.5, 1, 1, 1
].extend(ThresholdingAlgoMixin)

puts test_data.thresholding_algo.inspect

# Output: [
#   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
#   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0,
#   0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
#   1, 1, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0
# ]

เยี่ยมมากขอบคุณสำหรับการแบ่งปัน! ฉันจะเพิ่มคุณในรายการ ตรวจสอบให้แน่ใจว่าสำหรับแอปพลิเคชันเรียลไทม์คุณสร้างฟังก์ชั่นแยกต่างหากเพื่ออัปเดตสัญญาณเมื่อดาต้าพอยน์ใหม่มาถึง (แทนที่จะวนลูปดาต้าพอยน์ทั้งหมดทุกครั้ง)
Jean-Paul

4

รุ่นซ้ำใน python / numpy สำหรับคำตอบhttps://stackoverflow.com/a/22640362/6029703อยู่ที่นี่ รหัสนี้เร็วกว่าการคำนวณค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานทุกความล่าช้าสำหรับข้อมูลขนาดใหญ่ (100000+)

def peak_detection_smoothed_zscore_v2(x, lag, threshold, influence):
    '''
    iterative smoothed z-score algorithm
    Implementation of algorithm from https://stackoverflow.com/a/22640362/6029703
    '''
    import numpy as np
    labels = np.zeros(len(x))
    filtered_y = np.array(x)
    avg_filter = np.zeros(len(x))
    std_filter = np.zeros(len(x))
    var_filter = np.zeros(len(x))

    avg_filter[lag - 1] = np.mean(x[0:lag])
    std_filter[lag - 1] = np.std(x[0:lag])
    var_filter[lag - 1] = np.var(x[0:lag])
    for i in range(lag, len(x)):
        if abs(x[i] - avg_filter[i - 1]) > threshold * std_filter[i - 1]:
            if x[i] > avg_filter[i - 1]:
                labels[i] = 1
            else:
                labels[i] = -1
            filtered_y[i] = influence * x[i] + (1 - influence) * filtered_y[i - 1]
        else:
            labels[i] = 0
            filtered_y[i] = x[i]
        # update avg, var, std
        avg_filter[i] = avg_filter[i - 1] + 1. / lag * (filtered_y[i] - filtered_y[i - lag])
        var_filter[i] = var_filter[i - 1] + 1. / lag * ((filtered_y[i] - avg_filter[i - 1]) ** 2 - (
            filtered_y[i - lag] - avg_filter[i - 1]) ** 2 - (filtered_y[i] - filtered_y[i - lag]) ** 2 / lag)
        std_filter[i] = np.sqrt(var_filter[i])

    return dict(signals=labels,
                avgFilter=avg_filter,
                stdFilter=std_filter)

4

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

using Statistics
using Plots
function SmoothedZscoreAlgo(y, lag, threshold, influence)
    # Julia implimentation of http://stackoverflow.com/a/22640362/6029703
    n = length(y)
    signals = zeros(n) # init signal results
    filteredY = copy(y) # init filtered series
    avgFilter = zeros(n) # init average filter
    stdFilter = zeros(n) # init std filter
    avgFilter[lag - 1] = mean(y[1:lag]) # init first value
    stdFilter[lag - 1] = std(y[1:lag]) # init first value

    for i in range(lag, stop=n-1)
        if abs(y[i] - avgFilter[i-1]) > threshold*stdFilter[i-1]
            if y[i] > avgFilter[i-1]
                signals[i] += 1 # postive signal
            else
                signals[i] += -1 # negative signal
            end
            # Make influence lower
            filteredY[i] = influence*y[i] + (1-influence)*filteredY[i-1]
        else
            signals[i] = 0
            filteredY[i] = y[i]
        end
        avgFilter[i] = mean(filteredY[i-lag+1:i])
        stdFilter[i] = std(filteredY[i-lag+1:i])
    end
    return (signals = signals, avgFilter = avgFilter, stdFilter = stdFilter)
end


# Data
y = [1,1,1.1,1,0.9,1,1,1.1,1,0.9,1,1.1,1,1,0.9,1,1,1.1,1,1,1,1,1.1,0.9,1,1.1,1,1,0.9,
       1,1.1,1,1,1.1,1,0.8,0.9,1,1.2,0.9,1,1,1.1,1.2,1,1.5,1,3,2,5,3,2,1,1,1,0.9,1,1,3,
       2.6,4,3,3.2,2,1,1,0.8,4,4,2,2.5,1,1,1]

# Settings: lag = 30, threshold = 5, influence = 0
lag = 30
threshold = 5
influence = 0

results = SmoothedZscoreAlgo(y, lag, threshold, influence)
upper_bound = results[:avgFilter] + threshold * results[:stdFilter]
lower_bound = results[:avgFilter] - threshold * results[:stdFilter]
x = 1:length(y)

yplot = plot(x,y,color="blue", label="Y",legend=:topleft)
yplot = plot!(x,upper_bound, color="green", label="Upper Bound",legend=:topleft)
yplot = plot!(x,results[:avgFilter], color="cyan", label="Average Filter",legend=:topleft)
yplot = plot!(x,lower_bound, color="green", label="Lower Bound",legend=:topleft)
signalplot = plot(x,results[:signals],color="red",label="Signals",legend=:topleft)
plot(yplot,signalplot,layout=(2,1),legend=:topleft)

ผล


3

นี่คือการใช้ Groovy (Java) ของอัลกอริทึม z-score ที่ปรับให้เรียบ ( ดูคำตอบด้านบน )

/**
 * "Smoothed zero-score alogrithm" shamelessly copied from https://stackoverflow.com/a/22640362/6029703
 *  Uses a rolling mean and a rolling deviation (separate) to identify peaks in a vector
 *
 * @param y - The input vector to analyze
 * @param lag - The lag of the moving window (i.e. how big the window is)
 * @param threshold - The z-score at which the algorithm signals (i.e. how many standard deviations away from the moving mean a peak (or signal) is)
 * @param influence - The influence (between 0 and 1) of new signals on the mean and standard deviation (how much a peak (or signal) should affect other values near it)
 * @return - The calculated averages (avgFilter) and deviations (stdFilter), and the signals (signals)
 */

public HashMap<String, List<Object>> thresholdingAlgo(List<Double> y, Long lag, Double threshold, Double influence) {
    //init stats instance
    SummaryStatistics stats = new SummaryStatistics()

    //the results (peaks, 1 or -1) of our algorithm
    List<Integer> signals = new ArrayList<Integer>(Collections.nCopies(y.size(), 0))
    //filter out the signals (peaks) from our original list (using influence arg)
    List<Double> filteredY = new ArrayList<Double>(y)
    //the current average of the rolling window
    List<Double> avgFilter = new ArrayList<Double>(Collections.nCopies(y.size(), 0.0d))
    //the current standard deviation of the rolling window
    List<Double> stdFilter = new ArrayList<Double>(Collections.nCopies(y.size(), 0.0d))
    //init avgFilter and stdFilter
    (0..lag-1).each { stats.addValue(y[it as int]) }
    avgFilter[lag - 1 as int] = stats.getMean()
    stdFilter[lag - 1 as int] = Math.sqrt(stats.getPopulationVariance()) //getStandardDeviation() uses sample variance (not what we want)
    stats.clear()
    //loop input starting at end of rolling window
    (lag..y.size()-1).each { i ->
        //if the distance between the current value and average is enough standard deviations (threshold) away
        if (Math.abs((y[i as int] - avgFilter[i - 1 as int]) as Double) > threshold * stdFilter[i - 1 as int]) {
            //this is a signal (i.e. peak), determine if it is a positive or negative signal
            signals[i as int] = (y[i as int] > avgFilter[i - 1 as int]) ? 1 : -1
            //filter this signal out using influence
            filteredY[i as int] = (influence * y[i as int]) + ((1-influence) * filteredY[i - 1 as int])
        } else {
            //ensure this signal remains a zero
            signals[i as int] = 0
            //ensure this value is not filtered
            filteredY[i as int] = y[i as int]
        }
        //update rolling average and deviation
        (i - lag..i-1).each { stats.addValue(filteredY[it as int] as Double) }
        avgFilter[i as int] = stats.getMean()
        stdFilter[i as int] = Math.sqrt(stats.getPopulationVariance()) //getStandardDeviation() uses sample variance (not what we want)
        stats.clear()
    }

    return [
        signals  : signals,
        avgFilter: avgFilter,
        stdFilter: stdFilter
    ]
}

ด้านล่างนี้คือการทดสอบในชุดเดียวกันที่ทำให้ผลเช่นเดียวกับข้างต้นหลาม / การดำเนินงาน

    // Data
    def y = [1d, 1d, 1.1d, 1d, 0.9d, 1d, 1d, 1.1d, 1d, 0.9d, 1d, 1.1d, 1d, 1d, 0.9d, 1d, 1d, 1.1d, 1d, 1d,
         1d, 1d, 1.1d, 0.9d, 1d, 1.1d, 1d, 1d, 0.9d, 1d, 1.1d, 1d, 1d, 1.1d, 1d, 0.8d, 0.9d, 1d, 1.2d, 0.9d, 1d,
         1d, 1.1d, 1.2d, 1d, 1.5d, 1d, 3d, 2d, 5d, 3d, 2d, 1d, 1d, 1d, 0.9d, 1d,
         1d, 3d, 2.6d, 4d, 3d, 3.2d, 2d, 1d, 1d, 0.8d, 4d, 4d, 2d, 2.5d, 1d, 1d, 1d]

    // Settings
    def lag = 30
    def threshold = 5
    def influence = 0


    def thresholdingResults = thresholdingAlgo((List<Double>) y, (Long) lag, (Double) threshold, (Double) influence)

    println y.size()
    println thresholdingResults.signals.size()
    println thresholdingResults.signals

    thresholdingResults.signals.eachWithIndex { x, idx ->
        if (x) {
            println y[idx]
        }
    }

3

นี่คือ (ไม่ใช่สำนวน) เวอร์ชั่นสกาล่าของอัลกอริทึ่มคะแนนเรียบ :

/**
  * Smoothed zero-score alogrithm shamelessly copied from https://stackoverflow.com/a/22640362/6029703
  * Uses a rolling mean and a rolling deviation (separate) to identify peaks in a vector
  *
  * @param y - The input vector to analyze
  * @param lag - The lag of the moving window (i.e. how big the window is)
  * @param threshold - The z-score at which the algorithm signals (i.e. how many standard deviations away from the moving mean a peak (or signal) is)
  * @param influence - The influence (between 0 and 1) of new signals on the mean and standard deviation (how much a peak (or signal) should affect other values near it)
  * @return - The calculated averages (avgFilter) and deviations (stdFilter), and the signals (signals)
  */
private def smoothedZScore(y: Seq[Double], lag: Int, threshold: Double, influence: Double): Seq[Int] = {
  val stats = new SummaryStatistics()

  // the results (peaks, 1 or -1) of our algorithm
  val signals = mutable.ArrayBuffer.fill(y.length)(0)

  // filter out the signals (peaks) from our original list (using influence arg)
  val filteredY = y.to[mutable.ArrayBuffer]

  // the current average of the rolling window
  val avgFilter = mutable.ArrayBuffer.fill(y.length)(0d)

  // the current standard deviation of the rolling window
  val stdFilter = mutable.ArrayBuffer.fill(y.length)(0d)

  // init avgFilter and stdFilter
  y.take(lag).foreach(s => stats.addValue(s))

  avgFilter(lag - 1) = stats.getMean
  stdFilter(lag - 1) = Math.sqrt(stats.getPopulationVariance) // getStandardDeviation() uses sample variance (not what we want)

  // loop input starting at end of rolling window
  y.zipWithIndex.slice(lag, y.length - 1).foreach {
    case (s: Double, i: Int) =>
      // if the distance between the current value and average is enough standard deviations (threshold) away
      if (Math.abs(s - avgFilter(i - 1)) > threshold * stdFilter(i - 1)) {
        // this is a signal (i.e. peak), determine if it is a positive or negative signal
        signals(i) = if (s > avgFilter(i - 1)) 1 else -1
        // filter this signal out using influence
        filteredY(i) = (influence * s) + ((1 - influence) * filteredY(i - 1))
      } else {
        // ensure this signal remains a zero
        signals(i) = 0
        // ensure this value is not filtered
        filteredY(i) = s
      }

      // update rolling average and deviation
      stats.clear()
      filteredY.slice(i - lag, i).foreach(s => stats.addValue(s))
      avgFilter(i) = stats.getMean
      stdFilter(i) = Math.sqrt(stats.getPopulationVariance) // getStandardDeviation() uses sample variance (not what we want)
  }

  println(y.length)
  println(signals.length)
  println(signals)

  signals.zipWithIndex.foreach {
    case(x: Int, idx: Int) =>
      if (x == 1) {
        println(idx + " " + y(idx))
      }
  }

  val data =
    y.zipWithIndex.map { case (s: Double, i: Int) => Map("x" -> i, "y" -> s, "name" -> "y", "row" -> "data") } ++
    avgFilter.zipWithIndex.map { case (s: Double, i: Int) => Map("x" -> i, "y" -> s, "name" -> "avgFilter", "row" -> "data") } ++
    avgFilter.zipWithIndex.map { case (s: Double, i: Int) => Map("x" -> i, "y" -> (s - threshold * stdFilter(i)), "name" -> "lower", "row" -> "data") } ++
    avgFilter.zipWithIndex.map { case (s: Double, i: Int) => Map("x" -> i, "y" -> (s + threshold * stdFilter(i)), "name" -> "upper", "row" -> "data") } ++
    signals.zipWithIndex.map { case (s: Int, i: Int) => Map("x" -> i, "y" -> s, "name" -> "signal", "row" -> "signal") }

  Vegas("Smoothed Z")
    .withData(data)
    .mark(Line)
    .encodeX("x", Quant)
    .encodeY("y", Quant)
    .encodeColor(
      field="name",
      dataType=Nominal
    )
    .encodeRow("row", Ordinal)
    .show

  return signals
}

นี่คือการทดสอบที่ส่งคืนผลลัพธ์เช่นเดียวกับรุ่น Python และ Groovy:

val y = List(1d, 1d, 1.1d, 1d, 0.9d, 1d, 1d, 1.1d, 1d, 0.9d, 1d, 1.1d, 1d, 1d, 0.9d, 1d, 1d, 1.1d, 1d, 1d,
  1d, 1d, 1.1d, 0.9d, 1d, 1.1d, 1d, 1d, 0.9d, 1d, 1.1d, 1d, 1d, 1.1d, 1d, 0.8d, 0.9d, 1d, 1.2d, 0.9d, 1d,
  1d, 1.1d, 1.2d, 1d, 1.5d, 1d, 3d, 2d, 5d, 3d, 2d, 1d, 1d, 1d, 0.9d, 1d,
  1d, 3d, 2.6d, 4d, 3d, 3.2d, 2d, 1d, 1d, 0.8d, 4d, 4d, 2d, 2.5d, 1d, 1d, 1d)

val lag = 30
val threshold = 5d
val influence = 0d

smoothedZScore(y, lag, threshold, influence)

แผนภูมิผลลัพธ์ของสเวกัส

สรุปสาระสำคัญที่นี่


1 หมายถึงยอดเขา, -1 หมายถึงหุบเขา
Mike Roberts

3

ฉันต้องการสิ่งนี้ในโครงการ Android ของฉัน คิดว่าฉันอาจคืนการใช้Kotlin

/**
* Smoothed zero-score alogrithm shamelessly copied from https://stackoverflow.com/a/22640362/6029703
* Uses a rolling mean and a rolling deviation (separate) to identify peaks in a vector
*
* @param y - The input vector to analyze
* @param lag - The lag of the moving window (i.e. how big the window is)
* @param threshold - The z-score at which the algorithm signals (i.e. how many standard deviations away from the moving mean a peak (or signal) is)
* @param influence - The influence (between 0 and 1) of new signals on the mean and standard deviation (how much a peak (or signal) should affect other values near it)
* @return - The calculated averages (avgFilter) and deviations (stdFilter), and the signals (signals)
*/
fun smoothedZScore(y: List<Double>, lag: Int, threshold: Double, influence: Double): Triple<List<Int>, List<Double>, List<Double>> {
    val stats = SummaryStatistics()
    // the results (peaks, 1 or -1) of our algorithm
    val signals = MutableList<Int>(y.size, { 0 })
    // filter out the signals (peaks) from our original list (using influence arg)
    val filteredY = ArrayList<Double>(y)
    // the current average of the rolling window
    val avgFilter = MutableList<Double>(y.size, { 0.0 })
    // the current standard deviation of the rolling window
    val stdFilter = MutableList<Double>(y.size, { 0.0 })
    // init avgFilter and stdFilter
    y.take(lag).forEach { s -> stats.addValue(s) }
    avgFilter[lag - 1] = stats.mean
    stdFilter[lag - 1] = Math.sqrt(stats.populationVariance) // getStandardDeviation() uses sample variance (not what we want)
    stats.clear()
    //loop input starting at end of rolling window
    (lag..y.size - 1).forEach { i ->
        //if the distance between the current value and average is enough standard deviations (threshold) away
        if (Math.abs(y[i] - avgFilter[i - 1]) > threshold * stdFilter[i - 1]) {
            //this is a signal (i.e. peak), determine if it is a positive or negative signal
            signals[i] = if (y[i] > avgFilter[i - 1]) 1 else -1
            //filter this signal out using influence
            filteredY[i] = (influence * y[i]) + ((1 - influence) * filteredY[i - 1])
        } else {
            //ensure this signal remains a zero
            signals[i] = 0
            //ensure this value is not filtered
            filteredY[i] = y[i]
        }
        //update rolling average and deviation
        (i - lag..i - 1).forEach { stats.addValue(filteredY[it]) }
        avgFilter[i] = stats.getMean()
        stdFilter[i] = Math.sqrt(stats.getPopulationVariance()) //getStandardDeviation() uses sample variance (not what we want)
        stats.clear()
    }
    return Triple(signals, avgFilter, stdFilter)
}

ตัวอย่างโครงการด้วยกราฟการตรวจสอบสามารถพบได้ที่GitHub

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


! น่ากลัว ขอบคุณสำหรับการแบ่งปัน. สำหรับแอปพลิเคชันเรียลไทม์ให้แน่ใจว่าได้สร้างฟังก์ชั่นแยกต่างหากที่คำนวณสัญญาณใหม่ด้วยดาต้าพอยน์ที่เข้ามาแต่ละอัน อย่าห่วงมากกว่าข้อมูลเต็มรูปแบบทุกครั้งที่ DataPoint ใหม่มาถึงว่าจะไม่มีประสิทธิภาพมาก :)
Jean-Paul

1
จุดดีไม่ได้คิดอย่างนั้นเพราะหน้าต่างที่ฉันใช้ไม่ทับซ้อนกัน
leonardkraemer

3

นี่เป็นรุ่น Fortran เปลี่ยนแปลงของอัลกอริทึ่ม-คะแนน มันมีการเปลี่ยนแปลงโดยเฉพาะสำหรับการตรวจจับสูงสุด (กำทอน) ในฟังก์ชั่นการถ่ายโอนในพื้นที่ความถี่ (การเปลี่ยนแปลงแต่ละครั้งมีความคิดเห็นเล็ก ๆ ในรหัส)

การแก้ไขครั้งแรกให้คำเตือนแก่ผู้ใช้หากมีการสั่นพ้องใกล้กับขอบล่างของเวกเตอร์อินพุตที่ระบุโดยค่าเบี่ยงเบนมาตรฐานสูงกว่าเกณฑ์ที่กำหนด (10% ในกรณีนี้) นี่หมายความว่าสัญญาณไม่แบนพอสำหรับการตรวจสอบการเริ่มต้นใช้งานตัวกรองอย่างถูกต้อง

การปรับเปลี่ยนครั้งที่สองนั้นจะเพิ่มเฉพาะค่าสูงสุดของจุดสูงสุดไปยังจุดสูงสุดที่พบ สิ่งนี้สามารถเข้าถึงได้โดยการเปรียบเทียบค่าสูงสุดแต่ละค่าที่พบกับขนาดของรุ่นก่อนหน้า (lag) และตัวสืบทอด (lag)

การเปลี่ยนแปลงที่สามคือการเคารพว่าพีคเรโซแนนท์มักจะแสดงรูปแบบของสมมาตรรอบความถี่เรโซแนนซ์ ดังนั้นจึงเป็นธรรมดาที่จะคำนวณค่าเฉลี่ยและ std แบบสมมาตรรอบ ๆ จุดข้อมูลปัจจุบัน (แทนที่จะเป็นเพียงรุ่นก่อน) สิ่งนี้ส่งผลให้เกิดพฤติกรรมการตรวจจับจุดสูงสุดที่ดีกว่า

การปรับเปลี่ยนมีผลกระทบต่อการทำงานของสัญญาณทั้งหมดซึ่งเป็นกรณีปกติสำหรับการตรวจจับเสียงสะท้อน (เช่น Matlab ตัวอย่างของ Jean-Paul ที่จุดข้อมูลถูกสร้างขึ้นทันที)

function PeakDetect(y,lag,threshold, influence)
    implicit none
    ! Declaring part
    real, dimension(:), intent(in) :: y
    integer, dimension(size(y)) :: PeakDetect
    real, dimension(size(y)) :: filteredY, avgFilter, stdFilter
    integer :: lag, ii
    real :: threshold, influence

    ! Executing part
    PeakDetect = 0
    filteredY = 0.0
    filteredY(1:lag+1) = y(1:lag+1)
    avgFilter = 0.0
    avgFilter(lag+1) = mean(y(1:2*lag+1))
    stdFilter = 0.0
    stdFilter(lag+1) = std(y(1:2*lag+1))

    if (stdFilter(lag+1)/avgFilter(lag+1)>0.1) then ! If the coefficient of variation exceeds 10%, the signal is too uneven at the start, possibly because of a peak.
        write(unit=*,fmt=1001)
1001        format(1X,'Warning: Peak detection might have failed, as there may be a peak at the edge of the frequency range.',/)
    end if
    do ii = lag+2, size(y)
        if (abs(y(ii) - avgFilter(ii-1)) > threshold * stdFilter(ii-1)) then
            ! Find only the largest outstanding value which is only the one greater than its predecessor and its successor
            if (y(ii) > avgFilter(ii-1) .AND. y(ii) > y(ii-1) .AND. y(ii) > y(ii+1)) then
                PeakDetect(ii) = 1
            end if
            filteredY(ii) = influence * y(ii) + (1 - influence) * filteredY(ii-1)
        else
            filteredY(ii) = y(ii)
        end if
        ! Modified with respect to the original code. Mean and standard deviation are calculted symmetrically around the current point
        avgFilter(ii) = mean(filteredY(ii-lag:ii+lag))
        stdFilter(ii) = std(filteredY(ii-lag:ii+lag))
    end do
end function PeakDetect

real function mean(y)
    !> @brief Calculates the mean of vector y
    implicit none
    ! Declaring part
    real, dimension(:), intent(in) :: y
    integer :: N
    ! Executing part
    N = max(1,size(y))
    mean = sum(y)/N
end function mean

real function std(y)
    !> @brief Calculates the standard deviation of vector y
    implicit none
    ! Declaring part
    real, dimension(:), intent(in) :: y
    integer :: N
    ! Executing part
    N = max(1,size(y))
    std = sqrt((N*dot_product(y,y) - sum(y)**2) / (N*(N-1)))
end function std

สำหรับแอปพลิเคชันของฉันอัลกอริทึมทำงานเหมือนมีเสน่ห์! ป้อนคำอธิบายรูปภาพที่นี่


3

หากคุณมีข้อมูลของคุณในตารางฐานข้อมูลต่อไปนี้เป็นอัลกอริทึม z-score แบบ SQL อย่างง่าย:

with data_with_zscore as (
    select
        date_time,
        value,
        value / (avg(value) over ()) as pct_of_mean,
        (value - avg(value) over ()) / (stdev(value) over ()) as z_score
    from {{tablename}}  where datetime > '2018-11-26' and datetime < '2018-12-03'
)


-- select all
select * from data_with_zscore 

-- select only points greater than a certain threshold
select * from data_with_zscore where z_score > abs(2)

รหัสของคุณทำอย่างอื่นที่ไม่ใช่อัลกอริทึมที่ฉันเสนอ คำค้นหาของคุณเพียงคำนวณคะแนน z ([จุดข้อมูล - หมายถึง] / std) แต่ไม่รวมตรรกะของอัลกอริทึมของฉันที่จะละเว้นสัญญาณที่ผ่านมาเมื่อคำนวณเกณฑ์สัญญาณใหม่ นอกจากนี้คุณยังละเว้นพารามิเตอร์ทั้งสาม (ความล่าช้าอิทธิพลขีด จำกัด ) คุณสามารถแก้ไขคำตอบของคุณเพื่อรวมตรรกะที่เกิดขึ้นจริงได้หรือไม่?
Jean-Paul

1
ใช่คุณถูก. ตอนแรกฉันคิดว่าฉันจะได้ออกไปกับเวอร์ชั่นที่เรียบง่ายด้านบน .. ฉันได้ใช้วิธีแก้ปัญหาแบบเต็มของคุณไปยัง C # ดูคำตอบของฉันด้านล่าง เมื่อฉันมีเวลามากขึ้นฉันจะกลับไปที่เวอร์ชัน SQL นี้อีกครั้งและรวมอัลกอริทึมของคุณ ขอบคุณมากสำหรับคำตอบที่ยอดเยี่ยมและคำอธิบายด้วยภาพ
Ocean Airdrop

ไม่มีปัญหาและดีใจที่อัลกอริทึมสามารถช่วยคุณได้! ขอบคุณสำหรับการส่ง C # ของคุณว่ายังคงหายไป ฉันจะเพิ่มไปที่รายการแปล!
Jean-Paul

3

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

import numpy as np

class real_time_peak_detection():
    def __init__(self, array, lag, threshold, influence):
        self.y = list(array)
        self.length = len(self.y)
        self.lag = lag
        self.threshold = threshold
        self.influence = influence
        self.signals = [0] * len(self.y)
        self.filteredY = np.array(self.y).tolist()
        self.avgFilter = [0] * len(self.y)
        self.stdFilter = [0] * len(self.y)
        self.avgFilter[self.lag - 1] = np.mean(self.y[0:self.lag]).tolist()
        self.stdFilter[self.lag - 1] = np.std(self.y[0:self.lag]).tolist()

    def thresholding_algo(self, new_value):
        self.y.append(new_value)
        i = len(self.y) - 1
        self.length = len(self.y)
        if i < self.lag:
            return 0
        elif i == self.lag:
            self.signals = [0] * len(self.y)
            self.filteredY = np.array(self.y).tolist()
            self.avgFilter = [0] * len(self.y)
            self.stdFilter = [0] * len(self.y)
            self.avgFilter[self.lag - 1] = np.mean(self.y[0:self.lag]).tolist()
            self.stdFilter[self.lag - 1] = np.std(self.y[0:self.lag]).tolist()
            return 0

        self.signals += [0]
        self.filteredY += [0]
        self.avgFilter += [0]
        self.stdFilter += [0]

        if abs(self.y[i] - self.avgFilter[i - 1]) > self.threshold * self.stdFilter[i - 1]:
            if self.y[i] > self.avgFilter[i - 1]:
                self.signals[i] = 1
            else:
                self.signals[i] = -1

            self.filteredY[i] = self.influence * self.y[i] + (1 - self.influence) * self.filteredY[i - 1]
            self.avgFilter[i] = np.mean(self.filteredY[(i - self.lag):i])
            self.stdFilter[i] = np.std(self.filteredY[(i - self.lag):i])
        else:
            self.signals[i] = 0
            self.filteredY[i] = self.y[i]
            self.avgFilter[i] = np.mean(self.filteredY[(i - self.lag):i])
            self.stdFilter[i] = np.std(self.filteredY[(i - self.lag):i])

        return self.signals[i]

ขอบคุณสำหรับการโพสต์ฉันได้เพิ่มการแปลของคุณในรายการ
Jean-Paul

3

ฉันอนุญาตให้ตัวเองสร้างเวอร์ชันจาวาสคริปต์ อาจเป็นประโยชน์ จาวาสคริปต์ควรเป็นการถอดความ Pseudocode โดยตรง เป็นแพคเกจ npm และ repo github:

การแปล Javascript:

// javascript port of: /programming/22583391/peak-signal-detection-in-realtime-timeseries-data/48895639#48895639

function sum(a) {
    return a.reduce((acc, val) => acc + val)
}

function mean(a) {
    return sum(a) / a.length
}

function stddev(arr) {
    const arr_mean = mean(arr)
    const r = function(acc, val) {
        return acc + ((val - arr_mean) * (val - arr_mean))
    }
    return Math.sqrt(arr.reduce(r, 0.0) / arr.length)
}

function smoothed_z_score(y, params) {
    var p = params || {}
    // init cooefficients
    const lag = p.lag || 5
    const threshold = p.threshold || 3.5
    const influence = p.influece || 0.5

    if (y === undefined || y.length < lag + 2) {
        throw ` ## y data array to short(${y.length}) for given lag of ${lag}`
    }
    //console.log(`lag, threshold, influence: ${lag}, ${threshold}, ${influence}`)

    // init variables
    var signals = Array(y.length).fill(0)
    var filteredY = y.slice(0)
    const lead_in = y.slice(0, lag)
    //console.log("1: " + lead_in.toString())

    var avgFilter = []
    avgFilter[lag - 1] = mean(lead_in)
    var stdFilter = []
    stdFilter[lag - 1] = stddev(lead_in)
    //console.log("2: " + stdFilter.toString())

    for (var i = lag; i < y.length; i++) {
        //console.log(`${y[i]}, ${avgFilter[i-1]}, ${threshold}, ${stdFilter[i-1]}`)
        if (Math.abs(y[i] - avgFilter[i - 1]) > (threshold * stdFilter[i - 1])) {
            if (y[i] > avgFilter[i - 1]) {
                signals[i] = +1 // positive signal
            } else {
                signals[i] = -1 // negative signal
            }
            // make influence lower
            filteredY[i] = influence * y[i] + (1 - influence) * filteredY[i - 1]
        } else {
            signals[i] = 0 // no signal
            filteredY[i] = y[i]
        }

        // adjust the filters
        const y_lag = filteredY.slice(i - lag, i)
        avgFilter[i] = mean(y_lag)
        stdFilter[i] = stddev(y_lag)
    }

    return signals
}

module.exports = smoothed_z_score

ขอบคุณที่โพสต์คำแปลของคุณ ฉันได้เพิ่มรหัสของคุณในคำตอบของคุณเพื่อให้ผู้คนสามารถเห็นได้อย่างรวดเร็ว ฉันจะเพิ่มคำแปลของคุณในรายการ
Jean-Paul

ตอนนี้ฉันได้แจ้งอัลกอริทึมอื่น ๆ ให้กับจาวาสคริปต์แล้ว เวลานี้จากตัวเลข pyhon ซึ่งทำให้ฉันควบคุมได้มากขึ้นและทำงานได้ดีขึ้นสำหรับฉัน ยังบรรจุใน NPM และคุณสามารถหาข้อมูลเพิ่มเติมเกี่ยวกับอัลโกจากมหาวิทยาลัยรัฐวอชิงตันในหน้าจูปีเตอร์ของพวกเขาได้ npmjs.com/package/@joe_six/duarte-watanabe-peak-detection
Dirk Lüsebrink

2

หากค่าขอบเขตหรือเกณฑ์อื่น ๆ ขึ้นอยู่กับค่าในอนาคตวิธีแก้ปัญหาเดียว (โดยไม่มีเครื่องย้อนเวลาหรือความรู้อื่น ๆ เกี่ยวกับค่าในอนาคต) คือการชะลอการตัดสินใจใด ๆ จนกว่าจะมีค่าในอนาคตเพียงพอ หากคุณต้องการระดับเหนือค่าเฉลี่ยที่ครอบคลุมเช่น 20 คะแนนคุณจะต้องรอจนกว่าคุณจะมีอย่างน้อย 19 คะแนนก่อนการตัดสินใจสูงสุดหรือมิฉะนั้นจุดใหม่ต่อไปอาจทำให้เกณฑ์ของคุณสมบูรณ์ 19 คะแนนที่ผ่านมา .

พล็อตปัจจุบันของคุณไม่มีจุดสูงสุดใด ๆ ... เว้นแต่คุณจะรู้ล่วงหน้าว่าจุดต่อไปนี้ไม่ใช่ 1e99 ซึ่งหลังจากยกเลิกมิติ Y ในพล็อตของคุณอีกครั้งจะแบนจนจุดนั้น


อย่างที่ฉันพูดไว้ก่อนหน้านี้เราสามารถสันนิษฐานได้ว่าถ้ามีจุดสูงสุดเกิดขึ้นมันมีขนาดใหญ่เท่ากับยอดเขาในภาพและเบี่ยงเบนอย่างมีนัยสำคัญจากค่า 'ปกติ'
Jean-Paul

หากคุณรู้ว่ายอดเขาจะมีขนาดใหญ่ล่วงหน้าให้ตั้งค่าเฉลี่ยและ / หรือเกณฑ์ของคุณไว้ล่วงหน้าเพื่อให้อยู่ภายใต้ค่านั้น
hotpaw2

1
และนั่นคือสิ่งที่ฉันไม่รู้ล่วงหน้า
Jean-Paul

1
คุณเพิ่งโต้แย้งตัวเองและเขียนว่ายอดเขาเป็นขนาดในภาพ ไม่ว่าคุณจะรู้หรือไม่
hotpaw2

2
ฉันพยายามอธิบายให้คุณฟัง คุณเข้าใจแล้วใช่มั้ย 'วิธีระบุยอดเขาขนาดใหญ่อย่างมีนัยสำคัญ' คุณสามารถเข้าถึงปัญหาอย่างสถิติหรือด้วยอัลกอริทึมสมาร์ท ด้วย.. As large as in the pictureฉันหมายถึง: สำหรับสถานการณ์ที่คล้ายกันซึ่งมียอดเขาสูงและเสียงรบกวนพื้นฐาน
Jean-Paul

2

และนี่คือการติดตั้ง PHPของ ZSCORE algo:

<?php
$y = array(1,7,1.1,1,0.9,1,1,1.1,1,0.9,1,1.1,1,1,0.9,1,1,1.1,1,1,1,1,1.1,0.9,1,1.1,1,1,0.9,
       1,1.1,1,1,1.1,1,0.8,0.9,1,1.2,0.9,1,1,1.1,1.2,1,1.5,10,3,2,5,3,2,1,1,1,0.9,1,1,3,
       2.6,4,3,3.2,2,1,1,0.8,4,4,2,2.5,1,1,1);

function mean($data, $start, $len) {
    $avg = 0;
    for ($i = $start; $i < $start+ $len; $i ++)
        $avg += $data[$i];
    return $avg / $len;
}

function stddev($data, $start,$len) {
    $mean = mean($data,$start,$len);
    $dev = 0;
    for ($i = $start; $i < $start+$len; $i++) 
        $dev += (($data[$i] - $mean) * ($data[$i] - $mean));
    return sqrt($dev / $len);
}

function zscore($data, $len, $lag= 20, $threshold = 1, $influence = 1) {

    $signals = array();
    $avgFilter = array();
    $stdFilter = array();
    $filteredY = array();
    $avgFilter[$lag - 1] = mean($data, 0, $lag);
    $stdFilter[$lag - 1] = stddev($data, 0, $lag);

    for ($i = 0; $i < $len; $i++) {
        $filteredY[$i] = $data[$i];
        $signals[$i] = 0;
    }


    for ($i=$lag; $i < $len; $i++) {
        if (abs($data[$i] - $avgFilter[$i-1]) > $threshold * $stdFilter[$lag - 1]) {
            if ($data[$i] > $avgFilter[$i-1]) {
                $signals[$i] = 1;
            }
            else {
                $signals[$i] = -1;
            }
            $filteredY[$i] = $influence * $data[$i] + (1 - $influence) * $filteredY[$i-1];
        } 
        else {
            $signals[$i] = 0;
            $filteredY[$i] = $data[$i];
        }

        $avgFilter[$i] = mean($filteredY, $i - $lag, $lag);
        $stdFilter[$i] = stddev($filteredY, $i - $lag, $lag);
    }
    return $signals;
}

$sig = zscore($y, count($y));

print_r($y); echo "<br><br>";
print_r($sig); echo "<br><br>";

for ($i = 0; $i < count($y); $i++) echo $i. " " . $y[$i]. " ". $sig[$i]."<br>";

?>

ขอบคุณสำหรับการโพสต์ฉันได้เพิ่มการแปลของคุณในรายการ
Jean-Paul

1
หนึ่งความคิดเห็น: ระบุว่าขั้นตอนวิธีนี้ส่วนใหญ่จะนำไปใช้กับข้อมูลตัวอย่างผมแนะนำให้คุณใช้ค่าเบี่ยงเบนมาตรฐานของกลุ่มตัวอย่างโดยการหารโดย($len - 1)แทน$lenในstddev()
Jean-Paul

1

แทนที่จะเปรียบเทียบค่าสูงสุดกับค่าเฉลี่ยเราสามารถเปรียบเทียบค่าสูงสุดกับค่าต่ำสุดที่อยู่ติดกันโดยค่าต่ำสุดจะถูกกำหนดเหนือเกณฑ์เสียงเท่านั้น หากค่าสูงสุดในพื้นที่คือ> 3 ครั้ง (หรือปัจจัยความเชื่อมั่นอื่น ๆ ) ค่าต่ำสุดที่อยู่ติดกันแสดงว่าค่าสูงสุดเป็นค่าสูงสุด ความมุ่งมั่นสูงสุดนั้นแม่นยำยิ่งขึ้นด้วยหน้าต่างที่กว้างขึ้น ด้านบนใช้การคำนวณที่กึ่งกลางตรงกลางหน้าต่างโดยวิธีแทนที่จะคำนวณที่ท้ายหน้าต่าง (== lag)

โปรดทราบว่าจะต้องมีการเพิ่มสัญญาณสูงสุดก่อนและลดลงหลังจากนั้น


1

ฟังก์ชันscipy.signal.find_peaksตามชื่อแนะนำมีประโยชน์สำหรับสิ่งนี้ แต่สิ่งสำคัญคือต้องเข้าใจพารามิเตอร์widthให้thresholdดีdistance และเหนือสิ่งอื่นใดprominenceเพื่อให้ได้การแยกสูงสุด

ตามการทดสอบและเอกสารของฉันแนวคิดของความโดดเด่นคือ "แนวคิดที่มีประโยชน์" เพื่อรักษายอดเขาที่ดีและทิ้งยอดเขาที่มีเสียงดัง

คืออะไร(ภูมิประเทศ) โดดเด่น ? มันคือ"ความสูงขั้นต่ำที่จำเป็นในการลงมาจากยอดเขาสู่ภูมิประเทศที่สูงกว่า"ดังที่เห็นได้ที่นี่:

ความคิดคือ:

ยิ่งมีความโดดเด่นมากเท่าใดก็ยิ่งมีความสำคัญมากเท่านั้น


1

เวอร์ชันเชิงวัตถุของอัลกอริทึม z-score โดยใช้ mordern C +++

template<typename T>
class FindPeaks{
private:
    std::vector<T> m_input_signal;                      // stores input vector
    std::vector<T> m_array_peak_positive;               
    std::vector<T> m_array_peak_negative;               

public:
    FindPeaks(const std::vector<T>& t_input_signal): m_input_signal{t_input_signal}{ }

    void estimate(){
        int lag{5};
        T threshold{ 5 };                                                                                       // set a threshold
        T influence{ 0.5 };                                                                                    // value between 0 to 1, 1 is normal influence and 0.5 is half the influence

        std::vector<T> filtered_signal(m_input_signal.size(), 0.0);                                             // placeholdered for smooth signal, initialie with all zeros
        std::vector<int> signal(m_input_signal.size(), 0);                                                          // vector that stores where the negative and positive located
        std::vector<T> avg_filtered(m_input_signal.size(), 0.0);                                                // moving averages
        std::vector<T> std_filtered(m_input_signal.size(), 0.0);                                                // moving standard deviation

        avg_filtered[lag] = findMean(m_input_signal.begin(), m_input_signal.begin() + lag);                         // pass the iteartor to vector
        std_filtered[lag] = findStandardDeviation(m_input_signal.begin(), m_input_signal.begin() + lag);

        for (size_t iLag = lag + 1; iLag < m_input_signal.size(); ++iLag) {                                         // start index frm 
            if (std::abs(m_input_signal[iLag] - avg_filtered[iLag - 1]) > threshold * std_filtered[iLag - 1]) {     // check if value is above threhold             
                if ((m_input_signal[iLag]) > avg_filtered[iLag - 1]) {
                    signal[iLag] = 1;                                                                               // assign positive signal
                }
                else {
                    signal[iLag] = -1;                                                                                  // assign negative signal
                }
                filtered_signal[iLag] = influence * m_input_signal[iLag] + (1 - influence) * filtered_signal[iLag - 1];        // exponential smoothing
            }
            else {
                signal[iLag] = 0;                                                                                         // no signal
                filtered_signal[iLag] = m_input_signal[iLag];
            }

            avg_filtered[iLag] = findMean(filtered_signal.begin() + (iLag - lag), filtered_signal.begin() + iLag);
            std_filtered[iLag] = findStandardDeviation(filtered_signal.begin() + (iLag - lag), filtered_signal.begin() + iLag);

        }

        for (size_t iSignal = 0; iSignal < m_input_signal.size(); ++iSignal) {
            if (signal[iSignal] == 1) {
                m_array_peak_positive.emplace_back(m_input_signal[iSignal]);                                        // store the positive peaks
            }
            else if (signal[iSignal] == -1) {
                m_array_peak_negative.emplace_back(m_input_signal[iSignal]);                                         // store the negative peaks
            }
        }
        printVoltagePeaks(signal, m_input_signal);

    }

    std::pair< std::vector<T>, std::vector<T> > get_peaks()
    {
        return std::make_pair(m_array_peak_negative, m_array_peak_negative);
    }

};


template<typename T1, typename T2 >
void printVoltagePeaks(std::vector<T1>& m_signal, std::vector<T2>& m_input_signal) {
    std::ofstream output_file("./voltage_peak.csv");
    std::ostream_iterator<T2> output_iterator_voltage(output_file, ",");
    std::ostream_iterator<T1> output_iterator_signal(output_file, ",");
    std::copy(m_input_signal.begin(), m_input_signal.end(), output_iterator_voltage);
    output_file << "\n";
    std::copy(m_signal.begin(), m_signal.end(), output_iterator_signal);
}

template<typename iterator_type>
typename std::iterator_traits<iterator_type>::value_type findMean(iterator_type it, iterator_type end)
{
    /* function that receives iterator to*/
    typename std::iterator_traits<iterator_type>::value_type sum{ 0.0 };
    int counter = 0;
    while (it != end) {
        sum += *(it++);
        counter++;
    }
    return sum / counter;
}

template<typename iterator_type>
typename std::iterator_traits<iterator_type>::value_type findStandardDeviation(iterator_type it, iterator_type end)
{
    auto mean = findMean(it, end);
    typename std::iterator_traits<iterator_type>::value_type sum_squared_error{ 0.0 };
    int counter{ 0 };
    while (it != end) {
        sum_squared_error += std::pow((*(it++) - mean), 2);
        counter++;
    }
    auto standard_deviation = std::sqrt(sum_squared_error / (counter - 1));
    return standard_deviation;
}

2
การแปลที่ดี มันจะดีกว่าเล็กน้อยถ้าวัตถุยังช่วยประหยัดfiltered_signal, signal, avg_filteredและstd_filteredเป็นตัวแปรส่วนตัวและการปรับปรุงเพียงอาร์เรย์เหล่านั้นครั้งเดียวเมื่อ DataPoint ใหม่มาถึง (ตอนนี้ลูปรหัสมากกว่า datapoints ทุกทุกครั้งที่มันถูกเรียกว่า) นั่นจะช่วยปรับปรุงประสิทธิภาพของรหัสของคุณและเหมาะสมกับโครงสร้าง OOP ให้ดียิ่งขึ้น
Jean-Paul
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.