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

Обучение достойной модели глубокого обучения для обнаружения объектов требует больших усилий, которые усугубляются при развертывании и внедрении модели в веб-приложение для конечных пользователей. В этом руководстве мы намерены решить эту, казалось бы, сложную задачу, предоставив практический пример того, как разработать точную модель глубокого обучения с использованием Python и фреймворка Tensorflow, а также создать работающее веб-приложение, которое поддерживает обнаружение объектов на лету с помощью R Блестящий каркас. К концу руководства вы сможете создать полномасштабное приложение для распознавания объектов для таких продуктовых товаров, как это:

Обучение модели глубокого обучения обнаружению объектов

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

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

# Configuration parameters
CUSTOM_MODEL_NAME = 'my_ssd_mobnet' 

# SSD has good tradeoff between speed and accuracy; can switch to other pretrained model
PRETRAINED_MODEL_NAME = 'ssd_mobilenet_v2_fpnlite_320x320_coco17_tpu-8'
PRETRAINED_MODEL_URL = 'http://download.tensorflow.org/models/object_detection/tf2/20200711/ssd_mobilenet_v2_fpnlite_320x320_coco17_tpu-8.tar.gz'

# TF official script to encode training data to tf record format
TF_RECORD_SCRIPT_NAME = 'generate_tfrecord.py'

# Mapping dictionary between label and integer id
LABEL_MAP_NAME = 'label_map.pbtxt'

# Define a list of folder paths to be created (if needed) and used later
paths = {
    'WORKSPACE_PATH': os.path.join('Tensorflow', 'workspace'),
    'SCRIPTS_PATH': os.path.join('Tensorflow','scripts'),
    'APIMODEL_PATH': os.path.join('Tensorflow','models'),
    # bounding box annotation
    'ANNOTATION_PATH': os.path.join('Tensorflow', 'workspace','annotations'),
    'IMAGE_PATH': os.path.join('Tensorflow', 'workspace','images'),
    'MODEL_PATH': os.path.join('Tensorflow', 'workspace','models'),
    'PRETRAINED_MODEL_PATH': os.path.join('Tensorflow', 'workspace','pre-trained-models'),
    'CHECKPOINT_PATH': os.path.join('Tensorflow', 'workspace','models',CUSTOM_MODEL_NAME), 
    'OUTPUT_PATH': os.path.join('Tensorflow', 'workspace','models',CUSTOM_MODEL_NAME, 'export'), 
    'PROTOC_PATH':os.path.join('Tensorflow','protoc')
}

files = {
    'PIPELINE_CONFIG':os.path.join('Tensorflow', 'workspace','models', CUSTOM_MODEL_NAME, 'pipeline.config'),
    'TF_RECORD_SCRIPT': os.path.join(paths['SCRIPTS_PATH'], TF_RECORD_SCRIPT_NAME), 
    'LABELMAP': os.path.join(paths['ANNOTATION_PATH'], LABEL_MAP_NAME)
}
# Download TF model training utility scripts from TF model zoo
if not os.path.exists(os.path.join(paths['APIMODEL_PATH'], 'research', 'objection_detection')):
    !git clone https://github.com/tensorflow/models {paths['APIMODEL_PATH']}
# Install TF object detection library
if os.name=='posix':  
    !apt-get install protobuf-compiler
    !cd Tensorflow/models/research && protoc object_detection/protos/*.proto --python_out=. && cp object_detection/packages/tf2/setup.py . && python -m pip install .

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

# Download training images
import shutil

if os.path.exists('object_detection_using_tensorflow'):
    shutil.rmtree('object_detection_using_tensorflow')

!git clone https://github.com/jackliu333/object_detection_using_tensorflow.git
# Create label map
labels = [{'name':'Apple', 'id':1},
          {'name':'Avocado', 'id':2},
          {'name':'Banana', 'id':3},
          {'name':'Cabbage', 'id':4},
          {'name':'Carrot', 'id':5},
          {'name':'Potato', 'id':6}]

with open(files['LABELMAP'], 'w') as f:
    for label in labels:
        f.write('item { \n')
        f.write('\tname:\'{}\'\n'.format(label['name']))
        f.write('\tid:{}\n'.format(label['id']))
        f.write('}\n')

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

# Split into train test folders
tmp_folders = ['train', 'test']

for i in tmp_folders:
    if os.path.exists(os.path.join(paths['IMAGE_PATH'], i)):
        shutil.rmtree(os.path.join(paths['IMAGE_PATH'], i))
        !mkdir -p {os.path.join(paths['IMAGE_PATH'], i)}
    else:
        !mkdir -p {os.path.join(paths['IMAGE_PATH'], i)}
import shutil

for i in range(len(labels)):
    # print(labels[i]['name'])
    from_path = os.path.join('object_detection_using_tensorflow','images',labels[i]['name'])
    # print(from_path)

    # get unique file names
    tmp_files = os.listdir(from_path)
    tmp_names = []
    tmp_file_types = []
    for tmp_file in tmp_files:
        tmp_name = os.path.splitext(tmp_file)[0]
        tmp_file_type = os.path.splitext(tmp_file)[1]
        tmp_names.append(tmp_name)
        tmp_file_types.append(tmp_file_type)
    tmp_names = list(set(tmp_names))
    tmp_names = [i for i in tmp_names if i != '.DS_Store']
    tmp_file_types = list(set(tmp_file_types))
    tmp_file_types = [i for i in tmp_file_types if len(i) != 0]
    # random shuffle the files
    random.shuffle(tmp_names)
    
    # training and test files
    tmp_names_train = tmp_names[0:int(len(tmp_names)*0.9)]
    tmp_names_test = [i for i in tmp_names if i not in tmp_names_train]

    # move into respective target folders
    for tmp_name in tmp_names_train:
        for tmp_file_type in tmp_file_types:
            tmp_name_full = tmp_name + tmp_file_type
            shutil.copy(os.path.join(from_path, tmp_name_full), \
                        os.path.join(paths['IMAGE_PATH'], "train"))

    for tmp_name in tmp_names_test:
        for tmp_file_type in tmp_file_types:
            tmp_name_full = tmp_name + tmp_file_type
            shutil.copy(os.path.join(from_path, tmp_name_full), \
                        os.path.join(paths['IMAGE_PATH'], "test"))

Затем полученные данные изображения преобразуются в формат TF Record для более быстрой обработки.

# Create TF Record
# download conversion script
if not os.path.exists(files['TF_RECORD_SCRIPT']):
    !git clone https://github.com/nicknochnack/GenerateTFRecord {paths['SCRIPTS_PATH']}
!python {files['TF_RECORD_SCRIPT']} -x {os.path.join(paths['IMAGE_PATH'], 'train')} -l {files['LABELMAP']} -o {os.path.join(paths['ANNOTATION_PATH'], 'train.record')} 
!python {files['TF_RECORD_SCRIPT']} -x {os.path.join(paths['IMAGE_PATH'], 'test')} -l {files['LABELMAP']} -o {os.path.join(paths['ANNOTATION_PATH'], 'test.record')}

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

# Update configuration file for transfer learning
import tensorflow as tf
from object_detection.utils import config_util
from object_detection.protos import pipeline_pb2
from google.protobuf import text_format

# Read current configuration file
pipeline_config = pipeline_pb2.TrainEvalPipelineConfig()
with tf.io.gfile.GFile(files['PIPELINE_CONFIG'], "r") as f:                                                                                                                                                                                                                     
    proto_str = f.read()                                                                                                                                                                                                                                          
    text_format.Merge(proto_str, pipeline_config)  

# Update based on new labels
pipeline_config.model.ssd.num_classes = len(labels)
pipeline_config.train_config.batch_size = 4
pipeline_config.train_config.fine_tune_checkpoint = os.path.join(paths['PRETRAINED_MODEL_PATH'], PRETRAINED_MODEL_NAME, 'checkpoint', 'ckpt-0')
pipeline_config.train_config.fine_tune_checkpoint_type = "detection"
pipeline_config.train_input_reader.label_map_path= files['LABELMAP']
pipeline_config.train_input_reader.tf_record_input_reader.input_path[:] = [os.path.join(paths['ANNOTATION_PATH'], 'train.record')]
pipeline_config.eval_input_reader[0].label_map_path = files['LABELMAP']
pipeline_config.eval_input_reader[0].tf_record_input_reader.input_path[:] = [os.path.join(paths['ANNOTATION_PATH'], 'test.record')]

# Write to configuration file
config_text = text_format.MessageToString(pipeline_config)                                                                                                                                                                                                        
with tf.io.gfile.GFile(files['PIPELINE_CONFIG'], "wb") as f:                                                                                                                                                                                                                     
    f.write(config_text)

Обучение модели упрощается с помощью обучающего сценария, предоставленного Tensorflow.

TRAINING_SCRIPT = os.path.join(paths['APIMODEL_PATH'], 'research', 'object_detection', 'model_main_tf2.py')
command = "python {} --model_dir={} --pipeline_config_path={} --num_train_steps=2000".format(TRAINING_SCRIPT, paths['CHECKPOINT_PATH'],files['PIPELINE_CONFIG'])
!{command}

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

# Load trained model from checkpoint
import os
import tensorflow as tf
from object_detection.utils import label_map_util
from object_detection.utils import visualization_utils as viz_utils
from object_detection.builders import model_builder
from object_detection.utils import config_util

# Load pipeline config and build a detection model
configs = config_util.get_configs_from_pipeline_file(files['PIPELINE_CONFIG'])
detection_model = model_builder.build(model_config=configs['model'], is_training=False)

# Restore checkpoint
ckpt = tf.compat.v2.train.Checkpoint(model=detection_model)
ckpt.restore(os.path.join(paths['CHECKPOINT_PATH'], 'ckpt-3')).expect_partial()

# @tf.function
def detect_fn(image):
    image, shapes = detection_model.preprocess(image)
    prediction_dict = detection_model.predict(image, shapes)
    detections = detection_model.postprocess(prediction_dict, shapes)
    return detections

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

import cv2 
from matplotlib import pyplot as plt
%matplotlib inline

# Randomly select an image to be detected
tmp_img = random.choice([file for file in os.listdir(os.path.join(paths['IMAGE_PATH'], 
                                              'test')) if file.endswith(".jpg")])
IMAGE_PATH = os.path.join(paths['IMAGE_PATH'], 'test', tmp_img)

category_index = label_map_util.create_category_index_from_labelmap(files['LABELMAP'])

img = cv2.imread(IMAGE_PATH)
image_np = np.array(img)

input_tensor = tf.convert_to_tensor(np.expand_dims(image_np, 0), dtype=tf.float32)
detections = detect_fn(input_tensor)

num_detections = int(detections.pop('num_detections'))
detections = {key: value[0, :num_detections].numpy()
              for key, value in detections.items()}
detections['num_detections'] = num_detections

# detection_classes should be ints.
detections['detection_classes'] = detections['detection_classes'].astype(np.int64)

label_id_offset = 1
image_np_with_detections = image_np.copy()

viz_utils.visualize_boxes_and_labels_on_image_array(
            image_np_with_detections,
            detections['detection_boxes'],
            detections['detection_classes']+label_id_offset,
            detections['detection_scores'],
            category_index,
            use_normalized_coordinates=True,
            max_boxes_to_draw=5,
            min_score_thresh=.5,
            agnostic_mode=False)

plt.imshow(cv2.cvtColor(image_np_with_detections, cv2.COLOR_BGR2RGB))
plt.show()

Создание приложения веб-приложения с использованием R Shiny

R Shiny — отличный инструмент для создания современных веб-приложений без глубоких знаний HTML, CSS или Javascript. Мы можем быстро развернуть приложение, выбрав конкретную структуру приложения и заполнив сценарии внешнего интерфейса (ui.R) и внутреннего интерфейса (server.R), а также необязательный глобальный файл (global.R) для обработки среды. через приложение.

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

ui <- dashboardPage(
 skin=”blue”,
 #(1) Header
 dashboardHeader(title=”Object Recognition App”,#,style=”font-size: 120%; font-weight: bold; color: white”),
 titleWidth = 250,
 tags$li(class = “dropdown”),
 dropdownMenu(
 type = “notifications”, 
 icon = icon(“question-circle”),
 badgeStatus = NULL,
 headerText = “Feedback”,
 notificationItem(“Send email to developer”, icon = icon(“file”),
 href = “[email protected]”)
 )),
 #(2) Sidebar
 dashboardSidebar(
 width=250,
 fileInput(“input_image_upload”,”Upload image”, accept = c(‘.jpg’,’.jpeg’)),
 tags$br(),
 sliderInput(“min_score_threshold”,”Confidence threshold”,0,1,0.5),
 # tags$p(“Upload the image here.”)
 selectInput(inputId = “product_type”,label = “Choose product”,
 choices = c(“Flour”,”Baby Food”),
 selected = NA),
 selectInput(inputId = “halal_status”,label = “Halal status”,
 choices = c(“H”,”NH”),
 selected = NA),
 selectInput(inputId = “weight”,label = “Choose weight”,
 choices = c(“50g”,”100g”),
 selected = NA),
 actionButton(“submit”,”Submit”,icon(“paper-plane”), 
 style=”color: #fff; background-color: #337ab7; border-color: #2e6da4")
 ),
 
 
 #(3) Body
 
 dashboardBody(
 box(
 title = “Object Recognition”, width = 12, solidHeader = TRUE, status = “primary”,
 collapsible = T, collapsed = F,
 fluidRow(
 column(6,
 h4(“Instruction:”),
 # tags$br(),
 tags$p(“1. Upload image to be classified and set confidence threshold.”),
 tags$p(“2. Check prediction results.”),
 tags$p(“3. Select specific product category.”),
 tags$p(“4. Click submit to record in the system.”)
 ),
 column(6,
 h4(“Predicted Category:”),
 tableOutput(“text”)
 )
 ),
 
 fluidRow(
 column(h4(“Image:”),imageOutput(“output_image”), width=6),
 column(h4(“Predicted Image:”),imageOutput(“output_image2”), width=6)
 )
 ),
 box(
 title = “Image Gallery”, width = 12, solidHeader = TRUE, status = “success”,
 collapsible = T, collapsed = F,
 fluidRow(
 column(3,
 h3(“All categories”),
 verbatimTextOutput(“all_cats”)
 ),
 column(3, 
 selectInput(“input_image_select”, “Select image”,c(“”,ALL_IMGS),selected = “”),
 ),
 column(6,
 column(h4(“Image:”),imageOutput(“output_image_selected”), width=6),
 )
 )
 )
 
 # box(
 # title = “Product Recording”, width = 12, solidHeader = TRUE, status = “success”,
 # collapsible = T, collapsed = T,
 # “test”
 # )
 
 ))

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

Мы также определяем несколько служебных функций, а также параметры среды в глобальном файле. Обратите внимание, что R использует библиотеку reticulate для обработки скриптов Python. В этом случае мы сначала создадим новую виртуальную среду, чтобы настроить необходимую фоновую среду для обнаружения объектов conda, а затем загрузим основные функции Python для распознавания изображений, которые будут преобразованы в виде API для взаимодействия с экосистемой R Shiny.

Заключение

В этом руководстве мы рассмотрели, как настроить предварительно обученные модели глубокого обучения в Tensorflow с помощью трансферного обучения для распознавания продуктов, а также как создать современное веб-приложение для размещения модели для конечных пользователей с помощью R Shiny. Конечный продукт представляет собой комбинацию сценариев R и Python, в которой основные функции Python, такие как обнаружение объектов, органично обернуты и представлены в виде API для R, который занимается разработкой приложений. Мы надеемся, что это руководство послужит вам хорошей отправной точкой для развертывания вашей собственной модели и обмена ею с другими эффективным и увлекательным способом.

Все вспомогательные данные и коды доступны в сопроводительном github, а также в пошаговом руководстве на YouTube ниже.