Искусственные нейронные сети (ИНС) — мощный инструмент в области компьютерного зрения, особенно в задачах классификации изображений. Эта область применения была одной из первых, для которой ИНС были разработаны. Например, перцептрон Розенблатта [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'))
Приведенный выше скрипт генерирует следующую модель:
Вот её график обучения:
А вот график обучения нашей модели, основанной на EfficientNetB3:
Результаты применения EfficientNet налицо. Мы получили прирост точности классификации примерно на 10 %. А если провести больше экспериментов с настройками, то можно получить ещё большую точность.
И вот ещё один пример эффективности использования моделей EfficientNet [9]. Он тоже наглядно показывает преимущества архитектур.
Советы по использованию EfficientNet
Можно выделить несколько советов по использованию моделей:
-
Слои BatchNormalization нужно держать замороженными (не обучать). Если их ещё и сделать обучаемыми, то первая эпоха после разморозки значительно снизит точность [10].
-
В некоторых случаях может оказаться полезным разморозить (разрешить обучать) только часть слоёв вместо того, чтобы размораживать все. Это значительно ускорит точную настройку при переходе на более крупные модели, такие как B7.
-
Более крупные варианты EfficientNet (B3+) не гарантируют повышения производительности, особенно в задачах с меньшим количеством данных или классов. В таком случае, чем больший вариант EfficientNet выбран, тем сложнее настроить гиперпараметры.
-
Меньший размер пачки может положительно сказаться на точности проверки, возможно, из-за эффективной регуляризации.
Полезные ссылки
-
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
-
https://keras.io/api/applications/efficientnet/#efficientnetb0-function
-
https://www.microsoft.com/en-us/download/details.aspx?id=54765
-
https://www.kaggle.com/code/sid321axn/step-wise-approach-cnn-model-77-0344-accuracy
ссылка на оригинал статьи https://habr.com/ru/articles/828842/
Добавить комментарий