func (k *KMeans) 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, &k.Centroids)
	if err != nil {
		return err
	}
	return nil
}
RestoreFromFile takes in a path to a centroid vector
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 cluster
import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"os"
	"time"
	"github.com/cdipaolo/goml/base"
)
func diff(u, v []float64) float64 {
	var sum float64
	for i := range u {
		sum += (u[i] - v[i]) * (u[i] - v[i])
	}
	return sum
}
type KMeans struct {
	maxIterations int
	alpha float64
	trainingSet [][]float64
	guesses     []int
	Centroids [][]float64 `json:"centroids"`
	Output io.Writer
}
type OnlineParams struct {
	Alpha    float64
	Features int
}
func NewKMeans(k, maxIterations int, trainingSet [][]float64, params ...OnlineParams) *KMeans {
	var features int
	if len(params) != 0 {
		features = params[0].Features
	} else if len(trainingSet) != 0 {
		features = len(trainingSet[0])
	}
	alpha := 0.5
	if len(params) != 0 {
		alpha = params[0].Alpha
	}
	var guesses []int
	guesses = make([]int, len(trainingSet))
	rand.Seed(time.Now().UTC().Unix())
	centroids := make([][]float64, k)
	for i := range centroids {
		centroids[i] = make([]float64, features)
		for j := range centroids[i] {
			centroids[i][j] = 10 * (rand.Float64() - 0.5)
		}
	}
	return &KMeans{
		maxIterations: maxIterations,
		alpha: alpha,
		trainingSet: trainingSet,
		guesses:     guesses,
		Centroids: centroids,
		Output:    os.Stdout,
	}
}
func (k *KMeans) UpdateTrainingSet(trainingSet [][]float64) error {
	if len(trainingSet) == 0 {
		return fmt.Errorf("Error: length of given training set is 0! Need data!")
	}
	k.trainingSet = trainingSet
	k.guesses = make([]int, len(trainingSet))
	return nil
}
func (k *KMeans) UpdateLearningRate(a float64) {
	k.alpha = a
}
func (k *KMeans) LearningRate() float64 {
	return k.alpha
}
func (k *KMeans) Examples() int {
	return len(k.trainingSet)
}
func (k *KMeans) MaxIterations() int {
	return k.maxIterations
}
func (k *KMeans) Predict(x []float64, normalize ...bool) ([]float64, error) {
	if len(x) != len(k.Centroids[0]) {
		return nil, fmt.Errorf("Error: Centroid vector should be the same length as input vector!\n\tLength of x given: %v\n\tLength of centroid: %v\n", len(x), len(k.Centroids[0]))
	}
	if len(normalize) != 0 && normalize[0] {
		base.NormalizePoint(x)
	}
	var guess int
	minDiff := diff(x, k.Centroids[0])
	for j := 1; j < len(k.Centroids); j++ {
		difference := diff(x, k.Centroids[j])
		if difference < minDiff {
			minDiff = difference
			guess = j
		}
	}
	return []float64{float64(guess)}, nil
}
func (k *KMeans) Learn() error {
	if k.trainingSet == nil {
		err := fmt.Errorf("ERROR: Attempting to learn with no training examples!\n")
		fmt.Fprintf(k.Output, err.Error())
		return err
	}
	examples := len(k.trainingSet)
	if examples == 0 || len(k.trainingSet[0]) == 0 {
		err := fmt.Errorf("ERROR: Attempting to learn with no training examples!\n")
		fmt.Fprintf(k.Output, err.Error())
		return err
	}
	centroids := len(k.Centroids)
	features := len(k.trainingSet[0])
	fmt.Fprintf(k.Output, "Training:\n\tModel: K-Means++ Classification\n\tTraining Examples: %v\n\tFeatures: %v\n\tClasses: %v\n...\n\n", examples, features, centroids)
	k.Centroids[0] = k.trainingSet[rand.Intn(len(k.trainingSet))]
	distances := make([]float64, len(k.trainingSet))
	for i := 1; i < len(k.Centroids); i++ {
		var sum float64
		for j, x := range k.trainingSet {
			minDiff := diff(x, k.Centroids[0])
			for l := 1; l < i; l++ {
				difference := diff(x, k.Centroids[l])
				if difference < minDiff {
					minDiff = difference
				}
			}
			distances[j] = minDiff * minDiff
			sum += distances[j]
		}
		target := rand.Float64() * sum
		j := 0
		for sum = distances[0]; sum < target; sum += distances[j] {
			j++
		}
		k.Centroids[i] = k.trainingSet[j]
	}
	iter := 0
	for ; iter < k.maxIterations; iter++ {
		classTotal := make([][]float64, centroids)
		classCount := make([]int64, centroids)
		for j := range k.Centroids {
			classTotal[j] = make([]float64, features)
		}
		for i, x := range k.trainingSet {
			k.guesses[i] = 0
			minDiff := diff(x, k.Centroids[0])
			for j := 1; j < len(k.Centroids); j++ {
				difference := diff(x, k.Centroids[j])
				if difference < minDiff {
					minDiff = difference
					k.guesses[i] = j
				}
			}
			classCount[k.guesses[i]]++
			for j := range x {
				classTotal[k.guesses[i]][j] += x[j]
			}
		}
		newCentroids := append([][]float64{}, k.Centroids...)
		for j := range k.Centroids {
			if classCount[j] == 0 {
				for l := range k.Centroids[j] {
					k.Centroids[j][l] = 10 * (rand.Float64() - 0.5)
				}
				continue
			}
			for l := range k.Centroids[j] {
				k.Centroids[j][l] = classTotal[j][l] / float64(classCount[j])
			}
		}
		if len(newCentroids) != len(k.Centroids) {
			k.Centroids = newCentroids
		}
	}
	fmt.Fprintf(k.Output, "Training Completed in %v iterations.\n%v\n", iter, k)
	return nil
}
func (k *KMeans) 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
	}
	centroids := len(k.Centroids)
	features := len(k.Centroids[0])
	fmt.Fprintf(k.Output, "Training:\n\tModel: Online K-Means Classification\n\tFeatures: %v\n\tClasses: %v\n...\n\n", features, centroids)
	var point base.Datapoint
	var more bool
	oneMinusAlpha := 1.0 - k.alpha
	for {
		point, more = <-dataset
		if more {
			if len(point.X) != features {
				errors <- fmt.Errorf("ERROR: point.X must have the same dimensions as clusters (len %v). Point: %v", point)
			}
			minDiff := diff(point.X, k.Centroids[0])
			c := 0
			for j := 1; j < len(k.Centroids); j++ {
				difference := diff(point.X, k.Centroids[j])
				if difference < minDiff {
					minDiff = difference
					c = j
				}
			}
			for i := range k.Centroids[c] {
				k.Centroids[c][i] = k.alpha*point.X[i] + oneMinusAlpha*k.Centroids[c][i]
			}
			go onUpdate([][]float64{[]float64{float64(c)}, k.Centroids[c]})
		} else {
			fmt.Fprintf(k.Output, "Training Completed.\n%v\n\n", k)
			close(errors)
			return
		}
	}
}
func (k *KMeans) String() string {
	return fmt.Sprintf("h(θ,x) = argmin_j | x[i] - μ[j] |^2\n\tμ = %v", k.Centroids)
}
func (k *KMeans) Guesses() []int {
	return k.guesses
}
func (k *KMeans) Distortion() float64 {
	var sum float64
	for i := range k.trainingSet {
		sum += diff(k.trainingSet[i], k.Centroids[int(k.guesses[i])])
	}
	return sum
}
func (k *KMeans) SaveClusteredData(filepath string) error {
	floatGuesses := []float64{}
	for _, val := range k.guesses {
		floatGuesses = append(floatGuesses, float64(val))
	}
	return base.SaveDataToCSV(filepath, k.trainingSet, floatGuesses, true)
}
func (k *KMeans) 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(k.Centroids)
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(path, bytes, os.ModePerm)
	if err != nil {
		return err
	}
	return nil
}