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

feat: model inventory #250

Merged
merged 34 commits into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
23e04a4
feat: initial commit of synthetic experiment inventory
hollandjg Jan 20, 2023
7ea47e9
docs: add example of using registry
hollandjg Jan 20, 2023
7c555e2
test: add tests of registration and retrieval
hollandjg Jan 20, 2023
f4a46aa
chore: remove redundant code
hollandjg Jan 20, 2023
d1948fe
chore: remove redundant code
hollandjg Jan 20, 2023
b295239
refactor: change to using separate callable for the experiment and gr…
hollandjg Jan 25, 2023
0386222
refactor: move variables into functions as defaults
hollandjg Jan 25, 2023
168279f
refactor: make rng seedable for expected value
hollandjg Jan 25, 2023
438e943
refactor: make rng seedable for expected value
hollandjg Jan 25, 2023
50a6717
refactor: reorder variables
hollandjg Jan 25, 2023
740df73
fix: add missing plotter
hollandjg Jan 26, 2023
12232ca
fix: id_ parameter name
hollandjg Feb 1, 2023
5258581
feat: update inventory for new syntax
hollandjg Mar 6, 2023
67faebf
feat: update inventory for new syntax
hollandjg Mar 6, 2023
a74b87f
feat: add prospect theory
hollandjg Mar 6, 2023
0f9ede2
docs: update docs on synthetic models
hollandjg Mar 6, 2023
0ea56c1
test: update tests for model inventory
hollandjg Mar 6, 2023
8ba7849
docs: add example notebook
hollandjg Mar 6, 2023
5f40749
Merge branch 'main' into feat/model-inventory
hollandjg Mar 6, 2023
ccb8cda
refactor: shorten dataclass definition
hollandjg Mar 6, 2023
e84789f
Merge remote-tracking branch 'origin/feat/model-inventory' into feat/…
hollandjg Mar 6, 2023
40cf42e
test: update testcase name
hollandjg Mar 6, 2023
252a0e9
refactor: update filenames and docstrings
hollandjg Mar 6, 2023
04a93f9
docs: update inventory docs
hollandjg Mar 6, 2023
4923820
chore: update parameter name to id_
hollandjg Mar 6, 2023
78179d3
chore: update inventory module name
hollandjg Mar 6, 2023
8b4a82a
revert: erroneous changes
hollandjg Mar 6, 2023
921db8b
feat: add describe function
hollandjg Mar 9, 2023
e8df252
docs: make "describe" function print
hollandjg Mar 9, 2023
d321ad6
docs: add updated docstring for data
hollandjg Mar 9, 2023
da18e6c
docs: update docstring for describe function
hollandjg Mar 9, 2023
26d1388
Merge branch 'main' into feat/model-inventory
hollandjg Mar 9, 2023
ded1b54
feat: make inventory public
hollandjg Mar 9, 2023
dbb532a
Merge remote-tracking branch 'origin/feat/model-inventory' into feat/…
hollandjg Mar 9, 2023
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
1 change: 0 additions & 1 deletion .idea/autora.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 77 additions & 0 deletions autora/synthetic/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""
Provides an interface for loading and saving synthetic experiments.

Examples:
The registry is accessed using the `retrieve` function, optionally setting parameters:
>>> from autora.synthetic import retrieve, describe
>>> import numpy as np
>>> s = retrieve("weber_fechner",rng=np.random.default_rng(seed=180)) # the Weber-Fechner Law

Use the describe function to give information about the synthetic experiment:
>>> describe(s) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
Weber-Fechner Law...

The synthetic experiement `s` has properties like the name of the experiment:
>>> s.name
'Weber-Fechner Law'

... a valid metadata description:
>>> s.metadata # doctest: +ELLIPSIS
VariableCollection(...)

... a function to generate the full domain of the data (if possible)
>>> x = s.domain()
>>> x # doctest: +ELLIPSIS
array([[0...]])

... the experiment_runner runner which can be called to generate experimental results:
>>> import numpy as np
>>> y = s.experiment_runner(x) # doctest: +ELLIPSIS
>>> y
array([[ 0.00433955],
[ 1.79114625],
[ 2.39473454],
...,
[ 0.00397802],
[ 0.01922405],
[-0.00612883]])

... a function to plot the ground truth:
>>> s.plotter()

... against a fitted model if it exists:
>>> from sklearn.linear_model import LinearRegression
>>> model = LinearRegression().fit(x, y)
>>> s.plotter(model)

These can be used to run a full experimental cycle
>>> from autora.experimentalist.pipeline import make_pipeline
>>> from autora.experimentalist.pooler.general_pool import grid_pool
>>> from autora.experimentalist.sampler.random import random_sampler
>>> from functools import partial
>>> import random
>>> metadata = s.metadata
>>> pool = partial(grid_pool, ivs=metadata.independent_variables)
>>> random.seed(181) # set the seed for the random sampler
>>> sampler = partial(random_sampler, n=20)
>>> experimentalist_pipeline = make_pipeline([pool, sampler])

>>> from autora.cycle import Cycle
>>> theorist = LinearRegression()

>>> cycle = Cycle(metadata=metadata, experimentalist=experimentalist_pipeline,
... experiment_runner=s.experiment_runner, theorist=theorist)

>>> c = cycle.run(10)
>>> c.data.theories[-1].coef_ # doctest: +ELLIPSIS
array([-0.53610647, 0.58457307])
"""

from autora.synthetic import data
from autora.synthetic.inventory import (
Inventory,
SyntheticExperimentCollection,
describe,
register,
retrieve,
)
2 changes: 2 additions & 0 deletions autora/synthetic/data/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
""" Models bundled with AutoRA. """
from . import expected_value, prospect_theory, weber_fechner
184 changes: 184 additions & 0 deletions autora/synthetic/data/expected_value.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
from functools import partial

import numpy as np

from autora.variable import DV, IV, ValueType, VariableCollection

from ..inventory import SyntheticExperimentCollection, register


def get_metadata(minimum_value, maximum_value, resolution):
v_a = IV(
name="V_A",
allowed_values=np.linspace(
minimum_value,
maximum_value,
resolution,
),
value_range=(minimum_value, maximum_value),
units="dollar",
variable_label="Value of Option A",
type=ValueType.REAL,
)

v_b = IV(
name="V_B",
allowed_values=np.linspace(
minimum_value,
maximum_value,
resolution,
),
value_range=(minimum_value, maximum_value),
units="dollar",
variable_label="Value of Option B",
type=ValueType.REAL,
)

p_a = IV(
name="P_A",
allowed_values=np.linspace(0, 1, resolution),
value_range=(0, 1),
units="probability",
variable_label="Probability of Option A",
type=ValueType.REAL,
)

p_b = IV(
name="P_B",
allowed_values=np.linspace(0, 1, resolution),
value_range=(0, 1),
units="probability",
variable_label="Probability of Option B",
type=ValueType.REAL,
)

dv1 = DV(
name="choose_A",
value_range=(0, 1),
units="probability",
variable_label="Probability of Choosing Option A",
type=ValueType.PROBABILITY,
)

metadata_ = VariableCollection(
independent_variables=[v_a, p_a, v_b, p_b],
dependent_variables=[dv1],
)
return metadata_


def expected_value_theory(
name="Expected Value Theory",
choice_temperature: float = 0.1,
value_lambda: float = 0.5,
resolution=10,
minimum_value=-1,
maximum_value=1,
added_noise: float = 0.01,
rng=np.random.default_rng(),
):

params = dict(
name=name,
minimum_value=minimum_value,
maximum_value=maximum_value,
resolution=resolution,
choice_temperature=choice_temperature,
value_lambda=value_lambda,
added_noise=added_noise,
random_number_generator=rng,
)

metadata = get_metadata(
minimum_value=minimum_value, maximum_value=maximum_value, resolution=resolution
)

def experiment_runner(X: np.ndarray, added_noise_=added_noise):

Y = np.zeros((X.shape[0], 1))
for idx, x in enumerate(X):
value_A = value_lambda * x[0]
value_B = value_lambda * x[2]

probability_a = x[1]
probability_b = x[3]

expected_value_A = value_A * probability_a + rng.normal(0, added_noise_)
expected_value_B = value_B * probability_b + rng.normal(0, added_noise_)

# compute probability of choosing option A
p_choose_A = np.exp(expected_value_A / choice_temperature) / (
np.exp(expected_value_A / choice_temperature)
+ np.exp(expected_value_B / choice_temperature)
)

Y[idx] = p_choose_A

return Y

ground_truth = partial(experiment_runner, added_noise_=0.0)

def domain():
X = np.array(
np.meshgrid([x.allowed_values for x in metadata.independent_variables])
).T.reshape(-1, 4)
return X

def plotter(model=None):
import matplotlib.colors as mcolors
import matplotlib.pyplot as plt

v_a_list = [-1, 0.5, 1]
v_b = 0.5
p_b = 0.5
p_a = np.linspace(0, 1, 100)

for idx, v_a in enumerate(v_a_list):
X = np.zeros((len(p_a), 4))
X[:, 0] = v_a
X[:, 1] = p_a
X[:, 2] = v_b
X[:, 3] = p_b

y = ground_truth(X)
colors = mcolors.TABLEAU_COLORS
col_keys = list(colors.keys())
plt.plot(
p_a, y, label=f"$V(A) = {v_a}$ (Original)", c=colors[col_keys[idx]]
)
if model is not None:
y = model.predict(X)
plt.plot(
p_a,
y,
label=f"$V(A) = {v_a}$ (Recovered)",
c=colors[col_keys[idx]],
linestyle="--",
)

x_limit = [0, metadata.independent_variables[1].value_range[1]]
y_limit = [0, 1]
x_label = "Probability of Choosing Option A"
y_label = "Probability of Obtaining V(A)"

plt.xlim(x_limit)
plt.ylim(y_limit)
plt.xlabel(x_label, fontsize="large")
plt.ylabel(y_label, fontsize="large")
plt.legend(loc=2, fontsize="medium")
plt.title(name, fontsize="x-large")
plt.show()

collection = SyntheticExperimentCollection(
name=name,
metadata=metadata,
experiment_runner=experiment_runner,
ground_truth=ground_truth,
domain=domain,
plotter=plotter,
params=params,
)
return collection


register("expected_value", expected_value_theory)
Loading