ChaoukiBenzekri commited on
Commit
e17e074
·
1 Parent(s): b1430f9

Add own test py file

Browse files
Files changed (1) hide show
  1. own_test.py +437 -0
own_test.py ADDED
@@ -0,0 +1,437 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from PIL import Image, ImageOps
3
+ import tensorflow as tf
4
+ import keras
5
+ import os
6
+ import numpy as np
7
+ import pandas as pd
8
+ import time
9
+ import cv2
10
+ os.environ["KERAS_BACKEND"] = "tensorflow"
11
+
12
+ from custom_functions import make_gradcam_heatmap, save_and_display_gradcam
13
+ from keras.layers import Conv2D
14
+ from tensorflow.keras.preprocessing.image import img_to_array, array_to_img
15
+ from keras.models import load_model
16
+
17
+ #modele prédiction COVID
18
+ model_densenet = tf.keras.models.load_model("models/DenseNet201_finetuned.h5")
19
+ model_vgg = tf.keras.models.load_model("models/VGG16_finetuned.h5")
20
+ model_tri = tf.keras.models.load_model("models/filtermodel.h5")
21
+ model = None
22
+
23
+ def show_test():
24
+
25
+ # Style des onglets
26
+ st.markdown("""
27
+ <style>
28
+ .stTabs [data-baseweb="tab-list"] {
29
+ display: flex;
30
+ gap: 10px;
31
+ }
32
+
33
+ .stTabs [data-baseweb="tab"] {
34
+ padding: 10px 15px;
35
+ border: 1px solid transparent;
36
+ border-radius: 5px 5px 0 0;
37
+ background-color: transparent;
38
+ cursor: pointer;
39
+ transition: all 0.3s ease;
40
+ }
41
+
42
+ .stTabs [data-baseweb="tab"]:hover {
43
+ background-color: #8f8d9b;
44
+ }
45
+
46
+ .stTabs [aria-selected="true"] {
47
+ background-color: #57546a;
48
+ border-color: #ccc;
49
+ border-bottom-color: transparent;
50
+ }
51
+ </style>""", unsafe_allow_html = True)
52
+
53
+ tab1, tab2, tab3 = st.tabs(["👀 Analyse de la GRAD-CAM", "🚀 Réaliser des prédictions", "⚖️ Qualités et limites"])
54
+
55
+ with tab1:
56
+ st.header("Analyse de la GRAD-CAM")
57
+
58
+ col1, col2 = st.columns([0.6, 0.4])
59
+
60
+ with col1:
61
+ st.markdown('''
62
+ Le GRAD-CAM est une technique de visualisation : elle est utile pour comprendre quelles parties d'une image donnée ont conduit un réseau de neurones convolutifs à sa décision finale de classification.
63
+
64
+ Elle est utile pour déboguer le processus de décision d'un algorithme, en particulier dans le cas d'une erreur de classification.
65
+
66
+ Cette technique provient d'une catégorie plus générale de procédés appelée visualisation de __Class Activation Map__ (CAM).
67
+
68
+ Elle consiste à produire des heatmaps représentant les classes d'activation sur les images d'entrée. Une __class activation heatmap__ est associée à une classe de sortie spécifique.
69
+
70
+ Ces classes sont calculées pour chaque pixel d'une image d'entrée, indiquant l'importance de chaque pixel par rapport à la classe considérée.
71
+
72
+ Par exemple, si une image est utilisée dans un convnet chiens/chats, la visualisation CAM permet de générer une carte thermique pour la classe « chat », indiquant à quel point les différentes parties de l'image ressemblent à un chat, et également une carte thermique pour la classe « chien », indiquant à quel point les parties de l'image ressemblent à un chien.
73
+ ''')
74
+
75
+ with col2:
76
+ gradcam = Image.open('images\illustration_gradcam.jpeg')
77
+ st.image(gradcam, use_column_width = True, caption = "Illustration de la feature-map sur une image de chien")
78
+
79
+ with tab2:
80
+ st.header("Réaliser des prédictions")
81
+ # Configuration initiale de l'état de session
82
+ if 'model_selected' not in st.session_state:
83
+ st.session_state.model_selected = None
84
+ if 'model_loaded' not in st.session_state:
85
+ st.session_state.model_loaded = None
86
+
87
+ col1, col2, col3 = st.columns([0.3, 0.4, 0.3])
88
+
89
+ with col1:
90
+ st.session_state.model_selected = st.selectbox(
91
+ "Quel modèle utiliser ?",
92
+ ("DenseNet201", "VGG16"),
93
+ index=None,
94
+ placeholder="Choisissez un modèle..."
95
+ )
96
+ if st.session_state.model_selected == "DenseNet201":
97
+ st.write("Modèle le plus performant, mais aussi le plus lourd.")
98
+ st.session_state.model_loaded = 'DenseNet201'
99
+ elif st.session_state.model_selected == "VGG16":
100
+ st.write("Modèle le plus équilibré.")
101
+ st.session_state.model_loaded = 'VGG16'
102
+
103
+ with col2:
104
+ if st.session_state.model_selected == "DenseNet201" and st.session_state.model_loaded == 'DenseNet201':
105
+ model = model_densenet
106
+ st.success('👏 Modèle DenseNet201 chargé et prêt à prédire !')
107
+ elif st.session_state.model_selected == "VGG16" and st.session_state.model_loaded == 'VGG16':
108
+ model = model_vgg
109
+ st.success('👏 Modèle VGG16 chargé et prêt à prédire !')
110
+
111
+ with col3:
112
+ if st.button('Réinitialiser le modèle', type = 'primary', key = 321):
113
+ keys_to_delete = ['model_selected', 'model_loaded', 'file_uploaded']
114
+ for key in keys_to_delete:
115
+ if key in st.session_state:
116
+ del st.session_state[key]
117
+ st.experimental_rerun()
118
+
119
+ st.header("", divider = 'gray')
120
+
121
+ col1, col2 = st.columns([0.6, 0.4])
122
+
123
+ with col1:
124
+ file_container = st.empty()
125
+ uploaded_file = file_container.file_uploader("", type=['png', 'jpg', 'jpeg'])
126
+
127
+ if uploaded_file and st.session_state.model_selected is not None:
128
+ with col2:
129
+ st.write('')
130
+ st.write('')
131
+ #original = Image.open(uploaded_file)
132
+ bar_progress = 0
133
+ my_bar = st.progress(bar_progress, text = "Ouverture de l'image...")
134
+ time.sleep(0.5)
135
+ bar_progress = 10
136
+ my_bar.progress(bar_progress, text = "Réalisation du preprocessing...")
137
+
138
+ if (uploaded_file is not None) and (st.session_state.model_selected is not None):
139
+
140
+ file_bytes = np.asarray(bytearray(uploaded_file.read()), dtype=np.uint8)
141
+ image = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR)
142
+ resized_image = cv2.resize(image, (224, 224))
143
+ resized_image = resized_image/255
144
+ to_predict = np.expand_dims(resized_image, axis=0)
145
+ tri_image = model_tri.predict(to_predict)
146
+
147
+ if tri_image <= 0.5:
148
+
149
+ # Ouvrir l'image téléchargée
150
+ original = Image.open(uploaded_file)
151
+
152
+ # Image traitée
153
+ gray_image = original.convert('L')
154
+ channelized = gray_image.convert("RGB")
155
+ resized = channelized.resize((224, 224))
156
+ img_normalized = np.array(resized) / 255.0 # Convertir l'image en array et normaliser entre 0 et 1
157
+ img_normalized -= np.array([0.485, 0.456, 0.406]) # Soustraction de la moyenne par canal
158
+ img_normalized /= np.array([0.229, 0.224, 0.225]) # Division par l'écart-type par canal
159
+ img_normalized = img_normalized.reshape(-1, 224, 224, 3) # Remodeler pour correspondre aux attentes du modèle (batch_size, height, width, channels
160
+
161
+ bar_progress = 30
162
+ my_bar.progress(bar_progress, text = "Estimation des prédictions...")
163
+ time.sleep(0.5)
164
+ predictions = model.predict(img_normalized)
165
+ bar_progress = 70
166
+ my_bar.progress(bar_progress, text = "Génération de la GRAD-CAM...")
167
+ time.sleep(0.5)
168
+
169
+ col1, col2, col3 = st.columns([0.3, 0.4, 0.3])
170
+
171
+ def normalize_display_image(img_normalized):
172
+ img_display = (img_normalized * np.array([0.229, 0.224, 0.225])) + np.array([0.485, 0.456, 0.406])
173
+ img_display = np.clip(img_display, 0, 1)
174
+ img_display = (img_display * 255).astype(np.uint8)
175
+ return img_display
176
+
177
+ if original.width > 500:
178
+ width = 500
179
+ else:
180
+ width = original.width
181
+
182
+ with col1:
183
+ st.subheader("Image originale")
184
+ st.image(original, use_column_width = False, width = width, clamp = True)
185
+ st.warning("Image redimensionnée pour des raisons d'affichage.", icon = "⚠️") if original.width > 500 else None
186
+
187
+ with col2:
188
+ st.subheader("Image traitée")
189
+ st.image(img_normalized, use_column_width = False, clamp = True)
190
+
191
+ with col3:
192
+ st.subheader("GRAD-CAM")
193
+ # Préparation de l'image pour GRAD-CAM sans dimension de batch
194
+ heatm_img = np.squeeze(img_normalized)
195
+
196
+ last_conv_layer_name = None
197
+ for layer in reversed(model.layers):
198
+ if isinstance(layer, keras.layers.Conv2D): # Assure-toi que c'est bien keras.layers.Conv2D
199
+ last_conv_layer_name = layer.name
200
+ break
201
+
202
+ # Générer la heatmap à partir du modèle et de l'image traitée
203
+ heatmap = make_gradcam_heatmap(np.expand_dims(heatm_img, axis = 0), model, last_conv_layer_name)
204
+
205
+ # Préparation de l'image pour l'affichage de la superposition GRAD-CAM
206
+ img_display = normalize_display_image(heatm_img) # Convertir l'image normalisée en image affichable
207
+ grad_img = save_and_display_gradcam(img_display, heatmap) # Utilise l'image affichable ici
208
+ bar_progress = 100
209
+ my_bar.progress(bar_progress, text = "Exécution terminée")
210
+ time.sleep(0.5)
211
+ st.image(grad_img, use_column_width=False, clamp=True)
212
+
213
+ class_names = {0 : 'COVID',
214
+ 1 : 'Lung_Opacity',
215
+ 2 : 'Normal',
216
+ 3 : 'Viral Pneumonia'}
217
+ df_predictions = pd.DataFrame(predictions)
218
+ df_predictions = df_predictions.rename(columns = class_names)
219
+ df_predictions_sorted = df_predictions.sort_values(by = 0, axis = 1, ascending = False)
220
+ df_transposed = df_predictions_sorted.T
221
+ table_html = df_transposed.to_html(header = False, index = True)
222
+
223
+ classe_predite_indice = np.argmax(predictions)
224
+ nom_classe_predite = class_names[classe_predite_indice]
225
+ probabilite_predite = np.max(predictions)
226
+ probabilite_predite = "{:.4f}".format(probabilite_predite)
227
+
228
+ col1, col2, col3 = st.columns([0.3, 0.3, 0.4])
229
+
230
+ with col1:
231
+ # Afficher la classe prédite et sa probabilité
232
+ st.markdown("Tableau des probabilités estimées :")
233
+ st.write(table_html, unsafe_allow_html = True)
234
+
235
+ with col2:
236
+ st.markdown(
237
+ f"""
238
+ <div style='border-radius: 5px; border: 2px solid #d6d6d6; padding: 10px; max-width: 400px; background-color: rgba(255, 255, 255, 0.2);'>
239
+ <div style='display: flex; justify-content: space-around;'>
240
+ <div>
241
+ <p style='font-size: 20px; text-align: center; margin: 0;'>Classe prédite</p>
242
+ <p style='font-size: 30px; text-align: center; margin: 0;'>{nom_classe_predite}</p>
243
+ </div>
244
+ </div>
245
+ </div>
246
+ """,
247
+ unsafe_allow_html=True
248
+ )
249
+ st.write("")
250
+ percent_predit = float(probabilite_predite) * 100
251
+ st.markdown(
252
+ f"""
253
+ <div style='border-radius: 5px; border: 2px solid #d6d6d6; padding: 10px; max-width: 400px; background-color: rgba(255, 255, 255, 0.2);'>
254
+ <div style='display: flex; justify-content: space-around;'>
255
+ <div>
256
+ <p style='font-size: 20px; text-align: center; margin: 0;'>Confiance de la prédiction</p>
257
+ <p style='font-size: 30px; text-align: center; margin: 0;'>{percent_predit} %</p>
258
+ </div>
259
+ </div>
260
+ </div>
261
+ """,
262
+ unsafe_allow_html=True
263
+ )
264
+
265
+ with col3:
266
+ if float(probabilite_predite) > 0.90:
267
+ st.markdown(f"Avec une probabilité de {probabilite_predite}, il est **certain** que cette radiographie illustre un cas {nom_classe_predite}. Attention cependant, cette prédiction est à prendre en compte seulement si l'image s'agit bien d'une radiographie conforme.")
268
+ elif float(probabilite_predite) > 0.75:
269
+ st.markdown(f"Avec une probabilité de {probabilite_predite}, il est **probable** que cette radiographie illustre un cas {nom_classe_predite}. Attention cependant, cette prédiction est à prendre en compte seulement si l'image s'agit bien d'une radiographie conforme.")
270
+ elif float(probabilite_predite) > 0.5:
271
+ st.markdown(f"Avec une probabilité de {probabilite_predite}, il est **possible** que cette radiographie illustre un cas {nom_classe_predite}. Attention cependant, cette prédiction est à prendre en compte seulement si l'image s'agit bien d'une radiographie conforme.")
272
+ elif float(probabilite_predite) <= 0.5:
273
+ st.markdown(f"Avec une probabilité de {probabilite_predite}, il **n'est pas prudent de dire** que cette radiographie illustre un cas {nom_classe_predite}. Il est possible que l'image ne soit pas adaptée au modèle.")
274
+
275
+ else:
276
+ bar_progress = 0
277
+ my_bar.progress(bar_progress, text = "Exécution en attente...")
278
+ st.warning("L'image fournie ne semble pas être une radiographie. Souhaitez-vous quand même continuer ?", icon = "⚠️")
279
+ if st.button("Oui", key = 123):
280
+ # Ouvrir l'image téléchargée
281
+ original = Image.open(uploaded_file)
282
+
283
+ # Image traitée
284
+ gray_image = original.convert('L')
285
+ channelized = gray_image.convert("RGB")
286
+ resized = channelized.resize((224, 224))
287
+ img_normalized = np.array(resized) / 255.0 # Convertir l'image en array et normaliser entre 0 et 1
288
+ img_normalized -= np.array([0.485, 0.456, 0.406]) # Soustraction de la moyenne par canal
289
+ img_normalized /= np.array([0.229, 0.224, 0.225]) # Division par l'écart-type par canal
290
+ img_normalized = img_normalized.reshape(-1, 224, 224, 3) # Remodeler pour correspondre aux attentes du modèle (batch_size, height, width, channels
291
+
292
+ bar_progress = 30
293
+ my_bar.progress(bar_progress, text = "Estimation des prédictions...")
294
+ time.sleep(0.5)
295
+ predictions = model.predict(img_normalized)
296
+ bar_progress = 70
297
+ my_bar.progress(bar_progress, text = "Génération de la GRAD-CAM...")
298
+ time.sleep(0.5)
299
+
300
+ col1, col2, col3 = st.columns([0.3, 0.4, 0.3])
301
+
302
+ def normalize_display_image(img_normalized):
303
+ img_display = (img_normalized * np.array([0.229, 0.224, 0.225])) + np.array([0.485, 0.456, 0.406])
304
+ img_display = np.clip(img_display, 0, 1)
305
+ img_display = (img_display * 255).astype(np.uint8)
306
+ return img_display
307
+
308
+ if original.width > 500:
309
+ width = 500
310
+ else:
311
+ width = original.width
312
+
313
+ with col1:
314
+ st.subheader("Image originale")
315
+ st.image(original, use_column_width = False, width = width, clamp = True)
316
+ st.warning("Image redimensionnée pour des raisons d'affichage.", icon = "⚠️") if original.width > 500 else None
317
+
318
+ with col2:
319
+ st.subheader("Image traitée")
320
+ st.image(img_normalized, use_column_width = False, clamp = True)
321
+
322
+ with col3:
323
+ st.subheader("GRAD-CAM")
324
+ # Préparation de l'image pour GRAD-CAM sans dimension de batch
325
+ heatm_img = np.squeeze(img_normalized)
326
+
327
+ last_conv_layer_name = None
328
+ for layer in reversed(model.layers):
329
+ if isinstance(layer, keras.layers.Conv2D): # Assure-toi que c'est bien keras.layers.Conv2D
330
+ last_conv_layer_name = layer.name
331
+ break
332
+
333
+ # Générer la heatmap à partir du modèle et de l'image traitée
334
+ heatmap = make_gradcam_heatmap(np.expand_dims(heatm_img, axis = 0), model, last_conv_layer_name)
335
+
336
+ # Préparation de l'image pour l'affichage de la superposition GRAD-CAM
337
+ img_display = normalize_display_image(heatm_img) # Convertir l'image normalisée en image affichable
338
+ grad_img = save_and_display_gradcam(img_display, heatmap) # Utilise l'image affichable ici
339
+ bar_progress = 100
340
+ my_bar.progress(bar_progress, text = "Exécution terminée")
341
+ time.sleep(0.5)
342
+ st.image(grad_img, use_column_width=False, clamp=True)
343
+
344
+ class_names = {0 : 'COVID',
345
+ 1 : 'Lung_Opacity',
346
+ 2 : 'Normal',
347
+ 3 : 'Viral Pneumonia'}
348
+ df_predictions = pd.DataFrame(predictions)
349
+ df_predictions = df_predictions.rename(columns = class_names)
350
+ df_predictions_sorted = df_predictions.sort_values(by = 0, axis = 1, ascending = False)
351
+ df_transposed = df_predictions_sorted.T
352
+ table_html = df_transposed.to_html(header = False, index = True)
353
+
354
+ classe_predite_indice = np.argmax(predictions)
355
+ nom_classe_predite = class_names[classe_predite_indice]
356
+ probabilite_predite = np.max(predictions)
357
+ probabilite_predite = "{:.4f}".format(probabilite_predite)
358
+
359
+ col1, col2, col3 = st.columns([0.3, 0.3, 0.4])
360
+
361
+ with col1:
362
+ # Afficher la classe prédite et sa probabilité
363
+ st.markdown("Tableau des probabilités estimées :")
364
+ st.write(table_html, unsafe_allow_html = True)
365
+
366
+ with col2:
367
+ st.markdown(
368
+ f"""
369
+ <div style='border-radius: 5px; border: 2px solid #d6d6d6; padding: 10px; max-width: 400px; background-color: rgba(255, 255, 255, 0.2);'>
370
+ <div style='display: flex; justify-content: space-around;'>
371
+ <div>
372
+ <p style='font-size: 20px; text-align: center; margin: 0;'>Classe prédite</p>
373
+ <p style='font-size: 30px; text-align: center; margin: 0;'>{nom_classe_predite}</p>
374
+ </div>
375
+ </div>
376
+ </div>
377
+ """,
378
+ unsafe_allow_html=True
379
+ )
380
+ st.write("")
381
+ percent_predit = float(probabilite_predite) * 100
382
+ st.markdown(
383
+ f"""
384
+ <div style='border-radius: 5px; border: 2px solid #d6d6d6; padding: 10px; max-width: 400px; background-color: rgba(255, 255, 255, 0.2);'>
385
+ <div style='display: flex; justify-content: space-around;'>
386
+ <div>
387
+ <p style='font-size: 20px; text-align: center; margin: 0;'>Confiance de la prédiction</p>
388
+ <p style='font-size: 30px; text-align: center; margin: 0;'>{percent_predit} %</p>
389
+ </div>
390
+ </div>
391
+ </div>
392
+ """,
393
+ unsafe_allow_html=True
394
+ )
395
+
396
+ with col3:
397
+ if float(probabilite_predite) > 0.90:
398
+ st.markdown(f"Avec une probabilité de {probabilite_predite}, il est **certain** que cette radiographie illustre un cas {nom_classe_predite}. Attention cependant, cette prédiction est à prendre en compte seulement si l'image s'agit bien d'une radiographie conforme.")
399
+ elif float(probabilite_predite) > 0.75:
400
+ st.markdown(f"Avec une probabilité de {probabilite_predite}, il est **probable** que cette radiographie illustre un cas {nom_classe_predite}. Attention cependant, cette prédiction est à prendre en compte seulement si l'image s'agit bien d'une radiographie conforme.")
401
+ elif float(probabilite_predite) > 0.5:
402
+ st.markdown(f"Avec une probabilité de {probabilite_predite}, il est **possible** que cette radiographie illustre un cas {nom_classe_predite}. Attention cependant, cette prédiction est à prendre en compte seulement si l'image s'agit bien d'une radiographie conforme.")
403
+ elif float(probabilite_predite) <= 0.5:
404
+ st.markdown(f"Avec une probabilité de {probabilite_predite}, il **n'est pas prudent de dire** que cette radiographie illustre un cas {nom_classe_predite}. Il est possible que l'image ne soit pas adaptée au modèle.")
405
+
406
+ else:
407
+ # Message affiché tant qu'aucune image n'est téléchargée
408
+ st.warning("Veuillez télécharger une image pour commencer l'analyse.", icon = "⚠️")
409
+
410
+ with tab3:
411
+ st.header("Résumé des modèles")
412
+
413
+ col1, col2 = st.columns([0.5, 0.5])
414
+
415
+ with col1:
416
+ st.subheader("Avantages")
417
+ st.markdown('''
418
+ ✔️ Performances de prédictions équilibrées et satisfaisantes pour les différentes classes.
419
+
420
+ ✔️ Modèle réactif et léger à mettre en place, applicable à toutes les radiographies pulmonaires standards.
421
+
422
+ ✔️ Sensibilité et spécificité élevées, en particulier pour les classes COVID (0.96) et Normal (0.91).
423
+
424
+ ✔️ Equilibre contrôlé dans l'entrainement du modèle, réduisant le risque de data drift.
425
+ ''')
426
+
427
+ with col2:
428
+ st.subheader("Inconvénients")
429
+ st.markdown('''
430
+ ❌ Bien qu'efficace, les modèles peuvent encore être optimisés en visant la qualité des images et la sélection des jeux d'entrainement.
431
+
432
+ ❌ Les GRADCAM montrent encore des features "non pertinentes" pour certaines images avec de nombreux artefacts.
433
+
434
+ ❌ Bien que les métriques utilisés pour sélectionner les modèles soient complémentaires et pertinentes, personnaliser des métriques de perte permettraient d'améliorer d'autant plus les performances du modèle.
435
+
436
+ ❌ Le modèle ne fait pasl a différence entre une radiographie et un autre type d'image.
437
+ ''')