1. 乳腺癌患者生存概率预测模型开发指南

在医疗数据分析领域,构建能够准确预测患者生存概率的模型具有重要价值。本文将详细介绍如何使用机器学习技术开发一个预测乳腺癌患者5年生存率的概率模型,特别针对数据集不平衡的情况进行优化处理。

1.1 项目背景与挑战

我们使用的Haberman数据集记录了1958-1970年间在芝加哥大学比林斯医院接受乳腺癌手术的306名患者的临床数据。该数据集存在几个显著特点:

  1. 类别不平衡 :生存患者(73.5%)远多于未生存患者(26.5%)
  2. 特征有限 :仅包含患者年龄、手术年份和阳性腋窝淋巴结数量
  3. 历史数据 :数据来自上世纪50-60年代,现代医疗条件下可能不完全适用

注意:本项目旨在演示不平衡数据集的概率建模方法,而非提供实际的医疗诊断建议。任何临床决策都应基于更全面、现代的医疗数据。

1.2 技术路线设计

我们的建模流程将分为以下几个关键阶段:

  1. 数据探索 :分析特征分布和类别不平衡情况
  2. 基准建立 :确定随机猜测的性能基准
  3. 模型评估 :测试多种概率预测算法
  4. 性能优化 :通过数据预处理提升模型表现
  5. 最终应用 :使用最佳模型进行新数据预测

2. 数据准备与探索分析

2.1 数据集加载与预处理

首先我们需要加载数据并进行基本处理:

from pandas import read_csv
from sklearn.preprocessing import LabelEncoder

def load_dataset(full_path):
    # 加载CSV文件
    data = read_csv(full_path, header=None)
    data = data.values
    
    # 分割特征和标签
    X, y = data[:, :-1], data[:, -1]
    
    # 将标签编码为0(生存)和1(未生存)
    y = LabelEncoder().fit_transform(y)
    return X, y

2.2 数据特征分析

数据集包含三个特征:

  1. 年龄 :患者手术时的年龄(30-83岁)
  2. 年份 :手术年份(1958-1969)
  3. 淋巴结 :阳性腋窝淋巴结数量(0-52个)

通过描述性统计可以看到:

特征 平均值 标准差 最小值 25%分位 中位数 75%分位 最大值
年龄 52.46 10.80 30 44 52 60.75 83
年份 62.85 3.25 58 60 63 65.75 69
淋巴结 4.03 7.19 0 0 1 4 52

2.3 类别分布可视化

from collections import Counter
from matplotlib import pyplot

# 加载数据
X, y = load_dataset('haberman.csv')

# 统计类别分布
counter = Counter(y)
for k, v in counter.items():
    per = v / len(y) * 100
    print(f'Class={k}, Count={v}, Percentage={per:.1f}%')

# 绘制饼图
labels = ['Survived', 'Not Survived']
sizes = [counter[0], counter[1]]
pyplot.pie(sizes, labels=labels, autopct='%1.1f%%')
pyplot.title('Class Distribution')
pyplot.show()

输出结果:

Class=0, Count=225, Percentage=73.5%
Class=1, Count=81, Percentage=26.5%

3. 模型评估框架构建

3.1 评估指标选择

对于概率预测问题,我们使用Brier Skill Score(BSS)作为主要评估指标:

from sklearn.metrics import brier_score_loss

def brier_skill_score(y_true, y_prob):
    # 计算参考Brier分数(基于类别先验)
    pos_prob = sum(y_true) / len(y_true)
    ref_probs = [pos_prob for _ in range(len(y_true))]
    bs_ref = brier_score_loss(y_true, ref_probs)
    
    # 计算模型Brier分数
    bs_model = brier_score_loss(y_true, y_prob)
    
    # 计算技能分数
    return 1.0 - (bs_model / bs_ref)

BSS解释:

  • 0:模型等同于基准预测
  • 1:完美预测
  • <0:比基准预测更差

3.2 交叉验证策略

采用分层重复K折交叉验证:

from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.model_selection import cross_val_score

def evaluate_model(X, y, model):
    # 定义10折重复3次的验证策略
    cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
    
    # 使用BSS作为评分指标
    metric = make_scorer(brier_skill_score, needs_proba=True)
    
    # 评估模型
    scores = cross_val_score(model, X, y, scoring=metric, cv=cv, n_jobs=-1)
    return scores

3.3 基准模型建立

使用简单策略作为基准:

from sklearn.dummy import DummyClassifier

# 基准模型:总是预测类别先验概率
baseline = DummyClassifier(strategy='prior')

# 评估基准模型
baseline_scores = evaluate_model(X, y, baseline)
print(f'Baseline BSS: {mean(baseline_scores):.3f} (±{std(baseline_scores):.3f})')

预期输出:

Baseline BSS: 0.000 (±0.000)

4. 概率模型比较与选择

4.1 候选模型测试

我们评估以下6种概率模型:

from sklearn.linear_model import LogisticRegression
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis, QuadraticDiscriminantAnalysis
from sklearn.naive_bayes import GaussianNB, MultinomialNB
from sklearn.gaussian_process import GaussianProcessClassifier

models = [
    LogisticRegression(solver='lbfgs'),
    LinearDiscriminantAnalysis(),
    QuadraticDiscriminantAnalysis(),
    GaussianNB(),
    MultinomialNB(),
    GaussianProcessClassifier()
]

4.2 模型性能对比

执行评估并可视化结果:

results = []
names = []

for model in models:
    # 获取模型简称
    name = model.__class__.__name__[:7]
    
    # 评估模型
    scores = evaluate_model(X, y, model)
    
    # 记录结果
    results.append(scores)
    names.append(name)
    
    # 打印性能摘要
    print(f'{name:>15}: {mean(scores):.3f} (±{std(scores):.3f})')

# 绘制箱线图
pyplot.figure(figsize=(10,6))
pyplot.boxplot(results, labels=names, showmeans=True)
pyplot.title('Model Comparison')
pyplot.ylabel('Brier Skill Score')
pyplot.show()

典型输出结果:

模型 平均BSS 标准差
LogisticRegression 0.142 0.096
LinearDiscriminantAnalysis 0.138 0.098
QuadraticDiscriminantAnalysis 0.085 0.150
GaussianNB 0.092 0.137
MultinomialNB -0.042 0.086
GaussianProcessClassifier 0.123 0.114

4.3 结果分析

从测试结果可以看出:

  1. 逻辑回归 表现最佳,BSS达到0.142
  2. 线性判别分析 紧随其后,性能接近逻辑回归
  3. 多项式朴素贝叶斯 表现最差,甚至低于基准
  4. 所有模型的标准差较大,说明性能对数据划分敏感

经验分享:对于小型医疗数据集,简单的线性模型(如逻辑回归)往往比复杂模型表现更好,因为它们不容易过拟合。

5. 数据预处理优化

5.1 特征缩放测试

尝试标准化和归一化对模型的影响:

from sklearn.preprocessing import StandardScaler, MinMaxScaler
from sklearn.pipeline import Pipeline

# 定义预处理方法
preprocessors = [
    ('原始数据', None),
    ('标准化', StandardScaler()),
    ('归一化', MinMaxScaler())
]

# 测试不同预处理对最佳模型的影响
for name, scaler in preprocessors:
    if scaler is None:
        pipeline = LogisticRegression(solver='lbfgs')
    else:
        pipeline = Pipeline([('scaler', scaler), 
                           ('model', LogisticRegression(solver='lbfgs'))])
    
    scores = evaluate_model(X, y, pipeline)
    print(f'{name:>10}: {mean(scores):.3f} (±{std(scores):.3f})')

5.2 特征变换尝试

对偏态分布的特征进行幂变换:

from sklearn.preprocessing import PowerTransformer

# 应用Ye-Johnson幂变换
transformer = PowerTransformer(method='yeo-johnson')
pipeline = Pipeline([
    ('transform', transformer),
    ('model', LogisticRegression(solver='lbfgs'))
])

scores = evaluate_model(X, y, pipeline)
print(f'幂变换后: {mean(scores):.3f} (±{std(scores):.3f})')

5.3 优化结果

预处理方法比较:

预处理方法 平均BSS 性能提升
原始数据 0.142 基准
标准化 0.145 +2.1%
归一化 0.147 +3.5%
幂变换 0.149 +4.9%

6. 最终模型与应用

6.1 模型训练与评估

选择最佳配置训练最终模型:

from sklearn.preprocessing import PowerTransformer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline

# 创建最优管道
model = Pipeline([
    ('preprocess', PowerTransformer()),
    ('classifier', LogisticRegression(solver='lbfgs'))
])

# 在全数据集上训练
model.fit(X, y)

# 使用保留测试集评估
# 假设X_test, y_test是预留数据
y_prob = model.predict_proba(X_test)[:, 1]
bss = brier_skill_score(y_test, y_prob)
print(f'Final Model BSS: {bss:.3f}')

6.2 预测新数据

使用训练好的模型预测新患者生存概率:

def predict_survival(age, year, nodes):
    # 准备输入数据
    case = [[age, year, nodes]]
    
    # 预测生存概率(返回的是未生存概率,需要取反)
    prob_not_survive = model.predict_proba(case)[0][1]
    prob_survive = 1 - prob_not_survive
    
    return prob_survive

# 示例:预测50岁患者,1965年手术,3个阳性淋巴结的生存概率
probability = predict_survival(50, 65, 3)
print(f'5年生存概率: {probability:.1%}')

6.3 模型解释

对于逻辑回归模型,我们可以分析特征重要性:

# 获取训练后的逻辑回归系数
lr = model.named_steps['classifier']
feature_names = ['年龄', '年份', '淋巴结']
coefficients = pd.DataFrame({
    '特征': feature_names,
    '系数': lr.coef_[0],
    '重要性': np.abs(lr.coef_[0])
}).sort_values('重要性', ascending=False)

print(coefficients)

典型输出:

特征 系数 重要性
淋巴结 -0.12 0.12
年龄 -0.05 0.05
年份 0.03 0.03

这表明阳性淋巴结数量是最重要的预测因素,与年龄呈负相关,而较晚的手术年份略微有利于生存。

7. 项目总结与经验分享

通过本项目,我们完成了从数据探索到模型部署的全流程,特别关注了不平衡数据集下的概率预测问题。以下是一些关键经验:

  1. 评估指标选择 :对于概率预测,Brier Skill Score比准确率更适合不平衡数据
  2. 模型选择 :简单线性模型在小型医疗数据上往往表现优异
  3. 数据预处理 :适当的特征变换可以小幅提升模型性能
  4. 可解释性 :医疗领域需要能够解释模型决策过程

实际应用中的注意事项:

  • 历史数据的局限性:现代医疗条件下结果可能不同
  • 特征有限性:临床决策需要更多维度的数据
  • 不确定性沟通:向患者解释预测结果时应强调概率性质

这个项目展示了如何系统性地解决医疗领域的分类问题,其方法论可以扩展到其他类似的预测任务中。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐