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

Messy but working branch used for training models in parameter scan #54

Merged
merged 60 commits into from
Apr 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
a795296
major overhaul - two point and top observables implemented
May 23, 2020
6b366b7
update example runcards
May 26, 2020
143982f
clean up a bit, add docstrings, add 2m corr length
May 26, 2020
1cc9f5d
correct bug in autocorrelation: one dim at a time!
May 26, 2020
5f56997
log info acceptance fraction, not log debug
May 29, 2020
8b6383b
fix 'input_folder' attribute error when retraining
Jun 1, 2020
a9731d8
fix bug in plotting known pdfs
Jun 1, 2020
5b91259
von mises fix
Jun 1, 2020
130f4d8
first attempt at restructure
Jun 2, 2020
013db98
customised nn.Sequential
Jun 2, 2020
4556f71
abandon namespaces_
Jun 2, 2020
2c91f03
Merge branch 'fixes/plot_pdf' into models_restruct
Jun 2, 2020
195488c
real_nvp working with convex combinations
Jun 2, 2020
c94b35b
add docstrings
Jun 2, 2020
19f58eb
example runcard and fixes
Jun 2, 2020
a97aa7d
working 1d stereo
Jun 2, 2020
1d69f0e
total network flexibility, but sampling broken
Jun 3, 2020
7c29985
abandon namespaces_ and individual layer specification again
Jun 3, 2020
729d04b
Merge branch 'fixes/retrain' into models_restruct
Jun 3, 2020
eb86a51
also fix size/shape mistake
Jun 3, 2020
f234371
Merge branch 'fixes/plot_pdf' into models_restruct
Jun 3, 2020
922a118
add docstrings
Jun 3, 2020
fdc18ac
make correlators connected
Jun 3, 2020
b71aa8d
take sample interval into account in plots
Jun 3, 2020
78acaa9
docstrings on observables actions
Jun 3, 2020
9f21fa9
add 2d projection
Jun 4, 2020
3e632e4
change key model_id -> model
Jun 4, 2020
03dc59a
improve docstrings
Jun 4, 2020
5d76a0b
adjust default domains for uniform and von-mises
Jun 5, 2020
0c271f4
Merge branch 'fixes/dist_domains' into models_restruct
Jun 5, 2020
e3ff1b3
fix bug in ProjectionLayer
Jun 5, 2020
24d0d9a
remove Module labels, add s_final_activation
Jun 5, 2020
9ab5afe
just plot two points for uniform pdf
Jun 8, 2020
eaeea2a
fix bug in log density for complex combinations
Jun 9, 2020
18b8646
normalised log density for convex combs
Jun 10, 2020
259dd4d
correct normalisation for uniform dist
Jun 10, 2020
3cabe3c
add linear spline
Jun 11, 2020
2d7faef
add quadratic spline
Jun 11, 2020
6edb74b
add circular spline
Jun 11, 2020
78f083c
add example runcard and update README
Jun 11, 2020
5c48b20
fix bug in plotting known pdfs
Jun 1, 2020
d5d7923
correct typo
Jun 16, 2020
cef926c
merge observables
Jun 16, 2020
2f57a63
Observables revamp (#1)
jmarshrossney Jun 16, 2020
8d72ca7
Merge remote-tracking branch 'origin/fixes/retrain'
Jun 16, 2020
1404c80
Merge remote-tracking branch 'origin/fixes/plot_pdf'
Jun 16, 2020
5dff246
merge models_restruct
Jun 16, 2020
577d1b5
Feature/ncp circle (#3)
jmarshrossney Jun 16, 2020
982857b
Merge branch 'master' into feature/splines
Jun 16, 2020
17e0383
Merge branch 'feature/splines'
Jun 16, 2020
804d076
merge splines
Jun 16, 2020
af3bede
Feature/multimodal target (#5)
jmarshrossney Jun 16, 2020
697b22d
Feature/rq spline (#6)
jmarshrossney Jul 18, 2020
9557ac1
add batch norm layer
Aug 3, 2020
bce99a5
remove convex combs
Aug 3, 2020
f2305c9
affine transformations are odd
Aug 4, 2020
aa6c49f
fix typos in runcards
Aug 7, 2020
24cc272
Phi four paper (#7)
jmarshrossney Dec 7, 2020
a9ea0d2
redo checkpoints, connected correlator
jmarshrossney Jan 25, 2021
702fb87
option for conn corr with or without abs magnetisation
jmarshrossney Jan 28, 2021
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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ conda install -c conda-forge tqdm
conda install scipy
```

To use `spline' flows, the third-party package *torchsearchsorted* must be installed.
Download the source from the [github repo](https://github.com/aliutkus/torchsearchsorted) and, with the conda environment activated, run
```bash
pip install .
```
in the root folder.

These are the minimal requirements for running the code, however if you plan
on developing the code or doing small external tests, then I highly recommend also
installing the following packages
Expand Down
23 changes: 4 additions & 19 deletions anvil/benchmark_config/free_scalar_train.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,14 @@ m_sq: 4
lam: 0
use_arxiv_version: false

# Base
base: standard_normal

# Model
base: standard_normal
model: real_nvp
n_affine: 4

# Networks
standardise_inputs: false
s_network_spec:
hidden_shape: [24]
activation: leaky_relu
final_activation: leaky_relu
do_batch_norm: false

t_network_spec:
hidden_shape: [24]
activation: leaky_relu
final_activation: null
do_batch_norm: false
model_spec:
n_affine: 2

# Training length
n_batch: 2000
n_batch: 1000
epochs: 3000
save_interval: 1000

Expand Down
127 changes: 82 additions & 45 deletions anvil/checkpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,16 @@
is made so that we don't get unexpected results

"""
from pathlib import Path
from glob import glob

import torch

from reportengine.compat import yaml
from copy import deepcopy

from reportengine import collect

from anvil.models import NeuralNetwork

def neural_network(
size_half,
i_affine,
hidden_shape=(24,),
activation="leaky_relu",
final_activation=None,
do_batch_norm=False,
):
"""Returns an instance of NeuralNetwork to be used in real NVP

Parameters
----------
size_half: int
Number of nodes in the input and output layer of the network
hidden_shape: list like
List like specifying the number of nodes in the intermediate layers
activation: (str, None)
Key representing the activation function used for each layer
except the final one.
final_activation: (str, None)
Key representing the activation function used on the final
layer.
do_batch_norm: bool
Flag dictating whether batch normalisation should be performed
before the activation function.
name: str
A label for the neural network, used for diagnostics.
"""
return NeuralNetwork(
size_in=size_half,
size_out=size_half,
hidden_shape=hidden_shape,
activation=activation,
final_activation=final_activation,
do_batch_norm=do_batch_norm,
name=f"s{i_affine}"
)

s_networks = collect("neural_network", ("affine_layer_index", "s_network_spec",))
t_networks = collect("neural_network", ("affine_layer_index", "t_network_spec",))

def loaded_checkpoint(checkpoint):
if checkpoint is None:
Expand All @@ -62,14 +26,14 @@ def loaded_checkpoint(checkpoint):
def train_range(loaded_checkpoint, epochs):
if loaded_checkpoint is not None:
cp_epoch = loaded_checkpoint["epoch"]
train_range = (cp_epoch, cp_epoch + epochs)
train_range = (cp_epoch, epochs)
else:
train_range = (0, epochs)
return train_range


def loaded_model(loaded_checkpoint, flow_model):
new_model = deepcopy(flow_model) # need to copy model so we don't get weird results
def loaded_model(loaded_checkpoint, model_to_load):
new_model = deepcopy(model_to_load) # need to copy model so we don't get weird results
if loaded_checkpoint is not None:
new_model.load_state_dict(loaded_checkpoint["model_state_dict"])
return new_model
Expand All @@ -79,3 +43,76 @@ def current_loss(loaded_checkpoint):
if loaded_checkpoint is None:
return None
return loaded_checkpoint["loss"]


class InvalidCheckpointError(Exception):
pass


class InvalidTrainingOutputError(Exception):
pass


class TrainingRuncardNotFound(InvalidTrainingOutputError):
pass


class Checkpoint:
"""Class which saves and loads checkpoints and allows checkpoints to be
sorted"""

def __init__(self, path: str):
self.path = Path(path)
try:
self.epoch = int(self.path.stem.split("_")[-1]) # should be an int
except ValueError:
raise InvalidCheckpointError(
f"{self.path} does not match expected "
"name checkpoint: `checkpoint_<epoch>.pt`"
)

def __lt__(self, other):
return self.epoch < other.epoch

def __repr__(self):
return str(self.path)

def load(self):
"""Return checkpoint dictionary"""
return torch.load(self.path)


class TrainingOutput:
"""Class which acts as container for training output, which is a directory
containing training configuration, checkpoints and training logs
"""

_loaded_config = None

def __init__(self, path: str):
self.path = Path(path)
self.config = self.path / "runcard.yml"
if not self.config.is_file():
raise TrainingRuncardNotFound(
f"Invalid training output, no runcard found at: {self.config}"
)
self.checkpoints = [
Checkpoint(cp_path) for cp_path in glob(f"{self.path}/checkpoints/*")
]
self.cp_ids = [cp.epoch for cp in self.checkpoints]
self.name = self.path.name

def get_config(self):
if self._loaded_config is None:
with open(self.config, "r") as f:
self._loaded_config = yaml.safe_load(f)
return self._loaded_config

def as_input(self):
inp = dict(self.get_config()) # make copy
inp["checkpoints"] = self.checkpoints
inp["cp_ids"] = self.cp_ids
return inp

def final_checkpoint(self):
return max(self.checkpoints)
98 changes: 42 additions & 56 deletions anvil/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
from reportengine.report import Config
from reportengine.configparser import ConfigError, element_of, explicit_node

from anvil.core import TrainingOutput
from anvil.train import OPTIMIZER_OPTIONS, reduce_lr_on_plateau
from anvil.models import MODEL_OPTIONS
from anvil.core import normalising_flow
from anvil.geometry import Geometry2D
from anvil.checkpoint import TrainingOutput
from anvil.train import OPTIMIZER_OPTIONS, SCHEDULER_OPTIONS
from anvil.models import MODEL_OPTIONS
from anvil.distributions import BASE_OPTIONS, TARGET_OPTIONS
from anvil.fields import FIELD_OPTIONS

log = logging.getLogger(__name__)

Expand All @@ -36,7 +38,7 @@ def produce_lattice_size(self, lattice_length, lattice_dimension):
return pow(lattice_length, lattice_dimension)

def produce_config_size(self, lattice_size, target):
"""number of nodes in a single field configuration"""
"""Size of a single configuration or input vector for neural network."""
if target == "o3":
return 2 * lattice_size
return lattice_size
Expand All @@ -53,16 +55,8 @@ def produce_size_half(self, config_size):
def produce_geometry(self, lattice_length):
return Geometry2D(lattice_length)

def parse_target(self, target: str):
"""String specifying target distrbution."""
return target

def parse_base(self, base: str):
"""String specifying base distribution."""
return base

@explicit_node
def produce_target_dist(self, target: str):
def produce_target_dist(self, target):
"""Return the function which initialises the correct action"""
try:
return TARGET_OPTIONS[target]
Expand All @@ -71,6 +65,18 @@ def produce_target_dist(self, target: str):
f"invalid target distribution {target}", target, TARGET_OPTIONS.keys()
)

@explicit_node
def produce_field(self, target):
"""Return the function which instantiates the field object, for
calculating observables."""
try:
return FIELD_OPTIONS[target]
except KeyError:
log.warning(
f"Target {target} does not match an implemented field theory. Using generic field class."
)
return FIELD_OPTIONS[None]

@explicit_node
def produce_base_dist(self, base: str):
"""Return the action which loads appropriate base distribution"""
Expand Down Expand Up @@ -101,54 +107,26 @@ def parse_radius(self, rad: (int, float, str)):
"""Radius for semicircle distribution."""
return rad

def parse_m_sq(self, m: (float, int)):
"""Bare mass squared in scalar theory."""
return m

def parse_lam(self, lam: (float, int)):
"""Coefficient of quartic interaction in phi^4 theory."""
return lam

def parse_use_arxiv_version(self, do_use: bool):
"""If true, use the conventional phi^4 action. If false,
there is an additional factor of 1/2 for the kinetic part
of the phi^4 action."""
return do_use

def parse_beta(self, beta: (float, int)):
"""Inverse temperature."""
return beta
def parse_couplings(self, couplings: dict):
"""Couplings for field theory."""
return couplings # TODO: obviously need to be more fool-proof about this

def parse_model(self, model: str):
"""Label for normalising flow model."""
return model
def parse_parameterisation(self, param: str):
return param

@explicit_node
def produce_flow_model(self, model):
"""Return the action which instantiates the normalising flow model."""
def produce_model_action(self, model: str):
"""Given a string, return the flow model action indexed by that string."""
try:
return MODEL_OPTIONS[model]
except KeyError:
raise ConfigError(
f"invalid flow model {model}", model, MODEL_OPTIONS.keys()
)

def parse_standardise_inputs(self, do_stand: bool):
"""Flag specifying whether to standardise input vectors before
passing them through a neural network."""
return do_stand
raise ConfigError(f"Invalid model {model}", model, MODEL_OPTIONS.keys())

def parse_n_affine(self, n: int):
"""Number of affine layers."""
return n

def produce_affine_layer_index(self, n_affine):
"""Given n_affine, the number of affine layers, produces a list
with n_affine elements, the ith element is {i_affine: i}

we can use affine_layer_index to collect over when producing the model
"""
return [{"i_affine": i} for i in range(n_affine)]
@explicit_node
def produce_model_to_load(self, n_mixture=1):
"""Produce the generative model, whose parameters are to be loaded, which maps
the base to an approximate of the target distribution."""
return normalising_flow

def parse_n_batch(self, nb: int):
"""Batch size for training."""
Expand Down Expand Up @@ -206,9 +184,14 @@ def produce_loaded_optimizer(self, optimizer):
)

@explicit_node
def produce_scheduler(self):
def produce_loaded_scheduler(self, scheduler):
"""Currently fixed to ReduceLROnPlateau"""
return reduce_lr_on_plateau
try:
return SCHEDULER_OPTIONS[scheduler]
except KeyError:
raise ConfigError(
f"Invalid scheduler {scheduler}", scheduler, SCHEDULER_OPTIONS.keys()
)

def parse_target_length(self, targ: int):
"""Target number of decorrelated field configurations to generate."""
Expand Down Expand Up @@ -248,6 +231,9 @@ def parse_n_boot(self, n_boot: int):
log.warning(f"Using user specified n_boot: {n_boot}")
return n_boot

def parse_connected_correlator(self, connected: bool):
return connected

@element_of("windows")
def parse_window(self, window: float):
"""A numerical factor featuring in the calculation of the optimal 'window'
Expand Down
Loading