refactored

This commit is contained in:
2025-12-19 15:20:04 +01:00
parent a293fc31a0
commit a47922cb1c
8 changed files with 92 additions and 124 deletions
+2 -2
View File
@@ -213,8 +213,8 @@
"# Test with test data\n", "# Test with test data\n",
"test_batch = Mat([[0,0,0], [0,1,0], [1,0,0], [1,1,0]], dtype=np.float64)\n", "test_batch = Mat([[0,0,0], [0,1,0], [1,0,0], [1,1,0]], dtype=np.float64)\n",
"for pattern in test_batch:\n", "for pattern in test_batch:\n",
"\th = layer.entity.gibbs_v_to_h(pattern)\n", "\th = layer.entity.forward(pattern)\n",
"\tv = layer.entity.gibbs_h_to_v(h)\n", "\tv = layer.entity.reconstruct(h)\n",
"\tprint(f\"P{pattern} : {v}\")" "\tprint(f\"P{pattern} : {v}\")"
] ]
}, },
+21 -3
View File
@@ -1,7 +1,25 @@
from .params import EntityParams
from .state import RbmState from .state import RbmState
from .matrix import prob, Mat from .matrix import prob, Mat
class EntityParams:
def __init__(self):
# Entity parameters
self.num_gibbs_samples = 1
self.do_gaussian_visible = False
self.do_gaussian_hidden = False
@classmethod
def from_dict(cls, params: dict):
obj = EntityParams()
if "numGibbs" in params:
obj.num_gibbs_samples = params["numGibbs"]
if "doGaussianVisible" in params:
obj.do_gaussian_visible = params["doGaussianVisible"]
if "doGaussianHidden" in params:
obj.do_gaussian_hidden = params["doGaussianHidden"]
return obj
class Entity: class Entity:
def __init__(self, shape: tuple[int, int], params: EntityParams): def __init__(self, shape: tuple[int, int], params: EntityParams):
self.shape = shape self.shape = shape
@@ -22,7 +40,7 @@ class Entity:
return prob(state) return prob(state)
def gibbs_v_to_h(self, v: Mat) -> Mat: def forward(self, v: Mat) -> Mat:
h = self.v_to_ph(v) h = self.v_to_ph(v)
for i in range(self.params.num_gibbs_samples-1): for i in range(self.params.num_gibbs_samples-1):
h = self.h_to_pv(h) h = self.h_to_pv(h)
@@ -30,7 +48,7 @@ class Entity:
return h return h
def gibbs_h_to_v(self, h: Mat) -> Mat: def reconstruct(self, h: Mat) -> Mat:
v = self.h_to_pv(h) v = self.h_to_pv(h)
for i in range(self.params.num_gibbs_samples-1): for i in range(self.params.num_gibbs_samples-1):
v = self.v_to_ph(v) v = self.v_to_ph(v)
+5 -7
View File
@@ -1,15 +1,13 @@
from .params import EntityParams
from .state import RbmState from .state import RbmState
from .status import Status from .train import TrainingParams
from .train import train from .entity import Entity, EntityParams
from .entity import Entity
from .matrix import Mat, np
class Layer: class Layer:
def __init__(self, name: str, shape: tuple[int, int, int, int], params: EntityParams): def __init__(self, name: str, shape: tuple[int, int, int, int], entity_params: EntityParams, training_params: TrainingParams):
self.name = name self.name = name
self.shape = shape self.shape = shape
self.entity = Entity((shape[0]*shape[1]+shape[2], shape[3]), params) self.entity = Entity((shape[0]*shape[1]+shape[2], shape[3]), entity_params)
self.training_params = training_params
def init(self, std: float): def init(self, std: float):
self.entity.state.init(mu=0, std=std) self.entity.state.init(mu=0, std=std)
-74
View File
@@ -1,74 +0,0 @@
import json
class Params:
def save(self, filename: str):
with open(filename, "w") as fp:
json.dump(self.__dict__, fp, indent=4)
def load(self, filename: str):
with open(filename, "r") as fp:
self.__dict__ = json.load(fp)
class EntityParams(Params):
def __init__(self):
# Training parameters
self.learning_rate = 0.1
self.momentum = 0.5
self.weight_decay = 0
self.num_epochs = 1000
self.mini_batch_size = 0
self.do_rao_blackwell = False
self.do_gibbs_sample_visible = False
self.do_gibbs_sample_hidden = False
self.do_batch_sample = False
# Entity parameters
self.num_gibbs_samples = 1
self.do_gaussian_visible = False
self.do_gaussian_hidden = False
@classmethod
def from_dict(cls, params: dict, version: str = '0'):
obj = EntityParams()
if "0" in version or "1" in version:
# Training parameters
obj.learning_rate = params["learningRate"]
obj.momentum = params["momentum"]
obj.weight_decay = params["weightDecay"]
obj.num_epochs = params["numEpochs"]
obj.mini_batch_size = params["miniBatchSize"]
obj.do_rao_blackwell = params["doRaoBlackwell"]
obj.do_gibbs_sample_visible = params["gibbsDoSampleVisible"]
obj.do_gibbs_sample_hidden = params["gibbsDoSampleHidden"]
obj.do_batch_sample = params["doSampleBatch"]
# Entity parameters
obj.num_gibbs_samples = params["numGibbs"]
if "1" in version:
# Entity parameters
obj.do_gaussian_visible = params["doGaussianVisible"]
obj.do_gaussian_hidden = params["doGaussianHidden"]
return obj
class LayerParams(Params):
def __init__(self, num_visible, num_hidden):
self.num_visible = num_visible
self.num_hidden = num_hidden
if __name__ == "__main__":
test_filename = "../../test_params.json"
p1 = EntityParams()
p1.num_epochs = 31101970
p1.save(test_filename)
p2 = EntityParams()
p2.load(test_filename)
assert p2.__dict__ == p1.__dict__
print("Test: [passed]")
+4 -4
View File
@@ -19,21 +19,21 @@ class StackDeep(Stack):
def train(self, batch: Mat, status=Status()): def train(self, batch: Mat, status=Status()):
_batch = np.copy(batch) _batch = np.copy(batch)
for index, layer in enumerate(self.layers): for index, layer in enumerate(self.layers):
print(f"Train layer {index} for {layer.entity.params.num_epochs} epochs") print(f"Train layer {index} for {layer.training_params.num_epochs} epochs")
_batch = self.batch_from(batch, index) _batch = self.batch_from(batch, index)
train(layer.entity, _batch, status=status) train(layer.entity, _batch, layer.training_params, status=status)
def pass_up(self, visible: Mat, from_layer_id: int = 0): def pass_up(self, visible: Mat, from_layer_id: int = 0):
h = np.copy(visible) h = np.copy(visible)
for layer in self.layers[from_layer_id:]: for layer in self.layers[from_layer_id:]:
h = layer.entity.gibbs_v_to_h(h) h = layer.entity.forward(h)
return h return h
def pass_down(self, hidden: Mat, from_layer_id: int = 0): def pass_down(self, hidden: Mat, from_layer_id: int = 0):
v = np.copy(hidden) v = np.copy(hidden)
for layer in list(reversed(self.layers))[from_layer_id:]: for layer in list(reversed(self.layers))[from_layer_id:]:
v = layer.entity.gibbs_h_to_v(v) v = layer.entity.reconstruct(v)
return v return v
def pass_down_up(self, visible: Mat, from_layer_id: int = 0): def pass_down_up(self, visible: Mat, from_layer_id: int = 0):
+8 -11
View File
@@ -1,14 +1,14 @@
import json import json
from collections.abc import Callable
from .stack import StackType from .stack import StackType
from .layer import Layer from .layer import Layer
from .params import EntityParams from .entity import EntityParams
from .train import TrainingParams
from .stack_deep import StackDeep from .stack_deep import StackDeep
from .stack_rnn import StackRnn from .stack_rnn import StackRnn
class StackFactory: class StackFactory:
@classmethod @classmethod
def from_dict(cls, project: dict, work_dir: str = ".", layer_constructor: Callable = None) -> StackDeep|StackRnn: def from_dict(cls, project: dict, work_dir: str = ".") -> StackDeep|StackRnn:
name = project["stack"]["name"] name = project["stack"]["name"]
layers = project["stack"]["layers"] layers = project["stack"]["layers"]
@@ -38,15 +38,12 @@ class StackFactory:
num_context = 0 num_context = 0
# Determine version by existence of keys # Determine version by existence of keys
layer_params = layer["rbm"]["params"] params = layer["rbm"]["params"]
params_version = '0' entity_params = EntityParams.from_dict(params)
if "doGaussianHidden" in layer_params and "doGaussianVisible" in layer_params: training_params = TrainingParams.from_dict(params)
params_version = '1'
params = EntityParams.from_dict(layer_params, params_version)
# Create layer # Create layer
layer_obj = Layer(f"{layer_name}-{layer_id}", (num_visible_x, num_visible_y, num_context, num_hidden), params) layer_obj = Layer(f"{layer_name}-{layer_id}", (num_visible_x, num_visible_y, num_context, num_hidden), entity_params, training_params)
# Add layer to stack # Add layer to stack
obj.append(layer_obj) obj.append(layer_obj)
@@ -54,7 +51,7 @@ class StackFactory:
return obj return obj
@classmethod @classmethod
def from_file(cls, filename: str, work_dir: str = ".", layer_constructor: Callable = None) -> StackDeep|StackRnn: def from_file(cls, filename: str, work_dir: str = ".") -> StackDeep|StackRnn:
obj = None obj = None
with open(filename, "r") as fp: with open(filename, "r") as fp:
prj = json.load(fp) prj = json.load(fp)
+42 -14
View File
@@ -3,7 +3,35 @@ from .matrix import gaussian, sample, prob, rms_error_accu, Mat, np
from .entity import Entity from .entity import Entity
from .status import Status from .status import Status
def cd_jens(entity: Entity, v_states: Mat): class TrainingParams:
def __init__(self):
# Training parameters
self.learning_rate = 0.1
self.momentum = 0.5
self.weight_decay = 0
self.num_epochs = 1000
self.mini_batch_size = 0
self.do_rao_blackwell = False
self.do_gibbs_sample_visible = False
self.do_gibbs_sample_hidden = False
self.do_batch_sample = False
@classmethod
def from_dict(cls, params: dict):
obj = TrainingParams()
obj.learning_rate = params["learningRate"]
obj.momentum = params["momentum"]
obj.weight_decay = params["weightDecay"]
obj.num_epochs = params["numEpochs"]
obj.mini_batch_size = params["miniBatchSize"]
obj.do_rao_blackwell = params["doRaoBlackwell"]
obj.do_gibbs_sample_visible = params["gibbsDoSampleVisible"]
obj.do_gibbs_sample_hidden = params["gibbsDoSampleHidden"]
obj.do_batch_sample = params["doSampleBatch"]
return obj
def cd_jens(entity: Entity, v_states: Mat, params: TrainingParams):
v_probs = prob(v_states) v_probs = prob(v_states)
h_states = entity.v_to_ph(v_states) h_states = entity.v_to_ph(v_states)
h_probs = h_states h_probs = h_states
@@ -12,7 +40,7 @@ def cd_jens(entity: Entity, v_states: Mat):
h_states += gaussian(h_states.shape) h_states += gaussian(h_states.shape)
else: else:
h_probs = entity.v_to_ph(v_states) h_probs = entity.v_to_ph(v_states)
if entity.params.do_rao_blackwell: if params.do_rao_blackwell:
h_states = h_probs h_states = h_probs
else: else:
h_states = sample(h_probs) h_states = sample(h_probs)
@@ -24,13 +52,13 @@ def cd_jens(entity: Entity, v_states: Mat):
# Gibbs sampling with training params # Gibbs sampling with training params
for i in range(entity.params.num_gibbs_samples): for i in range(entity.params.num_gibbs_samples):
if entity.params.do_gibbs_sample_hidden: if params.do_gibbs_sample_hidden:
v_probs = entity.h_to_pv(sample(h_probs)) v_probs = entity.h_to_pv(sample(h_probs))
else: else:
v_probs = entity.h_to_pv(h_probs) v_probs = entity.h_to_pv(h_probs)
# Create hidden representation given v # Create hidden representation given v
if entity.params.do_gibbs_sample_visible: if params.do_gibbs_sample_visible:
h_probs = entity.v_to_ph(sample(v_probs)) h_probs = entity.v_to_ph(sample(v_probs))
else: else:
h_probs = entity.v_to_ph(v_probs) h_probs = entity.v_to_ph(v_probs)
@@ -42,14 +70,14 @@ def cd_jens(entity: Entity, v_states: Mat):
return dw, dbv, dbh return dw, dbv, dbh
def train(entity: Entity, batch: Mat, status: Status, cd_func: Callable = cd_jens): def train(entity: Entity, batch: Mat, params: TrainingParams, status: Status, cd_func: Callable = cd_jens):
training_remain = batch.shape[0] training_remain = batch.shape[0]
batch_size = min(entity.params.mini_batch_size, training_remain) batch_size = min(params.mini_batch_size, training_remain)
if batch_size == 0: if batch_size == 0:
batch_size = training_remain batch_size = training_remain
status.on_change() status.on_change()
d_progress = 100.0 / (training_remain*entity.params.num_epochs) d_progress = 100.0 / (training_remain*params.num_epochs)
progress = 0 progress = 0
batch_row_index = 0 batch_row_index = 0
keep_running = True keep_running = True
@@ -64,18 +92,18 @@ def train(entity: Entity, batch: Mat, status: Status, cd_func: Callable = cd_jen
inc_whv = np.zeros(entity.state.w_hv.shape) inc_whv = np.zeros(entity.state.w_hv.shape)
v_states = mini_batch v_states = mini_batch
if entity.params.do_batch_sample: if params.do_batch_sample:
v_states = sample(mini_batch) v_states = sample(mini_batch)
for epochs in range(entity.params.num_epochs): for epochs in range(params.num_epochs):
# Contrastive divergence learning: calculate gradients # Contrastive divergence learning: calculate gradients
dwhv, dbv, dbh = cd_func(entity, v_states) dwhv, dbv, dbh = cd_func(entity, v_states, params)
# Adjust weight and biases # Adjust weight and biases
kl = entity.params.learning_rate/batch_size kl = params.learning_rate/batch_size
inc_bv = entity.params.momentum*inc_bv + kl*dbv inc_bv = params.momentum*inc_bv + kl*dbv
inc_bh = entity.params.momentum*inc_bh + kl*dbh inc_bh = params.momentum*inc_bh + kl*dbh
inc_whv = entity.params.momentum*inc_whv + kl*dwhv - entity.params.weight_decay*entity.state.w_hv inc_whv = params.momentum*inc_whv + kl*dwhv - params.weight_decay*entity.state.w_hv
entity.state.b_v += inc_bv entity.state.b_v += inc_bv
entity.state.b_h += inc_bh entity.state.b_h += inc_bh
+10 -9
View File
@@ -1,20 +1,21 @@
import os.path import os.path
from rbm.params import EntityParams
from rbm.layer import Layer from rbm.layer import Layer
from rbm.status import Status from rbm.status import Status
from rbm.train import train from rbm.train import train, TrainingParams
from rbm.matrix import Mat, np from rbm.matrix import Mat, np
from rbm.entity import EntityParams
work_dir = "../../results" work_dir = "../../results"
def xor(): def xor():
# Create params # Create params
params = EntityParams() entity_params = EntityParams()
params.do_rao_blackwell = True training_params = TrainingParams()
params.num_gibbs_samples = 3 entity_params.do_rao_blackwell = True
entity_params.num_gibbs_samples = 3
# Create layer # Create layer
layer = Layer("Layer_0", (3, 1, 0, 16), params) layer = Layer("Layer_0", (3, 1, 0, 16), entity_params, training_params)
# Init weights # Init weights
layer.init(0.01) layer.init(0.01)
@@ -26,7 +27,7 @@ def xor():
training_batch = Mat([[0,1,1], [0,0,0], [1,1,0], [1,0,1]], dtype=np.float64) training_batch = Mat([[0,1,1], [0,0,0], [1,1,0], [1,0,1]], dtype=np.float64)
# Train layer # Train layer
train(layer.entity, training_batch, Status()) train(layer.entity, training_batch, training_params, Status())
# Save weights # Save weights
layer.save(os.path.join(work_dir, "xor_layer0_state.npz")) layer.save(os.path.join(work_dir, "xor_layer0_state.npz"))
@@ -34,8 +35,8 @@ def xor():
# Test with test data # Test with test data
test_batch = Mat([[0,0,0], [0,1,0], [1,0,0], [1,1,0]], dtype=np.float64) test_batch = Mat([[0,0,0], [0,1,0], [1,0,0], [1,1,0]], dtype=np.float64)
for pattern in test_batch: for pattern in test_batch:
h = layer.entity.gibbs_v_to_h(pattern) h = layer.entity.forward(pattern)
v = layer.entity.gibbs_h_to_v(h) v = layer.entity.reconstruct(h)
print(f"P{pattern} : {v}") print(f"P{pattern} : {v}")
if __name__ == "__main__": if __name__ == "__main__":