Skip to content

Commit

Permalink
Add TF graph & bump seldon serving image -> 0.3
Browse files Browse the repository at this point in the history
  • Loading branch information
milesgranger authored and epa095 committed Nov 23, 2018
1 parent 2ea40e3 commit 0aa39f0
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 11 deletions.
5 changes: 3 additions & 2 deletions Dockerfile-ModelBuilder
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ COPY . /code
COPY .git /code/

WORKDIR /code
RUN python setup.py sdist && \
mv /code/dist/$(ls /code/dist | head -1) /code/dist/gordo-components-packed.tar.gz
RUN rm -rf /code/dist \
&& python setup.py sdist \
&& mv /code/dist/$(ls /code/dist | head -1) /code/dist/gordo-components-packed.tar.gz

FROM python:3.6.6-slim-stretch

Expand Down
7 changes: 4 additions & 3 deletions Dockerfile-ModelServer
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ COPY . /code
COPY .git /code/

WORKDIR /code
RUN python setup.py sdist && \
mv /code/dist/$(ls /code/dist | head -1) /code/dist/gordo-components-packed.tar.gz
RUN rm -rf /code/dist \
&& python setup.py sdist \
&& mv /code/dist/$(ls /code/dist | head -1) /code/dist/gordo-components-packed.tar.gz

FROM seldonio/seldon-core-s2i-python3
FROM seldonio/seldon-core-s2i-python3:0.3

# Install requirements separately for improved docker caching
COPY requirements.txt /code/
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ push-builder: model-builder
push-dev-images: push-builder push-server

push-prod-images: export GORDO_PROD_MODE:="true"
push-prod-images: push-builder push-server
push-prod-images: push-builder push-server

# Make the python source distribution
sdist:
Expand Down
48 changes: 44 additions & 4 deletions gordo_components/model/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import json
from typing import Union, Callable, Dict, Any
from os import path
from contextlib import contextmanager

import keras.backend as K
from keras.wrappers.scikit_learn import KerasRegressor
from keras.models import Model as KerasBaseModel
from keras.models import load_model
Expand All @@ -19,6 +21,22 @@
logger = logging.getLogger(__name__)


@contextmanager
def possible_tf_mgmt(keras_model):
"""
When serving a Keras model backed by tensorflow, the object is expected
to have `_tf_graph` and `_tf_session` stored attrs. Which will be used
as the default when calling the Keras model
"""
logger.info(f'Keras backend: {K.backend()}')
if K.backend() == 'tensorflow':
logger.debug(f'Using keras_model {keras_model} local TF Graph and Session')
with keras_model._tf_graph.as_default(), keras_model._tf_session.as_default():
yield
else:
yield


class KerasModel(KerasRegressor, GordoBaseModel):

def __init__(self,
Expand Down Expand Up @@ -54,6 +72,17 @@ def this_function_returns_a_special_keras_model(n_features, extra_param1, extra_
building function and/or any additional args to be passed
to Keras' fit() method
"""
# Tensorflow requires managed graph/session as to not default to global
if K.backend() == 'tensorflow':
logger.info(f'Keras backend detected as tensorflow, keeping local graph')
import tensorflow as tf
self._tf_graph = tf.Graph()
self._tf_session = tf.Session(graph=self._tf_graph)
else:
logger.info(f'Keras backend detected as NOT tensorflow, but: {K.backend()}')
self._tf_session = None
self._tf_graph = None

self.build_fn = None
self.kwargs = kwargs

Expand All @@ -78,19 +107,28 @@ def get_params(self, **params):

def __call__(self):
build_fn = register_model_builder.factories[self.__class__.__name__][self.kind]
return build_fn(**self.sk_params)
with possible_tf_mgmt(self):
return build_fn(**self.sk_params)

def fit(self, X, y, sample_weight=None, **kwargs):
logger.debug(f'Fitting to data of length: {len(X)}')
self.kwargs.update({'n_features': X.shape[1]})
super().fit(X, y, sample_weight=None, **kwargs)
with possible_tf_mgmt(self):
super().fit(X, y, sample_weight=None, **kwargs)
return self

def predict(self, x, **kwargs):
logger.debug(f'Predicting data of length: {len(x)}')
with possible_tf_mgmt(self):
return super().predict(x, **kwargs)

def save_to_dir(self, directory: str):
params = self.get_params()
with open(path.join(directory, 'params.json'), 'w') as f:
json.dump(params, f)
if self.model is not None:
self.model.save(path.join(directory, 'model.h5'))
with possible_tf_mgmt(self):
self.model.save(path.join(directory, 'model.h5'))

@classmethod
def load_from_dir(cls, directory: str):
Expand All @@ -99,7 +137,9 @@ def load_from_dir(cls, directory: str):
obj = cls(**params)
model_file = path.join(directory, 'model.h5')
if path.isfile(model_file):
obj.model = load_model(model_file)
with possible_tf_mgmt(obj):
K.set_learning_phase(0)
obj.model = load_model(model_file)
return obj


Expand Down
2 changes: 1 addition & 1 deletion gordo_components/runtime/SeldonModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import os
import logging

from gordo_components import serializer

logger = logging.getLogger(__name__)
Expand All @@ -27,6 +26,7 @@ def __init__(self):
f'The supplied directory: "{model_location}" does not exist!')

logger.info(f'Loading up serialized model from dir: {model_location}')

self.model = serializer.load(model_location)
logger.info(f'Model loaded successfully, ready to serve predictions!')

Expand Down

0 comments on commit 0aa39f0

Please sign in to comment.