The lesson text is only available in Chinese for now. Lesson titles, introductions, menus and buttons are shown in English.

Lesson 2023 lessons in totalAbout 10 minIncludes an interactive lab

The four steps one line of code does for you: getting MFCCs in Python

The last lesson wrote the whole MFCC chain by hand. This lesson hands it to the library: librosa.feature.mfcc returns 13 rows of coefficients in one line, and librosa.feature.delta adds the first- and second-order rates of change — together, the familiar 39 dimensions per frame. As usual, the first thing to do after getting a one-line result is to break it back into the four hand-written steps from the last lesson and line them up digit by digit: only when all four defaults are set correctly does the largest difference drop to 0.0000. Two surprises come up along the way: delta is not “next frame minus previous frame”, and no single one of the 13 coefficients can separate the three genres on its own.

导读: 上一课把 MFCC 整条链子手写了一遍。这一课把它交给库:librosa.feature.mfcc 一行返回 13 行系数,librosa.feature.delta 再给出一阶和二阶变化率,拼起来就是常说的每帧 39 维。照例,一行拿到结果之后要做的第一件事是把它拆回上一课手写的那四步,逐位对齐——四个默认值全改对,最大差才降到 0.0000。中间还会撞上两件事:delta 不是「后一帧减前一帧」;13 个系数里没有哪一个单独分得开三种风格。

二十三课中的第二十课,把手写的 MFCC 链条和现成的库对齐

完整代码在第 20 课脚本,运行环境沿用课程总纲里的一次性配置。

一行就拿到了 13 个系数

换一段音乐:德彪西那段古典乐的前 30 秒。

signal, sr = librosa.load("debussy.wav")
mfccs = librosa.feature.mfcc(y=signal, sr=sr,
                             n_mfcc=13)
signal  661500 个样本,22050 Hz
mfccs   shape = (13, 1292)

librosa.load 不写采样率就默认重采样到 22050——前面十九课一直用的就是这个数,所以这批特征和之前算过的都能互相比较。

13 行是系数序号 0 到 12,1292 列是帧。 帧数还是第 15 课那条算式:,一帧覆盖 92.9 毫秒,每 23.2 毫秒挪一次。

数值范围是 −395.7 到 216.5。这不是分贝。 分贝停在取对数那一步;再做一次变换之后,出来的系数没有单位。

那一行到底等于哪几步

拆开来是四步,每一步都是前两课手写过的:

① melspectrogram(y, sr)   梅尔谱
② power_to_db(...)        取对数
③ dct(type=2, ortho)      再变换一次
④ [:n_mfcc]               取前 13 行

既然四步都写过,自己走一遍应该得到同一批数。试一次,一个都对不上。

起点不是随便挑的:第 18 课为了让库对上手写的滤波器组,我们亲手改过三个默认值(fmax=8000、norm=None、htk=True);上一课一直切 40 条梅尔带。就用这套设定当起点,然后一个一个改回去。

逐个改默认值时,自己算的 MFCC 与库的最大差从 344.2 降到 0.0000
起点:40 带 + 上一课那三个  344.2210
htk 改回 False           342.7487
norm 改回 'slaney'       205.2342
去掉 fmax(默认到 sr/2)   192.7903
梅尔带数 40 改成 128        0.0000

最后一步一下子归零。 但这不等于「只要改梅尔带数就够了」:从起点只把带数改成 128、别的三个不动,最大差还有 226.2。四个缺一不可。

其中梅尔带数最要命,而且它错得和另外三个不是同一种错。另外三个改的是三角形长什么样;带数改的是有几个三角形。上一课把 40 个数交给最后那一步变换,库交的是 128 个——输进去的原料根数都不同,出来的前 13 个系数当然不是同一批。这不是精度问题。

四个全改对之后,两边逐位相同。

还有一个不写出来就看不见的默认值

取对数那一步还藏着一个。power_to_db 默认 top_db=80:比整段最高点低 80 分贝以下的格子,全部抬到 −80。

这段音乐真实跨度   94.3 dB
被抬上来的格子     7709 个(占 4.66%)
关掉截断后的最大差  16.7231

4.66% 听起来不多,可它改变的是每一行。因为最后那一步变换是把整整一列一起变换的:一列里改动任何一个数,这一列的 13 个系数全都会跟着动。

上一课已经见过同一件事的另一面——第 18 课打印出的「跨度正好 80.00 分贝」也是这个 top_db 截出来的。它是同一个默认值在两课里咬人两次。

交互程序加载中……

13 行里,第 0 行说的是另一件事

把 13 行画成一张图,只看得出最下面那一条,其余十二行被压成同一片颜色。

13 行 MFCC 原样画出与逐行标准化之后的对比

逐行的平均绝对值是一道陡坡:第 0 行 245、第 1 行 168、第 2 行 29、第 3 行 23,第 4 行以后就掉到十上下。第 0 个系数是其余十二个平均的 10.4 倍。

它为什么这么大,上一课的公式已经给了答案:第 0 项就是把整列加起来乘一个常数,也就是「这一帧的对数梅尔谱平均有多高」。实测两者的相关系数是 1.00,和这一帧的均方根电平(分贝)相关 0.88。

它说的是这一帧多响,不是这一帧什么音色。 所以很多流水线会把第 0 行单独归一化,或者干脆扔掉。

把每一行各自减去自己的均值、除以自己的标准差之后,十二行各自的花纹才同时看得见——上图下半部分就是这么画的。

一阶差分与二阶差分

单独一帧的 13 个数是一张静态快照。要让模型看见声音怎么变,再算它的变化率:

delta_mfccs = librosa.feature.delta(mfccs)
delta2_mfccs = librosa.feature.delta(mfccs,
                                     order=2)
delta_mfccs   shape = (13, 1292)
delta2_mfccs  shape = (13, 1292)

行数列数一个没变:每一帧的 13 个系数,各自多了一个变化率、再多一个变化率的变化率。

它沿哪个轴差分

这个矩阵有两个方向:往右是时间,往下是系数序号。差分沿哪个方向做,结果完全不同——而这一点,一行 shape 是看不出来的。

做个实验:只把第 200 帧那一列整体加 10,别的地方一个字不动,然后看 delta 变了哪几列。

扰动一帧之后,MFCC 只变一列,它的 delta 变了八列
沿时间轴(默认 axis=-1)
    变了 8 列(第 196—204 列)
沿系数轴(axis=0)
    变了 1 列(第 200 列)

默认那一行摊开在九列宽的范围里,说明它确实是沿时间在差分。

而且它不是「后一帧减前一帧」。librosa.feature.delta 默认 width=9:把连续九帧摆在一起拟合一条直线,取那条直线的斜率。 所以一帧的改动会摊到左右各四帧上。

九列宽,真正变了的却只有八列——正中间的第 200 列纹丝不动。 因为这个斜率算的是「右边四帧减左边四帧」,被改的那一帧自己的权重正好是 0:一帧的 delta 完全不取决于它自己。

那它和逐帧相减差多少?两者逐行相关系数平均 0.40,方向一致;幅度只有 0.32 倍——九帧一起看,孤立的抖动被摊平了。窗口左右对称,所以估出来的斜率对准的还是中间那一帧,不会前后偏移。

拼成每帧 39 个数

mfccs_features = np.concatenate(
    (mfccs, delta_mfccs, delta2_mfccs))
shape = (39, 1292)

。这就是上一课末尾提到的那个 39。

是每一帧 39 个数,不是整段 39 个数。 这段 30 秒的音乐一共 1292 帧,所以矩阵里躺着 50388 个数。前 13 行说这一帧的谱包络长什么样,中间 13 行说它在往哪个方向变,后 13 行说这个变化本身在加快还是放慢。

这 13 个数分得开三种风格吗

回到第 07 课和第 09 课那条线:一个特征算出来了,它到底分不分得开?

三段音乐各取前 30 秒,每段 1292 帧,各算 13 个系数的均值和标准差。

三种风格在第 1—12 个 MFCC 系数上的均值曲线与一个标准差的带子

看「最难分的那一对,中心差了几个标准差」这个数——越大越分得开:

系数  debussy  redhot   duke   最难一对
  1     168.4    76.3  153.6     0.31
  3      19.3    44.9   58.2     0.38
  4      -0.3    21.1   32.3     0.39
  7      -2.8    10.5  -14.5     0.67

最分得开的第 7 个系数,最难分的那一对也只差 0.67 个标准差,离「两堆点不重叠」还差得远;13 个里有 7 个不到 0.2,基本重叠在一起。单看任何一个系数,三种风格都分不干净。

那 13 个一起看呢?拿最笨的办法验一下:每种风格算一个中心,再把每一帧判给最近的那个中心。

只用第 7 个系数        判对 70.3%
用最分得开的 4 个系数   判对 96.4%
13 个一起用           判对 95.3%

乱猜是 33.3%。这不是一个正经的分类器——没有分训练集,也没调任何参数——它只说明一件事:这些数放在一起,比其中任何一个都强得多。

还有一点值得注意:挑出来的 4 个(96.4%)比 13 个全用(95.3%)还高一点。 那 7 个基本重叠的系数,对这个笨办法来说只是噪声。特征多不等于分得更开——第 07、09 课得到的也是这个结论,只是这里的差距大得多。

这一课得到了什么,下一课接着讲什么

这一课把上一课手写的那条链子交给了库,然后做了一件 .shape 做不到的事:把一行拆回四步,逐位对齐。

五个可以自己核对的数:

  1. 四个默认值全改对之后,自己算的和库的最大差是 0.0000,逐位相同;只改梅尔带数一个,还差 226.2。
  2. power_to_db 默认截掉的那 4.66% 格子,会让每一行系数都变,最大差 16.7231。
  3. 第 0 个系数和「这一帧的对数梅尔谱平均有多高」相关系数 1.00,绝对值是其余十二个平均的 10.4 倍。
  4. 只改一帧,delta 在九列宽的范围里变了 8 列,正中间那一列不动。
  5. 最分得开的单个系数把三种风格判对 70.3%,13 个一起用 95.3%。

下一课离开 MFCC,回到频谱本身:一段声音的频谱,除了整张画出来,还能压成几个直接能算的数吗? 比如高频部分占了多少能量、频谱的重心落在哪里——这些数比 39 行小得多,却也能提供另一组可比较的证据。