RNN算法实战系列03 | 丙型肝炎预测
- 🍨 本文为🔗365天深度学习训练营中的学习记录博客
- 🍖 原作者:K同学啊
一、前期准备
1. 导入数据
import os
import random
import warnings
from datetime import datetime
import matplotlib.pyplot as plt
import numpy as np
import optuna
import pandas as pd
import seaborn as sns
import torch
import torch.utils.data as data
from sklearn import metrics
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder, StandardScaler
from torch import nn
from torch.utils.data import DataLoader
warnings.filterwarnings("ignore")
plt.rcParams["font.sans-serif"] = ["PingFang SC", "Arial Unicode MS", "Microsoft YaHei", "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False
plt.rcParams["figure.dpi"] = 150
SEED = 123
QUICK_RUN = os.environ.get("QUICK_RUN", "0") == "1"
N_TRIALS = 3 if QUICK_RUN else 20
EPOCHS = 10 if QUICK_RUN else 50
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
def set_seed(seed=SEED):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(seed)
set_seed()
optuna.logging.set_verbosity(optuna.logging.WARNING)
df = pd.read_csv("./data/R3-data/HepatitisCdata.csv") # 读取 CSV 文件
df.head(5)
|
Unnamed: 0 |
Category |
Age |
Sex |
ALB |
ALP |
ALT |
AST |
BIL |
CHE |
CHOL |
CREA |
GGT |
PROT |
|
|
0 |
1 |
0=Blood Donor |
32 |
m |
38.5 |
52.5 |
7.7 |
22.1 |
7.5 |
6.93 |
3.23 |
106.0 |
12.1 |
69.0 |
|
1 |
2 |
0=Blood Donor |
32 |
m |
38.5 |
70.3 |
18.0 |
24.7 |
3.9 |
11.17 |
4.80 |
74.0 |
15.6 |
76.5 |
|
2 |
3 |
0=Blood Donor |
32 |
m |
46.9 |
74.7 |
36.2 |
52.6 |
6.1 |
8.84 |
5.20 |
86.0 |
33.2 |
79.3 |
|
3 |
4 |
0=Blood Donor |
32 |
m |
43.2 |
52.0 |
30.6 |
22.6 |
18.9 |
7.33 |
4.74 |
80.0 |
33.8 |
75.7 |
|
4 |
5 |
0=Blood Donor |
32 |
m |
39.2 |
74.1 |
32.6 |
24.8 |
9.6 |
9.15 |
4.32 |
76.0 |
29.9 |
68.7 |
2. 探索热力图
# 对类别字段进行标签编码
le_category = LabelEncoder()
le_sex = LabelEncoder()
df["Category"] = le_category.fit_transform(df["Category"])
df["Sex"] = le_sex.fit_transform(df["Sex"])
# 删除样本编号列
df = df.drop(columns=["Unnamed: 0"], errors="ignore")
df.head()
|
Category |
Age |
Sex |
ALB |
ALP |
ALT |
AST |
BIL |
CHE |
CHOL |
CREA |
GGT |
PROT |
|
|
0 |
0 |
32 |
1 |
38.5 |
52.5 |
7.7 |
22.1 |
7.5 |
6.93 |
3.23 |
106.0 |
12.1 |
69.0 |
|
1 |
0 |
32 |
1 |
38.5 |
70.3 |
18.0 |
24.7 |
3.9 |
11.17 |
4.80 |
74.0 |
15.6 |
76.5 |
|
2 |
0 |
32 |
1 |
46.9 |
74.7 |
36.2 |
52.6 |
6.1 |
8.84 |
5.20 |
86.0 |
33.2 |
79.3 |
|
3 |
0 |
32 |
1 |
43.2 |
52.0 |
30.6 |
22.6 |
18.9 |
7.33 |
4.74 |
80.0 |
33.8 |
75.7 |
|
4 |
0 |
32 |
1 |
39.2 |
74.1 |
32.6 |
24.8 |
9.6 |
9.15 |
4.32 |
76.0 |
29.9 |
68.7 |
numeric_cols = df.select_dtypes(include=["int64", "float64"])
plt.figure(figsize=(12, 8))
sns.heatmap(numeric_cols.corr(), cmap="coolwarm", annot=True, fmt=".2f")
plt.title("Correlation Heatmap")
plt.show()

二、数据预处理
1. 处理缺失值
df.isnull().any() # 检查是否存在缺失值
Category False
Age False
Sex False
ALB True
ALP True
ALT True
AST False
BIL False
CHE False
CHOL True
CREA False
GGT False
PROT True
dtype: bool
这里统一使用 0 填充,实际项目中需结合字段实际情况选择合适的值进行填充,或者删除缺失数据。
df["ALB"] = df["ALB"].fillna(0)
df["ALP"] = df["ALP"].fillna(0)
df["ALT"] = df["ALT"].fillna(0)
df["CHOL"] = df["CHOL"].fillna(0)
df["PROT"] = df["PROT"].fillna(0)
df.isnull().any() # 再次检查
Category False
Age False
Sex False
ALB False
ALP False
ALT False
AST False
BIL False
CHE False
CHOL False
CREA False
GGT False
PROT False
dtype: bool
2. 划分训练集、测试集
X = df.iloc[:, 1:]
y = df.iloc[:, 0]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=SEED, stratify=y
)
X_train.shape, X_test.shape, y_train.shape, y_test.shape
((492, 12), (123, 12), (492,), (123,))
3. 探索字段重要性排行
查看一下机器学习模型的准确率有多少。
# 此处使用分类器,便于直接得到类别预测和字段重要性
rf = RandomForestClassifier(n_estimators=100, random_state=42, class_weight="balanced")
rf.fit(X_train, y_train)
feature_importance = pd.DataFrame({
"feature": X_train.columns,
"importance": rf.feature_importances_
}).sort_values("importance", ascending=False)
print("\n前10个重要特征:")
print(feature_importance.head(10))
前10个重要特征:
feature importance
2 ALB 0.142660
7 CHE 0.123471
5 AST 0.118462
3 ALP 0.109497
6 BIL 0.099682
11 PROT 0.091103
4 ALT 0.085846
10 GGT 0.078511
0 Age 0.060828
8 CHOL 0.052498
y_pred_rf = rf.predict(X_test)
class_report_rf = classification_report(y_test, y_pred_rf, zero_division=0)
print(class_report_rf)
precision recall f1-score support
0 0.99 1.00 1.00 107
1 1.00 1.00 1.00 1
2 0.50 0.60 0.55 5
3 0.50 0.25 0.33 4
4 0.83 0.83 0.83 6
accuracy 0.95 123
macro avg 0.76 0.74 0.74 123
weighted avg 0.95 0.95 0.95 123
4. 标准化
# 将数据标准化
sc = StandardScaler()
X_train = sc.fit_transform(X_train)
X_test = sc.transform(X_test)
# 增加序列维度,形状变为 [样本数, 1, 特征数]
X_train = torch.tensor(X_train, dtype=torch.float32).unsqueeze(1)
X_test = torch.tensor(X_test, dtype=torch.float32).unsqueeze(1)
y_train = torch.tensor(y_train.values, dtype=torch.int64)
y_test = torch.tensor(y_test.values, dtype=torch.int64)
X_train.shape, X_test.shape, y_train.shape, y_test.shape
(torch.Size([492, 1, 12]),
torch.Size([123, 1, 12]),
torch.Size([492]),
torch.Size([123]))
5. 构建 dataloader
batch_size = 32
# 封装数据
train_dataset = data.TensorDataset(X_train, y_train)
test_dataset = data.TensorDataset(X_test, y_test)
# 加载数据
train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=batch_size)
三、构建模型
1. 设置模型参数
device
device(type='cuda')
2. 定义模型
class model_lstm(nn.Module):
def __init__(self, hidden_size=200):
super(model_lstm, self).__init__()
self.lstm0 = nn.LSTM(input_size=12, hidden_size=hidden_size, num_layers=1, batch_first=True)
self.fc0 = nn.Linear(hidden_size, 5)
def forward(self, x):
out, _ = self.lstm0(x)
out = out[:, -1, :]
out = self.fc0(out)
return out
model = model_lstm(hidden_size=200).to(device)
print(model)
model_lstm(
(lstm0): LSTM(12, 200, batch_first=True)
(fc0): Linear(in_features=200, out_features=5, bias=True)
)
3. 编写训练函数
# 训练循环
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset) # 训练集的大小
num_batches = len(dataloader) # 批次数目
train_loss, train_acc = 0.0, 0
for 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()
# 记录 acc 与 loss
train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
train_loss += loss.item()
train_acc /= size
train_loss /= num_batches
return train_acc, train_loss
4. 编写测试函数
def test(dataloader, model, loss_fn):
size = len(dataloader.dataset)
num_batches = len(dataloader)
test_loss, test_acc = 0.0, 0
# 当不进行训练时,停止梯度更新,节省计算内存
with torch.no_grad():
for X, y in dataloader:
X, y = X.to(device), y.to(device)
y_pred = model(X)
loss = loss_fn(y_pred, y)
test_loss += loss.item()
test_acc += (y_pred.argmax(1) == y).type(torch.float).sum().item()
test_acc /= size
test_loss /= num_batches
return test_acc, test_loss
四、训练模型
1. 超参数探索
本模型较简单,因此先探索两个最常见的参数:LSTM 隐藏层大小 hidden_size 和学习率 lr。每次 trial 训练 1 个 epoch,以较低成本快速比较不同配置;后续可把 batch size、层数、dropout 等也加入搜索空间。
# 目标函数
def objective(trial):
hidden_size = trial.suggest_int("hidden_size", 128, 256, step=64)
lr = trial.suggest_float("lr", 1e-5, 1e-4, log=True)
model = model_lstm(hidden_size=hidden_size).to(device)
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
model.train()
epoch_train_acc, epoch_train_loss = train(train_dataloader, model, loss_fn, optimizer)
model.eval()
epoch_test_acc, epoch_test_loss = test(test_dataloader, model, loss_fn)
return epoch_test_loss # 目标是最小化 loss
# 创建实验
study = optuna.create_study(
direction="minimize", # 最小化 loss
sampler=optuna.samplers.TPESampler(seed=SEED),
pruner=optuna.pruners.MedianPruner()
)
# 运行优化
study.optimize(objective, n_trials=N_TRIALS, show_progress_bar=False)
# 输出最佳超参数
print("最佳超参数:", study.best_params)
最佳超参数: {'hidden_size': 128, 'lr': 9.898865469442126e-05}
2. 使用最佳超参训练
loss_fn = nn.CrossEntropyLoss()
learn_rate = study.best_params["lr"]
model = model_lstm(hidden_size=study.best_params["hidden_size"]).to(device)
opt = torch.optim.Adam(model.parameters(), lr=learn_rate)
epochs = EPOCHS
train_loss, train_acc = [], []
test_loss, test_acc = [], []
for epoch in range(epochs):
model.train()
epoch_train_acc, epoch_train_loss = train(train_dataloader, model, loss_fn, opt)
model.eval()
epoch_test_acc, epoch_test_loss = test(test_dataloader, 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)
Epoch: 1, Train_acc:61.6%, Train_loss:1.573, Test_acc:71.5%, Test_loss:1.571, Lr:9.90E-05
Epoch: 2, Train_acc:76.2%, Train_loss:1.563, Test_acc:81.3%, Test_loss:1.559, Lr:9.90E-05
Epoch: 3, Train_acc:84.6%, Train_loss:1.550, Test_acc:82.9%, Test_loss:1.548, Lr:9.90E-05
...
Epoch:48, Train_acc:91.7%, Train_loss:0.584, Test_acc:92.7%, Test_loss:0.564, Lr:9.90E-05
Epoch:49, Train_acc:91.7%, Train_loss:0.578, Test_acc:92.7%, Test_loss:0.547, Lr:9.90E-05
Epoch:50, Train_acc:91.7%, Train_loss:0.551, Test_acc:92.7%, Test_loss:0.530, Lr:9.90E-05
==================== Done ====================
五、Loss 与 Accuracy 图
current_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.tight_layout()
plt.show()

更多推荐




所有评论(0)