Repository: keras-team/autokeras

Issue:
Export the autokeras model
<!---
Please label your issue with `feature_request`.
-->

### Feature Description
<!---
A clear and concise description of idea/feature. If it is related to some problem you are facing, then explain the problem.
-->
Export the Autokeras model of its own type instead of exporting the Keras model.

### Reason
<!---
A clear and concise description of why this feature would be useful for the project.
-->
More flexible than Keras model.

### Solution
<!---
A clear and concise description of what you want to happen.
-->
Create a base class which can only be loaded and do the "predict" and "evaluation" function.
A class named "PortableImageClassifer" extends the base class.
It has a member of an instance of "Graph".
Refer to other parts of the project to see how to do "predict" and "evaluation" with an instance of Graph.

### Alternative Solutions
<!---
A clear and concise description of any alternative solutions or features you've considered.
-->
NA

### Additional Context
<!---
Add any other context or screenshots about the feature request here.
-->
NA


Discussion:
Can you look to export using onnx standard? 

So models are interoperable and deployable to production environments.

[https://onnx.ai](https://onnx.ai) 


Resolving patch:
diff --git a/autokeras/image_supervised.py b/autokeras/image_supervised.py
--- a/autokeras/image_supervised.py
+++ b/autokeras/image_supervised.py
@@ -12,7 +12,7 @@
 from sklearn.model_selection import train_test_split
 
 from autokeras.loss_function import classification_loss, regression_loss
-from autokeras.supervised import Supervised
+from autokeras.supervised import Supervised, PortableClass
 from autokeras.constant import Constant
 from autokeras.metric import Accuracy, MSE
 from autokeras.preprocessor import OneHotEncoder, DataTransformer
@@ -323,6 +323,16 @@ def get_best_model_id(self):
         """ Return an integer indicating the id of the best model."""
         return self.load_searcher().get_best_model_id()
 
+    def export_keras_model(self, model_file_name):
+        """ Exports the best Keras model to the given filename. """
+        self.load_searcher().load_best_model().produce_keras_model().save(model_file_name)
+
+    def export_autokeras_model(self, model_file_name):
+        """ Creates and Exports the AutoKeras model to the given filename. """
+        portable_model = PortableImageSupervised(graph=self.load_searcher().load_best_model(), \
+            y_encoder=self.y_encoder, data_transformer=self.data_transformer)
+        pickle_to_file(portable_model, model_file_name)
+
 
 class ImageClassifier(ImageSupervised):
     @property
@@ -365,3 +375,43 @@ def transform_y(self, y_train):
 
     def inverse_transform_y(self, output):
         return output.flatten()
+
+class PortableImageSupervised(PortableClass):
+    def __init__(self, graph, data_transformer, y_encoder):
+        """Initialize the instance.
+        Args:
+            graph: The graph form of the learned model
+        """
+        self.graph = graph
+        self.data_transformer = data_transformer
+        self.y_encoder = y_encoder
+
+    def predict(self, x_test):
+        """Return predict results for the testing data.
+
+        Args:
+            x_test: An instance of numpy.ndarray containing the testing data.
+
+        Returns:
+            A numpy.ndarray containing the results.
+        """
+        if Constant.LIMIT_MEMORY:
+            pass
+        test_loader = self.data_transformer.transform_test(x_test)
+        model = self.graph.produce_model()
+        model.eval()
+
+        outputs = []
+        with torch.no_grad():
+            for index, inputs in enumerate(test_loader):
+                outputs.append(model(inputs).numpy())
+        output = reduce(lambda x, y: np.concatenate((x, y)), outputs)
+        return self.inverse_transform_y(output)
+        
+    def inverse_transform_y(self, output):
+        return self.y_encoder.inverse_transform(output)
+
+    def evaluate(self, x_test, y_test):
+        """Return the accuracy score between predict value and `y_test`."""
+        y_predict = self.predict(x_test)
+        return accuracy_score(y_test, y_predict)
diff --git a/autokeras/supervised.py b/autokeras/supervised.py
--- a/autokeras/supervised.py
+++ b/autokeras/supervised.py
@@ -53,3 +53,30 @@ def predict(self, x_test):
     def evaluate(self, x_test, y_test):
         """Return the accuracy score between predict value and `y_test`."""
         pass
+
+class PortableClass(ABC):
+    def __init__(self, graph):
+        """Initialize the instance.
+
+        Args:
+            graph: The graph form of the learned model
+
+        """
+        self.graph = graph
+
+    @abstractmethod
+    def predict(self, x_test):
+        """Return predict results for the testing data.
+
+        Args:
+            x_test: An instance of numpy.ndarray containing the testing data.
+
+        Returns:
+            A numpy.ndarray containing the results.
+        """
+        pass
+
+    @abstractmethod
+    def evaluate(self, x_test, y_test):
+        """Return the accuracy score between predict value and `y_test`."""
+        pass
\ No newline at end of file


Test patch:
diff --git a/tests/test_image_supervised.py b/tests/test_image_supervised.py
--- a/tests/test_image_supervised.py
+++ b/tests/test_image_supervised.py
@@ -199,3 +199,37 @@ def test_fit_predict_regression(_):
     results = clf.predict(train_x)
     assert len(results) == len(train_x)
     clean_dir(path)
+
+def test_export_keras_model():
+    Constant.MAX_ITER_NUM = 1
+    Constant.MAX_MODEL_NUM = 1
+    Constant.SEARCH_MAX_ITER = 1
+    Constant.T_MIN = 0.8
+    train_x = np.random.rand(100, 25, 25, 1)
+    train_y = np.random.randint(0, 5, 100)
+    test_x = np.random.rand(100, 25, 25, 1)
+    path = 'tests/resources/temp'
+    clean_dir(path)
+    clf = ImageClassifier(path=path, verbose=False, resume=False)
+    clf.n_epochs = 100
+    clf.fit(train_x, train_y)
+    accuracy_score = clf.evaluate(train_x, train_y)
+    assert accuracy_score <= 1.0
+
+    model_file_name = 'test_keras_model.h5'
+    clf.export_keras_model(model_file_name)
+    from keras.models import load_model
+    model = load_model(model_file_name)
+    results = model.predict(test_x)
+    assert len(results) == len(test_x)
+    del model, results, model_file_name
+
+    model_file_name = 'test_autokeras_model.pkl'
+    clf.export_autokeras_model(model_file_name)
+    from autokeras.utils import pickle_from_file
+    model = pickle_from_file(model_file_name)
+    results = model.predict(test_x)
+    assert len(results) == len(test_x)
+    accuracy_score = model.evaluate(train_x, train_y)
+    assert accuracy_score <= 1.0
+    clean_dir(path)
\ No newline at end of file