时延神经网络(TDNNs)常用于声纹识别领域,例如著名的X-VECTOR基础结构就是TDNNs;它可以视作一维卷积神经网络1(1-d CNNs),甚至有人认为TDNNs是CNNs/LeNet-5的早期版本。
计算机视觉盛行的如今,数字图像处理中二维卷积(空域滤波/模版运算/互相关运算2)对于我们而言也不再陌生,无非就是中心数值等于按元素相乘后再相加嘛。
可是,到底如何理解一维卷积?它和二维卷积又有什么联系?
不妨通过实验,一探究竟。
首先,我们暂时忽略通道。假定输入为 [ 1 , 2 , − 1 , 1 , − 3 ] [1, 2, -1, 1, -3] [1,2,−1,1,−3],核函数为 [ 1 , 0 , − 1 ] [1, 0, -1] [1,0,−1](卷积核是一维的,移动方向也自然是一维的),如果卷积核在输入向量上移动,请问结果是多少呢?可以看看我有没有算错。
顺带一提,这里输入是一行五列的矩阵。不过换一个角度,我们也可以认为每次输入的是3帧信息,并按照某种权重加和汇入1个新帧。
这里的3是卷积核尺寸( k e r n e l s i z e kernel\ size kernel size),从共享参数的全连接输入的角度说,3也是上下文所涵盖的范围, ( n − 1 , n + 1 ) o r { n − 1 , n , n + 1 } (n-1,n+1) \ or \ \{n-1, n, n+1\} (n−1,n+1) or {n−1,n,n+1}。
# 输入
import torch.nn as nn
from torch.nn import Parameter
import torch.nn.functional as F
import torch
import numpy as np
import math
input = torch.tensor([
[[1, 2, -1, 1, -3], ]
], dtype=torch.float)
conv1d = nn.Conv1d(in_channels=1,
out_channels=1,
kernel_size=3,
stride=1,
padding=0,
dilation=1,
bias=False,
padding_mode='zeros')
# 卷积核
conv1d.weight = nn.Parameter(torch.tensor([
[[1., 0, -1], ]
]))
# 输出
conv1d(input)
tensor([[[2., 1., 2.]]], grad_fn=<ConvolutionBackward0>)
但是,一般说来,TDNNs输入的声学特征并不是一个向量,而是一个矩阵,横轴(宽度)与时间有关【不固定】,纵轴(高度)和频率相关【固定】,所以它有时也被称为时频谱图(Spectrogram)。
你还可以在 CHROME MUSIC LAB:SPECTROGRAM 上,亲自动手玩玩可视化自己的或自然的声音。
当然,上文所指的广义的时频谱也分很多种(常见的有MFCC、MFBank、MelSpec等),对应各种不同的处理方式(例如,模拟人耳对频率、声强的非线性,平稳信号等),不过它们一般都要经历短时傅里叶变换(分帧、加窗、离散傅里叶变换)。
我们暂时忽略输出通道。从一维卷积的视角说,[频率,时间] 应当被视作 [通道数,输入长度],即每个通道各有一个卷积核或一组共享权重,每个通道独立计算再把各个通道的结果相加3。
假定输入为双通道(例如表示高频和低频) [ [ 1 , 2 , − 1 , 1 , − 3 ] , [ 3 , 1 , 0 , − 1 , 2 ] ] [[1, 2, -1, 1, -3],[3, 1, 0, -1, 2]] [[1,2,−1,1,−3],[3,1,0,−1,2]]各通道上的核函数分别为 [ 1 , 0 , − 1 ] [1, 0, -1] [1,0,−1]和 [ 0.5 , 0 , 0.5 ] [0.5, 0, 0.5] [0.5,0,0.5],请问这次结果又是多少呢?相信你肯定比我算的快。
# 输入
input = torch.tensor([
[[1, 2, -1, 1, -3],
[3, 1, 0, -1, 2]]
], dtype=torch.float)
conv1d = nn.Conv1d(in_channels=2,
out_channels=1,
kernel_size=3,
stride=1,
padding=0,
dilation=1,
bias=False,
padding_mode='zeros')
# 卷积核
conv1d.weight = nn.Parameter(torch.tensor([
[[1., 0, -1],
[0.5, 0, 0.5]],
]))
# 输出
conv1d(input)
tensor([[[3.5000, 1.0000, 3.0000]]], grad_fn=<ConvolutionBackward0>)
最后,该如何理解输出通道呢?其实无非就是把单输出通道的那一份卷积核,扩增几倍而已。例如,仍然假定输入为双通道 [ [ 1 , 2 , − 1 , 1 , − 3 ] , [ 3 , 1 , 0 , − 1 , 2 ] ] [[1, 2, -1, 1, -3],[3, 1, 0, -1, 2]] [[1,2,−1,1,−3],[3,1,0,−1,2]]不过此时我们考虑简化的双输出通道的情况:
第一份输出:各输入通道上的核函数分别为 [ 1 , 0 , − 1 ] [1, 0, -1] [1,0,−1]和 [ 0.5 , 0 , 0.5 ] [0.5, 0, 0.5] [0.5,0,0.5];
第二份输出:各输入通道上的核函数仍然是 [ 1 , 0 , − 1 ] [1, 0, -1] [1,0,−1]和 [ 0.5 , 0 , 0.5 ] [0.5, 0, 0.5] [0.5,0,0.5]。
那么,我们就可以得到双倍的输出。
# 输入
input = torch.tensor([
[[1, 2, -1, 1, -3],
[3, 1, 0, -1, 2]]
], dtype=torch.float)
conv1d = nn.Conv1d(in_channels=2,
out_channels=2,
kernel_size=3,
stride=1,
padding=0,
dilation=1,
bias=False,
padding_mode='zeros')
# 卷积核
conv1d.weight = nn.Parameter(torch.tensor([
[[1., 0, -1],
[0.5, 0, 0.5]],
[[1., 0, -1],
[0.5, 0, 0.5]],
]))
# 输出
conv1d(input)
tensor([[[3.5000, 1.0000, 3.0000],
[3.5000, 1.0000, 3.0000]]], grad_fn=<ConvolutionBackward0>)
其实,如果将各个通道的一维卷积核拼接起来:所谓的一维卷积在各通道上的加和,完全可以用二维卷积核 [ c h a n n e l s , k e r n e l _ s i z e ] [channels, kernel\_ size] [channels,kernel_size]按元素相乘后再相加理解。
所以,许多博主所认为的一维卷积不代表卷积核是一维的,只代表卷积核的移动方向是一维的这种说法,从某种角度说,也有一定的道理。
不过值得注意的是,Pytorch中Conv1d和Conv2d输入维度不一样。
例如,
一维卷积 [ b a t c h , c h a n n e l s , l e n g t h ] [batch, channels, length] [batch,channels,length],
二维卷积 [ b a t c h , c h a n n e l s , h e i g h t , w i d t h ] [batch, channels, height, width] [batch,channels,height,width]。
# 输入
input = torch.tensor([
[[1, 2, -1, 1, -3],
[3, 1, 0, -1, 2]]
], dtype=torch.float)
input=input.unsqueeze(dim=1)
conv2d = nn.Conv2d(in_channels=1,
out_channels=2,
kernel_size=(input.shape[2],3),
stride=1,
padding=0,
dilation=1,
bias=False,
padding_mode='zeros')
# 卷积核
conv2d.weight = nn.Parameter(torch.tensor([
[[[1., 0, -1],
[0.5, 0, 0.5]]],
[[[1., 0, -1],
[0.5, 0, 0.5]]],
]))
# 输出
conv2d(input)
tensor([[[[3.5000, 1.0000, 3.0000]],
[[3.5000, 1.0000, 3.0000]]]], grad_fn=<ConvolutionBackward0>)
Daniel Povey et al. “Semi-Orthogonal Low-Rank Matrix Factorization for Deep Neural Networks…” conference of the international speech communication association(2018): n. pag. ↩︎
http://zh.d2l.ai/chapter_convolutional-neural-networks/conv-layer.html ↩︎
http://zh.d2l.ai/chapter_convolutional-neural-networks/channels.html#id2 ↩︎