拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

线性回归身高预测实战:数据预处理与模型评估全流程

线性回归身高预测实战:数据预处理与模型评估全流程

简介:这份资源面向机器学习入门者与需要掌握回归建模的开发者,围绕身高预测这一具体场景,讲解如何用线性回归建立身高与年龄、体重、性别等因素之间的依赖关系,帮助读者理解连续值预测的完整流程。压缩包共2个文件,包含1个xlsx数据表与1个py脚本,整体约80KB,前者用于存放身高、年龄、性别、体重等样本数据,后者基于scikit-learn实现数据预处理、模型训练、评估与预测。内容覆盖数据清洗与标准化、分类变量编码、训练测试集划分,以及MSE、RMSE、R²等评估指标的使用,并延伸讨论多项式回归、岭回归、套索回归与集成方法等改进思路。已有115人学习下载,适合希望用最小体量案例快速跑通线性回归全流程、并对照代码理解建模细节的读者参考。

1. 线性回归身高预测:三行代码能跑通,但数据没洗对全白搭

很多人第一次接触机器学习,都是从线性回归开始的。原因很简单:它数学直觉清晰、代码量少、结果可解释。但真正上手做身高预测这类任务时,翻车往往不在模型本身,而在数据。你拿到一份「身高预测参照表-1.xlsx」,里面可能有年龄、性别、体重、身高几列,看起来干净,实际上缺失值、量纲差异、性别编码方式都会直接影响模型能不能收敛、系数能不能解释。

这个资源包的核心价值在于:它把「线性回归算法」从公式拉到了一个具体可复现的场景里。LinearRegression_1.py是主脚本,身高预测参照表-1.xlsx是训练数据,整个流程覆盖了数据预处理、模型训练、评估和预测四个环节。适合两类人:一是正在做机器学习线性回归实验的学生,需要一份能跑通的参考实现;二是刚转行的工程师,想用一个最小闭环理解 scikit-learn 的回归流程。下面我会按实际拆包的顺序,把每一步的参数、坑和验证方法讲清楚。

2. 拆开资源包:数据表结构和脚本骨架先看懂

2.1 身高预测参照表里到底有什么

拿到身高预测参照表-1.xlsx之后,第一件事不是急着read_excel,而是先用 pandas 把列名、类型、缺失情况摸一遍。常见的身高预测数据集一般包含以下几类字段:

字段类型典型列名数据类型处理方式
连续特征年龄、体重float/int标准化或直接使用
分类特征性别object/int独热编码或标签编码
目标变量身高float作为 y,不参与特征缩放
无关列编号、姓名object直接丢弃

先跑这段代码做体检:

import pandas as pd df = pd.read_excel("身高预测参照表-1.xlsx") print(df.shape) # 行数、列数 print(df.dtypes) # 每列数据类型 print(df.isnull().sum()) # 每列缺失值数量 print(df.describe()) # 连续列的均值、标准差、分位数 print(df.head(10)) # 前10行肉眼检查

逻辑说明:shape告诉你样本量够不够,一般线性回归至少需要几十条以上才有统计意义;dtypes决定后面要不要做类型转换,比如性别如果是字符串,必须编码;isnull().sum()是血泪经验——很多人直接fit然后报错,就是因为某列有 NaN;describe()帮你看量纲,如果体重是 60 而年龄是 20,差距不大,但如果有一列是「克」另一列是「米」,不标准化会让系数失真。

参数说明:read_excel默认读第一个 sheet,如果参照表有多个 sheet,需要加sheet_name="Sheet1"指定。如果列名有空格或中文,后续引用时要用df["列名"]而不是df.列名。

2.2 LinearRegression_1.py 的典型骨架

这个脚本的常见结构是:导入库 → 读数据 → 特征工程 → 划分训练测试集 → 训练模型 → 评估 → 预测。我一般会把它拆成函数,方便单独调试每一步。核心依赖是scikit-learn和pandas,安装命令:

pip install scikit-learn pandas openpyxl

openpyxl是读.xlsx的引擎,不装会报ImportError。脚本里最关键的几行通常是:

from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error, r2_score X = df[["年龄", "体重", "性别编码"]] # 特征矩阵 y = df["身高"] # 目标向量 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) model = LinearRegression() model.fit(X_train, y_train) y_pred = model.predict(X_test) print("MSE:", mean_squared_error(y_test, y_pred)) print("R2:", r2_score(y_test, y_pred)) print("系数:", model.coef_) print("截距:", model.intercept_)

逻辑说明:train_test_split的test_size=0.2表示 20% 做测试,random_state=42保证每次划分一致,方便复现。fit做的就是最小化残差平方和,解析解直接算出系数。coef_告诉你每个特征对身高的影响方向和大小,intercept_是基准值。

参数说明:LinearRegression默认fit_intercept=True,即自动计算截距;如果数据已经中心化,可以设为False。n_jobs参数在普通线性回归里没用,别被误导。评估指标里 MSE 单位是身高的平方,RMSE 开根号后才是「平均差多少厘米」,更直观。

3. 从原始表到可训练矩阵:预处理四步不能省

3.1 缺失值和异常值处理

身高数据里最常见的脏数据是:身高填了 0 或 999、体重缺失、性别写成「男/女/未知」。处理顺序应该是先处理异常值,再处理缺失值,最后编码。

# 异常值:身高合理范围设为 100-250 cm df = df[(df["身高"] >= 100) & (df["身高"] <= 250)] # 缺失值:数值列用中位数填充,分类列用众数填充 df["体重"] = df["体重"].fillna(df["体重"].median()) df["性别"] = df["性别"].fillna(df["性别"].mode()[0])

逻辑说明:用中位数而不是均值,是因为身高体重类数据可能有偏态,均值容易被极端值拉偏。众数填充性别适合缺失比例低的情况,如果缺失超过 30%,考虑直接删列或做模型插补。

参数说明:median()和mode()[0]都是 pandas 内置方法,mode()返回的是 Series,取第一个值即可。

3.2 性别编码:标签编码还是独热编码

性别只有两个取值时,标签编码(男=0,女=1)就够用,线性回归会把系数解释为「性别从 0 变到 1 时身高的平均变化」。但如果性别有三个以上取值,必须用独热编码,否则模型会误以为类别之间有大小顺序。

# 二分类:标签编码 df["性别编码"] = df["性别"].map({"男": 0, "女": 1}) # 多分类:独热编码 # df = pd.get_dummies(df, columns=["性别"], drop_first=True)

逻辑说明:map是显式映射,比LabelEncoder更可控,因为你知道每个值对应什么。get_dummies的drop_first=True会丢掉一个类别作为基准,避免多重共线性。

参数说明:如果参照表里性别已经是 0/1,跳过这步。注意map遇到未定义的键会返回 NaN,所以映射前先unique()看一下实际取值。

3.3 特征缩放:标准化到底要不要做

线性回归的系数解对特征量纲敏感。如果年龄范围 10-80,体重范围 30-120,两者差距不大,不标准化也能跑。但如果加入「年收入」这种量级到万的列,不标准化会导致系数数值极小,解释困难。

from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test)

逻辑说明:fit_transform只在训练集上做,测试集用transform,这是防止数据泄露的铁律。标准化后系数表示「特征每变化一个标准差,身高变化多少」。

参数说明:StandardScaler做的是 (x - 均值) / 标准差。如果数据有极端离群值,改用RobustScaler更稳。

3.4 训练集测试集划分的随机种子

random_state不设的话,每次运行划分不同,评估指标会波动,你以为是模型改了,其实是数据换了。固定一个种子,比如 42 或 0,是实验可复现的基本要求。

X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) print("训练集:", X_train.shape, "测试集:", X_test.shape)

逻辑说明:输出 shape 确认划分比例对不对。如果样本量很小(比如不到 100 条),test_size=0.2可能只剩十几条测试数据,评估结果不稳定,可以考虑交叉验证。

4. 训练、评估与预测:指标怎么看才不被忽悠

4.1 拟合模型后先看系数和截距

模型训练完,不要只看 R²,先把系数打出来。系数正负代表特征与身高的正负相关,绝对值大小代表影响程度(标准化后可比)。

model = LinearRegression() model.fit(X_train, y_train) for name, coef in zip(X.columns, model.coef_): print(f"{name}: {coef:.4f}") print(f"截距: {model.intercept_:.4f}")

逻辑说明:如果「年龄」系数是正的,说明年龄越大身高越高(在样本范围内);如果「体重」系数是正的,说明体重越大身高越高。如果出现与常识相反的符号,先检查数据里有没有异常值或编码错误。

参数说明:zip把列名和系数配对,避免看错顺序。:.4f保留四位小数,方便对比。

4.2 MSE、RMSE、R² 三个指标各看什么

from sklearn.metrics import mean_squared_error, r2_score import numpy as np y_pred = model.predict(X_test) mse = mean_squared_error(y_test, y_pred) rmse = np.sqrt(mse) r2 = r2_score(y_test, y_pred) print(f"MSE: {mse:.2f}") print(f"RMSE: {rmse:.2f} cm") print(f"R2: {r2:.4f}")

逻辑说明:MSE 是均方误差,单位是 cm²,不直观;RMSE 开根号后单位是 cm,可以直接说「预测平均偏差多少厘米」;R² 表示模型解释了目标变量多少比例的方差,越接近 1 越好,但为负说明模型还不如直接用均值预测。

参数说明:np.sqrt是 numpy 的开根号函数。R² 在测试集上比训练集低很多,说明过拟合,但线性回归本身过拟合风险低,更可能是数据分布不一致。

4.3 用新样本做预测的输入格式

预测时输入的特征顺序必须和训练时一致,列名也要一致。常见错误是手动构造 DataFrame 时列顺序变了,导致预测结果离谱。

new_data = pd.DataFrame({ "年龄": [25], "体重": [70], "性别编码": [0] }) pred = model.predict(new_data) print(f"预测身高: {pred[0]:.1f} cm")

逻辑说明:new_data的列顺序和X.columns一致,scikit-learn 内部按位置对应。如果训练时用了标准化,预测前也要用同一个scaler.transform(new_data)。

参数说明:单条预测传入的是二维结构,[25]而不是25。pred[0]取出标量结果。

5. 避坑与排查:这五类报错我几乎每次都遇到

5.1 报错 Input contains NaN

现象:fit时抛出ValueError: Input contains NaN, infinity or a value too large。

原因:原始表里有空单元格,或者map编码时遇到未定义类别产生了 NaN。

解决:在fit之前加print(df.isnull().sum())确认哪列有缺失,用fillna或dropna处理。编码列用df["性别"].unique()检查实际取值。

5.2 R² 为负数

现象:测试集 R² 小于 0,模型看起来「还不如瞎猜」。

原因:测试集样本太少、特征与身高无线性关系、或者训练集和测试集分布差异大。

解决:先看样本量,如果测试集不到 20 条,改用交叉验证cross_val_score。再看散点图,确认特征和身高是否有大致线性趋势。如果确实非线性,考虑多项式回归。

5.3 系数符号与常识相反

现象:年龄的系数是负的,意味着年龄越大身高越矮。

原因:数据里混入了儿童和成年人,儿童年龄小但身高在增长期,成年人年龄大但身高已固定,整体不是单调关系。

解决:分年龄段建模,或者加入年龄的平方项。也可以先画年龄 vs 身高的散点图,肉眼确认关系形态。

5.4 预测值出现负数或离谱大数

现象:新样本预测出身高 -30 cm 或 500 cm。

原因:新样本特征超出了训练数据的取值范围,线性模型外推能力很差。

解决:预测前检查输入是否在训练集的min和max之间。如果必须外推,考虑改用树模型,树模型对外推更保守。

5.5 中文列名导致的 KeyError

现象:df["身高"]报KeyError。

原因:Excel 列名里有空格,比如"身高 "或"身高(cm)"。

解决:读入后先df.columns = df.columns.str.strip()去掉首尾空格,再用print(df.columns.tolist())确认实际列名,复制粘贴使用。

6. 进阶技巧:用交叉验证和残差图判断模型该不该换

线性回归跑通不难,难的是判断它在这个数据上到底够不够用。我一般会做两件事:交叉验证看稳定性,残差图看模式。

交叉验证把数据分成 K 份,轮流做测试集,能给出 R² 的均值和标准差。如果均值不错但标准差很大,说明模型对数据划分敏感,样本量可能不够。

from sklearn.model_selection import cross_val_score scores = cross_val_score(model, X, y, cv=5, scoring="r2") print(f"R2 均值: {scores.mean():.4f}") print(f"R2 标准差: {scores.std():.4f}")

逻辑说明:cv=5表示五折交叉验证,scoring="r2"指定评估指标。标准差超过 0.1 就要警惕,说明模型不稳定。

参数说明:cross_val_score内部会自动划分,不需要提前train_test_split。如果数据有顺序(比如按时间排列),要用TimeSeriesSplit。

残差图是另一个黑匣子探测器。残差 = 真实值 - 预测值。如果残差随机分布在 0 附近,说明线性假设合理;如果残差呈现 U 形或喇叭形,说明关系非线性或方差不齐。

import matplotlib.pyplot as plt residuals = y_test - y_pred plt.scatter(y_pred, residuals) plt.axhline(y=0, color="r", linestyle="--") plt.xlabel("预测身高") plt.ylabel("残差") plt.show()

逻辑说明:横轴是预测值,纵轴是残差。理想情况是点均匀分布在红线上下,没有明显形状。如果点呈现曲线趋势,考虑加多项式项;如果点越往右越散,考虑对目标变量做对数变换。

参数说明:axhline画一条 y=0 的参考线。plt.show()在脚本里会弹窗,在 Jupyter 里直接显示。

如果残差图显示明显非线性,可以试一下多项式回归:

from sklearn.preprocessing import PolynomialFeatures from sklearn.pipeline import make_pipeline poly_model = make_pipeline( PolynomialFeatures(degree=2, include_bias=False), LinearRegression() ) poly_model.fit(X_train, y_train) print("多项式 R2:", poly_model.score(X_test, y_test))

逻辑说明:PolynomialFeatures(degree=2)会自动生成特征的平方项和交叉项,make_pipeline把预处理和模型串起来。如果 R² 明显提升且残差图改善,说明线性假设确实不成立。

参数说明:degree不要一上来就设太高,2 或 3 足够,再高容易过拟合。include_bias=False是因为LinearRegression自己会算截距。

从那以后我每次跑线性回归,都强制走一遍「缺失值检查 → 散点图 → 交叉验证 → 残差图」这四步,不再只看一个 R² 就下结论。这套流程帮我省了很多后悔药,也希望帮到你。

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

返回列表