Skip to content

JuliaML/ValueHistories.jl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ValueHistories

Utility package for efficient tracking of optimization histories, training curves or other information of arbitrary types and at arbitrarily spaced sampling times

Package License PkgEval (Nanosoldier) Build Status
License PkgEval Build Status Build status Coverage Status

Installation

This package is registered in METADATA.jl and can be installed as usual

pkg> add ValueHistories

Overview

We provide two basic approaches for logging information over time or iterations. The sample points do not have to be equally spaced as long as time/iteration is strictly increasing.

  • Univalue histories: Intended for tracking the evolution of a single value over time.
  • Multivalue histories: Track an arbitrary amount of values over time, each of which can be of a different type and associated with a label

Note that both approaches are typestable.

Univalue Histories

This package provide two different concrete implementations

  • QHistory: Logs the values using a Dequeue
  • History: Logs the values using a Vector

Supported operations for univalue histories:

  • push!(history, iteration, value): Appends a value to the history
  • get(history): Returns all available observations as two vectors. The first vector contains the iterations and the second vector contains the values.
  • enumerate(history) Returns an enumerator over the observations (as tuples)
  • first(history): First stored observation (as tuple)
  • last(history): Last stored observation (as tuple)
  • length(history): Number of stored observations
  • increment!(history, iteration, value): Similar to push! but increments the value if the iteration already exists. Only supported by History.

Here is a little example code showing the basic usage:

using Primes

# Specify the type of value you wish to track
history = QHistory(Float64)

for i = 1:100
  # Store some value of the specified type
  # Note how the sampling times are not equally spaced
  isprime(i) && push!(history, i, sin(.1*i))
end

# Access stored values as arrays
x, y = get(history)
@assert typeof(x) <: Vector{Int}
@assert typeof(y) <: Vector{Float64}

# You can also enumerate over the observations
for (x, y) in enumerate(history)
  @assert typeof(x) <: Int
  @assert typeof(y) <: Float64
end

# Let's see how this prints to the REPL
history
QHistory
    types: Int64, Float64
    length: 25

For easy visualisation we also provide recipes for Plots.jl. Note that this is only supported for Real types.

using Plots
plot(history, legend=false)

qhistory

Multivalue Histories

Multivalue histories are more or less a dynamic collection of a number of univalue histories. Each individual univalue history is associated with a symbol key. If the user stores a value under a key that has no univalue history associated with it, then a new one is allocated and specialized for the given type.

Supported operations for multivalue histories:

  • push!(history, key, iteration, value): Appends a value to the multivalue history
  • get(history, key): Returns all available observations as two vectors. The first vector contains the iterations and the second vector contains the values.
  • enumerate(history, key) Returns an enumerator over the observations (as tuples)
  • first(history, key): First stored observation (as tuple)
  • last(history, key): Last stored observation (as tuple)
  • length(history, key): Number of stored observations
  • increment!(history, key, iteration, value): Similar to push! but increments the value if the key and iteration combination already exists.

Here is a little example code showing the basic usage:

using ValueHistories, Primes
history = MVHistory()

for i=1:100
    x = 0.1i

    # Store any kind of value without losing type stability
    # The first push! to a key defines the tracked type
    #   push!(history, key, iter, value)
    push!(history, :mysin, x, sin(x))
    push!(history, :mystring, i, "i=$i")

    # Sampling times can be arbitrarily spaced
    # Note how we store the sampling time as a Float32 this time
    isprime(i) && push!(history, :mycos, Float32(x), cos(x))
end

# Access stored values as arrays
x, y = get(history, :mysin)
@assert length(x) == length(y) == 100
@assert typeof(x) <: Vector{Float64}
@assert typeof(y) <: Vector{Float64}

# Each key can be queried individually
x, y = get(history, :mystring)
@assert length(x) == length(y) == 100
@assert typeof(x) <: Vector{Int64}
@assert typeof(y) <: Vector{String}
@assert y[1] == "i=1"

# You can also enumerate over the observations
for (x, y) in enumerate(history, :mycos)
  @assert typeof(x) <: Float32
  @assert typeof(y) <: Float64
end

# Let's see how this prints to the REPL
history
MVHistory{ValueHistories.History{I,V}}
  :mysin => 100 elements {Float64,Float64}
  :mystring => 100 elements {Int64,String}
  :mycos => 25 elements {Float32,Float64}

For easy visualisation we also provide recipes for Plots.jl. Note that this is only supported for Real types.

using Plots
plot(history)

mvhistory

License

This code is free to use under the terms of the MIT license.