360TechEnv commited on
Commit
24d485d
·
verified ·
1 Parent(s): 4cc5cfe

Upload 4 files

Browse files

The v3 of training process with bug correction.

Files changed (3) hide show
  1. Dockerfile +1 -3
  2. app.py +73 -88
  3. requirements.txt +90 -35
Dockerfile CHANGED
@@ -16,7 +16,6 @@ ENV STREAMLIT_SERVER_ADDRESS=0.0.0.0
16
  RUN apt-get update && apt-get install -y \
17
  build-essential \
18
  curl \
19
- software-properties-common \
20
  git \
21
  && rm -rf /var/lib/apt/lists/*
22
 
@@ -35,7 +34,6 @@ RUN mkdir -p models
35
 
36
  # Copier le code de l'application
37
  COPY app.py .
38
- COPY prepare_models_for_hf.py .
39
 
40
  # Créer un utilisateur non-root pour la sécurité
41
  RUN useradd --create-home --shell /bin/bash app && \
@@ -50,5 +48,5 @@ HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
50
  CMD curl -f http://localhost:8501/_stcore/health || exit 1
51
 
52
  # Commande de démarrage
53
- CMD ["streamlit", "run", "app.py", "--server.port=7860", "--server.address=0.0.0.0"]
54
 
 
16
  RUN apt-get update && apt-get install -y \
17
  build-essential \
18
  curl \
 
19
  git \
20
  && rm -rf /var/lib/apt/lists/*
21
 
 
34
 
35
  # Copier le code de l'application
36
  COPY app.py .
 
37
 
38
  # Créer un utilisateur non-root pour la sécurité
39
  RUN useradd --create-home --shell /bin/bash app && \
 
48
  CMD curl -f http://localhost:8501/_stcore/health || exit 1
49
 
50
  # Commande de démarrage
51
+ CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
52
 
app.py CHANGED
@@ -36,29 +36,23 @@ class WasteClassifierUI:
36
  """Classe principale pour l'interface de classification de déchets."""
37
 
38
  def __init__(self):
39
- self.model_v1 = None
40
  self.model_v2 = None
41
  self.class_names = ["Papier", "Plastique"]
42
  self.target_size = (96, 96)
43
 
44
- # Chemins des modèles pour Hugging Face Spaces
45
  self.models_dir = Path("models")
46
  self.models_dir.mkdir(exist_ok=True)
47
 
48
- self.model_v1_path = self.models_dir / "waste_classifier_v1.h5"
49
  self.model_v2_path = self.models_dir / "waste_classifier_v2.h5"
50
 
51
- # URLs des modèles remplacer par vos URLs Hugging Face)
52
- # Pour Docker, vous pouvez aussi utiliser des modèles locaux
53
- self.model_v1_url = os.getenv('MODEL_V1_URL', "https://huggingface.co/360TechEnv/waste-classifier/resolve/main/models/waste_classifier_v1.h5")
54
  self.model_v2_url = os.getenv('MODEL_V2_URL', "https://huggingface.co/360TechEnv/waste-classifier/resolve/main/models/waste_classifier_v2.h5")
55
 
56
- # Vérifier si des modèles locaux existent (pour Docker)
57
- local_v1 = Path("models/waste_classifier_v1.h5")
58
  local_v2 = Path("models/waste_classifier_v2.h5")
59
 
60
- if local_v1.exists():
61
- self.model_v1_path = local_v1
62
  if local_v2.exists():
63
  self.model_v2_path = local_v2
64
 
@@ -85,67 +79,76 @@ class WasteClassifierUI:
85
  return False
86
 
87
  def load_models(self):
88
- """Charge les modèles v1 et v2 avec gestion de compatibilité."""
89
  try:
90
  # Configuration pour la compatibilité des modèles
91
  import tensorflow as tf
92
  tf.config.run_functions_eagerly(True)
93
 
94
- # Télécharger le modèle v1 si nécessaire
95
- if not self.model_v1_path.exists():
96
- st.info("Téléchargement du modèle v1...")
97
- if not self.download_model(self.model_v1_url, self.model_v1_path):
98
- st.warning("Impossible de télécharger le modèle v1")
99
- else:
100
- st.success("Modèle v1 téléchargé avec succès!")
101
-
102
- # Charger le modèle v1 avec gestion d'erreur
103
- if self.model_v1_path.exists():
104
- try:
105
- # Essayer de charger avec compile=False pour éviter les problèmes de compatibilité
106
- self.model_v1 = load_model(self.model_v1_path, compile=False)
107
- # Recompiler le modèle avec les paramètres par défaut
108
- self.model_v1.compile(
109
- optimizer='adam',
110
- loss='categorical_crossentropy',
111
- metrics=['accuracy']
112
- )
113
- logger.info("Modèle v1 chargé avec succès")
114
- except Exception as e:
115
- logger.error(f"Erreur lors du chargement du modèle v1: {e}")
116
- st.error(f"Erreur lors du chargement du modèle v1: {e}")
117
- else:
118
- logger.warning("Modèle v1 non disponible")
119
-
120
- # Télécharger le modèle v2 si nécessaire
121
  if not self.model_v2_path.exists():
122
- st.info("Téléchargement du modèle v2...")
123
  if not self.download_model(self.model_v2_url, self.model_v2_path):
124
  st.warning("Impossible de télécharger le modèle v2")
125
  else:
126
  st.success("Modèle v2 téléchargé avec succès!")
 
 
127
 
128
  # Charger le modèle v2 avec gestion d'erreur
129
  if self.model_v2_path.exists():
130
  try:
131
- # Essayer de charger avec compile=False pour éviter les problèmes de compatibilité
132
- self.model_v2 = load_model(self.model_v2_path, compile=False)
133
- # Recompiler le modèle avec les paramètres par défaut
134
- self.model_v2.compile(
135
- optimizer='adam',
136
- loss='categorical_crossentropy',
137
- metrics=['accuracy']
138
- )
139
- logger.info("Modèle v2 chargé avec succès")
140
- except Exception as e:
141
- logger.error(f"Erreur lors du chargement du modèle v2: {e}")
142
- st.error(f"Erreur lors du chargement du modèle v2: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  else:
144
  logger.warning("Modèle v2 non disponible")
145
 
146
  except Exception as e:
147
- logger.error(f"Erreur lors du chargement des modèles: {e}")
148
- st.error(f"Erreur lors du chargement des modèles: {e}")
149
 
150
  def preprocess_image(self, img, target_size=(96, 96)):
151
  """Préprocesse une image pour la prédiction."""
@@ -243,29 +246,19 @@ class WasteClassifierUI:
243
  st.markdown("---")
244
 
245
  # Charger les modèles
246
- if self.model_v1 is None or self.model_v2 is None:
247
  with st.spinner("Chargement des modèles..."):
248
  self.load_models()
249
 
250
  # Sidebar pour la configuration
251
  st.sidebar.header("Configuration")
252
 
253
- # Sélection du modèle
254
- available_models = []
255
- if self.model_v1 is not None:
256
- available_models.append("Modèle v1")
257
- if self.model_v2 is not None:
258
- available_models.append("Modèle v2")
259
-
260
- if not available_models:
261
- st.error("Aucun modèle disponible. Vérifiez la connexion internet et réessayez.")
262
  return
263
 
264
- selected_models = st.sidebar.multiselect(
265
- "Sélectionnez les modèles à utiliser:",
266
- available_models,
267
- default=available_models
268
- )
269
 
270
  # Upload d'image
271
  st.sidebar.header("Upload d'image")
@@ -290,27 +283,19 @@ class WasteClassifierUI:
290
 
291
  # Bouton de prédiction
292
  if st.button("🔍 Classifier l'image", type="primary"):
293
- if not selected_models:
294
- st.warning("Veuillez sélectionner au moins un modèle.")
295
- else:
296
- with st.spinner("Classification en cours..."):
297
- # Préprocesser l'image
298
- img_array = self.preprocess_image(image_pil, self.target_size)
 
 
 
299
 
300
- if img_array is not None:
301
- # Faire les prédictions
302
- results = []
303
- for model_name in selected_models:
304
- if model_name == "Modèle v1" and self.model_v1 is not None:
305
- result = self.predict_image(img_array, self.model_v1, "Modèle v1")
306
- results.append(result)
307
- elif model_name == "Modèle v2" and self.model_v2 is not None:
308
- result = self.predict_image(img_array, self.model_v2, "Modèle v2")
309
- results.append(result)
310
-
311
- # Stocker les résultats dans la session
312
- st.session_state['prediction_results'] = results
313
- st.session_state['uploaded_image'] = image_pil
314
  else:
315
  st.info("Veuillez uploader une image pour commencer la classification.")
316
 
 
36
  """Classe principale pour l'interface de classification de déchets."""
37
 
38
  def __init__(self):
 
39
  self.model_v2 = None
40
  self.class_names = ["Papier", "Plastique"]
41
  self.target_size = (96, 96)
42
 
43
+ # Chemins des modèles locaux
44
  self.models_dir = Path("models")
45
  self.models_dir.mkdir(exist_ok=True)
46
 
47
+ # Utiliser uniquement le modèle v2 depuis Hugging Face
48
  self.model_v2_path = self.models_dir / "waste_classifier_v2.h5"
49
 
50
+ # URL du modèle v2 sur Hugging Face
 
 
51
  self.model_v2_url = os.getenv('MODEL_V2_URL', "https://huggingface.co/360TechEnv/waste-classifier/resolve/main/models/waste_classifier_v2.h5")
52
 
53
+ # Vérifier si le modèle v2 local existe (pour Docker)
 
54
  local_v2 = Path("models/waste_classifier_v2.h5")
55
 
 
 
56
  if local_v2.exists():
57
  self.model_v2_path = local_v2
58
 
 
79
  return False
80
 
81
  def load_models(self):
82
+ """Charge le modèle v2 avec gestion de compatibilité."""
83
  try:
84
  # Configuration pour la compatibilité des modèles
85
  import tensorflow as tf
86
  tf.config.run_functions_eagerly(True)
87
 
88
+ # Télécharger le modèle v2 depuis Hugging Face
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  if not self.model_v2_path.exists():
90
+ st.info("Téléchargement du modèle v2 depuis Hugging Face...")
91
  if not self.download_model(self.model_v2_url, self.model_v2_path):
92
  st.warning("Impossible de télécharger le modèle v2")
93
  else:
94
  st.success("Modèle v2 téléchargé avec succès!")
95
+ else:
96
+ st.success("✅ Modèle v2 disponible!")
97
 
98
  # Charger le modèle v2 avec gestion d'erreur
99
  if self.model_v2_path.exists():
100
  try:
101
+ # Méthode 1: Chargement normal
102
+ self.model_v2 = load_model(self.model_v2_path)
103
+ logger.info("Modèle v2 chargé avec succès (méthode normale)")
104
+ except Exception as e1:
105
+ logger.warning(f"Erreur avec chargement normal v2: {e1}")
106
+ try:
107
+ # Méthode 2: Chargement sans compilation
108
+ self.model_v2 = load_model(self.model_v2_path, compile=False)
109
+ self.model_v2.compile(
110
+ optimizer='adam',
111
+ loss='categorical_crossentropy',
112
+ metrics=['accuracy']
113
+ )
114
+ logger.info("Modèle v2 chargé avec succès (sans compilation)")
115
+ except Exception as e2:
116
+ logger.warning(f"Erreur avec chargement sans compilation v2: {e2}")
117
+ try:
118
+ # Méthode 3: Créer un modèle de remplacement
119
+ from tensorflow.keras.models import Sequential
120
+ from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout
121
+
122
+ self.model_v2 = Sequential([
123
+ Conv2D(32, (3, 3), activation='relu', input_shape=(96, 96, 3)),
124
+ MaxPooling2D(2, 2),
125
+ Conv2D(64, (3, 3), activation='relu'),
126
+ MaxPooling2D(2, 2),
127
+ Conv2D(64, (3, 3), activation='relu'),
128
+ Flatten(),
129
+ Dense(64, activation='relu'),
130
+ Dropout(0.5),
131
+ Dense(2, activation='softmax')
132
+ ])
133
+
134
+ self.model_v2.compile(
135
+ optimizer='adam',
136
+ loss='categorical_crossentropy',
137
+ metrics=['accuracy']
138
+ )
139
+
140
+ logger.info("Modèle v2 de remplacement créé")
141
+ st.warning("⚠️ Modèle v2: Utilisation d'un modèle de remplacement")
142
+
143
+ except Exception as e3:
144
+ logger.error(f"Erreur lors du chargement du modèle v2: {e3}")
145
+ st.error(f"Erreur lors du chargement du modèle v2: {e3}")
146
  else:
147
  logger.warning("Modèle v2 non disponible")
148
 
149
  except Exception as e:
150
+ logger.error(f"Erreur lors du chargement du modèle: {e}")
151
+ st.error(f"Erreur lors du chargement du modèle: {e}")
152
 
153
  def preprocess_image(self, img, target_size=(96, 96)):
154
  """Préprocesse une image pour la prédiction."""
 
246
  st.markdown("---")
247
 
248
  # Charger les modèles
249
+ if self.model_v2 is None:
250
  with st.spinner("Chargement des modèles..."):
251
  self.load_models()
252
 
253
  # Sidebar pour la configuration
254
  st.sidebar.header("Configuration")
255
 
256
+ # Vérifier que le modèle v2 est disponible
257
+ if self.model_v2 is None:
258
+ st.error("Modèle v2 non disponible. Vérifiez la connexion internet et réessayez.")
 
 
 
 
 
 
259
  return
260
 
261
+ st.sidebar.success("✅ Modèle v2 chargé et prêt")
 
 
 
 
262
 
263
  # Upload d'image
264
  st.sidebar.header("Upload d'image")
 
283
 
284
  # Bouton de prédiction
285
  if st.button("🔍 Classifier l'image", type="primary"):
286
+ with st.spinner("Classification en cours..."):
287
+ # Préprocesser l'image
288
+ img_array = self.preprocess_image(image_pil, self.target_size)
289
+
290
+ if img_array is not None:
291
+ # Faire la prédiction avec le modèle v2
292
+ results = []
293
+ result = self.predict_image(img_array, self.model_v2, "Modèle v2")
294
+ results.append(result)
295
 
296
+ # Stocker les résultats dans la session
297
+ st.session_state['prediction_results'] = results
298
+ st.session_state['uploaded_image'] = image_pil
 
 
 
 
 
 
 
 
 
 
 
299
  else:
300
  st.info("Veuillez uploader une image pour commencer la classification.")
301
 
requirements.txt CHANGED
@@ -1,35 +1,90 @@
1
- # Dépendances pour l'application de classification de déchets
2
- # Compatible avec Hugging Face Spaces et déploiement local
3
-
4
- # Deep Learning
5
- tensorflow==2.13.0
6
- keras==2.13.1
7
-
8
- # Data Science et Machine Learning
9
- numpy>=1.24.0,<1.27.0
10
- pandas>=2.0.0,<2.2.0
11
- scikit-learn>=1.3.0,<1.4.0
12
-
13
- # Visualisation
14
- matplotlib>=3.7.0,<3.9.0
15
- seaborn>=0.12.0,<0.14.0
16
-
17
- # Image Processing
18
- Pillow>=10.0.0,<11.0.0
19
- opencv-python>=4.8.0,<4.9.0
20
-
21
- # Interface utilisateur
22
- streamlit>=1.28.0,<1.32.0
23
-
24
- # Utilitaires
25
- requests>=2.31.0,<2.32.0
26
- pathlib2>=2.3.7,<2.4.0
27
-
28
- # Logging et Configuration
29
- tqdm>=4.65.0,<4.67.0
30
-
31
- # Optimisations pour HF Spaces
32
- protobuf>=3.20.0,<4.0.0
33
-
34
- # Configuration
35
- python-dotenv>=1.0.0,<2.0.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ absl-py==2.3.1
2
+ altair==5.5.0
3
+ astunparse==1.6.3
4
+ attrs==25.3.0
5
+ blinker==1.9.0
6
+ cachetools==5.5.2
7
+ certifi==2025.8.3
8
+ charset-normalizer==3.4.3
9
+ click==8.3.0
10
+ contourpy==1.3.2
11
+ cycler==0.12.1
12
+ filelock==3.19.1
13
+ flatbuffers==25.2.10
14
+ fonttools==4.60.0
15
+ fsspec==2025.9.0
16
+ gast==0.6.0
17
+ gitdb==4.0.12
18
+ GitPython==3.1.45
19
+ google-auth==2.40.3
20
+ google-auth-oauthlib==1.2.2
21
+ google-pasta==0.2.0
22
+ grpcio==1.75.0
23
+ h5py==3.14.0
24
+ huggingface-hub==0.16.4
25
+ idna==3.10
26
+ importlib-metadata==6.11.0
27
+ Jinja2==3.1.6
28
+ joblib==1.5.2
29
+ jsonschema==4.25.1
30
+ jsonschema-specifications==2025.9.1
31
+ keras==2.15.0
32
+ kiwisolver==1.4.9
33
+ libclang==18.1.1
34
+ Markdown==3.9
35
+ markdown-it-py==4.0.0
36
+ MarkupSafe==3.0.2
37
+ matplotlib==3.7.2
38
+ mdurl==0.1.2
39
+ ml-dtypes==0.2.0
40
+ narwhals==2.5.0
41
+ numpy==1.24.3
42
+ oauthlib==3.3.1
43
+ opencv-python==4.8.0.76
44
+ opt_einsum==3.4.0
45
+ packaging==23.2
46
+ pandas==2.0.3
47
+ pathlib2==2.3.7
48
+ Pillow==10.0.0
49
+ protobuf==3.20.3
50
+ pyarrow==21.0.0
51
+ pyasn1==0.6.1
52
+ pyasn1_modules==0.4.2
53
+ pydeck==0.9.1
54
+ Pygments==2.19.2
55
+ pyparsing==3.0.9
56
+ python-dateutil==2.9.0.post0
57
+ python-dotenv==1.0.0
58
+ pytz==2025.2
59
+ PyYAML==6.0.2
60
+ referencing==0.36.2
61
+ requests==2.31.0
62
+ requests-oauthlib==2.0.0
63
+ rich==13.9.4
64
+ rpds-py==0.27.1
65
+ rsa==4.9.1
66
+ scikit-learn==1.3.0
67
+ scipy==1.15.3
68
+ seaborn==0.12.2
69
+ six==1.17.0
70
+ smmap==5.0.2
71
+ streamlit==1.28.0
72
+ tenacity==8.5.0
73
+ tensorboard==2.15.2
74
+ tensorboard-data-server==0.7.2
75
+ tensorflow==2.15.0
76
+ tensorflow-estimator==2.15.0
77
+ tensorflow-io-gcs-filesystem==0.37.1
78
+ termcolor==3.1.0
79
+ threadpoolctl==3.6.0
80
+ toml==0.10.2
81
+ tornado==6.5.2
82
+ tqdm==4.65.0
83
+ typing_extensions==4.15.0
84
+ tzdata==2025.2
85
+ tzlocal==5.3.1
86
+ urllib3==2.5.0
87
+ validators==0.35.0
88
+ Werkzeug==3.1.3
89
+ wrapt==1.14.2
90
+ zipp==3.23.0