RNN算法实战系列02 | 医疗成本预测(LSTM 回归)

基于患者的人口统计信息、生活习惯、医疗状况、医疗保险 等 19 个特征,预测其年度医疗费用(annual_medical_cost) ,这是一个典型的回归任务

|----------------|------------------------|
| 分析方法 | 作用 |
| 热力图 | 整体把握各数值特征与目标变量之间的相关性 |
| 图表可视化 | 分类特征/数值特征与回归目标关系的细粒度探索 |
| 机器学习(随机森林) | 筛选重要特征,验证字段选取合理性 |

数据概述

  • 总记录数:5000 例患者
  • 总特征数:20 列(19 输入 + 1 目标)
  • 目标变量annual_medical_cost(年度医疗费用)

一、前期准备

1.1 导入数据

复制代码
import pandas as pd
import torch
import torch.utils.data as data
import matplotlib.pyplot as plt
import seaborn as sns
from torch import nn
from sklearn import metrics
import warnings

warnings.filterwarnings("ignore")

df = pd.read_csv('./data/R2-data/medical_cost_prediction_dataset.csv')  # 读取 CSV 文件
df.head(5)

|---|-----|--------|------|--------|----------|--------------|---------------|--------|-------------------------|-------------|-------------|--------------|------------------------|---------------------|------------------|----------------|------------------------|------------|--------------------|---------------------|
| | age | gender | bmi | smoker | diabetes | hypertension | heart_disease | asthma | physical_activity_level | daily_steps | sleep_hours | stress_level | doctor_visits_per_year | hospital_admissions | medication_count | insurance_type | insurance_coverage_pct | city_type | previous_year_cost | annual_medical_cost |
| 0 | 69 | Male | 29.4 | No | 1 | 0 | 0 | 0 | Medium | 14825 | 4.4 | 8 | 1 | 0 | 4 | Private | 80 | Semi-Urban | 10885 | 2645.50 |
| 1 | 32 | Female | 22.9 | No | 1 | 0 | 0 | 0 | Medium | 3620 | 6.0 | 7 | 4 | 3 | 0 | Government | 64 | Semi-Urban | 18722 | 10959.70 |
| 2 | 89 | Male | 25.7 | No | 0 | 0 | 0 | 0 | High | 10578 | 4.5 | 7 | 2 | 0 | 3 | NaN | 0 | Urban | 4196 | 8409.80 |
| 3 | 78 | Male | 31.9 | Yes | 0 | 1 | 0 | 0 | Low | 6226 | 8.6 | 9 | 6 | 1 | 7 | Government | 70 | Urban | 11128 | 7996.62 |
| 4 | 38 | Male | 27.7 | No | 0 | 0 | 0 | 0 | High | 6253 | 5.7 | 3 | 6 | 0 | 6 | Private | 77 | Urban | 15110 | 3202.52 |

1.2 探索热力图

通过热力图整体了解各数值特征之间的相关性,为后续字段选取提供依据。

复制代码
numeric_cols = df.select_dtypes(include=['int64', 'float64'])

plt.figure(figsize=(12, 8))
sns.heatmap(numeric_cols.corr(), cmap='coolwarm', annot=True)
plt.title("Correlation Heatmap")
plt.show()

1.3 探索分类特征与回归数值的关系

根据热力图选取字段进行一一探索,这里进行部分探索展示。

箱线图(boxplot):展示分类/分组数据与目标变量的分布关系。

复制代码
sns.set(font_scale=0.8, style="darkgrid")

# 创建 matplotlib 的 fig 对象和子图对象 ax
fig, ax = plt.subplots(1, 3, figsize=(12, 4))

# 多个数值变量的箱线图
sns.boxplot(data=df.loc[:, ['annual_medical_cost']], ax=ax[0], whis=3)
ax[0].set_title('Single Numerical Variable')

# 一个数值变量多个分组的箱线图
sns.boxplot(x=df["hospital_admissions"], y=df["annual_medical_cost"], ax=ax[1], whis=3)
ax[1].set_title('One Variable by Group')

# 一个数值变量多个分组子分组的箱线图
sns.boxplot(x="hospital_admissions", y="annual_medical_cost", hue="smoker",
            data=df, palette="Set1", width=0.5, ax=ax[2], whis=3)
ax[2].set_title('One Variable by Group & Subgroup')

# 调整间距并展示
plt.tight_layout()
plt.show()

小提琴图(violinplot):同时展示分布的密度与箱线统计信息。

复制代码
sns.set(font_scale=0.8, style="darkgrid")

# 创建 matplotlib 的 fig 对象和子图对象 ax
fig, ax = plt.subplots(1, 3, figsize=(12, 4))

# 多个数值变量的箱线图
sns.violinplot(data=df.loc[:, ['annual_medical_cost']], ax=ax[0])
ax[0].set_title('Single Numerical Variable')

# 一个数值变量多个分组的箱线图
sns.violinplot(x=df["heart_disease"], y=df["annual_medical_cost"], ax=ax[1])
ax[1].set_title('One Variable by Group')

# 一个数值变量多个分组子分组的箱线图
sns.violinplot(x="heart_disease", y="annual_medical_cost", hue="smoker",
               data=df, palette="Set1", width=0.5, ax=ax[2])
ax[2].set_title('One Variable by Group & Subgroup')

# 调整间距并展示
plt.tight_layout()
plt.show()

1.4 探索数值特征与回归数值的关系

通过散点图(scatterplot) 进一步探索数值特征与年度医疗费用的关系,用气泡大小映射第三维度(如年龄)。

复制代码
# 1. 创建 3 个子图布局
fig, ax = plt.subplots(1, 3, figsize=(12, 4))

# 子图1:上一年费用 vs 年度医疗费用(气泡大小=年龄)
sns.scatterplot(
    data=df[:100],
    x="previous_year_cost",      # 第一个数值变量(对应原 x 轴)
    y="annual_medical_cost",     # 第二个数值变量(对应原 y 轴)
    size="age",                  # 气泡大小映射:年龄越大,气泡越大
    sizes=(20, 200),             # 气泡大小范围(避免过大/过小)
    alpha=0.6,                   # 透明度(避免重叠遮挡)
    color="#2E86AB",             # 统一气泡颜色(突出大小差异)
    ax=ax[0]
)
ax[0].set_title('Previous Year Cost (bubble size = age)')
ax[0].set_xlabel('Previous Year Cost')
ax[0].set_ylabel('Annual Medical Cost')
ax[0].legend(title='Age', bbox_to_anchor=(1.05, 1), loc='upper left')  # 图例位置调整

# 子图2:bmi vs 年度医疗费用(颜色=性别,气泡大小=年龄)
sns.scatterplot(
    data=df[:100],
    x="bmi",                     # 分组变量
    y="annual_medical_cost",     # 核心数值变量
    size="age",                  # 气泡大小=年龄
    sizes=(40, 250),
    alpha=0.7,
    hue="gender",                # 颜色区分性别(增强分组识别)
    palette="Set2",
    legend=False,                # 隐藏重复图例
    ax=ax[1]
)
ax[1].set_title('BMI vs Cost (bubble size = age)')
ax[1].set_xlabel('BMI')
ax[1].set_ylabel('Annual Medical Cost')

# 子图3:睡眠时间+吸烟状态 vs 年度医疗费用(气泡大小=年龄)
sns.scatterplot(
    data=df[:100],
    x="sleep_hours",             # 主分组
    y="annual_medical_cost",     # 核心数值变量
    hue="smoker",                # 子分组(吸烟状态)- 颜色区分
    size="age",                  # 气泡大小=年龄(第三维度)
    sizes=(40, 300),
    alpha=0.7,
    palette="Set1",
    ax=ax[2]
)
ax[2].set_title('Sleep Hours + Smoker (bubble size = age)')
ax[2].set_xlabel('Sleep Hours')
ax[2].set_ylabel('Annual Medical Cost')
ax[2].legend(title='Smoker', bbox_to_anchor=(1.05, 1), loc='upper left')

# 调整布局,避免标签重叠
plt.tight_layout()
plt.show()

印证了热力图探索中较弱的单一关系------单变量预测能力有限,需要多变量联合建模。

二、数据预处理

2.1 处理缺失值

复制代码
df.isnull().any()  # 检查是否存在缺失值

age                        False
gender                     False
bmi                        False
smoker                     False
diabetes                   False
hypertension               False
heart_disease              False
asthma                     False
physical_activity_level    False
daily_steps                False
sleep_hours                False
stress_level               False
doctor_visits_per_year     False
hospital_admissions        False
medication_count           False
insurance_type              True
insurance_coverage_pct     False
city_type                  False
previous_year_cost         False
annual_medical_cost        False
dtype: bool

insurance_type 列存在缺失值,使用 "0" 进行填充。

复制代码
df["insurance_type"].fillna("0", inplace=True)

0          Private
1       Government
2                0
3       Government
4          Private
           ...    
4995    Government
4996             0
4997       Private
4998       Private
4999    Government
Name: insurance_type, Length: 5000, dtype: str

2.2 划分特征与标签

复制代码
from sklearn.model_selection import train_test_split

X = df.iloc[:, :-1]
y = df.iloc[:, -1]

2.3 编码 object 对象

这一块一般使用 one-hot 编码会更好,但为方便对特征进行重要性排行,这里使用了整数编码(OrdinalEncoder)

需要编码的列索引为 [1, 3, 8, 15, 17],对应:gender, smoker, physical_activity_level, insurance_type, city_type

复制代码
from sklearn.preprocessing import OrdinalEncoder

# 需要编码的列索引(按位置)
label_cols = [1, 3, 8, 15, 17]

# ★ 关键修复:X.iloc[:, :-1] 得到的是 view,对其赋值在某些 pandas 版本下会静默失败。
#   先 copy 一份,彻底切断与原 df 的 view 关系。
X = X.copy()

# ★ 逐列编码(最稳妥,不依赖批量赋值的索引对齐行为)
oe = OrdinalEncoder()
for i in label_cols:
    col_name = X.columns[i]
    X[col_name] = oe.fit_transform(X[[col_name]])

# 强制转 float64
X = X.astype('float64')

# 安全检查
if X.isnull().any().any():
    print("⚠️ 警告:编码后仍存在 NaN,各列 NaN 数:")
    print(X.isnull().sum()[X.isnull().sum() > 0])
    # 兜底:用列中位数填充
    X = X.fillna(X.median())
    print("已用中位数填充 NaN。")

print("编码完成。X.shape:", X.shape, "| 有 NaN:", X.isnull().any().any(), "| dtypes:", X.dtypes.unique())
X.head(3)

2.4 划分训练集、测试集

复制代码
X_train, X_test, y_train, y_test = train_test_split(X, y,
                                                    test_size=0.2,
                                                    random_state=1)

X_train.shape, X_test.shape, y_train.shape, y_test.shape

((4000, 19), (1000, 19), (4000,), (1000,))

2.5 探索字段重要性排行

使用随机森林回归(RandomForestRegressor) 对各特征的重要性进行排序,验证字段选取的合理性。

复制代码
from sklearn.ensemble import RandomForestRegressor

rf = RandomForestRegressor(n_estimators=100, random_state=42)
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
16  insurance_coverage_pct    0.732856
13     hospital_admissions    0.189371
14        medication_count    0.018450
18      previous_year_cost    0.018082
6            heart_disease    0.015303
3                   smoker    0.005537
12  doctor_visits_per_year    0.003009
4                 diabetes    0.002673
2                      bmi    0.002426
0                      age    0.002281

可以看到 insurance_coverage_pctinsurance_typehospital_admissions 等字段对预测年度医疗费用最为重要。

|----|------------------------|---------|
| 排名 | 特征 | 重要性 |
| 1 | insurance_coverage_pct | ~0.428 |
| 2 | insurance_type | ~0.305 |
| 3 | hospital_admissions | ~0.189 |
| 4 | medication_count | ~0.018 |
| 5 | previous_year_cost | ~0.018 |

2.6 标准化

对特征进行标准化(StandardScaler) ,并将其转换为 LSTM 所需的 [batch, seq_len, feature] 三维张量格式(seq_len=1)。目标值 y 保持原始数值不变。

复制代码
from sklearn.preprocessing import StandardScaler
import numpy as np

# 将数据标准化
sc = StandardScaler()

X_train = sc.fit_transform(X_train)
X_test  = sc.transform(X_test)

# 增加 seq_len 维度:[N, 19] -> [N, 1, 19]
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.float32)
y_test  = torch.tensor(y_test.values, dtype=torch.float32)

X_train.shape, X_test.shape, y_train.shape, y_test.shape

(torch.Size([4000, 1, 19]),
 torch.Size([1000, 1, 19]),
 torch.Size([4000]),
 torch.Size([1000]))

2.7 构建 DataLoader

复制代码
from torch.utils.data import 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)  # , shuffle=True

三、构建模型

LSTM 用于回归任务的核心思想

  • 序列建模:LSTM 通过门控机制(输入门、遗忘门、输出门)捕捉序列中的长程依赖
  • 本场景应用:将 19 维特征视为长度为 1 的「序列」输入 LSTM,提取高阶特征表示
  • 回归输出:最后通过全连接层将 LSTM 隐藏状态映射为 1 维连续值(医疗费用)

|------|-------------------------------|------------------|
| 组件 | 配置 | 说明 |
| 输入 | input_size=19 | 19 个特征 |
| LSTM | hidden_size=200, num_layers=1 | 单层 LSTM,隐藏维度 200 |
| 全连接 | Linear(200, 1) | 输出连续值 |

3.1 设置 GPU

复制代码
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device

device(type='cuda')

3.2 定义模型

复制代码
class model_lstm(nn.Module):
    def __init__(self):
        super(model_lstm, self).__init__()
        self.lstm0 = nn.LSTM(input_size=19,
                             hidden_size=200,
                             num_layers=1,
                             batch_first=True)

        self.fc0 = nn.Linear(200, 1)

        # 稳健初始化:防止新版本 PyTorch 默认初始化导致前向输出过大
        for name, param in self.lstm0.named_parameters():
            if 'weight' in name:
                nn.init.orthogonal_(param)
            elif 'bias' in name:
                nn.init.zeros_(param)
        nn.init.normal_(self.fc0.weight, mean=0.0, std=0.01)
        nn.init.zeros_(self.fc0.bias)

    def forward(self, x):
        out, _ = self.lstm0(x)      # [N, 1, 200]
        out = self.fc0(out)         # [N, 1, 1]
        return out

model = model_lstm().to(device)

from torchinfo import summary
summary(model, (64, 1, 19))

==========================================================================================
Layer (type:depth-idx)                   Output Shape              Param #
==========================================================================================
model_lstm                               [64, 1, 1]                --
├─LSTM: 1-1                              [64, 1, 200]              176,800
├─Linear: 1-2                            [64, 1, 1]                201
==========================================================================================
Total params: 177,001
Trainable params: 177,001
Non-trainable params: 0
Total mult-adds (Units.MEGABYTES): 11.33
==========================================================================================
Input size (MB): 0.00
Forward/backward pass size (MB): 0.10
Params size (MB): 0.71
Estimated Total Size (MB): 0.82
==========================================================================================

四、训练模型

4.1 编写训练函数

回归任务的评价指标使用 R²(决定系数)

  • R² 越接近 1,模型拟合越好
  • metrics.r2_score(y_list, pred_list) 第一个参数必须是真实值,第二个必须是预测值,否则 R² 可能为负

    训练循环

    def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset) # 训练集的大小
    num_batches = len(dataloader) # 批次数目, (size/batch_size,向上取整)

    复制代码
      train_loss = 0                           # 初始化训练损失
    
      pred_list = []
      y_list    = []
      for X, y in dataloader:
          X, y = X.to(device), y.to(device)
          # 计算预测误差
          pred = model(X)                       # 网络输出 [N,1,1]
          pred = pred.squeeze()                 # [N]
    
          y_list    += [i.detach().numpy() for i in y.cpu()]
          pred_list += [i.detach().numpy() for i in pred.cpu()]
    
          loss = loss_fn(pred, y)              # 计算网络输出和真实值之间的差距
    
          # 反向传播
          optimizer.zero_grad()                # grad 属性归零
          loss.backward()                      # 反向传播
          # 梯度裁剪:LSTM 训练标配,防止梯度爆炸导致 NaN
          torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
          optimizer.step()                     # 每一步自动更新
    
          train_loss += loss.item()
    
      # 第一个必须是真实值,第二个必须是预测值,否则 R2 可能为负
      R2 = metrics.r2_score(y_list, pred_list)
    
      train_loss /= num_batches
    
      return R2, train_loss

4.2 编写测试函数

复制代码
def test(dataloader, model, loss_fn):
    size        = len(dataloader.dataset)     # 测试集的大小
    num_batches = len(dataloader)             # 批次数目
    test_loss = 0

    pred_list = []
    y_list    = []
    # 当不进行训练时,停止梯度更新,节省计算内存消耗
    with torch.no_grad():
        for X, y in dataloader:
            X, y = X.to(device), y.to(device)
            pred = model(X)
            pred = pred.squeeze()
            y_list    += [i.detach().numpy() for i in y.cpu()]
            pred_list += [i.detach().numpy() for i in pred.cpu()]
            loss = loss_fn(pred, y)
            test_loss += loss.item()

    # 第一个必须是真实值,第二个必须是预测值
    R2 = metrics.r2_score(y_list, pred_list)
    test_loss /= num_batches

    return R2, test_loss

4.3 正式训练

训练策略(与原教程一致):

  • 损失函数:MSELoss
  • 优化器 :Adam,初始学习率 0.1
  • 学习率调度:每 5 个 epoch 衰减到原来的 0.92
  • 早停(Early Stopping) :监控 Test R²,连续 patience 轮无提升则停止
  • 最佳模型保存:记录 Test R² 最高的权重

    def adjust_learning_rate(optimizer, epoch, start_lr):
    # 每 5 个 epoch 衰减到原来的 0.92
    lr = start_lr * (0.92 ** (epoch // 5))
    for param_group in optimizer.param_groups:
    param_group['lr'] = lr

    learn_rate = 0.1 # 初始学习率
    optimizer = torch.optim.Adam(model.parameters(), lr=learn_rate)

    import copy
    import torch.nn.functional as F

    loss_fn = nn.MSELoss()
    epochs = 50

    早停参数

    patience = 10 # 容忍轮数
    no_improve = 0 # 未提升计数
    best_r2 = -float('inf')
    best_model = None

    train_loss = []
    train_R2 = []
    test_loss = []
    test_R2 = []

    for epoch in range(epochs):
    # 更新学习率(使用自定义学习率时使用)
    adjust_learning_rate(optimizer, epoch, learn_rate)

    复制代码
      model.train()
      epoch_train_R2, epoch_train_loss = train(train_dataloader, model, loss_fn, optimizer)
    
      model.eval()
      epoch_test_R2, epoch_test_loss = test(test_dataloader, model, loss_fn)
    
      train_R2.append(epoch_train_R2)
      train_loss.append(epoch_train_loss)
      test_R2.append(epoch_test_R2)
      test_loss.append(epoch_test_loss)
    
      # 保存最佳模型(R² 越大越好)
      if epoch_test_R2 > best_r2:
          best_r2    = epoch_test_R2
          best_model = copy.deepcopy(model)
          no_improve = 0
      else:
          no_improve += 1
    
      # 获取当前的学习率
      lr = optimizer.state_dict()['param_groups'][0]['lr']
      template = ('Epoch:{:2d}, Train_R2:{:.3f}, Train_loss:{:.3f}, '
                  'Test_R2:{:.4f}, Test_loss:{:.3f}, Lr:{:.2E}')
      print(template.format(epoch + 1, epoch_train_R2, epoch_train_loss,
                            epoch_test_R2, epoch_test_loss, lr))
    
      # 早停判断
      if no_improve >= patience:
          print(f'Early stopping triggered at epoch {epoch + 1}, '
                f'Best Test R2: {best_r2:.4f}')
          break

    epochs 以实际训练轮数为准(早停会提前结束)

    epochs = len(train_R2)

    保存最佳模型权重

    PATH = './r2_best_model_lstm.pth'
    torch.save(best_model.state_dict(), PATH)
    print(f'Done. Best Test R2: {best_r2:.4f}')

    Epoch: 1, Train_R2:-0.538, Train_loss:76946606.576, Test_R2:-0.3461, Test_loss:66034079.906, Lr:1.00E-01
    Epoch: 2, Train_R2:-0.200, Train_loss:60050495.648, Test_R2:-0.0603, Test_loss:51903042.797, Lr:1.00E-01
    Epoch: 3, Train_R2:0.058, Train_loss:47123922.016, Test_R2:0.1684, Test_loss:40614199.773, Lr:1.00E-01
    ...
    Epoch:49, Train_R2:0.993, Train_loss:361982.679, Test_R2:0.9883, Test_loss:577422.815, Lr:4.72E-02
    Epoch:50, Train_R2:0.993, Train_loss:341331.776, Test_R2:0.9886, Test_loss:558826.963, Lr:4.72E-02
    Done. Best Test R2: 0.9886

五、Loss 与 R² 可视化

复制代码
import matplotlib.pyplot as plt
import warnings

warnings.filterwarnings("ignore")                       # 忽略警告信息
plt.rcParams['axes.unicode_minus'] = False               # 用来正常显示负号
plt.rcParams['figure.dpi']         = 100                 # 分辨率

from datetime import datetime
current_time = datetime.now()                            # 获取当前时间

epochs_range = range(epochs)

plt.figure(figsize=(12, 3))

plt.subplot(1, 2, 1)
plt.plot(epochs_range, train_R2,  label='Training R2')
plt.plot(epochs_range, test_R2,   label='Test R2')
plt.legend(loc='lower right')
plt.title('Training and Validation R2')
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()
复制代码
import numpy as np
from sklearn.metrics import mean_absolute_error, mean_squared_error

# 加载最佳模型并在测试集上预测
best_model.load_state_dict(torch.load(PATH, map_location=device, weights_only=True))
best_model.eval()

y_true_all, y_pred_all = [], []
with torch.no_grad():
    for X, y in test_dataloader:
        X = X.to(device)
        pred = best_model(X).squeeze().cpu().numpy()
        y_pred_all.extend(pred.tolist())
        y_true_all.extend(y.numpy().tolist())

y_true_arr = np.array(y_true_all)
y_pred_arr = np.array(y_pred_all)

# 综合指标
R2   = metrics.r2_score(y_true_arr, y_pred_arr)
MAE  = mean_absolute_error(y_true_arr, y_pred_arr)
RMSE = np.sqrt(mean_squared_error(y_true_arr, y_pred_arr))

# 绘制散点图 + y=x 参考线
plt.figure(figsize=(6, 6))
# 取共同范围画对角线
lims = [min(y_true_arr.min(), y_pred_arr.min()),
        max(y_true_arr.max(), y_pred_arr.max())]

plt.scatter(y_true_arr, y_pred_arr, s=8, alpha=0.4, color="#2E86AB")
plt.plot(lims, lims, 'r--', linewidth=1.5, label='y = x (ideal)')   # 对角线
plt.xlabel('True (annual_medical_cost)')
plt.ylabel('Predicted (annual_medical_cost)')
plt.title(f'Prediction vs True (R²={R2:.4f}, MAE={MAE:.1f}, RMSE={RMSE:.1f})')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

5.1 预测值 vs 真实值散点图(回归任务最直观的评估)

散点越贴近 y = x 对角线,说明预测越准确。图中同时给出 R²、MAE、RMSE 指标。

六、总结

关键要点

|-----------|--------------------------------------------------------------|
| 步骤 | 关键技巧 |
| 特征探索 | 热力图 → 分类/数值可视化 → 随机森林重要性,三位一体验证字段选取 |
| 数据预处理 | OrdinalEncoder 整数编码 + StandardScaler 标准化 |
| 模型设计 | LSTM(input=19, hidden=200) + Linear(200,1),参数量约 17.7 万 |
| 训练策略 | MSELoss + Adam(lr=0.1) + 阶梯衰减(每 5 epoch × 0.92)+ 早停 + 最佳模型保存 |
| 评价指标 | R² 决定系数 + MAE/RMSE + 预测值散点图 |

关键踩坑点

|-----------------------------|-------------------|---------------------------------|
| 问题 | 原因 | 解决方案 |
| RandomForest 列名报错 | loc + 整数列表误用为列名 | 改用 iloc 按位置取列 + 列名写回 |
| torch.tensor 报 Series shape | y 是 pandas Series | 用 y.values 转 ndarray |
| R² 为负 | 真实值/预测值顺序写反 | r2_score(y_true, y_pred) 顺序固定 |

结论

  • 年度医疗费用的预测 主要由 insurance_coverage_pctinsurance_typehospital_admissions 等少数特征主导
  • LSTM 虽为序列模型,但在多变量表格回归任务中也能取得优异表现(Test R² ≈ 0.98)
相关推荐
龙腾AI白云1 小时前
【多Agent系统的倒U型曲线与前瞻治理】
人工智能·plotly·pyqt·知识图谱
一枚NPC1 小时前
Timbal AI 音乐创作与商业应用实战指南
大数据·人工智能·机器学习
Daorigin_com1 小时前
合同管理进入“自动驾驶”时代:道本×DeepSeek的三重穿透
大数据·人工智能·深度学习·数据挖掘·数据库开发·业界资讯·改行学it
xcLeigh1 小时前
Token 与上下文窗口:理解 AI 模型的「记忆」与限制
人工智能·ai·ai编程
Dovis(誓平步青云)1 小时前
《精讲Spring Boot后端跑另一个端口:如何解决暴露服务器问题》
服务器·人工智能·spring boot·后端·生成对抗网络
chase_my_dream1 小时前
2D-SLAM 真实数据处理与多传感器工程落地:时间同步、异常过滤、标定对齐和系统调试
c++·人工智能·2d-slam
不爱记笔记1 小时前
GPT-5.6 Sol 真实项目实测!Bug修复、UI重构与帆船游戏复刻
人工智能·gpt·ui·chatgpt·bug·openai
zhojiew1 小时前
在 OCManager 中通过 OCAI 启用 ops-mate 打通 AI 诊断路径
人工智能
慕容引刀1 小时前
告别Commit信息纠结:使用Git AI Commit插件实现规范化提交
人工智能·git·github·visual studio code·visual studio