[PYTHON] Leistungsvergleich durch Einbindung einer Sprungstruktur in SegNet (CaDIS: ein Katarakt-Datensatz)

1. Zuallererst

Dieser Artikel ist eine Fortsetzung des Artikels, der mit SegNet [^ 2] für CaDIS semantisch segmentiert wurde: ein Katarakt-Datensatz [^ 1], der [zuvor] durchgeführt wurde (https://qiita.com/burokoron/items/c730e607607c925c6fd1). ist. Dieses Mal werden wir die Leistung von U-Net [^ 3] mit einer in das Netzwerk von [previous] integrierten Sprungstruktur vergleichen (https://qiita.com/burokoron/items/c730e607607c925c6fd1).

Alle Codes

2. Umwelt

3. Datensatz & Datenaufteilung

Weitere Informationen finden Sie unter Letztes Mal.

4. Modellbau & Lernen

Dieses Mal werden wir U-Net [^ 3] mit einer Sprungstruktur in SegNet [^ 2] von previous implementieren und die Leistung vergleichen. U-Net verbindet den Ausgang vor dem Encoder Max Pooling mit dem Ausgang nach dem Up Sampling des Decoders und gibt ihn in die Convolution-Schicht im Netzwerk ein, wie in der folgenden Abbildung gezeigt. Auf diese Weise können verlorene Informationen wiederhergestellt werden, indem das Bild mit Max Pooling komprimiert wird.

image.png

Dieses Mal werden wir nur die Sprungstruktur in die Netzwerkstruktur von [vorherige] einführen (https://qiita.com/burokoron/items/c730e607607c925c6fd1). Verzweigen Sie zunächst das Netzwerk wie folgt.

x = Conv2D(32, (3, 3), padding='same', kernel_initializer='he_normal')(inputs)
x = BatchNormalization()(x)
x1 = Activation('relu')(x)
x = MaxPool2D(pool_size=(2, 2))(x1)

Verzweigen Sie die Ausgabe, indem Sie "x1" nicht aktualisieren. Verketten Sie anschließend die verzweigten Ausgaben wie folgt.

x = UpSampling2D(size=(2, 2))(x)
x = concatenate([x, x1], axis=-1)
x = Conv2D(64, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)

Sie können Eingangstensoren mit concatenate () verketten. Dieses Mal kombinieren wir "x1 = (Höhe, Breite, ch1)" und "x = (Höhe, Breite, ch2)", um den Ausgangstensor von "(Höhe, Breite, ch1 + ch2)" zu erhalten. Das Netzwerk, auf das dies in jedem Max Pooling und Up Sampling angewendet wird, ist unten dargestellt.

U-Net-Code
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, MaxPool2D, UpSampling2D, concatenate
from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation

# U-Net(8 Schichten Encoder, 8 Schichten Decoder)Bauen
def cnn(input_shape, classes):
    #Die Eingabebildgröße muss ein Vielfaches von 32 sein
    assert input_shape[0]%32 == 0, 'Input size must be a multiple of 32.'
    assert input_shape[1]%32 == 0, 'Input size must be a multiple of 32.'

    #Encoder
    ##Eingabeebene
    inputs = Input(shape=(input_shape[0], input_shape[1], 3))

    ##1. Schicht
    x = Conv2D(32, (3, 3), padding='same', kernel_initializer='he_normal')(inputs)
    x = BatchNormalization()(x)
    x1 = Activation('relu')(x)
    x = MaxPool2D(pool_size=(2, 2))(x1)

    ##2. Schicht
    x = Conv2D(64, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
    x = BatchNormalization()(x)
    x2 = Activation('relu')(x)
    x = MaxPool2D(pool_size=(2, 2))(x2)

    ##3. Schicht
    x = Conv2D(128, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
    x = BatchNormalization()(x)
    x3 = Activation('relu')(x)
    x = MaxPool2D(pool_size=(2, 2))(x3)

    ##4. Schicht
    x = Conv2D(256, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
    x = BatchNormalization()(x)
    x4 = Activation('relu')(x)
    x = MaxPool2D(pool_size=(2, 2))(x4)

    ##5. und 6. Schicht
    x = Conv2D(512, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    x = Conv2D(512, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
    x = BatchNormalization()(x)
    x5 = Activation('relu')(x)
    x = MaxPool2D(pool_size=(2, 2))(x5)

    ##7. und 8. Schicht
    x = Conv2D(1024, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    x = Conv2D(1024, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    #Decoder
    ##1. Schicht
    x = Conv2D(1024, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    ##2. und 3. Schicht
    x = UpSampling2D(size=(2, 2))(x)
    x = concatenate([x, x5], axis=-1)
    x = Conv2D(512, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    x = Conv2D(512, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    ##4. Schicht
    x = UpSampling2D(size=(2, 2))(x)
    x = concatenate([x, x4], axis=-1)
    x = Conv2D(256, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    ##5. Schicht
    x = UpSampling2D(size=(2, 2))(x)
    x = concatenate([x, x3], axis=-1)
    x = Conv2D(128, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    ##6. Schicht
    x = UpSampling2D(size=(2, 2))(x)
    x = concatenate([x, x2], axis=-1)
    x = Conv2D(64, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    ##7. und 8. Schicht
    x = UpSampling2D(size=(2, 2))(x)
    x = concatenate([x, x1], axis=-1)
    x = Conv2D(64, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
    x = Conv2D(classes, (1, 1), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
    outputs = Activation('softmax')(x)


    return Model(inputs=inputs, outputs=outputs)


#Netzwerkaufbau
model = cnn(image_size, classes)

Darüber hinaus denke ich, dass die Beschreibung des Netzwerkteils aufgrund der Vertiefung in der Zukunft überflüssig wird, sodass ich den Encoder "Conv + BN + Relu + MaxPool" und den Decoder "Conv + BN + Relu + Upsampl" funktionsfähig machen werde. Das Folgende ist eine leicht erfrischende Version der Funktion.

import dataclasses
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, MaxPool2D, UpSampling2D, concatenate
from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation

# U-Net(8 Schichten Encoder, 8 Schichten Decoder)Bauen
@dataclasses.dataclass
class CNN:
    input_shape: tuple #Bildgröße eingeben
    classes: int #Anzahl der Klassifizierungsklassen

    def __post_init__(self):
        #Die Eingabebildgröße muss ein Vielfaches von 32 sein
        assert self.input_shape[0]%32 == 0, 'Input size must be a multiple of 32.'
        assert self.input_shape[1]%32 == 0, 'Input size must be a multiple of 32.'


    #Encoderblock
    @staticmethod
    def encoder(x, blocks, filters, pooling):
        for i in range(blocks):
            x = Conv2D(filters, (3, 3), padding='same', kernel_initializer='he_normal')(x)
            x = BatchNormalization()(x)
            x = Activation('relu')(x)

        if pooling:
            return MaxPool2D(pool_size=(2, 2))(x), x
        else:
            return x


    #Decoderblock
    @staticmethod
    def decoder(x1, x2, blocks, filters):
        x = UpSampling2D(size=(2, 2))(x1)
        x = concatenate([x, x2], axis=-1)

        for i in range(blocks):
            x = Conv2D(filters, (3, 3), padding='same', kernel_initializer='he_normal')(x)
            x = BatchNormalization()(x)
            x = Activation('relu')(x)

        return x


    def create(self):
        #Encoder
        inputs = Input(shape=(self.input_shape[0], self.input_shape[1], 3)) #Eingabeebene
        x, x1 = self.encoder(inputs, blocks=1, filters=32, pooling=True) #1. Schicht
        x, x2 = self.encoder(x, blocks=1, filters=64, pooling=True) #2. Schicht
        x, x3 = self.encoder(x, blocks=1, filters=128, pooling=True) #3. Schicht
        x, x4 = self.encoder(x, blocks=1, filters=256, pooling=True) #4. Schicht
        x, x5 = self.encoder(x, blocks=2, filters=512, pooling=True) #5. und 6. Schicht
        x = self.encoder(x, blocks=2, filters=1024, pooling=False) #7. und 8. Schicht

        #Decoder
        x = self.encoder(x, blocks=1, filters=1024, pooling=False) #1. Schicht
        x = self.decoder(x, x5, blocks=2, filters=512) #2. und 3. Schicht
        x = self.decoder(x, x4, blocks=1, filters=256) #4. Schicht
        x = self.decoder(x, x3, blocks=1, filters=128) #5. Schicht
        x = self.decoder(x, x2, blocks=1, filters=64) #6. Schicht
        ##7. und 8. Schicht
        x = UpSampling2D(size=(2, 2))(x)
        x = concatenate([x, x1], axis=-1)
        x = Conv2D(64, (3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
        x = Conv2D(self.classes, (1, 1), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x)
        outputs = Activation('softmax')(x)


        return Model(inputs=inputs, outputs=outputs)


#Netzwerkaufbau
model = CNN(input_shape=image_size, classes=classes).create()

Die Lernmethode und die Parameter sind dieselben wie bei Letztes Mal. Die Lernergebnisse sind wie folgt. history.jpg

5. Bewertung

Die Bewertung wird durch die durchschnittliche IoU für jede Klasse und die mittlere IoU durchgeführt, die der Durchschnitt von ihnen ist, wie im vorherigen.

Nachfolgend sind die Bewertungsergebnisse aufgeführt. Aus der Tabelle können wir ersehen, dass der [vorherige] Wert (https://qiita.com/burokoron/items/c730e607607c925c6fd1) für viele Klassen, die leicht ableitbar sind, 0% betrug. Zusätzlich betrug das mittlere Io U 18,5%. Letztes Mal SegNet [^ 2] betrug 15,0%, sodass wir die Genauigkeit um 3,5% verbessern konnten.

Index Class average IoU(SegNet)[%] average IoU(U-Net)[%]
0 Pupil 85.3 86.5
1 Surgical Tape 53.3 57.1
2 Hand 6.57 6.96
3 Eye Retractors 21.9 53.6
4 Iris 74.4 76.0
5 Eyelid 0 0
6 Skin 49.7 48.4
7 Cornea 88.0 88.5
8 Hydro. Cannula 0 31.8
9 Visco. Cannula 0 4.36
10 Cap. Cystotome 0 3.71
11 Rycroft Cannula 0 4.37
12 Bonn Forceps 3.58 7.94
13 Primary Knife 5.35 10.3
14 Phaco. Handpiece 0.0781 12.3
15 Lens Injector 16.4 15.8
16 A/I Handpiece 16.4 20.5
17 Secondary Knife 6.08 11.8
18 Micromanipulator 0 8.99
19 A/I Handpiece Handle 6.49 8.16
20 Cap. Forceps 0 0.337
21 Rycroft Cannula Handle 0 0.00863
22 Phaco. Handpiece Handle 0 4.26
23 Cap. Cystotome Handle 0 0.407
24 Secondary Knife Handle 2.49 3.82
25 Lens Injector Handle 0 0
26 Water Sprayer
27 Suture Needle 0 0
28 Needle Holder
29 Charleux Cannula 0 0
30 Vannas Scissors
31 Primary Knife Handle 0 0
32 Viter. Handpiece 0 0
33 Mendez Ring
34 Biomarker
35 Marker

7. Zusammenfassung

In diesem Artikel haben wir U-Net [^ 3] implementiert, das eine Sprungstruktur in SegNet [^ 2] von [previous] enthält (https://qiita.com/burokoron/items/c730e607607c925c6fd1). Für CaDIS: einen Katarakt-Datensatz [^ 1] haben wir die Leistung mit dem mittleren IoU verglichen und eine Verbesserung der Genauigkeit um 3,5% bestätigt. Die höchste Genauigkeit des Papiers [^ 1] beträgt 34,16%, bis zu 52,66% von PSPNet. Basierend auf diesem Ergebnis werde ich weiterhin die neuesten Methoden wie Netzwerkstruktur und Datenerweiterungsmethode einbeziehen und die gleiche oder eine bessere Leistung anstreben.

Recommended Posts

Leistungsvergleich durch Einbindung einer Sprungstruktur in SegNet (CaDIS: ein Katarakt-Datensatz)
Bildsegmentierung mit CaDIS: ein Katarakt-Datensatz