Обнаружение рака кожи с помощью TensorFlow

Опубликовано: 21 Февраля, 2023

В этой статье мы узнаем, как реализовать модель обнаружения рака кожи с помощью Tensorflow. Мы будем использовать набор данных, содержащий изображения для двух категорий: злокачественных и доброкачественных. Мы будем использовать технику трансферного обучения, чтобы достичь лучших результатов при меньшем количестве тренировок. Мы будем использовать архитектуру EfficientNet в качестве основы нашей модели вместе с предварительно обученными весами того же самого, полученного путем обучения его на наборе данных сети изображений.

Импорт библиотек

Библиотеки Python позволяют нам очень легко обрабатывать данные и выполнять типичные и сложные задачи с помощью одной строки кода.

  • Pandas — эта библиотека помогает загружать фрейм данных в формате 2D-массива и имеет несколько функций для выполнения задач анализа за один раз.
  • Массивы Numpy очень быстрые и могут выполнять большие вычисления за очень короткое время.
  • Matplotlib — эта библиотека используется для рисования визуализаций.
  • Sklearn — этот модуль содержит несколько библиотек с предварительно реализованными функциями для выполнения задач от предварительной обработки данных до разработки и оценки моделей.
  • Tensorflow — это библиотека с открытым исходным кодом, которая используется для машинного обучения и искусственного интеллекта и предоставляет ряд функций для реализации сложных функций с помощью одной строки кода.

Python3




import numpy as np
import pandas as pd
import seaborn as sb
import matplotlib.pyplot as plt
  
from glob import glob
from PIL import Image
from sklearn.model_selection import train_test_split
  
import tensorflow as tf
from tensorflow import keras
from keras import layers
from functools import partial
  
AUTO = tf.data.experimental.AUTOTUNE
import warnings
warnings.filterwarnings("ignore")

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

Python3




images = glob("train/*/*.jpg")
len(images)

Выход:

2637

Python3




df = pd.DataFrame({"filepath": images})
df["label"] = df["filepath"].str.split("/", expand=True)[1]
df.head()

Выход:

Преобразование меток в 0 и 1 сохранит нашу работу по кодированию меток, поэтому давайте сделаем это прямо сейчас.

Python3




df["label_bin"] = np.where(df["label"].values == "malignant", 1, 0)
df.head()

Выход:

Python3




x = df["label"].value_counts()
plt.pie(x.values,
        labels=x.index,
        autopct="%1.1f%%")
plt.show()

Выход:

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

Python3




for cat in df["label"].unique():
    temp = df[df["label"] == cat]
  
    index_list = temp.index
    fig, ax = plt.subplots(1, 4, figsize=(15, 5))
    fig.suptitle(f"Images for {cat} category . . . .", fontsize=20)
    for i in range(4):
        index = np.random.randint(0, len(index_list))
        index = index_list[index]
        data = df.iloc[index]
  
        image_path = data[0]
  
        img = np.array(Image.open(image_path))
        ax[i].imshow(img)
plt.tight_layout()
plt.show()

Выход:

Теперь давайте разделим данные на обучающую и проверочную части с помощью функции train_test_split.

Python3




features = df["filepath"]
target = df["label_bin"]
  
X_train, X_val,
    Y_train, Y_val = train_test_split(features, target,
                                      test_size=0.15,
                                      random_state=10)
  
X_train.shape, X_val.shape

Выход:

((2241,), (396,))

Python3




def decode_image(filepath, label=None):
  
    img = tf.io.read_file(filepath)
    img = tf.image.decode_jpeg(img)
    img = tf.image.resize(img, [224, 224])
    img = tf.cast(img, tf.float32) / 255.0
  
    if label == None:
        return img
  
    return img, label

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

Python3




train_ds = (
    tf.data.Dataset
    .from_tensor_slices((X_train, Y_train))
    .map(decode_image, num_parallel_calls=AUTO)
    .map(partial(process_data), num_parallel_calls=AUTO)
    .batch(32)
    .prefetch(AUTO)
)
  
val_ds = (
    tf.data.Dataset
    .from_tensor_slices((X_val, Y_val))
    .map(decode_image, num_parallel_calls=AUTO)
    .batch(32)
    .prefetch(AUTO)
)

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

Разработка модели

Для этой задачи мы будем использовать архитектуру EfficientNet и использовать преимущества предварительно обученных весов таких больших сетей.

Архитектура модели

Мы реализуем модель с использованием функционального API Keras, которая будет содержать следующие части:

  • В данном случае базовой моделью является модель EfficientNet.
  • Слой Flatten сглаживает выходные данные базовой модели.
  • Тогда у нас будет два полносвязных слоя, за которыми следует вывод сглаженного слоя.
  • Мы включили несколько слоев BatchNormalization, чтобы обеспечить стабильное и быстрое обучение, и слой Dropout перед последним слоем, чтобы избежать любой возможности переобучения.
  • Последний слой — это выходной слой, который выводит мягкие вероятности для трех классов.

Python3




from tensorflow.keras.applications.efficientnet import EfficientNetB7
  
pre_trained_model = EfficientNetB7(
    input_shape=(224, 224, 3),
    weights="imagenet",
    include_top=False
)
  
for layer in pre_trained_model.layers:
    layer.trainable = False

Выход:

258076736/258076736 [==============================] - 3s 0us/step

Python3




from tensorflow.keras import Model
  
inputs = layers.Input(shape=(224, 224, 3))
x = layers.Flatten()(inputs)
  
x = layers.Dense(256, activation="relu")(x)
x = layers.BatchNormalization()(x)
x = layers.Dense(256, activation="relu")(x)
x = layers.Dropout(0.3)(x)
x = layers.BatchNormalization()(x)
outputs = layers.Dense(1, activation="sigmoid")(x)
  
model = Model(inputs, outputs)

При составлении модели мы предоставляем эти три основных параметра:

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

Python3




model.compile(
    loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
    optimizer="adam",
    metrics=["AUC"]
)

Теперь давайте обучим нашу модель.

Python3




history = model.fit(train_ds,
                    validation_data=val_ds,
                    epochs=5,
                    verbose=1)

Выход:

Epoch 1/5
71/71 [==============================] - 5s 54ms/step - loss: 0.5478 - auc: 0.8139 - val_loss: 2.6825 - val_auc: 0.6711
Epoch 2/5
71/71 [==============================] - 3s 49ms/step - loss: 0.4547 - auc: 0.8674 - val_loss: 1.1363 - val_auc: 0.8328
Epoch 3/5
71/71 [==============================] - 3s 48ms/step - loss: 0.4288 - auc: 0.8824 - val_loss: 0.8702 - val_auc: 0.8385
Epoch 4/5
71/71 [==============================] - 3s 48ms/step - loss: 0.4044 - auc: 0.8933 - val_loss: 0.6367 - val_auc: 0.8561
Epoch 5/5
71/71 [==============================] - 3s 49ms/step - loss: 0.3891 - auc: 0.9019 - val_loss: 0.9296 - val_auc: 0.8558

Давайте визуализируем потери при обучении и проверке и AUC для каждой эпохи.

Python3




hist_df = pd.DataFrame(history.history)
hist_df.head()

Выход:

Python3




hist_df["loss"].plot()
hist_df["val_loss"].plot()
plt.title("Loss v/s Validation Loss")
plt.legend()
plt.show()

Выход:

Потери при обучении со временем уменьшились не так сильно, как потери при проверке.