Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Lighthouse"
uuid = "ac2c24cd-07f0-4848-96b2-1b82c3ea0e59"
authors = ["Beacon Biosignals, Inc."]
version = "0.14.5"
version = "0.14.6"

[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Expand Down
32 changes: 28 additions & 4 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,42 @@ Lighthouse.is_early_stopping_exception
## The `learn!` Interface

```@docs
LearnLogger
learn!
upon
evaluate!
predict!
Lighthouse.forward_logs
Lighthouse.log_evaluation_row!
Lighthouse._calculate_ea_kappas
Lighthouse._calculate_ira_kappas
Lighthouse._calculate_spearman_correlation
```

## The logging interface

The following "primitives" must be defined for a logger to be used with Lighthouse:

```@docs
log_event!
log_value!
log_line_series!
log_plot!
step_logger!
```

These primitives can be used in implementations of [`train!`](@ref), [`evaluate!`](@ref), and [`predict!`](@ref), as well as in:

```@docs
Lighthouse.log_evaluation_row!
```

### `LearnLogger`s

`LearnLoggers` are a Tensorboard-backed logger which comply with the above logging interface. They also support additional callback functionality with `upon`:

```@docs
LearnLogger
upon
Lighthouse.forward_logs
```

## Performance Metrics

```@docs
Expand Down
1 change: 1 addition & 0 deletions src/Lighthouse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ export AbstractClassifier

include("learn.jl")
export LearnLogger, learn!, upon, evaluate!, predict!
export log_event!, log_line_series!, log_plot!, step_logger!, log_value!

end # module
2 changes: 1 addition & 1 deletion src/classifier.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ This method must be implemented for each `AbstractClassifier` subtype.
function classes end

"""
Lighthouse.train!(classifier::AbstractClassifier, batches, logger::LearnLogger)
Lighthouse.train!(classifier::AbstractClassifier, batches, logger)

Train `classifier` on the iterable `batches` for a single epoch. This function
is called once per epoch by [`learn!`](@ref).
Expand Down
50 changes: 47 additions & 3 deletions src/learn.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,45 @@ function LearnLogger(path, run_name; kwargs...)
return LearnLogger(path, tensorboard_logger, Dict{String,Any}())
end

"""
log_event!(logger, value::AbstractString)

Logs a string event given by `value` to `logger`.
"""
log_event!(logger, value)
Comment thread
ericphanson marked this conversation as resolved.
Outdated

function log_event!(logger::LearnLogger, value)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm, maybe we should move all of the LearnLogger-specific functions to their own file? Would make it extra clear what is specific to LearnLogger, and would make the "remove LearnLogger" PR as easy as "delete a file"....

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thought is maybe we could do something like

function log_event!(logger, value)

Because LWB is dispatching this for a different type of logger, and it makes sense for lighthouse to provide the fallback methods for the different logging actions

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added fallback!

logged = string(now(), " | ", value)
TensorBoardLogger.log_text(logger.tensorboard_logger, "events", logged)
return logged
end

"""
log_plot!(logger, field::AbstractString, plot, plot_data)

Log a `plot` to `logger` under field `field`.

* `plot`: the plot itself
* `plot_data`: an unstructured dictionary of values used in creating `plot`.

See also [`log_line_series!`](@ref).
"""
log_plot!(logger, field::AbstractString, plot, plot_data)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...I really really wish we didn't have to log both the plot and the data in this function; that was an addition that we made as a tb workaround (we were plotting an image of the plotted data, but weren't otherwise serializing the data used to do the plotting) and in hindsight that should have been a separate function call.

If there's a way to back out of it now, we should consider it...could this be, e.g., an args... situation where LearnLogger can have additional args but other usage need not?

Suggested change
log_plot!(logger, field::AbstractString, plot, plot_data)
log_plot!(logger, field::AbstractString, plot, args...)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this runs the risk of switching loggers and then existing code breaking (since it relies on being able to pass some number of args); ideally, you should be able to switch loggers without any code changes. I agree about log_plot! not being a nice function- I think we should deprecate it, pointing towards log_line_series! instead, and then delete it in the next breaking release.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I agree with this, especially as we build out more plot logging functionalities we will probably want to deprecate

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, sounds good to me---log_line_series! and/or log_image!.


function log_plot!(logger::LearnLogger, field::AbstractString, plot, plot_data)
values = get!(() -> Any[], logger.logged, field)
push!(values, plot_data)
TensorBoardLogger.log_image(logger.tensorboard_logger, field, plot; step=length(values))
return plot
end

"""
log_plot!(logger, field::AbstractString, value)
Comment thread
ericphanson marked this conversation as resolved.
Outdated

Log a scalar value `value` to `field`.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this has to be a scalar value? I think what it supports might depend on the logging backend...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I think it is fair to remove the term "scalar" here, since it is a plot, not sure if we can consider it a scalar or not!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah rename to log_value!, again I do not think the word scalar allows us the flexibility we will probably have, we should update the LighthouseWandb docs accordingly as well

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the word scalar!

Comment thread
ericphanson marked this conversation as resolved.
Outdated
"""
log_value!(logger, field::AbstractString, value)

function log_value!(logger::LearnLogger, field::AbstractString, value)
values = get!(() -> Any[], logger.logged, field)
push!(values, value)
Expand All @@ -45,12 +71,29 @@ function log_value!(logger::LearnLogger, field::AbstractString, value)
return value
end

"""
log_line_series!(logger, field::AbstractString, curves, labels=1:length(curves))

Logs a series plot to `logger` under `field`, where...

- `curves` is an iterable of the form `Tuple{Vector{Real},Vector{Real}}`, where each tuple contains `(x-values, y-values)`, as in the `Lighthouse.EvaluationRow` field `per_class_roc_curves`
- `labels` is the class label for each curve, which defaults to the numeric index of each curve.
"""
log_line_series!(logger, field::AbstractString, curves, labels=1:length(curves))

function log_line_series!(logger::LearnLogger, field::AbstractString, series, series_labels)
@warn "`log_line_series!` not implemented for `LearnLogger`"
function log_line_series!(logger::LearnLogger, field::AbstractString, curves, labels=1:length(curves))
@warn "`log_line_series!` not implemented for `LearnLogger`" maxlog=1
return nothing
end

"""
step_logger!(logger)

Increments the `logger`'s `step`, if any. Defaults to doing nothing.
"""
step_logger!(logger) = nothing


"""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, i'm not "allowed" to comment below this point, but I think we should specialize the below implementation of
log_evaluation_row! on LearnLogger so that special-casing spearman correlation doesn't have to happen by default---that is a very tb-specific logged item. E.g.,

function log_evaluation_row!(logger, field::AbstractString, metrics)
    metrics_plot = evaluation_metrics_plot(metrics)
    metrics_dict = _evaluation_row_dict(metrics)
    log_plot!(logger, field, metrics_plot, metrics_dict)
    return metrics_plot
end

or, better:

function log_evaluation_row!(logger, field::AbstractString, metrics)
    metrics_plot = evaluation_metrics_plot(metrics)
    log_plot!(logger, field, metrics_plot) # if we make the plot_data field optional
    # optionally, could also then log each field of `metrics_plot` with `log_values!`
    return metrics_plot
end

and

function log_evaluation_row!(logger::LearnLogger, field::AbstractString, metrics)
    metrics_plot = evaluation_metrics_plot(metrics)
    metrics_dict = _evaluation_row_dict(metrics)
    log_plot!(logger, field, metrics_plot, metrics_dict)
    if haskey(metrics_dict, "spearman_correlation")
        sp_field = replace(field, "metrics" => "spearman_correlation")
        log_value!(logger, sp_field, metrics_dict["spearman_correlation"].ρ)
    end
    return metrics_plot
end

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really get why spearman is TB-specific; shouldn't either all loggers want it or none?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I filed #64 to track this

log_evaluation_row!(logger, field::AbstractString, metrics)

Expand Down Expand Up @@ -186,7 +229,7 @@ end
evaluate!(predicted_hard_labels::AbstractVector,
predicted_soft_labels::AbstractMatrix,
elected_hard_labels::AbstractVector,
classes, logger::LearnLogger;
classes, logger;
logger_prefix, logger_suffix,
votes::Union{Nothing,AbstractMatrix}=nothing,
thresholds=0.0:0.01:1.0,
Expand Down Expand Up @@ -842,6 +885,7 @@ end
#####

"""
upon(logger::LearnLogger, field::AbstractString; condition, initial)
upon(logged::Dict{String,Any}, field::AbstractString; condition, initial)

Return a closure that can be called to check the most recent state of
Expand Down