在算法信号中寻找周期性

7

在测试以下递归关系的猜想时

enter image description here ,该猜想声称数字序列具有某种周期性。我编写了一个Python程序来计算这些序列并将它们打印成表格。

 1   # Consider the recursive relation x_{i+1} = p-1 - (p*i-1 mod x_i)
 2   # with p prime and x_0 = 1. What is the shortest period of the
 3   # sequence?
 4   
 5   from __future__ import print_function
 6   import numpy as np
 7   from matplotlib import pyplot  as plt
 8   
 9   # The length of the sequences.
 10  seq_length = 100
 11  
 12  upperbound_primes = 30
 13  
 14  # Computing a list of prime numbers up to n
 15  def primes(n):
 16   sieve = [True] * n
 17   for i in xrange(3,int(n**0.5)+1,2):
 18     if sieve[i]:
 19         sieve[i*i::2*i]=[False]*((n-i*i-1)/(2*i)+1)
 20   return [2] + [i for i in xrange(3,n,2) if sieve[i]]
 21  
 22  # The list of prime numbers up to upperbound_primes
 23  p = primes(upperbound_primes)
 24  
 25  # The amount of primes numbers
 26  no_primes = len(p)
 27  
 28  # Generate the sequence for the prime number p
 29  def sequence(p):
 30    x = np.empty(seq_length)
 31    x[0] = 1
 32    for i in range(1,seq_length):
 33      x[i] = p - 1 - (p * (i-1) - 1) % x[i-1]
 34    return x
 35  
 36  # List with the sequences.
 37  seq = [sequence(i) for i in p]  
 38  """
 39  # Print the sequences in a table where the upper row
 40  # indicates the prime numbers.
 41  for i in range(seq_length):
 42    if not i: 
 43      for n in p:
 44        print('\t',n,end='')
 45      print('')
 46    print(i+1,'\t',end='')
 47    for j in range(no_primes):
 48      print(seq[j][i],end='\t')
 49    print('\n',end='')
 50  """
 51  def autocor(x):
 52    result = np.correlate(x,x,mode='full')
 53    return result[result.size/2:]
 54  
 55  
 56  fig = plt.figure('Finding period in the sequences')
 57  k = 0
 58  for s in  seq:
 59    k = k + 1
 60    fig.add_subplot(no_primes,1,k)
 61    plt.title("Prime number %d" % p[k-1])
 62    plt.plot(autocor(s))
 63  plt.show()
 64  

现在我想调查我计算出来的这些序列中的周期性。在网上找了一下,似乎有两个选择:
  • 对数据进行自相关处理,并寻找第一个峰值。这应该可以给出近似周期。
  • 对数据进行FFT。这将显示数字的频率。我不知道这如何提供关于数字序列周期性的任何有用信息。
最后几行显示了我尝试使用自相关的情况,这是受到How can I use numpy.correlate to do autocorrelation?接受的答案的启发。
它给出了以下绘图结果: enter image description here 显然,我们看到所有质数的数字都是一个递减的序列。
当在以下简化的Python代码片段上测试相同的方法时:
 1   # Testing the autocorrelation of numpy
 2   
 3   import numpy as np
 4   from matplotlib import pyplot as plt
 5   
 6   num_samples = 1000
 7   t = np.arange(num_samples)
 8   dt = 0.1
 9   
 10  def autocor(x):
 11    result = np.correlate(x,x,mode='full')
 12    return result[result.size/2:]
 13  
 14  def f(x):
 15    return [np.sin(i * 2 * np.pi * dt) for i in range(num_samples)]
 16  
 17  plt.plot(autocor(f(t)))
 18  plt.show()

我得到了类似的结果,对于正弦函数,它给出以下绘图
如何从正弦函数情况中读取周期性呢?
无论如何,我不理解自相关的机制是如何导致峰值,从而提供信号周期性信息的。有人可以详细说明一下吗?在此背景下如何正确使用自相关性?
此外,在自相关实现中我做错了什么?
欢迎提出确定数列周期性的替代方法建议。
2个回答

4
这里有很多问题,所以我会从“3”的情况开始描述自相关如何产生周期,即您第一张图片的第二个子图。

对于质数3,序列是(经过不太一致的起步)1,2,1,2,1,2,1,2,...。为了计算自相关,数组基本上相对于自身进行翻译,所有对齐的元素都会相乘,然后将所有这些结果相加。因此,对于一些测试用例,它看起来像这样,其中A是自相关:
 0  1  2  3  4  5  6  7 ... 43 44 45 46 47 48 49         # indices 0    
 1  2  1  2  1  2  1  2      2  1  2  1  2  1  2         # values  0
 1  2  1  2  1  2  1  2      2  1  2  1  2  1  2         # values  1
 0  1  2  3  4  5  6  7 ... 43 44 45 46 47 48 49         # indices 1
 1  4  1  4  1  4  1  4      4  1  4  1  4  1  4         # products
 # above result A[0] = 5*25  5=1+4   25=# of pairs       # A[0] = 125


 0  1  2  3  4  5  6  7 ... 43 44 45 46 47 48 49         # indices 0    
 1  2  1  2  1  2  1  2      2  1  2  1  2  1  2         # values  0
    1  2  1  2  1  2  1  2      2  1  2  1  2  1  2         # values  1
    0  1  2  3  4  5  6  7 ... 43 44 45 46 47 48 49         # indices 1
    2  2  2  2  2  2  2      2  2  2  2  2  2  2         # products
 # above result A[1] = 4*24  4=2+2   24=# of pairs       # A[1] = 96

 0  1  2  3  4  5  6  7 ... 43 44 45 46 47 48 49         # indices 0    
 1  2  1  2  1  2  1  2      2  1  2  1  2  1  2         # values  0
       1  2  1  2  1  2  1  2      2  1  2  1  2  1  2         # values  1
       0  1  2  3  4  5  6  7 ... 43 44 45 46 47 48 49         # indices 1
       1  4  1  4  1  4  1  4      4  1  4  1  4         # products
 # above result A[2] = 5*23  5=4+1   23=# of pairs       # A[2] = 115

以上内容有三个要点:1.自相关,A,在相同元素排列和相乘时(每隔一个步骤),具有较大的值。 2.自相关的索引对应于相对位移。 3.当在完整数组上执行自相关时,如此所示,总是存在向下的斜坡,因为每个连续移位时添加在一起的点数会减少。

因此,您可以看到为什么“质数3”图表中有20%的周期性峰值:因为当它们对齐时,被加总的项是1 + 4,而不是2 + 2,即5与4相比。这就是您在读取周期时要查找的峰值。也就是说,在此处显示周期为2,因为这是您第一个峰值的索引。(另外,请注意,在上面,我只计算了成对数量,以了解这种已知周期性如何导致您在自相关中看到的结果,也就是说,通常不想考虑成对数量。)

在这些计算中,如果您在执行自相关之前先减去平均值,则相对于基线的颠簸值将增加。如果您使用修剪过端点的数组进行计算,则可以消除斜坡,因此始终存在相同的重叠;通常这是有意义的,因为通常人们寻找的周期远比完整样本短(因为需要多个振荡来定义良好的振荡周期)。


对于正弦波的自相关,基本答案是第一个峰值显示出周期。我重新绘制了图表,只是应用时间轴。在这些事情中,使用真实时间轴始终是最清晰的,因此我稍微更改了您的代码以包括它。(此外,我用适当的矢量化numpy表达式替换了列表理解,以计算正弦波,但这在这里并不重要。而且,我还明确定义了f(x)中的频率,只是为了更清楚地说明正在发生的事情-作为隐含频率1的混乱。)

主要观点是,由于自相关是通过沿着轴每次移动一个点来计算的,因此自相关的轴就是时间轴。因此,我将其绘制为轴,然后可以从中读取周期。在这里,我放大以清楚地看到它(下面是代码):

enter image description here

# Testing the autocorrelation of numpy

import numpy as np
from matplotlib import pyplot as plt

num_samples = 1000
dt = 0.1    
t = dt*np.arange(num_samples)   

def autocor(x):
  result = np.correlate(x,x,mode='full')
  return result[result.size/2:]

def f(freq):
  return np.sin(2*np.pi*freq*t)    

plt.plot(t, autocor(f(.3)))
plt.xlabel("time (sec)")
plt.show()                                              

也就是说,在上面的例子中,我将频率设置为0.3,图表显示周期约为3.3,这正是我们所期望的。


总的来说,在我的经验中,自相关通常适用于物理信号,但对算法信号的可靠性则不那么高。例如,如果一个周期性信号跳过一个步骤(这在算法中可能会发生),很容易导致错误。你可能认为计算算法信号周期应该很简单,但查找一下会发现并非如此,甚至很难定义“周期”是什么意思。例如以下序列:

1 2 1 2 1 2 0 1 2 1 2 1 2

不适用于自相关检验。


如果我们在自相关之前先减去平均值,那么相对于基础的颠簸值将如何增加?此外,使用修剪末端的数组是什么意思? - easytarget
  1. 关于re bump和mean subtraction:只需看数字。例如,1 2 1 2 1 2将4与5进行比较(如上所述);而0 1 0 1 0 1将0与1进行比较;101、102、101等将20604与20605进行比较。一般来说,自相关依赖于a2 + b2 >= 2ab,这总是正确的,但随着a*b减少,比率会增加。实际上,也许减去最小值更好,但你明白我的意思。2) 关于修剪末尾:np.correlate(a, a[100:-100], mode='valid'),因此每个步骤重叠的点数始终相同。
- tom10
@tom10,你有关于算法信号的参考资料吗? - parras
@tom10,如果您能帮我解答一个问题就太好了。您在下面的另一条评论中提到,如果将200Hz和300Hz的正弦波求和,则周期为1/100Hz,这意味着功率谱密度方法无法识别它,但自相关会。我理解这一点。然而,我们也可以在时域信号本身中找到相邻局部最大值之间的分离,并恢复1/100Hz的周期。自相关是否有其他有用之处?它是否与降噪有关?谢谢! - teeeeee
@teeeeee:是的,那绝对是正确的直觉。我们使用自相关所追求的是我们通过分离相邻峰值(在某些情况下可以测量)所追求的内容,自相关只是做得更好:1)峰值只是波形的一个特征,但自相关比较了沿着波形的所有点;2)自相关比较了多个周期;3)它对噪声不太敏感等等。但是,例如,如果您有一个明显具有1个峰/周期的数学生成的波形,您可以像您建议的那样直接测量T。 - tom10
太好了,谢谢你的快速回答。让我放心,我没有漏掉任何基础知识。 - teeeeee

2

更新。

@tom10对自相关进行了详细调查,并解释了为什么自相关的第一个峰值可以给出周期性信号的周期。

我尝试了FFT和自相关两种方法。它们的结果一致,虽然我更喜欢使用FFT而不是自相关,因为它可以更直接地给出周期。

在使用自相关时,我们只需确定第一个峰值的坐标。手动检查自相关图表将揭示您是否拥有“正确”的峰值,因为您可以注意到周期(尽管对于大于7的质数来说,这变得不太清晰)。我相信您也可以设计出一个简单的算法来计算“正确”的峰值。也许有人可以详细说明一些可以完成这项工作的简单算法?

例如,查看以下序列与其自相关的情况如下图所示。Sequences next to their autocorrelation代码:

 1   # Plotting sequences satisfying, x_{i+1} = p-1 - (p*i-1 mod x_i)
 2   # with p prime and x_0 = 1, next to their autocorrelation.
 3   
 4   from __future__ import print_function
 5   import numpy as np
 6   from matplotlib import pyplot  as plt
 7   
 8   # The length of the sequences.
 9   seq_length = 10000
 10  
 11  upperbound_primes = 12 
 12  
 13  # Computing a list of prime numbers up to n
 14  def primes(n):
 15   sieve = [True] * n
 16   for i in xrange(3,int(n**0.5)+1,2):
 17     if sieve[i]:
 18         sieve[i*i::2*i]=[False]*((n-i*i-1)/(2*i)+1)
 19   return [2] + [i for i in xrange(3,n,2) if sieve[i]]
 20  
 21  # The list of prime numbers up to upperbound_primes
 22  p = primes(upperbound_primes)
 23  
 24  # The amount of primes numbers
 25  no_primes = len(p)
 26  
 27  # Generate the sequence for the prime number p
 28  def sequence(p):
 29    x = np.empty(seq_length)
 30    x[0] = 1
 31    for i in range(1,seq_length):
 32      x[i] = p - 1 - (p * (i-1) - 1) % x[i-1]
 33    return x
 34  
 35  # List with the sequences.
 36  seq = [sequence(i) for i in p]  
 37  
 38  # Autocorrelation function.
 39  def autocor(x):
 40    result = np.correlate(x,x,mode='full')
 41    return result[result.size/2:]
 42  
 43  fig = plt.figure("The sequences next to their autocorrelation")
 44  plt.suptitle("The sequences next to their autocorrelation")
 45  
 46  # Proper spacing between subplots.
 47  fig.subplots_adjust(hspace=1.2)
 48  
 49  # Set up pyplot to use TeX.
 50  plt.rc('text',usetex=True)
 51  plt.rc('font',family='serif')
 52  
 53  # Maximize plot window by command.
 54  mng = plt.get_current_fig_manager()
 55  mng.resize(*mng.window.maxsize())
 56  
 57  k = 0 
 58  for s in  seq:
 59    k = k + 1
 60    fig.add_subplot(no_primes,2,2*(k-1)+1)
 61    plt.title("Sequence of the prime %d" % p[k-1])
 62    plt.plot(s)
 63    plt.xlabel(r"Index $i$")
 64    plt.ylabel(r"Sequence number $x_i$")
 65    plt.xlim(0,100)
 66    
 67    # Constrain the number of ticks on the y-axis, for clarity.
 68    plt.locator_params(axis='y',nbins=4)
 69  
 70    fig.add_subplot(no_primes,2,2*k)
 71    plt.title(r"Autocorrelation of the sequence $^{%d}x$" % p[k-1])
 72    plt.plot(autocor(s))
 73    plt.xlabel(r"Index $i$")
 74    plt.xticks
 75    plt.ylabel("Autocorrelation")
 76    
 77    # Proper scaling of the y-axis.
 78    ymin = autocor(s)[1]-int(autocor(s)[1]/10)
 79    ymax = autocor(s)[1]+int(autocor(s)[1]/10)
 80    plt.ylim(ymin,ymax)
 81    plt.xlim(0,500)
 82    
 83    plt.locator_params(axis='y',nbins=4)
 84  
 85    # Use scientific notation when 0< t < 1 or t > 10
 86    plt.ticklabel_format(style='sci',axis='y',scilimits=(0,1))
 87  
 88  plt.show()

使用FFT时,我们对序列进行傅里叶变换,并寻找第一个峰值。这个第一个峰值的坐标表示的是代表我们信号最粗略频率。这将给出我们的周期,因为最粗略的频率是我们序列理想情况下振荡的频率。
请参见以下序列与它们的傅里叶变换的图形。
代码:
 1   # Plotting sequences satisfying, x_{i+1} = p-1 - (p*i-1 mod x_i)
 2   # with p prime and x_0 = 1, next to their Fourier transforms.
 3   
 4   from __future__ import print_function
 5   import numpy as np
 6   from matplotlib import pyplot  as plt
 7   
 8   # The length of the sequences.
 9   seq_length = 10000
 10  
 11  upperbound_primes = 12 
 12  
 13  # Computing a list of prime numbers up to n
 14  def primes(n):
 15   sieve = [True] * n
 16   for i in xrange(3,int(n**0.5)+1,2):
 17     if sieve[i]:
 18         sieve[i*i::2*i]=[False]*((n-i*i-1)/(2*i)+1)
 19   return [2] + [i for i in xrange(3,n,2) if sieve[i]]
 20  
 21  # The list of prime numbers up to upperbound_primes
 22  p = primes(upperbound_primes)
 23  
 24  # The amount of primes numbers
 25  no_primes = len(p)
 26  
 27  # Generate the sequence for the prime number p
 28  def sequence(p):
 29    x = np.empty(seq_length)
 30    x[0] = 1
 31    for i in range(1,seq_length):
 32      x[i] = p - 1 - (p * (i-1) - 1) % x[i-1]
 33    return x
 34  
 35  # List with the sequences.
 36  seq = [sequence(i) for i in p]  
 37  
 38  fig = plt.figure("The sequences next to their FFT")
 39  plt.suptitle("The sequences next to their FFT")
 40  
 41  # Proper spacing between subplots.
 42  fig.subplots_adjust(hspace=1.2)
 43  
 44  # Set up pyplot to use TeX.
 45  plt.rc('text',usetex=True)
 46  plt.rc('font',family='serif')
 47  
 48  
 49  # Maximize plot window by command.
 50  mng = plt.get_current_fig_manager()
 51  mng.resize(*mng.window.maxsize())
 52  
 53  k = 0 
 54  for s in  seq:
 55    f = np.fft.rfft(s)
 56    f[0] = 0
 57    freq  = np.fft.rfftfreq(seq_length)
 58    k = k + 1
 59    fig.add_subplot(no_primes,2,2*(k-1)+1)
 60    plt.title("Sequence of the prime %d" % p[k-1])
 61    plt.plot(s)
 62    plt.xlabel(r"Index $i$")
 63    plt.ylabel(r"Sequence number $x_i$")
 64    plt.xlim(0,100)
 65    
 66    # Constrain the number of ticks on the y-axis, for clarity.
 67    plt.locator_params(nbins=4)
 68    
 69    fig.add_subplot(no_primes,2,2*k)
 70    plt.title(r"FFT of the sequence $^{%d}x$" % p[k-1])
 71    plt.plot(freq,abs(f))
 72    plt.xlabel("Frequency")
 73    plt.ylabel("Amplitude")
 74    plt.locator_params(nbins=4)
 75    
 76    # Use scientific notation when 0 < t < 0 or t > 10
 77    plt.ticklabel_format(style='sci',axis='y',scilimits=(0,1))
 78  
 79  plt.show()

为了说明FFT方法比自相关更方便,注意我们有一个明确的算法来确定周期:找到傅里叶变换的第一个峰值。对于足够数量的样本,这总是有效的。
请查看以下表格,通过FFT方法获得,与自相关方法一致。
 prime   frequency   period
 2       0.00        1000.00
 3       0.50        2.00
 5       0.08        12.00
 7       0.02        59.88
 11      0.00        1000.00

以下代码实现了该算法,打印一个表格,指定每个质数序列的频率和周期。
 1   # Print a table of periods, determined by the FFT method,
 2   # of sequences satisfying, 
 3   # x_{i+1} = p-1 - (p*i-1 mod x_i) with p prime and x_0 = 1.
 4   
 5   from __future__ import print_function
 6   import numpy as np
 7   from matplotlib import pyplot  as plt
 8   
 9   # The length of the sequences.
 10  seq_length = 10000
 11  
 12  upperbound_primes = 12 
 13  
 14  # Computing a list of prime numbers up to n
 15  def primes(n):
 16   sieve = [True] * n
 17   for i in xrange(3,int(n**0.5)+1,2):
 18     if sieve[i]:
 19         sieve[i*i::2*i]=[False]*((n-i*i-1)/(2*i)+1)
 20   return [2] + [i for i in xrange(3,n,2) if sieve[i]]
 21  
 22  # The list of prime numbers up to upperbound_primes
 23  p = primes(upperbound_primes)
 24  
 25  # The amount of primes numbers
 26  no_primes = len(p)
 27  
 28  # Generate the sequence for the prime number p
 29  def sequence(p):
 30    x = np.empty(seq_length)
 31    x[0] = 1
 32    for i in range(1,seq_length):
 33      x[i] = p - 1 - (p * (i-1) - 1) % x[i-1]
 34    return x
 35  
 36  # List with the sequences.
 37  seq = [sequence(i) for i in p]  
 38  
 39  # Function that finds the first peak.
 40  # Assumption: seq_length >> 10 so the Fourier transformed
 41  #        signal is sufficiently smooth. 
 42  def firstpeak(x):
 43    for i in range(10,len(x)-1):
 44      if x[i+1] < x[i]:
 45        return i
 46    return len(x)-1
 47  
 48  k = 0 
 49  for s in  seq:
 50    f = np.fft.rfft(s)
 51    freq  = np.fft.rfftfreq(seq_length)
 52    k = k + 1
 53    if k == 1:
 54      print("prime \t frequency \t period")
 55    print(p[k-1],'\t %.2f' % float(freq[firstpeak(abs(f))]), \
 56      '\t\t %.2f' % float(1/freq[firstpeak(abs(f))]))

在上述代码中,我使用了10000个样本(seq_length)。随着样本数量的增加,周期会收敛到某个整数值(使用FFT方法)。

对于算法信号中确定周期来说,FFT方法似乎是一种理想的工具,只受设备能处理多高样本数量的限制。


这基本上都是正确的,但是周期性(由自相关找到)与组件频率(由FFT找到)不同。这只取决于你想要什么。此外,您正在对微不足道的情况进行比较。当您达到更高的质数时,例如17和29,自相关显示出有趣的行为,在FFT中并不明显(至少对我来说)。 - tom10
@tom10 FFT的第一个峰值坐标是代表序列最粗糙周期的频率(对于更高的质数,我们需要增加样本数量才能达到相同的结果)。在自相关中,我们正在寻找这个相同的周期。因此,我只提到FFT的第一个峰值,而不是所有组成频率。您观察到更高的质数有什么有趣的行为吗?您也使用了10000个样本,对吗? - easytarget
1
人们使用自相关而不是FFT的原因是因为它可以给出周期;FFT的第一个峰值可能会给出,但通常不会。考虑一个200Hz + 300Hz正弦波。FFT将在200Hz和300Hz处给出峰值,而您的方法将给出1/200Hz周期。然而,周期是1/100Hz,这是自相关将给出的结果,也是您听到的声音,以及您查看的结果。这就是为什么人们使用自相关的原因。如果您想要FFT的第一个峰值,那没问题,我只是认为您应该知道它通常不是周期。 - tom10

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接