這個章節的主要目標是學習理解PyTorch深度學習框架的基礎知識。在第20章中,從最基礎的Tensor創建、操作、轉換等基本概念開始,循序漸進地學習PyTorch的核心功能,包括自動微分(Autograd)、資料預處理技術,以及在第21章中如何設計和訓練基本的神經網路模型來處理分類和迴歸問題。
而Exercise則將這些基礎知識應用於實際資料集,並擴展到其他機器學習模型進行比較。Exercise不僅實作了深度學習模型(MLP神經網路),還包含了多種傳統機器學習模型,如LogisticRegression、LogisticRegressionCV、DecisionTreeClassifier等,對這些模型在分類和迴歸任務上的表現進行了全面評估和比較。
OS: Windows 11 23H2
CPU: Intel Core i7-13700K
GPU: NVIDIA GeForce RTX 4070 SUPER 12G
RAM: DDR5 64G 5600MT/s
Python Package Manager: uv
Python Version: 3.10.16
Python Package: numpy, pandas, sklearn, rich, seaborn, matplotlib, PyTorch
IDE: Visual Studio Code
為方便查看運行結果,故額外使用rich函式庫建立兩個Function格式化印出指定內容。
20.1 Creating a Tensor
Code:
Explanation:
這段程式碼展示了pytorch tensor的基本操作,透過torch.tensor來創建一維行張量和二維列張量。
Discussion:
相較於numpy,pytorch tensor提供自動微分和GPU加速功能,非常適合深度學習,但在純數據分析時numpy有更豐富的函數庫和更好的CPU效能。
Result:
20.2 Creating a Tensor from NumPy
Code:
Explanation:
這段程式碼將numpy陣列轉換為pytorch tensor。首先創建一個一維numpy陣列,然後使用torch.from_numpy方法將其轉換為pytorch tensor。
Discussion:
pytorch和numpy共享記憶體,因此轉換非常高效;然而,這也意味著修改原始numpy陣列會影響pytorch tensor,可能導致意料之外的行為。另外pytorch tensor支援GPU加速,而numpy僅限CPU運算。
Result:
20.3 Creating a Sparse Tensor
Code:
Explanation:
這段程式碼將一個普通pytorch tensor轉換為稀疏tensor格式。首先創建一個包含多個零元素的2D tensor,然後使用to_sparse方法將其轉換為稀疏表示形式。
Discussion:
相比numpy的稀疏矩陣,pytorch稀疏tensor支援自動微分和GPU加速,非常適合深度學習中的大規模稀疏計算,但numpy的稀疏矩陣操作種類更多樣且有更成熟的科學計算生態系統支援。
Result:
20.4 Selecting Elements in a Tensor
Code:
Explanation:
這段程式碼展示了在pytorch中各種索引和切片tensor的方法。首先創建一個向量和一個矩陣,然後通過不同的索引操作來選取元素:單一元素選取、全選、前n個元素、後n個元素、負索引選取最後元素、矩陣行列選取,以及使用flip方法反轉tensor。
Discussion:
pytorch索引與numpy非常相似,但pytorch操作保留梯度信息,支援自動微分;而numpy雖然不支援自動微分,但在某些情況下操作更為高效,特別是進行大量CPU計算時。此外,pytorch索引返回的是視圖而非副本,修改索引後的元素會修改原始張量。
Result:
20.5 Describing a Tensor
Code:
Explanation:
這段程式碼檢查pytorch tensor的各項基本屬性。首先檢測是否有可用的CUDA GPU,選擇適當的設備創建tensor,然後獲取並顯示該tensor的重要屬性,包含shape、dtype、layout和device。
Discussion:
相較於numpy,pytorch提供了設備管理功能,使tensor可以在CPU與GPU間無縫切換;同時pytorch的tensor屬性查詢方式與numpy類似,但增加了布局和設備等深度學習特有的屬性,更適合需要硬體加速的深度學習任務。
Result:
20.6 Applying Operations to Elements
Code:
Explanation:
這段程式碼使用pytorch tensor的基本數學運算。首先創建了一個包含三個元素的tensor,然後將每個元素乘以100,展示了pytorch如何支援tensor的元素級別運算。
Discussion:
pytorch和numpy都支援廣播機制進行元素級運算,但pytorch運算會自動追蹤計算圖以支援梯度計算;pytorch也可利用GPU加速運算,適合深度學習中的大規模計算,而numpy僅限於CPU運算,更適合傳統科學計算。
Result:
20.7 Finding the Maximum and Minimum Values
Code:
Explanation:
這段程式碼在pytorch中找出tensor的最大值和最小值。首先創建一個一維tensor並使用max和min方法找出其最大值和最小值;然後創建一個二維張量並同樣找出其最大值。
Discussion:
pytorch和numpy的最大最小值查找函數用法相似,但pytorch操作支援自動微分,且可在GPU上執行以加速大型張量計算;此外,pytorch的max和min還可返回索引位置,這在深度學習任務如最大池化操作中特別有用
Result:
20.8 Reshaping Tensors
Code:
Explanation:
這段程式碼展示了pytorch中的tensor形狀重塑(reshape)操作。首先創建了一個形狀為4 x 3的二維tensor,然後使用reshape方法將其重新塑造為形狀為2 x 6的tensor,同時保持原始數據元素的總數和順序不變。
Discussion:
pytorch的reshape方法與numpy類似,但pytorch保留梯度信息以支援自動微分;此外,pytorch還有提供view方法作為更高效的選擇,但view方法要求tensor在記憶體中連續存儲,而reshape方法則更靈活,可處理非連續tensor。
Result:
20.9 Transposing a Tensor
Code:
Explanation:
這段程式碼使用兩種在pytorch中轉置tensor的方法。首先創建了一個3D tensor (1 x 1 x 3),然後分別使用.mT屬性和.permute方法進行轉置操作。.mT提供簡便的矩陣轉置,而.permute方法通過反向排列維度索引實現了相同的效果,將tensor從形狀(1 x 1 x 3)轉換為(3 x 1 x 1)。
Discussion:
相較於numpy,pytorch提供了更多樣化的轉置方法:.mT方法適用於矩陣轉置,而.permute方法則提供更靈活的維度重排功能;兩者都保留梯度流,支援自動微分。numpy的.T僅對2D矩陣最佳,而pytorch的.permute方法在處理多維張量時更加直觀。
Result:
20.10 Flattening a Tensor
Code:
Explanation:
這段程式碼使用pytorch中將多維tensor扁平化(flatten)的操作。首先創建了一個3 x 3的二維矩陣tensor,然後使用.flatten方法將其轉換為一維向量,將所有元素按行優先順序展開成一個包含9個元素的一維tensor。
Discussion:
pytorch的.flatten方法和numpy的.flatten方法功能相似,但pytorch保留梯度信息以支援自動微分;此外,pytorch的.flatten方法允許指定起始維度和結束維度進行部分扁平化,提供更精細的控制,而numpy則需要組合reshape等操作才能實現類似功能。
Result:
20.11 Calculating Dot Products
Code:
Explanation:
這段程式碼展示了如何在pytorch中計算兩個向量的點積和。首先創建了兩個各含三個元素的一維tensor,然後使用.dot方法計算它們的點積。
Discussion:
pytorch和numpy的點積運算在語法上相似,但pytorch的.dot方法支援自動微分和GPU加速運算;此外,對於更高維度的張量,pytorch提供了更豐富的矩陣乘法運算如matmul、@運算符等,這些在處理深度學習中的張量運算時更加靈活且性能優化。
Result:
20.12 Multiplying Tensors
Code:
Explanation:
這段程式碼使用pytorch中tensor的基本算術運算。首先創建了兩個含3個元素的一維tensor,然後進行元素間的乘法(對應位置相乘)、加法、減法和除法運算,展示了tensor間的元素級別運算特性。
Discussion:
pytorch和numpy的元素級運算語法相似,但pytorch操作會自動追蹤計算圖以支援梯度計算;pytorch還能利用GPU加速這些運算,特別適合處理大規模深度學習中的tensor操作。此外,pytorch能更好地處理混合精度計算,提高訓練效率。
Result:
21.1 Using Autograd with PyTorch
Code:
Explanation:
這段程式碼展示pytorch中自動微分(Autograd)的基本用法。首先創建一個需要計算梯度的tensor t,對其元素求和後調用backward方法,最後獲取梯度值。這個過程自動計算了tensor_sum對t中每個元素的偏導數,結果存儲在t.grad中,顯示所有元素的梯度均為1。
Discussion:
pytorch自動微分的優點是簡化了深度學習中的梯度計算過程,無需手動推導複雜公式。但它需要額外的記憶體來存儲計算圖,且在不需要梯度時可額外使用with torch.no_grad()來提高效率。
Result:
21.2 Preprocessing Data for Neural Networks
Code:
Explanation:
這段程式碼使用了兩種標準化資料的方法:一是使用sklearn的StandardScaler,二是直接用pytorch實現相同功能。首先將特徵數據轉換為零均值、單位方差的標準化形式,以便神經網絡更有效學習。兩種方法都計算了均值和標準差,然後用原始數據減去均值並除以標準差,最終得到相同的標準化結果。
Discussion:
pytorch方法的優點是直接支持梯度計算,便於在深度學習訓練中進行優化,同時計算過程更為靈活。然而sklearn的方法提供了更多預處理選項和更簡潔的API,但在深度學習的梯度下降過程中需要額外轉換步驟。
Result:
21.3 Designing a Neural Network
Code:
Explanation:
這段程式碼展示了使用pytorch設計神經網路的兩種不同方式。第一種方法直接繼承nn.Module並在forward方法中明確定義每層的操作流程;第二種方法使用Sequential方法將所有層按順序組合在一起。兩種實現創建了相同的三層神經網路結構:具有10個輸入特徵、兩個各有16個神經元的隱藏層(使用ReLU激活),以及一個使用sigmoid激活的輸出層,適合二元分類問題。
Discussion:
Sequential方法程式碼更簡潔易讀,適合線性結構的網路。但直接使用nn.Module方法更靈活,允許複雜的網路拓撲和條件分支,便於實現注意力機制或跳躍連接等高級結構。對於複雜架構,Sequential方法可能會受到限制。
Result:
21.4 Training a Binary Classifier
Code:
Explanation:
這段程式碼使用pytorch訓練一個二元分類神經網路。首先使用sklearn的make_classification方法生成合成二元分類資料並分割為訓練集和測試集。然後定義一個三層神經網路(輸入層10個特徵,兩個隱藏層各16個神經元,輸出層1個神經元),使用BCE損失函數和RMSprop優化器。程式碼使用DataLoader進行批次訓練,並應用torch.compile加速,最後在測試集上評估模型性能,計算損失和準確率。
Discussion:
這種方法優點是使用批次訓練提高效率,且運用pytorch的編譯功能加速訓練過程。限制是只訓練了很少的3個epochs,可能無法達到最佳性能;同時沒有實施早停或學習率調整等技術來避免過擬合和優化訓練過程。
Result:
21.5 Training a Multiclass Classifier
Code:
Explanation:
這段程式碼使用pytorch訓練一個三類別分類器。首先使用sklearn的make_classification方法生成具有三個類別的合成資料集,並進行訓練測試集分割。接著將類別標籤轉換為one-hot形式,並定義一個具有兩個隱藏層的神經網路,最後層使用Softmax激活函數輸出三個類別的機率分布。訓練過程使用CrossEntropyLoss損失函數、RMSprop優化器,以及DataLoader進行批次處理,同時使用torch.compile來加速運算。
Discussion:
多類別分類相比二元分類的主要優點是使用Softmax輸出層和交叉熵損失函數,能夠有效處理多類問題。然而,準確率計算方式不太適合多類別問題,應該使用argmax而非round來確定預測類別,這可能導致評估指標不準確。
Result:
21.6 Training a Regressor
Code:
Explanation:
這段程式碼使用pytorch建立和訓練迴歸模型。首先使用sklearn的make_regression方法生成迴歸問題的合成資料,並分割為訓練集和測試集。然後定義了一個神經網路,包含兩個16個神經元的隱藏層和一個線性輸出層(不使用激活函數,以便輸出連續值)。模型使用均方誤差(MSE)損失函數和RMSprop優化器,通過DataLoader進行批次訓練,並使用torch.compile加速。
Discussion:
迴歸模型的主要優點是能夠預測連續值,輸出層不使用激活函數以避免限制輸出範圍,同時採用MSE損失更適合迴歸問題。限制方面,此模型未實施正則化技術(如dropout或權重衰減)以防止過擬合,也沒有標準化輸入特徵或輸出目標,這可能導致訓練不穩定和收斂較慢。
Result:
21.7 Making Predictions
Code:
Explanation:
這段程式碼使用pytorch訓練二元分類神經網路並進行預測。首先透過sklearn的make_classification方法生成合成分類資料並分為訓練和測試集,然後定義一個三層神經網路(兩個隱藏層和一個sigmoid輸出層)。模型使用BCE損失函數和RMSprop優化器,經過三個epochs的批次訓練後,對訓練資料進行預測,並將連續輸出四捨五入為二元類別結果。最後顯示第一個樣本的預測結果。
Discussion:
此方法的優點是在預測時禁用梯度計算(使用torch.no_grad),提高效率和減少記憶體使用。然而,其限制在未實現預測概率的儲存,這在需要調整決策閾值或計算ROC曲線等應用場景中是必要的。
Result:
Settings
這邊使用scikit-learn的real-world datasets中的Forest Covertypes(Classification)與California Housing(Regression)作為兩個不同數量級的對比資料集,對此章節中所有的聚類模型調整各項參數進行訓練與比較差異。
Forest Covertypes 是一個著名的真實世界資料集,收錄於UCI機器學習儲存庫中。此資料集包含581,012筆觀測資料,每筆資料代表科羅拉多州羅斯福國家森林中30×30公尺的土地區塊。資料集包含54個特徵,主要來自地理製圖變數,如海拔高度、坡向、坡度、日照指數、土壤類型等,而非遠端感測資料。研究區域涵蓋四個原始荒野地區(Rawah、Neota、Comanche Peak及Cache la Poudre),這些地區受人為干擾極少,因此現有的森林覆蓋類型主要是生態過程的結果,而非森林管理實踐的產物。資料集的分類目標是預測七種不同的森林覆蓋類型,包括雲杉/冷杉、山松、黃松、棉白楊/柳樹、白楊、花旗松及高山矮松,使其成為一個多類別分類問題。
California Housing 是一個廣泛使用的真實世界資料集,源自1990年美國加州人口普查數據。此資料集包含20,640筆觀測樣本,每筆代表加州的一個人口普查區塊群(通常有600至3,000人口的地理單位)。資料集包含8個特徵變數,包括家庭收入中位數、房屋年齡中位數、每戶平均房間數、每戶平均臥室數、人口、平均住戶人數、以及地理位置(經度和緯度)。預測目標是每個區域的房屋價值中位數,以十萬美元為單位表示。此資料集特別適合用於迴歸問題,常被用於機器學習模型的訓練與測試,尤其是在房地產價格預測方面的應用。由於資料是按區域聚集的,所以數值代表該區域的平均值或中位數,這使得某些特徵(如平均房間數)在人口稀少的度假區可能會有意外高的數值。
Classification Model - Forest Covertypes dataset
Explanation:
首先透過fetch_covtype與fetch_california_housing方法載入兩個資料集,接著透過StandardScaler方法標準化資料集後,再使用train_test_split方法將資料集分為測試集與訓練集。
Code:
Explanation:
接著創建用於進行分類任務的Neural Network,並依照Forest Covertypes資料集的規格進行創建MLP Network。輸入為54,輸出為7(7個類別)。optimizer的部分也改為使用Adam以獲得更好的表現。
並且同時訓練之前章節中的各式Classification模型如LogisticRegression、LogisticRegressionCV、DecisionTreeClassifier、RandomForestClassifier、AdaBoostClassifier、XGBClassifier、LightGBM Classifier、KNeighborsClassifier。
Code:
Result:
Discussion:
從結果來看,RandomForestClassifier以95.62%的準確率領先所有模型,同時訓練時間僅需12.14秒,評估時間為0.77秒,呈現出優異的平衡性。這表明RandomForestClassifier在處理森林覆蓋類型這類複雜的地理空間資料時,能夠有效捕捉特徵間的非線性關係,同時避免過度擬合的問題。
DecisionTreeClassifier達到94.19%的準確率,且訓練時間更短,僅需8.06秒,評估速度也非常快,只需0.07秒。這顯示DecisionTreeClassifier在此資料集上表現優異,可能是因為森林覆蓋類型資料的特徵階層性明顯,適合DecisionTreeClassifier的分割規則。然而,相較於RandomForestClassifier,DecisionTreeClassifier可能更容易受到資料波動的影響。
KNeighborsClassifier展現了極端的性能特點:訓練時間極短(0.04秒),但評估時間卻高達75.93秒,是所有模型中最慢的。儘管如此,其92.66%的準確率仍然相當出色。這符合KNeighborsClassifier作為惰性學習法的特性,它在訓練階段僅儲存資料,將大部分計算延遲到預測階段,因此在大型資料集上的預測速度會成為瓶頸。
MLP Neural Network則呈現另一種極端,其訓練時間長達166.41秒,遠超其他模型,但評估速度較快(0.13秒),準確率達92.56%。這反映了神經網路模型需要大量的參數調整和迭代優化,但一旦訓練完成,推論速度可以相當快速。考慮到其準確率只比DecisionTreeClassifier和RandomForestClassifier略低,但訓練成本顯著增加,在實際應用中可能需要權衡時間成本與性能需求。
而XGBoost(86.80%準確率)和LightGBM(85.46%準確率),都展現了不錯的平衡性,訓練時間都在8秒左右,評估時間分別為0.23秒和0.59秒。這些模型在此資料集上的表現不如RandomForestClassifier和DecisionTreeClassifier,但優於邏輯斯迴歸類模型,顯示出集成方法在處理複雜資料集時的優勢。
邏輯斯迴歸類模型的準確率均在72%左右,相對較低,但評估速度極快(分別為0.02秒和0.03秒)。這說明線性模型在面對森林覆蓋類型這種可能具有高度非線性關係的資料時,難以捕捉複雜的模式。值得注意的是,使用交叉驗證的版本並未顯著提升準確率,但訓練時間增加了三倍。
AdaBoost分類器表現最為失常,準確率僅為64.63%,且訓練時間(36.62秒)和評估時間(1.74秒)都相對較長。這可能表明AdaBoost的疊代方式不適合處理本資料集的特徵分佈,或者預設的基礎分類器設定不理想。
Regression Model - California Housing dataset
Explanation:
接下來創建Regression類型的MLP,根據California Housing資料集的8個不同維度,設定MLP的輸入維度為8,輸出維度為1,同時這邊也設定optimizer為Adam以提高MSE Loss。
另外也訓練了兩個Regression類型的模型如DecisionTreeRegressor與RandomForestRegressor,在相同的資料集上進行訓練與評估。
Code:
Result:
Discussion:
根據實驗結果與視覺化圖表,RandomForestRegressor在整體表現上最為突出,擁有最低的MSE Loss,0.2542和最高的R2 Score,0.8079,這意味著它能解釋約80.79%的目標變數變異。從圖表可見,隨機森林在訓練時間與預測準確度之間取得了優異的平衡點,僅需0.73秒的訓練時間就達到了最佳性能。尤其在房價預測這種涉及複雜非線性關係的任務中,RandomForestRegressor能夠捕捉到更多細微的特徵互動。
MLP Neural Network的表現居中,MSE為0.3207,R2 Score為0.7577,但訓練時間最長,達4.76秒。從圖中可見,神經網路需要顯著更多的計算資源和時間。神經網路能夠自動學習特徵間的複雜關係,尤其在有充足資料的情況下表現強勁,但在此資料集上並未超越RandomForestRegressor。這可能是因為資料量不夠大,或者神經網路架構需要進一步優化。神經網路的性能可能還有提升空間,若進一步調整超參數或增加模型複雜度,可能會獲得更好的結果,但代價是更長的訓練時間。
DecisionTreeRegressor的訓練速度最快,僅需0.13秒,但性能較弱,MSE高達0.5294,R2 Score僅為0.5999。從圖表中可明顯看出,DecisionTreeRegressor在訓練速度上有絕對優勢,但預測準確度卻顯著落後於其他兩種模型。在複雜的房價預測任務中,單一DecisionTreeRegressor難以捕捉所有重要的特徵關係,尤其是連續特徵間的微妙互動。這也解釋了為何其衍生的RandomForestRegressor能夠表現得更好。
Difficulties Encountered and Solutions
1. torch.compile 在 Inductor 後端編譯失敗
Difficulties:使用torch.compile方法時若沒有額外設定其他參數,會造成以下問題
Solution:在torch.compile方法中加入參數backend="aot_eager"來指定torch使用其他後端,即可解決此問題。
References
[1] Machine Learning with Python Cookbook-2nd, by Kyle Gallatin and Chris Albon, O'Reilly, 2023, ISBN: 9781098135720
[2] 全格局使用PyTorch:深度學習和圖神經網路, 李金洪, 深智數位, 2024, ISBN: 9786267569078
[3] 精通機器學習:使用Scikit-Learn, Keras與TensorFlow(第三版), Aurélien Géron, 歐萊禮, 2024, ISBN: 9786263246676
[4] Python機器學習:基於PyTorch和Scikit-Learn, (美)塞巴斯蒂安·拉施卡, 機械工業出版社, 2023, ISBN: 9787111726814
[5] 核心開發者親授!PyTorch深度學習攻略, Eli Stevens, 旗標, 2021, ISBN: 9789863126737