⑴ 一個pytorch的bug,居然能讓整個Python社區崩潰
明敏 發自 凹非寺
量子位 報道 | 公眾號 QbitAI
到底是怎樣的一個清廳bug,能讓95%的Pytorch庫中招,就連特斯拉AI總監深受困擾?
還別說,這個bug雖小,但有夠「狡猾」的。
這就是最近Reddit上熱議的一個話題,是一位網友扮伍在使用再平常不過的Pytorch+Numpy組合時發現。
最主要的是,在代碼能夠跑通的情況下,它甚至還會影響模型的准確率!
除此之外,網友熱議的另外一個點,竟然是:
而是它到底算不算一個bug?
這究竟是怎麼一回事?
事情的起因是一位網友發現,在PyTorch中用NumPy來生成隨機數時,受到數據預處理的限制,會多進程並行載入數據,但最後每個進程返回的隨機數卻是相同的。
他還舉出例子證實了自己的說法。
如下是一個示例數據集,它會返回三個元素的隨機向量。這里採用的批量大小分別為2,工作進程為4個。
然後神奇的事情發生了:每個進程返回的隨機數都是一樣的。
這個結果會著實讓人有點一頭霧水,就好像數學應用題求小明走一段路程需要花費多少時間,而你卻算出來了負數。
發現了問題後,這位網友還在GitHub上下載了超過10萬個PyTorch庫,用同樣的方法產生隨機數。
結果更加令人震驚:居然有超過95%的庫都受到這個問題的困擾!
這其中不乏PyTorch的官方教程和OpenAI的代碼,連特斯拉AI總監Karpathy也承認自己「被坑過」!
但有一說一,這個bug想要解決也不難:只需要在每個epoch都重新設置seed,或者用python內置的隨機數生成器就可以避免這個問題。
到底是不是bug?
如果這個問題已經可以解決,為什麼還會引起如此大的討論呢?
因為網友們的重點已經上升到了「哲學」層面:
這到底是不是一個bug?
在Reddit上有人認為:這不是一個bug。
雖然這個問題非常常見,但它並不算是一個bug,而是一個在調試時不可以忽略的點。
就是這個觀點,激起了千層浪花,許多人都認為他忽略了問題的關鍵所在。
這不是產生偽隨機數的問題,也不是numpy的問題,問題的核心是在於PyTorch中的DataLoader的實現
對於包含隨機轉換的數據載入pipeline,這意味著每個worker都將選擇「相同」的轉換。而現在NN中的許多數據載入pipeline,都使用某種類型的隨機轉換來進行數據增強,所以不重新初始化可能是一個預設。
另一位網友也表示這個bug其實是在預設程序下運行才出現的,應該向更多用戶指出來。
並且95%以上的Pytorch庫受此困擾,也絕不是危言聳聽。
有人就分享出了自己此前的慘痛經歷答缺隱:
我認識到這一點是之前跑了許多進程來創建數據集時,然而發現其中一半的數據是重復的,之後花了很長的時間才發現哪裡出了問題。
也有用戶補充說,如果 95% 以上的用戶使用時出現錯誤,那麼代碼就是錯的。
順便一提,這提供了Karpathy定律的另一個例子:即使你搞砸了一些非常基本代碼,「neural nets want to work」。
你有踩過PyTorch的坑嗎?
如上的bug並不是偶然,隨著用PyTorch的人越來越多,被發現的bug也就越來越多,某乎上還有PyTorch的坑之總結,被瀏覽量高達49w。
其中從向量、函數到model.train(),無論是真bug還是自己出了bug,大家的血淚史還真的是各有千秋。
所以,關於PyTorch你可以分享的經驗血淚史嗎?
歡迎評論區留言討論~
參考鏈接:
[1]https://tanelp.github.io/posts/a-bug-that-plagues-thousands-of-open-source-ml-projects/
[2]https://www.reddit.com/r/MachineLearning/comments/mocpgj/p_using_pytorch_numpy_a_bug_that_plagues/
[3]https://www.hu.com/question/67209417/answer/866488638
— 完 —
⑵ PyTorch生成3D模型
本文將介紹如何利用深度學習技術生成3D模型,使用了PyTorch和PolyGen。
有一個新興的深度學習研究領域專注於將 DL 技術應用於 3D 幾何和計算機圖形應用程序,這一長期研究的集合證明了這一點。對於希望自己嘗試一些 3D 深度學習的 PyTorch 用戶,Kaolin 庫值得研究。對於 TensorFlow 用戶,還有TensorFlow Graphics。一個特別熱門的子領域是 3D 模型的生成。創造性地組合 3D 模型、從圖像快速生成 3D 模型以及為其他機器學習應用程序和模擬創建合成數據只是 3D 模型生成的無數用例中的一小部分。
然而,在 3D 深度學習研究領域,為你的數據選擇合適的表示是成功的一半。在計算機視覺中,數據的結構非常簡單:圖像由密集的像素組成,這些像素整齊均勻地排列成精確的網格。3D 數據的世界沒有這種一致性。3D 模型可以表示為體素、點雲、網格、多視圖圖像集等。這些輸入表示也都有自己的一組缺點。例如,體素盡管計算成本很高,但輸出解析度很低。點雲不編碼表面或其法線的概念,因此不能僅從點雲唯一地推好段型斷出拓撲。網格也不對拓撲進行唯一編碼,因為任何網格都可以細分以產生相似的表面。PolyGen,一種用於網格的神經生成模型,它聯合估計模型的面和頂點以直接生成網格。DeepMind GitHub 上提供了官方實現。
現在經典的PointNet論文為點雲數據建模提供了藍圖,例如 3D 模型的頂點。它是一種通用演算法,不對 3D 模型的面或佔用進行建模,因此無法單獨使用 PointNet 生成獨特的防水網格。3D-R2N2採用的體素方法將我們都熟悉的 2D 卷積擴展到 3D,並自然地從 RGB 圖像生成防水網格。然而,在更高的空間解析度下,體素表示的計算成本很高,有效地限制了它可以產生的網格的大小。
Pixel2Mesh可以通過變形模板網格(通常是橢圓體)從單個圖像預測 3D 模型的頂點和面。目標模型必須與模板網格同胚,因此使用凸模板網格(例如橢圓體)會在椅子和燈等高度非凸的物體上引入許多假面。拓撲修改網路(TMN) 通過引入兩個新階段在 Pixel2Mesh 上進行迭代:拓撲修改階段用於修剪會增加模型重建誤差的錯誤面,以及邊界細化階段以平滑由面修剪引入的鋸齒狀邊界。如果你有興趣,我強烈建議同時查看AtlasNet和Hierarchical Surface Prediction。
雖然變形和細化模板網格的常用方法表現良好,但它始於對模型拓撲的主要假設。就其核心而言,3D 模型只是 3D 空間中的一組頂點,通過各個面進行分組和連接在一起。是否可以避開中間表示並直接預測這些頂點和面?
PolyGen 通過將 3D 模型表示為頂點和面的嚴格排序序列,而不是圖像、體素或點雲,對模型生成任務採取了一種相當獨特的方法。這種嚴格的排序使他們能夠應用基於注意力的序列建模方法來生成 3D 網格,就像 BERT 或 GPT 模型對文本所做的那樣。
PolyGen 的總體目標有兩個:首先為 3D 模型生成一組合理的頂點(可能以圖像、體素或類標簽為條件),然後生成一系列面,一個接一個,連接頂點在一起,並為此模型提供一個合理的表面。組合模型將網格上的分布 p(M) 表示為兩個模型之間的聯合分布:頂點模型 p(V) 表示頂點,面模型 p(F|V) 表示以頂點為條件的面。
頂點模型是一個解碼器,它試圖預測以友猜先前標記為條件的序列中的下一個標記(並且可選地以圖像、體素欄位或類標簽為條件)。人臉模型由一個編碼器和一個解碼器指針網路組成,該網路表示頂點序列上的分布。該指針網路一次有效地「選擇」一個頂點,以添加到當前面序列並構建模型的面。該模型以先前的人臉序列和整個頂點序列為條件。由於 PolyGen 架構相當復雜並且依賴於各種概念,因此本文將僅限於頂點模型。
流行的ShapeNetCore數據集中的每個模型都可以表示為頂點和面的集合。每個頂點由一個 (x, y, z) 坐標組成,該坐標描述了 3D 網燃沒格中的一個點。每個面都是一個索引列表,指向構成該面角的頂點。對於三角形面,此列表長 3 個索引。對於 n 邊形面,此列表是可變長度的。原始數據集非常大,因此為了節省時間,我在此處提供了一個更輕量級的預處理數據集子集供你進行實驗。該子集僅包含來自 5 個形狀類別的模型,並且在轉換為 n 邊形後少於 800 個頂點(如下所述)。
為了使序列建模方法發揮作用,數據必須以一種受約束的、確定性的方式表示,以盡可能多地消除可變性。出於這個原因,作者對數據集進行了一些簡化。首先,他們將所有輸入模型從三角形(連接 3 個頂點的面)轉換為 n 邊形(連接 n 個頂點的面),使用Blender 的平面抽取修改器合並面。這為相同的拓撲提供了更緊湊的表示,並減少了三角剖分中的歧義,因為大型網格並不總是具有唯一的三角剖分。為了篇幅的緣故,我不會在這篇文章中討論 Blender 腳本,但有很多資源,包括官方文檔和GitHub 上的這套優秀示例,很好地涵蓋了這個主題。我提供的數據集已經預先抽取。
要繼續進行,請下載此示例 cube.obj 文件。這個模型是一個基本的立方體,有 8 個頂點和 6 個面。以下簡單代碼片段從單個 .obj 文件中讀取所有頂點。
其次,頂點首先從它們的 z 軸(在這種情況下為垂直軸)按升序排序,然後是 y 軸,最後是 x 軸。這樣,模型頂點是自下而上表示的。在 vanilla PolyGen 模型中,然後將頂點連接成一維序列向量,對於較大的模型,該向量最終會得到一個非常長的序列向量。作者在論文的附錄 E 中描述了一些減輕這種負擔的修改。
要對一系列頂點進行排序,我們可以使用字典排序。這與對字典中的單詞進行排序時採用的方法相同。要對兩個單詞進行排序,您將查看第一個字母,然後如果有平局,則查看第二個字母,依此類推。對於「aardvark」和「apple」這兩個詞,第一個字母是「a」和「a」,所以我們移動到第二個字母「a」和「p」來告訴我「aardvark」在「apple」之前。在這種情況下,我們的「字母」是按順序排列的 z、y 和 x 坐標。
最後,頂點坐標被歸一化,然後被量化以將它們轉換為離散的 8 位值。這種方法已在像素遞歸神經網路和WaveNet中用於對音頻信號進行建模,使它們能夠對頂點值施加分類分布。在最初的WaveNet論文中,作者評論說「分類分布更靈活,並且可以更容易地對任意分布進行建模,因為它不對它們的形狀做任何假設。」 這種質量對於建模復雜的依賴關系很重要,例如 3D 模型中頂點之間的對稱性。
頂點模型由一個解碼器網路組成,它具有變壓器模型的所有標准特徵:輸入嵌入、18 個變壓器解碼器層的堆棧、層歸一化,最後是在所有可能的序列標記上表示的 softmax 分布。給定一個長度為 N 的扁平頂點序列 Vseq ,其目標是在給定模型參數的情況下最大化數據序列的對數似然:
與 LSTM 不同的是,transformer 模型能夠以並行方式處理順序輸入,同時仍使來自序列一部分的信息能夠為另一部分提供上下文。這一切都歸功於他們的注意力模塊。3D 模型的頂點包含各種對稱性和遠點之間的復雜依賴關系。例如,考慮一個典型的桌子,其中模型對角的腿是彼此的鏡像版本。注意力模塊允許對這些類型的模式進行建模。
嵌入層是序列建模中用於將有限數量的標記轉換為特徵集的常用技術。在語言模型中,「國家」和「民族」這兩個詞的含義可能非常相似,但與「蘋果」這個詞卻相距甚遠。當單詞用唯一的標記表示時,就沒有相似性或差異性的固有概念。嵌入層將這些標記轉換為矢量表示,可以對有意義的距離感進行建模。
PolyGen 將同樣的原理應用於頂點。該模型使用三種類型的嵌入層:坐標表示輸入標記是 x、y 還是 z 坐標,值表示標記的值,以及位置編碼頂點的順序。每個都向模型傳達有關令牌的一條信息。由於我們的頂點一次在一個軸上輸入,坐標嵌入為模型提供了基本的坐標信息,讓它知道給定值對應的坐標類型。
值嵌入對我們之前創建的量化頂點值進行編碼。我們還需要一些序列控制點:額外的開始和停止標記分別標記序列的開始和結束,並將標記填充到最大序列長度。
由於並行化而丟失的給定序列位置 n的位置信息通過位置嵌入來恢復。 也可以使用位置編碼,一種不需要學習的封閉形式的表達。在經典的 Transformer 論文「 Attention Is All You Need 」中,作者定義了一種由不同頻率的正弦和餘弦函數組成的位置編碼。他們通過實驗確定位置嵌入的性能與位置編碼一樣好,但編碼的優勢在於比訓練中遇到的序列更長。有關位置編碼的出色視覺解釋,請查看此博客文章。
生成所有這些標記序列後,最後要做的是創建一些嵌入層並將它們組合起來。每個嵌入層都需要知道期望的輸入字典的大小和輸出的嵌入維度。每層的嵌入維數為 256,這意味著我們可以將它們與加法相結合。字典大小取決於輸入可以具有的唯一值的數量。對於值嵌入,它是量化值的數量加上控制標記的數量。對於坐標嵌入,對於每個坐標 x、y 和 z,它是一個,對於上述任何一個(控制標記)都不是一個。最後,位置嵌入對於每個可能的位置或最大序列長度都需要一個。
PolyGen 還廣泛使用無效預測掩碼來確保其生成的頂點和面部序列編碼有效的 3D 模型。例如,必須強制執行諸如「z 坐標不遞減」和「停止標記只能出現在完整頂點(z、y 和 x 標記的三元組)之後」之類的規則,以防止模型產生無效的網格. 作者在論文的附錄 F 中提供了他們使用的掩蔽的廣泛列表。這些約束僅在預測時強制執行,因為它們實際上會損害訓練性能。
與許多序列預測模型一樣,該模型是自回歸的,這意味著給定時間步的輸出是下一個時間步的可能值的分布。整個序列一次預測一個標記,模型在每一步都會查看先前時間步驟中的所有標記以選擇其下一個標記。解碼策略決定了它如何從這個分布中選擇下一個Token。
如果使用次優解碼策略,生成模型有時會陷入重復循環或產生質量較差的序列。我們都看到生成的文本看起來像是胡說八道。PolyGen 採用稱為 核采樣 的解碼策略來生成高質量序列。原始論文在文本生成上下文中應用了這種方法,但它也可以應用於頂點。前提很簡單:僅從 softmax 分布中共享 top-p 概率質量的標記中隨機抽取下一個標記。這在推理時應用以生成網格,同時避免序列退化。有關核采樣的 PyTorch 實現,請參閱此要點。
除了無條件生成模型外,PolyGen 還支持使用類標簽、圖像和體素進行輸入調節。這些可以指導生成具有特定類型、外觀或形狀的網格。類標簽通過嵌入投影,然後添加到每個注意力塊中的自注意力層之後。對於圖像和體素,編碼器創建一組嵌入,然後用於與轉換器解碼器的交叉注意。
PolyGen 模型描述了一個強大、高效和靈活的框架,用於有條件地生成 3D 網格。序列生成可以在各種條件和輸入類型下完成,從圖像到體素到簡單的類標簽,甚至只是一個起始標記。表示網格頂點分布的頂點模型只是聯合分布難題的一部分。我打算在以後的文章中介紹面部模型。同時,我鼓勵你查看DeepMind 的 TensorFlow 實現,並嘗試生成條件模型!
原文鏈接:http://www.bimant.com/blog/polygen-3d-models/
⑶ Pytorch訓練中斷,繼續訓練(轉)
pytorch保存模型非常簡單,主要有兩種方法:
一般地,採用一條語句即可保存參數螞碼:
其中model指定義的模型 實例變數 ,如 model=vgg16( ), path是保存參數的路徑,如 path='./model.pth' , path='./model.tar', path='./model.pkl', 保存參數的文件一定要有後綴擴展名。
特別地,如果還想保存某一次訓練採用的優化器、epochs等信息,可將這些信息組合起來構成團盯一個字典,然後將字典保存起來:
針對上述第一種情況,也只需要一句即可載入模型:
針對上述第二種以字典形式保存的方法,載入方塌物和式如下:
需要注意的是,只保存參數的方法在載入的時候要事先定義好跟原模型一致的模型,並在該模型的實例對象(假設名為model)上進行載入,即在使用上述載入語句前已經有定義了一個和原模型一樣的Net, 並且進行了實例化 model=Net( ) 。
另外,如果每一個epoch或每n個epoch都要保存一次參數,可設置不同的path,如 path='./model' + str(epoch) +'.pth',這樣,不同epoch的參數就能保存在不同的文件中,選擇保存識別率最大的模型參數也一樣,只需在保存模型語句前加個if判斷語句即可。
下面給出一個具體的例子程序,該程序只保存最新的參數:
在訓練模型的時候可能會因為一些問題導致程序中斷,或者常常需要觀察訓練情況的變化來更改學習率等參數,這時候就需要載入中斷前保存的模型,並在此基礎上繼續訓練,這時候只需要對上例中的 main() 函數做相應的修改即可,修改後的 main() 函數如下:
以上方法,如果想在命令行進行操作執行,都只需加入argpase模塊參數即可,相關方法可參考我的 博客
用法可參照上例。
⑷ Pytorch_循環神經網路RNN
RNN是Recurrent Neural Networks的縮寫,即循環神經網路,它常用於解決序列問題。RNN有記憶功能,除了當前輸入,還把上下文環境作為預測的依據。它常用於語音識別、翻譯等場景之中。
RNN是序列模型的基礎,盡管能夠直接調用現成的RNN演算法,但後續的復雜網路很多構建在RNN網路的基礎之上,如Attention方法需要使用RNN的隱藏層數據。RNN的原理並不復雜,但由於其中包括循環,很難用語言或者畫圖來描述,最好的方法是自己手動編寫一個RNN網路。本篇將介紹RNN網路的原理及具體實現。
在學習循環神經網路之前,先看看什麼是序列。序列sequence簡稱seq,是有先後順序的一組數據。自然語言處理是最為典型的序列問題,比如將一句話翻譯成另一句話時,其中某個詞彙的含義不僅取決於它本身,還與它前後的多個單詞相關。類似的,如果想預測電影的情節發展,不僅與當前的畫面有關,還與當前的一系列前情有關。在使用序列模型預測的過程中,輸入是序列,而輸出是一個或多個預測值。
在使用深度學習模型解決序列問題時, 最容易混淆的是,序列與序列中的元素 。在不同的場景中,定義序列的方式不同,當分析單詞的感情色彩時,一個單詞是一個序列seq;當分析句子感情色彩時,一個句子是一個seq,其中的每個單詞是序列中的元素;當分析文章感情色彩時,一篇文章是一個seq。簡單地說,seq是最終使用模型時的輸入數據,由一系列元素組成。
當分析句子的感情色彩時,以句為seq,而句中包含的各個單詞的含義,以及單詞間的關系是具體分析的對象,此時,單詞是序列中的元素,每一個單詞又可有多維特徵。從單詞中提取特徵的方法將在後面的自然語言處理中介紹。
RNN有很多種形式,單個輸入單個輸入;桐脊絕多個輸入多個輸出,單個輸入多個輸出等等。
舉個最簡單的例子:用模型預測一個四字短語的感情色彩,它的輸入為四個元素X={x1,x2,x3,x4},它的輸出為單個值Y={y1}。字的排列順序至關重要,比如「從好變壞」和「從壞變好」,表達的意思完全相反。之所以輸入輸出的個數不需要一一對應,是因為中間的隱藏層,變向存儲中間信息。
如果把模型設想成黑盒,如下圖所示:
如果模型使用全連接網路,在每次迭代時,模型將計算各個元素x1,x2...中各個特徵f1,f2...代入網路,求它們對結果y的貢獻度。
RNN網路則要復雜一些,在模型內部,它不是將序列中所有元素的特徵一次性輸入模型,而是每一次將序列中單個元素的特徵輸入模型,下圖描述了RNN的數據處理過程,左圖為分步展示,右圖將所有時序步驟抽象成單一模塊。
第一步:將第一個元素x1的特徵f1,f2...輸入模型,模型根據輸入計算出隱藏層h。
第二步:將第二個元素x2的特徵輸入模型,模型根據輸入和上一步產生的h再計算隱藏層h,其它元素以此類推。
第三步:將最後一個元素xn的特徵輸入模型,模型根據輸入和上一步產生的h計算隱藏層h和預測值y。
隱藏層h可視為將序列中前面元素的特徵和位置通過編碼向前傳遞,從而對輸出y發生作用,隱藏層的大小決定了模型攜帶信野中息量的多少。隱藏層也可以作為模型的輸入從外部傳入,以及作為模型的輸出返回給外部調用。
本例仍使用上篇中的航空乘客序列數據,分別用兩種方法實現RNN:自己編寫程序實現RNN模型,以及調用Pytorch提供的RNN模型。前一種方法主要用於剖析原理,後一種用於展示常用的調用方法。
首先導入頭文件,讀取乘客數據,做歸一化處理,並將數據切分為測試集和訓練集,與之前不同的是加入了create_dataset函數,用於生成序列數據,序列的輸入部分,每個局姿元素中包括兩個特徵:前一個月的乘客量prev和月份值mon,這里的月份值並不是關鍵特徵,主要用於在常式中展示如何使用多個特徵。
第一步:實現模型類,此例中的RNN模型除了全連接層,還生成了一個隱藏層,並在下一次前向傳播時將隱藏層輸出的數據與輸入數據組合後再代入模型運算。
第二步,訓練模型,使用全部數據訓練500次,在每次訓練時,內部for循環將序列中的每個元素代入模型,並將模型輸出的隱藏層和下一個元素一起送入下一次迭代。
第三步:預測和作圖,預測的過程與訓練一樣,把全部數據拆分成元素代入模型,並將每一次預測結果存儲在數組中,並作圖顯示。
需要注意的是,在訓練和預測過程中,每一次開始輸入新序列之前,都重置了隱藏層,這是由於隱藏層的內容只與當前序列相關,序列之間並無連續性。
程序輸出結果如下圖所示:
經過500次迭代,使用RNN的效果明顯優於上一篇中使用全連接網路的擬合效果,還可以通過調整超參數以及選擇不同特徵,進一步優化。
使用Pytorch提供的RNN模型,torch.nn.RNN類可直接使用,是循環網路最常用的解決方案。RNN,LSTM,GRU等循環網路都實現在同一源碼文件torch/nn/moles/rnn.py中。
第一步:創建模型,模型包含兩部分,第一部分是Pytorch提供的RNN層,第二部分是一個全連接層,用於將RNN的輸出轉換成輸出目標的維度。
Pytorch的RNN前向傳播允許將隱藏層數據h作為參數傳入模型,並將模型產生的h和y作為函數返回值。形如: pred, h_state = model(x, h_state)
什麼情況下需要接收隱藏層的狀態h_state,並轉入下一次迭代呢?當處理單個seq時,h在內部前向傳遞;當序列與序列之間也存在前後依賴關系時,可以接收h_state並傳入下一步迭代。另外,當模型比較復雜如LSTM模型包含眾多參數,傳遞會增加模型的復雜度,使訓練過程變慢。本例未將隱藏層轉到模型外部,這是由於模型內部實現了對整個序列的處理,而非處理單個元素,而每次代入的序列之間又沒有連續性。
第二步:訓練模型,與上例中把序列中的元素逐個代入模型不同,本例一次性把整個序列代入了模型,因此,只有一個for循環。
Pythorch支持批量處理,前向傳遞時輸入數據格式是[seq_len, batch_size, input_dim),本例中輸入數據的維度是[100, 1, 2],input_dim是每個元素的特徵數,batch_size是訓練的序列個數,seq_len是序列的長度,這里使用70%作為訓練數據,seq_len為100。如果數據維度的順序與要求不一致,一般使用transpose轉換。
第三步:預測和作圖,將全部數據作為序列代入模型,並用預測值作圖。
程序輸出結果如下圖所示:
可以看到,經過500次迭代,在前100個元素的訓練集上擬合得很好,但在測試集效果較差,可能存在過擬合。