美文网首页
07基于LSTM模型实现共享自行车需求预测

07基于LSTM模型实现共享自行车需求预测

作者: Jachin111 | 来源:发表于2023-02-14 16:05 被阅读0次

    数据

    # 导入库
    import pandas as pd
    import numpy as np
    
    import seaborn as sns
    import matplotlib.pyplot as plt
    sns.set(context="notebook",style="darkgrid",palette="deep",font="sans-serif",font_scale=1,color_codes=True)
    
    import warnings
    warnings.filterwarnings("ignore")
    
    # 读取数据
    data = pd.read_csv("london_merged.csv")
    data.head()
    
    image.png
    data.shape
    
    image.png
    data.dtypes
    
    image.png
    data.isnull().sum()
    
    image.png

    字段含义

    timestamp:用于将数据分组的时间戳字段
    cnt:新自行车份额的计数
    t1:以C为单位的实际温度
    t2:C中的温度“感觉像”,主观感受
    hum:湿度百分比
    windspeed:风速,以km / h为单位
    weathercode:天气类别;(具体的取值见下图中的最后)
    isholiday:布尔字段,1-假期,0-非假期
    isweekend:布尔字段,如果一天是周末,则为1
    Season:类别气象季节:0-春季;1-夏;2-秋;3-冬

    TensorFlow基本信息

    # # 查看tensorflow的GPU信息
    # import tensorflow.compat.v1 as tf
    
    # config = tf.ConfigProto()
    # config.gpu_options.allow_growth = True
    # tf.test.gpu_device_name()
    
    # 查看tensorflow版本
    import tensorflow as tf
    
    tf.__version__
    

    特征工程

    # 数据信息
    # 查看字段信息
    data.info(memory_usage="deep")
    
    image.png
    # 时间字段处理
    # 1.将时间戳转换成时间类型
    data["timestamp"] = pd.to_datetime(data["timestamp"])
    data.dtypes
    
    image.png
    # 2.转成索引:使用set_index方法将timestamp属性转换成索引
    data = data.set_index("timestamp")
    data.head()
    
    image.png
    # 3.提取时、一个月中的第几天、第几周、月份等信息
    # 提取时间相关的多个信息,同时查看数据的shape
    data["hour"] = data.index.hour
    data["day_of_month"] = data.index.day
    data["day_of_week"] = data.index.dayofweek
    data["month"] = data.index.month
    
    data.columns
    
    image.png
    data.shape
    
    image.png
    # 相关系数分析
    # 1.相关系数求出绝对值
    corr_matrix = data.corr().abs()
    corr_matrix
    
    image.png
    # 2.筛选两个属性之间的相关系数大于0.8
    high_corr_var = np.where(corr_matrix > 0.8)
    high_corr_var = [(corr_matrix.columns[x], corr_matrix.columns[y]) for x,y in zip(*high_corr_var) if x != y and x < y]
    high_corr_var
    
    image.png

    数据EDA

    # 相关系数热力图
    plt.figure(figsize=(16, 6))
    
    sns.heatmap(data.corr(),
               cmap="YlGnBu",  #色系
               square=True,  #方形
               linewidths=.2,
               center=0,
               linecolor="red")  #线条颜色
    
    plt.show()
    
    image.png

    通过热力图我们发现:t1和t2的相关系数是比较高的,和上面的“属性之间的系数大于0.8”的结论是吻合的

    # 空值判断
    data.isnull().sum()
    
    image.png

    文章中使用的方法是:基于热力图显示。图形中没有任何信息,表明数据是不存在空值的

    plt.figure(figsize=(16, 6))
    
    sns.heatmap(data.isnull(), cmap="viridis")
    
    plt.show()
    
    image.png
    # 需求量变化
    # 整体的需求量cnt随着时间变化的关系
    plt.figure(figsize=(15, 6))
    
    sns.lineplot(data=data,
                x=data.index,  #时间
                y=data.cnt)  #需求量
    
    plt.xticks(rotation=90)
    
    image.png

    从上面的图形,我们能够看到整体日期下的需求量变化情况。

    # 按月采样resample
    # pandas中的采样函数使用的是resample,频率可以是天、周、月等
    df_by_month = data.resample("M").sum()
    df_by_month.head()
    
    image.png
    # 查看随着时间的变化,每月的需求量变化情况
    plt.figure(figsize=(16, 6))
    
    sns.lineplot(data=df_by_month,
                x=df_by_month.index,
                y=df_by_month.cnt,
                color="red")
    
    plt.xticks(rotation=90)
    plt.show()
    
    image.png

    可以从图中观察到以下3点结论:
    1.年初到7、8月份需求量呈现上升趋势
    2.差不多在8月份达到一定的峰值
    3.8月份过后需求量开始降低

    # 每小时需求量
    plt.figure(figsize=(16, 6))
    
    sns.pointplot(data=data,
                 x=data.hour,  #小时
                 y=data.cnt,  #需求量
                 color="red")
    
    plt.show()
    
    image.png
    # 每月的需求量对比
    plt.figure(figsize=(16, 6))
    
    sns.pointplot(data=data,
                 x=data.month,
                 y=data.cnt,
                 color="red")
    
    plt.show()
    
    image.png

    明显的结论:7月份是需求的高峰期

    # 按照星期统计
    plt.figure(figsize=(16, 6))
    
    sns.pointplot(data=data,
                 x=data.day_of_week,
                 y=data.cnt,
                 color="black")
    
    plt.show()
    
    image.png

    从图中观察到:
    周1到周五的需求是明显高于周末两天;
    同时在周五的时候已经呈现下降趋势

    # 按照自然日
    plt.figure(figsize=(16, 6))
    
    sns.lineplot(data=data,
                x=data.day_of_month,  #一个月中的某天
                y=data.cnt,
                color="r")
    
    plt.show()
    
    image.png

    3点结论:
    前10天需求量在逐步增加
    中间10天存在一定的小幅波动
    最后10天波动加大,呈现下降趋势

    多个维度下的可视化效果

    # 基于是否节假日下的小时
    plt.figure(figsize=(16, 6))
    
    sns.pointplot(data=data,
                 x=data.hour,  #按照小时统计
                 y=data.cnt,
                 hue=data.is_holiday)  #节假日分组
    
    plt.show()
    
    image.png

    通过上面图形呈现的结果;
    非节假日下(is_holiday=0):在8点和下午的17、18点是用车的高峰期,恰好是上下班的时间点
    到了节假日(1)的情况下:下午的2-3点才是真正的用车高峰期

    # 基于是否节假日的月份
    plt.figure(figsize=(16, 6))
    
    sns.pointplot(data=data,
                 x=data.month,
                 y=data.cnt,
                 hue=data.is_holiday)
    
    plt.show()
    
    image.png

    在非节假日,7月份达到了用车的高峰期

    # 按照季度统计
    plt.figure(figsize=(16, 6))
    
    sns.pointplot(data=data,
                 y=data.cnt,
                 x=data.month,
                 hue=data.season)  #季度分组
    
    plt.show()
    
    image.png

    从上图中观察到:第3个季度(6–7-8月份)才是用车需求量最多的时候

    # 季度+是否节假日
    plt.figure(figsize=(16, 6))
    
    sns.countplot(data=data,
                 x=data.season,
                 hue=data.is_holiday)
    
    plt.show()
    
    image.png

    从1-2-3-4季度来看,非节假日中的整体需求量1和2季度是稍高于0和3季度;而节假日中,0-3季度则存在一定的需求

    # 是否周末+小时
    plt.figure(figsize=(16, 6))
    
    sns.lineplot(data=data,
                x=data.hour,
                y=data.cnt,
                hue=data.is_weekend)
    
    plt.show()
    
    image.png

    非周末(0):仍然是上午的7-8点和下午的17-18点是用车高峰期
    周末(1):下午的14-15点才是高峰期

    # 季度+小时
    plt.figure(figsize=(16, 6))
    
    sns.pointplot(data=data,
                 x=data.hour,
                 y=data.cnt,
                 hue=data.season)
    
    plt.show()
    
    image.png

    分季度查看每个小时的需求量:整体的趋势大体是相同的,都是在8点左右达到上午的高封期,下午的17-18点(下班的时候)达到另一个高封期

    天气因素

    # 湿度和需求量关系
    plt.figure(figsize=(16, 6))
    
    sns.pointplot(data=data,
                 x=data.hum,
                 y=data.cnt,
                 color="black")
    
    plt.xticks(rotation=90)
    plt.show()
    
    image.png

    可以看到:空气空气湿度越大,整体的需求量是呈现下降趋势

    # 风速和需求量
    plt.figure(figsize=(16, 6))
    
    sns.pointplot(data=data,
                 x=data.wind_speed,
                 y=data.cnt)
    
    plt.xticks(rotation=90)
    plt.show()
    
    image.png

    风速对需求量的影响:
    在风速为25.5的时候存在一个局部峰值
    风速偏高或者偏低的时候需求都有所降低

    # 不同天气情况weather_code
    plt.figure(figsize=(16, 6))
    
    sns.pointplot(data=data,
                 x=data.weather_code,
                 y=data.cnt)
    
    plt.xticks(rotation=90)
    plt.show()
    
    image.png

    可以看到在scattered coluds(weather_code=2)情况下,需求量是最大的

    # 天气情况+小时
    plt.figure(figsize=(16, 6))
    
    sns.pointplot(data=data,
                 x=data.hour,
                 y=data.cnt,
                 hue=data.weather_code)  #分天气统计
    
    plt.show()
    
    image.png

    从上午中观察到:不同的天气对小时需求量的趋势影响不大,仍然是在上下班高峰期的时候需求量最大,说明打工人上班出行几乎不受天气影响!!!

    # 自然天+天气情况
    plt.figure(figsize=(16, 6))
    
    sns.countplot(data=data,
                 x=data.day_of_week,  #一周中的第几天
                 hue=data.weather_code,  #天气情况
                 palette="viridis")
    
    plt.legend(loc="best")
    plt.show()
    
    image.png

    从上图中观察到:
    1.不同的星期日期,code=1下的需求量都是最大的
    2.礼拜1到礼拜5:满足code=1 > 2 > 3 > 7 > 4 的需求量
    3.到礼拜6和礼拜天:大家出行的时候对天气关注影响偏低,除去code=1,其他天气情况的需求差距也在缩小!

    箱型图

    # 按小时
    plt.figure(figsize=(16, 6))
    
    sns.boxplot(data=data,
               x=data.hour,
               y=data.cnt)
    
    plt.show()
    
    image.png

    从箱型图的分布观察到:两个重要的时间段:上午7-8点和下午的17-18点

    # 每周星期几
    plt.figure(figsize=(16, 6))
    
    sns.boxplot(data=data,
               x=data["day_of_week"],
               y=data.cnt)
    
    plt.show()
    
    image.png

    在基于星期的箱型图中,礼拜三的时候存在一定的用车高峰期

    # 月的自然天
    plt.figure(figsize=(16, 6))
    
    sns.boxplot(data=data,
               x=data["day_of_month"],
               y=data.cnt)
    
    plt.show()
    
    image.png

    在基于自然日的情况下,9号的存在高峰期

    # 按月
    plt.figure(figsize=(16, 6))
    
    sns.boxplot(data=data,
               x=data["month"],
               y=data.cnt)
    
    plt.show()
    
    image.png

    7-8月份存在一定的需求高峰期,两侧月份的需求相对较少些

    # 是否节假日+月的天
    plt.figure(figsize=(16, 6))
    
    sns.boxplot(data=data,
               x=data["day_of_month"],
               y=data.cnt,
               hue=data["is_holiday"])
    
    plt.show()
    
    image.png

    数据预处理

    # 切分数据
    # 按照9:1的比例来切分数据
    from sklearn.model_selection import train_test_split
    
    train, test = train_test_split(data, test_size=0.1, random_state=0)
    print(train.shape)
    print(test.shape)
    
    image.png
    # 数据归一化
    from sklearn.preprocessing import MinMaxScaler
    
    # 实例化对象
    scaler = MinMaxScaler()
    
    # 部分字段的拟合
    num_col = ["t1", "t2", "hum", "wind_speed"]
    trans_1 = scaler.fit(train[num_col].to_numpy())
    
    # 训练集转换
    train.loc[:, num_col] = trans_1.transform(train[num_col].to_numpy())
    # 测试集转换
    test.loc[:, num_col] = trans_1.transform(test[num_col].to_numpy())
    
    # 对标签cnt的归一化
    cnt_scaler = MinMaxScaler()
    # 数据拟合
    trans_2 = cnt_scaler.fit(train[["cnt"]])
    # 数据转化
    train["cnt"] = trans_2.transform(train[["cnt"]])
    test["cnt"] = trans_2.transform(test[["cnt"]])
    

    训练集和测试集

    # 用于显示进度条
    from tqdm import tqdm_notebook as tqdm
    tqdm().pandas()
    
    def prepare_data(X, y, time_steps=1):
        Xs = []
        Ys = []
        
        for i in tqdm(range(len(X) - time_steps)):
            a = X.iloc[i:(i + time_steps)].to_numpy()
            Xs.append(a)
            Ys.append(y.iloc[i + time_steps])
            
        return np.array(Xs), np.array(Ys)
    
    steps = 24
    
    X_train, y_train = prepare_data(train, train.cnt, time_steps=steps)
    X_test, y_test = prepare_data(test, test.cnt, time_steps=steps)
    
    print(X_train.shape)
    print(X_test.shape)
    print(y_train.shape)
    print(y_test.shape)
    
    image.png

    LSTM建模

    # 导入库
    from keras.preprocessing import sequence
    from keras.models import Sequential
    from keras.layers import Dense, Dropout, LSTM, Bidirectional
    
    # 实例化对象并拟合建模
    model = Sequential()
    model.add(Bidirectional(LSTM(128, input_shape=(X_train.shape[1], X_train.shape[2]))))
    model.add(Dropout(0.2))
    model.add(Dense(1, activation="sigmoid"))
    model.compile(optimizer="adam", loss="mse")
    
    # 模型准备
    # 传入训练集的数据后,进行数据的拟合建模过程
    with tf.device("/GPU:0"):
        prepared_model = model.fit(X_train,
                                  y_train,
                                  batch_size=32,
                                  epochs=100,
                                  validation_data=(X_test, y_test))
    
    # 均方差和Epoch的关系
    plt.plot(prepared_model.history["loss"], label="loss")
    plt.plot(prepared_model.history["val_loss"], label="val_loss")
    
    plt.legend(loc="best")
    plt.xlabel("No. Of Epochs")
    plt.ylabel("mse score")
    
    image.png

    需求量预测

    # 生成真实值和预测值
    pred = model.predict(X_test)
    y_test_inv = cnt_scaler.inverse_transform(y_test.reshape(-1, 1))
    pred_inv = cnt_scaler.inverse_transform(pred)
    pred_inv
    
    image.png
    # 绘图比较,将测试集转变后的值和基于模型的预测值进行绘图比较
    plt.figure(figsize=(16, 6))
    
    plt.plot(y_test_inv.flatten(), marker=".", label="actual")
    plt.plot(pred_inv.flatten(), marker=".", label="prediction",  color="r")
    
    plt.legend(loc="best")
    plt.show()
    
    image.png

    注意到,我们的模型仅预测未来的一个点。话虽如此,它仍做得很好。虽然我们的模型不能真正捕捉到极值,但它在预测(理解)一般模式方面还是做得很好

    # 生成数据
    # 将测试集的真实值和预测值进行对比,通过两个指标来进行评估
    # 原文方法
    y_test_actual = cnt_scaler.inverse_transform(y_test.reshape(-1, 1))
    y_test_pred = cnt_scaler.inverse_transform(pred)
    
    arr_1 = np.array(y_test_actual)
    arr_2 = np.array(y_test_pred)
    
    actual = pd.DataFrame(data=arr_1.flatten(), columns=["actual"])
    predicted = pd.DataFrame(data=arr_2.flatten(), columns=["predicted"])
    
    final = pd.concat([actual, predicted], axis=1)
    final.head()
    
    image.png
    # 个人方法
    y_test_actual = cnt_scaler.inverse_transform(y_test.reshape(-1, 1))
    y_test_pred = cnt_scaler.inverse_transform(pred)
    final = pd.DataFrame({"actual":y_test_actual.flatten(), "pred":y_test_pred.flatten()})
    final.head()
    
    image.png
    # 模型评价,通过mse和r2_score指标来评估模型
    from sklearn.metrics import mean_squared_error, r2_score
    
    rmse = np.sqrt(mean_squared_error(final.actual, final.pred))
    r2 = r2_score(final.actual, final.pred)
    
    print("rmse is : ", rmse)
    print("----------")
    print("r2_score is : ", r2)
    
    image.png
    # 绘图比较,对比真实值和预测值
    plt.figure(figsize=(16 ,6))
    
    plt.plot(final.actual, marker=".", label="Actual label")
    plt.plot(final.pred, marker=".", label="predicted label")
    
    plt.legend(loc="best")
    plt.show()
    
    image.png

    如你所见,我使用双向 LSTM 来训练我们的模型,并且我们的模型运行良好。我们的模型能够捕捉趋势而不是捕捉极值,这是一件非常好的事情。所以,我们可以说整体表现不错

    相关文章

      网友评论

          本文标题:07基于LSTM模型实现共享自行车需求预测

          本文链接:https://www.haomeiwen.com/subject/bmpekdtx.html