PyTorch代碼調試利器:自動print每行代碼的Tensor信息

2021-01-10 機器之心Pro

機器之心發布

作者:zasdfgbnm

本文介紹一個用於 PyTorch 代碼的實用工具 TorchSnooper。作者是TorchSnooper的作者,也是PyTorch開發者之一

GitHub 項目地址: https://github.com/zasdfgbnm/TorchSnooper

大家可能遇到這樣子的困擾:比如說運行自己編寫的 PyTorch 代碼的時候,PyTorch 提示你說數據類型不匹配,需要一個 double 的 tensor 但是你給的卻是 float;再或者就是需要一個 CUDA tensor, 你給的卻是個 CPU tensor。比如下面這種:

RuntimeError: Expected object of scalar type Double but got scalar type Float

這種問題調試起來很麻煩,因為你不知道從哪裡開始出問題的。比如你可能在代碼的第三行用 torch.zeros 新建了一個 CPU tensor, 然後這個 tensor 進行了若干運算,全是在 CPU 上進行的,一直沒有報錯,直到第十行需要跟你作為輸入傳進來的 CUDA tensor 進行運算的時候,才報錯。要調試這種錯誤,有時候就不得不一行行地手寫 print 語句,非常麻煩。

再或者,你可能腦子裡想像著將一個 tensor 進行什麼樣子的操作,就會得到什麼樣子的結果,但是 PyTorch 中途報錯說 tensor 的形狀不匹配,或者壓根沒報錯但是最終出來的形狀不是我們想要的。這個時候,我們往往也不知道是什麼地方開始跟我們「預期的發生偏離的」。我們有時候也得需要插入一大堆 print 語句才能找到原因。

TorchSnooper 就是一個設計了用來解決這個問題的工具。TorchSnooper 的安裝非常簡單,只需要執行標準的 Python 包安裝指令就好:

pip install torchsnooper

安裝完了以後,只需要用 @torchsnooper.snoop() 裝飾一下要調試的函數,這個函數在執行的時候,就會自動 print 出來每一行的執行結果的 tensor 的形狀、數據類型、設備、是否需要梯度的信息。

安裝完了以後,下面就用兩個例子來說明一下怎麼使用。

例子1

比如說我們寫了一個非常簡單的函數:

def myfunc(mask, x):y = torch.zeros(6) y.masked_scatter_(mask, x) return y

我們是這樣子使用這個函數的:

mask = torch.tensor([0, 1, 0, 1, 1, 0], device='cuda')source = torch.tensor([1.0, 2.0, 3.0], device='cuda')y = myfunc(mask, source)

上面的代碼看起來似乎沒啥問題,然而實際上跑起來,卻報錯了:

RuntimeError: Expected object of backend CPU but got backend CUDA for argument #2 'mask'

問題在哪裡呢?讓我們 snoop 一下!用 @torchsnooper.snoop() 裝飾一下 myfunc 函數:

import torchimport torchsnooper@torchsnooper.snoop()def myfunc(mask, x):y = torch.zeros(6) y.masked_scatter_(mask, x) return ymask = torch.tensor([0, 1, 0, 1, 1, 0], device='cuda')source = torch.tensor([1.0, 2.0, 3.0], device='cuda')y = myfunc(mask, source)

然後運行我們的腳本,我們看到了這樣的輸出:

Starting var:.. mask = tensor<(6,), int64, cuda:0>Starting var:.. x = tensor<(3,), float32, cuda:0>21:41:42.941668 call 5 def myfunc(mask, x):21:41:42.941834 line 6 y = torch.zeros(6)New var:....... y = tensor<(6,), float32, cpu>21:41:42.943443 line 7 y.masked_scatter_(mask, x)21:41:42.944404 exception 7 y.masked_scatter_(mask, x)

結合我們的錯誤,我們主要去看輸出的每個變量的設備,找找最早從哪個變量開始是在 CPU 上的。我們注意到這一行:

New var:....... y = tensor<(6,), float32, cpu>

這一行直接告訴我們,我們創建了一個新變量 y, 並把一個 CPU tensor 賦值給了這個變量。這一行對應代碼中的 y = torch.zeros(6)。於是我們意識到,在使用 torch.zeros 的時候,如果不人為指定設備的話,默認創建的 tensor 是在 CPU 上的。我們把這一行改成 y = torch.zeros(6, device='cuda'),這一行的問題就修復了。

這一行的問題雖然修復了,我們的問題並沒有解決完整,再跑修改過的代碼還是報錯,但是這個時候錯誤變成了:

RuntimeError: Expected object of scalar type Byte but got scalar type Long for argument #2 'mask'

好吧,這次錯誤出在了數據類型上。這次錯誤報告比較有提示性,我們大概能知道是我們的 mask 的數據類型錯了。再看一遍 TorchSnooper 的輸出,我們注意到:

Starting var:.. mask = tensor<(6,), int64, cuda:0>

果然,我們的 mask 的類型是 int64, 而不應該是應有的 uint8。我們把 mask 的定義修改好:

mask = torch.tensor([0, 1, 0, 1, 1, 0], device='cuda', dtype=torch.uint8)

然後就可以運行了。

例子 2

這次我們要構建一個簡單的線性模型:

model = torch.nn.Linear(2, 1)

我們想要擬合一個平面 y = x1 + 2 * x2 + 3,於是我們創建了這樣一個數據集:

x = torch.tensor([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]])y = torch.tensor([3.0, 5.0, 4.0, 6.0])

我們使用最普通的 SGD 優化器來進行優化,完整的代碼如下:

import torchmodel = torch.nn.Linear(2, 1)x = torch.tensor([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]])y = torch.tensor([3.0, 5.0, 4.0, 6.0])optimizer = torch.optim.SGD(model.parameters(), lr=0.1)for _ in range(10):optimizer.zero_grad() pred = model(x) squared_diff = (y - pred) ** 2 loss = squared_diff.mean() print(loss.item()) loss.backward() optimizer.step()

然而運行的過程我們發現,loss 降到 1.5 左右就不再降了。這是很不正常的,因為我們構建的數據都是無誤差落在要擬合的平面上的,loss 應該降到 0 才算正常。

乍看上去,不知道問題在哪裡。抱著試試看的想法,我們來 snoop 一下子。這個例子中,我們沒有自定義函數,但是我們可以使用 with 語句來激活 TorchSnooper。把訓練的那個循環裝進 with 語句中去,代碼就變成了:

import torchimport torchsnoopermodel = torch.nn.Linear(2, 1)x = torch.tensor([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]])y = torch.tensor([3.0, 5.0, 4.0, 6.0])optimizer = torch.optim.SGD(model.parameters(), lr=0.1)with torchsnooper.snoop():for _ in range(10): optimizer.zero_grad() pred = model(x) squared_diff = (y - pred) ** 2 loss = squared_diff.mean() print(loss.item()) loss.backward() optimizer.step()

運行程序,我們看到了一長串的輸出,一點一點瀏覽,我們注意到

New var:....... model = Linear(in_features=2, out_features=1, bias=True)New var:....... x = tensor<(4, 2), float32, cpu>New var:....... y = tensor<(4,), float32, cpu>New var:....... optimizer = SGD (Parameter Group 0 dampening: 0 lr: 0....omentum: 0 nesterov: False weight_decay: 0)02:38:02.016826 line 12 for _ in range(10):New var:....... _ = 002:38:02.017025 line 13 optimizer.zero_grad()02:38:02.017156 line 14 pred = model(x)New var:....... pred = tensor<(4, 1), float32, cpu, grad>02:38:02.018100 line 15 squared_diff = (y - pred) ** 2New var:....... squared_diff = tensor<(4, 4), float32, cpu, grad>02:38:02.018397 line 16 loss = squared_diff.mean()New var:....... loss = tensor<(), float32, cpu, grad>02:38:02.018674 line 17 print(loss.item())02:38:02.018852 line 18 loss.backward()26.97929000854492202:38:02.057349 line 19 optimizer.step()

仔細觀察這裡面各個 tensor 的形狀,我們不難發現,y 的形狀是 (4,),而 pred 的形狀卻是 (4, 1),他們倆相減,由於廣播的存在,我們得到的 squared_diff 的形狀就變成了 (4, 4)。

這自然不是我們想要的結果。這個問題修復起來也很簡單,把 pred 的定義改成 pred = model(x).squeeze() 即可。現在再看修改後的代碼的 TorchSnooper 的輸出:

New var:....... model = Linear(in_features=2, out_features=1, bias=True)New var:....... x = tensor<(4, 2), float32, cpu>New var:....... y = tensor<(4,), float32, cpu>New var:....... optimizer = SGD (Parameter Group 0 dampening: 0 lr: 0....omentum: 0 nesterov: False weight_decay: 0)02:46:23.545042 line 12 for _ in range(10):New var:....... _ = 002:46:23.545285 line 13 optimizer.zero_grad()02:46:23.545421 line 14 pred = model(x).squeeze()New var:....... pred = tensor<(4,), float32, cpu, grad>02:46:23.546362 line 15 squared_diff = (y - pred) ** 2New var:....... squared_diff = tensor<(4,), float32, cpu, grad>02:46:23.546645 line 16 loss = squared_diff.mean()New var:....... loss = tensor<(), float32, cpu, grad>02:46:23.546939 line 17 print(loss.item())02:46:23.547133 line 18 loss.backward()02:46:23.591090 line 19 optimizer.step()

現在這個結果看起來就正常了。並且經過測試,loss 現在已經可以降到很接近 0 了。大功告成。

相關焦點

  • 給訓練踩踩油門:編寫高效的PyTorch代碼技巧
    在很多方面,它和 NumPy 都非常相似,但是它可以在不需要代碼做多大改變的情況下,在 CPUs,GPUs,TPUs 上實現計算,以及非常容易實現分布式計算的操作。PyTorch 的其中一個最重要的特徵就是自動微分。它可以讓需要採用梯度下降算法進行訓練的機器學習算法的實現更加方便,可以更高效的自動計算函數的梯度。
  • PyTorch常用代碼段
    本次實驗的代碼大家可以到下面的 GitHub倉庫 連結中進行下載與學習(點擊文末閱讀原文可直達)。https://github.com/CVHuber/Pytorch_common_code張量基本信息tensor = torch.randn(3,4,5)print(tensor.type())  # 數據類型print(tensor.size())  # 張量大小print(tensor.dim())   #
  • 一行代碼即可調用18款主流模型!PyTorch Hub輕鬆解決論文可復現性
    圖靈獎得主Yann LeCun發推表示,只需要一行代碼就可以調用所有倉庫裡的模型,通過一個pull請求來發布你自己的模型。同時,PyTorch Hub整合了Google Colab,併集成了論文代碼結合網站Papers With Code,可以直接找到論文的代碼。PyTorch Hub怎麼用?
  • 60分鐘入門深度學習工具PyTorch
    ):x = torch.tensor([5.5, 3])print(x)tensor([5.5000, 3.0000])或者在已有的張量(tensor)中構建一個張量(tensor).tensor([1.3441], device='cuda:0')tensor([1.3441], dtype=torch.float64)本章的官方代碼:Jupyter notebook:
  • Python代碼性能調試和優化
    本文蟲蟲就給大家介紹一下如何調試Python應用的性能,以及怎麼對其進行優化。Python性能調試要進行Python性能,前提條件是要找出程序中的性能瓶頸。找出程序中影響程序性能的代碼。有經驗的開發者一般都能很容易能找出程序的瓶頸,但對於普通碼農找出系統的問題代碼則很難,為了能快捷有效的發現程序的性能瓶頸就需要進行性能調試,此處我們以一個實際例子進行介紹,以下程序是計算e的x(1..n)次的冪,其代碼如下:# performance.pyfrom decimal import *def exp(x):getcontext().
  • 分離硬體和代碼、穩定 API,PyTorch Lightning 1.0.0 版本正式發布
    PyTorch Lightning 倡導對深度學習代碼進行重構,將『工程(硬體)』與『科學(代碼)』分割開,然後將前者委託給框架。」博客地址:https://medium.com/pytorch/pytorch-lightning-1-0-from-0-600k-80fc65e2fab0 GitHub 地址:https://github.com/PyTorchLightning/pytorch-lightningPyTorch Lightning 的運行原理和目標人工智慧的發展速度比單一框架發展要快得多
  • TensorFlow與PyTorch之爭,哪個框架最適合深度學習
    有關其開發的更多信息請參閱論文《PyTorch 中的自動微分》。論文地址:https://openreview.net/pdf?id=BJJsrmfCZPyTorch 很簡潔、易於使用、支持動態計算圖而且內存使用很高效,因此越來越受歡迎。接下來還會更詳細地介紹。
  • 13個算法工程師必須掌握的PyTorch Tricks
    2、查看模型每層輸出詳情Keras有一個簡潔的API來查看模型的每一層輸出尺寸,這在調試網絡時非常有用。現在在PyTorch中也可以實現這個功能。@coldleaf 的補充)import cv2import torchimage = cv2.imread(img_path)image = torch.tensor(image)print(image.size())img = image.unsqueeze(dim=0) print(img.size
  • PyTorch自動求導:Autograd
    autograd 包為張量上的所有操作提供了自動求導機制。它是一個在運行時定義(define-by-run)的框架,這意味著反向傳播是根據代碼如何運行來決定的,並且每次迭代可以是不同的。張量張量:n維向量torch.Tensor 是這個包的核心類。如果設置它的屬性.requires_grad 為 True,那麼它將會追蹤對於該張量的所有操作。
  • 如何使用pytorch自動求梯度
    由損失函數求導的過程,稱為「反向傳播」,求導是件辛苦事兒,所以自動求導基本上是各種深度學習框架的基本功能和最重要的功能之一,PyTorch也不例外。一、pytorch自動求導初步認識比如有一個函數,y=x的平方(y=x2),在x=3的時候它的導數為6,我們通過代碼來演示這樣一個過程。
  • 輸入示例,自動生成代碼:TensorFlow官方工具TF-Coder已開源
    最近,谷歌 TensorFlow 開源了一個幫助開發者寫 TensorFlow 代碼的程序合成工具 TF-Coder。那麼,除了直接對張量操縱進行編碼以外,如果僅通過一個說明性示例進行演示,就能自動獲取相應的代碼呢?這個想法聽起來很誘人,而 TensorFlow Coder(TF-Coder)使這成為可能!
  • 一行代碼安裝,TPU也能運行PyTorch,修改少量代碼即可快速移植
    pip install pytorch-lightning該項目的開發者William Falcon說,PyTorch Lightning是他在紐約大學和FAIR做博士生時研發,專門為從事AI研究的專業研究人員和博士生創建的。
  • TensorFlow 2.1指南:keras模式、渴望模式和圖形模式(附代碼)
    儘管我一直是Keras-Tensorflow兼容的忠實擁護者,但始終有一個非常具體的缺點:調試功能。如你所知,在Tensorflow中,存在這樣的範例:首先定義計算圖,然後進行編譯(或將其移至GPU),然後運行它。這種範例非常好,從技術上來講很有意義,但是,一旦在GPU中擁有了模型,幾乎就不可能對其進行調試。
  • 華為雲應用編排,手把手教您完成pytorch代碼部署
    PyTorch作為Torch框架的繼任者,並不僅僅只是移植代碼並提供接口,而是深入支持了Python,對大量模塊進行了重構,並新增了最先進的變量自動求導系統,成為時下最流行的動態圖框架。在入門時,PyTorch提供了完整的文檔,並有著活躍的社區論壇,對於新手而言上手遇到的難關容易解決。
  • 初學AI神經網絡應該選擇Keras或是Pytorch框架?
    但比起Keras具有更大的靈活性和控制能力,但又不必進行任何複雜的聲明式編程,如果想深入了解機器學習pytorch庫就是不錯的選擇。二、應用領域keras比較適合入門級學習,如程式設計師、系統開發者等非專業開發者,結合其後端(tensorflow等)部署,在工業領域在目前應用範圍廣。
  • 雲計算學習:用PyTorch實現一個簡單的分類器
    所以我總結了一下自己當初學習的路線,準備繼續深入鞏固自己的 pytorch 基礎;另一方面,也想從頭整理一個教程,從沒有接觸過 pytorch 開始,到完成一些最新論文裡面的工作。以自己的學習筆記整理為主線,大家可以針對參考。第一篇筆記,我們先完成一個簡單的分類器。
  • Pytorch中的分布式神經網絡訓練|pytorch|bat|拆分|調用_網易訂閱
    以下是用於實現累積漸變的PyTorch代碼段。在此處了解有關它們的更多信息https://pytorch.org/tutorials/intermediate/dist_tuto.html#advanced-topics  在每個GPU上啟動單獨的進程。同樣使用torch.distributed.launch實用程序功能。假設我們在群集節點上有4個GPU,我們希望在這些GPU上用於設置分布式培訓。
  • Pytorch中的分布式神經網絡訓練
    以下是用於實現累積漸變的PyTorch代碼段。在此處了解有關它們的更多信息https://pytorch.org/tutorials/intermediate/dist_tuto.html#advanced-topics在每個GPU上啟動單獨的進程。同樣使用torch.distributed.launch實用程序功能。假設我們在群集節點上有4個GPU,我們希望在這些GPU上用於設置分布式培訓。
  • 華為雲pytorch代碼怎麼部署?
    PyTorch作為Torch框架的繼任者,並不僅僅只是移植代碼並提供接口,而是深入支持了Python,對大量模塊進行了重構,並新增了最先進的變量自動求導系統,成為時下最流行的動態圖框架。 在入門時,PyTorch提供了完整的文檔,並有著活躍的社區論壇,對於新手而言上手遇到的難關容易解決。
  • Pytorch框架安裝方法(基於Anaconda環境和Pycharm IDE)
    2.安裝PyTorch2.1 激活pytorch房間conda activate pytorch當前面從(base)變為(pytorch)時表示此時已經切換到pytorch房間,隨後正式進入安裝pytorch環節。