Использование моделей EfficientNet для классификации изображений

от автора

Искусственные нейронные сети (ИНС) — мощный инструмент в области компьютерного зрения, особенно в задачах классификации изображений. Эта область применения была одной из первых, для которой ИНС были разработаны. Например, перцептрон Розенблатта [1], созданный в 1957 году, является одним из самых ранних примеров ИНС, способной классифицировать изображения.

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

Однако выбор оптимальной архитектуры СНС может быть сложной задачей. Необходимо найти баланс между высокой точностью классификации и эффективным использованием ресурсов. Это включает в себя настройку глубины сети, размера фильтров и других параметров. В 2019 году команда исследователей из Google AI представила решение этой проблемы. Они разработали серию архитектур моделей под названием EfficientNet [3]. Эти модели отличаются высокой степенью эффективности и легко настраиваются. Они позволяют классифицировать изображения с высокой точностью, при этом потребляя минимальное количество ресурсов. EfficientNet стало значительным шагом вперед в развитии ИНС для классификации изображений и продолжает быть актуальным до сих пор.

Особенности EfficientNet

Архитектура EfficientNet представлена набором готовых к использованию моделей. Выбор зависит от требуемой точности, доступных ресурсов для обучения и разрешения входных изображений. Модели маркируются от B0 (самая простая) до B7 (самая «сильная»).

Структурная схема модели EfficientNetB0:

Сводная таблица входных разрешений для моделей EfficientNet:

Имя модели

Входное разрешение

EfficientNetB0

224х224

EfficientNetB1

240х240

EfficientNetB2

260х260

EfficientNetB3

300х300

EfficientNetB4

380х380

EfficientNetB5

456х456

EfficientNetB6

528х528

EfficientNetB7

600х600

Из таблицы видно, что модели EfficientNet принимают на вход изображения с разрешением, которое кратно 8

Вот сравнительный график зависимости точности разных моделей от количества параметров [3]:

Кроме этого, представленные модели позволяют использовать готовые веса, которые берутся из предобученных моделей, основанных на базе данных ImageNet [4]. Это позволяет значительно ускорить обучение итоговой модели, так как обучение с нуля требует значительно больше ресурсов.

Рассмотрим на примере EfficientNetB0 основные параметры, которые доступны для настройки [5]:

keras.applications.EfficientNetB0(     include_top=True,     weights="imagenet",     input_tensor=None,     input_shape=None,     pooling=None,     classes=1000,     classifier_activation="softmax",     **kwargs )

include_top — указывает, следует ли использовать готовый или пользовательский выходной слой. По умолчанию задано значение True.

Weights — может быть одним из следующих вариантов: None (инициализация весов случайными значениями), "imagenet" (готовые веса на основе ImageNet), или путь к файлу весов для загрузки. По умолчанию используется "imagenet".

input_tensor — Необязательный параметр, который позволяет использовать тензор Keras (т. е. вывод layers.Input()) в качестве входных данных для модели. 

input_shape — необязательный параметр, который указывает формат входного изображения. Указывается только в том случае, если include_top имеет значение False. Должен представлять собой кортеж из трёх чисел (img_shape = (height, width, channels)).

pooling — необязательный параметр, который позволяет настроить Pooling-слои модели. Нужно указывать, когда include_top имеет значение False. По умолчанию None. Есть три доступных значения для аргумента:

  • None означает, что выходные данные модели будут выходными данными 4D-тензора последнего свёрточного слоя.

  • avg означает, что к выходным данным последнего свёрточного слоя будет применён Pooling-слой c функцией усреднения, и, таким образом, выходные данные модели будут представлять собой двумерный тензор.

  • max означает, что будет применен Pooling-слой с функцией максимума.

classes — необязательный параметр, который указывает количество классов для классификации изображений. Указывается только в том случае, если include_top равен True и аргумент weights не указан. 1000 — это количество классов ImageNet. По умолчанию 1000.

classifier_activation — активационная функция выходного слоя. Принимает в качестве аргумента строку или вызываемый объект. Игнорируется, если include_top=True. По умолчанию выбрана функция "softmax". При загрузке предварительно подготовленных весов classifier_activation может быть только "None" или "softmax".

Вызов функции keras.applications.EfficientNetB0() возвращает модели объект, который можно использовать для создания итоговой пользовательской модели.

Пример использования EfficientNet

В одной из прошлых статей мы разбирались в вопросе установки и настройки Tensorflow [5]. Если Tensorflow у вас ещё не настроен, то предлагаю сначала этим заняться. А теперь вернёмся к EfficientNet. 

В качестве наглядного примера давайте классифицируем изображения кошек и собак. Обучающую выборку можно взять с сайта Microsoft [7].

В общей директории с вашим скриптом создаём папку «PetImages» и выгружаем туда содержимое архива (папки «Cat» и «Dog»). Обязательно удалите посторонние файлы и изображения с нулевым размером. Неправильные файлы могут нарушить обучение. А вот и сам основной скрипт:

Подключаем необходимые библиотеки:

import tensorflow as tf from tensorflow.keras.layers import Dense, Dropout from tensorflow.keras.optimizers import Adamax from tensorflow.keras import regularizers from tensorflow.keras.preprocessing.image import ImageDataGenerator from tensorflow.keras.models import Model import numpy as np import pandas as pd from sklearn.model_selection import train_test_split import matplotlib.pyplot as plt import os import seaborn as sns import logging # В utils мы реализуем дополнительные функции для визуализации процессора обучения import utils # Настраиваем логирование logging.getLogger("tensorflow").setLevel(logging.ERROR) # Настраиваем стиль графиков sns.set_style('darkgrid') # Проверяем подходит ли наша GPU для tensorflow gpus = tf.config.experimental.list_physical_devices('GPU') if gpus:   try:     for gpu in gpus:       tf.config.experimental.set_memory_growth(gpu, True)     logical_gpus = tf.config.list_logical_devices('GPU')     print(len(gpus), "Physical GPUs,", len(logical_gpus), "Logical GPUs")   except RuntimeError as e:     print(e)
Функции
# Рисует график обучения def tr_plot(tr_data, start_epoch):     #Plot the training and validation data     tacc = tr_data.history['accuracy']     tloss = tr_data.history['loss']     vacc = tr_data.history['val_accuracy']     vloss = tr_data.history['val_loss']     Epoch_count = len(tacc) + start_epoch     Epochs = []     for i in range (start_epoch, Epoch_count):         Epochs.append(i + 1)     index_loss  = np.argmin(vloss)#  this is the epoch with the lowest validation loss     val_lowest  = vloss[index_loss]     index_acc   = np.argmax(vacc)     acc_highest = vacc[index_acc]     plt.style.use('fivethirtyeight')     sc_label = 'Лучшая эпоха= '+ str(index_loss + 1 + start_epoch)     vc_label = 'Лучшая эпоха= '+ str(index_acc  + 1 + start_epoch)     fig,axes=plt.subplots(nrows=1, ncols = 2, figsize = (20,8))     axes[0].plot (Epochs,tloss, 'r', label = 'Потери при обучении')     axes[0].plot (Epochs,vloss,'g',label='Потери при валидации' )     axes[0].scatter (index_loss + 1 + start_epoch,val_lowest, s = 150, c = 'blue', label = sc_label)     axes[0].set_title('Потери при валидации и обучении')     axes[0].set_xlabel('Эпохи')     axes[0].set_ylabel('Потери')     axes[0].legend()     axes[1].plot (Epochs,tacc,'r', label = 'Точность при обучении')     axes[1].plot (Epochs,vacc,'g', label = 'Точность при валидации')     axes[1].scatter(index_acc + 1 + start_epoch, acc_highest, s = 150, c = 'blue', label = vc_label)     axes[1].set_title  ('Точность при валидации и обучении')     axes[1].set_xlabel ('Эпохи')     axes[1].set_ylabel ('Точность')     axes[1].legend()     plt.tight_layout     #plt.style.use('fivethirtyeight')     plt.show() # Создаем data_frame, в котором название папки является меткой класса изображений (коты и собаки в нашем случае) # Каждому элементу data_frame будет присвоена метка (DX) и название файла (image) # Функция возвращает объект frame def load_data_frame(sdir):     classlist = os.listdir(sdir)     filepaths = []     labels = []     for klass in classlist:         classpath=os.path.join(sdir, klass)         flist=os.listdir(classpath)         for f in flist:             fpath = os.path.join(classpath, f)             filepaths.append( fpath.replace('\\', '/') )             labels.append(klass)      Fseries=pd.Series( filepaths, name = 'image' )     Lseries=pd.Series(labels, name = 'dx')     return pd.concat([Fseries, Lseries], axis=1)

Подготавливаем обучающие данные для модели:

# Размер картинки для подачи в модели height   = 224 width    = 224 channels = 3  # Размер пачки для обучения batch_size = 20 # Размер пачки для валидации test_batch_size = 50  # Инициализаци рандом: None - всегда рандом, Число - повторяемый рандом my_random = None  # Загружаем сгенерированные картинки (картинка должны лежать по папкам с названием их DX) df2 = load_data_frame ("./PetImages/")  # Разделяем выборку на обучающую, тестовую и валидационную (случайным образом) train_df, test_df = train_test_split (df2, train_size= .9, shuffle = True, random_state = my_random) valid_df, test_df = train_test_split (test_df, train_size= .5, shuffle = True, random_state = my_random)  # Задаем параметры входящей картинки img_shape = (height, width, channels) img_size  = (height, width) length    = len(test_df)  # выводим найденное число n test_steps = int(length/test_batch_size) print ( 'test batch size: ' ,test_batch_size, '  test steps: ', test_steps)  # C помощью ImageDataGenerator () можно аугментировать изображения прямо во время обучения, но аугментация — это отдельная тема trgen = ImageDataGenerator()  # Генератор для тестовой выборки tvgen = ImageDataGenerator()  # Выборка для обучения модели train_gen = trgen.flow_from_dataframe ( train_df, directory = None, x_col = "image", y_col = "dx", target_size = img_size, class_mode = 'categorical',                                     color_mode='rgb', shuffle=True, batch_size=batch_size)  # Выборка для тестирования сети после обучения test_gen = tvgen.flow_from_dataframe ( test_df, directory = None, x_col= "image", y_col = "dx", target_size = img_size, class_mode = 'categorical',                                     color_mode='rgb', shuffle=False, batch_size=test_batch_size) # Выборка для тестирования сети во время обучения valid_gen = tvgen.flow_from_dataframe ( valid_df, directory = None, x_col="image", y_col = "dx", target_size = img_size, class_mode = 'categorical',                                     color_mode='rgb', shuffle = True, batch_size = batch_size)

Создаём архитектуру модели:

# Получаем метки классов classes     = list (train_gen.class_indices.keys()) class_count = len(classes) train_steps = np.ceil(len(train_gen.labels)/batch_size) # Задаем имя модели model_name = 'EfficientNetB0' # Генерируем экземпляр модели EfficientNetB0 base_model = tf.keras.applications.EfficientNetB0(include_top = False, weights = "imagenet", input_shape = img_shape, pooling = 'max') # Создаем выходной слой x = base_model.output x = tf.keras.layers.BatchNormalization(axis = -1, momentum = 0.99, epsilon = 0.001 )(x) x = Dense(256, kernel_regularizer = regularizers.l2(l = 0.016), activity_regularizer = regularizers.l1(0.006),                 bias_regularizer = regularizers.l1(0.006), activation = 'relu')(x) x = Dropout(rate = .45, seed = my_random)(x) #Создаем выходной полносвязный слой и присоединяем его к предыдущим слоям (количество нейронов совпадает с количеством классов output = Dense(class_count, activation = 'softmax')(x) # Собираем модель вместе model = Model(inputs = base_model.input, outputs = output) # Компилируем модель  model.compile(Adamax(lr = .001), loss = 'categorical_crossentropy', metrics = ['accuracy'])

Обучаем модель:

# Задаем параметры обучения epochs        = 15 # Количество эпох patience      = 1 # количество эпох, в течение которых необходимо отрегулировать lr, если отслеживаемое значение не улучшится stop_patience = 6 # количество эпох ожидания перед остановкой обучения, если отслеживаемое значение не улучшится threshold     = .9  factor        = .5  dwell         = True # если True и отслеживаемая метрика не улучшаются по сравнению с текущей эпохой, возвращают веса модели к весам предыдущей эпохи. freeze        = False #  ask_epoch     = 10 # количество эпох, которые нужно выполнить, прежде чем спросить, хотите ли вы остановить обучение batches       = train_steps  # utils.LRA реализует вывод информации прямо в процессе обучения #  Об этом стоит рассказать подробнее, но это тема для отдельной статьи callbacks = [utils.LRA(model = model,                        base_model = base_model,                        patience=patience,                        stop_patience = stop_patience,                        threshold = threshold,                        factor = factor,                        dwell = dwell,                        batches = batches,                        initial_epoch = 0,                        epochs = epochs,                        ask_epoch = ask_epoch )] # Запускаем обучение модели и сохраняем историю обучения history = model.fit (x = train_gen,  epochs = epochs, verbose = 0, callbacks = callbacks,  validation_data = valid_gen, validation_steps = None,  shuffle = False,  initial_epoch = 0)  # Рисуем график обучения и выводим tr_plot(history,0) # Проверяем точность модели на тестовой выборке и выводим результат тестирования save_dir = './' subject = 'Cat and Dog'  acc = model.evaluate( test_gen, batch_size = test_batch_size, verbose = 1, steps=test_steps, return_dict = False)[1]*100 msg = f'accuracy on the test set is {acc:5.2f} %' utils.print_in_color(msg, (0,255,0),(55,65,80))  # Сохраняем модель в файл, его потом можно загрузить и использовать без обучения для классификации изображений save_id   = str (model_name +  '-' + subject +'-'+ str(acc)[:str(acc).rfind('.')+3] + '.h5') save_loc  = os.path.join(save_dir, save_id) model.save(save_loc) generator = train_gen scale     = 1 result    = utils.saver(save_dir, model, model_name, subject, acc, img_size, scale,  generator)
Содержимое файла Utils.py
# -*- coding: utf-8 -*- """ Created on Tue Dec 20 02:45:02 2022  @author: Heigrast """ import tensorflow as tf from tensorflow import keras  import numpy as np import pandas as pd  import matplotlib.pyplot as plt import os import seaborn as sns from sklearn.metrics import confusion_matrix, classification_report  import time   # Выводит информацию о процессе обучения def print_info( test_gen, preds, print_code, save_dir, subject ):     class_dict = test_gen.class_indices     labels = test_gen.labels     file_names = test_gen.filenames     error_list = []     true_class = []     pred_class = []     prob_list  = []     new_dict   = {}     error_indices = []     y_pred = []     for key,value in class_dict.items():         new_dict[value] = key             # dictionary {integer of class number: string of class name}     # store new_dict as a text fine in the save_dir     classes = list(new_dict.values())     # list of string of class names     errors = 0     for i, p in enumerate(preds):         pred_index = np.argmax(p)         true_index = labels[i]  # labels are integer values         if pred_index != true_index: # a misclassification has occurred             error_list.append(file_names[i])             true_class.append(new_dict[true_index])             pred_class.append(new_dict[pred_index])             prob_list.append(p[pred_index])             error_indices.append(true_index)             errors = errors + 1         y_pred.append(pred_index)     if print_code != 0:         if errors > 0:             if print_code>errors:                 r = errors             else:                 r = print_code             msg ='{0:^28s}{1:^28s}{2:^28s}{3:^16s}'.format('Filename', 'Predicted Class' , 'True Class', 'Probability')             print_in_color(msg, (0,255,0),(55,65,80))             for i in range(r):                 split1 = os.path.split(error_list[i])                 split2 = os.path.split(split1[0])                 fname  = split2[1] + '/' + split1[1]                 msg    = '{0:^28s}{1:^28s}{2:^28s}{3:4s}{4:^6.4f}'.format(fname, pred_class[i],true_class[i], ' ', prob_list[i])                 print_in_color(msg, (255,255,255), (55,65,60))                 #print(error_list[i]  , pred_class[i], true_class[i], prob_list[i])         else:             msg = 'With accuracy of 100 % there are no errors to print'             print_in_color(msg, (0,255,0),(55,65,80))     if errors > 0:         plot_bar   = []         plot_class = []         for  key, value in new_dict.items():             count = error_indices.count(key)             if count != 0:                 plot_bar.append(count) # list containg how many times a class c had an error                 plot_class.append(value)   # stores the class         fig=plt.figure()         fig.set_figheight(len(plot_class)/3)         fig.set_figwidth(10)         plt.style.use('fivethirtyeight')         for i in range(0, len(plot_class)):             c = plot_class[i]             x = plot_bar[i]             plt.barh(c, x, )             plt.title( ' Errors by Class on Test Set')     y_true = np.array(labels)     y_pred = np.array(y_pred)     if len(classes)<= 30:         # create a confusion matrix         cm = confusion_matrix(y_true, y_pred )         length = len(classes)         if length < 8:             fig_width  = 8             fig_height = 8         else:             fig_width = int(length * .5)             fig_height = int(length * .5)         plt.figure(figsize = (fig_width, fig_height))         sns.heatmap(cm, annot = True, vmin = 0, fmt = 'g', cmap = 'Blues', cbar = False)         plt.xticks(np.arange(length) + .5, classes, rotation = 90)         plt.yticks(np.arange(length) + .5, classes, rotation =0)         plt.xlabel("Predicted")         plt.ylabel("Actual")         plt.title("Confusion Matrix")         plt.show()     clr = classification_report(y_true, y_pred, target_names=classes)     print("Classification Report:\n----------------------\n", clr)  # Сохраняет результаты обучения в файл def saver(save_path, model, model_name, subject, accuracy, img_size, scalar, generator):     # first save the model     save_id = str (model_name +  '-' + subject +'-'+ str(accuracy)[:str(accuracy).rfind('.')+3] + '.h5')     model_save_loc = os.path.join(save_path, save_id)     model.save(model_save_loc)     print_in_color ('model was saved as ' + model_save_loc, (0,255,0),(55,65,80))     # now create the class_df and convert to csv file     class_dict = generator.class_indices     height = []     width = []     scale = []     for i in range(len(class_dict)):         height.append(img_size[0])         width.append(img_size[1])         scale.append(scalar)     Index_series  = pd.Series(list(class_dict.values()), name='class_index')     Class_series  = pd.Series(list(class_dict.keys()), name='class')     Height_series = pd.Series(height, name='height')     Width_series  = pd.Series(width, name='width')     Scale_series  = pd.Series(scale, name='scale by')      class_df = pd.concat([Index_series, Class_series, Height_series, Width_series, Scale_series], axis=1)     csv_name ='class_dict.csv'     csv_save_loc = os.path.join(save_path, csv_name)     class_df.to_csv(csv_save_loc, index=False)     print_in_color ('class csv file was saved as ' + csv_save_loc, (0,255,0),(55,65,80))      return model_save_loc, csv_save_loc  def print_in_color(txt_msg, fore_tupple, back_tupple,):     #prints the text_msg in the foreground color specified by fore_tupple with the background specified by back_tupple     #text_msg is the text, fore_tupple is foreground color tupple (r,g,b), back_tupple is background tupple (r,g,b)     rf,gf,bf = fore_tupple     rb,gb,bb = back_tupple     msg = '{0}' + txt_msg     mat = '\33[38;2;' + str(rf) +';' + str(gf) + ';' + str(bf) + ';48;2;' + str(rb) + ';' +str(gb) + ';' + str(bb) +'m'     print(msg.format(mat), flush = True)     print('\33[0m', flush = True) # returns default print color to back to black     return  # Класс колбеков, он дает возможность выводить доп информацию во время обучения через переопределение методов родителя class LRA(keras.callbacks.Callback):     def __init__(self,model, base_model, patience, stop_patience, threshold, factor, dwell, batches, initial_epoch, epochs, ask_epoch):         super(LRA, self).__init__()         self.model=model         self.base_model = base_model         self.patience = patience # specifies how many epochs without improvement before learning rate is adjusted         self.stop_patience = stop_patience # specifies how many times to adjust lr without improvement to stop training         self.threshold = threshold # specifies training accuracy threshold when lr will be adjusted based on validation loss         self.factor = factor # factor by which to reduce the learning rate         self.dwell = dwell         self.batches = batches # number of training batch to run per epoch         self.initial_epoch = initial_epoch         self.epochs = epochs         self.ask_epoch = ask_epoch         self.ask_epoch_initial = ask_epoch # save this value to restore if restarting training         # callback variables         self.count = 0 # how many times lr has been reduced without improvement         self.stop_count = 0         self.best_epoch = 1   # epoch with the lowest loss         self.initial_lr = float(tf.keras.backend.get_value(model.optimizer.lr)) # get the initial learning rate and save it         self.highest_tracc = 0.0 # set highest training accuracy to 0 initially         self.lowest_vloss = np.inf # set lowest validation loss to infinity initially         self.best_weights = self.model.get_weights() # set best weights to model's initial weights         self.initial_weights = self.model.get_weights()   # save initial weights if they have to get restored      def on_train_begin(self, logs = None):         if self.base_model != None:             status=self.base_model.trainable             if status:                 msg = 'initializing callback starting train with base_model trainable'             else:                 msg = 'initializing callback starting training with base_model not trainable'         else:             msg = 'initialing callback and starting training'         print_in_color (msg, (244, 252, 3), (55,65,80))         msg = '{0:^8s}{1:^10s}{2:^9s}{3:^9s}{4:^9s}{5:^9s}{6:^9s}{7:^10s}{8:^8s}'.format('Epoch', 'Loss', 'Accuracy',                                                                                               'V_loss','V_acc', 'LR', 'Next LR', 'Monitor', 'Duration')         print_in_color(msg, (244,252,3), (55,65,80))         self.start_time = time.time()      def on_train_end(self, logs = None):         stop_time = time.time()         tr_duration = stop_time- self.start_time         hours = tr_duration // 3600         minutes = (tr_duration - (hours * 3600)) // 60         seconds = tr_duration - ((hours * 3600) + (minutes * 60))          self.model.set_weights(self.best_weights) # set the weights of the model to the best weights         msg=f'Training is completed - model is set with weights from epoch {self.best_epoch} '         print_in_color(msg, (0,255,0), (55,65,80))         msg = f'training elapsed time was {str(hours)} hours, {minutes:4.1f} minutes, {seconds:4.2f} seconds)'         print_in_color(msg, (0,255,0), (55,65,80))      def on_train_batch_end(self, batch, logs=None):         acc = logs.get('accuracy')* 100  # get training accuracy         loss = logs.get('loss')         msg = '{0:20s}processing batch {1:4s} of {2:5s} accuracy= {3:8.3f}  loss: {4:8.5f}'.format(' ', str(batch), str(self.batches), acc, loss)         print(msg, '\r', end='') # prints over on the same line to show running batch count      def on_epoch_begin(self,epoch, logs = None):         self.now = time.time()      def on_epoch_end(self, epoch, logs = None):  # method runs on the end of each epoch         later = time.time()         duration = later-self.now         lr=float(tf.keras.backend.get_value(self.model.optimizer.lr)) # get the current learning rate         current_lr = lr         v_loss = logs.get('val_loss')  # get the validation loss for this epoch         acc = logs.get('accuracy')  # get training accuracy         v_acc = logs.get('val_accuracy')         loss = logs.get('loss')         # if training accuracy is below threshold adjust lr based on training accuracy         if acc < self.threshold:             monitor = 'accuracy'             if acc > self.highest_tracc: # training accuracy improved in the epoch                 self.highest_tracc = acc # set new highest training accuracy                 self.best_weights = self.model.get_weights() # traing accuracy improved so save the weights                 self.count = 0 # set count to 0 since training accuracy improved                 self.stop_count = 0 # set stop counter to 0                 if v_loss < self.lowest_vloss:                     self.lowest_vloss = v_loss                 color = (0,255,0)                 self.best_epoch=epoch + 1  # set the value of best epoch for this epoch             else:                 # training accuracy did not improve check if this has happened for patience number of epochs                 # if so adjust learning rate                 if self.count >= self.patience -1: # lr should be adjusted                     color = (245, 170, 66)                     lr = lr * self.factor # adjust the learning by factor                     tf.keras.backend.set_value(self.model.optimizer.lr, lr) # set the learning rate in the optimizer                     self.count = 0 # reset the count to 0                     self.stop_count = self.stop_count + 1 # count the number of consecutive lr adjustments                     self.count = 0 # reset counter                     if self.dwell:                         self.model.set_weights(self.best_weights) # return to better point in N space                     else:                         if v_loss < self.lowest_vloss:                             self.lowest_vloss = v_loss                 else:                     self.count=self.count +1 # increment patience counter         else: # training accuracy is above threshold so adjust learning rate based on validation loss             monitor = 'val_loss'             if v_loss < self.lowest_vloss: # check if the validation loss improved                 self.lowest_vloss = v_loss # replace lowest validation loss with new validation loss                 self.best_weights = self.model.get_weights() # validation loss improved so save the weights                 self.count = 0 # reset count since validation loss improved                 self.stop_count = 0                 color=(0,255,0)                 self.best_epoch = epoch + 1 # set the value of the best epoch to this epoch             else: # validation loss did not improve                 if self.count >= self.patience-1: # need to adjust lr                     color = (245, 170, 66)                     lr = lr * self.factor # adjust the learning rate                     self.stop_count = self.stop_count + 1 # increment stop counter because lr was adjusted                     self.count = 0 # reset counter                     tf.keras.backend.set_value(self.model.optimizer.lr, lr) # set the learning rate in the optimizer                     if self.dwell:                         self.model.set_weights(self.best_weights) # return to better point in N space                 else:                     self.count = self.count + 1 # increment the patience counter                 if acc > self.highest_tracc:                     self.highest_tracc = acc         msg=f'{str(epoch+1):^3s}/{str(self.epochs):4s} {loss:^9.3f}{acc*100:^9.3f}{v_loss:^9.5f}{v_acc*100:^9.3f}{current_lr:^9.5f}{lr:^9.5f}{monitor:^11s}{duration:^8.2f}'         print_in_color (msg,color, (55,65,80))          with open('epoch statistics.txt', 'a') as epoch_stats:             epoch_stats.write(msg + '\n')          if self.stop_count > self.stop_patience - 1: # check if learning rate has been adjusted stop_count times with no improvement             msg = f' training has been halted at epoch {epoch + 1} after {self.stop_patience} adjustments of learning rate with no improvement'             print_in_color(msg, (0,255,255), (55,65,80))             self.model.stop_training = True # stop training         else:             if self.ask_epoch != None:                 if epoch + 1 >= self.ask_epoch:                     msg ='enter H to halt ,F to fine tune model, or an integer for number of epochs to run then ask again'                     print_in_color(msg, (0,255,255), (55,65,80))                     ans=input('')                     if ans =='H' or ans=='h':                         msg =f'training has been halted at epoch {epoch + 1} due to user input'                         print_in_color(msg, (0,255,255), (55,65,80))                         self.model.stop_training = True # stop training                     elif ans == 'F' or ans=='f':                         msg ='setting base_model as trainable for fine tuning of model'                         self.base_model.trainable = True                         print_in_color(msg, (0, 255,255), (55,65,80))                         msg='{0:^8s}{1:^10s}{2:^9s}{3:^9s}{4:^9s}{5:^9s}{6:^9s}{7:^10s}{8:^8s}'.format('Epoch', 'Loss', 'Accuracy',                                                                                               'V_loss','V_acc', 'LR', 'Next LR', 'Monitor', 'Duration')                         print_in_color(msg, (244,252,3), (55,65,80))                         self.count = 0                         self.stop_count = 0                         self.ask_epoch = epoch + 1 + self.ask_epoch_initial                      else:                         ans=int(ans)                         self.ask_epoch += ans                         msg = f' training will continue until epoch ' + str(self.ask_epoch)                         print_in_color(msg, (0, 255,255), (55,65,80))                         msg = '{0:^8s}{1:^10s}{2:^9s}{3:^9s}{4:^9s}{5:^9s}{6:^9s}{7:^10s}{8:^8s}'.format('Epoch', 'Loss', 'Accuracy',                                                                                               'V_loss','V_acc', 'LR', 'Next LR', 'Monitor', 'Duration')                         print_in_color(msg, (244,252,3), (55,65,80))

Если вы правильно настроили среду, то после запуска скрипта у вас должна появиться в консоли следующая картина:

В консоль выводится информация об обучении каждой текущей пачки изображений. А после завершения всего обучения система строит график:

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

# Размер картинки для подачи в модели height   = 224 width    = 224 channels = 3

Другие примеры использования EfficientNet

Кошечки и собачки — это, конечно, хорошо, но для демонстрации реальных преимуществ архитектуры EfficientNet давайте рассмотрим другой пример использования архитектуры:

Model_name = 'EfficientNetB3' base_model = tf.keras.applications.EfficientNetB3(include_top = False, weights = "imagenet", input_shape = img_shape, pooling = 'max') x = base_model.output x = tf.keras.layers.BatchNormalization(axis = -1, momentum = 0.99, epsilon = 0.001 )(x) x = Dense(256, kernel_regularizer = regularizers.l2(l = 0.016), activity_regularizer = regularizers.l1(0.006), bias_regularizer = regularizers.l1(0.006), activation = 'relu')(x) x = Dropout(rate = .45, seed = my_random)(x) output = Dense(class_count, activation = 'softmax')(x) model = Model(inputs = base_model.input, outputs = output) model.compile(Adamax(lr = .001), loss = 'categorical_crossentropy', metrics = ['accuracy'])

Эта модель отличается только применением EfficientNetB3, в остальном всё то же самое.

А вот пример архитектуры свёрточной сети без EfficientNet. Эта модель показала точность всего 77 % при решении задачи классификации кожных образований [8]:

# Set the CNN model  # my CNN architecture is In -> [[Conv2D->relu]*2 -> MaxPool2D -> Dropout]*2 -> Flatten -> Dense -> Dropout -> Out input_shape = (75, 100, 3) num_classes = 7  model = Sequential() model.add(Conv2D(32, kernel_size=(3, 3),activation='relu',padding = 'Same',input_shape=input_shape)) model.add(Conv2D(32,kernel_size=(3, 3), activation='relu',padding = 'Same',)) model.add(MaxPool2D(pool_size = (2, 2))) model.add(Dropout(0.25)) model.add(Conv2D(64, (3, 3), activation='relu',padding = 'Same')) model.add(Conv2D(64, (3, 3), activation='relu',padding = 'Same')) model.add(MaxPool2D(pool_size=(2, 2))) model.add(Dropout(0.40)) model.add(Flatten()) model.add(Dense(128, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(num_classes, activation='softmax'))

Приведенный выше скрипт генерирует следующую модель:

Вот её график обучения:

C:\Users\Heigrast\Downloads\__results___67_0.png

А вот график обучения нашей модели, основанной на EfficientNetB3:

D:\Main\Python\ML skin2\Модели\Модель 2\Figure 2022-12-12 123200.png

Результаты применения EfficientNet налицо. Мы получили прирост точности классификации примерно на 10 %. А если провести больше экспериментов с настройками, то можно получить ещё большую точность.

И вот ещё один пример эффективности использования моделей EfficientNet [9]. Он тоже наглядно показывает преимущества архитектур.

Советы по использованию EfficientNet

Можно выделить несколько советов по использованию моделей:

  • Слои BatchNormalization нужно держать замороженными (не обучать). Если их ещё и сделать обучаемыми, то первая эпоха после разморозки значительно снизит точность [10].

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

  • Более крупные варианты EfficientNet (B3+) не гарантируют повышения производительности, особенно в задачах с меньшим количеством данных или классов. В таком случае, чем больший вариант EfficientNet выбран, тем сложнее настроить гиперпараметры.

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

Полезные ссылки

  1. https://ru.wikipedia.org/wiki/%D0%9F%D0%B5%D1%80%D1%86%D0%B5%D0%BF%D1%82%D1%80%D0%BE%D0%BD

  2. https://ru.wikipedia.org/wiki/%D0%A1%D0%B2%D1%91%D1%80%D1%82%D0%BE%D1%87%D0%BD%D0%B0%D1%8F_%D0%BD%D0%B5%D0%B9%D1%80%D0%BE%D0%BD%D0%BD%D0%B0%D1%8F_%D1%81%D0%B5%D1%82%D1%8C

  3. https://arxiv.org/abs/1905.11946

  4. https://www.image-net.org/

  5. https://keras.io/api/applications/efficientnet/#efficientnetb0-function

  6. https://habr.com/ru/companies/sberbank/articles/819859/

  7. https://www.microsoft.com/en-us/download/details.aspx?id=54765

  8. https://www.kaggle.com/code/sid321axn/step-wise-approach-cnn-model-77-0344-accuracy

  9. https://ieeexplore.ieee.org/document/9320487

  10. https://keras.io/guides/transfer_learning/


ссылка на оригинал статьи https://habr.com/ru/articles/828842/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *