Skip to content
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

Stick to a single interface #127

Merged
merged 10 commits into from
Apr 10, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
24 changes: 24 additions & 0 deletions .github/workflows/CompatHelper.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: CompatHelper

on:
schedule:
- cron: '00 * * * *'

jobs:
CompatHelper:
runs-on: ${{ matrix.os }}
strategy:
matrix:
julia-version: [1.2.0]
julia-arch: [x86]
os: [ubuntu-latest]
steps:
- uses: julia-actions/setup-julia@latest
with:
version: ${{ matrix.julia-version }}
- name: Pkg.add("CompatHelper")
run: julia -e 'using Pkg; Pkg.add("CompatHelper")'
- name: CompatHelper.main()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: julia -e 'using CompatHelper; CompatHelper.main()'
190 changes: 9 additions & 181 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ _LossFunctions.jl is a Julia package that provides efficient and
well-tested implementations for a diverse set of loss functions
that are commonly used in Machine Learning._

| **Package Status** | **Package Evaluator** | **Build Status** |
|:------------------:|:---------------------:|:-----------------:|
| [![License](http://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](LICENSE.md) [![Docs](https://img.shields.io/badge/docs-stable-blue.svg)](https://JuliaML.github.io/LossFunctions.jl/stable) | [![Pkg Eval 0.6](http://pkg.julialang.org/badges/LossFunctions_0.6.svg)](http://pkg.julialang.org/?pkg=LossFunctions) [![Pkg Eval 0.7](http://pkg.julialang.org/badges/LossFunctions_0.7.svg)](http://pkg.julialang.org/?pkg=LossFunctions) | [![Build Status](https://travis-ci.org/JuliaML/LossFunctions.jl.svg?branch=master)](https://travis-ci.org/JuliaML/LossFunctions.jl) [![Build status](https://ci.appveyor.com/api/projects/status/xbwc2fiel40bajsp?svg=true)](https://ci.appveyor.com/project/Evizero/losses-jl) [![Coverage Status](https://coveralls.io/repos/github/JuliaML/LossFunctions.jl/badge.svg?branch=master)](https://coveralls.io/github/JuliaML/LossFunctions.jl?branch=master) |
[![License](http://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](LICENSE.md)
[![Docs](https://img.shields.io/badge/docs-stable-blue.svg)](https://JuliaML.github.io/LossFunctions.jl/stable)
[![Build Status](https://travis-ci.org/JuliaML/LossFunctions.jl.svg?branch=master)](https://travis-ci.org/JuliaML/LossFunctions.jl)
[![Build status](https://ci.appveyor.com/api/projects/status/xbwc2fiel40bajsp?svg=true)](https://ci.appveyor.com/project/Evizero/losses-jl)
[![Coverage Status](https://coveralls.io/repos/github/JuliaML/LossFunctions.jl/badge.svg?branch=master)](https://coveralls.io/github/JuliaML/LossFunctions.jl?branch=master)

## Available Losses

**Distance-based (Regression)** | **Margin-based (Classification)**
:-------------------------------:|:----------------------------------:
![distance_losses](https://rawgithub.com/JuliaML/FileStorage/master/LossFunctions/distance.svg) | ![margin_losses](https://rawgithub.com/JuliaML/FileStorage/master/LossFunctions/margin.svg)

Please consult the documentation for other losses available.
Please consult the [documentation](https://JuliaML.github.io/LossFunctions.jl/stable)
for other losses.

## Introduction

Expand All @@ -37,179 +40,6 @@ observations. In the case of arrays a user additionally has the
ability to define if and how element-wise results are averaged or
summed over.

## Example

The following code snippets show a simple "hello world" scenario
of how this package can be used to work with loss functions in
various ways.

```julia
using LossFunctions
```

All the concrete loss "functions" that this package provides are
actually defined as immutable types, instead of native Julia
functions. We can compute the value of some type of loss using
the function `value()`. Let us start with an example of how to
compute the loss for a group of three of observations. By default
the loss will be computed element-wise.

```julia
julia> true_targets = [ 1, 0, -2];

julia> pred_outputs = [0.5, 2, -1];

julia> value(L2DistLoss(), true_targets, pred_outputs)
# 3-element Array{Float64,1}:
# 0.25
# 4.0
# 1.0
```

Alternatively, one can also use an instance of a loss just like
one would use any other Julia function. This can make the code
significantly more readable while not impacting performance, as
it is a zero-cost abstraction (i.e. it compiles down to the same
code).

```julia
julia> loss = L2DistLoss()
# LossFunctions.LPDistLoss{2}()

julia> loss(true_targets, pred_outputs)
# 3-element Array{Float64,1}:
# 0.25
# 4.0
# 1.0

julia> loss(1, 0.5f0) # single observation
# 0.25f0
```

If you are not actually interested in the element-wise results
individually, but some accumulation of those (such as mean or
sum), you can additionally specify an average mode. This will
avoid allocating a temporary array and directly compute the
result.

```julia
julia> value(L2DistLoss(), true_targets, pred_outputs, AvgMode.Sum())
# 5.25

julia> value(L2DistLoss(), true_targets, pred_outputs, AvgMode.Mean())
# 1.75
```

Aside from these standard unweighted average modes, we also
provide weighted alternatives. These expect a weight-factor for
each observation in the predicted outputs and so allow to give
certain observations a stronger influence over the result.

```julia
julia> value(L2DistLoss(), true_targets, pred_outputs, AvgMode.WeightedSum([2,1,1]))
# 5.5

julia> value(L2DistLoss(), true_targets, pred_outputs, AvgMode.WeightedMean([2,1,1]))
# 1.375
```

We do not restrict the targets and outputs to be vectors, but
instead allow them to be arrays of any arbitrary shape. The shape
of an array may or may not have an interpretation that is
relevant for computing the loss. It is possible to explicitly
specify which dimension denotes the observations. This is
particularly useful for multivariate regression where one could
want to accumulate the loss per individual observation.

```julia
julia> A = rand(2,3)
# 2×3 Array{Float64,2}:
# 0.0939946 0.97639 0.568107
# 0.183244 0.854832 0.962534

julia> B = rand(2,3)
# 2×3 Array{Float64,2}:
# 0.0538206 0.77055 0.996922
# 0.598317 0.72043 0.912274

julia> value(L2DistLoss(), A, B, AvgMode.Sum())
# 0.420741920634

julia> value(L2DistLoss(), A, B, AvgMode.Sum(), ObsDim.First())
# 2-element Array{Float64,1}:
# 0.227866
# 0.192876

julia> value(L2DistLoss(), A, B, AvgMode.Sum(), ObsDim.Last())
# 3-element Array{Float64,1}:
# 0.1739
# 0.060434
# 0.186408
```

All these function signatures of `value` also apply for computing
the derivatives using `deriv` and the second derivatives using
`deriv2`.

```julia
julia> deriv(L2DistLoss(), true_targets, pred_outputs)
# 3-element Array{Float64,1}:
# -1.0
# 4.0
# 2.0

julia> deriv2(L2DistLoss(), true_targets, pred_outputs)
# 3-element Array{Float64,1}:
# 2.0
# 2.0
# 2.0
```

For computing the first and second derivatives we additionally
expose a convenience syntax which allows for a more math-like
look of the code.

```julia
julia> loss = L2DistLoss()
# LossFunctions.LPDistLoss{2}()

julia> loss'(true_targets, pred_outputs)
# 3-element Array{Float64,1}:
# -1.0
# 4.0
# 2.0

julia> loss''(true_targets, pred_outputs)
# 3-element Array{Float64,1}:
# 2.0
# 2.0
# 2.0
```

Additionally, we provide mutating versions for the subset of
methods that return an array. These have the same function
signatures with the only difference of requiring an additional
parameter as the first argument. This variable should always be
the preallocated array that is to be used as storage.

```julia
julia> buffer = zeros(3)
# 3-element Array{Float64,1}:
# 0.0
# 0.0
# 0.0

julia> deriv!(buffer, L2DistLoss(), true_targets, pred_outputs)
# 3-element Array{Float64,1}:
# -1.0
# 4.0
# 2.0
```

Note that this only shows a small part of the functionality this
package provides. For more information please have a look at
the documentation.

## Documentation

Check out the **[latest documentation](https://JuliaML.github.io/LossFunctions.jl/stable)**
Expand Down Expand Up @@ -251,12 +81,10 @@ search: HingeLoss L2HingeLoss L1HingeLoss SmoothedL1HingeLoss

## Installation

This package is registered in `METADATA.jl` and can be installed
as usual
Get the latest stable release with Julia's package manager:

```julia
import Pkg
Pkg.add("LossFunctions")
] add LossFunctions
```

## License
Expand Down
15 changes: 0 additions & 15 deletions docs/src/user/interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,21 +282,6 @@ deriv2(::SupervisedLoss, ::AbstractArray, ::AbstractArray)
deriv2!(::AbstractArray, ::SupervisedLoss, ::AbstractArray, ::AbstractArray)
```

## Function Closures

In some circumstances it may be convenient to have the loss
function or its derivative as a proper Julia function. Instead of
exporting special function names for every implemented loss (like
`l2distloss(...)`), we provide the ability to generate a true
function on the fly for any given loss.

```@docs
value_fun(::SupervisedLoss)
deriv_fun(::SupervisedLoss)
deriv2_fun(::SupervisedLoss)
value_deriv_fun(::SupervisedLoss)
```

## Properties of a Loss

In some situations it can be quite useful to assert certain
Expand Down
21 changes: 3 additions & 18 deletions src/LossFunctions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,10 @@ export

value,
value!,
deriv,
deriv!,
deriv2,
deriv2!,
value_fun,
deriv_fun,
deriv2_fun,
value_deriv_fun,

ZeroOneLoss,
LogitMarginLoss,
Expand Down Expand Up @@ -92,18 +91,4 @@ include("supervised/io.jl")

include("deprecated.jl")

# allow using some special losses as function
(loss::ScaledSupervisedLoss)(args...) = value(loss, args...)
(loss::WeightedBinaryLoss)(args...) = value(loss, args...)

# allow using SupervisedLoss as function
for T in filter(isconcretetype, subtypes(SupervisedLoss))
@eval (loss::$T)(args...) = value(loss, args...)
end

# allow using MarginLoss and DistanceLoss as function
for T in union(subtypes(DistanceLoss), subtypes(MarginLoss))
@eval (loss::$T)(args...) = value(loss, args...)
end

end # module
Loading