Mengqinxue commited on
Commit
2578657
·
verified ·
1 Parent(s): ce4bd59

Upload reproduce_eql.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. reproduce_eql.py +414 -0
reproduce_eql.py ADDED
@@ -0,0 +1,414 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ TensorFlow 2.x reproduction of the EQL (Equation Learner) symbolic regression
3
+ architecture from:
4
+ "Symbolic regression for scientific discovery: an application to wind speed forecasting"
5
+ Abdellaoui & Mehrkanoon, arXiv:2102.10570
6
+ """
7
+ import os, sys, time, copy
8
+ import numpy as np
9
+ from collections import OrderedDict
10
+ from tqdm import tqdm
11
+ from scipy.io import loadmat
12
+ from sklearn.metrics import mean_squared_error, mean_absolute_error
13
+ import tensorflow as tf
14
+ import sympy as sp
15
+ import h5py
16
+ import matplotlib
17
+ matplotlib.use('Agg')
18
+ from matplotlib import pyplot as plt
19
+
20
+ # ---------------------------------------------------------------------------
21
+ # 1. Primitive functions
22
+ # ---------------------------------------------------------------------------
23
+ class BaseFunction:
24
+ def __init__(self, norm=1):
25
+ self.norm = norm
26
+ def sp(self, x): return None
27
+ def tf(self, x):
28
+ z = sp.symbols('z')
29
+ return sp.utilities.lambdify(z, self.sp(z), 'tensorflow')(x)
30
+ def np(self, x):
31
+ z = sp.symbols('z')
32
+ return sp.utilities.lambdify(z, self.sp(z), 'numpy')(x)
33
+ def name(self, x): return str(self.sp)
34
+
35
+ class Constant(BaseFunction):
36
+ def tf(self, x): return tf.ones_like(x)
37
+ def sp(self, x): return 1
38
+ def np(self, x): return np.ones_like
39
+
40
+ class Identity(BaseFunction):
41
+ def tf(self, x): return tf.identity(x) / self.norm
42
+ def sp(self, x): return x / self.norm
43
+ def np(self, x): return np.array(x) / self.norm
44
+
45
+ class Square(BaseFunction):
46
+ def tf(self, x): return tf.square(x) / self.norm
47
+ def sp(self, x): return x ** 2 / self.norm
48
+ def np(self, x): return np.square(x) / self.norm
49
+
50
+ class Sin(BaseFunction):
51
+ def tf(self, x): return tf.sin(x * 4 * np.pi) / self.norm
52
+ def sp(self, x): return sp.sin(x * 4 * sp.pi) / self.norm
53
+ def np(self, x): return np.sin(x * 4 * np.pi) / self.norm
54
+
55
+ class Sigmoid(BaseFunction):
56
+ def sp(self, x): return 1 / (1 + sp.exp(-20 * x)) / self.norm
57
+ def np(self, x): return 1 / (1 + np.exp(-20 * x)) / self.norm
58
+ def name(self, x): return "sigmoid(x)"
59
+
60
+ class BaseFunction2:
61
+ def __init__(self, norm=1.0): self.norm = norm
62
+ def sp(self, x, y): return None
63
+ def tf(self, x, y):
64
+ a, b = sp.symbols('a b')
65
+ return sp.utilities.lambdify([a, b], self.sp(a, b), 'tensorflow')(x, y)
66
+ def np(self, x, y):
67
+ a, b = sp.symbols('a b')
68
+ return sp.utilities.lambdify([a, b], self.sp(a, b), 'numpy')(x, y)
69
+ def name(self, x, y): return str(self.sp)
70
+
71
+ class Product(BaseFunction2):
72
+ def __init__(self, norm=0.1): super().__init__(norm=norm)
73
+ def sp(self, x, y): return x * y / self.norm
74
+
75
+ def count_double(funcs):
76
+ return sum(1 for f in funcs if isinstance(f, BaseFunction2))
77
+
78
+ def count_inputs(funcs):
79
+ i = 0
80
+ for f in funcs:
81
+ if isinstance(f, BaseFunction): i += 1
82
+ elif isinstance(f, BaseFunction2): i += 2
83
+ return i
84
+
85
+ # ---------------------------------------------------------------------------
86
+ # 2. Symbolic network
87
+ # ---------------------------------------------------------------------------
88
+ class SymbolicLayer:
89
+ def __init__(self, funcs=None, initial_weight=None, variable=False, init_stddev=0.1):
90
+ if funcs is None:
91
+ funcs = default_func
92
+ self.initial_weight = initial_weight
93
+ self.W = None
94
+ self.built = False
95
+ if self.initial_weight is not None:
96
+ if not variable:
97
+ self.W = tf.Variable(self.initial_weight)
98
+ else:
99
+ self.W = self.initial_weight
100
+ self.built = True
101
+ self.output = None
102
+ self.init_stddev = init_stddev
103
+ self.n_funcs = len(funcs)
104
+ self.funcs = [f.tf for f in funcs]
105
+ self.n_double = count_double(funcs)
106
+ self.n_single = self.n_funcs - self.n_double
107
+ self.out_dim = self.n_funcs + self.n_double
108
+
109
+ def build(self, in_dim):
110
+ self.W = tf.Variable(tf.random.normal(shape=[in_dim, self.out_dim], stddev=self.init_stddev))
111
+ self.built = True
112
+
113
+ def __call__(self, x):
114
+ if not self.built:
115
+ self.build(int(x.shape[1]))
116
+ g = tf.matmul(x, self.W)
117
+ self.output = []
118
+ in_i, out_i = 0, 0
119
+ while out_i < self.n_single:
120
+ self.output.append(self.funcs[out_i](g[:, in_i]))
121
+ in_i += 1
122
+ out_i += 1
123
+ while out_i < self.n_funcs:
124
+ self.output.append(self.funcs[out_i](g[:, in_i], g[:, in_i + 1]))
125
+ in_i += 2
126
+ out_i += 1
127
+ self.output = tf.stack(self.output, axis=1)
128
+ return self.output
129
+
130
+ def get_weight(self): return self.W
131
+ def set_weight(self, weight): self.W = weight
132
+
133
+
134
+ class SymbolicNet:
135
+ def __init__(self, symbolic_depth, funcs=None, initial_weights=None,
136
+ initial_bias=None, variable=False, init_stddev=0.1):
137
+ self.depth = symbolic_depth
138
+ self.funcs = funcs
139
+ self.shape = (None, 1)
140
+ if initial_weights is not None:
141
+ self.symbolic_layers = [
142
+ SymbolicLayer(funcs=funcs, initial_weight=initial_weights[i], variable=variable)
143
+ for i in range(self.depth)
144
+ ]
145
+ if not variable:
146
+ self.output_weight = tf.Variable(initial_weights[-1])
147
+ else:
148
+ self.output_weight = initial_weights[-1]
149
+ else:
150
+ if isinstance(init_stddev, list):
151
+ self.symbolic_layers = [SymbolicLayer(funcs=funcs, init_stddev=init_stddev[i]) for i in range(self.depth)]
152
+ else:
153
+ self.symbolic_layers = [SymbolicLayer(funcs=funcs, init_stddev=init_stddev) for _ in range(self.depth)]
154
+ self.output_weight = tf.Variable(tf.random.uniform(shape=(self.symbolic_layers[-1].n_funcs, 1)))
155
+
156
+ def build(self, input_dim):
157
+ in_dim = input_dim
158
+ for i in range(self.depth):
159
+ self.symbolic_layers[i].build(in_dim)
160
+ in_dim = self.symbolic_layers[i].n_funcs
161
+
162
+ def __call__(self, input):
163
+ self.shape = (int(input.shape[1]), 1)
164
+ h = input
165
+ for i in range(self.depth):
166
+ h = self.symbolic_layers[i](h)
167
+ h = tf.matmul(h, self.output_weight)
168
+ return h
169
+
170
+ def get_weights(self):
171
+ return [self.symbolic_layers[i].W for i in range(self.depth)] + [self.output_weight]
172
+
173
+ def set_weights(self, weights):
174
+ for i in range(self.depth):
175
+ self.symbolic_layers[i].W = weights[i]
176
+ self.output_weight = weights[self.depth]
177
+
178
+
179
+ # ---------------------------------------------------------------------------
180
+ # 3. Regularization
181
+ # ---------------------------------------------------------------------------
182
+ def l12_smooth(input_tensor, a=0.05):
183
+ if isinstance(input_tensor, list):
184
+ return sum(l12_smooth(t, a) for t in input_tensor)
185
+ smooth_abs = tf.where(
186
+ tf.abs(input_tensor) < a,
187
+ tf.pow(input_tensor, 4) / (-8 * a ** 3) + tf.square(input_tensor) * 3 / 4 / a + 3 * a / 8,
188
+ tf.abs(input_tensor)
189
+ )
190
+ return tf.reduce_sum(tf.sqrt(smooth_abs))
191
+
192
+ # ---------------------------------------------------------------------------
193
+ # 4. Pretty print
194
+ # ---------------------------------------------------------------------------
195
+ def apply_activation(W, funcs, n_double=0):
196
+ W = sp.Matrix(W)
197
+ if n_double == 0:
198
+ for i in range(W.shape[0]):
199
+ for j in range(W.shape[1]):
200
+ W[i, j] = funcs[j](W[i, j])
201
+ else:
202
+ W_new = W.copy()
203
+ out_size = len(funcs)
204
+ for i in range(W.shape[0]):
205
+ in_j, out_j = 0, 0
206
+ while out_j < out_size - n_double:
207
+ W_new[i, out_j] = funcs[out_j](W[i, in_j])
208
+ in_j += 1
209
+ out_j += 1
210
+ while out_j < out_size:
211
+ W_new[i, out_j] = funcs[out_j](W[i, in_j], W[i, in_j + 1])
212
+ in_j += 2
213
+ out_j += 1
214
+ for _ in range(n_double):
215
+ W_new.col_del(-1)
216
+ W = W_new
217
+ return W
218
+
219
+
220
+ def sym_pp(W_list, funcs, var_names, threshold=1e-3, n_double=0):
221
+ vars = [sp.Symbol(v) if isinstance(v, str) else v for v in var_names]
222
+ expr = sp.Matrix(vars).T
223
+ for W in W_list:
224
+ W = filter_mat(sp.Matrix(W), threshold=threshold)
225
+ expr = expr * W
226
+ expr = apply_activation(expr, funcs, n_double=n_double)
227
+ return expr
228
+
229
+
230
+ def last_pp(eq, W):
231
+ return eq * filter_mat(sp.Matrix(W))
232
+
233
+
234
+ def network(weights, funcs, var_names, threshold=1e-3):
235
+ n_double = count_double(funcs)
236
+ funcs = [f.sp for f in funcs]
237
+ expr = sym_pp(weights[:-1], funcs, var_names, threshold=threshold, n_double=n_double)
238
+ expr = last_pp(expr, weights[-1])
239
+ expr = expr[0, 0]
240
+ return expr
241
+
242
+
243
+ def filter_mat(mat, threshold=0.01):
244
+ for i in range(mat.shape[0]):
245
+ for j in range(mat.shape[1]):
246
+ if abs(mat[i, j]) < threshold:
247
+ mat[i, j] = 0
248
+ return mat
249
+
250
+ # ---------------------------------------------------------------------------
251
+ # 5. Data helpers
252
+ # ---------------------------------------------------------------------------
253
+ def tensor_to_matrix(tensor):
254
+ number_features = tensor.shape[0]
255
+ number_cities = tensor.shape[1]
256
+ len_dates = tensor.shape[2]
257
+ matrix_for_scaling = np.ones((len_dates, number_cities * number_features))
258
+ for i in range(number_cities):
259
+ for j in range(len_dates):
260
+ features_city = tensor[:, i, j]
261
+ matrix_for_scaling[j, i * number_features:(i + 1) * number_features] = features_city
262
+ return matrix_for_scaling
263
+
264
+
265
+ def get_x_right_format(phase, steps_ahead, feature):
266
+ steps_ahead_to_step = {6: "1", 12: "2", 18: "3", 24: "4"}
267
+ filename = "{}/step{}.mat".format(feature, steps_ahead_to_step[steps_ahead])
268
+ data = loadmat('Denmark_data/{}'.format(filename))
269
+ x = data["Xtr"] if phase == "train" else data["Xtest"]
270
+ x = np.transpose(x, (0, 3, 2, 1)) # => (Dates, Features, Lags, Cities)
271
+ x = np.transpose(x, (0, 1, 3, 2)) # => (Dates, Features, Cities, Lags)
272
+ all_features_all_cities = x.shape[1] * x.shape[2]
273
+ x_output = np.zeros((x.shape[0], 80))
274
+ for i in range(x.shape[0]):
275
+ temp_matrix = tensor_to_matrix(x[i])
276
+ for j in range(temp_matrix.shape[0]):
277
+ x_output[i, j * all_features_all_cities:(j + 1) * all_features_all_cities] = temp_matrix[j]
278
+ return x_output
279
+
280
+
281
+ def generate_all_data(phase, steps_ahead, feature, city_index):
282
+ steps_ahead_to_step = {6: "1", 12: "2", 18: "3", 24: "4"}
283
+ filename = "{}/step{}.mat".format(feature, steps_ahead_to_step[steps_ahead])
284
+ data = loadmat('Denmark_data/{}'.format(filename))
285
+ if phase == "train":
286
+ y_temp = data["Ytr"][:, city_index]
287
+ elif phase == "test":
288
+ y_temp = data["Ytest"][:, city_index]
289
+ else:
290
+ return None, None
291
+ x = get_x_right_format(phase, steps_ahead, feature).astype("float32")
292
+ y = y_temp.reshape(y_temp.shape[0], 1).astype('float32')
293
+ return x, y
294
+
295
+
296
+ def generate_variable_list(alphabet_size, num_variables):
297
+ lower_case = [chr(i + 97) for i in range(26)]
298
+ variables = lower_case[:alphabet_size]
299
+ prefix_index = 0
300
+ letter_index = 0
301
+ prefix = variables[0]
302
+ for i in range(num_variables):
303
+ if (i + 1) % alphabet_size == 0:
304
+ variables.append(prefix + lower_case[letter_index])
305
+ prefix_index += 1
306
+ prefix = variables[prefix_index]
307
+ letter_index = 0
308
+ else:
309
+ variables.append(prefix + lower_case[letter_index])
310
+ letter_index += 1
311
+ return variables
312
+
313
+
314
+ # ---------------------------------------------------------------------------
315
+ # 6. Utils
316
+ # ---------------------------------------------------------------------------
317
+ def create_experiment_folder(experiment_number):
318
+ os.makedirs("ExperimentsSR/Experiment{}".format(experiment_number), exist_ok=True)
319
+
320
+
321
+ def get_experiment_number():
322
+ if not os.path.isdir("ExperimentsSR"):
323
+ return 1
324
+ folders = os.listdir("ExperimentsSR/")
325
+ nums = []
326
+ import re
327
+ for f in folders:
328
+ n = re.findall(r'\d+', f)
329
+ if n:
330
+ nums.append(int(n[0]))
331
+ return max(nums) + 1 if nums else 1
332
+
333
+
334
+ def create_summary_file(experiment_number):
335
+ open("ExperimentsSR/Experiment{}/summary_experiment{}.txt".format(experiment_number, experiment_number), "w").close()
336
+
337
+
338
+ def get_folders_started():
339
+ os.makedirs("ExperimentsSR", exist_ok=True)
340
+ experiment_number = get_experiment_number()
341
+ create_experiment_folder(experiment_number)
342
+ create_summary_file(experiment_number)
343
+ return experiment_number
344
+
345
+
346
+ def record_base_info(experiment_number, **config):
347
+ with open("ExperimentsSR/Experiment{}/summary_experiment{}.txt".format(experiment_number, experiment_number), "a+") as f:
348
+ for k, v in config.items():
349
+ f.write("{}: {}\n".format(k, v))
350
+
351
+
352
+ def append_text_to_summary(experiment_number, text):
353
+ with open("ExperimentsSR/Experiment{}/summary_experiment{}.txt".format(experiment_number, experiment_number), "a+") as f:
354
+ f.write(text)
355
+
356
+
357
+ def plot_train_vs_validation(experiment_number, num_epochs, train_loss, validation_loss, phase):
358
+ x = range(len(train_loss))
359
+ plt.plot(x, train_loss, label="Training")
360
+ plt.plot(x, validation_loss, label="Validation")
361
+ plt.ylabel("MSE")
362
+ plt.xlabel("Epochs")
363
+ plt.title(phase)
364
+ plt.legend()
365
+ plt.savefig("ExperimentsSR/Experiment{}/train_vs_validation_{}.jpg".format(experiment_number, phase))
366
+ plt.clf()
367
+
368
+
369
+ def plot_histogram(experiment_number, weights, phase, type_weights, a):
370
+ plt.hist(weights)
371
+ plt.title(phase + ", " + type_weights + ", a:{}".format(a))
372
+ plt.savefig("ExperimentsSR/Experiment{}/hist_{}_{}.jpg".format(experiment_number, phase, type_weights))
373
+ plt.clf()
374
+
375
+
376
+ def plot_descaled_real_vs_prediction(experiment_number, y_real, y_predicted, y_min, y_max):
377
+ yp = y_predicted.numpy() if hasattr(y_predicted, 'numpy') else np.array(y_predicted)
378
+ yr = y_real.numpy() if hasattr(y_real, 'numpy') else np.array(y_real)
379
+ y_predicted_rescaled = yp * (y_max - y_min) + y_min
380
+ y_test_rescaled = yr * (y_max - y_min) + y_min
381
+ mse = mean_squared_error(y_predicted_rescaled, y_test_rescaled)
382
+ mae = mean_absolute_error(y_predicted_rescaled, y_test_rescaled)
383
+ plt.figure(figsize=(10, 8))
384
+ plt.plot(y_test_rescaled[:2000], label="Real")
385
+ plt.plot(y_predicted_rescaled[:2000], label="Prediction from formula")
386
+ plt.legend()
387
+ plt.title("MAE: {:.2e}, MSE: {:.2e}".format(mae, mse))
388
+ plt.savefig("ExperimentsSR/Experiment{}/real_vs_prediction_phase1.jpg".format(experiment_number))
389
+ plt.clf()
390
+ return mae
391
+
392
+
393
+ # ---------------------------------------------------------------------------
394
+ # 7. Weights I/O
395
+ # ---------------------------------------------------------------------------
396
+ def save_weights(weights, experiment_number, phase, best_val_loss):
397
+ if phase == "phase1":
398
+ filename = "ExperimentsSR/Experiment{}/nt_val_weights_{:.2e}.hdf5".format(experiment_number, best_val_loss)
399
+ elif phase == "phase2":
400
+ filename = "ExperimentsSR/Experiment{}/phase2_val_weights_{:.2e}.hdf5".format(experiment_number, best_val_loss)
401
+ with h5py.File(filename, "w") as f:
402
+ for i in range(len(weights)):
403
+ w = weights[i]
404
+ if hasattr(w, 'numpy'):
405
+ w = w.numpy()
406
+ f.create_dataset('dataset{}'.format(i), data=w)
407
+
408
+
409
+ def load_weights(filename):
410
+ weights = []
411
+ with h5py.File(filename, "r") as f:
412
+ for i in range(3):
413
+ weights.append(f.get('dataset{}'.format(i))[()])
414
+ return weights