哈喽,大家好,我是翊博,今天我们开始第二章学习咯~

导语:ChatGPT 能写文章,是因为把文字变成了 token;那 AI 如何"写音乐"?答案是:把音频也变成 token!本文将带你实战 EnCodec 音频 Token 化,把连续的音乐波形离散成 AI 能理解的"单词序列"。


为什么需要 Audio Tokenization?

大语言模型的启示

你肯定知道,GPT 系列模型处理的是文本 token

"音乐" → [2345, 6789, 1234]

那音乐 AI 呢?

核心问题:音频是连续信号(波形),而 Transformer 只能处理离散序列。

解决方案:用 EnCodec 把音频压缩成离散 token!

音频波形 → EnCodec 编码 → [42, 156, 893, 234, ...]

这样,音乐生成就能像文本生成一样:

  • 输入:提示词 "欢快的钢琴曲"
  • 输出:token 序列 [42, 156, 893, ...]
  • 解码:用 EnCodec 解码回音频

这就是 MusicGen、AudioLDM 等音乐 AI 的核心技术!


环境准备

!pip install -q torch torchaudio encodec torchcodec huggingface_hub librosa

import torch
import torchaudio
import encodec

print("PyTorch version:", torch.__version__)
print("CUDA available:", torch.cuda.is_available())

核心库说明

  • encodec:Meta 开源的神经音频编解码器
  • torchaudio:PyTorch 音频处理
  • librosa:音频加载和预处理

1. 下载数据集:Maestro

什么是 Maestro 数据集?

Maestro 是 Google 发布的高质量钢琴演奏数据集:

  • 内容:10 年国际钢琴电子比赛(IPEA)的录音
  • 规模:约 200 小时,1200+ 首曲目
  • 格式:wav 音频 + MIDI 对齐标注
  • 版本:v3.0.0(最新版)

%%bash
total=$(wc -l < wavlist.txt)
count=0

while read file; do
  count=$((count+1))
  echo "Downloading $count / $total : $file"
  
  mkdir -p maestro_2004_wav/$(dirname "$file")
  wget -c --progress=bar:force \
  -O maestro_2004_wav/$file \
  https://hf-mirror.com/datasets/ddPn08/maestro-v3.0.0/resolve/main/$file
  
done < wavlist.txt

下载说明

  • 共 72 个 wav 文件(约 7GB)
  • 使用 -c 参数支持断点续传
  • 如果中断,删除 wavlist.txt 中已下载的文件名后重新运行

2. 音频预处理:加载 + 重采样

为什么需要预处理?

AI 模型训练需要统一的输入格式

  1. 单声道:减少计算量(立体声→单声道)
  2. 固定采样率:避免频谱错位(统一 24kHz)
  3. 归一化:振幅缩放到 [-1, 1]

方案一:使用 librosa(推荐)

import os
import torch
import librosa

AUDIO_DIR = "maestro_2004_wav/2004"
TARGET_SR = 24000

audio_tensors = []

for fname in os.listdir(AUDIO_DIR):
    if fname.endswith(".wav"):
        path = os.path.join(AUDIO_DIR, fname)
        
        # librosa 直接加载并重采样
        wav, sr = librosa.load(path, sr=TARGET_SR, mono=True)
        
        # 转成 torch tensor: [1, T]
        wav = torch.from_numpy(wav).unsqueeze(0)
        audio_tensors.append(wav)

print(f"Loaded {len(audio_tensors)} audios.")

# 查看音频信息
for i, wav in enumerate(audio_tensors[:5]):
    duration = wav.shape[1] / TARGET_SR
    print(f"Audio {i+1}: shape={wav.shape}, duration={duration:.2f}s")

看下output:

Loaded 72 audios.
Audio 1: shape=torch.Size([1, 4404480]), duration=183.52s
Audio 2: shape=torch.Size([1, 3892800]), duration=162.20s

方案二:使用 torchaudio

import torchaudio

audio_tensors = []

for fname in os.listdir(AUDIO_DIR):
    if fname.endswith(".wav"):
        path = os.path.join(AUDIO_DIR, fname)
        
        wav, sr = torchaudio.load(path)
        
        # 转单声道(如果是立体声)
        if wav.shape[0] > 1:
            wav = wav.mean(dim=0, keepdim=True)
        
        # 重采样
        if sr != TARGET_SR:
            resampler = torchaudio.transforms.Resample(sr, TARGET_SR)
            wav = resampler(wav)
        
        audio_tensors.append(wav)

采样率知识小课堂

采样率(Sample Rate):每秒采集多少个数据点

采样率 适用场景 最高频率
16kHz 语音识别 8kHz
24kHz 音乐生成 12kHz
44.1kHz CD 音质 22.05kHz
48kHz 专业音频 24kHz

奈奎斯特采样定理

最高可表达频率 = 采样率 / 2

  • 24kHz 采样率 → 最高表达 12kHz 频率
  • 人耳听觉范围:20Hz - 20kHz
  • 音乐生成用 24kHz 足够(12kHz 覆盖了大部分音乐信息)

3. EnCodec 的核心:RVQ 量化器

什么是 RVQ?

RVQ(Residual Vector Quantization,残差向量量化)是 EnCodec 的"秘密武器"。

通俗理解

  • 普通量化:用一本"字典"(codebook)替换向量
  • RVQ 量化:用多本字典,逐层逼近

类比

  • 第一本字典:找到"大致轮廓"(比如:这是个人)
  • 第二本字典:补充"细节"(比如:穿什么衣服)
  • 第三本字典:补充"更多细节"(比如:什么表情)
  • ...
  • 第 31 本字典:精确到"毛孔"

查看 EnCodec 的量化器

from encodec import EncodecModel

# 加载 24kHz 版本的 EnCodec
model = EncodecModel.encodec_model_24khz()
quantizer = model.quantizer

# 查看所有 codebook 的形状
codebooks = [layer.codebook for layer in quantizer.vq.layers]
print([cb.shape for cb in codebooks])

output

[torch.Size([1024, 128]), torch.Size([1024, 128]), ..., torch.Size([1024, 128])]
# 共 31 个 codebook,每个都是 1024 个词 × 128 维

解读

  • 31 层量化器 = 31 本"字典"
  • 每本字典有 1024 个词(vocabulary size)
  • 每个词是 128 维向量(embedding dimension)

查看 codebook 内容

# 查看第 1 个 codebook 的第一个向量
layer = quantizer.vq.layers[0]
codebook = layer.codebook

print("第 1 个向量的前 8 维:")
print(codebook[0][:8])

RVQ 多层量化查看

for l, layer in enumerate(quantizer.vq.layers):
    cb = layer.codebook
    print(f"第 {l} 级量化器: shape={cb.shape}")
    print(f"第一个向量前 8 维: {cb[0][:8]}...\n")

你会看到:不同层的 codebook 分布不同,这正是 RVQ 的精髓——逐层细化


4. 编码:把音频变成 Token

from encodec import EncodecModel
import torch
import os

device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using device: {device}")

# 1. 加载模型
model = EncodecModel.encodec_model_24khz()
model.set_target_bandwidth(3.0)  # 3 kbps 压缩率
model = model.to(device)
model.eval()

CHUNK_SIZE = 24000 * 10  # 10 秒音频
os.makedirs("maestro_tokens_3kbps", exist_ok=True)

total_chunks = 0

# 2. 遍历音频,逐块编码
for audio_idx, wav in enumerate(audio_tensors):
    song_tokens = []
    total_len = wav.shape[1]
    
    print(f"\nAudio {audio_idx + 1}/{len(audio_tensors)}")
    
    # 3. 切块处理(每次 10 秒)
    for chunk_idx, start in enumerate(range(0, total_len, CHUNK_SIZE)):
        chunk = wav[:, start:start + CHUNK_SIZE]
        if chunk.shape[1] < CHUNK_SIZE:
            continue  # 跳过不足 10 秒的片段
        
        with torch.no_grad():
            # 格式调整:[1, T] → [1, 1, T]
            chunk = chunk.unsqueeze(0).float().to(device)
            
            # 归一化:缩放到 [-1, 1]
            chunk = chunk / (chunk.abs().max() + 1e-9)
            
            # 编码:音频 → token
            encoded = model.encode(chunk)
            codes, scale = encoded[0]  # [1, K, T]
            
            # 移除 batch 维度:[1, K, T] → [K, T]
            codes = codes.squeeze(0).cpu()
            
            song_tokens.append(codes)
            total_chunks += 1
    
    print(f"codes shape: {codes.shape}")
    
    # 4. 保存为 .pt 文件
    save_data = {
        "tokens": song_tokens,
        "num_chunks": len(song_tokens),
        "bandwidth": 3.0,
        "sample_rate": 24000,
        "chunk_size": CHUNK_SIZE,
        "music_id": audio_idx
    }
    
    save_path = f"maestro_tokens_3kbps/music_{audio_idx:04d}.pt"
    torch.save(save_data, save_path)
    print(f"Saved {save_path}")

print(f"\nTotal token chunks: {total_chunks}")

步骤 1:模型配置

带宽(bandwidth)选择

  • 1.5 kbps:高压缩,音质一般
  • 3.0 kbps推荐,平衡音质和压缩率
  • 6.0 kbps:低压缩,音质更好
  • 12.0 kbps:几乎无损,但 token 更多
步骤 2:切块处理

为什么切块?

  • GPU 内存有限,无法一次性处理长音频
  • 10 秒是经验值(平衡效率和效果)
  • 训练时也可以按块采样
步骤 3:编码核心
  • codes:离散 token 索引,形状 [1, K, T]
    • K = 31:31 个量化层(codebook 数量)
    • T:时间步数(10 秒音频约 600 个时间步)
  • scale:缩放因子(用于解码时恢复振幅)
步骤 4:保存数据结构

save_data = {
    "tokens": song_tokens,      # token 列表
    "num_chunks": len(song_tokens),  # 块数
    "bandwidth": 3.0,           # 带宽
    "sample_rate": 24000,       # 采样率
    "chunk_size": CHUNK_SIZE,   # 块大小
    "music_id": audio_idx       # 音乐 ID
}

文件结构

maestro_tokens_3kbps/
├── music_0000.pt  # 第 1 首音乐的 token
├── music_0001.pt  # 第 2 首音乐的 token
├── music_0002.pt
├── ...
└── index.json     # 索引文件

5. 保存索引文件

import json

index = {
    "num_music": len(audio_tensors),
    "bandwidth": 3.0,
    "sample_rate": 24000,
    "chunk_size": CHUNK_SIZE,
    "music": [
        {"id": i, "file": f"music_{i:04d}.pt"}
        for i in range(len(audio_tensors))
    ]
}

with open("maestro_tokens_3kbps/index.json", "w") as f:
    json.dump(index, f, indent=2)

print("索引文件已保存!")

作用

  • 记录数据集元信息
  • 训练时快速定位文件
  • 方便数据集版本管理

验证:看看 Token 长什么样

import torch

# 加载第一个音乐的 token
data = torch.load("maestro_tokens_3kbps/music_0000.pt")

print("音乐 ID:", data["music_id"])
print("块数:", data["num_chunks"])
print("带宽:", data["bandwidth"], "kbps")
print("采样率:", data["sample_rate"], "Hz")

# 查看第一个块的 token
first_chunk = data["tokens"][0]
print("\n第一个块的 token 形状:", first_chunk.shape)
print("量化层数:", first_chunk.shape[0])
print("时间步数:", first_chunk.shape[1])

# 查看前 5 个时间步的 token
print("\n前 5 个时间步的 token(每行是一个时间步,31 个量化层):")
print(first_chunk[:, :5])

音频 Token 化 vs 文本 Token 化

维度 文本 Token 化 音频 Token 化(EnCodec)
输入 字符串 "音乐" 波形 [1, T]
处理 BPE/WordPiece 分词 编码器 + RVQ 量化
输出 [2345, 6789] [31, T](31 层×时间步)
词汇表 30,000-50,000 1024(每层)
层级 单层 31 层(逐层细化)
模型 Tokenizer EnCodec

关键区别

  • 文本 token 是单层的(一个词对应一个 token)
  • 音频 token 是多层的(一个时间步对应 31 个 token)

下一步:训练音乐 Transformer

现在你有了 token 数据集,下一步就是:

1.加载 token 数据

data = torch.load("maestro_tokens_3kbps/music_0000.pt")
tokens = data["tokens"]  # [num_chunks, 31, T]

2. 展平多层 token

# 把 31 层展平成一维序列
flat_tokens = tokens.reshape(31 * T)

3. 训练 Transformer

  • 输入:前 N 个 token
  • 预测:第 N+1 个 token
  • 损失:交叉熵损失

4. 生成音乐

  • 自回归采样(像 GPT 一样逐个生成)
  • 用 EnCodec 解码回音频

敬请期待下一章:《Music Transformer 训练实战》


常见问题 FAQ

Q1: 为什么用 24kHz 而不是 44.1kHz?

A:

  • 24kHz 覆盖 0-12kHz 频率,足够表达音乐
  • 数据量减半,训练更快
  • MusicGen、MusicLM 都用 24kHz

Q2: 带宽设多少合适?

A:

  • 3 kbps:推荐,平衡音质和效率
  • 6 kbps:音质更好,但 token 更多
  • 12 kbps:接近无损,适合高质量需求

Q3: 为什么要切块(10 秒)?

A:

  • GPU 内存限制
  • 训练时可以随机采样块
  • 避免长序列梯度消失

Q4: RVQ 为什么用 31 层?

A:

  • 更多层 = 更精细 = 音质更好
  • 但训练更慢,推理更慢
  • 31 层是经验值(平衡质量和效率)

Q5: 可以用自己的数据集吗?

A: 当然!只需:

  1. 准备 wav 文件
  2. 修改 AUDIO_DIR 路径
  3. 运行相同代码

互动话题

  1. 你尝试过用 EnCodec 处理自己的音乐吗?
  2. 你觉得 3kbps 的音质能接受吗?
  3. 你想用这套流程训练什么类型的音乐模型?

欢迎在评论区留言讨论! 👇


标签AI音乐 EnCodec 音频处理 RVQ 音乐生成 深度学习 PyTorch Token化

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐