【火炉炼AI】机器学习019-项目案例:使用SVM回归器估算交通流量
(本文所使用的Python库和版本号: Python 3.5, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2 )
我们都知道,SVM是一个很好地分类器,不仅适用于线性分类模型,而且还适用于非线性模型,但是,在另一方面,SVM不仅可以用于解决分类问题,还可以用于解决回归问题。
本项目打算使用SVM回归器来估算交通流量,所使用的方法和过程与我的上一篇文章【火炉炼AI】机器学习018-项目案例:根据大楼进出人数预测是否举办活动非常类似,所采用的数据处理方法也是大同小异。
1. 准备数据集
本项目所使用的数据集来源于UCI大学数据集,很巧合的是,这个数据集与上一篇文章(大楼进出人数)的数据集位于同一个网页中。
1.1 了解数据集
本数据集统计了洛杉矶Dodgers棒球队在主场比赛期间,体育场周围马路通过的车流量,数据存放在两个文件下:Dodgers.data文件和Dodgers.events文件,这两个文件各列的主要说明为:
Dodgers.data中有50400个样本数据,每一行的基本属性为:
Dodgers.data文件中各属性说明而Dodgers.events文件有中81行数据,每一行数据的基本属性为:
Dodges.events文件中各列属性说明1.2 数据规整
本项目的数据规整主要是将这两个文件中的内容整合到一个可用的数据集中。
1.2.1 规整之一:读取文件错误及其解决办法
本来,我以为读取这两个文件直接用pd.read_csv()即可,就像我以前的多篇文章中用到的一样,然而,本项目中,直接调用该方法读取文件却报错。
# 1 准备数据集
# 从文件中加载数据集
feature_data_path='E:\PyProjects\DataSet\BuildingInOut/Dodgers.data'
feature_set=pd.read_csv(feature_data_path,header=None)
print(feature_set.info())
# print(feature_set.head())
# print(feature_set.tail()) # 检查没有问题
label_data_path='E:\PyProjects\DataSet\BuildingInOut/Dodgers.events'
label_set=pd.read_csv(label_data_path,header=None)
print(label_set.info())
# print(label_set.head())
# print(label_set.tail()) # 读取没有问题,
上面的代码在第二个pd.read_csv()时报以下错误,看来是原始文件的编码出现问题。
-------------------------------------输---------出--------------------------------
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa0 in position 5: invalid start byte
--------------------------------------------完-------------------------------------
查看原始Dodger.events文件,我们可以发现,每一行的末尾有一个不能识别的未知字符。
由于每一行末尾出现未知字符故而不能直接读取内容此时我的解决办法是:用记事本将Dodger.events文件打开,在“另存为”中,将编码格式修改为"UTF-8",保存为一个新的文件,此时新文件中就没有了未知字符,如下图所示。
将编码格式修改为utf-8后再保存再用pd.read_csv()就没有任何问题。
1.2.2 规整之二:删除缺失数据,拆分数据
由于Dodger.data文件中的样本存在缺失数据,其车流量为-1的表示缺失数据,对缺失数据的处理方式有很多种,此处由于整个样本量比较大,故而我直接将缺失数据删除。另外,由于原始数据中并不全都是用逗号分隔开,故而需要将列进行分隔,代码如下:
# 删除缺失数据
feature_set2=feature_set[feature_set[1]!=-1] # 只获取不是-1的DataFrame即可。
# print(feature_set2) # 没有问题
feature_set2=feature_set2.reset_index(drop=True)
print(feature_set2.head())
# 第0列既包含日期,又包含时间,故要拆分成两列
need_split_col=feature_set2[0].copy()
feature_set2[0]=need_split_col.map(lambda x: x.split()[0].strip())
feature_set2[2]=need_split_col.map(lambda x: x.split()[1].strip())
print(feature_set2.head()) # 拆分没有问题
-------------------------------------输---------出--------------------------------
0 1
0 4/11/2005 7:35 23
1 4/11/2005 7:40 42
2 4/11/2005 7:45 37
3 4/11/2005 7:50 24
4 4/11/2005 7:55 39
0 1 2
0 4/11/2005 23 7:35
1 4/11/2005 42 7:40
2 4/11/2005 37 7:45
3 4/11/2005 24 7:50
4 4/11/2005 39 7:55
--------------------------------------------完-------------------------------------
1.2.3 规整之三:日期格式的统一化
在我们进行这两个DataFrame的合并和日期比较之前,需要先将这两个DataFrame中的日期格式统一化,从两个文件中读取的日期都是String类型,但是从Dodgers.data中读取的日期格式是如4/11/2005的形式,而从Dodgers.events中读取的日期格式是比如:05/01/05形式,显然这两个String之间难以直接比较。幸好Pandas有内置的to_datetime函数,可以直接将这两种日期进行格式统一化。代码为:
# 将两个DataFrame中的日期格式统一,两个DataFrame中的日期目前还是String类型,格式不统一无法比较
feature_set2[0]=pd.to_datetime(feature_set2[0])
print(feature_set2[0][:5]) # 打印第0列的前5行
label_set[0]=pd.to_datetime(label_set[0])
print(label_set[0][:5])
-------------------------------------输---------出--------------------------------
0 2005-04-11
1 2005-04-11
2 2005-04-11
3 2005-04-11
4 2005-04-11
Name: 0, dtype: datetime64[ns]
0 2005-04-12
1 2005-04-13
2 2005-04-15
3 2005-04-16
4 2005-04-17
Name: 0, dtype: datetime64[ns]
--------------------------------------------完-------------------------------------
1.2.4 规整之四:合并两个文件到一个数据集中
在合并文件的时候,我们要知道哪些特征属性是进行机器学习所必须的,此处我们选择的特征列包括有(日期,时间,对手球队名,是否比赛期间),故而我们需要从Dodgers.data文件中选取日期和时间,从Dodger.events文件中选取对手球队名和是否比赛期间的信息,放置到一个数据集中。具体代码如下:
# 合并两个文件到一个数据集中
feature_set2[3]='NoName' # 对手球队名称暂时用NoName来初始化
feature_set2[4]=0 # 是否比赛期间暂时用否来代替
def calc_mins(time_str):
nums=time_str.split(':')
return 60*int(nums[0])+int(nums[1]) # 将时间转换为分钟数
for row_id,date in enumerate(label_set[0]): # 先取出label中的日期
temp_df=feature_set2[feature_set2[0]==date]
if temp_df is None:
continue
# 只要这一天有比赛,不管是不是正在比赛,都把对手球队名称写入第3列
rows=temp_df.index.tolist()
feature_set2.loc[rows,3]=label_set.iloc[row_id,4]
start_min=calc_mins(label_set.iloc[row_id,1])
stop_min=calc_mins(label_set.iloc[row_id,2])
for row in temp_df[2]: # 在逐一判断时间是否位于label中时间之间
feature_min=calc_mins(row)
if feature_min>=start_min and feature_min<=stop_min:
feature_row=temp_df[temp_df[2]==row].index.tolist()
feature_set2.loc[feature_row,4]=1
# feature_set2.to_csv('d:/feature_set2_Dodgers.csv') # 保存后打印查看没有问题
打开保存的feature_set2_Dodgers.csv,查看发现有很多NoName的行数,这些行数表示当天没有比赛,故而没有对手名。对于NoName样本的处理方式也有多种多用,根据具体的需求不同而不同,可以把它当做ground truth来对待,也可以直接删除,也可以保留作为一种情况来进行训练。此处我直接将其删除。
1.2.5 规整之五:将日期转换为星期数并保存数据集
这部分主要是将日期转换为星期数,并保存数据集到硬盘,方便下次直接读取文件。代码为:
feature_set3=feature_set2[feature_set2[3]!='NoName'].reset_index(drop=True) # 去掉NoName的样本
# 进一步处理,由于日期在以后的日子里不可重复,作为feature并不合适,而可以用星期数来代替,
feature_set3[5]=feature_set3[0].map(lambda x: x.strftime('%w')) # 将日期转换为星期数
feature_set3=feature_set3.reindex(columns=[0,2,5,3,4,1])
print(feature_set3.tail()) # 查看转换没有问题
feature_set3.to_csv('E:\PyProjects\DataSet\BuildingInOut/Dodgers_Sorted_Set.txt') # 将整理好的数据集保存,下次可以直接读取
-------------------------------------输---------出--------------------------------
0 2 5 3 4 1
22411 2005-09-29 23:35 4 Arizona 0 9
22412 2005-09-29 23:40 4 Arizona 0 13
22413 2005-09-29 23:45 4 Arizona 0 11
22414 2005-09-29 23:50 4 Arizona 0 14
22415 2005-09-29 23:55 4 Arizona 0 17
--------------------------------------------完-------------------------------------
########################小**********结###############################
1. 这个项目的主要难点也在于数据处理方面,其主要的规整方法和上一篇文章类似。
#################################################################
2. 构建SVM回归模型
使用SVM构建回归模型的关键在于导入SVR模块,而不是分类模型中所用的SVC模块,其SVR所用的参数也需要做相应的调整,此处够安静SVM回归模型的代码为:
from sklearn.svm import SVR # 此处不一样,导入的是SVR而不是SVC
regressor = SVR(kernel='rbf',C=10.0,epsilon=0.2) # 这些参数是优化得来
regressor.fit(train_X, train_y)
-------------------------------------输---------出--------------------------------
SVR(C=10.0, cache_size=200, coef0=0.0, degree=3, epsilon=0.2, gamma='auto',
kernel='rbf', max_iter=-1, shrinking=True, tol=0.001, verbose=False)
--------------------------------------------完-------------------------------------
在对模型进行定义和训练之后,就需要使用测试集对模型进行测试,如下是测试代码和输出结果:
y_predict_test=regressor.predict(test_X)
# 使用评价指标来评估模型的好坏
import sklearn.metrics as metrics
print('平均绝对误差:{}'.format(
round(metrics.mean_absolute_error(y_predict_test,test_y),2)))
print('均方误差MSE:{}'.format(
round(metrics.mean_squared_error(y_predict_test,test_y),2)))
print('中位数绝对误差:{}'.format(
round(metrics.median_absolute_error(y_predict_test,test_y),2)))
print('解释方差分:{}'.format(
round(metrics.explained_variance_score(y_predict_test,test_y),2)))
print('R方得分:{}'.format(
round(metrics.r2_score(y_predict_test,test_y),2)))
-------------------------------------输---------出--------------------------------
平均绝对误差:5.16
均方误差MSE:50.45
中位数绝对误差:3.75
解释方差分:0.63
R方得分:0.62
--------------------------------------------完-------------------------------------
貌似结果不太好,可能SVR中的参数还需要进一步优化.
很多朋友给我留言,问我训练好的SVM模型如何保存和重新调用,这部分内容去我在前面的文章中已经介绍过了,具体请看:【火炉炼AI】机器学习003-简单线性回归器的创建,测试,模型保存和加载
注:本部分代码已经全部上传到(我的github)上,欢迎下载。
参考资料:
1, Python机器学习经典实例,Prateek Joshi著,陶俊杰,陈小莉译
网友评论