论文辅助笔记:t2vec 数据预处理

 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 格式的命令。

论文辅助笔记:t2vec 数据预处理_第1张图片

julia preprocess.jl

使用 Julia 语言执行 preprocess.jl 脚本。对数据进行一些预处理的操作。

论文辅助笔记:t2vec 数据预处理_第2张图片

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博客

论文辅助笔记:t2vec 数据预处理_第3张图片

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相关的内容

论文辅助笔记:t2vec 数据预处理_第4张图片


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 文件中,需要为目标城市设置适当的超参数。
    • 论文辅助笔记:t2vec 数据预处理_第5张图片

你可能感兴趣的:(笔记)