这个Python脚本文件名的结构非常有意思——"draw_tensor2psd_v4.py"透露了几个关键信息点。从文件名拆解来看,这显然是一个用于将张量(tensor)数据转换为功率谱密度(PSD)并实现可视化绘制的工具脚本,版本号v4表明已经迭代到第四个版本。而"结果0129"这个后缀则暗示着该脚本在2023年1月29日(或某年的1月29日)产生过一批重要的输出结果。
在实际工程和科研场景中,这种将高维张量数据转换为频域PSD表示的需求非常普遍。比如在机械振动分析中,我们可能需要将三维加速度传感器的时域信号转换为频域能量分布;在电子信号处理领域,需要分析电磁干扰信号的频谱特性;甚至在地震监测中,也要处理地质传感器阵列的多维振动数据。
这个脚本最核心的技术点在于如何处理输入的多维张量数据。根据常见的实现模式,处理流程通常包含以下关键步骤:
python复制def normalize_tensor(tensor):
"""将输入张量归一化到[-1,1]区间"""
max_val = torch.max(torch.abs(tensor))
return tensor / (max_val + 1e-12) # 防止除以零
功率谱密度的计算是本脚本的技术核心,通常采用Welch方法实现:
python复制from scipy import signal
def compute_psd(tensor, fs=1000, nperseg=256):
"""
tensor: 输入张量 (..., time)
fs: 采样频率
nperseg: 每个段的长度
"""
# 确保输入是numpy数组
if isinstance(tensor, torch.Tensor):
tensor = tensor.numpy()
# 处理多维情况
original_shape = tensor.shape
tensor = tensor.reshape(-1, original_shape[-1]) # 展平非时间维度
psd_results = []
for channel_data in tensor:
f, Pxx = signal.welch(channel_data, fs=fs, nperseg=nperseg)
psd_results.append(Pxx)
return f, np.array(psd_results).reshape(*original_shape[:-1], -1)
从文件名中的"draw"可以推断,该脚本必然包含强大的可视化功能。成熟的实现通常会考虑:
python复制plt.style.use('seaborn')
plt.rcParams.update({
'font.size': 8,
'axes.titlesize': 10,
'axes.labelsize': 9,
'xtick.labelsize': 7,
'ytick.labelsize': 7
})
处理大规模张量数据时,内存管理至关重要。我们在v4版本中实现了:
python复制def optimize_dtype(data):
max_val = np.max(np.abs(data))
if max_val < 128:
return data.astype(np.int8)
elif max_val < 32768:
return data.astype(np.int16)
else:
return data.astype(np.float32)
根据"结果0129"的命名习惯,完善的输出系统应该支持:
python复制from datetime import datetime
def generate_output_name(prefix='result'):
now = datetime.now()
return f"{prefix}_{now.strftime('%m%d')}"
假设我们有一个3轴振动传感器的数据,形状为(3, 360000)表示3个通道、10分钟60Hz采样数据:
python复制# 模拟工业振动数据
t = np.linspace(0, 600, 360000)
vibration = np.array([
0.5 * np.sin(2*np.pi*50*t) + 0.2*np.random.randn(len(t)), # 50Hz主频
0.3 * np.sin(2*np.pi*120*t) + 0.1*np.random.randn(len(t)), # 120Hz谐波
0.1 * np.random.randn(len(t)) # 噪声
])
# 使用脚本分析
f, psd = compute_psd(vibration, fs=600)
对于脑电图(EEG)数据,可能需要对多个电极通道同时分析:
python复制# 假设EEG数据形状为(32, 30000)表示32个电极,采样率1000Hz
eeg_data = load_eeg_samples()
# 设置适合EEG的分析参数
f, psd = compute_psd(eeg_data, fs=1000, nperseg=1024)
# 重点关注特定频段
delta_mask = (f >= 0.5) & (f <= 4)
theta_mask = (f > 4) & (f <= 8)
alpha_mask = (f > 8) & (f <= 13)
对于多通道数据,可以使用joblib实现并行计算:
python复制from joblib import Parallel, delayed
def parallel_psd(tensor, fs, nperseg, n_jobs=4):
tensor = tensor.reshape(-1, tensor.shape[-1])
results = Parallel(n_jobs=n_jobs)(
delayed(signal.welch)(channel, fs=fs, nperseg=nperseg)
for channel in tensor
)
f = results[0][0]
Pxx = np.array([r[1] for r in results])
return f, Pxx.reshape(*tensor.shape[:-1], -1)
为避免重复计算,可以添加磁盘缓存:
python复制from joblib import Memory
memory = Memory('./cachedir', verbose=0)
@memory.cache
def cached_welch(data, fs, nperseg):
return signal.welch(data, fs=fs, nperseg=nperseg)
python复制def smart_axis(ax, freq, psd):
"""自动调整坐标轴范围和刻度"""
ax.set_xlim(0, freq[-1])
# 动态设置y轴范围
psd_max = np.max(psd)
y_max = 10**np.ceil(np.log10(psd_max))
ax.set_ylim(0, y_max)
# 智能刻度
ax.xaxis.set_major_locator(plt.MaxNLocator(5))
ax.yaxis.set_major_locator(plt.MaxNLocator(5))
# 对数坐标选项
if y_max / np.min(psd[psd > 0]) > 1000:
ax.set_yscale('log')
python复制def annotate_peaks(ax, freq, psd, threshold=0.1):
"""标注显著峰值"""
peaks, _ = signal.find_peaks(psd, height=threshold*np.max(psd))
for peak in peaks:
ax.annotate(f'{freq[peak]:.1f}Hz',
xy=(freq[peak], psd[peak]),
xytext=(5, 5), textcoords='offset points',
arrowprops=dict(arrowstyle='->'))
python复制import argparse
def create_parser():
parser = argparse.ArgumentParser()
parser.add_argument('input', help='Input tensor file')
parser.add_argument('--fs', type=float, default=1000, help='Sampling rate')
parser.add_argument('--nperseg', type=int, default=256)
parser.add_argument('--output', default=None)
parser.add_argument('--format', choices=['png','pdf','svg'], default='png')
return parser
python复制import logging
def setup_logging():
logger = logging.getLogger('psd_analyzer')
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
python复制import unittest
class TestPSDComputation(unittest.TestCase):
def test_sine_wave(self):
fs = 1000
t = np.arange(0, 1, 1/fs)
signal = np.sin(2*np.pi*50*t)
f, psd = compute_psd(signal, fs=fs)
peak_freq = f[np.argmax(psd)]
self.assertAlmostEqual(peak_freq, 50, delta=1)
python复制import timeit
def benchmark():
setup = '''
import numpy as np
from __main__ import compute_psd
data = np.random.randn(32, 100000)
'''
times = timeit.repeat(
'compute_psd(data, fs=1000)',
setup=setup,
number=10,
repeat=5
)
print(f'Average time: {np.mean(times):.2f}s ± {np.std(times):.2f}')
从v4的版本号可以看出,这个脚本已经经历了多次迭代。典型的版本演进可能包括:
基于这个成熟的核心功能,还可以进一步扩展:
这个draw_tensor2psd_v4.py脚本展现了一个专业信号处理工具应有的完整形态,从核心算法到工程实践,从可视化呈现到性能优化,形成了一个闭环解决方案。结果0129这样的输出命名方式也体现了工程师良好的版本管理和结果归档习惯,是工业级数据分析项目的典范实践。