Backpropagation

For the convnet to learn, what is required is backpropagation, which propagates the errors, and a gradient descent function to update the weight matrices. To do this is relatively simple with Gorgonia, so simple that we can actually put it into our main function without impacting understandability:

func main() {
flag.Parse()
parseDtype()
imgs, err := readImageFile(os.Open("train-images-idx3-ubyte"))
if err != nil {
log.Fatal(err)
}
labels, err := readLabelFile(os.Open("train-labels-idx1-ubyte"))
if err != nil {
log.Fatal(err)
}

inputs := prepareX(imgs)
targets := prepareY(labels)

// the data is in (numExamples, 784).
// In order to use a convnet, we need to massage the data
// into this format (batchsize, numberOfChannels, height, width).
//
// This translates into (numExamples, 1, 28, 28).
//
// This is because the convolution operators actually understand height and width.
//
// The 1 indicates that there is only one channel (MNIST data is black and white).
numExamples := inputs.Shape()[0]
bs := *batchsize

if err := inputs.Reshape(numExamples, 1, 28, 28); err != nil {
log.Fatal(err)
}
g := gorgonia.NewGraph()
x := gorgonia.NewTensor(g, dt, 4, gorgonia.WithShape(bs, 1, 28, 28), gorgonia.WithName("x"))
y := gorgonia.NewMatrix(g, dt, gorgonia.WithShape(bs, 10), gorgonia.WithName("y"))
m := newConvNet(g)
if err = m.fwd(x); err != nil {
log.Fatalf("%+v", err)
}
losses := gorgonia.Must(gorgonia.HadamardProd(m.out, y))
cost := gorgonia.Must(gorgonia.Mean(losses))
cost = gorgonia.Must(gorgonia.Neg(cost))

// we wanna track costs
var costVal gorgonia.Value
gorgonia.Read(cost, &costVal)

if _, err = gorgonia.Grad(cost, m.learnables()...); err != nil {
log.Fatal(err)
}

For the errors, we use a simple cross-entropy by multiplying the expected output element-wise and then averaging it, as shown in this snippet:

    losses := gorgonia.Must(gorgonia.HadamardProd(m.out, y))
cost := gorgonia.Must(gorgonia.Mean(losses))
cost = gorgonia.Must(gorgonia.Neg(cost))

Following that, we simply call gorgonia.Grad(cost, m.learnables()...), which performs symbolic backpropagation. What is m.learnables()?, you may ask. It's simply the variables that we wish the machine to learn. The definition is as such:

func (m *convnet) learnables() gorgonia.Nodes {
return gorgonia.Nodes{m.w0, m.w1, m.w2, m.w3, m.w4}
}

Again, it's fairly simple.

One additional comment I want the reader to note is gorgonia.Read(cost, &costVal). Read is one of the more confusing parts of Gorgonia. But when framed correctly, it is quite simple to understand.

Earlier, in the section Describing a neural network, I likened Gorgonia to writing in another programming language. If so, then Read is the equivalent of io.WriteFile. What gorgonia.Read(cost, &costVal) says is that when the mathematical expression gets evaluated, make a copy of the result of cost and store it in costVal. This is necessary because of the way mathematical expressions are evaluated within the Gorgonia system.

Why is it called Read instead of Write? I initially modeled Gorgonia to be quite monadic (in the Haskell sense of monad), and as a result, one would read out a value. After a span of three years, the name sort of stuck.
..................Content has been hidden....................

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