Почему я пишу этот файл?

Недавно я принял внештатный проект для работы над базой данных Kaggle. Цель состояла в том, чтобы создать модель CNN (Convolutional Neural Network) для классификации изображений. Так как работа не предполагалась столь глубокой, а результат получился очень интересным, я подумал, что шаг за шагом поделюсь со своей сетью. Вот так?

Первые шаги

Поскольку я буду работать с CNN, я уже начал установку Tensorflow.

# Installing the TensorFlow library
!pip install tensorflow
# Importing the libraries that will be used
import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.layers import Dense,Conv2D,Flatten,Dropout,MaxPooling2D
from tensorflow.keras.optimizers import Adam
import random

Данные

Вы можете найти данные, которые я использовал, в этом пути.

Руки вверх

Как только это будет сделано, я прочитаю набор данных.

# I read the dataframes
x_train_df = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/Volcanoes/train_images.csv', header=None)
y_train_df = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/Volcanoes/train_labels.csv')
y_train_df.head()

Как вы можете видеть в y_train_df (наборе данных, в котором хранятся метки), у нас есть столбец, который сообщает нам, есть ли на изображении вулкан. Также предоставляется некоторая дополнительная информация.

print('Images shape: ', x_train_df.shape)
print('Labels shape: ', y_train_df.shape)

У нас есть в общей сложности 7 000 изображений, содержащих 12 100 пикселей.

import seaborn as sns
# As you can see below, there are 6 times more images WITHOUT volcano
plot_volcano = y_train_df.groupby('Volcano?')['Volcano?'].count()
plot_volcano.index = ['N','Y']
plot_volcano = plot_volcano.reset_index()
plot_volcano.columns = ['Volcano?','Value']
plot_volcano['relative_value'] = plot_volcano['Value'] / plot_volcano['Value'].sum()
print(plot_volcano, '\n')
sns.barplot(x='Value', y='Volcano?', data=plot_volcano)

Из этих 7 тысяч изображений мы знаем, что только на 15% есть вулкан. Мы позаботимся об этом позже.

Внимание к данным

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

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

Мы последуем совету и удалим такие изображения из нашей базы данных.

def corrupted_images(df):
  
    corruptedImagesIndex = []
for index, image in enumerate(np.resize(df, (df.shape[0], 12100))):
        sum = 0;
        for pixelIndex in range(0, len(image)):
            sum += image[pixelIndex]
            if pixelIndex == 10:
                break
        if sum == 0:
            corruptedImagesIndex.append(index)
        else:
            sum = 0
for index, image in enumerate(np.resize(df, (df.shape[0], 12100))):
        sum = 0;
        for pixelIndex in range(0, len(image), 110):
            sum += image[pixelIndex]
            if int(pixelIndex/110) == 10:
                break
        if sum == 0 and index not in corruptedImagesIndex:
            corruptedImagesIndex.append(index)
        else:
            sum = 0
return corruptedImagesIndex
corrupted_indexes = corrupted_images(x_train_df)
print("There are "+str(len(corrupted_indexes))+" corrupted images in the training set.")

import random
import matplotlib.pyplot as plt
# Shuffle the data
random.shuffle(corrupted_indexes)
# Resize the data to plot it
X_train_copy = np.resize(x_train_df, (7000, 110, 110))
f, axarr = plt.subplots(5,5,figsize=(10,10))
# I perform some random plots to see corrupted image situations
for i in range(5):
    for j in range(5):
        axarr[i,j].imshow(X_train_copy[corrupted_indexes[i*5+j]])

# Remove corrupted images from training base
x_train_df = x_train_df.drop(corrupted_indexes).reset_index(drop=True)
y_train_df = y_train_df.drop(corrupted_indexes).reset_index(drop=True)

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

Чтобы не быть таким избыточным в тексте, так как весь код уже прокомментирован (в проекте запрошено пошаговое описание), я продолжу делиться тем же и продолжу с выводами по всему тексту.

# Initial dataset size
LENGTH_TRAIN = len(x_train_df) # DS_SIZE
# I create an initial dataset from tensor slices using the dataset API
image_tensor_slices = tf.data.Dataset.from_tensor_slices(np.resize(x_train_df, (LENGTH_TRAIN, 110, 110, 1)))
# I create an isolated dataset where THERE IS VOLCANO to feed it back for 5x
# The idea is to balance the target variable within the dataset
vulcao_ind = y_train_df[y_train_df['Volcano?'] == 1].index
vulcao_image_tensor_slices = tf.data.Dataset.from_tensor_slices(np.resize(x_train_df.iloc[vulcao_ind], (len(vulcao_ind), 110, 110, 1)))
vulcao_label_tensor_slices = tf.data.Dataset.from_tensor_slices(tf.cast(y_train_df.iloc[vulcao_ind]['Volcano?'].values, tf.int32))
# This does not duplicate the data, but tells tensorflow how many times to repeat the data in the stream
image_tensor_slices = image_tensor_slices.concatenate(vulcao_image_tensor_slices.repeat(5)) # repeat(5) = repeat 5 x
# Synchronize dataset labels
label_tensor_slices = tf.data.Dataset.from_tensor_slices(tf.cast(y_train_df['Volcano?'].values, tf.int32))
label_tensor_slices = label_tensor_slices.concatenate(vulcao_label_tensor_slices.repeat(5))
# I define the shape of the image that I will feed into the Keras model
IMAGE_SHAPE = (110, 110, 1)
# I will adjust the dataset via mapping (.map)
def load_reprocess_image(image):
    image = tf.image.resize(image, IMAGE_SHAPE[0:2])
    image = tf.cast(image, tf.float64)
    image /= 255.0  # normalize to the range between [0,1]
    return image
# Pre-process all images (original + 5*volcano)
image_tensor_slices = image_tensor_slices.map(load_reprocess_image)

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

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

# I zip images and labels and then shuffle
image_label_ts = tf.data.Dataset.zip((image_tensor_slices, label_tensor_slices)).shuffle(LENGTH_TRAIN)
BATCH_SIZE = 64
# I divide the database into training (80%) and validation (20%)
train_size, val_size = int(0.8 * LENGTH_TRAIN), int(0.2 * LENGTH_TRAIN)
train_ds = (image_label_ts
           .take(train_size)
           .cache()
           .repeat()
           .batch(BATCH_SIZE)
           .prefetch(buffer_size=tf.data.experimental.AUTOTUNE))
val_ds = (image_label_ts
         .skip(train_size)
         .cache()
         .repeat()
         .batch(BATCH_SIZE)
         .prefetch(buffer_size=tf.data.experimental.AUTOTUNE))

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

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

# Model
def create_enhanced_model():
model = Sequential()
    
    model.add(Conv2D(64, (2, 2),  input_shape=IMAGE_SHAPE,  activation='relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(rate=0.3))
    
    model.add(Conv2D(96, (4, 4), activation='relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(rate=0.45))
model.add(Conv2D(128, (5, 5), activation='relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(rate=0.4))
    
    model.add(Conv2D(128, (6, 6), activation='relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(rate=0.4))
    
    model.add(Flatten())
    model.add(Dense(64, activation='relu'))
    model.add(Dropout(rate=0.35))
    model.add(Dense(1, activation="sigmoid"))
    
    return model
enhanced_model = create_enhanced_model()
# In this case I chose to use the rmsprop optimizer
enhanced_model.compile(optimizer='rmsprop', 
                          loss='binary_crossentropy',
                          metrics=['acc'])
# enhanced_model.summary()
train_steps = (train_size//BATCH_SIZE)+1
val_steps = (val_size//BATCH_SIZE)+1
# Fit the model to the training base
history = enhanced_model.fit(train_ds,
                                  steps_per_epoch=train_steps, 
                                  epochs=50,
                                  validation_data=val_ds, 
                                  validation_steps=val_steps,
                                  callbacks=None)
import matplotlib.pyplot as plt
plt.plot(history.history['acc'], label="train acc")
plt.plot(history.history['val_acc'], label="val acc")
plt.legend()
plt.show()

Модель после 10-й эпохи показала среднюю точность 98,21% для набора данных проверки (20% от общего числа). Обратите внимание, что точность проверочного набора данных увеличивалась с той же скоростью, что и у обучающей базы. Это означает, что модели удалось хорошо обобщить результат.

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

del x_train_df
del y_train_df
x_test_df = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/Volcanoes/train_images.csv', header=None)
y_test_df = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/Volcanoes/train_labels.csv')
LENGTH_TEST = len(x_test_df)
# Run on test dataset
test_image_tensor_slices = tf.data.Dataset.from_tensor_slices(np.resize(x_test_df, (LENGTH_TEST, 110, 110, 1)))
test_image_tensor_slices = test_image_tensor_slices.map(load_reprocess_image)
test_label_tensor_slices = tf.data.Dataset.from_tensor_slices(tf.cast(y_test_df['Volcano?'].values, tf.int32))
test_image_label_ts = tf.data.Dataset.zip((test_image_tensor_slices, test_label_tensor_slices)).batch(BATCH_SIZE)
enhanced_model.evaluate(test_image_label_ts, steps = (LENGTH_TEST//BATCH_SIZE)+1)

import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix
preds = enhanced_model.predict(test_image_label_ts, steps = (LENGTH_TEST//BATCH_SIZE)+1)
def print_matrix_confusion(confusion_matrix, class_names, figsize = (10,7), fontsize=10):
    df_cm = pd.DataFrame(
        confusion_matrix, index=class_names, columns=class_names, 
    )
    fig = plt.figure(figsize=figsize)
    heatmap = sns.heatmap(df_cm, annot=True, fmt="d")
    heatmap.yaxis.set_ticklabels(heatmap.yaxis.get_ticklabels(), rotation=0, ha='right', fontsize=fontsize)
    heatmap.xaxis.set_ticklabels(heatmap.xaxis.get_ticklabels(), rotation=45, ha='right', fontsize=fontsize)
    plt.ylabel('True Label')
    plt.xlabel('Prediction')
    return fig
conf_matrix = confusion_matrix(y_test_df['Volcano?'].values, np.round(preds))
conf_matrix_plt = print_matrix_confusion(conf_matrix, ["It's not volcano", "Volcano"], figsize = (5,4))

Соображения

  • Эта статья направлена ​​на то, чтобы представить приложение CNN чрезвычайно синтезированным способом. Алгоритмы сверточной нейронной сети чрезвычайно сложны и требуют глубокого изучения для эффективного применения.
  • Я не стал вводить слои, применяемые к модели, поэтому предлагаю читателю дополнительное изучение, чтобы из-за этого не образовался пробел в знаниях. Чтобы вникнуть в эту тему здесь, из статьи потребовалось бы много места.
  • Несмотря на то, что я использовал его в проекте, я решил не представлять здесь концепцию увеличения данных. Так же оставляю свою рекомендацию к прочтению интересующимся.

Подтверждение

Я благодарю вас за то, что зашли так далеко, и я надеюсь, что каким-то образом сотрудничал с вами.