dlspit 1.0.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.
@@ -0,0 +1,56 @@
1
+ """
2
+ dl_experiments
3
+ ==============
4
+ A pip-installable collection of Deep Learning experiment modules.
5
+
6
+ Quick start
7
+ -----------
8
+ import dl_experiments
9
+ dl_experiments.main() # run all experiments
10
+
11
+ dl_experiments.main("1a") # run a single experiment
12
+ dl_experiments.main("4", "5") # run multiple experiments
13
+
14
+ dl_experiments.list_experiments() # see all available keys
15
+ """
16
+
17
+ from .utils.registry import list_experiments, run_experiment
18
+
19
+ __version__ = "1.0.0"
20
+ __author__ = "Pratham"
21
+ __all__ = ["main", "list_experiments", "run_experiment", "__version__"]
22
+
23
+
24
+ def main(*experiments: str) -> None:
25
+ """
26
+ Orchestrate and run DL experiments.
27
+
28
+ Parameters
29
+ ----------
30
+ *experiments : str
31
+ Experiment keys to run (e.g. "1a", "2b", "10").
32
+ If none are provided, ALL experiments are run in order.
33
+
34
+ Examples
35
+ --------
36
+ >>> import dl_experiments
37
+ >>> dl_experiments.main() # run all
38
+ >>> dl_experiments.main("1a") # run only Exp 1a
39
+ >>> dl_experiments.main("4","5") # run Exp 4 and 5
40
+ """
41
+ keys = list(experiments) if experiments else list_experiments()
42
+
43
+ print("=" * 60)
44
+ print(" Deep Learning Experiments Suite")
45
+ print(f" Running: {', '.join(keys)}")
46
+ print("=" * 60)
47
+
48
+ for key in keys:
49
+ try:
50
+ run_experiment(key)
51
+ except Exception as exc: # noqa: BLE001
52
+ print(f"\n [ERROR] Experiment {key!r} failed: {exc}")
53
+
54
+ print("\n" + "=" * 60)
55
+ print(" All done!")
56
+ print("=" * 60)
@@ -0,0 +1,40 @@
1
+ """
2
+ Allows: python -m dl_experiments [exp_keys ...]
3
+ """
4
+ import argparse
5
+ import sys
6
+
7
+ from . import list_experiments, main
8
+
9
+
10
+ def cli() -> None:
11
+ parser = argparse.ArgumentParser(
12
+ prog="dl_experiments",
13
+ description="Run Deep Learning experiment modules.",
14
+ )
15
+ parser.add_argument(
16
+ "experiments",
17
+ nargs="*",
18
+ metavar="EXP",
19
+ help=(
20
+ "Experiment keys to run (e.g. 1a 2b 10). "
21
+ f"Available: {', '.join(list_experiments())}. "
22
+ "Omit to run ALL."
23
+ ),
24
+ )
25
+ parser.add_argument(
26
+ "--list",
27
+ action="store_true",
28
+ help="List available experiment keys and exit.",
29
+ )
30
+ args = parser.parse_args()
31
+
32
+ if args.list:
33
+ print("Available experiments:", ", ".join(list_experiments()))
34
+ sys.exit(0)
35
+
36
+ main(*args.experiments)
37
+
38
+
39
+ if __name__ == "__main__":
40
+ cli()
@@ -0,0 +1,32 @@
1
+ """DL experiment modules."""
2
+ from . import (
3
+ exp1a_mcculloch_pitts,
4
+ exp1b_mlp_binary,
5
+ exp2a_activations,
6
+ exp2b_backprop_mnist,
7
+ exp3_regularisation,
8
+ exp4_cnn,
9
+ exp5_resnet,
10
+ exp6_rnn,
11
+ exp7_lstm,
12
+ exp8_transformer,
13
+ exp9_gan,
14
+ exp9b_vae,
15
+ exp10_transfer,
16
+ )
17
+
18
+ __all__ = [
19
+ "exp1a_mcculloch_pitts",
20
+ "exp1b_mlp_binary",
21
+ "exp2a_activations",
22
+ "exp2b_backprop_mnist",
23
+ "exp3_regularisation",
24
+ "exp4_cnn",
25
+ "exp5_resnet",
26
+ "exp6_rnn",
27
+ "exp7_lstm",
28
+ "exp8_transformer",
29
+ "exp9_gan",
30
+ "exp9b_vae",
31
+ "exp10_transfer",
32
+ ]
@@ -0,0 +1,220 @@
1
+ import numpy as np
2
+
3
+ # =============================================================================
4
+ # EXPERIMENT 10: Transfer Learning with MobileNetV2 on CIFAR-10
5
+ # =============================================================================
6
+ #
7
+ # THEORY:
8
+ # Transfer Learning reuses a neural network pre-trained on a large dataset
9
+ # as a starting point for a new, related task.
10
+ # This dramatically reduces training time and data requirements.
11
+ #
12
+ # ---- Why Transfer Learning? ----
13
+ # Training deep CNNs from scratch requires:
14
+ # - Millions of labeled images
15
+ # - Days/weeks of GPU compute
16
+ # Transfer Learning solves this: a model pre-trained on ImageNet already knows
17
+ # how to detect edges, textures, shapes, and high-level objects.
18
+ # We just need to adapt the FINAL layers to our specific classes.
19
+ #
20
+ # ---- MobileNetV2 ----
21
+ # Lightweight CNN architecture designed for efficiency on mobile/edge devices.
22
+ # Pre-trained on ImageNet: 1.2 million images, 1000 classes.
23
+ # Key innovation: Depthwise Separable Convolutions
24
+ # - Standard Conv: applies filter across all channels simultaneously
25
+ # - Depthwise Conv: applies ONE filter per channel (much fewer parameters)
26
+ # - Pointwise Conv: 1x1 convolution to combine channels
27
+ # - Result: ~8-9x fewer parameters with similar accuracy vs standard Conv
28
+ #
29
+ # ---- Transfer Learning Strategy (2 Phases) ----
30
+ #
31
+ # Phase 1 - Feature Extraction (Frozen Base):
32
+ # base_model.trainable = False
33
+ # Only the new Dense(10, softmax) head is trained.
34
+ # The pre-trained MobileNetV2 features are used as fixed feature extractors.
35
+ # Fast and safe: ImageNet knowledge is preserved.
36
+ #
37
+ # Phase 2 - Fine-Tuning (Unfrozen):
38
+ # base_model.trainable = True
39
+ # ALL layers are trainable, but with a very small learning rate (1e-5).
40
+ # The entire network adapts to the new domain (CIFAR-10).
41
+ # Small lr prevents catastrophic forgetting of pre-trained features.
42
+ #
43
+ # ---- preprocess_input (MobileNetV2 Preprocessing) ----
44
+ # MobileNetV2 was trained with specific pixel normalization:
45
+ # preprocess_input scales pixels from [0,255] to [-1, 1]
46
+ # MUST use this for correct behavior with pre-trained MobileNetV2 weights.
47
+ #
48
+ # ---- Dataset: CIFAR-10 (built-in, no external download needed) ----
49
+ # 60,000 color images (32x32 RGB) in 10 classes:
50
+ # airplane, automobile, bird, cat, deer, dog, frog, horse, ship, truck
51
+ # 2,000 training samples used here for speed.
52
+ #
53
+ # ---- GlobalAveragePooling2D ----
54
+ # Converts (7, 7, 1280) feature maps from MobileNetV2 base -> (1280,) vector.
55
+ # More efficient than Flatten: fewer parameters, less overfitting.
56
+ #
57
+ # ---- Evaluation Metrics ----
58
+ # Classification Report: Precision, Recall, F1-score per class
59
+ # Precision = TP / (TP + FP): of all predicted positive, how many are correct?
60
+ # Recall = TP / (TP + FN): of all actual positive, how many did we find?
61
+ # F1-score = 2 * (P * R) / (P + R): harmonic mean of precision and recall
62
+ #
63
+ # =============================================================================
64
+ # IF YOUR OWN IMAGE DATASET (changing from CIFAR-10):
65
+ #
66
+ # Option A: Images in folders (recommended):
67
+ # Replace the CIFAR-10 loading block with:
68
+ #
69
+ # IMG_SIZE = (96, 96) # MobileNetV2 min is 32x32, recommended 96x96+
70
+ # train_dataset = tf.keras.preprocessing.image_dataset_from_directory(
71
+ # "path/to/your/train_images/", # Each subfolder = one class
72
+ # image_size=IMG_SIZE,
73
+ # batch_size=BATCH_SIZE
74
+ # )
75
+ # test_dataset = tf.keras.preprocessing.image_dataset_from_directory(
76
+ # "path/to/your/test_images/",
77
+ # image_size=IMG_SIZE,
78
+ # batch_size=BATCH_SIZE
79
+ # )
80
+ # class_names = train_dataset.class_names # Auto-detected from folder names
81
+ #
82
+ # 🔴 CHANGE 1: Number of output classes
83
+ # Dense(num_classes, activation='softmax')
84
+ # loss='sparse_categorical_crossentropy'
85
+ #
86
+ # 🔴 CHANGE 2: EPOCHS - increase for small datasets (10-20+)
87
+ # =============================================================================
88
+
89
+
90
+ def run():
91
+ print("\n=== Exp 10: Transfer Learning with MobileNetV2 on CIFAR-10 ===")
92
+
93
+ try:
94
+ import tensorflow as tf
95
+ from tensorflow.keras.applications import MobileNetV2
96
+ from tensorflow.keras.applications.mobilenet_v2 import preprocess_input
97
+ from tensorflow.keras import layers, models
98
+ except ImportError:
99
+ print(" [SKIP] tensorflow not installed. Install with: pip install tensorflow")
100
+ return
101
+
102
+ try:
103
+ from sklearn.metrics import classification_report, confusion_matrix
104
+ except ImportError:
105
+ print(" [SKIP] scikit-learn not installed. Install with: pip install scikit-learn")
106
+ return
107
+
108
+ # ==============================
109
+ # 1. PARAMETERS
110
+ # ==============================
111
+ # MobileNetV2 requires at least 32x32; using 96x96 is faster than 224x224
112
+ IMG_SIZE = (96, 96)
113
+ BATCH_SIZE = 32
114
+ EPOCHS = 3
115
+ N_TRAIN = 2000 # Use subset for speed; increase for better accuracy
116
+
117
+ CLASS_NAMES = [
118
+ 'airplane', 'automobile', 'bird', 'cat', 'deer',
119
+ 'dog', 'frog', 'horse', 'ship', 'truck'
120
+ ]
121
+
122
+ # ==============================
123
+ # 2. LOAD & PREPROCESS CIFAR-10
124
+ # ==============================
125
+ (x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()
126
+
127
+ # Use a small subset for speed
128
+ x_train, y_train = x_train[:N_TRAIN], y_train[:N_TRAIN]
129
+ x_test, y_test = x_test[:500], y_test[:500]
130
+
131
+ # Resize images from 32x32 to IMG_SIZE for MobileNetV2
132
+ x_train = tf.image.resize(x_train.astype("float32"), IMG_SIZE)
133
+ x_test = tf.image.resize(x_test.astype("float32"), IMG_SIZE)
134
+
135
+ # MobileNetV2-specific preprocessing: scales pixels to [-1, 1]
136
+ x_train = preprocess_input(x_train)
137
+ x_test = preprocess_input(x_test)
138
+
139
+ # Flatten labels from (N,1) -> (N,)
140
+ y_train = y_train.flatten()
141
+ y_test = y_test.flatten()
142
+
143
+ # ==============================
144
+ # 3. MODEL (TRANSFER LEARNING)
145
+ # ==============================
146
+ # Load MobileNetV2 pre-trained on ImageNet
147
+ # include_top=False: exclude the final 1000-class Dense layer
148
+ base_model = MobileNetV2(
149
+ input_shape=(IMG_SIZE[0], IMG_SIZE[1], 3),
150
+ include_top=False,
151
+ weights='imagenet'
152
+ )
153
+
154
+ # Phase 1: FREEZE the base model
155
+ base_model.trainable = False
156
+
157
+ model = models.Sequential([
158
+ base_model, # Pre-trained MobileNetV2 feature extractor
159
+ layers.GlobalAveragePooling2D(), # Spatial average: (7,7,1280) -> (1280,)
160
+ layers.Dense(10, activation='softmax') # 10-class CIFAR-10 output
161
+ ])
162
+
163
+ # ==============================
164
+ # 4. COMPILE (Phase 1)
165
+ # ==============================
166
+ model.compile(
167
+ optimizer='adam',
168
+ loss='sparse_categorical_crossentropy',
169
+ metrics=['accuracy']
170
+ )
171
+
172
+ # ==============================
173
+ # 5. TRAIN PHASE 1 (Feature Extraction)
174
+ # ==============================
175
+ print("\n--- Phase 1: Feature Extraction (frozen base) ---")
176
+ model.fit(
177
+ x_train, y_train,
178
+ validation_data=(x_test, y_test),
179
+ epochs=EPOCHS,
180
+ batch_size=BATCH_SIZE,
181
+ verbose=1
182
+ )
183
+
184
+ # ==============================
185
+ # 6. FINE-TUNING (Phase 2)
186
+ # ==============================
187
+ base_model.trainable = True # Unfreeze all layers
188
+
189
+ model.compile(
190
+ optimizer=tf.keras.optimizers.Adam(1e-5), # Very small lr to preserve features
191
+ loss='sparse_categorical_crossentropy',
192
+ metrics=['accuracy']
193
+ )
194
+
195
+ print("\n--- Phase 2: Fine-Tuning (unfrozen, lr=1e-5) ---")
196
+ model.fit(
197
+ x_train, y_train,
198
+ validation_data=(x_test, y_test),
199
+ epochs=2,
200
+ batch_size=BATCH_SIZE,
201
+ verbose=1
202
+ )
203
+
204
+ # ==============================
205
+ # 7. EVALUATION
206
+ # ==============================
207
+ y_pred_probs = model.predict(x_test, verbose=0)
208
+ y_pred = np.argmax(y_pred_probs, axis=1)
209
+
210
+ print("\nClassification Report:")
211
+ print(classification_report(y_test, y_pred, target_names=CLASS_NAMES))
212
+
213
+ cm = confusion_matrix(y_test, y_pred)
214
+ print("Confusion Matrix (rows=Actual, cols=Predicted):")
215
+ # Print header
216
+ header = " " + " ".join(f"{n[:4]:>4}" for n in CLASS_NAMES)
217
+ print(header)
218
+ for i, row in enumerate(cm):
219
+ row_str = " ".join(f"{v:>4}" for v in row)
220
+ print(f"{CLASS_NAMES[i][:7]:<7} {row_str}")
@@ -0,0 +1,94 @@
1
+ # =============================================================================
2
+ # EXPERIMENT 1a: McCulloch-Pitts (MP) Neuron Model
3
+ # =============================================================================
4
+ #
5
+ # THEORY:
6
+ # The McCulloch-Pitts (MP) Neuron (1943) is the earliest mathematical model
7
+ # of a biological neuron. It is a simple binary threshold unit.
8
+ #
9
+ # How it works:
10
+ # - Takes binary inputs (0 or 1)
11
+ # - Each input is multiplied by a weight (can be +1 for excitatory or -1 for inhibitory)
12
+ # - Computes a weighted sum: net = w1*x1 + w2*x2 + ...
13
+ # - Applies a threshold function: output = 1 if net >= theta, else 0
14
+ #
15
+ # Limitations:
16
+ # - Weights and threshold (theta) are FIXED (not learned automatically)
17
+ # - Only works with binary inputs and binary outputs
18
+ # - Cannot represent continuous / real-valued data
19
+ #
20
+ # This experiment implements XOR and XNOR using multi-layer MP neurons,
21
+ # demonstrating that non-linear functions need multiple neurons (layers).
22
+ #
23
+ # XOR Truth Table: XNOR Truth Table:
24
+ # 0,0 -> 0 0,0 -> 1
25
+ # 0,1 -> 1 0,1 -> 0
26
+ # 1,0 -> 1 1,0 -> 0
27
+ # 1,1 -> 0 1,1 -> 1
28
+ #
29
+ # =============================================================================
30
+ # IF CSV DATA IS GIVEN:
31
+ # This code does NOT use any dataset (it uses hardcoded logic).
32
+ # But if you want to test MP neuron on binary inputs from a CSV file:
33
+ #
34
+ # import pandas as pd
35
+ # df = pd.read_csv('your_data.csv')
36
+ # # CSV must have columns for inputs and expected output, e.g.:
37
+ # # x1, x2, label
38
+ # # 0, 0, 0
39
+ # # 0, 1, 1
40
+ # inputs = list(zip(df['x1'], df['x2']))
41
+ # # Then call mp_neuron() or xor() / xnor() for each row
42
+ # # NOTE: MP neuron only works with binary (0/1) inputs.
43
+ # # If your CSV has continuous values, you must binarize them first:
44
+ # # e.g., x1 = 1 if x1 > threshold else 0
45
+ # =============================================================================
46
+
47
+
48
+ def run():
49
+ print("\n=== Exp 1a: McCulloch-Pitts Neuron (XOR & XNOR) ===")
50
+
51
+ # Core MP Neuron function
52
+ # x1, x2 : binary inputs (0 or 1)
53
+ # w1, w2 : weights (positive = excitatory, negative = inhibitory)
54
+ # theta : threshold - if weighted sum >= theta, neuron fires (output=1)
55
+ def mp_neuron(x1, x2, w1, w2, theta):
56
+ # Compute weighted sum of inputs
57
+ # If the sum meets or exceeds the threshold, neuron fires (outputs 1)
58
+ return 1 if (w1 * x1 + w2 * x2) >= theta else 0
59
+
60
+ # XOR Gate using 2 MP neurons in hidden layer + 1 output neuron
61
+ # XOR is a non-linearly separable function -> needs multiple MP neurons
62
+ def xor(x1, x2):
63
+ # h1 fires when x1=1, x2=0 (x1 ON, x2 inhibitory)
64
+ h1 = mp_neuron(x1, x2, 1, -1, 1)
65
+
66
+ # h2 fires when x1=0, x2=1 (x2 ON, x1 inhibitory)
67
+ h2 = mp_neuron(x1, x2, -1, 1, 1)
68
+
69
+ # Output fires when either h1 OR h2 fires (but not both -> XOR)
70
+ y = mp_neuron(h1, h2, 1, 1, 1)
71
+ return y
72
+
73
+ # XNOR Gate using 2 MP neurons in hidden layer + 1 output neuron
74
+ # XNOR is the complement of XOR: outputs 1 when both inputs are same
75
+ def xnor(x1, x2):
76
+ # h1 fires when both x1=1 AND x2=1 (requires both: threshold=2)
77
+ h1 = mp_neuron(x1, x2, 1, 1, 2)
78
+
79
+ # h2 fires when both x1=0 AND x2=0 (negative weights, threshold=0)
80
+ # -1*0 + -1*0 = 0 >= 0 -> fires; -1*1 + -1*0 = -1 < 0 -> doesn't fire
81
+ h2 = mp_neuron(x1, x2, -1, -1, 0)
82
+
83
+ # Output fires when either h1 OR h2 fires
84
+ y = mp_neuron(h1, h2, 1, 1, 1)
85
+ return y
86
+
87
+ # All possible binary input combinations
88
+ inputs = [(0, 0), (0, 1), (1, 0), (1, 1)]
89
+
90
+ # Test both gates on all input combinations and print results
91
+ print(f"{'x1':>4} {'x2':>4} {'XOR':>6} {'XNOR':>6}")
92
+ print("-" * 24)
93
+ for x1, x2 in inputs:
94
+ print(f"{x1:>4} {x2:>4} {xor(x1, x2):>6} {xnor(x1, x2):>6}")
@@ -0,0 +1,113 @@
1
+ # =============================================================================
2
+ # EXPERIMENT 1b: Multi-Layer Perceptron (MLP) for Binary Classification
3
+ # =============================================================================
4
+ #
5
+ # THEORY:
6
+ # A Multi-Layer Perceptron (MLP) is a fully-connected feedforward neural network.
7
+ # It extends the MP Neuron by LEARNING the weights automatically via backpropagation.
8
+ #
9
+ # Architecture used here:
10
+ # Input Layer : 2 neurons (for 2 input features x1, x2)
11
+ # Hidden Layer : 8 neurons with ReLU activation
12
+ # Output Layer : 1 neuron with Sigmoid activation (binary output)
13
+ #
14
+ # Key Concepts:
15
+ # - ReLU (Rectified Linear Unit): f(x) = max(0, x)
16
+ # Helps avoid vanishing gradient; most common hidden-layer activation.
17
+ # - Sigmoid: f(x) = 1 / (1 + e^(-x))
18
+ # Squashes output to (0, 1) -> ideal for binary classification probability.
19
+ # - Binary Cross-Entropy Loss: measures error between predicted probability
20
+ # and true binary label (0 or 1).
21
+ # - Adam Optimizer: adaptive learning rate optimizer; combines momentum and
22
+ # RMSProp. Works well in practice without much tuning.
23
+ #
24
+ # Dataset here is XNOR truth table (manual, hardcoded):
25
+ # Inputs : X = [[0,0],[1,0],[0,1],[1,1]]
26
+ # Labels : y = [1, 0, 0, 1] <- XNOR pattern
27
+ #
28
+ # =============================================================================
29
+ # IF CSV DATA IS GIVEN (Binary Classification):
30
+ #
31
+ # import pandas as pd
32
+ # df = pd.read_csv('your_data.csv')
33
+ # # Your CSV should look like:
34
+ # # feature1, feature2, label
35
+ # # 0.5, 1.2, 1
36
+ # # 3.1, 0.8, 0
37
+ #
38
+ # X = df[['feature1', 'feature2']].values # shape: (n_samples, 2)
39
+ # y = df['label'].values # shape: (n_samples,)
40
+ #
41
+ # # If you have MORE than 2 features, change input_shape accordingly:
42
+ # # input_shape = (number_of_features,)
43
+ # # e.g., if 5 features: input_shape = (5,)
44
+ #
45
+ # # If you have more than 2 CLASSES, change:
46
+ # # - Output layer: Dense(num_classes, activation='softmax')
47
+ # # - Loss: 'sparse_categorical_crossentropy'
48
+ # # - Labels y: integer class indices (0, 1, 2, ..., num_classes-1)
49
+ #
50
+ # # Then replace the X and y definitions below with the above.
51
+ # =============================================================================
52
+
53
+ import numpy as np
54
+
55
+
56
+ def run():
57
+ print("\n=== Exp 1b: MLP for Binary Classification (XNOR) ===")
58
+
59
+ try:
60
+ import tensorflow as tf
61
+ except ImportError:
62
+ print(" [SKIP] tensorflow not installed. Install with: pip install tensorflow")
63
+ return
64
+
65
+ # ------------------------------------------------------------------
66
+ # Dataset: XNOR truth table (binary inputs -> binary output)
67
+ # X: input pairs, y: expected XNOR output
68
+ # ------------------------------------------------------------------
69
+ X = np.array([[0, 0], [1, 0], [0, 1], [1, 1]]) # shape: (4, 2)
70
+ y = np.array([1, 0, 0, 1]) # shape: (4,) -> XNOR labels
71
+
72
+ # ------------------------------------------------------------------
73
+ # Model Definition: Sequential MLP
74
+ # ------------------------------------------------------------------
75
+ model = tf.keras.Sequential([
76
+ # Hidden layer: 8 neurons, ReLU activation
77
+ # input_shape=(2,) means 2 input features
78
+ # CHANGE input_shape if your CSV has different number of features
79
+ tf.keras.layers.Dense(8, activation="relu", input_shape=(2,)),
80
+
81
+ # Output layer: 1 neuron, Sigmoid -> outputs probability in (0,1)
82
+ # For binary classification: threshold at 0.5 to get 0 or 1
83
+ tf.keras.layers.Dense(1, activation="sigmoid")
84
+ ])
85
+
86
+ # ------------------------------------------------------------------
87
+ # Compile: define optimizer, loss, and evaluation metric
88
+ # ------------------------------------------------------------------
89
+ model.compile(
90
+ optimizer='adam', # Adam: adaptive gradient optimizer
91
+ loss='binary_crossentropy', # Standard loss for binary classification
92
+ metrics=['accuracy'] # Track accuracy during training
93
+ )
94
+
95
+ # ------------------------------------------------------------------
96
+ # Train the model on dataset X, y for 200 epochs (silent)
97
+ # ------------------------------------------------------------------
98
+ model.fit(X, y, epochs=200, verbose=0)
99
+
100
+ # ------------------------------------------------------------------
101
+ # Predict: get raw sigmoid probabilities and convert to binary labels
102
+ # ------------------------------------------------------------------
103
+ pred = model.predict(X, verbose=0)
104
+ pred_binary = (pred > 0.5).astype(int).flatten()
105
+
106
+ print(f"{'x1':>4} {'x2':>4} {'Label':>7} {'Pred':>6} {'Prob':>8}")
107
+ print("-" * 35)
108
+ for i in range(len(X)):
109
+ x1, x2 = X[i]
110
+ print(f"{int(x1):>4} {int(x2):>4} {y[i]:>7} {pred_binary[i]:>6} {pred[i][0]:>8.4f}")
111
+
112
+ acc = float((pred_binary == y).mean())
113
+ print(f"\nFinal accuracy: {acc*100:.1f}%")