首页 / PyTorch 入门教程 / TorchAudio:语音处理入门

PyTorch 入门教程

TorchAudio:语音处理入门

本教程共 60 篇 · 第 45 篇 · 更新于 2026-08-17 · 约 4 分钟阅读

PyTorchTorchAudio语音处理梅尔谱MFCC波形音频特征

本节目标:认识 PyTorch 官方音频库 TorchAudio,学会加载波形、重采样、提取梅尔谱和 MFCC 特征,并了解现成的数据集与预训练语音管线。

声音在计算机里长什么样

先想一个问题:声音怎么存进电脑?麦克风每秒采样很多次,把每个瞬间的声压记成一个数字。这一串数字就是波形(waveform),每秒采样的次数叫采样率(sample rate),单位是赫兹(Hz)。

打个比方,波形像心电图上的折线。折线越密,说明记录得越精细。常见采样率有 16kHz(电话语音)和 44.1kHz(CD 音质)。记住一个公式:时长乘采样率,等于采样点个数。

除了采样率,还有个概念叫位深(bit depth),决定每个采样点用多少位存。16 位最常见,位数越高动态范围越大。入门阶段记住采样率就够了,位深一般不用手动管。

PyTorch 官方把音频工具做成了一个库,叫 TorchAudio。它和 TorchVision 一个套路,负责音频领域的四件事:读写音频、常见变换、数据集、预训练模型。本章把四条线都走一遍。

读取和保存音频

读写音频是第一步。TorchAudio 的接口非常简洁:

import torchaudio

waveform, sample_rate = torchaudio.load("hello.wav")
print(waveform.shape)   # torch.Size([1, 96000])
print(sample_rate)      # 16000

load 返回两个东西:波形张量和采样率。注意波形形状是(声道数, 采样点数),不是(采样点数,)。单声道是 [1, N],双声道是 [2, N]。这和图像张量 (C, H, W) 的套路一致,都是通道在前。

想看看文件的基本信息,用 torchaudio.info;保存则用 torchaudio.save。新版 TorchAudio 基于 FFmpeg 解码,wav、mp3、flac、ogg 都能读。

Note

波形数值范围一般是 [-1, 1] 的浮点数。这和图像归一化到 [0, 1] 是一个道理,模型更好处理。

重采样

不同来源的音频,采样率不一样。语音模型通常要求固定采样率,比如 16kHz。这时候就要重采样(resampling):

import torchaudio

waveform, sample_rate = torchaudio.load("hello.wav")
waveform_8k = torchaudio.functional.resample(waveform, sample_rate, 8000)
print(waveform_8k.shape)  # torch.Size([1, 48000])

这段音频有 6 秒,从 16kHz 降到 8kHz,采样点从 96000 变成 48000。音质会损失,但数据量减半。torchaudio.transforms 里也有对应的 Resample 变换,两者效果一样。我习惯在数据加载阶段直接调 functional 版本,少一层对象。

把波形变成特征:梅尔谱与 MFCC

波形太原始,直接喂给网络效果不好。语音领域通常先算特征,最常用的是梅尔谱(Mel-spectrogram)。

从波形到梅尔谱,中间还隔着一层:频谱(spectrogram)。把波形切成一小段一小段,对每段做傅里叶变换,得到频率成分随时间的变化,就是频谱。梅尔谱是在频谱的基础上,把频率轴换成梅尔刻度。TorchAudio 里的 SpectrogramMelSpectrogramMFCC 三个变换,正好是这条链上的三个台阶。

人耳对频率的感知不是线性的:低频敏感,高频迟钝。梅尔刻度(Mel scale)就是模拟这种感知的刻度。把频谱按梅尔刻度重新排布,得到梅尔谱。

import torchaudio

mel = torchaudio.transforms.MelSpectrogram(
    sample_rate=16000,
    n_fft=400,
    n_mels=80,
)
mel_spec = mel(waveform)
print(mel_spec.shape)  # torch.Size([1, 80, 帧数])

n_fft 是傅里叶变换的窗口大小,n_mels 是梅尔频带个数。80 是语音领域的常用值。视觉上梅尔谱就是一张竖着的图:横轴时间、纵轴频率、颜色深浅代表能量。

MFCC(梅尔频率倒谱系数)是梅尔谱的进一步压缩:先取对数,再做一次余弦变换,只保留前十几个系数。

mfcc = torchaudio.transforms.MFCC(sample_rate=16000, n_mfcc=13)
features = mfcc(waveform)
print(features.shape)  # torch.Size([1, 13, 帧数])

老牌语音识别系统爱用 MFCC,因为它把关键信息压进少量维度。深度学习时代梅尔谱更常用,因为信息更全。

数据增强:给频谱盖被子

数据不够,增强来凑。图像领域有翻转裁剪,音频领域对应的是 SpecAugment 思路:随机遮掉频谱上的一块时间或频率区域。TorchAudio 直接给了两个变换:

import torchaudio

mask_time = torchaudio.transforms.TimeMasking(time_mask_param=20)
mask_freq = torchaudio.transforms.FrequencyMasking(freq_mask_param=10)

mel = torchaudio.transforms.MelSpectrogram(sample_rate=16000, n_mels=80)
mel_spec = mel(waveform)
augmented = mask_freq(mask_time(mel_spec))

TimeMasking 随机遮掉一段连续时间,FrequencyMasking 随机遮掉一段频率。参数是最大遮罩长度,每次运行随机取值。模型对局部缺失不敏感了,相当于免费扩增数据。

注意遮罩参数别太大,遮掉一半频谱,信息就丢太多了。常用做法:时间遮罩 2040 帧,频率遮罩 1020 个频带。训练时随机遮,测试时不开。

现成的数据集和预训练管线

自己录语音太麻烦。TorchAudio 内置了常用数据集,比如 YESNO(一个人说 yes/no 的录音)和 SPEECHCOMMANDS(语音命令词):

import torchaudio

dataset = torchaudio.datasets.YESNO(".", download=True)
waveform, sample_rate, label = dataset[0]
print(label)  # 0 表示 yes,1 表示 no

更省事的是预训练管线(pipelines)。TorchAudio 把模型、配套解码器和元数据打包在一起。比如语音识别:

import torchaudio

bundle = torchaudio.pipelines.WAV2VEC2_ASR_BASE_960H
model = bundle.get_model()      # wav2vec 2.0 预训练模型
print(bundle.sample_rate)       # 模型要求的采样率:16000

接上模型之后,识别流程是固定的:先加载音频,采样率不对就重采样到 bundle.sample_rate,再在 torch.inference_mode() 里跑模型得到发射概率(emission),最后取每个时刻概率最大的 token 拼成文字。贪心解码可以自己写,二十行搞定:

import torch
import torchaudio

bundle = torchaudio.pipelines.WAV2VEC2_ASR_BASE_960H
model = bundle.get_model().eval()

waveform, sample_rate = torchaudio.load("speech.wav")
if sample_rate != bundle.sample_rate:
    waveform = torchaudio.functional.resample(
        waveform, sample_rate, bundle.sample_rate
    )

class GreedyCTCDecoder(torch.nn.Module):
    def __init__(self, labels, blank=0):
        super().__init__()
        self.labels = labels
        self.blank = blank

    def forward(self, emission):
        indices = torch.argmax(emission, dim=-1)
        indices = torch.unique_consecutive(indices, dim=-1)
        indices = indices[indices != self.blank]
        return "".join(self.labels[i] for i in indices)

with torch.inference_mode():
    emission, _ = model(waveform)

decoder = GreedyCTCDecoder(bundle.get_labels())
print(decoder(emission[0]))

unique_consecutive 把重复的相邻 token 合并,blank 是 CTC 的空白符号,去掉它再拼接就是识别的文字。

文本转语音(TTS)也有现成管线,Tacotron2 加 WaveRNN 的组合:

import torchaudio

bundle = torchaudio.pipelines.TACOTRON2_WAVERNN_CHAR_LJSPEECH
processor = bundle.get_text_processor()   # 文字转索引
tacotron2 = bundle.get_tacotron2()        # 文本转频谱
vocoder = bundle.get_vocoder()            # 频谱转波形

流程是:文字 → 索引 → 频谱 → 波形,最后用 torchaudio.save 存成 wav。模型权重第一次使用时自动下载。

踩过的坑

  • 声道顺序。load 出来的形状是(声道数, 帧数),很多新手按(帧数, 声道数)处理,维度对不上就报错。
  • 采样率不匹配。模型要 16kHz,你喂 44.1kHz 的音频,模型照样能跑,但结果很怪。先 resample 再进模型,这是语音任务的标准姿势。
  • 数据集下载慢。YESNO 这种小数据集还好,SPEECHCOMMANDS 有 1GB 多,第一次运行要耐心等。
  • 数据集落在你传的 root 目录。换台机器重跑,路径变了会重新下载,建议把 root 固定成一个专门目录,权重复用也方便。

小结

这一章把语音处理的主线走了一遍:波形加载、重采样、梅尔谱与 MFCC 特征、频谱增强,还有数据集和预训练管线。下一步可以试试把梅尔谱当成图像丢给 CNN,那就是语音命令分类的常见做法。