playground-data 1.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
plygdata/datahelper.py ADDED
@@ -0,0 +1,236 @@
1
+ # ==============================================================================
2
+ # Copyright 2018-2019 Digital Advantage Co., Ltd. All Rights Reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ==============================================================================
16
+
17
+ from __future__ import division
18
+
19
+ import matplotlib.pyplot as plt
20
+ from mpl_toolkits.axes_grid1 import make_axes_locatable
21
+ import numpy as np
22
+ from plygdata.dataset import generate_data
23
+ from plygdata.state import InputType
24
+ from plygdata.heatmap import HeatMap
25
+ from plygdata.playground import Player
26
+
27
+
28
+ POINT_DOMAIN = [-6.0, 6.0]
29
+ TICKS_POINT = [-6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6]
30
+ TICKS_MIDDLE = 6
31
+ TICKS_VALUE = [-1, 0, 1]
32
+
33
+
34
+ def split_data(data, validation_size=0.5, label_num=1, training_size=-1.0):
35
+ '''
36
+ Split data int training and validation data. And Each data will be split into input data and teacher labels.
37
+ :param data:
38
+ :param validation_size:
39
+ :param label_num:
40
+ :param training_size:
41
+ :return: (X_train, y_train, X_valid, y_valid)
42
+ '''
43
+
44
+ len_data = len(data)
45
+ if len_data == 0:
46
+ raise ValueError("No array error.")
47
+
48
+ mat = np.array(data)
49
+
50
+ # It is easy to use scikit-learn.
51
+ # X = mat[:,:-(label_num)] # data
52
+ # y = mat[:,-(label_num)] # label
53
+ # from sklearn.model_selection import train_test_split
54
+ # X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=validation_size)
55
+
56
+ # But, because this class doesn't use scikit-learn.
57
+ np.random.shuffle(mat)
58
+
59
+ X = mat[:, :-(label_num)] # data
60
+ y = mat[:, -label_num:] # labels
61
+
62
+ if training_size >= 0.0 and training_size <= 1.0:
63
+ validation_size = 1.0 - training_size
64
+
65
+ split_point = round(len(mat) * (1.0 - validation_size))
66
+ X_train = X[0:split_point, :] # train data
67
+ y_train = y[0:split_point] # train label
68
+ X_valid = X[split_point:, :] # validation data
69
+ y_valid = y[split_point:] # validation label
70
+
71
+ if label_num == 1:
72
+ y_train = np.reshape(y_train, [len(y_train), 1])
73
+ y_valid = np.reshape(y_valid, [len(y_valid), 1])
74
+
75
+ # To Pandas example:
76
+ # import numpy as np
77
+ # import pandas as pd
78
+ # df_X_train = pd.DataFrame(X_train)
79
+ # df_X_train.columns = ['x1', 'x2']
80
+ # print(df_X_train)
81
+ # df_y_train = pd.DataFrame(y_train)
82
+ # df_y_train.columns = ['label']
83
+ # print(df_y_train)
84
+
85
+ return X_train, y_train, X_valid, y_valid
86
+
87
+
88
+ def get_playground_figure(enable_colorbar=False):
89
+ if enable_colorbar:
90
+ fig = plt.figure(figsize=(6, 6), dpi=100)
91
+ else:
92
+ fig = plt.figure(figsize=(5, 5), dpi=100)
93
+ return fig
94
+
95
+
96
+ def get_playground_axes(fig):
97
+ ax = fig.add_subplot(111) # Row: 1, Column: 1, Place: 1
98
+ ax.set_facecolor("#e8eaeb")
99
+ ax.spines['top'].set_visible(False)
100
+ ax.spines['right'].set_visible(False)
101
+ ax.spines['bottom'].set_visible(False)
102
+ ax.spines['left'].set_visible(False)
103
+ ax.yaxis.tick_right()
104
+ ax.tick_params(axis='x', colors='#bbbbbb')
105
+ ax.tick_params(axis='y', colors='#bbbbbb')
106
+ ax.set_xlim(POINT_DOMAIN)
107
+ ax.set_ylim(POINT_DOMAIN)
108
+ ax.set_xticks(TICKS_POINT)
109
+ ax.set_yticks(TICKS_POINT)
110
+ ax.get_xticklabels()[TICKS_MIDDLE].set_color("#333333")
111
+ ax.get_yticklabels()[TICKS_MIDDLE].set_color("#333333")
112
+ #ax.grid(color='white', linestyle='-')
113
+ ax.grid(visible=False)
114
+ return ax
115
+
116
+
117
+ def plot_points(ax, X_train, y_train, X_valid=None, y_valid=None):
118
+
119
+ HeatMap.updateTrainPoints(ax, X_train, y_train)
120
+
121
+ if all([X_valid is not None, y_valid is not None]):
122
+ HeatMap.updateValidationPoints(ax, X_valid, y_valid)
123
+
124
+
125
+ def plot_points_with_playground_style(X_train, y_train, X_valid=None, y_valid=None, figsize=(5, 5), dpi=100):
126
+ """
127
+ if width 5 inches x height 5 inches x dpi 100, = 500 x 500 dots figure will be created.
128
+
129
+ :param X_train: train data
130
+ :param y_train: grain label
131
+ :param X_valid: validation data
132
+ :param y_valid: validation label
133
+ :param figsize: width inches (default: 5) x height (default: 5) inches
134
+ :param dpi: dpi (default: 100)
135
+ :return: a Figure object of matplotlib, and the axes (Coordinate axis)
136
+ """
137
+ fig = plt.figure(figsize=figsize, dpi=dpi)
138
+
139
+ # get the axes (Coordinate axis) designed for NeuralNetwork Playground of Deep Insider.
140
+ ax = get_playground_axes(fig)
141
+
142
+ plot_points(ax, X_train, y_train, X_valid, y_valid)
143
+
144
+ return fig, ax
145
+
146
+
147
+ def _add_colorbar_on_bottom(fig, ax, im):
148
+ divider = make_axes_locatable(ax)
149
+ ax_cb = divider.append_axes("bottom", size="5%", pad=0.3)
150
+ ax_cb.tick_params(axis='x', colors='#777777')
151
+ ax_cb.tick_params(axis='y', colors='#777777')
152
+ cb = fig.colorbar(im, cax=ax_cb, ticks=TICKS_VALUE, orientation='horizontal')
153
+ cb.outline.set_edgecolor('white')
154
+ cb.outline.set_linewidth(0)
155
+ cb.solids.set_edgecolor("face")
156
+ ax_cb.xaxis.set_ticks_position("bottom")
157
+
158
+
159
+ def plot_sample(data_type, noise=0.0, validation_size=0.5, visualize_validation_data=False, figsize=(5, 5), dpi=100, node_id=None, discretize=False, training_size=-1.0):
160
+
161
+ data_array = generate_data(data_type, noise)
162
+ if data_array is None:
163
+ return None, None
164
+
165
+ mat = np.array(data_array)
166
+ X_train, y_train, X_valid, y_valid = split_data(mat, validation_size=validation_size, training_size=training_size)
167
+
168
+ fig, ax = plot_points_with_playground_style(X_train, y_train, X_valid if (visualize_validation_data) else None, y_valid if (visualize_validation_data) else None, figsize=figsize, dpi=dpi)
169
+
170
+ if node_id is not None:
171
+ draw_decision_boundary(fig, ax, node_id=node_id, discretize=discretize)
172
+
173
+ return fig, ax
174
+
175
+
176
+ def predict_classes(trained_model, x, batch_size=32, verbose=0):
177
+ """Generate class predictions for the input samples.
178
+ The input samples are processed batch by batch.
179
+ # Arguments
180
+ x: input data, as a Numpy array or list of Numpy arrays
181
+ (if the trained_model has multiple inputs).
182
+ batch_size: integer.
183
+ verbose: verbosity mode, 0 or 1.
184
+ # Returns:
185
+ A numpy array of class predictions.
186
+ """
187
+ preds = trained_model.predict(x, batch_size=batch_size, verbose=verbose)
188
+ discretized = np.where(preds >= 0.0, 1, -1)
189
+ return discretized
190
+
191
+
192
+ def predict_proba(trained_model, x, batch_size=32, verbose=0):
193
+ """Generates class probability predictions for the input samples.
194
+ The input samples are processed batch by batch.
195
+ # Arguments
196
+ x: input data, as a Numpy array or list of Numpy arrays
197
+ (if the trained_model has multiple inputs).
198
+ batch_size: integer.
199
+ verbose: verbosity mode, 0 or 1.
200
+ # Returns
201
+ A Numpy array of probability predictions.
202
+ """
203
+ probability = trained_model.predict(x, batch_size=batch_size, verbose=verbose)
204
+ return probability
205
+
206
+
207
+ def predict_classes_proba(trained_model, x, batch_size=32, verbose=0):
208
+ """Generate class and probability predictions for the input samples.
209
+ The input samples are processed batch by batch.
210
+ # Arguments
211
+ x: input data, as a Numpy array or list of Numpy arrays
212
+ (if the trained_model has multiple inputs).
213
+ batch_size: integer.
214
+ verbose: verbosity mode, 0 or 1.
215
+ # Returns:
216
+ A numpy array of class and probability predictions.
217
+ """
218
+ probability = trained_model.predict(x, batch_size=batch_size, verbose=verbose)
219
+ discretized = np.where(probability >= 0.0, 1, -1)
220
+ return discretized, probability
221
+
222
+
223
+ def draw_decision_boundary(fig, ax, node_id=InputType.X1, trained_model=None, discretize=False, enable_colorbar=True):
224
+
225
+ if trained_model is None:
226
+ im = HeatMap.updateBackground(ax, None, node_id, discretize)
227
+ else:
228
+ boundary_array = Player.get_boundary_array()
229
+ probability = predict_proba(trained_model, boundary_array)
230
+ im = HeatMap.updateBackground(ax, None, None, discretize, probability)
231
+
232
+ if enable_colorbar:
233
+ _add_colorbar_on_bottom(fig, ax, im)
234
+
235
+ return im
236
+
plygdata/dataset.py ADDED
@@ -0,0 +1,268 @@
1
+ # ==============================================================================
2
+ # Copyright 2018-2019 Digital Advantage Co., Ltd. All Rights Reserved.
3
+ #
4
+ # This is a Python implementation of [tensorflow / playground (Deep playground) / dataset.ts](https://github.com/tensorflow/playground/blob/master/src/dataset.ts).
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ==============================================================================
18
+
19
+ from __future__ import division
20
+
21
+ import random
22
+ import math
23
+ from plygdata.scalelinear import ScaleLinear
24
+ from plygdata.state import DatasetType
25
+
26
+
27
+ NUM_SAMPLES_CLASSIFY = 500
28
+ NUM_SAMPLES_REGRESS = 1200
29
+
30
+
31
+ #class Example2D:
32
+ # """
33
+ # A two dimensional example: x and y coordinates with the label.
34
+ # """
35
+ # def __init__(self, x=0, y=0, label=0) :
36
+ # self.x = x
37
+ # self.y = y
38
+ # self.label = label
39
+
40
+ class Point:
41
+ def __init__(self, x = 0.0, y = 0.0):
42
+ self.x = x
43
+ self.y = y
44
+
45
+
46
+ def rand_uniform(a, b):
47
+ """
48
+ Returns a sample from a uniform [a, b] distribution.
49
+ """
50
+ return random.random() * (b - a) + a
51
+
52
+
53
+ def normal_random(mean = 0.0, variance = 1.0):
54
+ """
55
+ Samples from a normal distribution.
56
+
57
+ :param mean
58
+ The mean. Default is 0.0.
59
+ :param variance
60
+ The variance. Default is 1.0.
61
+ """
62
+ while True:
63
+ v1 = 2.0 * random.random() - 1.0
64
+ v2 = 2.0 * random.random() - 1.0
65
+ s = v1 * v1 + v2 * v2
66
+ if 0.0 < s <= 1.0:
67
+ break
68
+ result = math.sqrt(-2.0 * math.log(s) / s) * v1
69
+ return mean + math.sqrt(variance) * result
70
+
71
+
72
+ def dist(a, b):
73
+ """
74
+ Returns the euclidean distance between two points in space.
75
+ """
76
+ dx = a.x - b.x
77
+ dy = a.y - b.y
78
+ return math.sqrt(dx * dx + dy * dy)
79
+
80
+
81
+ def shuffle(array):
82
+ """
83
+ Shuffles the array using Fisher-Yates algorithm. Uses the seedrandom
84
+ library as the random generator.
85
+ """
86
+ counter = len(array)
87
+ temp = 0
88
+ index = 0
89
+ # While there are elements in the array
90
+ while counter > 0:
91
+ # Pick a random index
92
+ index = int(math.floor(random.random() * counter))
93
+ # Decrease counter by 1
94
+ counter -= 1
95
+ # And swap the last element with it
96
+ temp = array[counter]
97
+ array[counter] = array[index]
98
+ array[index] = temp
99
+
100
+
101
+ def generate_data(data_type, noise = 0.0):
102
+
103
+ if data_type == DatasetType.ClassifyTwoGaussData:
104
+ data_array = DataGenerator.classify_two_gauss(NUM_SAMPLES_CLASSIFY, noise=noise)
105
+ elif data_type == DatasetType.ClassifyXORData:
106
+ data_array = DataGenerator.classify_xor(NUM_SAMPLES_CLASSIFY, noise=noise)
107
+ elif data_type == DatasetType.ClassifyCircleData:
108
+ data_array = DataGenerator.classify_circle(NUM_SAMPLES_CLASSIFY, noise=noise)
109
+ elif data_type == DatasetType.ClassifySpiralData:
110
+ data_array = DataGenerator.classify_spiral(NUM_SAMPLES_CLASSIFY, noise=noise)
111
+ elif data_type == DatasetType.RegressPlane:
112
+ data_array = DataGenerator.regress_plane(NUM_SAMPLES_REGRESS, noise=noise)
113
+ elif data_type == DatasetType.RegressGaussian:
114
+ data_array = DataGenerator.regress_gaussian(NUM_SAMPLES_REGRESS, noise=noise)
115
+ else:
116
+ return None
117
+
118
+ return data_array
119
+
120
+
121
+ class DataGenerator:
122
+
123
+ @staticmethod
124
+ def classify_two_gauss(numSamples, noise = 0.0):
125
+ points = []
126
+
127
+ varianceScale = ScaleLinear(domain=[0.0, 0.5], slrange=[0.5, 4.0])
128
+ variance = varianceScale(noise)
129
+
130
+ def genGauss(cx, cy, label):
131
+ for _ in range(numSamples // 2):
132
+ x = normal_random(cx, variance)
133
+ y = normal_random(cy, variance)
134
+ points.append([x, y, label])
135
+
136
+ genGauss(2.0, 2.0, 1) # Gaussian with positive examples.
137
+ genGauss(-2.0, -2.0, -1) # Gaussian with negative examples.
138
+ return points
139
+
140
+
141
+ @staticmethod
142
+ def classify_xor(numSamples, noise = 0.0):
143
+
144
+ def getXORLabel(p):
145
+ return 1 if (p.x * p.y >= 0) else -1
146
+
147
+ points = []
148
+ for _ in range(numSamples):
149
+ x = rand_uniform(-5.0, 5.0)
150
+ padding = 0.3
151
+ x = x + padding if (x > 0) else x - padding # Padding.
152
+ y = rand_uniform(-5.0, 5.0)
153
+ y = y + padding if (y > 0) else y - padding
154
+ noiseX = rand_uniform(-5.0, 5.0) * noise
155
+ noiseY = rand_uniform(-5.0, 5.0) * noise
156
+ label = getXORLabel(Point(x + noiseX, y + noiseY))
157
+ points.append([x, y, label])
158
+ return points
159
+
160
+
161
+ @staticmethod
162
+ def classify_circle(numSamples, noise = 0.0):
163
+ points = []
164
+ radius = 5.0
165
+
166
+ def getCircleLabel(p, center):
167
+ return 1 if (dist(p, center) < (radius * 0.5)) else -1
168
+
169
+ # Generate positive points inside the circle.
170
+ for _ in range(numSamples // 2):
171
+ r = rand_uniform(0.0, radius * 0.5)
172
+ angle = rand_uniform(0.0, 2.0 * math.pi)
173
+ x = r * math.sin(angle)
174
+ y = r * math.cos(angle)
175
+ noiseX = rand_uniform(-radius, radius) * noise
176
+ noiseY = rand_uniform(-radius, radius) * noise
177
+ label = getCircleLabel(Point(x + noiseX, y + noiseY), Point(0.0, 0.0))
178
+ points.append([x, y, label])
179
+
180
+ # Generate negative points outside the circle.
181
+ for _ in range(numSamples // 2):
182
+ r = rand_uniform(radius * 0.7, radius)
183
+ angle = rand_uniform(0.0, 2.0 * math.pi)
184
+ x = r * math.sin(angle)
185
+ y = r * math.cos(angle)
186
+ noiseX = rand_uniform(-radius, radius) * noise
187
+ noiseY = rand_uniform(-radius, radius) * noise
188
+ label = getCircleLabel(Point(x + noiseX, y + noiseY), Point(0.0, 0.0))
189
+ points.append([x, y, label])
190
+
191
+ return points
192
+
193
+
194
+ @staticmethod
195
+ def classify_spiral(numSamples, noise = 0.0):
196
+ points = []
197
+ n = numSamples // 2
198
+
199
+ def genSpiral(deltaT, label):
200
+ for i in range(n):
201
+ r = i / n * 5
202
+ t = 1.75 * i / n * 2 * math.pi + deltaT
203
+ x = r * math.sin(t) + rand_uniform(-1.0, 1.0) * noise
204
+ y = r * math.cos(t) + rand_uniform(-1.0, 1.0) * noise
205
+ points.append([x, y, label])
206
+
207
+ genSpiral(0.0, 1) # Positive examples.
208
+ genSpiral(math.pi, -1) # Negative examples.
209
+
210
+ return points
211
+
212
+
213
+ @staticmethod
214
+ def regress_plane(numSamples, noise = 0.0):
215
+ radius = 6
216
+ labelScale = ScaleLinear(domain=[-10, 10], slrange=[-1, 1])
217
+ getLabel = lambda x, y: labelScale(x + y)
218
+
219
+ points = []
220
+ for _ in range(numSamples):
221
+ x = rand_uniform(-radius, radius)
222
+ y = rand_uniform(-radius, radius)
223
+ noiseX = rand_uniform(-radius, radius) * noise
224
+ noiseY = rand_uniform(-radius, radius) * noise
225
+ label = getLabel(x + noiseX, y + noiseY)
226
+ points.append([x, y, label])
227
+
228
+ return points
229
+
230
+
231
+ @staticmethod
232
+ def regress_gaussian(numSamples, noise = 0.0):
233
+ points = []
234
+
235
+ labelScale = ScaleLinear(domain=[0.0, 2.0], slrange=[1.0, 0.0], clamp=True)
236
+
237
+ gaussians = [
238
+ [-4.0, 2.5, 1.0],
239
+ [ 0.0, 2.5, -1.0],
240
+ [ 4.0, 2.5, 1.0],
241
+ [-4.0, -2.5, -1.0],
242
+ [ 0.0, -2.5, 1.0],
243
+ [ 4.0, -2.5, -1.0]
244
+ ]
245
+
246
+ def getLabel(x, y):
247
+ # Choose the one that is maximum in abs value.
248
+ curlabel = 0
249
+ for c in gaussians:
250
+ cx = c[0]
251
+ cy = c[1]
252
+ sign = c[2]
253
+ newLabel = sign * labelScale(dist(Point(x, y), Point(cx, cy)))
254
+ if abs(newLabel) > abs(curlabel):
255
+ curlabel = newLabel
256
+ return curlabel
257
+
258
+ radius = 6.0
259
+ for _ in range(numSamples):
260
+ x = rand_uniform(-radius, radius)
261
+ y = rand_uniform(-radius, radius)
262
+ noiseX = rand_uniform(-radius, radius) * noise
263
+ noiseY = rand_uniform(-radius, radius) * noise
264
+ label = getLabel(x + noiseX, y + noiseY)
265
+ points.append([x, y, label])
266
+
267
+ return points
268
+
plygdata/heatmap.py ADDED
@@ -0,0 +1,89 @@
1
+ # ==============================================================================
2
+ # Copyright 2018-2019 Digital Advantage Co., Ltd. All Rights Reserved.
3
+ #
4
+ # This is a Python implementation of [tensorflow / playground (Deep playground) / heatmap.ts](https://github.com/tensorflow/playground/blob/master/src/heatmap.ts).
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ==============================================================================
18
+
19
+ from __future__ import division
20
+
21
+ from plygdata.datacolor import DataColor
22
+ from plygdata.playground import Player
23
+
24
+
25
+ RECT_DOMAIN = [-6.0, 6.0, -6.0, 6.0]
26
+
27
+
28
+ class HeatMap:
29
+ '''
30
+ Draws a heatmap using matplotlib.
31
+ Used for showing the learned decision boundary of the classification algorithm.
32
+ Can also draw data points on top of the heatmap figure using matplotlib.
33
+ '''
34
+
35
+ @staticmethod
36
+ def draw_decision_boundary_of_node(ax, boundary_of_node):
37
+ cmap, norm = DataColor.get_colormap()
38
+ alpha = (160 / 255)
39
+ im = ax.imshow(boundary_of_node, extent=RECT_DOMAIN, cmap=cmap, norm=norm, alpha=alpha, interpolation='bicubic')
40
+ return im
41
+
42
+
43
+ @staticmethod
44
+ def updateBackground(ax, boundaries, node_id, discretize, probability=None):
45
+ if probability is None:
46
+ boundaries = Player.update_decision_boundary(boundaries, discretize)
47
+ boundary_of_node = boundaries[node_id]
48
+ else:
49
+ boundary_of_node = Player.get_decision_boundary_of_node(probability, discretize)
50
+ im = HeatMap.draw_decision_boundary_of_node(ax, boundary_of_node)
51
+ return im
52
+
53
+
54
+ @staticmethod
55
+ def updateCircles(ax, X_points, y_label, edgecolors):
56
+ for i in range(len(X_points)):
57
+ ax.scatter(X_points[i, 0], X_points[i, 1], s=20, c=DataColor.get_rgb(y_label[i].item()), alpha=0.9, linewidths=0.7, edgecolors=edgecolors)
58
+
59
+
60
+ @staticmethod
61
+ def updateTrainPoints(ax, X_train, y_train):
62
+ HeatMap.updateCircles(ax, X_train, y_train, "#ffffff")
63
+
64
+
65
+ @staticmethod
66
+ def updateValidationPoints(ax, X_valid, y_valid):
67
+ HeatMap.updateCircles(ax, X_valid, y_valid, "#555555")
68
+
69
+
70
+ @staticmethod
71
+ def updateTestPoints(ax, X_valid, y_valid):
72
+ HeatMap.updateCircles(ax, X_valid, y_valid, "#FF5555")
73
+
74
+
75
+ @staticmethod
76
+ def reduceMatrix(matrix, factor):
77
+
78
+ len_matrix = len(matrix)
79
+ if not isinstance(factor, int) or isinstance(factor, bool) or factor <= 0:
80
+ raise ValueError("The reduction factor must be a positive integer")
81
+ if not len_matrix or any(len(row) != len_matrix for row in matrix):
82
+ raise ValueError("The provided matrix must be a nonempty square matrix")
83
+ if len_matrix % factor:
84
+ raise ValueError("The width/height of the matrix must be divisible by the reduction factor")
85
+ return [
86
+ [sum(matrix[i + k][j + l] for k in range(factor) for l in range(factor))
87
+ / (factor * factor) for j in range(0, len_matrix, factor)]
88
+ for i in range(0, len_matrix, factor)
89
+ ]