-
Notifications
You must be signed in to change notification settings - Fork 4
/
StateModeling.py
2329 lines (2074 loc) · 101 KB
/
StateModeling.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import tensorflow as tf
import tensorflow_probability as tfp
# from tensorflow.core.protobuf import config_pb2
import numpy as np
# import os
# from fit_model import load_data
import matplotlib.pyplot as plt
import time
import numbers
import pandas as pd
import tf_keras_tfp_lbfgs as funfac
from dotenv import load_dotenv
import os
import requests
from datetime import datetime, timedelta
# for the file selection dialogue (see https://codereview.stackexchange.com/questions/162920/file-selection-button-for-jupyter-notebook)
import traitlets
from ipywidgets import widgets
from IPython.display import display
from tkinter import Tk, filedialog
class SelectFilesButton(widgets.Button):
"""A file widget that leverages tkinter.filedialog."""
# see https: // codereview.stackexchange.com / questions / 162920 / file - selection - button - for -jupyter - notebook
def __init__(self, out, CallBack=None,Load=True):
super(SelectFilesButton, self).__init__()
# Add the selected_files trait
self.add_traits(files=traitlets.traitlets.List())
# Create the button.
if Load:
self.description = "Load"
else:
self.description = "Save"
self.isLoad=Load
self.icon = "square-o"
self.style.button_color = "orange"
# Set on click behavior.
self.on_click(self.select_files)
self.CallBack = CallBack
self.out = widgets.Output()
@staticmethod
def select_files(b):
"""Generate instance of tkinter.filedialog.
Parameters
----------
b : obj:
An instance of ipywidgets.widgets.Button
"""
with b.out:
try:
# Create Tk root
root = Tk()
# Hide the main window
root.withdraw()
# Raise the root to the top of all windows.
root.call('wm', 'attributes', '.', '-topmost', True)
# List of selected files will be set to b.value
if b.isLoad:
filename = filedialog.askopenfilename() # multiple=False
else:
filename = filedialog.asksaveasfilename()
# print('Load/Save Dialog finished')
#b.description = "Files Selected"
#b.icon = "check-square-o"
#b.style.button_color = "lightgreen"
if b.CallBack is not None:
#print('Invoking CallBack')
b.CallBack(filename)
#else:
#print('no CallBack')
except:
#print('Problem in Load/Save')
#print('File is'+b.files)
pass
cumulPrefix = '_cumul_' # this is used as a keyword to identify whether this plot was already plotted
def getNumArgs(myFkt):
from inspect import signature
sig = signature(myFkt)
return len(sig.parameters)
class DataLoader(object):
def __init__(self):
load_dotenv()
def pull_data(self, uri='http://ec2-3-122-224-7.eu-central-1.compute.amazonaws.com:8080/daily_data'):
return requests.get(uri).json()
# return requests.get('http://ec2-3-122-224-7.eu-central-1.compute.amazonaws.com:8080/daily_data').json()
def get_new_data(self):
uri = "http://ec2-3-122-224-7.eu-central-1.compute.amazonaws.com:8080/data"
json_data = self.pull_data(uri)
table = np.array(json_data["rows"])
column_names = []
for x in json_data["fields"]:
column_names.append(x["name"])
df = pd.DataFrame(table, columns=column_names)
df["day"] = [datetime.fromtimestamp(x["$date"] / 1000) for x in df["day"].values]
df["id"] = df["latitude"].apply(lambda x: str(x)) + "_" + df["longitude"].apply(lambda x: str(x))
unique_ids = df["id"].unique()
regions = {}
for x in unique_ids:
regions[x] = {}
regions[x]["data_fit"] = df[df["id"] == x]
return regions, df
NumberTypes = (int, float, complex, np.ndarray, np.generic)
# The aim is to build a SEIR (Susceptible → Exposed → Infected → Removed)
# Model with a number of (fittable) parameters which may even vary from
# district to district
# The basic model is taken from the webpage
# https://gabgoh.github.io/COVID/index.html
# and the implementation is done in Tensorflow 1.3
# The temporal dimension is treated by unrolling the loop
CalcFloatStr = 'float32'
if False:
defaultLossDataType = "float64"
else:
defaultLossDataType = "float32"
defaultTFDataType = "float32"
defaultTFCpxDataType = "complex64"
def addDicts(dict1, dict2):
"""Merge dictionaries and keep values of common keys in list"""
dict3 = {**dict1, **dict2}
for key, value in dict3.items():
if key in dict1 and key in dict2:
val2 = dict1[key]
if equalShape(value.shape, val2.shape):
dict3[key] = value + val2
else:
print('Shape 1: ' + str(value.shape) + ", shape 2:" + str(val2.shape))
raise ValueError('Shapes of transfer values to add are not the same')
return dict3
def Init(noCuda=False):
"""
initializes the tensorflow system
"""
if noCuda is True:
os.environ["CUDA_VISIBLE_DEVICES"] = '-1'
tf.compat.v1.reset_default_graph() # currently just to shield tensorflow from the main program
# Init()
### tf.compat.v1.disable_eager_execution()
# sess = tf.compat.v1.Session()
# tf.device("/gpu:0")
# Here some code from the inverse Modeling Toolbox (Rainer Heintzmann)
def iterativeOptimizer(myTFOptimization, NIter, loss, verbose=False):
if NIter <= 0:
raise ValueError("NIter has to be positive")
for n in range(NIter):
myTFOptimization() # summary?
myloss = loss().numpy()
if np.isnan(myloss):
raise ValueError("Loss is NaN. Aborting iteration.")
if verbose:
print(str(n) + "/" + str(NIter) + ": " + str(myloss))
return myloss # , summary
def optimizer(loss, otype='L-BFGS-B', NIter=300, oparam={'gtol': 0, 'learning_rate': None}, var_list=None, verbose=False):
"""
defines an optimizer to be used with "Optimize"
This function combines various optimizers from tensorflow and SciPy (with tensorflow compatibility)
Parameters
----------
loss : the loss function, which is a tensor that has been initialized but contains variables
otype (default: L-BFGS : The method of optimization to be used the following options exist:
from Tensorflow:
sgrad
nesterov
adadelta
adam
proxgrad
and from SciPy all the optimizers in the package tf.contrib.opt.ScipyOptimizerInterface
NIter (default: 300) : Number of iterations to be used
oparam : a dictionary to be passed to the detailed optimizers containing optimization parameters (e.g. "learning-rate"). See the individual documentation
var_list (default: None meaning all) : list of tensorflow variables to be used during minimization
verbose (default: False) : prints the loss during iterations if True
Returns
-------
an optimizer funtion (or lambda function)
See also
-------
Example
-------
"""
if NIter < 0:
raise ValueError("NIter has to be positive or zero")
optimStep = 0
if (var_list is not None) and not np.iterable(var_list):
var_list = [var_list]
# these optimizer types work strictly stepwise
elif otype == 'SGD':
learning_rate = oparam["learning_rate"]
if learning_rate == None:
learning_rate = 0.00003
print("setting up sgrad optimization with ", NIter, " iterations.")
optimStep = lambda loss: tf.keras.optimizers.SGD(learning_rate).minimize(loss, var_list=var_list) # 1.0
elif otype == 'nesterov':
learning_rate = oparam["learning_rate"]
if learning_rate == None:
learning_rate = 0.00002
print("setting up nesterov optimization with ", NIter, " iterations.")
optimStep = lambda loss: tf.keras.optimizers.SGD(learning_rate, nesterov=True, momentum=1e-4).minimize(loss, var_list=var_list) # 1.0
elif otype == 'adam':
learning_rate = oparam["learning_rate"]
if learning_rate == None:
learning_rate = 0.0013
print("setting up adam optimization with ", NIter, " iterations, learning_rate: ", learning_rate, ".")
optimStep = lambda loss: tf.keras.optimizers.Adam(learning_rate, 0.9, 0.999).minimize(loss, var_list=var_list) # 1.0
elif otype == 'adadelta':
learning_rate = oparam["learning_rate"]
if learning_rate == None:
learning_rate = 0.0005
print("setting up adadelta optimization with ", NIter, " iterations.")
optimStep = lambda loss: tf.keras.optimizers.Adadelta(learning_rate, 0.9, 0.999).minimize(loss, var_list=var_list) # 1.0
elif otype == 'adagrad':
learning_rate = oparam["learning_rate"]
if learning_rate == None:
learning_rate = 0.0012
print("setting up adagrad optimization with ", NIter, " iterations.")
optimStep = lambda loss: tf.keras.optimizers.Adagrad(learning_rate).minimize(loss, var_list=var_list) # 1.0
if optimStep != 0:
myoptim = lambda: optimStep(loss)
myOptimizer = lambda: iterativeOptimizer(myoptim, NIter, loss, verbose=verbose)
# these optimizers perform the whole iteration
elif otype == 'L-BFGS':
# normFac = None
# if "normFac" in oparam: # "max", "mean" or None
# normFac = oparam["normFac"]
func = funfac.function_factory(loss, var_list) # normFactors=normFac
# convert initial model parameters to a 1D tf.Tensor
init_params = func.initParams() # retrieve the (normalized) initialization parameters
# use the L-BFGS solver
myOptimizer = lambda: LBFGSWrapper(func, init_params, NIter)
# myOptimizer = lambda: tfp.optimizer.lbfgs_minimize(value_and_gradients_function=func,
# initial_position=init_params,
# tolerance=1e-8,
# max_iterations=NIter)
# # f_relative_tolerance = 1e-6,
else:
raise ValueError('Unknown optimizer: ' + otype)
return myOptimizer # either an iterative one or 'L-BFGS'
def LBFGSWrapper(func, init_params, NIter):
optim_results = tfp.optimizer.lbfgs_minimize(value_and_gradients_function=func,
initial_position=init_params,
tolerance=1e-7,
num_correction_pairs=5,
max_iterations=NIter)
# f_relative_tolerance = 1e-6
# converged, failed, num_objective_evaluations, final_loss, final_gradient, position_deltas, gradient_deltas
if not optim_results.converged:
tf.print("WARNING: optimization did not converge")
if optim_results.failed:
tf.print("WARNING: lines search failed during iterations")
res = optim_results.position
func.assign_new_model_parameters(res)
return optim_results.objective_value
def doNormalize(val, normalize, reference):
if normalize == "max":
val = val * tf.reduce_max(reference)
elif normalize == "mean":
val = val * tf.reduce_mean(reference)
return val
def invNormalize(val, normalize, reference):
if normalize == "max":
val = val / tf.reduce_max(reference)
elif normalize == "mean":
val = val / tf.reduce_mean(reference)
return val
@tf.custom_gradient
def monotonicPos(val, b2=1.0): # can also be called forcePositive
"""
applies a monotonic transform mapping the full real axis to the positive half space
This can be used to implicitely force the reconstruction results to be all-positive. The monotonic function is derived from a hyperboloid:
The function is continues and differentiable.
This function can also be used as an activation function for neural networks.
Parameters
----------
val : tensorflow array
The array to be transformed
Returns
-------
tensorflow array
The transformed array
Example
-------
"""
mysqrt = tf.sqrt(b2 + tf.square(val) / 4.0)
def grad(dy):
return dy * (0.5 + val / mysqrt / 4.0), None # no abs here!
# return mysqrt + val / 2.0, grad # This is the original simple equation, but it is numerically very unstable for small numbers!
# slightly better but not good:
# return val * (0.5 + tf.sign(val) * tf.sqrt(b2/tf.square(val)+0.25)), grad
taylor1 = b2 / (2.0 * mysqrt)
diff = val / 2.0 + mysqrt # for negative values this is a difference
# print('diff: ' + str(diff)+", val"+str(val)+" taylor:"+str(taylor1))
# if tf.abs(diff/val) < 2e-4: # this seems a good compromise between finite subtraction and taylor series
Order2N = val * tf.where(tf.abs(diff / val) < 2e-4, taylor1, diff)
p = taylor1 + (b2 + Order2N) / (2.0 * mysqrt), grad # this should be numerically more stable
return p
# This monotonic positive function is based on a Hyperbola modified that one of the branches appraoches zero and the other one reaches a slope of one
def invMonotonicPos(invinput, b2=1.0, Eps=0.0):
# a constant value > 0.0 0 which regulates the shape of the hyperbola. The bigger the smoother it becomes.
tfinit = tf.clip_by_value(invinput, clip_value_min=tf.constant(Eps, dtype=CalcFloatStr),
clip_value_max=tf.constant(np.Inf, dtype=CalcFloatStr)) # assertion to obtain only positive input for the initialization
# return tf.cast(tfinit - (tf.constant(b2) / tfinit), dtype=CalcFloatStr) # the inverse of monotonicPos
return (tf.square(tfinit) - b2) / tfinit # the inverse of monotonicPos
# def piecewisePos(res):
# mask = res>=0
# mask2 = ~mask
# res2 = 1.0 / (1.0-res(mask2))
# res(mask2) = res2; # this hyperbola has a value of 1, a slope of 1 and a curvature of 2 at zero X
# res(mask) = abssqr(res(mask)+0.5)+0.75 # this parabola has a value of 1, a slope of 1 and a curvature of 2 at zero X
# def invPiecewisePos(invinput):
# mask=model >= 1.0
# mask2 = ~mask
# res2=model * 0.0
# res2(mask) = sqrt(model(mask) - 0.75)-0.5
# res2(mask2) = (model(mask2)-1.0) / model(mask2)
# res = afkt(res2) # the inverse of monotonicPos
# def forcePositive(self, State):
# for varN, var in State.items():
# State[varN] = self.monotonicPos(State[varN])
# return State
# def Reset():
# tf.compat.v1.reset_default_graph() # clear everything on the GPU
# def Optimize(Fwd,Loss,tfinit,myoptimizer=None,NumIter=40,PreFwd=None):
def Optimize(myoptimizer=None, loss=None, NumIter=40, TBSummary=False, TBSummaryDir="C:\\NoBackup\\TensorboardLogs\\", resVars=None, lossScale=1.0):
"""
performs the tensorflow optimization given a loss function and an optimizer
The optimizer currently also needs to know about the loss, which is a (not-yet evaluated) tensor
Parameters
----------
myoptimizer : an optimizer. See for example "optimizer" and its arguments
loss : the loss() function with no arguments
NumIter (default: 40) : Number of iterations to be used, in case that no optimizer is provided. Otherwise this argument is NOT used but the optimizer knows about the number of iterations.
TBSummary (default: False) : If True, the summary information for tensorboard is stored
TBSummaryDir (default: "C:\\NoBackup\\TensorboardLogs\\") : The directory whre the tensorboard information is stored.
Eager (default: False) : Use eager execution
resVars (default: None) : Which tensors to evaluate and return at the end.
Returns
-------
a tuple of tensors
See also
-------
Example
-------
"""
if myoptimizer is None:
myoptimizer = lambda loss: optimizer(loss, NIter=NumIter) # if none was provided, use the default optimizer
if loss != None:
mystartloss = loss().numpy() * lossScale # eval()
start_time = time.time()
if TBSummary:
summary = myoptimizer()
else:
myoptimizer()
duration = time.time() - start_time
# if TBSummary:
# tb_writer = tf.summary.FileWriter(TBSummaryDir + 'Optimize', session.graph)
# merged = tf.summary.merge_all()
# summary = session.run(merged)
# tb_writer.add_summary(summary, 0)
try:
optName = myoptimizer.optName
except:
optName = "unkown optimizer"
if loss != None:
myloss = loss().numpy() * lossScale
print(optName + ': Exec. time:{:.4}'.format(duration), '. Start L.:{:.4}'.format(mystartloss), ', Final L.:{:.4}'.format(myloss),
'. Relative L.:{:.4}'.format(myloss / mystartloss))
else:
print(optName + ': Exec. time:{:.4}'.format(duration))
if resVars == None and loss != None:
return myloss
else:
res = []
if isinstance(resVars, list) or isinstance(resVars, tuple):
for avar in resVars:
if not isinstance(avar, tf.Tensor) and not isinstance(avar, tf.Variable):
print("WARNING: Variable " + str(avar) + " is NOT a tensor.")
res.append(avar)
else:
try:
res.append(avar.eval())
except ValueError:
print("Warning. Could not evaluate result variable" + avar.name + ". Returning [] for this result.")
res.append([])
else:
res = resVars.eval()
return res
# nip.view(toshow)
def datatype(tfin):
if istensor(tfin):
return tfin.dtype
else:
if isinstance(tfin, np.ndarray):
return tfin.dtype.name
return tfin # assuming this is already the type
def istensor(tfin):
return isinstance(tfin, tf.Tensor) or isinstance(tfin, tf.Variable)
def iscomplex(mytype):
mytype = str(datatype(mytype))
return (mytype == "complex64") or (mytype == "complex128") or (mytype == "complex64_ref") or (mytype == "complex128_ref") or (mytype == "<dtype: 'complex64'>") or (
mytype == "<dtype: 'complex128'>")
def isNumber(val):
return isinstance(val, numbers.Number)
def isList(val):
return isinstance(val, list)
def isTuple(val):
return isinstance(val, tuple)
def removeCallable(ten):
if callable(ten):
return ten()
else:
return ten
def totensor(img):
if istensor(img) or callable(img):
return img
if isList(img):
img = np.array(img, CalcFloatStr)
if not isNumber(img) and ((img.dtype == defaultTFDataType) or (img.dtype == defaultTFCpxDataType)):
img = tf.constant(img)
else:
if iscomplex(img):
img = tf.constant(img, defaultTFCpxDataType)
else:
img = tf.constant(img, defaultTFDataType)
return img
def doCheckScaling(fwd, meas):
sF = tf.reduce_mean(input_tensor=totensor(fwd)).numpy()
sM = tf.reduce_mean(input_tensor=totensor(meas)).numpy()
R = sM / sF
if abs(R) < 0.7 or abs(R) > 1.3:
print("Mean of measured data: " + str(sM) + ", Mean of forward model with initialization: " + str(sF) + " Ratio: " + str(R))
print(
"WARNING!! The forward projected sum is significantly different from the provided measured data. This may cause problems during optimization. To prevent this warning: set checkScaling=False for your loss function.")
return tf.debugging.check_numerics(fwd, "Detected NaN or Inf in loss function") # also checks for NaN values during runtime
def Loss_SimpleGaussian(fwd, meas, lossDataType=None, checkScaling=False):
if lossDataType is None:
lossDataType = defaultLossDataType
with tf.compat.v1.name_scope('Loss_SimpleGaussian'):
# return tf.reduce_sum(tf.square(fwd-meas)) # version without normalization
return tf.reduce_mean(
input_tensor=tf.cast(tf.square(fwd - meas), lossDataType)) # to make everything scale-invariant. The TF framework hopefully takes care of precomputing this
# %% this section defines a number of loss functions. Note that they often need fixed input arguments for measured data and sometimes more parameters
def Loss_FixedGaussian(fwd, meas, lossDataType=None, checkScaling=False):
if lossDataType is None:
lossDataType = defaultLossDataType
if checkScaling:
fwd = doCheckScaling(fwd, meas)
with tf.compat.v1.name_scope('Loss_FixedGaussian'):
# return tf.reduce_sum(tf.square(fwd-meas)) # version without normalization
if iscomplex(fwd.dtype.as_numpy_dtype):
mydiff = (fwd - meas)
return tf.reduce_mean(input_tensor=tf.cast(mydiff * tf.math.conj(mydiff), lossDataType)) / \
tf.reduce_mean(input_tensor=tf.cast(meas, lossDataType)) # to make everything scale-invariant. The TF framework hopefully takes care of precomputing this
else:
return tf.reduce_mean(input_tensor=tf.cast(tf.square(fwd - meas), lossDataType)) / tf.reduce_mean(
input_tensor=tf.cast(meas, lossDataType)) # to make everything scale-invariant. The TF framework hopefully takes care of precomputing this
def Loss_ScaledGaussianReadNoise(fwd, meas, RNV=1.0, lossDataType=None, checkScaling=False):
if lossDataType is None:
lossDataType = defaultLossDataType
if checkScaling:
fwd = doCheckScaling(fwd, meas)
offsetcorr = tf.cast(tf.reduce_mean(tf.math.log(tf.math.maximum(meas, tf.constant(0.0, dtype=CalcFloatStr)) + RNV)),
lossDataType) # this was added to have the ideal fit yield a loss equal to zero
# with tf.compat.v1.name_scope('Loss_ScaledGaussianReadNoise'):
XMinusMu = tf.cast(meas - fwd, lossDataType)
muPlusC = tf.cast(tf.math.maximum(fwd, 0.0) + RNV, lossDataType) # the clipping at zero was introduced to avoid division by zero
# if tf.reduce_any(RNV == tf.constant(0.0, CalcFloatStr)):
# print("RNV is: "+str(RNV))
# raise ValueError("RNV is zero!.")
# if tf.reduce_any(muPlusC == tf.constant(0.0, CalcFloatStr)):
# print("Problem: Division by zero encountered here")
# raise ValueError("Division by zero HERE!.")
Fwd = tf.math.log(muPlusC) + tf.square(XMinusMu) / muPlusC
# Grad=Grad.*(1.0-2.0*XMinusMu-XMinusMu.^2./muPlusC)./muPlusC;
Fwd = tf.reduce_mean(input_tensor=Fwd)
# if tf.math.is_nan(Fwd):
# if tf.reduce_any(muPlusC == tf.constant(0.0, CalcFloatStr)):
# print("Problem: Division by zero encountered")
# raise ValueError("Division by zero.")
# else:
# raise ValueError("Nan encountered.")
return Fwd # - offsetcorr # to make everything scale-invariant. The TF framework hopefully takes care of precomputing this
# @tf.custom_gradient
def Loss_Poisson(fwd, meas, Bg=0.05, checkPos=False, lossDataType=None, checkScaling=False):
if lossDataType is None:
lossDataType = defaultLossDataType
if checkScaling:
fwd = doCheckScaling(fwd, meas)
with tf.compat.v1.name_scope('Loss_Poisson'):
# meas[meas<0]=0
meanmeas = tf.reduce_mean(meas)
# NumEl=tf.size(meas)
if checkPos:
fwd = ((tf.sign(fwd) + 1) / 2) * fwd
FwdBg = tf.cast(fwd + Bg, lossDataType)
totalError = tf.reduce_mean(input_tensor=(FwdBg - meas) - meas * tf.math.log(
(FwdBg) / (meas + Bg))) / meanmeas # the modification in the log normalizes the error. For full normalization see PoissonErrorAndDerivNormed
# totalError = tf.reduce_mean((fwd-meas) - meas * tf.log(fwd)) / meanmeas # the modification in the log normalizes the error. For full normalization see PoissonErrorAndDerivNormed
# def grad(dy):
# return dy*(1.0 - meas/(fwd+Bg))/meanmeas
# return totalError,grad
return totalError
def Loss_Poisson2(fwd, meas, Bg=0.05, checkPos=False, lossDataType=None, checkScaling=False):
if lossDataType is None:
lossDataType = defaultLossDataType
if checkScaling:
fwd = doCheckScaling(fwd, meas)
# with tf.compat.v1.name_scope('Loss_Poisson2'):
# meas[meas<0]=0
meanmeas = tf.reduce_mean(meas)
meassize = np.prod(meas.shape)
# NumEl=tf.size(meas)
if checkPos:
fwd = ((tf.sign(fwd) + 1) / 2) * fwd # force positive
# totalError = tf.reduce_mean((fwd-meas) - meas * tf.log(fwd)) / meanmeas # the modification in the log normalizes the error. For full normalization see PoissonErrorAndDerivNormed
@tf.custom_gradient
def BarePoisson(myfwd):
def grad(dy):
mygrad = dy * (1.0 - meas / (myfwd + Bg)) / meassize # the size accounts for the mean operation (rather than sum)
# image_shaped_input = tf.reshape(mygrad, [-1, mygrad.shape[0], mygrad.shape[1], 1])
# tf.summary.image('mygrad', image_shaped_input, 10)
return mygrad
toavg = (myfwd + Bg - meas) - meas * tf.math.log((myfwd + Bg) / (meas + Bg))
toavg = tf.cast(toavg, lossDataType)
totalError = tf.reduce_mean(input_tensor=toavg) # the modification in the log normalizes the error. For full normalization see PoissonErrorAndDerivNormed
return totalError, grad
return BarePoisson(fwd) / meanmeas
# ---- End of code from the inverse Modelling Toolbox
def retrieveData():
import json_to_pandas
dl = json_to_pandas.DataLoader() # instantiate DataLoader #from_back_end=True
data_dict = dl.process_data() # loads and forms the data dictionary
rki_data = data_dict["RKI_Data"] # only RKI dataframe
print('Last Day loaded: ' + str(pd.to_datetime(np.max(rki_data.Meldedatum), unit='ms')))
return rki_data
def deltas(WhenHowMuch, SimTimes):
res = np.zeros(SimTimes)
for w, h in WhenHowMuch:
res[w] = h;
return res
def showResiduum(meas, fit):
res1 = np.mean(meas - fit, (1, 2))
print('Loss: ' + str(np.mean(abs(res1) ** 2)))
plt.plot(res1)
plt.xlabel('days')
plt.ylabel('mean difference / cases')
plt.title('residuum')
def plotAgeGroups(res1, res2):
plt.figure()
plt.title('Age Groups')
plt.plot(res1)
plt.gca().set_prop_cycle(None)
plt.plot(res2, '--')
plt.xlabel('days')
plt.ylabel('population')
class axisType:
const = 'const'
gaussian = 'gaussian'
sigmoid = 'sigmoid'
individual = 'individual'
uniform = 'uniform'
def prependOnes(s1, s2):
l1 = len(s1);
l2 = len(s2)
maxDim = max(l1, l2)
return np.array((maxDim - l1) * [1] + list(s1)), np.array((maxDim - l2) * [1] + list(s2))
def equalShape(s1, s2):
if isinstance(s1, tf.TensorShape):
s1 = s1.as_list()
if isinstance(s2, tf.TensorShape):
s2 = s2.as_list()
s1, s2 = prependOnes(s1, s2)
return np.linalg.norm(s1 - s2) == 0
class Axis:
def ramp(self):
x = self.shape
if isinstance(x, np.ndarray) or isNumber(x) or isTuple(x) or isList(x):
aramp = tf.constant(np.arange(np.max(x)), dtype=CalcFloatStr)
if isNumber(x):
x = [x]
x = tf.reshape(aramp, x) # if you get an error here, the size is not 1D!
else:
x = totensor(x)
return x
def __init__(self, name, numAxis, maxAxes, entries=1, queue=False, labels=None):
self.name = name
self.queue = queue
self.shape = np.ones(maxAxes, dtype=int)
self.shape[-numAxis] = entries
self.curAxis = numAxis
self.Labels = labels
# self.initFkt = self.initZeros()
def __str__(self):
return self.name + ", number:" + str(self.curAxis) + ", is queue:" + str(self.queue)
def __repr__(self):
return self.__str__()
# def initZeros(self):
# return tf.constant(0.0, dtype=CalcFloatStr, shape=self.shape)
#
# def initOnes(self):
# return tf.constant(1.0, dtype=CalcFloatStr, shape=self.shape)
def init(self, vals):
if isNumber(vals):
return tf.constant(vals, dtype=CalcFloatStr, shape=self.shape)
else:
if isinstance(vals, list) or isinstance(vals, np.ndarray):
if len(vals) != np.prod(self.shape):
raise ValueError('Number of initialization values ' + str(len(vals)) + ' of variable ' + self.name + ' does not match its shape ' + str(self.shape))
vals = np.reshape(np.array(vals, dtype=CalcFloatStr), self.shape)
# if callable(vals):
# vshape = vals().shape
# else:
# vshape = vals.shape
# if not equalShape(vshape, self.shape):
# raise ValueError('Initialization shape ' + str(vshape) + ' of variable ' + self.name + ' does not match its shape ' + str(self.shape))
return totensor(vals)
# def initIndividual(self, vals):
# return tf.variable(vals, dtype=CalcFloatStr)
def initGaussian(self, mu=0.0, sig=1.0):
x = self.ramp()
mu = totensor(mu)
sig = totensor(sig)
initVals = tf.exp(-(x - mu) ** 2. / (2 * (sig ** 2.)))
initVals = initVals / tf.reduce_sum(input_tensor=initVals) # normalize (numerical !, since the domain is not infinite)
return initVals
def initDelta(self, pos=0):
x = self.ramp()
initVals = tf.cast(x == pos, CalcFloatStr) # 1.0 *
return initVals
def initSigmoid(self, mu=0.0, sig=1.0, offset=0.0):
"""
models a sigmoidal function starting near 0,
reaching 0.5 at mu and extending to one at inf, the width being controlled by sigma
"""
x = self.ramp()
mu = totensor(mu);
sig = totensor(sig)
initVals = 1. / (1. + tf.exp(-(x - mu) / sig)) + offset
initVals = initVals / tf.reduce_sum(input_tensor=initVals) # normalize (numerical !, since the domain is not infinite)
return initVals
def NDim(var):
if istensor(var):
return var.shape.ndims
else:
return var.ndim
def subSlice(var, dim, sliceStart, sliceEnd): # extracts a subslice along a particular dimension
numdims = NDim(var)
idx = [slice(sliceStart, sliceEnd) if (d == dim or numdims + dim == d) else slice(0, None) for d in range(numdims)]
return var[idx]
def firstSlice(var, dim): # extracts the first subslice along a particular dimension
return subSlice(var, dim, 0, 1)
def lastSlice(var, dim): # extracts the last subslice along a particular dimension
return subSlice(var, dim, -1, None)
def reduceSumTo(State, dst):
# redsz = min(sz1, sz2)
if isinstance(dst, np.ndarray):
dstSize = np.array(dst.shape)
else:
dstSize = np.array(dst.shape.as_list(), dtype=int)
if len(dst.shape) == 0: # i.e. a scalar
dstSize = np.ones(State.ndim, dtype=int)
rs = np.array(State.shape.as_list(), dtype=int)
toReduce = np.nonzero((rs > dstSize) & (dstSize == 1))
toReduce = list(toReduce[0])
if toReduce is not None:
State = tf.reduce_sum(input_tensor=State, axis=toReduce, keepdims=True)
return State
# class State:
# def __init__(self, name='aState'):
# self.name = name
# self.Axes = {}
class Model:
def __init__(self, name='stateModel', maxAxes=4, lossWeight={}, rand_seed=1234567):
self.__version__ = 1.02
self.lossWeight = {}
for varN in lossWeight:
self.lossWeight[varN] = tf.Variable(lossWeight[varN], dtype=defaultTFDataType)
self.name = name
self.maxAxes = maxAxes
self.curAxis = 1
self.QueueStates = {} # stores the queue axis in every entry
self.Axes = {}
self.RegisteredAxes = [] # just to have a convenient way of indexing them
self.State = {} # dictionary of state variables
self.Var = {} # may be variables or lambdas
self.VarDisplayLog = {} # display this variable with a logarithmic slider
self.VarAltAxes = {} # alternative list of axes numbers to interprete the meaning of this multidimensional variable. This is needed for example for matrices connecting a dimension to itself
self.rawVar = {} # saves the raw variables
self.toRawVar = {} # stores the inverse functions to initialize the rawVar
self.toVar = {} # stores the function to get from the rawVar to the Var
self.Original = {} # here the values previous to a distortion are stored (for later comparison)
self.Distorted = {} # here the values previous to a distortion are stored (for later comparison)
self.Simulations = {}
self.Measurements = {}
self.FitResultVals = {} # resulting fit results (e.g. forward model or other curves)
self.FitResultVars = {} # resulting fit variables
self.Rates = [] # stores the rate equations
self.Loss = []
self.ResultCalculator = {} # remembers the variable names that define the results
self.ResultVals = {}
self.Progression = {} # dictionary storing the state and resultVal(s) progression (List per Key)
self.DataDict = {} # used for plotting with bokeh
self.WidgetDict = {} # used for plotting with bokeh
self.FitButton = None
self.FitLossWidget = None
self.FitLossChoices = ['Poisson', 'SimpleGaussian', 'Gaussian', 'ScaledGaussian']
self.FitLossChoiceWidget = None
self.FitOptimChoices = ['L-BFGS', 'SGD','nesterov', 'adam', 'adadelta', 'adagrad']
self.FitOptimChoiceWidget = None
self.FitOptimLambdaWidget = None
self.FitStartWidget = None
self.FitStopWidget = None
self.Regularizations = [] # list tuples of regularizers with type, weight and name of variable e.g. [('TV',0.1, 'R')]
self.plotCumul = False
self.plotMatplotlib = False
np.random.seed(rand_seed)
def timeAxis(self, entries, queue=False, labels=None):
name = 'time'
axis = Axis(name, self.maxAxes, self.maxAxes, entries, queue, labels)
if name not in self.RegisteredAxes:
self.RegisteredAxes.append(axis)
return axis
def addAxis(self, name, entries, queue=False, labels=None):
axis = Axis(name, self.curAxis, self.maxAxes, entries, queue, labels)
self.curAxis += 1
self.Axes[name] = axis
self.RegisteredAxes.append(axis)
def initGaussianT0(self, t0, t, sigma=2.0):
initVals = tf.exp(-(t - t0) ** 2. / (2 * (sigma ** 2.)))
return initVals
def initDeltaT0(self, t0, t, sig=2.0):
initVals = ((t - t0) == 0.0) * 1.0
return initVals
def initSigmoidDropT0(self, t0, t, sig, dropTo=0.0):
initVals = (1. - dropTo) / (1. + tf.exp((t - t0) / sig)) + dropTo
return initVals
def newState(self, name, axesInit=None, makeInitVar=False):
# state = State(name)
# self.States[name]=state
if name in self.ResultCalculator:
raise ValueError('Key ' + name + 'already exists in results.')
elif name in self.State:
raise ValueError('Key ' + name + 'already exists as a state.')
prodAx = None
if not isinstance(axesInit, dict):
if (not isNumber(axesInit)) and (np.prod(removeCallable(axesInit).shape) != 1):
raise ValueError("State " + name + " has a non-scalar initialization but no related axis. Please make it a dictionary with keys being axes names.")
else:
# no changes (like reshape to the original tensors are allowed since this "breaks" the chain of connections
axesInit = {'StartVal': totensor(axesInit)} # so that it can be appended to the time trace
if axesInit is not None:
res = []
hasQueue = False
for AxName, initVal in axesInit.items():
if AxName in self.Axes:
myAxis = self.Axes[AxName]
if (initVal is None):
continue
# initVal = myAxis.init(1.0/np.prod(myAxis.shape, dtype=CalcFloatStr))
if (not isinstance(initVal, Axis) and not callable(initVal)) or isNumber(initVal):
initVal = myAxis.init(initVal)
if myAxis.queue:
if hasQueue:
raise ValueError("Each State can only have one queue axis. This state " + name + " wants to have more than one.")
hasQueue = True
self.QueueStates[name] = myAxis
else:
initVal = totensor(initVal)
if res == []:
res = initVal
elif callable(res):
if callable(initVal):
res = res() * initVal()
else:
res = res() * initVal
else:
if callable(initVal):
res = res * initVal()
else:
res = res * initVal
if makeInitVar: # make the initialization value a variable
prodAx = self.newVariables({name: prodAx}) # initially infected
elif not callable(res):
prodAx = lambda: res
else:
prodAx = res
self.State[name] = prodAx
def newVariables(self, VarList=None, forcePos=True, normalize='max', b2=1.0, overwrite=True, displayLog=True, AltAxes=None):
if VarList is not None:
for name, initVal in VarList.items():
if name in self.Var:
if not overwrite:
raise ValueError("Variable " + name + " was previously defined.")
else:
self.assignNewVar(name, initVal)
print('assigned new value to variable: ' + name)
continue
if name in self.State:
raise ValueError("Variable " + name + " is already defined as a State.")
if name in self.ResultVals:
raise ValueError("Variable " + name + " is already defined as a Result.")
toVarFkt = lambda avar: totensor(avar)
toRawFkt = lambda avar: totensor(avar)
if normalize is not None:
toRawFkt2 = lambda avar: invNormalize(toRawFkt(avar), normalize, initVal);
toVarFkt2 = lambda avar: toVarFkt(doNormalize(avar, normalize, initVal))
else:
toRawFkt2 = toRawFkt
toVarFkt2 = toVarFkt
if forcePos:
toRawFkt3 = lambda avar: invMonotonicPos(toRawFkt2(avar), b2);
toVarFkt3 = lambda avar: toVarFkt2(monotonicPos(avar, b2))
else:
toRawFkt3 = toRawFkt2
toVarFkt3 = toVarFkt2
rawvar = tf.Variable(toRawFkt3(initVal), name=name, dtype=CalcFloatStr)
self.toRawVar[name] = toRawFkt3
self.rawVar[name] = rawvar # this is needed for optimization
self.toVar[name] = toVarFkt3
self.Var[name] = lambda: toVarFkt3(rawvar)
self.VarDisplayLog[name] = displayLog
self.Original[name] = rawvar.numpy() # store the original
self.VarAltAxes[name] = AltAxes
return self.Var[name] # return the last variable for convenience
def restoreOriginal(self, dummy=None):
for varN, rawval in self.Original.items():
self.rawVar[varN].assign(rawval)
self.updateAllWidgets()
def assignWidgetVar(self, newval, varname=None, relval=None, idx=None, showResults=None):
"""
is called when a value has been changed. The coordinates this change refers to are determined by the
drop-down widget list accessible via idx
"""
# print('assignWidgetVar: '+varname+", val:" + str(newval))
mywidget = self.WidgetDict[varname]
if isinstance(mywidget, tuple) or isinstance(mywidget, list):
mywidget = mywidget[0]
self.adjustMinMax(mywidget, newval.new)
if idx is None:
newval = np.reshape(newval.new, self.Var[varname]().shape)
else:
newval = newval.new
idx = self.idxFromDropList(self.Var[varname]().shape, idx)
# print('assigning '+str(newval)+' to index: '+str(idx))
res = self.assignNewVar(varname, newval, relval, idx)
if showResults is not None:
# self.simulate('measured')
showResults()
return res
def assignNewVar(self, varname, newval=None, relval=None, idx=None):