-
Notifications
You must be signed in to change notification settings - Fork 2
add logging interface #60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6f2962e
86299ee
79a15c7
8935c90
341181e
635d3b7
fda20fb
b771a6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| ##### | ||
| ##### `LearnLogger` implementation of logging interface | ||
| ##### | ||
|
|
||
| """ | ||
| LearnLogger | ||
|
|
||
| A struct that wraps a `TensorBoardLogger.TBLogger` in order to enforce the following: | ||
|
|
||
| - all values logged to Tensorboard should be accessible to the `post_epoch_callback` | ||
| argument to [`learn!`](@ref) | ||
| - all values that are cached during [`learn!`](@ref) should be logged to Tensorboard | ||
|
|
||
| To access values logged to a `LearnLogger` instance, inspect the instance's `logged` field. | ||
| """ | ||
| struct LearnLogger | ||
| path::String | ||
| tensorboard_logger::TensorBoardLogger.TBLogger | ||
| logged::Dict{String,Vector{Any}} | ||
| end | ||
|
|
||
| function LearnLogger(path, run_name; kwargs...) | ||
| tensorboard_logger = TBLogger(joinpath(path, run_name); kwargs...) | ||
| return LearnLogger(path, tensorboard_logger, Dict{String,Any}()) | ||
| end | ||
|
|
||
| function log_value!(logger::LearnLogger, field::AbstractString, value) | ||
| values = get!(() -> Any[], logger.logged, field) | ||
| push!(values, value) | ||
| TensorBoardLogger.log_value(logger.tensorboard_logger, field, value; | ||
| step=length(values)) | ||
| return value | ||
| end | ||
|
|
||
| function log_event!(logger::LearnLogger, value::AbstractString) | ||
| logged = string(now(), " | ", value) | ||
| TensorBoardLogger.log_text(logger.tensorboard_logger, "events", logged) | ||
| return logged | ||
| end | ||
|
|
||
| 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 | ||
|
|
||
| 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 | ||
|
|
||
| """ | ||
| Base.flush(logger::LearnLogger) | ||
|
|
||
| Persist possibly transient logger state. | ||
| """ | ||
| Base.flush(logger::LearnLogger) = nothing | ||
|
|
||
| """ | ||
| forwarding_task = forward_logs(channel, logger::LearnLogger) | ||
|
|
||
| Forwards logs with values supported by `TensorBoardLogger` to `logger::LearnLogger`: | ||
| - string events of type `AbstractString` | ||
| - scalars of type `Union{Real,Complex}` | ||
| - plots that `TensorBoardLogger` can convert to raster images | ||
|
|
||
| returns the `forwarding_task:::Task` that does the forwarding. | ||
| To cleanly stop forwarding, `close(channel)` and `wait(forwarding_task)`. | ||
|
|
||
| outbox is a Channel or RemoteChannel of Pair{String, Any} | ||
| field names starting with "__plot__" forward to TensorBoardLogger.log_image | ||
| """ | ||
| function forward_logs(outbox, logger::LearnLogger) | ||
| @async try | ||
| while true | ||
| (field, value) = take!(outbox) | ||
| if typeof(value) <: AbstractString | ||
| log_event!(logger, value) | ||
| elseif startswith(field, "__plot__") | ||
| original_field = field[9:end] | ||
| values = get!(() -> Any[], logger.logged, original_field) | ||
| TensorBoardLogger.log_image(logger.tensorboard_logger, original_field, | ||
| value; step=length(values)) | ||
| elseif typeof(value) <: Union{Real,Complex} | ||
| log_value!(logger, field, value) | ||
| end | ||
| end | ||
| catch e | ||
| if !(isa(e, InvalidStateException) && e.state == :closed) | ||
| @error "error forwarding logs, STOPPING FORWARDING!" exception = (e, | ||
| catch_backtrace()) | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,56 +1,60 @@ | ||
| ##### | ||
| ##### `LearnLogger` | ||
| ##### Logging interface | ||
| ##### | ||
|
|
||
| # These must be implemented by every logger type. | ||
|
|
||
| """ | ||
| LearnLogger | ||
| log_plot!(logger, field::AbstractString, plot, plot_data) | ||
|
|
||
| A struct that wraps a `TensorBoardLogger.TBLogger` in order to enforce the following: | ||
| Log a `plot` to `logger` under field `field`. | ||
|
|
||
| - all values logged to Tensorboard should be accessible to the `post_epoch_callback` | ||
| argument to [`learn!`](@ref) | ||
| - all values that are cached during [`learn!`](@ref) should be logged to Tensorboard | ||
| * `plot`: the plot itself | ||
| * `plot_data`: an unstructured dictionary of values used in creating `plot`. | ||
|
|
||
| To access values logged to a `LearnLogger` instance, inspect the instance's `logged` field. | ||
| See also [`log_line_series!`](@ref). | ||
| """ | ||
| struct LearnLogger | ||
| path::String | ||
| tensorboard_logger::TensorBoardLogger.TBLogger | ||
| logged::Dict{String,Vector{Any}} | ||
| end | ||
| log_plot!(logger, field::AbstractString, plot, plot_data) | ||
|
|
||
| function LearnLogger(path, run_name; kwargs...) | ||
| tensorboard_logger = TBLogger(joinpath(path, run_name); kwargs...) | ||
| return LearnLogger(path, tensorboard_logger, Dict{String,Any}()) | ||
| end | ||
|
|
||
| function log_event!(logger::LearnLogger, value) | ||
| logged = string(now(), " | ", value) | ||
| TensorBoardLogger.log_text(logger.tensorboard_logger, "events", logged) | ||
| return logged | ||
| end | ||
| """ | ||
| log_value!(logger, field::AbstractString, value) | ||
|
|
||
| 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 a value `value` to `field`. | ||
| """ | ||
| log_value!(logger, field::AbstractString, value) | ||
|
|
||
| function log_value!(logger::LearnLogger, field::AbstractString, value) | ||
| values = get!(() -> Any[], logger.logged, field) | ||
| push!(values, value) | ||
| TensorBoardLogger.log_value(logger.tensorboard_logger, field, value; | ||
| step=length(values)) | ||
| return value | ||
| end | ||
|
|
||
| """ | ||
| 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`" | ||
| return nothing | ||
| 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)) | ||
|
|
||
| # The following have default implementations. | ||
|
|
||
| """ | ||
| step_logger!(logger) | ||
|
|
||
| Increments the `logger`'s `step`, if any. Defaults to doing nothing. | ||
| """ | ||
| step_logger!(::Any) = nothing | ||
|
|
||
|
|
||
| """ | ||
| log_event!(logger, value::AbstractString) | ||
|
|
||
| Logs a string event given by `value` to `logger`. Defaults to calling `log_value!` with a field named `event`. | ||
| """ | ||
| function log_event!(logger, value::AbstractString) | ||
| return log_value!(logger, "event", string(now(), " | ", value)) | ||
| end | ||
|
|
||
|
|
||
| """ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 or, better: and
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
|
||
|
|
@@ -83,49 +87,6 @@ function log_resource_info!(f, logger, section::AbstractString; suffix::Abstract | |
| return result | ||
| end | ||
|
|
||
| """ | ||
| Base.flush(logger) | ||
|
|
||
| Persist possibly transient logger state. | ||
| """ | ||
| Base.flush(logger::LearnLogger) = nothing | ||
|
|
||
| """ | ||
| forwarding_task = forward_logs(channel, logger::LearnLogger) | ||
|
|
||
| Forwards logs with values supported by `TensorBoardLogger` to `logger::LearnLogger`: | ||
| - string events of type `AbstractString` | ||
| - scalars of type `Union{Real,Complex}` | ||
| - plots that `TensorBoardLogger` can convert to raster images | ||
|
|
||
| returns the `forwarding_task:::Task` that does the forwarding. | ||
| To cleanly stop forwarding, `close(channel)` and `wait(forwarding_task)`. | ||
|
|
||
| outbox is a Channel or RemoteChannel of Pair{String, Any} | ||
| field names starting with "__plot__" forward to TensorBoardLogger.log_image | ||
| """ | ||
| function forward_logs(outbox, logger::LearnLogger) | ||
| @async try | ||
| while true | ||
| (field, value) = take!(outbox) | ||
| if typeof(value) <: AbstractString | ||
| log_event!(logger, value) | ||
| elseif startswith(field, "__plot__") | ||
| original_field = field[9:end] | ||
| values = get!(() -> Any[], logger.logged, original_field) | ||
| TensorBoardLogger.log_image(logger.tensorboard_logger, original_field, | ||
| value; step=length(values)) | ||
| elseif typeof(value) <: Union{Real,Complex} | ||
| log_value!(logger, field, value) | ||
| end | ||
| end | ||
| catch e | ||
| if !(isa(e, InvalidStateException) && e.state == :closed) | ||
| @error "error forwarding logs, STOPPING FORWARDING!" exception = (e, | ||
| catch_backtrace()) | ||
| end | ||
| end | ||
| end | ||
|
|
||
| ##### | ||
| ##### `predict!!` | ||
|
|
@@ -186,7 +147,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, | ||
|
|
@@ -842,6 +803,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 | ||
|
|
||
There was a problem hiding this comment.
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 whereLearnLoggercan have additional args but other usage need not?There was a problem hiding this comment.
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 towardslog_line_series!instead, and then delete it in the next breaking release.There was a problem hiding this comment.
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
There was a problem hiding this comment.
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/orlog_image!.