- 🍨 本文為🔗365天深度學習訓練營 中的學習記錄博客
- 🍖 原作者:K同學啊
一、過程解讀
PyTorch 實戰:阿爾茨海默病數據預測模型
今天,我將帶大家一起探索一個基于 PyTorch 的深度學習小項目——利用 RNN 模型對阿爾茨海默病數據進行預測。這個實例不僅涵蓋了數據預處理、模型構建、訓練與保存,還包含了如何在其他代碼中調用保存的模型進行預測。通過這個完整的流程,我們可以學到很多實用技能,下面讓我們一一進行詳細解讀。
數據準備與預處理:奠定模型基礎
數據是深度學習模型的基石,因此數據的準備和預處理步驟至關重要。在這個實例中,我們使用 pandas 讀取 CSV 格式的阿爾茨海默病數據,并進行了一些簡單的預處理操作,比如刪除第一列和最后一列,以及將特征數據標準化為標準正態分布。通過這些操作,我們確保了輸入模型的數據質量和一致性,為模型的訓練打下了良好的基礎。
模型構建:搭建 RNN 網絡
接下來是構建模型的核心環節。在這個實例中,我們設計了一個簡單的循環神經網絡(RNN)模型,它由一個 RNN 層和兩個全連接層組成。RNN 層能夠處理序列數據,這對于時間序列預測等任務非常有用。全連接層則用于將 RNN 的輸出映射到最終的分類結果。通過定義模型的結構,我們不僅學習了如何使用 PyTorch 構建自定義神經網絡,還了解了 RNN 和全連接層的基本原理和應用場景。
模型訓練:優化模型參數
模型構建完成后,我們進入訓練階段。在這個實例中,我們定義了訓練函數和測試函數,分別用于計算模型在訓練集和測試集上的損失和準確率,并更新模型參數。通過設置損失函數(如交叉熵損失)和優化器(如 Adam 優化器),我們能夠有效地優化模型的性能。此外,我們還學習了如何使用數據加載器來批量加載數據,以及如何在訓練過程中記錄和輸出模型的訓練進度和指標,這些技巧對于監控和調整模型的訓練過程非常有幫助。
模型保存與加載:實現模型的持久化和復用
當模型訓練完成后,我們將其保存到本地文件中。這樣做的好處是可以避免每次使用模型時都要重新訓練,節省了大量的時間和計算資源。在實例中,我們使用 PyTorch 提供的 torch.save
和 torch.load
函數來保存和加載模型的參數。通過這種方式,我們可以在其他代碼中輕松地加載模型,并直接使用它進行預測,而無需關心模型的訓練過程。
模型調用與預測:將模型付諸實踐
最后,我們在其他代碼中加載了之前保存的模型,并使用它對新的數據進行預測。在這個過程中,我們學習了如何將輸入數據轉換為模型所需的格式,以及如何調用模型進行預測并獲取結果。此外,我們還通過打印模型的預測結果來驗證模型的性能,并使用混淆矩陣來評估模型的分類效果。這些步驟展示了如何將深度學習模型應用到實際問題中,并為后續的模型優化和改進提供了依據。
當然!循環神經網絡(Recurrent Neural Network, RNN)是一種常用于處理序列數據的神經網絡架構。與傳統的前饋神經網絡(Feedforward Neural Network)不同,RNN 具有“記憶”功能,能夠處理長度可變的輸入序列,并通過內部狀態捕獲序列中的時間依賴關系。下面將對 RNN 的基本原理、結構和應用場景進行詳細講解。
RNN網絡的基本原理
-
序列數據的處理
- RNN 適用于處理序列數據,如時間序列、文本序列、語音序列等。序列數據具有時間上的依賴關系,后續數據點與前面的數據點相關。RNN 通過循環結構,將前面時間步的隱藏狀態傳遞到當前時間步,實現對序列信息的累積和記憶。
-
循環結構
- RNN 的核心是循環(recurrence)結構,它允許信息在神經網絡中沿時間步進行傳遞。基本的 RNN 單元在每個時間步接收兩個輸入:當前時間步的輸入 ( x_t ) 和前一時間步的隱藏狀態 ( h_{t-1} ),然后計算當前時間步的隱藏狀態 ( h_t ) 和輸出 ( o_t )。
-
狀態更新公式
-
RNN 的狀態更新通常使用以下公式:
其中:
- ( x_t ) 是當前時間步的輸入。
- ( h_{t-1} ) 是前一時間步的隱藏狀態。
- ( W_{xh} ) 和 ( W_{hh} ) 分別是輸入到隱藏層和隱藏層到隱藏層的權重矩陣。
- ( b_h ) 是隱藏層的偏置項。
- ( \sigma ) 是激活函數,如 tanh 或 ReLU。
- ( o_t ) 是當前時間步的輸出,( W_{ho} ) 和 ( b_o ) 是隱藏層到輸出層的權重矩陣和偏置項。
-
RNN網絡的基本結構
-
單向 RNN
- 單向 RNN 只能利用過去的上下文信息來預測當前時間步的輸出。它的信息流動是單向的,從過去到未來。
-
雙向 RNN
- 雙向 RNN 可以同時利用過去的上下文信息和未來的上下文信息來預測當前時間步的輸出。它包含兩個隱藏層,一個處理正向的時間序列,另一個處理反向的時間序列。
-
多層 RNN
- 多層 RNN 是將多個 RNN 層堆疊在一起,每個 RNN 層的輸出作為下一層的輸入。這種結構可以學習到更復雜的特征表示。
RNN網絡的訓練
-
反向傳播
- RNN 的訓練通常使用反向傳播算法,稱為“隨時間反向傳播”(Backpropagation Through Time, BPTT)。它將 RNN 展開為一個沿時間步的計算圖,然后對每個時間步的損失進行反向傳播,以更新網絡的參數。
-
梯度消失與梯度爆炸問題
- 在訓練 RNN 時,可能會遇到梯度消失或梯度爆炸的問題。這是因為長序列中的梯度在反向傳播時會不斷乘以權重矩陣的導數,導致梯度變得非常小或非常大。為了解決這些問題,可以使用梯度裁剪(Gradient Clipping)、LSTM(Long Short-Term Memory)或 GRU(Gated Recurrent Unit)等改進的 RNN 變體。
RNN網絡的應用場景
-
序列預測
- 預測時間序列的未來值,如股票價格預測、天氣預測等。
-
自然語言處理
- 在文本生成、機器翻譯、情感分析等任務中,RNN 能夠捕獲文本中的上下文信息。
-
語音識別
- 將語音信號轉換為文字,RNN 可以處理語音信號的時間序列特征。
-
生物信息學
- 分析 DNA 序列、蛋白質序列等生物數據。
RNN網絡的變體
-
長短期記憶網絡(LSTM)
- LSTM 是一種改進的 RNN 變體,能夠更好地處理長序列數據。它引入了“記憶單元”和多個“門”(輸入門、遺忘門和輸出門),可以控制信息的流動,從而緩解梯度消失問題。
-
門控循環單元(GRU)
- GRU 是另一種改進的 RNN 變體,結構相對 LSTM 更簡單。它將遺忘門和輸入門合并為一個“更新門”,并移除了記憶單元,直接使用隱藏狀態來存儲長期信息。
-
深度 RNN
- 深度 RNN 是將多個 RNN 層堆疊在一起,形成一個多層的 RNN 架構。每一層的輸出作為下一層的輸入,可以學習到更復雜的特征表示。
二、代碼實現
1.導入庫函數
import torch
from torch import nn
import torch.nn.functional as F
import seaborn as sns
from torch.utils.data import TensorDataset, DataLoader
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
import warnings
from datetime import datetime
2.導入數據
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
df = pd.read_csv("./data/alzheimers_disease_data.csv")
# 刪除第一列和最后一列
df = df.iloc[:, 1:-1]
3.標準化
X = df.iloc[:, :-1]
y = df.iloc[:, -1]# 將每一列特征標準化為標準正態分布,注意,標準化是針對每一列而言的
sc = StandardScaler()
X = sc.fit_transform(X)
4.數據集構建
X = torch.tensor(np.array(X), dtype=torch.float32)
y = torch.tensor(np.array(y), dtype=torch.int64)X_train, X_test, y_train, y_test = train_test_split(X, y,test_size=0.1,random_state=1)train_dl = DataLoader(TensorDataset(X_train, y_train),batch_size=64,shuffle=False)test_dl = DataLoader(TensorDataset(X_test, y_test),batch_size=64,shuffle=False)
5.模型構建
class model_rnn(nn.Module):def __init__(self):super(model_rnn, self).__init__()self.rnn0 = nn.RNN(input_size=32, hidden_size=200,num_layers=1, batch_first=True)self.fc0 = nn.Linear(200, 50)self.fc1 = nn.Linear(50, 2)def forward(self, x):out, hidden1 = self.rnn0(x)out = self.fc0(out)out = self.fc1(out)return outmodel = model_rnn().to(device)
model_rnn((rnn0): RNN(32, 200, batch_first=True)(fc0): Linear(in_features=200, out_features=50, bias=True)(fc1): Linear(in_features=50, out_features=2, bias=True)
)
6.構建測試訓練
def train(dataloader, model, loss_fn, optimizer):size = len(dataloader.dataset)num_batches = len(dataloader)train_loss, train_acc = 0, 0for X, y in dataloader:X, y = X.to(device), y.to(device)pred = model(X)loss = loss_fn(pred, y)optimizer.zero_grad()loss.backward()optimizer.step()train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()train_loss += loss.item()train_acc /= sizetrain_loss /= num_batchesreturn train_acc, train_loss
7. 構建訓練函數
def test(dataloader, model, loss_fn):size = len(dataloader.dataset)num_batches = len(dataloader)test_loss, test_acc = 0, 0with torch.no_grad():for imgs, target in dataloader:imgs, target = imgs.to(device), target.to(device)target_pred = model(imgs)loss = loss_fn(target_pred, target)test_loss += loss.item()test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()test_acc /= sizetest_loss /= num_batchesreturn test_acc, test_loss
8.訓練模型并保存
loss_fn = nn.CrossEntropyLoss()
learn_rate = 5e-5
opt = torch.optim.Adam(model.parameters(), lr=learn_rate)
epochs = 50train_loss = []
train_acc = []
test_loss = []
test_acc = []for epoch in range(epochs):model.train()epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt)model.eval()epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)train_acc.append(epoch_train_acc)train_loss.append(epoch_train_loss)test_acc.append(epoch_test_acc)test_loss.append(epoch_test_loss)lr = opt.state_dict()['param_groups'][0]['lr']template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E}')print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss,epoch_test_acc*100, epoch_test_loss, lr))print("="*20, 'Done', "="*20)
#保存模型
torch.save(model.state_dict(), "./model_rnn.pth") # 保存模型參數
print("模型已保存到 ./model_rnn.pth")
Epoch: 1, Train_acc:62.9%, Train_loss:0.673, Test_acc:71.6%, Test_loss:0.655, Lr:5.00E-05
Epoch: 2, Train_acc:70.1%, Train_loss:0.644, Test_acc:71.2%, Test_loss:0.629, Lr:5.00E-05
Epoch: 3, Train_acc:69.7%, Train_loss:0.617, Test_acc:67.9%, Test_loss:0.603, Lr:5.00E-05
Epoch: 4, Train_acc:67.6%, Train_loss:0.593, Test_acc:66.5%, Test_loss:0.584, Lr:5.00E-05
Epoch: 5, Train_acc:67.6%, Train_loss:0.574, Test_acc:67.9%, Test_loss:0.570, Lr:5.00E-05
Epoch: 6, Train_acc:69.9%, Train_loss:0.555, Test_acc:68.8%, Test_loss:0.556, Lr:5.00E-05
Epoch: 7, Train_acc:73.0%, Train_loss:0.537, Test_acc:70.7%, Test_loss:0.542, Lr:5.00E-05
Epoch: 8, Train_acc:75.3%, Train_loss:0.518, Test_acc:73.0%, Test_loss:0.527, Lr:5.00E-05
Epoch: 9, Train_acc:77.7%, Train_loss:0.498, Test_acc:74.9%, Test_loss:0.513, Lr:5.00E-05
Epoch:10, Train_acc:79.7%, Train_loss:0.479, Test_acc:77.2%, Test_loss:0.499, Lr:5.00E-05
Epoch:11, Train_acc:80.9%, Train_loss:0.461, Test_acc:77.7%, Test_loss:0.486, Lr:5.00E-05
Epoch:12, Train_acc:81.8%, Train_loss:0.444, Test_acc:78.6%, Test_loss:0.473, Lr:5.00E-05
Epoch:13, Train_acc:82.6%, Train_loss:0.428, Test_acc:79.1%, Test_loss:0.462, Lr:5.00E-05
Epoch:14, Train_acc:82.9%, Train_loss:0.414, Test_acc:78.1%, Test_loss:0.452, Lr:5.00E-05
Epoch:15, Train_acc:83.4%, Train_loss:0.401, Test_acc:79.1%, Test_loss:0.444, Lr:5.00E-05
Epoch:16, Train_acc:83.7%, Train_loss:0.390, Test_acc:78.6%, Test_loss:0.436, Lr:5.00E-05
Epoch:17, Train_acc:84.1%, Train_loss:0.380, Test_acc:79.5%, Test_loss:0.430, Lr:5.00E-05
Epoch:18, Train_acc:84.9%, Train_loss:0.372, Test_acc:80.0%, Test_loss:0.425, Lr:5.00E-05
Epoch:19, Train_acc:85.3%, Train_loss:0.364, Test_acc:80.0%, Test_loss:0.420, Lr:5.00E-05
Epoch:20, Train_acc:85.6%, Train_loss:0.358, Test_acc:79.1%, Test_loss:0.417, Lr:5.00E-05
Epoch:21, Train_acc:85.9%, Train_loss:0.352, Test_acc:79.1%, Test_loss:0.414, Lr:5.00E-05
Epoch:22, Train_acc:85.8%, Train_loss:0.347, Test_acc:79.5%, Test_loss:0.412, Lr:5.00E-05
Epoch:23, Train_acc:86.0%, Train_loss:0.343, Test_acc:78.6%, Test_loss:0.410, Lr:5.00E-05
Epoch:24, Train_acc:86.3%, Train_loss:0.339, Test_acc:78.1%, Test_loss:0.409, Lr:5.00E-05
Epoch:25, Train_acc:86.7%, Train_loss:0.335, Test_acc:78.6%, Test_loss:0.408, Lr:5.00E-05
Epoch:26, Train_acc:86.7%, Train_loss:0.332, Test_acc:77.7%, Test_loss:0.408, Lr:5.00E-05
Epoch:27, Train_acc:86.8%, Train_loss:0.329, Test_acc:77.2%, Test_loss:0.408, Lr:5.00E-05
Epoch:28, Train_acc:86.8%, Train_loss:0.327, Test_acc:77.2%, Test_loss:0.408, Lr:5.00E-05
Epoch:29, Train_acc:86.8%, Train_loss:0.324, Test_acc:77.2%, Test_loss:0.408, Lr:5.00E-05
Epoch:30, Train_acc:87.0%, Train_loss:0.322, Test_acc:76.7%, Test_loss:0.409, Lr:5.00E-05
Epoch:31, Train_acc:87.2%, Train_loss:0.320, Test_acc:76.3%, Test_loss:0.409, Lr:5.00E-05
Epoch:32, Train_acc:87.3%, Train_loss:0.318, Test_acc:75.8%, Test_loss:0.410, Lr:5.00E-05
Epoch:33, Train_acc:87.7%, Train_loss:0.316, Test_acc:75.8%, Test_loss:0.411, Lr:5.00E-05
Epoch:34, Train_acc:87.7%, Train_loss:0.314, Test_acc:75.8%, Test_loss:0.412, Lr:5.00E-05
Epoch:35, Train_acc:88.0%, Train_loss:0.312, Test_acc:75.8%, Test_loss:0.413, Lr:5.00E-05
Epoch:36, Train_acc:88.1%, Train_loss:0.310, Test_acc:75.3%, Test_loss:0.414, Lr:5.00E-05
Epoch:37, Train_acc:88.3%, Train_loss:0.309, Test_acc:76.3%, Test_loss:0.416, Lr:5.00E-05
Epoch:38, Train_acc:88.4%, Train_loss:0.307, Test_acc:76.3%, Test_loss:0.417, Lr:5.00E-05
Epoch:39, Train_acc:88.3%, Train_loss:0.305, Test_acc:76.3%, Test_loss:0.418, Lr:5.00E-05
Epoch:40, Train_acc:88.3%, Train_loss:0.304, Test_acc:76.3%, Test_loss:0.420, Lr:5.00E-05
Epoch:41, Train_acc:88.4%, Train_loss:0.302, Test_acc:76.7%, Test_loss:0.421, Lr:5.00E-05
Epoch:42, Train_acc:88.4%, Train_loss:0.301, Test_acc:77.7%, Test_loss:0.423, Lr:5.00E-05
Epoch:43, Train_acc:88.4%, Train_loss:0.299, Test_acc:77.7%, Test_loss:0.425, Lr:5.00E-05
Epoch:44, Train_acc:88.5%, Train_loss:0.297, Test_acc:77.7%, Test_loss:0.426, Lr:5.00E-05
Epoch:45, Train_acc:88.6%, Train_loss:0.296, Test_acc:77.7%, Test_loss:0.428, Lr:5.00E-05
Epoch:46, Train_acc:88.8%, Train_loss:0.294, Test_acc:78.1%, Test_loss:0.430, Lr:5.00E-05
Epoch:47, Train_acc:88.9%, Train_loss:0.293, Test_acc:78.6%, Test_loss:0.432, Lr:5.00E-05
Epoch:48, Train_acc:88.9%, Train_loss:0.291, Test_acc:78.6%, Test_loss:0.435, Lr:5.00E-05
Epoch:49, Train_acc:89.0%, Train_loss:0.290, Test_acc:78.1%, Test_loss:0.437, Lr:5.00E-05
Epoch:50, Train_acc:89.0%, Train_loss:0.288, Test_acc:78.6%, Test_loss:0.439, Lr:5.00E-05
==================== Done ====================
模型已保存到 ./model_rnn.pth
9.模型評估
warnings.filterwarnings("ignore")
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['figure.dpi'] = 200current_time = datetime.now()epochs_range = range(epochs)plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.xlabel(current_time)plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, test_loss, label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
10.混淆矩陣
print("===============輸入數據Shape為===============")
print("X_test.shape: ", X_test.shape)
print("y_test.shape: ", y_test.shape)pred = model(X_test.to(device)).argmax(1).cpu().numpy()print("\n===============輸出數據Shape為===============")
print("pred.shape: ", pred.shape)
===============輸入數據Shape為===============
X_test.shape: torch.Size([215, 32])
y_test.shape: torch.Size([215])===============輸出數據Shape為===============
pred.shape: (215,)
cm = confusion_matrix(y_test, pred)
plt.figure(figsize=(6,5))
plt.suptitle('')
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues")plt.xticks(fontsize=10)
plt.yticks(fontsize=10)
plt.title("Confusion Matrix", fontsize=12)
plt.xlabel("Predicted Label", fontsize=10)
plt.ylabel("True Label", fontsize=10)plt.tight_layout()
plt.show()
11.調用模型進行預測
import torch
from torch import nn
import numpy as np
import pandas as pd
from datetime import datetime
import P27_阿爾茲海默癥 as ar# 設置設備(GPU或CPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 定義模型結構
class model_rnn(nn.Module):def __init__(self):super(model_rnn, self).__init__()self.rnn0 = nn.RNN(input_size=32, hidden_size=200,num_layers=1, batch_first=True)self.fc0 = nn.Linear(200, 50)self.fc1 = nn.Linear(50, 2)def forward(self, x):out, hidden1 = self.rnn0(x)out = self.fc0(out)out = self.fc1(out)return out# 加載模型
model = model_rnn().to(device)
model.load_state_dict(torch.load("./model_rnn.pth")) # 加載保存的模型參數
model.eval() # 設置為評估模式# 假設X_test是測試數據,已經轉換為torch.Tensor
# X_test = ... # 實際數據加載代碼# 對單個樣本進行預測
test_X = ar.X_test[0].reshape(1, -1).to(device) # X_test[0]即我們的輸入數據
pred = model(test_X).argmax(1).item()
print("模型預測結果為:", pred)
print("=="*20)
print("0: 未患病")
print("1: 已患病")
模型預測結果為: 0
========================================
0: 未患病
1: 已患病