【PyTorch深度学习】对共享单车轨迹数据进行出入流预测实战(附源码和数据集)

需要源码和数据集请点赞关注收藏后评论区留言私信~~~

一、数据预处理及可视化

基于经纬度的共享单车数据集,如CitiBike、DivvyBike等。首先,我们对数据集进行简单的分析及预处理,并进行可视化

然后,进一步讲述如何根据经纬度,对整个城市进行网格划分,将整个城市看作一张“图片”,从原始GPS轨迹数据提取城市不同区域的出入流时空信息

以2015年1月CitiBik数据为例,首先利用Python3和Pandas对数据进行读取,并输出前5条示例数据

由于字段太多,我们只取所需信息字段,并更改字段名,使其更为直观

然后利用Matplotlib库,统计并可视化共享单车于不同经纬度的使用频率,本节以乘客的上车点为例,经纬度间隔设置为0.002

结果如下图所示,可以发现人们在市中心的活动频率比较高,符合真实世界的规律。共享单车上车点于不同经度(左图)、纬度(右图)使用频率分布

 最后,我们取样一部分数据,分析数据的空间分布,并利用散点图进行可视化

结果如下图所示,结合纽约市地图,可以发现,大部分使用共享单车的乘客集中在纽约市中心,符合客观规律。(横轴:经度、纵轴:纬度)

在正式讲述代码前,我们先给出几个必要的定义来帮助我们更好地理解上述概念

定义1 (子区域):基于经纬度,将城市分成m ×n个网格,每个网格为一个子区域且大小相等,用R=r_1,1,…,r_i,j,…,r_{_m,n}来表示这些子区域,其中r_i,j代表第i行、第j列的子区域

定义2(出入流“图像”):设P是共享单车轨迹的集合,给定一个子区域r_i,j,其相应的入流与出流定义如下:

其中T_r:g_1→g_2→…→g_T_r是P中在时间间隔t的一段子轨迹,g_l∈r_i,j表示g_l位于区域r_i,j中,|⋅|代表集合的势。将在时间间隔t的入流和出流定义成人流张量(Tensor) X^t∈ℛ^m×n×2,其中 X_i,j,0^t=x_in,i,j^t,X_i,j,1^t=x_out,i,j^t

现从代码角度讲述如何将基于GPS的原始轨迹数据进行区域划分并建模成上述出入流“图像”数据。 首先,导入所需要的库,定义函数geo_to_grid(),将城市划分成网格,geo_to_grid()函数返回子区域的编号

然后,定义bike_get_day_hour()函数,对原始轨迹中的时间字段进行解析,获取当前记录的日期和时间

结合上述数据分析与预先定义的geo_to_grid()与bike_get_day_hour()函数,对原始轨迹数据进行解析,以1小时为时间间隔,将城市划分成子区域,并获取数据集中不同日期的不同时间段各个子区域的出入流数据,并存储到预先定义好的flow_array矩阵中,最后将处理好的数据按月存储到本地当前目录下

bike_stat()函数首先读取CitiBike原始轨迹数据,提取原始所需信息,如时间、经纬度等,并设置经纬度过滤条件,即所在城市的经纬度范围,将在所需经纬度之外的数据点作为异常点删除,然后对城市进行网格划分,并获取相应轨迹数据的起始点网格编号、终止点网格编号、日期、时间等信息,根据上述出入流公式,计算每个网格代表的子区域的出入流,并存储到矩阵对应的位置,最后将矩阵本地化存储于当前目录

二、问题陈述及模型框架

 基于公开的共享单车CitiBike数据集与纽约出租车数据集NYCTaxi,构建一个简单的基于迁移学习的深度学习模型,进行实战应用与详解

在真实应用中,由于各种原因,如数据采集机制落后、数据隐私保护等,我们在某些城市能获取到的共享单车数据可能十分有限,而城市中的某些时空数据又十分丰富,如出租车数据等。出租车数据与共享单车数据存在时空相关性,可以借助出租车数据蕴含的知识辅助共享单车进行预测,本节利用迁移学习中深度域适应网络的思想,利用最大均值差异(Maximum Mean Discrepancy, MMD),建立一个深度时空域适应网络,从数据分布的角度,借助数据丰富的源域城市知识辅助数据稀疏的目标域城市,进行共享单车的出入流预测。

模型框架如图所示 :

给定一个数据充足的源域和一个数据稀疏的目标域,首先采用堆叠的3D卷积(Conv3D)网络将原始的时空共享单车数据映射到一个公共嵌入空间中,然后使用全连接网络(FC)学习每个域特有的特征,并将这些特征嵌入到希尔伯特可再生核空间中,利用最大均值差异MMD作为约束,减少域间差异,从而达到知识迁移的目的,最终,借助一层全连接网络进行预测

我们假设纽约出租车数据NYCTaxi丰富,纽约共享单车CitiBike数据稀疏,故将NYCTaxi作为源域,CitiBike作为目标域,具体来说,我们将所有经过数据预处理的NYCTaxi训练数据输入模型,而随机采样一部分CitiBike数据用于训练

三、数据准备

使用2015年全年的CitiBike和NYCTaxi数据,以1小时为时间粒度,按照上述数据预处理生成区域级的出入流数据,使用过去3个时间步预测未来1个时间步的数据,对于源域,选择前300天的数据为训练集,对于目标域在前300天中随机选取64个时间片的数据进行训练,此后32天的数据做为验证集,最后32天的数据为测试集

首先,整合十二个月的数据,制作所需的数据集,并进行归一化,改写torch.utils.data.Dataset中的__getitem__和__len__来载入我们自己的数据集。并在创建完Dataset类之后,与dataloader一起使用,以在模型训练时不断为模型提供数据

 四、模型构建

模型构建方面,本节主要构建三个网络:

SharedNet MMDNet  PredictNet

首先,建立SharedNet,使用堆叠的3D卷积层学习原始出入流“图像”序列的数据表示,3D卷积可以用于同时捕获空间与时间相关性,我们利用3D卷积层对出入流“图像”序列数据进行表示学习,以编码时空依赖,所有的3D卷积层由两个域的数据共享,从而将源域和目标域嵌入到一个共同的潜在表示空间中,在SharedNet中使用五层3D卷积层,可视实际情况加减层数

为了迁移两个域之间的知识,我们借助深度自适应网络DAN的思想,将由3D卷积层学到的源域与目标域的特征输入到本节建立的深度自适应网络MMDNet中, MMDNet由堆叠的全连接网络组成,目的在于学习每个域的特征,同时,使用最大均值差异MMD作为约束进行知识迁移,为了学习两个域之间的可迁移特征,MMDNet将每个域的数据表示嵌入到可再生希尔伯特空间中,然后将最大均值差异MMD作为约束,通过梯度下降与反向传播算法,将在可再生希尔伯特空间中源域与目标域的分布距离拉近,达到知识迁移的目的

最后,将源域特征输入PredictNet进行预测,PredictNet由一层全连接网络组成,可视情况进行层数加减

五、模型训练以及测试

模型搭建完成后,接着训练神经网络,并对模型进行验证,以保存相对最优模型

首先,设定超参数,如学习率等,将epoch总数设置为200

均方根误差RMSE和最大均值差异加权求和作为损失函数,使用Adam优化器对模型进行优化。对每一个epoch,在训练集上,按批次将数据输入网络学习特征,并进行预测,将预测的结果与真实值进行比较,利用设计好的损失函数与梯度下降方法优化模型

然后,每隔几个epoch对模型进行验证,如果结果比上次更好,则保存更好的模型

模型测试部分代码与上述代码中模型验证部分类似,但稍有不同。 测试过程首先需要理由torch.load()函数将保存于本地的最佳模型重新导入,然后利用load_state_dict()函数将保存的参数字典加载到模型中,此时,模型中的参数即为训练过程中训练好的参数

六、模型训练过程及结果展示

SharedNet模型展示:

MMDNet模型展示:

 

PredictNet模型展示:

 

模型训练过程展示:

 七、代码

 最后 部分代码如下 需要源码和数据集请点赞关注收藏后评论区留言私信~~~

预处理代码如下

import numpy as np
import pandas as pd
import os
import multiprocessing as mul
import time
import warnings
warnings.filterwarnings('ignore')

# data_path与bike_bike可根据自身电脑数据集存储路径进行相应更改
data_path = '
bike_path ='
INTERVAL_NUM = 32
LNG_START = -74.02
LNG_STOP = -73.95
LNG_INTERVAL = abs(LNG_STOP - LNG_START) / INTERVAL_NUM
LAT_START = 40.67
LAT_STOP = 40.77
LAT_INTERVAL = abs(LAT_STOP - LAT_START) / INTERVAL_NUM


def geo_to_grid(geo):
    lat, lng = geo[0], geo[1]
    if (lat > LAT_STOP
            or lat < LAT_START
            or lng > LNG_STOP
            or lng < LNG_START
    ):
        return -1
    lng_ind = int(np.floor((lng - LNG_START) / LNG_INTERVAL))
    lat_ind = int(np.floor((lat - LAT_START) / LAT_INTERVAL))
    return lng_ind, lat_ind


def bike_get_day_hour(x):
    time_list = x.split(' ')
    date_str = time_list[0]
    date_list = date_str.split('/')
    time_str = time_list[1]
    day = int(date_list[1])
    hour = int(time_str.split(':')[0])
    return day, hour


def bike_stat(date_str):
    tl = []
    tl.append(time.time())
    df = pd.read_csv(data_path + '{}-citibike-tripdata.csv'.format(date_str), sep=',')
    tl.append(time.time())
    print('%s Data loaded in %.2fsec' %(date_str, (tl[-1]-tl[-2])))

    df_columns = [
        'starttime', 'stoptime',
        'start station longitude','start station latitude',
        'end station longitude', 'end station latitude'
    ]
    new_col_name = [
        'pick_up_time', 'drop_off_time',
        'pick_up_lon', 'pick_up_lat',
        'drop_off_lon', 'drop_off_lat'
    ]

    df = df.loc[:,df_columns]
    df.columns = new_col_name
    df_main = df[
        (LNG_START<df.pick_up_lon) & (df.pick_up_lon < LNG_STOP)
        & (LAT_START<df.pick_up_lat) & (df.pick_up_lat < LAT_STOP)
        & (LNG_START<df.drop_off_lon) & (df.drop_off_lon < LNG_STOP)
        & (LAT_START<df.drop_off_lat) & (df.drop_off_lat < LAT_STOP)
        ]


    df_main.pick_up_time = df_main.pick_up_time.apply(str)
    df_main.drop_off_time = df_main.drop_off_time.apply(str)
    with mul.Pool(48) as pool:
        pick_up_array = np.array(list(pool.map(bike_get_day_hour, df_main.pick_up_time)))
        drop_off_array = np.array(list(pool.map(bike_get_day_hour, df_main.drop_off_time)))
        start_grid_array = np.array(list(pool.map(geo_to_grid, df_main.loc[:, ["pick_up_lat", "pick_up_lon"]].values)))
        end_grid_array = np.array(list(pool.map(geo_to_grid, df_main.loc[:, ["drop_off_lat", "drop_off_lon"]].values)))
    tl.append(time.time())
    print('Get %s Using %.2f min' % (date_str, (tl[-1]-tl[-2])/60))
    df_main['start_x'] = start_grid_array[:, 0]
    df_main['start_y'] = start_grid_array[:, 1]
    df_main['end_x'] = end_grid_array[:, 0]
    df_main['end_y'] = end_grid_array[:, 1]
    df_main['start_day'] = pick_up_array[:, 0]
    df_main['start_hour'] = pick_up_array[:, 1]
    df_main['end_day'] = drop_off_array[:, 0]
    df_main['end_hour'] = drop_off_array[:, 1]

    start_g = df_main.groupby(['start_day','start_hour', 'start_x', 'start_y'])
    end_g = df_main.groupby(['end_day','end_hour', 'end_x', 'end_y'])
    start_count = start_g.agg('count').reset_index()
    end_count = end_g.agg('count').reset_index()
    print(date_str, 'have {} days which contains {} hours'.format(max(df_main.start_day), max(df_main.start_hour) + 1))
    flow_array = np.zeros((max(df_main.start_day), max(df_main.start_hour) + 1, INTERVAL_NUM, INTERVAL_NUM, 2))
    for row in start_count.iterrows():
        x_vle = row[1]['start_x']
        y_vle = row[1]['start_y']
        day = row[1]['start_day']-1
        hour = row[1]['start_hour']
        times = row[1]['end_day']
        try:
            flow_array[day, hour, x_vle, y_vle, 0] = times
        except:
            print(row[1])
    for row in end_count.iterrows():
        x_vle = row[1]['end_x']
        y_vle = row[1]['end_y']
        day = row[1]['end_day']-1
        hour = row[1]['end_hour']
        times = row[1]['start_day']
        try:
            flow_array[day, hour, x_vle, y_vle, 1] = times
        except:
            print(row[1])
    flow_array.tofile('./Data/CitiBike/' +'bike_%s' % date_str)
    print('%s Finished %.2f min' % (date_str, (tl[-1] - tl[-2]) / 60))
    print('*'*100)


def bike_count():
    global INTERVAL_NUM, LNG_START, LNG_STOP, LNG_INTERVAL
    global LAT_START, LAT_STOP, LAT_INTERVAL
    global data_path, taxi_path
    data_path = 'E:/CodingDocument/Pycharm/STMultiFlow/DataPreprocessing/DataSet/raw/Bike/'
    taxi_path = 'E:/CodingDocument/Pycharm/STMultiFlow/DataPreprocessing/DataSet/raw/Bike/'
    # taxi lng/lat
    INTERVAL_NUM = 32
    LNG_START = -74.02
    LNG_STOP = -73.95
    LNG_INTERVAL = abs(LNG_STOP - LNG_START) / INTERVAL_NUM
    LAT_START = 40.67
    LAT_STOP = 40.77
    LAT_INTERVAL = abs(LAT_STOP - LAT_START) / INTERVAL_NUM
    for year in range(2015, 2016):
        for mon in range(1, 13):
            if mon < 10:
                date_str = str(year) + '0' + str(mon)
            else:
                date_str = str(year) + str(mon)
            file_str = taxi_path+'bike_%s' % date_str
            if os.path.exists(file_str):
                print('bike_%s' % date_str + ' Exist!!!')
            else:
                bike_stat(date_str)


if __name__ == '__main__':
    bike_count()

训练代码

import numpy as np
import time
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt

from SrcDataLoader import *
from TgtDataLoader import *
from Shared_net import SharedNet
from model import MMDNet, PredictNet
from loss import *

data_split = [72*24, 20*24, 20*24]
# tgt_split = [32, 20*24, 20*24]
batch_size = 32

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# source_pretrain_dataset = PeopleDataset(mode='train', split=data_split)
# source_pretrain_loader = DataLoader(dataset=source_pretrain_dataset, batch_size=32, shuffle=True)

# source_prevalidate_dataset = PeopleDataset(mode='validate', split=data_split)
# source_prevalidate_loader = DataLoader(dataset=source_prevalidate_dataset, batch_size=32, shuffle=False)

# source_pretest_dataset = PeopleDataset(mode='test', split=data_split)
# source_pretest_loader = DataLoader(dataset=source_pretest_dataset, batch_size=32, shuffle=False)

src_train_dataset = NYCTaxiDataset(mode='train', split=data_split)
src_train_dataloader = DataLoader(dataset=src_train_dataset, batch_size=32, shuffle=True)

src_validate_dataset = NYCTaxiDataset(mode='validate', split=data_split)
src_validate_dataloader = DataLoader(dataset=src_validate_dataset, batch_size=32, shuffle=False)

src_test_dataset = NYCTaxiDataset(mode='test', split=data_split)
src_test_dataloader = DataLoader(dataset=src_test_dataset, batch_size=32, shuffle=False)

tgt_train_dataset = CitiBikeDataset(mode='train', split=data_split)
tgt_train_dataloader = DataLoader(dataset=tgt_train_dataset, batch_size=32, shuffle=False)

tgt_validate_dataset = CitiBikeDataset(mode='validate', split=data_split)
tgt_validate_dataloader = DataLoader(dataset=tgt_validate_dataset, batch_size=32, shuffle=False)

tgt_test_dataset = CitiBikeDataset(mode='test', split=data_split)
tgt_test_dataloader = DataLoader(dataset=tgt_test_dataset, batch_size=32, shuffle=False)

len_src_train = len(src_train_dataloader)
len_tgt_train = len(tgt_train_dataloader)

# lr = 0.0001
lr = 0.00001
l2_decay = 5e-4
num_epoches = 200

src_loss_list = []
total_loss_list = []
tgt_val_loss_list = []

seed = 32
np.random.seed(seed=seed)
torch.manual_seed(seed)
if device == 'cuda':
    torch.cuda.manual_seed(seed)

task_criterion = nn.MSELoss()

BaseNet = SharedNet().to(device)
# BaseNet.load_state_dict(torch.load('./Trained_model/best_model_normlization.pkl'))
TransferNet = MMDNet().to(device)
TaskNet = PredictNet().to(device)

for param in BaseNet.parameters():
    param.requires_grad = False

optimizer = optim.Adam([
    {'params': BaseNet.parameters()},
    {'params': TransferNet.parameters()},
    {'params': TaskNet.parameters()}], lr=lr, weight_decay=l2_decay)
best_rmse = 10000

for epoch in range(num_epoches):
    t0 = time.time()
    BaseNet.train()
    TransferNet.train()
    TaskNet.train()

    src_train_aver_rmse = 0
    mmd_loss = 0
    
    iter_src = iter(src_train_dataloader)
    iter_tgt = iter(tgt_train_dataloader)

    num_iter = len_src_train

    for i in range(0, num_iter):
        src_data_x, src_data_y = next(iter_src)
        tgt_data_x, tgt_data_y = next(iter_tgt)

        if (i+1) % len_tgt_train == 0:
            iter_tgt = iter(tgt_train_dataloader)
        
        src_data_x = src_data_x.float().to(device)
        src_data_y = src_data_y.float().to(device)
        tgt_data_x = tgt_data_x.float().to(device)
        tgt_data_y = tgt_data_y.float().to(device)

        optimizer.zero_grad()

        # print(src_data_x.shape, tgt_data_x.shape)
        
        inputs = torch.cat((src_data_x, tgt_data_x), dim=0)
        features = BaseNet(inputs)
        features = TransferNet(features)
        outputs = TaskNet(features)

        # print(outputs.shape, src_data_y.shape, inputs.size(0)/2)

        task_loss = torch.sqrt(task_criterion(outputs.narrow(0, 0, int(inputs.size(0)/2)), src_data_y))

        transfer_loss = DAN(features.narrow(0, 0, int(features.size(0)/2)), features.narrow(0, int(features.size(0)/2), int(features.size(0)/2)))

        total_loss = 0.1*transfer_loss + task_loss
        
        src_train_aver_rmse += total_loss.item()
        mmd_loss += transfer_loss.item()
        total_loss.backward()
        optimizer.step()
    src_train_aver_rmse /= len_src_train
    mmd_loss /= len_src_train
    src_loss_list.append(src_train_aver_rmse)
    total_loss_list.append(src_train_aver_rmse+mmd_loss)
    
    if (epoch+1) % 5 == 0 or epoch == 0:
        BaseNet.eval()
        TransferNet.eval()
        TaskNet.eval()
        tgt_validate_aver_rmse = 0
        len_tgt_validate = len(tgt_validate_dataloader)
        for i, (tgt_data_x, tgt_data_y) in enumerate(tgt_validate_dataloader):
            tgt_data_x, tgt_data_y = tgt_data_x.float().to(device), tgt_data_y.float().to(device)
            features = TransferNet(BaseNet(tgt_data_x))
            tgt_output = TaskNet(features)
            tgt_loss = torch.sqrt(task_criterion(tgt_output, tgt_data_y))
            tgt_validate_aver_rmse += tgt_loss.item()
        tgt_validate_aver_rmse /= len_tgt_validate
        tgt_val_loss_list.append(tgt_validate_aver_rmse)
        if tgt_validate_aver_rmse < best_rmse:
            best_rmse = tgt_validate_aver_rmse
            torch.save(BaseNet.state_dict(), 'best_BaseNet.pkl')
            torch.save(TransferNet.state_dict(), 'best_TransferNet.pkl')
            torch.save(TaskNet.state_dict(), 'best_TaskNet.pkl')
    t1 = time.time()
    print('Epoch: [{}/{}], Source train loss: {}, MMD loss: {}, tgt_best_validate_loss: {}, Cost {}min.'.format(epoch+1, num_epoches, src_train_aver_rmse, mmd_loss, best_rmse, (t1-t0)/60))

# loss_train_list = np.array(loss_train_list)
# loss_validate_list = np.array(loss_validate_list)
# np.save('loss_train_normlization.npy', loss_train_list)
# np.save('loss_validate_normlization.npy', loss_validate_list)

src_loss_list = np.array(src_loss_list)
total_loss_list = np.array(total_loss_list)
tgt_val_loss_list = np.array(tgt_val_loss_list)

np.save('src_loss_train_64hours.npy', src_loss_list)
np.save('total_loss_train_64hours.npy', total_loss_list)
np.save('tgt_loss_validate_64hours.npy', tgt_val_loss_list)

创作不易 觉得有帮助请点赞关注收藏~~~

猜你喜欢

转载自blog.csdn.net/jiebaoshayebuhui/article/details/130461710