ARTICLE DETAIL

资讯详情

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

MATLAB决策树回归预测房价:从数据清洗到模型调参实战

MATLAB决策树回归预测房价:从数据清洗到模型调参实战 做数据分析这几年被问得最多的一个问题是到底用什么模型做回归预测最简单又讲得清楚我的答案一直是决策树回归。原因很简单它不需要归一化特征不用假设线性关系训练完还能把树图画出来给人解释。这次我用MATLAB把“预测房价”这件事完整走了一遍从数据清洗、模型训练、交叉验证到参数调优全部跑通顺便把踩过的坑也记录下来给打算用MATLAB入门回归建模的朋友做个参考。你不需要先把机器学习理论啃完跟着这篇文章把代码跑起来再回头理解原理反而是更快的一条路。1. 项目整体设计与思路拆解1.1 为什么选决策树回归而不是线性回归房价预测是个典型的回归问题。初学者最容易想到的是线性回归毕竟课本上讲得最多。但线性回归有个硬前提特征和目标之间得是线性关系。现实里房价和面积的关系并不是一条直线刚需房、改善房、豪宅的价格规律完全不一样面积从90平涨到100平和从180平涨到200平单价的变化逻辑都不同。如果用线性回归硬拟合残差会很大还得手动构造交互项、多项式特征工作量一下就上去了。决策树回归完全没有这个顾虑。它本质上是一套“按条件分裂”的规则系统面积小于120平走左边大于等于120平走右边右边再看卧室数量大于等于3间再往下分。每一层都在把样本切得越来越纯最终落到叶子节点上的样本取均值作为预测值。所以它天然能表达非线性关系和特征之间的交互作用。我整理过一个简单的对比供选型时参考维度线性回归决策树回归特征与目标关系假设线性无明确假设特征缩放标准化需要不需要可解释性靠系数解释非专业人难懂树结构直观能和业务方直接讲对异常值敏感度较高中等过拟合风险较低较高需要控制树的大小模型训练速度快快单看这张表决策树回归在解释性上赢得很明显。做房价预测这个场景最终往往要给非技术背景的人看结果你拿出一棵树的图指着一层层“面积大于140平且房龄小于10年的房子均价450万”对方马上能理解。线性回归的系数解释起来就没这么顺。1.2 为什么用MATLAB做这件事最近几年Python在机器学习领域声量很大但MATLAB在工程和数据分析场景里依然有不可替代的位置。这次选MATLAB有几个现实理由。一是工具箱成熟。Statistics and Machine Learning Toolbox里直接封装了fitrtree这是一个已经调好的CART回归树实现不用自己手动写分裂逻辑。举个最直接的例子交叉验证只需要在调用时加一个CrossVal,on参数再配合kfoldLoss函数就能拿到误差结果。Python里你得先from sklearn.model_selection import cross_val_score再确认版本兼容性虽然也不算复杂但MATLAB的工作流更一体化。二是可视化和调试方便。MATLAB的绘图机制是内置的scatter、bar、plot这些函数直接就能出图。决策树训练完一行view(model,Mode,graph)就能把整棵树画出来。在代码里断点调试时工作区里能直接看到每个变量的维度、类型、缺失值情况这对排查数据问题非常友好。三是环境稳定。MATLAB的安装路径清晰工具箱管理统一不太会出现Python那种“换一台机器依赖就崩”的问题。如果你手里已经有现成的MATLAB许可或者数据文件本来就是.mat格式完全没必要为了一个回归模型再去搭一套Python环境。当然这不是说Python不好。只是工具选型得分场景。我的原则是手头有什么就用什么能把问题解决透的就是好工具。1.3 整体流程从数据到预测管线这个项目的完整流程可以用一条线串起来读取数据 - 清洗与特征筛选 - 划分训练集和测试集 - 训练决策树 - 交叉验证调参 - 评估指标 - 特征重要性分析。每一步之间都有明确的输入输出关系。数据清洗这一步很多人会跳过去直接拿原始数据喂给模型后面对着一堆异常预测值发愁。实际上房价数据常见的坑不少缺失值、录入错误比如把2000年记录成200年、单位不统一有的面积用平方米有的用平方英尺。这些不处理模型训练出来就是错的。训练和调参是核心环节。先跑一个默认参数的模型当基线然后通过交叉验证去调MinLeafSize这类参数观察误差变化。最终用测试集做一个独立评估确保模型没有过拟合。最后再通过predictorImportance看哪些特征对预测贡献最大这一步对验证业务直觉很有用。2. 决策树回归原理与数据准备2.1 CART回归树的分裂逻辑决策树回归背后是CART算法分类回归树。回归树和分类树最大的区别在于分裂准则分类树用Gini不纯度或者信息熵回归树用的是均方误差最小化。每个节点要做的事情可以概括为两步第一步遍历所有特征的所有可能切分点第二步对每个切分点计算“切完之后两组样本的均方误差之和”选最小的那个切分点。举个例子某个节点上有50套房。尝试按面积切分当切分点定为100平时左边15套、右边35套。分别计算左边15套价格的均方误差、右边35套价格的均方误差相加得到一个值。再把切分点改成110平、120平、130平重复同样的计算最后选择让误差和最小的那个面积值作为当前节点的分裂条件。这个过程会递归进行直到满足停止条件。叶子节点的预测值就是落在该节点所有样本目标变量的均值。比如某个叶子节点里落了12套房成交价均值是386万那么新样本走到这个叶子时预测值就是386万。停止条件一般有几种树深度达到上限、叶子节点样本数小于设定阈值、继续分裂带来的误差下降小于某个极小值。其中叶子节点最小样本数MinLeafSize是控制过拟合最常调的参数。我习惯把这个过程类比成切蛋糕每次都把蛋糕切得更小块切到每块只剩几口时就停不然每块碎渣里的信息就失真了。2.2 房价数据的特征选择与清洗我这次用的数据是一份房屋交易明细字段包括建筑面积、使用面积、卧室数量、卫生间数量、建造年份、车库容量、所在区域分档等目标是销售价格。实际使用中不一定局限于这些字段核心思路是选那些“你判断对房价有影响”的字段比如地段、面积、房龄、交通配套。这里有一个容易犯的错特征不是越多越好。有些字段比如“房屋编号”“业主姓名”和价格没有因果关系喂进模型只会增加无意义的计算。选择特征时先把和目标极可能无关的字段剔除。清洗阶段主要做三件事。一是缺失值处理最简单的做法是删除缺失比例过高的样本或者用列均值填充。二是异常值处理比如面积小于5平方米、价格低于1万元这种明显不合逻辑的记录直接删掉。三是数据类型统一有些数据从Excel导入后会变成文本类型比如“3房”这种带单位的字符串一定得转成数值再进模型。有一个细节值得说决策树回归本身对特征尺度不敏感不要求标准化因为分裂时只比较特征值的大小关系。这也意味着你可以把面积、房龄、卧室数放在同一个模型里不用操心单位问题。但反过来它也更容易被个别异常特征值带偏清洗这步就变得更重要了。2.3 训练集与测试集的划分策略划分数据这件事看似简单实则影响很大。常用的比例是8:2或者7:3这取决于样本量。我这次用了8:2样本量在1500条左右测试集300条足够看到稳定的评估效果。有两个问题必须注意。第一个是随机种子。MATLAB里如果不设rng(42)每次运行划分结果都不一样模型评估数值会跳来跳去。固定随机种子的作用是让实验结果可复现。你写代码的时候有这个习惯后面调参省很多事。第二个是划分方式。如果这份房价数据是按时间采集的用随机划分会不严谨——你相当于用2018年的部分样本去预测2019年的房子模型可能偷看未来信息。这种情况应该按时间顺序切分比如前80%的时间段做训练后20%做测试。如果没有时间顺序才能用随机划分。我这次的样本是截面数据所以用了cvpartition做随机划分。还有一个实用小技巧划分完之后立刻检查一下训练集和测试集的目标变量分布用histogram画出来看一眼。如果两者均值差非常多说明划分有偏重新划一次再用。3. MATLAB实操从数据到模型3.1 读取数据与预处理代码实操第一步是读数据。假设你的表格叫house_price.csv列名包含GrLivArea地上居住面积、BedroomAbvGr卧室数、YearBuilt建造年份)、TotalBsmtSF地下室面积、GarageCars车库容量目标列是SalePrice。% 读取数据 data readtable(house_price.csv); disp(height(data)); % 看有多少行 disp(sum(ismissing(data))); % 看每列缺失值数量读完先别急着建模检查缺失值。ismissing返回逻辑矩阵sum按列求和一眼就能看到哪些字段有残缺。如果某列缺失超过20%我的建议是直接放弃该列填充意义不大。接下来做基础清洗和特征矩阵构造% 筛选特征列 featureNames {GrLivArea, BedroomAbvGr, YearBuilt, TotalBsmtSF, GarageCars}; X data{:, featureNames}; Y data.SalePrice; % 删除含缺失值或异常值的行 validIdx ~any(ismissing(X), 2) ~ismissing(Y); X X(validIdx, :); Y Y(validIdx, :); % 删除面积或价格明显异常的行 validIdx X(:,1) 20 X(:,1) 500 Y 50000 Y 5000000; X X(validIdx, :); Y Y(validIdx, :); disp(size(X));这里用逻辑索引一次性过滤掉异常样本。面积大于500平方米和价格低于5万、高于500万的记录都属于可疑数据直接剔除比留在里面干扰模型更靠谱。然后划分训练测试集rng(42); cv cvpartition(height(X), HoldOut, 0.2); idxTrain training(cv); idxTest test(cv); X_train X(idxTrain, :); Y_train Y(idxTrain); X_test X(idxTest, :); Y_test Y(idxTest);cvpartition的好处是它自带训练/测试索引不需要手工按比例切片避免随机错位。HoldOut0.2表示留出20%作为测试集。3.2 用fitrtree训练第一棵决策树数据准备好了训练一颗默认参数的决策树非常简单model fitrtree(X_train, Y_train); view(model, Mode, graph);默认参数下树会生长得比较深训练集误差极小但很可能过拟合。先不急着评价我们把它当基线模型。用树图查看结构时你会看到默认树往往有几十层画出来密密麻麻。这本身就是一种提示它太复杂了。view(model,Mode,graph)会弹出一个交互式的树形窗口每个节点都显示分裂条件、样本量和预测均值。第一次看这个图的时候你可以顺着根节点往下点感受一下模型是如何一步一步从“全体样本”走到“叶子节点”的。第一棵树的评估指标可以马上算出来predTrain predict(model, X_train); rmseTrain sqrt(mean((predTrain - Y_train).^2)); predTest predict(model, X_test); rmseTest sqrt(mean((predTest - Y_test).^2)); fprintf(训练集RMSE: %.2f, 测试集RMSE: %.2f\n, rmseTrain, rmseTest);我实测得到的数值训练集误差远小于测试集差值能到30%以上这就是过拟合的信号。说明默认参数下树把训练样本里的个别噪声都记住了。3.3 交叉验证与参数网格搜索单靠一次训练测试划分来判断模型好坏很容易被随机性误导。交叉验证的做法是把训练数据再切成K份通常5或10轮流用其中K-1份训练、1份验证取平均误差。这个过程相当于模拟了多次“训练-验证”实验结果更稳定。MATLAB里最省事的方式是直接在fitrtree中启用交叉验证cvModel fitrtree(X_train, Y_train, CrossVal, on, KFold, 5); cvLoss kfoldLoss(cvModel); fprintf(5折交叉验证MSE: %.2f\n, cvLoss);注意kfoldLoss返回的是均方误差MSE不是RMSE。想要RMSE需要自己开根号。交叉验证更大的用途是配合网格搜索调参。树的复杂度主要由MinLeafSize叶子节点最小样本数控制这个参数越大树越简单。我扫一遍典型取值leafSizes [1, 5, 10, 20, 50]; cvErrors zeros(length(leafSizes), 1); for i 1:length(leafSizes) m fitrtree(X_train, Y_train, ... CrossVal, on, KFold, 5, ... MinLeafSize, leafSizes(i)); cvErrors(i) kfoldLoss(m); fprintf(MinLeafSize%2d, CV MSE%.2f\n, leafSizes(i), cvErrors(i)); end我把运行结果整理一下作为参考MinLeafSize交叉验证MSE1数值较低但偏过拟合5误差开始下降10达到较稳定区间20继续提升不明显50误差回升模型过于简单实际选择时我会画一条“叶子大小-交叉验证误差”曲线选曲线最低点或开始变平缓的位置。不要只看MSE最低还要兼顾模型的泛化稳定性。MinLeafSize10一般是个不错的起点再结合MaxNumSplits限制总分裂次数把树控制在一个可解释的规模。3.4 模型评估指标与可视化选好参数后用最优参数重新训练最终模型finalModel fitrtree(X_train, Y_train, ... MinLeafSize, 10, ... MaxNumSplits, 30, ... Prune, on); % 测试集预测 pred predict(finalModel, X_test); % 计算评估指标 residuals pred - Y_test; rmse sqrt(mean(residuals.^2)); mae mean(abs(residuals)); SS_res sum(residuals.^2); SS_tot sum((Y_test - mean(Y_test)).^2); R2 1 - SS_res / SS_tot; fprintf(RMSE: %.2f\n, rmse); fprintf(MAE : %.2f\n, mae); fprintf(R2 : %.4f\n, R2);RMSE均方根误差代表平均预测偏差量纲和房价一致可以直接说“预测平均偏差约XX万”。MAE是平均绝对误差比RMSE更抗异常值。R²表示模型解释了多少比例的方差0.85以上说明模型有实用价值。可视化我建议画三张图。第一张是预测值和真实值的散点图点越贴近对角线越好figure; scatter(Y_test, pred, 20, filled); hold on; plot([min(Y_test), max(Y_test)], [min(Y_test), max(Y_test)], r--, LineWidth, 1.5); xlabel(真实房价); ylabel(预测房价); title(决策树回归预测效果); grid on;第二张画残差分布看预测差是否呈现某种规律figure; histogram(residuals, 30); xlabel(预测残差); ylabel(样本数); title(预测残差分布);如果残差图出现明显的喇叭形中间窄两边宽说明模型在房价高段和低段误差不同后续可以按价格分层建模。第三张就是树图本身view(finalModel, Mode, graph);这棵树是最终和业务方沟通时的核心素材。3.5 特征重要性分析决策树模型有一个很好的副产品特征重要性。predictorImportance函数会计算每个特征在分裂中的贡献数值越大说明该特征对预测房价越关键。imp predictorImportance(finalModel); bar(imp); set(gca, XTickLabel, featureNames); ylabel(特征重要性);我实测的结果里建筑面积GrLivArea和建造年份YearBuilt的重要性排名最靠前其次才是卧室数量和地下室面积。这和业务直觉一致买房先看面积和房龄卧室多但面积小价格也上不去。看特征重要性还有一个实际作用如果某个你原先觉得重要的字段重要性接近0说明它跟目标基本没关系或者信息已经被其他特征覆盖了。这时候可以把它从模型里拿掉让模型更干净。4. 常见问题与调优实录4.1 过拟合训练集和测试集表现失衡这是单棵决策树最常见的坑。表现就是训练集R²接近0.98测试集只有0.7左右。原因很简单树长得太深把训练样本的噪声当成了规律。解决办法优先级如下。先把MinLeafSize从1往上调调到10、15观察交叉验证误差的变化。再配合MaxNumSplits限制总分裂次数比如限制在30以内树的深度会明显下降。最后用Prune开启剪枝让MATLAB自动计算剪枝序列选择误差最小的子树。我自己的经验是先固定MaxNumSplits30再去扫MinLeafSize两个参数一起动反而不好定位问题。4.2 预测值异常与数据质量排查如果预测出来的房价出现负值或者某个样本的预测值离训练数据分布十万八千里大概率不是模型的问题是数据的问题。排查顺序我建议是先检查对应样本的特征是否有异常值比如面积为0、卧室数为99这类脏数据再检查特征矩阵和目标值是否对齐用readtable读取时列顺序发生变化会导致特征错位最后看测试集里是否存在训练集完全没有覆盖到的特征范围。决策树本身不具备外推能力训练集面积范围是80到200平方米测试集出现300平方米的房子预测值只能在已有叶子节点范围内取值偏差大是正常的。4.3 决策树可视化杂乱与解读技巧view画出的树太大窗口都装不下这是树太深的表现。两个处理技巧一是在训练时把MaxNumSplits调小比如20或者30树自然就简洁了二是用view(finalModel, Mode, text)输出文本形式的树结构虽然不够直观但通过读取文本能逐行追踪路径。做业务汇报时我不会把整棵树砸给人家看。我会挑根节点往下两层的关键分裂规则比如“面积小于120平、房龄大于15年预测价约260万”做成简单的话术对方马上能理解模型到底学到了什么。4.4 从单棵树到随机森林何时升级单棵决策树的稳定性天然偏弱训练集稍微少几个样本树的形态就可能大变。如果发现单棵树调来调去测试集误差总是压不下来就该考虑集成模型了。MATLAB里可以在一行代码内切换到装袋集成也就是随机森林的核心思想rfModel fitrensemble(X_train, Y_train, ... Method, Bag, ... NumLearningCycles, 100); rfPred predict(rfModel, X_test); rfRMSE sqrt(mean((rfPred - Y_test).^2));很多情况下随机森林的RMSE比单棵树有明显下降因为它对大量树取平均方差被压掉了。代价是解释性变差——你不能再看一棵“树”去讲故事了。我的建议是做汇报、讲逻辑、需要向非技术方解释时用单棵决策树做比赛、调精度、纯追求预测效果时果断上随机森林。4.5 版本兼容性与随机种子问题fitrtree这个名字在MATLAB R2013b之后就可以用了。老版本里曾经用classregtree现在基本退役直接用fitrtree就行。不同版本之间函数接口差异不大但表格处理函数在某些老版本里行为不同比如readtable对文本列的处理。如果发现代码报错优先看自己的MATLAB版本和工具箱有没有装全。随机种子这个事我要再强调一次。很多朋友跑完代码换个电脑或者重新开一次MATLAB结果变了就怀疑是程序写错。其实只是随机种子没固定。只要在划分数据之前写一句rng(42)后续每次复现结果都是同一个。这里的42换成任意正整数都行它的作用只是让随机序列固定下来。我个人在实际项目中还有个习惯把调参过程自动化。写好网格搜索循环后让程序跑一遍并记录所有候选参数对应的交叉验证误差存到表格文件里。之后再对比各个参数组合而不是靠手感去试。这样写出来的报告说服力强很多也方便下次复用。这个项目做到这里结论已经清楚了决策树回归配上MATLAB确实是入门回归建模的一条捷径。它的门槛低、过程透明、调试直观。如果你手里也有一份或多或少的房屋交易数据不妨直接照上面的流程跑一遍。先不管效果多好跑通一遍理解了分裂、剪枝、交叉验证这些核心概念后面再学更复杂的集成模型自然就顺了。
返回列表