dl-experiment-code 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.
- dl_experiment_code/__init__.py +1 -0
- dl_experiment_code/cli.py +94 -0
- dl_experiment_code/templates/exp10_local.py +120 -0
- dl_experiment_code/templates/exp10_online.py +95 -0
- dl_experiment_code/templates/exp5_local.py +87 -0
- dl_experiment_code/templates/exp5_online.py +60 -0
- dl_experiment_code/templates/exp6_local.py +97 -0
- dl_experiment_code/templates/exp6_online.py +68 -0
- dl_experiment_code/templates/exp7_local.py +66 -0
- dl_experiment_code/templates/exp7_online.py +66 -0
- dl_experiment_code/templates/exp8_local.py +135 -0
- dl_experiment_code/templates/exp8_online.py +118 -0
- dl_experiment_code/templates/exp9_local.py +84 -0
- dl_experiment_code/templates/exp9_online.py +49 -0
- dl_experiment_code/templates/rnn_scratch.py +130 -0
- dl_experiment_code-1.0.0.dist-info/METADATA +146 -0
- dl_experiment_code-1.0.0.dist-info/RECORD +20 -0
- dl_experiment_code-1.0.0.dist-info/WHEEL +5 -0
- dl_experiment_code-1.0.0.dist-info/entry_points.txt +2 -0
- dl_experiment_code-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Experiment 7 - Autoencoder dimensionality reduction using Keras MNIST.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import tensorflow as tf
|
|
7
|
+
from tensorflow import keras
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
|
|
10
|
+
(x_train, _), (x_test, _) = keras.datasets.mnist.load_data()
|
|
11
|
+
|
|
12
|
+
x_train = x_train.astype("float32") / 255.0
|
|
13
|
+
x_test = x_test.astype("float32") / 255.0
|
|
14
|
+
|
|
15
|
+
x_train = x_train.reshape(len(x_train), -1)
|
|
16
|
+
x_test = x_test.reshape(len(x_test), -1)
|
|
17
|
+
|
|
18
|
+
input_dim = x_train.shape[1]
|
|
19
|
+
encoding_dim = 32
|
|
20
|
+
|
|
21
|
+
encoder = keras.Sequential([
|
|
22
|
+
keras.layers.Input(shape=(input_dim,)),
|
|
23
|
+
keras.layers.Dense(128, activation="relu"),
|
|
24
|
+
keras.layers.Dense(encoding_dim, activation="relu"),
|
|
25
|
+
], name="encoder")
|
|
26
|
+
|
|
27
|
+
decoder = keras.Sequential([
|
|
28
|
+
keras.layers.Input(shape=(encoding_dim,)),
|
|
29
|
+
keras.layers.Dense(128, activation="relu"),
|
|
30
|
+
keras.layers.Dense(input_dim, activation="sigmoid"),
|
|
31
|
+
], name="decoder")
|
|
32
|
+
|
|
33
|
+
autoencoder = keras.Sequential([encoder, decoder])
|
|
34
|
+
|
|
35
|
+
autoencoder.compile(
|
|
36
|
+
optimizer="adam",
|
|
37
|
+
loss="binary_crossentropy",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
autoencoder.fit(
|
|
41
|
+
x_train,
|
|
42
|
+
x_train,
|
|
43
|
+
epochs=10,
|
|
44
|
+
batch_size=256,
|
|
45
|
+
validation_data=(x_test, x_test),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
encoded = encoder.predict(x_test, verbose=0)
|
|
49
|
+
decoded = autoencoder.predict(x_test, verbose=0)
|
|
50
|
+
|
|
51
|
+
print("Original dimension:", input_dim)
|
|
52
|
+
print("Reduced dimension :", encoded.shape[1])
|
|
53
|
+
|
|
54
|
+
plt.figure(figsize=(10, 4))
|
|
55
|
+
|
|
56
|
+
for i in range(5):
|
|
57
|
+
plt.subplot(2, 5, i + 1)
|
|
58
|
+
plt.imshow(x_test[i].reshape(28, 28), cmap="gray")
|
|
59
|
+
plt.axis("off")
|
|
60
|
+
|
|
61
|
+
plt.subplot(2, 5, i + 6)
|
|
62
|
+
plt.imshow(decoded[i].reshape(28, 28), cmap="gray")
|
|
63
|
+
plt.axis("off")
|
|
64
|
+
|
|
65
|
+
plt.suptitle("Original (top) vs Reconstructed (bottom)")
|
|
66
|
+
plt.show()
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Experiment 8 - Educational single-object detector using a LOCAL Pascal VOC dataset.
|
|
3
|
+
|
|
4
|
+
Expected structure:
|
|
5
|
+
data/object_dataset/
|
|
6
|
+
images/
|
|
7
|
+
annotations/
|
|
8
|
+
|
|
9
|
+
Each XML annotation should contain at least one object.
|
|
10
|
+
This experiment predicts one object class and one bounding box.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import xml.etree.ElementTree as ET
|
|
15
|
+
import numpy as np
|
|
16
|
+
import tensorflow as tf
|
|
17
|
+
from tensorflow import keras
|
|
18
|
+
from sklearn.model_selection import train_test_split
|
|
19
|
+
from PIL import Image
|
|
20
|
+
|
|
21
|
+
IMAGE_DIR = "data/object_dataset/images"
|
|
22
|
+
ANNOTATION_DIR = "data/object_dataset/annotations"
|
|
23
|
+
IMAGE_SIZE = (128, 128)
|
|
24
|
+
|
|
25
|
+
def parse_annotation(xml_path):
|
|
26
|
+
root = ET.parse(xml_path).getroot()
|
|
27
|
+
size = root.find("size")
|
|
28
|
+
width = float(size.find("width").text)
|
|
29
|
+
height = float(size.find("height").text)
|
|
30
|
+
|
|
31
|
+
obj = root.find("object")
|
|
32
|
+
label = obj.find("name").text
|
|
33
|
+
box = obj.find("bndbox")
|
|
34
|
+
|
|
35
|
+
xmin = float(box.find("xmin").text) / width
|
|
36
|
+
ymin = float(box.find("ymin").text) / height
|
|
37
|
+
xmax = float(box.find("xmax").text) / width
|
|
38
|
+
ymax = float(box.find("ymax").text) / height
|
|
39
|
+
|
|
40
|
+
return label, [xmin, ymin, xmax, ymax]
|
|
41
|
+
|
|
42
|
+
image_files, labels, boxes = [], [], []
|
|
43
|
+
|
|
44
|
+
for filename in os.listdir(ANNOTATION_DIR):
|
|
45
|
+
if not filename.endswith(".xml"):
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
xml_path = os.path.join(ANNOTATION_DIR, filename)
|
|
49
|
+
image_name = os.path.splitext(filename)[0]
|
|
50
|
+
|
|
51
|
+
for extension in [".jpg", ".jpeg", ".png"]:
|
|
52
|
+
candidate = os.path.join(IMAGE_DIR, image_name + extension)
|
|
53
|
+
if os.path.exists(candidate):
|
|
54
|
+
label, box = parse_annotation(xml_path)
|
|
55
|
+
image_files.append(candidate)
|
|
56
|
+
labels.append(label)
|
|
57
|
+
boxes.append(box)
|
|
58
|
+
break
|
|
59
|
+
|
|
60
|
+
classes = sorted(set(labels))
|
|
61
|
+
class_to_id = {name: i for i, name in enumerate(classes)}
|
|
62
|
+
|
|
63
|
+
X, y_class, y_box = [], [], []
|
|
64
|
+
|
|
65
|
+
for image_path, label, box in zip(image_files, labels, boxes):
|
|
66
|
+
image = Image.open(image_path).convert("RGB").resize(IMAGE_SIZE)
|
|
67
|
+
X.append(np.asarray(image, dtype="float32") / 255.0)
|
|
68
|
+
y_class.append(class_to_id[label])
|
|
69
|
+
y_box.append(box)
|
|
70
|
+
|
|
71
|
+
X = np.array(X)
|
|
72
|
+
y_class = np.array(y_class)
|
|
73
|
+
y_box = np.array(y_box, dtype="float32")
|
|
74
|
+
|
|
75
|
+
X_train, X_test, yc_train, yc_test, yb_train, yb_test = train_test_split(
|
|
76
|
+
X, y_class, y_box,
|
|
77
|
+
test_size=0.2,
|
|
78
|
+
random_state=42,
|
|
79
|
+
stratify=y_class,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
inputs = keras.Input(shape=(128, 128, 3))
|
|
83
|
+
|
|
84
|
+
x = keras.layers.Conv2D(32, 3, activation="relu")(inputs)
|
|
85
|
+
x = keras.layers.MaxPooling2D()(x)
|
|
86
|
+
x = keras.layers.Conv2D(64, 3, activation="relu")(x)
|
|
87
|
+
x = keras.layers.MaxPooling2D()(x)
|
|
88
|
+
x = keras.layers.Conv2D(128, 3, activation="relu")(x)
|
|
89
|
+
x = keras.layers.MaxPooling2D()(x)
|
|
90
|
+
x = keras.layers.Flatten()(x)
|
|
91
|
+
x = keras.layers.Dense(128, activation="relu")(x)
|
|
92
|
+
|
|
93
|
+
class_output = keras.layers.Dense(
|
|
94
|
+
len(classes),
|
|
95
|
+
activation="softmax",
|
|
96
|
+
name="class_output",
|
|
97
|
+
)(x)
|
|
98
|
+
|
|
99
|
+
box_output = keras.layers.Dense(
|
|
100
|
+
4,
|
|
101
|
+
activation="sigmoid",
|
|
102
|
+
name="box_output",
|
|
103
|
+
)(x)
|
|
104
|
+
|
|
105
|
+
model = keras.Model(inputs=inputs, outputs=[class_output, box_output])
|
|
106
|
+
|
|
107
|
+
model.compile(
|
|
108
|
+
optimizer="adam",
|
|
109
|
+
loss={
|
|
110
|
+
"class_output": "sparse_categorical_crossentropy",
|
|
111
|
+
"box_output": "mse",
|
|
112
|
+
},
|
|
113
|
+
metrics={
|
|
114
|
+
"class_output": "accuracy",
|
|
115
|
+
"box_output": "mae",
|
|
116
|
+
},
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
model.summary()
|
|
120
|
+
|
|
121
|
+
model.fit(
|
|
122
|
+
X_train,
|
|
123
|
+
{"class_output": yc_train, "box_output": yb_train},
|
|
124
|
+
validation_split=0.2,
|
|
125
|
+
epochs=10,
|
|
126
|
+
batch_size=16,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
results = model.evaluate(
|
|
130
|
+
X_test,
|
|
131
|
+
{"class_output": yc_test, "box_output": yb_test},
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
print("\nClasses:", classes)
|
|
135
|
+
print("Evaluation:", results)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Experiment 8 - Educational single-object detector using Pascal VOC via TFDS.
|
|
3
|
+
|
|
4
|
+
Dataset is downloaded automatically from the TensorFlow Datasets catalogue.
|
|
5
|
+
|
|
6
|
+
Note:
|
|
7
|
+
This is intentionally a simple one-object educational detector, matching
|
|
8
|
+
the original experiment concept. It uses the first annotated object per image.
|
|
9
|
+
It is NOT a production multi-object detector like YOLO/SSD/Faster R-CNN.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import tensorflow as tf
|
|
14
|
+
from tensorflow import keras
|
|
15
|
+
import tensorflow_datasets as tfds
|
|
16
|
+
|
|
17
|
+
IMAGE_SIZE = (128, 128)
|
|
18
|
+
MAX_SAMPLES = 3000
|
|
19
|
+
|
|
20
|
+
(ds, info) = tfds.load(
|
|
21
|
+
"voc",
|
|
22
|
+
split="train",
|
|
23
|
+
with_info=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
class_names = info.features["objects"]["label"].names
|
|
27
|
+
X, y_class, y_box = [], [], []
|
|
28
|
+
|
|
29
|
+
for example in tfds.as_numpy(ds):
|
|
30
|
+
objects = example["objects"]
|
|
31
|
+
if len(objects["label"]) == 0:
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
image = tf.image.resize(example["image"], IMAGE_SIZE).numpy()
|
|
35
|
+
image = image.astype("float32") / 255.0
|
|
36
|
+
|
|
37
|
+
# Use the first object for this educational single-object experiment.
|
|
38
|
+
label = int(objects["label"][0])
|
|
39
|
+
ymin = float(objects["bbox"]["ymin"][0])
|
|
40
|
+
xmin = float(objects["bbox"]["xmin"][0])
|
|
41
|
+
ymax = float(objects["bbox"]["ymax"][0])
|
|
42
|
+
xmax = float(objects["bbox"]["xmax"][0])
|
|
43
|
+
|
|
44
|
+
X.append(image)
|
|
45
|
+
y_class.append(label)
|
|
46
|
+
y_box.append([xmin, ymin, xmax, ymax])
|
|
47
|
+
|
|
48
|
+
if len(X) >= MAX_SAMPLES:
|
|
49
|
+
break
|
|
50
|
+
|
|
51
|
+
X = np.array(X, dtype="float32")
|
|
52
|
+
y_class = np.array(y_class, dtype="int32")
|
|
53
|
+
y_box = np.array(y_box, dtype="float32")
|
|
54
|
+
|
|
55
|
+
# Keep only classes represented in the selected sample.
|
|
56
|
+
used_classes = sorted(np.unique(y_class))
|
|
57
|
+
class_remap = {old: new for new, old in enumerate(used_classes)}
|
|
58
|
+
y_class = np.array([class_remap[x] for x in y_class])
|
|
59
|
+
|
|
60
|
+
X_train, X_test, yc_train, yc_test, yb_train, yb_test = (
|
|
61
|
+
__import__("sklearn.model_selection", fromlist=["train_test_split"])
|
|
62
|
+
.train_test_split(
|
|
63
|
+
X, y_class, y_box,
|
|
64
|
+
test_size=0.2,
|
|
65
|
+
random_state=42,
|
|
66
|
+
stratify=y_class,
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
inputs = keras.Input(shape=(128, 128, 3))
|
|
71
|
+
x = keras.layers.Conv2D(32, 3, activation="relu")(inputs)
|
|
72
|
+
x = keras.layers.MaxPooling2D()(x)
|
|
73
|
+
x = keras.layers.Conv2D(64, 3, activation="relu")(x)
|
|
74
|
+
x = keras.layers.MaxPooling2D()(x)
|
|
75
|
+
x = keras.layers.Conv2D(128, 3, activation="relu")(x)
|
|
76
|
+
x = keras.layers.MaxPooling2D()(x)
|
|
77
|
+
x = keras.layers.Flatten()(x)
|
|
78
|
+
x = keras.layers.Dense(128, activation="relu")(x)
|
|
79
|
+
|
|
80
|
+
class_output = keras.layers.Dense(
|
|
81
|
+
len(used_classes), activation="softmax", name="class_output"
|
|
82
|
+
)(x)
|
|
83
|
+
|
|
84
|
+
box_output = keras.layers.Dense(
|
|
85
|
+
4, activation="sigmoid", name="box_output"
|
|
86
|
+
)(x)
|
|
87
|
+
|
|
88
|
+
model = keras.Model(inputs, [class_output, box_output])
|
|
89
|
+
|
|
90
|
+
model.compile(
|
|
91
|
+
optimizer="adam",
|
|
92
|
+
loss={
|
|
93
|
+
"class_output": "sparse_categorical_crossentropy",
|
|
94
|
+
"box_output": "mse",
|
|
95
|
+
},
|
|
96
|
+
metrics={
|
|
97
|
+
"class_output": "accuracy",
|
|
98
|
+
"box_output": "mae",
|
|
99
|
+
},
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
model.summary()
|
|
103
|
+
|
|
104
|
+
model.fit(
|
|
105
|
+
X_train,
|
|
106
|
+
{"class_output": yc_train, "box_output": yb_train},
|
|
107
|
+
validation_split=0.2,
|
|
108
|
+
epochs=10,
|
|
109
|
+
batch_size=16,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
results = model.evaluate(
|
|
113
|
+
X_test,
|
|
114
|
+
{"class_output": yc_test, "box_output": yb_test},
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
print("\nUsed VOC classes:", [class_names[i] for i in used_classes])
|
|
118
|
+
print("Evaluation:", results)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Experiment 9 - LSTM sentiment analysis using a LOCAL CSV.
|
|
3
|
+
|
|
4
|
+
CSV format:
|
|
5
|
+
text,label
|
|
6
|
+
"I loved this movie",positive
|
|
7
|
+
"The movie was terrible",negative
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import tensorflow as tf
|
|
11
|
+
from tensorflow import keras
|
|
12
|
+
import pandas as pd
|
|
13
|
+
from sklearn.model_selection import train_test_split
|
|
14
|
+
from sklearn.preprocessing import LabelEncoder
|
|
15
|
+
|
|
16
|
+
CSV_PATH = "data/sentiment.csv"
|
|
17
|
+
TEXT_COLUMN = "text"
|
|
18
|
+
LABEL_COLUMN = "label"
|
|
19
|
+
MAX_WORDS = 10000
|
|
20
|
+
MAX_LENGTH = 150
|
|
21
|
+
|
|
22
|
+
df = pd.read_csv(CSV_PATH)[[TEXT_COLUMN, LABEL_COLUMN]].dropna()
|
|
23
|
+
|
|
24
|
+
texts = df[TEXT_COLUMN].astype(str).values
|
|
25
|
+
encoder = LabelEncoder()
|
|
26
|
+
labels = encoder.fit_transform(df[LABEL_COLUMN])
|
|
27
|
+
|
|
28
|
+
X_train, X_test, y_train, y_test = train_test_split(
|
|
29
|
+
texts,
|
|
30
|
+
labels,
|
|
31
|
+
test_size=0.2,
|
|
32
|
+
random_state=42,
|
|
33
|
+
stratify=labels,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
vectorizer = keras.layers.TextVectorization(
|
|
37
|
+
max_tokens=MAX_WORDS,
|
|
38
|
+
output_sequence_length=MAX_LENGTH,
|
|
39
|
+
)
|
|
40
|
+
vectorizer.adapt(X_train)
|
|
41
|
+
|
|
42
|
+
model = keras.Sequential([
|
|
43
|
+
keras.layers.Input(shape=(), dtype=tf.string),
|
|
44
|
+
vectorizer,
|
|
45
|
+
keras.layers.Embedding(
|
|
46
|
+
input_dim=MAX_WORDS,
|
|
47
|
+
output_dim=128,
|
|
48
|
+
mask_zero=True,
|
|
49
|
+
),
|
|
50
|
+
keras.layers.LSTM(64),
|
|
51
|
+
keras.layers.Dense(32, activation="relu"),
|
|
52
|
+
keras.layers.Dropout(0.4),
|
|
53
|
+
keras.layers.Dense(len(encoder.classes_), activation="softmax"),
|
|
54
|
+
])
|
|
55
|
+
|
|
56
|
+
model.compile(
|
|
57
|
+
optimizer="adam",
|
|
58
|
+
loss="sparse_categorical_crossentropy",
|
|
59
|
+
metrics=["accuracy"],
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
model.summary()
|
|
63
|
+
|
|
64
|
+
model.fit(
|
|
65
|
+
X_train,
|
|
66
|
+
y_train,
|
|
67
|
+
validation_split=0.2,
|
|
68
|
+
epochs=10,
|
|
69
|
+
batch_size=32,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
loss, accuracy = model.evaluate(X_test, y_test)
|
|
73
|
+
print("\nTest accuracy:", round(accuracy, 4))
|
|
74
|
+
|
|
75
|
+
sample_texts = [
|
|
76
|
+
"This product is excellent",
|
|
77
|
+
"I hate this product",
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
predictions = model.predict(sample_texts, verbose=0)
|
|
81
|
+
|
|
82
|
+
for text, prediction in zip(sample_texts, predictions):
|
|
83
|
+
index = prediction.argmax()
|
|
84
|
+
print(f"{text} -> {encoder.inverse_transform([index])[0]}")
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Experiment 9 - LSTM sentiment analysis using the Keras IMDB dataset.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import tensorflow as tf
|
|
7
|
+
from tensorflow import keras
|
|
8
|
+
|
|
9
|
+
MAX_WORDS = 10000
|
|
10
|
+
MAX_LENGTH = 150
|
|
11
|
+
|
|
12
|
+
(x_train, y_train), (x_test, y_test) = keras.datasets.imdb.load_data(
|
|
13
|
+
num_words=MAX_WORDS
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
x_train = keras.preprocessing.sequence.pad_sequences(
|
|
17
|
+
x_train, maxlen=MAX_LENGTH
|
|
18
|
+
)
|
|
19
|
+
x_test = keras.preprocessing.sequence.pad_sequences(
|
|
20
|
+
x_test, maxlen=MAX_LENGTH
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
model = keras.Sequential([
|
|
24
|
+
keras.layers.Input(shape=(MAX_LENGTH,)),
|
|
25
|
+
keras.layers.Embedding(MAX_WORDS, 128),
|
|
26
|
+
keras.layers.LSTM(64),
|
|
27
|
+
keras.layers.Dense(32, activation="relu"),
|
|
28
|
+
keras.layers.Dropout(0.4),
|
|
29
|
+
keras.layers.Dense(1, activation="sigmoid"),
|
|
30
|
+
])
|
|
31
|
+
|
|
32
|
+
model.compile(
|
|
33
|
+
optimizer="adam",
|
|
34
|
+
loss="binary_crossentropy",
|
|
35
|
+
metrics=["accuracy"],
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
model.summary()
|
|
39
|
+
|
|
40
|
+
history = model.fit(
|
|
41
|
+
x_train,
|
|
42
|
+
y_train,
|
|
43
|
+
validation_split=0.2,
|
|
44
|
+
epochs=10,
|
|
45
|
+
batch_size=32,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
loss, accuracy = model.evaluate(x_test, y_test)
|
|
49
|
+
print("\nTest accuracy:", round(accuracy, 4))
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Additional Experiment - Vanilla RNN implemented from scratch with NumPy.
|
|
3
|
+
|
|
4
|
+
No TensorFlow/Keras recurrent layer is used.
|
|
5
|
+
|
|
6
|
+
Task:
|
|
7
|
+
Predict the next value of a sine-wave sequence.
|
|
8
|
+
|
|
9
|
+
The RNN equations implemented manually are:
|
|
10
|
+
|
|
11
|
+
h_t = tanh(Wxh x_t + Whh h_(t-1) + bh)
|
|
12
|
+
y_t = Why h_t + by
|
|
13
|
+
|
|
14
|
+
Training uses manually implemented backpropagation through time (BPTT).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
np.random.seed(42)
|
|
20
|
+
|
|
21
|
+
# Generate a simple sequence dataset.
|
|
22
|
+
t = np.linspace(0, 100, 2000)
|
|
23
|
+
sequence = np.sin(t).astype(np.float32)
|
|
24
|
+
|
|
25
|
+
SEQ_LEN = 20
|
|
26
|
+
HIDDEN_SIZE = 32
|
|
27
|
+
LEARNING_RATE = 0.01
|
|
28
|
+
EPOCHS = 20
|
|
29
|
+
|
|
30
|
+
X = []
|
|
31
|
+
Y = []
|
|
32
|
+
|
|
33
|
+
for i in range(len(sequence) - SEQ_LEN):
|
|
34
|
+
X.append(sequence[i:i + SEQ_LEN])
|
|
35
|
+
Y.append(sequence[i + SEQ_LEN])
|
|
36
|
+
|
|
37
|
+
X = np.array(X)
|
|
38
|
+
Y = np.array(Y)
|
|
39
|
+
|
|
40
|
+
split = int(0.8 * len(X))
|
|
41
|
+
X_train, X_test = X[:split], X[split:]
|
|
42
|
+
Y_train, Y_test = Y[:split], Y[split:]
|
|
43
|
+
|
|
44
|
+
# Parameters.
|
|
45
|
+
Wxh = np.random.randn(HIDDEN_SIZE, 1) * 0.1
|
|
46
|
+
Whh = np.random.randn(HIDDEN_SIZE, HIDDEN_SIZE) * 0.1
|
|
47
|
+
Why = np.random.randn(1, HIDDEN_SIZE) * 0.1
|
|
48
|
+
|
|
49
|
+
bh = np.zeros((HIDDEN_SIZE, 1))
|
|
50
|
+
by = np.zeros((1, 1))
|
|
51
|
+
|
|
52
|
+
def forward(sequence):
|
|
53
|
+
h = np.zeros((HIDDEN_SIZE, 1))
|
|
54
|
+
states = [(None, h)]
|
|
55
|
+
outputs = []
|
|
56
|
+
|
|
57
|
+
for value in sequence:
|
|
58
|
+
x = np.array([[value]], dtype=np.float32)
|
|
59
|
+
h = np.tanh(Wxh @ x + Whh @ h + bh)
|
|
60
|
+
y = Why @ h + by
|
|
61
|
+
states.append((x, h))
|
|
62
|
+
outputs.append(y.item())
|
|
63
|
+
|
|
64
|
+
return np.array(outputs), states
|
|
65
|
+
|
|
66
|
+
def train_step(sequence, target):
|
|
67
|
+
global Wxh, Whh, Why, bh, by
|
|
68
|
+
|
|
69
|
+
predictions, states = forward(sequence)
|
|
70
|
+
|
|
71
|
+
# Loss is based on the final prediction.
|
|
72
|
+
error = predictions[-1] - target
|
|
73
|
+
loss = 0.5 * error ** 2
|
|
74
|
+
|
|
75
|
+
dWxh = np.zeros_like(Wxh)
|
|
76
|
+
dWhh = np.zeros_like(Whh)
|
|
77
|
+
dWhy = np.zeros_like(Why)
|
|
78
|
+
dbh = np.zeros_like(bh)
|
|
79
|
+
dby = np.array([[error]])
|
|
80
|
+
|
|
81
|
+
# Gradient from the final output.
|
|
82
|
+
dWhy += error * states[-1][1]
|
|
83
|
+
|
|
84
|
+
dh = Why.T * error
|
|
85
|
+
|
|
86
|
+
# Backpropagation through time.
|
|
87
|
+
for step in reversed(range(1, len(states))):
|
|
88
|
+
x, h = states[step]
|
|
89
|
+
h_prev = states[step - 1][1]
|
|
90
|
+
|
|
91
|
+
dh_raw = (1 - h * h) * dh
|
|
92
|
+
|
|
93
|
+
dWxh += dh_raw @ x.T
|
|
94
|
+
dWhh += dh_raw @ h_prev.T
|
|
95
|
+
dbh += dh_raw
|
|
96
|
+
|
|
97
|
+
dh = Whh.T @ dh_raw
|
|
98
|
+
|
|
99
|
+
# Clip gradients for stability.
|
|
100
|
+
for grad in [dWxh, dWhh, dWhy, dbh, dby]:
|
|
101
|
+
np.clip(grad, -1, 1, out=grad)
|
|
102
|
+
|
|
103
|
+
Wxh -= LEARNING_RATE * dWxh
|
|
104
|
+
Whh -= LEARNING_RATE * dWhh
|
|
105
|
+
Why -= LEARNING_RATE * dWhy
|
|
106
|
+
bh -= LEARNING_RATE * dbh
|
|
107
|
+
by -= LEARNING_RATE * dby
|
|
108
|
+
|
|
109
|
+
return loss
|
|
110
|
+
|
|
111
|
+
for epoch in range(EPOCHS):
|
|
112
|
+
total_loss = 0.0
|
|
113
|
+
|
|
114
|
+
for sequence, target in zip(X_train, Y_train):
|
|
115
|
+
total_loss += train_step(sequence, target)
|
|
116
|
+
|
|
117
|
+
average_loss = total_loss / len(X_train)
|
|
118
|
+
print(f"Epoch {epoch + 1:02d}/{EPOCHS} - loss: {average_loss:.6f}")
|
|
119
|
+
|
|
120
|
+
test_losses = []
|
|
121
|
+
|
|
122
|
+
for sequence, target in zip(X_test[:200], Y_test[:200]):
|
|
123
|
+
prediction, _ = forward(sequence)
|
|
124
|
+
test_losses.append(0.5 * (prediction[-1] - target) ** 2)
|
|
125
|
+
|
|
126
|
+
print("\nMean test loss:", np.mean(test_losses))
|
|
127
|
+
|
|
128
|
+
sample_prediction, _ = forward(X_test[0])
|
|
129
|
+
print("Actual next value :", Y_test[0])
|
|
130
|
+
print("Predicted next value:", sample_prediction[-1])
|