文章目录
- 1. 实验概览
- 2. 数据集下载
- 3. 数据预处理
- 3.1 异常点去除
- 3.2 停留点检测与环绕点检测
- 3.3 轨迹分段
- 4. 基于轨迹信息的数据挖掘
- 4.1 路口检测
- 4.1.1 地图分割与轨迹点速度计算
- 4.2 偏好学习
通常,我们将一个连续的GPS信号点序列称为一个轨迹(Trajectory),在拥有GPS数据轨迹的情况下,我们能通过这些数据信息挖掘出哪些有效信息是数据挖掘在轨迹信息上的重要研究内容。在该实验中我们使用UCI上公开数据集,利用该轨迹数据集尝试挖掘其中的隐藏信息,在信息挖掘之前,我们需要对原始数据信息进行数据预处理,以保证算法拥有较为优质的输入数据。
1. 实验概览
通常,我们将一个连续的GPS信号点序列称为一个轨迹(Trajectory),在拥有GPS数据轨迹的情况下,我们能通过这些数据信息挖掘出哪些有效信息是数据挖掘在轨迹信息上的重要研究内容。在该实验中我们使用UCI上公开数据集,利用该轨迹数据集尝试挖掘其中的隐藏信息,在信息挖掘之前,我们需要对原始数据信息进行数据预处理,以保证算法拥有较为优质的输入数据,因此整个实验一共分为以下几个步骤:
- 下载轨迹数据集
- 对轨迹数据集进行数据预处理
- 相关信息挖掘
接下来,我们按照该顺序进行依次讲解。
2. 数据集下载
本次实验我们选用UCI上的公开数据集(链接),数据集中共包括两个文件:go_track_tracks.csv 和 go_track_trackpoints.csv,两个文件内容分别如下所示:
可以分析出,go_track_tracks.csv 文件中存放的是每一条 trajectory 的全局信息,例如该条 trajectory 的 id、该条 trajectory 是属于车辆的轨迹还是公共汽车的轨迹、平均速度等(具体细节参考前文UCI链接);而与之对应的,go_track_trackpoints.csv 文件中存放的是每一条 trajectory 中每一个轨迹点的记录信息,例如经纬度、时间戳等。
我们对轨迹数据进行解析,利用 OSMNX 将数据集中的轨迹做可视化,这里挑出其中一个轨迹,可视化结果如下:
通过可视化可以看出,原始的轨迹信息十分杂乱,甚至会出现一些因为GPS设备误差而生成的不合理轨迹点。如上图中所示,可以看到一条轨迹中参杂了一个偏离非常远的轨迹点,使得整个轨迹非常突兀,因此在进行数据挖掘前,我们首先需要对这些原始数据集做数据预处理工作。
3. 数据预处理
数据预处理是在进行数据分析之前非常重要的一个步骤,对于轨迹数据的预处理,该实验中一共经历了3个阶段:
- 异常点去除
- 停留点与围绕点的检测与去除
- 轨迹分段
下面,我们就依照这个顺序将数据集中的轨迹数据进行一步步的清洗。
3.1 异常点去除
异常点是指因为GPS设备误差或者其他原因等产生的不合理的一些噪声信号点,识别异常点的一个方法是根据相邻两个点之间的间隔时间与间隔距离计算出行驶速度,将明显不符合实际速度的轨迹点给清除掉,部分代码如下:
# 遍历计算坐标轨迹中所有相邻点的距离和间隔时间并计算出速度,与最大速度阈值比较,筛选出符合正常速度区间的轨迹点for i in range(1, len(origin_positions)):distance = geodesic(origin_positions[i-1], origin_positions[i]).kmhours_used = get_hours_from_two_time_string(time_list[i-1], time_list[i])speed = max_speed_threshold if hours_used == 0 else distance / hours_usedif speed < max_speed_threshold:result_positions.append(origin_positions[i])result_time_list.append(time_list[i])result = {track_id: {'positions': result_positions, 'time_list': result_time_list, 'mean_speed': mean_speed}}
下图是我们未进行异常点去除和进行了异常点去除之后的对比图,可以看出,经过最大速度值为 150km/h 的条件筛选后,右下角的异常数据点已经被清除掉,轨迹变得比清洗前连续性显得更好。
3.2 停留点检测与环绕点检测
在轨迹数据中,我们需要分析用户在运动过程中在哪些地点进行了停留。“停留” 一共存在两种情况:一种是用户在原地停留,轨迹点间隔很近;另一种是用户会围绕某一点进行徘徊,例如景观点的附近会存在较多环绕点。我们需要分别对这两种类型的点进行检测筛选。
对于原地停留的情况比较容易检测,我们直接判断轨迹点中相邻点之间的距离差即可,但对于 “环绕停留” 的情况,我们需要使用 Stop Point Detection 的相关算法完成检测,论文链接参考这里。
如上图所示,P3点为原地停留的情况,而右侧 P5,P6,P7,P8 四个点为 “环绕停留” 点,红色 Stay Point 2 为这些环绕点的中心点。为了使得整个轨迹更加平滑,我们通常会将使用红色点来代替原轨迹中的环绕点。那么,环绕停留点如何去检测呢?论文中采用了如下方法:
我们定义一个步进距离阈值 distThreh 和一个间隔时间阈值 timeThreh,遍历整个轨迹,判断当后面某个点与当前点的距离超过了距离阈值且时间间隔大于了时间阈值时,这个轨迹片段中的所有点都被判定为围绕停留点。举例来说,假设当前点遍历到了P5,则我们判断 P6,P7,P8 到 P5 的距离都没有超过距离阈值,直到计算到 P9 点时,发现 P9 和 P5 之间的距离超过了预设距离阈值,此时判断 P9 点和 P5 点之间的时间间隔是否大于时间阈值,若间隔时间大于了预设时间阈值,则 P5,P6,P7,P8 四个点均为环绕等待点,使用四个点的中心点作为拟合路径点,填入到原轨迹中去。
具体实现代码如下:
points_without_around_points = [points_without_stop_points[0]]time_list_without_around_points = [time_list_without_stop_points[0]]i = 0# 遍历所有轨迹点,将点多个点围绕的情况替换成其中心点while i < len(points_without_stop_points):j = i + 1# 从 i+1 开始,一直向后遍历,找到距离大于最小阈值的最近轨迹点while j < len(points_without_stop_points):distance = geodesic(points_without_stop_points[i], points_without_stop_points[j]).km# 若当前点(j点)到i点距离大于预设判断距离,则对该轨迹片段进行类型判断if distance >= min_delta_dist:# 求从i点到最近大于距离阈值点j共消耗的时间delta_time = get_hours(time_list_without_stop_points[i], time_list_without_stop_points[j])# 若时间大于预设停留时间,则代表该轨迹片段为围绕轨迹,替换这些轨迹点为中心围绕点if delta_time > min_delta_time:points_sequence = np.array(points_without_stop_points[i:j+1])centroid = [np.mean(points_sequence[:, 0]), np.mean(points_sequence[:, 1])]# 只有该中心点与之前拟合出的中心点隔的比较远,才将这个新的中心点加入中心点列表中 if len(around_points_centroids) == 0 or get_distance(centroid, around_points_centroids[-1]) > min_centroid_threshold:around_points_centroids.append(centroid)points_without_around_points.append(centroid)time_list_without_around_points.append(time_list_without_stop_points[j])# 若时间小于预设停留时间,则代表该轨迹片段不是围绕轨迹,保留该轨迹段中的所有轨迹点else:points_without_around_points.extend(points_without_stop_points[i+1:j+1])time_list_without_around_points.extend(time_list_without_stop_points[i+1:j+1]) i = jbreakj += 1if j == len(points_without_stop_points):break
下图为算法执行后的效果图,图左为只执行停留点检测的结果,蓝色标记处为存在长时间停留的检测点;图右为使用了围绕停留点检测的结果图,绿色标记为多个围绕点拟合出的中心标记点,明显可以看出,使用拟合点拟合围绕停留点后,整个轨迹看起来要简洁明了许多。
3.3 轨迹分段
轨迹分段是数据预处理中比较重要的一个步骤,从前两章可以看出,一个完整的轨迹大多时候是比较混乱的。包含自相交、重复行走和突然变向等情况,类似的情况会使得我们在做轨迹数据可视化的时候,轨迹显得混乱不堪,并不利于我们做数据分析。因此,我们需要将一个完整轨迹按照某种规律切分为多个不同的轨迹段,便于我们下一步的分析。
在轨迹分段中,我们通常选用 “时间” 和 “转弯角” 这两个因素作为切割因素,若一个轨迹段中出现 “急转弯” 、 “时间间隔较久” 或是 “空间间隔过大” 的这三种情况,我们都将其切分为一个独立的子轨迹段。下图是我们进行轨迹分段后的结果,图左为未进行数据分段的可视化效果图,图右为执行数据分段后的效果图,可以看出,地图左半部分的区域在未执行数据分段之前较为较混乱,执行分段后不再出现自相交的情况。
数据分段的部分代码如下:
for i in range(len(origin_positions)):# 若该 segment 中轨迹点数目还不足2个,则填满2个后再计算if len(temp_segment) < 2:# 若新点和旧点之间的距离大于分割距离,则把旧点给弹出删掉if len(temp_segment) == 1 and geodesic(temp_segment[-1], origin_positions[i]).km > segment_distance:temp_segment.pop(0)temp_time_list.pop(0)temp_segment.append(origin_positions[i])temp_time_list.append(time_list[i])continue# 求该 segment 中最后两个点形成的 headingrelative_pos = Utils.get_relative_pos(temp_segment[-2], temp_segment[-1])temp_heading, _ = Utils.transfer2polar(relative_pos[0], relative_pos[1])# 求新点与上一个点形成的 heading 以及从上一点到新点耗费的时间new_relative_pos = Utils.get_relative_pos(temp_segment[-1], origin_positions[i])new_heading, _ = Utils.transfer2polar(new_relative_pos[0], new_relative_pos[1])time_used = get_hours_from_two_time_string(temp_time_list[-1], time_list[i])distance = geodesic(temp_segment[-1], origin_positions[i]).km# 如果转向超过阈值或等待时间超过阈值,则划分为新的一段 segmentif abs(new_heading - temp_heading) > math.radians(segment_angle) or time_used > segment_time or distance > segment_distance:segments.append(temp_segment)segments_time.append(temp_time_list)temp_segment = [origin_positions[i]]temp_time_list = [time_list[i]]# 否则,将该点加入到当前轨迹段中else:temp_segment.append(origin_positions[i])temp_time_list.append(time_list[i])
4. 基于轨迹信息的数据挖掘
至此,我们的数据预处理工作就暂告一个段落,接下来我们就可以在现有数据集上尝试进行相关信息的数据挖掘尝试了。在该实验中,我们主要从路口检测和偏好学习两方面进行入手,接下来我们分别对两个实验做出相应的介绍。
4.1 路口检测
路口检测是指,利用现有用户行径数据来判断哪些位置可能存在路口。路口检测通常可以用于地图的实时更新,当挖掘出的路口与地图数据不匹配时,可以帮助我们快速的发现一些现实世界的变更信息(如某个地区新修建了一个路口等)。通常在路口的时候,汽车会存在转弯的行为,因此路口处的速度方向应该比普通直行道路上的速度方向更加丰富。我们考虑利用聚类算法,将各个区域的点按照速度方向进行聚类,类别比较丰富的区域更有可能存在路口。
4.1.1 地图分割与轨迹点速度计算
首先,我们按照 100mx100m 的正方形对地图进行分割,并将每一个轨迹点分类到其对应的格子中。如下所示,为验证轨迹点归类算法的正确性,我们对每一个格子随机生成一种颜色,同一个格子中的轨迹点使用相同颜色进行绘制:
在确认归类算法无误后,我们将整个地图中的点连同其速度矢量一起绘制到地图上,线段方向代表速度方向,线段长短代表速度大小:
基于此,我们依照速度的方向和大小,使用 DBSCAN 算法对每个网格中的轨迹点进行聚类。DBSCAN 的好处在于不用人为定义类别个数,同时,GPS 的噪声也能正好被 DBSCAN 算法给融合。我们对同一个网格下,不同类别的路径点使用不同颜色进行区分,结果如下所示:
值得一提的是,在我们选择聚类时,需要先选择聚类的指标,也就是说我们需要使用哪些特征对轨迹点进行聚类。我们挑选出了3种待选的方案:
- 使用速度的原始 Vx,Vy
- 将速度转换为方向角 r
- 将速度转换为极坐标系,即方向角 r 和速度标量 d(由于r,d数量级相差较大,因此需要归一化)
为了验证这3个指标哪一个更为合理,我们挑选较有代表性的几个地图格,并分别对这几个地图格使用上述三种指标进行可视化分析。我们首先选择一个可能存在路口的地图格,使用该地图格中的轨迹点进行3种不同指标的聚类,结果如图所示。
图中第一列、第二列、第三列分别为速度原始值、速度方向角、速度极坐标(归一化后)的数据分布图,第一行和第二行分别代表各指标原始数据分布图和数据聚类后的结果图。可以看出,在地图格当中我们可以看到轨迹点应该大致被分为3类(从左到右、从右到左、从上到下),而第一列(使用原始速度)和第二列(速度方向角)最后的聚类结果也在3类,因此比较符合真实结果。但第三列(使用方向角和速度值)最后归一化出了5类,不是很合理。因此在该次实验中,我们选择速度原始值或速度方向角指标更为合理。
随即,我们选择一个非路口地图格,但该格中存在多种不同速度方向的轨迹点,我们对地图格中的轨迹点进行3种不同指标的聚类,并展示结果。
从地图格中,我们可以看出速度应被分为两类(从下到上、从上到下)。对比前两列聚类结果,按照速度原始值聚类结果为3类,按照方向角聚类结果为4类(-1类代表噪声类)。因此我们可以发现,再这种情况下,使用速度原始值分类可以更加贴近真实结果,如果只考虑方向角而步考虑速度大小可能会使得聚类结果偏多。对于第三列结果,可以看到虽然聚类结果被分类为了2类,但其实是噪声类+有效类,实际只被聚出了一类,因此我们发现在两种实验场景下,使用极坐标法(归一化)都不具备较好的效果。
最后,我们进行潜在路口的筛选。我们筛选出所有网格中聚类数目大于 3 的网格(DBSCAN 中会将异常类标志为 -1 类别),将这些网格视为路口可能出现的潜在单元格,并在地图中利用特殊矩形框标记识别出,结果如下所示:
我们挑选出几个潜在网格查看,这些潜在网格中并不是所有网格的判断都是正确的。由于缺乏数据,某些路口未能被正常识别出;此外,由于依赖速度转向做聚类结果,某些双行道同样也有可能被判断为是路口网格。因此,使用聚类方法只是为我们提供了哪些网格可能是包含路口的潜在网格,具体是否确实包含路口还需要在预测结果的基础上进一步的进行判断。
4.2 偏好学习
(Work In Process)