├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── initialized-ksonnet-kubeflow.tar.gz
├── tensorflow-model
├── Dockerfile
├── MNIST.py
└── README.md
└── web-ui
├── Dockerfile
├── README.md
├── flask_server.py
├── mnist_client.py
├── static
├── scripts
│ └── material.min.js
└── styles
│ ├── demo.css
│ └── material.deep_purple-pink.min.css
└── templates
└── index.html
/.gitignore:
--------------------------------------------------------------------------------
1 | ks-kubeflow/environments/*
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution,
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Community Guidelines
26 |
27 | This project follows [Google's Open Source Community
28 | Guidelines](https://opensource.google.com/conduct/).
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Introduction to Kubeflow on Google Kubernetes Engine
2 |
3 |
4 | ### Depreciation Notice
5 |
6 | The [corresponding codelab](https://codelabs.developers.google.com/codelabs/kubeflow-introduction/index.html#0) has been updated to support newer versions of Kubeflow, and this repository has been deprecated in the process. The code associated with the codelab is now available in the official [Kubeflow examples repository](https://github.com/kubeflow/examples/tree/master/mnist)
7 |
8 | ---
9 |
10 | This codelab explains how to use a Google Kubernetes Engine cluster to deploy an open source machine learning stack
11 | (Kubeflow) and make use of it to train and deploy a model that will then serve traffic.
12 |
13 | ---
14 | This is not an officially supported Google product
15 |
--------------------------------------------------------------------------------
/initialized-ksonnet-kubeflow.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/kubeflow-introduction/c871d217edb469e356ac7927994bfd7603dc3e9a/initialized-ksonnet-kubeflow.tar.gz
--------------------------------------------------------------------------------
/tensorflow-model/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM tensorflow/tensorflow:1.10.0
2 | MAINTAINER "Daniel Sanche"
3 |
4 | # add google cloud storage
5 | RUN pip --no-cache-dir install \
6 | google-cloud-storage \
7 | && \
8 | python -m ipykernel.kernelspec
9 |
10 | # include service account key
11 | RUN mkdir /home/tensorflow
12 | ADD key.json /home/tensorflow
13 | ENV GOOGLE_APPLICATION_CREDENTIALS "/home/tensorflow/key.json"
14 |
15 | # show python logs as they occur
16 | ENV PYTHONUNBUFFERED=0
17 |
18 | # include version and bucket as arguments to docker build
19 | ARG version=1
20 | ENV VERSION=$version
21 | ARG bucket
22 | ENV BUCKET=$bucket
23 |
24 | # run MNIST.py with included arguments
25 | ADD MNIST.py /home/tensorflow
26 | WORKDIR /home/tensorflow
27 | ENTRYPOINT /usr/bin/python /home/tensorflow/MNIST.py --version $VERSION --bucket $BUCKET
28 |
--------------------------------------------------------------------------------
/tensorflow-model/MNIST.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright 2018 Google LLC
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | https://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | '''
16 |
17 |
18 | import os
19 |
20 | from google.cloud import storage
21 | import tensorflow as tf
22 | from tensorflow.examples.tutorials.mnist import input_data
23 |
24 | ###############################################################
25 | # SETUP
26 | ###############################################################
27 |
28 | # define input arguments
29 | tf.app.flags.DEFINE_string('version', '1',
30 | 'model version')
31 | tf.app.flags.DEFINE_string('bucket', None,
32 | 'name of the GCS bucket')
33 | tf.app.flags.DEFINE_integer('steps', 2000,
34 | 'number of runs through entire training set')
35 | arg_version = tf.app.flags.FLAGS.version
36 | arg_bucket = tf.app.flags.FLAGS.bucket
37 | arg_steps = tf.app.flags.FLAGS.steps
38 |
39 | # network parameters
40 | mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
41 | feature_size = 784
42 | num_classes = 10
43 | hidden_size = feature_size - 100
44 | hidden_size2 = hidden_size - 100
45 | batch_size = 50
46 | learning_rate = 1e-4
47 |
48 |
49 | ###############################################################
50 | # BUILD THE GRAPH
51 | ###############################################################
52 |
53 | def create_layer(shape, prev_layer, is_output):
54 | W = tf.Variable(tf.truncated_normal(shape, stddev=0.1))
55 | b = tf.Variable(tf.constant(0.1, shape=[shape[1]]))
56 | activation = tf.matmul(prev_layer, W) + b
57 | if is_output:
58 | new_layer = tf.nn.softmax(activation)
59 | else:
60 | new_layer = tf.nn.relu(activation)
61 | tf.nn.dropout(new_layer, dropout_prob)
62 | return new_layer
63 |
64 |
65 | # define inputs
66 | x = tf.placeholder(tf.float32, [None, feature_size], name='x-input')
67 | y = tf.placeholder(tf.float32, [None, num_classes], name='y-input')
68 | dropout_prob = tf.placeholder(tf.float32)
69 |
70 | # define layer structure
71 | layer1 = create_layer([feature_size, hidden_size], x, False)
72 | layer2 = create_layer([hidden_size, hidden_size2], layer1, False)
73 | outlayer = create_layer([hidden_size2, num_classes], layer2, True)
74 | prediction = tf.argmax(outlayer, 1)
75 |
76 | # training ops
77 | total_cost = -tf.reduce_sum(y * tf.log(outlayer), reduction_indices=[1])
78 | mean_cost = tf.reduce_mean(total_cost)
79 | train = tf.train.AdamOptimizer(learning_rate).minimize(mean_cost)
80 |
81 | # accuracy ops
82 | correct_prediction = tf.equal(prediction, tf.argmax(y, 1))
83 | accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
84 |
85 | ###############################################################
86 | # TRAIN
87 | ###############################################################
88 |
89 | sess = tf.Session()
90 | init = tf.global_variables_initializer()
91 | sess.run(init)
92 |
93 | for i in range(arg_steps):
94 | batch_x, batch_y = mnist.train.next_batch(batch_size)
95 | feed_dict = {x: batch_x, y: batch_y, dropout_prob: 0.5}
96 | sess.run(train, feed_dict=feed_dict)
97 | if i % 100 == 0:
98 | feed_dict = {x: batch_x, y: batch_y, dropout_prob: 0.5}
99 | train_acc = sess.run(accuracy, feed_dict=feed_dict)
100 | print("step %d/%d, training accuracy %g" % (i, arg_steps, train_acc))
101 | # print final accuracy on test images
102 | feed_dict = {x: mnist.test.images, y: mnist.test.labels, dropout_prob: 1.0}
103 | print (sess.run(accuracy, feed_dict=feed_dict))
104 |
105 | ###############################################################
106 | # EXPORT TRAINED MODEL
107 | ###############################################################
108 |
109 | # create signature for TensorFlow Serving
110 | tensor_info_x = tf.saved_model.utils.build_tensor_info(x)
111 | tensor_info_pred = tf.saved_model.utils.build_tensor_info(prediction)
112 | tensor_info_scores = tf.saved_model.utils.build_tensor_info(outlayer)
113 | tensor_info_ver = tf.saved_model.utils.build_tensor_info(tf.constant([str(arg_version)]))
114 | prediction_signature = (tf.saved_model.signature_def_utils.build_signature_def(
115 | inputs={'images': tensor_info_x},
116 | outputs={'prediction': tensor_info_pred, 'scores': tensor_info_scores,
117 | 'model-version': tensor_info_ver},
118 | method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME))
119 | legacy_init_op = tf.group(tf.tables_initializer(), name='legacy_init_op')
120 |
121 | print("saving model locally")
122 | # save model to disk
123 | export_path = arg_version
124 | builder = tf.saved_model.builder.SavedModelBuilder(export_path)
125 | builder.add_meta_graph_and_variables(
126 | sess, [tf.saved_model.tag_constants.SERVING],
127 | signature_def_map={
128 | 'predict_images': prediction_signature
129 | },
130 | legacy_init_op=legacy_init_op)
131 | builder.save()
132 |
133 | ###############################################################
134 | # UPLOAD MODEL TO GOOGLE CLOUD STORAGE
135 | ###############################################################
136 |
137 | print("uploading to " + arg_bucket + "/" + arg_version)
138 | client = storage.Client()
139 | bucket = client.get_bucket(arg_bucket)
140 | if bucket:
141 | for root, dirs, files in os.walk(export_path):
142 | for file in files:
143 | path = os.path.join(root, file)
144 | blob = bucket.blob(path)
145 | blob.upload_from_filename(path)
146 |
--------------------------------------------------------------------------------
/tensorflow-model/README.md:
--------------------------------------------------------------------------------
1 | # tensorflow-model
2 |
3 | This folder contains files necessary to build a container for a tf-job
4 |
5 | - MNIST.py
6 | - contains TensorFlow code to train a model and upload the results to a Google Cloud Storage bucket
7 | - Dockerfile
8 | - describes how to build a container to run the job
9 |
10 | Note: before the container can be built, a service account key must be added to this folder that gives us access to the GCS bucket
11 |
12 |
13 | ---
14 | This is not an officially supported Google product
15 |
--------------------------------------------------------------------------------
/web-ui/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:16.04
2 | MAINTAINER "Daniel Sanche"
3 |
4 | # add TF dependencies
5 | RUN apt-get update && apt-get install -y --no-install-recommends \
6 | build-essential \
7 | curl \
8 | libfreetype6-dev \
9 | libpng12-dev \
10 | libzmq3-dev \
11 | pkg-config \
12 | python3 \
13 | python-dev \
14 | rsync \
15 | software-properties-common \
16 | unzip \
17 | && \
18 | apt-get clean && \
19 | rm -rf /var/lib/apt/lists/*
20 |
21 | # add python dependencies
22 | RUN curl -O https://bootstrap.pypa.io/get-pip.py && \
23 | python get-pip.py && \
24 | rm get-pip.py
25 |
26 | RUN pip --no-cache-dir install \
27 | Pillow \
28 | h5py \
29 | ipykernel \
30 | numpy \
31 | tensorflow \
32 | tensorflow-serving-api \
33 | flask \
34 | && \
35 | python -m ipykernel.kernelspec
36 |
37 | # show python logs as they occur
38 | ENV PYTHONUNBUFFERED=0
39 |
40 | # add project files
41 | ADD *.py /home/
42 | ADD templates/* /home/templates/
43 | ADD static/styles /home/static/styles/
44 | RUN mkdir /home/static/tmp/
45 | ADD static/scripts/ /home/static/scripts/
46 |
47 | # start server on port 5000
48 | WORKDIR /home/
49 | EXPOSE 5000
50 | ENTRYPOINT python flask_server.py
51 |
--------------------------------------------------------------------------------
/web-ui/README.md:
--------------------------------------------------------------------------------
1 | # web-ui
2 |
3 | The files in this folder define a web interface that can be used to interact with a TensorFlow server
4 |
5 | - flask_server.py
6 | - main server code. Handles incoming requests, and renders HTML from template
7 | - mnist_client.py
8 | - code to interact with TensorFlow model server
9 | - takes in an image and server details, and returns the server's response
10 | - Dockerfile
11 | - builds a runnable container out of the files in this directory
12 |
13 | ---
14 | This is not an officially supported Google product
15 |
--------------------------------------------------------------------------------
/web-ui/flask_server.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright 2018 Google LLC
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | https://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | '''
16 |
17 | import os
18 | from threading import Timer
19 | import uuid
20 |
21 | from flask import Flask, render_template, request
22 | from mnist_client import get_prediction, random_mnist
23 |
24 | app = Flask(__name__)
25 |
26 |
27 | # handle requests to the server
28 | @app.route("/")
29 | def main():
30 | # get url parameters for HTML template
31 | name_arg = request.args.get('name', 'mnist-serve')
32 | addr_arg = request.args.get('addr', 'mnist-serve')
33 | port_arg = request.args.get('port', '9000')
34 | args = {"name": name_arg, "addr": addr_arg, "port": port_arg}
35 | print(args)
36 |
37 | output = None
38 | connection = {"text": "", "success": False}
39 | img_id = str(uuid.uuid4())
40 | img_path = "static/tmp/" + img_id + ".png"
41 | try:
42 | # get a random test MNIST image
43 | x, y, _ = random_mnist(img_path)
44 | # get prediction from TensorFlow server
45 | pred, scores, ver = get_prediction(x,
46 | server_host=addr_arg,
47 | server_port=int(port_arg),
48 | server_name=name_arg,
49 | timeout=10)
50 | # if no exceptions thrown, server connection was a success
51 | connection["text"] = "Connected (model version: " + str(ver) + ")"
52 | connection["success"] = True
53 | # parse class confidence scores from server prediction
54 | scores_dict = []
55 | for i in range(0, 10):
56 | scores_dict += [{"index": str(i), "val": scores[i]}]
57 | output = {"truth": y, "prediction": pred,
58 | "img_path": img_path, "scores": scores_dict}
59 | except:
60 | # server connection failed
61 | connection["text"] = "Could Not Connect to Server"
62 | # after 10 seconds, delete cached image file from server
63 | t = Timer(10.0, remove_resource, [img_path])
64 | t.start()
65 | # render results using HTML template
66 | return render_template('index.html', output=output,
67 | connection=connection, args=args)
68 |
69 |
70 | def remove_resource(path):
71 | """
72 | attempt to delete file from path. Used to clean up MNIST testing images
73 |
74 | :param path: the path of the file to delete
75 | """
76 | try:
77 | os.remove(path)
78 | print("removed " + path)
79 | except OSError:
80 | print("no file at " + path)
81 |
82 |
83 | if __name__ == '__main__':
84 | app.run(debug=True, host='0.0.0.0')
85 |
--------------------------------------------------------------------------------
/web-ui/mnist_client.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python2.7
2 | '''
3 | Copyright 2018 Google LLC
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | '''
17 |
18 |
19 | from __future__ import print_function
20 |
21 | from grpc.beta import implementations
22 | import numpy as np
23 | from PIL import Image
24 | import tensorflow as tf
25 | from tensorflow.examples.tutorials.mnist import input_data
26 | from tensorflow_serving.apis import predict_pb2
27 | from tensorflow_serving.apis import prediction_service_pb2
28 |
29 |
30 | def get_prediction(image, server_host='127.0.0.1', server_port=9000,
31 | server_name="server", timeout=10.0):
32 | """
33 | Retrieve a prediction from a TensorFlow model server
34 |
35 | :param image: a MNIST image represented as a 1x784 array
36 | :param server_host: the address of the TensorFlow server
37 | :param server_port: the port used by the server
38 | :param server_name: the name of the server
39 | :param timeout: the amount of time to wait for a prediction to complete
40 | :return 0: the integer predicted in the MNIST image
41 | :return 1: the confidence scores for all classes
42 | :return 2: the version number of the model handling the request
43 | """
44 |
45 | print("connecting to:%s:%i" % (server_host, server_port))
46 | # initialize to server connection
47 | channel = implementations.insecure_channel(server_host, server_port)
48 | stub = prediction_service_pb2.beta_create_PredictionService_stub(channel)
49 |
50 | # build request
51 | request = predict_pb2.PredictRequest()
52 | request.model_spec.name = server_name
53 | request.model_spec.signature_name = 'predict_images'
54 | request.inputs['images'].CopyFrom(
55 | tf.contrib.util.make_tensor_proto(image, shape=image.shape))
56 |
57 | # retrieve results
58 | result = stub.Predict(request, timeout)
59 | resultVal = result.outputs['prediction'].int64_val
60 | scores = result.outputs['scores'].float_val
61 | version = result.outputs['model-version'].string_val
62 | return resultVal[0], scores, version[0]
63 |
64 |
65 | def random_mnist(save_path=None):
66 | """
67 | Pull a random image out of the MNIST test dataset
68 | Optionally save the selected image as a file to disk
69 |
70 | :param savePath: the path to save the file to. If None, file is not saved
71 | :return 0: a 1x784 representation of the MNIST image
72 | :return 1: the ground truth label associated with the image
73 | :return 2: a bool representing whether the image file was saved to disk
74 | """
75 |
76 | mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
77 | batch_size = 1
78 | batch_x, batch_y = mnist.test.next_batch(batch_size)
79 | saved = False
80 | if save_path is not None:
81 | # save image file to disk
82 | try:
83 | data = (batch_x * 255).astype(np.uint8).reshape(28, 28)
84 | img = Image.fromarray(data, 'L')
85 | img.save(save_path)
86 | saved = True
87 | except:
88 | pass
89 | return batch_x, np.argmax(batch_y), saved
90 |
--------------------------------------------------------------------------------
/web-ui/static/scripts/material.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * material-design-lite - Material Design Components in CSS, JS and HTML
3 | * @version v1.0.6
4 | * @license Apache-2.0
5 | * @copyright 2015 Google, Inc.
6 | * @link https://github.com/google/material-design-lite
7 | */
8 | !function(){"use strict";function e(e,t){if(e){if(t.element_.classList.contains(t.CssClasses_.MDL_JS_RIPPLE_EFFECT)){var s=document.createElement("span");s.classList.add(t.CssClasses_.MDL_RIPPLE_CONTAINER),s.classList.add(t.CssClasses_.MDL_JS_RIPPLE_EFFECT);var i=document.createElement("span");i.classList.add(t.CssClasses_.MDL_RIPPLE),s.appendChild(i),e.appendChild(s)}e.addEventListener("click",function(s){s.preventDefault();var i=e.href.split("#")[1],n=t.element_.querySelector("#"+i);t.resetTabState_(),t.resetPanelState_(),e.classList.add(t.CssClasses_.ACTIVE_CLASS),n.classList.add(t.CssClasses_.ACTIVE_CLASS)})}}function t(e,t,s,i){if(i.tabBar_.classList.contains(i.CssClasses_.JS_RIPPLE_EFFECT)){var n=document.createElement("span");n.classList.add(i.CssClasses_.RIPPLE_CONTAINER),n.classList.add(i.CssClasses_.JS_RIPPLE_EFFECT);var a=document.createElement("span");a.classList.add(i.CssClasses_.RIPPLE),n.appendChild(a),e.appendChild(n)}e.addEventListener("click",function(n){n.preventDefault();var a=e.href.split("#")[1],l=i.content_.querySelector("#"+a);i.resetTabState_(t),i.resetPanelState_(s),e.classList.add(i.CssClasses_.IS_ACTIVE),l.classList.add(i.CssClasses_.IS_ACTIVE)})}var s={upgradeDom:function(e,t){},upgradeElement:function(e,t){},upgradeElements:function(e){},upgradeAllRegistered:function(){},registerUpgradedCallback:function(e,t){},register:function(e){},downgradeElements:function(e){}};s=function(){function e(e,t){for(var s=0;s