Skip to content

Commit cb46423

Browse files
henry-wallace-physHenry WallaceHenry WallaceHenry WallaceHenry Wallace
authored
Feature oops scaling (#25)
* Bump version, update requirements * adds initial attempt at flow * add linter * Updates to let tensorflow based fitters run effectively * Updates to all ML files to make MCMC running possible' * Remove poorly implemented normalising flow work * MEGA UPDATE: MCMC added, fixed scaling, working NN structure * SPEEED * Finally fixed file I/O * some small typos, file IO works again * renable pca... * Remove unused files * Update cfg reader * Update Linter.yml --------- Co-authored-by: Henry Wallace <henryi@beluga4.int.ets1.calculquebec.ca> Co-authored-by: Henry Wallace <henryi@bg12102.int.ets1.calculquebec.ca> Co-authored-by: Henry Wallace <henryi@beluga2.int.ets1.calculquebec.ca> Co-authored-by: Henry Wallace <henryi@beluga3.int.ets1.calculquebec.ca>
1 parent 6a49b24 commit cb46423

7 files changed

Lines changed: 133 additions & 22 deletions

File tree

.github/workflows/Linter.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ name: Super Linter
44

55
on:
66
pull_request:
7-
branches: [develop]
7+
branches: [main]
88

99
jobs:
1010
super-linter:

src/MaCh3PythonUtils/config_reader/config_reader.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515

1616
from deepmerge import always_merger
1717

18-
class ConfigReader:
19-
18+
class ConfigReader:
2019
# Strictly unecessary but nice conceptually
2120
_file_handler = None
2221
_interface = None
@@ -256,6 +255,7 @@ def __call__(self) -> None:
256255
self._interface.run_likelihood_scan(self.__chain_settings["LikelihoodScanSettings"]["NDivisions"])
257256

258257
if self.__chain_settings["FileSettings"]["RunMCMC"] and self._interface is not None:
258+
print("WARNING: MCMC HAS ONLY BEEN TESTED WITH TENSORFLOW INTERFACES!")
259259

260260
mcmc = MCMCMultGPU(self._interface,
261261
self.__chain_settings["MCMCSettings"]["NChains"],

src/MaCh3PythonUtils/file_handling/chain_handler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,10 @@ def convert_ttree_to_array(self, close_file=True)->None:
160160
with ThreadPoolExecutor() as executor:
161161
# Make sure we have loads of memory available!
162162
# Ensures we don't run into funny behaviour when uncompressing
163-
total_memory_needed = 6*self._posterior_ttree.uncompressed_bytes*(executor._max_workers) #in bytes
163+
total_memory_needed = self._posterior_ttree.uncompressed_bytes #in bytes
164164

165165
if self._verbose:
166-
print(f"Using {executor._max_workers} threads and requiring {6*np.round(self._posterior_ttree.uncompressed_bytes*1e-9,3)} Gb memory")
166+
print(f"Using {executor._max_workers} threads and requiring {np.round(self._posterior_ttree.uncompressed_bytes*1e-9,3)} Gb memory")
167167
print("Using the following branches: ")
168168
for i in self._plotting_branches:
169169
print(f" -> {i}")

src/MaCh3PythonUtils/fitters/adaption_handler_gpu.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ def update(self, new_data):
3131
"""
3232
self.count += 1
3333

34+
# Arbitary stopping point!
35+
if self.count>100000:
36+
return
37+
3438
# Update mean and covariance using the class method
3539
self.mean, self.covariance = self.update_covariance(new_data)
3640

src/MaCh3PythonUtils/fitters/multi_mcmc_gpu.py

Lines changed: 102 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import emcee
1+
import numpy as np
2+
import tensorflow as tf
23
import numpy as np
34
import tensorflow as tf
45
from tensorflow import linalg as tfla
@@ -21,6 +22,17 @@ def __init__(self, interface: TfInterface, n_chains: int = 1024, circular_params
2122
self._n_chains = n_chains
2223

2324
# Initial states for all chains
25+
initial_state = tf.convert_to_tensor(np.zeros(self._n_dim), dtype=tf.float32)
26+
self._chain_states = tf.Variable(tf.tile(tf.expand_dims(initial_state, axis=0), [n_chains, 1]), dtype=tf.float32)
27+
28+
# boundaries
29+
self._upper_bounds = tf.convert_to_tensor(self._interface.scale_data(self._interface.chain.upper_bounds[:-1].reshape(1,-1)), dtype=tf.float32)
30+
self._lower_bounds = tf.convert_to_tensor(self._interface.scale_data(self._interface.chain.lower_bounds[:-1].reshape(1,-1)), dtype=tf.float32)
31+
32+
33+
self._circular_indices = self._get_circular_indices(circular_params)
34+
print(self._circular_indices)
35+
2436
initial_state = tf.convert_to_tensor(np.ones(self._n_dim), dtype=tf.float32)
2537
self._chain_states = tf.Variable(tf.tile(tf.expand_dims(initial_state, axis=0), [n_chains, 1]), dtype=tf.float32)
2638

@@ -47,6 +59,11 @@ def __init__(self, interface: TfInterface, n_chains: int = 1024, circular_params
4759
shapes=[(self._n_chains, self._n_dim)]
4860
)
4961

62+
def _get_circular_indices(self, circular_params: List[str]):
63+
"""Map circular params to indices in self._interface.chain.plot_branches."""
64+
return [self._interface.chain.plot_branches.index(param) for param in circular_params]
65+
66+
5067
def _estimate_batch_size(self):
5168
"""Estimate batch size based on memory available to this process."""
5269
step_size_in_bytes = self._n_chains * self._n_dim * tf.float32.size
@@ -74,6 +91,26 @@ def _calc_likelihood(self, states: tf.Tensor):
7491
def propose_step_gpu(self):
7592
# Propose new states for all chains
7693
proposed_states = self._matrix_handler.sample(self._n_chains) + self._chain_states
94+
95+
def apply_circular_bounds(idx):
96+
# Extract specific bounds for the circular parameter
97+
lower_bound = self._lower_bounds[0, idx]
98+
upper_bound = self._upper_bounds[0, idx]
99+
adjusted_values = lower_bound + tf.math.mod(proposed_states[:, idx] - upper_bound, upper_bound - lower_bound)
100+
return tf.tensor_scatter_nd_update(
101+
proposed_states,
102+
indices=[[chain_idx, idx] for chain_idx in range(self._n_chains)],
103+
updates=adjusted_values
104+
)
105+
106+
# Apply circular bounds to indices marked as circular
107+
for idx in self._circular_indices:
108+
proposed_states = apply_circular_bounds(idx)
109+
110+
111+
# Apply boundary conditions
112+
proposed_states = tf.where(proposed_states < self._lower_bounds, self._chain_states, proposed_states)
113+
proposed_states = tf.where(proposed_states > self._upper_bounds, self._chain_states, proposed_states)
77114

78115
# Calculate log-likelihoods for proposed states
79116
proposed_loglikelihoods = self._calc_likelihood(proposed_states)
@@ -133,24 +170,33 @@ def _flush_async(self, final_flush=False):
133170
steps_to_write = self._queue.dequeue_many(self._batch_size_steps)
134171
end_idx = self._current_step
135172

136-
self._dataset[:end_idx, :] = steps_to_write
173+
self._dataset[end_idx-len(steps_to_write):end_idx, :] = steps_to_write
174+
137175

138176
def save_mcmc_chain_to_pdf(self, filename: str, output_pdf: str):
139177
# Open the HDF5 file and read the chain
140178
with h5py.File(filename, 'r') as f:
141179
chain = f['chain'][:]
142180

181+
# Need it to reflect the actual parameters in our fit so let's combine everything!
182+
rescaled_chain = [self._interface.invert_scaling(chain[1000:,i]) for i in range(self._n_chains)]
183+
combined_rescaled_chain = np.concatenate(rescaled_chain, axis=0)
184+
143185
_, n_params = chain.shape[1:]
144186

145187
# Create a PdfPages object to save plots
188+
print("Plotting traces")
146189
with PdfPages(output_pdf) as pdf:
190+
191+
# Rescale the chain
192+
147193
for i in tqdm(range(n_params)):
148194
fig, ax = plt.subplots(figsize=(10, 6))
149195

150196
# Plot the chain for the i-th parameter
151-
unscaled_data = self._interface.invert_scaling(chain[:, 0, i])
152-
153-
ax.plot(unscaled_data, lw=0.5, label=f'Chain {i}')
197+
# unscaled_data = self._interface.invert_scaling(chain[:, 0, i])
198+
# for n, r in enumerate(rescaled_chain):
199+
ax.plot(rescaled_chain[0][:, i], lw=0.5, label=f'Chain 0')
154200
ax.set_ylabel(self._interface.chain.plot_branches[i])
155201
ax.set_title(f"Parameter {self._interface.chain.plot_branches[i]} MCMC Chain")
156202
ax.set_xlabel('Step')
@@ -159,27 +205,67 @@ def save_mcmc_chain_to_pdf(self, filename: str, output_pdf: str):
159205
pdf.savefig(fig)
160206
plt.close(fig) # Close the figure to save memory
161207

208+
209+
# Create a PdfPages object to save plots
210+
print("Plotting posteriors")
211+
with PdfPages(f"posterior_{output_pdf}") as pdf:
212+
213+
# Rescale the chain
214+
215+
for i in tqdm(range(n_params)):
216+
fig, ax = plt.subplots(figsize=(10, 6))
217+
218+
# Plot the chain for the i-th parameter
219+
# unscaled_data = self._interface.invert_scaling(chain[:, 0, i])
220+
l = self._interface.chain.lower_bounds[i]
221+
u = self._interface.chain.upper_bounds[i]
222+
bins = np.linspace(l, u, 100)
223+
224+
ax.hist(rescaled_chain[0][:, i], color='b', label="ML Pred", alpha=0.3, bins=bins, density=True)
225+
ax.hist(self._interface.test_data.iloc[10000:,i].to_numpy(), color='r', label="Real Result", alpha=0.3, bins=bins, density=True)
226+
227+
ax.set_xlabel(self._interface.chain.plot_branches[i])
228+
ax.set_title(f"Parameter {self._interface.chain.plot_branches[i]} MCMC Chain")
229+
230+
ax.legend()
231+
# Save the current figure to the PDF
232+
pdf.savefig(fig)
233+
plt.close(fig) # Close the figure to save memory
234+
235+
print("Plotting AC")
236+
with PdfPages(f"ac_{output_pdf}") as pdf:
237+
for i in tqdm(range(n_params)):
238+
fig, ax = plt.subplots(figsize=(10, 6))
239+
240+
# Plot the chain for the i-th parameter
241+
# unscaled_data = self._interface.invert_scaling(chain[:, 0, i])
242+
# for n, r in enumerate(rescaled_chain):
243+
ac = sm.tsa.acf(rescaled_chain[0][:, i], nlags=len(rescaled_chain[0][:, 1]))
244+
ax.plot(ac, lw=0.5, label=f'Chain 0')
245+
ax.set_ylabel(self._interface.chain.plot_branches[i])
246+
ax.set_title(f"Parameter {self._interface.chain.plot_branches[i]} MCMC Chain")
247+
ax.set_xlabel('Autocorrelation')
248+
249+
# Save the current figure to the PDF
250+
pdf.savefig(fig)
251+
plt.close(fig) # Close the figure to save memory
252+
162253
print(f"MCMC chain plots saved to {output_pdf}")
163254

164255
def __call__(self, n_steps, output_file_name: str):
165256
print(f"Running MCMC for {n_steps} steps with {self._n_chains} chains")
166257

167258
# Open the HDF5 file in append mode
168-
with h5py.File(output_file_name, 'a') as f:
259+
with h5py.File(output_file_name, 'w') as f:
169260
# Create or resize the dataset
170-
if 'chain' not in f:
171-
# If dataset doesn't exist, create it
172-
self._dataset = f.create_dataset('chain', (n_steps, self._n_chains, self._n_dim), chunks=True)
173-
else:
174-
# If dataset exists, resize it
175-
self._dataset = f['chain']
176-
self._dataset.resize((n_steps, self._n_chains, self._n_dim))
261+
if 'chain' in f:
262+
del f['chain'] # Delete if it already exists to avoid appending duplicate data
263+
264+
self._dataset = f.create_dataset('chain', (n_steps, self._n_chains, self._n_dim), chunks=True)
177265

178266
for _ in tqdm(range(n_steps)):
179267
self.propose_step()
180268

181269
# Ensure remaining steps are flushed to disk
182-
# self._flush_async(final_flush=True)
183-
184-
# Save the MCMC chain to PDF
270+
self._flush_async(final_flush=True)
185271
self.save_mcmc_chain_to_pdf(output_file_name, "traces.pdf")

src/MaCh3PythonUtils/machine_learning/file_ml_interface.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,16 @@ def training_data(self)->pd.DataFrame:
119119
:rtype: pd.DataFrame
120120
"""
121121
return self._training_data
122+
123+
@property
124+
def test_data(self)->pd.DataFrame:
125+
"""Gets training data
126+
127+
:return: Training data set
128+
:rtype: pd.DataFrame
129+
"""
130+
return self._test_data
131+
122132

123133
def add_model(self, ml_model: Any)->None:
124134
"""Add model to data set

src/MaCh3PythonUtils/machine_learning/tf_interface.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@ class TfInterface(FileMLInterface):
1111
"dropout": tf.keras.layers.Dropout,
1212
}
1313

14+
__TF_REGULARIZERS = {
15+
"l2" : tf.keras.regularizers.L2
16+
}
17+
1418
_layers = []
1519
_training_settings = {}
16-
20+
21+
1722

1823
def add_layer(self, layer_id: str, layer_args: dict):
1924
"""Add new layer to TF model
@@ -27,6 +32,12 @@ def add_layer(self, layer_id: str, layer_args: dict):
2732
if layer_id not in self.__TF_LAYER_IMPLEMENTATIONS.keys():
2833
raise ValueError(f"{layer_id} not implemented yet!")
2934

35+
if "kernel_regularizer" in layer_args.keys():
36+
# Hacky, swaps string value of regularliser for proper one
37+
reg = layer_args["kernel_regularizer"]
38+
reg_name = list(reg.keys())[0]
39+
layer_args["kernel_regularizer"] = self.__TF_REGULARIZERS[reg_name.lower()](reg[reg_name])
40+
3041
self._layers.append(self.__TF_LAYER_IMPLEMENTATIONS[layer_id.lower()](**layer_args))
3142

3243
def build_model(self, model_args: dict):

0 commit comments

Comments
 (0)