Skip to content

Commit

Permalink
MLPClassifier/Regressor Unmarshal allow to reload a MLP saved from sc…
Browse files Browse the repository at this point in the history
…ikit-learn
  • Loading branch information
pa-m committed Apr 7, 2019
1 parent 8933cca commit 8789562
Show file tree
Hide file tree
Showing 18 changed files with 302 additions and 108 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ coverage.txt
*.debug
_*.sh
FIXME*
cover.out
coverage.txt
7 changes: 6 additions & 1 deletion base/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@ type TransformerCloner interface {
Clone() Transformer
}

// Predicter predicts
type Predicter interface {
Predict(X, Y *mat.Dense)
}

// Regressor is the common interface for all regressors
type Regressor interface {
Transformer
Predict(X, Y *mat.Dense) Regressor
Predicter
Score(X, T *mat.Dense) float64
}

Expand Down
3 changes: 1 addition & 2 deletions cluster/dbscan.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,7 @@ func (m *DBSCAN) Fit(X, Y *mat.Dense) base.Transformer {
}

// Predict for DBSCAN
func (m *DBSCAN) Predict(X, Y *mat.Dense) base.Transformer {
return m
func (m *DBSCAN) Predict(X, Y *mat.Dense) {

}

Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ golang.org/x/tools v0.0.0-20190401205534-4c644d7e323d/go.mod h1:LCzVGOaR6xXOjkQ3
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485 h1:OB/uP/Puiu5vS5QMRPrXCDWUPb+kt8f1KW8oQzFejQw=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
gonum.org/v1/gonum v0.0.0-20190403090810-169ee079a3fc h1:2LHkGw2/tb7MFqffWPgtGbdKyz81a+33jjtiVvOe6yI=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e h1:jRyg0XfpwWlhEV8mDfdNGBeSJM2fuyh9Yjrnd8kF2Ts=
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
Expand Down
9 changes: 4 additions & 5 deletions linear_model/Base.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import (
"github.com/pa-m/sklearn/preprocessing"

//"gonum.org/v1/gonum/diff/fd"
"golang.org/x/exp/rand"
"math"
"runtime"

"golang.org/x/exp/rand"

"gonum.org/v1/gonum/mat"
"gonum.org/v1/gonum/optimize"
)
Expand Down Expand Up @@ -111,9 +112,8 @@ func (regr *RegularizedRegression) Fit(X0, Y0 *mat.Dense) base.Transformer {
}

// Predict predicts y for X using Coef
func (regr *LinearRegression) Predict(X, Y *mat.Dense) base.Regressor {
func (regr *LinearRegression) Predict(X, Y *mat.Dense) {
regr.DecisionFunction(X, Y)
return regr
}

// FitTransform is for Pipeline
Expand Down Expand Up @@ -273,9 +273,8 @@ func (regr *SGDRegressor) Fit(X0, y0 *mat.Dense) base.Transformer {
}

// Predict predicts y from X using Coef
func (regr *SGDRegressor) Predict(X, Y *mat.Dense) base.Regressor {
func (regr *SGDRegressor) Predict(X, Y *mat.Dense) {
regr.DecisionFunction(X, Y)
return regr
}

// FitTransform is for Pipeline
Expand Down
7 changes: 2 additions & 5 deletions linear_model/bayes.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,18 +208,17 @@ func (regr *BayesianRidge) Fit(X0, Y0 *mat.Dense) base.Transformer {
// yMean : array, shape = (nSamples,)
// Mean of predictive distribution of query points.
// """
func (regr *BayesianRidge) Predict(X, Y *mat.Dense) base.Regressor {
func (regr *BayesianRidge) Predict(X, Y *mat.Dense) {
// d := func(X mat.Matrix) string { r, c := X.Dims(); return fmt.Sprintf("%d,%d", r, c) }
// fmt.Println("Predict", d(X), d(regr.Coef))
Y.Mul(X, regr.Coef)
Y.Apply(func(i int, j int, y float64) float64 {
return y + regr.Intercept.At(0, j)
}, Y)
return regr
}

// Predict2 returns y and stddev
func (regr *BayesianRidge) Predict2(X, Y, yStd *mat.Dense) base.Regressor {
func (regr *BayesianRidge) Predict2(X, Y, yStd *mat.Dense) {
nSamples, nFeatures := X.Dims()
regr.Predict(X, Y)
Xn := mat.DenseCopyOf(X)
Expand All @@ -243,8 +242,6 @@ func (regr *BayesianRidge) Predict2(X, Y, yStd *mat.Dense) base.Regressor {
yStd.Apply(func(i int, j int, sigmasSquaredData float64) float64 {
return sigmasSquaredData + 1./regr.Alpha
}, sigmasSquaredData)

return regr
}

// FitTransform is for Pipeline
Expand Down
4 changes: 2 additions & 2 deletions model_selection/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,8 @@ func (gscv *GridSearchCV) Fit(X, Y *mat.Dense) base.Transformer {
}

// Predict ...
func (gscv *GridSearchCV) Predict(X, Y *mat.Dense) base.Transformer {
return gscv
func (gscv *GridSearchCV) Predict(X, Y *mat.Dense) {
gscv.BestEstimator.(base.Predicter).Predict(X, Y)
}

// Transform ...
Expand Down
4 changes: 2 additions & 2 deletions neighbors/classification.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ func (m *KNeighborsClassifier) Fit(X, Y *mat.Dense) base.Transformer {
}

// Predict for KNeighborsClassifier
func (m *KNeighborsClassifier) Predict(X, Y *mat.Dense) base.Transformer {
return m._predict(X, Y, false)
func (m *KNeighborsClassifier) Predict(X, Y *mat.Dense) {
m._predict(X, Y, false)
}

// PredictProba for KNeighborsClassifier
Expand Down
4 changes: 2 additions & 2 deletions neighbors/nearest_centroid.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ func (m *NearestCentroid) Fit(X, Y *mat.Dense) base.Transformer {
}

// Predict for NearestCentroid
func (m *NearestCentroid) Predict(X, Y *mat.Dense) base.Transformer {
return m._predict(X, Y, false)
func (m *NearestCentroid) Predict(X, Y *mat.Dense) {
m._predict(X, Y, false)
}

// PredictProba for NearestCentroid
Expand Down
3 changes: 1 addition & 2 deletions neighbors/regression.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (m *KNeighborsRegressor) Fit(X, Y *mat.Dense) base.Transformer {
}

// Predict ...
func (m *KNeighborsRegressor) Predict(X, Y *mat.Dense) base.Regressor {
func (m *KNeighborsRegressor) Predict(X, Y *mat.Dense) {
NFitSamples, _ := m.Xscaled.Dims()
NX, _ := X.Dims()
_, outputs := m.Y.Dims()
Expand Down Expand Up @@ -79,7 +79,6 @@ func (m *KNeighborsRegressor) Predict(X, Y *mat.Dense) base.Regressor {

}
})
return m
}

// Transform for KNeighborsRegressor
Expand Down
138 changes: 108 additions & 30 deletions neural_network/basemlp32.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package neuralnetwork

import (
"encoding/json"
"fmt"
"log"
"reflect"
"sort"
"strings"
"time"
Expand All @@ -16,37 +18,37 @@ import (

// BaseMultilayerPerceptron32 closely matches sklearn/neural_network/multilayer_perceptron.py
type BaseMultilayerPerceptron32 struct {
Activation string
Solver string
Alpha float32
WeightDecay float32
BatchSize int
LearningRate string
LearningRateInit float32
PowerT float32
MaxIter int
LossFuncName string
HiddenLayerSizes []int
Shuffle bool
RandomState base.RandomState
Tol float32
Verbose bool
WarmStart bool
Momentum float32
NesterovsMomentum bool
EarlyStopping bool
ValidationFraction float32
Beta1 float32
Beta2 float32
Epsilon float32
NIterNoChange int
Activation string `json:"activation"`
Solver string `json:"solver"`
Alpha float32 `json:"alpha"`
WeightDecay float32 `json:"weight_decay"`
BatchSize int `json:"batch_size"`
LearningRate string `json:"learning_rate"`
LearningRateInit float32 `json:"learning_rate_init"`
PowerT float32 `json:"power_t"`
MaxIter int `json:"max_iter"`
LossFuncName string `json:"loss_func_name"`
HiddenLayerSizes []int `json:"hidden_layer_sizes"`
Shuffle bool `json:"shuffle"`
RandomState base.RandomState `json:"random_state"`
Tol float32 `json:"tol"`
Verbose bool `json:"verbose"`
WarmStart bool `json:"warm_start"`
Momentum float32 `json:"momentum"`
NesterovsMomentum bool `json:"nesterovs_momentum"`
EarlyStopping bool `json:"early_stopping"`
ValidationFraction float32 `json:"validation_fraction"`
Beta1 float32 `json:"beta_1"`
Beta2 float32 `json:"beta_2"`
Epsilon float32 `json:"epsilon"`
NIterNoChange int `json:"n_iter_no_change"`

// Outputs
NLayers int
NIter int
NOutputs int
Intercepts [][]float32
Coefs []blas32General
Intercepts [][]float32 `json:"intercepts_"`
Coefs []blas32General `json:"coefs_"`
OutActivation string
Loss float32

Expand Down Expand Up @@ -360,12 +362,12 @@ func (mlp *BaseMultilayerPerceptron32) backprop(X, y blas32General, activations,
return loss
}

func (mlp *BaseMultilayerPerceptron32) initialize(y blas32General, layerUnits []int, isClassifier, isMultiClass bool) {
func (mlp *BaseMultilayerPerceptron32) initialize(yCols int, layerUnits []int, isClassifier, isMultiClass bool) {
// # set all attributes, allocate weights etc for first call
// # Initialize parameters
mlp.NIter = 0
mlp.t = 0
mlp.NOutputs = y.Cols
mlp.NOutputs = yCols

//# Compute the number of layers
mlp.NLayers = len(layerUnits)
Expand Down Expand Up @@ -473,7 +475,7 @@ func (mlp *BaseMultilayerPerceptron32) fit(X, y blas32General, incremental bool)
break
}
}
mlp.initialize(y, layerUnits, isClassifier, isMulticlass)
mlp.initialize(y.Cols, layerUnits, isClassifier, isMulticlass)
}

// # lbfgs does not support mini-batches
Expand Down Expand Up @@ -538,7 +540,7 @@ func (mlp *BaseMultilayerPerceptron32) Predict(X, Y Matrix) {

ymut, ok := Y.(Mutable)
if !ok {
log.Panicf("Y bus be mutable")
log.Panicf("Y must be mutable")
}

for r, rpos := 0, 0; r < yg.Rows; r, rpos = r+1, rpos+yg.Stride {
Expand Down Expand Up @@ -1011,3 +1013,79 @@ func accuracyScore32(Y, H blas32General) float32 {
return float32(N) / float32(Y.Rows)

}

// SetParams allow settings params from a map. (used by Unmarshal)
func (mlp *BaseMultilayerPerceptron32) SetParams(params map[string]interface{}) {
r := reflect.Indirect(reflect.ValueOf(mlp))
for k, v := range params {
field := r.FieldByNameFunc(func(s string) bool {
return strings.EqualFold(s, k)
})
if field.Kind() != 0 {
field.Set(reflect.ValueOf(v))
}
}
}

// Unmarshal init params intercepts_ coefs_ from json
func (mlp *BaseMultilayerPerceptron32) Unmarshal(buf []byte) error {
type Map = map[string]interface{}
mp := Map{}
err := json.Unmarshal(buf, &mp)
if err != nil {
panic(err)
}
if params, ok := mp["params"]; ok {
if pmap, ok := params.(Map); ok {
mlp.SetParams(pmap)
}
} else {
mlp.SetParams(mp)
}
if coefs, ok := mp["coefs_"]; ok {
intercepts, ok := mp["intercepts_"]
if !ok {
return fmt.Errorf("intercepts_ not set")
}
intercepts2, ok := intercepts.([]interface{})
if !ok {
return fmt.Errorf("intercepts_ must be an array")
}
if c64, ok := coefs.([]interface{}); ok {

if len(c64) == 0 {
return fmt.Errorf("coefs_ must be non-empty")
}
b64coefs := make([]blas64General, len(c64), len(c64))
for i := range b64coefs {
b64coefs[i] = blas64FromInterface(c64[i])
}
mlp.NLayers = len(b64coefs) + 1
mlp.HiddenLayerSizes = make([]int, mlp.NLayers-2, mlp.NLayers-2)

NInputs := b64coefs[0].Rows
mlp.NOutputs = b64coefs[len(b64coefs)-1].Cols
layerUnits := make([]int, mlp.NLayers)
layerUnits[0] = NInputs
packedSize := 0
for il := range c64 {
layerUnits[il+1] = b64coefs[il].Cols
packedSize += (1 + layerUnits[il]) * layerUnits[il+1]
}
layerUnits[mlp.NLayers-1] = mlp.NOutputs
mlp.initialize(mlp.NOutputs, layerUnits, true, mlp.NOutputs > 1)

for i := 0; i < mlp.NLayers-1; i++ {
intercept64 := floats64FromInterface(intercepts2[i])
for off := 0; off < len(mlp.Intercepts[i]); off++ {
mlp.Intercepts[i][off] = float32(intercept64[off])
}
g := General32(mlp.Coefs[i])
(&g).Copy(General64(b64coefs[i]))
}
} else {
return fmt.Errorf("coefs_ must be [][][]float64, found %T", coefs)
}
}
return err
}
Loading

0 comments on commit 8789562

Please sign in to comment.