在本质上讲,声波是一种连续信号,这意味着它在给定的时间内包含无数个信号值。这对需要有限数组的数字设备来说是个问题。要由数字设备处理、存储和传输连续声波,需要将其转换成一系列离散值,即所谓的数字表示。
如果你查看任何音频数据集,你会发现带有声音片段的数字文件,例如文本叙述或音乐。你可能遇到不同的文件格式,例如.wav
(波形音频文件)、.flac
(免费无损音频编解码器)和 .mp3
(MPEG-1 音频层 3)。这些格式的主要区别在于它们如何压缩音频信号的数字表示。
让我们看看如何从连续信号得出这种表示形式。模拟信号首先被麦克风捕获,麦克风将声波转换为电信号。然后,电信号被模数转换器数字化,通过采样获得数字表示。
采样和采样率
采样是测量固定时间步长连续信号值的过程。采样波形是离散的,因为它包含有限数量的均匀间隔的信号值。
采样率(也称为采样频率)是一秒钟内进行的样本数,以赫兹(hz)为单位。为了给你提供参考点,CD 质量音频的采样率为 44100 Hz,这意味着每秒采样 44100 次。相比之下,高分辨率音频的采样率为 192000 hz 或 192 kHz。训练语音模型时常用的采样率为 16000 Hz 或 16 kHz。
采样率的选择主要决定了可以从信号中捕获的最高频率。这也称为 Nyquist limit
(奈奎斯特极限),恰好是采样率的一半。人类语音的可听音频低于 8 kHz,因此以 16 kHz 采样语音就足够了。使用更高的采样率不会捕获更多的信息,只会导致处理此类文件的计算成本增加。另一方面,以太低的采样率对音频进行采样会导致信息丢失。以 8 kHz 采样的语音听起来会很低沉,因为以此速率无法捕捉到较高的频率。
在执行任何音频任务时,确保数据集中的所有音频示例具有相同的采样率非常重要。如果你计划使用自定义音频数据来微调预训练模型,则数据的采样率应该与模型预训练数据的采样率相匹配。采样率决定了连续音频样本之间的时间间隔,这影响音频数据的时间分辨率。考虑一个例子:采样率为 16000 Hz 的 5 秒声音将表示为一系列 80000 个值,而采样率为 8000 Hz 的相同 5 秒声音将表示为一系列 40000 个值。解决音频任务的 Transformer 模型将示例视为序列,并依靠注意力机制来学习音频或多模态表示。由于不同采样率的音频示例的序列不同,因此模型在采样率之间进行推广将具有挑战性。重采样 是使采样率匹配的过程,是音频数据预处理的一部分。
振幅和位深度
虽然采样率可以告诉你采样的频率,但是每个样本中的值到底是什么呢?
声音是由人类可听见的频率的气压变化产生的。声音的振幅描述了任何特定时刻的声压级,以分贝(dB)为单位。我们将振幅视为响度。举个例子,正常说话的声音低于 60 分贝,而摇滚音乐会的声音则可能达到 125 分贝左右,这超出了人类听觉的极限。
在数字音频中,每个音频样本都会记录某一时间点的音频波的振幅。样本的位深度决定了该振幅值的描述精度。位深度越高,数字表示越忠实地接近原始连续声波。
最常见的音频位深度是 16 位和 24 位。每个都是二进制项,表示将幅度值从连续转换为离散时可以量化的可能部署:16 位音频位 65536 步,24 位音频则高达 16777216 步。由于量化涉及将连续值四舍五入为离散值,因此采样过程会引入噪声。位深度越高,量化噪声越小。实际上,16 位音频的量化噪声已经足够小,可以听到,并且通常不需要使用更高的位深度。
你可能还会遇到 32 位音频。这将样本存储位浮点值,而 16 位和 24 位音频使用整数样本。32 位浮点值的精度为 24 位,因此其位深度与 24 位音频相同。浮点音频样本预计位于 [-1.0, 1.0] 范围内。由于机器学习模型自然地在浮点数据上起作用,因此必须将音频转换为浮点格式,然后才能用于训练模型。我们将在下一节 “预处理” 中看到如何做到这一点。
与连续音频信号一样,数字音频的幅度通常以分贝(dB)表示。由于人类的听觉本质上是对数的 --- 我们的耳朵对安静声音的细小波动比对大声音的细小波动更敏感 --- 如果振幅以分贝为单位,而分贝也是对数的,那么声音的响度就更容易解释。真实世界音频的分贝标度从 0 dB开始,这代表人类可以听到的最小声音,声音越大,分贝值越大。然而,对于数字音频信号,0 dB是可能的最大振幅,而所有其它振幅都是负值。一个简单的经验法则:每 -6 dB就是振幅减半,并且低于 -60 dB 的任何声音通常都听不见,除非你真的调高音量。
音频波形
你可能已经看到过以波形形式呈现的声音,它绘制了随时间变化的样本值并说明了声音振幅的变化,这也称为声音的时域表示。
这种可视化对于识别音频信号的特定特征很有用,例如单个声音事件的时间、信号的整体响度以及音频中存在的任何不规则或噪音。
要绘制音频信号的波形,我们可以使用为 librosa
的 Python 库:
pip install librosa
让我们以库中自带的名为 “trumpet” 的声音为例:
import librosa
array, sampling_rate = librosa.load(librosa.ex("trumpet"))
该示例以音频时间序列(此处我们称之为数组)和采样率(sampling_rate)的元组形式加载。让我们使用 librosa 的 waveshow() 函数查看此声音的波形:
import matplotlib.pyplot as plt
import librosa.display
plt.figure().set_figwidth(12)
librosa.display.waveshow(array, sr=sampling_rate)
这在 y 轴上绘制了信号的幅度,在 x 轴上绘制了时间。换句话说,每个点对应于对该声音进行采样时获取的单个样本值。还要注意,librosa 已经将音频作为浮点值返回,并且幅度值确实在 [-1.0, 1.0] 范围内。
将音频可视化并聆听它对于理解你正在处理的数据的有用工具。你可以看到信号的形状,观察模式,学会发现噪音或失真。如果你以某些方式对数据进行预处理,例如规范化、重采样或过滤,则可以直观地确认预处理步骤已按预期应用。训练模型后,你还可以可视化出现错误的样本(例如在音频分类任务中)来调试问题。
频谱
可视化音频数据的另一种方法是绘制音频信号的频谱,也称为频域表示。频谱是使用离散傅里叶变换(DFT)计算的。它描述了组成信号的各个频率及其强度。
让我们使用 numpy 的 rfft()
函数进行 DFT,绘制相同小号声音的频谱。虽然可以绘制整个声音的频谱,但观察一小部分区域更有用。这里我们将对前 4096 个样本进行 DFT,这大致是演奏的第一个音符的长度:
import numpy as np
dft_input = array[:4096]
# 计算 DFT
window = np.hanning(len(dft_input))
windowed_input = dft_input * window
dft = np.fft.rfft(windowed_input)
# 获取以分贝为单位的振幅谱
amplitude = np.abs(dft)
amplitude_db = librosa.amplitude_to_db(amplitude, ref=np.max)
# 获取频率点
frequency = librosa.fft_frequencies(sr=sampling_rate, n_fft=len(dft_input))
plt.figure().set_figwidth(12)
plt.plot(frequency, amplitude_db)
plt.xlabel("Frequency (Hz)")
plt.ylabel("Amplitude (dB)")
plt.xscale("log")
这绘制了该音频片段中存在的各种频率成分的强度。频率值在 x 轴上,通常以对数刻度绘制,而其幅度在 y 轴上。
我们绘制的频谱显示出几个峰值。这些峰值与正在演奏的音符的谐波相对应,其中谐波越高,声音越安静。由于第一个峰值位于 620 Hz 左右,因此这是 E♭ 音符的频谱。
DFT 的输出是一个复数数组,由实部和虚部组成。使用 np.abs(dft)
测量幅度可以从频谱图中提取振幅信息。实部和虚部之间的角度提供了所谓的相位谱,但这在机器学习应用中经常被丢弃。
你使用 librosa.amplitude_to_db()
将振幅值转换为分贝刻度,从而更容易看到频谱中的更精细的细节。有时人们使用功率谱,它测量能量而不是振幅;这只是振幅值平方的频谱。
💡 在实践中,人们交替使用术语 FFT 与 DFT,因为 FFT 或快速傅里叶变换是在计算机上计算 DFT 的唯一有效方法。
音频信号的频谱包含与其波形完全相同的信息 --- 它们只是查看统一数据的两种不同方式(这里是来自小号声音的前 4096 个样本)。波形图绘制了音频信号随时间变化的振幅,而频谱则显示了固定时间点各个频率的幅度。
频谱图
如果我们想看看音频信号中的频率如何变化,该怎么办?小号吹奏几个音符,它们都有不同的频率。问题在于频谱仅显示特定时刻频率的冻结快照。解决方案是采用多个 DFT,每个 DFT 仅覆盖一小段时间,然后将得倒的频谱堆叠在一起形成频谱图。
频谱图绘制了音频信号随时间变化的频率内容。它允许你在一张图上查看时间、频率和振幅。执行此计算的算法是 STFT 或短时傅里叶变换。
频谱图是你可以使用的最具信息量的音频工具之一。例如,在处理音乐录音时,你可以看到各种乐器和人声曲目以及他们对整体声音的贡献。在语音中,你可以识别不同的元音,因为每个元音都有特定的频率特征。
让我们使用 librosa 的 sftf()
和 specshow()
函数绘制相同 “trumpet” 声音的声谱图:
import numpy as np
D = librosa.stft(array)
S_db = librosa.amplitude_to_db(np.abs(D), ref=np.max)
plt.figure().set_figwidth(12)
librosa.display.specshow(S_db, x_axis="time", y_axis="hz")
plt.colorbar()
在此图中,x 轴表示时间,如波形可视化所示,但现在 y 轴表示频率(单位为赫兹)。颜色的强度给出了每个时间频率成分的幅度或功率,以分贝(dB)为单位。
频谱图时通过截取音频信号的短片段(通常持续几毫秒)并计算每个片段的离散傅里叶变换以获取得其频谱来创建。然后得到的光谱堆叠在时间轴上以创建光谱图。从顶部看,该图像的每个垂直切片都对应一个频谱。默认情况下,librosa.stft()
将音频信号分成 2048 个样本的段,这在频率分辨率和时间分辨率之间实现了良好的平衡。
由于频谱图和波形在同一数据的不同视图,因此可以使用逆 STFT 将频谱图转换回原始波形。然后,这除了幅度信息之外还需要相位信息。如果频谱图是由机器学习模型生成的,它通常只输出振幅。在这种情况下,我们可以使用相位重建算法,例如经典的 Griffin-Lim 算法,或者使用称为声码器的神经网络,从频谱图中重建波形。
频谱图不仅仅用于可视化。许多机器学习模型将频谱图(而不是波形)作为输入,并生成频谱图作为输出。
现在我们知道了什么是声谱图以及它是如何生成的,让我们看下广泛用于语音处理的变体:梅尔声频谱图。
梅尔频谱图
梅尔频谱图是语音处理和机器学习任务中常使用的频谱图的变体。它类似于频谱图,显示音频信号随时间的变化的频率内容,但是在不同的频率轴上。
在标准频谱图中,频率轴是线性的,以赫兹(Hz)为单位。然而,人类的听觉系统对低频的变化比高频的变化更敏感,并且这种敏感度随着频率的增加而呈对数下降。梅尔标度是一种近似人耳非线性频率响应的感知标度。
为了创建梅尔频谱图,使用 STFT 的方式与之前一样,将音频分成短段以获取一系列频谱。此外,每个频谱都经过一组滤波器(即所谓的梅尔滤波器组),将频率转换为梅尔尺度。
让我们看看如何使用 librosa 的 melspectrogram()
函数绘制梅尔频谱图,该函数为我们执行了所有这些步骤:
S = librosa.feature.melspectrogram(y=array, sr=sampling_rate, n_mels=128, fmax=8000)
S_dB = librosa.power_to_db(S, ref=np.max)
plt.figure().set_figwidth(12)
librosa.display.specshow(S_dB, x_axis="time", y_axis="mel", sr=sampling_rate, fmax=8000)
plt.colorbar()
在上面的例子中,n_mels
代表要生成的梅尔带的数量。梅尔频带定义了一组频率范围,将频谱划分为具有感知意义的分量,使用一组滤波器,这些滤波器的形状和间距经过选择,以模仿人耳对不同频率的响应方式。n_mels
的常见值是 40 或 80。fmax
表示我们关心的最高频率(以 Hz 为单位)。
与常规频谱图一样,通常用分贝来表示梅尔频率成分的强度。这通常被称为对数梅尔频谱图,因为转换为分贝射击对数运算。上面的例子使用 librosa.power_to_db()
作为 librosa.feature.lemspectrogram()
创建功率谱图。
💡并非所有梅尔频谱图都相同!有两种不同的梅尔标度常用(“htk”和“slaney”),并且可以使用振幅频谱图代替功率频谱图。转换为对数梅尔频谱图并不总是计算真正的分贝,而可能只是取“对数”。因此,如果机器学习模型需要梅尔频谱作为输入,请仔细检查以确保你以相同的方式计算它。
创建梅尔频谱图是一项有损操作,因为它涉及过滤信号。将梅尔频谱图转换回波形比常规频谱图更困难,因为它需要估计被丢弃的频率。这就是为什么需要 HiFiGAN 声码器等机器学习模型从梅尔频谱图产生波形的原因。
与标准频谱图相比,梅尔频谱图能够捕捉到更符合人类感知的音频特征,这使得它在语音识别、说话人识别和音乐流派分类等任务中成为一种常用的选择。
现在你已经知道了如何可视化音频数据示例,请继续尝试看看你最喜欢的声音是什么样的。:)