func (l *Logistic) RestoreFromFile(path string) error {
	if path == "" {
		return fmt.Errorf("ERROR: you just tried to restore your model from a file with no path! That's a no-no. Try it with a valid filepath")
	}
	bytes, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}
	err = json.Unmarshal(bytes, &l.Parameters)
	if err != nil {
		return err
	}
	return nil
}
RestoreFromFile takes in a path to a parameter vector theta
and assigns the model it's operating on's parameter vector
to that.

The path must ba an absolute path or a path from the current
directory

This would be useful in persisting data between running
a model on data, or for graphing a dataset with a fit in
another framework like Julia/Gadfly.
package linear
import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"math"
	"os"
	"github.com/cdipaolo/goml/base"
)
type Logistic struct {
	alpha          float64
	regularization float64
	maxIterations  int
	method base.OptimizationMethod
	trainingSet     [][]float64
	expectedResults []float64
	Parameters []float64 `json:"theta"`
	Output io.Writer
}
func NewLogistic(method base.OptimizationMethod, alpha, regularization float64, maxIterations int, trainingSet [][]float64, expectedResults []float64, features ...int) *Logistic {
	var params []float64
	if len(features) != 0 {
		params = make([]float64, features[0]+1)
	} else if trainingSet == nil || len(trainingSet) == 0 {
		params = []float64{}
	} else {
		params = make([]float64, len(trainingSet[0])+1)
	}
	return &Logistic{
		alpha:          alpha,
		regularization: regularization,
		maxIterations:  maxIterations,
		method: method,
		trainingSet:     trainingSet,
		expectedResults: expectedResults,
		Parameters: params,
		Output: os.Stdout,
	}
}
func (l *Logistic) UpdateTrainingSet(trainingSet [][]float64, expectedResults []float64) error {
	if len(trainingSet) == 0 {
		return fmt.Errorf("Error: length of given training set is 0! Need data!")
	}
	if len(expectedResults) == 0 {
		return fmt.Errorf("Error: length of given result data set is 0! Need expected results!")
	}
	l.trainingSet = trainingSet
	l.expectedResults = expectedResults
	return nil
}
func (l *Logistic) UpdateLearningRate(a float64) {
	l.alpha = a
}
func (l *Logistic) LearningRate() float64 {
	return l.alpha
}
func (l *Logistic) Examples() int {
	return len(l.trainingSet)
}
func (l *Logistic) MaxIterations() int {
	return l.maxIterations
}
func (l *Logistic) Predict(x []float64, normalize ...bool) ([]float64, error) {
	if len(x)+1 != len(l.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(l.Parameters))
	}
	if len(normalize) != 0 && normalize[0] {
		base.NormalizePoint(x)
	}
	sum := l.Parameters[0]
	for i := range x {
		sum += x[i] * l.Parameters[i+1]
	}
	result := 1 / (1 + math.Exp(-sum))
	return []float64{result}, nil
}
func (l *Logistic) Learn() error {
	if l.trainingSet == nil || l.expectedResults == nil {
		err := fmt.Errorf("ERROR: Attempting to learn with no training examples!\n")
		fmt.Fprintf(l.Output, err.Error())
		return err
	}
	examples := len(l.trainingSet)
	if examples == 0 || len(l.trainingSet[0]) == 0 {
		err := fmt.Errorf("ERROR: Attempting to learn with no training examples!\n")
		fmt.Fprintf(l.Output, err.Error())
		return err
	}
	if len(l.expectedResults) == 0 {
		err := fmt.Errorf("ERROR: Attempting to learn with no expected results! This isn't an unsupervised model!! You'll need to include data before you learn :)\n")
		fmt.Fprintf(l.Output, err.Error())
		return err
	}
	fmt.Fprintf(l.Output, "Training:\n\tModel: Logistic (Binary) Classification\n\tOptimization Method: %v\n\tTraining Examples: %v\n\tFeatures: %v\n\tLearning Rate α: %v\n\tRegularization Parameter λ: %v\n...\n\n", l.method, examples, len(l.trainingSet[0]), l.alpha, l.regularization)
	var err error
	if l.method == base.BatchGA {
		err = base.GradientAscent(l)
	} else if l.method == base.StochasticGA {
		err = base.StochasticGradientAscent(l)
	} else {
		err = fmt.Errorf("Chose a training method not implemented for Logistic regression")
	}
	if err != nil {
		fmt.Fprintf(l.Output, "\nERROR: Error while learning –\n\t%v\n\n", err)
		return err
	}
	fmt.Fprintf(l.Output, "Training Completed.\n%v\n\n", l)
	return nil
}
func (l *Logistic) 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(l.Output, "Training:\n\tModel: Logistic (Binary) Classifier\n\tOptimization Method: Online Stochastic Gradient Descent\n\tFeatures: %v\n\tLearning Rate α: %v\n...\n\n", len(l.Parameters), l.alpha)
	norm := len(normalize) != 0 && normalize[0]
	var point base.Datapoint
	var more bool
	for {
		point, more = <-dataset
		if more {
			if len(point.Y) != 1 {
				errors <- fmt.Errorf("ERROR: point.Y must have a length of 1. Point: %v", point)
			}
			if norm {
				base.NormalizePoint(point.X)
			}
			newTheta := make([]float64, len(l.Parameters))
			for j := range l.Parameters {
				dj, err := func(point base.Datapoint, j int) (float64, error) {
					prediction, err := l.Predict(point.X)
					if err != nil {
						return 0, err
					}
					var x float64
					if j == 0 {
						x = 1
					} else {
						x = point.X[j-1]
					}
					var gradient float64
					gradient = (point.Y[0] - prediction[0]) * x
					if j != 0 {
						gradient += l.regularization * l.Parameters[j]
					}
					return gradient, nil
				}(point, j)
				if err != nil {
					errors <- err
					continue
				}
				newTheta[j] = l.Parameters[j] + l.alpha*dj
			}
			for j := range l.Parameters {
				newθ := newTheta[j]
				if math.IsInf(newθ, 0) || math.IsNaN(newθ) {
					errors <- fmt.Errorf("Sorry! Learning diverged. Some value of the parameter vector theta is ±Inf or NaN")
					continue
				}
				l.Parameters[j] = newθ
			}
			go onUpdate([][]float64{l.Parameters})
		} else {
			fmt.Fprintf(l.Output, "Training Completed.\n%v\n\n", l)
			close(errors)
			return
		}
	}
}
func (l *Logistic) String() string {
	features := len(l.Parameters) - 1
	if len(l.Parameters) == 0 {
		fmt.Fprintf(l.Output, "ERROR: Attempting to print model with the 0 vector as it's parameter vector! Train first!\n")
	}
	var buffer bytes.Buffer
	buffer.WriteString(fmt.Sprintf("h(θ,x) = 1 / (1 + exp(-θx))\nθx = %.3f + ", l.Parameters[0]))
	length := features + 1
	for i := 1; i < length; i++ {
		buffer.WriteString(fmt.Sprintf("%.5f(x[%d])", l.Parameters[i], i))
		if i != features {
			buffer.WriteString(fmt.Sprintf(" + "))
		}
	}
	return buffer.String()
}
func (l *Logistic) Dj(j int) (float64, error) {
	if j > len(l.Parameters)-1 {
		return 0, fmt.Errorf("J (%v) would index out of the bounds of the training set data (len: %v)", j, len(l.Parameters))
	}
	var sum float64
	for i := range l.trainingSet {
		prediction, err := l.Predict(l.trainingSet[i])
		if err != nil {
			return 0, err
		}
		var x float64
		if j == 0 {
			x = 1
		} else {
			x = l.trainingSet[i][j-1]
		}
		sum += (l.expectedResults[i] - prediction[0]) * x
	}
	if j != 0 {
		sum += l.regularization * l.Parameters[j]
	}
	return sum, nil
}
func (l *Logistic) Dij(i int, j int) (float64, error) {
	prediction, err := l.Predict(l.trainingSet[i])
	if err != nil {
		return 0, err
	}
	var x float64
	if j == 0 {
		x = 1
	} else {
		x = l.trainingSet[i][j-1]
	}
	var gradient float64
	gradient = (l.expectedResults[i] - prediction[0]) * x
	if j != 0 {
		gradient += l.regularization * l.Parameters[j]
	}
	return gradient, nil
}
func (l *Logistic) Theta() []float64 {
	return l.Parameters
}
func (l *Logistic) PersistToFile(path string) error {
	if path == "" {
		return fmt.Errorf("ERROR: you just tried to persist your model to a file with no path!! That's a no-no. Try it with a valid filepath")
	}
	bytes, err := json.Marshal(l.Parameters)
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(path, bytes, os.ModePerm)
	if err != nil {
		return err
	}
	return nil
}