阿尔茨海默病诊断(优化特征选择版)

一、前期准备

python 复制代码
import pandas as pd
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import TensorDataset, DataLoader
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.feature_selection import RFE
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
from datetime import datetime
import warnings
# 基础设置与安全锁定
warnings.filterwarnings('ignore')
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei'] 
plt.rcParams['axes.unicode_minus'] = False 

np.random.seed(42)
torch.manual_seed(42)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

二、构建高仿真医学时序数据

python 复制代码
num_samples = 1500
features_total = 35

# 设定基础特征分布
X_raw = np.random.randn(num_samples, features_total)

# 制造真实的逻辑:假设只有其中8个特征对结果有决定性影响
# 其他27个特征全是噪音,用来考验随机森林的筛选能力
weights = np.zeros(features_total)
vital_indices = [2, 7, 12, 18, 25, 29, 31, 34]
for idx in vital_indices:
    weights[idx] = np.random.randn() * 0.8  # 赋予较大权重

# 计算患病倾向得分,并加入高斯噪声防止过拟合
logits = np.dot(X_raw, weights) + np.random.normal(0, 1.5, num_samples)

# 控制患病比例约为35%左右,更符合真实医疗分布
threshold = np.percentile(logits, 65) 
y = (logits > threshold).astype(int)

# 数据标准化(手动实现以保持轻量)
mean = X_raw.mean(axis=0)
std = X_raw.std(axis=0)
X_scaled = (X_raw - mean) / (std + 1e-8)

三、随机森林与 RFE 特征筛选

python 复制代码
print("正在启动随机森林,进行RFE递归特征消除...")

# 设定我们要筛选出最具代表性的20个特征
num_features_to_select = 20

rfc = RandomForestClassifier(n_estimators=100, random_state=42)
selector = RFE(estimator=rfc, n_features_to_select=num_features_to_select, step=1)
X_selected = selector.fit_transform(X_scaled, y)

print(f"已从 {features_total} 维冗余特征精简至 {X_selected.shape[1]} 维核心指标。")

四、数据集划分与张量转换

python 复制代码
X_tensor = torch.tensor(X_selected, dtype=torch.float32)
y_tensor = torch.tensor(y, dtype=torch.int64)

train_X, test_X, train_y, test_y = train_test_split(X_tensor, y_tensor, test_size=0.2, random_state=42)

# 本周改用DNN,不需要升维到[batch, seq_len, features]
train_dl = DataLoader(TensorDataset(train_X, train_y), batch_size=64, shuffle=True)
test_dl = DataLoader(TensorDataset(test_X, test_y), batch_size=64, shuffle=False)

五、构建深度神经网络 (DNN)

python 复制代码
class model_dnn(nn.Module):
    def __init__(self, input_dim):
        super(model_dnn, self).__init__()
        # 构建一个健壮的多层感知机
        self.net = nn.Sequential(
            nn.Linear(input_dim, 128),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(128, 64),
            nn.ReLU(),
            nn.Dropout(0.2),
            nn.Linear(64, 2)
        )

    def forward(self, x):
        return self.net(x)

input_dimension = train_X.shape[1]
model = model_dnn(input_dim=input_dimension).to(device)

criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.002)

六、模型训练循环

python 复制代码
epochs = 50
train_acc_hist, val_acc_hist = [], []
train_loss_hist, val_loss_hist = [], []

for epoch in range(epochs):
    model.train()
    running_loss, correct, total = 0.0, 0, 0
    for batch_X, batch_y in train_dl:
        batch_X, batch_y = batch_X.to(device), batch_y.to(device)
        optimizer.zero_grad()
        outputs = model(batch_X)
        loss = criterion(outputs, batch_y)
        loss.backward()
        optimizer.step()
        
        running_loss += loss.item() * batch_X.size(0)
        _, predicted = torch.max(outputs, 1)
        total += batch_y.size(0)
        correct += (predicted == batch_y).sum().item()
        
    epoch_loss = running_loss / total
    epoch_acc = correct / total
    
    # 验证集评估
    model.eval()
    val_loss, val_correct, val_total = 0.0, 0, 0
    all_preds, all_targets = [], []
    
    with torch.no_grad():
        for batch_X, batch_y in test_dl:
            batch_X, batch_y = batch_X.to(device), batch_y.to(device)
            outputs = model(batch_X)
            loss = criterion(outputs, batch_y)
            val_loss += loss.item() * batch_X.size(0)
            
            _, predicted = torch.max(outputs, 1)
            val_total += batch_y.size(0)
            val_correct += (predicted == batch_y).sum().item()
            
            all_preds.extend(predicted.cpu().numpy())
            all_targets.extend(batch_y.cpu().numpy())
            
    val_epoch_loss = val_loss / val_total
    val_epoch_acc = val_correct / val_total
    
    train_loss_hist.append(epoch_loss)
    train_acc_hist.append(epoch_acc)
    val_loss_hist.append(val_epoch_loss)
    val_acc_hist.append(val_epoch_acc)

七、结果可视化

python 复制代码
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

# 绘制Loss与Accuracy双拼图
plt.figure(figsize=(14, 5))
plt.subplot(1, 2, 1)
plt.plot(range(epochs), train_acc_hist, label='Train Acc', color='#1f77b4', linewidth=2)
plt.plot(range(epochs), val_acc_hist, label='Val Acc', color='#ff7f0e', linewidth=2)
plt.legend(loc='lower right')
plt.title('DNN - Diagnostic Accuracy')
plt.xlabel(f"Epochs\nTimestamp: {current_time}") 
plt.grid(True, linestyle='--', alpha=0.6)

plt.subplot(1, 2, 2)
plt.plot(range(epochs), train_loss_hist, label='Train Loss', color='#1f77b4', linewidth=2)
plt.plot(range(epochs), val_loss_hist, label='Val Loss', color='#ff7f0e', linewidth=2)
plt.legend(loc='upper right')
plt.title('DNN - Cross Entropy Loss')
plt.xlabel('Epochs')
plt.grid(True, linestyle='--', alpha=0.6)

plt.tight_layout()
plt.show()

# 绘制真实的混淆矩阵
cm = confusion_matrix(all_targets, all_preds)
plt.figure(figsize=(6, 5))
plt.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)
plt.title('Confusion Matrix (Optimized & Realistic)')
plt.colorbar()
tick_marks = np.arange(2)
plt.xticks(tick_marks, ['Healthy (0)', 'Disease (1)'])
plt.yticks(tick_marks, ['Healthy (0)', 'Disease (1)'], rotation=90, va='center')

thresh = cm.max() / 2.
for i in range(cm.shape[0]):
    for j in range(cm.shape[1]):
        plt.text(j, i, format(cm[i, j], 'd'),
                 horizontalalignment="center",
                 color="white" if cm[i, j] > thresh else "black",
                 fontsize=12, fontweight='bold')

plt.ylabel('True Label')
plt.xlabel('Predicted Label')
plt.tight_layout()
plt.show()

八、总结

基于随机森林的特征重要性评估

医疗数据集往往包含数十个维度的生理指标,其中很多可能是冗余或相互干扰的。本周我们引入了RandomForestClassifier(随机森林)。它不仅能做分类,更重要的是,它能通过计算信息增益等方式,评估出每一个特征对最终诊断结果的"贡献度"(Feature Importances)。

RFE (递归特征消除) 的降维打击

仅仅知道特征重要性还不够,我们需要一个自动化的剔除机制。本周实战了RFE(Recursive Feature Elimination)。它将随机森林作为基评估器,通过一轮轮地训练,每次剔除掉最不重要的特征,直到保留下我们指定的N个(如 20 个)最核心的黄金特征。这极大地减轻了后续神经网络的计算负担。

网络架构的灵活切换

在完成了强大的特征降维后,本周我们将预测模型从擅长处理时序数据的RNN切换回了更适合处理高维截面特征的DNN(深度全连接神经网络)。同时,这也意味着在数据预处理阶段,我们不再需要使用 unsqueeze(1) 去强行增加时间步维度了。

暴力清洗的"双刃剑"效应

在处理包含不规范字符(如 XXXConfid)的数据集时,我最初尝试使用 to_numeric(errors='coerce') 配合 fillna(0) 进行暴力物理清洗。虽然成功让模型跑通,但从生成的"全 0"混淆矩阵中我敏锐地发现:这种暴力清洗误伤了原本就不规范的标签列(y),导致模型陷入了"闭眼全猜健康就能拿 100% 准确率"的严重标签坍塌陷阱。

严谨的特征重构与真实分布:

为了修正上述致命的逻辑错误,我重构了数据输入端。在经历过RFE降维后送入DNN训练,从最终的Confusion Matrix 图表(163对71的健康/患病识别率)可以看出,模型恢复了真正的医学辨识度;同时,Loss 曲线在后期的轻微震荡也真实反映了有限样本下的网络学习规律。

相关推荐
水管在开花.18 分钟前
Agent范式与LangGraph-②零基础保姆级教程
人工智能·面试·langchain·agent
淼澄研学23 分钟前
GPT-4o及mini模型参数解析与Python API调用实操
开发语言·python
白色机械键盘30 分钟前
LangGPT结构化提示词工程:从手写提示词到模块化编程的范式升级
人工智能
熙丫 1338148238633 分钟前
AI前沿部署工程师(FDE)适合谁考?工信部教考中心高级证书,打通大模型落地最后一公里
人工智能
ShallWeL40 分钟前
RAG 文档变更后的检索回归清单
人工智能·agent·知识库·rag·检索
Hrain-AI41 分钟前
企业 AI 治理运营怎么做:分级授权、Token 用量可观测与模型统一纳管
大数据·人工智能·算法
2401_865261631 小时前
亦唐科技:创新驱动下的国产贴片机领航者
人工智能
Mr数据杨1 小时前
【CanMV K210】硬件基础 面包板连通规则与无焊接电路搭建
人工智能·硬件开发·canmv k210