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

 1 波尔图数据

1.0 整体流程

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

得到的V矩阵前几行都是自己的行号,D前几行都是0

这里vocab_start为4,因为前4个是PAD这些

 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 |> rle |> first

  • 这里使用了Julia的管道操作符 |>,它将左边的值传递给右边的函数
    • seq |> rle
      • seq 传递给 rle 函数
      • rle 是一个进行运行长度编码(Run Length Encoding)的函数。运行长度编码是一种简单的压缩方法,它将连续的重复值表示为一个值和计数。
        • 例如,对于序列 [4,4,4,4,4,1,1,1,2,2,3,3,3,3],其RLE是
          ([4, 1, 2, 3], [5, 3, 2, 4]),表示数字4重复了5次,数字1重复了3次,数字2重复了2次,数字3重复了4次。
    • ... |> first
      • 将上一步的结果(即 rle 函数的输出)传递给 first 函数。
      • first 函数只返回序列的第一个元素
    • 假设 seq[4,4,4,4,4,1,1,1,2,2,3,3,3,3],那么 trip2seq 函数的最后返回值是 [4, 1, 2, 3]

假设seq是[4,4,4,1,1,1,2,2,4,4,3,3,3,3],那么rle完了是([4, 1, 2, 4, 3], [3, 3, 2, 2, 4]),first完了是[4, 1, 2, 4, 3]

'''
此函数接受一个整数向量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 保存train和validation

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)
            ## 根据当前轨迹的索引,确定是训练数据还是验证数据
            '''
如果 i <= ntrain 为真,那么将使用括号中的第一组值 (即 (trainsrc, traintrg, trainmta)),并分别赋值给 srcio, trgio, mtaio。
    【也即训练集】

如果 i <= ntrain 为假,则使用括号中的第二组值 (即 (validsrc, validtrg, validmta)),并分别赋值给 srcio, trgio, mtaio。
    【也即测试集】
            '''

            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

  • 对于波尔图数据来说
    • mta元数据训练集16711980条,测试集167520条
    • val.src, train.src

 1.4.14 保存/加载空间区域

'''
将一个空间区域的参数保存到文件
'''
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 文件中,需要为目标城市设置适当的超参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UQI-LIUWJ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值