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

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

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

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

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

对于表格数据和信号,他们的特征根本就不同,比如说的概念,傅里叶变换小波变换的想法,以及独立分量分析(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月前
|
存储 人工智能 运维
【01】做一个精美的打飞机小游戏,浅尝阿里云通义灵码python小游戏开发AI编程-之飞机大战小游戏上手实践-优雅草央千澈-用ai开发小游戏尝试-分享源代码和游戏包
【01】做一个精美的打飞机小游戏,浅尝阿里云通义灵码python小游戏开发AI编程-之飞机大战小游戏上手实践-优雅草央千澈-用ai开发小游戏尝试-分享源代码和游戏包
194 48
【01】做一个精美的打飞机小游戏,浅尝阿里云通义灵码python小游戏开发AI编程-之飞机大战小游戏上手实践-优雅草央千澈-用ai开发小游戏尝试-分享源代码和游戏包
|
1月前
|
机器学习/深度学习 数据可视化 数据挖掘
使用Python实现基于矩阵分解的长期事件(MFLEs)时间序列分析
在现代数据分析中,高维时间序列数据的处理和预测极具挑战性。基于矩阵分解的长期事件(MFLEs)分析技术应运而生,通过降维和时间序列特性结合,有效应对大规模数据。MFLE利用矩阵分解提取潜在特征,降低计算复杂度,过滤噪声,并发现主要模式。相比传统方法如ARIMA和深度学习模型如LSTM,MFLE在多变量处理、计算效率和可解释性上更具优势。通过合理应用MFLE,可在物联网、金融等领域获得良好分析效果。
60 0
使用Python实现基于矩阵分解的长期事件(MFLEs)时间序列分析
|
1月前
|
数据可视化 算法 数据挖掘
Python时间序列分析工具Aeon使用指南
**Aeon** 是一个遵循 scikit-learn API 风格的开源 Python 库,专注于时间序列处理。它提供了分类、回归、聚类、预测建模和数据预处理等功能模块,支持多种算法和自定义距离度量。Aeon 活跃开发并持续更新至2024年,与 pandas 1.4.0 版本兼容,内置可视化工具,适合数据探索和基础分析任务。尽管在高级功能和性能优化方面有提升空间,但其简洁的 API 和完整的基础功能使其成为时间序列分析的有效工具。
79 37
Python时间序列分析工具Aeon使用指南
|
21天前
|
存储 缓存 Java
Python高性能编程:五种核心优化技术的原理与Python代码
Python在高性能应用场景中常因执行速度不及C、C++等编译型语言而受质疑,但通过合理利用标准库的优化特性,如`__slots__`机制、列表推导式、`@lru_cache`装饰器和生成器等,可以显著提升代码效率。本文详细介绍了这些实用的性能优化技术,帮助开发者在不牺牲代码质量的前提下提高程序性能。实验数据表明,这些优化方法能在内存使用和计算效率方面带来显著改进,适用于大规模数据处理、递归计算等场景。
58 5
Python高性能编程:五种核心优化技术的原理与Python代码
|
1月前
|
机器学习/深度学习 运维 数据可视化
Python时间序列分析:使用TSFresh进行自动化特征提取
TSFresh 是一个专门用于时间序列数据特征自动提取的框架,支持分类、回归和异常检测等机器学习任务。它通过自动化特征工程流程,处理数百个统计特征(如均值、方差、自相关性等),并通过假设检验筛选显著特征,提升分析效率。TSFresh 支持单变量和多变量时间序列数据,能够与 scikit-learn 等库无缝集成,适用于大规模时间序列数据的特征提取与模型训练。其工作流程包括数据格式转换、特征提取和选择,并提供可视化工具帮助理解特征分布及与目标变量的关系。
68 16
Python时间序列分析:使用TSFresh进行自动化特征提取
|
2月前
|
Python
课程设计项目之基于Python实现围棋游戏代码
游戏进去默认为九路玩法,当然也可以选择十三路或是十九路玩法 使用pycharam打开项目,pip安装模块并引用,然后运行即可, 代码每行都有详细的注释,可以做课程设计或者毕业设计项目参考
78 33
|
2月前
|
JavaScript API C#
【Azure Developer】Python代码调用Graph API将外部用户添加到组,结果无效,也无错误信息
根据Graph API文档,在单个请求中将多个成员添加到组时,Python代码示例中的`members@odata.bind`被错误写为`members@odata_bind`,导致用户未成功添加。
50 10
|
2月前
|
数据可视化 算法 数据挖掘
Python量化投资实践:基于蒙特卡洛模拟的投资组合风险建模与分析
蒙特卡洛模拟是一种利用重复随机抽样解决确定性问题的计算方法,广泛应用于金融领域的不确定性建模和风险评估。本文介绍如何使用Python和EODHD API获取历史交易数据,通过模拟生成未来价格路径,分析投资风险与收益,包括VaR和CVaR计算,以辅助投资者制定合理决策。
112 15
|
2月前
|
数据可视化 Python
以下是一些常用的图表类型及其Python代码示例,使用Matplotlib和Seaborn库。
通过这些思维导图和分析说明表,您可以更直观地理解和选择适合的数据可视化图表类型,帮助更有效地展示和分析数据。
103 8
|
2月前
|
Python
探索Python中的装饰器:简化代码,增强功能
在Python的世界里,装饰器就像是给函数穿上了一件神奇的外套,让它们拥有了超能力。本文将通过浅显易懂的语言和生动的比喻,带你了解装饰器的基本概念、使用方法以及它们如何让你的代码变得更加简洁高效。让我们一起揭开装饰器的神秘面纱,看看它是如何在不改变函数核心逻辑的情况下,为函数增添新功能的吧!