Skip to content

Commit

Permalink
Rename loss => elementwise_loss and full_objective => loss_function
Browse files Browse the repository at this point in the history
  • Loading branch information
MilesCranmer committed Feb 10, 2024
1 parent fc67c56 commit d16abb4
Show file tree
Hide file tree
Showing 11 changed files with 82 additions and 82 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ model = PySRRegressor(
],
extra_sympy_mappings={"inv": lambda x: 1 / x},
# ^ Define operator for SymPy as well
loss="loss(prediction, target) = (prediction - target)^2",
elementwise_loss="loss(prediction, target) = (prediction - target)^2",
# ^ Custom loss function (julia syntax)
)
```
Expand Down
8 changes: 4 additions & 4 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ but there are still some additional steps you can take to reduce the effect of n

One thing you could do, which we won't detail here, is to create a custom log-likelihood
given some assumed noise model. By passing weights to the fit function, and
defining a custom loss function such as `loss="myloss(x, y, w) = w * (x - y)^2"`,
defining a custom loss function such as `elementwise_loss="myloss(x, y, w) = w * (x - y)^2"`,
you can define any sort of log-likelihood you wish. (However, note that it must be bounded at zero)

However, the simplest thing to do is preprocessing, just like for feature selection. To do this,
Expand Down Expand Up @@ -380,7 +380,7 @@ end
model = PySRRegressor(
niterations=100,
binary_operators=["*", "+", "-"],
full_objective=objective,
loss_function=objective,
)
```

Expand Down Expand Up @@ -462,7 +462,7 @@ let's also create a custom loss function
that looks at the error in log-space:

```python
loss = """function loss_fnc(prediction, target)
elementwise_loss = """function loss_fnc(prediction, target)
scatter_loss = abs(log((abs(prediction)+1e-20) / (abs(target)+1e-20)))
sign_loss = 10 * (sign(prediction) - sign(target))^2
return scatter_loss + sign_loss
Expand All @@ -476,7 +476,7 @@ Now let's define our model:
model = PySRRegressor(
binary_operators=["+", "-", "*", "/"],
unary_operators=["square"],
loss=loss,
elementwise_loss=elementwise_loss,
complexity_of_constants=2,
maxsize=25,
niterations=100,
Expand Down
14 changes: 7 additions & 7 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ train the parameters within JAX (and is differentiable).

The default loss is mean-square error, and weighted mean-square error.
One can pass an arbitrary Julia string to define a custom loss, using,
e.g., `loss="myloss(x, y) = abs(x - y)^1.5"`. For more details,
e.g., `elementwise_loss="myloss(x, y) = abs(x - y)^1.5"`. For more details,
see the
[Losses](https://milescranmer.github.io/SymbolicRegression.jl/dev/losses/)
page for SymbolicRegression.jl.
Expand All @@ -253,40 +253,40 @@ Here are some additional examples:
abs(x-y) loss

```python
PySRRegressor(..., loss="f(x, y) = abs(x - y)^1.5")
PySRRegressor(..., elementwise_loss="f(x, y) = abs(x - y)^1.5")
```

Note that the function name doesn't matter:

```python
PySRRegressor(..., loss="loss(x, y) = abs(x * y)")
PySRRegressor(..., elementwise_loss="loss(x, y) = abs(x * y)")
```

With weights:

```python
model = PySRRegressor(..., loss="myloss(x, y, w) = w * abs(x - y)")
model = PySRRegressor(..., elementwise_loss="myloss(x, y, w) = w * abs(x - y)")
model.fit(..., weights=weights)
```

Weights can be used in arbitrary ways:

```python
model = PySRRegressor(..., weights=weights, loss="myloss(x, y, w) = abs(x - y)^2/w^2")
model = PySRRegressor(..., weights=weights, elementwise_loss="myloss(x, y, w) = abs(x - y)^2/w^2")
model.fit(..., weights=weights)
```

Built-in loss (faster) (see [losses](https://astroautomata.com/SymbolicRegression.jl/dev/losses/)).
This one computes the L3 norm:

```python
PySRRegressor(..., loss="LPDistLoss{3}()")
PySRRegressor(..., elementwise_loss="LPDistLoss{3}()")
```

Can also uses these losses for weighted (weighted-average):

```python
model = PySRRegressor(..., weights=weights, loss="LPDistLoss{3}()")
model = PySRRegressor(..., weights=weights, elementwise_loss="LPDistLoss{3}()")
model.fit(..., weights=weights)
```

Expand Down
2 changes: 1 addition & 1 deletion example.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
],
extra_sympy_mappings={"inv": lambda x: 1 / x},
# ^ Define operator for SymPy as well
loss="loss(x, y) = (x - y)^2",
elementwise_loss="loss(x, y) = (x - y)^2",
# ^ Custom loss function (julia syntax)
)

Expand Down
2 changes: 1 addition & 1 deletion examples/pysr_demo.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@
"outputs": [],
"source": [
"model = PySRRegressor(\n",
" loss=\"myloss(x, y, w) = w * abs(x - y)\", # Custom loss function with weights.\n",
" elementwise_loss=\"myloss(x, y, w) = w * abs(x - y)\", # Custom loss function with weights.\n",
" niterations=20,\n",
" populations=20, # Use more populations\n",
" binary_operators=[\"+\", \"*\"],\n",
Expand Down
4 changes: 1 addition & 3 deletions pysr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
from .julia_import import jl, SymbolicRegression # isort:skip

from . import sklearn_monkeypatch
from .deprecated import best, best_callable, best_row, best_tex, pysr
from .deprecated import best, best_callable, best_row, best_tex, install, pysr
from .export_jax import sympy2jax
from .export_torch import sympy2torch
from .julia_helpers import install
from .sr import PySRRegressor

# This file is created by setuptools_scm during the build process:
Expand All @@ -28,5 +27,4 @@
"best_tex",
"pysr",
"__version__",
"install",
]
77 changes: 41 additions & 36 deletions pysr/deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@
import warnings


def install(*args, **kwargs):
del args, kwargs
warnings.warn(
"The `install` function has been removed. "
"PySR now uses the `juliacall` package to install its dependencies automatically at import time. "
)


def init_julia(*args, **kwargs):
del args, kwargs
warnings.warn(
"The `init_julia` function has been removed. "
"Julia is now initialized automatically at import time."
)


def pysr(X, y, weights=None, **kwargs): # pragma: no cover
from .sr import PySRRegressor

Expand Down Expand Up @@ -55,39 +71,28 @@ def best_callable(*args, **kwargs): # pragma: no cover
)


def make_deprecated_kwargs_for_pysr_regressor():
"""Create dict of deprecated kwargs."""

deprecation_string = """
fractionReplaced => fraction_replaced
fractionReplacedHof => fraction_replaced_hof
npop => population_size
hofMigration => hof_migration
shouldOptimizeConstants => should_optimize_constants
weightAddNode => weight_add_node
weightDeleteNode => weight_delete_node
weightDoNothing => weight_do_nothing
weightInsertNode => weight_insert_node
weightMutateConstant => weight_mutate_constant
weightMutateOperator => weight_mutate_operator
weightSwapOperands => weight_swap_operands
weightRandomize => weight_randomize
weightSimplify => weight_simplify
crossoverProbability => crossover_probability
perturbationFactor => perturbation_factor
batchSize => batch_size
warmupMaxsizeBy => warmup_maxsize_by
useFrequency => use_frequency
useFrequencyInTournament => use_frequency_in_tournament
ncyclesperiteration => ncycles_per_iteration
"""
# Turn this into a dict:
deprecated_kwargs = {}
for line in deprecation_string.splitlines():
line = line.replace(" ", "")
if line == "":
continue
old, new = line.split("=>")
deprecated_kwargs[old] = new

return deprecated_kwargs
DEPRECATED_KWARGS = {
"fractionReplaced": "fraction_replaced",
"fractionReplacedHof": "fraction_replaced_hof",
"npop": "population_size",
"hofMigration": "hof_migration",
"shouldOptimizeConstants": "should_optimize_constants",
"weightAddNode": "weight_add_node",
"weightDeleteNode": "weight_delete_node",
"weightDoNothing": "weight_do_nothing",
"weightInsertNode": "weight_insert_node",
"weightMutateConstant": "weight_mutate_constant",
"weightMutateOperator": "weight_mutate_operator",
"weightSwapOperands": "weight_swap_operands",
"weightRandomize": "weight_randomize",
"weightSimplify": "weight_simplify",
"crossoverProbability": "crossover_probability",
"perturbationFactor": "perturbation_factor",
"batchSize": "batch_size",
"warmupMaxsizeBy": "warmup_maxsize_by",
"useFrequency": "use_frequency",
"useFrequencyInTournament": "use_frequency_in_tournament",
"ncyclesperiteration": "ncycles_per_iteration",
"loss": "elementwise_loss",
"full_objective": "loss_function",
}
10 changes: 1 addition & 9 deletions pysr/julia_helpers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""Functions for initializing the Julia environment and installing deps."""
import warnings

import numpy as np
from juliacall import convert as jl_convert # type: ignore

from .deprecated import init_julia, install
from .julia_import import jl

jl.seval("using Serialization: Serialization")
Expand All @@ -15,14 +15,6 @@
jl.seval("using SymbolicRegression: plus, sub, mult, div, pow")


def install(*args, **kwargs):
del args, kwargs
warnings.warn(
"The `install` function has been removed. "
"PySR now uses the `juliacall` package to install its dependencies automatically at import time. "
)


def _escape_filename(filename):
"""Turn a path into a string with correctly escaped backslashes."""
str_repr = str(filename)
Expand Down
4 changes: 2 additions & 2 deletions pysr/param_groupings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
- population_size
- ncycles_per_iteration
- The Objective:
- loss
- full_objective
- elementwise_loss
- loss_function
- model_selection
- dimensional_constraint_penalty
- Working with Complexities:
Expand Down
37 changes: 21 additions & 16 deletions pysr/sr.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from sklearn.utils.validation import _check_feature_names_in, check_is_fitted

from .denoising import denoise, multi_denoise
from .deprecated import make_deprecated_kwargs_for_pysr_regressor
from .deprecated import DEPRECATED_KWARGS
from .export_jax import sympy2jax
from .export_latex import sympy2latex, sympy2latextable, sympy2multilatextable
from .export_numpy import sympy2numpy
Expand Down Expand Up @@ -268,7 +268,7 @@ class PySRRegressor(MultiOutputMixin, RegressorMixin, BaseEstimator):
arguments are treated the same way, and the max of each
argument is constrained.
Default is `None`.
loss : str
elementwise_loss : str
String of Julia code specifying an elementwise loss function.
Can either be a loss from LossFunctions.jl, or your own loss
written as a function. Examples of custom written losses include:
Expand All @@ -284,11 +284,11 @@ class PySRRegressor(MultiOutputMixin, RegressorMixin, BaseEstimator):
`ModifiedHuberLoss()`, `L2MarginLoss()`, `ExpLoss()`,
`SigmoidLoss()`, `DWDMarginLoss(q)`.
Default is `"L2DistLoss()"`.
full_objective : str
loss_function : str
Alternatively, you can specify the full objective function as
a snippet of Julia code, including any sort of custom evaluation
(including symbolic manipulations beforehand), and any sort
of loss function or regularizations. The default `full_objective`
of loss function or regularizations. The default `loss_function`
used in SymbolicRegression.jl is roughly equal to:
```julia
function eval_loss(tree, dataset::Dataset{T,L}, options)::L where {T,L}
Expand Down Expand Up @@ -637,7 +637,7 @@ class PySRRegressor(MultiOutputMixin, RegressorMixin, BaseEstimator):
... "inv(x) = 1/x", # Custom operator (julia syntax)
... ],
... model_selection="best",
... loss="loss(x, y) = (x - y)^2", # Custom loss function (julia syntax)
... elementwise_loss="loss(x, y) = (x - y)^2", # Custom loss function (julia syntax)
... )
>>> model.fit(X, y)
>>> model
Expand Down Expand Up @@ -675,8 +675,8 @@ def __init__(
timeout_in_seconds: Optional[float] = None,
constraints: Optional[Dict[str, Union[int, Tuple[int, int]]]] = None,
nested_constraints: Optional[Dict[str, Dict[str, int]]] = None,
loss: Optional[str] = None,
full_objective: Optional[str] = None,
elementwise_loss: Optional[str] = None,
loss_function: Optional[str] = None,
complexity_of_operators: Optional[Dict[str, Union[int, float]]] = None,
complexity_of_constants: Union[int, float] = 1,
complexity_of_variables: Union[int, float] = 1,
Expand Down Expand Up @@ -769,8 +769,8 @@ def __init__(
self.timeout_in_seconds = timeout_in_seconds
self.early_stop_condition = early_stop_condition
# - Loss parameters
self.loss = loss
self.full_objective = full_objective
self.elementwise_loss = elementwise_loss
self.loss_function = loss_function
self.complexity_of_operators = complexity_of_operators
self.complexity_of_constants = complexity_of_constants
self.complexity_of_variables = complexity_of_variables
Expand Down Expand Up @@ -849,11 +849,10 @@ def __init__(
# Once all valid parameters have been assigned handle the
# deprecated kwargs
if len(kwargs) > 0: # pragma: no cover
deprecated_kwargs = make_deprecated_kwargs_for_pysr_regressor()
for k, v in kwargs.items():
# Handle renamed kwargs
if k in deprecated_kwargs:
updated_kwarg_name = deprecated_kwargs[k]
if k in DEPRECATED_KWARGS:
updated_kwarg_name = DEPRECATED_KWARGS[k]
setattr(self, updated_kwarg_name, v)
warnings.warn(
f"{k} has been renamed to {updated_kwarg_name} in PySRRegressor. "
Expand Down Expand Up @@ -1251,8 +1250,10 @@ def _validate_and_set_init_params(self):
"to True and `procs` to 0 will result in non-deterministic searches. "
)

if self.loss is not None and self.full_objective is not None:
raise ValueError("You cannot set both `loss` and `full_objective`.")
if self.elementwise_loss is not None and self.loss_function is not None:
raise ValueError(
"You cannot set both `elementwise_loss` and `loss_function`."
)

# NotImplementedError - Values that could be supported at a later time
if self.optimizer_algorithm not in VALID_OPTIMIZER_ALGORITHMS:
Expand Down Expand Up @@ -1587,9 +1588,13 @@ def _run(self, X, y, mutated_params, weights, seed):
complexity_of_operators_str += ")"
complexity_of_operators = jl.seval(complexity_of_operators_str)

custom_loss = jl.seval(str(self.loss) if self.loss is not None else "nothing")
custom_loss = jl.seval(
str(self.elementwise_loss)
if self.elementwise_loss is not None
else "nothing"
)
custom_full_objective = jl.seval(
str(self.full_objective) if self.full_objective is not None else "nothing"
str(self.loss_function) if self.loss_function is not None else "nothing"
)

early_stop_condition = jl.seval(
Expand Down
4 changes: 2 additions & 2 deletions pysr/test/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def test_multiprocessing_turbo_custom_objective(self):
multithreading=False,
turbo=True,
early_stop_condition="stop_if(loss, complexity) = loss < 1e-10 && complexity == 1",
full_objective="""
loss_function="""
function my_objective(tree::Node{T}, dataset::Dataset{T}, options::Options) where T
prediction, flag = eval_tree_array(tree, dataset.X, options)
!flag && return T(Inf)
Expand All @@ -100,7 +100,7 @@ def test_high_precision_search_custom_loss(self):
model = PySRRegressor(
**self.default_test_kwargs,
early_stop_condition="stop_if(loss, complexity) = loss < 1e-4 && complexity == 3",
loss="my_loss(prediction, target) = (prediction - target)^2",
elementwise_loss="my_loss(prediction, target) = (prediction - target)^2",
precision=64,
parsimony=0.01,
warm_start=True,
Expand Down

0 comments on commit d16abb4

Please sign in to comment.