func (gdpe *gradientDescentParameterEstimator) Estimate(initialParameters []float64) ([]float64, error) {
	if gdpe.trainingSet == nil {
		return nil, gdeErrors.NewUntrainedEstimatorError()
	}

	if len(initialParameters) == 0 {
		return nil, gdeErrors.NewEmptyInitialParametersError()
	}

	gradient := func(guess []float64) ([]float64, error) {
		sumLossGradient := make([]float64, len(initialParameters))

		for i := 0; i < gdpe.trainingSet.NumRows(); i++ {
			row, _ := gdpe.trainingSet.Row(i)
			features, _ := row.Features().(slice.FloatSlice)
			target, _ := row.Target().(slice.FloatSlice)
			x := features.Values()
			y := target.Values()[0]

			lossGradient, err := gdpe.plgf(guess, x, y)
			if err != nil {
				return nil, err
			}
			sumLossGradient = vectorutilities.Add(sumLossGradient, lossGradient)
		}

		return sumLossGradient, nil
	}

	return gradientdescent.GradientDescent(initialParameters, gdpe.learningRate, gdpe.precision, gdpe.maxIterations, gradient)
}
var _ = Describe("GradientDescent", func() {
	var goodGradient func([]float64) ([]float64, error)

	BeforeEach(func() {
		goodGradient = func(x []float64) ([]float64, error) {
			g := make([]float64, len(x))
			for i, xi := range x {
				g[i] = 2 * xi
			}
			return g, nil
		}
	})

	Context("When given an empty initial guess", func() {
		It("Returns an error", func() {
			_, err := gradientdescent.GradientDescent([]float64{}, 0.05, 0.0005, 100000, goodGradient)
			Ω(err).Should(HaveOccurred())
		})
	})

	Context("When the given gradient function returns an error", func() {
		It("Returns an error", func() {
			badGradient := func(x []float64) ([]float64, error) {
				return nil, errors.New("I'm bad")
			}
			_, err := gradientdescent.GradientDescent([]float64{0.3, -0.4}, 0.05, 0.0005, 100000, badGradient)
			Ω(err).Should(HaveOccurred())
		})
	})

	Context("When given reasonable inputs", func() {