【深度学习】深度学习中的单元测试
作者 | Manpreet Singh Minhas
編譯 | VK
來源 | Towards Data Science
深度學習/機器學習工作流程通常不同于人們對正常軟件開發過程的期望。但這并不意味著人們不應該從這些年來不斷發展的軟件開發中汲取靈感并進行實踐。
在本文中,我將討論單元測試以及為什么以及如何在代碼中包含這些測試。我們將首先簡要介紹單元測試,然后是一個深度學習中的單元測試示例,以及如何通過命令行和VS代碼測試資源管理器運行這些測試。
介紹
單元測試是軟件開發人員熟悉的概念。這是一種非常有用的技術,可以幫助你防止代碼中出現明顯的錯誤和bug。它包括測試源代碼的各個單元,如函數、方法和類,以確定它們是否滿足要求并具有預期的行為。
單元測試通常很小,執行起來不需要太多時間。測試的輸入范圍很廣,通常包括邊界和邊緣情況。這些輸入的輸出通常由開發人員手動計算,以測試被測試單元的輸出。
例如,對于加法器函數,我們將有如下測試用例。(稍后我們將看到一個深度學習的示例。)
你可以用正輸入、零輸入、負輸入、正輸入和負輸入測試用例。
如果我們正在測試的函數/方法的輸出與單元測試中為所有輸入案例定義的輸出相等,那么你的單元將通過測試,否則它將失敗。你將確切地知道哪個測試用例失敗。可以進一步調查,找出問題所在。
如果有多個開發人員正在處理一個大型項目。假設有人基于某些假設和數據大小編寫了一段代碼,而新的開發人員更改了代碼庫中不再滿足這些假設的內容。那么代碼肯定會失敗。單元測試允許避免這種情況。
下面是單元測試的一些好處。
強制你編寫具有明確定義的輸入和輸出的模塊化和可重用代碼。因此,你的代碼將更易于集成。
提高了更改/維護代碼的信心。它有助于識別代碼更改引入的bug。
提高了對單元本身的信心,因為如果它通過了單元測試,我們可以確定邏輯沒有明顯的錯誤,并且單元按預期運行。
調試變得更容易,因為你可以知道哪個單元失敗了,以及哪些特定的測試用例失敗了。
Python中的單元測試
每種語言都有自己的工具和包可用于進行單元測試。Python還提供了一些單元測試框架。unittest包是標準Python庫的一部分。
我將討論如何通過命令行/bash和VS Code UI界面來使用這個框架。它的靈感來自JUnit,與其他語言中的主要單元測試框架有相似的風格。它支持測試自動化、共享測試的設置和關閉代碼、將測試聚合到集合中以及獨立于測試的報告框架[4]。
在這個框架中,單元測試的基本構建塊是測試用例——必須設置并檢查其正確性的場景。在unittest中,測試用例是unittest.TestCase。要生成測試用例,必須編寫TestCase的子類。
TestCase實例的測試用例應該是自包含的,這樣它可以單獨運行,也可以與任何數量的其他測試用例任意組合運行。TestCase子類的測試方法應該在名稱中有test前綴,并執行特定的測試代碼。
為了執行測試,TestCase基類有幾個assert方法,允許你對照被測試單元的輸出檢查測試用例的輸出。如果測試失敗,將引發異常并給出解釋性消息,unittest將測試用例標識為失敗。任何其他異常都將被視為錯誤。
有兩種類型的setup方法可用于為測試設置類。
setUp -這將在類中的每個測試方法之前調用。
setUpClass-整個類只運行一次。這是你應該用來做深度學習測試的方法。在此方法中加載模型,以避免在執行每個測試方法之前重新加載模型。這將節省模型重新加載時間。
請注意,各種測試的運行順序是通過根據字符串的內置順序對測試方法名稱進行排序來確定的。
現在讓我們看看我為一個項目的PyTorch數據加載器而創建的單元測試。代碼如下所示。
import?unittest from?pathlib?import?Pathimport?torch from?PIL?import?Image from?segdataset?import?SegmentationDataset from?torch.utils.data?import?DataLoader from?torchvision?import?transformsclass?Test_TestSegmentationDataset(unittest.TestCase):@classmethoddef?setUpClass(cls)?->?None:seg_dataset?=?SegmentationDataset("CrackForest","Images","Masks",transforms=transforms.Compose([transforms.ToTensor()]))seg_dataloader?=?DataLoader(seg_dataset,batch_size=4,shuffle=False,num_workers=8)cls.samples?=?next(iter(seg_dataloader))def?test_image_tensor_dimensions(self):image_tensor_shape?=?Test_TestSegmentationDataset.samples['image'].shapeself.assertEqual(image_tensor_shape[0],?4)self.assertEqual(image_tensor_shape[1],?3)self.assertEqual(image_tensor_shape[2],?320)self.assertEqual(image_tensor_shape[3],?480)def?test_mask_tensor_dimensions(self):mask_tensor_shape?=?Test_TestSegmentationDataset.samples['mask'].shapeself.assertEqual(mask_tensor_shape[0],?4)self.assertEqual(mask_tensor_shape[1],?1)self.assertEqual(mask_tensor_shape[2],?320)self.assertEqual(mask_tensor_shape[3],?480)def?test_mask_img_pair(self):ref_image_tensor?=?transforms.ToTensor()(Image.open(Path("CrackForest/Images/001.jpg")))ref_mask_tensor?=?transforms.ToTensor()(Image.open(Path("CrackForest/Masks/001_label.PNG")))datagen_image_tensor?=?Test_TestSegmentationDataset.samples['image'][0]datagen_mask_tensor?=?Test_TestSegmentationDataset.samples['mask'][0]self.assertTrue(torch.equal(ref_image_tensor,?datagen_image_tensor))self.assertTrue(torch.equal(ref_mask_tensor,?datagen_mask_tensor)) ??2021?GitHub,?Inc.被測試的分割數據集需要批量加載相應的圖像和mask對。將正確的圖像映射到正確的mask是至關重要的。
為此,通常,圖像和mask的名稱中都有相同的數字。如果你正在通過一些增強來調整圖像的大小,那么你的結果大小應該與預期的一樣。對于PyTorch,數據加載器返回的張量應該是BxCxHxW形式,其中B是批大小,C是通道數,H是高度,W是寬度。
現在,我來解釋代碼中發生了什么。我創建了一個從unittest.TestCase測試用例基類。如前所述,我創建了一個setUpClass方法,它是一個類方法,用于確保初始化只執行一次。
class?Test_TestSegmentationDataset(unittest.TestCase):@classmethoddef?setUpClass(cls)?->?None:seg_dataset?=?SegmentationDataset("CrackForest","Images","Masks",transforms=transforms.Compose([transforms.ToTensor()]))seg_dataloader?=?DataLoader(seg_dataset,batch_size=4,shuffle=False,num_workers=8)cls.samples?=?next(iter(seg_dataloader))這里需要注意的一點是,為了測試,我在dataloader中禁用了shuffle。因為我希望名稱中帶有001的映像和mask出現在dataloader創建的第一批的索引0中。
從不同的批次中檢查不同的樣本索引將是一個更好的測試,因為你將確保不同批次的順序是一致的。我把第一批儲存在cls作為類屬性。
現在初始化完成了,我們來看看各個測試。
在第一個測試中,我檢查dataloader返回的圖像張量維度。因為我沒有調整大小的圖像,我希望大小為320x480和這些圖像正在讀取為RGB,所以應該有3個通道。在setUpClass方法中,我將批大小指定為4,因此張量的第一個維度應該是4。如果尺寸有問題,這個測試就會失敗。
????def?test_image_tensor_dimensions(self):image_tensor_shape?=?Test_TestSegmentationDataset.samples['image'].shapeself.assertEqual(image_tensor_shape[0],?4)self.assertEqual(image_tensor_shape[1],?3)self.assertEqual(image_tensor_shape[2],?320)self.assertEqual(image_tensor_shape[3],?480)下一個測試是完全相同的,除了它是為mask張量。在這個特定的數據集中,mask只有一個通道。所以我希望通道數是1。批量大小應為4。mask形狀應為320x480。
????def?test_mask_tensor_dimensions(self):mask_tensor_shape?=?Test_TestSegmentationDataset.samples['mask'].shapeself.assertEqual(mask_tensor_shape[0],?4)self.assertEqual(mask_tensor_shape[1],?1)self.assertEqual(mask_tensor_shape[2],?320)self.assertEqual(mask_tensor_shape[3],?480)最后一個測試檢查兩件事。首先是通過手動應用dataloader中指定的變換獲得的張量是否產生與dataloader相同的結果。其次是圖像和mask對是正確的。
要直接應用torchvision變換,需要實例化transform并將圖像作為輸入傳遞給該實例。如果transform需要一個PIL圖像或numpy數組(對于ToTensor就是這種情況),任何其他格式都會導致錯誤。
????def?test_mask_img_pair(self):ref_image_tensor?=?transforms.ToTensor()(Image.open(Path("CrackForest/Images/001.jpg")))ref_mask_tensor?=?transforms.ToTensor()(Image.open(Path("CrackForest/Masks/001_label.PNG")))datagen_image_tensor?=?Test_TestSegmentationDataset.samples['image'][0]datagen_mask_tensor?=?Test_TestSegmentationDataset.samples['mask'][0]self.assertTrue(torch.equal(ref_image_tensor,?datagen_image_tensor))self.assertTrue(torch.equal(ref_mask_tensor,?datagen_mask_tensor))現在我們已經準備好了unittest,讓我們先看看如何通過命令行運行這個測試。
可以使用以下命令:
python -m unittest discover -s Tests -p "test_*"
一旦指定了搜索目錄和搜索模式,Unittest就可以發現測試。
-s或--start directory directory:它指定開始發現目錄。在我們的例子中,由于測試位于tests文件夾中,所以我們將該文件夾指定為該標志的值。-p或--pattern:它指定匹配模式。我指定了一個自定義模式,只是為了向你展示這個功能是可用的。因為默認模式是test*.py,所以它在默認情況下適用于我們的測試腳本。-v或--verbose:如果你指定這個值,你將獲得測試類中每個測試方法的輸出。非詳細輸出和詳細輸出如下所示。如果所有的測試方法都通過了,那么最后會收到一條OK消息。
但是,如果任何一個測試方法失敗,你將得到一條失敗消息,其中指定了失敗的測試。你會知道哪個斷言失敗了。如前所述,這對調試和查找破壞代碼的原因非常有幫助。在本例中,我更改了正在讀取的圖像,但沒有更改正在比較的張量,這導致了錯誤。
你可以將此測試執行行包含在任何自動批處理或bash文件中,這些文件可用于自動部署。例如,我們在GitHub操作中使用類似的測試,在更新版本自動推送到包存儲庫之前自動驗證代碼是否工作。
接下來,我將向你展示如何使用VS代碼測試資源管理器通過UI運行這些測試。
在VS Code[3]中運行Python單元測試
在VS代碼中,Python中的測試在默認情況下是禁用的。
要啟用測試,請在命令Pallete上使用Python:configuretests命令。此命令提示你選擇測試框架、包含測試的文件夾以及用于標識測試文件的模式。
最后兩個輸入與我們用于通過命令行運行單元測試的輸入完全相同。Unittest框架不需要進一步安裝。但是,如果你選擇的框架包沒有安裝在你的環境中,VS代碼會提示你安裝它。
一旦發現被正確設置,我們將在VS代碼活動欄中看到帶有圖標的測試資源管理器。測試資源管理器幫助你可視化、導航和運行測試。
你還可以在測試腳本中看到直接可用的運行測試和調試測試選項。你可以從該視圖運行所有或單個測試,還可以導航到不同類中的單個測試方法。
如果測試失敗,我會出現一個紅色的十字而不是綠色的勾號。如果你想節省時間,你可以選擇只運行失敗的測試,而不是再次運行所有測試。
結論
本文結束了關于深度學習單元測試的文章。我們簡要地了解了什么是單元測試以及它們的好處。
接下來,我們介紹了一個使用unittest包框架用PyTorch編寫的數據加載器單元的實際示例。我們學習了如何通過命令行和Python測試資源管理器從VS代碼運行這些測試。
我希望你開始為代碼編寫單元測試并從中獲益!謝謝你閱讀這篇文章。代碼位于:https://github.com/msminhas93/deeplabv3finetunning
參考引用
[1]https://softwaretestingfundamentals.com/unit-testing/
[2]https://www.tutorialspoint.com/unittest_framework/unittest_framework_overview.htm
[3]https://code.visualstudio.com/docs/python/testing
[4]https://docs.python.org/3/library/unittest.html
[5]https://stackoverflow.com/questions/23667610/what-is-the-difference-between-setup-and-setupclass-in-python-unittest/23670844
往期精彩回顧適合初學者入門人工智能的路線及資料下載機器學習及深度學習筆記等資料打印機器學習在線手冊深度學習筆記專輯《統計學習方法》的代碼復現專輯 AI基礎下載機器學習的數學基礎專輯溫州大學《機器學習課程》視頻 本站qq群851320808,加入微信群請掃碼: 與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的【深度学习】深度学习中的单元测试的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【学术相关】选导师犹如选对象:真真切切,
- 下一篇: 爱链工具怎么使用 爱链工具详细使用图文教