本文将通过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操作等调整来寻找更优的模型,但要注意过拟合的情况。