本文对笔者关于 CNN 因果性的理解作以记录。如有表述不当之处欢迎批评指正。欢迎任何形式的转载,但请务必注明出处。

目录

  • 1. 引言
  • 2. 因果性
  • 3. Encoder 架构
    • 3.1. 一层卷积网络
      • 3.1.1. 网络的输入,输出以及 label
      • 3.1.2. 决定网络的因果性
      • 3.1.3. 补零操作
      • 3.1.4. 总结
    • 3.2. 多层卷积网络
      • 3.2.1. 输入层补零
      • 3.2.2. 逐层补零
      • 3.2.3. 两种补零方式对比
      • 3.2.4. 逐层补零需要注意的地方
      • 3.2.5. 总结
    • 3.3. 总结
  • 4. Encoder-Decoder 架构
    • 4.1. 转置卷积
    • 4.2. 一层卷积(转置卷积)网络
      • 4.2.1. 控制网络的因果性和感受野
      • 4.2.2. 总结
    • 4.3. 多层卷积(转置卷积)网络
    • 4.4. 总结
  • 5. 后记

1. 引言

音频信号处理已经进入了神经网络时代,而 CNN 由于其强大的建模能力,已被广泛地应用在了各种音频信号处理网络中,像最近几届 DNS Challenge 的冠军所提出的网络均大量使用了 CNN。与图像处理不同,音频信号处理在大多数应用场景下都需要满足实时性要求,比如在线会议场景,直播场景等。满足实时性一般需要满足以下两个条件:算法是因果的且计算复杂度要低。本文主要讨论如何控制 CNN 网络的因果性,并不对其计算复杂度做过多讨论。

从网络架构来说,以 CNN 为主的音频信号处理网络可以分为以下两大类:

  • 只包含卷积的 Encoder 架构
  • 即包含卷积又包含转置卷积的 Encoder-Decoder 架构

本文对上述两种网络架构均进行了讨论,并进一步讨论了在实际 coding 过程中应该怎么做以及可能遇到的一些问题。

2. 因果性

在讨论之前,先简单介绍下系统的因果性。因果性是指:当一个系统当前时刻的输出只依赖于当前时刻的输入以及(或)过去时刻的输入时,该系统就是因果的。与之相对应的,当一个系统当前时刻的输出会依赖未来时刻的输入时,该系统就是非因果的。

可见,在实时应用中,音频信号处理算法往往需要是因果的,以对当前时刻的输入信号作出及时的响应。

3. Encoder 架构

Encoder 架构主要由卷积网络构成, 该节先从最简单的一层卷积网络开始讨论,接着拓展到多层卷积网络的情况。

3.1. 一层卷积网络

考虑一个只包含了一层二维卷积的网络,该二维卷积在时间维度上的参数为:kernel = 3,stride = 1。

3.1.1. 网络的输入,输出以及 label

假设此时输入给网络一个时长为 5 帧的音频信号,那么输出信号的时长应该为 5 - kernel + 1 = 5 - 3 + 1 = 3 帧,网络的输出比输入少了 2 帧(这 2 帧作为音频信号的上下文信息被网络消耗掉了)。那么问题来了:输出信号只有 3 帧,但 label 信号和输入信号一样都是 5 帧,输出信号的时长不等于 label 信号的时长,该怎么计算 loss 函数那?

3.1.2. 决定网络的因果性

一个自然而然的想法就是:从 label 信号中选取 3 帧信号,保证 label 信号和输出信号的时长一样不就得了?没错,确实是这样做,而且正是该选取过程决定了卷积网络的因果性。

以目前所讨论的这个网络来说,有 3 种选取方案,如图1 所示。

图1 一层卷积网络因果性示例

  

  • 图1(a) 选取 label 信号中的最后 3 帧,这种选取方式确定了该网络是因果的。因为从图中可以看出当前时刻的输出帧只利用了当前时刻的输入帧以及历史的两帧信息;
  • 图1(b) 选取 label 信号中最中间的 3 帧,这种选取方式确定了该网络是非因果的。因为从图中可以看出当前时刻的输出帧除了利用当前时刻的输入帧以及历史的 1 帧信息外,还使用了未来的 1 帧信息;
  • 图1(c) 选取 label 信号中最前面的 3 帧,这种选取方式确定了该网络是非因果的。因为从图中可以看出当前时刻的输出帧除了利用当前时刻的输入帧外,还使用了未来的 2 帧信息;

3.1.3. 补零操作

通过上述分析,相信读者已经熟悉该如何决定卷积网络的因果性,以及如何控制卷积网络的感受野。那么在实际 coding 的过程中,当拥有了输入数据和等时长的 label 数据之后,该怎么做,才能实现图1 所示的 3 种方式那?主要有以下两种方法:

  • 丢 label 中的数据;
  • 给输入数据补零。

丢 label 中的数据:这种实现方式其实就是直接根据 3.1.2. 小节所分析的来做的。丢弃 label 信号中的前两帧信号得到的就是图1(a) 所表示的;丢弃 label 信号中的第一帧和最后一帧信号得到的就是图1(b) 所表示的;丢弃 label 信号中的最后两帧信号得到的就是图1(c) 所表示的;

给输入数据补零:那么可不可以保持 label 数据不变,通过其他方法来实现那?当然可以!正所谓 “山不过来,我就过去”,既然要求 label 数据不变,那就变输入数据,给输入数据补零。相信读者在一些论文或者开源代码中也见过补零这种实现方式。补零相比上述实现方式有什么好处那?笔者考虑了一下,主要想出了以下两点说得过去的原因:

  • 为了不浪费数据。从上述分析可以看到,当卷积网络在时间维度上的 kernel 大于 1 时,网络的输出时长会比输入时长少 kernel - 1 帧。而 kernel 越大,网络的输出时长就越小。为了能将辛辛苦苦生成的训练数据全部用上,可以给输入信号补 kernel - 1 帧的零,这样就能使得网络的输出时长等于输入时长(有效的输入时长,即补零之前的时长);
  • 为了和实时推理代码保持一致。 考虑一个实时应用会遇到的一个场景:假设某个音频处理算法运行在时频域(其 STFT 的窗长和帧长均为 20ms,帧移为 10ms),当算法接收到第一帧 10ms 信号的时候,为了做 STFT,往往需要在前面补一帧 10ms 的零,凑足 20ms。在因果卷积网络中补零的原因之一也是为了和这种场景保持一致。

不同的补零方式会导致不同的因果关系,下面就详细说说,具体该怎么补零(读者可自行画图分析):

  • 为了实现图1(a),可以在输入数据的最前面补两帧零;
  • 为了实现图1(b),可以在输入数据的最前面和最后面各补一帧零;
  • 为了实现图1(c),可以在输入数据的最后面补两帧零。

3.1.4. 总结

从对一层卷积网络的分析过程来看,可以得到以下两个重要结论:

  • 从 label 信号中选取所需信号的过程决定了卷积网络的因果性和感受野;
  • 在具体 coding 的过程中,可以通过给输入数据补零来实现不同的因果关系。

3.2. 多层卷积网络

本节考虑包含了两层二维卷积的网络,且每层二维卷积在时间维度上的参数均为:kernel = 3,stride = 1。类似于图1,现给出相应的两层 CNN 网络的因果性示例图,如图2 所示。

图2 二层卷积网络因果性示例

  
需要注意的是,图2 只给出了其中的三种输出结果,还有其余两种输出结果读者可自行分析。通过图2 可以看出:

  • 图2(a) 表示的是非因果网络,因为输出帧除了利用当前时刻的输入帧外,还使用了未来的 4 帧信息;
  • 图2(b) 表示的是非因果网络,因为输出帧除了利用当前时刻的输入帧以及历史的 2 帧信息外,还使用了未来的 2 帧信息;
  • 图2(c) 表示的是因果网络,因为输出帧只利用了当前时刻的输入帧以及历史的 4 帧信息。

与 3.1. 节的分析过程一样,在实际 coding 过程中,该如何补零才能使得两层 CNN 网络实现图 2 中所示的因果性那?对于两层 CNN 网络来说,笔者能想到两种补零方式:输入层补零和逐层补零。下面就详细讨论下这两种方式,以及各自的优缺点。

3.2.1. 输入层补零

输入层补零其实就是把两层卷积网络等效于一层卷积网络。上述两层卷积网络可以等效于 kernel = 5,stride = 1 的一层卷积网络(只是从因果性和感受野角度来说是可以等效的)。从 3.1.3. 小节的分析可知:

  • 给输入数据的最后面补四帧零,可以实现图2(a);
  • 给输入数据的最前面和最后面各补两帧零,可以实现图2(b);
  • 给输入数据的最前面补四帧零,可以实现图2(c)。

也就是说输入层补零只在输入数据上补零,网络隐藏层不需要补零;下面要讨论的逐层补零除了在输入层补零外也在网络隐藏层补零。

3.2.2. 逐层补零

具体来说,逐层补零在实际 coding 的过程中:

  • 为了实现图2(a),可以在输入层和隐藏层的最后面分别补两帧零;
  • 为了实现图2(b),可以在输入层和隐藏层的最前面和最后面分别补一帧零;
  • 为了实现图2(c),可以在输入层和隐藏层的最前面分别补两帧零。

逐层补零只考虑当前层。以图2(a) 为例,输入层为 5 帧数据,为了使第一层 CNN 的输出也为 5 帧数据,并且保持只看当前帧和未来帧的因果性,需要给输入层的最后面补 kernel - 1 = 3 - 1 = 2 帧零。同样,为了使第二层 CNN 的输出也为 5 帧数据,并且保持只看当前帧和未来帧的因果性,需要给第一层 CNN 输出(隐藏层)的最后面补 kernel - 1 = 3 - 1 = 2 帧零。可以看出逐层补零保证每层 CNN 的输出时长都和 label 信号的时长保持一致。

3.2.3. 两种补零方式对比

两种不同的补零方式可以说体现出了两种不同的思维方式。

  • 输入层补零:将多层 CNN 网络当成一个整体看待。先分析这个整体总共需要补多少零,需要怎么补,然后直接只在输入层操作就行;
  • 逐层补零:将多层 CNN 网络中的每一层当成单独的个体看待。根据当前层的 kernel 和 stride 求出当前层需要补多少零,根据整个网络要满足的因果性和感受野确定当前层的零该怎么补,然后在当前层直接操作就行。 逐层补零能保证每层的输出时长和输入时长(有效的输入时长,即补零之前的时长)一致,继而保证整个网络的输出时长和输入时长一致。当前层只需要干好它自己要干的事就好,不必考虑其他层。层与层之间的关系可以用一句古语来概括 “各人自扫门前雪,莫管他人瓦上霜”。

下面再举个额外的例子,用以说明在实际 coding 过程中两种补零方式分别该怎么做。比如开发者要搭建一个两层 CNN 网络,且第一层 CNN 的参数配置为:kernel = 3,stride = 1,第二层 CNN 的参数配置为:kernel = 2,stride = 1。现在想要使这个网络的输出帧只能看未来一帧的输入信息。

输入层补零的做法
这个两层 CNN 网络可以等效为 kernel = 4,stride = 1 的一层 CNN 网络。因此,需要给输入数据补 kernel - 1 = 4 - 1 = 3 帧零。为了只看未来一帧的输入信息,需要将 2 帧零补在输入数据的最前面,1 帧零补在输入数据的最后面。

逐层补零的做法
可知输入层需要补 3 - 1 = 2 帧零,第一层 CNN 的输出需要补 2 - 1 = 1 帧零。接下来就是确定每层的零具体该怎么补。为了只看未来一帧的输入信息,可以有以下两种补零方案:

  • 输入层需要补的 2 帧零全补在最前面,且第一层 CNN 的输出需要补的 1 帧零补在最后面;这种方式使得第一层 CNN 没利用未来的输入信息,而第二层 CNN 利用了未来一帧的输入信息。
  • 输入层需要补的 2 帧零在最前面和最后面各补 1 帧,且第一层 CNN 的输出需要补的 1 帧零补在最前面;这种方式使得第一层 CNN 利用了未来一帧的输入信息,第二层 CNN 没有利用未来的输入信息。

可以看到,在具体 coding 的时候,输入层补零这种方式实现方法唯一,而逐层补零这种方式有多种实现方法。

3.2.4. 逐层补零需要注意的地方

假设一个这样的使用场景:1)模型是按照逐层补零的方式实现的因果模型;2)在验证模型效果的时候(即推理的时候)直接使用 load() 函数加载模型,而且按照线上运行的方式那样,每次给模型输入一帧数据,然后模型输出一帧数据;这个时候得到的输出结果很大概率上是不正确的。这是因为在训练模型的时候,模型的输入数据时长往往远远大于所补零的时长,除了输入数据中比较靠前的数据看到的历史信息是补的零之外,其余输入数据看到的历史信息基本都是有效的语音信息。而在推理过程中,每次只输入一帧数据,这导致输入的所有帧看到的历史信息都是补的零,而不是有效的语音信息。因此,输出结果大概率是不正确的。为了避免这种状况,1)只是为了验证模型效果的话,在推理的时候,可以增加模型的输入数据时长,而不是输入一帧数据;2)如果非要按照线上运行的方式那样,输入一帧数据输出一帧数据,那么就需要用 c/python 实现推理过程,与此同时需要维护好每层网络的输入 buffer。这是一项工作量较大的工程。
同样的场景,输入层补零的方式解决起来相对简单些,只需维护好输入层的 buffer 就行。

3.2.5. 总结

本节以二层卷积网络为例,分析了多层网络如何通过补零来实现不同的因果性。在实际 coding 过程中主要有输入层补零和逐层补零两种方式,可根据需要选择适合的补零方式。

3.3. 总结

对于 Encoder 架构而言,通过上述的分析可知,不管是一层卷积网络还是多层卷积网络,不管是输入层补零还是逐层补零,如果想要实现的网络是因果的,那么零应该全部补在最前面,如果想要实现的网络是非因果的,那么应该在最后面补零。在最前面补了几帧零,整个网络就使用了多少帧的历史信息。同理,在最后面补了几帧零,整个网络就使用了多少帧的未来信息。

4. Encoder-Decoder 架构

Encoder-Decoder 是一种对称的网络结构。其中,Encoder 包含的是卷积网络,Decoder 包含的是转置(或称之为反转/逆)卷积网络。而且一般 Decoder 中每个转置卷积网络层和与之对应的 Encoder 中的卷积网络层的参数配置(kernel,stride……)是完全一样的。本节先简单回顾下转置卷积,然后分析一层卷积(转置卷积)网络的因果性,最后分析多层卷积(转置卷积)网络的因果性。

4.1. 转置卷积

与卷积相反,转置卷积会增加输入特征的维度。本节以 kernel = 3,stride = 1 的一层转置卷积网络为例进行说明。

当给同等参数配置下的一层卷积网络输入 5 帧的音频信号时,它会输出 3 帧的音频信号。而如果将这 3 帧音频信号输入给一层转置卷积网络,那么将会输出 5 帧音频信号。图3 展示了该转置卷积网络的计算过程。

图3 转置卷积网络计算过程示例

  
由于 kernel = 3,所以转置卷积网络每一帧输入所对应的输出都是 3 帧,将对应位置上的所有帧的输出相加得到转置卷积网络的最终输出。比如输出中的第二帧等于第一帧输出的第二个元素与第二帧输出的第一个元素之和,如图3 中红圈所示。

4.2. 一层卷积(转置卷积)网络

考虑一个 kernel = 3,stride = 1 的一层卷积(转置卷积)网络。假设网络的输入是时长为 5 帧的音频信号,那么经过卷积网络之后,时长变为 3 帧;接着,该 3 帧信号经过转置卷积网络之后,输出时长又变为 5 帧,与输入信号和 label 信号的时长是相等的。因此,此时可以正常计算 loss 函数。注意在此过程中,是没有额外的补零或者其他什么操作的。那么,此时网络的因果性和感受野是怎么样的那?可以通过图4 分析一下整个计算过程。以输出的第 3 帧为例,可以看出计算该帧的过程中,除了使用输入的当前帧和历史两帧信息外,还使用了未来两帧信息。其余输出帧所使用的输入帧信息可以通过图4 中的箭头走向分析得到。可以看到,此时网络是非因果网络,且使用了未来两帧的信息。

图4 一层卷积网络和转置卷积网络计算过程示例

4.2.1. 控制网络的因果性和感受野

那么,在具体 coding 过程中,该怎么做才能得到一个因果网络,并且使得该网络输出的有效时长还是 5 帧那?答案是输入数据补零,输出数据丢帧。图5 展示了该计算过程。

图5 因果网络计算过程示例

  
在输入数据的最前面补两帧零之后,网络的总体输入变为了 7 帧,经过卷积和转置卷积之后,网络的输出依然是 7 帧。但是 label 数据时长和补零前的输入数据时长一样,都是 5 帧。此时,为了计算 loss 函数,需要从输出的 7 帧数据中丢弃 2 帧数据。该丢弃过程在决定网络的因果性和感受野方面也起到了重要作用。

从图5 中的箭头走向可以分析出,为了得到因果网络,只能丢弃输出数据中的最后两帧;同样可以分析出,为了得到非因果网络,且使用未来一帧的输入信息,只能丢弃输出数据中的第一帧和最后一帧;为了得到非因果网络,且使用未来两帧的输入信息,只能丢弃输出数据中最前面的两帧。

4.2.2. 总结

本节介绍了如何通过给输入数据补零和丢弃输出数据来达到控制网络因果性和感受野的目的。本节给出的示例只是在输入数据的最前面补零,读者可按照同样的方法分析一下给输入数据最后面补零,或者最前面和最后面都补零会达到什么样的效果。

4.3. 多层卷积(转置卷积)网络

可使用 3.2. 小节中的分析方法,对多层卷积(转置卷积)网络的因果性和感受野进行分析。即,可以把多层卷积(转置卷积)网络当成一个整体,只在输入数据和输出数据上做相应的补零或丢帧操作。也可以把网络中的每一层当成一个单独的个体,在每一层上做相应的操作,通过控制每一层的因果性和感受野来控制整个网络的因果性和感受野。具体分析过程,此处不在赘述。

如果想要使 Encoder-Decoder 架构的网络在实际推理的过程中输入一帧数据输出一帧数据,那么,同样也会遇到 3.2.4. 小节所提到的问题。不管训练的时候采用的是哪种补零(或者丢帧)方式,都需要动手实现网络的推理过程,并维护好每一层的输入输出 buffer。

4.4. 总结

对于 Encoder-Decoder 架构的网络来说,为了控制网络的因果性和感受野,在实际 coding 的过程中,可以通过给输入数据补零,并且丢弃输出数据中的帧来实现。

5. 后记

前前后后历时一周,终于抽时间把这篇很久之前就想写的博客写完了。笔者是想到哪写到哪,有的点可能也没考虑到,如果读者感觉哪块写的不清楚或者有不同的意见和建议,欢迎提出,大家一起探讨。

“先把书读厚,再把书读薄”。再次感谢读者阅读到此处,和笔者一起经历了 “先把书读厚” 的过程。下面献上一张图片,大家一起 “再把书读薄”。


这幅图是 Ke Tan,DeLiang Wang 的论文 “A Convolutional Recurrent Neural Network for Real-time speech enhancement” 中的图,笔者就是根据这幅图理解的因果卷积。从下往上看,这幅图可以理解为 Encoder 的计算过程,从上往下看,这幅图可以理解为 Decoder 的计算过程。只要能完全理解这幅图片,什么 Encoder 架构,Encoder-Decoder 架构,什么输入层补零,逐层补零都不是问题,可以做到 “一图走天下”。

在 CNN 的基础上能构建的网络形式有很多,比如设置 stride>1,dilation>1,比如网络中加入残差链接等。在 coding 的过程中,需要认真分析搭建的网络结构,以正确地控制网络的因果性和感受野。

周末愉快!

谈谈音频信号处理中 CNN 的因果性相关推荐

  1. 深度学习在音频信号处理领域中的进展(截止至2019年5月)

    最近在arXiv上看到一篇关于深度学习在音频信号处理中的进展的文章,感觉写的不错,所以根据自己的理解和大家分享一下.如果有些地方博主理解的不对,或者解释不清,可以在下面评论,博主会一一回复. 论文链接 ...

  2. 学术干货:看清华教授如何将深度学习引入音频信号处理

    https://www.toutiao.com/a6687760578368242180/ 人工智能论坛如今浩如烟海,有硬货.有干货的讲座却百里挑一."AI未来说·青年学术论坛"系 ...

  3. 声纹技术(二):音频信号处理基础【模拟信号(连续)--采样-->数字信号(离散)--量化-->振幅简化为整数--编码-->二进制序列】【WAV音频格式】【SoX】【分帧-加窗-】

    2.1 欲懂声纹,先学音频 从学科分类上讲,声纹技术是语音信号处理的一个分支,而语音信号处理则属于音频信号处理这个大类. 语音信号和音频信号,这二者的区别在于: 语音信号专指人类说话时所发出的具有社会 ...

  4. 傅里叶变换音频可视化_音频可视化中的信号处理方案

    声明: 原创文章,未经允许不得转载. 音频可视化是一个"听"起来非常"美"好的话题,其复杂程度很大程度上依赖视觉方案(一些例子),不同的视觉方案决定了你的技术方 ...

  5. 【数字信号处理】傅里叶变换性质 ( 傅里叶变换频移性质示例 | PCM 音频信号处理 | 使用 matlab 进行频移操作 )

    文章目录 一.PCM 音频信号处理 二.要点说明 一.PCM 音频信号处理 给定一段 PCM 音频数据 , 封装到 .WAV 文件 中 , 此时的信号 是 实信号 , 每个 PCM 音频采样都是一个 ...

  6. Python音频信号处理 2.使用谱减法去除音频底噪

    使用谱减法去除音频底噪 上一篇文章我主要分享了短时傅立叶变换及其逆变换在python中的实现,有兴趣的可以阅读一下该篇文章,地址如下: Python音频信号处理 1.短时傅里叶变换及其逆变换 那么在本 ...

  7. 深度学习在物理层信号处理中的应用研究

    摘要:本文主要介绍基于深度学习的物理层应用,并提出一种基于深度Q网络(DQN)的MIMO系统位置信息验证方案,接收者在多变未知的信道环境下利用深度Q网络不断更新. 01引言 随着移动流量呈现的爆发式增 ...

  8. 音频信号处理——DTW

    音频信号处理--DTW 标签(空格分隔): 音频处理 DTW全名Dynamic Time Warping由日本科学家 Itakura 提出,用于衡量两个长度不同时间序列的相似度.应用也十分广泛,主要是 ...

  9. 音频信号处理——基音周期

    音频信号处理--基音周期 标签(空格分隔): 音频处理 一.简介 1.1 什么是基音?什么是基音周期 基音顾名思义就是声音的基础.这里我们主要讨论人的发声,根据声带震动的方式的不同,将声音信号分为清音 ...

最新文章

  1. 数据蒋堂 | 大清单报表应当怎么做?
  2. 03-spring bean
  3. 《组织行为学》_09 彩虹理论:人力资本越来越高怎么办?
  4. 亚马逊AWS本月第三次出现数据中心断电故障,Coinbase、Slack等受影响
  5. 程序员最害怕的5件事,你中招了吗?
  6. 专题解读 |「知识图谱」领域近期值得读的 6 篇顶会论文
  7. mysql列别_MySQL基础及CRUD
  8. 43行代码AC——例题6-8 树(Tree,UVa 548)——解题报告
  9. Requests库实战(三)---爬取豆瓣电影详细信息
  10. java mysql ssl警告_连接到MySQL数据库时有关SSL连接的警告
  11. Qt文档阅读笔记-Text QML Type官方解析及实例
  12. 官宣!vue.ant.design 低调上线
  13. java ant 详解
  14. Spring MVC 常用注解
  15. C语言经典例题(菜鸟教程100例)
  16. Android小项目--2048小游戏,flutter人脸识别插件
  17. 域控下发脚本_让系统及时的通过域用户脚本自动的打补丁
  18. 开放有限元分析计算平台介绍
  19. OSChina 周日乱弹 ——愿你在天堂也能写代码
  20. org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; 前言中不允许有内容。

热门文章

  1. 数据结构_线索二叉树(C语言)
  2. C++多态及重载(overload),覆盖(override),隐藏(hide)的区别
  3. c语言getline函数什么意思,详解C++ cin.getline函数
  4. matplotlib绘图形状、颜色汇总
  5. 计算机硬件知识哪里学,计算机硬件基础知识 计算机硬件是由哪几部分组成的...
  6. layui tab --隐藏删除图标
  7. Android仿支付宝订单确认和支付
  8. 蚂蚁金服:支付宝核心账务去Oracle实践
  9. SQL server查询时找不到表,显示:对象名 ‘XXX‘ 无效。
  10. 【RL-TCPnet网络教程】第37章 RL-TCPnet之FTP客户端