Многозначная классификация и прогноз масок изображений

Обнаружение дефектов стали - это конкурс, проводимый на kaggle одной из крупнейших компаний-производителей стали Северсталь. Вот ссылка на этот конкурс. Посетите сайт kaggle, чтобы получить более подробную информацию об этом конкурсе

Постановка задачи

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

О данных: источник
DataTrain_images.Zip: ZIP-файл, содержащий все изображения поездов (12568 уникальный)
Test_images.zip: ZIP-файл, содержащий все изображения поездов (1801 уникальный)
train.csv : содержит столбцы Imageid и Encoded Pixels
submission.csv: тестовый файл CSV, содержащий столбцы Imageid и Encoded Pixels

Описание данных:
Каждое изображение может иметь без дефектов, дефект единственного класса или дефекты нескольких классов (ClassId = [1, 2, 3, 4]).

Цель:
Наша задача по изображению - классифицировать дефект и определить его сегментацию. Для каждого изображения необходимо сегментировать дефекты, если оно принадлежит к каждому классу (ClassId = [1, 2, 3, 4]).
Метки классов и информация о маске:
С этой проблемой связаны две задачи
1. Классификация изображения на 4 дефектных класса ( ClassId = [1, 2, 3, 4]).
1. спрогнозировать местоположение обнаруженных дефектов (сегментация)

ПРИМЕЧАНИЕ: сегмент для каждого класса дефектов будет закодирован в одну строку, даже если на изображении есть несколько несмежных местоположений дефектов, вы можете прочитать об этом здесь.

Различные стратегии трубопроводов

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

pipeline-1: Базовый конвейер с использованием моделей сегментации

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

Это простой конвейер, где все входные данные обучаются с использованием сильной обучающей модели (скорее всего, предварительно обученной). Выходным слоем модели является CNN (4), где для каждого входного изображения мы получаем четыре маски (изображения) с сегментацией этого класса, то есть класса ([1,2,3,4]).

pipeline-2: сегментация с использованием двоичной классификации

Эта стратегия касается построения модели сегментации наряду с двоичной классификацией, сильная модель сегментации (предварительно обученная) строится на всех данных обучения и сохраняет модель с использованием model.save (model_name.h5). также сохраните модель двоичной классификации аналогичным образом при переходе к тестовым данным, отфильтруйте дефектные изображения с помощью предварительно обученного двоичного классификатора и отправьте только дефектные изображения в модель сегментации, таким образом мы можем быстрее тестировать результаты, поскольку мы сохраняем нашу модель, а также фильтрация дефектных изображений из исходных данных.

pipeline-3: Сегментация с использованием классификации по нескольким меткам

Эта стратегия аналогична предыдущей, вместо фильтрации дефектных изображений мы используем предварительно обученную модель классификации с несколькими метками для получения дефектных изображений, поскольку это классификатор с несколькими метками, одно изображение может принадлежать более чем к одному классу. . мы напрямую переносим выходные данные от прохода классификации с несколькими метками к четырем моделям сегментации для класса ([1,2,3,4]), здесь каждая модель представляет собой предварительно обученную модель сегментации с соответствующим классом дефекта ([1,2,3 , 4]).

pipeline-4: Модель сегментации с использованием двоичной и множественной классификации меток

Как вы уже догадались, да! Это комбинация трубопровода-3 и трубопровода-4. Что ж, я частично попробовал все вышеперечисленные стратегии и придерживаюсь этого, потому что это дало мне лучшие результаты, чем все. Это также заняло меньше времени, чтобы проверить результаты, потому что мы отфильтровываем исправные изображения и отправляем только дефектные изображения в модель сегментации. Позвольте мне дать вам подробный обзор того, как я реализую этот подход ниже.

Исследовательский анализ данных

Давайте посмотрим на EDA, чтобы узнать больше о данных!

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

Классификация по нескольким меткам

Как мы видим, это сложная проблема, когда наша классификация с несколькими метками представляет собой несбалансированность данных, поскольку дефектные изображения класса 2 очень малы по размеру данных, а дефекты класса 1 очень велики в данных, классы 3,4 в некоторой степени сбалансированы.

Помня о несбалансированности классификации по нескольким меткам, мы наносим на график количество классов на изображение.

Мы можем заметить, что первые 2 класса распределения больше, а 3,4 класса почти равны нулю на изображениях.

Визуализация каждого типа дефекта в данных поезда:

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

Дефект 1-го класса

Оттенки красного цвета на изображениях описывают класс-1. Класс 1, кажется, имеет меньше дефектов и почти похож на исправные изображения.

Дефект 2-го класса

Оттенки зеленого цвета на изображениях описывают класс 2. Их можно нарисовать с использованием кодированных пикселей, предоставленных в данных поезда. Мы можем заметить, что они похожи на дефекты класса 1, и их довольно сложно классифицировать.

Дефект 3-го класса

Пиксели, окрашенные в синий и желтый цвета на изображениях, описывают класс 3, так как мы можем заметить, что изображения класса 3 хуже с точки зрения дефектов по сравнению с классом 1,2.

Дефект 4-го класса

пиксели, которые имеют голубой цвет на изображениях, описывают класс 4. мы можем видеть, что эти изображения класса 4 являются наиболее поврежденными изображениями. Их можно легко классифицировать и сегментировать, потому что эти уникальные типы ребер и дефектов, а не другие классы. Мы, наконец, делаем вывод, что классы 1,2 похожи и менее дефектны, в то время как классы 3,4 менее похожи, но более дефектны, и, следовательно, их легко найти. классифицируйте их.

Модель сегментации с использованием двоичной и множественной классификации меток

Как я уже упоминал, в качестве стратегии я использовал pipeline-4, давайте рассмотрим подробнее!

Бинарная и мульти-маркировочная классификация

Разделение поездов и резюме:

Наши данные не являются данными временного ряда, поэтому мы можем случайным образом разбить данные на train-Cv. Читая обсуждения kaggle и другие записные книжки, я обнаружил, что данные о поездах не похожи на предоставленные тестовые данные. Так что желательно сделать хотя бы увеличение данных. Поступая так, мы не сможем решить проблему полностью, а частично. Вот код для генерации и увеличения данных с помощью генератора данных keras.

#reference 
columns =['class1','class2','class3','class4']

mtr_df, mval_df = train_test_split( mc, random_state=42, test_size=0.1825)
print('train_data shape:',mtr_df.shape,'val_data:',mval_df.shape)

datagen=ImageDataGenerator(rescale=1./255.,
                           shear_range=0.1,
                           zoom_range=0.1,
                           brightness_range=[0.6,1.0],
                           rotation_range=60,
                           horizontal_flip=True,
                           vertical_flip=True
                           )
#test_datagen=ImageDataGenerator(rescale=1./255.)

train_gen=datagen.flow_from_dataframe(
dataframe=mtr_df,
directory=dir1+"./train_images",
x_col="imageid",
y_col=columns,
batch_size=16,
seed=42,
shuffle=False,
class_mode="other",
target_size=(299,299))

val_gen=datagen.flow_from_dataframe(
dataframe=mval_df,
directory=dir1+"./train_images",
x_col="imageid",
y_col=columns,
batch_size=16,
seed=42,
shuffle=False,
class_mode="other",
target_size=(299,299))

Модели классификации двоичных и нескольких этикеток

Для обеих моделей я использовал предварительно обученную модель InceptionResNetV2 () от keras и веса, обученные на основе данных image-net.

model = InceptionResNetV2(weights=None, input_shape=(299,299,3), include_top=False)
model.load_weights('/kaggle/input/inceptionresnetv2/inception_resent_v2_weights_tf_dim_ordering_tf_kernels_notop.h5')
model.trainable=False

x=model.output
x=GlobalAveragePooling2D()(x)
x=Dense(128,activation='relu')(x)
x=Dense(64,activation='relu')(x) 
out=Dense(1,activation='sigmoid')(x) #final layer binary classifier

model_binary=Model(inputs=model.input,outputs=out) 

Аналогичным образом, модель классификации с несколькими метками загружается таким же образом, за исключением выходного уровня, следовательно, это классификатор с четырьмя метками, который равен out = Dense (4, activate = 'сигмовидная') (X)

Обучив генератор за несколько эпох, мы получили хорошую производительность моделей.

Как мы видим, наша бинарная модель получила точность 90 и отзыв 96 к концу 17 эпох, это означает, что наша бинарная модель работает хорошо :).

Давайте посмотрим, насколько хорошо работает наш Классификатор с несколькими метками. Я показываю обе модели одновременно, потому что мы использовали одну и ту же предварительно обученную модель, чтобы мы могли использовать общий код без повторения.

Наша модель с несколькими этикетками также имеет хорошие характеристики, точность составляет 95.

Модели измерения эффективности:

почему мы используем точность и отзыв?

Напомнить фактически вычисляет, сколько фактических положительных результатов наша модель получает, помечая ее как положительную (истинно положительную), а Напомнить будет метрикой модели, если с ней связаны высокие затраты. Ложно-отрицательный. В нашем случае ложно-отрицательный классифицирует дефектное изображение как недефектное, поэтому для нас имеет смысл, что мы не могли позволить нашей модели делать ложно-отрицательные, так как это может полностью исключить отправку изображения. к дальнейшей модели.

Для классификации по нескольким меткам мы использовали и точность, и отзыв, потому что при переходе к классификации по нескольким меткам или нескольким классам рекомендуется использовать оба показателя.

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

#For multi label classification, precision and recall
model_mul=load_model('/kaggle/input/multicaugg/multic_aug.h5',
                     custom_objects="recall":recall,
                     "precision":precision}
)
#use only recall for binary classification

Увеличение времени тестирования

Для повышения производительности модели мы использовали увеличение времени тестирования, что улучшило производительность нашей модели. Мы использовали TTA как для бинарной, так и для множественной классификации. Я предоставлю фрагмент кода для простого TTA, вы можете узнать больше о TTA здесь.

#TTA
tta_steps = 10
multi_class= []
for val in tqdm_notebook(test_gen_mul):
    
    batch_pred = []
    for i in range(tta_steps):
        preds = model_mul.predict_generator(augmentation_gen.flow(val,batch_size=bs, shuffle=False),steps=1)
        batch_pred.append(preds)

    pred = np.mean(batch_pred, axis=0)
    multi_class.append(pred)

Модели сегментации (прогноз по маске)

Загрузка данных

Необходимо использовать другой тип генератора, а не keras generatot, потому что нам нужно получить EncodedPixels (data_y’s) в нашем генераторе для обучения, что невозможно при использовании генератора изображений keras. Это можно сделать с помощью специального генератора данных от stanford edu.

class DataGenerator(keras.utils.Sequence):
    'Generates data for Keras'
    def __init__(self, list_IDs, df, target_df=None, mode='fit',
                 base_path='../input/severstal-steel-defect-detection/train_images',
                 batch_size=16, dim=(128, 800),preprocess=None, n_channels=3,
                 n_classes=1, random_state=2019, shuffle=False):
        self.dim = dim
        self.batch_size = batch_size
        self.df = df
        self.mode = mode
        self.preprocess = preprocess
        self.base_path = base_path
        self.target_df = target_df
        self.list_IDs = list_IDs
        self.n_channels = n_channels
        self.n_classes = n_classes
        self.shuffle = shuffle
        self.random_state = random_state
        self.on_epoch_end()
    

    def __len__(self):
        'Denotes the number of batches per epoch'
        return int(np.floor(len(self.list_IDs) / self.batch_size))

    def __getitem__(self, index):
        'Generate one batch of data'
        # Generate indexes of the batch
        indexes = self.indexes[index*self.batch_size:(index+1)*self.batch_size]
        #print(indexes)
        # Find list of IDs
        list_IDs_batch = [self.list_IDs[k] for k in indexes]
        
        X = self.__generate_X(list_IDs_batch)
        
            
        if self.mode == 'fit':
            y = self.__generate_y(list_IDs_batch)
            return X, y
        
        elif self.mode == 'predict':
            return X

        else:
            raise AttributeError('The mode parameter should be set to "fit" or "predict".')
        
    def on_epoch_end(self):
        'Updates indexes after each epoch'
        self.indexes = np.arange(len(self.list_IDs))
        if self.shuffle == True:
            np.random.seed(self.random_state)
            np.random.shuffle(self.indexes)
    
    def __generate_X(self, list_IDs_batch):
        'Generates data containing batch_size samples'
        # Initialization
        X = np.empty((self.batch_size, *self.dim, self.n_channels))
        
        # Generate data
        for i, ID in enumerate(list_IDs_batch):
            #print(i,ID)
            im_name = self.df['imageid'][ID]
            img_path = f"{self.base_path}/{im_name}"
            img = self.__load_rgb(img_path)
            #print(im_name,img_path)
            # Store samples
            img = cv2.resize(img,(800,128))
            X[i,] = img 
            #print(" X sahpe",X.shape)
            #print(" img sahpe",img.shape)
            # normalize 
            #X = X / 255
        if self.preprocess!=None: X = self.preprocess(X)

        return X
    
    def __generate_y(self, list_IDs_batch):
        y = np.empty((self.batch_size, *self.dim, self.n_classes), dtype=int)
        
        for i, ID in enumerate(list_IDs_batch):
            im_name = self.df['imageid'][ID]
            #image_df = self.target_df[self.target_df['imageid'] == im_name]
            
            rles = self.df['EncodedPixels'][ID]
            h,w=self.dim
            masks = rle_to_mask(rles, 256,1600)
            masks = cv2.resize(masks,(800,128))

            #print(" y sahpe",y.shape)
            #print(" masks sahpe",masks.shape)
            y[i, ] = np.expand_dims(masks, -1)
            y = (y > 0).astype(int)
        return y 

        
    
    def __load_rgb(self, img_path):
        img = cv2.imread(img_path)
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        img = img.astype(np.float32) / 255.

        return img
    
    def __load_grayscale(self, img_path):
        img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
        img = img.astype(np.float32) / 255.
        img = np.expand_dims(img, axis=-1)

        return img

RLE (кодировщик длины серий)

Для прогнозирования дефектных областей пикселей нам необходимо преобразовать их в RLE. Это делается с помощью kaggle, чтобы уменьшить размер отправляемого файла. Я предоставлю для этого фрагмент кода, вы можете обратиться здесь для получения дополнительной информации о том, как преобразовать пиксели в RLE.

def mask_to_rle(mask):
    '''
    Convert a mask into RLE
    
    Parameters: 
    mask (numpy.array): binary mask of numpy array where 1 - mask, 0 - background

    Returns: 
    sring: run length encoding 
    '''
    pixels= mask.T.flatten()
    pixels = np.concatenate([[0], pixels, [0]])
    runs = np.where(pixels[1:] != pixels[:-1])[0] + 1
    runs[1::2] -= runs[::2]
    return ' '.join(str(x) for x in runs)

А также нам нужно преобразовать RLE, предоставленные в данных поезда, в маски, чтобы они соответствовали данным поезда, поэтому мы делаем это следующим образом

def rle_to_mask(rle_string, height, width):
    
    rows, cols = height, width
    img = np.zeros(rows * cols, dtype=np.uint8)
    if len(str(rle_string)) > 1:
        rle_numbers = [int(numstring) for numstring in rle_string.split(' ')]
        rle_pairs = np.array(rle_numbers).reshape(-1, 2)
        for index, length in rle_pairs:
            index -= 1
            img[index:index+length] = 255
    else: img = np.zeros(cols*rows)
    img = img.reshape(cols, rows)
    img = img.T
    return img

Модели сегментации

Выходные данные предыдущих моделей (дефектные изображения) будут входными данными для модели сегментации. Модели сегментации очень полезны для прогнозирования регионов, модель сегментации дала нам хороший результат, на GitHub есть много замечательных репозиториев предварительно обученных моделей сегментации. Тот, который я использовал, принадлежит quvbel, который разместил модели как в keras, так и в pytorch. Все, что вам нужно сделать, это ввести pip install segmentation-models и затем импортировать.

from segmentation_models import Unet
model = Unet('resnet34')

и начать обучение

model.compile()
model.fit(X,y)

Модель обучения

Мы построим четыре модели сегментации для обучения четырех классов дефектов по отдельности, чтобы мы могли передавать каждое дефектное изображение (которое является выходом мульти-метки) в соответствующую модель сегментации для прогнозирования местоположения области дефекта.

#For class-1 defective images
pred_c1 = Unet('resnet50', input_shape=(128, 800, 3), classes=1, activation='sigmoid')
pred_c1.compile(optimizer='adam', loss='binary_crossentropy', metrics=[dice_coef,loss_dice_coef])
#pred_c1.summary()
history = pred_c1.fit_generator(
    trainc1,
    validation_data=valc1,
    use_multiprocessing=False,
    workers=1,
    epochs=25 )

Аналогично для класса 2,3,4 мы построили и обучили аналогичные модели и сохранили модели для использования в будущем. Давайте посмотрим на результаты работы с данными поездов.

Как мы уже обсуждали, и из результатов train-Cv можно заметить, что класс 1,2 имеет низкий коэффициент кости, а класс 3,4 имеет хороший коэффициент dice_coef, поскольку у класса 3,4 больше ребер и большие дефекты.

Dice-коэффициент

Коэффициент Дайса можно использовать для сравнения пиксельного соответствия между прогнозируемой сегментацией и соответствующей базовой истинностью. Формула определяется следующим образом:

#reference
2 ∗(|X∩Y|) / (|X|+|Y|)

где X - прогнозируемый набор пикселей, а Y - истинное значение. Коэффициент Dice определяется равным 1, когда и X, и Y пусты. Рейтинг таблицы лидеров - это среднее значение коэффициентов игральных костей для каждой <ImageId, ClassId> пары в тестовом наборе.

Полученные результаты

Давайте взглянем на некоторые результаты предсказания случайной маски.

Класс-1

2-й класс

3 класс

Класс 4

Эти результаты показывают, что наша модель работает хорошо.

Дальнейший объем

Пока у нас хорошие результаты, но есть еще возможности для улучшения модели:

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

использованная литература

  1. https://stanford.edu/~shervine/blog/keras-how-to-generate-data-on-the-fly
  2. https://github.com/qubvel
  3. https://www.appliedaicourse.com/
  4. https://kaggle.com/c/severstal-steel-defect-detection

Связаться

LinkedIn: https://www.linkedin.com/in/sai-krishna-reddy-1bbb56169/