[PYTHON] Les débutants en Deep Learning ont essayé de comprendre le plus possible le cours en ligne sur l'IA médicale [chapitre 5]

introduction

Tout le code de cet article est [Medical AI Online Course Chapter 5 Practical Edition: Segmentation of MRI Images](https://japan-medical-ai.github.io/medical-ai-course-materials/notebooks/05_Image_Segmentation] J'ai essayé de comprendre en citant .html). </ b> Le site Web original du cours en ligne a été créé par ** Medical AI Society ** et est une page très sophistiquée.

Cet article est une organisation explicite de ce code sophistiqué pour les débutants en Deep Learning, mais ** Tout d'abord, il est préférable de visiter le site Web ci-dessus. ** **

  • Les numéros de paragraphe de cet article sont basés sur le site afin qu'il puisse être comparé au site. Nous sommes.

5.3. Ensemble de données à utiliser

Le premier est le téléchargement de données.

!if [ ! -d train ]; then curl -L -O https://github.com/mitmul/chainer-handson/releases/download/SegmentationDataset/train.zip && unzip train.zip && rm -rf train.zip; fi
!if [ ! -d val ]; then curl -L -O https://github.com/mitmul/chainer-handson/releases/download/SegmentationDataset/val.zip && unzip val.zip && rm -rf val.zip; fi
  • ** À propos de [!] **

«Au fait, beaucoup d'entre vous se sont peut-être demandé ce que le`!

  • Site pertinent vous permet de l'exécuter avec ** google colab **. , Nous examinons le code python sous forme de notebook.
  • Vous pouvez exécuter ** code shell ** avec google colab etc. en ajoutant ! Au code qui s'exécute à l'invite de commande (terminal pour mac)

Passons à l'explication du contenu.

  • ** À propos de [ʻif`] **

  • ʻSi xxx; then ooo; fi` signifie que si c'est xxx, il exécutera ooo. C'est presque la même chose que l'anglais. --Pour python, écrivez-le comme si xxx: ooo. ――Cette fois, c'est un script shell, donc la notation est différente, mais ce que vous faites est la même.

  • ** À propos de [curl] **

--curl signifie ** obtenir des fichiers du site Web **. (Une commande avec des fonctionnalités similaires serait probablement wget, etc.) -- -L et -O sont des options pour curl. Ajoutez -L pour activer la ** redirection **. Il semble que «-O» signifie ** télécharger avec le nom de fichier du site d'origine **.

  • Dans ce cas, en ajoutant -O, vous pouvez télécharger les fichiers train.sip et val.zip depuis la page cible de github.com avec les mêmes noms.

  • ** À propos de [&&] ** --&&est l'un des soi-disant opérateurs logiques. Dans ce cas, lorsque la commande précédente est terminée, la commande suivante est exécutée.

  • ** À propos de [ʻunzip`] **

--ʻUnzip` signifie ** décompressez le fichier zip **. Cette fois, décompressez les fichiers compressés train.zip et val.zip dans des dossiers appelés train et val.

  • ** [rm] ** À propos --rm est une commande ** effacer **. J'ai créé le train.zip et val.zip téléchargés avec unzip, donc je ne peux pas le saisir. Alors je l'efface.
  • L'option -rf doit être comprise dans le sens d'effacer plus puissamment que rm sans rien ajouter.
  • Je ne l'ai pas du tout confirmé, mais je pense que -r est une abréviation de récursif et -f est une abréviation de force. -r est souvent utilisé lorsque vous voulez supprimer ** des dossiers et des choses ** dans un script shell.
%matplotlib inline

import matplotlib.pyplot as plt
import numpy as np

from PIL import Image

#Charger des images avec la bibliothèque PIL
img = np.asarray(Image.open('train/image/000.png'))
label = np.asarray(Image.open('train/label/000.png'))

#Affichez deux images côte à côte à l'aide de la bibliothèque matplotlib
fig, axes = plt.subplots(1, 2)
axes[0].set_axis_off()
axes[0].imshow(img, cmap='gray')
axes[1].set_axis_off()
axes[1].imshow(label, cmap='gray')
plt.show()
  • ** À propos de [% matplotlib inline] **

  • C'est comme un tour de magie lors de l'écriture de code python au format notebook. Si cela ne vous dérange pas et écrivez au ** format notebook ** (lorsque vous utilisez jupyter notebook, jupyter lab ou google colab), écrivez ceci.

  • ** À propos de [from ooo import xxx as @@@] **

    • import matplotlib.pyplot as plt Cela signifie télécharger «pyplot» dans la bibliothèque «matplotlib» en tant que «plt». --Lorsque j'utilisais python, j'avais un problème avec from PIL import Image parce que c'était un désordre de mettre ʻas à la fin mais avant. (Peut-être juste moi) ――Cela signifie que seule la partie nommée ʻImage est importée de la bibliothèque nommée PIL. En bref, l'image est qu'il y a un outil appelé ʻImage dans une grande boîte appelée PIL. --Pour que l'ordinateur récupère la pièce, il est nécessaire de ** spécifier une grande case avant le nom de la pièce **. Je pense qu'il sera plus difficile de faire une erreur à l'avenir.
  • Même avec le nom du chemin, le nom du dossier est écrit en premier, puis le fichier qu'il contient est extrait.

  • ** À propos de [np.asarray ()] [ʻImage.open () `] **

    • img = np.asarray(Image.open('train/image/000.png')) ―― L'image que vous avez téléchargée précédemment est lue avec ʻImage, convertie au format tableau en utilisant numpy`, et stockée (assignée) dans une variable appelée img.
  • ** À propos de [plt.subplots ()] **

    • fig, axes = plt.subplots(1, 2)
  • L'allocation du graphique à dessiner à l'aide de «plt» est décidée. Pour plt.subplots (1, 2), 1 dans le sens vertical et 2 dans le sens horizontal. Autrement dit, les deux images sont alignées horizontalement. Au contraire, s'il s'agit de «plt.subplots (2, 1)», deux feuilles seront alignées verticalement.

  • ** À propos de [.set_axis_off ()] **

    • axes[0].set_axis_off() ――Le premier axe aligné côte à côte est effacé. Si vous ne le faites pas, la balance restera et sera affichée. Je veux juste afficher l'image, mais c'est ennuyeux. -La raison de [0] est que ** l'index python commence à 0 **.

Allons-y régulièrement.

  • ** À propos de [.imshow ()] **
    • axes[0].imshow(img, cmap='gray') --Afficher img sur la première feuille. cmap est un format de couleur d'image, n'est-ce pas? Puisque cette fois c'est une image IRM, elle est en noir et blanc, donc "gris" est spécifié.

Enfin, il sera affiché avec plt.show (). Ce n'est pas du tout difficile.

5.4. Segmentation avec un réseau neuronal entièrement connecté

5.4.1. Préparation de l'ensemble de données

Maintenant, un sentiment d'apprentissage profond est sorti de ce domaine. faisons de notre mieux.

import glob
from chainer import datasets

def create_dataset(img_filenames, label_filenames):
    img = datasets.ImageDataset(img_filenames)
    img = datasets.TransformDataset(img, lambda x: x / 255.)  # 0-Normalisé à 1
    label = datasets.ImageDataset(label_filenames, dtype=np.int32)
    dataset = datasets.TupleDataset(img, label)
    return dataset
  • ** À propos de [glob] ** ―― Cette bibliothèque est une bibliothèque qui spécifie un dossier etc. et obtient le chemin des fichiers qu'il contient au format liste. Je l'utilise tout le temps. --Bien que le fonctionnement des fichiers et des dossiers soit légèrement différent avec une bibliothèque appelée os, vous pouvez faire quelque chose de similaire.

  • ** À propos de [datasets.ImageDataset ()] ** ――Selon le site officiel, il se comporte comme si les images étaient au format liste. C'est extrêmement pratique. ** Comme prévu, il s'agit du PFN. ** ** ――En termes d'image, si vous l'utilisez, spécifiez simplement le chemin, et l'image adaptée au chemin sera renvoyée au format np.array.

  • ** À propos de [datasets.TransformDataset (img, lambda x: x / 255.)] **

  • Au fait, avez-vous entendu dire que de nombreuses images (images 8 bits) sont représentées par 256 gradations ** de ** [0-255] **? (RVB, échelle de gris, etc.).

  • Lors de l'apprentissage avec le deep learning, il n'est pas très facile de gérer jusqu'à [0-255]. Il semble que ce soit massivement [0-1] qui soit facile à gérer. --Ainsi, en utilisant la fonction lambda, divisez les valeurs de tous les pixels de l'image par 255 pour aligner la plage sur [0-1].

  • ** [0-255] → [0-1] **, non?

  • ** À propos de [datasets.TupleDataset (img, label)] **

  • Il s'agit d'une fonction qui renvoie des variables sous la forme de tuple, qui a une correspondance biunivoque entre img et label. Comme pour tout ** apprentissage supervisé **, les entrées et les sorties doivent correspondre. C'est le faire. ――Par exemple, même si vous avez une image IRM cardiaque, dans ce cas, si vous n'associez pas quelle partie est le ventricule gauche, vous ne pourrez pas apprendre quelle partie du modèle est le ventricule gauche. Cela prépare le modèle à le faire.

def create_datasets():
    #Nom du fichier image IRM utilisant le glob standard Python/Obtenir une liste des noms de fichiers d'image d'étiquette
    train_img_filenames = sorted(glob.glob('train/image/*.png'))
    train_label_filenames = sorted(glob.glob('train/label/*.png'))

    #Créer un train d'objets de jeu de données, en passant une liste
    train = create_dataset(train_img_filenames, train_label_filenames)

    #Faites de même pour les données de validation
    val_img_filenames = sorted(glob.glob('val/image/*.png'))
    val_label_filenames = sorted(glob.glob('val/label/*.png'))
    val = create_dataset(val_img_filenames, val_label_filenames)

    return train, val
  • ** À propos de [glob.glob (... / *. Png)] ** --glob.glob est celui qui obtient la liste des chemins, comme je l'ai mentionné un peu plus tôt. Par exemple, dans glob.glob ('train / image / *. Png') ** "tout dans le dossier nommé image dans le dossier nommé train qui se termine par l'extension .png " Cela signifie obtenir **. --*est appelé un caractère générique " toutes les chaînes de caractères d'une longueur égale ou supérieure à 0 ". C'est une sorte d'expression régulière. Il est utile de se souvenir de cela seul. Je m'en souviens aussi.

  • ** À propos de [sorted (...)] **

  • Le contenu de la liste est trié par nom. Le tri est appelé tri. ――Si l'ordre des chemins inclus dans le contenu de train_img_filenames et train_label_filenames est dans le désordre et qu'ils ne correspondent pas, je pense que vous l'utilisez car vous ne pouvez pas l'apprendre.

train, val = create_datasets()
print('Dataset size:\n\ttrain:\t{}\n\tvalid:\t{}'.format(len(train), len(val)))
  • Cette partie vérifie le nombre d'images incluses dans train et val. Le nombre de feuilles est déjà évident lorsque vous le téléchargez, mais dans le codage réel, il serait plus facile de le vérifier comme ça.

  • Les valeurs de retour (valeurs retournées par retour) de la fonction create_datasets () définies précédemment sont stockées dans les variables train et val. ――Il arrive que les noms soient les mêmes cette fois, mais les objets train et val créés par la fonction create_datasets () sont affectés aux variables train et val.

  • ** Je pense que vous pouvez utiliser print () **, mais peut-être que vous n'êtes pas habitué à écrire comme \ n, \ t ou .format (). Hmm. \ n est un saut de ligne, \ t est un caractère de tabulation et .format () s'affiche en saisissant des valeurs dans le {} entouré par ''.

Ce n’est pas très cool,

print('Dataset size:')
print('train: '+str(len(train)))
print('val: '+str(len(val))) 

Même si vous le faites, il sera probablement produit avec un sentiment similaire. Ce n'est pas cool (deuxième fois).

5.4.2. Définition du modèle

Il n'est pas exagéré de dire que ce chapitre est la plus grande montagne, le cœur et ** la partie la plus importante du ** réseau ** d'apprentissage **.

import chainer
import chainer.functions as F
import chainer.links as L

class MultiLayerPerceptron(chainer.Chain):

    def __init__(self, out_h, out_w):
        super().__init__()
        with self.init_scope():
            self.l1 = L.Linear(None, 100)
            self.l2 = L.Linear(100, 100)
            self.l3 = L.Linear(100, out_h * out_w)
        self.out_h = out_h
        self.out_w = out_w

    def forward(self, x):
        h = F.relu(self.l1(x))
        h = F.relu(self.l2(h))
        h = self.l3(h)
        n = x.shape[0]

        return h.reshape((n, 1, self.out_h, self.out_w))

Au fait, vous souvenez-vous tous de la classe? Qu'est-ce que l'héritage?

Je vais expliquer dans l'ordre.

  • ** À propos de [class MultiLayer Perceptron (chainer.Chain):] **

  • Cela facilite la création d'un nouveau MultiLayerPerceptron () en utilisant une classe existante appelée chainer.Chain. `` Il est impossible pour les gens ordinaires de fabriquer une voiture à partir de rien, mais il semble que vous puissiez toujours le faire simplement en changeant la couleur de la voiture (je pense qu'une technologie spéciale est en fait nécessaire). En bref, il nous est impossible de construire une voiture à partir de zéro, mais ** nous le faisons comme ça pour ne pouvoir changer qu'une partie et la déplacer **.

  • ** À propos de [super () .__ init __ ()] ** --Ceci appelle la partie init () depuis la classe parente (chainer.Chain dans cet exemple).

  • ** À propos de [avec self.init_scope ():] ** ――Je ne sais pas en détail, mais cette partie est-elle une manière caractéristique d'écrire lors de la création d'un réseau avec chainer? Je vais mettre ici la couche utilisée pour l'apprentissage. Je vais regarder le contenu à partir de maintenant.

  • ** À propos de [self.l1 = L.Linear (None, 100)] ** -Cela signifie le stocker dans une variable appelée «l1» qui est conservée dans cette classe. Cette fois, c'est «L.Linear ()». Comme vous pouvez le voir le L en haut du code d'origine

import chainer.links as L 

Est écrit.

  • En bref, nous avons utilisé une partie appelée Linear () dans chainer.links. ――Et qu'est-ce que exactement Linear ()? Apparemment, c'est une couche entièrement connectée. (Strictement parlant, cela peut être un peu différent)
  • En considérant la correspondance avec les keras, ** links ** est ** couches ** en keras, ** Linear () ** est ** Dense () ** en keras. Est-ce un sentiment de correspondance? Ici, trois couches entièrement connectées sont connectées. Il a une structure simple.
def forward(self, x):
  • [def forward(self, x):] ―― La définition ici est ** avant **, c'est-à-dire ** propagation avant **. Dans le deep learning, les valeurs sont transmises d'avant en arrière en couches afin de convertir l'entrée en sortie. Je le fait. ――Relu est une fonction relu, n'est-ce pas? C'est l'une des ** fonctions d'activation **. Puisqu'il s'agit d'un très important, de nombreuses personnes peuvent le connaître. ――Dans le deep learning, l'entrée est calculée en couches et la fonction d'activation ** la rend non linéaire ** successivement.
  • Dans certains cas, il peut s'agir d'un ** abandon ** ou d'une ** normalisation par lots ** peut être connecté. (Il y en a peut-être d'autres.) Peut-être que vous pouvez également essayer ce modèle.
h = F.relu(self.l1(x))
h = F.relu(self.l2(h))
h = self.l3(h)

――Ce processus est simplement attribué dans l'ordre. --X (exemple: tableau d'images) traité par self.l1 est traité par la fonction relu et affecté à h. --Mise à jour h en traitant h traité par self.l2 avec la fonction relu. ... Je me sens dit.

5.4.3. Définition du formateur

Dans ce chapitre def create_trainer (batchsize, train, val, stop, device = -1): Définit la formation pour le modèle.

model = MultiLayerPerceptron(out_h=256, out_w=256)

Avec cela, le réseau créé précédemment est affecté au modèle. ʻOut_h = 256, out_w = 256` a une image d'entrée de 256 × 256, donc cela correspond.

train_model = L.Classifier(
        model, lossfun=F.sigmoid_cross_entropy, accfun=F.binary_accuracy)
  • ** À propos de [L.classifier] ** --C'est une fonction de perte ou un réseau (modèle au sens large?) En ce qui concerne le document chainer. , Je passe à un modèle de calcul de la fonction acc (modèle au sens strict?). ―― Est-ce à l'image que la poupée est vêtue des vêtements nécessaires? Cette fois, il s'agit d'une classification binaire qui classe chaque pixel de l'image qui prédit le ventricule gauche comme 1 et les autres comme 0. --Ainsi, nous utilisons sigmoid_cross_entropy pour la fonction de perte et binary_accuracy pour la fonction acc.
  • (Pour la classification multi-classes, je pense que vous utiliseriez une ʻaccuracy normale au lieu de softmax_cross_entoropypour la perte etbinary_accuracy` pour l'acc.)
optimizer = optimizers.Adam()
optimizer.setup(train_model)
  • ** À propos de [ʻoptimaizer] ** ――Faites quelque chose comme comment changer les paramètres lors de l'entraînement. Cette fois, vous utilisez ʻAdam () . En outre, sgd () est également célèbre.

Pour le reste du formateur, vous pouvez probablement dire ce que vous faites en lisant les commentaires qui sont ajoutés ligne par ligne.

5.4.4. Apprentissage

%%time
trainer = create_trainer(64, train, val, (20, 'epoch'), device=0)
trainer.run()

--``` def create_trainer (batchsize, train, val, stop, device = -1): `` `de la fonction précédente Vous pouvez voir ce que représente chaque valeur en comparant avec. --64 lots, train pour les données de formation, val pour la validation, arrêt d'époque à 20 et gpu ou cpu.

5.4.5. Évaluation

preds = []
for img, label in val:
    img = cuda.to_gpu(img[np.newaxis], device)
    pred = model(img)
    pred = cuda.to_cpu(pred.data[0, 0] > 0)
    preds.append((pred, label[0]))
pred_labels, gt_labels = zip(*preds)

――La prochaine chose qui semble être un problème est ici dans def evaluer (trainer, val, device = -1): . Les auteurs-compositeurs peuvent être to_gpu``` ou `` to_cpu```. ―― Dans le chainer, il est nécessaire de distinguer si ce que vous faites maintenant (même pytorch etc.) est fait sur gpu ou cpu. --Avec to_gpu, l'image qui est le matériau pour apprendre le modèle avec gpu est passée de cpu à gpu. (Je pense) --to_cpu transmet le résultat sur gpu à cpu. (Je pense)

Après le chapitre 5.5

Les éléments qui suivent sont implémentés dans la couche de convolution, mais en dehors de la compréhension de ** kernel **, ** stride **, ** pad ** etc., la ligne principale est la couche entièrement connectée ci-dessus. Est le même. (Pour comprendre ces éléments, veuillez vous référer au site concerné ou lire le livre super-majeur "Deep Learning from scratch".)

Expérimentez et devenez un talent multi omics dans le monde médical!

Les références

finalement

J'ai pensé que j'étais prudent, mais je pense qu'il y a probablement beaucoup de descriptions incorrectes, il serait donc utile que vous puissiez signaler les erreurs dans l'article.

Puisque le développement de chainer a été arrêté, j'envisage de réécrire le code ci-dessus avec pytorch, mais je ne sais pas si je le ferai. De plus, je ne sais pas si je le ferai car il était assez difficile de faire un chapitre pour d'autres chapitres.

Cet article est basé sur la philosophie académique de "promouvoir la recherche et l'éducation sur l'IA médicale et contribuer au développement de la médecine moderne au Japon", et est conçu pour aider ceux qui viennent de commencer à programmer. Ceci est un résumé de ce que je pensais nécessaire du point de vue d'une personne expérimentée avec peu d'expérience. Nous ne revendiquons aucun droit. Si vous rencontrez des problèmes de droits d'auteur avec la Medical AI Society, veuillez nous contacter.

Recommended Posts

Les débutants en Deep Learning ont essayé de comprendre le plus possible le cours en ligne sur l'IA médicale [chapitre 5]
Comment étudier pour le test G de Deep Learning Association (pour les débutants) [version 2020]
Dédié aux débutants! Comment apprendre la programmation avec le moins d'argent possible
Enregistrez les étapes pour comprendre l'apprentissage automatique
Deep Learning from scratch ① Chapitre 6 "Techniques liées à l'apprentissage"
[Bouclier d'épée Pokémon] J'ai essayé de visualiser la base de jugement de l'apprentissage en profondeur en utilisant la classification des trois familles comme exemple