前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深度学习基础知识(六)--LPCNet之GRU稀疏化

深度学习基础知识(六)--LPCNet之GRU稀疏化

原创
作者头像
languageX
发布2022-11-01 11:24:22
1.4K0
发布2022-11-01 11:24:22
举报
文章被收录于专栏:计算机视觉CV

上文介绍了LPCNet的算法原理和工程,本文主要介绍LPCNet的加速方案之稀疏化处理。

我们首先了解GRU,然后再看作者如何对GRU进行稀疏化,来提升网络性能。

GRU(门控循环单元)流程

GRU的整个流程如下图所示:

重置门和更新门:重置门和更新门的输入为当前时刻输入X_t 和上一个时刻隐藏状态H_{t-1} ,通过全连接层和激活层得到输出

R_tZ_t ,sigmoid激活所以值域0,1。

候选隐藏状态:当前时刻R_t 和上一时刻隐藏状态做乘法,结果和当前时刻输入结合,通过全连接和激活得到当前时刻候选隐藏状态,激活为tanh,s所以值域[-1,1]。

\widetilde{H_t}= tanh(X_tW_{xh} + (R_t \cdot H_{t-1})W_{hh} + b_h)

隐藏状态:最后,时刻t的隐藏状态是由当前时刻的Z_t 结合上一时刻的隐藏状态和当前时刻的候选隐藏状态组合得到。

H_t = Z_{t} \cdot H_{t-1} + (1-Z_t) \cdot \widetilde{H_t}

以上就是GRU的流程,其中W为权重参数,B为偏差参数,具体参数量我们在下一节详细介绍。

GRU(门控循环单元)实现

keras实现GRU源码:

https://github.com/keras-team/keras/blob/v2.10.0/keras/layers/rnn/gru.py#L394-L905

注意其中DNNGRU和GRU实现的区别:

为了使用CuDNNGRU训练,兼容GRU,必须设置reset_after=Truerecurrent_activation="sigmoid"

GRU( recurrent_activation="sigmoid", reset_after='true')

另外bias的shape也有点区别

GRU(门控循环单元)参数量

了解了GRU的过程,下面通过LPCNet里的使用来看GRUA的参数。

通过以上介绍,GRU参数如下:

W_{xr},W_{xz},W_{xh} X*W的权重:d(输入维度)*units(输出维度)*n_gates(3个门)

W_{hr},W_{hz},W_{hh} H*W的权重:units*units*3(3个门)

bias: 2*units*n_gates

在LPCNet的GRUA:

代码语言:javascript
复制
rnn = GRU(rnn_units1, return_sequences=True, return_state=True, recurrent_activation="sigmoid", reset_after='true', name='gru_a', stateful=True, recurrent_constraint = constraint, recurrent_regularizer=quant)

其中rnn_units1=384,GRUA输入N,L,512

三个循环无关的W_x参数量:512*_384*_3

三个循环相关的W_h参数量:384*_384*_3

另外还有bias:384*3*2

参数量:512*384*3+384*384*3+384*3*2=1034496

稀疏化

下面我们介绍LPCNet中如何实现稀疏化

代码如下:

代码语言:javascript
复制
#Training from scratch
sparsify = lpcnet.Sparsify(2000, 40000, 400, density)
grub_sparsify = lpcnet.SparsifyGRUB(2000, 40000, 400,args.grua_size, grub_density)

表示2000之前batch迭代不进行稀疏化;2000-40000每间隔400个迭代进行一次稀疏化;40000后每个迭代进行稀疏化,这里通过加一个callback对象sparsify。

针对gru_a层的三个和循环相关重置门,更新门,隐藏状态参数的W_{hr},W_{hz},W_{hh}进行稀疏(3个384*384),但是和循环无关的Wx不需要稀疏。稀疏过程中保留对角位置的权重,其他位置根据阈值砍掉小权重。

代码语言:javascript
复制
def on_batch_end(self, batch, logs=None):
        # print("\n callback batch number", self.batch)
        self.batch += 1
        # t_start前的batch不进行稀疏,t_start--t_end每interval稀疏一次;t_end之后每个迭代都稀疏
        if self.quantize or (self.batch > self.t_start and (self.batch-self.t_start) % self.interval == 0) or self.batch >= self.t_end:
            # 针对gru_a层的三个和循环相关重置门,更新门,隐藏状态参数的W_hr,W_hu,W_hs进行稀疏(3个384*384),但是和循环无关的Wx不需要稀疏
            layer = self.model.get_layer('gru_a')
            w = layer.get_weights()
            p = w[1]
            # print("gru_a layer p", p.shape)
            # p.shape--[384,1152]
            nb = p.shape[1]//p.shape[0]
            N = p.shape[0]
            # print("nb = ", nb, ", N = ", N);
            # default:(0.05, 0.05, 0.2)
            # print ("density = ", self.final_density)
            for k in range(nb):
                density = self.final_density[k]
                if self.batch < self.t_end and not self.quantize:
                    r = 1 - (self.batch-self.t_start)/(self.t_end - self.t_start)
                    density = 1 - (1-self.final_density[k])*(1 - r*r*r)
                A = p[:, k*N:(k+1)*N]
                # 保留对角位置的权重,其他位置根据阈值砍掉小权重
                A = A - np.diag(np.diag(A))
                #This is needed because of the CuDNNGRU strange weight ordering
                A = np.transpose(A, (1, 0))
                # 按4*4块进行稀疏
                L=np.reshape(A, (N//4, 4, N//8, 8))
                S=np.sum(L*L, axis=-1)
                S=np.sum(S, axis=1)
                SS=np.sort(np.reshape(S, (-1,)))
                thresh = SS[round(N*N//32*(1-density))]
                mask = (S>=thresh).astype('float32')
                mask = np.repeat(mask, 4, axis=0)
                mask = np.repeat(mask, 8, axis=1)
                mask = np.minimum(1, mask + np.diag(np.ones((N,))))
                #This is needed because of the CuDNNGRU strange weight ordering
                mask = np.transpose(mask, (1, 0))
                p[:, k*N:(k+1)*N] = p[:, k*N:(k+1)*N]*mask
                print("thresh", thresh, np.mean(mask))
            if self.quantize and ((self.batch > self.t_start and (self.batch-self.t_start) % self.interval == 0) or self.batch >= self.t_end):
                if self.batch < self.t_end:
                    threshold = .5*(self.batch - self.t_start)/(self.t_end - self.t_start)
                else:
                    threshold = .5
                quant = np.round(p*128.)
                res = p*128.-quant
                mask = (np.abs(res) <= threshold).astype('float32')
                p = mask/128.*quant + (1-mask)*p

            w[1] = p
            layer.set_weights(w)

最后在保存模型参数dump_data时,只保存非0值和索引,非结构化系数矩阵的保存减少.c文件的大小。

嵌入式

LPCNet对于输入2400,3进行了embedding,这里作者对embedding做了一个可微和非整数查找的技巧。对输入信号2400,3通过256,128的矩阵embed,得到2400,3,128--2400,3*128的矩阵。

总结LPCnet的加速技巧:

  1. 采样网络一帧计算一次
  2. embed_sig使用embed将cpcm信号(1*3)embed到1*384
  3. gru和循环无关的与W_{xr},W_{xz},W_{xh}矩阵运算提前合并计算好,因为X要先嵌入再和W_x相乘,可以将两个操作一起计算
  4. 与循环相关的W_{wr},W_{wz},W_{wh}进行了稀疏化,dump_data时只保存非0值和索引

参考资料:

https://zh-v1.d2l.ai/chapter_recurrent-neural-networks/gru.html

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • GRU(门控循环单元)流程
  • GRU(门控循环单元)实现
  • GRU(门控循环单元)参数量
  • 稀疏化
  • 嵌入式
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档