WeHelp
PyTorch 可以應用在迴歸預測模型、分類預測模型、影像辨識、物件偵測、生成式模型等等領域。
  1. 房屋價格預測
  2. 乳癌良性 / 惡性分類
  3. 紅酒產地分類
  4. CNN 影像辨識
  5. R-CNN 物件偵測
  6. GAN 生成對抗網路
紅酒產地分類
以下示範一個簡單的「紅酒產地分類」案例:根據紅酒的化學分析資料,預測其產地類別。 > 使用的 Wine Dataset 來自 scikit-learn,共有 3 種紅酒產地、13 個化學特徵與 178 筆資料。這是一個適合示範多分類模型的入門資料集。 --- ## 一、問題定義 ### 輸入特徵 每筆紅酒資料包含 13 個化學特徵,例如: - Alcohol - Malic acid - Ash - Alcalinity of ash - Magnesium - Total phenols - Flavanoids - Nonflavanoid phenols - Proanthocyanins - Color intensity - Hue - OD280/OD315 of diluted wines - Proline ### 預測目標 將紅酒分類為 3 種產地: ```text Class 0 Class 1 Class 2 ``` 這是一個多分類問題,因此模型最後輸出 3 個類別分數。 --- # 二、完整 PyTorch 實作 ## 1. 載入資料與前處理 ```python import copy import random import numpy as np import torch import torch.nn as nn from torch.utils.data import TensorDataset, DataLoader from sklearn.datasets import load_wine from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.metrics import ( accuracy_score, classification_report, confusion_matrix ) ``` 設定隨機種子: ```python def set_seed(seed=42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed) # 讓結果較容易重現 torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False set_seed(42) ``` 載入資料: ```python wine = load_wine() X = wine.data.astype(np.float32) y = wine.target.astype(np.int64) print("特徵形狀:", X.shape) print("標籤形狀:", y.shape) print("類別名稱:", wine.target_names) ``` 可能輸出: ```text 特徵形狀: (178, 13) 標籤形狀: (178,) 類別名稱: ['class_0' 'class_1' 'class_2'] ``` --- ## 2. 切分訓練集、驗證集與測試集 先分出測試集,再從剩餘資料中分出驗證集: ```python X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, stratify=y, random_state=42 ) X_train, X_val, y_train, y_val = train_test_split( X_train, y_train, test_size=0.2, stratify=y_train, random_state=42 ) print("訓練集:", X_train.shape) print("驗證集:", X_val.shape) print("測試集:", X_test.shape) ``` 這裡使用 `stratify=y`,確保三種產地在訓練、驗證和測試資料中的比例大致相同。 --- ## 3. 特徵標準化 由於不同特徵的數值範圍差異很大,例如: - Alcohol 約為 12~14 - Proline 可能超過 1000 因此需要進行標準化。 重要的是:`StandardScaler` 只能使用訓練集計算平均值與標準差,避免資料洩漏。 ```python scaler = StandardScaler() X_train = scaler.fit_transform(X_train) X_val = scaler.transform(X_val) X_test = scaler.transform(X_test) ``` 將 NumPy 陣列轉成 PyTorch Tensor: ```python X_train_tensor = torch.tensor(X_train, dtype=torch.float32) y_train_tensor = torch.tensor(y_train, dtype=torch.long) X_val_tensor = torch.tensor(X_val, dtype=torch.float32) y_val_tensor = torch.tensor(y_val, dtype=torch.long) X_test_tensor = torch.tensor(X_test, dtype=torch.float32) y_test_tensor = torch.tensor(y_test, dtype=torch.long) ``` 建立資料載入器: ```python batch_size = 16 train_dataset = TensorDataset(X_train_tensor, y_train_tensor) val_dataset = TensorDataset(X_val_tensor, y_val_tensor) test_dataset = TensorDataset(X_test_tensor, y_test_tensor) train_loader = DataLoader( train_dataset, batch_size=batch_size, shuffle=True ) val_loader = DataLoader( val_dataset, batch_size=batch_size, shuffle=False ) test_loader = DataLoader( test_dataset, batch_size=batch_size, shuffle=False ) ``` --- # 三、建立多分類神經網路 這裡使用一個簡單的多層感知器,也就是 MLP。 架構如下: ```text 13 個輸入特徵 ↓ Linear(13, 32) ↓ ReLU ↓ Dropout ↓ Linear(32, 16) ↓ ReLU ↓ Linear(16, 3) ``` ```python class WineClassifier(nn.Module): def __init__(self, input_dim=13, num_classes=3): super().__init__() self.network = nn.Sequential( nn.Linear(input_dim, 32), nn.ReLU(), nn.Dropout(p=0.2), nn.Linear(32, 16), nn.ReLU(), nn.Linear(16, num_classes) ) def forward(self, x): return self.network(x) ``` 建立模型: ```python device = torch.device( "cuda" if torch.cuda.is_available() else "cpu" ) model = WineClassifier( input_dim=X_train.shape[1], num_classes=3 ).to(device) print(model) ``` --- # 四、設定損失函數與最佳化器 ## 1. 損失函數 多分類問題使用: ```python nn.CrossEntropyLoss() ``` 模型的最後一層只需要輸出 logits,不要自行加上 `Softmax`,因為 `CrossEntropyLoss` 內部已經包含了相應的計算。 ```python criterion = nn.CrossEntropyLoss() ``` ## 2. 最佳化器 使用 Adam: ```python optimizer = torch.optim.Adam( model.parameters(), lr=0.001, weight_decay=1e-4 ) ``` 主要設定: | 設定 | 值 | 說明 | |---|---:|---| | Optimizer | Adam | 適合小型神經網路 | | Learning rate | 0.001 | 每次參數更新幅度 | | Weight decay | 1e-4 | 輕微的 L2 正則化 | | Batch size | 16 | 每次使用 16 筆樣本 | | Loss | CrossEntropyLoss | 多分類損失 | --- # 五、撰寫訓練與評估函式 ## 訓練函式 ```python def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss = 0.0 total_correct = 0 total_samples = 0 for X_batch, y_batch in loader: X_batch = X_batch.to(device) y_batch = y_batch.to(device) optimizer.zero_grad() logits = model(X_batch) loss = criterion(logits, y_batch) loss.backward() optimizer.step() total_loss += loss.item() * X_batch.size(0) predictions = torch.argmax(logits, dim=1) total_correct += (predictions == y_batch).sum().item() total_samples += X_batch.size(0) avg_loss = total_loss / total_samples accuracy = total_correct / total_samples return avg_loss, accuracy ``` ## 評估函式 ```python def evaluate(model, loader, criterion, device): model.eval() total_loss = 0.0 total_correct = 0 total_samples = 0 all_predictions = [] all_labels = [] with torch.inference_mode(): for X_batch, y_batch in loader: X_batch = X_batch.to(device) y_batch = y_batch.to(device) logits = model(X_batch) loss = criterion(logits, y_batch) total_loss += loss.item() * X_batch.size(0) predictions = torch.argmax(logits, dim=1) total_correct += (predictions == y_batch).sum().item() total_samples += X_batch.size(0) all_predictions.extend(predictions.cpu().numpy()) all_labels.extend(y_batch.cpu().numpy()) avg_loss = total_loss / total_samples accuracy = total_correct / total_samples return ( avg_loss, accuracy, np.array(all_labels), np.array(all_predictions) ) ``` --- # 六、訓練模型 這裡訓練 100 個 epoch,並保存驗證集表現最佳的模型。 ```python num_epochs = 100 best_val_loss = float("inf") best_model_state = None history = { "train_loss": [], "train_acc": [], "val_loss": [], "val_acc": [] } for epoch in range(num_epochs): train_loss, train_acc = train_one_epoch( model, train_loader, criterion, optimizer, device ) val_loss, val_acc, _, _ = evaluate( model, val_loader, criterion, device ) history["train_loss"].append(train_loss) history["train_acc"].append(train_acc) history["val_loss"].append(val_loss) history["val_acc"].append(val_acc) # 儲存驗證集損失最低的模型 if val_loss < best_val_loss: best_val_loss = val_loss best_model_state = copy.deepcopy(model.state_dict()) if (epoch + 1) % 10 == 0: print( f"Epoch [{epoch+1:3d}/{num_epochs}] | " f"Train Loss: {train_loss:.4f}, " f"Train Acc: {train_acc:.4f} | " f"Val Loss: {val_loss:.4f}, " f"Val Acc: {val_acc:.4f}" ) ``` 訓練結束後,載入驗證集表現最好的模型: ```python model.load_state_dict(best_model_state) ``` --- # 七、在測試集上評估 ```python test_loss, test_acc, y_true, y_pred = evaluate( model, test_loader, criterion, device ) print(f"\nTest Loss: {test_loss:.4f}") print(f"Test Accuracy: {test_acc:.4f}") ``` 顯示分類報告: ```python print("\nClassification Report:") print( classification_report( y_true, y_pred, target_names=wine.target_names, digits=4 ) ) ``` 顯示混淆矩陣: ```python cm = confusion_matrix(y_true, y_pred) print("Confusion Matrix:") print(cm) ``` 這個資料集通常可以得到約 90%~100% 的測試準確率,但實際結果會受: - 隨機切分方式 - 隨機種子 - 網路結構 - 訓練 epoch 數 - 學習率 等因素影響。 --- # 八、單筆資料預測 假設有一筆新的紅酒化學分析資料: ```python new_wine = np.array([[ 13.2, # Alcohol 1.78, # Malic acid 2.14, # Ash 11.2, # Alcalinity of ash 100.0, # Magnesium 2.65, # Total phenols 2.76, # Flavanoids 0.26, # Nonflavanoid phenols 1.28, # Proanthocyanins 4.38, # Color intensity 1.05, # Hue 3.40, # OD280/OD315 1050.0 # Proline ]], dtype=np.float32) ``` 必須使用訓練階段的 scaler 進行轉換: ```python new_wine_scaled = scaler.transform(new_wine) new_wine_tensor = torch.tensor( new_wine_scaled, dtype=torch.float32 ).to(device) ``` 模型預測: ```python model.eval() with torch.inference_mode(): logits = model(new_wine_tensor) probabilities = torch.softmax(logits, dim=1) predicted_class = torch.argmax(probabilities, dim=1).item() print("預測類別:", wine.target_names[predicted_class]) print("各類別機率:", probabilities.cpu().numpy()) ``` `softmax` 適合在推論階段將 logits 轉成機率,但訓練時不應在模型最後額外加入 `Softmax`。 --- # 九、關鍵步驟與設定說明 ## 1. 使用分層切分 ```python stratify=y ``` 確保每個資料子集都包含三種產地,避免某一類在驗證集或測試集中比例過低。 ## 2. 避免資料洩漏 正確方式: ```python scaler.fit(X_train) scaler.transform(X_val) scaler.transform(X_test) ``` 不能將全部資料一起 `fit_transform`,否則測試資料的統計資訊會被模型間接使用。 ## 3. 使用 `CrossEntropyLoss` 對於三分類問題,標籤格式應為: ```text 0、1、2 ``` 而不是 one-hot 向量。模型輸出形狀為: ```text [batch_size, 3] ``` 例如: ```text [[ 1.2, -0.3, 0.8], [ 0.1, 2.4, -1.0]] ``` ## 4. 不在模型中加入 Softmax 模型最後一層: ```python nn.Linear(16, 3) ``` 即可。因為: ```python nn.CrossEntropyLoss() ``` 已經會處理 logits 與類別機率之間的轉換。 ## 5. 使用驗證集選擇最佳模型 不要直接選擇最後一個 epoch 的模型,而是根據驗證集損失保存最佳模型,能降低過度擬合的風險。 ## 6. Dropout 與 Weight Decay 資料集只有 178 筆,樣本數不大,因此使用: ```python nn.Dropout(0.2) ``` 以及: ```python weight_decay=1e-4 ``` 來增加正則化效果。 --- # 十、可進一步改善的方向 如果要將此案例拓展到實際應用,可以考慮: 1. 使用交叉驗證,而不只進行一次資料切分。 2. 調整隱藏層大小與學習率。 3. 加入 Early Stopping。 4. 繪製訓練與驗證損失曲線。 5. 使用 Precision、Recall、F1-score 評估各產地的分類效果。 6. 進行特徵重要性分析。 7. 與 Logistic Regression、Random Forest、SVM 等模型比較。 8. 對產地標籤進行實際商業定義與資料品質檢查。 總結而言,這個案例的核心流程是: ```text 載入資料 → 分層切分資料 → 只用訓練集進行標準化 → 建立 PyTorch MLP → 使用 CrossEntropyLoss 訓練 → 以驗證集選擇最佳模型 → 使用測試集計算 Accuracy、F1-score 與混淆矩陣 ```
相關學習地圖、教學課程
Python 人工智慧
建議完成「Python 資料工程」教程後,繼續學習以下課程。