Nasywan commited on
Commit
2bc90a3
·
0 Parent(s):

Initial commit: Iris Classification API

Browse files
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /flask_model_api
4
+
5
+ COPY requirements.txt requirements.txt
6
+ RUN pip install --upgrade pip && pip install -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ EXPOSE 7860
11
+
12
+ CMD ["python", "app.py"]
Tugas_12_Iris_Flower_Classification_with_Decision_Tree.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
app.py ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ import joblib
4
+ import numpy as np
5
+ import pandas as pd
6
+ from flask import Flask, jsonify, render_template_string, request
7
+ from flask_cors import CORS
8
+ from sklearn.datasets import load_iris
9
+ from sklearn.metrics import accuracy_score
10
+ from sklearn.model_selection import train_test_split
11
+ from sklearn.tree import DecisionTreeClassifier
12
+
13
+ app = Flask(__name__)
14
+ CORS(app)
15
+
16
+ # Global variables for model and iris data
17
+ model = None
18
+ iris = None
19
+ feature_names = None
20
+ target_names = None
21
+
22
+ def load_or_train_model():
23
+ """Load existing model or train new one if not exists"""
24
+ global model, iris, feature_names, target_names
25
+
26
+ # Load iris dataset
27
+ iris = load_iris()
28
+ feature_names = iris.feature_names
29
+ target_names = iris.target_names
30
+
31
+ model_path = 'iris_decision_tree_model.pkl'
32
+
33
+ if os.path.exists(model_path):
34
+ # Load existing model
35
+ model = joblib.load(model_path)
36
+ print("Model loaded from file")
37
+ else:
38
+ # Train new model
39
+ print("Training new model...")
40
+ X = iris.data
41
+ y = iris.target
42
+
43
+ X_train, X_test, y_train, y_test = train_test_split(
44
+ X, y, test_size=0.2, random_state=42
45
+ )
46
+
47
+ model = DecisionTreeClassifier(random_state=42)
48
+ model.fit(X_train, y_train)
49
+
50
+ # Save model
51
+ joblib.dump(model, model_path)
52
+
53
+ # Print accuracy
54
+ y_pred = model.predict(X_test)
55
+ accuracy = accuracy_score(y_test, y_pred)
56
+ print(f"Model trained with accuracy: {accuracy:.4f}")
57
+
58
+ # Initialize model on startup
59
+ load_or_train_model()
60
+
61
+ @app.route('/')
62
+ def home():
63
+ html = """
64
+ <!DOCTYPE html>
65
+ <html>
66
+ <head>
67
+ <title>Iris Flower Classification API</title>
68
+ <style>
69
+ body { font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }
70
+ .container { max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 0 20px rgba(0,0,0,0.1); }
71
+ h1 { color: #2c3e50; text-align: center; }
72
+ .feature { margin: 10px 0; }
73
+ .feature label { display: inline-block; width: 200px; font-weight: bold; }
74
+ .feature input { padding: 8px; width: 200px; border: 1px solid #ddd; border-radius: 4px; }
75
+ button { background: #3498db; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; margin: 10px 5px; }
76
+ button:hover { background: #2980b9; }
77
+ .result { margin: 20px 0; padding: 15px; background: #ecf0f1; border-radius: 5px; }
78
+ .examples { background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 20px 0; }
79
+ </style>
80
+ </head>
81
+ <body>
82
+ <div class="container">
83
+ <h1>🌸 Iris Flower Classification API</h1>
84
+ <p>Masukkan nilai fitur bunga Iris untuk memprediksi spesiesnya:</p>
85
+
86
+ <form id="irisForm">
87
+ <div class="feature">
88
+ <label>Sepal Length (cm):</label>
89
+ <input type="number" step="0.1" id="sepal_length" placeholder="e.g., 5.1" required>
90
+ </div>
91
+ <div class="feature">
92
+ <label>Sepal Width (cm):</label>
93
+ <input type="number" step="0.1" id="sepal_width" placeholder="e.g., 3.5" required>
94
+ </div>
95
+ <div class="feature">
96
+ <label>Petal Length (cm):</label>
97
+ <input type="number" step="0.1" id="petal_length" placeholder="e.g., 1.4" required>
98
+ </div>
99
+ <div class="feature">
100
+ <label>Petal Width (cm):</label>
101
+ <input type="number" step="0.1" id="petal_width" placeholder="e.g., 0.2" required>
102
+ </div>
103
+
104
+ <button type="submit">Prediksi Spesies</button>
105
+ <button type="button" onclick="loadExample(1)">Contoh Setosa</button>
106
+ <button type="button" onclick="loadExample(2)">Contoh Versicolor</button>
107
+ <button type="button" onclick="loadExample(3)">Contoh Virginica</button>
108
+ </form>
109
+
110
+ <div id="result" class="result" style="display:none;">
111
+ <h3>Hasil Prediksi:</h3>
112
+ <p id="prediction"></p>
113
+ <p id="confidence"></p>
114
+ </div>
115
+
116
+ <div class="examples">
117
+ <h3>Contoh Data:</h3>
118
+ <p><strong>Setosa:</strong> Sepal Length: 5.1, Sepal Width: 3.5, Petal Length: 1.4, Petal Width: 0.2</p>
119
+ <p><strong>Versicolor:</strong> Sepal Length: 7.0, Sepal Width: 3.2, Petal Length: 4.7, Petal Width: 1.4</p>
120
+ <p><strong>Virginica:</strong> Sepal Length: 6.3, Sepal Width: 3.3, Petal Length: 6.0, Petal Width: 2.5</p>
121
+ </div>
122
+ </div>
123
+
124
+ <script>
125
+ function loadExample(type) {
126
+ if (type === 1) {
127
+ document.getElementById('sepal_length').value = 5.1;
128
+ document.getElementById('sepal_width').value = 3.5;
129
+ document.getElementById('petal_length').value = 1.4;
130
+ document.getElementById('petal_width').value = 0.2;
131
+ } else if (type === 2) {
132
+ document.getElementById('sepal_length').value = 7.0;
133
+ document.getElementById('sepal_width').value = 3.2;
134
+ document.getElementById('petal_length').value = 4.7;
135
+ document.getElementById('petal_width').value = 1.4;
136
+ } else if (type === 3) {
137
+ document.getElementById('sepal_length').value = 6.3;
138
+ document.getElementById('sepal_width').value = 3.3;
139
+ document.getElementById('petal_length').value = 6.0;
140
+ document.getElementById('petal_width').value = 2.5;
141
+ }
142
+ }
143
+
144
+ document.getElementById('irisForm').addEventListener('submit', function(e) {
145
+ e.preventDefault();
146
+
147
+ const data = {
148
+ sepal_length: parseFloat(document.getElementById('sepal_length').value),
149
+ sepal_width: parseFloat(document.getElementById('sepal_width').value),
150
+ petal_length: parseFloat(document.getElementById('petal_length').value),
151
+ petal_width: parseFloat(document.getElementById('petal_width').value)
152
+ };
153
+
154
+ fetch('/predict', {
155
+ method: 'POST',
156
+ headers: {
157
+ 'Content-Type': 'application/json',
158
+ },
159
+ body: JSON.stringify(data)
160
+ })
161
+ .then(response => response.json())
162
+ .then(data => {
163
+ if (data.error) {
164
+ alert('Error: ' + data.error);
165
+ } else {
166
+ document.getElementById('prediction').innerHTML =
167
+ `<strong>Spesies: ${data.species}</strong>`;
168
+ document.getElementById('confidence').innerHTML =
169
+ `Confidence: ${data.confidence}`;
170
+ document.getElementById('result').style.display = 'block';
171
+ }
172
+ })
173
+ .catch(error => {
174
+ alert('Error: ' + error);
175
+ });
176
+ });
177
+ </script>
178
+ </body>
179
+ </html>
180
+ """
181
+ return html
182
+
183
+ @app.route('/predict', methods=['POST'])
184
+ def predict_iris():
185
+ try:
186
+ # Ambil data dari request
187
+ data = request.json
188
+
189
+ if not data:
190
+ return jsonify({'error': 'No data provided'}), 400
191
+
192
+ # Validasi input
193
+ required_fields = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
194
+ for field in required_fields:
195
+ if field not in data:
196
+ return jsonify({'error': f'Missing field: {field}'}), 400
197
+ if not isinstance(data[field], (int, float)):
198
+ return jsonify({'error': f'Invalid value for {field}. Must be a number'}), 400
199
+
200
+ # Konversi ke array numpy
201
+ features = np.array([[
202
+ data['sepal_length'],
203
+ data['sepal_width'],
204
+ data['petal_length'],
205
+ data['petal_width']
206
+ ]])
207
+
208
+ # Validasi range nilai (opsional)
209
+ if any(val < 0 for val in features[0]):
210
+ return jsonify({'error': 'All feature values must be positive'}), 400
211
+
212
+ # Prediksi
213
+ prediction = model.predict(features)[0]
214
+ prediction_proba = model.predict_proba(features)[0]
215
+
216
+ # Konversi ke nama spesies
217
+ species = target_names[prediction]
218
+ confidence = f"{prediction_proba[prediction]:.2%}"
219
+
220
+ # Tambahan info untuk debugging
221
+ probabilities = {
222
+ target_names[i]: f"{prob:.2%}"
223
+ for i, prob in enumerate(prediction_proba)
224
+ }
225
+
226
+ return jsonify({
227
+ 'species': species,
228
+ 'species_code': int(prediction),
229
+ 'confidence': confidence,
230
+ 'all_probabilities': probabilities,
231
+ 'input_features': {
232
+ 'sepal_length': data['sepal_length'],
233
+ 'sepal_width': data['sepal_width'],
234
+ 'petal_length': data['petal_length'],
235
+ 'petal_width': data['petal_width']
236
+ }
237
+ })
238
+
239
+ except Exception as e:
240
+ return jsonify({'error': str(e)}), 500
241
+
242
+ @app.route('/model-info', methods=['GET'])
243
+ def model_info():
244
+ """Endpoint untuk mendapatkan informasi model"""
245
+ try:
246
+ # Dapatkan feature importance
247
+ feature_importance = model.feature_importances_
248
+ feature_info = {
249
+ feature_names[i]: float(importance)
250
+ for i, importance in enumerate(feature_importance)
251
+ }
252
+
253
+ return jsonify({
254
+ 'model_type': 'Decision Tree Classifier',
255
+ 'features': list(feature_names),
256
+ 'target_classes': list(target_names),
257
+ 'feature_importance': feature_info,
258
+ 'tree_depth': model.get_depth(),
259
+ 'number_of_leaves': model.get_n_leaves(),
260
+ 'training_samples': len(iris.data)
261
+ })
262
+
263
+ except Exception as e:
264
+ return jsonify({'error': str(e)}), 500
265
+
266
+ @app.route('/health', methods=['GET'])
267
+ def health():
268
+ return jsonify({
269
+ 'status': 'OK',
270
+ 'message': 'Iris Classification API is running',
271
+ 'model_loaded': model is not None
272
+ }), 200
273
+
274
+ if __name__ == '__main__':
275
+ app.run(debug=True, host='0.0.0.0', port=7860)
iris_dataset_info.pkl ADDED
Binary file (432 Bytes). View file
 
iris_decision_tree_model.pkl ADDED
Binary file (3.3 kB). View file
 
requirements.txt ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core data science libraries
2
+ pandas>=1.3.0
3
+ numpy>=1.21.0
4
+ matplotlib>=3.4.0
5
+ seaborn>=0.11.0
6
+ scikit-learn>=1.0.0
7
+
8
+ # Web framework and API
9
+ Flask>=2.0.0
10
+ flask-cors>=3.0.0
11
+
12
+ # Machine learning model persistence
13
+ joblib>=1.0.0
14
+
15
+ # Development and notebook
16
+ jupyter>=1.0.0
17
+
18
+ # Optional: for better performance
19
+ gunicorn>=20.0.0