Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exchangerate #2222

Closed
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions common/logistic/logistic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package logistic

import (
"fmt"
"math/big"

"github.com/dominant-strategies/go-quai/common"
"github.com/dominant-strategies/go-quai/common/math"
"gonum.org/v1/plot"
"gonum.org/v1/plot/plotter"
"gonum.org/v1/plot/vg"
)

var (
c_learningRate = big.NewFloat(0.001)
c_epochLength = 100000
)

// LogisticRegression represents a logistic regression model.
type LogisticRegression struct {
beta0 *big.Float // Model bias (intercept)
beta1 *big.Float // Model weight (slope)
}

// NewLogisticRegression initializes a new LogisticRegression model.
func NewLogisticRegression() *LogisticRegression {
return &LogisticRegression{
beta0: big.NewFloat(0.1),
beta1: big.NewFloat(0.000081),
}
}

// sigmoid computes the sigmoid function.
func sigmoid(z *big.Float) *big.Float {
// Compute exp(-z)
negZ := new(big.Float).Neg(z)
expNegZ := math.EToTheX(negZ)

// Compute 1 + exp(-z)
denom := new(big.Float).Add(new(big.Float).SetInt(common.Big1), expNegZ)

// Compute 1 / (1 + exp(-z))
result := new(big.Float).Quo(new(big.Float).SetInt(common.Big1), denom)

return result
}

// Predict computes the probability that the input belongs to class 1.
func (lr *LogisticRegression) Predict(x *big.Float) *big.Float {
// z = beta0 + beta1 * x
beta1x := new(big.Float).Mul(lr.beta1, x)
z := new(big.Float).Add(lr.beta0, beta1x)

// Apply sigmoid function
return sigmoid(z)
}

// PredictLabel predicts the class label (0 or 1) for the input.
func (lr *LogisticRegression) PredictLabel(x *big.Float) int {
prob := lr.Predict(x)
cmp := prob.Cmp(big.NewFloat(0.5))
if cmp >= 0 {
return 1
}
return 0
}

// Train trains the logistic regression model using gradient descent.
func (lr *LogisticRegression) Train(x []*big.Int, y []*big.Int) {
nSamples := len(y)

var xfloat, yfloat []*big.Float
for i := 0; i < nSamples; i++ {
xfloat = append(xfloat, new(big.Float).SetInt(x[i]))
yfloat = append(yfloat, new(big.Float).SetInt(y[i]))
}

for epoch := 0; epoch < c_epochLength; epoch++ {
// Initialize gradients
dw := new(big.Float).SetInt(common.Big0)
db := new(big.Float).SetInt(common.Big0)

// Compute gradients
for i := 0; i < nSamples; i++ {
xi := xfloat[i]
yi := yfloat[i]
pred := lr.Predict(xi)
error := new(big.Float).Sub(pred, yi)
dwTerm := new(big.Float).Mul(error, xi)
dw.Add(dw, dwTerm)
db.Add(db, error)
}

nSamplesFloat := new(big.Float).SetInt(big.NewInt(int64(nSamples))) //big.NewFloat(float64(nSamples))

// Compute gradient averages
dw.Quo(dw, nSamplesFloat)
db.Quo(db, nSamplesFloat)

// Update weight: beta1 = beta1 - LearningRate * dw
lrUpdateW := new(big.Float).Mul(c_learningRate, dw)
lr.beta1.Sub(lr.beta1, lrUpdateW)

// Update bias: beta0 = beta0 - LearningRate * db
lrUpdateB := new(big.Float).Mul(c_learningRate, db)
lr.beta0.Sub(lr.beta0, lrUpdateB)
}
}

// Beta0 returns the model's bias (intercept) term.
func (lr *LogisticRegression) Beta0() *big.Float {
return new(big.Float).Set(lr.beta0)
}

// Beta1 returns the model's weight (slope) term.
func (lr *LogisticRegression) Beta1() *big.Float {
return new(big.Float).Set(lr.beta1)
}

// BigBeta0 returns the model's bias (intercept) term.
func (lr *LogisticRegression) BigBeta0() *big.Int {
bigBeta := new(big.Float).Mul(lr.beta0, new(big.Float).SetInt(common.Big2e64))
bigBetaInt, _ := bigBeta.Int(nil)
return bigBetaInt
}

// BigBeta1 returns the model's weight (slope) term.
func (lr *LogisticRegression) BigBeta1() *big.Int {
bigBeta := new(big.Float).Mul(lr.beta1, new(big.Float).SetInt(common.Big2e64))
bigBetaInt, _ := bigBeta.Int(nil)
return bigBetaInt
}

// Plot the given trained logistic regression values with Beta0 and Beta1
func (lr *LogisticRegression) PlotSigmoid(xValues, yValues []float64) error {
// Create a new plot
p := plot.New()

beta0, _ := lr.beta0.Float64()
beta1, _ := lr.beta1.Float64()

p.Title.Text = fmt.Sprintf("Sigmoid Function: Beta0=%.10f, Beta1=%.10f", beta0, beta1)
p.X.Label.Text = "x"
p.Y.Label.Text = "sigmoid(x)"

plotValues := make(plotter.XYs, 0)
for i := range xValues {
value := plotter.XY{xValues[i], yValues[i]}
plotValues = append(plotValues, value)
}

// Create a line plotter with x and y values
line, err := plotter.NewLine(plotValues)
if err != nil {
return err
}

// Add the line to the plot
p.Add(line)

// Create the function to be plotted
sigmoidFunc := plotter.NewFunction(func(x float64) float64 {
result := lr.Predict(big.NewFloat(x))
resultF, _ := result.Float64()
return resultF
})

// Set the style for the function line
sigmoidFunc.Color = plotter.DefaultLineStyle.Color
sigmoidFunc.Width = vg.Points(2)

// Set the range for x-axis values
// Find the min and max in the xValues
xMin := float64(math.MaxInt64)
xMax := float64(0)
for _, x := range xValues {
if x < xMin {
xMin = x
} else if x > xMax {
xMax = x
}
}
sigmoidFunc.XMin = xMin
sigmoidFunc.XMax = xMax

p.Add(sigmoidFunc)

// Save the plot as a PNG image
if err := p.Save(6*vg.Inch, 4*vg.Inch, "sigmoid.png"); err != nil {
return err
}

return nil
}
32 changes: 32 additions & 0 deletions common/logistic/logistic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package logistic

import (
"fmt"
"math/big"
"testing"
)

func TestLogistic(t *testing.T) {
r := NewLogisticRegression()

X := []*big.Float{big.NewFloat(1), big.NewFloat(2), big.NewFloat(3), big.NewFloat(4), big.NewFloat(10), big.NewFloat(11), big.NewFloat(12), big.NewFloat(13)}
Y := []*big.Float{big.NewFloat(0), big.NewFloat(0), big.NewFloat(0), big.NewFloat(0), big.NewFloat(1), big.NewFloat(1), big.NewFloat(1), big.NewFloat(1)}

r.Train(X, Y)

Check failure on line 15 in common/logistic/logistic_test.go

View workflow job for this annotation

GitHub Actions / call-common-workflow / buildDeployDevGo / build

cannot use X (variable of type []*big.Float) as []*big.Int value in argument to r.Train

Check failure on line 15 in common/logistic/logistic_test.go

View workflow job for this annotation

GitHub Actions / call-common-workflow / buildDeployDevGo / build

cannot use Y (variable of type []*big.Float) as []*big.Int value in argument to r.Train

fmt.Println("Beta0/1 after training", r.Beta0(), r.Beta1())

xValues := make([]float64, 0)
for _, x := range X {
xF, _ := x.Float64()
xValues = append(xValues, xF)
}

yValues := make([]float64, 0)
for _, y := range Y {
yF, _ := y.Float64()
yValues = append(yValues, yF)
}

r.PlotSigmoid(xValues, yValues)
}
Binary file added common/logistic/sigmoid.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 67 additions & 0 deletions common/math/big.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,70 @@ func TwoToTheX(x *big.Float) *big.Float {

return result
}

// EToTheX computes the expression 1 + x + (1/2)*x^2 + (1/6)*x^3 + (1/24)*x^4 + (1/120)*x^5 + (1/720)*x^6 + (1/5040)*x^7
func EToTheX(x *big.Float) *big.Float {
// Set the desired precision
prec := uint(16) // You can adjust the precision as needed

// Initialize constants with the specified precision
one := new(big.Float).SetPrec(prec).SetFloat64(1.0)
gameofpointers marked this conversation as resolved.
Show resolved Hide resolved
half := new(big.Float).SetPrec(prec).SetFloat64(0.5)
oneSixth := new(big.Float).SetPrec(prec).Quo(
new(big.Float).SetPrec(prec).SetFloat64(1.0),
new(big.Float).SetPrec(prec).SetFloat64(6.0),
)
oneTwentyFourth := new(big.Float).SetPrec(prec).Quo(
new(big.Float).SetPrec(prec).SetFloat64(1.0),
new(big.Float).SetPrec(prec).SetFloat64(24.0),
)
oneOneTwentieth := new(big.Float).SetPrec(prec).Quo(
new(big.Float).SetPrec(prec).SetFloat64(1.0),
new(big.Float).SetPrec(prec).SetFloat64(120.0),
)
oneOneSevenTwentieth := new(big.Float).SetPrec(prec).Quo(
new(big.Float).SetPrec(prec).SetFloat64(1.0),
new(big.Float).SetPrec(prec).SetFloat64(720.0),
)
oneFiveThousandFourtieth := new(big.Float).SetPrec(prec).Quo(
new(big.Float).SetPrec(prec).SetFloat64(1.0),
new(big.Float).SetPrec(prec).SetFloat64(5040.0),
)

// Compute x^2
x2 := new(big.Float).SetPrec(prec).Mul(x, x)

// Compute x^3
x3 := new(big.Float).SetPrec(prec).Mul(x2, x)

// Compute x^4
x4 := new(big.Float).SetPrec(prec).Mul(x3, x)

// Compute x^5
x5 := new(big.Float).SetPrec(prec).Mul(x4, x)

// Compute x^6
x6 := new(big.Float).SetPrec(prec).Mul(x5, x)

// Compute x^7
x7 := new(big.Float).SetPrec(prec).Mul(x6, x)

// Compute terms
term2 := new(big.Float).SetPrec(prec).Mul(half, x2) // 0.5 * x^2
term3 := new(big.Float).SetPrec(prec).Mul(oneSixth, x3) // (1/6) * x^3
term4 := new(big.Float).SetPrec(prec).Mul(oneTwentyFourth, x4) // (1/24) * x^4
term5 := new(big.Float).SetPrec(prec).Mul(oneOneTwentieth, x5) // (1/120) * x^5
term6 := new(big.Float).SetPrec(prec).Mul(oneOneSevenTwentieth, x6) // (1/720) * x^6
term7 := new(big.Float).SetPrec(prec).Mul(oneFiveThousandFourtieth, x7) // (1/5040) * x^7

// Sum up the terms: result = 1 + x + term2 + term3 + term4
result := new(big.Float).SetPrec(prec).Add(one, x) // result = 1 + x
result.Add(result, term2) // result += term2
result.Add(result, term3) // result += term3
result.Add(result, term4) // result += term4
result.Add(result, term5) // result += term5
result.Add(result, term6) // result += term6
result.Add(result, term7) // result += term7

return result
}
50 changes: 50 additions & 0 deletions common/math/big_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,3 +384,53 @@ func TestTwoToTheX(t *testing.T) {
}
}
}

func TestEtoTheX(t *testing.T) {
tests := []struct {
x *big.Float
actual *big.Float
}{
{
x: big.NewFloat(1.625),
actual: big.NewFloat(5.0784190371800815),
},
{
x: big.NewFloat(0.765),
actual: big.NewFloat(2.1489943746552203),
},
{
x: big.NewFloat(1.685),
actual: big.NewFloat(5.392450932349507),
},
{
x: big.NewFloat(2.0),
actual: big.NewFloat(7.38905609893065),
},
{
x: big.NewFloat(-1.25),
actual: big.NewFloat(0.2865047968601901),
},
{
x: big.NewFloat(5.5),
actual: big.NewFloat(244.69193226422038),
},
{
x: big.NewFloat(-0.00000045456),
actual: big.NewFloat(0.9999999999),
},
{
x: big.NewFloat(0.0),
actual: big.NewFloat(1.0), // e^0 = 1
},
}

for _, test := range tests {
result := EToTheX(test.x)
lowerBound := new(big.Float).Mul(test.actual, big.NewFloat(0.98))
upperBound := new(big.Float).Mul(test.actual, big.NewFloat(1.02))

if result.Cmp(lowerBound) < 0 || result.Cmp(upperBound) > 0 {
t.Errorf("TwoToTheX(%s) = %g, want %g within 3%%", test.x.Text('f', -1), result, test.actual)
}
}
}
Loading
Loading