戰(zhàn)手冊(cè):從參數(shù)精講到性能優(yōu)化與避坑指南)
1. 項(xiàng)目概述為什么我們需要一份“詳解大全”如果你正在用PyTorch做深度學(xué)習(xí)項(xiàng)目或者正準(zhǔn)備從TensorFlow、Keras等其他框架切換過來大概率會(huì)遇到一個(gè)非常具體又讓人頭疼的問題某個(gè)API的文檔看懂了但一上手就報(bào)錯(cuò)或者文檔里只說了“是什么”但沒說“什么時(shí)候用”和“為什么這么用”。官方文檔固然權(quán)威但它更像一本字典追求的是準(zhǔn)確和全面而不是手把手的教學(xué)。對(duì)于一個(gè)復(fù)雜的、參數(shù)眾多的函數(shù)比如torch.nn.functional.conv2d你往往需要自己反復(fù)試錯(cuò)才能搞明白padding填‘same’和填具體數(shù)字的區(qū)別或者groups參數(shù)在深度可分離卷積里到底怎么玩。這就是我動(dòng)手整理這份“PyTorch Python API詳解大全”的初衷。它不是一個(gè)簡(jiǎn)單的官方文檔鏡像而是一個(gè)由一線開發(fā)者視角出發(fā)的、帶有大量注釋、示例、避坑指南和性能考量的實(shí)戰(zhàn)手冊(cè)。我的目標(biāo)是當(dāng)你對(duì)某個(gè)API的用法存疑時(shí)來這里不僅能找到標(biāo)準(zhǔn)的函數(shù)簽名更能看到它在真實(shí)項(xiàng)目里的樣子理解其設(shè)計(jì)哲學(xué)并避開我踩過的那些坑。這份文檔是“持續(xù)更新”的因?yàn)镻yTorch生態(tài)本身就在快速演進(jìn)我也會(huì)把在新版本、新項(xiàng)目中驗(yàn)證過的經(jīng)驗(yàn)和技巧不斷補(bǔ)充進(jìn)來。2. 內(nèi)容整體設(shè)計(jì)與思路拆解2.1 核心定位從“字典”到“向?qū)А笔忻嫔系腜yTorch學(xué)習(xí)資料很多但大多集中在模型構(gòu)建和訓(xùn)練流程的宏觀講解上。對(duì)于API細(xì)節(jié)往往淺嘗輒止。這份大全的定位非常明確深度聚焦于Python API層做精做透。它服務(wù)于以下幾類讀者PyTorch初學(xué)者在跟著教程跑通第一個(gè)模型后希望深入理解每一行代碼背后的含義。中級(jí)開發(fā)者在構(gòu)建復(fù)雜模型或進(jìn)行性能優(yōu)化時(shí)需要精確掌握高級(jí)API的用法和細(xì)微差別。從其他框架遷移的開發(fā)者需要快速找到PyTorch中與TensorFlow/Keras等框架功能對(duì)應(yīng)的API并理解其差異?;谶@個(gè)定位我的內(nèi)容設(shè)計(jì)遵循以下原則按功能模塊組織完全參照torch,torch.nn,torch.nn.functional,torch.optim,torch.utils.data等核心模塊來劃分章節(jié)符合開發(fā)者的查找習(xí)慣。超越官方文檔每個(gè)API的解析都會(huì)包含“官方定義”、“參數(shù)精講”、“代碼示例”、“常見誤區(qū)”和“性能提示”五個(gè)部分。其中“參數(shù)精講”和“常見誤區(qū)”是精華所在。強(qiáng)調(diào)對(duì)比與選擇對(duì)于功能相似的API如torch.cat和torch.stacknn.MaxPool2d和nn.AdaptiveMaxPool2d會(huì)制作對(duì)比表格清晰闡述適用場(chǎng)景幫助你做技術(shù)選型。2.2 信息架構(gòu)如何讓海量信息變得易查易用PyTorch的API數(shù)量龐大如何組織是關(guān)鍵。我采用了“樹狀結(jié)構(gòu)標(biāo)簽索引”的方式。樹狀結(jié)構(gòu)是主干即按照官方模塊的層次來組織內(nèi)容。例如- torch - 創(chuàng)建操作 (如torch.tensor, torch.zeros, torch.randn) - 索引、切片、連接、換位 (如torch.cat, torch.stack, torch.transpose) - 數(shù)學(xué)運(yùn)算 (如torch.add, torch.mm, torch.matmul) - torch.nn - 容器 (如nn.Sequential, nn.ModuleList) - 卷積層 (如nn.Conv2d) - 池化層 (如nn.MaxPool2d) - ...這保證了內(nèi)容的系統(tǒng)性和完整性。標(biāo)簽索引是枝葉用于橫向關(guān)聯(lián)。我會(huì)為每個(gè)API打上多個(gè)標(biāo)簽例如#張量創(chuàng)建、#形狀操作、#數(shù)學(xué)運(yùn)算#高頻使用、#易錯(cuò)點(diǎn)、#性能關(guān)鍵#1.0-兼容、#2.0-新特性未來計(jì)劃構(gòu)建一個(gè)簡(jiǎn)單的靜態(tài)網(wǎng)頁搜索讓你可以通過標(biāo)簽或關(guān)鍵字快速定位到所有相關(guān)API的解析解決“我知道有個(gè)功能但忘了函數(shù)名”的問題。3. 核心細(xì)節(jié)解析與實(shí)操要點(diǎn)3.1 解析的深度以torch.nn.functional.conv2d為例官方文檔可能只給出函數(shù)簽名和數(shù)學(xué)公式。而在這里我們會(huì)拆解得“體無完膚”。1. 參數(shù)精講 -padding的“暗坑”padding參數(shù)可以接受兩種輸入一個(gè)整數(shù)int或一個(gè)二元組(int, int)。文檔會(huì)告訴你整數(shù)會(huì)在高和寬兩個(gè)方向填充相同大小。但這里有個(gè)關(guān)鍵細(xì)節(jié)填充是對(duì)稱的。如果你設(shè)置padding1意味著在輸入張量的上下左右各填充1行/列零值總共會(huì)讓高度和寬度各增加2。import torch import torch.nn.functional as F # 輸入形狀: (batch_size, channels, height, width) input torch.randn(1, 3, 5, 5) # 一個(gè)樣本3通道5x5大小 # 卷積核: (out_channels, in_channels, kernel_height, kernel_width) weight torch.randn(6, 3, 3, 3) # padding1: 上下左右各補(bǔ)1圈0輸入從5x5變?yōu)?x7 output F.conv2d(input, weight, padding1) print(output.shape) # torch.Size([1, 6, 5, 5]) # 輸出仍是5x5 (因?yàn)?(52-3)/1 1 5)注意這與某些框架如早期Keras的‘same’填充邏輯不同。‘same’的目標(biāo)是讓輸出尺寸與輸入相同可能會(huì)采用非對(duì)稱填充例如在右側(cè)和下側(cè)多補(bǔ)一個(gè)像素。PyTorch的padding參數(shù)是確定性的對(duì)稱填充。如果你需要‘same’效果需要自己計(jì)算padding值通常為kernel_size // 2對(duì)于奇數(shù)核。2.groups參數(shù)與深度可分離卷積這是理解現(xiàn)代輕量級(jí)模型如MobileNet的關(guān)鍵。當(dāng)groupsin_channels且out_channels是in_channels的整數(shù)倍時(shí)就實(shí)現(xiàn)了深度可分離卷積。groups1標(biāo)準(zhǔn)卷積每個(gè)輸出通道由所有輸入通道卷積求和得到。groupsin_channels深度卷積。每個(gè)輸入通道獨(dú)立地與一個(gè)卷積核卷積產(chǎn)生對(duì)應(yīng)一個(gè)輸出通道。此時(shí)要求out_channels必須是in_channels的整數(shù)倍比如in_channels3,out_channels6倍數(shù)為2。這極大地減少了參數(shù)量。# 標(biāo)準(zhǔn)卷積參數(shù)量 std_conv nn.Conv2d(in_channels3, out_channels6, kernel_size3, groups1) print(sum(p.numel() for p in std_conv.parameters())) # (6*3*3*3) 6 168 # 深度可分離卷積分為兩步這里演示groups參數(shù) # 第一步深度卷積 (groupsin_channels) depthwise_conv nn.Conv2d(in_channels3, out_channels3, kernel_size3, groups3) # 注意out_channelsin_channels print(sum(p.numel() for p in depthwise_conv.parameters())) # (3*1*3*3) 3 30 # 第二步逐點(diǎn)卷積 (1x1卷積融合通道) pointwise_conv nn.Conv2d(in_channels3, out_channels6, kernel_size1, groups1) print(sum(p.numel() for p in pointwise_conv.parameters())) # (6*3*1*1) 6 24 # 總參數(shù)量: 30 24 54遠(yuǎn)小于標(biāo)準(zhǔn)的168。通過這個(gè)例子你不僅知道了groups怎么用更理解了它背后“分組卷積”到“深度可分離卷積”的設(shè)計(jì)演進(jìn)和參數(shù)量?jī)?yōu)勢(shì)。3.2 性能提示torch.einsum的強(qiáng)大與陷阱torch.einsum愛因斯坦求和約定是一個(gè)表達(dá)力極強(qiáng)的API可以用極其簡(jiǎn)潔的公式完成復(fù)雜的張量運(yùn)算。但它是一把雙刃劍。強(qiáng)大之處一行代碼替代多重循環(huán)或多個(gè)庫函數(shù)調(diào)用。# 計(jì)算兩個(gè)批次矩陣的矩陣乘法 A torch.randn(10, 3, 4) # 10個(gè)3x4的矩陣 B torch.randn(10, 4, 5) # 10個(gè)4x5的矩陣 # 使用einsum進(jìn)行批次矩陣乘法 C torch.einsum(bij,bjk-bik, A, B) # 形狀: (10, 3, 5) # 等價(jià)于 torch.bmm(A, B)但einsum更通用。性能陷阱隱式復(fù)制某些einsum表達(dá)式在內(nèi)部實(shí)現(xiàn)時(shí)可能會(huì)創(chuàng)建中間張量導(dǎo)致額外的內(nèi)存開銷。對(duì)于超大規(guī)模張量這可能成為瓶頸。優(yōu)化限制PyTorch的einsum底層會(huì)調(diào)用一些優(yōu)化的線性代數(shù)庫但對(duì)于非常特殊的、非標(biāo)準(zhǔn)模式的求和可能無法映射到最底層的BLAS操作從而無法達(dá)到手寫矩陣乘法或使用torch.matmul的極致速度。實(shí)操心得在性能關(guān)鍵的代碼段如模型中的核心計(jì)算如果存在對(duì)應(yīng)的專用函數(shù)如torch.matmul,torch.bmm,torch.tensordot優(yōu)先使用專用函數(shù)。專用函數(shù)經(jīng)過了極致的優(yōu)化。einsum更適合用于快速原型設(shè)計(jì)、編寫清晰易懂的代碼或者處理那些沒有現(xiàn)成專用函數(shù)的復(fù)雜張量操作。在部署前可以用性能分析工具如PyTorch Profiler對(duì)比一下。4. 實(shí)操過程與核心環(huán)節(jié)實(shí)現(xiàn)4.1 以torch.utils.data.DataLoader為例構(gòu)建高效數(shù)據(jù)管道DataLoader是訓(xùn)練循環(huán)的“后勤部長(zhǎng)”它的配置直接影響GPU利用率和訓(xùn)練速度。一個(gè)高效的DataLoader配置需要考慮多個(gè)環(huán)節(jié)。1. 核心參數(shù)配置解析from torch.utils.data import DataLoader, Dataset class MyDataset(Dataset): # ... 實(shí)現(xiàn) __len__ 和 __getitem__ ... dataset MyDataset(...) dataloader DataLoader( dataset, batch_size32, # 批次大小根據(jù)GPU內(nèi)存調(diào)整。常用32, 64, 128。 shuffleTrue, # 訓(xùn)練集必須為True打亂數(shù)據(jù)防止模型學(xué)習(xí)到順序偏差。 num_workers4, # **關(guān)鍵參數(shù)**用于數(shù)據(jù)加載的子進(jìn)程數(shù)。 pin_memoryTrue, # **關(guān)鍵參數(shù)**將數(shù)據(jù)鎖頁內(nèi)存加速CPU到GPU的數(shù)據(jù)傳輸。 drop_lastFalse, # 當(dāng)樣本數(shù)不能被batch_size整除時(shí)是否丟棄最后一個(gè)不完整的batch。 collate_fnNone, # 自定義如何將多個(gè)樣本組成一個(gè)batch。默認(rèn)是 torch.stack。 )2.num_workers的設(shè)置藝術(shù)這個(gè)參數(shù)決定了有多少個(gè)子進(jìn)程并行加載數(shù)據(jù)。設(shè)置太小GPU等數(shù)據(jù)空閑設(shè)置太大進(jìn)程間切換開銷增加可能適得其反甚至導(dǎo)致內(nèi)存溢出。經(jīng)驗(yàn)公式通常設(shè)置為CPU核心數(shù)或CPU核心數(shù)-1。你可以通過os.cpu_count()獲取。動(dòng)態(tài)調(diào)整在訓(xùn)練開始時(shí)觀察GPU利用率可以用nvidia-smi -l 1監(jiān)控。如果GPU利用率長(zhǎng)期低于90%且num_workers未飽和可以嘗試逐步增加它。如果系統(tǒng)變得卡頓或內(nèi)存不足則需要減少。平臺(tái)差異在Windows上num_workers 0有時(shí)會(huì)引發(fā)多進(jìn)程問題特別是使用spawn啟動(dòng)方式時(shí)如果遇到報(bào)錯(cuò)可以嘗試設(shè)置為0在主進(jìn)程加載但會(huì)損失性能。3.pin_memoryTrue的必要性當(dāng)數(shù)據(jù)從CPU轉(zhuǎn)移到GPU時(shí)需要經(jīng)過PCIe總線。如果數(shù)據(jù)在CPU的普通內(nèi)存中轉(zhuǎn)移前需要先“釘”在物理內(nèi)存上一個(gè)耗時(shí)操作。設(shè)置pin_memoryTrue后DataLoader會(huì)使用鎖頁內(nèi)存來存放數(shù)據(jù)這部分內(nèi)存不會(huì)被操作系統(tǒng)交換到磁盤并且支持異步的、更快的DMA拷貝到GPU。在絕大多數(shù)擁有GPU的訓(xùn)練場(chǎng)景下都應(yīng)該開啟此選項(xiàng)。它用少量額外的CPU內(nèi)存開銷換來了顯著的數(shù)據(jù)傳輸加速。4. 自定義collate_fn處理變長(zhǎng)序列默認(rèn)的collate_fn使用torch.stack要求一個(gè)batch內(nèi)的所有樣本在每一個(gè)維度上大小都相同。但在NLP任務(wù)中句子長(zhǎng)度通常不一致。def my_collate_fn(batch): # batch 是一個(gè)列表每個(gè)元素是 dataset.__getitem__ 返回的 (data, label) data_list, label_list zip(*batch) # 解壓成兩個(gè)元組 # 假設(shè) data 是文本索引列表長(zhǎng)度不一 # 1. 對(duì)數(shù)據(jù)部分進(jìn)行填充 data_lengths [len(x) for x in data_list] max_len max(data_lengths) padded_data [x [0] * (max_len - len(x)) for x in data_list] # 用0填充 data_tensor torch.tensor(padded_data, dtypetorch.long) # 2. 標(biāo)簽部分直接堆疊 label_tensor torch.tensor(label_list, dtypetorch.float) # 3. 返回填充后的數(shù)據(jù)、標(biāo)簽以及原始長(zhǎng)度用于后續(xù)的pack_padded_sequence return data_tensor, label_tensor, data_lengths # 使用自定義的collate_fn dataloader DataLoader(dataset, batch_size4, collate_fnmy_collate_fn)通過自定義collate_fn我們靈活地處理了非規(guī)整數(shù)據(jù)并保留了必要的元信息data_lengths為后續(xù)RNN/LSTM的變長(zhǎng)序列處理做好了準(zhǔn)備。5. 常見問題與排查技巧實(shí)錄在長(zhǎng)期使用和解答社區(qū)問題的過程中我積累了大量關(guān)于PyTorch API的“坑點(diǎn)”。這里分享幾個(gè)最高頻的。5.1 張量形狀不匹配從錯(cuò)誤信息中快速定位PyTorch的報(bào)錯(cuò)信息相對(duì)友好但形狀錯(cuò)誤依然是最常見的。關(guān)鍵是要學(xué)會(huì)解讀錯(cuò)誤信息。RuntimeError: The size of tensor a (100) must match the size of tensor b (200) at non-singleton dimension 1這個(gè)錯(cuò)誤告訴你在第一個(gè)非單一維度dimension 1即索引為1的維度上張量a的大小是100而張量b的大小是200它們不匹配。排查步驟立即打印相關(guān)張量的形狀在出錯(cuò)行之前添加print(a.shape, b.shape)。理解廣播規(guī)則很多操作支持廣播。廣播規(guī)則是從后往前從最右邊的維度開始比對(duì)每個(gè)維度要么相等要么其中一個(gè)是1要么其中一個(gè)不存在。例如(3, 1, 5)和(5,)可以廣播因?yàn)閺挠彝?和5相等然后1和“不存在”可以廣播最后3和“不存在”可以廣播。但(3, 4)和(4, 3)不能廣播。使用torch.unsqueeze和torch.squeeze這是調(diào)整維度最常用的工具。unsqueeze增加一個(gè)大小為1的維度squeeze移除所有大小為1的維度。a torch.randn(3, 4) b torch.randn(4) # 想計(jì)算 a b但b需要廣播到(3,4) b_reshaped b.unsqueeze(0) # 形狀變?yōu)?(1, 4) # 現(xiàn)在 b_reshaped 可以廣播到 (3,4) 了 result a b_reshaped5.2 就地操作In-place Operation與自動(dòng)求導(dǎo)的沖突PyTorch中以下劃線_結(jié)尾的函數(shù)通常是就地操作如add_(),zero_()它們會(huì)直接修改原張量而不創(chuàng)建新的張量。這在節(jié)省內(nèi)存時(shí)很有用但在計(jì)算圖中使用是危險(xiǎn)的。問題場(chǎng)景import torch x torch.tensor([1., 2., 3.], requires_gradTrue) y x 2 z y * y * 3 out z.mean() # 錯(cuò)誤做法在反向傳播前對(duì)葉子節(jié)點(diǎn)x或計(jì)算圖中間的變量y進(jìn)行就地操作 # x.add_(1) # 這會(huì)破壞x的歷史導(dǎo)致反向傳播出錯(cuò) # y.add_(1) # 同樣錯(cuò)誤y是計(jì)算圖的一部分 out.backward() print(x.grad) # 如果執(zhí)行了就地操作這里可能會(huì)報(bào)錯(cuò)或得到錯(cuò)誤梯度黃金法則對(duì)任何設(shè)置了requires_gradTrue的張量或者由它們計(jì)算得到的張量避免使用就地操作。如果非要用確保操作發(fā)生在with torch.no_grad():上下文管理器中或者在對(duì).data屬性操作時(shí)需格外小心。更安全的做法是使用非就地版本如y y 1而不是y.add_(1)讓PyTorch管理新的內(nèi)存。5.3torch.nn與torch.nn.functional的選擇這是新手常問的問題。兩者功能大量重疊如何選torch.nn.Module(如nn.Conv2d,nn.ReLU)特點(diǎn)是類內(nèi)部維護(hù)可學(xué)習(xí)的參數(shù)weight,bias。使用場(chǎng)景當(dāng)你需要包含參數(shù)的層時(shí)必須用它。它會(huì)被自動(dòng)注冊(cè)到模型中其參數(shù)可以被優(yōu)化器識(shí)別和更新。優(yōu)點(diǎn)集成度高使用方便直接self.conv nn.Conv2d(...)易于保存和加載整個(gè)模型。torch.nn.functional(如F.conv2d,F.relu)特點(diǎn)是純函數(shù)不維護(hù)狀態(tài)參數(shù)。你需要自己傳入權(quán)重。使用場(chǎng)景無參數(shù)的操作如激活函數(shù)F.relu、池化F.max_pool2d、DropoutF.dropout在訓(xùn)練和評(píng)估模式下的行為不同需配合model.train()/model.eval()。需要更靈活控制時(shí)例如你想在循環(huán)中重復(fù)使用同一個(gè)權(quán)重或者實(shí)現(xiàn)自定義的、非常規(guī)的卷積操作。在forward函數(shù)中很多人在模型的forward方法里喜歡用F來調(diào)用函數(shù)代碼看起來更函數(shù)式。我的建議對(duì)于標(biāo)準(zhǔn)的、帶參數(shù)的層卷積、全連接、BatchNorm等統(tǒng)一使用nn.Module子類。代碼更清晰不易出錯(cuò)。對(duì)于無參數(shù)的操作兩者皆可。用nn.Module如nn.ReLU()可以使其成為模型的一個(gè)子模塊在模型摘要中可見用F.relu則更輕量。我個(gè)人在forward里傾向于用F因?yàn)樗鼜?qiáng)調(diào)了這是一個(gè)無狀態(tài)的函數(shù)調(diào)用。不要混用避免在同一個(gè)模型中一部分用nn.Conv2d另一部分又用F.conv2d并手動(dòng)傳參這會(huì)讓代碼風(fēng)格不一致增加維護(hù)成本。5.4 CUDA內(nèi)存管理與“Out of Memory”排查GPU內(nèi)存不足是訓(xùn)練大模型時(shí)最令人沮喪的錯(cuò)誤之一。除了增大batch_size可以從以下方面排查1. 使用torch.cuda.memory_summary()和torch.cuda.memory_allocated()在代碼關(guān)鍵位置插入這些命令可以了解內(nèi)存的分配和釋放情況。import torch print(f初始內(nèi)存: {torch.cuda.memory_allocated() / 1024**2:.2f} MB) model MyModel().cuda() input torch.randn(32, 3, 224, 224).cuda() output model(input) print(f前向傳播后內(nèi)存: {torch.cuda.memory_allocated() / 1024**2:.2f} MB) loss output.sum() loss.backward() print(f反向傳播后內(nèi)存: {torch.cuda.memory_allocated() / 1024**2:.2f} MB) # 更詳細(xì)的摘要 print(torch.cuda.memory_summary(deviceNone, abbreviatedFalse))2. 警惕張量的長(zhǎng)期引用在訓(xùn)練循環(huán)中如果你將中間變量如每個(gè)batch的損失、準(zhǔn)確率追加到一個(gè)列表里而這個(gè)列表在循環(huán)外定義那么這些張量即使很小也會(huì)一直保留在GPU內(nèi)存中因?yàn)樗鼈儽灰粋€(gè)Python列表引用著。# 錯(cuò)誤示例 losses [] for data, target in dataloader: data, target data.cuda(), target.cuda() output model(data) loss criterion(output, target) losses.append(loss) # 這里append的是包含計(jì)算圖的loss張量 optimizer.zero_grad() loss.backward() optimizer.step() # 循環(huán)結(jié)束后losses列表里的所有張量都還在GPU內(nèi)存里修正方法只保留標(biāo)量值或轉(zhuǎn)移到CPU。losses [] for data, target in dataloader: ... loss criterion(output, target) losses.append(loss.item()) # 使用 .item() 獲取Python標(biāo)量 # 或者 # losses.append(loss.detach().cpu().item()) ...3. 使用梯度累積來模擬大Batch當(dāng)GPU內(nèi)存裝不下目標(biāo)batch_size時(shí)可以使用梯度累積。原理是用小batch_size進(jìn)行多次前向傳播累加梯度達(dá)到等效大batch_size的效果后再更新參數(shù)。batch_size 32 accumulation_steps 4 # 模擬的等效batch_size是 32 * 4 128 effective_batch_size batch_size * accumulation_steps optimizer.zero_grad() # 在累積循環(huán)開始前清零一次 for i, (data, target) in enumerate(dataloader): output model(data) loss criterion(output, target) loss loss / accumulation_steps # 損失按累積步數(shù)縮放 loss.backward() # 梯度累積 if (i 1) % accumulation_steps 0: optimizer.step() # 累積足夠步數(shù)后更新參數(shù) optimizer.zero_grad() # 清零梯度準(zhǔn)備下一輪累積這樣每次參數(shù)更新時(shí)使用的梯度是基于effective_batch_size個(gè)樣本計(jì)算得到的但內(nèi)存中同時(shí)只需要處理batch_size個(gè)樣本。這份“PyTorch Python API詳解大全”的構(gòu)建本身也是一個(gè)持續(xù)學(xué)習(xí)、驗(yàn)證和總結(jié)的過程。每一個(gè)API條目的補(bǔ)充都源于實(shí)際項(xiàng)目中的一次深入使用或解決了一個(gè)棘手問題。我堅(jiān)持認(rèn)為最好的學(xué)習(xí)方式就是帶著問題去探索并將探索的結(jié)果系統(tǒng)化地沉淀下來。希望這份持續(xù)更新的手冊(cè)能成為你PyTorch學(xué)習(xí)之路上一份可靠的“實(shí)戰(zhàn)地圖”減少你重復(fù)踩坑的時(shí)間把精力更多地投入到創(chuàng)造性的模型設(shè)計(jì)和算法實(shí)現(xiàn)中去。如果在使用某個(gè)API時(shí)發(fā)現(xiàn)了新的技巧或遇到了本文未提及的疑難雜癥也歡迎通過項(xiàng)目渠道反饋?zhàn)屛覀児餐晟扑?