Dianor commited on
Commit
487cef1
·
verified ·
1 Parent(s): 6c096c8

Create custom_trading_layers.py

Browse files
Files changed (1) hide show
  1. custom_trading_layers.py +410 -0
custom_trading_layers.py ADDED
@@ -0,0 +1,410 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tensorflow as tf
2
+ import tensorflow.keras.backend as K
3
+ from tensorflow.keras import layers, Model
4
+ from tensorflow.keras.layers import Lambda
5
+ from tensorflow.keras.optimizers.schedules import LearningRateSchedule
6
+ from tensorflow.keras.saving import register_keras_serializable
7
+
8
+
9
+ @register_keras_serializable()
10
+ class AdaptiveContextLayer(tf.keras.layers.Layer):
11
+ def __init__(self, context_percentage=0.2, **kwargs):
12
+ super().__init__(**kwargs)
13
+ self.context_percentage = context_percentage
14
+
15
+ def call(self, inputs):
16
+ sequence_length = tf.shape(inputs)[1]
17
+ window_size = tf.cast(tf.math.ceil(tf.cast(sequence_length, tf.float32) * self.context_percentage), tf.int32)
18
+ return inputs[:, -window_size:, :]
19
+
20
+ def get_config(self):
21
+ config = super().get_config()
22
+ config.update({
23
+ "context_percentage": self.context_percentage
24
+ })
25
+ return config
26
+
27
+ @classmethod
28
+ def from_config(cls, config):
29
+ return cls(**config)
30
+
31
+
32
+ class CNNBlock(layers.Layer):
33
+ def __init__(self, filters, kernel_size, **kwargs):
34
+ super(CNNBlock, self).__init__(**kwargs)
35
+ self.filters = filters
36
+ self.kernel_size = kernel_size
37
+
38
+ # Определяем слои в init
39
+ self.conv1 = None
40
+ self.bn1 = None
41
+ self.conv2 = None
42
+ self.bn2 = None
43
+ self.relu = None
44
+ self.pool = None
45
+
46
+ def build(self, input_shape):
47
+ # Инициализируем слои в методе build
48
+ self.conv1 = layers.Conv2D(self.filters, self.kernel_size, padding='same')
49
+ self.bn1 = layers.BatchNormalization()
50
+ self.conv2 = layers.Conv2D(self.filters, self.kernel_size, padding='same')
51
+ self.bn2 = layers.BatchNormalization()
52
+ self.relu = layers.ReLU()
53
+ self.pool = layers.MaxPooling2D((2, 2))
54
+ super(CNNBlock, self).build(input_shape)
55
+
56
+ def call(self, x):
57
+ x = self.conv1(x)
58
+ x = self.bn1(x)
59
+ x = self.relu(x)
60
+ x = self.conv2(x)
61
+ x = self.bn2(x)
62
+ x = self.relu(x)
63
+ return self.pool(x)
64
+
65
+ def get_config(self):
66
+ config = super(CNNBlock, self).get_config()
67
+ config.update({
68
+ "filters": self.filters,
69
+ "kernel_size": self.kernel_size
70
+ })
71
+ return config
72
+
73
+ @classmethod
74
+ def from_config(cls, config):
75
+ return cls(**config)
76
+
77
+
78
+ @tf.keras.utils.register_keras_serializable()
79
+ class TransposeLayer(tf.keras.layers.Layer):
80
+ def __init__(self, **kwargs):
81
+ super(TransposeLayer, self).__init__(**kwargs)
82
+
83
+ def call(self, inputs):
84
+ return tf.transpose(inputs, perm=[0, 2, 1, 3])
85
+
86
+ def compute_output_shape(self, input_shape):
87
+ return (input_shape[0], input_shape[2], input_shape[1], input_shape[3])
88
+
89
+ def get_config(self):
90
+ config = super(TransposeLayer, self).get_config()
91
+ return config
92
+
93
+
94
+ @tf.keras.utils.register_keras_serializable()
95
+ class ReshapeLayer(tf.keras.layers.Layer):
96
+ def __init__(self, **kwargs):
97
+ super(ReshapeLayer, self).__init__(**kwargs)
98
+
99
+ def call(self, inputs):
100
+ return tf.reshape(inputs, (tf.shape(inputs)[0], tf.shape(inputs)[1], -1))
101
+
102
+ def compute_output_shape(self, input_shape):
103
+ if input_shape[0] is None:
104
+ batch_size = None
105
+ else:
106
+ batch_size = input_shape[0]
107
+ return (batch_size, input_shape[1], input_shape[2] * input_shape[3])
108
+
109
+ def get_config(self):
110
+ config = super(ReshapeLayer, self).get_config()
111
+ return config
112
+
113
+
114
+ class TemporalBlock(layers.Layer):
115
+ def __init__(self, in_channels, out_channels, kernel_size, dilation_rate, dropout=0.2, **kwargs):
116
+ super(TemporalBlock, self).__init__(**kwargs)
117
+ self.in_channels = in_channels
118
+ self.out_channels = out_channels
119
+ self.kernel_size = kernel_size
120
+ self.dilation_rate = dilation_rate
121
+ self.dropout = dropout
122
+
123
+ self.conv1 = layers.Conv1D(
124
+ filters=out_channels,
125
+ kernel_size=kernel_size,
126
+ dilation_rate=dilation_rate,
127
+ padding='causal',
128
+ kernel_initializer='he_normal'
129
+ )
130
+ self.batch_norm1 = layers.BatchNormalization()
131
+ self.relu1 = layers.ReLU()
132
+ self.dropout1 = layers.Dropout(dropout)
133
+
134
+ self.conv2 = layers.Conv1D(
135
+ filters=out_channels,
136
+ kernel_size=kernel_size,
137
+ dilation_rate=dilation_rate,
138
+ padding='causal',
139
+ kernel_initializer='he_normal'
140
+ )
141
+ self.batch_norm2 = layers.BatchNormalization()
142
+ self.relu2 = layers.ReLU()
143
+ self.dropout2 = layers.Dropout(dropout)
144
+
145
+ if in_channels != out_channels:
146
+ self.downsample = layers.Conv1D(
147
+ filters=out_channels,
148
+ kernel_size=1,
149
+ padding='same'
150
+ )
151
+ else:
152
+ self.downsample = None
153
+
154
+ def call(self, x):
155
+ out = self.conv1(x)
156
+ out = self.batch_norm1(out)
157
+ out = self.relu1(out)
158
+ out = self.dropout1(out)
159
+
160
+ out = self.conv2(out)
161
+ out = self.batch_norm2(out)
162
+ out = self.relu2(out)
163
+ out = self.dropout2(out)
164
+
165
+ res = self.downsample(x) if self.downsample is not None else x
166
+ return self.relu2(out + res)
167
+
168
+ def get_config(self):
169
+ config = super(TemporalBlock, self).get_config()
170
+ config.update({
171
+ "in_channels": self.in_channels,
172
+ "out_channels": self.out_channels,
173
+ "kernel_size": self.kernel_size,
174
+ "dilation_rate": self.dilation_rate,
175
+ "dropout": self.dropout
176
+ })
177
+ return config
178
+
179
+ @classmethod
180
+ def from_config(cls, config):
181
+ return cls(**config)
182
+
183
+
184
+ class TemporalConvNet(layers.Layer):
185
+ def __init__(self, num_channels, kernel_size=2, dropout=0.2, **kwargs):
186
+ super(TemporalConvNet, self).__init__(**kwargs)
187
+ self.num_channels = num_channels
188
+ self.kernel_size = kernel_size
189
+ self.dropout = dropout
190
+ self.tcn_layers = []
191
+
192
+ def build(self, input_shape):
193
+ in_channels = input_shape[-1]
194
+ for i, out_channels in enumerate(self.num_channels):
195
+ dilation_size = 2 ** i
196
+ tblock = TemporalBlock(
197
+ in_channels=in_channels,
198
+ out_channels=out_channels,
199
+ kernel_size=self.kernel_size,
200
+ dilation_rate=dilation_size,
201
+ dropout=self.dropout
202
+ )
203
+ self.tcn_layers.append(tblock)
204
+ in_channels = out_channels
205
+
206
+ def call(self, x):
207
+ for layer in self.tcn_layers:
208
+ x = layer(x)
209
+ return x
210
+
211
+ def get_config(self):
212
+ config = super(TemporalConvNet, self).get_config()
213
+ config.update({
214
+ "num_channels": self.num_channels,
215
+ "kernel_size": self.kernel_size,
216
+ "dropout": self.dropout
217
+ })
218
+ return config
219
+
220
+ @classmethod
221
+ def from_config(cls, config):
222
+ return cls(**config)
223
+
224
+
225
+ @register_keras_serializable()
226
+ class CustomOneCycleLR(LearningRateSchedule):
227
+ def __init__(self, max_lr, steps_per_epoch, epochs, pct_start=0.3,
228
+ anneal_strategy='cos', final_div_factor=25.0, **kwargs):
229
+ super().__init__(**kwargs)
230
+ self.max_lr = max_lr
231
+ self.steps_per_epoch = steps_per_epoch
232
+ self.epochs = epochs
233
+ self.pct_start = pct_start
234
+ self.anneal_strategy = anneal_strategy
235
+ self.final_div_factor = final_div_factor
236
+
237
+ def __call__(self, step):
238
+ # Реализация One Cycle LR policy
239
+ total_steps = self.steps_per_epoch * self.epochs
240
+ if step > total_steps:
241
+ return self.max_lr / self.final_div_factor
242
+
243
+ pct = step / total_steps
244
+ if pct <= self.pct_start:
245
+ # Фаза разгона
246
+ return self.max_lr * (pct / self.pct_start)
247
+ else:
248
+ # Фаза торможения
249
+ pct = (pct - self.pct_start) / (1 - self.pct_start)
250
+ return self.max_lr * (1 - pct) / self.final_div_factor
251
+
252
+ def get_config(self):
253
+ config = {
254
+ 'max_lr': self.max_lr,
255
+ 'steps_per_epoch': self.steps_per_epoch,
256
+ 'epochs': self.epochs,
257
+ 'pct_start': self.pct_start,
258
+ 'anneal_strategy': self.anneal_strategy,
259
+ 'final_div_factor': self.final_div_factor
260
+ }
261
+ return config
262
+
263
+
264
+ @register_keras_serializable()
265
+ class CrossAttention(layers.Layer):
266
+ def __init__(self, num_heads, key_dim, **kwargs):
267
+ super(CrossAttention, self).__init__(**kwargs)
268
+ self.num_heads = num_heads
269
+ self.key_dim = key_dim
270
+ self.mha = None
271
+ self.layernorm = None
272
+ self.add = None
273
+
274
+ def build(self, input_shape):
275
+ self.mha = layers.MultiHeadAttention(
276
+ num_heads=self.num_heads,
277
+ key_dim=self.key_dim
278
+ )
279
+ self.layernorm = layers.LayerNormalization(epsilon=1e-6)
280
+ self.add = layers.Add()
281
+ super(CrossAttention, self).build(input_shape)
282
+
283
+ def call(self, x, context):
284
+ attn_output = self.mha(x, context)
285
+ return self.add([x, self.layernorm(attn_output)])
286
+
287
+ def get_config(self):
288
+ config = super(CrossAttention, self).get_config()
289
+ config.update({
290
+ "num_heads": self.num_heads,
291
+ "key_dim": self.key_dim
292
+ })
293
+ return config
294
+
295
+ @classmethod
296
+ def from_config(cls, config):
297
+ return cls(**config)
298
+
299
+
300
+ @register_keras_serializable()
301
+ class F1Score(tf.keras.metrics.Metric):
302
+ def __init__(self, name='f1_score', **kwargs):
303
+ super().__init__(name=name, **kwargs)
304
+ self.precision = tf.keras.metrics.Precision()
305
+ self.recall = tf.keras.metrics.Recall()
306
+
307
+ def update_state(self, y_true, y_pred, sample_weight=None):
308
+ self.precision.update_state(y_true, y_pred, sample_weight)
309
+ self.recall.update_state(y_true, y_pred, sample_weight)
310
+
311
+ def result(self):
312
+ p = self.precision.result()
313
+ r = self.recall.result()
314
+ return 2 * ((p * r) / (p + r + tf.keras.backend.epsilon()))
315
+
316
+ def reset_state(self):
317
+ self.precision.reset_state()
318
+ self.recall.reset_state()
319
+
320
+ def get_config(self):
321
+ config = super(F1Score, self).get_config()
322
+ return config
323
+
324
+
325
+ @register_keras_serializable()
326
+ class PositionalEncoding(layers.Layer):
327
+ def __init__(self, max_position=2048, **kwargs):
328
+ super().__init__(**kwargs)
329
+ self.max_position = max_position
330
+ self.pe = None
331
+
332
+ def build(self, input_shape):
333
+ _, seq_length, d_model = input_shape
334
+
335
+ # Создаем матрицу позиционного кодирования
336
+ position = tf.range(seq_length, dtype=tf.float32)[:, tf.newaxis]
337
+ div_term = tf.exp(
338
+ tf.range(0, d_model, 2, dtype=tf.float32) * (-tf.math.log(10000.0) / d_model)
339
+ )
340
+
341
+ # Создаем синусоидальные паттерны
342
+ pe = tf.zeros((seq_length, d_model))
343
+ pe = tf.tensor_scatter_nd_update(
344
+ pe,
345
+ tf.stack([
346
+ tf.repeat(tf.range(seq_length), tf.shape(div_term)),
347
+ tf.tile(tf.range(0, d_model, 2), [seq_length])
348
+ ], axis=1),
349
+ tf.reshape(tf.sin(position * div_term), [-1])
350
+ )
351
+ pe = tf.tensor_scatter_nd_update(
352
+ pe,
353
+ tf.stack([
354
+ tf.repeat(tf.range(seq_length), tf.shape(div_term)),
355
+ tf.tile(tf.range(1, d_model, 2), [seq_length])
356
+ ], axis=1),
357
+ tf.reshape(tf.cos(position * div_term), [-1])
358
+ )
359
+
360
+ # Сохраняем как веса слоя (не тренируемые)
361
+ self.pe = tf.Variable(
362
+ initial_value=pe[tf.newaxis, :, :],
363
+ trainable=False,
364
+ name="positional_encoding",
365
+ dtype=tf.float32
366
+ )
367
+
368
+ def call(self, x):
369
+ pe_cast = tf.cast(self.pe[:, :tf.shape(x)[1], :], dtype=x.dtype)
370
+ return x + 0.1 * pe_cast
371
+
372
+ def get_config(self):
373
+ config = super().get_config()
374
+ config.update({
375
+ "max_position": self.max_position,
376
+ })
377
+ return config
378
+
379
+ @classmethod
380
+ def from_config(cls, config):
381
+ return cls(**config)
382
+
383
+ def compute_output_shape(self, input_shape):
384
+ return input_shape
385
+
386
+
387
+ def mean_axis1(x):
388
+ return K.mean(x, axis=1)
389
+
390
+
391
+ # Функция для получения словаря custom_objects
392
+ def get_custom_objects():
393
+ custom_objects = {
394
+ 'CustomOneCycleLR': CustomOneCycleLR,
395
+ 'F1Score': F1Score,
396
+ 'mean_axis1': mean_axis1,
397
+ 'CNNBlock': CNNBlock,
398
+ 'CrossAttention': CrossAttention,
399
+ 'TemporalConvNet': TemporalConvNet,
400
+ 'TemporalBlock': TemporalBlock,
401
+ 'TransposeLayer': TransposeLayer,
402
+ 'ReshapeLayer': ReshapeLayer,
403
+ 'AdaptiveContextLayer': AdaptiveContextLayer,
404
+ 'PositionalEncoding': PositionalEncoding,
405
+ 'mean_axis1_lambda': Lambda(
406
+ mean_axis1,
407
+ output_shape=lambda input_shape: (input_shape[0], input_shape[2], input_shape[3])
408
+ ),
409
+ }
410
+ return custom_objects