Phát hiện tín hiệu cực đại trong dữ liệu thời gian thực


242

Cập nhật: Thuật toán hoạt động tốt nhất cho đến nay thuật toán này .


Câu hỏi này khám phá các thuật toán mạnh mẽ để phát hiện các đỉnh đột ngột trong dữ liệu thời gian thực.

Hãy xem xét các tập dữ liệu sau:

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];

(Định dạng Matlab nhưng không phải về ngôn ngữ mà là về thuật toán)

Lô dữ liệu

Bạn có thể thấy rõ rằng có ba đỉnh lớn và một số đỉnh nhỏ. Bộ dữ liệu này là một ví dụ cụ thể về lớp bộ dữ liệu thời gian mà câu hỏi là về. Lớp bộ dữ liệu này có hai tính năng chung:

  1. Có tiếng ồn cơ bản với một trung bình chung
  2. Có những " đỉnh " lớn hoặc " điểm dữ liệu cao hơn " lệch đáng kể so với nhiễu.

Chúng ta cũng giả sử như sau:

  • chiều rộng của các đỉnh không thể được xác định trước
  • chiều cao của các đỉnh rõ ràng và sai lệch đáng kể so với các giá trị khác
  • thuật toán được sử dụng phải tính toán thời gian thực (vì vậy thay đổi theo từng biểu dữ liệu mới)

Đối với tình huống như vậy, một giá trị biên cần được xây dựng để kích hoạt tín hiệu. Tuy nhiên, giá trị biên không thể là tĩnh và phải được xác định thời gian thực dựa trên thuật toán.


Câu hỏi của tôi: một thuật toán tốt để tính các ngưỡng như vậy trong thời gian thực là gì? Có các thuật toán cụ thể cho các tình huống như vậy? Các thuật toán nổi tiếng nhất là gì?


Các thuật toán mạnh mẽ hoặc hiểu biết hữu ích đều được đánh giá cao. (có thể trả lời bằng bất kỳ ngôn ngữ nào: đó là về thuật toán)


5
phải có một số yêu cầu chiều cao tuyệt đối là một đỉnh cao trong Ngoài các yêu cầu bạn đã đưa ra. Mặt khác, đỉnh ở thời điểm 13 nên được coi là một đỉnh. (Tương đương: nếu trong tương lai, các đỉnh đã tăng lên 1000 hoặc hơn, thì hai đỉnh ở 25 và 35 không nên được coi là các đỉnh.)
j_random_hacker 22/03/2016

Tôi đồng ý. Chúng ta hãy giả sử những đỉnh này là những cái chúng ta chỉ cần xem xét.
Jean-Paul

Bạn có thể đang hỏi sai câu hỏi. Thay vì hỏi làm thế nào bạn có thể phát hiện mà không bị trì hoãn, bạn có thể hỏi liệu có thể phát hiện một loại tín hiệu nhất định mà không bị trì hoãn chỉ đưa ra những gì đã biết trước thời điểm đó hay những gì cần biết về tín hiệu để phát hiện điều gì đó với một số tín hiệu đã cho sự chậm trễ.
hotpaw2

2
Tôi đã từng làm điều này để phát hiện sự thay đổi đột ngột của cường độ ánh sáng trên máy quang. Tôi đã làm điều này bằng cách di chuyển trung bình bỏ qua bất kỳ điểm dữ liệu nào lớn hơn ngưỡng. Lưu ý rằng ngưỡng này khác với ngưỡng xác định đỉnh. Vì vậy, giả sử bạn chỉ bao gồm các điểm dữ liệu nằm trong một stddev cho trung bình di chuyển của bạn và coi các điểm dữ liệu đó có nhiều hơn ba stddev là các đỉnh. Thuật toán này đã làm rất tốt cho bối cảnh ứng dụng của chúng ta lúc đó.
vừa rồi

1
Ah tôi thấy. Tôi đã không mong đợi nó ở dạng mã. Nếu tôi đã thấy câu hỏi này sớm hơn có lẽ bạn sẽ nhận được câu trả lời đó nhanh hơn nhiều = D. Dù sao, ứng dụng của tôi lúc đó là phát hiện xem bộ cảm quang có bị cản trở từ nguồn sáng xung quanh hay không (đây là lý do tại sao chúng ta cần trung bình di động, vì nguồn sáng xung quanh có thể thay đổi dần theo thời gian). Chúng tôi đã tạo ra trò chơi này như một trò chơi trong đó bạn nên đưa tay lên các cảm biến theo mô hình cụ thể. = D
cần

Câu trả lời:


334

Thuật toán phát hiện cực đại mạnh mẽ (sử dụng điểm z)

Tôi đã đưa ra một thuật toán hoạt động rất tốt cho các loại bộ dữ liệu này. Nó dựa trên nguyên tắc phân tán : nếu một điểm dữ liệu mới là một số x sai lệch tiêu chuẩn nhất định so với một số trung bình di chuyển, các tín hiệu thuật toán (còn gọi là điểm z ). Thuật toán rất mạnh mẽ vì nó xây dựng một giá trị trung bình và độ lệch di chuyển riêng biệt , sao cho tín hiệu không làm hỏng ngưỡng. Do đó, các tín hiệu trong tương lai được xác định với độ chính xác xấp xỉ như nhau, bất kể lượng tín hiệu trước đó là bao nhiêu. Các thuật toán mất 3 đầu vào: lag = the lag of the moving window, threshold = the z-score at which the algorithm signalsinfluence = the influence (between 0 and 1) of new signals on the mean and standard deviation. Ví dụ, a lagtrong 5 sẽ sử dụng 5 quan sát cuối cùng để làm mịn dữ liệu. Mộtthresholdlà 3,5 sẽ báo hiệu nếu một điểm dữ liệu là 3,5 độ lệch chuẩn so với giá trị trung bình di chuyển. Và influence0,5 cho tín hiệu một nửa ảnh hưởng của các biểu dữ liệu thông thường. Tương tự, influence0 hoàn toàn bỏ qua các tín hiệu để tính toán lại ngưỡng mới. Do đó, ảnh hưởng của 0 là lựa chọn mạnh mẽ nhất (nhưng giả sử ổn định ); đặt tùy chọn ảnh hưởng ở mức 1 là kém mạnh mẽ nhất. Đối với dữ liệu không cố định, do đó, tùy chọn ảnh hưởng nên được đặt ở đâu đó trong khoảng từ 0 đến 1.

Nó hoạt động như sau:

Mã giả

# 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

Quy tắc ngón tay cái để chọn tham số tốt cho dữ liệu của bạn có thể được tìm thấy dưới đây.


Bản giới thiệu

Trình diễn thuật toán ngưỡng mạnh mẽ

Mã Matlab cho bản demo này có thể được tìm thấy ở đây . Để sử dụng bản demo, chỉ cần chạy nó và tự tạo một chuỗi thời gian bằng cách nhấp vào biểu đồ phía trên. Thuật toán bắt đầu làm việc sau khi vẽ lagsố lượng quan sát.


Kết quả

Đối với câu hỏi ban đầu, thuật toán này sẽ đưa ra đầu ra sau khi sử dụng các cài đặt sau lag = 30, threshold = 5, influence = 0::

Ví dụ thuật toán ngưỡng


Triển khai trong các ngôn ngữ lập trình khác nhau:


Quy tắc ngón tay cái để cấu hình thuật toán

lag: tham số độ trễ xác định mức độ dữ liệu của bạn sẽ được làm mịn và mức độ thích ứng của thuật toán với các thay đổi trong mức trung bình dài hạn của dữ liệu. Dữ liệu của bạn càng đứng yên, bạn càng bao gồm độ trễ (điều này sẽ cải thiện tính mạnh mẽ của thuật toán). Nếu dữ liệu của bạn chứa các xu hướng thay đổi theo thời gian, bạn nên xem xét mức độ nhanh chóng bạn muốn thuật toán thích ứng với các xu hướng này. Tức là, nếu bạn đặt lagở mức 10, phải mất 10 'giai đoạn' trước khi ngưỡng của thuật toán được điều chỉnh theo bất kỳ thay đổi hệ thống nào trong trung bình dài hạn. Vì vậy, chọn lagtham số dựa trên hành vi xu hướng của dữ liệu của bạn và mức độ thích ứng mà bạn muốn thuật toán thực hiện.

influence: tham số này xác định ảnh hưởng của tín hiệu đến ngưỡng phát hiện của thuật toán. Nếu đặt ở 0, tín hiệu không có ảnh hưởng đến ngưỡng, sao cho tín hiệu trong tương lai được phát hiện dựa trên ngưỡng được tính toán với độ lệch trung bình và độ lệch chuẩn không bị ảnh hưởng bởi các tín hiệu trong quá khứ. Một cách khác để suy nghĩ về điều này là nếu bạn đặt mức ảnh hưởng về 0, bạn hoàn toàn giả định sự đứng yên (nghĩa là cho dù có bao nhiêu tín hiệu, chuỗi thời gian luôn trở về mức trung bình trong thời gian dài). Nếu đây không phải là trường hợp, bạn nên đặt tham số ảnh hưởng ở đâu đó trong khoảng từ 0 đến 1, tùy thuộc vào mức độ tín hiệu có thể ảnh hưởng một cách có hệ thống xu hướng thay đổi theo thời gian của dữ liệu. Ví dụ, nếu tín hiệu dẫn đến phá vỡ cấu trúc của mức trung bình dài hạn của chuỗi thời gian, tham số ảnh hưởng phải được đặt ở mức cao (gần bằng 1) để ngưỡng có thể điều chỉnh nhanh chóng với những thay đổi này.

threshold: tham số ngưỡng là số độ lệch chuẩn so với giá trị trung bình di chuyển ở trên mà thuật toán sẽ phân loại một điểm dữ liệu mới là tín hiệu. Ví dụ: nếu một điểm dữ liệu mới là độ lệch chuẩn 4.0 trên giá trị trung bình di chuyển và tham số ngưỡng được đặt là 3.5, thuật toán sẽ xác định điểm dữ liệu là tín hiệu. Tham số này nên được đặt dựa trên số lượng tín hiệu bạn mong đợi. Ví dụ: nếu dữ liệu của bạn được phân phối bình thường, ngưỡng (hoặc: z-points) là 3,5 tương ứng với xác suất báo hiệu là 0,00047 (từ bảng này), ngụ ý rằng bạn mong đợi tín hiệu một lần sau mỗi 2128 điểm dữ liệu (1 / 0,00047). Do đó, ngưỡng ảnh hưởng trực tiếp đến mức độ nhạy cảm của thuật toán và do đó tần suất tín hiệu của thuật toán. Kiểm tra dữ liệu của riêng bạn và xác định ngưỡng hợp lý để tạo tín hiệu thuật toán khi bạn muốn (một số lỗi và thử có thể cần ở đây để đạt ngưỡng tốt cho mục đích của bạn).


CẢNH BÁO: Đoạn mã trên luôn lặp trên tất cả các điểm dữ liệu mỗi khi nó chạy. Khi thực hiện mã này, đảm bảo tách tính toán của tín hiệu thành một hàm riêng (không có vòng lặp). Sau đó, khi một datapoint mới đến, cập nhật filteredY, avgFilterstdFiltermột lần. Không tính toán lại các tín hiệu cho tất cả dữ liệu mỗi khi có một biểu dữ liệu mới (như trong ví dụ trên), điều đó sẽ cực kỳ không hiệu quả và chậm!

Các cách khác để sửa đổi thuật toán (để cải thiện tiềm năng) là:

  1. Sử dụng trung bình thay vì trung bình
  2. Sử dụng thước đo mạnh mẽ của thang đo , như MAD, thay vì độ lệch chuẩn
  3. Sử dụng lề báo hiệu, do đó tín hiệu không chuyển đổi quá thường xuyên
  4. Thay đổi cách tham số ảnh hưởng hoạt động
  5. Điều trị tín hiệu lênxuống khác nhau (điều trị không đối xứng)
  6. Tạo một influencetham số riêng cho giá trị trung bình và tiêu chuẩn ( như được thực hiện trong bản dịch Swift này )

(Được biết) trích dẫn học thuật cho câu trả lời StackOverflow này:

Công việc khác sử dụng thuật toán

Các ứng dụng khác của thuật toán này

Liên kết đến các thuật toán phát hiện đỉnh khác


Nếu bạn sử dụng chức năng này ở đâu đó, xin vui lòng ghi có cho tôi hoặc câu trả lời này. Nếu bạn có bất kỳ câu hỏi nào liên quan đến thuật toán này, hãy đăng chúng trong các bình luận bên dưới hoặc liên hệ với tôi trên LinkedIn .



Liên kết đến movestd bị hỏng, nhưng bạn có thể tìm thấy mô tả về nó ở đây
Phylliida

@reasra Hóa ra chức năng không cần độ lệch chuẩn di chuyển sau khi viết lại. Hiện tại nó có thể được sử dụng với các hàm Matlab tích hợp đơn giản :)
Jean-Paul

1
Tôi đang thử mã Matlab cho một số dữ liệu gia tốc, nhưng vì một số lý do, thresholdbiểu đồ chỉ trở thành một đường màu xanh lá cây phẳng sau khi tăng đột biến lên đến 20 trong dữ liệu và nó vẫn như vậy đối với phần còn lại của biểu đồ ... Nếu Tôi loại bỏ sike, điều này không xảy ra, vì vậy nó dường như được gây ra bởi sự tăng đột biến trong dữ liệu. Bất cứ ý tưởng những gì có thể xảy ra? Tôi là người mới chơi Matlab, vì vậy tôi không thể hiểu được ...
Magnus W

@BadCash Bạn có thể cung cấp một ví dụ (với dữ liệu) không? Có lẽ đặt câu hỏi của riêng bạn ở đây trên SO và cho tôi biết liên kết?
Jean-Paul

2
Có nhiều cách để cải thiện thuật toán này, vì vậy hãy sáng tạo (cách xử lý khác nhau lên / xuống; trung bình thay vì trung bình; std mạnh mẽ; viết mã dưới dạng chức năng hiệu quả bộ nhớ; ngưỡng ngưỡng để tín hiệu không chuyển đổi quá thường xuyên, v.v. .).
Jean-Paul

41

Dưới đây là Python/ numpythực hiện thuật toán z-points được làm mịn (xem câu trả lời ở trên ). Bạn có thể tìm thấy ý chính ở đây .

#!/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))

Dưới đây là thử nghiệm trên cùng một tập dữ liệu mang lại âm mưu giống như trong câu trả lời ban đầu cho 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 'y' thực sự là tín hiệu và 'tín hiệu' là tập hợp các điểm dữ liệu, tôi có hiểu đúng không?
TheTank

1
@TheTank ylà mảng dữ liệu bạn truyền vào, signalslà mảng +1hoặc -1đầu ra chỉ ra cho mỗi datapoint y[i]cho dù datapoint đó là "đỉnh đáng kể" cho các cài đặt bạn sử dụng.
Jean-Paul

23

Một cách tiếp cận là phát hiện các đỉnh dựa trên quan sát sau:

  • Thời gian t là cực đại nếu (y (t)> y (t-1)) && (y (t)> y (t + 1))

Nó tránh được dương tính giả bằng cách đợi cho đến khi xu hướng tăng kết thúc. Nó không chính xác là "thời gian thực" theo nghĩa là nó sẽ bỏ lỡ đỉnh một dt. độ nhạy có thể được kiểm soát bằng cách yêu cầu một lề để so sánh. Có một sự đánh đổi giữa phát hiện ồn ào và thời gian phát hiện trễ. Bạn có thể làm phong phú mô hình bằng cách thêm nhiều tham số:

  • đỉnh nếu (y (t) - y (t-dt)> m) && (y (t) - y (t + dt)> m)

trong đó dtm là các tham số để kiểm soát độ nhạy và thời gian trễ

Đây là những gì bạn nhận được với thuật toán được đề cập: nhập mô tả hình ảnh ở đây

Đây là đoạn mã để tái tạo cốt truyện trong python:

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()

Bằng cách cài đặt m = 0.5, bạn có thể nhận được tín hiệu sạch hơn chỉ với một dương tính giả: nhập mô tả hình ảnh ở đây


Sớm hơn = tốt hơn vì vậy tất cả các đỉnh là đáng kể. Cảm ơn! Rất tuyệt!
Jean-Paul

Làm thế nào tôi sẽ đi về việc thay đổi độ nhạy cảm?
Jean-Paul

Tôi có thể nghĩ về hai cách tiếp cận: 1: đặt m thành giá trị lớn hơn để chỉ phát hiện các đỉnh lớn hơn. 2: thay vì tính y (t) - y (t-dt) (và y (t) - y (t + dt)), bạn tích hợp từ t-dt đến t (và t đến t + dt).
aha

2
Theo tiêu chí nào bạn đang từ chối 7 đỉnh khác?
hotpaw2

4
Có một vấn đề với các đỉnh phẳng, vì những gì bạn làm chủ yếu là phát hiện cạnh 1-D (như làm mất tín hiệu với [1 0 -1])
ben

18

Trong xử lý tín hiệu, phát hiện cực đại thường được thực hiện thông qua biến đổi wavelet. Về cơ bản, bạn thực hiện một biến đổi wavelet rời rạc trên dữ liệu chuỗi thời gian của bạn. Không giao nhau trong các hệ số chi tiết được trả về sẽ tương ứng với các đỉnh trong tín hiệu chuỗi thời gian. Bạn nhận được biên độ đỉnh khác nhau được phát hiện ở các mức hệ số chi tiết khác nhau, mang lại cho bạn độ phân giải đa cấp.


1
Câu trả lời của bạn cho tôi bài viết nàycâu trả lời này sẽ giúp tôi xây dựng một thuật toán tốt để thực hiện. Cảm ơn!
Jean-Paul

@cklin Bạn có thể giải thích cách bạn tính toán các điểm giao nhau của các sóng con không, vì chúng không cùng thang thời gian với chuỗi thời gian ban đầu. Bất kỳ ref về cách sử dụng này?
horaceT

11

Chúng tôi đã cố gắng sử dụng thuật toán điểm z được làm mịn trên tập dữ liệu của chúng tôi, kết quả là quá nhạy hoặc quá nhạy (tùy thuộc vào cách các tham số được điều chỉnh), với ít điểm trung bình. Trong tín hiệu lưu lượng truy cập trang web của chúng tôi, chúng tôi đã quan sát đường cơ sở tần số thấp đại diện cho chu kỳ hàng ngày và ngay cả với các thông số tốt nhất có thể (hiển thị bên dưới), nó vẫn bị tắt đặc biệt là vào ngày thứ 4 vì hầu hết các điểm dữ liệu được nhận dạng là bất thường .

Dựa trên thuật toán điểm z ban đầu, chúng tôi đã đưa ra một cách để giải quyết vấn đề này bằng cách lọc ngược lại. Các chi tiết của thuật toán sửa đổi và ứng dụng của nó trên thuộc tính thương mại truyền hình được đăng trên blog của nhóm chúng tôi .

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


Thật tuyệt khi thấy rằng thuật toán là điểm khởi đầu cho phiên bản nâng cao hơn của bạn. Dữ liệu của bạn có một mẫu rất đặc biệt, do đó, thực sự sẽ có ý nghĩa hơn khi trước tiên loại bỏ mẫu bằng cách sử dụng một số kỹ thuật khác và sau đó áp dụng thuật toán trên phần dư. Ngoài ra, bạn có thể muốn sử dụng một trung tâm thay vì cửa sổ trễ để tính trung bình / st.dev. Một nhận xét khác: giải pháp của bạn di chuyển từ phải sang trái để xác định các đột biến, nhưng điều này là không thể trong các ứng dụng thời gian thực (đó là lý do tại sao thuật toán gốc rất đơn giản, vì thông tin trong tương lai không thể truy cập được).
Jean-Paul

10

Trong cấu trúc liên kết tính toán, ý tưởng về tương đồng liên tục dẫn đến một giải pháp hiệu quả - nhanh như sắp xếp số - giải pháp. Nó không chỉ phát hiện các đỉnh, nó định lượng "tầm quan trọng" của các đỉnh theo cách tự nhiên cho phép bạn chọn các đỉnh có ý nghĩa đối với bạn.

Tóm tắt thuật toán. Trong cài đặt 1 chiều (chuỗi thời gian, tín hiệu có giá trị thực), thuật toán có thể được mô tả dễ dàng bằng hình sau:

Đỉnh cao nhất

Hãy nghĩ về biểu đồ chức năng (hoặc tập hợp cấp dưới của nó) như một cảnh quan và xem xét mức nước giảm bắt đầu từ mức vô cực (hoặc 1,8 trong hình này). Trong khi mức độ giảm, tại các đảo cực đại địa phương bật lên. Ở cực tiểu địa phương những hòn đảo này hợp nhất với nhau. Một chi tiết trong ý tưởng này là hòn đảo xuất hiện sau đó được sáp nhập vào hòn đảo cũ hơn. "Sự bền bỉ" của một hòn đảo là thời gian sinh của nó trừ đi thời gian chết. Độ dài của các thanh màu xanh mô tả sự bền bỉ, đó là "ý nghĩa" được đề cập ở trên của một đỉnh.

Hiệu quả. Không quá khó để tìm thấy một triển khai chạy trong thời gian tuyến tính - thực tế nó là một vòng lặp đơn, đơn giản - sau khi các giá trị hàm được sắp xếp. Vì vậy, việc thực hiện này phải nhanh chóng trong thực tế và cũng dễ dàng thực hiện.

Người giới thiệu. Có thể tìm thấy một bài viết của toàn bộ câu chuyện và đề cập đến động lực từ tương đồng liên tục (một lĩnh vực trong cấu trúc liên kết đại số tính toán) tại đây: https://www.sthu.org/blog/13-perstopology-peakdetection/index.html


Thuật toán này nhanh hơn và chính xác hơn nhiều, ví dụ, scipy.signal.find_peaks. Đối với chuỗi thời gian "thực" với 1053896 điểm dữ liệu, nó đã phát hiện 137516 đỉnh (13%). Thứ tự của các đỉnh (quan trọng nhất trước tiên) cho phép các đỉnh đáng kể nhất được trích xuất. Nó cung cấp điểm bắt đầu, đỉnh và điểm cuối của mỗi đỉnh. Hoạt động tốt với dữ liệu ồn ào.
vinh

Theo dữ liệu thời gian thực, bạn có nghĩa là một thuật toán được gọi là trực tuyến, trong đó các điểm dữ liệu được nhận theo thời gian. Tầm quan trọng của một đỉnh có thể được xác định bởi các giá trị trong tương lai. Sẽ thật tốt khi mở rộng thuật toán để trở thành trực tuyến bằng cách sửa đổi các kết quả trong quá khứ mà không phải hy sinh sự phức tạp thời gian quá nhiều.
S. Huber

9

Tìm thấy một thuật toán khác của GH Palshikar trong Thuật toán đơn giản để phát hiện cực đại trong chuỗi thời gian .

Thuật toán diễn ra như sau:

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

Ưu điểm

  • Bài viết cung cấp 5 thuật toán khác nhau để phát hiện cực đại
  • Các thuật toán hoạt động trên dữ liệu chuỗi thời gian thô (không cần làm mịn)

Nhược điểm

  • Khó xác định kvà làm htrước
  • Các đỉnh không thể bằng phẳng (như đỉnh thứ ba trong dữ liệu thử nghiệm của tôi)

Thí dụ:

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


Thực sự giấy thú vị. S4 có vẻ như là một chức năng tốt hơn để sử dụng theo ý kiến ​​của mình. Nhưng quan trọng hơn là làm rõ khi k <i <Nk không đúng. Làm thế nào một người định nghĩa hàm S1 (S2, ..) cho i = 0 tôi chỉ đơn giản là không chia cho 2 và bỏ qua toán hạng đầu tiên và với mỗi khác tôi bao gồm cả hai toán hạng nhưng đối với i <= k thì có ít toán hạng hơn ở bên trái sau đó ở bên phải
daniels_pa

8

Dưới đây là một triển khai của thuật toán điểm số Smoothed (ở trên) trong Golang. Nó giả định một lát []int16(mẫu PCM 16 bit). Bạn có thể tìm thấy một ý chính ở đây .

/*
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 Tôi không hoàn toàn chắc chắn mọi thứ đều đúng, vì vậy có thể có lỗi.
Xeoncross

1
Bạn đã thử sao chép đầu ra ví dụ demo từ Matlab / R chưa? Đó phải là một xác nhận tốt về chất lượng.
Jean-Paul

7

Dưới đây là một triển khai C ++ của thuật toán z-points được làm mịn từ câu trả lời này

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
Hãy cẩn thận: Việc thực hiện này không thực sự cung cấp một phương pháp để tính toán độ lệch trung bình và độ lệch chuẩn. Đối với C ++ 11, một phương pháp dễ dàng có thể được tìm thấy ở đây: stackoverflow.com/a/12405793/3250829
rayryeng

6

Vấn đề này trông giống như tôi gặp phải trong một khóa học hệ thống lai / nhúng, nhưng điều đó có liên quan đến việc phát hiện lỗi khi đầu vào từ cảm biến bị nhiễu. Chúng tôi đã sử dụng bộ lọc Kalman để ước tính / dự đoán trạng thái ẩn của hệ thống, sau đó sử dụng phân tích thống kê để xác định khả năng xảy ra lỗi . Chúng tôi đã làm việc với các hệ thống tuyến tính, nhưng các biến thể phi tuyến tồn tại. Tôi nhớ cách tiếp cận thích nghi đáng ngạc nhiên, nhưng nó đòi hỏi một mô hình động lực học của hệ thống.


Bộ lọc Kalman rất thú vị, nhưng dường như tôi không thể tìm thấy một thuật toán áp dụng cho mục đích của mình. Tôi đánh giá rất cao câu trả lời và tôi sẽ xem xét một số tài liệu phát hiện cao điểm như bài này để xem liệu tôi có thể học được từ bất kỳ thuật toán nào không. Cảm ơn!
Jean-Paul

6

Thực hiện 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

Theo dõi từ giải pháp đề xuất của @ Jean-Paul, tôi đã triển khai thuật toán của anh ấy trong 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;
    }
}

Ví dụ sử dụng:

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
Này @ Jean-Paul. Chúc mừng. Có, tôi đã kiểm tra đầu ra so với phiên bản R của bạn để đảm bảo nó phù hợp. Cảm ơn một lần nữa cho giải pháp của bạn cho vấn đề này.
Ocean Airdrop

Xin chào, tôi nghĩ rằng có một lỗi trong mã đó, trong phương thức StdDev bạn lấy giá trị.Count () - 1, có nên dựa vào -1 không? Tôi nghĩ bạn sẽ muốn số lượng vật phẩm và đó là những gì bạn nhận được từ các giá trị.Count ().
Viktor

1
Hmm .. Điểm tốt. Mặc dù ban đầu tôi đã chuyển thuật toán sang C #, nhưng tôi chưa bao giờ kết thúc việc sử dụng nó. Tôi có thể sẽ thay thế toàn bộ chức năng đó bằng một cuộc gọi đến thư viện nuget MathNet. "Cài đặt-Gói MathNet.Numerics" Nó có các chức năng dựng sẵn cho PeopleSt ChuẩnDeviation () và StandardDeviation (); ví dụ. var Dân sốStdDev = Danh sách mới <double> (1,2,3,4) .PopulationSt ChuẩnDeviation (); var sampleStdDev = Danh sách mới <double> (1,2,3,4) .St ChuẩnDeviation ();
Ocean Airdrop

6

Đây là một triển khai C của điểm số Smoothed Z của @ Jean-Paul cho bộ vi điều khiển Arduino được sử dụng để đọc chỉ số gia tốc và quyết định xem hướng của tác động đến từ bên trái hay bên phải. Điều này thực hiện rất tốt vì thiết bị này trả về tín hiệu bị trả lại. Đây là đầu vào cho thuật toán phát hiện cực đại này từ thiết bị - hiển thị tác động từ bên phải theo sau và tác động từ bên trái. Bạn có thể thấy sự tăng đột biến ban đầu sau đó là sự dao động của cảm biến.

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

#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;
}

Ghét kết quả có ảnh hưởng = 0

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

Không tuyệt vời nhưng ở đây có ảnh hưởng = 1

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

đó là rất tốt


5

Đây là một triển khai Java thực tế dựa trên câu trả lời Groovy được đăng trước đó. (Tôi biết đã có các triển khai Groovy và Kotlin được đăng, nhưng đối với một người như tôi chỉ thực hiện Java, thật rắc rối khi tìm ra cách chuyển đổi giữa các ngôn ngữ khác và Java).

(Kết quả khớp với biểu đồ của người khác)

Thuật toán thực hiện

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
}

Phương pháp chính

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));
            }
        }
    }
}

Các kết quả

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

Đồ thị hiển thị dữ liệu và kết quả thực hiện java


5

Phụ lục 1 cho câu trả lời gốc: MatlabRbản dịch

Mã 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

Thí dụ:

% 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]);

Mã 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))
}

Thí dụ:

# 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)

Mã này (cả hai ngôn ngữ) sẽ mang lại kết quả sau cho dữ liệu của câu hỏi ban đầu:

Ví dụ ngưỡng từ mã Matlab


Phụ lục 2 cho câu trả lời ban đầu: Matlabmã trình diễn

(bấm vào để tạo dữ liệu)

Bản demo 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

Đây là nỗ lực của tôi trong việc tạo ra một giải pháp Ruby cho "thuật toán số z được làm mịn" từ câu trả lời được chấp nhận:

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

Và ví dụ sử dụng:

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
# ]

Tuyệt vời, cảm ơn vì đã chia sẻ! Tôi sẽ thêm bạn vào danh sách. Đảm bảo rằng đối với các ứng dụng thời gian thực, bạn tạo một chức năng riêng để cập nhật các tín hiệu khi có một điểm dữ liệu mới (thay vì lặp tất cả các điểm dữ liệu mọi lúc).
Jean-Paul

4

Một phiên bản lặp lại trong python / numpy để trả lời https://stackoverflow.com/a/22640362/6029703 có ở đây. Mã này nhanh hơn trung bình tính toán và độ lệch chuẩn mỗi độ trễ cho dữ liệu lớn (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

Nghĩ rằng tôi sẽ cung cấp cho Julia việc thực hiện thuật toán cho người khác. Ý chính có thể được tìm thấy ở đây

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)

Các kết quả


3

Dưới đây là một triển khai Groovy (Java) của thuật toán z-points được làm mịn ( xem câu trả lời ở trên ).

/**
 * "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
    ]
}

Dưới đây là một thử nghiệm trên cùng một bộ dữ liệu mang lại kết quả tương tự như việc triển khai Python / numpy ở trên .

    // 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

Dưới đây là phiên bản Scala (không phải thành ngữ) của thuật toán z-points được làm mịn :

/**
  * 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
}

Đây là một thử nghiệm trả về kết quả giống như các phiên bản Python và 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)

biểu đồ vegas của kết quả

Gist ở đây


1 đại diện cho các đỉnh, -1 đại diện cho các thung lũng.
Mike Roberts

3

Tôi cần một cái gì đó như thế này trong dự án Android của tôi. Nghĩ rằng tôi có thể trả lại việc thực hiện 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)
}

dự án mẫu với các biểu đồ xác minh có thể được tìm thấy tại github .

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


Tuyệt vời! Cám ơn vì đã chia sẻ. Đối với các ứng dụng thời gian thực, hãy đảm bảo tạo một hàm riêng để tính toán tín hiệu mới với mỗi biểu dữ liệu đến. Đừng lặp lại toàn bộ dữ liệu mỗi khi dữ liệu mới đến, điều đó sẽ cực kỳ kém hiệu quả :)
Jean-Paul

1
Điểm hay, không nghĩ về điều đó, bởi vì các cửa sổ tôi sử dụng không trùng nhau.
leonardkraemer

3

Đây là một phiên bản Fortran đã thay đổi của thuật toán z-points . Nó được thay đổi đặc biệt để phát hiện cực đại (cộng hưởng) trong các hàm truyền trong không gian tần số (Mỗi thay đổi có một nhận xét nhỏ trong mã).

Sửa đổi đầu tiên đưa ra cảnh báo cho người dùng nếu có cộng hưởng gần giới hạn dưới của vectơ đầu vào, được biểu thị bằng độ lệch chuẩn cao hơn ngưỡng nhất định (trong trường hợp này là 10%). Điều này đơn giản có nghĩa là tín hiệu không đủ phẳng để phát hiện khởi tạo bộ lọc đúng cách.

Điều chỉnh thứ hai là chỉ có giá trị cao nhất của một đỉnh được thêm vào các đỉnh tìm thấy. Điều này đạt được bằng cách so sánh từng giá trị đỉnh tìm thấy với cường độ của các bậc tiền bối (độ trễ) và các độ kế thừa (độ trễ) của nó.

Thay đổi thứ ba là tôn trọng các đỉnh cộng hưởng thường hiển thị một số dạng đối xứng xung quanh tần số cộng hưởng. Vì vậy, việc tính giá trị trung bình và tiêu chuẩn đối xứng xung quanh điểm dữ liệu hiện tại là điều tự nhiên (thay vì chỉ dành cho người tiền nhiệm). Điều này dẫn đến một hành vi phát hiện đỉnh tốt hơn.

Các sửa đổi có tác dụng là phải biết toàn bộ tín hiệu trước chức năng, đây là trường hợp thông thường để phát hiện cộng hưởng (ví dụ như Ví dụ Matlab của Jean-Paul nơi các điểm dữ liệu được tạo khi đang bay sẽ không hoạt động).

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

Đối với ứng dụng của tôi, thuật toán hoạt động như một nét duyên dáng! nhập mô tả hình ảnh ở đây


3

Nếu bạn đã có dữ liệu của mình trong bảng cơ sở dữ liệu, đây là phiên bản SQL của thuật toán điểm z đơn giản:

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)

Mã của bạn làm một cái gì đó khác với thuật toán tôi đã đề xuất. Truy vấn của bạn chỉ đơn giản là tính điểm z ([điểm dữ liệu - trung bình] / std), nhưng không kết hợp logic của thuật toán của tôi mà bỏ qua các tín hiệu trong quá khứ khi tính các ngưỡng tín hiệu mới. Bạn cũng bỏ qua ba tham số (độ trễ, ảnh hưởng, ngưỡng). Bạn có thể sửa đổi câu trả lời của bạn để kết hợp logic thực tế?
Jean-Paul

1
Vâng, bạn đúng. Lúc đầu, tôi nghĩ rằng tôi có thể thoát khỏi phiên bản đơn giản hóa ở trên .. Tôi đã lấy giải pháp đầy đủ của bạn và chuyển nó sang C #. Xem câu trả lời của tôi dưới đây. Khi có nhiều thời gian hơn, tôi sẽ truy cập lại phiên bản SQL này và kết hợp thuật toán của bạn. Nhân tiện, cảm ơn bạn cho một câu trả lời tuyệt vời và giải thích trực quan.
Ocean Airdrop

Không có vấn đề và vui mừng thuật toán có thể giúp bạn ra ngoài! Cảm ơn bạn đã gửi C # của bạn, cái đó vẫn còn thiếu. Tôi sẽ thêm nó vào danh sách các bản dịch!
Jean-Paul

3

Phiên bản Python hoạt động với các luồng thời gian thực (không tính toán lại tất cả các điểm dữ liệu khi đến của từng điểm dữ liệu mới). Bạn có thể muốn điều chỉnh những gì hàm lớp trả về - với mục đích của tôi, tôi chỉ cần các tín hiệu.

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]

Cảm ơn bạn đã đăng bài, tôi đã thêm bản dịch của bạn vào danh sách.
Jean-Paul

3

Tôi cho phép mình tạo một phiên bản javascript của nó. Nó có thể hữu ích. Các javascript nên được sao chép trực tiếp mã giả được đưa ra ở trên. Có sẵn dưới dạng gói npm và github repo:

Dịch thuật 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

Cảm ơn bạn đã đăng bản dịch của bạn. Tôi đã thêm mã của bạn vào câu trả lời của bạn để mọi người có thể nhanh chóng nhìn thấy nó. Tôi sẽ thêm bản dịch của bạn vào danh sách.
Jean-Paul

Đến bây giờ, tôi đã chuyển một số thuật toán khác sang javascript. Lần này là từ pyhon số, giúp tôi kiểm soát nhiều hơn và làm việc tốt hơn cho tôi. Cũng được đóng gói trong npm và bạn có thể tìm thêm thông tin về thuật toán từ trường đại học bang washington trên trang jupyter của họ. npmjs.com/package/@joe_six/duarte-watanabe-peak-detection
Dirk Lüsebrink

2

Nếu giá trị biên hoặc các tiêu chí khác phụ thuộc vào các giá trị trong tương lai, thì giải pháp duy nhất (không có cỗ máy thời gian hoặc kiến ​​thức khác về các giá trị trong tương lai) là trì hoãn bất kỳ quyết định nào cho đến khi có đủ giá trị trong tương lai. Nếu bạn muốn một mức trên trung bình kéo dài, ví dụ 20 điểm, thì bạn phải đợi cho đến khi bạn có ít nhất 19 điểm trước bất kỳ quyết định cao điểm nào, nếu không, điểm mới tiếp theo có thể hoàn toàn vượt qua ngưỡng 19 điểm của bạn trước đây .

Cốt truyện hiện tại của bạn không có bất kỳ đỉnh nào ... trừ khi bạn biết trước rằng điểm tiếp theo không phải là 1e99, sau khi thay đổi kích thước chiều Y của âm mưu của bạn, sẽ bị xẹp cho đến thời điểm đó.


Giống như tôi đã nói trước đây, chúng ta có thể giả sử rằng NẾU một đỉnh xảy ra, nó lớn bằng các đỉnh trong ảnh và sai lệch đáng kể so với các giá trị 'bình thường'.
Jean-Paul

Nếu bạn biết các đỉnh sẽ lớn đến mức nào, thì hãy đặt trước giá trị trung bình và / hoặc ngưỡng của bạn ở ngay dưới giá trị đó.
hotpaw2

1
Và đó chính xác là những gì tôi không biết trước.
Jean-Paul

1
Bạn chỉ mâu thuẫn với chính mình và viết rằng các đỉnh được biết là kích thước trong hình. Hoặc bạn biết điều đó hoặc bạn không.
hotpaw2

2
Tôi đang cố gắng giải thích nó cho bạn. Bạn có ý tưởng bây giờ phải không? "Cách xác định các đỉnh lớn đáng kể". Bạn có thể tiếp cận vấn đề theo thống kê hoặc bằng thuật toán thông minh. Với .. As large as in the pictureý tôi là: đối với các tình huống tương tự khi có các đỉnh đáng kể và nhiễu cơ bản.
Jean-Paul

2

Và đây là cách triển khai PHP của thuật toán ZSCORE:

<?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>";

?>

Cảm ơn bạn đã đăng bài, tôi đã thêm bản dịch của bạn vào danh sách.
Jean-Paul

1
Một nhận xét: cho rằng thuật toán này chủ yếu sẽ được sử dụng trên dữ liệu được lấy mẫu, tôi khuyên bạn nên thực hiện độ lệch chuẩn mẫu bằng cách chia cho ($len - 1)thay vì $lentrongstddev()
Jean-Paul

1

Thay vì so sánh cực đại với giá trị trung bình, người ta cũng có thể so sánh cực đại với cực tiểu liền kề trong đó cực tiểu chỉ được xác định trên ngưỡng nhiễu. Nếu cực đại cục bộ> 3 lần (hoặc hệ số tin cậy khác) hoặc cực tiểu liền kề, thì cực đại đó là cực đại. Xác định đỉnh là chính xác hơn với các cửa sổ di chuyển rộng hơn. Bằng cách này, sử dụng một phép tính tập trung ở giữa cửa sổ, nhân tiện, thay vì một phép tính ở cuối cửa sổ (== lag).

Lưu ý rằng cực đại phải được xem là tăng tín hiệu trước và giảm sau.


1

Hàm scipy.signal.find_peaks, như tên của nó cho thấy, rất hữu ích cho việc này. Nhưng điều quan trọng là phải hiểu tốt các thông số của nó width, threshold, distance và trên tất cảprominence để có được một khai thác đỉnh tốt.

Theo các thử nghiệm của tôi và tài liệu, khái niệm về sự nổi bật là "khái niệm hữu ích" để giữ các đỉnh tốt và loại bỏ các đỉnh ồn ào.

Sự nổi bật (địa hình) là gì? Đó là "chiều cao tối thiểu cần thiết để hạ xuống để đi từ đỉnh lên bất kỳ địa hình cao hơn nào" , như có thể thấy ở đây:

Ý tưởng là:

Độ nổi bật càng cao, đỉnh "càng quan trọng".


1

Phiên bản hướng đối tượng của thuật toán điểm z bằng cách sử dụng hiện đại 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
Bản dịch hay. Nó sẽ là một chút đẹp hơn nếu đối tượng cũng giúp tiết kiệm filtered_signal, signal, avg_filteredstd_filteredbiến chế độ riêng tư và chỉ cập nhật những mảng một lần khi một datapoint mới đến (nay là vòng mã trên tất cả các datapoints Mỗi lần nó được gọi là). Điều đó sẽ cải thiện hiệu suất mã của bạn và phù hợp với cấu trúc OOP hơn nữa.
Jean-Paul
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.