After we have got our sampled encoding, we then feed it to our decoder, which is essentially the same structure as our encoder, but in reverse. The arrangement looks a little like this:
The actual implementation in Gorgonia looks like the following:
// Decoding - Part 3
if c5, err = gorgonia.Mul(sz, m.w5); err != nil {
return errors.Wrap(err, "Layer 5 Convolution failed")
}
if l5, err = gorgonia.Rectify(c5); err != nil {
return errors.Wrap(err, "Layer 5 activation failed")
}
log.Printf("l6 shape %v", l1.Shape())
if c6, err = gorgonia.Mul(l5, m.w6); err != nil {
return errors.Wrap(err, "Layer 6 Convolution failed")
}
if l6, err = gorgonia.Rectify(c6); err != nil {
return errors.Wrap(err, "Layer 6 activation failed")
}
log.Printf("l6 shape %v", l6.Shape())
if c7, err = gorgonia.Mul(l6, m.w7); err != nil {
return errors.Wrap(err, "Layer 7 Convolution failed")
}
if l7, err = gorgonia.Sigmoid(c7); err != nil {
return errors.Wrap(err, "Layer 7 activation failed")
}
log.Printf("l7 shape %v", l7.Shape())
We put a Sigmoid activation on the last layer, as we want the output to be more continuous than ReLU usually provides.