Saving an sklearn.svm.SVR model as JSON instead of pickling

Viewed 103

I have a trained SVR model which needs to be saved in a JSON format instead of pickling.

The idea behind JSONifying the trained model is to simply capture the state of the weights and other 'fitted' attributes. Then, I can set these attributes later to make predictions. Here is an implementation of it I did:

    # assume SVR has been trained
    regressor = SVR()
    regressor.fit(x_train, y_train)

    # saving the regressor params in a JSON file for later retrieval
    with open(f'saved_regressor_params.json', 'w', encoding='utf-8') as outfile:
        json.dump(regressor.get_params(), outfile)

    # finding the fitted attributes of SVR()
    # if an attribute is trailed by '_', it's a fitted attribute
    attrs = [i for i in dir(regressor) if i.endswith('_') and not i.endswith('__')]
    remove_list = ['coef_', '_repr_html_', '_repr_mimebundle_'] # unnecessary attributes
    
    for attr in remove_list:
        if attr in attrs:
            attrs.remove(attr)


    # deserialize NumPy arrays and save trained attribute values into JSON file
    attr_dict = {i: getattr(regressor, i) for i in attrs}

    for k in attr_dict:
        if isinstance(attr_dict[k], np.ndarray):
            attr_dict[k] = attr_dict[k].tolist()

    # dump JSON for prediction
    with open(f'saved_regressor_{index}.json', 'w', encoding='utf-8') as outfile:    
        json.dump(attr_dict, 
                    outfile, 
                    separators=(',', ':'), 
                    sort_keys=True, 
                    indent=4)

This would create two separate json files. One file called saved_regressor_params.json which saves certain required parameters for SVR and another is called saved_regressor.json which stores attributes and their trained values as objects. Example (saved_regressor.json):

{
    "_dual_coef_":[
        [
            -1.0,
            -1.0,
            -1.0,
        ]
    ],
    "_intercept_":[
        1.323423423
    ],
         ...
         ...

    "_n_support_":[
        3
    ]
}

Later, I can create a new SVR() model and simply set these parameters and attributes into it by calling them from the existing JSON files we just created. Then, call in the predict() method to predict. Like so (in a new file):

predict_svr = SVR()

#load the json from the files
obj_text = codecs.open('saved_regressor_params.json', 'r', encoding='utf-8').read()
params = json.loads(obj_text)

obj_text = codecs.open('saved_regressor.json', 'r', encoding='utf-8').read()
attributes = json.loads(obj_text)

#setting params
predict_svr.set_params(**params)

# setting attributes
for k in attributes:
        if isinstance(attributes[k], list):
            setattr(predict_svr, k, np.array(attributes[k]))
        else:
            setattr(predict_svr, k, attributes[k])
        
predict_svr.predict(...)

However, during this process, a particular attribute called: n_support_ cannot be set due to some reason. And even if I ignore n_support_ attribute, it creates additional errors. (Is my logic wrong or am I missing something here?)

Therefore, I am looking for different ways or ingenious methods to save an SVR model into JSON.

I have tried the existing third party helper libraries like: sklearn_json. These libraries tend to export perfectly for linear models but not for support vectors.

1 Answers

Making a reproducible example missing in the OP, based on the docs (version 1.1.2)

from sklearn.svm import SVR
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
import numpy as np
n_samples, n_features = 10, 5
rng = np.random.RandomState(0)
y = rng.randn(n_samples)
X = rng.randn(n_samples, n_features)
regressor = SVR(C=1.0, epsilon=0.2)
regressor.fit(X, y)

Then a sketch of the a JSON serialization/deserialization

import json
# serialize
serialized = json.dumps({
    k: v.tolist() if isinstance(v, np.ndarray) else v 
    for k, v in regressor.__dict__.items()
})

# deserialize
regressor2 = SVR()
regressor2.__dict__ = {
     k: np.asarray(v) if isinstance(v, list) else v 
     for k, v in json.loads(serialized).items()
}

# test
assert np.all(regressor.predict(X) == regressor2.predict(X))

EDIT: Serialization preserving data type

A not so elegant solution to address the first issue mentioned in a comment is to save the data type together with the data.

import json
# serialize


serialized = json.dumps({
    k: [v.tolist(), 'np.ndarray', str(v.dtype)] if isinstance(v, np.ndarray) else v 
    for k, v in regressor.__dict__.items()
})

# deserialize
regressor2 = SVR()
regressor2.__dict__ = {
     k: np.asarray(v[0], dtype=v[2]) if isinstance(v, list) and v[1] == 'np.ndarray' else v 
     for k, v in json.loads(serialized).items()
}

# test
assert np.all(regressor.predict(X) == regressor2.predict(X))
Related