「精通机器学习回归:共享单车需求预测完整实战指南」
在人工智能与大数据时代,共享单车已成为城市交通的重要组成部分。准确预测共享单车的需求不仅能优化运营效率,还能为城市交通规划提供数据支持。本文将系统讲解如何运用机器学习技术构建高精度的共享单车需求预测模型。

一、项目目标与意义
共享单车需求预测是典型的回归任务,通过分析历史数据预测未来某一时段的单车租赁量。这一预测对于共享单车公司的资源调配、维护计划制定以及服务质量提升具有重要价值。
二、数据科学全流程实战
1、数据准备与探索
- 导入核心工具库(pandas、numpy、matplotlib、scikit-learn等)
- 加载数据集并检查基本信息(形状、前几行数据、统计描述)
- 全面检查数据缺失情况,确保数据完整性
# 导入必要的库
import pandas as pd # 用于数据处理和分析
import numpy as np # 用于数值计算
import matplotlib.pyplot as plt # 用于数据可视化
import seaborn as sns # 用于高级数据可视化
from sklearn.model_selection import train_test_split, GridSearchCV # 用于数据分割和超参数优化
from sklearn.preprocessing import StandardScaler, OneHotEncoder # 用于数据预处理
from sklearn.compose import ColumnTransformer # 用于组合不同类型的转换器
from sklearn.pipeline import Pipeline # 用于创建机器学习工作流
from sklearn.linear_model import LinearRegression, Ridge, Lasso # 线性回归模型
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor # 集成学习模型
from sklearn.metrics import mean_squared_error, r2_score # 用于模型评估的指标
import warnings
warnings.filterwarnings('ignore') # 忽略警告信息
# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# 加载数据
# 根据实际情况修改路径
# 数据集下载地址:https://archive.ics.uci.edu/dataset/275/bike+sharing+dataset
hour_data = pd.read_csv('hour.csv') # 读取小时级别的共享单车数据
# 查看数据基本信息
print("数据集基本信息:")
print(f"数据集形状:{hour_data.shape}") # 显示数据集的行数和列数
print("\n数据集前5行:")
print(hour_data.head()) # 显示数据集的前5行
print("\n数据集描述性统计:")
print(hour_data.describe()) # 显示数据的统计摘要(均值、标准差、最小值等)
# 检查缺失值
print("\n缺失值检查:")
print(hour_data.isnull().sum()) # 计算每列的缺失值数量
2、特征工程精细化处理
-
剔除无预测价值的冗余列('instant'、'dteday')
-
规避数据泄露陷阱(移除'casual'和'registered',因为 cnt = casual + registered)
-
明确区分特征矩阵(X)和目标变量(y='cnt')
-
精准识别分类特征与数值特征
-
构建专业预处理管道:
- 对数值型特征实施标准化处理(StandardScaler)
- 对分类型特征执行独热编码(OneHotEncoder,设置 drop='first' 避免共线性问题)
# 数据预处理
# 删除不需要的列,如'instant'(只是记录ID)和'dteday'(日期,已经有了年、月、日等特征)
# 如果使用'casual'和'registered'预测'cnt',会有数据泄露,因为cnt = casual + registered
X = hour_data.drop(['instant', 'dteday', 'casual', 'registered', 'cnt'], axis=1) # 提取特征
y = hour_data['cnt'] # 提取目标变量(自行车租借量)
# 特征工程
# 分类特征和数值特征
# 分类特征:需要进行独热编码的特征
categorical_features = ['season', 'yr', 'mnth', 'hr', 'holiday', 'weekday', 'workingday', 'weathersit']
# 数值特征:需要进行标准化的特征
numerical_features = ['temp', 'atemp', 'hum', 'windspeed']
# 创建预处理Pipeline
# ColumnTransformer用于对不同类型的特征应用不同的转换
preprocessor = ColumnTransformer(
transformers=[
('num', StandardScaler(), numerical_features), # 对数值特征进行标准化
('cat', OneHotEncoder(drop='first'), categorical_features) # 对分类特征进行独热编码,drop='first'避免共线性
])
3、科学的数据分割
- 采用 7:3 比例划分训练集和测试集
- 设置 random_state 参数确保实验可重复性
# 数据分割
# 按照要求,将数据分成70%的训练集和30%的测试集,random_state确保结果可复现
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0, test_size=0.3)
4、深入的探索性数据分析
- 绘制相关性热力图,揭示特征间的内在联系
- 分析租借量随时间维度的变化规律(小时、工作日、月份)
- 探究不同天气状况对租借行为的影响
- 研究温度与租借量的关系曲线
# 数据探索性分析
# 1. 相关性热力图 - 用于分析数值特征之间的相关关系
plt.figure(figsize=(12, 10)) # 设置图的大小
correlation = hour_data[numerical_features + ['cnt']].corr() # 计算相关系数矩阵
sns.heatmap(correlation, annot=True, cmap='coolwarm', fmt=".2f") # 绘制热力图,annot=True显示具体数值
plt.title('特征相关性热力图', fontsize=15) # 添加标题
plt.savefig('相关性热力图.png', dpi=300, bbox_inches='tight') # 保存图片
plt.show() # 显示图片
# 2. 租借量随时间变化图 - 分析租借量在不同时间维度的变化模式
fig, axes = plt.subplots(3, 1, figsize=(12, 15)) # 创建3个子图
# 按小时变化 - 分析一天内不同时间的租借模式
hour_group = hour_data.groupby('hr')['cnt'].mean() # 按小时分组并计算平均租借量
axes[0].plot(hour_group.index, hour_group.values, '-o', linewidth=2) # 绘制折线图
axes[0].set_title('不同小时的平均租借量', fontsize=14)
axes[0].set_xlabel('小时')
axes[0].set_ylabel('平均租借量')
axes[0].grid(True) # 添加网格线
# 按工作日变化 - 分析一周内不同日期的租借模式
weekday_group = hour_data.groupby('weekday')['cnt'].mean() # 按工作日分组并计算平均租借量
axes[1].bar(weekday_group.index, weekday_group.values, color='skyblue') # 绘制柱状图
axes[1].set_title('不同工作日的平均租借量', fontsize=14)
axes[1].set_xlabel('星期几 (0=周日)')
axes[1].set_ylabel('平均租借量')
axes[1].set_xticks(range(7))
axes[1].grid(True)
# 按月份变化 - 分析一年内不同月份的租借模式
month_group = hour_data.groupby('mnth')['cnt'].mean() # 按月份分组并计算平均租借量
axes[2].plot(month_group.index, month_group.values, '-o', color='green', linewidth=2) # 绘制折线图
axes[2].set_title('不同月份的平均租借量', fontsize=14)
axes[2].set_xlabel('月份')
axes[2].set_ylabel('平均租借量')
axes[2].set_xticks(range(1, 13))
axes[2].grid(True)
plt.tight_layout() # 调整子图之间的间距
plt.savefig('租借量随时间变化图.png', dpi=300, bbox_inches='tight')
plt.show()
# 3. 天气对租借量的影响 - 分析不同天气条件下的租借模式
plt.figure(figsize=(10, 6))
weather_group = hour_data.groupby('weathersit')['cnt'].mean() # 按天气状况分组并计算平均租借量
plt.bar(weather_group.index, weather_group.values, color='orange') # 绘制柱状图
plt.title('不同天气状况下的平均租借量', fontsize=14)
plt.xlabel('天气状况 (1=晴朗, 2=多云, 3=小雨/雪, 4=大雨/雪)')
plt.ylabel('平均租借量')
plt.xticks([1, 2, 3, 4])
plt.grid(axis='y')
plt.show()
# 4. 温度和租借量的关系 - 分析温度对租借量的影响
plt.figure(figsize=(10, 6))
plt.scatter(hour_data['temp'], hour_data['cnt'], alpha=0.5) # 绘制散点图,alpha控制透明度
plt.title('温度与租借量的关系', fontsize=14)
plt.xlabel('标准化温度')
plt.ylabel('租借量')
plt.grid(True)
plt.show()
5、多模型构建与性能评估
-
搭建多种回归算法模型:
- 线性回归(基准模型)
- 岭回归(L2正则化,控制过拟合)
- Lasso回归(L1正则化,实现特征选择)
- 随机森林回归(集成学习,提升稳定性)
- 梯度提升回归(渐进优化,提高精度)
-
全方位评估模型表现:
- 计算均方误差(MSE),衡量预测误差大小
- 测量决定系数(R²),判断拟合优度
-
对比分析不同模型性能并制作可视化图表
# 构建和评估模型
# 创建不同类型的回归模型
models = {
'线性回归': LinearRegression(), # 基本的线性回归模型
'岭回归': Ridge(alpha=1.0), # 带L2正则化的线性回归
'Lasso回归': Lasso(alpha=0.1), # 带L1正则化的线性回归,可以进行特征选择
'随机森林回归': RandomForestRegressor(n_estimators=100, random_state=42), # 基于决策树的集成学习方法
'梯度提升回归': GradientBoostingRegressor(n_estimators=100, random_state=42) # 另一种强大的集成学习方法
}
# 结果保存
results = {} # 用字典存储每个模型的结果
# 训练并评估模型
for name, model in models.items():
# 创建Pipeline包含预处理和模型 - Pipeline可以将数据预处理和模型训练组合在一起
pipeline = Pipeline(steps=[
('preprocessor', preprocessor), # 第一步:数据预处理
('model', model) # 第二步:模型训练
])
# 训练模型
pipeline.fit(X_train, y_train)
# 在测试集上进行预测
y_pred = pipeline.predict(X_test)
# 评估模型性能
mse = mean_squared_error(y_test, y_pred) # 计算均方误差
r2 = r2_score(y_test, y_pred) # 计算决定系数R²
# 保存结果
results[name] = {
'model': pipeline,
'mse': mse,
'r2': r2,
'predictions': y_pred
}
# 打印评估结果
print(f"\n{name}:")
print(f"均方误差 (MSE): {mse:.2f}")
print(f"决定系数 (R²): {r2:.2f}")
# 模型性能比较
plt.figure(figsize=(12, 6))
# MSE比较 - 均方误差越小越好
plt.subplot(1, 2, 1) # 创建左侧子图
names = list(results.keys())
mse_values = [results[name]['mse'] for name in names]
plt.bar(names, mse_values, color='coral') # 绘制柱状图
plt.title('各模型MSE比较')
plt.ylabel('均方误差 (MSE)')
plt.xticks(rotation=45) # 旋转x轴标签
# R²比较 - 决定系数越接近1越好
plt.subplot(1, 2, 2) # 创建右侧子图
r2_values = [results[name]['r2'] for name in names]
plt.bar(names, r2_values, color='skyblue') # 绘制柱状图
plt.title('各模型R²比较')
plt.ylabel('决定系数 (R²)')
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('模型性能比较.png', dpi=300, bbox_inches='tight')
plt.show()
# 找出表现最佳的模型 - 根据R²值
best_model_name = max(results, key=lambda k: results[k]['r2']) # 选择R²值最高的模型
print(f"\n表现最佳的模型是: {best_model_name}")
print(f"MSE: {results[best_model_name]['mse']:.2f}")
print(f"R²: {results[best_model_name]['r2']:.2f}")
# 最佳模型预测结果可视化 - 预测值与实际值的对比
plt.figure(figsize=(10, 6))
plt.scatter(y_test, results[best_model_name]['predictions'], alpha=0.5) # 散点图
plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--') # 添加对角线,表示完美预测
plt.title(f'{best_model_name}:预测值 vs 实际值', fontsize=14)
plt.xlabel('实际值')
plt.ylabel('预测值')
plt.grid(True)
plt.savefig('预测vs实际值比较图.png', dpi=300, bbox_inches='tight')
plt.show()
6、特征重要性深度解析
- 针对树模型(随机森林、梯度提升)提取特征重要性指标
- 可视化展示关键特征,并进行专业解读
- 识别影响共享单车需求的核心因素
# 特征重要性分析(仅对随机森林和梯度提升有效)
# 这些模型可以计算每个特征对预测的重要性
if 'Random Forest' in best_model_name or 'Gradient Boosting' in best_model_name:
# 获取特征名称(包括独热编码后的特征)
preprocessor.fit(X)
# 构建特征名称列表,包括数值特征和独热编码后的分类特征
feature_names = numerical_features + [f"{col}_{val}" for i, col in enumerate(categorical_features)
for val in preprocessor.transformers_[1][1].categories_[i][1:]]
# 获取特征重要性
if 'Random Forest' in best_model_name:
importance = results[best_model_name]['model']['model'].feature_importances_
else:
importance = results[best_model_name]['model']['model'].feature_importances_
# 只选择前15个最重要的特征展示
indices = np.argsort(importance)[-15:] # 获取重要性排名前15的特征索引
plt.figure(figsize=(10, 8))
plt.barh(range(len(indices)), importance[indices], align='center') # 水平柱状图
plt.yticks(range(len(indices)), [feature_names[i] for i in indices]) # 设置y轴标签为特征名称
plt.title('特征重要性分析', fontsize=14)
plt.xlabel('重要性')
plt.tight_layout()
plt.savefig('特征重要性.png', dpi=300, bbox_inches='tight')
plt.show()
7、超参数精细优化
- 运用 GridSearchCV 技术对表现最优的模型进行参数网格搜索
- 优化关键参数组合(如 n_estimators、max_depth、learning_rate 等)
- 通过交叉验证确保模型泛化能力与稳定性
# 超参数优化(以梯度提升模型为例)
# 超参数是模型的配置,需要在训练前设置,通过优化可以提高模型性能
print("\n开始超参数优化...")
# 定义参数网格 - 要尝试的不同超参数组合
param_grid = {
'model__n_estimators': [100, 200], # 弱学习器(决策树)的数量
'model__max_depth': [3, 5, 7], # 决策树的最大深度
'model__learning_rate': [0.05, 0.1, 0.2], # 学习率
'model__min_samples_split': [5, 10] # 内部节点需要的最小样本数
}
# 创建Pipeline
pipeline = Pipeline(steps=[
('preprocessor', preprocessor),
('model', GradientBoostingRegressor(random_state=42))
])
# 使用网格搜索进行超参数优化
# GridSearchCV会尝试所有可能的参数组合,并通过交叉验证选择最佳组合
grid_search = GridSearchCV(pipeline, param_grid, cv=3, scoring='r2', n_jobs=-1)
grid_search.fit(X_train, y_train) # 执行网格搜索
# 输出最佳参数和性能
print(f"最佳参数: {grid_search.best_params_}")
print(f"最佳交叉验证分数 (R²): {grid_search.best_score_:.4f}")
8、最终模型全面评估
- 对比分析优化前后的性能提升幅度
- 绘制预测值与实际值对比曲线,直观展示预测效果
- 总结模型优势与局限,提出应用建议
# 使用最佳模型进行预测和评估
best_pipeline = grid_search.best_estimator_ # 获取最佳模型
y_pred_best = best_pipeline.predict(X_test) # 使用最佳模型进行预测
mse_best = mean_squared_error(y_test, y_pred_best) # 计算MSE
r2_best = r2_score(y_test, y_pred_best) # 计算R²
print(f"优化后的均方误差 (MSE): {mse_best:.2f}")
print(f"优化后的决定系数 (R²): {r2_best:.2f}")
# 可视化优化前后的性能对比
plt.figure(figsize=(10, 6))
models_comp = ['优化前的梯度提升', '优化后的梯度提升']
mse_comp = [results['梯度提升回归']['mse'], mse_best]
r2_comp = [results['梯度提升回归']['r2'], r2_best]
# 左侧子图:MSE对比
plt.subplot(1, 2, 1)
plt.bar(models_comp, mse_comp, color=['coral', 'darkred'])
plt.title('MSE对比')
plt.ylabel('均方误差 (MSE)')
plt.xticks(rotation=45)
# 右侧子图:R²对比
plt.subplot(1, 2, 2)
plt.bar(models_comp, r2_comp, color=['skyblue', 'darkblue'])
plt.title('R²对比')
plt.ylabel('决定系数 (R²)')
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('优化前后性能对比.png', dpi=300, bbox_inches='tight')
plt.show()
# 可视化优化后的模型预测结果
plt.figure(figsize=(10, 6))
plt.scatter(y_test, y_pred_best, alpha=0.5) # 绘制散点图
plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--') # 添加对角线
plt.title('优化后模型:预测值 vs 实际值', fontsize=14)
plt.xlabel('实际值')
plt.ylabel('预测值')
plt.grid(True)
plt.savefig('优化后预测vs实际值比较图.png', dpi=300, bbox_inches='tight')
plt.show()
三、核心概念详解
【数据泄露】
数据泄露是机器学习中的一个严重问题,指模型训练过程中使用了实际预测时无法获取的信息。在共享单车预测案例中: 若使用 'casual'(临时用户数)和 'registered'(注册用户数)来预测 'cnt'(总用户数),就会导致数据泄露,因为 cnt = casual + registered。这种做法会:
- 使模型获得不切实际的高精度,因为它实际上是在使用计算公式而非学习真实模式
- 导致模型在实际应用中失效,因为预测时我们并不知道 casual 和 registered 的值
【独热编码与标准化】
这两种技术针对不同类型的特征进行处理: 独热编码 (One-Hot Encoding):
-
专门用于处理分类特征(如季节、天气状况等)
-
将每个类别值转换为单独的二进制特征(0或1)
-
例如,原始季节数据 [1(春), 2(夏), 3(秋), 4(冬)] 转换为:
- season_spring = [1, 0, 0, 0]
- season_summer = [0, 1, 0, 0]
- season_fall = [0, 0, 1, 0]
- season_winter = [0, 0, 0, 1] 标准化 (Standardization):
-
专用于处理数值特征,将其转换为均值为0、方差为1的分布
-
计算公式:z = (x - μ) / σ
-
主要优势:
- 使不同量级的特征具有可比性
- 减少特征因取值范围差异导致的权重不平衡
- 加速梯度下降等优化算法的收敛速度
【共线性问题】
在独热编码时使用 drop='first' 参数可有效避免共线性问题:
-
共线性指特征间存在线性相关性,会影响模型性能
-
完全独热编码会创建线性依赖关系(如四季编码列的总和永远为1)
-
使用 drop='first' 后,会删除第一个类别对应的列:
- 例如只保留 season_summer, season_fall, season_winter
- 当三个列都为0时,隐含表示为春季
-
这种处理减少了特征数量,避免了共线性,同时不损失信息
【随机状态控制】
random_state 参数在机器学习实验中至关重要:
- 确保结果可重现性,固定随机种子后每次运行得到相同结果
- 适用于数据集划分、模型参数初始化、随机算法等场景
- 有助于准确比较不同模型性能、调试问题及科学实验
- 例如:train_test_split(X, y, test_size=0.3, random_state=42)
四、项目亮点与应用价值
本项目通过系统化的机器学习流程,构建了高精度的共享单车需求预测模型。这套方法不仅可应用于共享单车领域,还可迁移至其他时间序列预测场景,如共享汽车、网约车需求预测等。通过数据驱动的决策支持,企业能够优化资源配置,提升服务质量,最终实现运营效率与用户体验的双重提升。
想进一步提升模型性能?可尝试引入更多外部数据源(如天气预报、大型活动信息、交通状况等),或探索深度学习方法如LSTM等时序模型,挖掘数据中更复杂的非线性关系。
更多干货内容,欢迎关注
如果您对机器学习与数据科学感兴趣,想了解更多实战案例与技术干货,欢迎关注我们的公众号【码途有你】,定期分享前沿算法、项目实战和行业应用,助您在AI时代脱颖而出。