
简介这是一份面向Python数据分析与GUI开发学习者的二手房价分析与预测期末大作业项目包基于Python和PyQt5完成覆盖数据读取、清洗统计、特征分析、模型训练、房价预测和可视化展示等完整环节适合期末答辩、课程设计及自学进阶。压缩包共17个文件包含6个Python源码文件、6张PNG界面素材、2个pyc缓存、1个Qt界面文件、1个CSV数据集和1个说明文档整体体积仅132KB目录结构清晰便于按模块阅读与复用。项目中利用Pandas完成数据清洗与统计摘要Scikit-Learn构建预测模型Matplotlib生成分布与结果图表并通过PyQt5搭建交互界面实现加载数据、选择模型、查看结果的一站式操作。源码附带详细注释可将Pandas、Scikit-Learn、Matplotlib、PyQt5各技术点与工程实现一一对应同时内置可用的CSV数据集与界面素材省去自行准备数据的麻烦。目前已有343人学习适合需要完整案例参考的在校学生和初级开发人员。1. 期末大作业的二手房价分析与预测系统它到底解决了什么问题期末项目选“二手房价分析与预测”这个题目几乎是每年 Python 课程设计的保留曲目。需求看起来很简单拿到一批二手房数据训练一个能预测价格的模型再用图形界面把结果展示出来。但真做起来数据清洗、特征构造、模型调参、界面联调每一环都够折腾几天。这套系统就是把这些环节串成一个完整的闭环用 Python 处理数据、训练模型用 PyQt5 做桌面界面用户在界面上输入户型、面积、楼层、朝向等条件就能看到预测价格和周边小区对比。适合正在做期末大作业、毕业设计或者想快速搭一个数据展示型桌面应用的读者。想用最短路径把作业跑通同时把每个模块的坑提前踩掉这篇就是按这个思路写的。2. 二手房价数据集的清洗与特征工程喂给模型之前先做这几步2.1 先看数据长什么样字段类型与缺失值排查打开二手房价数据集的 CSV 文件常见字段大概是这几类小区名称、区域、户型室厅卫、面积、朝向、楼层、装修、建筑年代、总价、单价。第一件事不是直接训练而是把数据读进来逐字段看一眼。很多二手房价数据在采集时就有问题比如“朝向”列出现“南北通透”“南 北”两种写法“楼层”列混着“低楼层/共6层”这种文本这类字段直接丢给模型会变成灾难。我一般会把数据读取和初步体检写成一个脚本先跑一遍再决定怎么做特征import pandas as pd df pd.read_csv(house_data.csv, encodingutf-8) print(df.shape) print(df.dtypes) print(df.isnull().sum()) # 抽样看几行确认文本字段的写法 print(df.head(10).to_string())这里有几个关键点。encoding参数特别容易踩坑很多爬下来的 CSV 是gbk编码直接用utf-8读会抛UnicodeDecodeError。报错时先尝试encodinggbk或encodinggb18030。isnull().sum()是看缺失值的入口先知道哪些列缺得厉害再决定是删行、填值还是拆特征。打印dtypes则是看数据类型的隐患比如面积列被读成object说明里面有类似“89.5㎡”这种带单位的脏数据。2.2 把文本特征变成数值特征面积、楼层、朝向的处理方式文本特征不处理模型就训不了。目标是把每一列都收拾成干净的数值或类别编码。先说面积列如果里面混了单位先用正则把数字提取出来楼层列比较典型常见写法是“低楼层/共6层”或“3/6层”这里可以拆成两个特征当前楼层数、总楼层数再用总楼层数算出一个相对位置。朝向这类文本列用get_dummies转成哑变量或者用LabelEncoder编码。我的做法是能拆出数值就拆数值朝向这种没有大小关系的列才做哑变量。import re # 提取面积中的数字89.5㎡ - 89.5 df[area] df[area].str.extract(r(\d\.?\d*)).astype(float) # 楼层列处理低楼层/共6层 - current1, total6 def parse_floor(s): m re.search(r共(\d)层, str(s)) c re.search(r(\d)/, str(s)) if c and m: return int(c.group(1)), int(m.group(1)) return None, None df[current_floor], df[total_floor] zip(*df[floor].apply(parse_floor)) df[floor_ratio] df[current_floor] / df[total_floor] # 朝向只保留第一个方向减少类别数 df[direction] df[direction].str.split(/).str[0] df pd.get_dummies(df, columns[direction], prefixdir)str.extract配合正则提取数字是处理混合格式最顺手的方式记得提取后要astype(float)转换不然还是文本。floor_ratio的含义是“楼层的相对高度”0.3 代表在总楼层偏下位置0.8 代表在高区。这个特征对房价的影响其实是有的高层采光好但也要看有没有电梯。朝向拆成哑变量之前先只保留第一个方向能让列数少一点避免“南北”“南 北”这种同一含义拆成两列。2.3 对总价做归一化为什么预测总价比预测单价更稳作业里很多人直接拿总价做预测目标但二手房数据里的总价受面积影响太大同样 300 万30 平米和 120 平米的房子完全不是一个量级。这里有一个常见分歧预测总价还是预测单价。我建议做单价预测因为单价剔除了面积的影响更能反映地段、楼层、装修这些特征的作用。预测完之后展示时再乘上用户输入的面积这样用户看到的总价也更直观。# 计算单价作为预测目标 df[unit_price] df[total_price] / df[area] # 对连续特征做标准化 from sklearn.preprocessing import StandardScaler feature_cols [area, bedrooms, living_rooms, bathrooms, floor_ratio, building_age] scaler StandardScaler() df[feature_cols] scaler.fit_transform(df[feature_cols])单位价的单位是“万元/平米”还是“元/平米”取决于原始总价单位分析时要先看好数据集里的字段说明。用StandardScaler做标准化的一个实际好处是后面用线性回归、岭回归这类模型时收敛更快系数也能直接比较大小用来判断“哪个特征对房价影响更大”。如果数据集里的房子坐标、板块信息可用还可以考虑加“距地铁站距离”这类衍生特征这类资料往往要去高德或百度地图的 API 算作业时间紧的话用小区均价替代也行。重要提示fit_transform只应该在训练集上调用验证集和测试集要用训练集拟合好的scaler做transform不要在验证集上重新算均值方差。这是很多作业被老师看出“数据泄漏”的点后面我还会细说。3. 房价预测模型选型与训练从线性回归到集成模型的全过程3.1 先用线性回归打基线结果再差也要有一个参照拿到干净数据后的第一件事不是直接上随机森林、XGBoost而是先用一个最朴素的线性回归跑一遍得到一组“下限分数”。这样做的价值是后面换任何模型都有了参照系——新模型比线性回归高多少分才算真正有效果。很多作业翻车就是先上复杂模型结果调参半天也不知道自己调到什么程度算“好”。from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, r2_score X df[feature_cols [c for c in df.columns if c.startswith(dir_)]] y df[unit_price] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) model LinearRegression() model.fit(X_train, y_train) y_pred model.predict(X_test) print(RMSE:, mean_squared_error(y_test, y_pred, squaredFalse)) print(R2:, r2_score(y_test, y_pred))这套流程是标准的分割、训练、评估三件套。random_state42固定随机种子保证每次运行结果一致这是作业里必须养成的习惯不然答辩的时候跑两次结果不一样没法跟老师解释。R²在房价预测里一般能到 0.6~0.8 就算不错的线性结果如果低于 0.4说明数据里非线性关系强或者特征太粗糙这时候再去换集成模型才有意义。3.2 换随机森林处理非线性关系的主要手段线性回归跑完接下来上随机森林。随机森林对异常值和特征尺度不敏感不用再担心标准化的问题而且能给出特征重要性方便你在答辩时跟老师说“哪些特征对房价影响最大”。随机森林的主要参数是n_estimators、max_depth、min_samples_split这些参数不要盲目抄网上的值要结合数据量来定。from sklearn.ensemble import RandomForestRegressor rf RandomForestRegressor( n_estimators200, max_depth12, min_samples_split5, min_samples_leaf2, random_state42, n_jobs-1 ) rf.fit(X_train, y_train) y_pred_rf rf.predict(X_test) print(RF RMSE:, mean_squared_error(y_test, y_pred_rf, squaredFalse)) print(RF R2:, r2_score(y_test, y_pred_rf)) print(pd.Series(rf.feature_importances_, indexX.columns).sort_values(ascendingFalse))这里n_estimators200和max_depth12是我试过比较多组之后觉得比较稳的起点。数据量在几千条的时候200 棵树比 100 棵的误差略低再往上就边际递减了。max_depth如果设得太深比如 20 以上很容易过拟合训练集 R² 接近 1但测试集掉得厉害。特征重要性输出之后通常会发现面积、建筑年代、floor_ratio 排在最前朝向的哑变量普遍靠后这是数据本身的信号不代表朝向没用只能说在这个数据集里变化不够大。3.3 再用 XGBoost 调高一点网格搜索要控制时间随机森林效果如果已经不错可以再试 XGBoost。XGBoost 在房价这类表格式数据上成绩稳定而且处理缺失值、非线性都比线性模型强。常见的调参顺序是先定学习率learning_rate和树数量n_estimators再调max_depth和min_child_weight最后调subsample和colsample_bytree。作业时间有限不要做全量网格搜索用GridSearchCV也要控制参数组合数量不然一个交叉验证可能跑半小时以上。import xgboost as xgb from sklearn.model_selection import GridSearchCV xgb_model xgb.XGBRegressor( learning_rate0.05, n_estimators300, max_depth6, subsample0.8, colsample_bytree0.8, random_state42 ) param_grid { max_depth: [4, 6, 8], subsample: [0.7, 0.8, 0.9] } grid GridSearchCV( xgb_model, param_grid, cv5, scoringr2, n_jobs-1, verbose1 ) grid.fit(X_train, y_train) print(grid.best_params_) print(grid.best_score_)cv5的意思是五折交叉验证每一折都用训练集的一部分做验证最终best_score_是五折的平均 R²这比单次划分测试集更稳定答辩时也是一个可说的亮点。n_jobs-1会用满所有 CPU 核心但如果你的电脑是 4 核老笔记本跑网格搜索时记得把verbose1打开能看到进度不然看起来像死机。还有一个实操细节XGBoost 在数据量小的时候容易过于自信测试集 R² 高但真实场景不一定好所以在作业里最好同时保留随机森林和 XGBoost 两个模型界面上做一个模型下拉切换效果对比更直观。参数调优是门玄学没有一组参数适合所有数据集。比较稳的办法是固定一颗随机种子用交叉验证评估每次只动一个参数记录分数变化这样既能看到趋势也能省时间。4. 用 PyQt5 把预测模型包装成交互界面从模型文件到桌面应用4.1 界面布局输入区和结果区怎么规划预测模型跑通后下一步就是用 PyQt5 做一个桌面界面。界面一般分成三个区域左侧是房源信息输入区包括面积、户型、朝向、楼层、建筑年代等右侧是预测结果展示区显示预测单价、总价和一个小区的对比底部放一个“开始预测”按钮和状态栏。PyQt5 的布局用QFormLayout加下拉框QComboBox和数值输入框QSpinBox的组合比手写坐标定位方便得多。from PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QFormLayout, QComboBox, QSpinBox, QDoubleSpinBox, QPushButton, QLabel, QVBoxLayout, QHBoxLayout ) class HousePriceWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(二手房价格分析与预测系统) self.setMinimumSize(720, 480) central QWidget() self.setCentralWidget(central) layout QVBoxLayout(central) form QFormLayout() self.area_input QDoubleSpinBox() self.area_input.setRange(20.0, 300.0) self.area_input.setSuffix( ㎡) self.room_input QSpinBox() self.room_input.setRange(1, 6) self.floor_input QSpinBox() self.floor_input.setRange(1, 40) self.direction_input QComboBox() self.direction_input.addItems([东, 南, 西, 北]) form.addRow(面积:, self.area_input) form.addRow(卧室数:, self.room_input) form.addRow(所在楼层:, self.floor_input) form.addRow(朝向:, self.direction_input) layout.addLayout(form)这里每个输入控件的数据范围都做了约束比如面积限制在 20 到 300 平米这是根据数据集分布来的。QDoubleSpinBox支持小数适合面积QSpinBox只能整数适合卧室数这类离散值。QComboBox做朝向选择避免用户输入五花八门的文本这是桌面应用里很讨巧的设计——从源头杜绝脏数据。4.2 按钮事件与预测逻辑打通加载模型、构造特征、输出结果界面搭好之后核心工作量在“点按钮之后干什么”。我习惯把模型保存成文件界面启动时用joblib加载而不是每次点按钮都重新训练。预测的逻辑链是读取界面控件里的值 → 按训练时的顺序构造特征向量 → 喂给模型 → 把输出格式化成“单价 总价”展示。这里最容易出错的点是特征顺序错乱。训练时的特征列顺序是固定的如果界面这边少传了一列、多传了一列预测结果就跑偏而且这种错很难查。import joblib # 训练完成后保存模型和特征列 # joblib.dump(model, model_rf.pkl) # joblib.dump(list(X.columns), feature_columns.pkl) class HousePriceWindow(QMainWindow): def __init__(self): # ... 上面的控件初始化代码 ... self.predict_btn QPushButton(开始预测) self.result_label QLabel(预测结果将显示在这里) layout.addWidget(self.predict_btn) layout.addWidget(self.result_label) self.predict_btn.clicked.connect(self.on_predict) def on_predict(self): self.model joblib.load(model_rf.pkl) self.feature_cols joblib.load(feature_columns.pkl) area self.area_input.value() rooms self.room_input.value() floor self.floor_input.value() direction self.direction_input.currentText() # direction 的哑变量必须与训练时一致 feat {} for c in self.feature_cols: if c.startswith(dir_): feat[c] 1 if c fdir_{direction} else 0 elif c area: feat[c] area elif c floor_ratio: feat[c] floor / (floor 9) # 假设数据中位总楼层 else: feat[c] 0 import pandas as pd sample pd.DataFrame([feat])[self.feature_cols] pred self.model.predict(sample)[0] total_price pred * area self.result_label.setText( f预测单价: {pred:.0f} 元/平米\n f预测总价: {total_price:.0f} 元 )这段代码里有一个关键细节哑变量是按feature_cols里的顺序生成的而且只生成训练时出现过的方向列。如果训练数据里有“东南”这个朝向界面里却没有这个选项预测时就会漏列并报错。解决办法就是上面这种“从feature_cols反推构造字典”的思路保证列名、列顺序永远跟着训练时的记录走。floor_ratio的计算这里用了floor / (floor 9)来模拟相对楼层实际作业时可以简化成从界面传总楼层和所在楼层一起算更准确。关于模型文件保存有一套配套命令先跑# 训练完模型后执行保存 import joblib joblib.dump(rf, model_rf.pkl) joblib.dump(list(X.columns), feature_columns.pkl)保存两个文件的原因是为了还原特征顺序这也避免了把scaler和模型绑在一起时出现的状态错乱。如果数据里还有标准化步骤最好用joblib把scaler也保存成一个文件加载模型后先做标准化再喂数据这个顺序别颠倒。4.3 PyQt5 内嵌 matplotlib用图表把预测结果讲清楚只有一行数字的界面显得单薄加分项是在界面里加一个图表展示用户输入的房源与周边小区均价对比或者展示该小区历史价格走势。PyQt5 内嵌 matplotlib 的常见做法是用FigureCanvasQTAgg把画布嵌到窗口里替代传统的plt.show()弹窗模式。import matplotlib.pyplot as plt from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure class ChartWidget(FigureCanvas): def __init__(self, parentNone): self.fig Figure(figsize(5, 3), dpi100) super().__init__(self.fig) self.setParent(parent) self.ax self.fig.add_subplot(111) def update_chart(self, price, avg_price): self.ax.clear() labels [预测单价, 小区均价] values [price, avg_price] bars self.ax.bar(labels, values, color[#4C72B0, #DD8452]) self.ax.set_ylabel(价格元/平米) for bar, v in zip(bars, values): self.ax.text(bar.get_x() bar.get_width()/2, v, f{v:.0f}, hacenter, vabottom) self.ax.set_title(预测价格 vs 周边均价) self.draw()这里FigureCanvasQTAgg是把 matplotlib 的图嵌入 PyQt5 的关键接口update_chart每次先clear()再重新绘制避免画布重影。要注意的是dpi100在 1080p 屏幕上显示还行分辨率更高的屏幕上字会偏小可以调到 120~150。图表数据里的avg_price可以从数据集里按小区分组算好均值存成一个 dict界面加载时直接读取即可不必实时查数据库。5. 避坑期末大作业最常见的 6 个翻车现场与排查方法5.1 PyQt5 安装失败报错信息对不上怎么办期末季最热门的问题之一就是“PyQt5 安装不上”。常见现象是pip install pyqt5卡在下载阶段或者装完之后from PyQt5.QtWidgets导入报错ModuleNotFoundError。原因有两个一是 PyQt5 的 wheel 包体积大默认源下载慢二是 Python 版本和 PyQt5 版本不匹配。解决方法是换国内镜像源安装同时注意 Python 版本Python 3.9 以上装 5.15 系列基本没问题Python 3.6 太老的话装新版会直接拒绝安装。在命令行里执行下面的命令装完验证一次导入能省去后面大量的界面调试时间pip install pyqt5 -i https://pypi.tuna.tsinghua.edu.cn/simple python -c from PyQt5.QtWidgets import QApplication; print(ok)如果提示ModuleNotFoundError: No module named PyQt5.sip这是 PyQt5 和 PyQt5-sip 版本不对齐的老问题单独再装一次pip install pyqt5-sip通常就解决了。5.2 模型预测结果几乎全是同一个值辛苦训练完之后发现不管是输入什么户型预测出来的价格都差不多图表上是一条平线。这个现象的原因集中在特征上要么是特征列顺序错乱模型吃进去的数值和训练时完全不对应要么是界面构造的feat字典缺失关键列模型只能用默认值填充。排查方法是先打印出每次预测时的特征向量检查面积、楼层这类高权重特征是否真的随界面输入在变化。可以在预测代码里临时加一行print(pd.DataFrame([feat])[self.feature_cols].to_string())然后切换几次界面的输入值看对应列的数字有没有变化。如果发现floor_ratio恒为某个固定值那问题多半出在楼层换算公式上如果所有列都不变那是feat字典的键名没跟feature_cols对齐。5.3 PyQt5 界面点击按钮后无响应或卡死点“开始预测”之后窗口卡住转圈几秒甚至直接崩溃。这类问题几乎都是因为在主线程里跑了重型计算。on_predict里如果直接加载几百 MB 的模型文件或者把网格搜索也放进去了界面就会阻塞。解决方法是把模型加载放到窗口初始化阶段只做一次另外把预测运算放到QThread里界面保持响应。期末作业如果数据量不大模型文件一般只有几十 MB放在__init__里加载是足够快的。线程问题是 PyQt5 里最典型的翻车点之一。简单做法是写一个Worker类用QThread的run方法执行预测并发射信号返回结果from PyQt5.QtCore import QThread, pyqtSignal class PredictWorker(QThread): finished pyqtSignal(float, float) def __init__(self, model, sample_df): super().__init__() self.model model self.sample_df sample_df def run(self): pred self.model.predict(self.sample_df)[0] self.finished.emit(pred, pred * self.sample_df[area].values[0])界面上实例化这个 worker把finished信号连到一个槽函数更新self.result_label。信号槽机制是 PyQt5 里跨线程回传数据的事实标准用好了整个界面都不会卡。注意线程对象要在界面类里持有引用防止被垃圾回收掉导致信号发不出来。5.4 数据集里出现重复或异常明显的离群值二手房价数据里有大量重复的房子记录很常见同一个小区同一套户型在不同时间被抓了多次还有那种 2000 年的老破小卖 2000 万或者郊区 500 平别墅单价 1 万这样的极端值。不去重、不处理离群值模型会被这些点拉偏。处理分两步第一步按房源 ID 或“小区面积户型”去重第二步用箱线图法把单价在上下四分位之外 5 倍四分位距的点标出来人工判断是删还是留。# 去重 df df.drop_duplicates(subset[community, area, bedrooms, living_rooms]) # 单价的四分位距过滤 Q1 df[unit_price].quantile(0.25) Q3 df[unit_price].quantile(0.75) IQR Q3 - Q1 df df[(df[unit_price] Q1 - 5 * IQR) (df[unit_price] Q3 5 * IQR)]这里用5 * IQR而不是常见的1.5 * IQR是因为房价数据本身方差大用1.5会把大量正常房源误杀。这个倍数没有标准答案可以先画出单价分布直方图再按倍数调整看保留样本量是否合理。离群值处理会直接影响最终分数值得多花时间。5.5 界面打包成 exe 后缺少依赖交作业时导师经常要求打包成可执行文件双击就能跑。用pyinstaller -w main.py打完的 exe 经常在别人的电脑上报错最常见的是缺 PyQt5 的插件目录或者缺模型文件的路径。解决方法是把模型文件和代码放在同一个目录打包时用--add-data把模型文件带进去并且在代码里用sys._MEIPASS处理临时解压路径。pyinstaller -w -F main.py --add-data model_rf.pkl;. --add-data feature_columns.pkl;.-w是隐藏控制台窗口-F是打包成单文件。--add-data后面的分号在 Windows 下是路径分隔符Linux/macOS 下要换成冒号。代码中读取文件的路径需要写成import sys, os base_path getattr(sys, _MEIPASS, os.path.dirname(os.path.abspath(__file__))) model_path os.path.join(base_path, model_rf.pkl)用_MEIPASS的原因是 PyInstaller 单文件打包后资源文件会被解压到临时目录直接读相对路径会找不到文件。这个坑比较隐蔽属于打包阶段才能发现的类型提前写对能省很多时间。5.6 训练集和验证集的数据泄漏你精心调出来的分数可能无效这是最容易被老师问倒的一个点。如果先对整个数据集做了StandardScaler再切分训练集和测试集验证集的分布信息已经提前泄露给了训练过程。这个泄漏会让测试分数虚高答辩时老师抽查代码很容易发现。正确顺序是先train_test_split再在训练集上fit然后transform两个集。随机森林这类树模型不需要归一化但如果你的流程里用了线性回归或者神经网络这个顺序就必须严格。X_train, X_test, y_train, y_test train_test_split(...) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)这组代码体现的规则是所有“学习参数”的操作都只允许在训练集上做。缺失值填补、特征选择、降维同理都要先把 fit 和 transform 分开。很多网上的教程没有强调这一点作业里被老师提出来就是硬伤。6. 把作业做深一个档次交叉验证、特征重要性与答辩验证技巧到了这个阶段你的系统已经能跑通了数据处理、模型训练、PyQt5 界面、打包成 exe 全套都有。但如果想让老师觉得“这个学生真的理解自己在做什么”还有三件性价比很高的事可以做最后一件对答辩最有用。第一件事是把单次train_test_split评估换成cross_val_score。交叉验证用多轮数据切分取平均能给出模型真实水平的上限和下限规避单次划分运气好或运气坏的问题。把交叉验证结果打成一个表格放到 README 或者答辩 PPT 里比只报一个测试 R² 要有说服力得多。代码很简单from sklearn.model_selection import cross_val_score scores cross_val_score(rf, X_train, y_train, cv10, scoringr2) print(f10折交叉验证 R2: {scores.mean():.4f} ± {scores.std():.4f})±后面的标准差代表模型在不同数据子集上的稳定性这个数字越小越好。如果标准差超过了 0.1说明模型对数据划分太敏感这时候就要考虑是不是数据量太少或者模型过拟合了。第二件事是在界面上加一个“特征重要性”的展示。把随机森林的feature_importances_画成横向条形图面积、建筑年代、楼层位置排在前三名这个图表既是模型的解释工具也是答辩时的讲解素材。可以利用前面写的ChartWidget类把条形图嵌到界面右侧用户点击“模型分析”按钮就能切换到这张图。这个功能做起来半小时但对技术深度的展示效果远超过预期。第三件事也是回报最高的是做一个简单的模型对比表把线性回归、随机森林、XGBoost 三个模型的 RMSE、R²、训练时间都列出来。不用做得多复杂一个QTableWidget就能搞定或者直接在报告里画一张表格。内容上要写清楚“为什么随机森林在这个数据集上比 XGBoost 好”这个分析本身就体现了差异化的思考能力。常见原因是数据集量不大、特征不多时XGBoost 的复杂度优势发挥不出来而随机森林更容易收敛。我个人的习惯是把整个项目按“数据 → 特征 → 模型 → 界面 → 验证”分成五个目录每个目录放一个README.md写明这个环节做了什么、选了什么参数、踩了什么坑。这个习惯在答辩时帮了大忙——老师说“你这个楼层比例特征怎么想到的”我直接打开特征工程的 README里面写了两行我当时对比过不同楼层算法的结果比现场组织语言要从容得多。整个项目做下来最大的感受是期末大作业拼的不只是调包能力更是工程化的组织能力。数据不乱、模型可复现、界面可交互、验证说得清这四个维度做到位分数不会差。希望这篇笔记能帮到你在动手之前少走几个弯路。本文还有配套的精品资源点击获取