quantumbit commited on
Commit
7e18445
·
verified ·
1 Parent(s): 06aff79

Upload 7 files

Browse files
Dockerfile ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "app:app"]
app.py ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify
2
+ import numpy as np
3
+ import tensorflow as tf
4
+ from PIL import Image
5
+ import io
6
+ import base64
7
+ import re
8
+ import joblib
9
+ import os
10
+
11
+ app = Flask(__name__)
12
+
13
+ # Ensure the "images" directory exists
14
+ IMAGE_DIR = "images"
15
+ if not os.path.exists(IMAGE_DIR):
16
+ os.makedirs(IMAGE_DIR)
17
+
18
+ # Load all models - use absolute paths for Hugging Face
19
+ MODEL_DIR = os.path.join(os.getcwd(), "models")
20
+ models = {
21
+ "cnn": tf.keras.models.load_model(os.path.join(MODEL_DIR, "mnist_cnn_model.h5")),
22
+ "svm": joblib.load(os.path.join(MODEL_DIR, "mnist_svm.pkl")),
23
+ "logistic": joblib.load(os.path.join(MODEL_DIR, "mnist_logistic_regression.pkl")),
24
+ "random_forest": joblib.load(os.path.join(MODEL_DIR, "mnist_random_forest.pkl"))
25
+ }
26
+
27
+ # [Keep your existing classification_reports, preprocess_image,
28
+ # and create_simulated_scores functions exactly as they are]
29
+ # Classification reports for each model
30
+ classification_reports = {
31
+ "cnn": """
32
+ precision recall f1-score support
33
+ 0 0.99 1.00 0.99 980
34
+ 1 1.00 1.00 1.00 1135
35
+ 2 0.99 0.99 0.99 1032
36
+ 3 0.99 1.00 0.99 1010
37
+ 4 1.00 0.99 0.99 982
38
+ 5 0.98 0.99 0.99 892
39
+ 6 1.00 0.98 0.99 958
40
+ 7 0.99 0.99 0.99 1028
41
+ 8 1.00 0.99 0.99 974
42
+ 9 0.99 0.99 0.99 1009
43
+ accuracy 0.99 10000
44
+ macro avg 0.99 0.99 0.99 10000
45
+ weighted avg 0.99 0.99 0.99 10000
46
+ """,
47
+ "svm": """
48
+ precision recall f1-score support
49
+ 0 0.9874 0.9896 0.9885 1343
50
+ 1 0.9882 0.9925 0.9903 1600
51
+ 2 0.9706 0.9819 0.9762 1380
52
+ 3 0.9783 0.9749 0.9766 1433
53
+ 4 0.9777 0.9822 0.9800 1295
54
+ 5 0.9827 0.9796 0.9811 1273
55
+ 6 0.9858 0.9921 0.9889 1396
56
+ 7 0.9768 0.9807 0.9788 1503
57
+ 8 0.9813 0.9683 0.9748 1357
58
+ 9 0.9807 0.9669 0.9738 1420
59
+ accuracy 0.9810 14000
60
+ macro avg 0.9809 0.9809 0.9809 14000
61
+ weighted avg 0.9810 0.9810 0.9810 14000
62
+ """,
63
+ "random_forest": """
64
+ precision recall f1-score support
65
+ 0 0.9844 0.9866 0.9855 1343
66
+ 1 0.9831 0.9831 0.9831 1600
67
+ 2 0.9522 0.9674 0.9597 1380
68
+ 3 0.9579 0.9532 0.9556 1433
69
+ 4 0.9617 0.9699 0.9658 1295
70
+ 5 0.9707 0.9631 0.9669 1273
71
+ 6 0.9800 0.9828 0.9814 1396
72
+ 7 0.9668 0.9681 0.9674 1503
73
+ 8 0.9599 0.9528 0.9564 1357
74
+ 9 0.9566 0.9465 0.9515 1420
75
+ accuracy 0.9675 14000
76
+ macro avg 0.9673 0.9674 0.9673 14000
77
+ weighted avg 0.9675 0.9675 0.9675 14000
78
+ """,
79
+ "logistic": """
80
+ precision recall f1-score support
81
+ 0 0.9636 0.9650 0.9643 1343
82
+ 1 0.9433 0.9675 0.9553 1600
83
+ 2 0.9113 0.8935 0.9023 1380
84
+ 3 0.9021 0.8939 0.8980 1433
85
+ 4 0.9225 0.9290 0.9257 1295
86
+ 5 0.8846 0.8790 0.8818 1273
87
+ 6 0.9420 0.9534 0.9477 1396
88
+ 7 0.9273 0.9421 0.9347 1503
89
+ 8 0.8973 0.8696 0.8832 1357
90
+ 9 0.9019 0.9000 0.9010 1420
91
+ accuracy 0.9204 14000
92
+ macro avg 0.9196 0.9193 0.9194 14000
93
+ weighted avg 0.9201 0.9204 0.9202 14000
94
+ """
95
+ }
96
+
97
+ # Preprocess image before prediction
98
+ def preprocess_image(image, model_type):
99
+ image = image.resize((28, 28)).convert('L') # Convert to grayscale
100
+ img_array = np.array(image) / 255.0 # Normalize
101
+
102
+ if model_type == "cnn":
103
+ # CNN expects 4D tensor with channel dimension
104
+ return np.expand_dims(np.expand_dims(img_array, axis=0), axis=-1)
105
+ else:
106
+ # Other models expect flattened 1D array
107
+ return img_array.flatten().reshape(1, -1)
108
+
109
+ @app.route('/')
110
+ def home():
111
+ return jsonify({
112
+ "message": "MNIST Classifier API",
113
+ "available_models": list(models.keys()),
114
+ "endpoints": {
115
+ "/predict": "POST - Send image and model_type",
116
+ "/get_classification_report": "POST - Get model metrics"
117
+ }
118
+ })
119
+
120
+ # [Keep your existing /get_classification_report and /predict routes exactly as they are]
121
+ @app.route('/get_classification_report', methods=['POST'])
122
+ def get_classification_report():
123
+ model_type = request.json['model_type']
124
+ if model_type in classification_reports:
125
+ return jsonify({
126
+ 'report': classification_reports[model_type]
127
+ })
128
+ return jsonify({'error': 'Model not found'})
129
+
130
+ @app.route('/predict', methods=['POST'])
131
+ def predict():
132
+ if request.method == 'POST':
133
+ data = request.json['image']
134
+ model_type = request.json['model_type']
135
+
136
+ img_data = re.sub('^data:image/png;base64,', '', data)
137
+ img = Image.open(io.BytesIO(base64.b64decode(img_data)))
138
+
139
+ # Save the image to "images" folder
140
+ image_path = os.path.join(IMAGE_DIR, "digit.png")
141
+ img.save(image_path)
142
+
143
+ # Preprocess image and predict
144
+ processed_image = preprocess_image(img, model_type)
145
+
146
+ if model_type in models:
147
+ model = models[model_type]
148
+
149
+ # Model-specific prediction logic
150
+ if model_type == "cnn":
151
+ # For CNN, use softmax probabilities
152
+ prediction = model.predict(processed_image)
153
+ predicted_digit = np.argmax(prediction)
154
+ confidence_scores = prediction[0].tolist()
155
+ score_type = "probability"
156
+
157
+ elif model_type == "svm":
158
+ # For SVM, use decision function distances
159
+ predicted_digit = model.predict(processed_image)[0]
160
+
161
+ # Try to get decision function scores
162
+ if hasattr(model, "decision_function") and callable(getattr(model, "decision_function")):
163
+ try:
164
+ # Get raw decision scores
165
+ decision_scores = model.decision_function(processed_image)
166
+
167
+ # One-vs-One SVMs have a different shape for decision_function output
168
+ if len(decision_scores.shape) == 2:
169
+ # This is a standard one-vs-rest SVM, shape should be (1, n_classes)
170
+ confidence_scores = decision_scores[0].tolist()
171
+ else:
172
+ # One-vs-One SVM returns pairwise comparisons
173
+ # Convert to a simplified score per class (this is an approximation)
174
+ confidence_scores = [0] * 10
175
+ for i in range(10):
176
+ # Count how many times class i wins in pairwise comparisons
177
+ confidence_scores[i] = sum(1 for score in decision_scores[0] if score > 0)
178
+
179
+ # Normalize scores to positive values for visualization
180
+ min_score = min(confidence_scores)
181
+ if min_score < 0:
182
+ confidence_scores = [score - min_score for score in confidence_scores]
183
+
184
+ score_type = "decision_distance"
185
+ except (AttributeError, NotImplementedError) as e:
186
+ print(f"Error getting decision function: {e}")
187
+ confidence_scores = create_simulated_scores(int(predicted_digit))
188
+ score_type = "simulated"
189
+ else:
190
+ # Fallback if decision_function is not available
191
+ confidence_scores = create_simulated_scores(int(predicted_digit))
192
+ score_type = "simulated"
193
+
194
+ else:
195
+ # For other models (Random Forest, Logistic Regression)
196
+ predicted_digit = model.predict(processed_image)[0]
197
+
198
+ # Try to get probability estimates
199
+ if hasattr(model, "predict_proba") and callable(getattr(model, "predict_proba")):
200
+ try:
201
+ confidence_scores = model.predict_proba(processed_image)[0].tolist()
202
+ score_type = "probability"
203
+ except (AttributeError, NotImplementedError):
204
+ confidence_scores = create_simulated_scores(int(predicted_digit))
205
+ score_type = "simulated"
206
+ else:
207
+ confidence_scores = create_simulated_scores(int(predicted_digit))
208
+ score_type = "simulated"
209
+
210
+ return jsonify({
211
+ 'digit': int(predicted_digit),
212
+ 'confidence_scores': confidence_scores,
213
+ 'score_type': score_type
214
+ })
215
+
216
+ return jsonify({'error': 'Model not found'})
217
+
218
+ def create_simulated_scores(predicted_digit):
219
+ """Create simulated confidence scores that sum to 1.0 with highest probability for the predicted digit."""
220
+ # Assign base probabilities
221
+ scores = [0.01] * 10 # Give each digit a small base probability
222
+
223
+ # Calculate remaining probability (should be around 0.9)
224
+ remaining = 1.0 - sum(scores)
225
+
226
+ # Assign the remaining probability to the predicted digit
227
+ scores[predicted_digit] += remaining
228
+
229
+ return scores
230
+
231
+ if __name__ == '__main__':
232
+ app.run(host='0.0.0.0', port=7860) # Hugging Face uses port 7860
models/mnist_cnn_model.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4f31254d7c3f1c601d4f9e9dac7412af3a4121207e861d046b271e52628c3c41
3
+ size 782992
models/mnist_logistic_regression.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:49a1943a9c672caace5d1af724f439b1176bb04cebec1df27fed35dca6e10054
3
+ size 63639
models/mnist_random_forest.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7bd2a6d25c6a2c3869951fde30cc8687407dae809310aa52ec8b632c100885b1
3
+ size 101529065
models/mnist_svm.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:35038ec8695cb4d2364138f19d2f6d1709818f4bea9491b3811b89035ae3bc9f
3
+ size 66700155
requirements.txt ADDED
Binary file (7.54 kB). View file