rdsarjito commited on
Commit
fa30b73
Β·
1 Parent(s): 7e019c7
app.py CHANGED
@@ -1,222 +1,266 @@
1
  import streamlit as st
2
- import pandas as pd
3
  import pickle
 
4
  import numpy as np
5
- import os
6
- from datetime import datetime
 
7
 
8
  # Konfigurasi halaman
9
  st.set_page_config(
10
- page_title="Prediksi Alergen Makanan",
11
  page_icon="🍽️",
12
- layout="wide"
 
13
  )
14
 
15
- # Judul aplikasi
16
- st.title("🍽️ Sistem Prediksi Alergen Makanan")
17
- st.markdown("---")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
- # Cache untuk memuat model
20
- @st.cache_data
21
  def load_models():
22
- """Memuat semua model dan vectorizer yang tersimpan"""
23
- models = {}
24
- model_files = {
25
- "XGBoost": "models/XGBoost_model.pkl",
26
- "KNN": "models/KNN_model.pkl",
27
- "Random Forest": "models/Random Forest_model.pkl"
28
- }
29
-
30
- # Load TF-IDF vectorizer
31
  try:
32
- with open('models/tfidf_vectorizer.pkl', 'rb') as f:
33
- tfidf_vectorizer = pickle.load(f)
34
- except FileNotFoundError:
35
- st.error("❌ TF-IDF vectorizer tidak ditemukan! Pastikan file 'models/tfidf_vectorizer.pkl' ada.")
36
- return None, None
37
-
38
- # Load models
39
- for name, path in model_files.items():
40
- try:
41
- with open(path, 'rb') as f:
42
- models[name] = pickle.load(f)
43
- except FileNotFoundError:
44
- st.warning(f"⚠️ Model {name} tidak ditemukan di {path}")
45
-
46
- return models, tfidf_vectorizer
 
 
 
 
 
47
 
48
- # Fungsi prediksi
49
- def predict_allergens(text, model, vectorizer):
50
- """Melakukan prediksi alergen dari teks input"""
51
- # Transform text menggunakan TF-IDF vectorizer
52
- text_vector = vectorizer.transform([text])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- # Prediksi
55
- prediction = model.predict(text_vector)
56
- prediction_proba = model.predict_proba(text_vector)
57
 
58
- return prediction[0], prediction_proba
59
-
60
- # Load models
61
- models, tfidf_vectorizer = load_models()
62
-
63
- if models is None or tfidf_vectorizer is None:
64
- st.stop()
65
-
66
- # Sidebar untuk pemilihan model
67
- st.sidebar.header("βš™οΈ Pengaturan")
68
- selected_model = st.sidebar.selectbox(
69
- "Pilih Model:",
70
- list(models.keys()),
71
- help="Pilih model machine learning untuk prediksi"
72
- )
73
-
74
- # Label alergen
75
- allergen_labels = ['Susu', 'Kacang', 'Telur', 'Makanan Laut', 'Gandum']
76
- allergen_emojis = ['πŸ₯›', 'πŸ₯œ', 'πŸ₯š', '🦐', '🌾']
77
-
78
- # Main interface
79
- col1, col2 = st.columns([2, 1])
80
-
81
- with col1:
82
- st.header("πŸ“ Input Teks Makanan")
83
 
84
- # Text input
85
- user_input = st.text_area(
86
- "Masukkan deskripsi makanan atau ingredients:",
87
- placeholder="Contoh: nasi goreng dengan telur, udang, dan kacang tanah",
88
- height=100
89
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
- # Contoh input
92
- st.subheader("πŸ’‘ Contoh Input:")
93
- examples = [
94
- "pizza dengan keju mozzarella dan seafood",
95
- "roti gandum dengan selai kacang",
96
- "cake coklat dengan butter dan telur",
97
- "sup tom yum dengan udang dan cumi",
98
- "mie instan rasa ayam"
99
- ]
100
 
101
- example_cols = st.columns(len(examples))
102
- for i, example in enumerate(examples):
103
- if example_cols[i].button(f"Contoh {i+1}", help=example):
104
- user_input = example
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  st.rerun()
106
-
107
- with col2:
108
- st.header("ℹ️ Informasi Model")
109
- if selected_model in models:
110
- st.success(f"βœ… Model {selected_model} siap digunakan")
111
- st.info(f"πŸ“Š Jumlah label: {len(allergen_labels)}")
112
 
113
- # Model info
114
- model_info = {
115
- "XGBoost": "Gradient Boosting yang efisien",
116
- "KNN": "K-Nearest Neighbors",
117
- "Random Forest": "Ensemble dari decision trees"
118
- }
119
- st.write(f"**Deskripsi:** {model_info.get(selected_model, 'Model machine learning')}")
120
-
121
- # Prediksi
122
- if st.button("πŸ” Prediksi Alergen", type="primary", use_container_width=True):
123
- if user_input.strip():
124
- with st.spinner("Sedang melakukan prediksi..."):
125
- try:
126
- # Prediksi
127
- prediction, prediction_proba = predict_allergens(
128
- user_input,
129
- models[selected_model],
130
- tfidf_vectorizer
131
- )
132
 
133
- st.markdown("---")
134
- st.header("πŸ“Š Hasil Prediksi")
 
 
 
135
 
136
- # Hasil prediksi dalam bentuk metrics
137
- st.subheader("🎯 Deteksi Alergen")
138
-
139
- # Buat columns untuk menampilkan hasil
140
- cols = st.columns(len(allergen_labels))
141
-
142
- detected_allergens = []
143
- for i, (label, emoji) in enumerate(zip(allergen_labels, allergen_emojis)):
144
- with cols[i]:
145
- if prediction[i] == 1:
146
- st.success(f"{emoji} **{label}**\n\nβœ… **TERDETEKSI**")
147
- detected_allergens.append(label)
148
- else:
149
- st.info(f"{emoji} **{label}**\n\n❌ Tidak terdeteksi")
150
-
151
- # Ringkasan
152
- st.subheader("πŸ“‹ Ringkasan")
153
- if detected_allergens:
154
- st.warning(f"⚠️ **Alergen terdeteksi:** {', '.join(detected_allergens)}")
155
- st.write("**Rekomendasi:** Harap berhati-hati jika Anda memiliki alergi terhadap bahan-bahan tersebut.")
156
  else:
157
- st.success("βœ… **Tidak ada alergen utama yang terdeteksi**")
158
- st.write("**Catatan:** Selalu periksa label produk untuk memastikan keamanan.")
159
-
160
- # Probability scores (jika tersedia)
161
- try:
162
- st.subheader("πŸ“ˆ Tingkat Kepercayaan")
163
- prob_data = []
164
-
165
- for i, (label, emoji) in enumerate(zip(allergen_labels, allergen_emojis)):
166
- # Ambil probabilitas untuk kelas positif (indeks 1)
167
- if hasattr(prediction_proba[i], 'shape') and len(prediction_proba[i][0]) > 1:
168
- prob = prediction_proba[i][0][1] # Probabilitas kelas 1 (positif)
169
- else:
170
- prob = 0.5 # Default jika tidak ada probabilitas
171
-
172
- prob_data.append({
173
- 'Alergen': f"{emoji} {label}",
174
- 'Probabilitas': prob,
175
- 'Persentase': f"{prob*100:.1f}%"
176
- })
177
-
178
- prob_df = pd.DataFrame(prob_data)
179
-
180
- # Progress bars
181
- for _, row in prob_df.iterrows():
182
- st.write(f"**{row['Alergen']}**")
183
- st.progress(row['Probabilitas'])
184
- st.write(f"Kepercayaan: {row['Persentase']}")
185
- st.write("")
186
 
187
- except Exception as e:
188
- st.info("πŸ’‘ Tingkat kepercayaan tidak tersedia untuk model ini")
189
 
190
- # Timestamp
191
- st.markdown("---")
192
- st.caption(f"Prediksi dilakukan pada: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
 
 
 
 
 
 
193
 
194
- except Exception as e:
195
- st.error(f"❌ Terjadi kesalahan saat prediksi: {str(e)}")
196
- st.write("Pastikan semua file model sudah tersedia dan format input benar.")
197
- else:
198
- st.warning("⚠️ Silakan masukkan teks untuk prediksi!")
199
-
200
- # Footer
201
- st.markdown("---")
202
- st.markdown("""
203
- ### πŸ“Œ Catatan Penting:
204
- - Sistem ini adalah alat bantu dan tidak menggantikan konsultasi medis profesional
205
- - Selalu periksa label produk dan konsultasikan dengan dokter untuk alergi yang serius
206
- - Akurasi prediksi tergantung pada kualitas data training dan input yang diberikan
207
- """)
208
-
209
- # Informasi tambahan di sidebar
210
- st.sidebar.markdown("---")
211
- st.sidebar.header("πŸ“‹ Informasi Alergen")
212
- st.sidebar.markdown("""
213
- **Alergen yang dideteksi:**
214
- - πŸ₯› **Susu**: Produk dairy, keju, yogurt
215
- - πŸ₯œ **Kacang**: Kacang tanah, almond, dll
216
- - πŸ₯š **Telur**: Telur ayam dan produk turunannya
217
- - 🦐 **Makanan Laut**: Udang, ikan, kerang
218
- - 🌾 **Gandum**: Tepung terigu, roti, pasta
219
- """)
 
 
 
 
 
 
 
 
 
 
220
 
221
- st.sidebar.markdown("---")
222
- st.sidebar.info("πŸ’‘ **Tips:** Semakin detail deskripsi makanan yang Anda berikan, semakin akurat hasil prediksinya.")
 
1
  import streamlit as st
 
2
  import pickle
3
+ import pandas as pd
4
  import numpy as np
5
+ from sklearn.feature_extraction.text import TfidfVectorizer
6
+ import warnings
7
+ warnings.filterwarnings('ignore')
8
 
9
  # Konfigurasi halaman
10
  st.set_page_config(
11
+ page_title="🍽️ Deteksi Alergen Makanan",
12
  page_icon="🍽️",
13
+ layout="wide",
14
+ initial_sidebar_state="expanded"
15
  )
16
 
17
+ # CSS untuk styling
18
+ st.markdown("""
19
+ <style>
20
+ .main-header {
21
+ text-align: center;
22
+ color: #2E86AB;
23
+ font-size: 3rem;
24
+ font-weight: bold;
25
+ margin-bottom: 1rem;
26
+ }
27
+ .sub-header {
28
+ text-align: center;
29
+ color: #A23B72;
30
+ font-size: 1.2rem;
31
+ margin-bottom: 2rem;
32
+ }
33
+ .allergen-box {
34
+ background-color: #f0f2f6;
35
+ border-radius: 10px;
36
+ padding: 15px;
37
+ margin: 10px 0;
38
+ border-left: 5px solid #FF6B6B;
39
+ }
40
+ .safe-box {
41
+ background-color: #e8f5e8;
42
+ border-radius: 10px;
43
+ padding: 15px;
44
+ margin: 10px 0;
45
+ border-left: 5px solid #4CAF50;
46
+ }
47
+ .model-info {
48
+ background-color: #e3f2fd;
49
+ border-radius: 10px;
50
+ padding: 15px;
51
+ margin: 10px 0;
52
+ border-left: 5px solid #2196F3;
53
+ }
54
+ </style>
55
+ """, unsafe_allow_html=True)
56
 
57
+ @st.cache_resource
 
58
  def load_models():
59
+ """Load semua model dan vectorizer"""
 
 
 
 
 
 
 
 
60
  try:
61
+ models = {}
62
+
63
+ # Load TF-IDF Vectorizer
64
+ with open('saved_models/tfidf_vectorizer.pkl', 'rb') as f:
65
+ tfidf = pickle.load(f)
66
+
67
+ # Load Models
68
+ model_names = ['XGBoost', 'KNN', 'Random Forest']
69
+ for name in model_names:
70
+ try:
71
+ with open(f'saved_models/{name}_model.pkl', 'rb') as f:
72
+ models[name] = pickle.load(f)
73
+ except FileNotFoundError:
74
+ st.warning(f"Model {name} tidak ditemukan!")
75
+ continue
76
+
77
+ return tfidf, models
78
+ except Exception as e:
79
+ st.error(f"Error loading models: {str(e)}")
80
+ return None, {}
81
 
82
+ def predict_allergens(text, model, tfidf):
83
+ """Prediksi alergen dari teks"""
84
+ try:
85
+ # Transform teks menggunakan TF-IDF
86
+ X = tfidf.transform([text])
87
+
88
+ # Prediksi
89
+ prediction = model.predict(X)[0]
90
+ prediction_proba = model.predict_proba(X)
91
+
92
+ # Nama alergen
93
+ allergen_names = ['Susu', 'Kacang', 'Telur', 'Makanan Laut', 'Gandum']
94
+
95
+ results = {}
96
+ for i, allergen in enumerate(allergen_names):
97
+ results[allergen] = {
98
+ 'predicted': bool(prediction[i]),
99
+ 'probability': float(prediction_proba[i][0][1]) if len(prediction_proba[i][0]) > 1 else 0.0
100
+ }
101
+
102
+ return results
103
+ except Exception as e:
104
+ st.error(f"Error dalam prediksi: {str(e)}")
105
+ return {}
106
+
107
+ def main():
108
+ # Header
109
+ st.markdown('<h1 class="main-header">🍽️ Deteksi Alergen Makanan</h1>', unsafe_allow_html=True)
110
+ st.markdown('<p class="sub-header">Aplikasi AI untuk mendeteksi kandungan alergen dalam makanan berdasarkan deskripsi teks</p>', unsafe_allow_html=True)
111
 
112
+ # Load models
113
+ tfidf, models = load_models()
 
114
 
115
+ if not models:
116
+ st.error("❌ Tidak ada model yang berhasil dimuat. Pastikan file model ada di folder 'saved_models/'")
117
+ st.info("πŸ“ File yang dibutuhkan:")
118
+ st.code("""
119
+ saved_models/
120
+ β”œβ”€β”€ tfidf_vectorizer.pkl
121
+ β”œβ”€β”€ XGBoost_model.pkl
122
+ β”œβ”€β”€ KNN_model.pkl
123
+ └── Random Forest_model.pkl
124
+ """)
125
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
+ # Sidebar - Model Selection
128
+ with st.sidebar:
129
+ st.markdown("### βš™οΈ Pengaturan Model")
130
+ selected_model = st.selectbox(
131
+ "Pilih Model:",
132
+ list(models.keys()),
133
+ help="Pilih model machine learning untuk prediksi"
134
+ )
135
+
136
+ st.markdown("### πŸ“Š Info Model")
137
+ st.markdown(f"""
138
+ <div class="model-info">
139
+ <strong>Model Aktif:</strong> {selected_model}<br>
140
+ <strong>Alergen yang Dideteksi:</strong><br>
141
+ β€’ πŸ₯› Susu<br>
142
+ β€’ πŸ₯œ Kacang<br>
143
+ β€’ πŸ₯š Telur<br>
144
+ β€’ 🦐 Makanan Laut<br>
145
+ β€’ 🌾 Gandum
146
+ </div>
147
+ """, unsafe_allow_html=True)
148
 
149
+ # Main content
150
+ col1, col2 = st.columns([2, 1])
 
 
 
 
 
 
 
151
 
152
+ with col1:
153
+ st.markdown("### πŸ“ Input Deskripsi Makanan")
154
+
155
+ # Text input
156
+ user_input = st.text_area(
157
+ "Masukkan deskripsi makanan atau bahan-bahan:",
158
+ placeholder="Contoh: Kue coklat dengan krim susu, ditaburi kacang almond dan remah biskuit gandum...",
159
+ height=150,
160
+ help="Masukkan deskripsi makanan dalam bahasa Indonesia"
161
+ )
162
+
163
+ # Contoh input
164
+ st.markdown("#### πŸ’‘ Contoh Input:")
165
+ examples = [
166
+ "Kue coklat dengan krim susu dan kacang almond",
167
+ "Nasi goreng seafood dengan udang dan cumi",
168
+ "Roti gandum dengan selai kacang",
169
+ "Es krim vanilla dengan topping biskuit",
170
+ "Salad sayuran segar tanpa dressing"
171
+ ]
172
+
173
+ selected_example = st.selectbox("Pilih contoh atau tulis sendiri:", [""] + examples)
174
+ if selected_example and st.button("πŸ“‹ Gunakan Contoh"):
175
+ user_input = selected_example
176
  st.rerun()
177
+
178
+ with col2:
179
+ st.markdown("### 🎯 Hasil Prediksi")
 
 
 
180
 
181
+ if user_input and st.button("πŸ” Analisis Alergen", type="primary"):
182
+ with st.spinner("Menganalisis..."):
183
+ results = predict_allergens(user_input, models[selected_model], tfidf)
184
+
185
+ if results:
186
+ # Tampilkan hasil
187
+ allergens_detected = []
188
+ safe_allergens = []
 
 
 
 
 
 
 
 
 
 
 
189
 
190
+ for allergen, result in results.items():
191
+ if result['predicted']:
192
+ allergens_detected.append((allergen, result['probability']))
193
+ else:
194
+ safe_allergens.append((allergen, result['probability']))
195
 
196
+ # Alergen terdeteksi
197
+ if allergens_detected:
198
+ st.markdown("#### ⚠️ Alergen Terdeteksi:")
199
+ for allergen, prob in allergens_detected:
200
+ emoji_map = {'Susu': 'πŸ₯›', 'Kacang': 'πŸ₯œ', 'Telur': 'πŸ₯š', 'Makanan Laut': '🦐', 'Gandum': '🌾'}
201
+ st.markdown(f"""
202
+ <div class="allergen-box">
203
+ <strong>{emoji_map.get(allergen, '🚨')} {allergen}</strong><br>
204
+ Confidence: {prob:.2%}
205
+ </div>
206
+ """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
207
  else:
208
+ st.markdown("""
209
+ <div class="safe-box">
210
+ <strong>βœ… Aman</strong><br>
211
+ Tidak ada alergen yang terdeteksi
212
+ </div>
213
+ """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
 
215
+ # Detail semua hasil
216
+ st.markdown("#### πŸ“Š Detail Lengkap:")
217
 
218
+ # Buat DataFrame untuk hasil
219
+ df_results = pd.DataFrame([
220
+ {
221
+ 'Alergen': allergen,
222
+ 'Status': '⚠️ Terdeteksi' if result['predicted'] else 'βœ… Aman',
223
+ 'Confidence': f"{result['probability']:.2%}"
224
+ }
225
+ for allergen, result in results.items()
226
+ ])
227
 
228
+ st.dataframe(df_results, use_container_width=True, hide_index=True)
229
+
230
+ elif user_input:
231
+ st.info("πŸ‘† Klik tombol 'Analisis Alergen' untuk memulai prediksi")
232
+ else:
233
+ st.info("πŸ“ Masukkan deskripsi makanan terlebih dahulu")
234
+
235
+ # Footer information
236
+ st.markdown("---")
237
+ st.markdown("### ℹ️ Informasi Aplikasi")
238
+
239
+ col1, col2, col3 = st.columns(3)
240
+
241
+ with col1:
242
+ st.markdown("""
243
+ **🎯 Tujuan:**
244
+ - Deteksi otomatis alergen dalam makanan
245
+ - Membantu penderita alergi makanan
246
+ - Analisis berbasis AI/ML
247
+ """)
248
+
249
+ with col2:
250
+ st.markdown("""
251
+ **πŸ”¬ Teknologi:**
252
+ - TF-IDF Vectorization
253
+ - Multi-output Classification
254
+ - XGBoost, KNN, Random Forest
255
+ """)
256
+
257
+ with col3:
258
+ st.markdown("""
259
+ **⚠️ Disclaimer:**
260
+ - Hasil prediksi tidak 100% akurat
261
+ - Selalu konsultasi dengan ahli
262
+ - Untuk referensi saja
263
+ """)
264
 
265
+ if __name__ == "__main__":
266
+ main()
requirements.txt CHANGED
@@ -1,6 +1,7 @@
1
- streamlit
2
- pandas
3
- scikit-learn
4
- xgboost
5
- numpy
6
- pickle
 
 
1
+ streamlit==1.28.1
2
+ pandas==2.0.3
3
+ numpy==1.24.3
4
+ scikit-learn==1.3.0
5
+ xgboost==1.7.6
6
+ tqdm==4.65.0
7
+ pickle-mixin==1.0.2
{models β†’ saved_models}/KNN_model.pkl RENAMED
File without changes
{models β†’ saved_models}/Random Forest_model.pkl RENAMED
File without changes
{models β†’ saved_models}/XGBoost_model.pkl RENAMED
File without changes
{models β†’ saved_models}/tfidf_vectorizer.pkl RENAMED
File without changes