Skip to content

Commit a45f9cb

Browse files
refactor: code cleanup and style improvements for PEP8 and Ruff compliance
Performed extensive refactoring to conform to PEP8 and Ruff linting rules across the entire DBN-RBM implementation. - Fixed line lengths and wrapped docstrings for readability. - Replaced legacy NumPy random calls with numpy.random.Generator for modern style. - Marked unused variables by prefixing with underscore to eliminate warnings. - Sorted and cleaned import statements. - Renamed variables and arguments for proper casing to adhere to style guidelines. - Improved code formatting, spacing, and consistency. No functional changes were introduced, only stylistic and maintainability improvements.
1 parent deb7c5a commit a45f9cb

File tree

1 file changed

+105
-78
lines changed

1 file changed

+105
-78
lines changed
Lines changed: 105 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,38 @@
11
"""
22
- - - - - -- - - - - - - - - - - - - - - - - - - - - - -
33
Name - - Deep Belief Network (DBN) Using Restricted Boltzmann Machines (RBMs)
4-
Goal - - Unsupervised layer-wise feature learning and pretraining for deep neural networks
4+
Goal - - Unsupervised layer-wise feature learning and pretraining
5+
for deep neural networks
56
Detail: Multi-layer DBN constructed by stacking RBMs trained via contrastive divergence.
67
Implements Gibbs sampling for binary units, manual weight updates with NumPy.
78
Developed for Intrusion Detection System (IDS) in WiFi networks.
8-
This implementation is written entirely in pure NumPy, with no deep learning frameworks.
9+
This implementation is written entirely in pure NumPy,
10+
with no deep learning frameworks.
911
Can be extended for fine-tuning deep neural networks.
1012
11-
Author: Adhithya Laxman Ravi Shankar Geetha
13+
Author: Adhithya Laxman Ravi Shankar Geetha
1214
GitHub: https://github.com/Adhithya-Laxman/
1315
Date: 2025.10.21
1416
- - - - - -- - - - - - - - - - - - - - - - - - - - - - -
1517
"""
1618

17-
import numpy as np
1819
import matplotlib.pyplot as plt
20+
import numpy as np
1921

2022

2123
class RBM:
22-
def __init__(self, n_visible, n_hidden, learning_rate=0.01, k=1, epochs=10, batch_size=64, mode='bernoulli'):
24+
def __init__(
25+
self,
26+
n_visible,
27+
n_hidden,
28+
learning_rate=0.01,
29+
k=1,
30+
epochs=10,
31+
batch_size=64,
32+
mode="bernoulli",
33+
):
2334
"""
24-
Initialize an RBM.
35+
Initialize an RBM (Restricted Boltzmann Machine).
2536
2637
Args:
2738
n_visible (int): Number of visible units.
@@ -40,20 +51,22 @@ def __init__(self, n_visible, n_hidden, learning_rate=0.01, k=1, epochs=10, batc
4051
self.batch_size = batch_size
4152
self.mode = mode
4253

54+
self.rng = np.random.default_rng()
55+
4356
# Initialize weights and biases
44-
self.weights = np.random.normal(0, 0.01, (n_visible, n_hidden))
57+
self.weights = self.rng.normal(0, 0.01, (n_visible, n_hidden))
4558
self.hidden_bias = np.zeros(n_hidden)
4659
self.visible_bias = np.zeros(n_visible)
4760

4861
def sigmoid(self, x):
4962
"""
50-
Compute the sigmoid activation function.
63+
Compute the sigmoid activation function element-wise.
5164
5265
Args:
5366
x (np.ndarray): Input array.
5467
5568
Returns:
56-
np.ndarray: Sigmoid of input.
69+
np.ndarray: Sigmoid output of input.
5770
"""
5871
return 1.0 / (1.0 + np.exp(-x))
5972

@@ -65,16 +78,16 @@ def sample_prob(self, probs):
6578
probs (np.ndarray): Probabilities of activation.
6679
6780
Returns:
68-
np.ndarray: Sampled binary values.
81+
np.ndarray: Binary sampled values.
6982
"""
70-
return (np.random.rand(*probs.shape) < probs).astype(float)
83+
return (self.rng.random(probs.shape) < probs).astype(float)
7184

7285
def sample_hidden_given_visible(self, v):
7386
"""
7487
Sample hidden units conditioned on visible units.
7588
7689
Args:
77-
v (np.ndarray): Visible units.
90+
v (np.ndarray): Visible unit batch.
7891
7992
Returns:
8093
tuple: (hidden probabilities, hidden samples)
@@ -88,7 +101,7 @@ def sample_visible_given_hidden(self, h):
88101
Sample visible units conditioned on hidden units.
89102
90103
Args:
91-
h (np.ndarray): Hidden units.
104+
h (np.ndarray): Hidden unit batch.
92105
93106
Returns:
94107
tuple: (visible probabilities, visible samples)
@@ -99,27 +112,27 @@ def sample_visible_given_hidden(self, h):
99112

100113
def contrastive_divergence(self, v0):
101114
"""
102-
Perform Contrastive Divergence (CD-k) step.
115+
Perform Contrastive Divergence (CD-k) for a single batch.
103116
104117
Args:
105118
v0 (np.ndarray): Initial visible units (data batch).
106119
107120
Returns:
108-
float: Reconstruction loss for the batch.
121+
float: Reconstruction loss (mean squared error) for batch.
109122
"""
110123
h_probs0, h0 = self.sample_hidden_given_visible(v0)
111124
vk, hk = v0, h0
112125

113126
for _ in range(self.k):
114-
v_probs, vk = self.sample_visible_given_hidden(hk)
127+
_v_probs, vk = self.sample_visible_given_hidden(hk)
115128
h_probs, hk = self.sample_hidden_given_visible(vk)
116129

117-
# Compute gradients
118130
positive_grad = np.dot(v0.T, h_probs0)
119131
negative_grad = np.dot(vk.T, h_probs)
120132

121-
# Update weights and biases
122-
self.weights += self.learning_rate * (positive_grad - negative_grad) / v0.shape[0]
133+
self.weights += (
134+
self.learning_rate * (positive_grad - negative_grad) / v0.shape[0]
135+
)
123136
self.visible_bias += self.learning_rate * np.mean(v0 - vk, axis=0)
124137
self.hidden_bias += self.learning_rate * np.mean(h_probs0 - h_probs, axis=0)
125138

@@ -128,52 +141,52 @@ def contrastive_divergence(self, v0):
128141

129142
def train(self, data):
130143
"""
131-
Train the RBM on given data.
144+
Train the RBM on the entire dataset.
132145
133146
Args:
134-
data (np.ndarray): Training data matrix.
147+
data (np.ndarray): Training dataset matrix.
135148
"""
136149
n_samples = data.shape[0]
137150
for epoch in range(self.epochs):
138-
np.random.shuffle(data)
151+
self.rng.shuffle(data)
139152
losses = []
140153

141154
for i in range(0, n_samples, self.batch_size):
142-
batch = data[i:i + self.batch_size]
155+
batch = data[i : i + self.batch_size]
143156
loss = self.contrastive_divergence(batch)
144157
losses.append(loss)
145158

146159
print(f"Epoch [{epoch + 1}/{self.epochs}] avg loss: {np.mean(losses):.6f}")
147160

148161

149162
class DeepBeliefNetwork:
150-
def __init__(self, input_size, layers, mode='bernoulli', k=5, save_path=None):
163+
def __init__(self, input_size, layers, mode="bernoulli", k=5, save_path=None):
151164
"""
152-
Initialize a Deep Belief Network.
165+
Initialize a Deep Belief Network (DBN) with multiple RBM layers.
153166
154167
Args:
155-
input_size (int): Number of input features.
156-
layers (list): List of hidden layer sizes.
168+
input_size (int): Number of features in input layer.
169+
layers (list): List of hidden layer unit counts.
157170
mode (str): Sampling mode ('bernoulli' or 'gaussian').
158171
k (int): Number of sampling steps in generate_input_for_layer.
159-
save_path (str): Path to save trained parameters.
172+
save_path (str): Path for saving trained model parameters (optional).
160173
"""
161174
self.input_size = input_size
162175
self.layers = layers
163176
self.k = k
164177
self.mode = mode
165178
self.save_path = save_path
166-
self.layer_params = [{'W': None, 'hb': None, 'vb': None} for _ in layers]
179+
self.layer_params = [{"W": None, "hb": None, "vb": None} for _ in layers]
167180

168181
def sigmoid(self, x):
169182
"""
170-
Sigmoid activation function.
183+
Compute sigmoid activation function.
171184
172185
Args:
173186
x (np.ndarray): Input array.
174187
175188
Returns:
176-
np.ndarray: Sigmoid output.
189+
np.ndarray: Sigmoid of input.
177190
"""
178191
return 1.0 / (1.0 + np.exp(-x))
179192

@@ -182,52 +195,53 @@ def sample_prob(self, probs):
182195
Sample binary states from probabilities.
183196
184197
Args:
185-
probs (np.ndarray): Probabilities.
198+
probs (np.ndarray): Activation probabilities.
186199
187200
Returns:
188-
np.ndarray: Binary samples.
201+
np.ndarray: Binary sampled values.
189202
"""
190-
return (np.random.rand(*probs.shape) < probs).astype(float)
203+
rng = np.random.default_rng()
204+
return (rng.random(probs.shape) < probs).astype(float)
191205

192-
def sample_h(self, x, W, hb):
206+
def sample_h(self, x, w, hb):
193207
"""
194-
Sample hidden units given visible units.
208+
Sample hidden units given visible units for a DBN layer.
195209
196210
Args:
197211
x (np.ndarray): Visible units.
198-
W (np.ndarray): Weight matrix.
199-
hb (np.ndarray): Hidden biases.
212+
w (np.ndarray): Weight matrix.
213+
hb (np.ndarray): Hidden bias vector.
200214
201215
Returns:
202-
tuple: (hidden probabilities, hidden samples)
216+
tuple: Hidden probabilities and binary samples.
203217
"""
204-
probs = self.sigmoid(np.dot(x, W) + hb)
218+
probs = self.sigmoid(np.dot(x, w) + hb)
205219
samples = self.sample_prob(probs)
206220
return probs, samples
207221

208-
def sample_v(self, y, W, vb):
222+
def sample_v(self, y, w, vb):
209223
"""
210-
Sample visible units given hidden units.
224+
Sample visible units given hidden units for a DBN layer.
211225
212226
Args:
213227
y (np.ndarray): Hidden units.
214-
W (np.ndarray): Weight matrix.
215-
vb (np.ndarray): Visible biases.
228+
w (np.ndarray): Weight matrix.
229+
vb (np.ndarray): Visible bias vector.
216230
217231
Returns:
218-
tuple: (visible probabilities, visible samples)
232+
tuple: Visible probabilities and binary samples.
219233
"""
220-
probs = self.sigmoid(np.dot(y, W.T) + vb)
234+
probs = self.sigmoid(np.dot(y, w.T) + vb)
221235
samples = self.sample_prob(probs)
222236
return probs, samples
223237

224238
def generate_input_for_layer(self, layer_index, x):
225239
"""
226-
Generate smoothed input for a layer by stacking and averaging samples.
240+
Generate input for a particular DBN layer by sampling and averaging.
227241
228242
Args:
229-
layer_index (int): Index of the current layer.
230-
x (np.ndarray): Input data.
243+
layer_index (int): Layer index for which input is generated.
244+
x (np.ndarray): Original input data.
231245
232246
Returns:
233247
np.ndarray: Smoothed input for the layer.
@@ -238,16 +252,18 @@ def generate_input_for_layer(self, layer_index, x):
238252
for _ in range(self.k):
239253
x_dash = x.copy()
240254
for i in range(layer_index):
241-
_, x_dash = self.sample_h(x_dash, self.layer_params[i]['W'], self.layer_params[i]['hb'])
255+
_, x_dash = self.sample_h(
256+
x_dash, self.layer_params[i]["W"], self.layer_params[i]["hb"]
257+
)
242258
samples.append(x_dash)
243259
return np.mean(np.stack(samples, axis=0), axis=0)
244260

245261
def train_dbn(self, x):
246262
"""
247-
Train the DBN layer-wise.
263+
Layer-wise train the DBN using RBMs.
248264
249265
Args:
250-
x (np.ndarray): Training data.
266+
x (np.ndarray): Training dataset.
251267
"""
252268
for idx, layer_size in enumerate(self.layers):
253269
n_visible = self.input_size if idx == 0 else self.layers[idx - 1]
@@ -256,67 +272,78 @@ def train_dbn(self, x):
256272
rbm = RBM(n_visible, n_hidden, k=5, epochs=300)
257273
x_input = self.generate_input_for_layer(idx, x)
258274
rbm.train(x_input)
259-
self.layer_params[idx]['W'] = rbm.weights
260-
self.layer_params[idx]['hb'] = rbm.hidden_bias
261-
self.layer_params[idx]['vb'] = rbm.visible_bias
275+
self.layer_params[idx]["W"] = rbm.weights
276+
self.layer_params[idx]["hb"] = rbm.hidden_bias
277+
self.layer_params[idx]["vb"] = rbm.visible_bias
262278
print(f"Finished training layer {idx + 1}/{len(self.layers)}")
263279

264280
def reconstruct(self, x):
265281
"""
266-
Reconstruct input data through forward and backward sampling.
282+
Reconstruct input through forward and backward Gibbs sampling.
267283
268284
Args:
269-
x (np.ndarray): Input data.
285+
x (np.ndarray): Input data to reconstruct.
270286
271287
Returns:
272-
tuple: (encoded representation, reconstructed input, reconstruction error)
288+
tuple: (encoded representation, reconstructed input, MSE error)
273289
"""
274-
# Forward pass
275290
h = x.copy()
276291
for i in range(len(self.layer_params)):
277-
_, h = self.sample_h(h, self.layer_params[i]['W'], self.layer_params[i]['hb'])
292+
_, h = self.sample_h(
293+
h, self.layer_params[i]["W"], self.layer_params[i]["hb"]
294+
)
278295
encoded = h.copy()
279296

280-
# Backward pass
281297
for i in reversed(range(len(self.layer_params))):
282-
_, h = self.sample_v(h, self.layer_params[i]['W'], self.layer_params[i]['vb'])
298+
_, h = self.sample_v(
299+
h, self.layer_params[i]["W"], self.layer_params[i]["vb"]
300+
)
283301
reconstructed = h
284302

285-
# Compute reconstruction error (Mean Squared Error)
286303
error = np.mean((x - reconstructed) ** 2)
287304
print(f"Reconstruction error: {error:.6f}")
288305

289306
return encoded, reconstructed, error
290307

308+
291309
# Usage example
292310
if __name__ == "__main__":
293-
# Generate synthetic dataset
294-
data = np.random.randint(0, 2, (100, 16)).astype(float)
311+
rng = np.random.default_rng() # for random number generation
312+
data = rng.integers(0, 2, size=(100, 16)).astype(float)
295313

296-
# Initialize DBN
297314
dbn = DeepBeliefNetwork(input_size=16, layers=[16, 8, 4])
298315

299-
# Train DBN
300316
dbn.train_dbn(data)
301317

302-
# Reconstruct
303318
encoded, reconstructed, error = dbn.reconstruct(data[:5])
304319
print("Encoded shape:", encoded.shape)
305320
print("Reconstructed shape:", reconstructed.shape)
306-
# Visualization of original vs reconstructed samples
307-
features_to_show = 16 # Show only the first 20 features
321+
322+
features_to_show = 16
308323
plt.figure(figsize=(12, 5))
309324
for i in range(5):
310325
plt.subplot(2, 5, i + 1)
311-
plt.title(f"Original {i+1}")
312-
plt.imshow(data[i][:features_to_show].reshape(1, -1), cmap='gray', aspect='auto', interpolation='nearest')
313-
plt.axis('off')
326+
plt.title(f"Original {i + 1}")
327+
plt.imshow(
328+
data[i][:features_to_show].reshape(1, -1),
329+
cmap="gray",
330+
aspect="auto",
331+
interpolation="nearest",
332+
)
333+
plt.axis("off")
314334

315335
plt.subplot(2, 5, i + 6)
316-
plt.title(f"Reconstructed {i+1}")
317-
plt.imshow(reconstructed[i][:features_to_show].reshape(1, -1), cmap='gray', aspect='auto', interpolation='nearest')
318-
plt.axis('off')
319-
plt.suptitle(f"DBN Reconstruction (First {features_to_show} Features, MSE: {error:.6f})")
336+
plt.title(f"Reconstructed {i + 1}")
337+
plt.imshow(
338+
reconstructed[i][:features_to_show].reshape(1, -1),
339+
cmap="gray",
340+
aspect="auto",
341+
interpolation="nearest",
342+
)
343+
plt.axis("off")
344+
plt.suptitle(
345+
f"DBN Reconstruction (First {features_to_show} Features, MSE: {error:.6f})"
346+
)
320347
plt.tight_layout()
321-
plt.savefig('reconstruction_subset.png')
348+
plt.savefig("reconstruction_subset.png")
322349
print("Subset reconstruction plot saved as 'reconstruction_subset.png'")

0 commit comments

Comments
 (0)