1 波尔图数据
curl http://archive.ics.uci.edu/ml/machine-learning-databases/00339/train.csv.zip -o data/porto.csv.zip
这条命令使用 curl
从给定的URL下载一个名为 train.csv.zip
的压缩文件,并将其保存为 data/porto.csv.zip
。
unzip data/porto.csv.zip
使用 unzip
命令解压 data/porto.csv.zip
这个文件。
mv train.csv data/porto.csv
这条命令将解压后的 train.csv
文件移动(或重命名)到 data
目录下,并将其命名为 porto.csv
。
cd preprocessing
进入名为 preprocessing
的目录。
julia porto2h5.jl
使用 Julia 语言执行 porto2h5.jl
这个脚本。将 porto.csv
转换为 .h5
格式的命令。
julia preprocess.jl
使用 Julia 语言执行 preprocess.jl
脚本。对数据进行一些预处理的操作。
1.1 porto2h5.jl
using ArgParse
#导入 ArgParse 模块( Julia 中用于解析命令行参数的一个库)
include("utils.jl")
#导入了 utils.jl 文件
args = let s = ArgParseSettings()
@add_arg_table s begin
"--datapath"
arg_type=String
default="/home/xiucheng/Github/t2vec/data"
end
parse_args(s; as_symbols=true)
end
#定义 args:
#`let s = ArgParseSettings() 创建一个新的 ArgParseSettings 对象,这是用于配置命令行参数解析的。
#`@add_arg_table s begin ... end 是一个宏,用于添加参数到 s 中。
# 这里定义了一个名为 --datapath 的参数,它的类型是 String,默认值是 "/home/xiucheng/Github/t2vec/data"。
#parse_args(s; as_symbols=true) 解析命令行参数,并将结果存储在 args 中。
datapath = args[:datapath]
#从 args 中取出 --datapath 参数的值,并将其赋值给 datapath 变量。
porto2h5("$datapath/porto.csv")
#调用 porto2h5 函数,参数是 porto.csv 文件的完整路径。
#这个函数是在 utils.jl 中定义的,它的功能是将 CSV 格式的数据转换为 .h5 格式。
1.2 utils.jl
波尔图数据集:数据集笔记: Porto_UQI-LIUWJ的博客-CSDN博客
using CSV, DataFrames, HDF5
#导入三个模块:CSV、DataFrames、HDF5。
#这些模块分别用于处理CSV文件、数据框操作和HDF5文件操作。
"""
读取原始的波尔图出租车csv数据,将所有trip保存至hdf5文件中
每一个trip都是一个2*n的矩阵,n是trip的长度,每一行是经纬度
"""
function porto2h5(csvfile::String)
df = CSV.File(csvfile) |> DataFrame
#使用CSV模块读取CSV文件,然后将其转化为DataFrame对象。
df = df[df.MISSING_DATA .== false, :]
#对DataFrame df 进行筛选,去除其中 MISSING_DATA 列值为 true 的行。
sort!(df, [:TIMESTAMP])
#对DataFrame df 进行排序,按照 TIMESTAMP 列进行排序。
println("Processing $(size(df, 1)) trips...")
## 打印一个消息,告诉用户正在处理多少条出行记录。
h5open("../data/porto.h5", "w") do f
#使用HDF5模块打开(或创建)一个HDF5文件,并以写入模式进行操作。
#这个文件将保存处理后的数据。
num, num_incompleted = 0, 0
'''
初始化两个变量:
num 记录成功保存的出行记录数量
num_incompleted 记录不完整的出行记录数量
'''
for trip in df.POLYLINE
对DataFrame中的 POLYLINE 列进行遍历,每一个元素都是一条出行轨迹。
try
trip = Meta.parse(trip) |> eval
catch e
num_incompleted += 1
continue
end
'''
使用 Meta.parse 尝试解析 trip,然后使用 eval 进行求值。
如果解析或求值失败,增加 num_incompleted 的计数并跳过这次循环。
'''
tripLength = length(trip)
tripLength == 0 && continue
#获取出行轨迹的长度。如果长度为0,则跳过这次循环。
trip = hcat(trip...)
#使用 hcat 函数将 trip 里的元素水平串联起来。
num += 1
#增加成功处理的出行记录的数量。
f["/trips/$num"] = trip
#在HDF5文件中保存处理后的出行轨迹。
f["/timestamps/$num"] = collect(0:tripLength-1) * 15.0
#在HDF5文件中保存处理后的出行轨迹。
num % 100_000 == 0 && println("$num")
end
attributes(f)["num"] = num
#在HDF5文件的属性中保存成功处理的出行记录的总数量。
println("Incompleted trip: $num_incompleted.\nSaved $num trips.")
end
end
1.2.1 补充说明(Mata.parse)
Meta.parse
是Julia中的一个函数,它可以将一个字符串解析为Julia的表达式。- 在上述代码中,假设
trip
原始的值是一个表示列表或数组的字符串,例如:"[(2.0, 3.0), (4.0, 5.0)]"
,那么使用Meta.parse
可以将其转换为Julia中的一个真正的列表或数组。 - 一旦你有了一个Julia表达式,你想要执行它。这就是
eval
函数的作用。eval
取一个表达式作为输入,并执行它,返回执行的结果。- 不使用
eval
,你得到的是一个描述表达式结构的Expr
对象,而不是表达式的实际执行结果
- 还是以上面的
"[(2.0, 3.0), (4.0, 5.0)]"为例:
不使用eval的话:输出类型就是Expr 表达式
-
str = "[(2.0, 3.0), (4.0, 5.0)]" expr = Meta.parse(str) println(expr) println(typeof(expr)) ''' [(2.0, 3.0), (4.0, 5.0)] Expr '''
-
- 使用eval的话:输出类型就是float数组
-
str = "[(2.0, 3.0), (4.0, 5.0)]" expr = Meta.parse(str) |> eval println(expr) println(typeof(expr)) ''' [(2.0, 3.0), (4.0, 5.0)] Vector{Tuple{Float64, Float64}} '''
-
- 在上述代码中,假设
1.2.2 hcat(trip...)补充说明
- 在Julia中,
...
(也称为"splatting"操作符)用于将数组或集合的元素展开为单独的参数trip
是一个包含多个元组的数组,如[(a1, b1), (a2, b2), ...]
,使用trip...
会展开这些元组,效果等同于列出它们:(a1, b1), (a2, b2), ...
。
hcat
是Julia中的一个函数,用于水平地串联数组。它将多个数组(或元组)作为输入,并将它们并排放置,形成一个新的二维数组。
trip = [(2.0, 3.0), (4.0, 5.0), (6.0, 7.0)]
result = hcat(trip...)
result
''
1×3 Matrix{Tuple{Float64, Float64}}:
(2.0, 3.0) (4.0, 5.0) (6.0, 7.0)
'''
1.3 preporcess.jl
1.3.1 一些定义和命令行参数
using JSON
using DataStructures
using NearestNeighbors
using Serialization, ArgParse
'''
导入了五个模块/库:
`JSON(处理JSON数据)
`DataStructures(提供数据结构)
`NearestNeighbors(最近邻搜索)
`Serialization(序列化和反序列化对象)
`ArgParse(命令行参数解析)
'''
include("SpatialRegionTools.jl")
args = let s = ArgParseSettings()
@add_arg_table s begin
"--datapath"
arg_type=String
default="/home/xiucheng/Github/t2vec/data"
end
parse_args(s; as_symbols=true)
end
'''
使用 ArgParse 库定义并解析命令行参数。
定义了一个命令行参数 --datapath,其类型为字符串,并为其提供了默认值。
'''
datapath = args[:datapath]
#从解析的命令行参数中提取 datapath 参数的值,并赋值给变量 datapath
1.3.2 hyper-parameters.json相关的内容
param = JSON.parsefile("../hyper-parameters.json")
'''
使用 JSON.parsefile 函数读取一个名为 "hyper-parameters.json" 的JSON文件,并将内容解析为Julia的数据结构。
'''
regionps = param["region"]
cityname = regionps["cityname"]
cellsize = regionps["cellsize"]
#从解析的JSON数据中提取关于空间区域的参数,包括城市名称和单元格大小。
if !isfile("$datapath/$cityname.h5")
println("Please provide the correct hdf5 file $datapath/$cityname.h5")
exit(1)
end
#检查是否存在一个名为 cityname.h5 的HDF5文件。如果文件不存在,打印错误信息并退出程序。
1.3.2 SpatialRegion 对象
region = SpatialRegion(cityname,
regionps["minlon"], regionps["minlat"],
regionps["maxlon"], regionps["maxlat"],
cellsize, cellsize,
regionps["minfreq"], # minfreq
40_000, # maxvocab_size
10, # k
4) # vocab_start
#使用提取的参数创建一个 SpatialRegion 对象(一个自定义的数据结构,在"SpatialRegionTools.jl" 文件中定义。
println("Building spatial region with:
cityname=$(region.name),
minlon=$(region.minlon),
minlat=$(region.minlat),
maxlon=$(region.maxlon),
maxlat=$(region.maxlat),
xstep=$(region.xstep),
ystep=$(region.ystep),
minfreq=$(region.minfreq)")
#打印关于创建的 SpatialRegion 对象的详细信息。
paramfile = "$datapath/$(region.name)-param-cell$(Int(cellsize))"
if isfile(paramfile)
println("Reading parameter file from $paramfile")
region = deserialize(paramfile)
else
println("Creating paramter file $paramfile")
num_out_region = makeVocab!(region, "$datapath/$cityname.h5")
serialize(paramfile, region)
end
'''
检查参数文件是否已经存在:
如果存在,则从该文件中读取 SpatialRegion 对象的数据。
如果不存在,则创建新的参数文件,并使用 makeVocab! 函数(在 "SpatialRegionTools.jl" 中定义)来填充它,并将 SpatialRegion 对象序列化到文件中。
'''
println("Vocabulary size $(region.vocab_size) with cell size $cellsize (meters)")
#打印空间区域的词汇大小和单元格大小信息。
println("Creating training and validation datasets...")
createTrainVal(region, "$datapath/$cityname.h5", datapath, downsamplingDistort, 1_000_000, 10_000)
#打印消息并使用 createTrainVal 函数(在 "SpatialRegionTools.jl" 中定义)创建训练和验证数据集。
saveKNearestVocabs(region, datapath)
#使用 saveKNearestVocabs 函数(在 "SpatialRegionTools.jl" 中定义)保存最近的词汇信息。
1.4 SpatialRegionTools.jl
1.4.1 导入库和SpatialRegion结构体
#module SpatialRegionTools
using StatsBase:rle
using HDF5
using DataStructures
using NearestNeighbors
using Statistics, Printf
include("utils.jl")
'''
导入库和模块:
从StatsBase库导入rle函数。
导入HDF5库,用于处理HDF5格式的数据。
导入DataStructures库,提供各种数据结构。
导入NearestNeighbors库,用于最近邻搜索。
导入Statistics和Printf模块。
包含外部文件"utils.jl"。
'''
#export SpatialRegion, makeVocab!, gps2vocab, saveKNearestVocabs,
# trip2seq, seq2trip, createTrainVal
#列出了此模块中定义的功能和数据结构,这些功能和数据结构可以在外部使用
const UNK = 3
"""
example:
SpatialRegion的使用示例。
region = SpatialRegion("porto",
-8.735152, 40.953673,
-8.156309, 41.307945,
cellsize, cellsize,
50, # minfreq
50_000, # maxvocab_size
5, # k
4) # vocab_start
"""
mutable struct SpatialRegion
#声明一个可变的结构体(即其字段的值可以在创建后被更改)名为SpatialRegion。
name::String #区域的名称,例如城市名。
minlon::Float64
minlat::Float64
maxlon::Float64
maxlat::Float64
'''
定义了一个边界框(bounding box),通过其最小和最大的经度和纬度来描述。
这个边界框表示空间区域的地理范围。
'''
minx::Float64
miny::Float64
maxx::Float64
maxy::Float64
#边界框在某种度量空间(如米)上的表示。它表示经纬度转换为米后的坐标。
xstep::Float64
ystep::Float64
#在x和y方向上的单元格大小或步长
numx::Int
numy::Int
#在x和y方向上的单元格数量
minfreq::Int
#最小频率,某个单元格中的轨迹数量达到多少时才被认为是"热门"的
maxvocab_size::Int #词汇表的最大大小
k::Int
cellcount #描述每个单元格被击中(即有多少轨迹通过)的次数
hotcell::Vector{Int} #表示被标记为"热门"的单元格的列表或索引
hotcell_kdtree #与"热门"单元格相关的k-d树结构,用于高效的空间搜索
hotcell2vocab::Dict{Int, Int} #一个字典,将"热门"单元格映射到词汇表ID
vocab2hotcell::Dict{Int, Int} #一个字典,将词汇表ID映射回其对应的"热门"单元格
vocab_start::Int #词汇表开始的索引或ID
vocab_size::Int #词汇表的大小
built::Bool
#构造函数
function SpatialRegion(name::String,
minlon::Float64,
minlat::Float64,
maxlon::Float64,
maxlat::Float64,
xstep::Float64,
ystep::Float64,
minfreq::Int,
maxvocab_size::Int,
k::Int,
vocab_start::Int)
minx, miny = lonlat2meters(minlon, minlat)
maxx, maxy = lonlat2meters(maxlon, maxlat)
numx = round(maxx - minx, digits=6) / xstep
numx = convert(Int, ceil(numx))
numy = round(maxy - miny, digits=6) / ystep
numy = convert(Int, ceil(numy))
new(name,
minlon, minlat, maxlon, maxlat,
minx, miny, maxx, maxy,
xstep, ystep,
numx, numy, minfreq, maxvocab_size, k,
Accumulator{Int, Int}(),
Int[],
Any,
Dict(),
Dict(),
vocab_start,
vocab_start,
false)
end
end
1.4.2 cell id和Web Mercator坐标之间的转换
"""
将给定的Web Mercator坐标(x, y)转换为一个单元格ID
"""
function coord2cell(region::SpatialRegion, x::Float64, y::Float64)
xoffset = round(x - region.minx, digits=6) / region.xstep
yoffset = round(y - region.miny, digits=6) / region.ystep
#计算x/y坐标相对于区域的最小x/y坐标的偏移量(以单元格数计)
xoffset = convert(Int, floor(xoffset))
yoffset = convert(Int, floor(yoffset))
#将x/y的偏移量向下取整,以得到x/y在哪个单元格内
yoffset * region.numx + xoffset
#由于每行有region.numx个单元格,所以yoffset乘以region.numx得到前面所有行的单元格总数。
#然后再加上xoffset就得到了该坐标所在单元格的ID
end
"""
将一个单元格ID转换为对应的Web Mercator坐标(x, y)
"""
function cell2coord(region::SpatialRegion, cell::Int)
yoffset = div(cell, region.numx)
#将单元格ID除以每行的单元格数量。结果yoffset表示这个ID对应的单元格在第几行
xoffset = mod(cell, region.numx)
#计算单元格ID除以每行的单元格数量的余数。结果xoffset表示这个ID对应的单元格在这一行的第几列
y = region.miny + (yoffset + 0.5) * region.ystep
x = region.minx + (xoffset + 0.5) * region.xstep
'''
计算单元格中心的y坐标。
首先,从区域的最小y坐标开始,并加上yoffset乘以单元格的高度(即region.ystep),这样我们就得到了该单元格的上边缘的y坐标。
然后,再加上半个单元格的高度,即(0.5 * region.ystep)
这样就得到了该单元格中心的y坐标
'''
x, y
end
1.4.3 cell id 和经纬度之间的转换
'''
将给定的GPS坐标(经度lon和纬度lat)转换为一个单元格ID
'''
function gps2cell(region::SpatialRegion, lon::Float64, lat::Float64)
x, y = lonlat2meters(lon, lat)
#将经度和纬度转换为Web Mercator坐标系中的x和y坐标
coord2cell(region, x, y)
#调用之前定义的coord2cell函数,将Web Mercator坐标转换为对应的单元格ID。
end
'''
将给定的单元格ID转换回其对应的GPS坐标(经度和纬度)
'''
function cell2gps(region::SpatialRegion, cell::Int)
x, y = cell2coord(region, cell)
#将单元格ID转换为Web Mercator坐标系中的x和y坐标
meters2lonlat(x, y)
#将Web Mercator坐标转换回经度和纬度
end
1.4.4 SpatialRegion 内的相对偏移量
'''
根据提供的GPS坐标(经度lon和纬度lat)计算其在SpatialRegion区域内的相对偏移量
'''
function gps2offset(region::SpatialRegion, lon::Float64, lat::Float64)
x, y = lonlat2meters(lon, lat)
#将GPS坐标转换为Web Mercator坐标系下的x和y坐标
xoffset = round(x - region.minx, digits=6) / region.xstep
yoffset = round(y - region.miny, digits=6) / region.ystep
#计算x/y坐标的相对偏移量
xoffset, yoffset
end
1.4.5 inregion 点/轨迹 是否在指定region中
'''
检查给定的经度lon和纬度lat是否在SpatialRegion区域内
'''
function inregion(region::SpatialRegion, lon::Float64, lat::Float64)
lon >= region.minlon && lon < region.maxlon &&
lat >= region.minlat && lat < region.maxlat
'''
检查经度和纬度是否位于region定义的边界之内。
如果都在范围内,函数返回true
否则返回false
'''
end
'''
接受一个类型为Matrix{Float64}的trip参数,其中每列都是一个经度/纬度对
'''
function inregion(region::SpatialRegion, trip::Matrix{Float64})
for i = 1:size(trip, 2)
inregion(region, trip[:, i]...) || return false
end
true
'''
循环检查trip矩阵中的每一个经纬度对是否都在region区域内。
如果所有的点都在范围内,函数返回true
否则一旦发现某个点不在范围内就立即返回false
'''
end
1.4.6 MakeVocab! 为热点单元格创建词汇表
"""
该函数从hdf5文件中读取轨迹,统计每个单元格中的点数,并为最常出现的单元格建立一个词汇表
"""
function makeVocab!(region::SpatialRegion, trjfile::String)
region.cellcount = Accumulator{Int, Int}()
#为region初始化一个累加器,用于统计每个单元格中的点数
num_out_region = 0
#初始化一个变量来计数不在区域内的点
h5open(trjfile, "r") do f
#打开hdf5文件进行读取。
num = read(attributes(f)["num"])
#读取hdf5文件中的属性“num”,表示有多少轨迹。
for i = 1:num
trip = read(f["/trips/$i"])
#遍历每个轨迹,并读取其数据
for p = 1:size(trip, 2)
lon, lat = trip[:, p]
#对于每个轨迹,遍历其所有的点。
if !inregion(region, lon, lat)
num_out_region += 1
#如果点不在region内,则增加计数。
else
cell = gps2cell(region, lon, lat)
push!(region.cellcount, cell)
#否则,将该点转换为一个单元格,并在cellcount累加器中增加该单元格的计数
end
end
i % 100_000 == 0 && println("Processed $i trips")
#每处理100,000个轨迹,打印一个消息
end
end
max_num_hotcells = min(region.maxvocab_size, length(region.cellcount))
#确定热点单元格的最大数量。
topcellcount = sort(collect(region.cellcount), by=last, rev=true)[1:max_num_hotcells]
#对单元格按其计数排序,并选择前max_num_hotcells个。
println("Cell count at max_num_hotcells:$(max_num_hotcells) is $(last(topcellcount[end]))")
region.hotcell = filter(p -> last(p) >= region.minfreq, topcellcount) .|> first
#筛选那些计数大于或等于minfreq的热点单元格。
region.hotcell2vocab = Dict([(cell, i-1+region.vocab_start)
for (i, cell) in enumerate(region.hotcell)])
#为每个热点单元格构建一个到词汇ID的映射。
region.vocab2hotcell = Dict(last(p) => first(p) for p in region.hotcell2vocab)
#构建从词汇ID到热点单元格的反向映射。
region.vocab_size = region.vocab_start + length(region.hotcell)
#更新词汇的大小。
coord = hcat(map(x->collect(cell2coord(region, x)), region.hotcell)...)
#为热点单元格获取其坐标。
region.hotcell_kdtree = KDTree(coord)
#使用这些坐标构建一个KDTree,便于后续的搜索。
region.built = true
#标记region已构建。
num_out_region
end
push!(region.cellcount, cell)
这行代码中的push!
是Julia中的一个函数,它用于将一个元素添加到集合的末尾。
- 在这里,它正在将
cell
值添加到region.cellcount
中。- 通常情况下,
push!
函数是用于数组的。
- 然而,在这个上下文中,
region.cellcount
是一个Accumulator
对象Accumulator
是一个特殊的数据结构,通常用于计数,其中键是要计数的项,值是相应的计数。所以,
push!(region.cellcount, cell)
这行代码在做以下事情:
- 检查
cell
是否已经作为键存在于region.cellcount
中。
- 如果
cell
已存在,那么其对应的计数值会增加1。- 如果
cell
不存在,那么它将被添加到Accumulator
中,并且其计数被设置为1。
1.4.7 找到最近的热点单元格
'''
从给定的单元格cell中找到k个最近的热点单元格
'''
function knearestHotcells(region::SpatialRegion, cell::Int, k::Int)
@assert region.built == true "Build index for region first"
#首先确保region的索引已经被构建。如果没有,函数会抛出一个错误。
coord = cell2coord(region, cell) |> collect
#将给定的单元格转换为其对应的坐标
idxs, dists = knn(region.hotcell_kdtree, coord, k)
#使用KDTree进行k最近邻搜索来找到k个最近的热点单元格的索引和到给定单元格的距离。
region.hotcell[idxs], dists
#返回找到的热点单元格和它们到给定单元格的距离。
end
'''
找到给定单元格cell的最近的热点单元格
'''
function nearestHotcell(region::SpatialRegion, cell::Int)
@assert region.built == true "Build index for region first"
#首先确保region的索引已经被构建
hotcell, _ = knearestHotcells(region, cell, 1)
#使用knearestHotcells函数找到单一最近的热点单元格。
hotcell[1]
#返回找到的热点单元格
end
1.4.8 为每个词汇找到k个最近的词汇和相应的距离
"""
为每个词汇找到k个最近的词汇和相应的距离,并将它们保存到一个hdf5文件中
"""
function saveKNearestVocabs(region::SpatialRegion, datapath::String)
V = zeros(Int, region.k, region.vocab_size)
D = zeros(Float64, region.k, region.vocab_size)
'''
定义两个矩阵V和D。
V矩阵存储每个词汇的k个最近词汇的索引,而D矩阵存储与这些词汇的相应距离。
'''
for vocab in 0:region.vocab_start-1
V[:, vocab+1] .= vocab
D[:, vocab+1] .= 0.0
end
#使用一个for循环初始化矩阵V和D的前几列(从0到region.vocab_start-1)。
#在这些列中,每个词汇的最近词汇被认为是它自己,距离是0
for vocab in region.vocab_start:region.vocab_size-1
cell = region.vocab2hotcell[vocab]
kcells, dists = knearestHotcells(region, cell, region.k)
kvocabs = map(x->region.hotcell2vocab[x], kcells)
V[:, vocab+1] .= kvocabs
D[:, vocab+1] .= dists
end
'''
对于region.vocab_start到region.vocab_size-1的每一个词汇,使用knearestHotcells函数找到其最近的单元格和距离。
然后将这些单元格转换为相应的词汇,并将结果存储在V和D中。
'''
cellsize = Int(region.xstep)
file = joinpath(datapath, region.name * "-vocab-dist-cell$(cellsize).h5")
#定义文件名。文件名是根据region.name、单元格的大小(region.xstep)和"-vocab-dist-cell"构建的。
h5open(file, "w") do f
f["V"], f["D"] = V, D
#使用h5open函数将V和D矩阵保存到hdf5文件中。
end
println("Saved cell distance into $file")
nothing
'''
打印一条消息,说明距离已经被保存到文件中。
函数返回nothing,表示这个函数不返回任何值。
'''
end
1.4.9 cell2vocab 单元格转换到词汇ID
function cell2vocab(region::SpatialRegion, cell::Int)
@assert region.built == true "Build index for region first"
if haskey(region.hotcell2vocab, cell)
return region.hotcell2vocab[cell]
else
hotcell = nearestHotcell(region, cell)
return region.hotcell2vocab[hotcell]
end
'''
如果单元格是热门单元格,则直接从hotcell2vocab字典中返回其词汇ID。
如果不是热门单元格,则使用nearestHotcell函数找到其最近的热门单元格,并返回该热门单元格的词汇ID。
'''
end
1.4.10 gps2vocab gps坐标转换到词汇ID
function gps2vocab(region::SpatialRegion, lon::Float64, lat::Float64)
inregion(region, lon, lat) || return UNK
#首先,检查GPS坐标是否在区域内。如果不在,就返回UNK
cell2vocab(region, gps2cell(region, lon, lat))
#如果GPS坐标在区域内,就使用cell2vocab函数和gps2cell函数将GPS坐标转换为单元格ID,然后将单元格ID转换为词汇ID
end
1.4.11 trip2seq seq2trip trip和词汇序列互转
'''
将一个trip(一系列GPS坐标)转换为一个词汇序列
'''
function trip2seq(region::SpatialRegion, trip::Matrix{Float64})
seq = Int[]
for i in 1:size(trip, 2)
lon, lat = trip[:, i]
push!(seq, gps2vocab(region, lon, lat))
end
#对于trip中的每个GPS点,使用gps2vocab函数将其转换为一个词汇ID,并将这些ID添加到seq数组中
seq |> rle |> first
#使用rle函数(对序列进行运行长度编码)处理seq,并返回结果中的第一个元素
end
'''
此函数接受一个整数向量seq,其中每个整数是一个词汇ID,并将其转换回相应的GPS坐标轨迹。
'''
function seq2trip(region::SpatialRegion, seq::Vector{Int})
trip = zeros(Float64, 2, length(seq))
for i = 1:length(seq)
cell = get(region.vocab2hotcell, seq[i], -1)
cell == -1 && error("i=$i is out of vocabulary")
lon, lat = cell2gps(region, cell)
#对于每个词汇ID,它首先查找与之关联的单元格ID(从vocab2hotcell),然后使用cell2gps转换为GPS坐标
trip[:, i] = [lon, lat]
end
trip
#结果是一个二维矩阵,其中每列是一个GPS点的坐标
end
1.4.12 tripmeta, seqmeta 计算元数据
'''
从一个给定的轨迹中计算元数据,特别是轨迹的中心点坐标
'''
function tripmeta(region::SpatialRegion, trip::Matrix{Float64})
mins, maxs = minimum(trip, dims=2), maximum(trip, dims=2)
#计算轨迹中所有点的最小和最大坐标
centroids = mins + (maxs - mins) / 2
#计算并返回这些坐标范围的中点
gps2offset(region, centroids...)
#使用gps2offset将中心点转换为与区域关联的偏移量
end
'''
接受一个词汇序列并返回该序列对应的轨迹的中心点坐标
'''
function seqmeta(region::SpatialRegion, seq::Vector{Int})
trip = seq2trip(region, seq)
#使用seq2trip将词汇序列转换为一个GPS坐标轨迹
tripmeta(region, trip)
#使用tripmeta计算并返回轨迹的中心点坐标
end
1 .4.13
function createTrainVal(region::SpatialRegion,
trjfile::String,
datapath::String,
injectnoise::Function,
ntrain::Int,
nval::Int;
nsplit=5,
min_length=20,
max_length=100)
seq2str(seq) = join(map(string, seq), " ") * "\n"
#定义一个seq2str的局部函数,将序列转化为一个由空格分隔的字符串
h5open(trjfile, "r") do f
#使用h5open打开h5格式的轨迹文件
trainsrc = open("$datapath/train.src", "w")
traintrg = open("$datapath/train.trg", "w")
trainmta = open("$datapath/train.mta", "w")
validsrc = open("$datapath/val.src", "w")
validtrg = open("$datapath/val.trg", "w")
validmta = open("$datapath/val.mta", "w")
#为训练和验证数据集创建文件,包括源数据、目标数据和元数据文件
for i = 1:ntrain+nval
# 遍历h5文件中的轨迹
trip = f["/trips/$i"] |> read
# 从h5文件中读取单个轨迹
min_length <= size(trip, 2) <= max_length || continue
#检查轨迹长度是否在指定的范围内
trg = trip2seq(region, trip) |> seq2str
#将轨迹转化为目标序列并格式化
meta = tripmeta(region, trip)
mta = @sprintf "%.2f %.2f\n" meta[1] meta[2]
# 计算轨迹的元数据并格式化
noisetrips = injectnoise(trip, nsplit)
# 使用injectnoise函数在原始轨迹上生成噪声轨迹
srcio, trgio, mtaio = i <= ntrain ? (trainsrc, traintrg, trainmta) : (validsrc, validtrg, validmta)
## 根据当前轨迹的索引,确定是训练数据还是验证数据
for noisetrip in noisetrips
# 对于每个噪声轨迹
src = trip2seq(region, noisetrip) |> seq2str
## 转化噪声轨迹为源序列并格式化
write(srcio, src)
write(trgio, trg)
write(mtaio, mta)
## 将源序列、目标序列和元数据写入相应的文件
end
i % 100_000 == 0 && println("Scaned $i trips...")
#i >= 8_000 && break
end
close(trainsrc), close(traintrg), close(trainmta), close(validsrc), close(validtrg), close(validmta)
end
nothing
end
1.4.13 保存/加载空间区域
'''
将一个空间区域的参数保存到文件
'''
function saveregion(region::SpatialRegion, paramfile::String)
save(paramfile,
# JLD cannot handle Accumulator correctly
#"cellcount", region.cellcount.map,
"hotcell", region.hotcell,
"hotcell2vocab", region.hotcell2vocab,
"vocab2hotcell", region.vocab2hotcell,
"hotcell_kdtree", region.hotcell_kdtree,
"vocab_size", region.vocab_size)
nothing
end
'''
从文件中加载一个空间区域的参数,并更新给定的空间区域对象
'''
function loadregion!(region::SpatialRegion, paramfile::String)
jldopen(paramfile, "r") do f
#region.cellcount = read(f, "cellcount")
region.hotcell = read(f, "hotcell")
region.hotcell2vocab = read(f, "hotcell2vocab")
region.vocab2hotcell = read(f, "vocab2hotcell")
region.hotcell_kdtree = read(f, "hotcell_kdtree")
region.vocab_size = read(f, "vocab_size")
region.built = true
end
nothing
end
2 其他城市
要处理一个新城市的数据,你需要按照特定的格式提供一个 hdf5 输入文件,并设置适当的超参数
- 提供 hdf5 输入
- 为目标城市提供一个名为
t2vec/data/cityname.h5
的 hdf5 文件,其中cityname
是你要处理的城市的名称 - hdf5 输入的格式
attributes(f)["num"]
:存储的是轨迹的总数。f["/trips/i"]
:存储的是第 i 条轨迹的 GPS 数据,它是一个 2xn 的矩阵。第一行是经度序列,第二行是纬度序列。f["/timestamps/i"]
:存储的是第 i 条轨迹的时间戳序列,它是一个 n 维的向量。
- 为目标城市提供一个名为
- 设置超参数
- 在
t2vec/hyper-parameters.json
文件中,需要为目标城市设置适当的超参数。
- 在