精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

如何才能信任你的深度學習代碼?

人工智能 深度學習
非常詳細的介紹并演示了如何將單元測試用于深度學習,讓你的代碼更加可信。深度學習是一門很難評估代碼正確性的學科。隨機初始化、龐大的數據集和權重的有限可解釋性意味著,要找到模型為什么不能訓練的確切問題,大多數時候都需要反復試驗。

 深度學習是一門很難評估代碼正確性的學科。隨機初始化、龐大的數據集和權重的有限可解釋性意味著,要找到模型為什么不能訓練的確切問題,大多數時候都需要反復試驗。在傳統的軟件開發中,自動化單元測試是確定代碼是否完成預期任務的面包和黃油。它幫助開發人員信任他們的代碼,并在引入更改時更加自信。一個破壞性的更改將會被單元測試檢測到。

[[338647]]

從GitHub上許多研究庫的情況來看,深度學習的實踐者們還不喜歡這種方法。從業者不知道他們的代碼是否正常工作,他們能接受嗎?通常,由于上述三個原因,學習系統的每個組件的預期行為并不容易定義。然而,我相信實踐者和研究人員應該重新考慮他們對單元測試的厭惡,因為它可以幫助研究過程更加順利。你只需要學習如何信任你的代碼。

顯然,我不是第一個,也不是最后一個談論用于深度學習的單元測試的人。如果你對這個話題感興趣,你可以看看這里:

  • A Recipe for Training Neural Networks by Andrej Karpathy
  • How to Unit Test Deep Learning by Sergios Karagiannakos

這篇文章的靈感來自于上面提到的,可能還有很多我現在想不起來的。為了在討論中增加一些內容,我們將重點關注如何編寫可重用的單元測試,這樣就可以“不去自己重復自己“。

我們的例子將測試用PyTorch編寫的系統的組件,該系統在MNIST 上訓練可變自動編碼器(VAE)。你可以在github.com/tilman151/unittest_dl上找到本文中的所有代碼。

什么是單元測試?

如果您熟悉單元測試,可以跳過此部分。對于其他人,我們將看到Python中的單元測試是什么樣子的。為了簡單起見,我們將使用內置的包unittest,而不是其他花哨的包。

一般來說,單元測試的目的是檢查代碼是否正確地運行。通常(我也為此感到內疚很長一段時間),你會看到這樣的東西在一個文件的結尾:

 

  1. if __name__ == 'main'
  2.     net = Network() 
  3.     x = torch.randn(4, 1, 32, 32) 
  4.     y = net(x) 
  5.     print(y.shape) 

如果直接執行該文件,則代碼片段將構建一個網絡,執行前向傳遞并打印輸出的形狀。這樣,我們就可以看到向前傳播是否會拋出錯誤,以及輸出的形狀是否可信。如果將代碼分發到不同的文件中,則必須手動運行每個文件,并檢查打印到控制臺的內容。更糟糕的是,這個代碼片段有時會在運行后被刪除,當有變化時被重寫。

原則上,這已經是一個基本的單元測試。我們所要做的就是將它形式化一點,使它能夠輕松地自動運行。它看起來是這樣的:

 

  1. import unittest 
  2.  
  3. class MyFirstTest(unittest.TestCase): 
  4.     def test_shape(self): 
  5.         net = Network() 
  6.         x = torch.randn(4, 1, 32, 32) 
  7.         y = net(x) 
  8.         self.assertEqual(torch.Size((10,)), y.shape) 

unittest包的主要組件是類TestCase。單個單元測試是TestCase子類的成員函數。在我們的例子中,包將自動檢測類MyFirstTest并運行函數'test_shape。如果滿足assertEqual調用的條件,則測試成功。否則,或者如果它崩潰,測試將失敗。

我需要測試些什么?

現在我們已經了解了單元測試是如何工作的,下一個問題是我們應該測試什么。下面你可以看到我們的例子的代碼結構:

 

  1. |- src 
  2.    |- dataset.py 
  3.    |- model.py 
  4.    |- trainer.py 
  5.    |- run.py 

我們將測試每個文件中的功能除了run.py,因為它只是我們程序的入口點。

Dataset

我們在例子中使用的數據集是torchvisionMNIST類。因此,我們可以假設像加載圖像和訓練/測試分割這樣的基本功能可以正常工作。然而,MNIST類為配置提供了充足的機會,因此我們應該測試是否正確配置了所有內容。dataset.py文件包含一個名為MyMNIST的類,它有兩個成員變量。成員train_data有torchvisionMNIST類的一個實例,該實例被配置為加載數據的訓練部分,而test_data 中的實例加載測試部分。兩種方法都將每幅圖像每邊填充2個像素,并將像素值歸一化在[- 1,1]之間。此外,train_data 對每個圖像應用隨機旋轉來增強數據。

數據的形狀

為了繼續使用上面的代碼片段,我們將首先測試數據集是否輸出了我們想要的形狀。圖像的填充意味著,它們現在的大小應該是32x32像素。我們的測試看起來是這樣的:

 

  1. def test_shape(self): 
  2.     dataset = MyMNIST() 
  3.     sample, _ = dataset.train_data[0] 
  4.     self.assertEqual(torch.Shape((1, 32, 32)), sample.shape) 

現在我們可以確定我們的padding是我們想要的。這可能看起來很瑣碎,你們中的一些人可能會認為我在測試這個方面很迂腐,但是我不知道我有多少次因為我搞不清楚填充函數是如何工作的而導致了形狀錯誤。像這樣的簡單測試編寫起來很快,并且可以為你以后省去許多麻煩。

數據的縮放

我們配置的下一件事是數據的縮放。在我們的例子中,這非常簡單。我們希望確保每個圖像的像素值在[- 1,1]之間。與之前的測試相反,我們將對數據集中的所有圖像進行測試。通過這種方式,我們可以確定我們關于如何縮放數據的假設對于整個數據集是有效的。

 

  1. def test_scaling(self): 
  2.     dataset = MyMNIST() 
  3.     for sample, _ in dataset.train_data: 
  4.         self.assertGreaterEqual(1, sample.max()) 
  5.         self.assertLessEqual(-1, sample.min()) 
  6.         self.assertTrue(torch.any(sample < 0)) 
  7.         self.assertTrue(torch.any(sample > 0)) 

如你所見,我們不僅要測試每個圖像的最大值和最小值是否在范圍內。我們還通過斷言測試是否存在大于零和小于零的值,我們將值縮放到[0,1]。這個測試之所以有效,是因為我們可以假設MNIST中的每個圖像都覆蓋了整個范圍的值。對于更復雜的數據,比如自然圖像,我們需要一個更復雜的測試條件。如果你的縮放基于數據的統計信息,那么測試一下是否只使用訓練部分來計算這些統計信息也是一個好主意。

數據增強

增加訓練數據可以極大地幫助提高模型的性能,特別是在數據量有限的情況下。另一方面,我們不會增加我們的測試數據,因為我們想要保持我們的模型的評估確定性。這意味著,我們應該測試我們的訓練數據是否增加了,而我們的測試數據沒有。敏銳的讀者會在這一點上注意到一些重要的東西。到目前為止,我們的測試只涵蓋了訓練數據。這是需要強調的一點:

始終在訓練和測試數據上運行測試

僅僅因為你的代碼在數據的一個部分上工作,并不能保證在另一個部分上不存在未檢測到的bug。對于數據增強,我們甚至希望為每個部分斷言代碼的不同行為。

對于我們的增強問題,一個簡單的測試現在是加載一個樣本兩次,然后檢查兩個版本是否相等。簡單的解決方案是為我們的每一個部分寫一個測試函數:

 

  1. def test_augmentation_active_train_data(self): 
  2.     dataset = MyMNIST() 
  3.     are_same = [] 
  4.     for i in range(len(dataset.train_data)): 
  5.         sample_1, _ = dataset.train_data[i] 
  6.         sample_2, _ = dataset.train_data[i] 
  7.         are_same.append(0 == torch.sum(sample_1 - sample_2)) 
  8.  
  9.     self.assertTrue(not all(are_same)) 
  10.  
  11. def test_augmentation_inactive_test_data(self): 
  12.     dataset = MyMNIST() 
  13.     are_same = [] 
  14.     for i in range(len(dataset.test_data)): 
  15.         sample_1, _ = dataset.test_data[i] 
  16.         sample_2, _ = dataset.test_data[i] 
  17.         are_same.append(0 == torch.sum(sample_1 - sample_2)) 
  18.  
  19.     self.assertTrue(all(are_same)) 

這些函數測試我們想要測試的內容,但是,正如你所看到的,它們幾乎就是重復的。這有兩個主要的缺點。首先,如果在測試中需要更改某些內容,我們必須記住在兩個函數中都要更改。其次,如果我們想添加另一個部分,例如一個驗證部分,我們將不得不第三次復制測試。要解決這個問題,我們應該將測試功能提取到一個單獨的函數中,然后由真正的測試函數調用兩次。重構后的測試看起來像這樣:

 

  1. def test_augmentation(self): 
  2.     dataset = MyMNIST() 
  3.     self._check_augmentation(dataset.train_data, active=True
  4.     self._check_augmentation(dataset.test_data, active=False
  5.  
  6. def _check_augmentation(self, data, active): 
  7.     are_same = [] 
  8.     for i in range(len(data)): 
  9.         sample_1, _ = data[i] 
  10.         sample_2, _ = data[i] 
  11.         are_same.append(0 == torch.sum(sample_1 - sample_2)) 
  12.  
  13.     if active: 
  14.         self.assertTrue(not all(are_same)) 
  15.     else
  16.         self.assertTrue(all(are_same)) 

_check_augmentation函數斷言給定的數據集是否進行了增強,并有效地刪除代碼中的重復。函數本身不會由unittest包自動運行,因為它不是以test_開頭的。因為我們的測試函數現在真的很短,我們把它們合并成一個組合函數。它們測試了增強是如何工作的這一單一的概念,因此應該屬于相同的測試函數。但是,通過這個組合,我們引入了另一個問題。如果測試失敗了,現在很難直接看到哪一個部分失敗了。這個包只告訴我們組合函數的名稱。進入subTest函數。TestCase類有一個成員函數subTest,它可以在一個測試函數中標記不同的測試組件。這樣,包就可以準確地告訴我們測試的哪一部分失敗了。最后的函數是這樣的:

 

  1. def test_augmentation(self): 
  2.     dataset = MyMNIST() 
  3.     with self.subTest(split='train'): 
  4.         self._check_augmentation(dataset.train_data, active=True
  5.     with self.subTest(split='test'): 
  6.         self._check_augmentation(dataset.test_data, active=False

現在我們有了一個無重復、精確定位、可重用的測試功能。我們在此所使用的核心原則可以應用到我們在前面幾節中編寫的所有其他單元測試中。你可以在附帶的存儲庫中看到結果測試。

數據的加載

數據集的最后一種類型的單元測試與我們的例子并不完全相關,因為我們使用的是內置數據集。無論如何我們都會把它包括進來,因為它涵蓋了我們學習系統的一個重要部分。通常,你將在dataloader類中使用數據集,該類處理批處理并可以并行化加載。因此,測試你的數據集在單進程和多進程模式下是否與dataloader一起工作是一個好主意。考慮到我們所學到的增強測試,測試函數如下所示:

 

  1. def test_single_process_dataloader(self): 
  2.     dataset = MyMNIST() 
  3.     with self.subTest(split='train'): 
  4.         self._check_dataloader(dataset.train_data, num_workers=0) 
  5.     with self.subTest(split='test'): 
  6.         self._check_dataloader(dataset.test_data, num_workers=0) 
  7.  
  8. def test_multi_process_dataloader(self): 
  9.     dataset = MyMNIST() 
  10.     with self.subTest(split='train'): 
  11.         self._check_dataloader(dataset.train_data, num_workers=2) 
  12.     with self.subTest(split='test'): 
  13.         self._check_dataloader(dataset.test_data, num_workers=2) 
  14.  
  15. def _check_dataloader(self, data, num_workers): 
  16.     loader = DataLoader(data, batch_size=4, num_workers=num_workers) 
  17.     for _ in loader: 
  18.         pass 

函數_check_dataloader不會對加載的數據進行任何測試。我們只是想檢查加載過程是否沒有拋出錯誤。理論上,ni 也可以檢查諸如正確的批大小或填充的序列數據的不同長度。因為我們為dataloader使用了最基本的配置,所以可以省略這些檢查。

同樣,這個測試可能看起來瑣碎而沒有必要,但是讓我給你一個例子,在這個簡單的檢查中節省了我的時間。這個項目需要從pandas的dataframes中加載序列數據,并從這些datafames上的滑動窗口中構造樣本。我們的數據集太大了,無法裝入內存,所以我們必須按需加載數據模型,并從中剪切出所請求的序列。為了提高加載速度,我們決定用一個LRU cache來緩存一些數據文件。它在我們早期的單進程實驗中如預期的那樣工作,因此我們決定將它包含在代碼庫中。結果是,這個緩存不能很好地用于多進程,但是我們的單元測試提前發現了這個問題。在使用多進程時,我們停用了緩存,避免了以后出現令人不快的意外。

最后要注意的

有些人可能已經在我們的單元測試中看到了另一個重復的模式。每個測試對訓練數據運行一次,對測試數據運行一次,產生相同的四行代碼:

 

  1. with self.subTest(split='train'): 
  2.     self._check_something(dataset.train_data) 
  3. with self.subTest(split='test'): 
  4.     self._check_dataloader(dataset.test_data) 

也完全有理由消除這種重復。不幸的是,這將涉及到創建一個高階函數,以函數_check_something作為參數。有時,例如對于增強測試,我們還需要向_check_something函數傳遞額外的參數。最后,所需的編程構造將引入更多的復雜性,并模糊要測試的概念。一般的規則是,為了可讀性和可重用性,讓你的測試代碼盡可能在需要的范圍內變復雜。

Model

模型可以說是學習系統的核心組件,通常需要是完全可配置的。這意味著,還有很多東西需要測試。幸運的是,PyTorch中用于神經網絡模型的API非常簡潔,大多數實踐者都非常嚴格地使用它。這使得為模型編寫可重用的單元測試相當容易。

我們的模型是一個簡單的VAE,由一個全連接的編碼器和解碼器組成。前向函數接受輸入圖像,對其進行編碼,執行重新參數化操作,然后將隱編碼解碼為圖像。雖然相對簡單,但這種變換可以演示幾個值得進行單元測試的方面。

模型的輸出形狀

我們在本文開頭看到的第一段代碼是幾乎每個人都要做的測試。我們也已經知道這個測試是如何寫成單元測試的。我們要做的唯一一件事就是添加要測試的正確形狀。對于一個自動編碼器,就簡單的判斷和輸入的形狀是否相同:

 

  1. @torch.nograd() 
  2. def test_shape(self): 
  3.     net = model.MLPVAE(input_shape=(1, 32, 32), bottleneck_dim=16) 
  4.     inputs = torch.randn(4, 1, 32, 32) 
  5.     outputs = net(x) 
  6.     self.assertEqual(inputs.shape, outputs.shape) 

同樣,這很簡單,但有助于找到一些最惱人的bug。例如,在將模型輸出從拉平的表示中reshape時忘記添加通道維度。

我們最后增加的測試是torch.nograd 。它告訴PyTorch這個函數不需要記錄梯度,并給我們一個小的加速。對于每個測試來說,它可能不是很多,但是你永遠不知道需要編寫多少。同樣,這是另一個可引用的單元測試智慧:

讓你的測試更快。否則,沒有人會想要運行它們。

單元測試應該在開發期間非常頻繁地運行。如果你的測試運行時間很長,那么你可以跳過它們。

模型的移動

在CPU上訓練深度神經網絡在大多數時候都非常慢。這就是為什么我們使用GPU來加速它。為此,我們所有的模型參數必須駐留在GPU上。因此,我們應該斷言我們的模型可以在設備(CPU和多個GPU)之間正確地移動。

我們可以用一個常見的錯誤來說明我們的例子VAE中的問題。這里你可以看到bottleneck函數,執行重新參數化的技巧:

 

  1. def bottleneck(self, mu, log_sigma): 
  2.     noise = torch.randn(mu.shape) 
  3.     latent_code = log_sigma.exp() * noise + mu 
  4.  
  5.     return latent_code 

它取隱先驗的參數,從標準高斯分布中采樣一個噪聲張量,并使用參數對其進行變換。這在CPU上運行沒有問題,但當模型移動到GPU時失敗。問題是噪音張量是在CPU內存中創建的,因為它是默認的,并沒有移動到模型所在的設備上。一個簡單的錯誤和一個簡單的解決方案。我們用noise = torch.randn_like(mu)替換了這行有問題的代碼。這就產生了一個與張量mu相同形狀和在相同設備上的噪聲張量。

幫助我們盡早捕獲這些bug的測試:

 

  1. @torch.no_grad() 
  2. @unittest.skipUnless(torch.cuda.is_available(), 'No GPU was detected'
  3. def test_device_moving(self): 
  4.     net = model.MLPVAE(input_shape=(1, 32, 32), bottleneck_dim=16) 
  5.     net_on_gpu = net.to('cuda:0'
  6.     net_back_on_cpu = net_on_gpu.cpu() 
  7.      
  8.     inputs = torch.randn(4, 1, 32, 32) 
  9.  
  10.     torch.manual_seed(42) 
  11.     outputs_cpu = net(inputs) 
  12.     torch.manual_seed(42) 
  13.     outputs_gpu = net_on_gpu(inputs.to('cuda:0')) 
  14.     torch.manual_seed(42) 
  15.     outputs_back_on_cpu = net_back_on_cpu(inputs) 
  16.  
  17.     self.assertAlmostEqual(0., torch.sum(outputs_cpu - outputs_gpu.cpu())) 
  18.     self.assertAlmostEqual(0., torch.sum(outputs_cpu - outputs_back_on_cpu)) 

我們把網絡從一個CPU移動到另一個CPU,然后再移動回來,只是為了確保正確。現在我們有了網絡的三份拷貝(移動網絡復制了它們),并使用相同的輸入張量向前傳遞。如果網絡被正確移動,前向傳遞應該在不拋出錯誤的情況下運行,并且每次產生相同的輸出。

為了運行這個測試,我們顯然需要一個GPU,但也許我們想在筆記本電腦上做一些快速測試。如果PyTorch沒有檢測到GPU,unittest.skipUnless 可以跳過測試。這樣可以避免將測試結果與失敗的測試混淆。

你還可以看到,我們在每次通過之前固定了torch的隨機種子。我們必須這樣做,因為VAEs是非確定性的,否則我們會得到不同的結果。這說明了深度學習代碼單元測試的另一個重要概念:

在測試中控制隨機性。

如果你不能確保你的模型能到邊界情況,你如何測試你的模型的一個罕見邊界條件?如何確保模型的輸出是確定性的?你如何知道一個失敗的測試是由于隨機的偶然還是由于你引入的bug ?通過手動設置深度學習框架的種子,可以消除函數中的隨機性。此外,還應該將CuDNN設置為確定性模式。這主要影響卷積,但無論如何是一個好主意。

注意確定正在使用的所有框架的種子。Numpy和內置的Python隨機數生成器有它們自己的種子,必須分別設置。有一個這樣的函數是很有用的:

 

  1. def make_deterministic(seed=42): 
  2.     # PyTorch 
  3.     torch.manual_seed(seed) 
  4.     if torch.cuda.is_available(): 
  5.         torch.backends.cudnn.deterministic = True 
  6.         torch.backends.cudnn.benchmark = False 
  7.      
  8.     # Numpy 
  9.     np.random.seed(seed) 
  10.      
  11.     # Built-in Python 
  12.     random.seed(seed) 

模型到采樣獨立性

在99。99%的情況下,你都想用隨機梯度下降的方式來訓練你的模型。你給你的模型一個minibatch的樣本,并計算他們的平均損失。批量處理訓練樣本假設你的模型可以處理每個樣本,也就是你可以獨立的把樣本喂給模型。換句話說,你的batch中的樣本在你的模型處理時不會相互影響。這個假設是很脆弱的,如果在一個錯誤的張量維度上進行錯誤的reshape或aggregation,就會打破這個假設。

下面的測試通過執行與輸入相關的前向和后向傳遞來檢查樣本的獨立性。在對這個batch做平均損失之前,我們把損失乘以零。如果我們的模型保持樣本獨立性,這將導致一個零梯度。唯一的事情,我們必須斷言,如果只有masked的樣本梯度是零:

 

  1. def test_batch_independence(self): 
  2.     inputs = torch.randn(4, 1, 32, 32) 
  3.     inputs.requires_grad = True 
  4.     net = model.MLPVAE(input_shape=(1, 32, 32), bottleneck_dim=16) 
  5.  
  6.     # Compute forward pass in eval mode to deactivate batch norm 
  7.     net.eval() 
  8.     outputs = net(inputs) 
  9.     net.train() 
  10.  
  11.     # Mask loss for certain samples in batch 
  12.     batch_size = inputs[0].shape[0] 
  13.     mask_idx = torch.randint(0, batch_size, ()) 
  14.     mask = torch.ones_like(outputs) 
  15.     mask[mask_idx] = 0 
  16.     outputs = outputs * mask 
  17.  
  18.     # Compute backward pass 
  19.     loss = outputs.mean() 
  20.     loss.backward() 
  21.  
  22.     # Check if gradient exists and is zero for masked samples 
  23.     for i, grad in enumerate(inputs.grad): 
  24.         if i == mask_idx: 
  25.             self.assertTrue(torch.all(grad == 0).item()) 
  26.         else
  27.             self.assertTrue(not torch.all(grad == 0)) 

如果你準確地閱讀了代碼片段,你會注意到我們將模型設置為evaluation模式。這是因為batch normalization違反了我們上面的假設。進程均值和標準差的處理交叉污染了我們batch中的樣本,所以我們通過evaluation模式停止了對樣本的更新。我們可以這樣做,因為我們的模型在訓練和評估模式中表現相同。如果你的模型不是這樣的,你將不得不找到另一種方法來禁用它進行測試。一個選項是用instance normalization臨時替換它。

上面的測試函數非常通用,可以按原樣復制。例外情況是,如果你的模型接受多個輸入。處理這個問題的附加代碼是必要的。

模型的參數更新

下一個測試也與梯度有關。當你的網絡架構變得更加復雜時,比如初始化,很容易構建死子圖。死子圖是網絡中包含可學習參數的一部分,前向傳遞、后向傳遞或兩者都不使用。這就像在構造函數中構建一個網絡層,然后忘記在forward函數中應用它一樣簡單。

找到這些死子圖可以通過運行優化步驟并檢查梯度你的網絡參數:

 

  1. def test_all_parameters_updated(self): 
  2.     net = model.MLPVAE(input_shape=(1, 32, 32), bottleneck_dim=16) 
  3.     optim = torch.optim.SGD(net.parameters(), lr=0.1) 
  4.  
  5.     outputs = net(torch.randn(4, 1, 32, 32)) 
  6.     loss = outputs.mean() 
  7.     loss.backward() 
  8.     optim.step() 
  9.  
  10.     for param_name, param in self.net.named_parameters(): 
  11.         if param.requires_grad: 
  12.             with self.subTest(name=param_name): 
  13.                 self.assertIsNotNone(param.grad) 
  14.                 self.assertNotEqual(0., torch.sum(param.grad ** 2)) 

參數函數返回的模型的所有參數在優化步驟后都應該有一個梯度張量。此外,對于我們所使用的損失,它不應該是零。測試假設模型中的所有參數都需要梯度。即使是那些不應該被更新的參數也會首先檢查requires_grad標志。如果任何參數在測試中失敗,子測試的名稱將提示你在哪里查找。

提高重用性

現在我們已經寫出了模型的所有測試,我們可以將它們作為一個整體進行分析。我們將注意到這些測試有兩個共同點。所有測試都從創建模型和定義示例輸入批處理開始。與以往一樣,這種冗余級別有可能導致拼寫錯誤和不一致。此外,你不希望在更改模型的構造函數時分別更新每個測試。

幸運的是,unittest為我們提供了一個簡單的解決方案,即setUp函數。這個函數在執行TestCase中的每個測試函數之前被調用,通常為空。通過在setUp中將模型和輸入定義為TestCase的成員變量,我們可以在一個地方初始化測試的組件。

 

  1. class TestVAE(unittest.TestCase): 
  2.     def setUp(self): 
  3.         self.net = model.MLPVAE(input_shape=(1, 32, 32), bottleneck_dim=16) 
  4.         self.test_input = torch.random(4, 1, 32, 32) 
  5.  
  6.     ... # Test functions 

現在我們用各自的成員變量替換出現的net和inputs,這樣就完成了。如果你想更進一步,對所有測試使用相同的模型實例,您可以使用setUpClass。這個函數在構造TestCase時被調用一次。如果構建速度很慢,并且你不想多次進行構建,那么這是非常有用的。

在這一點上,我們有一個整潔的系統來測試我們的VAE模型。我們可以輕松地添加測試,并確保每次都測試模型的相同版本。但是如果你想引入一種新的卷積層,會發生什么呢?它將在相同的數據上運行,也應該具有相同的行為,因此將應用相同的測試。

僅僅復制整個TestCase 顯然不是首選的解決方案,但是通過使用setUp,我們已經在正確的軌道上了。我們將所有測試函數轉移到一個基類中,而將setUp保留為一個抽象函數。

 

  1. class AbstractTestVAE(unittest.TestCase): 
  2.     def setUp(self): 
  3.         raise NotImplementedError 
  4.  
  5.     ... # Test functions 

你的IDE會提示類沒有成員變量net 和test_inputs,但是Python并不關心。只要子類添加了它們,它就可以工作。對于我們想要測試的每個模型,我們創建這個抽象類的一個子類,并在其中實現setUp。為多個模型或同一個模型的多個配置創建TestCases 就像:

 

  1. class TestCNNVAE(AbstractTestVAE): 
  2.     def setUp(self): 
  3.         self.test_inputs = torch.randn(4, 1, 32, 32) 
  4.         self.net = model.CNNVAE(input_shape=(1, 32, 32), bottleneck_dim=16) 
  5.  
  6. class TestMLPVAE(AbstractTestVAE): 
  7.     def setUp(self): 
  8.         self.test_inputs = torch.randn(4, 1, 32, 32) 
  9.         self.net = model.MLPVAE(input_shape=(1, 32, 32), bottleneck_dim=16) 

只剩下一個問題了。unittest包發現并運行unittest.TestCase的所有子元素。因為這包括不能實例化的抽象基類,所以我們總是會有一個失敗的測試。

解決方案是由一個流行的設計模式提出的。通過刪除TestCase作為AbstractTestVAE的父類,它就不再被發現了。相反,我們讓我們的具體測試有兩個父類, TestCase和AbstractTestVAE。抽象類和具體類之間的關系不再是父類和子類之間的關系。相反,具體類使用抽象類提供的共享功能。這個模式稱為MixIn。

 

  1. class AbstractTestVAE: 
  2.     ... 
  3.  
  4. class TestCNNVAE(unittest.TestCase, AbstractTestVAE): 
  5.     ... 
  6.  
  7. class TestMLPVAE(unittest.TestCase, AbstractTestVAE): 
  8.     ... 

父類的順序很重要,因為方法查找是從左到右進行的。這意味著TestCase將覆蓋AbstractTestVAE的共享方法。在我們的例子中,這不是一個問題,但無論如何知道都是好的。

Trainer

我們的學習系統的最后一部分是trainer類。它將你所有的組件(數據集、優化器和模型)放在一起,并使用它們來訓練模型。此外,它還實現了一個評估函數,輸出測試數據的平均損失。在訓練時,所有的損失和指標都被寫入一個TensorBoard event文件中以便可視化。

在這一部分中,編寫可重用測試是最困難的,因為它允許最大程度的自由實現。有些人只在腳本文件中使用簡單的代碼進行訓練,有些人將其封裝在函數中,還有一些人試圖保持更面向對象的風格。我不會判斷你喜歡哪種方式。我唯一要說的是,在我的經驗中,整潔封裝的trainer類使單元測試變得最舒適。

然而,我們會發現我們之前學過的一些原則在這里也適用。

trainer的損失

大多數時候,你只需要從torch上選擇一個預先實現的損失函數就可以了。但話說回來,你所選擇的損失函數可能無法實現。這種情況可能是由于實現相對簡單,函數太小眾或者太新。無論如何,如果你自己實現了它,你也應該測試它。

我們的例子使用Kulback-Leibler (KL)散度作為整體損失函數的一部分,這在PyTorch中是不存在的(現在的版本里有了)。我們的實現是這樣的:

 

  1. def _kl_divergence(log_sigma, mu): 
  2.     return 0.5 * torch.sum((2 * log_sigma).exp() + mu ** 2 - 1 - 2 * log_sigma) 

函數取多變量高斯分布的標準偏差和平均值的對數,并計算在封閉形式中的標準高斯分布的KL散度。

檢查這種損失的一種方法是手工計算,然后硬編碼以便比較。更好的方法是在另一個包中找到一個參考實現,并根據它的輸出檢查代碼。幸運的是,scipy包有一個離散KL散度的實現,我們可以使用:

 

  1. @torch.no_grad() 
  2. def test_kl_divergence(self): 
  3.     mu = np.random.randn(10) * 0.25  # means around 0. 
  4.     sigma = np.random.randn(10) * 0.1 + 1.  # stds around 1. 
  5.     standard_normal_samples = np.random.randn(100000, 10) 
  6.     transformed_normal_sample = standard_normal_samples * sigma + mu 
  7.  
  8.     bins = 1000 
  9.     bin_range = [-2, 2] 
  10.     expected_kl_div = 0 
  11.     for i in range(10): 
  12.         standard_normal_dist, _ = np.histogram(standard_normal_samples[:, i], bins, bin_range) 
  13.         transformed_normal_dist, _ = np.histogram(transformed_normal_sample[:, i], bins, bin_range) 
  14.         expected_kl_div += scipy.stats.entropy(transformed_normal_dist, standard_normal_dist) 
  15.  
  16.     actual_kl_div = self.vae_trainer._kl_divergence(torch.tensor(sigma).log(), torch.tensor(mu)) 
  17.  
  18.     self.assertAlmostEqual(expected_kl_div, actual_kl_div.numpy(), delta=0.05) 

我們首先從標準高斯函數和一個不同均值和標準差的高斯函數中抽取一個足夠大的樣本。然后我們用np.histogram函數,得到基本pdf的離散逼近。有了這些,我們就可以用scipy.stats.entropy得到一個KL散度來比較。我們使用一個相對較大的delta來進行比較,因為scipy.stats.entropy只是一個近似值。

你可能已經注意到,我們沒有創建Trainer對象,而是使用TestCase的成員。我們在這里使用了與模型測試相同的技巧,并在setUp函數中創建了它。我們還固定了PyTorch和NumPy的種子。因為我們這里不需要任何梯度,所以我們用@torch.no_grad來裝飾函數。

trainer的日志記錄

我們使用TensorBoard來記錄我們的訓練過程的損失和度量。為此,我們希望確保按預期寫入所有日志。一種方法是在訓練后打開event文件,查找正確的event。同樣,這也是一個有效的選項,但我們將以另一種方式來看看unittest包的一個有趣功能:mock。

mock允許你用一個監視其自身是如何調用的函數來打包一個函數或對象。我們將替換summary writer的add_scalar 函數,并確保以這種方式記錄我們關心的所有損失和指標。

 

  1. def test_logging(self): 
  2.     with mock.patch.object(self.vae_trainer.summary, 'add_scalar'as add_scalar_mock: 
  3.         self.vae_trainer.train(1) 
  4.  
  5.     expected_calls = [mock.call('train/recon_loss', mock.ANY, 0), 
  6.                       mock.call('train/kl_div_loss', mock.ANY, 0), 
  7.                       mock.call('train/loss', mock.ANY, 0), 
  8.                       mock.call('test/loss', mock.ANY, 0)] 
  9.     add_scalar_mock.assert_has_calls(expected_calls) 

assert_has_calls 函數匹配預期調用列表和實際記錄的調用。mock.ANY 表示我們不關心記錄的標量的值,因為無論如何我們都不知道它。

因為我們不需要對整個數據集執行完一個epoch,所以我們在setUp 中將訓練數據配置為只有一個batch。這樣,我們可以顯著地加快我們的測試速度。

trainer的擬合

最后一個問題也是最難回答的。我的訓練最終會收斂嗎?要確切地回答這個問題,我們需要用我們所有的數據進行一次全面的訓練并對其打分。

由于這非常耗時,我們將使用一種更快的方法。我們將看看我們的訓練是否能使模型對單個batch的數據進行過擬合。測試函數相當簡單:

 

  1. def test_overfit_on_one_batch(self): 
  2.     self.vae_trainer.train(500) 
  3.     self.assertGreaterEqual(30, self.vae_trainer.eval()) 

如前一節所述,setUp函數創建一個只包含一個batch的數據集的trainer。此外,我們也使用訓練數據作為測試數據。通過這種方式,我們可以從 eval函數中獲得訓練batch的損失,并將其與我們預期的損失進行比較。

對于一個分類問題,當我們完全過擬合時,我們期望損失為零。“VAE”的問題是,它是一個非確定性的生成模型,零損失是不現實的。這就是為什么我們預期的損失是30,這等于每像素的誤差為0.04。

這是迄今為止運行時間最長的測試,它可以運行500 epochs。最后,在我的筆記本電腦上用1.5分鐘左右就可以了,這仍然是合理的。為了在不降低對沒有GPU的機器的支持的情況下進一步加速,我們可以簡單地在setUp中添加這一行:

 

  1. device = 'cuda:0' if torch.cuda.is_available() else 'cpu' 

這樣一來,如果我們有GPU,我們就可以利用它,如果沒有,就利用CPU進行訓練。

最后要注意的

在我們進行日志記錄時,你可能會注意到,針對trainer的單元測試往往會使你的文件夾充滿event文件。為了避免這種情況,我們使用tempfile 包為trainer創建一個臨時日志目錄。測試結束后,我們只需要再次刪除它和它的內容。為此,我們使用了孿生函數setUp,和tearDown。在每個測試函數后調用此函數,清理過程簡單如下:

 

  1. def tearDown(self): 
  2.     shutil.rmtree(self.log_dir) 

總結

我們看完了這篇文章。讓我們評估一下我們從整個磨難中得到了什么。

我們為我們的小例子編寫的測試套件包含58個單元測試,整個運行大約需要3.5分鐘。對于這58個測試,我們只編寫了20個函數。所有測試都可以確定地、獨立地運行。如果有GPU,我們可以運行額外的測試。大多數測試,例如數據集和模型測試,可以在其他項目中輕松重用。我們可以通過使用:

  • 子測試為我們的數據集的多種配置運行一個測試
  • setUp和tearDown函數一致地初始化和清理我們的測試
  • 抽象測試類來測試VAE的不同實現
  • torch.no_grad裝飾器在可能的情況下禁用梯度計算
  • mock模塊檢查函數是否被正確調用

最后,我希望我能夠說服至少有人在他們的深度學習項目中使用單元測試。本文的配套git倉庫可以作為起點。

 

責任編輯:華軒 來源: 今日頭條
相關推薦

2010-06-07 15:07:34

云計算

2011-03-03 15:51:54

2015-06-03 16:51:19

代碼技術信任

2015-06-05 10:13:22

程序員信任

2015-12-17 13:19:29

編寫高性能Swift

2010-03-15 14:47:19

Python內置對象

2017-06-22 09:30:40

深度學習機器閱讀數據集

2023-08-17 14:22:17

深度學習機器學習

2023-10-23 10:19:23

自動駕駛深度學習

2024-09-13 15:24:20

深度學習自動微分

2021-09-17 16:28:22

零信任網絡防御

2022-07-20 15:56:02

零信任網絡安全漏洞

2025-10-14 08:00:00

2010-04-30 14:14:42

IT運維網絡管理摩卡軟件

2017-05-17 15:09:06

深度學習人工智能

2021-07-23 08:00:00

深度學習框架模型

2018-03-23 09:29:56

深度學習神經網絡前端設計模型

2011-06-08 17:41:47

網站信任度

2017-08-03 11:00:20

2016-12-27 14:06:36

Python代碼基礎
點贊
收藏

51CTO技術棧公眾號

精品久久久国产精品999| 成熟丰满熟妇高潮xxxxx视频| 一本色道无码道dvd在线观看| 欧美三级网站在线观看| 国产精品极品| 国产麻豆成人精品| 日韩精品视频免费| 欧美性猛交内射兽交老熟妇| 欧美成人一区二区视频| 99视频精品视频高清免费| 色综合亚洲欧洲| 久久艳妇乳肉豪妇荡乳av| 久久久久久久福利| 亚洲三级av| 亚洲精品日韩综合观看成人91| 国产精品久久久av| 日本二区在线观看| 免费在线看污片| 日韩中文首页| 欧美三级乱人伦电影| 四虎一区二区| 中文字幕福利视频| 日韩欧美精品| 日韩精品免费在线视频观看| 午夜影院免费版| 欧美家庭影院| 国产精品久久影院| 亚洲综合色av| 日韩黄色a级片| 欧美自拍一区| 91激情五月电影| 亚洲一一在线| 国产黄色美女视频| 亚洲精品社区| 亚洲一品av免费观看| 日韩一区二区三区久久| 久久77777| 成人看片黄a免费看在线| 91高清在线免费观看| 91中文精品字幕在线视频| 国产亚洲欧美在线精品| 91超薄肉色丝袜交足高跟凉鞋| 国产精品日本一区二区| 18禁裸乳无遮挡啪啪无码免费| 91超碰在线| 成人免费视频网站在线观看| 成人美女av在线直播| 天天操天天摸天天爽| 青青草原免费观看| 高潮按摩久久久久久av免费| 欧美丰满美乳xxx高潮www| 国产主播精品在线| 久久精品这里热有精品| 免费看的黄色大片| 丰满的护士2在线观看高清| 91视频精品在这里| 国产精品三级久久久久久电影| 麻豆视频免费在线播放| 榴莲视频成人app| 精品日本美女福利在线观看| 少妇特黄a一区二区三区| 国产三级电影在线| 国产精品18久久久久久久久 | chinese偷拍一区二区三区| 国产亚洲欧洲| 在线观看91久久久久久| 韩国三级与黑人| 正在播放日韩精品| 国产精品成人网| 国产在线一区二区三区欧美| 中文字幕在线观看精品| 另类中文字幕网| 韩日精品中文字幕| 97视频在线看| 日本高清黄色片| а√中文在线天堂精品| 日本精品一级二级| 色婷婷成人在线| 亚洲伦理久久| 色综合色综合色综合色综合色综合 | 97精品国产aⅴ7777| 国产精品免费av一区二区| 成人综合久久| 亚洲精品suv精品一区二区| 一本到不卡免费一区二区| 午夜啪啪福利视频| 亚洲va欧美va在线观看| 一级片在线免费观看视频| 亚洲国产精品一区制服丝袜| 91大神福利视频在线| 国产情侣免费视频| 国产成人日日夜夜| 日本精品一区二区三区视频| 色哟哟国产精品色哟哟| 国产专区欧美精品| 国产精品欧美在线| 日本在线观看不卡| av片免费观看| 99精品免费| 欧美国产高跟鞋裸体秀xxxhd| 国产视频不卡在线| 欧美三区不卡| 欧美高清视频免费观看| 日韩在线 中文字幕| 精品亚洲porn| 成人美女av在线直播| 欧美综合视频在线| 国产69精品久久久久777| 91在线中文字幕| 三级视频网站在线| 韩国中文字幕hd久久精品| 亚洲精品日韩久久| 成人欧美一区二区三区在线| 污视频网站免费观看| 成人综合在线网站| 亚洲乱码一区二区三区三上悠亚| av在线免费观看网| 午夜欧美视频在线观看| 人妻少妇精品久久| av资源网在线播放| 6080日韩午夜伦伦午夜伦| www.久久av.com| 在线视频成人| 亚洲性生活视频| 日本黄色片视频| 国产精品一品二品| 伊人久久av导航| 一色桃子av在线| 亚洲一区电影777| 丁香花在线影院观看在线播放| 狠狠久久伊人中文字幕| 欧美高清视频www夜色资源网| 亚洲一区二区三区综合| 精品一区欧美| 精品国产依人香蕉在线精品| 秋霞av一区二区三区| 91在线视频在线| 日本亚洲导航| 亚洲色图官网| 日韩精品在线视频| 超碰97av在线| 老**午夜毛片一区二区三区| 国产精品自拍小视频| 国产老妇伦国产熟女老妇视频| 国产大陆a不卡| 三年中文高清在线观看第6集| 丝袜综合欧美| 日韩欧美中文字幕在线观看| 国产一级不卡毛片| 国产亚洲精aa在线看| 亚洲高清在线观看| 久久免费手机视频| 日本欧美加勒比视频| 91精品久久久久久蜜桃| 免费在线视频一级不卡| 亚洲三级视频在线观看| 日日碰狠狠添天天爽超碰97| 国内精品国产成人国产三级粉色| 久久久久久18| 中文字幕在线观看第二页| 国产精品免费久久| 91日韩精品视频| 999国产精品一区| 久久6免费高清热精品| 亚洲欧美偷拍视频| 国产天堂亚洲国产碰碰| 免费网站永久免费观看| 久久91导航| 欧美tk—视频vk| 三区四区在线观看| 麻豆视频观看网址久久| 色中文字幕在线观看| 日韩三级久久| 原创国产精品91| 88av在线视频| 亚洲成a人片综合在线| 国产视频1区2区3区| 国产精品videosex性欧美| 99理论电影网| 亚乱亚乱亚洲乱妇| 欧美日韩在线免费观看| 欧美丰满老妇熟乱xxxxyyy| 理论片日本一区| 成人小视频在线观看免费| 日韩精品丝袜美腿| 欧美高清不卡在线| 久久伊伊香蕉| 日韩一卡二卡三卡四卡| 免费黄色国产视频| 肉肉av福利一精品导航| 精品伦精品一区二区三区视频| av毛片在线播放| 日韩高清人体午夜| 91尤物国产福利在线观看| 亚洲国产成人tv| 99久久99久久精品免费| 国产成人a级片| 欧美日韩大尺度| 伊人精品成人久久综合软件| 国产精品免费看一区二区三区| 日韩欧美另类一区二区| 国产视频精品一区二区三区| 一级特黄aaa| 精品欧美国产一区二区三区| www.99re6| 久久久久久影视| 黄色一级一级片| 欧美三级乱码| 一区二区三区四区视频在线| 日韩欧美美女在线观看| 亚洲一区二区三区四区在线播放| 345成人影院| 亚洲欧洲黄色网| 国产99免费视频| 国产丝袜美腿一区二区三区| 免费高清视频在线观看| 蜜桃传媒麻豆第一区在线观看| 亚洲看片网站| 中文字幕中文字幕精品| 国产精品1区2区在线观看| 美丽的姑娘在线观看免费动漫| 91精品国产欧美一区二区成人| 特级片在线观看| 丁香亚洲综合激情啪啪综合| 一级片视频免费观看| 久久动漫亚洲| 亚洲精品国产精品国自产| 欧美一级全黄| 国产精品日韩欧美一区二区三区 | 亚洲精品日韩av| 电影一区二区| 久久天天躁狠狠躁老女人| 国产白浆在线观看| 欧美中文字幕久久| 中文字幕电影av| 成人丝袜高跟foot| 天天操精品视频| 韩国av一区二区| 五月天丁香花婷婷| 国精产品一区一区三区mba视频 | 欧美亚洲精品一区二区| 一区在线视频| 日本一道本久久| 精品国产视频| 亚洲一区亚洲二区亚洲三区| 欧美久久久网站| 国产综合香蕉五月婷在线| 欧美jizz18| 成人激情视频免费在线| 亚洲成人a级片| 97超碰色婷婷| 深夜av在线| 伦理中文字幕亚洲| 久久综合网导航| 久久成人人人人精品欧| 在线观看三级视频| 国内精品模特av私拍在线观看 | 播播国产欧美激情| 黄片毛片在线看| 亚洲国语精品自产拍在线观看| 神马午夜精品95| 亚洲丝袜av一区| 秋霞成人影院| 亚洲色图15p| 1024视频在线| 亚洲精品一区av在线播放| 久久精品色图| 欧美一级二级在线观看| 精品国产九九九| 欧美性生活影院| 国产成人免费观看视频| 日韩欧美黄色动漫| 中文在线a天堂| 欧美一级在线免费| 日本黄视频在线观看| 亚洲嫩模很污视频| 天堂av一区二区三区| 亚洲黄页视频免费观看| 懂色一区二区三区| 欧美乱妇高清无乱码| 欧美办公室脚交xxxx| 国产精品一区二区三区免费视频| 国产精品一区免费在线| 久久国产日韩欧美| 粉嫩精品导航导航| 日本高清不卡一区二区三| 希岛爱理av一区二区三区| 日韩国产伦理| 中文乱码免费一区二区三区下载| 日本在线免费观看一区| 婷婷亚洲五月| 欧美综合在线播放| 美国三级日本三级久久99| 国产麻豆剧传媒精品国产| 国产在线精品一区二区不卡了| 亚洲女则毛耸耸bbw| 久久久久久夜精品精品免费| 男人与禽猛交狂配| 亚洲欧美另类久久久精品2019| 国产大片中文字幕| 午夜亚洲国产au精品一区二区| 天天干,天天干| 精品国产乱码久久久久久牛牛| 国产成人精品免费看视频| 精品一区二区三区四区| 麻豆av在线导航| 日韩美女免费线视频| 波多野结衣久久精品| 91丨九色丨国产| 日韩电影一区| 国产精品无码专区av在线播放| 成人性色生活片| 国产精品99久久久久久成人| 91国产免费观看| 色一情一乱一区二区三区| 欧美美女18p| 四虎地址8848精品| 视频一区亚洲| 老司机久久99久久精品播放免费| 无码任你躁久久久久久老妇| 91一区二区三区在线观看| 九九热最新地址| 欧美日韩夫妻久久| www.成人精品| 日韩在线观看高清| 99久久婷婷国产综合精品首页| 国产欧美在线播放| 国产精品亚洲片在线播放| 色就是色欧美| 奶水喷射视频一区| www.17c.com喷水少妇| 91免费观看视频在线| 久一区二区三区| 日韩欧美一级精品久久| 成人午夜视频一区二区播放| 久久精品久久久久久国产 免费| 国精产品一区一区三区四川| 欧美日韩无遮挡| 国产精品91一区二区三区| 超碰影院在线观看| 久久噜噜亚洲综合| 97在线观看视频免费| 一级特黄大欧美久久久| 青青青国产在线| 欧美精品色一区二区三区| 大胆av不用播放器在线播放| 青青久久aⅴ北条麻妃| 亚洲视频自拍| 日本黄色播放器| 国产乱对白刺激视频不卡 | 偷拍亚洲欧洲综合| 天堂成人在线视频| 97在线视频免费观看| 欧美自拍视频| 日韩欧美黄色大片| 国产精品美女www爽爽爽| 91精品国产乱码久久| 欧美成人亚洲成人日韩成人| 九九精品调教| 国产精品久久久久久婷婷天堂| 精品视频免费在线观看| 亚洲综合欧美激情| 综合中文字幕亚洲| 亚洲自拍一区在线观看| 怡红院精品视频| 精品99re| 国产一区二区网| 欧美激情在线免费观看| 亚洲精品77777| 亚洲欧美成人一区二区在线电影| 成人国产网站| 99热都是精品| 99riav一区二区三区| 免费看污视频的网站| 久久人人爽亚洲精品天堂| 成人h动漫精品一区二区器材| 日韩欧美视频网站| 国产精品护士白丝一区av| 欧美熟妇另类久久久久久不卡 | 四虎影视国产精品| 国产欧美日韩小视频| 国产真实乱偷精品视频免| 欧美激情精品久久| 亚洲精品视频免费在线观看| 欧美videos粗暴| 国产综合av在线| 亚洲欧洲精品成人久久奇米网| 日本激情一区二区三区| 国产一区二区在线播放| 99视频+国产日韩欧美| 国产精品18在线| 亚洲精品国偷自产在线99热| 福利视频一区| av之家在线观看| 亚洲乱码中文字幕| 成人资源www网在线最新版| 国产传媒一区二区| 韩日成人在线| 下面一进一出好爽视频| 欧美午夜精品久久久久久人妖 |