本文将介绍如何利用已有的数据集训练lstm网络实现多变量时序预测模型,以此来检测GNSS欺骗攻击。我们使用了来自 Comma.ai 的真实数据集,名为 Comma2k19,其中包含各种自动驾驶车辆传感器数据。该数据集的下载和处理过程请参考我的另一篇文章comma2k19数据集使用。
1.数据准备
Comma.ai 使用的视听设备有一个前置摄像头、温度计和9轴惯性测量单元。除了这些传感器数据,Comma2k19 数据集还包含来自全球导航卫星系统(GNSS)和控制区域网络(CAN)的测量值(见表 1 和表 2)。数据收集使用了可跟踪全球导航卫星系统的 u-blox M8 全球导航卫星系统模块,水平位置精度为 2.5 米。位置测量使用了全球定位系统(GPS)和全球轨道导航卫星系统(GLONASS)信号。此外,使用开源的GNSS处理库 Laika 来减少定位误差,定位误差降低了 40%。
在这里我们选择部分数据用于接下来的模型中:
- GNSS数据集包含来自 u-blox 和 Qcom 的实时和原始导航卫星系统数据。每个实时数据包括纬度、经度、速度、utc 时间戳、高度和方位角数据。但它们都是未被Laika优化的数据,为了得到更好的效果,我们采用
global_pose
文件夹中的frame_position
、frame_gps_times
和frame_velocities
的数据,后续可看情况加入frame_orientations
数据。综合起来用于训练的GNSS数据有时间,经纬度(高度可不考虑)和速度,如下表所示:
其中global_pose\frame_position
中的坐标是ECEF的(x, y, z),须将其转化为GPS常用的经纬度坐标(wgs845),Python代码实现如下:
import pyproj
transformer = pyproj.Transformer.from_crs(
{
"proj":'geocent', "ellps":'WGS84', "datum":'WGS84'},
{
"proj":'latlong', "ellps":'WGS84', "datum":'WGS84'},
)
lon, lat, alt = transformer.transform(x,y,z,radians=False)
print (lat1, lon1, alt1 )
要注意返回的经纬度顺序是Longitude在前,与常规有所区别。
- 相关的CAN数据是CAN时间,车速和方向盘转角数据(见表2),可分别从
processed_log/CAN/speed/t
、processed_log/CAN/speed/value
和processed_log/CAN/steering_angle/value
读取, 样例如下表所示。
- 同样,相关的IMU数据是三个方向的加速度,可分别从processed_log/IMU/accelerometer/t和processed_log/IMU/accelerometer/value中读取,样例如下表所示。
时间处理
值得注意的是,用于分析的数据集包含7200个GNSS观测值、35726个CAN观测值和72148个IMU观测值,GNSS、CAN和IMU数据的频率分别为10、50和100赫兹。这意味着不同数据源的时间是有略微不同的,在本项目中GNSS时间被用作参考时间,所有其他传感器数据被同步,以便为LSTM模型准备训练和测试数据集。为了在作为参考时间的准确时间获得CAN和IMU的数据,在GNSS的两个最近观测值之间要对CAN和IMU进行插值,在这里我采用scipy的样条插值方法,Python代码实现如下:
from scipy.interpolate import make_interp_spline
CAN_time = np.load(example_segment + 'processed_log/CAN/speed/t')
gps_time = np.load(example_segment + 'global_pose/frame_times')
# 对CAN的速度进行插值
CAN_speed = np.load(example_segment + 'processed_log/CAN/speed/value')
new_CAN_speed = make_interp_spline(CAN_time, CAN_speed)(gps_time)
plt.figure(figsize=(12, 12))
plt.plot(CAN_time, CAN_speed, label='CAN')
plt.plot(gps_time, new_CAN_speed, label='new_CAN')
plt.legend(fontsize=25)
plt.xlabel('boot time (s)', fontsize=18)
plt.ylabel('speed (m/s)', fontsize=18)
plt.show()
当处理时间数据的时候我发现它们存在一些小瑕疵,有重复值,而且即使是同样来自CAN的数据,speed的时间和steering_angle的时间也是不一致的,这意味着它们要分别读取,分别插值。Python代码实现:
# 数组去重
def unique(old_list):
newList = []
# 判断相邻时间是否相等
if np.any(old_list[1:] == old_list[:-1]):
for x in old_list:
if x in newList:
# 若相等,则加上一个微小的数使其不等
x = x + 0.005
newList.append(x)
return np.array(newList)
else: return old_list
temp_CAN_times = np.load(main_dir + '\\processed_log\\CAN\\speed\\t')
# 确保时间无重复值
temp_CAN_speed_times = unique(temp_CAN_times)
# CAN_angles_times和CAN_speed_times有时不一致
temp_CAN_angles_times = np.load(main_dir + '\\processed_log\\CAN\\steering_angle\\t')
temp_CAN_angles_times = unique(temp_CAN_angles_times)
temp_IMU_times = np.load(main_dir + '\\processed_log\\IMU\\accelerometer\\t')
距离计算
当我们预测自动驾驶车辆当前位置和最近未来位置之间的行驶距离时,从上一个时间步长开始的每个时间步长中的行驶距离是使用纬度和经度坐标以及以下哈弗辛大圆公式计算的:
Python实现如下:
import math
EARTH_REDIUS = 6378.137
def rad(d):
return d * math.pi / 180.0
def getDistance(lats1, lngs1, lats2, lngs2):
# 对数组取元素做运算
res = []
for i in range(len(lat1)):
radLat1 = rad(lat1[i])
radLat2 = rad(lat2[i])
a = radLat1 - radLat2
b = rad(lng1[i]) - rad(lng2[i])
s = 2 * math.asin(math.sqrt(math.pow(math.sin(a / 2), 2) + math.cos(radLat1) * math.cos(radLat2) * math.pow(
math.sin(b / 2), 2)))
s = s * EARTH_REDIUS * 1000
res.append(s)
return res
# 计算距离
distance = getDistance(lats1, lngs1, lats2, lngs2)
plt.plot(times[:-1], distance, label='distance')
plt.title('Traveled distance between two consecutive timestamps', fontsize=20);
plt.legend(fontsize=20);
plt.xlabel('boot time (s)', fontsize=18);
plt.ylabel('distance(m) ', fontsize=18);
plt.show()
与速度图相比较,可以发现形状相似,说明计算无误。
对一个segment的distance绘图没有问题,但如果对一个route的多个segment一个绘图就会发现存在异常值,如下图所示:
异常值使得曲线非常不光滑
蓝色框中的数据和周边数据明显不同,对于这个问题我咨询comma.ai得到的解释是每个segment的数据是分别优化的,并不保证segment之间坐标的连续性,所以每跨一个段时(60s),就会出现这样的异常值。由于这些异常值占比极小,可忽略不计。
合并数据集
对比GNSS的速度与CAN的速度我们会发现存在偏差,考虑到CAN速度会比GNSS速度更能反映实际情况,故只采用CAN的速度,我们使用LSTM模型来预测每个时间戳的当前位置和最近的未来位置之间的距离,使用无攻击的CAN、IMU和GNSS数据。在本文中,我们使用的LSTM模型由一个输入层、一个具有50个神经元的递归隐藏层和一个输出层组成。训练LSTM模型的输入数据包括来自控制器局域网的速度和转向角数据以及来自惯性测量单元的前向加速度数据。输出是每个时间戳当前位置和最近未来位置之间的距离。
在训练数据规模上我们有考量,每个chunk之间甚至同一个chunk内不同route的数据都是不相关的,他们之间的不连续性使得它们没法反映真实情况,所幸同一个route之间的segments是基本连续的,我们将训练范围缩小至一个route文件夹的数据,如果用其他场景丰富且时间连续的数据集训练效果会更好,读取数据的代码实现如下:
dataset_directory = 'D:\comma2k19'
chunk_set = []
for chunk in os.listdir(dataset_directory):
# 忽略生成的csv文件
if ".csv" in chunk:
continue
# 如果序号为单个时在前补零,以便后面排序
if len(chunk) == 7:
used_name = chunk
chunk = str_insert(chunk,6,'0')
os.rename(os.path.join(dataset_directory, used_name), os.path.join(dataset_directory, chunk))
chunk_set.append(os.path.join(dataset_directory, chunk))
# 将序号小的片段放在前面
chunk_set.sort()
# 选一个chunk来训练(200分钟