1. 从零开始理解波士顿房价预测
第一次接触机器学习时,我选择了波士顿房价预测作为入门项目。这个经典案例就像编程界的"Hello World",但远比打印一行文字有趣得多。想象你是一位房产评估师,手上有506份波士顿郊区的房屋资料,包括房间数量、社区犯罪率、学区质量等13个特征,你需要根据这些信息预测房屋的中位数价格。
为什么选择线性回归?因为它就像用尺子画直线一样直观。当我在Jupyter Notebook上第一次看到散点图上那条拟合的直线时,突然明白了机器学习的魅力——用数学捕捉现实世界的规律。不过很快发现,原始数据就像未经雕琢的玉石,直接扔给模型效果并不理想,这才意识到特征工程的重要性。
2. 数据预处理实战技巧
2.1 数据加载与清洗
拿到原始数据的第一件事是用Pandas做个全面体检。这个数据集虽然经典,但格式有点特别——用空格分隔且列间空格数不一致。我常用的读取方法是:
import pandas as pd import numpy as np def clean_space(s): return s != '' data = pd.read_csv('housing.data', header=None) cleaned_data = np.empty((len(data), 14)) for i, row in enumerate(data.values): # 处理不规则空格分隔 cleaned_row = list(map(float, filter(clean_space, row[0].split(' ')))) cleaned_data[i] = cleaned_row给数据列加上有意义的标签很重要,我习惯用字典记录每个特征的含义:
feature_names = { 'CRIM': '城镇人均犯罪率', 'ZN': '住宅用地比例', 'RM': '房间数', # 其他特征... } df = pd.DataFrame(cleaned_data, columns=feature_names.keys())2.2 探索性数据分析(EDA)
Matplotlib的subplot功能非常适合快速观察各特征与房价的关系。我发现一个实用技巧:使用plt.subplot2grid创建网格布局,比直接subplot更灵活:
import matplotlib.pyplot as plt fig = plt.figure(figsize=(12,8)) for i, col in enumerate(df.columns[:-1]): # 排除最后的房价列 ax = plt.subplot2grid((3,5), (i//5, i%5)) # 3行5列布局 ax.scatter(df[col], df['MEDV'], s=10) ax.set_title(col) plt.tight_layout()从散点图能直观看出:房间数(RM)与房价呈明显正相关,而低收入人群比例(LSTAT)则呈负相关。这些观察后续会指导我们的特征选择。
3. 特征工程的艺术
3.1 特征选择策略
原始13个特征不是都有用。我常用两种方法筛选特征:
相关系数法简单直接:
corr_matrix = df.corr() print(corr_matrix['MEDV'].sort_values(ascending=False))LASSO回归则更智能,它能自动将不重要特征的系数压缩为零:
from sklearn.linear_model import LassoCV lasso = LassoCV(cv=5).fit(X, y) important_features = np.where(lasso.coef_ != 0)[0] print("重要特征索引:", important_features)实际项目中,我通常会保留RM(房间数)、LSTAT(低收入比例)、PTRATIO(师生比)和DIS(就业中心距离)这几个关键特征。记得有一次,我固执地用了全部特征,结果模型效果反而变差了——这就是"维度诅咒"的现实教训。
3.2 创造新特征
有时原始特征不够用,需要制造新特征。比如我发现房间数与低收入比例的交互项能提升模型表现:
df['RM_LSTAT'] = df['RM'] * df['LSTAT']多项式特征是另一个利器,Scikit-learn的PolynomialFeatures可以自动生成:
from sklearn.preprocessing import PolynomialFeatures poly = PolynomialFeatures(degree=2, include_bias=False) X_poly = poly.fit_transform(X[['RM', 'LSTAT']])但要注意,随着阶数增加,特征数量会爆炸式增长(二阶时从2个变为5个)。我的经验法则是:先试二阶,如果验证集表现提升再考虑更高阶,但要小心过拟合。
4. 模型构建与优化
4.1 基础线性回归
建立第一个基准模型很简单:
from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) lr = LinearRegression().fit(X_train, y_train) print(f"训练集R²: {lr.score(X_train, y_train):.3f}") print(f"测试集R²: {lr.score(X_test, y_test):.3f}")但很快会发现问题:如果特征量纲差异大(如房间数3-9,税率100-400),模型会偏向大数值特征。这时就需要标准化:
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) # 注意用相同的scaler4.2 正则化方法
当特征间存在多重共线性时,普通最小二乘回归会不稳定。我常用岭回归(Ridge)来解决:
from sklearn.linear_model import RidgeCV ridge = RidgeCV(alphas=[0.1, 1, 10]).fit(X_train_scaled, y_train) print(f"最优alpha: {ridge.alpha_}")ElasticNet结合了L1和L2正则化,适合特征选择与系数收缩的平衡:
from sklearn.linear_model import ElasticNetCV en = ElasticNetCV(l1_ratio=[.1, .5, .9]).fit(X_train_scaled, y_train)记得在一次比赛中,我通过网格搜索找到最佳正则化参数,模型得分提升了8%,这让我深刻理解到调参的重要性。
5. 模型评估与改进
5.1 交叉验证策略
简单train_test_split可能不够可靠。我更喜欢用K折交叉验证:
from sklearn.model_selection import cross_val_score scores = cross_val_score(lr, X, y, cv=5, scoring='r2') print(f"R²均值: {scores.mean():.3f} (±{scores.std():.3f})")学习曲线能帮助判断模型是否欠拟合或过拟合:
from sklearn.model_selection import learning_curve train_sizes, train_scores, val_scores = learning_curve( estimator=lr, X=X, y=y, cv=5) plt.plot(train_sizes, np.mean(train_scores, axis=1), label='训练集') plt.plot(train_sizes, np.mean(val_scores, axis=1), label='验证集')5.2 误差分析
除了R²,还应关注误差分布:
predictions = lr.predict(X_test) errors = predictions - y_test plt.hist(errors, bins=30) plt.xlabel('预测误差')我曾遇到误差呈偏态分布的情况,通过对数变换房价数据解决了这个问题:
y_log = np.log1p(y) # 记得预测后要反向转换 predictions = np.expm1(lr.predict(X_test))6. 完整项目实战
6.1 项目结构设计
规范的目录结构让项目更易维护:
boston_housing/ ├── data/ │ ├── raw/ # 原始数据 │ └── processed/ # 处理后的数据 ├── notebooks/ # Jupyter笔记本 ├── src/ │ ├── features/ # 特征工程 │ └── models/ # 模型代码 └── config.py # 全局配置6.2 可复现的流水线
用Pipeline封装预处理和建模步骤:
from sklearn.pipeline import make_pipeline pipe = make_pipeline( StandardScaler(), PolynomialFeatures(degree=2), Ridge(alpha=1.0) ) pipe.fit(X_train, y_train)保存模型供后续使用:
import joblib joblib.dump(pipe, 'model.pkl') # 加载模型 loaded_pipe = joblib.load('model.pkl')6.3 性能优化技巧
对于大数据集,可以尝试:
- 使用SGDRegressor替代普通线性回归
- 对类别特征进行分箱处理
- 用PCA降维减少特征数量
记得在一次性能优化中,我将特征从13个精简到4个关键特征,不仅训练速度提升了3倍,模型准确率还提高了5%。这验证了"少即是多"的机器学习哲学。