pytorch訓練時給隱層網絡特徵圖添加隨機噪聲導致訓練效率低的解決辦法

問題描述:

最近在訓練說話人識別模型x-vector時,在網絡結構中,將frame-level特徵進行 statistics pooling前給TDNN輸出的特徵添加隨機噪聲以提高模型的性能,但踩了個坑導致訓練效率非常低,用nvidia-smi命令查看GPU的效率,時而100%時而0%,這其中肯定有問題。通過排查發現不是數據加載的問題,而是模型定義的問題。下面詳細分析。

模型定義的代碼如下:

class xvecTDNN(nn.Module):
	def __init__(self, numSpks, p_dropout):
		...
		
    def forward(self, x, eps):
        # Note: x must be (batch_size, feat_dim, chunk_len)
        x = self.dropout_tdnn1(self.bn_tdnn1(F.relu(self.tdnn1(x))))
        x = self.dropout_tdnn2(self.bn_tdnn2(F.relu(self.tdnn2(x))))
        x = self.dropout_tdnn3(self.bn_tdnn3(F.relu(self.tdnn3(x))))
        x = self.dropout_tdnn4(self.bn_tdnn4(F.relu(self.tdnn4(x))))
        x = self.dropout_tdnn5(self.bn_tdnn5(F.relu(self.tdnn5(x))))
        if self.training:
            x = x + torch.randn(x.size()).to(self.device)*eps
        stats = torch.cat((x.mean(dim=2), x.std(dim=2)), dim=1)
        x = self.dropout_fc1(self.bn_fc1(F.relu(self.fc1(stats))))
        x = self.dropout_fc2(self.bn_fc2(F.relu(self.fc2(x))))
        x = self.fc3(x)
        return x

原因分析:

導致訓練效率低下的主要原因是添加噪聲的那行代碼:

		x = x + torch.randn(x.size()).to(self.device)*eps

可以發現這行代碼是先利用CPU生成一個與x的shape一樣的矩陣,然後再傳送到GPU。由於CPU處理速度數據的速度遠比GPU慢,並且CPU和GPU之間的數據傳輸需要時間,所以這一步的實現嚴重影響了模型訓練的速度。利用time()函數打印每一步的時間發現,這一行代碼執行的時間居然要2秒多!而整個模型跑前向後向加起來總共也就0.02秒,拖慢了一百多倍!不可忍!

解決辦法:

查閱PyTorch文檔後發現,torch.randn(shape, out)可以直接在GPU中生成隨機數,只要shape是tensor.cuda.Tensor類型即可。這樣,就不需要在 CPU 中生成大矩陣再傳給GPU,而直接在GPU中生成就行,這樣就節省了大量的時間了。因此,下面的代碼就可以進行這種操作了。

        if self.training:
            shape=x.size()
            noise = torch.cuda.FloatTensor(shape) if torch.cuda.is_available() else torch.FloatTensor(shape)
            torch.randn(shape, out=noise)
            x += noise*eps

因此,將模型的代碼替換如下,即可實現既能添加噪聲又能高效訓練:

class xvecTDNN(nn.Module):
	def __init__(self, numSpks, p_dropout):
		...
		
    def forward(self, x, eps):
        # Note: x must be (batch_size, feat_dim, chunk_len)
        x = self.dropout_tdnn1(self.bn_tdnn1(F.relu(self.tdnn1(x))))
        x = self.dropout_tdnn2(self.bn_tdnn2(F.relu(self.tdnn2(x))))
        x = self.dropout_tdnn3(self.bn_tdnn3(F.relu(self.tdnn3(x))))
        x = self.dropout_tdnn4(self.bn_tdnn4(F.relu(self.tdnn4(x))))
        x = self.dropout_tdnn5(self.bn_tdnn5(F.relu(self.tdnn5(x))))
        if self.training:
            shape=x.size()
            noise = torch.cuda.FloatTensor(shape) if torch.cuda.is_available() else torch.FloatTensor(shape)
            torch.randn(shape, out=noise)
            x += noise*eps
        stats = torch.cat((x.mean(dim=2), x.std(dim=2)), dim=1)
        x = self.dropout_fc1(self.bn_fc1(F.relu(self.fc1(stats))))
        x = self.dropout_fc2(self.bn_fc2(F.relu(self.fc2(x))))
        x = self.fc3(x)
        return x

修改代碼後,用nvidia-smi查看GPU使用情況,會發現GPU的利用率一直在100%
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章