func (p *Perceptron) OnlineLearn(errors chan error, dataset chan base.Datapoint, onUpdate func([][]float64), normalize ...bool) {
	if errors == nil {
		errors = make(chan error)
	}
	if dataset == nil {
		errors <- fmt.Errorf("ERROR: Attempting to learn with a nil data stream!\n")
		close(errors)
		return
	}
	fmt.Fprintf(p.Output, "Training:\n\tModel: Perceptron Classifier\n\tOptimization Method: Online Perceptron\n\tFeatures: %v\n\tLearning Rate α: %v\n...\n\n", len(p.Parameters), p.alpha)
	norm := len(normalize) != 0 && normalize[0]
	var point base.Datapoint
	var more bool
	for {
		point, more = <-dataset
		if more {
			if norm {
				base.NormalizePoint(point.X)
			}
			guess, err := p.Predict(point.X)
			if err != nil {
				errors <- err
				continue
			}
			if len(point.Y) != 1 {
				errors <- fmt.Errorf("The binary perceptron model requires that the data results (y) have length 1 - given %v", len(point.Y))
				continue
			}
			if len(point.X) != len(p.Parameters)-1 {
				errors <- fmt.Errorf("The binary perceptron model requires that the length of input data (currently %v) be one less than the length of the parameter vector (%v)", len(point.X), len(p.Parameters))
				continue
			}
			if guess[0] != point.Y[0] {
				p.Parameters[0] += p.alpha * (point.Y[0] - guess[0])
				for i := 1; i < len(p.Parameters); i++ {
					p.Parameters[i] += p.alpha * (point.Y[0] - guess[0]) * point.X[i-1]
				}
				go onUpdate([][]float64{p.Parameters})
			}
		} else {
			fmt.Fprintf(p.Output, "Training Completed.\n%v\n\n", p)
			close(errors)
			return
		}
	}
}
OnlineLearn runs off of the datastream within the Perceptron
structure. Whenever the model makes a wrong prediction
the parameter vector theta is updated to reflect that,
as discussed in the documentation for the Perceptron
struct itself, and the OnUpdate function is called with
the newly updated parameter vector. Learning will stop
when the data channel is closed and all remaining
datapoints within the channel have been read.

The errors channel will be closed when learning is
completed so you know when it's done if you're relying
on that for whatever reason

onUpdate func ([]float64):

onUpdate is a function that is called whenever
the perceptron updates it's parameter vector
theta. This acts almost like a callback and
passes the newly updated parameter vector
theta as a slice of floats.

This might be useful is you want to maintain
an up to date persisted model in a database of
your choosing and you'd like to update it
constantly.

This will be spawned into a new goroutine, so
don't worry about the function taking a long
time, or blocking.

If you want to monitor errors happening within
this function, just have a channel of errors
you send do within this channel, or some other
method if it fits your scenario better.

NOTE that there is an optional last parameter which,
when true, will normalize all data given on the
stream. This will potentially help gradient descent
converge faster. This is given as a parameter because
you won't have direct access to the dataset before
hand like you would in batch/stochastic settings.

Example Online, Binary Perceptron (no layers, etc.):

// create the channel of data and errors
stream := make(chan base.Datapoint, 100)
errors := make(chan error)

model := NewPerceptron(0.1, 1, stream)

go model.OnlineLearn(errors, stream, func (theta []float64) {
// do something with the new theta (persist
// to database?) in here.
fmt.Fprintf(p.Output, "Theta updated to %v!\n", theta)
})

// start passing data to our datastream
//
// we could have data already in our channel
// when we instantiated the Perceptron, though
//
// and note that this data could be coming from
// some web server, or whatever!!
go func() {
for i := -500.0; abs(i) > 1; i *= -0.997 {
if 10 + (i-20)/2 > 0 {
stream <- base.Datapoint{
X: []float64{i-20},
Y: []float64{1.0},
}
} else {
stream <- base.Datapoint{
X: []float64{i-20},
Y: []float64{0},
}
}
}
}()

// close the dataset
close(stream)
for {
err, more := <- errors
if err != nil {
fmt.Fprintf(p.Output, "Error passed: %v", err)
} else {
// training is done!
break
}
}

// now you can predict!!
// note that guess is a []float64 of len() == 1
// when it isn't nil
guess, err := model.Predict([]float64{i})
if err != nil {
panic("EGATZ!! I FOUND AN ERROR! BETTER CHECK YOUR INPUT DIMENSIONS!")
}
package perceptron
import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"github.com/cdipaolo/goml/base"
)
type Perceptron struct {
	alpha float64
	Parameters []float64 `json:"theta"`
	Output io.Writer
}
func NewPerceptron(alpha float64, features int) *Perceptron {
	var params []float64
	params = make([]float64, features+1)
	return &Perceptron{
		alpha: alpha,
		Parameters: params,
		Output:     os.Stdout,
	}
}
func (p *Perceptron) UpdateLearningRate(a float64) {
	p.alpha = a
}
func (p *Perceptron) Predict(x []float64, normalize ...bool) ([]float64, error) {
	if len(x)+1 != len(p.Parameters) {
		return nil, fmt.Errorf("Error: Parameter vector should be 1 longer than input vector!\n\tLength of x given: %v\n\tLength of parameters: %v\n", len(x), len(p.Parameters))
	}
	if len(normalize) != 0 && normalize[0] {
		base.NormalizePoint(x)
	}
	sum := p.Parameters[0]
	for i := range x {
		sum += x[i] * p.Parameters[i+1]
	}
	result := -1.0
	if sum > 0 {
		result = 1
	}
	return []float64{result}, nil
}