Почему мой Keras CNN для обнаружения диабетической ретинопатии не работает вообще - PullRequest
2 голосов
/ 30 сентября 2019

Мне нужно сделать CNN, чтобы обнаружить диабетическую ретинопатию на 4-й стадии (он должен определить, есть ли DR на 4-й стадии или нет, не требует обнаружения других уровней). На входе будут такие изображения: https://i.imgur.com/DsU06Xv.jpg

Чтобы лучше классифицировать, я уточняю свое изображение: https://i.imgur.com/X1p9G1c.png

Итак, у меня есть база данных с 700 изображениями сетчаткина уровне 0 и 700 сетчатках с уровнем 4.

Проблема в том, что все модели, которые я пытался сделать, не работают, как правило, это стало проблемой переоснащения ..

Я уже пытался использоватьПоследовательная модель, Функциональный API ... и в одном из моих вопросов пользователь рекомендовал мне использовать VGG16 >> вопрос: https://datascience.stackexchange.com/questions/60706/how-do-i-handle-with-my-keras-cnn-overfitting

А сейчас я пытаюсь использовать VGG16, но все еще не работает. не работает, все мои прогнозы равны 0 , и я понятия не имею, что с этим делать ..

Это мой поезд.py:

import cv2
import os
import numpy as np

from keras.layers.core import Flatten, Dense, Dropout, Reshape
from keras.layers.normalization import BatchNormalization
from keras.layers.convolutional import Conv2D
from keras.layers.pooling import MaxPooling2D
from keras import regularizers
from keras.models import Model
from keras.layers import Input, ZeroPadding2D, Dropout
from keras import optimizers
from keras.optimizers import SGD
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.utils import to_categorical 

from keras.applications.vgg16 import VGG16

# example of using a pre-trained model as a classifier
from keras.preprocessing.image import load_img
from keras.preprocessing.image import img_to_array
from keras.applications.vgg16 import preprocess_input
from keras.applications.vgg16 import decode_predictions

TRAIN_DIR = 'train/'
TEST_DIR = 'test/'
v = 'v/'
BATCH_SIZE = 32
NUM_EPOCHS = 5

def ReadImages(Path):
    LabelList = list()
    ImageCV = list()
    classes = ["nonPdr", "pdr"]

    # Get all subdirectories
    FolderList = [f for f in os.listdir(Path) if not f.startswith('.')]

    # Loop over each directory
    for File in FolderList:
        for index, Image in enumerate(os.listdir(os.path.join(Path, File))):
            # Convert the path into a file
            ImageCV.append(cv2.resize(cv2.imread(os.path.join(Path, File) + os.path.sep + Image), (224,224)))
            #ImageCV[index]= np.array(ImageCV[index]) / 255.0
            LabelList.append(classes.index(os.path.splitext(File)[0])) 

            ImageCV[index] = cv2.addWeighted(ImageCV[index],4, cv2.GaussianBlur(ImageCV[index],(0,0), 224/30), -4, 128)

    return ImageCV, LabelList

data, labels = ReadImages(TRAIN_DIR)
valid, vlabels = ReadImages(TEST_DIR)

vgg16_model = VGG16(weights="imagenet", include_top=True)

# (1) visualize layers
print("VGG16 model layers")
for i, layer in enumerate(vgg16_model.layers):
    print(i, layer.name, layer.output_shape)

# (2) remove the top layer
base_model = Model(input=vgg16_model.input, 
                   output=vgg16_model.get_layer("block5_pool").output)

# (3) attach a new top layer
base_out = base_model.output
base_out = Reshape((25088,))(base_out)
top_fc1 = Dropout(0.5)(base_out)
# output layer: (None, 5)
top_preds = Dense(1, activation="sigmoid")(top_fc1)

# (4) freeze weights until the last but one convolution layer (block4_pool)
for layer in base_model.layers[0:14]:
    layer.trainable = False

# (5) create new hybrid model
model = Model(input=base_model.input, output=top_preds)

# (6) compile and train the model
sgd = SGD(lr=1e-4, momentum=0.9)
model.compile(optimizer=sgd, loss="binary_crossentropy", metrics=["accuracy"])

datagen = ImageDataGenerator(
    featurewise_center=True,
    featurewise_std_normalization=True,
    rotation_range=20,
    width_shift_range=0.2,
    height_shift_range=0.2,
    horizontal_flip=True)

# compute quantities required for featurewise normalization
# (std, mean, and principal components if ZCA whitening is applied)
datagen.fit(data)

# fits the model on batches with real-time data augmentation:
model.fit_generator(datagen.flow(np.array(data), np.array(labels), batch_size=32), 
                    steps_per_epoch=len(np.array(data)) / 32, epochs=5)


#history = model.fit([data], [labels], nb_epoch=NUM_EPOCHS, 
#                    batch_size=BATCH_SIZE, validation_split=0.1)

# evaluate final model
#vlabels = model.predict(np.array(valid))

model.save('model.h5')

Когда я запускаю его, возвращает точность ~ 1,0 или 0,99% с минимальными потерями ~ 0,01 ..

Это мой предикат.py:

from keras.models import load_model
import cv2
import os
import json
import h5py
import numpy as np
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input

TEST_DIR = 'v/'

def fix_layer0(filename, batch_input_shape, dtype):
    with h5py.File(filename, 'r+') as f:
        model_config = json.loads(f.attrs['model_config'].decode('utf-8'))
        layer0 = model_config['config']['layers'][0]['config']
        layer0['batch_input_shape'] = batch_input_shape
        layer0['dtype'] = dtype
        f.attrs['model_config'] = json.dumps(model_config).encode('utf-8')

fix_layer0('model.h5', [None, 224, 224, 3], 'float32')

model = load_model('model.h5')

for filename in os.listdir(r'v/'):
    if filename.endswith(".jpg") or filename.endswith(".ppm") or filename.endswith(".jpeg") or filename.endswith(".png"):
        ImageCV = cv2.resize(cv2.imread(os.path.join(TEST_DIR) + filename), (224,224))

        x = image.img_to_array(ImageCV)
        x = np.expand_dims(x, axis=0)
        x = preprocess_input(x)
        print(np.argmax(model.predict(x)))

Когда я его запускаю, всемои прогнозы равны 0 .. и если отбросить np.argmax и запустить только model.predict, возвращает следующий результат:

[[0.03993018]]
[[0.9984968]]
[[1.]]
[[1.]]
[[0.]]
[[0.9999999]]
[[0.8691623]]
[[1.01611796e-07]]
[[1.]]
[[0.]]
[[1.]]
[[0.17786741]]

Учитывая, что 2 first изображения - класс 0, а остальные - класс 1 (уровень 4), результаты не 0.99 или 1.0 соотв. ..

Что я должен сделать? Я очень, очень ценю любую помощь!

ОБНОВЛЕНИЕ

Я обновил свой код, как сказал @Manoj .. Я добавил проверку и досрочное прекращение:

es = EarlyStopping(monitor='val_loss', verbose=1)

# fits the model on batches with real-time data augmentation:
model.fit_generator(datagen.flow(np.array(data), np.array(labels), batch_size=32), 
                    steps_per_epoch=len(np.array(data)) / 32, epochs=5,
                    validation_data=(np.array(valid), np.array(vlabels)),
                    nb_val_samples=72, callbacks=[es])

И возвращает эти числа:

Epoch 1/5
44/43 [==============================] - 452s 10s/step - loss: 0.2377 - acc: 0.9162 - val_loss: 1.9521 - val_acc: 0.8472
Epoch 2/5
44/43 [==============================] - 445s 10s/step - loss: 0.0229 - acc: 0.9991 - val_loss: 1.8908 - val_acc: 0.8611
Epoch 3/5
44/43 [==============================] - 447s 10s/step - loss: 0.0107 - acc: 0.9993 - val_loss: 1.7658 - val_acc: 0.8611
Epoch 4/5
44/43 [==============================] - 458s 10s/step - loss: 0.0090 - acc: 0.9993 - val_loss: 1.6805 - val_acc: 0.8750
Epoch 5/5
44/43 [==============================] - 463s 11s/step - loss: 0.0052 - acc: 0.9993 - val_loss: 1.6730 - val_acc: 0.8750

Но после этого мои прогнозы (которые были верны 7/12) теперь верны 5/12 ..

Что я могу сделать, чтобы справиться с этим?

ОБНОВЛЕНИЕ 2

Я поместил этот код в мой train.py:

mean = datagen.mean  
std = datagen.std

print(mean, "mean")
print(std, "std")

и значения, возвращаемые этими отпечатками, которые я вставил в Forex.py:

def normalize(x, mean, std):
    x[..., 0] -= mean[0]
    x[..., 1] -= mean[1]
    x[..., 2] -= mean[2]
    x[..., 0] /= std[0]
    x[..., 1] /= std[1]
    x[..., 2] /= std[2]
    return x

for filename in os.listdir(r'v/'):
    if filename.endswith(".jpg") or filename.endswith(".ppm") or filename.endswith(".jpeg") or filename.endswith(".png"):
        ImageCV = cv2.resize(cv2.imread(os.path.join(TEST_DIR) + filename), (224,224))

        x = image.img_to_array(ImageCV)
        x = np.expand_dims(x, axis=0)
        x = normalize(x, [59.5105,61.141457,61.141457], [60.26705,61.85445,63.139835])

        prob = model.predict(x)
        if prob < 0.5:
            print("nonPDR")
        else:
            print("PDR")
        print(filename)

и теперь все мои прогнозы - (класс 1) PDR ... Я сделал что-то не так?

ОБНОВЛЕНИЕ 3

Я выпалgaussianblur, который я использовал в ReadImages, и включал следующее:

data = np.asarray(data)
valid = np.asarray(valid)

data = data.astype('float32')
valid = valid.astype('float32')

data /= 255
valid /= 255

И после запуска мой train.py:

Epoch 1/15

44/43 [==============================] - 476s 11s/step - loss: 0.7153 - acc: 0.5788 - val_loss: 0.6937 - val_acc: 0.5556

Epoch 2/15

44/43 [==============================] - 468s 11s/step - loss: 0.5526 - acc: 0.7275 - val_loss: 0.6838 - val_acc: 0.5833

Epoch 3/15

44/43 [==============================] - 474s 11s/step - loss: 0.5068 - acc: 0.7595 - val_loss: 0.6927 - val_acc: 0.5694

Epoch 00003: early stopping

После того, как я обновляю стандартное значение и среднее значение на Forex.py:

for filename in os.listdir(r'v/'):
    if filename.endswith(".jpg") or filename.endswith(".ppm") or filename.endswith(".jpeg") or filename.endswith(".png"):
        ImageCV = cv2.resize(cv2.imread(os.path.join(TEST_DIR) + filename), (224,224))

        ImageCV = np.asarray(ImageCV)

        ImageCV = ImageCV.astype('float32')

        ImageCV /= 255  
        x = ImageCV

        x = np.expand_dims(x, axis=0)
        x = normalize(x, [0.12810835, 0.17897758, 0.23883381], [0.14304605, 0.18229756, 0.2362126])

        prob = model.predict(x)
        if prob <= 0.70: # I CHANGE THE THRESHOLD TO 0.7
            print("nonPDR >>>", filename)
            nonPdr += 1
        else:
            print("PDR >>>", filename)
            pdr += 1
        print(prob)
print("Number of retinas with PDR: ",pdr)
print("Number of retinas without PDR: ",nonPdr)

И после запуска этого кода я получаю примерно 75% точности в моем тестовом каталоге.

Итак, я могу что-то улучшить, или это максимум для этихкрошечное число яМаги

1 Ответ

1 голос
/ 30 сентября 2019
  1. Этапы предварительной обработки данных должны быть одинаковыми для обучения и тестирования. Я вижу как минимум две несоответствия. Во-первых, по данным поезда GaussianBlur применяется ко всем изображениям. Обычно такие преобразования используются в качестве стратегий дополнения данных и не применяются ко всему обучающему набору. Во-вторых, нормализация, используемая для обучения и тестирования, должна быть одинаковой. В приведенных выше фрагментах кода для прогнозов применяется vgg16.preprocess_input, который использует среднее значение / дисперсию данных imagenet, в то время как во время обучения среднее значение / дисперсия рассчитывается на основе самих данных обучения. Что вы можете сделать, это взять значения datagen.mean и datagen.std после вызова datagen.fit и использовать их во время прогнозирования для нормализации данных вместо preprocess_input.

  2. Вы нене определен генератор валидации. При обучении вы используете тренировочный набор и набор проверки и прекращаете обучение, когда потеря проверки не улучшается. В противном случае модель будет соответствовать учебному набору данных.

    https://gist.github.com/fchollet/7eb39b44eb9e16e59632d25fb3119975 https://keras.io/callbacks/#earlystopping

  3. Поскольку последний уровень вашей сети является сигмоидом, подобным этому

    top_preds = Dense (1, активация = "sigmoid") (top_fc1)

    есть только один выход, и это значение вероятности от 0 до 1. np.argmax здесь не имеет значения.

    np.argmax используется, когда последний слой использует softmax активацию с двумя выходами, вероятности которых равны 1, и в качестве результата выбран индекс с более высокой вероятностью.

    Возвращаясь к результатам, которые выполучить с sigmoid, обычно выбирается порог, чтобы решить, следует ли классифицировать его как класс 0 или класс 1. Пороговое значение по умолчанию - 0,5. Кривая ROC может быть создана с использованием вероятностей для определения оптимального порога.

    https://scikit -learn.org / stable / modules / generate / sklearn.metrics.roc_curve.html

    Использование порога 0,5,

    prob = model.predict(x)
    if prob < 0.5:
        output = 0
    else:
        output = 1
    
    [[0.03993018]] => < 0.5, class 0 correct
    [[0.9984968]]  => > 0.5, class 1 incorrect
    [[1.]]         => > 0.5, class 1 correct
    [[1.]]         => > 0.5, class 1 correct
    [[0.]]         => < 0.5, class 0 incorrect
    [[0.9999999]]  => > 0.5, class 1 correct
    [[0.8691623]]  => > 0.5, class 1 correct
    [[1.01611796e-07]] => < 0.5, class 0 incorrect
    [[1.]]             => > 0.5, class 1 correct
    [[0.]]             => < 0.5, class 0 incorrect
    [[1.]]             => > 0.5, class 1 correct
    [[0.17786741]]     => < 0.5, class 0 incorrect
    

    Точность = 7/12 = 58%

...