Building an RBM in Gorgonia

Now that we've cleaned up our data, created a training or test set, and written the code necessary to produce the input required by our network, we can begin coding up the RBM itself.

First, we begin with our now standard struct, the scaffold onto which we attach the various components of our network:

const cdS = 1
type ggRBM struct {
g *ExprGraph
v *Node // visible units
vB *Node // visible unit biases - same size tensor as v
h *Node // hidden units
hB *Node // hidden unit biases - same size tensor as h
w *Node // connection weights
cdSamples int // number of samples for contrastive divergence - WHAT ABOUT MOMENTUM
}
func (m *ggRBM) learnables() Nodes {
return Nodes{m.w, m.vB, m.hB}
}

Then, we add the helper functions that attach to our RBM:

  1. First, we add func for our ContrastiveDivergence learning algorithm (with Gibbs sampling):
// Uses Gibbs Sampling
func (r *ggRBM) ContrastiveDivergence(input *Node, learnRate float64, k int) {
rows := float64(r.TrainingSize)

// CD-K
phMeans, phSamples := r.SampleHiddenFromVisible(input)
nvSamples := make([]float64, r.Inputs)
// iteration 0

_, nvSamples, nhMeans, nhSamples := r.Gibbs(phSamples, nvSamples)

for step := 1; step < k; step++ {

/*nvMeans*/ _, nvSamples, nhMeans, nhSamples = r.Gibbs(nhSamples, nvSamples)

}

// Update weights
for i := 0; i < r.Outputs; i++ {

for j := 0; j < r.Inputs; j++ {

r.Weights[i][j] += learnRate * (phMeans[i]*input[j] - nhMeans[i]*nvSamples[j]) / rows
}
r.Biases[i] += learnRate * (phSamples[i] - nhMeans[i]) / rows
}

// update hidden biases
for j := 0; j < r.Inputs; j++ {

r.VisibleBiases[j] += learnRate * (input[j] - nvSamples[j]) / rows
}
}
  1. Now, we add functions to sample our respective visible or hidden layers:
func (r *ggRBM) SampleHiddenFromVisible(vInput *Node) (means []float64, samples []float64) {
means = make([]float64, r.Outputs)
samples = make([]float64, r.Outputs)
for i := 0; i < r.Outputs; i++ {
mean := r.PropagateUp(vInput, r.Weights[i], r.Biases[i])
samples[i] = float64(binomial(1, mean))
means[i] = mean
}
return means, samples
}

func (r *ggRBM) SampleVisibleFromHidden(hInput *Node) (means []float64, samples []float64) {
means = make([]float64, r.Inputs)
samples = make([]float64, r.Inputs)
for j := 0; j < r.Inputs; j++ {
mean := r.PropagateDown(hInput, j, r.VisibleBiases[j])
samples[j] = float64(binomial(1, mean))
means[j] = mean
}
return means, samples
}
  1. Next, we add a couple of functions to handle the propagation of weight updates:
func (r *ggRBM) PropagateDown(h *Node, j int, hB *Node) *Node {
retVal := 0.0
for i := 0; i < r.Outputs; i++ {
retVal += r.Weights[i][j] * h0[i]
}
retVal += bias
return sigmoid(retVal)
}

func (r *ggRBM) PropagateUp(v *Node, w *Node, vB *Node) float64 {
retVal := 0.0
for j := 0; j < r.Inputs; j++ {
retVal += weights[j] * v0[j]
}
retVal += bias
return sigmoid(retVal)
}
  1. Now, we add a function for Gibbs sampling (as used in our previous ContrastiveDivergence function) as well as a function to perform the reconstruction step in our network:
func (r *ggRBM) Gibbs(h, v *Node) (vMeans []float64, vSamples []float64, hMeans []float64, hSamples []float64) {
vMeans, vSamples = r.SampleVisibleFromHidden(r.h)
hMeans, hSamples = r.SampleHiddenFromVisible(r.v)
return
}

func (r *ggRBM) Reconstruct(x *Node) *Node {
hiddenLayer := make([]float64, r.Outputs)
retVal := make([]float64, r.Inputs)

for i := 0; i < r.Outputs; i++ {
hiddenLayer[i] = r.PropagateUp(x, r.Weights[i], r.Biases[i])
}

for j := 0; j < r.Inputs; j++ {
activated := 0.0
for i := 0; i < r.Outputs; i++ {
activated += r.Weights[i][j] * hiddenLayer[i]
}
activated += r.VisibleBiases[j]
retVal[j] = sigmoid(activated)
}
return retVal
}
  1. After that, we add the function that instantiates our RBM:
func newggRBM(g *ExprGraph, cdS int) *ggRBM {

vT := tensor.New(tensor.WithBacking(tensor.Random(tensor.Int, 3952)), tensor.WithShape(3952, 1))

v := NewMatrix(g,
tensor.Int,
WithName("v"),
WithShape(3952, 1),
WithValue(vT),
)

hT := tensor.New(tensor.WithBacking(tensor.Random(tensor.Int, 200)), tensor.WithShape(200, 1))

h := NewMatrix(g,
tensor.Int,
WithName("h"),
WithShape(200, 1),
WithValue(hT),
)

wB := tensor.Random(tensor.Float64, 3952*200)
wT := tensor.New(tensor.WithBacking(wB), tensor.WithShape(3952*200, 1))
w := NewMatrix(g,
tensor.Float64,
WithName("w"),
WithShape(3952*200, 1),
WithValue(wT),
)

return &ggRBM{
g: g,
v: v,
h: h,
w: w,
// hB: hB,
// vB: vB,
cdSamples: cdS,
}
}
  1. Finally, we train the model:
func main() {
g := NewGraph()
m := newggRBM(g, cdS)
data, err := ReadDataFile(datasetfilename)
if err != nil {
log.Fatal(err)
}
fmt.Println("Data read from CSV: ", data)
vm := NewTapeMachine(g, BindDualValues(m.learnables()...))
// solver := NewVanillaSolver(WithLearnRate(1.0))
for i := 0; i < 1; i++ {
if vm.RunAll() != nil {
log.Fatal(err)
}
}
}

Before we execute the code, we need to preprocess our data a little bit. This is because the delimiter used in our dataset is :: but we want to change it to ,. The repository for this chapter includes preprocess.sh in the root of the folder, which does the following for us:

#!/bin/bash
export LC_CTYPE=C
export LANG=C
cat ratings.dat | sed 's/::/,/g' > cleanratings.csv
cat movies.dat | sed 's/,//g; s/::/,/g' > cleanmovies.csv

Now that we have our data formatted nicely, let's execute the code for our RBM and observe the output as follows:

Here, we see our data import functions processing the ratings and movie index files, as well as building the per-user vectors of a length of 3706 that index all the users' ratings (normalized to 0/1): 

After the training phase is complete (here, it is set at 1,000 iterations), the RBM generates a set of recommendations for a randomly selected user.

You can now experiment with the different hyperparameters and try feeding in your own data!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset