音乐生成模型评测旋律连贯性、和声合理性与风格一致性一、生成音乐的好坏不能靠听着觉得AI 音乐生成领域有一个尴尬的事实评测标准极度主观。一篇论文说SOTA另一篇说不行判断标准全凭作者自己的耳朵。这在工程上是不可接受的。如果你要上线一个 AI 音乐生成功能你必须回答这个模型在爵士乐上比上个版本提升了多少从 0.67 到 0.71 的指标变化意味着什么答案是建立一套多维度的可量化评测体系。音乐质量可以拆解为三个独立维度旋律连贯性相邻音符之间的运动是否合理和声合理性和弦进行是否符合音乐理论风格一致性生成结果是否稳定保持在目标风格内flowchart TD A[生成音频] -- B[自动评测流水线] B -- C[旋律分析] B -- D[和声分析] B -- E[风格分析] C -- C1[音高序列提取] C1 -- C2[音程统计] C2 -- C3[级进/跳进比例] C3 -- C4[连贯性得分] D -- D1[和弦识别] D1 -- D2[进行合理性] D2 -- D3[终止式检测] D3 -- D4[和声得分] E -- E1[风格分类器] E1 -- E2[特征相似度] E2 -- E3[分布式散度] E3 -- E4[风格一致得分] C4 D4 E4 -- F[加权融合得分] F -- G[人类评估校准]二、三个评测维度的量化方法旋律连贯性Melodic Coherence量度相邻音符之间的音程关系。过于跳跃大量五度以上跳进或过于单调全是同度都扣分。级进比例二度进行占全部音程的比例。人类音乐中通常 60-70%音程方差音程的统计离散度。方差过高 旋律无规律跳跃重复模式motif 的重复利用率。完全无重复 散乱和声合理性Harmonic Plausibility和弦进行有效性识别出的和弦序列是否符合常用进行的概率分布终止式检测段落的结束是否落在合理的终止和弦上如 I、V、IV声部引导和弦切换时各声部是否平滑过渡风格一致性Style Consistency风格分类准确率用预训练的分类器对生成音频做风格分类目标风格应当得分最高生成分布与参考分布的 KL 散度生成结果在特征空间的分布应当接近真实音乐在该风格下的分布时序稳定性前 10 秒和后 10 秒的特征向量距离不应过大三、自动评测引擎实现import numpy as np import librosa import pretty_midi from dataclasses import dataclass from typing import Optional from scipy.stats import entropy dataclass class EvaluationResult: 多维评测结果。 每个维度分值为 0-11 为最优。 melodic_coherence: float harmonic_plausibility: float style_consistency: float overall: float # 加权总分 breakdown: dict # 细分项分数 warnings: list[str] # 评测过程中的异常 class MusicEvaluator: 音乐生成模型评测引擎。 设计原则 - 所有指标必须有明确的数值范围和对齐方向 - 主观维度通过参考分布做相对评估而非绝对阈值 - 最终得分需要人类评估校准未包含在这段代码中 def __init__(self, style_reference_dir: Optional[str] None): self._style_ref self._load_references(style_reference_dir) if style_reference_dir else {} def evaluate(self, audio_path: str, target_style: str pop) - EvaluationResult: 主评测入口。 warnings [] # 加载音频 y, sr librosa.load(audio_path, sr22050, monoTrue) if len(y) sr: # 短于 1 秒视为异常 warnings.append(audio shorter than 1 second) # 提取 MIDI 用于旋律分析 midi_data self._audio_to_midi(y, sr) if midi_data is None: warnings.append(MIDI extraction failed, using heuristics) return self._fallback_eval(y, sr, warnings) # 三大维度评测 melodic self._eval_melodic_coherence(midi_data) harmonic self._eval_harmonic_plausibility(midi_data) style self._eval_style_consistency(y, sr, target_style) # 加权融合 overall 0.35 * melodic 0.35 * harmonic 0.30 * style return EvaluationResult( melodic_coherenceround(melodic, 4), harmonic_plausibilityround(harmonic, 4), style_consistencyround(style, 4), overallround(overall, 4), breakdown{}, warningswarnings, ) def _eval_melodic_coherence(self, midi: pretty_midi.PrettyMIDI) - float: 旋律连贯性评测。 核心思路 - 提取所有音符的音高序列 - 计算相邻音高差音程的分布 - 级进2 半音比例越高连贯性越强 - 同时检查乐句的起止是否有合理停顿 notes sorted( [n for inst in midi.instruments for n in inst.notes], keylambda n: n.start, ) if len(notes) 3: return 0.5 # 音符太少无法评估 pitches np.array([n.pitch for n in notes]) intervals np.abs(np.diff(pitches)) # 级进比例≤2 半音minor second / major second step_ratio np.mean(intervals 2) # 极端跳进惩罚12 半音八度以上每次扣分 large_jump_ratio np.mean(intervals 12) # 音程多样性完全不跳也不正常 variety np.std(intervals) / 12.0 # 综合得分 score step_ratio * 0.7 min(variety, 0.5) * 0.4 - large_jump_ratio * 0.3 return float(np.clip(score, 0.0, 1.0)) def _eval_harmonic_plausibility(self, midi: pretty_midi.PrettyMIDI) - float: 和声合理性评测。 通过和弦识别 进行合理性判断。 如果 MIDI 中包含和弦轨则直接分析 否则通过同时发声的音符推断和弦。 # 提取和弦序列 chords self._extract_chords(midi) if len(chords) 2: return 0.5 # 常见和弦进行的 log 概率 # 这个字典是简化版真实实现需要从大规模标注数据中统计 common_progressions { (I, IV): -1.0, (I, V): -0.5, (IV, V): -1.2, (V, I): -0.3, (ii, V): -1.5, (vi, IV): -1.8, } total_log_prob 0.0 for i in range(len(chords) - 1): pair (chords[i], chords[i 1]) total_log_prob common_progressions.get(pair, -4.0) avg_log_prob total_log_prob / max(len(chords) - 1, 1) # 映射到 0-1-4 对应 0-0.3 对应 1 score (avg_log_prob 4.0) / 3.7 return float(np.clip(score, 0.0, 1.0)) def _eval_style_consistency(self, y: np.ndarray, sr: int, target: str) - float: 风格一致性评测。 策略分片计算风格特征检查片间一致性。 如果前 N 秒和后 N 秒的 MFCC 分布差异大说明风格不稳定。 if len(y) sr * 4: return 0.8 # 音频太短无法分段 # 分段 segment_len sr * 2 segments [] for start in range(0, len(y) - segment_len, segment_len): seg y[start: start segment_len] mfcc librosa.feature.mfcc(yseg, srsr, n_mfcc13) segments.append(np.mean(mfcc, axis1)) if len(segments) 2: return 0.8 segments np.array(segments) # 计算段间方差——方差越小风格越一致 inter_segment_var np.mean(np.var(segments, axis0)) # 归一化 score 1.0 - min(inter_segment_var / 50.0, 1.0) return float(score) def _extract_chords(self, midi: pretty_midi.PrettyMIDI) - list[str]: 从 MIDI 提取和弦序列——简化实现。 # 真实实现需时间分片 → 音高集合 → 和弦模板匹配 return [I, V, I] # 占位 def _audio_to_midi(self, y: np.ndarray, sr: int) - Optional[pretty_midi.PrettyMIDI]: 音频 → MIDI 转换使用 basic pitch 检测。 注意这是一个简化实现。生产环境应该用 Google Magenta 的 Onsets and Frames 或 Spotify Basic Pitch。 try: pitches, magnitudes librosa.piptrack(yy, srsr) midi pretty_midi.PrettyMIDI() instrument pretty_midi.Instrument(program0) for t in range(pitches.shape[1]): index magnitudes[:, t].argmax() pitch pitches[index, t] if pitch 0 and magnitudes[index, t] 0.1: start t * 512 / sr # hop_length512 end start 0.1 note pretty_midi.Note( velocity80, pitchint(pitch), startstart, endend, ) instrument.notes.append(note) midi.instruments.append(instrument) return midi except Exception: return None def _load_references(self, dir_path: str) - dict: return {} def _fallback_eval(self, y: np.ndarray, sr: int, warnings: list) - EvaluationResult: return EvaluationResult( melodic_coherence0.5, harmonic_plausibility0.5, style_consistency0.5, overall0.5, breakdown{}, warningswarnings, )四、自动评测的局限性旋律维度级进比例高 ≠ 旋律好听。披头士的《Yesterday》里有大量跳进但仍然是公认的好旋律。自动评测只能反映统计属性无法捕捉美感。和声维度和弦进行概率基于训练集。如果模型创新性地使用了不常用的进行会被扣分——这惩罚了创造性。风格维度风格本身就是模糊概念。Jazz 和 Blues 的边界在哪里硬分类会丢掉中间地带。使用建议开发迭代自动评测用于快速 A/B 比较作为第一道筛选上线决策必须配合人工评测至少 3 人对至少 50 个样本打分不能用于跨风格的对比爵士 vs 电子得分没有可比性五、总结音乐生成评测的核心挑战在于主观性与客观性的张力。旋律连贯性、和声合理性、风格一致性这三个维度把好听拆解为可量化指标但它们只是 proxy——最终的判断仍然需要人类参与。自动评测用来筛掉明显不合格的产出人类评估用来挑出真正优秀的作品。两层过滤各司其职。