Ivan Shelonik commited on
Commit
df0d440
·
1 Parent(s): 68ec720

first commit

Browse files
Files changed (6) hide show
  1. Dockerfile +21 -0
  2. README.md +1 -0
  3. api_client.py +70 -0
  4. api_server.py +111 -0
  5. artifacts/models/.gitignore +2 -0
  6. model.py +46 -0
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use the official Python base image
2
+ FROM python:3.9
3
+
4
+ # Set the working directory in the container
5
+ WORKDIR /app
6
+
7
+ # Copy the requirements.txt file and install the Python dependencies
8
+ COPY requirements.txt .
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+ # Copy the Flask application code into the container
12
+ COPY . .
13
+
14
+ # Expose the port on which the Flask application will run
15
+ EXPOSE 5000
16
+
17
+ # Set the environment variable for Flask
18
+ ENV FLASK_APP=app.py
19
+
20
+ # Run the Flask application
21
+ CMD ["flask", "run", "--host=0.0.0.0"]
README.md CHANGED
@@ -4,6 +4,7 @@ emoji: 🐠
4
  colorFrom: indigo
5
  colorTo: blue
6
  sdk: docker
 
7
  pinned: false
8
  ---
9
 
 
4
  colorFrom: indigo
5
  colorTo: blue
6
  sdk: docker
7
+ app_port: 5000
8
  pinned: false
9
  ---
10
 
api_client.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import numpy as np
4
+ import requests
5
+ import matplotlib.pyplot as plt
6
+
7
+ # Disable tensorflow warnings
8
+ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
9
+
10
+ from keras.datasets import mnist
11
+ from typing import List
12
+
13
+
14
+ # Set random seed for reproducibility
15
+ np.random.seed(50)
16
+
17
+ # Number of images taken from test dataset to make prediction
18
+ N_IMAGES = 9
19
+
20
+ def get_image_prediction(image: List):
21
+ """Get Model prediction for a given image
22
+ :param
23
+ image: List
24
+ Grayscale Image
25
+ :return: Json
26
+ HTTP Response format:
27
+ {
28
+ "prediction": predicted_label,
29
+ "ml-latency-ms": latency_in_milliseconds
30
+ (Measures time only for ML operations preprocessing with predict)
31
+ }
32
+ """
33
+ # Making prediction request API
34
+ response = requests.post(url='http://127.0.0.1:5000/predict', json={'image': image})
35
+ # Parse the response JSON
36
+ return response.json()
37
+
38
+ # Load the dataset from keras.datasets
39
+ (x_train, y_train), (x_test, y_test) = mnist.load_data()
40
+
41
+ # Select N-th (N_IMAGES) random indices from x_test
42
+ indices = np.random.choice(len(x_test), N_IMAGES, replace=False)
43
+
44
+ # Get the images and labels based on the selected indices
45
+ images, labels, predictions = x_test[indices], y_test[indices], []
46
+
47
+ # Iterate over each image, invoke prediction API and save results to predictions array
48
+ for i in range(N_IMAGES):
49
+ # Send the POST request to the Flask server
50
+ start_time = time.time()
51
+ model_response = get_image_prediction(images[i].tolist())
52
+ print('Model Response:', model_response)
53
+ print('Total Measured Time (ms):', round((time.time() - start_time) * 1000, 3))
54
+ # Save prediction data into predictions array
55
+ predictions.append(model_response['prediction'])
56
+
57
+ def plot_images_and_results_plot(images_, labels_, predictions_):
58
+ """Plotting the images with their labels and predictions
59
+ """
60
+ fig, axes = plt.subplots(N_IMAGES, 1, figsize=(6, 10))
61
+
62
+ for i in range(N_IMAGES):
63
+ axes[i].imshow(images_[i], cmap='gray')
64
+ axes[i].axis('off')
65
+ axes[i].set_title("Label/Prediction: {}/{}".format(labels_[i], predictions_[i]))
66
+
67
+ plt.tight_layout()
68
+ plt.show()
69
+
70
+ plot_images_and_results_plot(images, labels, predictions)
api_server.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import numpy as np
4
+
5
+ # Disable tensorflow warnings
6
+ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
7
+
8
+ from tensorflow import keras
9
+ from flask import Flask, jsonify, request
10
+
11
+ # Load the saved model into memory
12
+ model = keras.models.load_model('artifacts/models/mnist_model.h5')
13
+
14
+ # Initialize the Flask application
15
+ app = Flask(__name__)
16
+
17
+
18
+ # API route for prediction
19
+ @app.route('/predict', methods=['POST'])
20
+ def predict():
21
+ """
22
+ Predicts the class label of an input image.
23
+
24
+ Request format:
25
+ {
26
+ "image": [[pixel_values_gray]]
27
+ }
28
+
29
+ Response format:
30
+ {
31
+ "prediction": predicted_label,
32
+ "ml-latency-ms": latency_in_milliseconds
33
+ (Measures time only for ML operations preprocessing with predict)
34
+ }
35
+ """
36
+ start_time = time.time()
37
+
38
+ # Get the image data from the request
39
+ image_data = request.get_json()['image']
40
+
41
+ # Preprocess the image
42
+ processed_image = preprocess_image(image_data)
43
+
44
+ # Make a prediction, verbose=0 to disable progress bar in logs
45
+ prediction = model.predict(processed_image, verbose=0)
46
+
47
+ # Get the predicted class label
48
+ predicted_label = np.argmax(prediction)
49
+
50
+ # Calculate latency in milliseconds
51
+ latency_ms = (time.time() - start_time) * 1000
52
+
53
+ # Return the prediction result and latency as JSON response
54
+ response = {'prediction': int(predicted_label),
55
+ 'ml-latency-ms': round(latency_ms, 4)}
56
+
57
+ # dictionary is not a JSON: https://www.quora.com/What-is-the-difference-between-JSON-and-a-dictionary
58
+ # flask.jsonify vs json.dumps https://sentry.io/answers/difference-between-json-dumps-and-flask-jsonify/
59
+ # The flask.jsonify() function returns a Response object with Serializable JSON and content_type=application/json.
60
+ return jsonify(response)
61
+
62
+
63
+ # Helper function to preprocess the image
64
+ def preprocess_image(image_data):
65
+ """Preprocess image for Model Inference
66
+
67
+ :param image_data: Raw image
68
+ :return: image: Preprocessed Image
69
+ """
70
+ # Resize the image to match the input shape of the model
71
+ image = np.array(image_data).reshape(1, 28, 28)
72
+
73
+ # Normalize the pixel values
74
+ image = image.astype('float32') / 255.0
75
+
76
+ return image
77
+
78
+
79
+ # API route for health check
80
+ @app.route('/health', methods=['GET'])
81
+ def health():
82
+ """
83
+ Health check API to ensure the application is running.
84
+ Returns "OK" if the application is healthy.
85
+ Demo Usage: "curl http://localhost:5000/health" or using alias "curl http://127.0.0.1:5000/health"
86
+ """
87
+ return 'OK'
88
+
89
+
90
+ # API route for version
91
+ @app.route('/version', methods=['GET'])
92
+ def version():
93
+ """
94
+ Returns the version of the application.
95
+ Demo Usage: "curl http://127.0.0.1:5000/version" or using alias "curl http://127.0.0.1:5000/version"
96
+ """
97
+ return '1.0'
98
+
99
+
100
+ # Start the Flask application
101
+ if __name__ == '__main__':
102
+ app.run()
103
+
104
+
105
+ ##################
106
+ # Flask API usages:
107
+ # 1. Just a wrapper over OpenAI API
108
+ # 2. You can use Chain calls of OpenAI API
109
+ # 3. Using your own ML model in combination with openAPI functionality
110
+ # 4. ...
111
+ ##################
artifacts/models/.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ *
2
+ !.gitignore
model.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import numpy as np
4
+
5
+ # disable tensorflow warnings
6
+ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
7
+
8
+ import tensorflow as tf
9
+ from tensorflow import keras
10
+ from keras.datasets import mnist
11
+
12
+ # Set the random seed for reproducibility, remember these lines :)
13
+ SEED = 42
14
+ random.seed(SEED)
15
+ np.random.seed(SEED)
16
+ tf.random.set_seed(SEED)
17
+
18
+ # Load the dataset from keras.datasets (so noone would need to download it manually from any sources)
19
+ (x_train, y_train), (x_test, y_test) = mnist.load_data()
20
+
21
+ # Preprocess the dataset
22
+ x_train = x_train.astype('float32') / 255.0
23
+ x_test = x_test.astype('float32') / 255.0
24
+
25
+ # Define the model architecture
26
+ model = keras.Sequential([
27
+ keras.layers.Flatten(input_shape=(28, 28)),
28
+ keras.layers.Dense(128, activation='relu'),
29
+ keras.layers.Dense(10, activation='softmax')
30
+ ])
31
+
32
+ # Compile and train the model
33
+ # target in one-hot categorical_crossentropy -> [0,0,1,0,0,0,0,0,0]
34
+ # target can be as integer sparse_categorical_crossentropy -> 3
35
+ model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
36
+
37
+ # 4-epoch is overfitting, 3-rd is okay
38
+ model.fit(x_train, y_train, validation_data=(x_test, y_test), epochs=4, shuffle=True, batch_size=32)
39
+
40
+ # Evaluate the model
41
+ print('\n')
42
+ _, test_accuracy = model.evaluate(x_test, y_test)
43
+ print('Test accuracy:', test_accuracy)
44
+
45
+ # Save the model
46
+ model.save('artifacts/models/mnist_model.h5')