{"metadata":{"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":20270,"databundleVersionId":1222630,"sourceType":"competition"},{"sourceId":64447,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":53742}],"dockerImageVersionId":30673,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# SIIM-ISIC Melanoma Classification SoSe24","metadata":{}},{"cell_type":"markdown","source":"Der Folgende Block lädt relevante Libraries, die für dieses Projekt benötigt werden. Besondere sind: \n\n**ThreadPoolExecutor**\n\n- Multithreaden von rechenschweren Prozessen\n- Beschleunigen von Ladeaufgaben\n\n\n**tqdm** \n\n- Anzeigen von Ladebalken. \n- Weiteres Feedback, wenn ein Prozess beendet wird\n\n\n----\n<span style=\"color:grey\">\n\n\n**Kommentar:**\n\nEs können Artefakte in Form von importierten Libraries geben, die nicht weiter ausgeführt werden.\n</span>","metadata":{}},{"cell_type":"code","source":"import os\nimport datetime\nimport numpy as np\nimport pandas as pd\nimport warnings\nfrom tqdm import tqdm\nfrom concurrent.futures import ThreadPoolExecutor\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, confusion_matrix\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator  # type: ignore\nfrom tensorflow.keras import models, layers, optimizers  # type: ignore\nfrom tensorflow.keras.models import load_model  # type: ignore\nfrom tensorflow.keras.callbacks import EarlyStopping # type: ignore\n\nwarnings.filterwarnings('ignore')\n\ndatetime.datetime.now().strftime(\"Fertiggestellt um %X den %x\")","metadata":{"execution":{"iopub.status.busy":"2024-06-12T07:27:43.665821Z","iopub.execute_input":"2024-06-12T07:27:43.666135Z","iopub.status.idle":"2024-06-12T07:27:59.750104Z","shell.execute_reply.started":"2024-06-12T07:27:43.666110Z","shell.execute_reply":"2024-06-12T07:27:59.749255Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Laden der Bilder und dazugehörigen CSV Dateien. \nDer folgende Block lädt Bilder und CSV Daten. Diese werden multithreaded, um die Ladezeiten um etwa das x-Fache der verfügbaren Prozessorkerne zu verkürzen.\n\nBilder werden in 128x128 mit Farbe in den RAM geladen, zusätzlich werden die **Namen** und **target** Labels aus der csv-Datei extrahiert und mit den Bildern synchronisiert.\n\nIm letzten Schritt werden die Trainingsbilder in Trainings- und Validierungssätzen aufgeteilt. Hierbei werden 20% der Trainingsdaten zu Validierungsdaten allokiert.","metadata":{}},{"cell_type":"code","source":"path_data_train = '/kaggle/input/siim-isic-melanoma-classification/train.csv'\npath_image_train = '/kaggle/input/siim-isic-melanoma-classification/jpeg/train'\n\n# Bilder und .csv Dateien laden\ndef load_image(image_path, target_size=(128, 128)):\n    image = Image.open(image_path).convert('RGB')\n    image = image.resize(target_size)\n    return np.array(image)\n\ndef load_data(csv_file, image_dir, max_images=None, target_size=(128, 128), num_workers=None):\n    data = pd.read_csv(csv_file)\n    if max_images is not None:\n        data = data.head(max_images)\n\n    # Bildname und target extrahieren\n    image_names = data['image_name'].values\n    targets = data['target'].values\n\n    images = []\n    with ThreadPoolExecutor(max_workers=num_workers) as executor:\n        futures = []\n        for image_name in image_names:\n            image_path = os.path.join(image_dir, image_name + '.jpg')\n            futures.append(executor.submit(load_image, image_path, target_size))\n\n        for future in tqdm(futures, desc=\"Lade Bilder\", total=len(futures)):\n            images.append(future.result())\n\n    return np.array(images), np.array(targets), image_names\n\nnum_workers = os.cpu_count() or 1   # Alle verfügbaren Kerne, die das System erkennt, ansonsten 1\n\n# Laden der Trainingsdaten: Bildname, Bild, target\ntrain_images, train_targets, train_image_names = load_data(path_data_train, path_image_train, max_images=None, num_workers=num_workers)\n\n# Aufteilen der Trainingsdaten in Trainings- und Validierungssätze\ntrain_images, val_images, train_targets, val_targets, train_image_names, val_image_names = train_test_split(train_images, \n                                                                                                            train_targets, \n                                                                                                            train_image_names, \n                                                                                                            test_size=0.2,\n                                                                                                            random_state=42, \n                                                                                                            stratify=train_targets)\n\n# Verteilung der Labels anzeigen\nprint(\"Verteilung der Trainingslabels:\", np.bincount(train_targets))\nprint(\"Verteilung der Validierungslabels:\", np.bincount(val_targets))\n\ndatetime.datetime.now().strftime(\"Fertiggestellt um %X den %x\")","metadata":{"execution":{"iopub.status.busy":"2024-06-11T18:58:29.823516Z","iopub.execute_input":"2024-06-11T18:58:29.824430Z","iopub.status.idle":"2024-06-11T19:40:08.760763Z","shell.execute_reply.started":"2024-06-11T18:58:29.824396Z","shell.execute_reply":"2024-06-11T19:40:08.759756Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Datenaugmentierung von Trainings- und Validierungsdaten\nDer folgende Block verändert die Verteilung der Trainings- und Validierungsdaten von einer Ungleichheit von '98% zu 2%' zu einer ausgewogeneren Verteilung von '50% zu 50%'. Dies wird durchgeführt, um zu verhindern, dass das Modell dazu neigt, überrepräsentierte Klassen, wie Nullen oder 'benign', zu bevorzugen. An den folgenden zwei Bildern wird die Verteilung durch die Augmentation verdeutlicht\n","metadata":{}},{"cell_type":"code","source":"datagen = ImageDataGenerator(\n    rotation_range=20,\n    width_shift_range=0.2,\n    height_shift_range=0.2,\n    shear_range=0.2,\n    zoom_range=0.2,\n    horizontal_flip=True,\n    fill_mode='nearest')\n\ndef augment_class_images(class_images, augment_size, generator):\n    augmented_images = []\n    for i in tqdm(range(augment_size), desc=\"Augmentiere Bilder\"):\n        augmented_image = generator.random_transform(class_images[i % len(class_images)])\n        augmented_images.append(augmented_image)\n    return np.array(augmented_images)\n\n# Ermittlung der unterrepräsentierten Daten -> Hier Training- und Validierungsbilder und ihre Labels\ntrain_minority_class_images = train_images[train_targets == 1]\nval_minority_class_images = val_images[val_targets == 1]\n\n# Festlegen der Größe des zu augmentiertenden Datensatzes; Hier 50% zu 50%\ntrain_augment_size = len(train_images[train_targets == 0]) - len(train_minority_class_images)\nval_augment_size = len(val_images[val_targets == 0]) - len(val_minority_class_images)\n\n# Trainings- und Validierungsdaten augmentieren\ntrain_augmented_images = augment_class_images(train_minority_class_images, train_augment_size, datagen)\ntrain_augmented_targets = np.ones(train_augment_size)\nval_augmented_images = augment_class_images(val_minority_class_images, val_augment_size, datagen)\nval_augmented_targets = np.ones(val_augment_size)\n\n# Kombinieren der augmentierten Bilder mit den ursprünglichen Daten\ntrain_images_balanced = np.concatenate((train_images, train_augmented_images), axis=0)\ntrain_targets_balanced = np.concatenate((train_targets, train_augmented_targets), axis=0)\nval_images_balanced = np.concatenate((val_images, val_augmented_images), axis=0)\nval_targets_balanced = np.concatenate((val_targets, val_augmented_targets), axis=0)\n\n# Mischen der Daten\ndef shuffle_data(images, targets):\n    indices = np.arange(images.shape[0])\n    np.random.shuffle(indices)\n    return images[indices], targets[indices]\n\ntrain_images_balanced, train_targets_balanced = shuffle_data(train_images_balanced, train_targets_balanced)\nval_images_balanced, val_targets_balanced = shuffle_data(val_images_balanced, val_targets_balanced)\n\ntrain_targets_balanced = train_targets_balanced.astype(int)\nval_targets_balanced = val_targets_balanced.astype(int)\n\n# Verteilung der Labels nach der Augmentation anzeigen\nprint(\"Verteilung der Trainingslabels nach der Augmentation:\", np.bincount(train_targets_balanced))\nprint(\"Verteilung der Validierungslabels nach der Augmentation:\", np.bincount(val_targets_balanced))\n","metadata":{"execution":{"iopub.status.busy":"2024-06-11T20:05:07.263544Z","iopub.execute_input":"2024-06-11T20:05:07.264168Z","iopub.status.idle":"2024-06-11T20:06:59.552136Z","shell.execute_reply.started":"2024-06-11T20:05:07.264135Z","shell.execute_reply":"2024-06-11T20:06:59.551095Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Erstellen eines CNN Models\nDer folgende Block definiert ein CNN (Convolutional Neural Network). Mit einem Input von 128x128 Pixeln und 3 Farbkanälen und setzt sich aus drei Convolutional und drei MaxPooling Schichten zusammen. \n\nDie Conv2D Schichten starten bei 32 und verdoppeln sich pro Ebene bis 128. \n\nMaxPooling wird zwischen die Conv Schichten geschaltet, um die räumliche Größe auf die wichtigsten Pixel zu halbieren.\n\nFlatten transformiert die 3 Dimensionale Matrix in eine 1 Dimensionale, um diese an die folgende Dense Schicht verbinden zu können.\n\nDropout deaktiviert 30% zufälliger Neuronen. (Ursprünglich nicht da, und das Modell hat zu jedem Bild denselben Wert ausgegeben)\n\nDie letzte Schicht beschreibt die Ausgabe Schicht und gibt nur Werte zwischen 0 und 1 wider.\n","metadata":{}},{"cell_type":"code","source":"model = models.Sequential([\n    layers.Conv2D(32, (3, 3), activation='relu', input_shape=(128, 128, 3)),\n    layers.MaxPooling2D((2, 2)),\n    layers.Conv2D(64, (3, 3), activation='relu'),\n    layers.Conv2D(128, (3, 3), activation='relu'),\n    layers.Conv2D(256, (3, 3), activation='relu'),\n    layers.MaxPooling2D((2, 2)),\n    layers.Flatten(),\n    layers.Dense(512, activation='relu'),\n    layers.Dropout(0.3),\n    layers.Dense(1, activation='sigmoid')\n])\nmodel.summary()","metadata":{"execution":{"iopub.status.busy":"2024-06-11T20:07:03.149091Z","iopub.execute_input":"2024-06-11T20:07:03.149758Z","iopub.status.idle":"2024-06-11T20:07:03.937229Z","shell.execute_reply.started":"2024-06-11T20:07:03.149722Z","shell.execute_reply":"2024-06-11T20:07:03.936378Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Kompilieren und trainieren des Models","metadata":{}},{"cell_type":"code","source":"model.compile(optimizer=optimizers.Adam(learning_rate=1e-4), loss='binary_crossentropy', metrics=['accuracy'])\n\n# Early Stopping\nearly_stopping = EarlyStopping(monitor='val_loss', patience=2, restore_best_weights=True)\n\n# Model trainieren\nhistory = model.fit(train_images_balanced, \n                    train_targets_balanced, \n                    batch_size=32,\n                    epochs=20, \n                    validation_data=(val_images_balanced, val_targets_balanced))\n","metadata":{"execution":{"iopub.status.busy":"2024-06-11T20:07:07.993011Z","iopub.execute_input":"2024-06-11T20:07:07.993410Z","iopub.status.idle":"2024-06-11T20:14:36.877841Z","shell.execute_reply.started":"2024-06-11T20:07:07.993371Z","shell.execute_reply":"2024-06-11T20:14:36.876885Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model speichern","metadata":{}},{"cell_type":"code","source":"model.save(\"/kaggle/working/modelfunktioniert.h5\")\ndatetime.datetime.now().strftime(\"Fertiggestellt um %X den %x\")","metadata":{"execution":{"iopub.status.busy":"2024-06-11T20:15:16.560353Z","iopub.execute_input":"2024-06-11T20:15:16.561115Z","iopub.status.idle":"2024-06-11T20:15:16.822049Z","shell.execute_reply.started":"2024-06-11T20:15:16.561085Z","shell.execute_reply":"2024-06-11T20:15:16.821177Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import gc\n\ndel train_images\n# del train_targets\ndel val_images\n# del val_targets\n\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2024-06-11T20:22:06.083200Z","iopub.execute_input":"2024-06-11T20:22:06.083592Z","iopub.status.idle":"2024-06-11T20:22:06.306126Z","shell.execute_reply.started":"2024-06-11T20:22:06.083564Z","shell.execute_reply":"2024-06-11T20:22:06.304990Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Visualisierung der Ergebnisse","metadata":{}},{"cell_type":"code","source":"# Visualisierung der Trainings- und Validierungsverluste\nplt.plot(history.history['loss'], label='train_loss')\nplt.plot(history.history['val_loss'], label='val_loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.ylim(0, max(history.history['loss'] + history.history['val_loss']))\nplt.legend()\nplt.show()\n\n# Visualisierung der Trainings- und Validierungsgenauigkeit\nplt.plot(history.history['accuracy'], label='train_accuracy')\nplt.plot(history.history['val_accuracy'], label='val_accuracy')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.ylim(0, 1)\nplt.legend()\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-06-11T20:22:13.395998Z","iopub.execute_input":"2024-06-11T20:22:13.396613Z","iopub.status.idle":"2024-06-11T20:22:13.919742Z","shell.execute_reply.started":"2024-06-11T20:22:13.396580Z","shell.execute_reply":"2024-06-11T20:22:13.918855Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Die Konfusionsmatrix zeigt hohe Werte in TP und TN an, was bedeutet, dass das Modell richtig funktioniert","metadata":{}},{"cell_type":"code","source":"# Vorhersagen auf den augmentierten Validierungsdaten\nval_predictions = (model.predict(val_images_balanced) > 0.5).astype(\"int32\")\n\n# Berechnung der Genauigkeit\naccuracy = accuracy_score(val_targets_balanced, val_predictions)\nprint(f\"Genauigkeit: {accuracy:.2f}\")\n\n# Confusion Matrix berechnen\nconf_matrix = confusion_matrix(val_targets_balanced, val_predictions)\n\n# Berechnung von TP, TN, FP und FN\nTP = conf_matrix[1, 1]\nTN = conf_matrix[0, 0]\nFP = conf_matrix[0, 1]\nFN = conf_matrix[1, 0]\n\n# Plot der Confusion Matrix mit Werten\nplt.figure(figsize=(8, 6))\nplt.imshow(conf_matrix, interpolation='nearest', cmap='Blues')\nplt.title('Konfusionsmatrix')\nplt.colorbar()\ntick_marks = np.arange(2)\nplt.xticks(tick_marks, ['Negativ', 'Positiv'])\nplt.yticks(tick_marks, ['Negativ', 'Positiv'])\nplt.xlabel('Wahre Werte')\nplt.ylabel('Vorhersage')\n\n# Anzeigen von TP, TN, FP und FN\nplt.text(0, 0, f\"True Negatives (TN): {TN}\", horizontalalignment='center', verticalalignment='center', color='black')\nplt.text(1, 1, f\"True Positives (TP): {TP}\", horizontalalignment='center', verticalalignment='center', color='black')\nplt.text(0, 1, f\"False Positives (FP): {FP}\", horizontalalignment='center', verticalalignment='center', color='black')\nplt.text(1, 0, f\"False Negatives (FN): {FN}\", horizontalalignment='center', verticalalignment='center', color='black')\n\nplt.show()\n","metadata":{"execution":{"iopub.status.busy":"2024-06-11T20:22:16.163897Z","iopub.execute_input":"2024-06-11T20:22:16.164253Z","iopub.status.idle":"2024-06-11T20:22:16.453934Z","shell.execute_reply.started":"2024-06-11T20:22:16.164223Z","shell.execute_reply":"2024-06-11T20:22:16.452998Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Vorbereitung für die Submission in Kaggle\nDie Testbilder werden geladen, die Namen aus den Bildpfaden extrahiert und in einer Datei submission.csv unter image_name mit den entsprechenden Targets des Modells gespeichert.\n\n| image_name | target |\n| ----------- | ----------- |\n|ISIC_XXXXXXX|0|\n|ISIC_XXXXXXX|0|\n|ISIC_XXXXXXX|1|\n|ISIC_XXXXXXX|0|\n|ISIC_XXXXXXX|0|","metadata":{}},{"cell_type":"code","source":"path_image_test = '/kaggle/input/siim-isic-melanoma-classification/jpeg/test'\n# path_model = '/kaggle/input/model1/tensorflow1/model_abgabe_97_prozent/1/modelfunktioniert.h5' # Vorerstelltes Modell\npath_model = '/kaggle/working/modelfunktioniert.h5' # Neuerstelltes Modell, für submission\n\n# Funktion zum Laden von Bildern\ndef load_image(image_path, target_size=(128, 128)):\n    image = Image.open(image_path).convert('RGB')\n    image = image.resize(target_size)\n    return np.array(image)\n\n# Modell laden\nmodel = load_model(path_model)\n\n# Testbilder laden\ndef load_test_images(image_dir, target_size=(128, 128), num_workers=None):\n    image_names = os.listdir(image_dir)\n    images = []\n\n    with ThreadPoolExecutor(max_workers=num_workers) as executor:\n        futures = []\n        for image_name in image_names:\n            image_path = os.path.join(image_dir, image_name)\n            futures.append(executor.submit(load_image, image_path, target_size))\n\n        for future in tqdm(futures, desc=\"Lade Testbilder\", total=len(futures)):\n            images.append(future.result())\n\n    return np.array(images), image_names\n\n# Anzahl der Prozesse oder Threads einstellen\nnum_workers = os.cpu_count() or 1   # Alle verfügbaren Kerne, die das System erkennt, ansonsten 1\n\n# Laden der Testbilder\ntest_images, test_image_names = load_test_images(path_image_test, num_workers=num_workers)\n\ndatetime.datetime.now().strftime(\"Fertiggestellt um %X den %x\")","metadata":{"execution":{"iopub.status.busy":"2024-06-12T07:28:00.966126Z","iopub.execute_input":"2024-06-12T07:28:00.966708Z","iopub.status.idle":"2024-06-12T07:39:56.784590Z","shell.execute_reply.started":"2024-06-12T07:28:00.966679Z","shell.execute_reply":"2024-06-12T07:39:56.783602Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Vorhersagen auf den Testbildern\ntest_predictions = (model.predict(test_images) > 0.5).astype(\"int32\")\n\n# Entfernen der \".jpg\"-Erweiterung von den Bildnamen\ntest_image_names = [os.path.splitext(name)[0] for name in test_image_names]\n\n# Ergebnisse in eine DataFrame speichern\nresults = pd.DataFrame({'image_name': test_image_names, 'target': test_predictions.flatten()})\n\n# Speichern der Ergebnisse in eine CSV-Datei\noutput_path = '/kaggle/working/submission.csv'\nresults.to_csv(output_path, index=False)\n\ndatetime.datetime.now().strftime(\"Fertiggestellt um %X den %x\")","metadata":{"execution":{"iopub.status.busy":"2024-06-12T07:44:39.339803Z","iopub.execute_input":"2024-06-12T07:44:39.340443Z","iopub.status.idle":"2024-06-12T07:44:41.953031Z","shell.execute_reply.started":"2024-06-12T07:44:39.340399Z","shell.execute_reply":"2024-06-12T07:44:41.952109Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Lesen der CSV-Datei\nsubmission_df = pd.read_csv(output_path)\n\n# Überprüfen, ob die Datei korrekt eingelesen wurde\nprint(submission_df.head())\n\n# Zählen der Anzahl von 0 und 1 in der 'prediction' Spalte\ncounts = submission_df['target'].value_counts()\n\n# Ausgabe der Ergebnisse\nprint(f\"Anzahl der 0 in den Vorhersagen: {counts.get(0, 0)}\")\nprint(f\"Anzahl der 1 in den Vorhersagen: {counts.get(1, 1)}\")","metadata":{"execution":{"iopub.status.busy":"2024-06-12T07:45:46.580190Z","iopub.execute_input":"2024-06-12T07:45:46.580687Z","iopub.status.idle":"2024-06-12T07:45:46.625254Z","shell.execute_reply.started":"2024-06-12T07:45:46.580653Z","shell.execute_reply":"2024-06-12T07:45:46.624281Z"},"trusted":true},"execution_count":null,"outputs":[]}]}