一、前期准备

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()

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐