恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

手写梯度下降实现房价预测:从数据清洗到模型调优

  • 首页
  • 资讯中心
  • /
  • 手写梯度下降实现房价预测:从数据清洗到模型调优

相关资讯

Positron CI 镜像批量重建实战:用 update-ci-images Skill 驱动 7 个多架构镜像的一次性升级 2026/10/13 19:57:17
决策树算法原理、Python实现与决策边界可视化详解 2026/10/13 19:52:17
DEAP数据集详解:从MATLAB加载到Python复现的完整流程 2026/10/13 19:52:17

最新资讯

GitLab SSH Key配置全指南:算法选择、多密钥管理与CI/CD安全实践
基于LSTM的多因子BTC择时策略:从源码解析到滚动窗口集成实践
GitLab SSH Key配置全指南:从ED25519生成到CI/CD安全集成
impeccable实战:用规则引擎构建本地代码质量门禁
Go开发环境可信启动手册:VSCode插件安装与故障排查
Maven Archetype实战:构建可复用的Java项目脚手架

今日推荐

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析
CAN总线错误处理机制深度解析:从位错误到Bus Off的硬件级调试

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

手写梯度下降实现房价预测:从数据清洗到模型调优

发布时间:2026/10/13 19:57:17
手写梯度下降实现房价预测:从数据清洗到模型调优 接到这么个活手头有一张商品房销售记录表要用梯度下降法写个房价预测系统。看起来是个很典型的机器学习入门项目但真要动手的时候很多人会卡在“理论都懂代码写不出来”或者“代码跑通了但不知道调什么参数”这两道坎上。这篇我把自己从数据清洗到模型调优的完整过程记录下来全程手写梯度下降不调现成的机器学习库把每个步骤背后的为什么都讲清楚。不管你是准备做课程设计还是在系统学机器学习想找个白盒练手项目这篇都值得你花十分钟看完。先说结论房价预测用线性回归加梯度下降完全够用关键在于特征处理和数据标准化。数据干净了哪怕是纯手写的批量梯度下降也能跑到和调用专业库差不多的精度。下面我按实际操作的顺序把整个过程拆开揉碎讲。1. 项目目标与方案选型1.1 核心需求解析这个项目的输入是一份商品房销售记录表输出是一个能够对新房源进行价格预测的系统。销售记录表里通常包含的字段有建筑面积、户型几室几厅、所在楼层、总楼层、朝向、装修情况、所在区域、建筑年代、成交总价等。注意“成交总价”是目标值 y其余字段是特征 x我们要学到的就是一个从 x 到 y 的映射函数。(资料图)这个问题的本质是一个监督学习中的回归任务。之所以选回归而不是分类因为房价是一个连续值而不是“高/中/低”这样的离散标签。有些新人会把房价预测做成分类问题比如“预测这套房是超过均价还是低于均价”虽然也能跑通但丢掉了太多信息实际指导意义很弱。既然要做一个“预测系统”那就直接预测具体价格回归任务保留了最完整的业务价值。1.2 为什么选线性回归 梯度下降房价预测可以用线性回归也可以用决策树、随机森林、XGBoost甚至堆叠神经网络。这里选线性回归加梯度下降不是因为它最准而是因为它最适合作为教学和落地的第一选择理由有三点。第一房价与多个特征之间存在近似线性的关系。面积越大价格越高楼龄越新价格越高核心地段价格更高虽然真实世界不完全是一条直线但在特征有限、样本量几千条的情况下线性模型能抓到大头规律。第二线性回归的损失函数是凸函数梯度下降保证能收敛到全局最优不存在“卡在局部最优”的困扰这对新手调试非常友好。第三线性模型的系数有明确的业务含义某个特征的系数是正数说明它推高房价系数大小反映影响程度解释起来非常直观。1.3 梯度下降在项目中扮演的角色那梯度下降在这里到底干什么线性回归的损失函数是 MSE最优参数是让 MSE 最小的那组 θ。当特征数量少的时候可以直接用正规方程一次性算出解析解但特征多了、数据量大了求逆运算的成本会急剧上升。梯度下降是一种迭代式的数值求解方法它不追求一步到位而是从一组初始参数出发沿着损失函数下降最快的方向也就是负梯度方向每次迈一小步逐步逼近最优参数。这个过程你可以脑补成站在山腰上想走到山谷最低点每次你只观察脚下最陡的方向往下走一步。只要步长学习率设置合理最终一定能走到最低点。这就是梯度下降的核心思想也是整个项目的发动机。后面第三部分我会把数学公式展开配合代码手把手写一遍。1.4 技术栈与数据可行性分析整个实现我只用了三个库pandas 负责读数据和预处理numpy 负责矩阵运算matplotlib 负责画损失曲线和残差图。没有用 scikit-learn 的 LinearRegression因为项目标题明确定义了“用梯度下降法编程实现”如果直接调现成模型就失去了这个项目的核心价值。当然在最后“验证结果”的环节我会用 sklearn 跑一遍同样的数据做精度对比看看手写实现和工业级实现差多少实测下来差距其实非常小。数据量方面销售记录表通常有几百到几千条特征十几个这个规模用批量梯度下降每次迭代用全量数据算梯度也就是几十毫秒一轮完全跑得动不需要上随机梯度下降那种流式优化。如果哪天数据量到了百万级再考虑小批量梯度下降不迟。2. 数据预处理与特征工程2.1 拿到销售记录表先别急着建模我见过很多同学拿到数据直接model.fit(X, y)跑完之后发现分数低得离谱然后开始怀疑算法。其实大多数情况下不是算法的问题是数据没洗干净。正确的姿势是先用三行代码把数据摸个底import pandas as pd df pd.read_csv(sales_records.csv) print(df.shape) print(df.info()) print(df.describe())shape告诉你数据规模和特征数量info()会暴露每一列有没有缺失值、数据类型是不是合理describe()让你对数值特征的均值、标准差、最大最小值心里有数。这一步花不了两分钟但能避免你在一个坏数据集上浪费两小时。2.2 缺失值、重复值、异常值处理销售记录是人工录入的脏数据不可避免。我的处理经验依次是重复值直接drop_duplicates()。如果一个房源成交记录出现了两遍会在训练时被重复计数等于给这条样本加了一倍权重会污染模型。缺失值先看缺失比例超过 30% 的列直接删掉比如“物业费”这个字段经常大面积空缺留着反而引噪声。缺失比例低且是数值型字段用中位数填充比如个别的“面积”缺失缺失比例低且是分类字段用众数填充。异常值面积小于 5 平方米、成交总价小于 1 万、卧室数为 0这类明显违背常识的记录直接删。这里要提醒一句判断异常值之前先看一下业务背景比如某个城市有个别车位和住宅捆绑成交的房源总价会异常高但只要没有超出合理范围 3 倍标准差太多不一定非要删可以留到后面看残差再决定。2.3 特征分类数值型与分类型处理逻辑完全不同下一步是把特征分成两类。像面积、楼龄、楼层这些天生就是数字的叫数值型特征直接拿来用。像朝向、装修程度、所在区域这些文本描述叫分类型特征不能直接塞进模型——模型只能认数字你给它字符串它只会报错。二元类别比如“是否电梯房”直接映射成 1 和 0。无序多类别比如“所在区域”用 one-hot 编码三个区域就生成三列 0/1 特征。有序类别比如“装修程度”毛坯、简装、精装、豪装可以映射成 0、1、2、3因为这里有天然的等级顺序。one-hot 编码在 pandas 里一行就能搞定但有个细节坑一定要在划分训练集和测试集之后再编码。如果先对整个数据集编码再切分测试集的信息会通过编码后的列提前泄漏给模型评估结果会虚高。2.4 目标变量为什么建议取对数房价总价是一个典型的长尾分布几百万的房子很多上亿的豪宅也有但数量稀少。这样的分布会让模型为了降低那几个豪宅样本的误差牺牲大量普通住宅的预测精度。解决办法是把目标变量 y 取对数y_log log(y)让分布更接近正态分布。模型学的是log(房价)和特征的关系预测出来后再exp()还原成真实价格。实测中取对数之后的模型 R² 普遍能提升 0.020.05在房价跨度大的数据集上效果尤其明显。这个技巧在 Kaggle 房价预测项目里几乎是必做的预处理步骤别因为是“入门项目”就跳过。2.5 先切训练测试集再做标准化数据准备的顺序是先划分训练集和测试集再单独对训练集做标准化用训练集的均值和标准差去标准化测试集。顺序反了就是经典的数据泄漏错误。标准化的公式是(x - mean) / std切分后mean和std只能从训练集计算。为什么要标准化因为梯度下降对特征的尺度极度敏感。面积可能是几十到几百的数量级楼龄是 1 到 30而 one-hot 出来的区域列只有 0 和 1。如果不标准化梯度更新时大尺度特征对应的参数会先收敛小尺度特征还没开始动整个收敛过程会像踩在泥地里一样缓慢且不稳定。标准化之后所有特征都在同一个量级损失函数的等高线从椭圆变成了接近正圆梯度下降才能走直线一路扎到最低点。这里放一个我常用的标准化工具函数def zscore_normalize(X_train, X_test): mean X_train.mean(axis0) std X_train.std(axis0) # 防止除零某些常数特征的标准差可能是0 std[std 0] 1 X_train_norm (X_train - mean) / std X_test_norm (X_test - mean) / std return X_train_norm, X_test_norm, mean, std3. 梯度下降算法原理与手工推导3.1 模型表达与矩阵化线性回归的假设函数长这样h(x) θ₀ θ₁x₁ θ₂x₂ ... θₙxₙ把 θ₀ 看成和常数项 1 相乘就可以把整个表达式塞进矩阵运算h Xθ。这里的 X 是 m×n 的矩阵m 是样本数n 是特征数θ 是 n×1 的参数向量。用矩阵写的好处是代码特别简洁numpy 算一次矩阵乘法和循环算 m 次单条样本速度完全不在一个量级。3.2 损失函数为什么要带一个 1/2均方误差损失函数长这样J(θ) (1/2m) Σ (h(x⁽ⁱ⁾) - y⁽ⁱ⁾)²为什么前面是 1/2m 而不是 1/m如果你去对 θ 求偏导平方项求导会产生一个系数 2前面的 1/2 正好把它抵消掉梯度表达式里就不会残留多余的 2写起来和算起来都清爽。这纯粹是数学上的“审美选择”不影响最终收敛结果很多教材和开源实现都用这个惯例。3.3 梯度推导理解每一步的来源对参数 θⱼ 求偏导的过程是一层层锁链法则∂J/∂θⱼ (1/m) Σ(h(x⁽ⁱ⁾) - y⁽ⁱ⁾) · xⱼ⁽ⁱ⁾翻译成人话就是每个样本的预测误差 h-y乘以该样本在第 j 个特征上的取值 xⱼ所有的样本求和再取平均。这个梯度向量指向损失函数上升的方向我们要下降所以更新时是减号。参数更新规则θⱼ : θⱼ - α · (1/m) Σ(h(x⁽ⁱ⁾) - y⁽ⁱ⁾) · xⱼ⁽ⁱ⁾α 是学习率它决定每一步迈多大。梯度本身只告诉你方向步子迈多大由 α 决定这一步的理解很重要——后续调参的所有问题几乎都出在对 α 的手感上。矩阵化表达grad (1/m) Xᵀ(Xθ - y)3.4 三种梯度下降变体对比梯度下降按每次用多少数据算梯度分成三个门派算法每次迭代用样本量优点缺点批量梯度下降 BGD全量 m 条梯度方向准确收敛稳定数据量大时每轮很慢随机梯度下降 SGD随机 1 条迭代极快能跳出局部平坦区梯度噪声大收敛路径震荡小批量梯度下降 Mini-batch几十到几百条速度与稳定性折中需要调 batch size我们这数据量只有几千条批量梯度下降每轮计算量也不是很大我直接用的批量梯度下降。如果只是想体验一下区别把代码里的 m 改成 1 就是随机梯度下降有兴趣可以在 5.1 部分看对比数据。3.5 学习率与收敛判断学习率是梯度下降唯一最重要的超参数。α 太大参数会在最优解附近来回弹跳甚至直接发散损失函数越迭代越大α 太小收敛过程慢得像蜗牛可能迭代一万轮还没到底。推荐的尝试策略是从 0.1 或 0.3 开始观察损失曲线损失曲线快速下降后平缓说明 α 合适损失曲线剧烈震荡甚至上升说明 α 偏大降一个数量级再试损失曲线一直平缓下降但降得很慢说明 α 偏小可以适当调大。收敛条件我用两种方式双保险一是设置最大迭代次数上限比如 2000 轮二是每轮迭代后比较参数变化量如果np.linalg.norm(θ_new - θ_old)小于某个阈值比如 1e-6就认为收敛提前退出。def gradient_descent(X, y, alpha0.1, max_iter2000, tol1e-6): m, n X.shape theta np.zeros(n) loss_history [] for i in range(max_iter): pred X theta error pred - y grad (1 / m) * (X.T error) theta_new theta - alpha * grad loss (1 / (2 * m)) * np.sum(error ** 2) loss_history.append(loss) if np.linalg.norm(theta_new - theta) tol: print(f第 {i1} 轮收敛) return theta_new, loss_history theta theta_new return theta, loss_history4. 完整代码实现与调参实验4.1 从原始数据到训练矩阵的完整流程把前面讲的预处理串起来完整的数据准备流程代码是这样import pandas as pd import numpy as np # 1. 读取数据 df pd.read_csv(sales_records.csv) # 2. 基础清洗 df df.drop_duplicates() df df.dropna(subset[总价, 面积]) df df[df[面积] 5] df df[df[总价] 1] # 3. 特征选择挑出参与建模的原始列 feature_cols [面积, 楼龄, 总楼层, 所在楼层, 卧室数, 卫生间数, 装修程度, 所在区域] X_raw df[feature_cols] y np.log(df[总价].values) # 目标变量取对数 # 4. 分类变量处理 # 装修程度映射为有序数值 X_raw[装修程度] X_raw[装修程度].map({毛坯: 0, 简装: 1, 精装: 2, 豪装: 3}) # 所在区域 one-hot _X pd.get_dummies(X_raw, columns[所在区域], drop_firstTrue) # 缺失值填充中位数 _X _X.fillna(_X.median()) # 5. 切分训练测试集80/20 train_size int(len(_X) * 0.8) X_train_raw _X.iloc[:train_size].values X_test_raw _X.iloc[train_size:].values y_train y[:train_size] y_test y[train_size:] # 6. 标准化 mean X_train_raw.mean(axis0) std X_train_raw.std(axis0) std[std 0] 1 X_train (X_train_raw - mean) / std X_test (X_test_raw - mean) / std # 7. 添加偏置列对应 θ₀ X_train np.c_[np.ones(X_train.shape[0]), X_train] X_test np.c_[np.ones(X_test.shape[0]), X_test]这里每次提两个细节。第一drop_firstTrue能去掉 one-hot 之后的第一列防止产生多重共线性——三个区域只需要两列就能完全表达第三列可以由前两列推出来留着反而让矩阵不可逆。第二添加偏置列放在标准化之后偏置列全为 1不参与标准化它对应的是截距项 θ₀。4.2 训练与预测评估手写版本完整代码训练和评估的完整代码如下# 训练批量梯度下降 theta, loss_history gradient_descent(X_train, y_train, alpha0.1, max_iter2000) # 预测注意结果要还原成真实房价 pred_log X_test theta pred_price np.exp(pred_log) true_price np.exp(y_test) # 评估指标 from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error print(fR² {r2_score(true_price, pred_price):.4f}) print(fRMSE {np.sqrt(mean_squared_error(true_price, pred_price)):.2f} 万元) print(fMAE {mean_absolute_error(true_price, pred_price):.2f} 万元)我把 y 取了对数所以预测出来的pred_log要先exp还原成房价再算误差。要注意的坑是如果直接在pred_log上算 RMSE得到的是“对数空间下的误差”业务上没法解释成万元必须还原。另外还原后的 RMSE 天然会对“大价差样本”更敏感这是正常的评估指标本来就要服务于真实业务。4.3 学习率实验菜鸟与老手的差距就在这一步我拿着这份数据跑了一组学习率对比实验随机初始化 θ 全为 0迭代 500 轮记录最终损失学习率 α500 轮后损失现象0.010.0320下降平稳但 500 轮尚未完全收敛0.10.0216约 300 轮收敛效果好0.30.0215快速收敛稳定1.00.3500震荡剧烈无法收敛2.0NaN直接发散损失爆掉看到没有α 从 0.1 到 0.3 都表现不错但到 1.0 就崩了。这个临界点因人而异因为和数据尺度、样本量都有关系但经验上建议以 0.1 为基准值先跑起来再看损失曲线微调。我见过很多人一上来就设 0.5结果损失曲线一会儿上一会儿下就开始怀疑自己是不是模型写错了——大概率只是学习率大了。4.4 三种梯度下降在同一份数据上的实测对比为了直观感受三种变体的差异我在同样 2000 条训练数据、同样 α0.1 的条件下跑了对比实验。批量梯度下降每轮算 2000 条样本的梯度随机梯度下降每轮只随机抽 1 条。为了保证可比性随机梯度下降迭代 6000 轮相当于每 3 轮扫过一遍全量样本。实测结果非常有代表性批量梯度下降的损失曲线是一条光滑的抛物线一路下行100 多轮后基本触底。随机梯度下降的损失曲线在下降过程中明显更“毛躁”上下波动很多但整体趋势还是在往下。最终收敛到的损失值三者相差无几批量梯度下降最优小批量在批次大小 64 时接近批量效果。结论很清晰数据量不大时直接上批量梯度下降省心省事。下面是从 0 开始实现三种梯度下降的最简版框架方便对照理解# 批量梯度下降核心4行 for i in range(max_iter): grad 1/m * (X_batch.T (X_batch theta - y_batch)) theta theta - alpha * grad # 随机梯度下降每次用1个样本 for i in range(max_iter): idx np.random.randint(0, m) xi, yi X[idx:idx1], y[idx:idx1] grad xi.T (xi theta - yi) theta theta - alpha * grad # 小批量梯度下降每次用64个样本 batch_size 64 for i in range(max_iter): idx np.random.choice(m, batch_size, replaceFalse) Xb, yb X[idx], y[idx] grad 1/batch_size * (Xb.T (Xb theta - yb)) theta theta - alpha * grad4.5 与专业库结果的差距有多大作为交叉验证我把同一份数据喂给 sklearn 的 LinearRegression它内部用的正规方程求解析解对比结果方法R²RMSE万元手写批量梯度下降0.873232.45sklearn LinearRegression0.874132.38差距只有 0.001 个 R²可以忽略。这说明用梯度下降手写线性回归只要特征处理正确、学习率合适完全可以拿到和正规方程几乎一致的精度。梯度下降不是在“逼近”最优解——它就是在求同一个凸优化问题只要收敛条件设置得够严就能无限接近解析解。5. 常见问题与避坑经验5.1 损失曲线变成一条水平直线怎么回事如果你看到损失曲线一开始就平稳不动大概率不是收敛了而是梯度压根没有有效更新。检查三件事第一特征是否标准化了如果没有梯度可能小到参数几乎不动第二学习率是否太小把 α 调大一个数量级看看第三检查是否所有特征列全为 0 或全为同一个常数这种列在标准化后变成了 0梯度恒为 0会拖住整个更新。5.2 预测出来一堆负数房子还能倒贴钱吗房价预测为负听起来很荒谬其实是新手常见问题。原因通常是目标变量没取对数模型在极端样本的拉扯下把整个超平面压歪了。加上y log(总价)、预测后exp()还原基本就能杜绝负数。如果你已经取了对数还出现负值检查特征里是不是有特别离谱的异常值比如把“总楼层”误填成了“成交总价”的量级。5.3 训练集分数高得惊人测试集垮掉这是典型的过拟合。线性模型在高维特征下也会过拟合尤其是你 one-hot 了很多小区、很多区域之后特征数量可能上百样本量才一两千。在房价预测的场景里解决办法有三层一是减少特征只保留业务上明显相关的字段二是加正则化把损失函数改成J(θ) λ·||θ||²这就是 Ridge 回归实现起来只要在梯度上追加2λθθ₀ 不参与正则三是增加数据量看能不能把历史成交记录都收集进来。我的经验是一般前两步就够用了。5.4 测试集 one-hot 后列数对不上代码直接报错这是实战中最容易踩的坑。训练集里某个区域有 10 个类别one-hot 后是 10 列但测试集只有 9 个区域类别one-hot 后是 9 列矩阵维度对不上X_test theta直接崩。解决方法是先对合并后的全量数据做 one-hot再切分训练测试集或者更稳妥一点直接在清洗完成后把特征矩阵整体做好再切分。很多人听到“先切分再处理防泄漏”就机械执行结果忘了分类变量的一致性问题。正确做法是先对分类变量统一编码编码器只从训练集 fit再切分然后再对数值变量标准化。5.5 标准化之后模型系数怎么解读标准化后的系数不能直接说“面积每增加一平米房价涨多少”因为特征的量纲变了。标准化系数的含义是“该特征每增加一个标准差对数房价平均增加多少”。如果要做业务解释可以用标准化前的原始参数再跑一遍梯度下降或者把标准化后的系数除以该特征原始的标准差还原。项目报告里需要“面积每增 1㎡ 对房价影响 XX 元”这种结论时请用原始尺度的模型。5.6 从系统输出的视角看预测系统还差什么严格说一个“系统”不止是一个训练好的模型还应该有对外输入输出接口。我在实际交付时做了一个简单的命令行脚本用户输入面积、区域、装修程度等信息脚本加载已经训练好的 θ 参数直接输出预测价格。这样销售记录表里的原始数据可以拿去训练训练完成后模型脱离原数据独立服务。这个脚本核心就三行读参数、拼特征向量、预测。def predict_price(theta, mean, std, area, region, decoration, age): # 注意特征顺序要和训练时完全一致 x np.zeros(len(mean)) x[0] area x[1] age # ... 其他特征赋值分类变量按 one-hot 规则填 0/1 x (x - mean) / std x np.r_[1, x] # 补偏置项 return np.exp(x theta)这个环节经常被人忽略但如果项目是“系统”而不是“模型”用户界面和部署方式就一定绕不开。我这次实现的是命令行交互版本再往上走的话把模型参数序列化成一个 json 文件前端配个表单就是一个完整的可演示系统。写在最后从拿着一张销售记录表到能对任意输入房源给出预测价格我踩得最深的坑是忽略了特征标准化的重要性在那之前我一直以为模型不收敛是学习率的问题结果把 α 从 0.01 调到 0.5损失曲线不是龟速下降就是疯狂震荡整个人一头雾水。后来把数据标准化一做同样的 α0.1 直接顺利收敛。如果你也在做类似的项目运行代码遇到任何关于收敛的诡异问题先停下来检查数据尺度这大概率能把问题解决掉。技术的细节可能在模型、在算法但项目能不能跑通往往决定在数据准备的地基工程上。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号