Реализация Keras UNET очень плохо предсказывает - PullRequest
0 голосов
/ 29 апреля 2020

Я новичок в Deep Learning, но я хочу стать профи. Кажется, как без внешнего руководства это трудно сделать: -)

Я пытаюсь принять этот подход https://www.youtube.com/watch?v=azM57JuQpQI&t=23s, который основан на этой статье https://www.depends-on-the-definition.com/unet-keras-segmenting-images/ к моей задаче сегментации спутниковых изображений с использованием UNET с Keras.

Вот мой код для обучения сети

import tensorflow as tf
import os
import random
import numpy as np
from tqdm import tqdm
import cv2
from keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau

from skimage.io import imread, imshow
from skimage.transform import resize
import matplotlib.pyplot as plt

os.environ['KERAS_BACKEND'] = 'tensorflow'

seed = 42
np.random.seed = seed

IMAGE_HEIGHT = 256
IMAGE_WIDTH = 256
IMAGE_CHANELS = 3

trainImageFolderPath = os.path.join(os.path.dirname(__file__), 'Bright Dunes Groups')

train_ids = next(os.walk(trainImageFolderPath))[1]

X_train = np.zeros((len(train_ids), IMAGE_HEIGHT, IMAGE_WIDTH, IMAGE_CHANELS), dtype=np.uint8)
Y_train = np.zeros((len(train_ids), IMAGE_HEIGHT, IMAGE_WIDTH, 1), dtype=np.bool)

print('Building training set...')

for n, id_ in tqdm(enumerate(train_ids), total=len(train_ids)):
    path = os.path.join(trainImageFolderPath, id_)
    imagePath = os.path.join(path, id_ + '.jpg')
    img = cv2.imread(imagePath)
    img = resize(img, (IMAGE_HEIGHT, IMAGE_WIDTH), mode='constant', preserve_range=True)

    X_train[n] = img

    mask = np.zeros((IMAGE_HEIGHT, IMAGE_WIDTH, 1), dtype=np.bool)

    for mask_file in next(os.walk(os.path.join(path, 'masks')))[2]:
        maskPath = os.path.join(path, 'masks', mask_file)
        mask_ = cv2.imread(maskPath, cv2.IMREAD_GRAYSCALE)
        mask_ = resize(mask_, (IMAGE_HEIGHT, IMAGE_WIDTH), mode='constant', preserve_range=True)

        mask_ = np.expand_dims(mask_, axis=-1)
        mask = np.maximum(mask, mask_)

    Y_train[n] = mask

inputs = tf.keras.layers.Input((IMAGE_HEIGHT, IMAGE_WIDTH, IMAGE_CHANELS))

s = tf.keras.layers.Lambda(lambda x: x/255)(inputs)

c1 = tf.keras.layers.Conv2D(16, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(s)
c1 = tf.keras.layers.Dropout(0.1)(c1)
c1 = tf.keras.layers.Conv2D(16, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(c1)
p1 = tf.keras.layers.MaxPooling2D((2,2))(c1)

c2 = tf.keras.layers.Conv2D(32, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(p1)
c2 = tf.keras.layers.Dropout(0.1)(c2)
c2 = tf.keras.layers.Conv2D(32, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(c2)
p2 = tf.keras.layers.MaxPooling2D((2,2))(c2)

c3 = tf.keras.layers.Conv2D(64, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(p2)
c3 = tf.keras.layers.Dropout(0.2)(c3)
c3 = tf.keras.layers.Conv2D(64, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(c3)
p3 = tf.keras.layers.MaxPooling2D((2,2))(c3)

c4 = tf.keras.layers.Conv2D(128, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(p3)
c4 = tf.keras.layers.Dropout(0.2)(c4)
c4 = tf.keras.layers.Conv2D(128, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(c4)
p4 = tf.keras.layers.MaxPooling2D((2,2))(c4)

c5 = tf.keras.layers.Conv2D(256, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(p4)
c5 = tf.keras.layers.Dropout(0.3)(c5)
c5 = tf.keras.layers.Conv2D(256, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(c5)

u6 = tf.keras.layers.Conv2DTranspose(128, (2,2), strides=(2,2), padding='same')(c5)
u6 = tf.keras.layers.concatenate([u6, c4])
c6 = tf.keras.layers.Conv2D(128, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(u6)
c6 = tf.keras.layers.Dropout(0.2)(c6)
c6 = tf.keras.layers.Conv2D(128, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(c6)

u7 = tf.keras.layers.Conv2DTranspose(64, (2,2), strides=(2,2), padding='same')(c6)
u7 = tf.keras.layers.concatenate([u7, c3])
c7 = tf.keras.layers.Conv2D(64, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(u7)
c7 = tf.keras.layers.Dropout(0.2)(c7)
c7 = tf.keras.layers.Conv2D(64, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(c7)

u8 = tf.keras.layers.Conv2DTranspose(32, (2,2), strides=(2,2), padding='same')(c7)
u8 = tf.keras.layers.concatenate([u8, c2])
c8 = tf.keras.layers.Conv2D(32, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(u8)
c8 = tf.keras.layers.Dropout(0.1)(c8)
c8 = tf.keras.layers.Conv2D(32, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(c8)

u9 = tf.keras.layers.Conv2DTranspose(16, (2,2), strides=(2,2), padding='same')(c8)
u9 = tf.keras.layers.concatenate([u9, c1], axis=3)
c9 = tf.keras.layers.Conv2D(16, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(u9)
c9 = tf.keras.layers.Dropout(0.1)(c9)
c9 = tf.keras.layers.Conv2D(16, (3,3), activation='relu', kernel_initializer='he_normal', padding='same')(c9)

outputs = tf.keras.layers.Conv2D(1, (1,1), activation='sigmoid')(c9)

model = tf.keras.Model(inputs=[inputs], outputs=[outputs])
model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

model.summary()


######################################################################################################
callbacks = [
    EarlyStopping(patience=5, verbose=1),
    ReduceLROnPlateau(factor=0.2, patience=3, min_lr=0.001, verbose=1),
    ModelCheckpoint('bright_Dunes_Groups_model.h5', verbose=1, save_best_only=True)
]
######################################################################################################


results = model.fit(X_train, 
                    Y_train,
                    validation_split=0.25,
                    batch_size=16,
                    epochs=80,
                    callbacks = callbacks)

###############################################################
plt.figure(figsize=(8, 8))
plt.title("Learning curve")
plt.plot(results.history["loss"], label="loss")
plt.plot(results.history["val_loss"], label="val_loss")
plt.plot( np.argmin(results.history["val_loss"]), np.min(results.history["val_loss"]), marker="x", color="r", label="best model")
plt.xlabel("Epochs")
plt.ylabel("log_loss")
plt.legend()
###############################################################

idx = random.randint(0, len(X_train))

predictions_train = model.predict(X_train[:int(X_train.shape[0]*0.9)], verbose=1)
predictions_value = model.predict(X_train[int(X_train.shape[0]*0.9):], verbose=1)

predictions_train_t = (predictions_train > 0.5).astype(np.uint8)
predictions_value_t = (predictions_value > 0.5).astype(np.uint8)

###### random training sample

ix = random.randint(0, len(predictions_train_t))
imshow(X_train[ix])
plt.show()
imshow(np.squeeze(Y_train[ix]))
plt.show()
imshow(np.squeeze(predictions_train_t[ix]))
plt.show()

###### random validation sample
ix = random.randint(0, len(predictions_value_t))
imshow(X_train[int(X_train.shape[0]*0.9):][ix])
plt.show()
imshow(np.squeeze(Y_train[int(Y_train.shape[0]*0.9):][ix]))
plt.show()
imshow(np.squeeze(predictions_value_t[ix]))
plt.show()

Мой набор данных trainig состоит из 141 изображения (X_train) и набора масок для каждого из них. Я знаю, это небольшой объем данных, но я бы ожидал чего-то хотя бы от этого набора данных. Точность составляет около 75%, но когда я пытаюсь проверить ее с помощью приведенного ниже кода, я получаю очень плохие результаты.

import tensorflow as tf
import os
import random
import numpy as np
from tqdm import tqdm
import cv2

from skimage.io import imread, imshow
from skimage.transform import resize
import matplotlib.pyplot as plt

os.environ['KERAS_BACKEND'] = 'tensorflow'

IMAGE_HEIGHT = 256
IMAGE_WIDTH = 256
IMAGE_CHANELS = 3

modelFilePath = 'bright_Dunes_Groups_model.h5'
model = tf.keras.models.load_model(modelFilePath)

testImageFolderPath = os.path.join(os.path.dirname(__file__), 'TestDunes')

test_ids = next(os.walk(testImageFolderPath))[2]

for n, id_ in tqdm(enumerate(test_ids), total=len(test_ids)):
    imagePath = os.path.join(testImageFolderPath, id_)
    img = cv2.imread(imagePath)

    img = cv2.resize(img, (IMAGE_HEIGHT, IMAGE_WIDTH))

    imshow(img)
    plt.show()

    img = np.expand_dims(img, axis=0)

    predictions = model.predict(img, verbose=1)

    predictions_value_t = (predictions > 0.33).astype(np.uint8)
    imshow(np.squeeze(predictions_value_t))
    plt.show()

Прогнозы очень плохие.

Итак, я подозреваю, что проблема как минимум в 1 из 2 мест: 1) в коде 2) в наборе данных

One образца моего X_train это

образец X_train

Соответствующая маска выглядит следующим образом

Соответствующая маска X_train

Каждый X_train может иметь много масок, но для этого конкретного изображения есть только одна маска.

Каждое изображение X_train имеет размеры 227 * 227 пикселей. В коде я изменяю его размер (и маску) до 256 * 256

. Для каждого изображения X_train я выполняю увеличение данных вручную (поворачиваю X_train и соответствующую маску на 90, 180, 270 градусов и переворачиваю горизонтально и вертикально). Как я уже упоминал, все вместе дополненные данные дают мне 141 изображение X_train.

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

То, что я также подозреваю как один из источников проблем, что маски могут иметь очень различную форму. Может ли быть так, что неоднородная форма маски создает такой плохой результат предсказания?

Ответы [ 2 ]

0 голосов
/ 30 апреля 2020

Итак, я последовал совету Башира Казими, и теперь я вижу значительное улучшение!

Прежде всего, я должен отметить, что изображения, которые я использовал для обучения, были в оттенках серого, а не в RGB, как это было в образце, который я пытался принять. Я думаю, что это было главной причиной таких неточных предсказаний.

То, что я сделал 1) заменил чтение и изменение размера openCV на лыжный урон как в обучении, так и в тестировании 2) убрал для лямбда-слоя деление входа на 255.

Для 2) Я не уверен, что хорошо понял, что предложил Башир Казими, но сейчас, насколько я понимаю, нет необходимости масштабировать изображения X_train до диапазона [0, 255], потому что эти изображения уже находятся в этот диапазон. Это единственное существенное, что я изменил на данный момент. Было ли что-то еще, что предполагалось, или нет?

Теперь лямбда-слой выглядит следующим образом

s = tf.keras.layers.Lambda(lambda x: x)(inputs)
0 голосов
/ 29 апреля 2020

Я вижу некоторую отправную точку (не решить, но) перепроверить ваш код / ​​набор данных. Я суммирую их ниже:

  1. Вы изменяете размеры тренировочных данных, используя skimage.transform.resize, но вы изменяете размеры тестовых данных, используя cv2.resize. Я был бы осторожен. Убедитесь, что они оба делают то же самое. Некоторые функции масштабируют диапазон значений при изменении размера.
  2. У вас есть лямбда-слой, который должен масштабировать входные данные в диапазоне [0, 255] до диапазона [0, 1]. Опять же, убедитесь, что ваша функция изменения размера предварительной обработки еще не сделала этого за вас.
  3. Обычно деление на 255.0 является хорошим способом для изменения масштаба входных изображений в диапазоне [0, 1] и хорошо работает для изображений RGB, поскольку значения пикселей находятся в диапазоне [0, 255] , но, пожалуйста, убедитесь, что ваши спутниковые снимки также имеют этот диапазон, в противном случае эта операция не работает должным образом, и вам нужно разделить ее на максимальное значение в ваших собственных спутниковых снимках, а не на 255.

Надеюсь, поможет. Удачи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...