ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

健康保险理赔预测实战:七种回归模型对比与SVR调优

健康保险理赔预测实战:七种回归模型对比与SVR调优

简介:健康保险理赔预测Python源码及配套数据集,面向需要掌握医疗费用回归建模的开发者、数据分析初学者及机器学习学习者。资源基于Kaggle医疗费用个人数据集,完成了特征可视化、数据探索到线性回归模型训练与对比的完整流程。压缩包共30个文件,包括7个Python源码脚本、11个pkl模型文件、10张可视化图表、1份csv数据集及1份md说明文档,整体仅1.25MB,结构清晰便于快速定位。可视化部分涵盖年龄、性别、BMI、子女数、吸烟情况及地区对医疗支出的相关性分析;模型脚本除线性回归外,还提供Lasso、Ridge、ElasticNet、SVR等多种回归算法的可复用实现与性能对比,pkl文件可直接加载验证。已有781人学习浏览,适合课程设计、比赛练习或入门保险理赔支出预测,是一份兼顾代码、图表与模型产出的轻量级实战资料。

1. 健康保险理赔预测:一个能直接跑的回归项目,七种模型都在里面

理赔支出预测在保险行业从来不是玄学,但也不是套一个线性回归就能交差的活。这份基于 Kaggle 医疗费用个人数据集的健康保险理赔预测源码,把整条链路摆出来了:1338 条客户特征记录、两套可视化脚本、七个训练好的回归模型文件,外加 train.py 和 test.py 两条可直接运行的入口。对刚入门机器学习的人来说,这是一个能完整跑通的回归实战项目;对保险数据相关从业者来说,里面的特征相关性分析和多模型对比结果,能直接用来做保费估算和核保规则的前置参考。我拿到压缩包后的第一反应是看 README 和数据字典,确认特征含义,再逐个加载 pkl 看模型边界——这就是本篇要带你走完的完整过程。

2. 理赔成本预测的技术选型:为什么同时上线性回归和 SVR

2.1 数据集的七个字段,先搞清楚每个特征在说什么

insurance.csv 是这份资源的核心数据,打开后是典型的表格结构,七列对应七种信息。第一列 age 是主要受益人年龄,连续性数值,范围在 18 到 64 岁之间;第二列 sex 是性别编码,只有 male 和 female 两个取值;第三列 bmi 是身体质量指数,计算公式是体重除以身高的平方,理想区间是 18.5 到 24.9,这是一个在理赔预测里非常关键的特征,bmi 过高往往意味着更多慢性病支出;第四列 children 是健康保险覆盖的子女或受抚养人数;第五列 smoker 是是否吸烟,二分类变量;第六列 region 是受益人居住的美国地区,分东北、东南、西南、西北四类;最后一列 charges 是个人住院医疗费用,也就是我们要预测的目标变量。

拿到这张表之后,我先用 pandas 跑了一遍 info 和 describe,确认有没有缺失值和异常值,这是第一步必须做的事。常见做法是先看数据类型有没有混入字符串,再看数值字段的分布区间是否合理。这里的 charges 有一个明显特征:分布严重右偏,少数高额赔付把均值拉得很高,这一点直接影响后续模型评估指标的选择。

表格整理如下:

字段类型取值/范围在预测中的作用
ageint18~64年龄越大,慢性病概率越高
sexstrmale/female类别特征,需编码
bmifloat以 kg/m² 计强连续特征,需标准化
childrenint0~5受抚养人数,弱相关
smokerstryes/no最强的单变量特征
regionstr4 个地区相关性较弱,需编码
chargesfloat连续值目标变量,右偏分布

2.2 七个模型的定位:它们不是重复造轮子

压缩包里同时出现了 hic-LinearRegression.pkl、hic-Ridge.pkl、hic-RidgeCV.pkl、hic-Lasso.pkl、hic-ElasticNet.pkl、hic-SGDRegressor.pkl,以及四个核函数的 SVR 模型文件。第一次看到这么多 pkl 的新手会问,为什么要同时训练这么多模型,选一个最好的不行吗。答案是:线性回归负责建立基线,Ridge 和 Lasso 处理正则化,SGDRegressor 应对大数据量下的效率问题,SVR 则用来捕获非线性关系。

先看线性回归。它是最朴素的 baseline,模型假设特征和目标之间是线性关系,公式是 charges = w·x + b。这个数据集里吸烟者和非吸烟者的理赔金额差距很大,线性模型能抓到这个主效应,所以线性回归的拟合优度不会太差,但它的缺陷是无法处理 bmi 与 charges 之间的非线性关系。bmi 超过 30 之后,理赔金额的增速明显加快,这种拐点效应线性模型天生捕捉不到。

再看正则化系列。Ridge 在损失函数里加了 L2 范数惩罚,作用是收缩系数但不把系数压到零;RidgeCV 是自动通过交叉验证选 alpha 的版本,省去手动调参的麻烦;Lasso 用的是 L1 惩罚,会把不重要的特征系数直接压成零,起特征选择的作用;ElasticNet 则是把 L1 和 L2 组合在一起,两个惩罚项的权重用 l1_ratio 控制。这个数据集里 region 特征对预测没什么贡献,Lasso 在训练后把这个系数清成零是正常现象,不是模型坏了。

SGDRegressor 是随机梯度下降版的线性模型,适合样本量特别大的场景。这里只有 1338 条数据,它的效果不会比普通线性回归好,但训练速度快,可以作为后续扩展到大数据的参考。

最后是支持向量机回归,也就是 SVR。这是本项目中表现最好的一类模型。SVR 的思路和分类 SVM 类似,它找一个宽度为 epsilon 的管道,让尽可能多的样本落在管道内,管道外的点才计算损失。SVR 的关键在于核函数,压缩包里出现了四个核:linear、rbf、poly、sigmoid。rbf 核在绝大多数回归任务里是首选,它能度量样本之间的相似度,对非线性关系的拟合能力最强。poly 核适合有一定周期性或多项式关系的数据,sigmoid 核在实际场景中效果通常不如前两者。参数 C 控制对管道外点的惩罚力度,C 越大越容易过拟合;epsilon 控制管道的宽度,epsilon 越小模型越较真,但会牺牲泛化能力。

以下是 SVR 模型文件与参数配置的对应关系:

文件核函数典型适用场景关键参数
hic-SVR-linear.pkllinear线性但含噪声的数据C、epsilon
hic-SVR-rbf.pklrbf非线性关系,首选C、gamma、epsilon
hic-SVR-poly.pklpoly多项式趋势数据C、degree、coef0
hic-SVR-sigmoid.pklsigmoid特殊分布,慎用C、gamma、coef0

2.3 SVR 在这个数据集上胜出的原因

为什么 SVR 在健康保险理赔预测里能拿到最好效果,需要回到特征分布上看。可视化脚本生成的 swarmplot-smoker_vs_charges.png 和 scatter_matrix 系列图说明了一个关键事实:吸烟者和非吸烟者的理赔金额分布是明显的双峰结构,非吸烟者的费用集中在低区间,吸烟者的费用分布更宽且整体更高。这种分布形态对线性回归来说是一个挑战,它会在两个峰之间找一个折中平面,导致两端都有误差。

SVR 的 rbf 核通过计算样本在高维空间中的相似度,可以把双峰结构分开处理。理论上 rbf 核能逼近任意连续函数,这是它在这个问题上超过线性模型的理论基础。实际参数配置时,C 设置在 1 到 100 之间,epsilon 设置在 0.01 到 0.1 之间,gamma 的默认值是 1 除以特征数量,这个项目里特征不多,gamma 不用特殊调。血泪经验是:SVR 对特征量纲极其敏感,bmi 和 charges 的取值范围相差几个数量级,不做标准化直接喂给 SVR,训练出来的模型基本是废的,R² 甚至可能是负数。这一点在避坑章节还会详细展开。

3. 动手复现全流程:从 CSV 到训练到出图的完整路径

3.1 数据加载与可视化脚本的解读

先把数据读进来,这是所有后续操作的前提。hic_visualizations.py 和 hic_visualizations-2.py 两个脚本做的是特征探索和可视化,运行前确认 pandas、seaborn、matplotlib 都装好了。读取数据的代码块如下:

import pandas as pd df = pd.read_csv("data/insurance.csv") print(df.head()) print(df.info()) print(df.describe())

这段代码做的事情很直接:head 查看前五条数据确认列名,info 查看字段类型和缺失值计数,describe 查看数值字段的均值、标准差、最小最大值的分布。初次拿到数据的人一定要跑这三行,缺了这个步骤直接训练,后面出了问题排查成本高得多。

数据读取之后是可视化分析。scatter_matrix 是散点图矩阵,用于观察特征之间以及特征与目标之间的两两关系;swarmplot 是蜂群图,适合看类别变量对连续变量的影响。脚本中大量使用了这种组合,因为 smoker、sex、region 是类别变量,直接进回归模型之前,必须先通过画图确认它们和 charges 的关系形态。swarmplot-smoker_vs_charges.png 显示,吸烟者的理赔金额中位数比非吸烟者高一倍以上,这是整个数据集中最显著的信号;swarmplot-sex_vs_charges.png 显示性别之间的差异并不明显;swarmplot-region_vs_charges.png 显示四个地区的分布几乎重叠,region 对预测的贡献非常有限。

可视化还有一个作用是为特征工程提供依据。看到 bmi 与 charges 之间存在明显的非线性区域,就知道线性模型会有瓶颈,也解释了为什么这个项目要同时训练 SVR。至于为什么分区画图而没有用一张热力图,原因很简单:类别变量和连续变量的组合用蜂群图比热力图直观得多,这就是该项目可视化选型的合理性。

3.2 特征编码、标准化与数据集划分

模型训练之前需要把原始字符串转成数值。sex、smoker、region 这三列机器不认,必须编码。本项目最可能的做法是用 LabelEncoder 或 pandas 的 factorize 做标签编码,这是最直接的方式,代码如下:

from sklearn.preprocessing import LabelEncoder # 将类别列转为数值:male/female -> 0/1, yes/no -> 0/1 le_sex = LabelEncoder() le_smoker = LabelEncoder() le_region = LabelEncoder() df["sex"] = le_sex.fit_transform(df["sex"]) df["smoker"] = le_smoker.fit_transform(df["smoker"]) df["region"] = le_region.fit_transform(df["region"]) X = df.drop("charges", axis=1) y = df["charges"]

编码完成后,特征和标签分离。X 包含六列特征,y 是连续的 charges 值。标签编码在这个场景够用,因为这是一个回归任务,不是树模型,特征本身是分类变量,数值的顺序不影响回归系数的解释。但要注意,如果你用的是逻辑回归或者做系数解释,建议转成 One-Hot;这里因为目标是回归预测且模型种类多,标签编码是更简洁的通用方案。

接着划分训练集和测试集,并做标准化:

from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 按 8:2 划分训练集和测试集,固定随机种子保证可复现 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test)

这里有两个容易踩坑的细节。第一是 random_state 必须固定,project 里的 test.py 和 train.py 只有用同一个随机种子划分出来的测试集才是同一套,否则两个脚本加载同一个 pkl 却得到不同的评估指标,会把人绕晕。第二是标准化必须用 fit_transform 处理训练集、用 transform 处理测试集,绝不能让测试集数据参与 scaler 的拟合,否则就是数据泄漏。

3.3 训练核心模型并保存为 pkl

一切就绪后进入训练环节。以 SVR-rbf 和 Lasso 为例,训练脚本的核心逻辑如下:

from sklearn.svm import SVR from sklearn.linear_model import Lasso, LinearRegression import joblib # 线性回归作为基线模型 lr = LinearRegression() lr.fit(X_train_scaled, y_train) joblib.dump(lr, "hic-LinearRegression.pkl") # Lasso 自带特征选择,alpha 控制正则强度 lasso = Lasso(alpha=0.01) lasso.fit(X_train_scaled, y_train) joblib.dump(lasso, "hic-Lasso.pkl") # SVR-rbf 处理非线性关系,C 和 epsilon 是核心参数 svr_rbf = SVR(kernel="rbf", C=10, epsilon=0.1) svr_rbf.fit(X_train_scaled, y_train) joblib.dump(svr_rbf, "hic-SVR-rbf.pkl")

Lasso 的 alpha 参数控制正则化强度,这个项目里取 0.01 是个合理的起点;alpha 太大,所有系数都被压到接近零,模型只剩截距;alpha 太小,正则化形同虚设。SVR 的 C 取 10,epsilon 取 0.1,意味着对落在管道外的样本惩罚力度适中,管道宽度允许约 0.1 的误差。joblib.dump 保存的是完整的模型对象,之后加载不需要重新训练。

我一般会用 joblib 而不是 pickle 来存模型,因为 joblib 对 numpy 数组的序列化效率更高,对于 sklearn 模型是事实标准。每个 pkl 文件名都带了前缀 hic 和模型名,从命名能看出原作者的实验顺序:先训练基础线性模型,再尝试正则化模型,最后转向 SVR 找提升空间。

3.4 测试脚本加载 pkl 做预测

test.py 和 hic_test.py 做的事情是加载 pkl 并对测试集做预测。核心代码如下:

import joblib from sklearn.metrics import r2_score, mean_absolute_error model = joblib.load("hic-SVR-rbf.pkl") y_pred = model.predict(X_test_scaled) print("R2:", r2_score(y_test, y_pred)) print("MAE:", mean_absolute_error(y_test, y_pred))

这里的关键点是 X_test_scaled 必须用训练时同一个 scaler 的 transform 结果。如果直接用原始数据 X_test 去 predict,量纲不一致导致预测结果完全跑偏。R² 越接近 1 越好,MAE 的含义是平均每个预测值偏差多少美元,更容易向业务解释。

4. 模型对比与指标解读:七个 pkl 之间到底差多少

4.1 R²、MAE、RMSE 的优先级

回归任务的评估指标有三个常见选项:R²、MAE、RMSE。R² 是决定系数,代表模型解释了多少比例的方差,0.7 意味着 70% 的波动可以被特征解释;MAE 是平均绝对误差,直接反映预测值平均偏差多少钱;RMSE 是均方根误差,对大误差更敏感。三者之中,R² 最直观但容易被极端值带偏,RMSE 在异常赔付记录存在时会被拉大,MAE 最稳健。

在这份资源里,model-compare.py 的输出同时包含多个指标,对比的方法是统一的:每个模型加载同一个测试集,计算同一套指标,横向比较后选择最优模型。从实际经验看,线性回归系列在这个数据集上的 R² 大约在 0.7 到 0.8 区间,SVR 的 rbf 核会高出几个百分点,MAE 会低不少。region 这个特征因为可视化时已经确认相关性弱,所以贡献很小。

4.2 model-compare.py 的评估逻辑拆解

这个脚本的核心逻辑是一段循环加载和评估的过程,常见做法是先把所有模型路径放在一个字典里,然后逐个 predict 和评分。伪代码如下:

import joblib from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error model_files = { "LinearRegression": "hic-LinearRegression.pkl", "Ridge": "hic-Ridge.pkl", "Lasso": "hic-Lasso.pkl", "ElasticNet": "hic-ElasticNet.pkl", "SGDRegressor": "hic-SGDRegressor.pkl", "SVR_rbf": "hic-SVR-rbf.pkl", "SVR_poly": "hic-SVR-poly.pkl", } for name, path in model_files.items(): model = joblib.load(path) y_pred = model.predict(X_test_scaled) print(f"{name}: R2={r2_score(y_test, y_pred):.4f}, " f"MAE={mean_absolute_error(y_test, y_pred):.2f}, " f"MSE={mean_squared_error(y_test, y_pred):.2f}")

输出的每一行都包含三个指标。看结果时先看 R²,确认模型有没有抓住主要规律;再看 MAE,确认误差的绝对值在业务上是否可接受;MSE 作为参考。SVR 的四个核函数在这里也要做同样评估:rbf 通常最优,linear 接近线性回归,poly 在小数据集上容易过拟合,sigmoid 在多数情况下垫底。如果你跑出来的结果和预设不一致,先检查特征顺序和数据是否标准化,数据预处理差异导致的指标波动,可能比模型本身的差距更大。

4.3 从可视化结论反推模型预期

可视化脚本生成的图表不是摆设,它们直接决定了模型选型的预期。swarmplot-smoker_vs_bmi.png 显示吸烟者的 bmi 分布与非吸烟者没有明显差异,说明 bmi 和 smoker 是相互独立的特征,建模时可以保留两个特征而不担心共线性;scatter_matrix-smoker.png 则显示 smoker 与 charges 之间存在清晰的条带分布,这解释了为什么添加 smoker 特征后模型提升明显。

从这个角度回头看,region 特征相关性弱,Lasso 会把它的系数压到接近零——这是数据驱动下的正常表现,不是资源有 bug。理解了这个逻辑,你拿到模型对比结果时就能秒懂:最好的模型一定是在 smoker、bmi、age 这几个强特征上拟合充分的模型。可视化在这个项目里的价值,是让选型决策不再停留在指标数字上,而是找到了指标差异背后的数据形态原因。

5. 避坑与常见问题排查:复现这个项目最容易翻车的五个地方

5.1 现象:加载 pkl 后 predict 报错,维度对不上

原因是训练时用的特征矩阵和测试时用的特征矩阵列顺序不一致。原始数据的列顺序是 age、sex、bmi、children、smoker、region,如果你在编码过程中调整过列顺序,或者把 region 做了 one-hot 展开,pkl 里的模型期望的特征维度就是旧的,predict 时输入数据维度就对不上。

解决方法是固定特征列顺序,在拆分训练测试集之前,显式声明特征列表:

feature_cols = ["age", "sex", "bmi", "children", "smoker", "region"] X = df[feature_cols]

这样无论后续怎么处理,喂给模型的永远是同一列顺序。从那以后我每次跑别人的模型文件,第一件事就是打印 model.n_features_in_ 看模型期望的维度和实际特征维度是否一致,能够省下一个小时的排查时间。

5.2 现象:R² 是负数,模型比瞎猜还差

负数 R² 的典型原因有两个。一是 SVR 没有做特征标准化,bmi 的数值范围是 10 到 50,charges 是几千到几万,量纲差异让 SVR 的核函数计算距离时完全失效;二是 epsilon 设置过大,管道把所有样本都包进去了,模型学不到任何规律,预测结果趋近于常数。解决方式是对全量特征做 StandardScaler,epsilon 从 0.1 往下调,C 从 10 往上试几个值。

5.3 现象:SVR 训练速度慢到怀疑电脑出问题

SVR 的复杂度大致是样本数的平方到立方,1338 条数据本应秒级完成,但如果出现长时间不结束,先检查是不是用了默认的 libsvm 实现且没有收敛,再检查 C 值是否设得太大,比如 C=1000 会让优化问题变得极难收敛。解决方法是先把 C 控制在 1 到 100 之间,如果数据量大再考虑用 SGDRegressor 的 epsilon-insensitive 损失替代 SVR,速度会快两个数量级。

5.4 现象:test.py 和 hic_test.py 的评估结果对不上

出现这种问题,几乎一定是两个脚本使用了不同的 train_test_split 划分,或者一个用了标准化后的数据,另一个直接用原始数据。这个项目压缩包里没有额外保存 scaler 文件,意味着每次运行测试脚本前都要重新 fit 一次 StandardScaler。解决方法是把标准化器也持久化保存,这样测试脚本和训练脚本共用同一个变换逻辑。

5.5 现象:我复现的结果没有原作者在 README 里报的好

这是所有下载源码项目最普遍的落差来源。原作者的环境依赖版本、随机种子、数据清洗方式都可能不同。如果指标差距在几个百分点以内,属于正常浮动;如果差距巨大,优先怀疑训练集和测试集的划分方式不一致,或者原作者的预处理步骤你没完全复现。这个压缩包里如果存在 hic_visualizations-2.py 这种文件名带版本号的脚本,说明原作者也经历过多次调整,复现时以 README 的描述为准,不要假设所有脚本都在同一条流水线上。

6. 拿模型去做理赔预估:单条样本推理和交叉验证的实战技巧

资源里的 pkl 模型不能只活在训练脚本里,实际业务中我们真正需要的是输入一个人的特征,立刻得到预估理赔金额。下面这段代码展示如何加载训练好的 SVR-rbf 模型,对一条新样本做预测。

import joblib import numpy as np # 加载训练时保存的模型和标准化器 model = joblib.load("hic-SVR-rbf.pkl") scaler = joblib.load("scaler.pkl") # 如果没有单独保存,需从训练脚本里导出 # 特征顺序固定:age, sex, bmi, children, smoker, region # sex: 0=女, 1=男;smoker: 0=不吸烟, 1=吸烟 # region: 0=东北, 1=西北, 2=东南, 3=西南 sample = np.array([[50, 1, 28.7, 2, 1, 2]]) sample_scaled = scaler.transform(sample) pred = model.predict(sample_scaled) print(f"预测理赔金额: ${pred[0]:.2f}")

这段代码能跑通的前提是特征顺序和编码方式与训练时完全一致。如果你手里没有 scaler.pkl,需要用训练脚本重新 fit 一次,然后单独保存。我一般会在训练脚本里加一行 joblib.dump(scaler, "scaler.pkl"),后续推理就再也不用回头翻训练代码了。

除了单条推理,还要验证模型的稳定性。SVR 在小数据集上的表现受数据划分影响比较大,一个更稳妥的做法是用交叉验证:

from sklearn.model_selection import cross_val_score from sklearn.svm import SVR from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler pipeline = make_pipeline(StandardScaler(), SVR(kernel="rbf", C=10, epsilon=0.1)) scores = cross_val_score(pipeline, X, y, cv=5, scoring="r2") print("5折交叉验证 R2:", scores) print("平均 R2:", scores.mean())

交叉验证得到的是模型在未知数据上的稳定表现,比单次划分的指标更可信。如果你用这个项目做保险定价的前置探索,建议把交叉验证结果写在结论里,比单次 test set 的 R² 更有说服力。这个资源真正做到了一件事:把一份公开医疗费用数据集、七种回归模型和完整的可视化分析整合成可复现的工程包,你要做的不是把代码跑完就结束,而是沿着可视化的结论理解每个特征对理赔支出影响的强弱,再根据业务场景选择合适的部署方式。从那以后我每次拿到这类带训练好的 pkl 的资源,第一件事都是先看特征编码、再复跑 test.py 验证基线,最后才敢把模型文件用于推理——这个习惯帮我避开了很多黑匣子式的翻车,希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进