Kevinyogap commited on
Commit
824c383
·
1 Parent(s): 0fb4951

Initial commit

Browse files
Files changed (7) hide show
  1. .gitignore +139 -0
  2. Dockerfile +12 -0
  3. README copy.md +119 -0
  4. app.py +275 -0
  5. iris_dataset_info.pkl +3 -0
  6. iris_decision_tree_model.pkl +3 -0
  7. requirements.txt +19 -0
.gitignore ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+ MANIFEST
23
+
24
+ # Virtual environments
25
+ .env
26
+ .venv
27
+ env/
28
+ venv/
29
+ ENV/
30
+ env.bak/
31
+ venv.bak/
32
+
33
+ # PyInstaller
34
+ *.manifest
35
+ *.spec
36
+
37
+ # Installer logs
38
+ pip-log.txt
39
+ pip-delete-this-directory.txt
40
+
41
+ # Unit test / coverage reports
42
+ htmlcov/
43
+ .tox/
44
+ .nox/
45
+ .coverage
46
+ .coverage.*
47
+ .cache
48
+ nosetests.xml
49
+ coverage.xml
50
+ *.cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+
54
+ # Jupyter Notebook
55
+ .ipynb_checkpoints
56
+
57
+ # IPython
58
+ profile_default/
59
+ ipython_config.py
60
+
61
+ # pyenv
62
+ .python-version
63
+
64
+ # Environments
65
+ .env
66
+ .env.local
67
+ .env.development
68
+ .env.test
69
+ .env.production
70
+
71
+ # Flask
72
+ instance/
73
+ .webassets-cache
74
+
75
+ # Scrapy
76
+ .scrapy
77
+
78
+ # Sphinx documentation
79
+ docs/_build/
80
+
81
+ # PyBuilder
82
+ target/
83
+
84
+ # mypy
85
+ .mypy_cache/
86
+ .dmypy.json
87
+ dmypy.json
88
+
89
+ # Pyre type checker
90
+ .pyre/
91
+
92
+ # IDEs
93
+ .vscode/
94
+ .idea/
95
+ *.swp
96
+ *.swo
97
+ *~
98
+
99
+ # OS
100
+ .DS_Store
101
+ .DS_Store?
102
+ ._*
103
+ .Spotlight-V100
104
+ .Trashes
105
+ ehthumbs.db
106
+ Thumbs.db
107
+
108
+ # Logs
109
+ *.log
110
+ logs/
111
+
112
+ # Model files (if you want to exclude them)
113
+ # *.pkl
114
+ # *.joblib
115
+ # *.h5
116
+ # *.model
117
+
118
+ # Data files (if you want to exclude them)
119
+ # *.csv
120
+ # *.json
121
+ # *.xlsx
122
+
123
+ # Temporary files
124
+ *.tmp
125
+ *.temp
126
+ *.bak
127
+ *.swp
128
+ *~.nib
129
+
130
+ # Docker
131
+ .dockerignore
132
+
133
+ # Git
134
+ .git/
135
+ .gitattributes
136
+
137
+ # Backup files
138
+ *.backup
139
+ *.old
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"]
README copy.md ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Iris Flower Classification
3
+ emoji: 🌸
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ sdk_version: "4.36.2"
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ ---
12
+
13
+ # 🌸 Iris Flower Classification API
14
+
15
+ Aplikasi Machine Learning untuk klasifikasi bunga Iris menggunakan Decision Tree Algorithm. API ini dapat memprediksi spesies bunga Iris (Setosa, Versicolor, atau Virginica) berdasarkan fitur morfologi bunga.
16
+
17
+ ## 📊 Dataset & Model
18
+
19
+ - **Dataset**: Iris Dataset dari scikit-learn
20
+ - **Algorithm**: Decision Tree Classifier
21
+ - **Features**:
22
+ - Sepal Length (cm)
23
+ - Sepal Width (cm)
24
+ - Petal Length (cm)
25
+ - Petal Width (cm)
26
+ - **Target Classes**: Setosa, Versicolor, Virginica
27
+
28
+ ## 🚀 API Endpoints
29
+
30
+ ### 1. Home Page
31
+ ```
32
+ GET /
33
+ ```
34
+ Web interface interaktif untuk testing model
35
+
36
+ ### 2. Predict Species
37
+ ```
38
+ POST /predict
39
+ Content-Type: application/json
40
+
41
+ {
42
+ "sepal_length": 5.1,
43
+ "sepal_width": 3.5,
44
+ "petal_length": 1.4,
45
+ "petal_width": 0.2
46
+ }
47
+ ```
48
+
49
+ ### 3. Model Information
50
+ ```
51
+ GET /model-info
52
+ ```
53
+ Informasi detail tentang model dan feature importance
54
+
55
+ ### 4. Health Check
56
+ ```
57
+ GET /health
58
+ ```
59
+ Status kesehatan API
60
+
61
+ ## 🧪 Example Usage
62
+
63
+ ### Prediksi Setosa:
64
+ ```json
65
+ {
66
+ "sepal_length": 5.1,
67
+ "sepal_width": 3.5,
68
+ "petal_length": 1.4,
69
+ "petal_width": 0.2
70
+ }
71
+ ```
72
+
73
+ ### Prediksi Versicolor:
74
+ ```json
75
+ {
76
+ "sepal_length": 7.0,
77
+ "sepal_width": 3.2,
78
+ "petal_length": 4.7,
79
+ "petal_width": 1.4
80
+ }
81
+ ```
82
+
83
+ ### Prediksi Virginica:
84
+ ```json
85
+ {
86
+ "sepal_length": 6.3,
87
+ "sepal_width": 3.3,
88
+ "petal_length": 6.0,
89
+ "petal_width": 2.5
90
+ }
91
+ ```
92
+
93
+ ## 📈 Model Performance
94
+
95
+ - **Accuracy**: 100% (pada test set)
96
+ - **Algorithm**: Decision Tree dengan random_state=42
97
+ - **Training Data**: 120 samples
98
+ - **Test Data**: 30 samples
99
+
100
+ ## 🔗 Key Decision Rules
101
+
102
+ Berdasarkan Decision Tree yang dihasilkan:
103
+
104
+ 1. **Setosa**: Petal Length ≤ 2.45 cm
105
+ 2. **Versicolor**: Petal Length > 2.45 cm AND Petal Width ≤ 1.75 cm
106
+ 3. **Virginica**: Petal Length > 2.45 cm AND Petal Width > 1.75 cm
107
+
108
+ ## 🛠️ Technology Stack
109
+
110
+ - **Backend**: Flask + Python 3.11
111
+ - **ML**: scikit-learn, pandas, numpy
112
+ - **Model Persistence**: joblib
113
+ - **Container**: Docker
114
+ - **Deployment**: Hugging Face Spaces
115
+
116
+ ## 👨‍💻 Author
117
+
118
+ Tugas 27 - Machine Learning Model Deployment
119
+ **Universitas/Institusi**: Infinite Learning
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
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:80cdf80af780e5869c3dcb58a9cd0727be89385b7e95f2bf5e0dca0a22b199a2
3
+ size 432
iris_decision_tree_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cf10bdbf4836130c150782b8526ebe4cfdb0d0317fa3780cfa87ef555a0af13e
3
+ size 3297
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