端到端训练脚本 runme.sh 指南
runme.sh 是 bytedance/piano_transcription 仓库的一键式流水线入口脚本,它把"预训练模型推理演示 → MAESTRO 数据集打包 → 音符模型训练 → 踏板模型训练 → 双模型合并 → 论文指标评估"六个阶段串联成 8 条顺序执行的命令行调用,是复现论文训练与评估结果的最短路径。
Purpose and Scope
本页覆盖以下内容,并逐一给出对应的真实源码命令与参数说明:
runme.sh脚本本身的完整结构、阶段划分与执行顺序- 脚本引用的各 Python 入口(
utils/features.py、pytorch/main.py、pytorch/combine_note_and_pedal_models.py、pytorch/calculate_score_for_paper.py、pytorch/inference.py)的调用契约(即如何被脚本调用、传了哪些参数) pytorch/main.py中训练子命令train的命令行参数定义(已在源码中验证)- 训练前的环境/数据前置条件、失败模式与工程注意事项
以下相关主题有意留给兄弟页面,本页仅做交叉指引,不展开实现细节:
- CRNN 网络结构与前向计算 —— 见「模型架构」相关页面
inference.py推理后处理(音符/踏板事件解码)—— 见推理相关页面utils/features.py内部的 CQT/特征提取实现 —— 见特征工程相关页面- 评估指标的计算逻辑—— 见评估相关页面
Overview
runme.sh 位于仓库根目录,共 39 行,是官方 README 推荐的复现入口。它的设计意图是:把一次完整的"从零训练到论文指标"实验固化成一个线性脚本,让用户只需改 3~5 个路径变量即可跑通全流程,而不必逐条记忆每个 Python 入口的参数组合。
脚本分为两大块:
- 推理演示块(L3-L8):从 Zenodo 下载官方预训练 checkpoint(
CRNN_note_F1=0.9677_pedal_F1=0.9186.pth),并对resources/cut_liszt.mp3做一次示例推理。这一块是独立的,不依赖训练块。 - 从零训练块(L10-L39):训练流水线本体,依次执行数据打包 → 音符模型训练 → 踏板模型训练 → 模型合并 → (可选)评估。
关键术语:
| 术语 | 含义 |
|---|---|
Note 模型 | Regress_onset_offset_frame_velocity_CRNN,回归 onset / offset / frame / velocity 四类输出的 CRNN |
Pedal 模型 | Regress_pedal_CRNN,回归延音/弱音踏板输出的 CRNN |
| 两阶段训练 | 音符与踏板是两个独立网络分别训练,最后通过 combine_note_and_pedal_models.py 合并为单一 checkpoint |
DATASET_DIR | MAESTRO 数据集的本地根目录(需用户自行下载) |
WORKSPACE | 工作区目录,训练产物(HDF5 缓存、日志、checkpoint)都写在其下 |
Architecture
下面是 runme.sh 驱动的完整流水线架构图。每个节点都对应脚本中一条真实命令(或其产物),边表示文件/数据流向:
架构要点解读:
- 单一数据源,双路训练:阶段 1 只执行一次
pack_maestro_dataset_to_hdf5,产出的 HDF5 特征缓存同时供音符模型与踏板模型两个训练进程读取,避免重复计算特征(这是该脚本将打包独立成阶段的核心设计动机)。 - 合并阶段是"人工接力":脚本 L27 注释明确写着 Users should copy and rename the following paths to their trained model paths,即
NOTE_CHECKPOINT_PATH/PEDAL_CHECKPOINT_PATH默认值是官方示例名,用户必须手工改成自己训练产物路径后重跑该行。 - 评估与训练解耦:评估块标注为 optional,
infer_prob与calculate_metrics是两步操作——前者用合并后的 checkpoint 在测试集上推理概率,后者基于概率计算论文指标(maestro 与 maps 两个数据集各一次)。
流水线阶段详解
阶段 0:预训练模型推理演示(L3-L8)
脚本开头先做一次"零训练成本"的可用性验证:通过 wget 从 Zenodo(record 4034264)拉取官方发布的 CRNN_note_F1=0.9677_pedal_F1=0.9186.pth(checkpoint 文件名即其论文指标:音符 F1=0.9677、踏板 F1=0.9186),再用 MODEL_TYPE="Note_pedal" 调用 pytorch/inference.py 对一段李斯特音频做转写。这一块的价值在于:即使没有 GPU 训练资源,用户也能立刻验证环境正确性。
阶段 1:MAESTRO 打包为 HDF5(L11-L18)
1# ============ Train piano transcription system from scratch ============
2# MAESTRO dataset directory. Users need to download MAESTRO dataset into this folder.
3DATASET_DIR="./datasets/maestro/dataset_root"
4
5# Modify to your workspace
6WORKSPACE="./workspaces/piano_transcription"
7
8# Pack audio files to hdf5 format for training
9python3 utils/features.py pack_maestro_dataset_to_hdf5 --dataset_dir=$DATASET_DIR --workspace=$WORKSPACESource: runme.sh
三个变量构成整个训练块的全部配置面:
DATASET_DIR:用户必须预先下载 MAESTRO 数据集到此目录(脚本不会下载,注释 L11 明确说明)。WORKSPACE:后续所有阶段的输出根目录,也决定了评估阶段读取产物的位置。- 打包命令把音频统一转成 HDF5,为两个训练进程提供一次性的、可随机访问的特征存储。
阶段 2:训练音符(Note)转写系统(L20-L21)
# --- 1. Train note transcription system ---
python3 pytorch/main.py train --workspace=$WORKSPACE --model_type='Regress_onset_offset_frame_velocity_CRNN' --loss_type='regress_onset_offset_frame_velocity_bce' --augmentation='none' --max_note_shift=0 --batch_size=12 --learning_rate=5e-4 --reduce_iteration=10000 --resume_iteration=0 --early_stop=300000 --cudaSource: runme.sh
这一行定义了论文的"标准训练配置":augmentation='none'(不使用数据增强)、max_note_shift=0(不做音高偏移)、batch_size=12、learning_rate=5e-4、每 10000 次迭代衰减学习率(reduce_iteration)、从迭代 0 开始(resume_iteration)、300000 次迭代早停(early_stop)、启用 CUDA。
阶段 3:训练踏板(Pedal)转写系统(L23-L24)
# --- 2. Train pedal transcription system ---
python3 pytorch/main.py train --workspace=$WORKSPACE --model_type='Regress_pedal_CRNN' --loss_type='regress_pedal_bce' --augmentation='none' --max_note_shift=0 --batch_size=12 --learning_rate=5e-4 --reduce_iteration=10000 --resume_iteration=0 --early_stop=300000 --cudaSource: runme.sh
与阶段 2 唯一的差别是 model_type 与 loss_type 换成了踏板专用组合(Regress_pedal_CRNN + regress_pedal_bce),其余超参数完全一致——这保证两个模型在相同训练预算与学习率策略下训练,便于公平比较与合并。
阶段 4:合并音符与踏板模型(L26-L31)
1# --- 3. Combine the note and pedal models ---
2# Users should copy and rename the following paths to their trained model paths
3NOTE_CHECKPOINT_PATH="Regress_onset_offset_frame_velocity_CRNN_onset_F1=0.9677.pth"
4PEDAL_CHECKPOINT_PATH="Regress_pedal_CRNN_onset_F1=0.9186.pth"
5NOTE_PEDAL_CHECKPOINT_PATH="CRNN_note_F1=0.9677_pedal_F1=0.9186.pth"
6python3 pytorch/combine_note_and_pedal_models.py --note_checkpoint_path=$NOTE_CHECKPOINT_PATH --pedal_checkpoint_path=$PEDAL_CHECKPOINT_PATH --output_checkpoint_path=$NOTE_PEDAL_CHECKPOINT_PATHSource: runme.sh
设计意图:训练时两个网络独立(各自的损失与输出头不同),但推理与部署时只需要一个 Note_pedal 模型。此命令把两个 checkpoint 的权重装进同一个合并模型并另存为 output_checkpoint_path。注意脚本给出的三个路径都是官方示例名,必须手工替换为自己训练产物(L27 注释)。这正是本脚本"人工接力"的断点——若直接顺序执行整份脚本,合并步骤会因找不到文件而失败。
阶段 5:论文指标评估(L33-L39,可选)
1# ============ Evaluate (optional) ============
2# Inference probability for evaluation
3python3 pytorch/calculate_score_for_paper.py infer_prob --workspace=$WORKSPACE --model_type='Note_pedal' --checkpoint_path=$NOTE_PEDAL_CHECKPOINT_PATH --augmentation='none' --dataset='maestro' --split='test' --cuda
4
5# Calculate metrics
6python3 pytorch/calculate_score_for_paper.py calculate_metrics --workspace=$WORKSPACE --model_type='Note_pedal' --augmentation='aug' --dataset='maestro' --split='test'
7python3 pytorch/calculate_score_for_paper.py calculate_metrics --workspace=$WORKSPACE --model_type='Note_pedal' --augmentation='aug' --dataset='maps' --split='test'Source: runme.sh
评估分两步三命令:
infer_prob:用合并模型(model_type='Note_pedal')在 maestro 测试集上推理概率并落盘到WORKSPACE;注意此步传--cuda且--augmentation='none'。calculate_metrics× 2:读取上一步概率,分别在 maestro 与 maps 数据集上计算指标;注意此步不传--cuda且--augmentation='aug'(按论文的增强设定统计)。两个数据集共用同一--split='test'。
一个值得留意的参数差异:infer_prob 只对 maestro 做推理,而 calculate_metrics 却对 maestro 和 maps 都计算——意味着 maps 的概率文件应在 WORKSPACE 中已有来源(本脚本未包含为 maps 生成概率的命令,属于需要用户按需补充的环节)。
Core Flow:一次完整执行的时序
时序中的三个关键点:
- 阶段 0 与训练块相互独立:删除 L3-L8 也不影响从零训练。
- HDF5 只打包一次:两个
train调用共享同一份特征缓存,WORKSPACE是唯一的数据交换媒介。 - 唯一的人工断点在 L26-L31:脚本不可全自动跑完,用户须在两次训练结束后回填 checkpoint 路径。
训练参数 API Reference(pytorch/main.py train 子命令)
脚本 L21/L24 的训练命令,其参数在 pytorch/main.py 中通过 argparse 严格定义。以下定义已逐条核对源码:
1parser_train.add_argument('--workspace', type=str, required=True)
2parser_train.add_argument('--model_type', type=str, required=True)
3parser_train.add_argument('--loss_type', type=str, required=True)
4parser_train.add_argument('--augmentation', type=str, required=True, choices=['none', 'aug'])
5parser_train.add_argument('--max_note_shift', type=int, required=True)
6parser_train.add_argument('--batch_size', type=int, required=True)
7parser_train.add_argument('--learning_rate', type=float, required=True)
8parser_train.add_argument('--reduce_iteration', type=int, required=True)
9parser_train.add_argument('--resume_iteration', type=int, required=True)
10parser_train.add_argument('--early_stop', type=int, required=True)
11parser_train.add_argument('--mini_data', action='store_true', default=False)Source: pytorch/main.py
参数速查表(含 runme.sh 中实际取值):
| 参数 | 类型 | 必填 | runme.sh 取值 | 说明 |
|---|---|---|---|---|
--workspace | str | ✅ | ./workspaces/piano_transcription | 工作区根目录,HDF5/日志/checkpoint 均写于此 |
--model_type | str | ✅ | Regress_onset_offset_frame_velocity_CRNN / Regress_pedal_CRNN | 网络结构选择(音符四头回归 vs 踏板回归) |
--loss_type | str | ✅ | regress_onset_offset_frame_velocity_bce / regress_pedal_bce | 与 model_type 配套的损失函数 |
--augmentation | str(枚举) | ✅ | none | 只允许 none / aug,超出取值 argparse 直接报错 |
--max_note_shift | int | ✅ | 0 | 音高偏移增强幅度;0 表示禁用 |
--batch_size | int | ✅ | 12 | 训练批大小 |
--learning_rate | float | ✅ | 5e-4 | 初始学习率 |
--reduce_iteration | int | ✅ | 10000 | 学习率衰减周期(迭代数) |
--resume_iteration | int | ✅ | 0 | 恢复训练的起始迭代号;0 表示全新训练 |
--early_stop | int | ✅ | 300000 | 早停迭代上限(即总训练迭代预算) |
--mini_data | flag | — | 未使用 | 仅用小样本数据的调试开关(默认 False) |
--cuda | flag | — | 已传入 | 启用 GPU;infer_prob/inference.py 同样支持 |
设计意图解读:train 子命令把所有超参数都设为 required=True(除 --mini_data),不含任何隐式默认值。这是刻意的——让每次实验的完整配置都显式落在命令行(进而落在 runme.sh)里,保证实验可复现、可追溯,避免"默认值漂移"导致的静默差异。runme.sh 因此成为论文配置的单一事实来源。
Failure Modes, Edge Cases & Concurrency
以下事项均可直接从脚本源码推导(无源码可查的部分已明确标注):
| 场景 | 触发条件 | 脚本行为 / 后果 | 缓解方式 |
|---|---|---|---|
| MAESTRO 未下载 | DATASET_DIR 目录不存在或为空 | 阶段 1 打包命令失败 | 预先下载数据集到 ./datasets/maestro/dataset_root(脚本注释 L11 明确要求) |
| checkpoint 路径未手工替换 | 顺序执行整份脚本 | 阶段 4 合并命令因找不到 Regress_onset_offset_frame_velocity_CRNN_onset_F1=0.9677.pth 等示例文件而失败 | 训练完成后回填 L28-L30 的三个路径变量 |
| Zenodo 网络不可达 | wget 下载预训练模型失败 | 仅影响阶段 0 推理演示,不影响训练块 | 阶段 0 可整体跳过 |
| CUDA 不可用 | 无 GPU 环境 | 传了 --cuda 的训练/推理命令无法启用 GPU | 需移除 --cuda 或改用 CPU 模式(本页不展开具体差异) |
| maps 指标缺概率文件 | 直接跑 L39 | calculate_metrics --dataset='maps' 找不到对应概率 | 需额外执行一次针对 maps 的 infer_prob(脚本未提供该命令) |
| 脚本中途失败后重跑 | 任一阶段出错退出 | 脚本无 set -e,默认 shell 下后续命令仍会继续执行 | 建议以 bash runme.sh 分段执行,或逐条复制命令 |
并发与幂等性说明:
- 脚本是纯顺序、单进程的:两个
train命令先后执行,不会同时占用同一 GPU。 - 阶段 1 的 HDF5 打包是一次性的预计算——若目录已存在同名 HDF5,其覆盖行为取决于
utils/features.py内部实现(此细节未在本次源码阅读中验证,见该文件对应页面)。 resume_iteration=0表明脚本的默认路径是"全新训练";若要断点续训,需手工修改该值并重跑 L21/L24。
Performance & Operational Notes
- 总训练预算:两个模型各
early_stop=300000次迭代、batch_size=12,reduce_iteration=10000意味着学习率在整个训练过程中按万级迭代阶梯衰减,长尾收敛需要较长时间。 - 磁盘规划:
WORKSPACE需要容纳 MAESTRO 全量特征的 HDF5 缓存 + 两个模型的 checkpoint + 评估概率文件;MAESTRO 本体(约数百 GB 的 WAV)需另行预留DATASET_DIR空间。 - checkpoint 命名即指标:官方发布的 checkpoint 文件名内嵌 F1 值(如
CRNN_note_F1=0.9677_pedal_F1=0.9186.pth),这是仓库的命名惯例,便于在合并/评估时直接读出模型质量。 - 评估阶段的 CPU/GPU 分工:
infer_prob带--cuda(概率推理是 GPU 任务),而两条calculate_metrics不带(指标统计在 CPU 上做即可)。 - 运维建议:把
runme.sh当作"配置模板 + 命令清单"而非严格自动化脚本使用——先跑阶段 0 验证环境,再逐段执行训练块,最后手工完成合并与评估。
Extension Points
- 换数据集:流水线对 MAESTRO 的依赖集中在阶段 1 的
pack_maestro_dataset_to_hdf5;替换数据集需提供等价的打包入口(属utils/features.py页面范畴)。 - 启用增强:训练命令中的
--augmentation='none'与--max_note_shift=0是论文基准设定;argparse 允许改为aug/ 非 0 值做增强实验(choices=['none','aug']已在源码验证)。 - 小样本调试:
--mini_data开关(默认关)用于快速冒烟测试整条训练链路,适合在改动代码后验证可跑通。 - 断点续训:
--resume_iteration是唯一的续训入口,配合WORKSPACE中既有的迭代产物即可恢复。 - 合并模型复用:
combine_note_and_pedal_models.py的三参数接口(note/pedal/output 路径)是独立的通用工具,可脱离runme.sh单独调用,用于任意两个训练产物的组合。
Related Links
- 预训练推理入口:pytorch/inference.py
- 训练主程序:pytorch/main.py
- 模型合并工具:pytorch/combine_note_and_pedal_models.py
- 论文指标评估:pytorch/calculate_score_for_paper.py
- 特征打包工具:utils/features.py
- 流水线脚本本体:runme.sh