Etiquetado del marco MMYOLO, entrenamiento, prueba de todo el proceso (suplementario)

prefacio

problema de instalacion de pycocotools

  • Primero MMYOLOdescargue el proyecto completo desde la dirección del proyecto y luego ingréselo en la ventana de comandos pip install openmim. Luego use para cd mmyoloingresar el archivo del proyecto. Tenga en cuenta que esta mmyoloes una ruta. Por ejemplo, si su archivo de proyecto está en la unidad D, entonces debe escribirlo cd D:\mmyolo. Después de ingresar a la carpeta del proyecto, ingresemim install -r requirements/mminstall.txt
  • Durante el proceso de instalación, solo informé un error durante la instalación de una biblioteca, que es pycocotoolsla biblioteca Microsoft Visual C++ 14.0 or greater is required. El autor de la biblioteca Github enfatizó que el mensaje de error no estaba instalado Visual C++ 2015 build tools. En el documento, el autor proporcionó una dirección de descarga , pero una siempre se informaba de un error durante el proceso de descarga. Así que encontré la versión sin conexión, enlace de descarga
  • Una vez completada la instalación, intente nuevamente mim install -r requirements/mminstall.txtsin error, ¡el problema está resuelto!

problema con la ruta del archivo xml

  • Debido a que dos personas marcaron por separado durante la calibración, lo que resultó en rutas inconsistentes xmlen el archivo path, pero no todos tienen este problema, aquí hay solo un pequeño registro.
import xml.etree.ElementTree as ET
import os
def modify_xml_path(xml_file):
    tree = ET.parse(xml_file)
    root = tree.getroot()

    for path_elem in root.iter('path'):
        path_elem.text = os.path.basename(path_elem.text)

    tree.write(xml_file)

folder_path = './data/xml'

for file in os.listdir(folder_path):
        file_path = os.path.join(folder_path, file)
        modify_xml_path(file_path)

archivo xml a archivo json

  • MMYOLODebido a que no leí el documento del tutorial cuando estaba haciendo la calibración de datos antes , usé Labelimgel software para calibrar, VOCformatear y generar xmlarchivos.
  • LabelmeSin embargo, los archivos generados se utilizan en el tutorial jsony la subsiguiente separación de datos, inspección de etiquetado, exploración de conjuntos de datos, etc. se basan en jsonarchivos, por lo que xmllos archivos deben convertirse en jsonarchivos.
  • Primero organice los archivos de la siguiente manera:
-mmyolo
	- data
		- images
			- 0001.bmp
			- 0002.bmp
			- ...
		- xml
			- 0001.xml
			- 0002.xml
			- ...
	 - configs
	 ...
  • Cree una nueva carpeta en la carpeta del proyecto, coloque la imagen en mmyoloy coloque el archivo de calibración en .data.\data\images.\data\xml
  • Luego .\tools\dataset_converterscrea un nuevo archivo en la carpeta xml2json.pyy completa el código:
import xml.etree.ElementTree as ET
import os
import json

coco = dict()
coco['images'] = []
coco['type'] = 'instances'
coco['annotations'] = []
coco['categories'] = []

category_set = dict()
image_set = set()

category_item_id = -1
image_id = 0
annotation_id = 0


def addCatItem(name):
    global category_item_id
    category_item = dict()
    category_item['supercategory'] = 'none'
    category_item_id += 1
    category_item['id'] = category_item_id
    category_item['name'] = name
    coco['categories'].append(category_item)
    category_set[name] = category_item_id
    return category_item_id


def addImgItem(file_name, size):
    global image_id
    if file_name is None:
        raise Exception('Could not find filename tag in xml file.')
    if size['width'] is None:
        raise Exception('Could not find width tag in xml file.')
    if size['height'] is None:
        raise Exception('Could not find height tag in xml file.')
    image_id += 1
    image_item = dict()
    image_item['id'] = image_id
    print(file_name)
    image_item['file_name'] = file_name + ".jpg"
    image_item['width'] = size['width']
    image_item['height'] = size['height']
    coco['images'].append(image_item)
    image_set.add(file_name)
    return image_id


def addAnnoItem(object_name, image_id, category_id, bbox):
    global annotation_id
    annotation_item = dict()
    annotation_item['segmentation'] = []
    seg = []
    # bbox[] is x,y,w,h
    # left_top
    seg.append(bbox[0])
    seg.append(bbox[1])
    # left_bottom
    seg.append(bbox[0])
    seg.append(bbox[1] + bbox[3])
    # right_bottom
    seg.append(bbox[0] + bbox[2])
    seg.append(bbox[1] + bbox[3])
    # right_top
    seg.append(bbox[0] + bbox[2])
    seg.append(bbox[1])

    annotation_item['segmentation'].append(seg)

    annotation_item['area'] = bbox[2] * bbox[3]
    annotation_item['iscrowd'] = 0
    annotation_item['ignore'] = 0
    annotation_item['image_id'] = image_id
    annotation_item['bbox'] = bbox
    annotation_item['category_id'] = category_id
    annotation_id += 1
    annotation_item['id'] = annotation_id
    coco['annotations'].append(annotation_item)


def parseXmlFiles(xml_path):
    for f in os.listdir(xml_path):
        if not f.endswith('.xml'):
            continue
        xmlname = f.split('.xml')[0]

        bndbox = dict()
        size = dict()
        current_image_id = None
        current_category_id = None
        file_name = None
        size['width'] = None
        size['height'] = None
        size['depth'] = None

        xml_file = os.path.join(xml_path, f)
        print(xml_file)

        tree = ET.parse(xml_file)
        root = tree.getroot()
        if root.tag != 'annotation':
            raise Exception('pascal voc xml root element should be annotation, rather than {}'.format(root.tag))

        # elem is <folder>, <filename>, <size>, <object>
        for elem in root:
            current_parent = elem.tag
            current_sub = None
            object_name = None

            if elem.tag == 'folder':
                continue

            if elem.tag == 'filename':
                file_name = xmlname
                if file_name in category_set:
                    raise Exception('file_name duplicated')

            # add img item only after parse <size> tag
            elif current_image_id is None and file_name is not None and size['width'] is not None:
                if file_name not in image_set:
                    current_image_id = addImgItem(file_name, size)
                    print('add image with {} and {}'.format(file_name, size))
                else:

                    raise Exception('duplicated image: {}'.format(file_name))

                    # subelem is <width>, <height>, <depth>, <name>, <bndbox>
            for subelem in elem:
                bndbox['xmin'] = None
                bndbox['xmax'] = None
                bndbox['ymin'] = None
                bndbox['ymax'] = None

                current_sub = subelem.tag
                if current_parent == 'object' and subelem.tag == 'name':
                    object_name = subelem.text
                    if object_name not in category_set:
                        current_category_id = addCatItem(object_name)
                    else:
                        current_category_id = category_set[object_name]

                elif current_parent == 'size':
                    if size[subelem.tag] is not None:
                        raise Exception('xml structure broken at size tag.')
                    size[subelem.tag] = int(subelem.text)

                # option is <xmin>, <ymin>, <xmax>, <ymax>, when subelem is <bndbox>
                for option in subelem:
                    if current_sub == 'bndbox':
                        if bndbox[option.tag] is not None:
                            raise Exception('xml structure corrupted at bndbox tag.')
                        bndbox[option.tag] = int(float(option.text))

                # only after parse the <object> tag
                if bndbox['xmin'] is not None:
                    if object_name is None:
                        raise Exception('xml structure broken at bndbox tag')
                    if current_image_id is None:
                        raise Exception('xml structure broken at bndbox tag')
                    if current_category_id is None:
                        raise Exception('xml structure broken at bndbox tag')
                    bbox = []
                    # x
                    bbox.append(bndbox['xmin'])
                    # y
                    bbox.append(bndbox['ymin'])
                    # w
                    bbox.append(bndbox['xmax'] - bndbox['xmin'])
                    # h
                    bbox.append(bndbox['ymax'] - bndbox['ymin'])
                    print('add annotation with {},{},{},{}'.format(object_name, current_image_id, current_category_id,
                                                                   bbox))
                    addAnnoItem(object_name, current_image_id, current_category_id, bbox)


if __name__ == '__main__':
    xml_path = './data/xml'
    json_file = './data/annotations/annotations_all.json'
    parseXmlFiles(xml_path)
    json.dump(coco, open(json_file, 'w'))
  • Después de ejecutar el código, los archivos ./data/annotationsse pueden generar en la carpeta annotations_all.json. El código de conversión anterior es una referencia al blog de un blogger, no escrito por mí, pero debido a limitaciones de tiempo, olvidé la dirección de origen. Si alguien lo ve, envíeme un mensaje privado e indique la fuente.
  • Cabe señalar aquí que si el sufijo de su imagen no es jpgy png, abra el annotations_all.jsonarchivo generado, verifique file_namelos campos y use un editor de texto para reemplazar el sufijo. Por ejemplo, si mi imagen está .bmpen formato, entonces necesito .jpgreemplazarlo con.bmp
  • Finalmente, debe ./data/annotationscrear uno nuevo en la carpeta class_with_id.txtpara guardar el tipo correspondiente a la etiqueta de valor. Podemos abrir annotations_all.jsonel archivo nuevamente, arrastrar hasta el final y encontrar ''categories''el campo, como mi jsonarchivo
"categories": [{
    
    "supercategory": "none", "id": 0, "name": "cat"}, {
    
    "supercategory": "none", "id": 1, "name": "dog"}]}
  • Puedes ver que el tipo 0corresponde al cattipo , abrimos el archivo y llenamos el siguiente contenido:1dogclass_with_id.txt
0 cat
1 dog
  • Hasta ahora, hemos completado la conversión del tutorial 3.1 usando scripts y su formato es el mismo que el del tutorial. Organización del formato de archivo final:
-mmyolo
	- data
		- images
			- 0001.bmp
			- 0002.bmp
			- ...
		- xml
			- 0001.xml
			- 0002.xml
			- ...
		- annotations
			- annotations_all.json
			- class_with_id.txt
	 - configs
	 ...

Verifique la etiqueta COCO convertida

  • Verifique el formato de datos usando los archivos mmyoloen la carpeta del proyecto .\tools\analysis_tools\browse_coco_json.py.
  • Modifique el valor predeterminado del parámetro del archivo, de acuerdo con el tutorial, solo modifique --img-direl --ann-fileparámetro y, agregue defaultopciones, el código es el siguiente
def parse_args():
    parser = argparse.ArgumentParser(description='Show coco json file')
    parser.add_argument('--data-root', default=None, help='dataset root')
    parser.add_argument(
        '--img-dir', default='data/images', help='image folder path')
    parser.add_argument(
        '--ann-file',
        default='data/annotations/annotations_all.json',
        help='ann file path')
    parser.add_argument(
        '--wait-time', type=float, default=2, help='the interval of show (s)')
    parser.add_argument(
        '--disp-all',
        action='store_true',
        help='Whether to display all types of data, '
        'such as bbox and mask.'
        ' Default is to display only bbox')
    parser.add_argument(
        '--category-names',
        type=str,
        default=None,
        nargs='+',
        help='Display category-specific data, e.g., "bicycle", "person"')
    parser.add_argument(
        '--shuffle',
        action='store_true',
        help='Whether to display in disorder')
    args = parser.parse_args()
    return args
  • También puede seguir el método en el tutorial e ingresar en la ventana de comandos:
python tools/analysis_tools/browse_coco_json.py --img-dir ${
    
    图片文件夹路径} \
                                                --ann-file ${
    
    COCO label json 路径}
  • Después de que la verificación sea correcta, este paso se completa

Dividir el conjunto de datos

  • Todavía podemos usar los archivos debajo del archivo del proyecto .\tools\misc\coco_split.pypara completar este paso
  • Modifique el valor predeterminado del parámetro del archivo, de acuerdo con el tutorial, solo modifique los --jsonparámetros y, agregue --out-diropciones , el código es el siguiente--ratiosdefault
def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--json', type=str, default='./data/annotations/annotations_all.json', help='COCO json label path')
    parser.add_argument(
        '--out-dir', type=str, default='./data/annotations', help='output path')
    parser.add_argument(
        '--ratios',
        default=[0.9,0.1],
        nargs='+',
        type=float,
        help='ratio for sub dataset, if set 2 number then will generate '
        'trainval + test (eg. "0.8 0.1 0.1" or "2 1 1"), if set 3 number '
        'then will generate train + val + test (eg. "0.85 0.15" or "2 1")')
    parser.add_argument(
        '--shuffle',
        action='store_true',
        help='Whether to display in disorder')
    parser.add_argument('--seed', default=2023, type=int, help='seed')
    args = parser.parse_args()
    return args
  • En particular, debe prestar atención --ratiosal método de escritura, ya que [0.9,0.1]también puede usar el método en el tutorial e ingresar en la ventana de comando:
python tools/misc/coco_split.py --json ${
    
    COCO label json 路径} \
                                --out-dir ${
    
    划分 label json 保存根路径} \
                                --ratios ${
    
    划分比例} \
                                [--shuffle] \
                                [--seed ${
    
    划分的随机种子}]
python tools/misc/coco_split.py --json ./data/cat/annotations/annotations_all.json \
                                --out-dir ./data/cat/annotations \
                                --ratios 0.8 0.2 \
                                --shuffle \
                                --seed 10
-mmyolo
	- data
		- images
			- 0001.bmp
			- 0002.bmp
			- ...
		- xml
			- 0001.xml
			- 0002.xml
			- ...
		- annotations
			- annotations_all.json
			- class_with_id.txt
			- trainval.json
			- test.json
	 - configs
	 ...

Crear un nuevo archivo de configuración

  • Cree una nueva carpeta debajo de ./configsla carpeta custom_datasety custom_datasetcree un nuevo archivo debajo de la carpeta yolov6_l_syncbn_fast_1xb8-100e_animal.py. De hecho, el archivo de configuración se puede nombrar directamente, pero este nombre tiene un cierto significado, por ejemplo, el anterior yolov6_l_syncbn_fastsignifica que estoy entrenando YOLOV6-lel backbone, y syncbnsignifica que los datos en todas las tarjetas (datos de muestra global) se utilizan para calcule la capa BN durante el entrenamiento con varias tarjetas. La media y la desviación estándar fastson los modelos del modelo, 1xb8-100elo que indica que uso 1 GPU para el entrenamiento, batch sizeque es 8 y max_epoch100. De ahí el nombre.
  • Cree una nueva carpeta en la carpeta del proyecto work_dirscomo directorio para guardar modelos y otros trabajos. Abra el archivo en la carpeta del proyecto .\configs\yolov6\README.md, descargue el peso previo al entrenamiento de YOLOv6-l por adelantado yolov6_l_syncbn_fast_8xb32-300e_coco_20221109_183156-91e3c447.pthy colóquelo work_dirsen la carpeta.
    inserte la descripción de la imagen aquí
  • Como estoy entrenando YOLOV6-lun modelo, lo que heredo es yolov6_l_syncbn_fast_8xb32-300e_coco.pyel archivo.
    Por favor agregue una descripción de la imagen
  • El archivo de configuración y sus comentarios son los siguientes:
# 继承的配置文件
_base_ = '../yolov6/yolov6_l_syncbn_fast_8xb32-300e_coco.py'

# 训练的最大epochs数
max_epochs = 100
# 数据所在文件夹
data_root = './data/'

# 模型工作目录
work_dir = './work_dirs'

# 模型预训练权重
load_from = './work_dirs/yolov6_l_syncbn_fast_8xb32-300e_coco_20221109_183156-91e3c447.pth'  # noqa

# 根据自己的 GPU 情况,修改 batch size,YOLOv6-l 默认为 8卡 x 32bs
# 设定batch size为8
train_batch_size_per_gpu = 8
# train_num_workers = nGPU x 4,即1 x 4 = 4
train_num_workers = 4
# 每 interval 轮迭代进行一次保存一次权重
save_epoch_intervals = 2

# 根据自己的 GPU 情况,修改 base_lr,修改的比例是 base_lr_default * (your_bs / default_bs)
# 即(your_bs / default_bs) = (8 / (8 x 32)) = 1 / 32
base_lr = _base_.base_lr / 32

# 根据 class_with_id.txt 类别信息,设置 class_name,顺序一定要对
class_name = ('cracked', 'complete')
num_classes = len(class_name)
# palette参数里面的元组,有多少种类就有多少个元组(r,g,b),否则报错
metainfo = dict(
    classes=class_name,
    palette=[(220, 17, 58), (0, 143, 10)]  # 画图时候的颜色,随便设置即可
)

train_cfg = dict(
    max_epochs=max_epochs,
    val_begin=20,  # 第几个 epoch 后验证,这里设置 20 是因为前 20 个 epoch 精度不高,测试意义不大,故跳过
    val_interval=save_epoch_intervals,  # 每 val_interval 轮迭代进行一次测试评估
    dynamic_intervals=[(max_epochs - _base_.num_last_epochs, 1)] # 到max_epochs - _base_.num_last_epochs时,每1轮执行一次评估
)

model = dict(
    bbox_head=dict(
        head_module=dict(num_classes=num_classes)),
    train_cfg=dict(
        initial_assigner=dict(num_classes=num_classes),
        assigner=dict(num_classes=num_classes))
)

train_dataloader = dict(
    batch_size=train_batch_size_per_gpu,
    num_workers=train_num_workers,
    dataset=dict(
        _delete_=True,
        type='ClassBalancedDataset',
        oversample_thr=0.5,
        dataset=dict(
            type=_base_.dataset_type,
            data_root=data_root,
            metainfo=metainfo,
            ann_file='annotations/trainval.json',
            data_prefix=dict(img='images/'),
            filter_cfg=dict(filter_empty_gt=False, min_size=32),
            pipeline=_base_.train_pipeline)))

val_dataloader = dict(
    dataset=dict(
        metainfo=metainfo,
        data_root=data_root,
        ann_file='annotations/trainval.json',
        data_prefix=dict(img='images/')))

test_dataloader = val_dataloader

val_evaluator = dict(ann_file=data_root + 'annotations/trainval.json')
test_evaluator = val_evaluator

optim_wrapper = dict(optimizer=dict(lr=base_lr))

default_hooks = dict(
    checkpoint=dict(
        type='CheckpointHook',
        interval=save_epoch_intervals,
        max_keep_ckpts=5,
        save_best='auto'),
    param_scheduler=dict(max_epochs=max_epochs),
    # logger 输出的间隔
    logger=dict(type='LoggerHook', interval=10))

custom_hooks = [
    dict(
        type='EMAHook',
        ema_type='ExpMomentumEMA',
        momentum=0.0001,
        update_buffers=True,
        strict_load=False,
        priority=49),
    dict(
        type='mmdet.PipelineSwitchHook',
        switch_epoch=max_epochs - _base_.num_last_epochs,
        switch_pipeline=_base_.train_pipeline_stage2)
]

visualizer = dict(vis_backends=[dict(type='LocalVisBackend'), dict(type='WandbVisBackend')])
visualizer = dict(vis_backends=[dict(type='LocalVisBackend'),dict(type='TensorboardVisBackend')])
  • No es difícil entender la primera mitad del archivo de configuración, pero train_cfgpuede ser un poco confuso en ese momento, lo explicaré con más detalle en secciones posteriores.

Explicación detallada de la sección de configuración

  • De hecho, la escritura de los archivos de configuración anteriores se hereda a mmenginela biblioteca, la dirección del proyecto de Github y los documentos de referencia . Hay una versión en chino del documento, que no es demasiado difícil de entender.
  • En general, todos los archivos de configuración se mmengine.runnerescriben en función de los métodos. Puede leer su API para tener una comprensión más profunda de los archivos de configuración.

tren_cfg

  • En mmengine.runnerel método, train_cfglos parámetros se describen de la siguiente manera: una contraseña para establecer un ciclo de entrenamiento. Si no proporciona una clave de "tipo", debe contener "by_epoch" para decidir qué tipo de bucle de entrenamiento se debe usar EpochBasedTrainLoopo no IterBasedTrainLoop. Si se especifica train_cfg, también debe especificarse train_dataloader. El valor predeterminado es Ninguno.
  • Documentación del parámetro EpochBasedTrainLoop , veamos el código de configuración
train_cfg = dict(
    max_epochs=max_epochs,
    val_begin=20, 
    val_interval=save_epoch_intervals,
    dynamic_intervals=[(max_epochs - _base_.num_last_epochs, 1)])
  • max_epochs = max_epochs: máxima max_epochsconducción de entrenamiento
  • val_begin = 20epoch: Evalúe el conjunto de prueba después del 20
  • val_interval = save_epoch_intervals: Una evaluación de prueba val_intervalpor iteración
  • dynamic_intervals = [(max_epochs - _base_.num_last_epochs, 1)]): Cuando max_epochs - _base_.num_last_epochsllega el momento, la evaluación se realiza cada 1 ronda

modelo

  • modelEsta pieza se utiliza principalmente para controlar la arquitectura del modelo, por lo que sus cambios están relacionados con el modelo original heredado, por ejemplo, lo que quiero entrenar es, luego seguiré buscando YOLOV6-lla _base_clase básica de acuerdo a lo siguiente, es decir, .\configs\yolov6\yolov6_s_syncbn_fast_8xb32-400e_coco.pypara encontrar bbox_headel campo El código es el siguiente:
bbox_head=dict(
        type='YOLOv6Head',
        head_module=dict(
            type='YOLOv6HeadModule',
            num_classes=num_classes,
            in_channels=[128, 256, 512],
            widen_factor=widen_factor,
            norm_cfg=dict(type='BN', momentum=0.03, eps=0.001),
            act_cfg=dict(type='SiLU', inplace=True),
            featmap_strides=[8, 16, 32]),
        loss_bbox=dict(
            type='IoULoss',
            iou_mode='giou',
            bbox_format='xyxy',
            reduction='mean',
            loss_weight=2.5,
            return_iou=False)),
  • También hay train_cfgcampos, el código es el siguiente:
train_cfg=dict(
        initial_epoch=4,
        initial_assigner=dict(
            type='BatchATSSAssigner',
            num_classes=num_classes,
            topk=9,
            iou_calculator=dict(type='mmdet.BboxOverlaps2D')),
        assigner=dict(
            type='BatchTaskAlignedAssigner',
            num_classes=num_classes,
            topk=13,
            alpha=1,
            beta=6),
    ),
  • Compara el código dado en el tutorial.
model = dict(
    bbox_head=dict(
        head_module=dict(num_classes=num_classes)),
    train_cfg=dict(
        initial_assigner=dict(num_classes=num_classes),
        assigner=dict(num_classes=num_classes))
)
  • Se puede ver que solo se modifican los parámetros relacionados con el número de categorías.Para una explicación más detallada de los parámetros de la arquitectura, puede leer el tutorial oficial para aprender el archivo de configuración de YOLOV5

tren_cargador de datos

  • train_dataloader: Runner.train()se utiliza en , para proporcionar datos de entrenamiento para el modelo; para obtener DataLoadermás parámetros configurables, consulte la documentación de la API de PyTorch
  • Debido a la pequeña cantidad de datos en el tutorial, datasethay una operación en la que se repiten las veces del conjunto de datos actual RepeatDataseten cada , y configurar 5 significa repetir 5 veces. Si su conjunto de datos es lo suficientemente grande y no necesita tal operación, puede eliminarlo directamente y convertirse en:epochn
train_dataloader = dict(
    batch_size=train_batch_size_per_gpu,
    num_workers=train_num_workers,
    dataset=dict(
        type=_base_.dataset_type,
        data_root=data_root,
        metainfo=metainfo,
        ann_file='annotations/trainval.json',
        data_prefix=dict(img='images/'),
        filter_cfg=dict(filter_empty_gt=False, min_size=32),
        pipeline=_base_.train_pipeline))
  • Debido a que hay un problema de muestras desequilibradas en mi conjunto de datos, utilizo ClassBalancedDatasetla operación, que hace que el número de muestras de cada categoría sea relativamente equilibrado al volver a muestrear el conjunto de datos original o ajustar los pesos de las muestras.
  • oversample_thres un número de punto flotante entre 0 y 1. Especifica un umbral para determinar qué clases de muestras deben sobremuestrearse. Específicamente, si el número de muestras de una determinada clase es menor que oversample_thr * max_samples, las muestras de esa clase se sobremuestrearán.
train_dataloader = dict(
    batch_size=train_batch_size_per_gpu,
    num_workers=train_num_workers,
    dataset=dict(
        _delete_=True,
        type='ClassBalancedDataset',
        oversample_thr=0.5,
        dataset=dict(
            type=_base_.dataset_type,
            data_root=data_root,
            metainfo=metainfo,
            ann_file='annotations/trainval.json',
            data_prefix=dict(img='images/'),
            filter_cfg=dict(filter_empty_gt=False, min_size=32),
            pipeline=_base_.train_pipeline)))
  • Para conocer otros métodos de procesamiento de datos, puede ver la documentación
  • Para datasetparámetros más detallados en , puede BASEDATASETencontrarlos en , consulte la documentación , MMYOLOtambién hay un nuevo parámetro en , el valor predeterminado typees el formato de datos 'CocoDataset'COCO
    • type: tipo de formato de datos
    • data_root: data_prefixy ann_fileel directorio raíz de
    • metainfo: metadatos del conjunto de datos, como información de clase
    • ann_file: ruta del archivo de anotación
    • data_prefix: El prefijo de los datos de entrenamiento. El valor predeterminado es dict(img_path='')
    • filter_cfg: configuración para el filtrado de datos
    • pipeline: tubería de procesamiento

val_cargador de datos

  • val_dataloaderAlgunos de datasetlos parámetros train_dataloaderson los mismos que los de , por lo que no entraré en detalles aquí.
val_dataloader = dict(
    dataset=dict(
        metainfo=metainfo,
        data_root=data_root,
        ann_file='annotations/trainval.json',
        data_prefix=dict(img='images/')))

test_dataloader

  • En el tutorial, se val_dataloaderasigna directamente a test_dataloader. Por supuesto, también podemos escribir los nuestros.
test_dataloader = dict(
    dataset=dict(
        metainfo=metainfo,
        data_root=data_root,
        ann_file='annotations/test.json',
        data_prefix=dict(img='images/')))

val_evaluador

  • val_evaluatorUn objeto evaluador para calcular métricas de validación. Puede ser un diccionario o una lista de diccionarios para construir el evaluador.
val_evaluator = dict(ann_file=data_root + 'annotations/trainval.json')
  • En el archivo de configuración heredado, la val_evaluatorconfiguración completa es:
val_evaluator = dict(
    type='mmdet.CocoMetric',
    proposal_nums=(100, 1, 10),
    ann_file=data_root + val_ann_file,
    metric='bbox')
  • Equivalente a cambiar solo annfilelos parámetros en el archivo heredado

test_evaluator

  • val_evaluatorEn el tutorial, el valor se asigna directamente a test_evaluator, y también podemos escribirlo nosotros mismos
test_evaluator = dict(ann_file=data_root + 'annotations/test.json')

envoltura_optima

  • optim_wrapperCalcula el gradiente de los parámetros del modelo. Si se requiere un entrenamiento automático de precisión mixta o acumulación de gradientes. optim_wrapperEl tipo debe ser AmpOptimizerWrapper.
optim_wrapper = dict(optimizer=dict(lr=base_lr))
  • En el archivo de configuración heredado, el código completo del paquete optimizador es:
optim_wrapper = dict(
    type='OptimWrapper',
    optimizer=dict(
        type='SGD',
        lr=base_lr,
        momentum=0.937,
        weight_decay=weight_decay,
        nesterov=True,
        batch_size_per_gpu=train_batch_size_per_gpu),
    constructor='YOLOv5OptimizerConstructor')
  • La forma de escribir en el tutorial es equivalente a solo cambiar la tasa de aprendizaje en el archivo de herencia

gancho

  • hookLa programación es un modo de programación, que se refiere a establecer una ubicación (punto de montaje) en una o más ubicaciones del programa. Cuando el programa se ejecuta en una determinada ubicación, llamará automáticamente a todos los métodos registrados en la ubicación en tiempo de ejecución.

gancho predeterminado

default_hooks = dict(
    checkpoint=dict(
        type='CheckpointHook',
        interval=save_epoch_intervals,
        max_keep_ckpts=5,
        save_best='auto'),
    param_scheduler=dict(max_epochs=max_epochs),
    ## logger 输出的间隔
    logger=dict(type='LoggerHook', interval=10))
  • hookEl tipo por defecto en el tutorial CheckpointHookes CheckpointHookguardar el peso del modelo en un intervalo dado, si se distribuye entrenamiento multitarjeta, solo el proceso master (maestro) guardará el peso.
  • Si quieres saber más sobre sus funciones, es decir, más parámetros, puedes consultar la documentación de la API de CheckpointHook , aquí selecciono los parámetros que aparecen en el archivo del tutorial.
    • interval: guardar ciclo. Si by_epoch=True, el intervalo representa épocas (período); de lo contrario, representa el número de iteraciones.
    • max_keep_ckpts: Máximo de puntos de control para mantener. En algunos casos, solo necesitamos los últimos puntos de control y queremos eliminar los antiguos para ahorrar espacio en disco.
    • save_best: si se especifica una métrica, medirá el mejor punto de control durante la evaluación. Si se aprueba un conjunto de métricas, mide el mejor conjunto de puntos de control correspondientes a las métricas aprobadas.
  • Acerca de ParamSchedulerHook, podemos encontrar los parámetros en el archivo de configuración heredado:
default_hooks = dict(
    param_scheduler=dict(
        type='YOLOv5ParamSchedulerHook',
        scheduler_type='cosine',
        lr_factor=lr_factor,
        max_epochs=max_epochs),
    checkpoint=dict(
        type='CheckpointHook',
        interval=save_epoch_intervals,
        max_keep_ckpts=max_keep_ckpts,
        save_best='auto'))
  • La forma en que está escrito el tutorial es equivalente a solo cambiar param_scheduleren max_epochs.
  • LoggerHookResponsable de recopilar registros y enviarlos a la terminal o enviarlos a archivos, TensorBoard y otros backends. interval=10Genere (o guarde) un registro cada 10 iteraciones ( ) en el tutorial

gancho personalizado

custom_hooks = [
    dict(
        type='EMAHook',
        ema_type='ExpMomentumEMA',
        momentum=0.0001,
        update_buffers=True,
        strict_load=False,
        priority=49),
    dict(
        type='mmdet.PipelineSwitchHook',
        switch_epoch=max_epochs - _base_.num_last_epochs,
        switch_pipeline=_base_.train_pipeline_stage2)
]
  • EMAHookSe realiza una operación de promedio móvil exponencial en el modelo durante el entrenamiento para mejorar la robustez del modelo. Nota: El modelo generado por el promedio móvil exponencial solo se usa para validación y prueba, y no afecta el entrenamiento.
  • Documentación de la API EMAHooK , documentación de la API ExponentialMovingAverage , explicación de algunos parámetros
    • momentum: emael impulso utilizado para actualizar los parámetros
    • update_buffers: En caso afirmativo True, calcula los promedios móviles de los parámetros del modelo y los búferes.
    • strict_load: si hacer cumplir estrictamente que state_dictlas claves en el punto de control coincidan con las claves devueltasself.module.state_dict
    • priority: hookprioridad
  • mmdet.PipelineSwitchHookes MMDetectionparte de la biblioteca para switch_epochcambiar canalizaciones de datos, documentos API

Visualización de conjuntos de datos

  • Podemos seguir el tutorial y usar .\tools\analysis_tools\dataset_analysis.pyel archivo para analizar los datos. Nota: Los datos en este momento se han transformado, como la operación ClassBalancedDatasetORRepeatDataset
  • Este archivo puede generar 4 tipos de gráficos de análisis:
    • Muestre bboxun gráfico de distribución de clases y recuentos de instancias:show_bbox_num
    • Muestre bboxla distribución de anchos y altos de clases e instancias:show_bbox_wh
    • bboxDiagrama de distribución que muestra las proporciones de ancho/alto de instancia y clase :show_bbox_wh_ratio
    • bboxMuestre la distribución de las áreas de clases e instancias bajo la regla basada en áreas :show_bbox_area
  • Modifique el valor predeterminado del parámetro del archivo, de acuerdo con el tutorial, modifique los ----configparámetros , --val-dataset, --class-name, y , (--area-rule--func--out-dirconfigTenga en cuenta que debe reemplazar el código en el código --configpara ejecutarlo; de lo contrario, se informará un error) para agregar defaultopciones, el código es el siguiente
def parse_args():
    parser = argparse.ArgumentParser(
        description='Distribution of categories and bbox instances')
    parser.add_argument('--config', default='./configs/custom_dataset/yolov6_l_syncbn_fast_1xb8-100e_animal.py', help='config file path')
    parser.add_argument(
        '--val-dataset',
        default=False,
        action='store_true',
        help='The default train_dataset.'
        'To change it to val_dataset, enter "--val-dataset"')
    parser.add_argument(
        '--class-name',
        default=None,
        type=str,
        help='Display specific class, e.g., "bicycle"')
    parser.add_argument(
        '--area-rule',
        default=None,
        type=int,
        nargs='+',
        help='Redefine area rules,but no more than three numbers.'
        ' e.g., 30 70 125')
    parser.add_argument(
        '--func',
        default=None,
        type=str,
        choices=[
            'show_bbox_num', 'show_bbox_wh', 'show_bbox_wh_ratio',
            'show_bbox_area'
        ],
        help='Dataset analysis function selection.')
    parser.add_argument(
        '--out-dir',
        default='./dataset_analysis',
        type=str,
        help='Output directory of dataset analysis visualization results,'
        ' Save in "./dataset_analysis/" by default')
    args = parser.parse_args()
    return args
  • También puede seguir el método en el tutorial e ingresar en la ventana de comandos:
python tools/analysis_tools/dataset_analysis.py ${
    
    CONFIG} \
                                                [--val-dataset ${
    
    TYPE}] \
                                                [--class-name ${
    
    CLASS_NAME}] \
                                                [--area-rule ${
    
    AREA_RULE}] \
                                                [--func ${
    
    FUNC}] \
                                                [--out-dir ${
    
    OUT_DIR}]
  • Comprobar la distribución de datos del conjunto de entrenamiento
python tools/analysis_tools/dataset_analysis.py ./configs/custom_dataset/yolov6_l_syncbn_fast_1xb8-100e_animal.py \
                                                --out-dir work_dirs/dataset_analysis_cat/train_dataset

Optimizar tamaño de ancla

  • Como estoy entrenando el modelo YOLOV6, este paso no es necesario.

La parte de procesamiento de datos en la configuración de configuración de visualización

  • Podemos seguir el tutorial y usar .\tools\analysis_tools\browse_dataset.pyla parte de procesamiento de datos de visualización de archivos.
  • Modifique el valor predeterminado del parámetro del archivo, siga el tutorial, modifique configa --config, ejecute el código
def parse_args():
    parser = argparse.ArgumentParser(description='Browse a dataset')
    parser.add_argument('--config', default='./configs/custom_dataset/yolov6_l_syncbn_fast_1xb8-100e_animal.py', help='train config file path')
    parser.add_argument(
        '--phase',
        '-p',
        default='train',
        type=str,
        choices=['train', 'test', 'val'],
        help='phase of dataset to visualize, accept "train" "test" and "val".'
        ' Defaults to "train".')
    parser.add_argument(
        '--mode',
        '-m',
        default='transformed',
        type=str,
        choices=['original', 'transformed', 'pipeline'],
        help='display mode; display original pictures or '
        'transformed pictures or comparison pictures. "original" '
        'means show images load from disk; "transformed" means '
        'to show images after transformed; "pipeline" means show all '
        'the intermediate images. Defaults to "transformed".')
    parser.add_argument(
        '--out-dir',
        default='output',
        type=str,
        help='If there is no display interface, you can save it.')
    parser.add_argument('--not-show', default=False, action='store_true')
    parser.add_argument(
        '--show-number',
        '-n',
        type=int,
        default=sys.maxsize,
        help='number of images selected to visualize, '
        'must bigger than 0. if the number is bigger than length '
        'of dataset, show all the images in dataset; '
        'default "sys.maxsize", show all images in dataset')
    parser.add_argument(
        '--show-interval',
        '-i',
        type=float,
        default=3,
        help='the interval of show (s)')
    parser.add_argument(
        '--cfg-options',
        nargs='+',
        action=DictAction,
        help='override some settings in the used config, the key-value pair '
        'in xxx=yyy format will be merged into config file. If the value to '
        'be overwritten is a list, it should be like key="[a,b]" or key=a,b '
        'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" '
        'Note that the quotation marks are necessary and that no white space '
        'is allowed.')
    args = parser.parse_args()
    return args
  • Puede usar el siguiente comando para verificar si el procesamiento de datos está a la altura:
python tools/analysis_tools/browse_dataset.py ./configs/custom_dataset/yolov6_l_syncbn_fast_1xb8-100e_animal.py \
                                              --show-interval 3

tren

visualización de entrenamiento

  • MMYOLOActualmente proporciona 2 formas wandby TensorBoard, de acuerdo con su propia situaciónelige unopoder

varita mágica

  • Personalmente recomiendo un método, porque solo necesita iniciar sesión en la página web para ver la situación del entrenamiento en tiempo real, lo cual es muy conveniente y la visualización es mejor.
  • En primer lugar, debe registrarse en el sitio web oficial de wandb y obtener las `` Claves API en la configuraciónwandb
  • Luego instale wandb en la línea de comando e inicie sesión
pip install wandb
# 运行了 wandb login 后输入上文中获取到的 API Keys ,便登录成功。
wandb login
  • Finalmente, agregue el código de configuración al final del configarchivo recién creado .\configs\custom_dataset\yolov6_l_syncbn_fast_1xb8-100e_animal.py:
visualizer = dict(vis_backends=[dict(type='LocalVisBackend'), dict(type='WandbVisBackend')])

TensorTablero

  • Primero necesitas instalar Tensorboardel entorno.
pip install tensorboard
  • Luego agregue el código de configuración al final del configarchivo recién creado:.\configs\custom_dataset\yolov6_l_syncbn_fast_1xb8-100e_animal.py
visualizer = dict(vis_backends=[dict(type='LocalVisBackend'),dict(type='TensorboardVisBackend')])
  • Después de ejecutar el comando de entrenamiento, Tensorboardel archivo se generará en la carpeta de visualización work_dirs\yolov6_l_syncbn_fast_1xb8-100e_animal\${TIMESTAMP}\vis_dataEjecute el siguiente comando para usar Tensorboardla visualización loss, la tasa de aprendizaje y coco/bbox_mAPotros datos de visualización en la página web:

entrenamiento ejecutivo

  • Abra .\tools\train.pyel archivo, modifique los parámetros, configcambie a --configy configure el valor predeterminado.
def parse_args():
    parser = argparse.ArgumentParser(description='Train a detector')
    parser.add_argument('--config',default='./configs/custom_dataset/yolov6_l_syncbn_fast_1xb8-100e_animal.py' ,help='train config file path')
    parser.add_argument('--work-dir', help='the dir to save logs and models')
    parser.add_argument(
        '--amp',
        action='store_true',
        default=False,
        help='enable automatic-mixed-precision training')
    parser.add_argument(
        '--resume',
        nargs='?',
        type=str,
        const='auto',
        help='If specify checkpoint path, resume from it, while if not '
        'specify, try to auto resume from the latest checkpoint '
        'in the work directory.')
    parser.add_argument(
        '--cfg-options',
        nargs='+',
        action=DictAction,
        help='override some settings in the used config, the key-value pair '
        'in xxx=yyy format will be merged into config file. If the value to '
        'be overwritten is a list, it should be like key="[a,b]" or key=a,b '
        'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" '
        'Note that the quotation marks are necessary and that no white space '
        'is allowed.')
    parser.add_argument(
        '--launcher',
        choices=['none', 'pytorch', 'slurm', 'mpi'],
        default='none',
        help='job launcher')
    parser.add_argument('--local_rank', type=int, default=0)
    args = parser.parse_args()
    if 'LOCAL_RANK' not in os.environ:
        os.environ['LOCAL_RANK'] = str(args.local_rank)

    return args
  • Después de correr, puedes ver la información específica del entrenamiento en wandbla página web o entensorboard
    inserte la descripción de la imagen aquí
  • La siguiente es la precisión de 1 x 2080Ti, tamaño de lote = 8, entrenando 100 pesos de precisión de época work_dirs\best_coco_bbox_mAP_epoch_97:
coco/bbox_mAP: 0.8910  coco/bbox_mAP_50: 1.0000  coco/bbox_mAP_75: 1.0000  coco/bbox_mAP_s: -1.0000  coco/bbox_mAP_m: -1.0000  coco/bbox_mAP_l: 0.8910  data_time: 0.0004  time: 0.0256

razonamiento

  • Utilice el mejor modelo para la inferencia, abra .\demo\image_demo.pyel archivo, cambie --imglos parámetros, vale la pena señalar que los parámetros pueden ser rutas de carpetas, rutas de archivos individuales --configo . El parámetro es el archivo de configuración que creamos. Los parámetros son los pesos óptimos para el entrenamiento.--checkpointimgURL--config--checkpoint
def parse_args():
    parser = ArgumentParser()
    parser.add_argument('--img', default='./data/images/Image_20230621152815633.bmp', help='Image path, include image file, dir and URL.')
    parser.add_argument('--config', default='./configs/custom_dataset/yolov6_l_syncbn_fast_1xb8-100e_animal.py', help='Config file')
    parser.add_argument('--checkpoint', default='./work_dirs/best_coco_bbox_mAP_epoch_97.pth', help='Checkpoint file')
    parser.add_argument(
        '--out-dir', default='./output', help='Path to output file')
    parser.add_argument(
        '--device', default='cpu', help='Device used for inference')
    parser.add_argument(
        '--show', action='store_true', help='Show the detection results')
    parser.add_argument(
        '--deploy',
        action='store_true',
        help='Switch model to deployment mode')
    parser.add_argument(
        '--tta',
        action='store_true',
        help='Whether to use test time augmentation')
    parser.add_argument(
        '--score-thr', type=float, default=0.3, help='Bbox score threshold')
    parser.add_argument(
        '--class-name',
        nargs='+',
        type=str,
        help='Only Save those classes if set')
    parser.add_argument(
        '--to-labelme',
        action='store_true',
        help='Output labelme style label file')
    args = parser.parse_args()
    return args

Supongo que te gusta

Origin blog.csdn.net/qq_20144897/article/details/131476209
Recomendado
Clasificación