@@ -160,8 +160,9 @@ def contrastive_divergence(self, visible_zero: np.ndarray) -> float:
160160
161161 >>> rbm = RBM(3, 2, cd_steps=2)
162162 >>> data = np.array([[0., 1., 0.]])
163- >>> round(rbm.contrastive_divergence(data), 5)
164- 0.0 < 1.0 # Loss should be a non-negative float less than 1
163+ >>> loss = rbm.contrastive_divergence(data)
164+ >>> 0 <= loss and loss < 1
165+ np.True_
165166 """
166167 h_probs0 , h0 = self .sample_hidden_given_visible (visible_zero )
167168 vk , hk = visible_zero , h0
@@ -182,19 +183,20 @@ def contrastive_divergence(self, visible_zero: np.ndarray) -> float:
182183 loss = np .mean ((visible_zero - vk ) ** 2 )
183184 return loss
184185
185- def train (self , dataset : np .ndarray ) -> None :
186+ def train (self , dataset : np .ndarray , verbose : bool = True ) -> None :
186187 """
187188 Train the RBM on the entire dataset.
188189
189190 Args:
190191 dataset (np.ndarray): Training dataset matrix.
191192
192- >>> rbm = RBM(6, 3, epochs=1, batch_size=2)
193- >>> data = np.random.randint(0, 2, (4, 6)).astype(float)
194- >>> rbm.train(data) # runs without error
193+ >>> rbm = RBM(16, 3, epochs=1, batch_size=2)
194+ >>> rng = np.random.default_rng() # for random number generation
195+ >>> data = rng.integers(0, 2, size=(4, 16)).astype(float)
196+ >>> rbm.train(data, verbose = False) # doctest: +ELLIPSIS
195197 """
196198 n_samples = dataset .shape [0 ]
197- for epoch in range (self .epochs ):
199+ for _epoch in range (self .epochs ):
198200 self .rng .shuffle (dataset )
199201 losses = []
200202
@@ -203,7 +205,8 @@ def train(self, dataset: np.ndarray) -> None:
203205 loss = self .contrastive_divergence (batch )
204206 losses .append (loss )
205207
206- print (f"Epoch [{ epoch + 1 } /{ self .epochs } ] avg loss: { np .mean (losses ):.6f} " )
208+ if verbose :
209+ print (f"Epoch [{ _epoch + 1 } /{ self .epochs } ] avg loss: { np .mean (losses ):.6f} " )
207210
208211
209212class DeepBeliefNetwork :
@@ -355,64 +358,113 @@ def generate_input_for_layer(
355358 samples .append (x_dash )
356359 return np .mean (np .stack (samples , axis = 0 ), axis = 0 )
357360
358- def train_dbn (self , training_data : np .ndarray ) -> None :
361+ def train_dbn (self , training_data : np .ndarray , verbose : bool = True ) -> None :
359362 """
360363 Layer-wise train the DBN using RBMs.
361364
362365 Args:
363366 training_data (np.ndarray): Training dataset.
364367
365- >>> dbn = DeepBeliefNetwork(4, [3])
366- >>> data = np.random.randint(0, 2, (10, 4)).astype(float)
367- >>> dbn.train_dbn(data) # runs without error
368+ >>> dbn = DeepBeliefNetwork(input_size=16, layers=[16, 8, 4])
369+ >>> rng = np.random.default_rng() # for random number generation
370+ >>> data = rng.integers(0, 2, size=(100, 16)).astype(float)
371+ >>> dbn.train_dbn(data, verbose=False) # doctest: +ELLIPSIS
368372 """
369373 for idx , layer_size in enumerate (self .layers ):
370374 n_visible = self .input_size if idx == 0 else self .layers [idx - 1 ]
371375 n_hidden = layer_size
372376
373- rbm = RBM (n_visible , n_hidden , cd_steps = 5 , epochs = 300 )
377+ rbm = RBM (n_visible = n_visible , n_hidden = n_hidden , cd_steps = 5 , epochs = 300 )
374378 x_input = self .generate_input_for_layer (idx , training_data )
375- rbm .train (x_input )
379+ rbm .train (x_input , verbose = verbose )
376380 self .layer_params [idx ]["W" ] = rbm .weights
377381 self .layer_params [idx ]["hb" ] = rbm .hidden_bias
378382 self .layer_params [idx ]["vb" ] = rbm .visible_bias
383+
384+ if verbose :
379385 print (f"Finished training layer { idx + 1 } /{ len (self .layers )} " )
380386
381387 def reconstruct (
382388 self , input_data : np .ndarray
383389 ) -> tuple [np .ndarray , np .ndarray , float ]:
384390 """
385- Reconstruct input through forward and backward Gibbs sampling .
391+ Reconstructs the input through the stacked RBMs .
386392
387- Args:
388- input_data (np.ndarray): Input data to reconstruct.
393+ Parameters
394+ ----------
395+ input_data : np.ndarray
396+ Input data for reconstruction.
389397
390- Returns:
391- tuple: (encoded representation, reconstructed input, MSE error)
398+ Returns
399+ -------
400+ tuple[np.ndarray, np.ndarray, float]
401+ A tuple containing the encoded representation,
402+ reconstructed data, and reconstruction error.
392403
404+ Examples
405+ --------
406+ >>> import numpy as np
393407 >>> dbn = DeepBeliefNetwork(4, [3])
394408 >>> data = np.ones((2, 4))
409+ >>> dbn.train_dbn(data, verbose=False) # doctest: +ELLIPSIS
395410 >>> encoded, reconstructed, error = dbn.reconstruct(data)
396411 >>> encoded.shape
397412 (2, 3)
413+ >>> reconstructed.shape
414+ (2, 4)
415+ >>> isinstance(error, float)
416+ True
398417 """
399- h = input_data .copy ()
400- for i in range (len (self .layer_params )):
401- _ , h = self .sample_h (
402- h , self .layer_params [i ]["W" ], self .layer_params [i ]["hb" ]
403- )
404- encoded = h .copy ()
418+ # --- Ensure weights are initialized ---
419+ prev_size = self .input_size
420+ for i , layer in enumerate (self .layer_params ):
421+ if layer ["W" ] is None :
422+ n_visible = prev_size
423+ n_hidden = self .layers [i ]
424+ rng = np .random .default_rng ()
425+ layer ["W" ] = rng .normal (0 , 0.01 , (n_visible , n_hidden ))
426+ layer ["hb" ] = np .zeros (n_hidden )
427+ layer ["vb" ] = np .zeros (n_visible )
428+ prev_size = self .layers [i ]
429+
430+ # --- Forward pass (encoding) ---
431+ input_data = self ._normalize_input (input_data )
432+ encoded = input_data .copy ()
433+ for layer in self .layer_params :
434+ result = self .sample_h (encoded , layer ["W" ], layer ["hb" ])
435+ encoded = result [0 ] if isinstance (result , tuple ) else result
436+
437+ # --- Backward pass (decoding) ---
438+ reconstructed = encoded .copy ()
439+ for layer in reversed (self .layer_params ):
440+ result = self .sample_v (reconstructed , layer ["W" ], layer ["vb" ])
441+ reconstructed = result [0 ] if isinstance (result , tuple ) else result
442+
443+ # --- Reconstruction error ---
444+ error = float (np .mean ((input_data - reconstructed ) ** 2 ))
405445
406- for i in reversed (range (len (self .layer_params ))):
407- _ , h = self .sample_v (
408- h , self .layer_params [i ]["W" ], self .layer_params [i ]["vb" ]
409- )
410- reconstructed = h
446+ return encoded , reconstructed , error
411447
412- error = np .mean ((input_data - reconstructed ) ** 2 )
413- print (f"Reconstruction error: { error :.6f} " )
448+ def _normalize_input (self , data : np .ndarray ) -> np .ndarray :
449+ """
450+ Normalize the input data to range [0, 1] if not already binary.
414451
415- return encoded , reconstructed , error
452+ Args:
453+ data (np.ndarray): Input data.
454+
455+ Returns:
456+ np.ndarray: Normalized data.
457+
458+ >>> dbn = DeepBeliefNetwork(4, [3])
459+ >>> import numpy as np
460+ >>> x = np.array([[2., 4.], [0., 6.]])
461+ >>> np.allclose(dbn._normalize_input(x).max(), 1.0)
462+ True
463+ """
464+ data = np .asarray (data , dtype = float )
465+ if data .max () > 1.0 or data .min () < 0.0 :
466+ data = (data - data .min ()) / (data .max () - data .min ())
467+ return data
416468
417469
418470# Usage example
0 commit comments