摘要:實數網絡在圖像領域取得極大成功,但在音頻中,信號特徵大多數是複數,如頻譜等。簡單分離實部虛部,或者考慮幅度和相位角都丟失了複數原本的關係。論文按照複數計算的定義,設計了深度複數網絡,能對複數的輸入數據進行卷積、激活、批規範化等操作。在音頻信號的處理中,該網絡應該有極大的優勢。這裏對論文提出的幾種複數操作進行介紹,並給出簡單的pytorch實現方法。
目錄
- 關於複數卷積操作
- 關於複數激活函數
- 關於複數Dropout
- 關於複數權重初始化
- 關於複數BatchNormalization
- 關於複數反向傳播
主要參考文獻
【1】“DEEP COMPLEX NETWORKS”
【2】論文作者給出的源碼地址,使用Theano後端的Keras實現:“https://github.com/ChihebTrabelsi/deep_complex_networks”
【3】“https://github.com/wavefrontshaping/complexPyTorch” 給出了部分操作的Pytorch實現版本。
1. 關於複數卷積操作
複數卷積通過如下形式定義:
在具體實現中,可以使用下圖所示的簡單結構實現。
因此,利用pytorch的nn.Conv2D
實現,嚴格遵守上面複數卷積的定義式:
class ComplexConv2d(Module):
def __init__(self, input_channels, output_channels,
kernel_sizes=3, stride=1, padding=0, dilation=0, groups=1, bias=True):
super(ComplexConv2d, self).__init__()
self.conv_real = Conv2d(input_channels, output_channels, kernel_size, stride, padding, dilation, groups, bias)
self.conv_imag = Conv2d(input_channels, output_channels, kernel_size, stride, padding, dilation, groups, bias)
def forward(self, input_real, input_imag):
assert input_real.shape == input_imag.shape
return self.conv_real(input_real) - self.conv_imag(input_imag), self.conv_imag(input_real) + self.conv_real(input_imag)
2. 關於複數激活函數
論文作者提出了一種複數激活函數——CReLU,同時又介紹了另外兩種複數激活函數——modReLU和zReLU。
複數激活函數需要滿足Cauchy-Riemann Equations才能進行復數微分操作,其中
- modReLU不滿足;
- zReLU在實部爲0,虛部大於0或者虛部爲0,實部大於0的時候不滿足,即在x和y的正半軸不滿足;
- CReLU只在實部虛部同時大於零或同時小於零的時候滿足,即在第2、4象限不滿足;
以作者提出的CReLU的實現爲例:
from torch.nn.functional import relu
def complex_relu(input_real, input_imag):
return relu(input_real), relu(input_imag)
3. 關於複數Dropout
複數Dropout個人感覺實部虛部需要同時置0,作者源碼中沒用到Dropout層。
所以【3】中的Dropout好像不太對。實現起來和普通的一樣,共享兩個Dropout層的參數即可。
4. 關於複數權重初始化
作者介紹了兩種初始化方法的複數形式:Glorot、He初始化。
如原文介紹的,初始化時需要對幅度和相位分別初始化。
利用Pytorch實現,直接在源碼上進行修改,_calculate_correct_fan()
源碼中有。
def complex_kaiming_normal_(tensor_real, tensor_imag, a=0, mode='fan_in'):
fan = _calculate_correct_fan(tensor_real, mode)
s = 1. / fan
rng = RandomState()
modulus = rng.rayleigh(scale=s, size=tensor.shape)
phase = rng.uniform(low=-np.pi, high=np.pi, size=tensor.shape)
weight_real = modulus * np.cos(phase)
weight_imag = modulus * np.sin(phase)
weight = np.concatenate([weight_real, weight_imag], axis=-1)
with torch.no_grad():
return torch.tensor(weight)
上述計算過程參考【1】和【2】,但這種兩個張量的初始化不知道怎麼直接使用init這樣的形式,只能配合如下手動初始化方法食用。
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
# 第一一個卷積層,我們可以看到它的權值是隨機初始化的
w=torch.nn.Conv2d(2,2,3,padding=1)
print(w.weight)
# 第一種方法
print("1.使用另一個Conv層的權值")
q=torch.nn.Conv2d(2,2,3,padding=1) # 假設q代表一個訓練好的卷積層
print(q.weight) # 可以看到q的權重和w是不同的
w.weight=q.weight # 把一個Conv層的權重賦值給另一個Conv層
print(w.weight)
# 第二種方法
print("2.使用來自Tensor的權值")
ones=torch.Tensor(np.ones([2,2,3,3])) # 先創建一個自定義權值的Tensor,這裏爲了方便將所有權值設爲1
w.weight=torch.nn.Parameter(ones) # 把Tensor的值作爲權值賦值給Conv層,這裏需要先轉爲torch.nn.Parameter類型,否則將報錯
print(w.weight)
5. 關於複數BatchNormalization
首先肯定不能用常規的BN方法,否則實部和虛部的分佈就不能保證了。但正如常規BN方法,首先要對輸入進行0均值1方差的操作,只是方法有所不同。
通過下面的操作,可以確保輸出的均值爲0,協方差爲1,相關爲0。
同時BN中還有和兩個參數。因此最終的BN結果如下。
核心的計算步驟如下,在訓練階段首先去均值並對方差進行歸一化,然後按公式計算BN輸出,完整代碼參考【3】。
class ComplexBatchNorm2d(_ComplexBatchNorm):
def forward(self, input_r, input_i):
assert(input_r.size() == input_i.size())
assert(len(input_r.shape) == 4)
exponential_average_factor = 0.0
if self.training and self.track_running_stats:
if self.num_batches_tracked is not None:
self.num_batches_tracked += 1
if self.momentum is None: # use cumulative moving average
exponential_average_factor = 1.0 / float(self.num_batches_tracked)
else: # use exponential moving average
exponential_average_factor = self.momentum
if self.training:
# calculate mean of real and imaginary part
mean_r = input_r.mean([0, 2, 3])
mean_i = input_i.mean([0, 2, 3])
mean = torch.stack((mean_r,mean_i),dim=1)
# update running mean
with torch.no_grad():
self.running_mean = exponential_average_factor * mean\
+ (1 - exponential_average_factor) * self.running_mean
input_r = input_r-mean_r[None, :, None, None]
input_i = input_i-mean_i[None, :, None, None]
# Elements of the covariance matrix (biased for train)
n = input_r.numel() / input_r.size(1)
Crr = 1./n*input_r.pow(2).sum(dim=[0,2,3])+self.eps
Cii = 1./n*input_i.pow(2).sum(dim=[0,2,3])+self.eps
Cri = (input_r.mul(input_i)).mean(dim=[0,2,3])
with torch.no_grad():
self.running_covar[:,0] = exponential_average_factor * Crr * n / (n - 1)\
+ (1 - exponential_average_factor) * self.running_covar[:,0]
self.running_covar[:,1] = exponential_average_factor * Cii * n / (n - 1)\
+ (1 - exponential_average_factor) * self.running_covar[:,1]
self.running_covar[:,2] = exponential_average_factor * Cri * n / (n - 1)\
+ (1 - exponential_average_factor) * self.running_covar[:,2]
else:
mean = self.running_mean
Crr = self.running_covar[:,0]+self.eps
Cii = self.running_covar[:,1]+self.eps
Cri = self.running_covar[:,2]#+self.eps
input_r = input_r-mean[None,:,0,None,None]
input_i = input_i-mean[None,:,1,None,None]
# calculate the inverse square root the covariance matrix
det = Crr*Cii-Cri.pow(2)
s = torch.sqrt(det)
t = torch.sqrt(Cii+Crr + 2 * s)
inverse_st = 1.0 / (s * t)
Rrr = (Cii + s) * inverse_st
Rii = (Crr + s) * inverse_st
Rri = -Cri * inverse_st
input_r, input_i = Rrr[None,:,None,None]*input_r+Rri[None,:,None,None]*input_i, \
Rii[None,:,None,None]*input_i+Rri[None,:,None,None]*input_r
if self.affine:
input_r, input_i = self.weight[None,:,0,None,None]*input_r+self.weight[None,:,2,None,None]*input_i+\
self.bias[None,:,0,None,None], \
self.weight[None,:,2,None,None]*input_r+self.weight[None,:,1,None,None]*input_i+\
self.bias[None,:,1,None,None]
return input_r, input_i