I've got a (TF2) saved-model full of training ops clutter and I'm trying to optimize it for inference using grappler, but I want to save it back to a TF2 saved-model subsequently (to keep the general workflow away from TF1).
I currently have:
from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2_as_graph
from tensorflow.lite.python.util import run_graph_optimizations, get_grappler_config
# Load the saved-model and get the inference concrete function
sm = tf.saved_model.load('path/to/savedmodel/dir')
func = sm.signatures['serving_default']
# Replace variables with constants in order to get rid of the training clutter
frozen_func, graph_def = convert_variables_to_constants_v2_as_graph(func)
# Use grappler to optimize the concrete function graph after replacing vars with constants
input_tensors = [tsr for tsr in frozen_func.inputs if tsr.dtype != tf.resource]
output_tensors = frozen_func.outputs
graph_def = run_graph_optimizations(graph_def, input_tensors, output_tensors,
config=get_grappler_config(["constfold", "function"]),
graph=frozen_func.graph)
# Here the intention is to somehow reconvert the optimized graph-def into a concrete function
# and subsequently re-save that as a TF2(not TF1!) saved-model, is there a way to do that?
frozen_func_graph = tf.Graph()
with frozen_func_graph.as_default():
tf.import_graph_def(graph_def, name='')
# ... what now?
The issue is, since direct tf.Graph usage has been deprecated in TF2, I intend to convert the optimized graph back to a TF2 saved-model. I was thinking of doing that by somehow manually constructing a ConcreteFunction wrapping this optimized graph, but as far as I've researched, there seems to be now way to do that. This would basically mean I'd still have to use TF1 compat APIs, which ideally I'd like to avoid.
The ugly (ugly) option I'd really like to avoid would be (haven't tried it yet but would probably work):
use v1 APIs to construct a TF1 saved-model using tf.compat.v1.saved_model.builder.SavedModelBuilder and save the TF1 saved-model
load back the TF1 saved-model using v2 API (so tf.saved_model.load instead of tf.compat.v1.saved_model.load, the former converts a TF1 saved-model automatically to a TF2 saved-model)
(re)save the converted TF2 saved-model
Is there a way to do this nicely? Preferably also without being forced to dump the optimized saved-model if I don't want to, seems that constructing saved-models in memory is not possible? (that's not such a big issue though)
Finally got it, not ideal since I use internal (more or less) TF2 API calls, but at least TF1 compat APIs are not used at all. Here's the full code.
import tensorflow as tf
from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2_as_graph
from tensorflow.lite.python.util import run_graph_optimizations, get_grappler_config
from tensorflow.python.tools.optimize_for_inference_lib import optimize_for_inference
from tensorflow.python.eager import context, wrap_function
# Load the saved-model and get the inference concrete function
sm = tf.saved_model.load('path/to/savedmodel/dir')
func = sm.signatures['serving_default'] # note: key might differ according to what your model's inference function is
# Replace variables with constants in order to get rid of the training clutter
frozen_func, graph_def = convert_variables_to_constants_v2_as_graph(func)
# Use grappler to optimize the concrete function graph after replacing vars with constants
input_tensors = [tsr for tsr in frozen_func.inputs if tsr.dtype != tf.resource]
output_tensors = frozen_func.outputs
graph_def = run_graph_optimizations(graph_def, input_tensors, output_tensors,
config=get_grappler_config(["constfold", "function"]),
graph=frozen_func.graph)
# Optimize for inference
input_tsr_names = [tsr.name for tsr in input_tensors]
output_tsr_names = [tsr.name for tsr in output_tensors]
input_node_names = list(set([tsr_name.rsplit(':', 1)[0] for tsr_name in input_tsr_names]))
output_node_names = list(set([tsr_name.rsplit(':', 1)[0] for tsr_name in output_tsr_names]))
graph_def = optimize_for_inference(input_graph_def=graph_def,
input_node_names=input_node_names,
placeholder_type_enum=tf.dtypes.float32.as_datatype_enum,
output_node_names=output_node_names,
toco_compatible=True)
# This next part inspired from _construct_concrete_function function here: https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/framework/convert_to_constants.py#L1062
# Remove old functions to use updated functions from graph def - not sure if this is actually needed here, didn't look into it
for f in graph_def.library.function:
if context.context().has_function(f.signature.name):
context.context().remove_function(f.signature.name)
# GraphDef to concrete function
opt_frozen_func = wrap_function.function_from_graph_def(graph_def,
input_tsr_names,
output_tsr_names)
# Wrap concrete function into module to export as saved-model
class OptimizedFrozenModel(tf.Module):
def __init__(self, name=None):
super().__init__(name)
module = OptimizedFrozenModel()
module.__call__ = opt_frozen_func
# Export frozen & optimized saved-model
tf.saved_model.save(module, 'path/to/optimized_savedmodel/dir', signatures=opt_frozen_func)
Related
I have been trying to load the PyGad trained instance in another file, in order to make some prediction. But I have been having some problems in the loading process.
After the training phase, I saved the instance like this:
The saving function:
filename = 'GNN_CPTNet' #GNN_CPTNet.pkl
ga_instance.save(filename=filename)
The loading function:
loaded_ga_instance = pygad.load(filename=filename)
loaded_ga_instance.plot_result()
But, when I tried to load the instance in a new notebook or script, I could not load the instance, especially the "GNN_CPT Net.pkl" file.
In the new script, you should define the the fitness function and all the callback functions you used in the original script.
For example, if you used only the on_generation (callback_generation) parameter, then the following functions should be defined:
def fitness_func(solution, solution_idx):
...
def callback_generation(ga_instance):
...
This way, the saved instance will be loaded correctly.
Anyway, it is better to post the sample codes you used to give more accurate answer.
Thanks for using PyGAD :)
Is it possible to make the PyTorch distributions create their samples directly on GPU.
If I do
from torch.distributions import Uniform, Normal
normal = Normal(3, 1)
sample = normal.sample()
Then sample will be on CPU. Of course it is possible to do sample = sample.to(torch.device("cuda")) to make it on GPU. But is there a way to have the sample go directly to GPU without first creating it on CPU?
PyTorch distributions inherit from Object, not nn.Module so it does not have a to method the put the distribution instance on GPU.
Any ideas?
Distributions use the reparametrization trick. Thus giving size 0 tensors which are on GPU to the distribution constructor works. As follows:
normal = Normal(torch.tensor(0).to(device=torch.device("cuda")), torch.tensor(1).to(device=torch.device("cuda")))
In my case, I'm using a Normal Distribution as my prior in a neural net model. I have a class called class1, for example, and in the init function I have to initiate my prior. However, calling .to('cuda') of an instance of class1 doesn't change the distribution device and causes error in later usages. Therefore, I could have used register buffers to manage it as follows.
class class1(nn.Module):
def __init__(self):
super().__init__()
self.register_buffer("mean", torch.tensor(0.))
self.register_buffer("var", torch.tensor(1.))
def get_dist(self):
return torch.distributions.Normal(self.mean, self.var)
However, I have several priors, and it's not possible to register_buffer a list. So, an option could be initiating distributions in get_dist property unless you don't care about the time complexity of initiating distributions. I decided to define a function for initiating distributions and a try-except in get_dist to handle different states. If the distributions variable is not assigned or on CPU while we expect it to be on GPU, it jumps to except where I initiate the distributions using torch.zeros(..).to(device).
Overall, to handle this error of CPU/GPU device, you need to initiate a distribution using Tensor input parameters with appropriate device. And the main reason is torch.Distribution module hasn't a device attribute unfortunately.
I just came across the same problem, and thanks to the other answers here for the pointers. I want to offer another option if you want a distribution inside a module, which is to override the to method in the module and manually call the to methods on the distribution parameter tensors. I've only tested with Uniform but works well here.
class MyModule(nn.Module):
def __init__(self, ...):
self.rng = Uniform(
low=torch.zeros(3),
high=torch.ones(3)
)
def to(self, *args, **kwargs):
super().to(*args, **kwargs)
self.rng.low = self.rng.low.to(*args, **kwargs)
self.rng.high = self.rng.high.to(*args, **kwargs)
Now you can put your model on the gpu as usual and self.rng.sample() will return a sample on the correct device.
You can solve the problem of "transferring non-parameter/buffer attributes to GPU" by overriding self._apply(self, fn) method of your network. Like this:
def _apply(self, fn):
# apply fn() to your modules
for module in self.children(): # like 'ResNet_backbone'
module._apply(fn)
# apply fn() to your prior
self.prior.attr1 = fn(self.prior.attr1) # like 'MultivariateNormal.loc', need to be Tensor
self.prior.attr2 = fn(self.prior.attr2)
···
self.prior.attrN = fn(self.prior.attrN)
# if we do not use register_buffer(Tensor)
# apply fn() to your non-parameter/buffer attributes
# need to be Tensor too
self.attr1 = fn(self.attr1)
self.attr2 = fn(self.attr2)
···
self.attrN = fn(self.attrN)
I am trying to pickle a sklearn machine-learning model, and load it in another project. The model is wrapped in pipeline that does feature encoding, scaling etc. The problem starts when i want to use self-written transformers in the pipeline for more advanced tasks.
Let's say I have 2 projects:
train_project: it has the custom transformers in src.feature_extraction.transformers.py
use_project: it has other things in src, or has no src catalog at all
If in "train_project" I save the pipeline with joblib.dump(), and then in "use_project" i load it with joblib.load() it will not find something such as "src.feature_extraction.transformers" and throw exception:
ModuleNotFoundError: No module named 'src.feature_extraction'
I should also add that my intention from the beginning was to simplify usage of the model, so programist can load the model as any other model, pass very simple, human readable features, and all "magic" preprocessing of features for actual model (e.g. gradient boosting) is happening inside.
I thought of creating /dependencies/xxx_model/ catalog in root of both projects, and store all needed classes and functions in there (copy code from "train_project" to "use_project"), so structure of projects is equal and transformers can be loaded. I find this solution extremely inelegant, because it would force the structure of any project where the model would be used.
I thought of just recreating the pipeline and all transformers inside "use_project" and somehow loading fitted values of transformers from "train_project".
The best possible solution would be if dumped file contained all needed info and needed no dependencies, and I am honestly shocked that sklearn.Pipelines seem to not have that possibility - what's the point of fitting a pipeline if i can not load fitted object later? Yes it would work if i used only sklearn classes, and not create custom ones, but non-custom ones do not have all needed functionality.
Example code:
train_project
src.feature_extraction.transformers.py
from sklearn.pipeline import TransformerMixin
class FilterOutBigValuesTransformer(TransformerMixin):
def __init__(self):
pass
def fit(self, X, y=None):
self.biggest_value = X.c1.max()
return self
def transform(self, X):
return X.loc[X.c1 <= self.biggest_value]
train_project
main.py
from sklearn.externals import joblib
from sklearn.preprocessing import MinMaxScaler
from src.feature_extraction.transformers import FilterOutBigValuesTransformer
pipeline = Pipeline([
('filter', FilterOutBigValuesTransformer()),
('encode', MinMaxScaler()),
])
X=load_some_pandas_dataframe()
pipeline.fit(X)
joblib.dump(pipeline, 'path.x')
test_project
main.py
from sklearn.externals import joblib
pipeline = joblib.load('path.x')
The expected result is pipeline loaded correctly with transform method possible to use.
Actual result is exception when loading the file.
I found a pretty straightforward solution. Assuming you are using Jupyter notebooks for training:
Create a .py file where the custom transformer is defined and import it to the Jupyter notebook.
This is the file custom_transformer.py
from sklearn.pipeline import TransformerMixin
class FilterOutBigValuesTransformer(TransformerMixin):
def __init__(self):
pass
def fit(self, X, y=None):
self.biggest_value = X.c1.max()
return self
def transform(self, X):
return X.loc[X.c1 <= self.biggest_value]
Train your model importing this class from the .py file and save it using joblib.
import joblib
from custom_transformer import FilterOutBigValuesTransformer
from sklearn.externals import joblib
from sklearn.preprocessing import MinMaxScaler
pipeline = Pipeline([
('filter', FilterOutBigValuesTransformer()),
('encode', MinMaxScaler()),
])
X=load_some_pandas_dataframe()
pipeline.fit(X)
joblib.dump(pipeline, 'pipeline.pkl')
When loading the .pkl file in a different python script, you will have to import the .py file in order to make it work:
import joblib
from utils import custom_transformer # decided to save it in a utils directory
pipeline = joblib.load('pipeline.pkl')
Apparently this problem raises when you split definitions and saving code part in two different files. So I have found this workaround that has worked for me.
It consists in these steps:
Guess we have your 2 projects/repositories : train_project and use_project
train_project:
On your train_project create a jupyter notebook or .py
On that file lets define every Custom transformer in a class, and import all other tools needed from sklearn to design the pipelines. Then lets write the saving code to pickle just inside the same file.(Don't create an external .py file src.feature_extraction.transformers to define your customtransformers).
Then fit and dumb your pipeline by running that file.
On use_project:
Create a customthings.py file with all the functions and transformers defined inside.
Create another file_where_load.py where you wish load the pickle. Inside, make sure you have imported all the definitions from customthings.py . Ensure that functions and classes have the same name than the ones you've used on train_project.
I hope it works for everyone with same problem
I have created a workaround solution. I do not consider it a complete answer to my question, but non the less it let me move on from my problem.
Conditions for the workaround to work:
I. Pipeline needs to have only 2 kinds of transformers:
sklearn transformers
custom transformers, but with only attributes of types:
number
string
list
dict
or any combination of those e.g. list of dicts with strings and numbers. Generally important thing is that attributes are json serializable.
II. names of pipeline steps need to be unique (even if there is pipeline nesting)
In short model would be stored as a catalog with joblib dumped files, a json file for custom transformers, and a json file with other info about model.
I have created a function that goes through steps of a pipeline and checks __module__ attribute of transformer.
If it finds sklearn in it it then it runs joblib.dump function under a name specified in steps (first element of step tuple), to some selected model catalog.
Otherwise (no sklearn in __module__) it adds __dict__ of transformer to result_dict under a key equal to name specified in steps. At the end I json.dump the result_dict to model catalog under name result_dict.json.
If there is a need to go into some transformer, because e.g. there is a Pipeline inside a pipeline, you can probably run this function recursively by adding some rules to the beginning of the function, but it becomes important to have always unique steps/transformers names even between main pipeline and subpipelines.
If there are other information needed for creation of model pipeline then save them in model_info.json.
Then if you want to load the model for usage:
You need to create (without fitting) the same pipeline in target project. If pipeline creation is somewhat dynamic, and you need information from source project, then load it from model_info.json.
You can copy function used for serialization and:
replace all joblib.dump with joblib.load statements, assign __dict__ from loaded object to __dict__ of object already in pipeline
replace all places where you added __dict__ to result_dict with assignment of appropriate value from result_dict to object __dict__ (remember to load result_dict from file beforehand)
After running this modified function, previously unfitted pipeline should have all transformer attributes that were effect of fitting loaded, and pipeline as a whole ready to predict.
The main things I do not like about this solution is that it needs pipeline code inside target project, and needs all attrs of custom transformers to be json serializable, but I leave it here for other people that stumble on a similar problem, maybe somebody comes up with something better.
I was similarly surprised when I came across the same problem some time ago. Yet there are multiple ways to address this.
Best practice solution:
As others have mentioned, the best practice solution is to move all dependencies of your pipeline into a separate Python package and define that package as a dependency of your model environment.
The environment then has to be recreated whenever the model is deployed. In simple cases this can be done manually e.g. via virtualenv or Poetry. But model stores and versioning frameworks (MLflow being one example) typically provide a way to define the required Python environment (e.g. via conda.yaml). They often can automatically recreate the environment at deployment time.
Solution by putting code into main:
In fact, class and function declearations can be serialized, but only declarations in __main__ actually get serialized. __main__ is the entry point of the script, the file that is run. So if all the custom code and all of its dependencies are in that file, then custom objects can later be loaded in Python environments that do not include the code. This kind of solves the problem, but who wants to have all that code in __main__? (Note that this property also applies to cloudpickle)
Solution by "mainifying":
There is one other way which is to "mainify" the classes or function objects before saving. I came across that same problem some time ago and have written a function that does that. It essentially redefines an existing object's code in __main__. Its application is simple: Pass object to function, then serialize the object, voilà, it can be loaded anywhere. Like so:
# ------ In file1.py: ------
class Foo():
pass
# ------ In file2.py: ------
from file1 import Foo
foo = Foo()
foo = mainify(foo)
import dill
with open('path/file.dill', 'wb') as f
dill.dump(foo, f)
I post the function code below. Note that I have tested this with dill, but I think it should work with pickle as well.
Also note that the original idea is not mine, but came from a blog post that I cannot find right now. I will add the reference/acknowledgement when I find it.
Edit: Blog post by Oege Dijk by which my code was inspired.
def mainify(obj, warn_if_exist=True):
''' If obj is not defined in __main__ then redefine it in main. Allows dill
to serialize custom classes and functions such that they can later be loaded
without them being declared in the load environment.
Parameters
---------
obj : Object to mainify (function or class instance)
warn_if_exist : Bool, default True. Throw exception if function (or class) of
same name as the mainified function (or same name as mainified
object's __class__) was already defined in __main__. If False
don't throw exception and instead use what was defined in
__main__. See Limitations.
Limitations
-----------
Assumes `obj` is either a function or an instance of a class.
'''
if obj.__module__ != '__main__':
import __main__
is_func = True if isinstance(obj, types.FunctionType) else False
# Check if obj with same name is already defined in __main__ (for funcs)
# or if class with same name as obj's class is already defined in __main__.
# If so, simply return the func with same name from __main__ (for funcs)
# or assign the class of same name to obj and return the modified obj
if is_func:
on = obj.__name__
if on in __main__.__dict__.keys():
if warn_if_exist:
raise RuntimeError(f'Function with __name__ `{on}` already defined in __main__')
return __main__.__dict__[on]
else:
ocn = obj.__class__.__name__
if ocn in __main__.__dict__.keys():
if warn_if_exist:
raise RuntimeError(f'Class with obj.__class__.__name__ `{ocn}` already defined in __main__')
obj.__class__ = __main__.__dict__[ocn]
return obj
# Get source code and compile
source = inspect.getsource(obj if is_func else obj.__class__)
compiled = compile(source, '<string>', 'exec')
# "declare" in __main__, keeping track which key of __main__ dict is the new one
pre = list(__main__.__dict__.keys())
exec(compiled, __main__.__dict__)
post = list(__main__.__dict__.keys())
new_in_main = list(set(post) - set(pre))[0]
# for function return mainified version, else assign new class to obj and return object
if is_func:
obj = __main__.__dict__[new_in_main]
else:
obj.__class__ = __main__.__dict__[new_in_main]
return obj
Have you tried using cloud pickle?
https://github.com/cloudpipe/cloudpickle
Based on my research it seems that the best solution is to create a Python package that includes your trained pipeline and all files.
Then you can pip install it in the project where you want to use it and import the pipeline with from <package name> import <pipeline name>.
Credit to Ture Friese for mentioning cloudpickle >=2.0.0, but here's an example for your use case.
import cloudpickle
cloudpickle.register_pickle_by_value(FilterOutBigValuesTransformer)
with open('./pipeline.cloudpkl', mode='wb') as file:
pipeline.dump(
obj=Pipe
, file=file
)
register_pickle_by_value() is the key as it will ensure your custom module (src.feature_extraction.transformers) is also included when serializing your primary object (pipeline). However, this is not built for recursive module dependence, e.g. if FilterOutBigValuesTransformer also contains another import statement
Calling the location of the transform.py file with sys.path.append may resolve the issue.
import sys
sys.path.append("src/feature_extraction/transformers")
I am having a script in tensorflow which contains the custom tensorflow ops. I want to port the code to keras and I am not sure how to call the custom ops within keras code.
I want to use tensorflow within keras, so the tutorial I found so far is describing the opposite to what I want: https://blog.keras.io/keras-as-a-simplified-interface-to-tensorflow-tutorial.html.
I also read about Lambda layers that can wrap arbitrary custom function, yet I did not see an example for tf.ops.
If you could provide code snippet with a simplest example how to do that I would be very grateful. For example assuming the tf.ops as:
outC = my_custom_op(inA, inB)
---EDIT:
Similar problem has been described in here - essentially calling this custom op in keras, however I cannot grasp the solution how to apply it on another example that I want, for instance this one. This custom tf op is first compiled (for gpu) and then so far used within tensorflow as here, see # line 40. It is clear for me how to use a custom (lambda) function wrapped in Lambda layer, what I would like to understand is how to use the compiled custom ops, if I use keras.
You can wrap arbitrary tensorflow functions in a keras Lambda layer and add them to your model. Minimal working example from this answer:
import tensorflow as tf
from keras.layers import Dense, Lambda, Input
from keras.models import Model
W = tf.random_normal(shape=(128,20))
b = tf.random_normal(shape=(20,))
inp = Input(shape=(10,))
x = Dense(128)(inp)
# Custom linear transformation
y = Lambda(lambda x: tf.matmul(x, W) + b, name='custom_layer')(x)
model = Model(inp, y)
I am having a question that is very similar to this topic but I want to reuse the StandardScaler instead of LabelEncoder. Here's what I have done:
# in one program
dict = {"mean": scaler.mean_, "var": scaler.var_}
# and save the dict
# in another program
# load the dict first
new_scaler = StandardScaler()
new_scaler.mean_ = dict['mean'] # Hoever it doesn't work
new_scaler.var_ = dict['var'] # Doesn't work either...
I also tried set_params but it can only change these parameters: copy, with_mean, and with_std.
So, how can I re-use the scaler I got in program one? Thanks!
Just pickle the whole thing.
Follow the official docs.
You can either use python's standard-pickle from the first link or the specialized joblib-pickle mentioned in the second link (which i recommend; often more efficient, although not that important for this simple kind of object = scaler):
import joblib
import sklearn.preprocessing as skp
new_scaler = skp.StandardScaler()
# ...fit it... do something ...
joblib.dump(new_scaler , 'my_scaler.pkl') # save to disk
loaded_scaler = joblib.load('my_scaler.pkl') # load from disk
If you by any chance want to store your sklearn-objects in databases like MySQL, MongoDB, Redis and co., the above example using file-based storage won't work of course.
The easy approach then: use python-pickle's dumps which will dump to a bytes-object (ready for most DB-wrappers).
For the more efficient joblib, you have to use python's BytesIO to use it in a similar way (as the method itself is file-based, but can be used on file-like objects).