时间序列特征提取:从理论到Python代码实践

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
实时计算 Flink 版,5000CU*H 3个月
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
简介: 时间序列是一种特殊的存在。这意味着你对表格数据或图像进行的许多转换/操作/处理技术对于时间序列来说可能根本不起作用。

时间序列是一种特殊的存在。这意味着你对表格数据或图像进行的许多转换/操作/处理技术对于时间序列来说可能根本不起作用。

"特征提取"的想法是对我们拥有的数据进行"加工",确保我们提取所有有意义的特征,以便下一步(通常是机器学习应用)可以从中受益。也就是说它是一种通过提供重要特征并过滤掉所有不太重要的特征来"帮助"机器学习步骤的方法。

这是完整的特征提取过程:

对于表格数据和信号,他们的特征根本就不同,比如说的概念,傅里叶变换小波变换的想法,以及独立分量分析(ICA)的概念只有在处理信号时才真正有意义。

目前有两大类进行特征提取的方法:

  • 基于数据驱动的方法: 这些方法旨在仅通过观察信号来提取特征。它们忽略机器学习步骤及其目标(例如分类、预测或回归),只看信号,对其进行处理,并从中提取信息。
  • 基于模型的方法: 这些方法着眼于整个流程,旨在为特定问题找到解决方案的特征

数据驱动方法的优点是它们通常在计算上简单易用,不需要相应的目标输出。它们的缺点是特征不是针对你的特定问题的。例如,对信号进行傅里叶变换并将其用作特征可能不如在端到端模型中训练的特定特征那样优化。

在这篇文章中,为了简单起见,我们将只讨论数据驱动方法。并且讨论将基于领域特定的方法、基于频率的方法、基于时间的方法和基于统计的方法。

1、领域特定特征提取

提取特征的最佳方法是考虑特定的问题。例如,假设正在处理一个工程实验的信号,我们真正关心的是t = 6s后的振幅。这些是特征提取在一般情况下并不真正有意义,但对特定情况实际上非常相关。这就是我们所说的领域特定特征提取。这里没有太多的数学和编码,但这就是它应该是的样子,因为它极度依赖于你的特定情况。

2、基于频率的特征提取

这种方法与我们的时间序列/信号的谱分析有关。我们有一个自然域,自然域是查看信号的最简单方式,它是时间域,意味着我们将信号视为给定时间(或向量)。

例如考虑这个信号,在其自然域中:

绘制它就可以得到:

这是自然(时间)域,也是我们数据集的最简单域。

然后我们可以将其转换为频率域。 信号有三个周期性组件。频率域的想法是将信号分解为其周期性组件的频率振幅相位

信号y(t)的傅里叶变换Y(k)如下:

这描述了频率为k的分量的振幅相位。在提取有意义特征方面,可以提取10个主要分量(振幅最高的)的振幅、相位和频率值。这将是10x3个特征(振幅、频率和相位 x 10),它们将根据谱信息描述时间序列。

这种方法可以扩展。例如,可以将我们的信号分解,不是基于正弦/余弦函数,而是基于小波,这是另一种形式的周期波。这种分解被称为小波分解

我们使用代码来详细解释,首先从非常简单的傅里叶变换开始。

首先,我们需要邀请一些朋友来参加派对:

 importnumpyasnp
 importmatplotlib.pyplotasplt
 importpandasaspd

现在让我们以这个信号为例:

 plt.figure(figsize=(10,5))
 x=np.linspace(-2*np.pi,2*np.pi,1000)
 y=np.sin(x) +0.4*np.cos(2*x) +2*np.sin(3.2*x)
 plt.plot(x,y,color='firebrick',lw=2)
 plt.xlabel('Time (t)',fontsize=24)
 plt.xticks(fontsize=14)
 plt.yticks(fontsize=14)
 plt.grid(alpha=0.4)
 plt.ylabel('y',fontsize=24)

这个信号有三个主要组成部分。一个振幅 = 1和频率 = 1,一个振幅 = 0.4和频率 = 2,一个振幅 = 2和频率 = 3.2。我们可以通过运行傅里叶变换来恢复它们:

 importnumpyasnp
 importmatplotlib.pyplotasplt

 # Generate the time-domain signal
 x=np.linspace(-8*np.pi, 8*np.pi, 10000)
 y=np.sin(x) +0.4*np.cos(2*x) +2*np.sin(3.2*x)
 y=y-np.mean(y)
 # Perform the Fourier Transform
 Y=np.fft.fft(y)
 # Calculate the frequency bins
 frequencies=np.fft.fftfreq(len(x), d=(x[1] -x[0]) / (2*np.pi))
 # Normalize the amplitude of the FFT
 Y_abs=2*np.abs(Y) /len(x)
 # Zero out very small values to remove noise
 Y_abs[Y_abs<1e-6] =0
 relevant_frequencies=np.where((frequencies>0) & (frequencies<10))
 Y_phase=np.angle(Y)[relevant_frequencies]
 frequencies=frequencies[relevant_frequencies]
 Y_abs=Y_abs[relevant_frequencies]

 # Plot the magnitude of the Fourier Transform
 plt.figure(figsize=(10, 6))
 plt.plot(frequencies, Y_abs)
 plt.xlim(0, 10)  # Limit x-axis to focus on relevant frequencies
 plt.xticks([3.2,1,2])
 plt.title('Fourier Transform of the Signal')
 plt.xlabel('Frequency (Hz)')
 plt.ylabel('Magnitude')
 plt.grid(True)
 plt.show()

我们可以清楚地看到三个峰值,对应的振幅和频率。

可以用一个非常简单的函数来完成所有工作

 defextract_fft_features( y, x=None,  num_features=5,max_frequency=10):
   y=y-np.mean(y)
   # Perform the Fourier Transform
   Y=np.fft.fft(y)
   # Calculate the frequency bins
   ifxisNone:
     x=np.linspace(0,len(y))
   frequencies=np.fft.fftfreq(len(x), d=(x[1] -x[0]) / (2*np.pi))
   Y_abs=2*np.abs(Y) /len(x)
   Y_abs[Y_abs<1e-6] =0
   relevant_frequencies=np.where((frequencies>0) & (frequencies<max_frequency))
   Y_phase=np.angle(Y)[relevant_frequencies]
   frequencies=frequencies[relevant_frequencies]
   Y_abs=Y_abs[relevant_frequencies]
   largest_amplitudes=np.flip(np.argsort(Y_abs))[0:num_features]
   top_5_amplitude=Y_abs[largest_amplitudes]
   top_5_frequencies=frequencies[largest_amplitudes]
   top_5_phases=Y_phase[largest_amplitudes]
   fft_features=top_5_amplitude.tolist()+top_5_frequencies.tolist()+top_5_phases.tolist()
   amp_keys= ['Amplitude '+str(i) foriinrange(1,num_features+1)]
   freq_keys= ['Frequency '+str(i) foriinrange(1,num_features+1)]
   phase_keys= ['Phase '+str(i) foriinrange(1,num_features+1)]
   fft_keys=amp_keys+freq_keys+phase_keys
   fft_dict= {fft_keys[i]:fft_features[i] foriinrange(len(fft_keys))}
   fft_data=pd.DataFrame(fft_features).T
   fft_data.columns=fft_keys
   returnfft_dict, fft_data

所以如果输入信号y和(可选):

  • x时间数组,考虑的特征数量(或峰值),最大频率

输出:

 x=np.linspace(-8*np.pi, 8*np.pi, 10000)
 y=np.sin(x) +0.4*np.cos(2*x) +2*np.sin(3.2*x)
 extract_fft_features(x=x,y=y,num_features=3)[1]

如果想使用小波(不是正弦/余弦)*来提取特征,可以进行小波变换。需要安装这个库:

 pip install PyWavelets

然后运行这个:

 importnumpyasnp
 importpywt
 importpandasaspd

 defextract_wavelet_features(y, wavelet='db4', level=3, num_features=5):
     y=y-np.mean(y)  # Remove the mean

     # Perform the Discrete Wavelet Transform
     coeffs=pywt.wavedec(y, wavelet, level=level)

     # Flatten the list of coefficients into a single array
     coeffs_flat=np.hstack(coeffs)

     # Get the absolute values of the coefficients
     coeffs_abs=np.abs(coeffs_flat)

     # Find the indices of the largest coefficients
     largest_coeff_indices=np.flip(np.argsort(coeffs_abs))[0:num_features]

     # Extract the largest coefficients as features
     top_coeffs=coeffs_flat[largest_coeff_indices]

     # Generate feature names for the wavelet features
     feature_keys= ['Wavelet Coeff '+str(i+1) foriinrange(num_features)]

     # Create a dictionary for the features
     wavelet_dict= {feature_keys[i]: top_coeffs[i] foriinrange(num_features)}

     # Create a DataFrame for the features
     wavelet_data=pd.DataFrame(top_coeffs).T
     wavelet_data.columns=feature_keys

     returnwavelet_dict, wavelet_data

 # Example usage:
 wavelet_dict, wavelet_data=extract_wavelet_features(y)
 wavelet_data

3.、基于统计的特征提取

特征提取的另一种方法是依靠统计学。 给定一个信号,有多种方法可以从中提取一些统计信息。从简单到复杂,这是我们可以提取的信息列表:

  • 平均值,只是信号的总和除以信号的时间步数。

  • 方差,即信号与平均值的偏离程度:

  • 偏度峰度。这些是测试你的时间序列分布"不是高斯分布"程度的指标。偏度描述它有多不对称,峰度描述它有多"拖尾"。
  • 分位数:这些是将时间序列分成具有概率范围的区间的值。例如,0.25分位数值为10意味着你的时间序列中25%的值低于10,剩余75%大于10
  • 自相关。这基本上告诉你时间序列有多"模式化",意味着时间序列中模式的强度。或者这个指标表示时间序列值与其自身过去值的相关程度。
  • :表示时间序列的复杂性或不可预测性。

这些属性中的每一个都可以用一行代码实现:

 importnumpyasnp
 importpandasaspd
 fromscipy.statsimportskew, kurtosis

 defextract_statistical_features(y):
     defcalculate_entropy(y):
         # Ensure y is positive and normalized
         y=np.abs(y)
         y_sum=np.sum(y)

         # Avoid division by zero
         ify_sum==0:
             return0

         # Normalize the signal
         p=y/y_sum

         # Calculate entropy
         entropy_value=-np.sum(p*np.log2(p+1e-12))  # Add a small value to avoid log(0)

         returnentropy_value
     # Remove the mean to center the data
     y_centered=y-np.mean(y)
     y=y+np.max(np.abs(y))*10**-4

     # Statistical features
     mean_value=np.mean(y)
     variance_value=np.var(y)
     skewness_value=skew(y)
     kurtosis_value=kurtosis(y)
     autocorrelation_value=np.correlate(y_centered, y_centered, mode='full')[len(y) -1] /len(y)
     quantiles=np.percentile(y, [25, 50, 75])
     entropy_value=calculate_entropy(y)  # Add a small value to avoid log(0)

     # Create a dictionary of features
     statistical_dict= {
         'Mean': mean_value,
         'Variance': variance_value,
         'Skewness': skewness_value,
         'Kurtosis': kurtosis_value,
         'Autocorrelation': autocorrelation_value,
         'Quantile_25': quantiles[0],
         'Quantile_50': quantiles[1],
         'Quantile_75': quantiles[2],
         'Entropy': entropy_value
     }

     # Convert to DataFrame for easy visualization and manipulation
     statistical_data=pd.DataFrame([statistical_dict])

     returnstatistical_dict, statistical_data

 y=np.sin(x) +0.4*np.cos(2*x) +2*np.sin(3.2*x)
 wavelet_dict, wavelet_data=extract_statistical_features(y)
 wavelet_data

4、基于时间的特征提取器

这部分将专注于如何通过仅提取时间特征来提取时间序列的信息。比如提取峰值和谷值的信息。我们将使用来自SciPy的find_peaks函数

 importnumpyasnp
 fromscipy.signalimportfind_peaks

 defextract_peaks_and_valleys(y, N=10):
     # Find peaks and valleys
     peaks, _=find_peaks(y)
     valleys, _=find_peaks(-y)

     # Combine peaks and valleys
     all_extrema=np.concatenate((peaks, valleys))
     all_values=np.concatenate((y[peaks], -y[valleys]))

     # Sort by absolute amplitude (largest first)
     sorted_indices=np.argsort(-np.abs(all_values))
     sorted_extrema=all_extrema[sorted_indices]
     sorted_values=all_values[sorted_indices]

     # Select the top N extrema
     top_extrema=sorted_extrema[:N]
     top_values=sorted_values[:N]

     # Pad with zeros if fewer than N extrema are found
     iflen(top_extrema) <N:
         padding=10-len(top_extrema)
         top_extrema=np.pad(top_extrema, (0, padding), 'constant', constant_values=0)
         top_values=np.pad(top_values, (0, padding), 'constant', constant_values=0)

     # Prepare the features
     features= []
     foriinrange(N):
         features.append(top_values[i])
         features.append(top_extrema[i])

     # Create a dictionary of features
     feature_dict= {f'peak_{i+1}': features[2*i] foriinrange(N)}
     feature_dict.update({f'loc_{i+1}': features[2*i+1] foriinrange(N)})

     returnfeature_dict,pd.DataFrame([feature_dict])

 # Example usage:
 x=np.linspace(-2*np.pi,2*np.pi,1000)
 y=np.sin(x) +0.4*np.cos(2*x) +2*np.sin(3.2*x)
 features=extract_peaks_and_valleys(y, N=10)
 features[1]

我们选择N = 10个峰值,但信号实际上只有M = 4个峰值,剩余的6个位置和峰值振幅将为0。

5、使用哪种方法?

我们已经看到了4种不同类别的方法。

我们应该使用哪一种?

如果你有一个基于领域的特征提取,那总是最好的选择:如果实验的物理或问题的先验知识是清晰的,你应该依赖于它并将这些特征视为最重要的,甚至可能将它们视为唯一的特征。

频率统计基于时间的特征而言,在可以将它们一起使用。将这些特征添加到你的数据集中,然后看看其中的一些是否有帮助,没有帮助就把他们删除掉,这是一个测试的过程,就像超参数搜索一样,我们无法确定好坏,所以只能靠最后的结果来判断。

https://avoid.overfit.cn/post/5790f6f01f7940cdabf3afa4d351b7bb

作者:Piero Paialunga

目录
相关文章
|
1天前
|
SQL JavaScript 前端开发
基于Python访问Hive的pytest测试代码实现
根据《用Java、Python来开发Hive应用》一文,建立了使用Python、来开发Hive应用的方法,产生的代码如下
12 6
基于Python访问Hive的pytest测试代码实现
|
3天前
|
设计模式 缓存 开发者
Python中的装饰器:简化代码,提高可读性
【9月更文挑战第10天】在Python编程的世界中,装饰器是一种强大的工具,它允许开发者在不修改原函数代码的情况下增加额外的功能。本文将通过简单易懂的语言和生动的例子,带你了解装饰器的概念、使用方法及其在实际开发中的应用价值。我们将一起探索如何利用装饰器来简化代码结构,提升代码的可读性和可维护性,让你的编程之旅更加顺畅。
|
2天前
|
存储 安全 数据安全/隐私保护
安全升级!Python AES加密实战,为你的代码加上一层神秘保护罩
【9月更文挑战第12天】在软件开发中,数据安全至关重要。本文将深入探讨如何使用Python中的AES加密技术保护代码免受非法访问和篡改。AES(高级加密标准)因其高效性和灵活性,已成为全球最广泛使用的对称加密算法之一。通过实战演练,我们将展示如何利用pycryptodome库实现AES加密,包括生成密钥、初始化向量(IV)、加密和解密文本数据等步骤。此外,还将介绍密钥管理和IV随机性等安全注意事项。通过本文的学习,你将掌握使用AES加密保护敏感数据的方法,为代码增添坚实的安全屏障。
15 8
|
4天前
|
开发者 Python
探索Python中的装饰器:从入门到实践
【8月更文挑战第41天】本文通过深入浅出的方式,引导读者理解Python装饰器的概念、原理及应用。我们将从装饰器的定义出发,逐步深入其背后的工作原理,并通过实际代码示例,展示如何自定义装饰器以及装饰器的高级用法。文章旨在帮助初学者快速掌握装饰器的使用,同时为有一定基础的开发者提供进阶知识。
|
1天前
|
机器学习/深度学习 测试技术 数据处理
KAN专家混合模型在高性能时间序列预测中的应用:RMoK模型架构探析与Python代码实验
Kolmogorov-Arnold网络(KAN)作为一种多层感知器(MLP)的替代方案,为深度学习领域带来新可能。尽管初期测试显示KAN在时间序列预测中的表现不佳,近期提出的可逆KAN混合模型(RMoK)显著提升了其性能。RMoK结合了Wav-KAN、JacobiKAN和TaylorKAN等多种专家层,通过门控网络动态选择最适合的专家层,从而灵活应对各种时间序列模式。实验结果显示,RMoK在多个数据集上表现出色,尤其是在长期预测任务中。未来研究将进一步探索RMoK在不同领域的应用潜力及其与其他先进技术的结合。
13 4
|
1天前
|
Rust API Python
Python Requests 库中的重试策略实践
在网络请求中,由于网络波动或服务暂时不可达等原因,请求可能失败。为增强客户端健壮性,自动重试机制变得尤为重要。本文介绍如何在 Python 的 `requests` 库中实现请求自动重试,通过 `urllib3` 的 `Retry` 类配置重试策略,并提供了一个具体示例,展示了如何设置重试次数、状态码集合及异常类型等参数,从而提高系统的可靠性和容错能力。
|
4天前
|
开发者 Python
Python中的装饰器:简化你的代码
【9月更文挑战第9天】本文将介绍Python中的一种强大工具——装饰器。我们将从基础概念开始,逐步深入到装饰器的实际应用,包括函数装饰器和类装饰器。我们将通过实例来展示如何利用装饰器简化代码,提高代码的可读性和可维护性。最后,我们将探讨装饰器的一些高级用法,以及如何避免在使用时可能遇到的问题。无论你是初学者还是有经验的开发者,这篇文章都将帮助你更好地理解和使用装饰器。
14 6
|
4天前
|
安全 数据安全/隐私保护 Python
Python系统编程实战:文件系统操作与I/O管理,让你的代码更优雅
【9月更文挑战第10天】Python不仅在数据分析和Web开发中表现出色,在系统编程领域也展现出独特魅力。本文将带你深入探讨Python中的文件系统操作与I/O管理,涵盖os、shutil和pathlib等模块的基础使用方法,并通过示例代码展示如何优雅地实现这些功能。通过掌握缓冲、异步I/O等高级特性,你将能够编写更高效、安全且易于维护的Python代码。示例包括使用pathlib遍历目录、设置缓冲区提升文件写入性能以及使用aiofiles实现异步文件操作。掌握这些技能,让你在Python系统编程中更加得心应手。
11 2
|
4月前
|
算法 编译器 开发者
如何提高Python代码的性能:优化技巧与实践
本文探讨了如何提高Python代码的性能,重点介绍了一些优化技巧与实践方法。通过使用适当的数据结构、算法和编程范式,以及利用Python内置的性能优化工具,可以有效地提升Python程序的执行效率,从而提升整体应用性能。本文将针对不同场景和需求,分享一些实用的优化技巧,并通过示例代码和性能测试结果加以说明。
|
7天前
|
机器学习/深度学习 人工智能 算法
探索人工智能:机器学习的基本原理与Python代码实践
【9月更文挑战第6天】本文深入探讨了人工智能领域中的机器学习技术,旨在通过简明的语言和实际的编码示例,为初学者提供一条清晰的学习路径。文章不仅阐述了机器学习的基本概念、主要算法及其应用场景,还通过Python语言展示了如何实现一个简单的线性回归模型。此外,本文还讨论了机器学习面临的挑战和未来发展趋势,以期激发读者对这一前沿技术的兴趣和思考。