11"""
22- - - - - -- - - - - - - - - - - - - - - - - - - - - - -
33Name - - 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
56Detail: 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
1214GitHub: https://github.com/Adhithya-Laxman/
1315Date: 2025.10.21
1416- - - - - -- - - - - - - - - - - - - - - - - - - - - - -
1517"""
1618
17- import numpy as np
1819import matplotlib .pyplot as plt
20+ import numpy as np
1921
2022
2123class 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
149162class 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
292310if __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