我在上一篇文章中简单地讨论了一些关于KNN的细节,这几天花了点时间用python实现了KNN方法在短时交通流预测的应用,我把主要代码和一些注解记录在下文中。
数据来源和预处理:
这次的数据来自Kaggle(Traffic Flow),这个数据集中的数据根据不同的路口编号为文件命名,文件中每五分钟记录一组数据,一个完整的文件的时间跨度约在60天左右。
经过简单观察后发现,这些数据多存在数据缺失的问题,因而取拥有最长的不含0值的时间序列的train26804.csv作为试验展开的数据集。这次的预测模型的建立和结果分析都将基于train26803.csv的第5760行至第14687行进行,其历时正30天。
KNN模型的建立方案
在上一篇文章中提到KNN的几个重要因素中就含有时间延迟τ、历史值向量维数m和近邻个数K,由于本次数据已经给出了一个确定的时间延迟5min,因此这次KNN模型的建立将分三步进行。
首先是找到合适的历史值向量维数m和近邻个数K。本人查阅过的部分论文中均表示,对于m值和K值的选取,长期以来并未有一个统一的标准,但是多数研究者关于m和K的计算结果都处在5~25的区间内,所以我准备结合下面会提到的预测值计算公式和误差计算,遍历所有区间内的m和K的组合,在一个随意选取的时间段内计算各个组合的总误差值,取总误差最小的组合为模型所用。
第二步是计算预测值,预测值将根据与当前历史值向量的距离进行加权平均得到,计算公式:
。根据短时交通流的周期性特征,这次试验中匹配最近邻向量的操作中,将只在当前时间点最近的7天内寻找最近邻向量。距离计算采取欧拉距离计算。
最后,将通过相对方差对预测结果进行评价,计算公式:
。
求模型参数m和K
首先是建立数据库,这个数据库表现为列表的列表。母列表包含了原数据的各个不同版本的副本,各个不同版本的副本的区别在于追加了多少维度的历史值向量。上一段提到这次试验的匹配最近邻环节将只在最近7天的数据内寻找,所以为观察值最佳历史值向量只发生在第288行也就是第七天之后。
import pandas as pd
import numpy as py
data=pd.read_csv('train26803.csv')
data=data.iloc[5760:14688]
temp=data.values
matrix=[]
for i in temp:
matrix.append(list(i))
re=[]
for m in range(25):
if m != 0:
matrix=[]
for i in temp:
matrix.append(list(i))
for i in range(len(matrix)):
if i >= 288:
for j in range(m):
if j != 0:
matrix[i].append(matrix[i-j][1])
# print(matrix)
re.append(matrix)
print('done')
接着是根据数据库的形式确定距离计算和预测值计算的函数,第三个函数(takeElem)将在寻找最近邻操作中使用到:
def dis(x,y):
t=0
for i in range(len(x)):
if i >= 2:
t+=pow((x[i]-y[i]),2)
return pow(t,1/2)
def pred(ds,i):
if i < 288:
print('error')
return 0
res=[]
# res.append(ds[i])
for j in range(2016):
temp=[]
temp.append(dis(ds[i],ds[i-j-1]))
temp.append(ds[i-j-1][1])
res.append(temp)
return res
def takeFirst(elem):
return elem[0]
然后就是遍历m和K的各个组合,计算各个组合的误差总和,并在其中找到误差最小的组合:
allRes={}
for x in re:
if re.index(x)>2:
tempRes={}
print('m=',str(re.index(x)))
for k in range(25):
if k>2:
print('k=',str(k))
errT=0
n=0
for i in range(2016):
realValue=float(re[0][4068+i][-1])
distances=pred(x,i+4068)
distances.sort(key=takeFirst)
# print('real is:')
# print(re[0][4068+i][-1])
# print('predict in ',str(i+4068))
distancesa=distances[:k]
button = 0
res=0
for i in distancesa:
if i[0]==0:
i[0]=0.00000001
button += 1/(i[0])
for i in distancesa:
res += (1/i[0])*i[1]/button
res=round(res,2)
# print((res-realValue)/realValue)
n+=1
errT+=pow(((res-realValue)/realValue),2)
# print(errT/n)
tempRes[str(k)]=pow((errT/n),2)
allRes[str(re.index(x))]=tempRes
print('done')
##m和k的遍历结果已存入jsonFile.json 待调出处理
minm='3'
mink='3'
for m in allRes:
for k in allRes[m]:
if allRes[m][k]
minm=m
mink=k
print('done')
print("the most suitable m is :",minm)
print("the most suitable k is :",mink)
上述的这一段代码将运行很长的时间,我的小笔记本在这一步需要花四个小时左右进行计算。经过这一步的计算,我们可以得到,针对这个数据集的最优m值为18,最优K值为14。
将上述代码稍加改造就可以直接用来进行预测工作:
allRes={}
for x in re:
if re.index(x)==18:
tempRes={}
print('m=',str(re.index(x)))
for k in range(25):
if k==14:
print('k=',str(k))
errT=0
n=0
tt=[]
for i in range(4068):
realValue=float(re[0][4068+i][-1])
totalT=[]
totalT.append(re[0][4068+i][0])
# print(i)
distances=pred(x,i+4068)
distances.sort(key=takeFirst)
distancesa=distances[:k]
button = 0
res=0
for i in distancesa:
if i[0]==0:
i[0]=0.00000001
button += 1/(i[0])
for i in distancesa:
res += (1/i[0])*i[1]/button
res=round(res,2)
n+=1
errT=((res-realValue)/realValue)
totalT.append(realValue)
totalT.append(res)
totalT.append(round((res-realValue),2))
totalT.append(errT)
tt.append(totalT)
allRes[str(re.index(x))]=tt
print('done')
晚上上述工作之后,所有的预测结果将存在allRes中。
结果展示与误差分析
经过统计后发现,这个统计结果的相对误差和只有0.00133,绝对误差总和只有0.315,这说明,即便这个模型的建立方式相当粗糙,其得到的预测结果还是相对可靠的。
为了直观起见,我对预测值和观察值进行了可视化处理,代码和效果展示如下:
import matplotlib.pyplot as plt
for i in range(15):
sx=[]
sy1=[]
sy2=[]
n=1
for i in tt[i*288:(i+1)*288]:
sx.append(n)
sy1.append(float(i[1]))
sy2.append(float(i[2]))
n+=1
plt.plot(sx,sy1,"x-",label="res")
plt.plot(sx,sy2,"+-",label="real")
"""open the grid"""
plt.grid(True)
plt.legend(bbox_to_anchor=(1.0, 1), loc=1, borderaxespad=0.)
plt.show()