背景
用AIS船舶数据使用DBSCAN算法进行船舶轨迹聚类
DBSCAN 模型
DBSCAN 模型简要介绍:
DBSCAN (Density-based Spatial Clustering of Applications with Noise)是一种以密度为基础的空间聚类算法,可以用密度的概念剔除不属于任意类别的噪声点。该算法将簇定义为密度相连的点的最大集合,将具有足够密度的区域划分为簇,并可以发现任意形状的簇。
下图为DBSCAN算法演示,要求圆圈的半径越小或圆内最小样本数越大时,对于簇的产生越严格,密度的要求就越高,离群点会越多。
图片来源:《Python大数据分析与机器学习商业案例实战》
源数据:
| RECORD_DATETIME | MMSI | LONGITUDE | LATITUDE | DIRECTION | HEADING | SPEED | STATUS | ROT | POSITION_ACCURACY | UTC_HOUR | UTC_MINUTE | UTC_SECOND | MESSAGE_ID | REV_DATETIME | SOURCE_ID |
| 2016/9/4 9:24 | 150203145 | 117.8877 | 38.95203 | 100.1 | 511 | 9.8 | 15 | -128 | 0 | 24 | 60 | 40 | 18 | 1472952282 | 1 |
| 2016/9/4 9:26 | 150203145 | 117.8927 | 38.95112 | 107.5 | 511 | 9.9 | 15 | -128 | 0 | 24 | 60 | 9 | 18 | 1472952372 | 1 |
| 2016/9/4 9:29 | 150203145 | 117.9044 | 38.94808 | 105.1 | 511 | 9.9 | 15 | -128 | 0 | 24 | 60 | 39 | 18 | 1472952581 | 1 |
| 2016/9/4 9:29 | 150203145 | 117.9044 | 38.94808 | 105.1 | 511 | 9.9 | 15 | 0 | 1 | 24 | 60 | 60 | 1 | 1472952584 | 11 |
import pandas as pd
data = pd.read_csv('天津港数据.csv') # 导入数据
data['MMSI'].unique().size # MMSI为船舶识别号码,查看下共有多少船舶
data['RECORD_DATETIME'] = pd.to_datetime(data['RECORD_DATETIME'])
pd.to_datetime(data['RECORD_DATETIME'],format='%Y%m%d%H%M')
# 将时间列转换为datetime格式
data['year'] = data['RECORD_DATETIME'].dt.year
data['month'] = data['RECORD_DATETIME'].dt.month
data['day'] = data['RECORD_DATETIME'].dt.day
data['hour'] = data['RECORD_DATETIME'].dt.hour
data['minute'] = data['RECORD_DATETIME'].dt.minute
# 将其中的年月日时分提取,分别成列。方便后续训练模型。
data1 = data.iloc[:,1:9] # 将其中需要训练的特征提取
data2 = data.iloc[:,-5:] # 将刚刚的年月日时分列提取(强迫症,喜欢将时间放前列)
data = pd.concat([data2, data1],axis=1) # 进行数据拼接(时间列与非时间特征列组合)
模型搭建及调参优化
from sklearn.cluster import DBSCAN import numpy as np model = DBSCAN(eps=10000000, min_samples=1300) # eps参数为圆半径,min_samples参数为圆半径内的最小样本数 model.fit(data) # 输入特征数据 label = model.labels_ np.unique(label, return_counts=True) # 查看分类情况,在这里输出的是一个数组,我们可以观察到分成几类,return_counts=True参数可以查看每个分组的个数,来判断分组的情况。可视化
import seaborn as sns
import matplotlib.pyplot as plt
data['group'].replace({-1:'簇1',0:'簇2(噪声)',1:'簇3',2:'簇4',3:'簇5',4:'簇6'},inplace=True)
# 将分组后数值标签转换为文本标签,为了后续可视化展示,这里的'(噪声)'是可视化后判断添加的,并非一看是就知道簇2是噪声
# 1.第一张图查看聚类情况(横坐标为经度,纵坐标为纬度)
sns.set_theme(context='notebook',style='darkgrid',palette='hls',font='SimHei',font_scale=1,rc=None)
fig = plt.figure(figsize=(15,8))
ax1 = fig.add_subplot(111)
markers = {'簇1':'o','簇2(噪声)':'X','簇3':'o','簇4':'o','簇5':'o','簇6':'o'}
sns.scatterplot(x='LONGITUDE', y='LATITUDE', hue='group', data=data, markers=markers, style='group')
ax1.legend(title='',fontsize=10)
ax1.set_xlabel('经度',size=20)
ax1.set_ylabel('纬度',size=20)
plt.xticks(size=12)
plt.yticks(size=12)
plt.title('天津港段船舶AIS轨迹及典型轨迹',size=30)
plt.tight_layout()
plt.savefig(r'天津港段船舶AIS轨迹及典型轨迹.jpg',dpi=500)
# 2.第二张图查看聚类后回归曲线(横坐标为经度,纵坐标为纬度)
fig = plt.figure(figsize=(15,8))
ax1 = fig.add_subplot(111)
img=plt.imread('天津港.jpg')
# 这里从高德地图中下载了天津港的地图图片
ax1.imshow(img, extent=[117.88,118.04,38.89,38.965])
for i in range(3,7):
sns.regplot(x='LONGITUDE', y='LATITUDE',data=data[(data.group=='簇'+str(i))], order=2, scatter=None, label='簇'+str(i))
ax1.legend(title='',fontsize=16)
ax1.set_xlabel('经度',size=20)
ax1.set_ylabel('纬度',size=20)
plt.xticks(size=12)
plt.yticks(size=12)
plt.title('天津港段船舶运动典型轨迹',size=30)
plt.tight_layout()
plt.savefig(r'天津港段船舶运动典型轨迹.jpg',dpi=500)
# 这张图其实做的不是很好,第一地图下载的有点问题(经纬度不是很对应);第二回归曲线展示船舶运动轨迹的效果不是很好。希望有大佬指点!
总结
心路历程
仅仅是我个人的理解。这里可以看到圆的半径非常大,其实也是我不断傻瓜式调参优化后的结果,这里最关键的一个数据集是船号,因为它是在太大了(与其他特征值相比较),因此可能会导致其他数据基本上无法发挥作用。考虑到这时,我想到的第一个解决方案就是标准化,可以有效解决部分特征数值过大从而影响整个特征集。
我并没有标准化的原因有两个:1)是否会过多削弱船号特征值的影响(个人认为船号在航行轨迹聚类中还是非常非常关键的,可以区分每艘船的行动轨迹)。2)完全就是想尝试下在非标准化时能否做到还不错的聚类效果。当然如果你的数据集过大的话,还是建议标准化比较好,因为我在这个过程中调了太多次参数了。这里首先可以结合查看聚类分组个数以及分组后每个组的数据量来初步判断聚类效果,后续再观察可视化结果。
小白笔记,希望大佬们指点!有错误还希望请指出!



