diff --git a/hopfieldnetwork/HopfieldNetwork.go b/hopfieldnetwork/HopfieldNetwork.go index 04514c6..72590e8 100644 --- a/hopfieldnetwork/HopfieldNetwork.go +++ b/hopfieldnetwork/HopfieldNetwork.go @@ -3,12 +3,10 @@ package hopfieldnetwork import ( "fmt" - "hmcalister/hopfield/hopfieldnetwork/activationfunction" "hmcalister/hopfield/hopfieldnetwork/datacollector" "hmcalister/hopfield/hopfieldnetwork/domain" - "hmcalister/hopfield/hopfieldnetwork/energyfunction" - "hmcalister/hopfield/hopfieldnetwork/learningmappingfunction" "hmcalister/hopfield/hopfieldnetwork/noiseapplication" + "hmcalister/hopfield/hopfieldnetwork/states/statemanager" "hmcalister/hopfield/hopfieldutils" "log" @@ -27,6 +25,7 @@ type HopfieldNetwork struct { matrix *mat.Dense dimension int domain domain.DomainEnum + domainStateManager statemanager.StateManager forceSymmetric bool forceZeroDiagonal bool learningMethod LearningMethod @@ -168,9 +167,7 @@ func (network *HopfieldNetwork) String() string { // A float64 representing the energy of the given state with respect to the network. // Note a lower energy is more stable - but a negative state energy may still be unstable! func (network *HopfieldNetwork) StateEnergy(state *mat.VecDense) float64 { - stateCopy := mat.VecDenseCopyOf(state) - learningmappingfunction.GetLearningMappingFunction(network.domain)(stateCopy) - return energyfunction.StateEnergy(network.matrix, stateCopy) + return network.domainStateManager.StateEnergy(network.matrix, state) } // Get the energy of a given unit (indexed by i) in the state with respect to the network matrix. @@ -184,9 +181,7 @@ func (network *HopfieldNetwork) StateEnergy(state *mat.VecDense) float64 { // // A float64 representing the energy of the given unit within the state. func (network *HopfieldNetwork) UnitEnergy(state *mat.VecDense, unitIndex int) float64 { - stateCopy := mat.VecDenseCopyOf(state) - learningmappingfunction.GetLearningMappingFunction(network.domain)(stateCopy) - return energyfunction.UnitEnergy(network.matrix, stateCopy, unitIndex) + return network.domainStateManager.UnitEnergy(network.matrix, state, unitIndex) } // Get the energy of a each unit within a state with respect to the network matrix. @@ -199,10 +194,7 @@ func (network *HopfieldNetwork) UnitEnergy(state *mat.VecDense, unitIndex int) f // // A slice of float64 representing the energy of the given state's units with respect to the network. func (network *HopfieldNetwork) AllUnitEnergies(state *mat.VecDense) []float64 { - stateCopy := mat.VecDenseCopyOf(state) - learningmappingfunction.GetLearningMappingFunction(network.domain)(stateCopy) - unitEnergies := energyfunction.AllUnitEnergies(network.matrix, stateCopy) - return unitEnergies.RawVector().Data + return network.domainStateManager.AllUnitEnergies(network.matrix, state) } // Determine if a given state is unstable. @@ -262,7 +254,7 @@ func (network *HopfieldNetwork) AllStatesAreStable(states []*mat.VecDense) bool // states []*mat.VecDense: A collection of states to learn func (network *HopfieldNetwork) LearnStates(states []*mat.VecDense) []*datacollector.LearnStateData { for _, state := range states { - activationfunction.GetActivationFunction(network.domain)(state) + network.domainStateManager.ActivationFunction(state) } network.targetStates = append(network.targetStates, states...) @@ -298,7 +290,7 @@ func (network *HopfieldNetwork) UpdateState(state *mat.VecDense) { for _, unitIndex := range chunk { state.SetVec(unitIndex, newState.AtVec(unitIndex)) } - activationfunction.GetActivationFunction(network.domain)(state) + network.domainStateManager.ActivationFunction(state) } } @@ -334,7 +326,7 @@ func (network *HopfieldNetwork) RelaxState(state *mat.VecDense) *RelaxationResul for _, unitIndex := range chunk { state.SetVec(unitIndex, newState.AtVec(unitIndex)) } - activationfunction.GetActivationFunction(network.domain)(state) + network.domainStateManager.ActivationFunction(state) } // Collect the current history item if requested @@ -412,7 +404,7 @@ StateRecvLoop: for _, unitIndex := range chunk { state.SetVec(unitIndex, newState.AtVec(unitIndex)) } - activationfunction.GetActivationFunction(network.domain)(state) + network.domainStateManager.ActivationFunction(state) } // Collect the current history item if requested diff --git a/hopfieldnetwork/HopfieldNetworkBuilder.go b/hopfieldnetwork/HopfieldNetworkBuilder.go index 19ce741..45fac96 100644 --- a/hopfieldnetwork/HopfieldNetworkBuilder.go +++ b/hopfieldnetwork/HopfieldNetworkBuilder.go @@ -4,6 +4,7 @@ import ( "hmcalister/hopfield/hopfieldnetwork/datacollector" "hmcalister/hopfield/hopfieldnetwork/domain" "hmcalister/hopfield/hopfieldnetwork/noiseapplication" + "hmcalister/hopfield/hopfieldnetwork/states/statemanager" "log" "time" @@ -239,6 +240,7 @@ func (networkBuilder *HopfieldNetworkBuilder) Build() *HopfieldNetwork { matrix: matrix, dimension: networkBuilder.dimension, domain: networkBuilder.domain, + domainStateManager: statemanager.GetDomainStateManager(networkBuilder.domain), forceSymmetric: networkBuilder.forceSymmetric, forceZeroDiagonal: networkBuilder.forceZeroDiagonal, learningMethod: networkBuilder.learningMethod, diff --git a/hopfieldnetwork/LearningRule.go b/hopfieldnetwork/LearningRule.go index 94eea11..241a524 100644 --- a/hopfieldnetwork/LearningRule.go +++ b/hopfieldnetwork/LearningRule.go @@ -1,10 +1,6 @@ package hopfieldnetwork import ( - "fmt" - "hmcalister/hopfield/hopfieldnetwork/activationfunction" - "hmcalister/hopfield/hopfieldnetwork/learningmappingfunction" - "gonum.org/v1/gonum/mat" ) @@ -69,17 +65,18 @@ func getLearningRule(learningRule LearningRuleEnum) LearningRule { // // A pointer to a new matrix that stabilizes the given states as much as possible. func hebbian(network *HopfieldNetwork, states []*mat.VecDense) *mat.Dense { + var instanceContribution float64 updatedMatrix := mat.DenseCopyOf(network.GetMatrix()) updatedMatrix.Zero() for _, state := range states { - stateCopy := mat.VecDenseCopyOf(state) - learningmappingfunction.GetLearningMappingFunction(network.domain)(stateCopy) - fmt.Printf("%v\n", stateCopy) for i := 0; i < network.GetDimension(); i++ { for j := 0; j < network.GetDimension(); j++ { - val := stateCopy.AtVec(i) * stateCopy.AtVec(j) - val += updatedMatrix.At(i, j) - updatedMatrix.Set(i, j, val) + if state.AtVec(i) == state.AtVec(j) { + instanceContribution = 1.0 + } else { + instanceContribution = -1.0 + } + updatedMatrix.Set(i, j, updatedMatrix.At(i, j)+instanceContribution) } } } @@ -112,7 +109,7 @@ func delta(network *HopfieldNetwork, states []*mat.VecDense) *mat.Dense { relaxedStates[stateIndex] = mat.VecDenseCopyOf(states[stateIndex]) // We also apply some noise to the state to aide in learning network.learningNoiseMethod(network.randomGenerator, relaxedStates[stateIndex], network.learningNoiseScale) - activationfunction.GetActivationFunction(network.domain)(relaxedStates[stateIndex]) + network.domainStateManager.ActivationFunction(relaxedStates[stateIndex]) } // This is the most important call - relax all the states! @@ -124,16 +121,11 @@ func delta(network *HopfieldNetwork, states []*mat.VecDense) *mat.Dense { stateHistory := relaxationResults[stateIndex].StateHistory relaxedState := stateHistory[len(stateHistory)-1] - stateCopy := mat.VecDenseCopyOf(state) - learningmappingfunction.GetLearningMappingFunction(network.domain)(stateCopy) - relaxedCopy := mat.VecDenseCopyOf(relaxedState) - learningmappingfunction.GetLearningMappingFunction(network.domain)(relaxedCopy) - relaxationDifference.Zero() - relaxationDifference.SubVec(stateCopy, relaxedCopy) + relaxationDifference.SubVec(state, relaxedState) stateContribution.Zero() - stateContribution.Outer(0.5, relaxationDifference, stateCopy) + stateContribution.Outer(0.5, relaxationDifference, state) updatedMatrix.Add(updatedMatrix, stateContribution) } diff --git a/hopfieldnetwork/activationfunction/ActivationFunction.go b/hopfieldnetwork/activationfunction/ActivationFunction.go deleted file mode 100644 index a4be47d..0000000 --- a/hopfieldnetwork/activationfunction/ActivationFunction.go +++ /dev/null @@ -1,65 +0,0 @@ -// Definitions of activation functions for the Hopfield Network -// -// Includes map from network domain to activation function -package activationfunction - -import ( - "hmcalister/hopfield/hopfieldnetwork/domain" - - "gonum.org/v1/gonum/mat" -) - -// Defines the activation function as a mapping that alters a vector in place -type ActivationFunction = func(*mat.VecDense) - -func GetActivationFunction(domainEnum domain.DomainEnum) ActivationFunction { - activationFunctionMap := map[domain.DomainEnum]ActivationFunction{ - domain.BipolarDomain: bipolarActivationFunction, - domain.BinaryDomain: binaryActivationFunction, - } - - return activationFunctionMap[domainEnum] -} - -// Apply a binary mapping to the given vector. The vector is modified in place. -// Elements of the vector set according to sign. -// -// # Arguments -// -// - vector *mat.VecDense: The vector to apply a binary mapping to. -func binaryActivationFunction(vector *mat.VecDense) { - for n := 0; n < vector.Len(); n++ { - if vector.AtVec(n) <= 0.0 { - vector.SetVec(n, 0.0) - } else { - vector.SetVec(n, 1.0) - } - } -} - -// Apply a bipolar mapping to the given vector. The vector is modified in place. -// Elements of the vector have their values set to their sign. (0 is mapped to 1.0). -// -// # Arguments -// -// - vector *mat.VecDense: The vector to apply a bipolar mapping to. -func bipolarActivationFunction(vector *mat.VecDense) { - for n := 0; n < vector.Len(); n++ { - if vector.AtVec(n) <= 0.0 { - vector.SetVec(n, -1.0) - } else { - vector.SetVec(n, 1.0) - } - } -} - -func continuousBipolarActivationFunction(vector *mat.VecDense) { - for n := 0; n < vector.Len(); n++ { - if vector.AtVec(n) < -1.0 { - vector.SetVec(n, -1.0) - } - if vector.AtVec(n) > 1.0 { - vector.SetVec(n, 1.0) - } - } -} diff --git a/hopfieldnetwork/domain/DomainEnum.go b/hopfieldnetwork/domain/DomainEnum.go index 3310c90..f9c2674 100644 --- a/hopfieldnetwork/domain/DomainEnum.go +++ b/hopfieldnetwork/domain/DomainEnum.go @@ -1,7 +1,6 @@ package domain // An enum to note the domain of the network. -// This determines the learning mapping function to use. type DomainEnum int const ( diff --git a/hopfieldnetwork/energyfunction/EnergyFunction.go b/hopfieldnetwork/energyfunction/EnergyFunction.go deleted file mode 100644 index 9c7b6b5..0000000 --- a/hopfieldnetwork/energyfunction/EnergyFunction.go +++ /dev/null @@ -1,34 +0,0 @@ -package energyfunction - -import ( - "gonum.org/v1/gonum/mat" -) - -// Defines the energy of a single unit in a vector with respect to a matrix. -func UnitEnergy(matrix *mat.Dense, vector *mat.VecDense, i int) float64 { - dimension, _ := vector.Dims() - energy := 0.0 - for j := 0; j < dimension; j++ { - energy += -1 * matrix.At(i, j) * vector.AtVec(i) * vector.AtVec(j) - } - return energy -} - -// Defines the overall energy for a state with respect to a matrix. -func StateEnergy(matrix *mat.Dense, vector *mat.VecDense) float64 { - energyVector := AllUnitEnergies(matrix, vector) - energy := 0. - for i := 0; i < energyVector.Len(); i++ { - energy += energyVector.AtVec(i) - } - return energy -} - -// Gets all the unit energies of a given vector with respect to a matrix. -func AllUnitEnergies(matrix *mat.Dense, vector *mat.VecDense) *mat.VecDense { - energyVector := mat.NewVecDense(vector.Len(), nil) - energyVector.MulVec(matrix, vector) - energyVector.MulElemVec(energyVector, vector) - energyVector.ScaleVec(-1, energyVector) - return energyVector -} diff --git a/hopfieldnetwork/learningmappingfunction/LearningMappingFunction.go b/hopfieldnetwork/learningmappingfunction/LearningMappingFunction.go deleted file mode 100644 index a522deb..0000000 --- a/hopfieldnetwork/learningmappingfunction/LearningMappingFunction.go +++ /dev/null @@ -1,36 +0,0 @@ -package learningmappingfunction - -import ( - "hmcalister/hopfield/hopfieldnetwork/domain" - - "gonum.org/v1/gonum/mat" -) - -// A learningMappingFunction is a map taking a state vector -// (specifically a target state vector) and maps it into a domain -// that the learning rule can be applied to. For example, the -// bipolar domain requires no mapping as the learning rules already -// support this directly, while the binary domain needs to be mapped -// into the bipolar domain. -type LearningMappingFunction func(*mat.VecDense) - -func GetLearningMappingFunction(domainEnum domain.DomainEnum) LearningMappingFunction { - learningMappingFunctionMap := map[domain.DomainEnum]LearningMappingFunction{ - domain.BipolarDomain: bipolarLearningMappingFunction, - domain.BinaryDomain: binaryLearningMappingFunction, - } - - return learningMappingFunctionMap[domainEnum] -} - -func binaryLearningMappingFunction(vector *mat.VecDense) { - negativeOneVector := mat.NewVecDense(vector.Len(), nil) - for i := 0; i < negativeOneVector.Len(); i++ { - negativeOneVector.SetVec(i, -1.0) - } - vector.AddScaledVec(negativeOneVector, 2, vector) -} - -func bipolarLearningMappingFunction(vector *mat.VecDense) { - -} diff --git a/hopfieldnetwork/states/StateGenerator.go b/hopfieldnetwork/states/stategenerator/StateGenerator.go similarity index 90% rename from hopfieldnetwork/states/StateGenerator.go rename to hopfieldnetwork/states/stategenerator/StateGenerator.go index a815b17..4195921 100644 --- a/hopfieldnetwork/states/StateGenerator.go +++ b/hopfieldnetwork/states/stategenerator/StateGenerator.go @@ -2,8 +2,7 @@ package states import ( - "hmcalister/hopfield/hopfieldnetwork/activationfunction" - "hmcalister/hopfield/hopfieldnetwork/domain" + "hmcalister/hopfield/hopfieldnetwork/states/statemanager" "gonum.org/v1/gonum/mat" "gonum.org/v1/gonum/stat/distuv" @@ -13,9 +12,9 @@ import ( // // Note this struct should be initialized using the StateGeneratorBuilder from [hmcalister/hopfield/hopfieldnetwork/states]. type StateGenerator struct { - domain domain.DomainEnum - rng distuv.Uniform - dimension int + domainStateManager statemanager.StateManager + rng distuv.Uniform + dimension int } // Creates and returns a fresh array that can store a state. @@ -48,7 +47,7 @@ func (gen *StateGenerator) NextState(dataArray []float64) *mat.VecDense { } state := mat.NewVecDense(gen.dimension, dataArray) - activationfunction.GetActivationFunction(gen.domain)(state) + gen.domainStateManager.ActivationFunction(state) return state } diff --git a/hopfieldnetwork/states/StateGeneratorBuilder.go b/hopfieldnetwork/states/stategenerator/StateGeneratorBuilder.go similarity index 95% rename from hopfieldnetwork/states/StateGeneratorBuilder.go rename to hopfieldnetwork/states/stategenerator/StateGeneratorBuilder.go index c1c34c3..cffdf28 100644 --- a/hopfieldnetwork/states/StateGeneratorBuilder.go +++ b/hopfieldnetwork/states/stategenerator/StateGeneratorBuilder.go @@ -2,6 +2,7 @@ package states import ( "hmcalister/hopfield/hopfieldnetwork/domain" + "hmcalister/hopfield/hopfieldnetwork/states/statemanager" "time" "golang.org/x/exp/rand" @@ -124,7 +125,8 @@ func (builder *StateGeneratorBuilder) Build() *StateGenerator { } return &StateGenerator{ - rng: rand_dist, - dimension: builder.dimension, + domainStateManager: statemanager.GetDomainStateManager(builder.domain), + rng: rand_dist, + dimension: builder.dimension, } } diff --git a/hopfieldnetwork/states/statemanager/BinaryStateManager.go b/hopfieldnetwork/states/statemanager/BinaryStateManager.go new file mode 100644 index 0000000..0d0335b --- /dev/null +++ b/hopfieldnetwork/states/statemanager/BinaryStateManager.go @@ -0,0 +1,53 @@ +package statemanager + +import "gonum.org/v1/gonum/mat" + +type BinaryStateManager struct { +} + +func (manager *BinaryStateManager) ActivationFunction(vector *mat.VecDense) { + for n := 0; n < vector.Len(); n++ { + if vector.AtVec(n) <= 0.0 { + vector.SetVec(n, 0.0) + } else { + vector.SetVec(n, 1.0) + } + } +} + +func (manager *BinaryStateManager) InvertState(vector *mat.VecDense) { + onesVector := mat.NewVecDense(vector.Len(), nil) + for i := 0; i < onesVector.Len(); i++ { + onesVector.SetVec(i, 1.0) + } + vector.AddScaledVec(onesVector, -1.0, vector) + manager.ActivationFunction(vector) +} + +// TODO: ENERGIES + +func (manager *BinaryStateManager) UnitEnergy(matrix *mat.Dense, vector *mat.VecDense, i int) float64 { + dimension, _ := vector.Dims() + energy := 0.0 + for j := 0; j < dimension; j++ { + energy += -1 * matrix.At(i, j) * vector.AtVec(i) * vector.AtVec(j) + } + return energy +} + +func (manager *BinaryStateManager) AllUnitEnergies(matrix *mat.Dense, vector *mat.VecDense) []float64 { + energyVector := mat.NewVecDense(vector.Len(), nil) + energyVector.MulVec(matrix, vector) + energyVector.MulElemVec(energyVector, vector) + energyVector.ScaleVec(-1, energyVector) + return energyVector.RawVector().Data +} + +func (manager *BinaryStateManager) StateEnergy(matrix *mat.Dense, vector *mat.VecDense) float64 { + energyVector := manager.AllUnitEnergies(matrix, vector) + energy := 0.0 + for _, unitEnergy := range energyVector { + energy += unitEnergy + } + return energy +} diff --git a/hopfieldnetwork/states/statemanager/BipolarStateManager.go b/hopfieldnetwork/states/statemanager/BipolarStateManager.go new file mode 100644 index 0000000..f3de58b --- /dev/null +++ b/hopfieldnetwork/states/statemanager/BipolarStateManager.go @@ -0,0 +1,47 @@ +package statemanager + +import "gonum.org/v1/gonum/mat" + +type BipolarStateManager struct { +} + +func (manager *BipolarStateManager) ActivationFunction(vector *mat.VecDense) { + for n := 0; n < vector.Len(); n++ { + if vector.AtVec(n) <= 0.0 { + vector.SetVec(n, -1.0) + } else { + vector.SetVec(n, 1.0) + } + } +} + +func (manager *BipolarStateManager) InvertState(vector *mat.VecDense) { + vector.ScaleVec(-1.0, vector) + manager.ActivationFunction(vector) +} + +func (manager *BipolarStateManager) UnitEnergy(matrix *mat.Dense, vector *mat.VecDense, i int) float64 { + dimension, _ := vector.Dims() + energy := 0.0 + for j := 0; j < dimension; j++ { + energy += -1 * matrix.At(i, j) * vector.AtVec(i) * vector.AtVec(j) + } + return energy +} + +func (manager *BipolarStateManager) AllUnitEnergies(matrix *mat.Dense, vector *mat.VecDense) []float64 { + energyVector := mat.NewVecDense(vector.Len(), nil) + energyVector.MulVec(matrix, vector) + energyVector.MulElemVec(energyVector, vector) + energyVector.ScaleVec(-1, energyVector) + return energyVector.RawVector().Data +} + +func (manager *BipolarStateManager) StateEnergy(matrix *mat.Dense, vector *mat.VecDense) float64 { + energyVector := manager.AllUnitEnergies(matrix, vector) + energy := 0.0 + for _, unitEnergy := range energyVector { + energy += unitEnergy + } + return energy +} diff --git a/hopfieldnetwork/states/statemanager/StateManager.go b/hopfieldnetwork/states/statemanager/StateManager.go new file mode 100644 index 0000000..a9b0518 --- /dev/null +++ b/hopfieldnetwork/states/statemanager/StateManager.go @@ -0,0 +1,24 @@ +package statemanager + +import ( + "hmcalister/hopfield/hopfieldnetwork/domain" + + "gonum.org/v1/gonum/mat" +) + +type StateManager interface { + ActivationFunction(*mat.VecDense) + InvertState(*mat.VecDense) + UnitEnergy(*mat.Dense, *mat.VecDense, int) float64 + AllUnitEnergies(*mat.Dense, *mat.VecDense) []float64 + StateEnergy(*mat.Dense, *mat.VecDense) float64 +} + +func GetDomainStateManager(targetDomain domain.DomainEnum) StateManager { + domainStateManagerMap := map[domain.DomainEnum]StateManager{ + domain.BipolarDomain: &BipolarStateManager{}, + domain.BinaryDomain: &BinaryStateManager{}, + } + + return domainStateManagerMap[targetDomain] +} diff --git a/main.go b/main.go index 16fafe8..56358ac 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,7 @@ import ( "hmcalister/hopfield/hopfieldnetwork/datacollector" "hmcalister/hopfield/hopfieldnetwork/domain" "hmcalister/hopfield/hopfieldnetwork/noiseapplication" - "hmcalister/hopfield/hopfieldnetwork/states" + states "hmcalister/hopfield/hopfieldnetwork/states/stategenerator" "hmcalister/hopfield/hopfieldutils" )