从零训练音乐 AI:用 EnCodec 把音乐变成“单词“(附完整代码)
哈喽,大家好,我是翊博,今天我们开始第二章学习咯~
导语: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 模型训练需要统一的输入格式:
- 单声道:减少计算量(立体声→单声道)
- 固定采样率:避免频谱错位(统一 24kHz)
- 归一化:振幅缩放到 [-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: 当然!只需:
- 准备 wav 文件
- 修改
AUDIO_DIR路径 - 运行相同代码
互动话题
- 你尝试过用 EnCodec 处理自己的音乐吗?
- 你觉得 3kbps 的音质能接受吗?
- 你想用这套流程训练什么类型的音乐模型?
欢迎在评论区留言讨论! 👇
标签:AI音乐 EnCodec 音频处理 RVQ 音乐生成 深度学习 PyTorch Token化
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)