scleronomic's answer 中建议的卷积方法非常有前途,特别是如果您要寻找两个以上的连续元素。
但是,该答案中提出的实现可能不是最有效的,因为它包含两个步骤:diff(),然后是convolve()。
替代实现
如果我们认为diff()也可以使用卷积计算,我们可以将这两个步骤合并为一个卷积。
下面的替代实现只需要对完整信号进行一次卷积,如果信号有很多元素,这是有利的。
请注意,我们不能取diff 的绝对值(为了防止误报,如this comment 中所述),因此我们向单元内核添加了一些随机噪声。
# example signal
signal = numpy.array([1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0])
# minimum number of consecutive elements
n_consecutive = 3
# convolution kernel for weighted moving sum (with small random component)
rng = numpy.random.default_rng()
random_kernel = 1 + 0.01 * rng.random(n_consecutive - 1)
# convolution kernel for first-order difference (similar to numpy.diff)
diff_kernel = [1, -1]
# combine the kernels so we only need to do one convolution with the signal
combined_kernel = numpy.convolve(diff_kernel, random_kernel, mode='full')
# convolve the signal to get the moving weighted sum of differences
moving_sum_of_diffs = numpy.convolve(signal, combined_kernel, mode='valid')
# check if moving sum is zero anywhere
result = numpy.any(moving_sum_of_diffs == 0)
有关卷积的详细讨论,请参阅DSP guide。
时间
两种实现的区别归结为:
def original(signal, unit_kernel):
return numpy.convolve(numpy.abs(numpy.diff(signal)), unit_kernel, mode='valid')
def alternative(signal, combined_kernel):
return numpy.convolve(signal, combined_kernel, mode='valid')
unit_kernel = numpy.ones(n_consecutive - 1) 和 combined_kernel 在上面定义。
比较这两个函数,使用timeit,表明alternative() 可以快几倍,对于小内核大小(即n_consecutive 的小值)。但是,对于较大的内核大小,优势变得可以忽略不计,因为卷积占主导地位(与 diff 相比)。
注意事项:
- 对于较大的内核,我更喜欢原来的两步法,因为我认为它更容易理解。
- 由于数字问题,可能需要将
numpy.any(moving_sum_of_diffs == 0) 替换为 numpy.any(numpy.abs(moving_sum_of_diffs) < very_small_number),参见例如here。