{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Mein Ansatz für die PANDA Challenge\nMit diesem \"Notizbuch\" möchte ich meinen Bekannten einen Einblick in die Welt der Künstlichen Intelligenz (KI) geben. Der Inhalt soll hierbei möglichst allgemeinverständlich und anschaulich dargestellt werden. In den nachfolgenden Kapiteln möchte ich die Leser Schritt für Schritt in \"meine Welt\" einführen, und zeigen wie ich - leider ohne großen Erfolg - KI für die PANDA Challenge eingesetzt habe.\n\nIn den folgenden Abschnitten möchte ich diese Fragen beantworten:\n1. Worum gehts bei der PANDA Challenge?\n2. Wie funktioniert Künstliche Intelligenz bei Bildern?\n3. Wie habe ich Künstliche Intelligenz auf die PANDA Challenge angewandt?\n","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"## Panda 2020 Wettbewerb\nPanda ist ein Akronyom für **P**rostate c**AN**cer gra**D**e **A**ssessment (PANDA), also die Bewertung des Grades von Prostatakrebs. Die [PANDA Challenge](https://www.kaggle.com/c/prostate-cancer-grade-assessment) war ein Wettbewerb, bei dem die Teilnehmer KI Modelle entwickelt und eingereicht haben. Die Modelle ermitteln den Grad der Krebserkrankung einer Prostata Biopsie. Die Ergebnisse der Modelle wurden mit denen von Fachärzten verglichen. Das Modell mit der höchsten Übereinstimmung hat den Wettbewerb gewonnen.\n\nDer Wettbewerb fand auf der Plattform Kaggle statt, auf der ihr euch gerade befindet. Kaggle bietet die Möglichkeit, sogenannte Notebooks zu erstellen. In Notebooks kann man Programmcode ausführen und das Ergebnis anzeigen lassen. Außerdem kann man auch reinen Text eintragen. Diese Kombination eignet sich sehr gut, um Ergebnisse und Erkenntnisse mit anderen zu teilen. Ihr ahnt es schon richtig, das hier ist ein Notebook. In der nächsten Zeile seht ihr Python Programmcode und in der Zeile darauf die Ausgabe, wenn man den Code ausführt.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"print('Hallo Welt!')\na = 1 + 2 * 3\nprint('a hat den Wert', a)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Unter Verwendung dieser Notebooks erstellten die Teilnehmer Ihre Modelle. Die Notebooks wurden dann für den Wettbewerb eingereicht, und die Modelle mit Testdaten geprüft und bewertet. Die besten Platzierungen erhielten Geldpreise (der erste Platz zum Beispiel 12.000 USD).\n\n## Künstliche Intelligenz in der Bildverarbeitung\nKI findet in vielen Bereichen Anwendung. Zum Beispiel beim autonomen Fahren, oder bei der Spracherkennung, wie wir sie von Alexa oder Siri kennen. Für alle KI Modelle trifft zu, dass Sie trainiert werden um Muster in den (Trainings-)Daten zu entdecken, um diese dann in neuen Daten zu erkennen.\n\nFür die PANDA Challenge benötigt man KI Modelle, welche Muster für jeden möglichen Grad des Prostatakrebs in Bildern entdecken können. Hierbei hat sich Deep Learning bewährt. \"Deep\" bezieht sich auf die Anzahl an Schichten, die so ein Modell hat, je mehr Schichten, umso tiefer das Modell. In den ersten Schichten wird zuerst nach simplen Mustern wie z.B. vertikalen, horizontalen und diagonalen Linien gesucht. Aus der Kombination dieser Muster lassen sich dann Ecken und Konturen zusammenstellen, und diese können wiederum zu (Teilen von) Objekten kombiniert werden.\n\nGoogLeNet ist beispielsweise ein Deep Learning Modell. Mit seinen 22 Schichten und insgesamt über 6 Millionen Neuronen, kann es 1000 verschiedene Objekte auf Bildern erkennen. Im nachfolgenden Bild sieht man jeweils 6 Neuronen von 5 Schichten (entnommen aus diesem [Blog](https://ai.googleblog.com/2017/11/feature-visualization.html)). Man sieht je tiefer die Schicht, umso komplexer werden die Muster.\n\n![https://ai.googleblog.com/2017/11/feature-visualization.html](https://1.bp.blogspot.com/-icbxyuiDoA0/WgEivsyFIgI/AAAAAAAACKo/jsfMgFlfiVA233zXg8xAH3ZAKOchgLb-wCLcBGAs/s640/image4.png)\n\nWird nun ein Bild von dem KI Modell GoogLeNet analysiert, werden kleine Ausschnitte des Bildes entnommen und mit den Mustern der Neuronen verglichen. Je nach Übereinstimmung reagieren die Neuronen auf den Bildausschnitt. Bei hoher Ähnlichkeit gibt das Neuron ein starkes Signal an die nachfolgenden Neuronen weiter, bei geringer Ähnlichkeit gibt es ein schwaches Signal weiter. Die Signale werden über alle Ausschnitte und alle Schichten hinweg kombiniert. \n\nDas ist ein bisschen wie bei dem Gesellschaftsspiel Personenraten, die Kombination der Details ergibt das Gesamtbild. Ein Neuron fragt: habe ich Fell? Ein anderes fragt nach spitzen Ohren. GoogLeNet stellt so gesehen Millionen dieser Fragen an jedes Bild, und gibt dann an, um welches Objekt es sich wahrscheinlich handelt.\n\nWie viele Schichten und Neuronen soll man verwenden? Wie werden die Signale der Neuronen aufbereitet? Das sind Fragen zur Architektur von KI Modellen. Ständig werden neue Architekturen entwickelt, um noch bessere Ergebnisse zu erzielen.","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"## Mein Beitrag zur PANDA Challenge\n### Architektur\nIch habe keine eigene Architektur entwickelt. Stattdessen habe ich verschiedene vorhandene Architekturen genommen und nur leicht modifziert, so dass sie auf die Problemstellung passen. Dazu entfernt man die letzte Schicht einer Architektur und ersetzt sie durch eine neue. \n* **Vorher:** GoogLeNet kann mit 6 Millionen Fragen 1000 Objekte bestimmen\n* **Nachher:** GoogLeNet kann mit 6 Millionen Fragen 5 Grade von Prostatakrebs bestimmen\n\nDieses Vorgehen wird auch Transferlernen genannt. So muss man nicht bei 0 anfangen und kann auf vorhandene Muster zurückgreifen.\n## Vorgehen\n### Verwendung des kompletten Bildes der Biopsie\nIn einem ersten Ansatz habe ich das Bild einer Biopsie als Ganzes verarbeitet. Das hatte folgende Nachteile\n* Die großen Bilder mussten stark verkleinert werden, was zum Verlust von wichtigen Details führte\n* Auf vielen Bildern ist der Anteil an Prostatagewebe sehr klein, und die meisten Pixel sind weißer Hintergrund. Zum einen werden Ressourcen verschwendet, weil alle Bildauschnitte auf alle Muster geprüft werden (auch der weiße Hintergrund). Außerdem bietet es die Gefahr, dass das Modell Muster in der Form oder Position der Gewebeproben sucht, anstelle die Muster innnerhalb der Gewebeproben zu suchen.\n\nNachfolgend wird je Zeile eine Biopsie dargestellt. In der ersten Spalte jeweils das (verkleinerte) Gesamtbild, in den nachfolgenden Spalten jeweils zufällige Felder mit Gewebe. Hier werden die zuvor genannten Nachteile deutlich.\n","execution_count":null},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nfrom fastai.vision import *\nfrom fastai.metrics import error_rate\n\ndummy = ImageList.from_folder('/kaggle/input/fastai-resnet152-on-gleason-data/__results___files/')\ndummy.items\n\ntrain = pd.read_csv('/kaggle/input/prostate-cancer-grade-assessment/train.csv')\n\ntest = pd.read_csv('/kaggle/input/prostate-cancer-grade-assessment/test.csv')\n\nsub = pd.read_csv('/kaggle/input/prostate-cancer-grade-assessment/sample_submission.csv')\n\n#train.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"import random\nimport itertools\nimport numpy as np\nimport cv2\nimport time\nimport openslide\nfrom matplotlib import pyplot as plt\nimport torchvision.transforms as T\n\ndef maskoff(img, thr1=200, thr2=200, krnlr=3,krnlc=3, target_dim=(8192, 26368), pw=512,ph=512):\n    # resize image\n    #resized = cv2.resize(img, dim, interpolation = cv2.INTER_AREA)\n    resized = cv2.resize(img, (pw,ph), interpolation = cv2.INTER_AREA)\n    \n    edges = cv2.Canny(resized, thr1,thr2)\n    kernel = np.ones((krnlr,krnlc), np.uint8)\n    closing = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel, iterations=3)\n    erosion = cv2.morphologyEx(closing, cv2.MORPH_ERODE, kernel, iterations=1)\n\n    # When using Grabcut the mask image should be:\n    #    0 - sure background\n    #    1 - sure foreground\n    #    2 - unknown\n\n    mask = np.zeros(resized.shape[:2], np.uint8)\n    mask[:] = 2\n    mask[erosion == 255] = 1\n\n    bgdmodel = np.zeros((1, 65), np.float64)\n    fgdmodel = np.zeros((1, 65), np.float64)\n\n    out_mask = mask.copy()\n    start = time.time()\n    #print('resized.shape', resized.shape)\n    out_mask, _, _ = cv2.grabCut(resized,out_mask,None,bgdmodel,fgdmodel,1,cv2.GC_INIT_WITH_MASK)\n    end = time.time()\n    #print('Maskoff grabcut', end-start)\n    out_mask = np.where((out_mask==2)|(out_mask==0),0,1).astype('uint8')\n    \n    out_mask_small = out_mask.copy()\n    \n    out_mask = cv2.resize(out_mask, target_dim, interpolation = cv2.INTER_CUBIC)\n    \n    itemindex = np.where(out_mask==1)\n    hmin = itemindex[0].min()\n    hmax = itemindex[0].max()\n    wmin = itemindex[1].min()\n    wmax = itemindex[1].max()\n    \n    # Calculate grid for patches\n    h, w = out_mask.shape\n    xes = [x for x in range(wmin, wmax, pw)] \n    ys = [y for y in range(hmin, hmax, ph)] \n\n    if (wmax-wmin)%pw!=0:\n        xes = xes + [wmax-pw]\n    if (hmax-hmin)%ph!=0:\n        ys = ys + [hmax-ph]\n    # list of all coordinates that have at least 5% pixels that are on the mask\n    coords = [(x,y) for (x,y) in list(itertools.product(xes, ys)) if out_mask[y:y+ph,x:x+pw].sum()/(pw*ph)>0.25]\n    \n    return resized, out_mask_small, out_mask, coords\n\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"k_samples = train[train.data_provider=='karolinska'].image_id.sample(6).values\n\nr_samples = train[train.data_provider=='radboud'].image_id.sample(6).values\n\nIMAGE_DIR = '/kaggle/input/prostate-cancer-grade-assessment/train_images/'\n\nim_list = [i for i in os.listdir(IMAGE_DIR)]\n\n\n\nf, axes = plt.subplots(3,5, figsize=(15,10))\n  \nfor i,filename in enumerate(random.sample(im_list,3)):\n    image = openslide.OpenSlide(os.path.join(IMAGE_DIR, filename))\n    image_data = image.read_region((0,0), image.level_count - 1, image.level_dimensions[-1])\n    \n    img_cv = np.array(image_data.convert('RGB'))\n    img = img_cv[:, :, ::-1].copy()\n    \n    pw=512\n    ph=512\n    image_resized, image_mask_small, image_mask, coords = maskoff(img, target_dim=image.level_dimensions[0])\n    axes[i][0].imshow(cv2.cvtColor(image_resized, cv2.COLOR_BGR2RGB))\n    axes[i][0].axis('off')\n    #axes[i][0].imshow(image_resized)\n    #axes[i][1].imshow(image_mask_small)\n    if i==0:\n        at = axes[i][0].set_title('Biopsie Gesamt')\n    for j, coord in enumerate(random.sample(coords, 4)):\n        x,y = coord\n        patch = image.read_region(coord, 0, (pw,ph))\n        axes[i][j+1].imshow(patch)\n        num = (image_mask[y:y+ph,x:x+pw].sum().sum())/(pw*ph)\n        #at = axes[i][j+2].set_title(num)\n        axes[i][j+1].axis('off')\n        if i==0:\n            at = axes[i][j+1].set_title('Zufälliges Feld')\n    image.close()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Die Ergebnisse des Models waren sehr schlecht (Platz 942 von 1010).\n### Rastern und Analyse Feld für Feld\nIn dem zweiten Ansatz wollte ich die Nachteile aushebeln. Und das Vorgehen eines Arztes imitieren.\n\n1. Das Bild in Originalgröße in Felder der Größe 512x512 Pixel aufteilen\n2. Für jedes Feld (welches Gewebe enthält) ermitteln welcher Grad Krebs vorliegt\n3. Den Anteil der Grade für die gesamte Biopsie (und damit den Gesamtgrad) ermitteln\n\n\nDer Arzt schaut sich eine Biopsie im Detail an und ermittelt wie viel Gewebe dem jeweiligen Gleason Grad zuzuordnen ist.\n\n![https://www.prostata.de/prostatakrebs/was-ist-pca/klassifikation-des-prostatakarzinoms](https://www.prostata.de/sites/prostata.de/files/2018-11/gleason_prostatakarzinom.gif)\n\nDanach wird es kompliziert. Denn er ermittelt den ISUP Grad, welcher zwischen 1 und 5 liegt. Vereinfacht gesagt, bewertet der Arzt die Biopsie dann nach dem höchsten Gleason Grad, den er gefunden hat. Weitere Details gibts es zum Beispiel [hier](https://de.wikipedia.org/wiki/Gleason-Score).\n\nNachfolgend sind beispielhaft vier Gewebeproben mit unterschiedlichen Gleason Graden zu sehen.\n\n![https://www.kaggle.com/c/prostate-cancer-grade-assessment/overview/additional-resources](https://storage.googleapis.com/kaggle-media/competitions/PANDA/GleasonPattern_4squares%20copy500.png)\n\n * **A** Gesundes Gewebe\n * **B** Gleason Grad 3 \n * **C** Gleason Grad 4\n * **D** Gleason Grad 5\n","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"Leider konnte ich den zweiten Ansatz bis zum Ende des Wettbewerbs nicht vollständig abschließen, beziehungsweise das Notebook nicht erfolgreich einreichen. Daher blieb es bei der Platzierung auf Rang 942.\n\nDie Vorgehensweise war aufgrund der Verwendung der vollen Auflösung der Biopsien zwar vielsprechend, deshalb jedoch mit einem starken Ressourcenverbrauch verbunden. Die Überschreitung des Ressourcen- und Zeitlimits hat einige Einreichungen fehlschlagen lassen. Zuletzt war es wohl noch ein Sonderfall, den ich übersehen habe, oder ein Fehler in meinem Programmcode, der eine erfolgreiche Einreichung in den letzten Tagen des Wettbewerbs verhindert hat.\n\n### Fazit\n![https://knowyourmeme.com/photos/918810-funny-error-messages](https://i.kym-cdn.com/photos/images/facebook/000/918/810/a22.jpg)\nMein Ziel, bei diesem Wettbewerb unter die besten 50% zu kommen, habe ich weit verfehlt. Aber ich konnte dennoch einiges aus diesem Wettbwerb mitnehmen. Zum einen habe ich Wertvolles über das Thema Prostatakrebs, die Diagnose und die Bewertung gelernt. Zum anderen habe ich mehr Erfahrung im Umgang mit KI gesammelt.","execution_count":null}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}