Обнаружение рака кожи с помощью TensorFlow
В этой статье мы узнаем, как реализовать модель обнаружения рака кожи с помощью Tensorflow. Мы будем использовать набор данных, содержащий изображения для двух категорий: злокачественных и доброкачественных. Мы будем использовать технику трансферного обучения, чтобы достичь лучших результатов при меньшем количестве тренировок. Мы будем использовать архитектуру EfficientNet в качестве основы нашей модели вместе с предварительно обученными весами того же самого, полученного путем обучения его на наборе данных сети изображений.
Импорт библиотек
Библиотеки Python позволяют нам очень легко обрабатывать данные и выполнять типичные и сложные задачи с помощью одной строки кода.
- Pandas — эта библиотека помогает загружать фрейм данных в формате 2D-массива и имеет несколько функций для выполнения задач анализа за один раз.
- Массивы Numpy очень быстрые и могут выполнять большие вычисления за очень короткое время.
- Matplotlib — эта библиотека используется для рисования визуализаций.
- Sklearn — этот модуль содержит несколько библиотек с предварительно реализованными функциями для выполнения задач от предварительной обработки данных до разработки и оценки моделей.
- Tensorflow — это библиотека с открытым исходным кодом, которая используется для машинного обучения и искусственного интеллекта и предоставляет ряд функций для реализации сложных функций с помощью одной строки кода.
Python3
import numpy as npimport pandas as pdimport seaborn as sbimport matplotlib.pyplot as plt from glob import globfrom PIL import Imagefrom sklearn.model_selection import train_test_split import tensorflow as tffrom tensorflow import kerasfrom keras import layersfrom functools import partial AUTO = tf.data.experimental.AUTOTUNEimport warningswarnings.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() |
Выход:
Потери при обучении со временем уменьшились не так сильно, как потери при проверке.
Python3
hist_df["auc"].plot()hist_df["val_auc"].plot()plt.title("AUC v/s Validation AUC")plt.legend()plt.show() |