mirror of
https://github.com/gonum/gonum.git
synced 2026-04-23 00:37:29 +08:00
stat/distuv: add ScoreInput implementations for Uniform and Triangle
Also fixes a bug in Laplace ScoreInput, and tests existing ScoreInput implementations.
This commit is contained in:
committed by
GitHub
parent
48db94ddb5
commit
c8de933feb
+40
-12
@@ -193,6 +193,7 @@ func parametersEqual(p1, p2 []Parameter, tol float64) bool {
|
||||
type derivParamTester interface {
|
||||
LogProb(x float64) float64
|
||||
Score(deriv []float64, x float64) []float64
|
||||
ScoreInput(x float64) float64
|
||||
Quantile(p float64) float64
|
||||
NumParameters() int
|
||||
parameters([]Parameter) []Parameter
|
||||
@@ -206,14 +207,33 @@ func testDerivParam(t *testing.T, d derivParamTester) {
|
||||
quantiles := make([]float64, nTest)
|
||||
floats.Span(quantiles, 0.1, 0.9)
|
||||
|
||||
deriv := make([]float64, d.NumParameters())
|
||||
fdDeriv := make([]float64, d.NumParameters())
|
||||
scoreInPlace := make([]float64, d.NumParameters())
|
||||
fdDerivParam := make([]float64, d.NumParameters())
|
||||
|
||||
if !panics(func() { d.Score(make([]float64, d.NumParameters()+1), 0) }) {
|
||||
t.Errorf("Expected panic for wrong derivative slice length")
|
||||
}
|
||||
if !panics(func() { d.parameters(make([]Parameter, d.NumParameters()+1)) }) {
|
||||
t.Errorf("Expected panic for wrong parameter slice length")
|
||||
}
|
||||
|
||||
initParams := d.parameters(nil)
|
||||
tooLongParams := make([]Parameter, len(initParams)+1)
|
||||
copy(tooLongParams, initParams)
|
||||
if !panics(func() { d.setParameters(tooLongParams) }) {
|
||||
t.Errorf("Expected panic for wrong parameter slice length")
|
||||
}
|
||||
badNameParams := make([]Parameter, len(initParams))
|
||||
copy(badNameParams, initParams)
|
||||
const badName = "__badName__"
|
||||
for i := 0; i < len(initParams); i++ {
|
||||
badNameParams[i].Name = badName
|
||||
if !panics(func() { d.setParameters(badNameParams) }) {
|
||||
t.Errorf("Expected panic for wrong %d-th parameter name", i)
|
||||
}
|
||||
badNameParams[i].Name = initParams[i].Name
|
||||
}
|
||||
|
||||
init := make([]float64, d.NumParameters())
|
||||
for i, v := range initParams {
|
||||
init[i] = v.Value
|
||||
@@ -221,11 +241,11 @@ func testDerivParam(t *testing.T, d derivParamTester) {
|
||||
for _, v := range quantiles {
|
||||
d.setParameters(initParams)
|
||||
x := d.Quantile(v)
|
||||
gotDeriv := d.Score(deriv, x)
|
||||
if &gotDeriv[0] != &deriv[0] {
|
||||
t.Errorf("Returned a different derivative slice than passed in. Got %v, want %v", gotDeriv, deriv)
|
||||
score := d.Score(scoreInPlace, x)
|
||||
if &score[0] != &scoreInPlace[0] {
|
||||
t.Errorf("Returned a different derivative slice than passed in. Got %v, want %v", score, scoreInPlace)
|
||||
}
|
||||
f := func(p []float64) float64 {
|
||||
logProbParams := func(p []float64) float64 {
|
||||
params := d.parameters(nil)
|
||||
for i, v := range p {
|
||||
params[i].Value = v
|
||||
@@ -233,14 +253,22 @@ func testDerivParam(t *testing.T, d derivParamTester) {
|
||||
d.setParameters(params)
|
||||
return d.LogProb(x)
|
||||
}
|
||||
fd.Gradient(fdDeriv, f, init, nil)
|
||||
if !floats.EqualApprox(deriv, fdDeriv, 1e-6) {
|
||||
t.Errorf("Derivative mismatch at x = %g. Want %v, got %v", x, fdDeriv, deriv)
|
||||
fd.Gradient(fdDerivParam, logProbParams, init, nil)
|
||||
if !floats.EqualApprox(scoreInPlace, fdDerivParam, 1e-6) {
|
||||
t.Errorf("Score mismatch at x = %g. Want %v, got %v", x, fdDerivParam, scoreInPlace)
|
||||
}
|
||||
d.setParameters(initParams)
|
||||
d2 := d.Score(nil, x)
|
||||
if !floats.EqualApprox(d2, deriv, 1e-14) {
|
||||
t.Errorf("Derivative mismatch when input nil Want %v, got %v", d2, deriv)
|
||||
score2 := d.Score(nil, x)
|
||||
if !floats.EqualApprox(score2, scoreInPlace, 1e-14) {
|
||||
t.Errorf("Score mismatch when input nil Want %v, got %v", score2, scoreInPlace)
|
||||
}
|
||||
logProbInput := func(x2 float64) float64 {
|
||||
return d.LogProb(x2)
|
||||
}
|
||||
scoreInput := d.ScoreInput(x)
|
||||
fdDerivInput := fd.Derivative(logProbInput, x, nil)
|
||||
if !absEqTol(scoreInput, fdDerivInput, 1e-6) {
|
||||
t.Errorf("ScoreInput mismatch at x = %g. Want %v, got %v", x, fdDerivInput, scoreInput)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,11 +210,11 @@ func (l Laplace) Score(deriv []float64, x float64) []float64 {
|
||||
// derivative of the log-likelihood
|
||||
// (d/dx) log(p(x)) .
|
||||
// Special cases:
|
||||
// ScoreInput(l.Mu) = 0
|
||||
// ScoreInput(l.Mu) = NaN
|
||||
func (l Laplace) ScoreInput(x float64) float64 {
|
||||
diff := x - l.Mu
|
||||
if diff == 0 {
|
||||
return 0
|
||||
return math.NaN()
|
||||
}
|
||||
if diff > 0 {
|
||||
return -1 / l.Scale
|
||||
|
||||
@@ -105,6 +105,10 @@ func testLaplace(t *testing.T, dist Laplace, i int) {
|
||||
if score[1] != -1/dist.Scale {
|
||||
t.Errorf("Mismatch in score over Scale value for x == Mu, got: %v, want: %g", score[1], -1/dist.Scale)
|
||||
}
|
||||
scoreInput := dist.ScoreInput(dist.Mu)
|
||||
if !math.IsNaN(scoreInput) {
|
||||
t.Errorf("Expected NaN input score for x == Mu, got %v", scoreInput)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLaplaceFit(t *testing.T) {
|
||||
@@ -133,6 +137,18 @@ func TestLaplaceFit(t *testing.T) {
|
||||
wantMu: 1,
|
||||
wantScale: 0,
|
||||
},
|
||||
{
|
||||
samples: []float64{1, 1, 10},
|
||||
weights: []float64{1, 1, 0},
|
||||
wantMu: 1,
|
||||
wantScale: 0,
|
||||
},
|
||||
{
|
||||
samples: []float64{10},
|
||||
weights: nil,
|
||||
wantMu: 10,
|
||||
wantScale: 0,
|
||||
},
|
||||
}
|
||||
for i, test := range cases {
|
||||
d := Laplace{}
|
||||
@@ -169,3 +185,18 @@ func TestLaplaceFitRandomSamples(t *testing.T) {
|
||||
t.Errorf("unexpected scale result for random test got:%f, want:%f", le.Scale, l.Scale)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLaplaceFitPanic(t *testing.T) {
|
||||
t.Parallel()
|
||||
l := Laplace{
|
||||
Mu: 3,
|
||||
Scale: 5,
|
||||
Src: nil,
|
||||
}
|
||||
if !panics(func() { l.Fit([]float64{1, 1, 1}, []float64{0.4, 0.4}) }) {
|
||||
t.Errorf("Expected panic in Fit for len(sample) != len(weights)")
|
||||
}
|
||||
if !panics(func() { l.Fit([]float64{}, nil) }) {
|
||||
t.Errorf("Expected panic in Fit for len(sample) == 0")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,6 +194,23 @@ func (t Triangle) Score(deriv []float64, x float64) []float64 {
|
||||
return deriv
|
||||
}
|
||||
|
||||
// ScoreInput returns the score function with respect to the input of the
|
||||
// distribution at the input location specified by x. The score function is the
|
||||
// derivative of the log-likelihood
|
||||
// (d/dx) log(p(x)) .
|
||||
// Special cases (c is the mode of the distribution):
|
||||
// ScoreInput(c) = NaN
|
||||
// ScoreInput(x) = NaN for x not in (a, b)
|
||||
func (t Triangle) ScoreInput(x float64) float64 {
|
||||
if (x <= t.a) || (x >= t.b) || (x == t.c) {
|
||||
return math.NaN()
|
||||
}
|
||||
if x < t.c {
|
||||
return 1 / (x - t.a)
|
||||
}
|
||||
return 1 / (x - t.b)
|
||||
}
|
||||
|
||||
// Skewness returns the skewness of the distribution.
|
||||
func (t Triangle) Skewness() float64 {
|
||||
n := math.Sqrt2 * (t.a + t.b - 2*t.c) * (2*t.a - t.b - t.c) * (t.a - 2*t.b + t.c)
|
||||
|
||||
@@ -165,3 +165,15 @@ func logProbDerivative(t Triangle, x float64, i int, h float64) float64 {
|
||||
t.setParameters(origParams)
|
||||
return (lpUp - lpDown) / (2 * h)
|
||||
}
|
||||
|
||||
func TestTriangleScoreInput(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := Triangle{a: -0.5, b: 0.7, c: 0.1}
|
||||
xs := []float64{f.a, f.b, f.c, f.a - 0.0001, f.b + 0.0001}
|
||||
for _, x := range xs {
|
||||
scoreInput := f.ScoreInput(x)
|
||||
if !math.IsNaN(scoreInput) {
|
||||
t.Errorf("Expected NaN input score for x == %g, got %v", x, scoreInput)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +154,17 @@ func (u Uniform) Score(deriv []float64, x float64) []float64 {
|
||||
return deriv
|
||||
}
|
||||
|
||||
// ScoreInput returns the score function with respect to the input of the
|
||||
// distribution at the input location specified by x. The score function is the
|
||||
// derivative of the log-likelihood
|
||||
// (d/dx) log(p(x)) .
|
||||
func (u Uniform) ScoreInput(x float64) float64 {
|
||||
if (x <= u.Min) || (x >= u.Max) {
|
||||
return math.NaN()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Skewness returns the skewness of the distribution.
|
||||
func (Uniform) Skewness() float64 {
|
||||
return 0
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package distuv
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
@@ -78,3 +79,19 @@ func testUniform(t *testing.T, u Uniform, i int) {
|
||||
checkQuantileCDFSurvival(t, i, x, u, 1e-2)
|
||||
testDerivParam(t, &u)
|
||||
}
|
||||
|
||||
func TestUniformScoreInput(t *testing.T) {
|
||||
t.Parallel()
|
||||
u := Uniform{0, 1, nil}
|
||||
scoreInput := u.ScoreInput(0.5)
|
||||
if scoreInput != 0 {
|
||||
t.Errorf("Mismatch in input score for U(0, 1) at x == 0.5: got %v, want 0", scoreInput)
|
||||
}
|
||||
xs := []float64{-0.0001, 0, 1, 1.0001}
|
||||
for _, x := range xs {
|
||||
scoreInput = u.ScoreInput(x)
|
||||
if !math.IsNaN(scoreInput) {
|
||||
t.Errorf("Expected NaN score input for U(0, 1) at x == %g, got %v", x, scoreInput)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user