基于Python预测出租车出行费用(Taxi trip fare prediction)

        本文将通过Python对Kaggle中的数据集进行分析。本文会包含所有代码,尽可能多的使用有关可视化的函数以及数据分析的模型。  

一、项目简介

1.1 数据集简介

数据集来源:本文基于Kaggle的数据集,链接:Taxi trip fare prediction | Kaggle

数据集内容:下图为原文中的介绍

中文说明:

 trip_duration行驶时间
distance_traveled行驶距离
num_of_passengers乘客数量
fare行驶费用
tip司机小费
miscellaneous_fees额外费用
total_fare费用总和
surge_applied动态定价

1.2研究内容

        预测费用总和。在实际应用上,可以与打车平台合作,用户给定目的地、出发时间、乘客人数。通过知道行车距离、预计的行驶时间、乘客人数来预测行车费用返回给用户,用户可根据预测的行车费用来确定是否打车。

二、数据处理与可视化

2.1 数据预处理

2.1.1 导包

包含数据处理与可视化的所有包,其中可视化中文代码根据自己情况编写,这里是苹果系统

#导包
import pandas as pd
#可视化
import matplotlib.pyplot as plt
import seaborn as sns
#可视化中文
from matplotlib import font_manager
plt.rcParams['font.sans-serif']=['Heiti TC']

2.1.2 导入数据

登录上文中的网站下载原始数据集。

文件初始状态:

用过Pandas包的方法导入原文件

#导入原文件
data = pd.read_csv('train.csv')

2.1.3 数据清洗

使用Pandas中针对DateFrame中的分析函数

#数据集概括
data.info()

由输出结果,我们可以发现一共有209673行数据且没有空值

data.describe()

         为了后文更加清楚,我将列名转化为了中文,这里大家自由选择即可,如果没有转化下文中涉及到列名的记得自己变换

#列名转中文
names = ['行驶时间','行驶距离','乘客数量','行驶费用','司机小费','额外费用','费用总和','动态定价']
data.columns = names
data

 2.2 数据可视化

为了后文构建模型更加准确,这里我们先进行数据可视化观察数据的大致特征

我们先简单查看我们需要预测的费用总和的情况

#描述统计
data['费用总和'].describe()

  绘制费用总和的直方图查看分布情况,bins设置柱子(箱子)的数量

#费用总和
plt.hist(data['费用总和'],bins = 50)
plt.show()

 由于数据中存在远超均值的数据,导致X轴拉伸过长,无法精确观察数据

#划分
df = data[data['费用总和'] < 800]
df['费用总和'].describe()

这里我们用800划分,可以发现数量变为了209320个,少了300多个数据,对数据整体影响不大

plt.hist(df['费用总和'], bins=30)
plt.xlabel('费用总和')
plt.title('费用总和(小于800)')
plt.show()

这里我们可以发现费用总和集中在0-200之间

        按照相同的方法,可绘制其他变量的分布情况,有些变量存在极大的数值,例如上文中的费用总和,可进行筛选后绘图来绘制出更清楚的图。

三、模型构建

        通过查看数据集,我们可以发现费用总和等于行驶费用+司机小费+额外费用,因此我们的问题转换为预测行驶费用。此外,由于数据集数量过大(20w条),因此我这里只选择前2000条,大家可以根据自己情况进行选择,数据量越大后文模型计算时间越长。

data = data[0:2000]

3.1 模型构造准备

3.1.1 评估指标

        均方误差(Mean Squared Error,MSE):计算预测值与真实值之间差异的平方的平均值。MSE越小越好,它对较大的误差更敏感。MSE公式如下:

         Python中实现如下:

def Mse(result):
    #计算差异
    diff = result['真实值'] - result['预测值']
    #计算差异平方
    square_diff = np.square(diff)
    #计算总和
    sum_square_diff = np.sum(square_diff)
    #计算平均差异平方
    mse = sum_square_diff / len(result['真实值'])
    return mse

         MAPE(平均绝对百分比误差):由上文中我们可以发现数据中需要预测的费用方差较大,因此我选择了MAPE方法评估模型。此指标对相对误差敏感,不会因目标变量的全局缩放而改变,适合目标变量量纲差距较大的问题。计算公式如下:

         Python中实现如下:

#MAPE误差
def Mape(result):

    #删除包含0值的行
    result['真实值'].replace(0,None, inplace=True)
    result = result.dropna(subset=['真实值'],axis=0)
    
    # 计算MAPE误差
    mape = np.mean(np.abs((result['真实值'] - result['预测值']) / result['真实值'])) * 100
    return mape

        这里我们要考虑到一个问题,上文的数据集描述中行驶费用有等于0的情况,这就会导致MAPE计算中的分母为0,结果为inf。因此我们要先去除值为0的数据,这里去除0对数据整体无影响。在下文中我们将调用Mse和Mape函数计算误差,以选择最优模型。

3.1.2 划分为训练集和测试集

from sklearn.model_selection import train_test_split
#设置自变量和因变量
X = data[['行驶时间','行驶距离','乘客数量','动态定价']]
Y = data['行驶费用']
#划分训练集和测试集
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=666)

        这里我们设置了80%的数据是训练数据,20%的数据是测试数据,可自由设置。               

        random_state是种子,确保每次运行数据划分一致

3.2 线性回归

我们先导入使用线性回归模型中的所有包

#导包
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression

使用LinearRegression类构造模型,fit函数拟合模型 

#创建线性回归模型并拟合数据
model = LinearRegression()
model.fit(X_train, Y_train)
# 打印回归系数和截距
print("回归系数:", model.coef_)
print("截距:", model.intercept_)

继续通过模型预测

#使用训练好的模型进行预测
Y_pred = model.predict(X_test)

预测后我们要将其和真实值转化为一个DataFrame以进行评估

#转化为DF + 重设索引
Y_pred = pd.DataFrame(Y_pred).reset_index(drop=True)
Y_test = pd.DataFrame(Y_test).reset_index(drop=True)
#连接
result = pd.concat([Y_pred,Y_test], axis=1)
#重命名
names = ['预测值','真实值']
result.columns = names

计算MSE和MAPE值

#MSE误差
MSE = Mse(result)
print("MSE误差: {:.2f}".format(MSE))
#MAPE误差
MAPE = Mape(result)
print("MAPE误差: {:.2f}%".format(MAPE))

上图为预测的结果,同时通过调用上文设置评价指标中的函数计算MSE和MAPE值。

        MSE值为1447,MAPE值为16%。总体上看,模型较优。但同时我们需要考虑我们只选取了前2000条数据,原数据集的方差过大,方差越大此模型越不精准。随着数据的增多可能导致误差率提升。

3.3 决策树

我们先导入使用决策树模型中的所有包

from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeRegressor
from sklearn import metrics

 构造模型 

#创建决策树模型
model = DecisionTreeRegressor()

#在训练集上拟合模型
model.fit(X_train, Y_train)

#在测试集上进行预测
Y_pred = model.predict(X_test)

 通过构造的模型进行预测

#转化为DF + 重设索引
Y_pred = pd.DataFrame(Y_pred).reset_index(drop=True)
Y_test = pd.DataFrame(Y_test).reset_index(drop=True)
#连接
result = pd.concat([Y_pred,Y_test], axis=1)
names = ['预测值','真实值']
result.columns = names
result

 这里我们可以发现模型准确率有明显提升,再计算下MSE和MAPE值

 计算决策树模型的MAPE值为11.05%,相较于线性回归有了部分的提升。

3.4 神经网络

3.4.1 引言

在构造神经网络模型的时候,我们通常要考虑参数的设置,主要包括:

        1)隐藏层的数量和其中神经元的数量

        2)激活函数

        3)损失函数

        4)优化算法

        5)学习率

在接下来的模型构造中,我们会对我们的模型进行参数的设置,让模型的准确率提升

3.4.2 数据处理

首先导入构造模型的包

import tensorflow as tf
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler

划分数据集

#设置自变量和因变量
X = data[['行驶时间','行驶距离','乘客数量','动态定价']]
Y = data['行驶费用']
#划分训练集和测试集
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=666)

3.4.3 构建随机模型

        这里我们随机构造一个神经网络模型,查看其准确率。先构造一个随机参数的神经网络模型。神经元层数为2,神经元个数为32,迭代次数为200,一次输入数据量为32,采用Amad优化器。

model = tf.keras.Sequential()
model.add(tf.keras.layers.Dense(32, input_dim=4, activation='relu'))
model.add(tf.keras.layers.Dense(32, activation='relu'))
model.add(tf.keras.layers.Dense(1))

#创建优化器,并设置学习率
model.compile(loss='mean_squared_error', optimizer='adam')
model.fit(X_train, Y_train, epochs=100, batch_size=32, verbose=0)

#预测
Y_pred = model.predict(X_test)
#转化为DF + 重设索引
Y_pred = pd.DataFrame(Y_pred).reset_index(drop=True)
Y_test = pd.DataFrame(Y_test).reset_index(drop=True)
#连接
result = pd.concat([Y_pred,Y_test], axis=1)
names = ['预测值','真实值']
result.columns = names
#MSE误差
MSE = Mse(result)
print("MSE误差: {:.2f}".format(MSE))
#MAPE误差
MAPE = Mape(result)
print("MAPE误差: {:.2f}%".format(MAPE))

         通过查看其MSE和MAPE我们可以发现其误差为17.87%,随机设置的参数所得的结果不如线性回归和决策树。

3.4.4 调整参数

        在下文中我将通过控制变量的方法对模型构建仅改变一个参数,并通过计算预测结果的MSE和MAPE值确定最优模型。可以使用网格搜索,这里直接用for循环。
        先构造存储运行结果的DataFrame。包括神经元个数、损失函数、迭代次数、批量处理量、学习率、MSE、MAPE列。将每次构建模型的参数填入其中,随后构造模型预测数据,将预测数据的结果存入MSE、MAPE列。

df = {
    "神经元个数": [0],
    "损失函数": [0],
    "迭代次数":[0],
    "批量处理量":[0],
    "学习率":[0],
    "MSE":[0],
    "MAPE":[0]
}
res_all = pd.DataFrame(df)
res_all

此模型为控制多个变量达到最优情况时,所以用for循环让变量多个取不同值以避免此情况。

#神经元个数
for N_n in [32,64]:
    #迭代次数
    for epochs in [100,200,500,1000]:
        #输入数据量
        for batch_size in [32,64]:
            #学习率
            for learning_rate in [0.001,0.01,0.05]:
                 
                model = tf.keras.Sequential()
                model.add(tf.keras.layers.Dense(N_n, input_dim=4, activation='relu'))
                model.add(tf.keras.layers.Dense(N_n, activation='relu'))
                model.add(tf.keras.layers.Dense(1))
                
                #创建优化器,并设置学习率
                optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
                model.compile(loss='mean_squared_error', optimizer=optimizer)
                model.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, verbose=0)

                #预测
                Y_pred = model.predict(X_test)
                #转化为DF + 重设索引
                Y_pred = pd.DataFrame(Y_pred).reset_index(drop=True)
                Y_test = pd.DataFrame(Y_test).reset_index(drop=True)
                #连接
                result = pd.concat([Y_pred,Y_test], axis=1)
                names = ['预测值','真实值']
                result.columns = names

                #MSE误差
                MSE = Mse(result)
                #MAPE误差
                MAPE = Mape(result)

                #导入
                argument = {
                        "神经元个数": N_n,
                        "损失函数": 'MSE',
                        "迭代次数":epochs,
                        "批量处理量":batch_size,
                        "学习率":learning_rate,
                        "MSE":MSE,
                        "MAPE":MAPE}
                res_all.loc[len(res_all)] = argument      

        这里设置了神经元个数、迭代次数、一次输入的数据量和学习率。神经元个数取值为32、64,迭代次数为100、200、500、1000,一次输入数据量为32、64,学习率为0.01、0.01、0.05。这块可根据自己情况自由添加,但要注意运行时间的变化极大。
        最后将结果保存到res_all中。
        由于损失函数上文中有两种计算方式,一种是MSE,一种是MAPE。上个多变量神经网络是以MSE为损失函数的构造模型。以下为MAPE为损失函数的模型。

        先定义MAPE损失函数

#自定义MAPE损失函数
def loss_Mape(y_true, y_pred):
    #去除真实值为0的行
    mask = tf.not_equal(y_true, 0)  
    masked_true = tf.boolean_mask(y_true, mask)  
    masked_pred = tf.boolean_mask(y_pred, mask)  

    error = tf.abs((masked_true - masked_pred) / masked_true)
    mape = tf.reduce_mean(error)  # 平均绝对百分比误差

    return mape

        随后构造以MAPE作为损失函数的模型。这里代码跟上上个代码模块中仅有以下图片中loss值不一样,自己修改一下就好。

 最后将初始构造DataFrame中的第一行0去除并保存为result文件。

res_all.drop(0, inplace=True)
res_all.to_csv("result.csv")

打开result文件,如下图(前40条数据),一共96条数据。


​​​​​​​

我们分别对MSE、MAPE进行排序以确定最优模型(黄色为前10的模型)。

 以MSE排序:

  以MAPE排序:

        由于在模型参数选择的时候只选择了前2000条,原始数据的误差必然大于此模型构建所使用数据的误差,因此我们主要考虑MAPE,MSE为次要因素。
        通过以MAPE排序后的结果我们可以发现,编号为85号的参数设置使得MSE误差在所有参数设置中为第五,MAPE误差为第一。MSE最小为1085,此参数设置为1105、MAPE最小为3.81%,下一参数为3.94%,提升0.1%。

        因此总的来看,此参数设置模型在所有可认为是最优模型,在后文中将以此参数进行预测。参数为神经元个数:64;损失函数:MAPE、迭代次数:500;批量处理量:32;学习率:0.001。

        我们构建参数为上述值的模型,并绘制其MAPE损失率的变化,如下图,可以看到在400次后就区域平缓,且损失值波动不再明显。

#构建全部数据的预测
model = tf.keras.Sequential()
model.add(tf.keras.layers.Dense(64, input_dim=4, activation='relu'))
model.add(tf.keras.layers.Dense(64, activation='relu'))
model.add(tf.keras.layers.Dense(1))
#编译模型
#创建优化器,并设置学习率
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
model.compile(loss=loss_Mape, optimizer=optimizer)
history = model.fit(X_train, Y_train, epochs=500, batch_size=32, verbose=0)
loss_values = history.history['loss']
epochs = range(1, len(loss_values)+1)
#绘图
plt.plot(epochs, loss_values, 'b-', label='MAPE损失率')
plt.title('代价函数')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

        为减少误差,防止特殊情况,我们通过for循环重新构造此模型10次,通过两个列表list存储每次MSE和MAPE的数值。

        通过观察最终结果可以发现MSE大部分在1100左右浮动,存在一次异常值4200,MAPE在大部分在3-5之间浮动,存在一次较小波动,因此此参数设置的准确率较高。下文由这些参数构建的模型称为‘最优模型’。 

3.4.5 过拟合检测

        为确保所构建神经网络没有出现过拟合的情况,我们继续取原始数据(20w数据集)中的2000-4000条数据,命名为data2,并对其中X并进行标准化后命名为X_test2。随后用‘最优模型’进行预测。

#导入原文件
data = pd.read_csv('train.csv')
#列名转中文
names = ['行驶时间','行驶距离','乘客数量','行驶费用','司机小费','额外费用','费用总和','动态定价']
data.columns = names
data2 = data[2000:4000]
#设置自变量和因变量
X_test2 = data2[['行驶时间','行驶距离','乘客数量','动态定价']]
Y_test2 = data2['行驶费用']
#构建全部数据的预测
model = tf.keras.Sequential()
model.add(tf.keras.layers.Dense(64, input_dim=4, activation='relu'))
model.add(tf.keras.layers.Dense(64, activation='relu'))
model.add(tf.keras.layers.Dense(1))
#编译模型
#创建优化器,并设置学习率
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
model.compile(loss=loss_Mape, optimizer=optimizer)
#用构造最优参数的训练集训练模型
model.fit(X_train, Y_train, epochs=500, batch_size=32, verbose=0)
#预测新数据
Y_pred2 = model.predict(X_test2)

#转化为DF + 重设索引
Y_pred2 = pd.DataFrame(Y_pred2).reset_index(drop=True)
Y_test2 = pd.DataFrame(Y_test2).reset_index(drop=True)
#连接
result = pd.concat([Y_pred2,Y_test2], axis=1)
names = ['预测值','真实值']
result.columns = names
#MSE误差
MSE = Mse(result)
print("MSE误差: {:.2f}".format(MSE))
#MAPE误差
MAPE = Mape(result)
print("MAPE误差: {:.2f}%".format(MAPE))

         预测结果如上图,MSE值为1183,MAPE误差率为5.35%。总体上说误差率提升不大,因此可认为不存在过拟合的情况。

        我们绘制预测值和真实值的这线图,由于一共有2000个数据,数据量较大,因此绘图时候每10个数据才在图上显示,x轴坐标每200次显示一次。最后图像如下:

        我们通过观察图像可以发现,预测值大部分时候与真实值接近,但也存在异常情况,例如第400左右条数据出现了偏差较大的情况。总体上看预测较为准确。 

        绘图代码:

#切片,每20个点取一个
result_sliced = result.iloc[::10]

#绘制折线图
plt.plot(result_sliced.index, result_sliced['预测值'], label='预测值')
plt.plot(result_sliced.index, result_sliced['真实值'], label='真实值')

#设置刻度和标签显示的间隔
x_ticks = np.arange(0, len(result), 200)
plt.xticks(x_ticks)
plt.xticks(rotation=45)

plt.title('预测值与真实值折线图')
plt.xlabel('样本索引')
plt.legend()

plt.show()

3.5 模型选择

        在上文中,我一共采取了三种模型构造预测结果,分别为线性回归、决策树、神经网络。其中各个模型的准确率如下(四舍五入)。

模型

评价指标

MSE值

MAPE误差

线性回归

1448

15.85%

决策树

1531

11.05%

神经网络(随机参数)

1740

17.87%

神经网络(最差)

5314

68.87%

神经网络(最优)

1106

3.82%

        其中神经网络(最差)为选择最优参数中MAPE最差的模型。

        根据表格,神经网络(最优)模型具有最低的MSE值和MAPE误差,这意味着该模型在预测方面表现较好,预测结果与实际观测值的差异较小。且在预测新的数据时候,MAPE仍在10%一下,不存在过拟合。这里通过调参的方法让准确率大幅度提升,但同时也要注意过拟合。

四、总结

        此次项目,我用线性回归、决策树、神经网络三种机器学习方法构建模型预测出租车行驶费用。最终最优模型为神经网络,MAPE误差为3.82%,且无过拟合情况出现,模型准确率较高。在实际应用上,可以与打车平台合作,用户给定目的地、出发时间、乘客人数。通过知道行车距离、预计的行驶时间、乘客人数来预测行车费用返回给用户,用户可根据预测的行车费用来确定是否打车。

        后续读者可以继续从多方面分析数据。调查司机小费与行驶时间、行驶距离是否有潜在的关系。神经网络调整优化器为SDG(随机梯度下降)、改变学习率、增加神经元的同时执行drop out操作等调整来寻找更优的模型,但要注意过拟合的情况。

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值