{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# windows下使用Yolov5l用于牛仔行头检测\n\n本次牛仔行头检测我使用的是[ultralytics/yolov5](https://github.com/ultralytics/yolov5) ，该仓库提供了s、m、l、x四个规模的预训练模型，我使用的预训练模型是`Yolov5l`，最后可以在公榜(也就是`valid.csv`)上达到60的MAP值。\n\n由于kaggle上连续训练不能超过6个小时，所以我是在本地上进行的训练和测试。我的电脑系统是windows10，显卡为RTX2070s-8G，训练了200个Epoch。\n\n本次是目标检测竞赛，一张图片上可能存在多个目标，每个目标可以用四个值来进行表示，即目标的种类和目标的位置，位置用四个值来进行表示，目标检测常用的格式可以为了3种：coco、voc和yolo。coco的坐标表示为`[x， y， w， h]`，即bbox左上角的坐标和bbox的宽和高；voc的坐标表示为`[x_min, y_min, x_max, y_max]`，即bbox左上角的坐标和右下角的坐标；yolo的坐标表示为`[center_x, center_y, w, h]`，yolo这里表示的是bbox中心点的坐标和bbox的宽高，注意yolo这里是值是经过归一化处理的，都是介于0到1之前的浮点数。本次比赛我们使用的是coco格式，我使用yolo模型来进行训练的，所以在训练和提交结果之前要对<font color='red'>格式进行转换</font>，否则大家可能遇到训练的MAP和提交测试的MAP相差过多的情况出现。\n\n## 环境配置\n\n配置之前请确保已经安装好了英伟达显卡的驱动，在命令行中输入`nvidia-smi`会输出相应的显卡信息。由于我们使用的是ananconda来控制虚拟环境，使用conda命令安装Pytorch的时候会自动检查依赖并进行安装，所以不需要额外在本地安装cudnn。\n\n首先把代码下载到本地，执行下列命令。\n\n```cmd\ngit clone https://github.com/ultralytics/yolov5.git\n```\n\n创建并激活虚拟环境。\n\n```cmd\nconda create -n yolo python==3.8.5\nconda activate yolo\n```\n\n安装GPU版本的Pytorch，注意30系列显卡是安培架构，只支持11版本的cuda。\n\n```\nconda install pytorch==1.7.0 torchvision torchaudio cudatoolkit=10.2\n```\n\n安装程序所需的其他的第三方库。\n\n```\ncd yolov5\npip intall -r requirements \n```\n\n## 数据预处理\n\n数据部分需要处理成yolo的数据格式，如果觉得比较麻烦的小伙伴可以使用我处理好的，链接如下：\n\n>链接：https://pan.baidu.com/s/1SGdjCTAq6Sa4LLgAG9Xx5w \n>提取码：v9re \n\n原始的数据包含四部分的内容\n\n```\nimages # 存放图片文件\ntest.csv # 私榜需要测试的图片 \ntrain.json # 训练的数据以及标注\nvaild.csv # 公榜需要测试的图片\n```\n\n这里的数据使用的是coco格式的数据，需要转化为yolo格式的数据。我这里的方法比较笨，由于我之前写过一个统一的voc转yolo的代码，所以我的处理方案是先把coco格式的数据转化为voc格式，然后再从voc格式的数据转化为yolo格式。\n\n### 将数据集转化为统一的voc格式\n\nvoc中使用xml文件来对数据格式进行标注，下面的代码可以将json中的数据解析为xml文件\n\n```python\nimport json\nfrom xml.dom.minidom import *\nimport xml.etree.ElementTree as ET\nimport numpy as np\n\n\n\n\n# 美化xml文件\ndef pretty_xml(element, indent, newline, level=0):  # elemnt为传进来的Elment类，参数indent用于缩进，newline用于换行\n    if element:  # 判断element是否有子元素\n        if (element.text is None) or element.text.isspace():  # 如果element的text没有内容\n            element.text = newline + indent * (level + 1)\n        else:\n            element.text = newline + indent * (level + 1) + element.text.strip() + newline + indent * (level + 1)\n            # else:  # 此处两行如果把注释去掉，Element的text也会另起一行\n            # element.text = newline + indent * (level + 1) + element.text.strip() + newline + indent * level\n    temp = list(element)  # 将element转成list\n    for subelement in temp:\n        if temp.index(subelement) < (len(temp) - 1):  # 如果不是list的最后一个元素，说明下一个行是同级别元素的起始，缩进应一致\n            subelement.tail = newline + indent * (level + 1)\n        else:  # 如果是list的最后一个元素， 说明下一行是母元素的结束，缩进应该少一个\n            subelement.tail = newline + indent * level\n        pretty_xml(subelement, indent, newline, level=level + 1)  # 对子元素进行递归操作\n\n# 写入xml文件\ndef write_xml(img_name, width, height, object_dicts, save_path, folder='XJTU_dataset'):\n    '''\n    object_dict = {'name': classes[int(object_category)],\n                            'truncated': int(truncation),\n                            'difficult': int(occlusion),\n                            'xmin':int(bbox_left),\n                            'ymin':int(bbox_top),\n                            'xmax':int(bbox_left) + int(bbox_width),\n                            'ymax':int(bbox_top) + int(bbox_height)\n                            }\n    '''\n    doc = Document\n    root = ET.Element('Annotation')\n    ET.SubElement(root, 'folder').text = folder\n    ET.SubElement(root, 'filename').text = img_name\n    size_node = ET.SubElement(root, 'size')\n    ET.SubElement(size_node, 'width').text = str(width)\n    ET.SubElement(size_node, 'height').text = str(height)\n    ET.SubElement(size_node, 'depth').text = '3'\n    for object_dict in object_dicts:\n        object_node = ET.SubElement(root, 'object')\n        ET.SubElement(object_node, 'name').text = object_dict['name']\n        ET.SubElement(object_node, 'pose').text = 'Unspecified'\n        ET.SubElement(object_node, 'truncated').text = str(object_dict['truncated'])\n        ET.SubElement(object_node, 'difficult').text = str(object_dict['difficult'])\n        bndbox_node = ET.SubElement(object_node, 'bndbox')\n        ET.SubElement(bndbox_node, 'xmin').text = str(object_dict['xmin'])\n        ET.SubElement(bndbox_node, 'ymin').text = str(object_dict['ymin'])\n        ET.SubElement(bndbox_node, 'xmax').text = str(object_dict['xmax'])\n        ET.SubElement(bndbox_node, 'ymax').text = str(object_dict['ymax'])\n\n    pretty_xml(root, '\\t', '\\n')\n    tree = ET.ElementTree(root)\n    tree.write(save_path, encoding='utf-8')\n\nif __name__ == '__main__':\n    # class_names = ['road', 'car', 'motorcycle', 'person']\n    f = open(\"../../../cow/train.json\", encoding='utf-8')\n    data = json.load(f)\n    annotations = data['annotations']\n    images = data['images']\n    cate = {\n        \"87\": \"belt\",\n        \"1034\": \"sunglasses\",\n        \"131\": \"boot\",\n        \"318\": \"cowboy_hat\",\n        \"588\": \"jacket\"\n    }\n    images_num = len(images)\n    # print()\n    bbox_infos = {}\n    for image in images:\n        print()\n        bbox_infos[str(image[\"id\"])] = []\n\n    for ann in annotations:\n        print(ann)\n        image_id = ann['image_id']\n        bbox = ann['bbox']\n        bbox_x = bbox[0]\n        bbox_y = bbox[1]\n        bbox_w = bbox[2]\n        bbox_h = bbox[3]\n        class_id = ann['category_id']\n        object_dict = {'name': cate[str(class_id)],\n                       'truncated': 0,\n                       'difficult': 0,\n                       'xmin': int(bbox_x),\n                       'ymin': int(bbox_y),\n                       'xmax': int(bbox_x+bbox_w),\n                       'ymax': int(bbox_y+bbox_h)\n                       }\n        print(object_dict)\n        bbox_infos[str(image_id)].append(object_dict)\n\n\n    txt_results = []\n    for image in images:\n        print(image)\n        image_height = image['height']\n        image_width = image['width']\n        image_id = image['id']\n        image_file_name = image['file_name']\n        object_dicts = bbox_infos[str(image_id)]\n        xml_file_name = image_file_name.strip('.jpg') + '.xml'\n        txt_results.append(image_file_name.strip('.jpg'))\n        # print(image_file_name, image_width, image_height, object_dicts, )\n        write_xml(image_file_name, image_width, image_height, object_dicts, \"E:/holiday/cowboy/anns/\"+xml_file_name)\n\n    # np.savetxt()\n    # np.savetxt('train.txt', txt_results, fmt=\"%s\", delimiter=\"\\n\")\n    # for image in images:\n    #     print(image)\n\n```\n\n解析得到xml文件之后，我们需要按照VOC格式来放置我们的图片数据和标注文件数据，放置的文件夹格式如下：\n\n```\nVOC2012\n├─ Annotations # 存放新生成的xml文件\n├─ ImageSets # Main目录下放train.txt,val.txt和test.txt，我们按照8比2的比例将数据分开\n├─ JPEGImages # 放图片文件\n└─ Labels # 新建一个空文件，后面放我们生成的yolo格式的文件\n```\n\n### voc格式的数据转化为yolo格式\n\n接下来通过下面的程序就可以得到yolo格式的数据集了。\n\n```python\n# -*- coding: utf-8 -*-\n# @Time    : 20210610\n# @Author  : dejahu\n# @File    : gen_yolo_data.py\n# @Software: PyCharm\n# @Brief   : 生成测试、验证、训练的图片和标签\n\nimport os\nfrom pathlib import Path\nfrom shutil import copyfile\n\nfrom PIL import Image, ImageDraw\nfrom xml.dom.minidom import parse\nimport numpy as np\n\n# todo 修改为你的数据的根目录\nFILE_ROOT = \"E:/holiday/cow/\"\n\nIMAGE_SET_ROOT = FILE_ROOT + \"VOC2012/ImageSets/Main\"  # 图片区分文件的路径\nIMAGE_PATH = FILE_ROOT + \"VOC2012/JPEGImages\"  # 图片的位置\nANNOTATIONS_PATH = FILE_ROOT + \"VOC2012/Annotations\"  # 数据集标签文件的位置\nLABELS_ROOT = FILE_ROOT + \"VOC2012/Labels\"  # 进行归一化之后的标签位置\n\nDEST_IMAGES_PATH = \"cow_yolo_dataset/score/images\"  # 区分训练集、测试集、验证集的图片目标路径\nDEST_LABELS_PATH = \"cow_yolo_dataset/score/labels\"  # 区分训练集、测试集、验证集的标签文件目标路径\n\n# todo 修改为你数据集的标签名称\nlabel_names = [\"belt\", \"sunglasses\", \"boot\", \"cowboy_hat\", \"jacket\"]\n\nprint(label_names)\nprint(len(label_names))\n\n\ndef cord_converter(size, box):\n    \"\"\"\n    将标注的 xml 文件标注转换为 darknet 形的坐标\n    :param size: 图片的尺寸： [w,h]\n    :param box: anchor box 的坐标 [左上角x,左上角y,右下角x,右下角y,]\n    :return: 转换后的 [x,y,w,h]\n    \"\"\"\n    x1 = int(box[0])\n    y1 = int(box[1])\n    x2 = int(box[2])\n    y2 = int(box[3])\n\n    dw = np.float32(1. / int(size[0]))\n    dh = np.float32(1. / int(size[1]))\n\n    w = x2 - x1\n    h = y2 - y1\n    x = x1 + (w / 2)\n    y = y1 + (h / 2)\n\n    x = x * dw\n    w = w * dw\n    y = y * dh\n    h = h * dh\n    return [x, y, w, h]\n\n\ndef save_file(img_jpg_file_name, size, img_box):\n    # print(\"保存图片\")\n    save_file_name = LABELS_ROOT + '/' + img_jpg_file_name + '.txt'\n    print(save_file_name)\n    file_path = open(save_file_name, \"a+\")\n    # 默认给定的是id为0，防止错误数据的出现\n    for box in img_box:\n        box_name = box[0]\n        cls_num = 0\n        if box_name in label_names:\n            cls_num = label_names.index(box_name)\n        new_box = cord_converter(size, box[1:])\n        file_path.write(f\"{cls_num} {new_box[0]} {new_box[1]} {new_box[2]} {new_box[3]}\\n\")\n    file_path.flush()\n    file_path.close()\n\n\ndef test_dataset_box_feature(file_name, point_array):\n    \"\"\"\n    使用样本数据测试数据集的建议框\n    :param image_name: 图片文件名\n    :param point_array: 全部的点 [建议框sx1,sy1,sx2,sy2]\n    :return: None\n    \"\"\"\n    im = Image.open(rf\"{IMAGE_PATH}\\{file_name}\")\n    imDraw = ImageDraw.Draw(im)\n    for box in point_array:\n        x1 = box[1]\n        y1 = box[2]\n        x2 = box[3]\n        y2 = box[4]\n        imDraw.rectangle((x1, y1, x2, y2), outline='red')\n\n    im.show()\n\n\ndef get_xml_data(file_path, img_xml_file):\n    img_path = file_path + '/' + img_xml_file + '.xml'\n    print(img_path)\n\n    dom = parse(img_path)\n    root = dom.documentElement\n    img_name = root.getElementsByTagName(\"filename\")[0].childNodes[0].data\n    img_size = root.getElementsByTagName(\"size\")[0]\n    objects = root.getElementsByTagName(\"object\")\n    img_w = img_size.getElementsByTagName(\"width\")[0].childNodes[0].data\n    img_h = img_size.getElementsByTagName(\"height\")[0].childNodes[0].data\n    img_c = img_size.getElementsByTagName(\"depth\")[0].childNodes[0].data\n    img_box = []\n    for box in objects:\n        cls_name = box.getElementsByTagName(\"name\")[0].childNodes[0].data\n        x1 = int(float(box.getElementsByTagName(\"xmin\")[0].childNodes[0].data))\n        y1 = int(float(box.getElementsByTagName(\"ymin\")[0].childNodes[0].data))\n        x2 = int(float(box.getElementsByTagName(\"xmax\")[0].childNodes[0].data))\n        y2 = int(float(box.getElementsByTagName(\"ymax\")[0].childNodes[0].data))\n        # print(\"box:(c,xmin,ymin,xmax,ymax)\", cls_name, x1, y1, x2, y2)\n        img_jpg_file_name = img_xml_file + '.jpg'\n        img_box.append([cls_name, x1, y1, x2, y2])\n    # print(img_box)\n\n    # test_dataset_box_feature(img_jpg_file_name, img_box)\n    save_file(img_xml_file, [img_w, img_h], img_box)\n\n\ndef copy_data(img_set_source, img_labels_root, imgs_source, type):\n    file_name = img_set_source + '/' + type + \".txt\"\n    file = open(file_name)\n\n    # 判断文件夹是否存在，不存在则创建\n    root_file = Path(FILE_ROOT + DEST_IMAGES_PATH + '/' + type)\n    if not root_file.exists():\n        print(f\"Path {root_file} is not exit\")\n        os.makedirs(root_file)\n\n    root_file = Path(FILE_ROOT + DEST_LABELS_PATH + '/' + type)\n    if not root_file.exists():\n        print(f\"Path {root_file} is not exit\")\n        os.makedirs(root_file)\n\n    # 遍历文件夹\n    for line in file.readlines():\n        print(line)\n        img_name = line.strip('\\n')\n        img_sor_file = imgs_source + '/' + img_name + '.jpg'\n        label_sor_file = img_labels_root + '/' + img_name + '.txt'\n\n        # print(img_sor_file)\n        # print(label_sor_file)\n        # im = Image.open(rf\"{img_sor_file}\")\n        # im.show()\n\n        # 复制图片\n        DICT_DIR = FILE_ROOT + DEST_IMAGES_PATH + '/' + type\n        img_dict_file = DICT_DIR + '/' + img_name + '.jpg'\n        copyfile(img_sor_file, img_dict_file)\n\n        # 复制 label\n        DICT_DIR = FILE_ROOT + DEST_LABELS_PATH + '/' + type\n        img_dict_file = DICT_DIR + '/' + img_name + '.txt'\n        copyfile(label_sor_file, img_dict_file)\n\n\nif __name__ == '__main__':\n    # 生成标签\n    root = ANNOTATIONS_PATH\n    files = os.listdir(root)\n    for file in files:\n        print(\"file name: \", file)\n        file_xml = file.split(\".\")\n        get_xml_data(root, file_xml[0])\n\n    # 将文件进行 train 和 val 的区分\n    img_set_root = IMAGE_SET_ROOT\n    imgs_root = IMAGE_PATH\n    img_labels_root = LABELS_ROOT\n    copy_data(img_set_root, img_labels_root, imgs_root, \"train\")\n    copy_data(img_set_root, img_labels_root, imgs_root, \"val\")\n    copy_data(img_set_root, img_labels_root, imgs_root, \"test\")\n\n    # data = list(np.loadtxt(\"tt100k.txt\", dtype=str))\n    # print(data)\n    # print(len(data))\n```\n\n新生成的数据集再`E:/holiday/cow/cow_yolo_dataset/score/images`目录下，记住这个目录，后面的配置文件中我们将会用到。\n\n## 修改配置文件\n\n### 在data目录下设置数据集的配置文件\n\n我们在data目录下新建一个`cow_data.yaml`的文件，在文件中指定训练集和验证集的地址、数据集的目标数目和目标类名\n\n```yaml\n# Custom data for safety helmet\n\n\n# train and val data as 1) directory: path/images/, 2) file: path/images.txt, or 3) list: [path1/images/, path2/images/]\ntrain: /data1/scm/2021/com/cow/cow_yolo_dataset/score/images/train\nval: /data1/scm/2021/com/cow/cow_yolo_dataset/score/images/val\n\n# number of classes\nnc: 5\n\n# class names\nnames: [\"belt\", \"sunglasses\", \"boot\", \"cowboy_hat\", \"jacket\"]\n```\n\n### 在models目录下设置模型的配置文件\n\n在models目录下新建一个`cow_yolov5l.yaml`的模型配置文件，具体内容如下\n\n```yaml\n# parameters\nnc: 5  # number of classes # 修改为我们模型的类别数目\ndepth_multiple: 1.0  # model depth multiple\nwidth_multiple: 1.0  # layer channel multiple\n\n# anchors\nanchors:\n  - [10,13, 16,30, 33,23]  # P3/8\n  - [30,61, 62,45, 59,119]  # P4/16\n  - [116,90, 156,198, 373,326]  # P5/32\n\n# YOLOv5 backbone\nbackbone:\n  # [from, number, module, args]\n  [[-1, 1, Focus, [64, 3]],  # 0-P1/2\n   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4\n   [-1, 3, C3, [128]],\n   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8\n   [-1, 9, C3, [256]],\n   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16\n   [-1, 9, C3, [512]],\n   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32\n   [-1, 1, SPP, [1024, [5, 9, 13]]],\n   [-1, 3, C3, [1024, False]],  # 9\n  ]\n\n# YOLOv5 head\nhead:\n  [[-1, 1, Conv, [512, 1, 1]],\n   [-1, 1, nn.Upsample, [None, 2, 'nearest']],\n   [[-1, 6], 1, Concat, [1]],  # cat backbone P4\n   [-1, 3, C3, [512, False]],  # 13\n\n   [-1, 1, Conv, [256, 1, 1]],\n   [-1, 1, nn.Upsample, [None, 2, 'nearest']],\n   [[-1, 4], 1, Concat, [1]],  # cat backbone P3\n   [-1, 3, C3, [256, False]],  # 17 (P3/8-small)\n\n   [-1, 1, Conv, [256, 3, 2]],\n   [[-1, 14], 1, Concat, [1]],  # cat head P4\n   [-1, 3, C3, [512, False]],  # 20 (P4/16-medium)\n\n   [-1, 1, Conv, [512, 3, 2]],\n   [[-1, 10], 1, Concat, [1]],  # cat head P5\n   [-1, 3, C3, [1024, False]],  # 23 (P5/32-large)\n\n   [[17, 20, 23], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5)\n  ]\n```\n\n\n\n## 训练模型\n\n首先从https://github.com/ultralytics/yolov5/releases下载预训练的模型到本地，存放在yolov5的weights目录下。\n\n上面的过程中我们已经设置好了数据集和模型的配置文件，我们只需要在项目的根目录下执行下列代码即可开始训练，由于我的显卡只有8G，所以这里的batchsize设置为4，训练的轮数设置为200，具体的命令如下：\n\n```bash\npython train.py --data data/cow_data.yaml --cfg models/cow_yolov5l.yaml --weights weights/yolov5l.pt --batch-size 4 --epochs 100\n```\n\n训练的结果会保存在`runs/train/exp`目录下，其中有各种可视化的过程图，weights目录下是训练好的权重文件，也就是我们最后需要的模型。\n\n训练的详细代码如下：\n\n```python\nimport argparse\nimport logging\nimport math\nimport os\nimport random\nimport time\nimport warnings\nfrom copy import deepcopy\nfrom pathlib import Path\nfrom threading import Thread\n\nimport numpy as np\nimport torch.distributed as dist\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nimport torch.optim.lr_scheduler as lr_scheduler\nimport torch.utils.data\nimport yaml\nfrom torch.cuda import amp\nfrom torch.nn.parallel import DistributedDataParallel as DDP\nfrom torch.utils.tensorboard import SummaryWriter\nfrom tqdm import tqdm\n\nimport test  # import test.py to get mAP after each epoch\nfrom models.experimental import attempt_load\nfrom models.yolo import Model\nfrom utils.autoanchor import check_anchors\nfrom utils.datasets import create_dataloader\nfrom utils.general import labels_to_class_weights, increment_path, labels_to_image_weights, init_seeds, \\\n    fitness, strip_optimizer, get_latest_run, check_dataset, check_file, check_git_status, check_img_size, \\\n    check_requirements, print_mutation, set_logging, one_cycle, colorstr\nfrom utils.google_utils import attempt_download\nfrom utils.loss import ComputeLoss\nfrom utils.plots import plot_images, plot_labels, plot_results, plot_evolution\nfrom utils.torch_utils import ModelEMA, select_device, intersect_dicts, torch_distributed_zero_first, de_parallel\nfrom utils.wandb_logging.wandb_utils import WandbLogger, check_wandb_resume\n\nlogger = logging.getLogger(__name__)\n\n\ndef train(hyp, opt, device, tb_writer=None):\n    logger.info(colorstr('hyperparameters: ') + ', '.join(f'{k}={v}' for k, v in hyp.items()))\n    save_dir, epochs, batch_size, total_batch_size, weights, rank = \\\n        Path(opt.save_dir), opt.epochs, opt.batch_size, opt.total_batch_size, opt.weights, opt.global_rank\n\n    # Directories\n    wdir = save_dir / 'weights'\n    wdir.mkdir(parents=True, exist_ok=True)  # make dir\n    last = wdir / 'last.pt'\n    best = wdir / 'best.pt'\n    results_file = save_dir / 'results.txt'\n\n    # Save run settings\n    with open(save_dir / 'hyp.yaml', 'w') as f:\n        yaml.safe_dump(hyp, f, sort_keys=False)\n    with open(save_dir / 'opt.yaml', 'w') as f:\n        yaml.safe_dump(vars(opt), f, sort_keys=False)\n\n    # Configure\n    plots = not opt.evolve  # create plots\n    cuda = device.type != 'cpu'\n    init_seeds(2 + rank)\n    with open(opt.data) as f:\n        data_dict = yaml.safe_load(f)  # data dict\n\n    # Logging- Doing this before checking the dataset. Might update data_dict\n    loggers = {'wandb': None}  # loggers dict\n    if rank in [-1, 0]:\n        opt.hyp = hyp  # add hyperparameters\n        run_id = torch.load(weights).get('wandb_id') if weights.endswith('.pt') and os.path.isfile(weights) else None\n        wandb_logger = WandbLogger(opt, save_dir.stem, run_id, data_dict)\n        loggers['wandb'] = wandb_logger.wandb\n        data_dict = wandb_logger.data_dict\n        if wandb_logger.wandb:\n            weights, epochs, hyp = opt.weights, opt.epochs, opt.hyp  # WandbLogger might update weights, epochs if resuming\n\n    nc = 1 if opt.single_cls else int(data_dict['nc'])  # number of classes\n    names = ['item'] if opt.single_cls and len(data_dict['names']) != 1 else data_dict['names']  # class names\n    assert len(names) == nc, '%g names found for nc=%g dataset in %s' % (len(names), nc, opt.data)  # check\n    is_coco = opt.data.endswith('coco.yaml') and nc == 80  # COCO dataset\n\n    # Model\n    pretrained = weights.endswith('.pt')\n    if pretrained:\n        with torch_distributed_zero_first(rank):\n            weights = attempt_download(weights)  # download if not found locally\n        ckpt = torch.load(weights, map_location=device)  # load checkpoint\n        model = Model(opt.cfg or ckpt['model'].yaml, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device)  # create\n        exclude = ['anchor'] if (opt.cfg or hyp.get('anchors')) and not opt.resume else []  # exclude keys\n        state_dict = ckpt['model'].float().state_dict()  # to FP32\n        state_dict = intersect_dicts(state_dict, model.state_dict(), exclude=exclude)  # intersect\n        model.load_state_dict(state_dict, strict=False)  # load\n        logger.info('Transferred %g/%g items from %s' % (len(state_dict), len(model.state_dict()), weights))  # report\n    else:\n        model = Model(opt.cfg, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device)  # create\n    with torch_distributed_zero_first(rank):\n        check_dataset(data_dict)  # check\n    train_path = data_dict['train']\n    test_path = data_dict['val']\n\n    # Freeze\n    freeze = []  # parameter names to freeze (full or partial)\n    for k, v in model.named_parameters():\n        v.requires_grad = True  # train all layers\n        if any(x in k for x in freeze):\n            print('freezing %s' % k)\n            v.requires_grad = False\n\n    # Optimizer\n    nbs = 64  # nominal batch size\n    accumulate = max(round(nbs / total_batch_size), 1)  # accumulate loss before optimizing\n    hyp['weight_decay'] *= total_batch_size * accumulate / nbs  # scale weight_decay\n    logger.info(f\"Scaled weight_decay = {hyp['weight_decay']}\")\n\n    pg0, pg1, pg2 = [], [], []  # optimizer parameter groups\n    for k, v in model.named_modules():\n        if hasattr(v, 'bias') and isinstance(v.bias, nn.Parameter):\n            pg2.append(v.bias)  # biases\n        if isinstance(v, nn.BatchNorm2d):\n            pg0.append(v.weight)  # no decay\n        elif hasattr(v, 'weight') and isinstance(v.weight, nn.Parameter):\n            pg1.append(v.weight)  # apply decay\n\n    if opt.adam:\n        optimizer = optim.Adam(pg0, lr=hyp['lr0'], betas=(hyp['momentum'], 0.999))  # adjust beta1 to momentum\n    else:\n        optimizer = optim.SGD(pg0, lr=hyp['lr0'], momentum=hyp['momentum'], nesterov=True)\n\n    optimizer.add_param_group({'params': pg1, 'weight_decay': hyp['weight_decay']})  # add pg1 with weight_decay\n    optimizer.add_param_group({'params': pg2})  # add pg2 (biases)\n    logger.info('Optimizer groups: %g .bias, %g conv.weight, %g other' % (len(pg2), len(pg1), len(pg0)))\n    del pg0, pg1, pg2\n\n    # Scheduler https://arxiv.org/pdf/1812.01187.pdf\n    # https://pytorch.org/docs/stable/_modules/torch/optim/lr_scheduler.html#OneCycleLR\n    if opt.linear_lr:\n        lf = lambda x: (1 - x / (epochs - 1)) * (1.0 - hyp['lrf']) + hyp['lrf']  # linear\n    else:\n        lf = one_cycle(1, hyp['lrf'], epochs)  # cosine 1->hyp['lrf']\n    scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)\n    # plot_lr_scheduler(optimizer, scheduler, epochs)\n\n    # EMA\n    ema = ModelEMA(model) if rank in [-1, 0] else None\n\n    # Resume\n    start_epoch, best_fitness = 0, 0.0\n    if pretrained:\n        # Optimizer\n        if ckpt['optimizer'] is not None:\n            optimizer.load_state_dict(ckpt['optimizer'])\n            best_fitness = ckpt['best_fitness']\n\n        # EMA\n        if ema and ckpt.get('ema'):\n            ema.ema.load_state_dict(ckpt['ema'].float().state_dict())\n            ema.updates = ckpt['updates']\n\n        # Results\n        if ckpt.get('training_results') is not None:\n            results_file.write_text(ckpt['training_results'])  # write results.txt\n\n        # Epochs\n        start_epoch = ckpt['epoch'] + 1\n        if opt.resume:\n            assert start_epoch > 0, '%s training to %g epochs is finished, nothing to resume.' % (weights, epochs)\n        if epochs < start_epoch:\n            logger.info('%s has been trained for %g epochs. Fine-tuning for %g additional epochs.' %\n                        (weights, ckpt['epoch'], epochs))\n            epochs += ckpt['epoch']  # finetune additional epochs\n\n        del ckpt, state_dict\n\n    # Image sizes\n    gs = max(int(model.stride.max()), 32)  # grid size (max stride)\n    nl = model.model[-1].nl  # number of detection layers (used for scaling hyp['obj'])\n    imgsz, imgsz_test = [check_img_size(x, gs) for x in opt.img_size]  # verify imgsz are gs-multiples\n\n    # DP mode\n    if cuda and rank == -1 and torch.cuda.device_count() > 1:\n        model = torch.nn.DataParallel(model)\n\n    # SyncBatchNorm\n    if opt.sync_bn and cuda and rank != -1:\n        model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model).to(device)\n        logger.info('Using SyncBatchNorm()')\n\n    # Trainloader\n    dataloader, dataset = create_dataloader(train_path, imgsz, batch_size, gs, opt,\n                                            hyp=hyp, augment=True, cache=opt.cache_images, rect=opt.rect, rank=rank,\n                                            world_size=opt.world_size, workers=opt.workers,\n                                            image_weights=opt.image_weights, quad=opt.quad, prefix=colorstr('train: '))\n    mlc = np.concatenate(dataset.labels, 0)[:, 0].max()  # max label class\n    nb = len(dataloader)  # number of batches\n    assert mlc < nc, 'Label class %g exceeds nc=%g in %s. Possible class labels are 0-%g' % (mlc, nc, opt.data, nc - 1)\n\n    # Process 0\n    if rank in [-1, 0]:\n        testloader = create_dataloader(test_path, imgsz_test, batch_size * 2, gs, opt,  # testloader\n                                       hyp=hyp, cache=opt.cache_images and not opt.notest, rect=True, rank=-1,\n                                       world_size=opt.world_size, workers=opt.workers,\n                                       pad=0.5, prefix=colorstr('val: '))[0]\n\n        if not opt.resume:\n            labels = np.concatenate(dataset.labels, 0)\n            c = torch.tensor(labels[:, 0])  # classes\n            # cf = torch.bincount(c.long(), minlength=nc) + 1.  # frequency\n            # model._initialize_biases(cf.to(device))\n            if plots:\n                plot_labels(labels, names, save_dir, loggers)\n                if tb_writer:\n                    tb_writer.add_histogram('classes', c, 0)\n\n            # Anchors\n            if not opt.noautoanchor:\n                check_anchors(dataset, model=model, thr=hyp['anchor_t'], imgsz=imgsz)\n            model.half().float()  # pre-reduce anchor precision\n\n    # DDP mode\n    if cuda and rank != -1:\n        model = DDP(model, device_ids=[opt.local_rank], output_device=opt.local_rank,\n                    # nn.MultiheadAttention incompatibility with DDP https://github.com/pytorch/pytorch/issues/26698\n                    find_unused_parameters=any(isinstance(layer, nn.MultiheadAttention) for layer in model.modules()))\n\n    # Model parameters\n    hyp['box'] *= 3. / nl  # scale to layers\n    hyp['cls'] *= nc / 80. * 3. / nl  # scale to classes and layers\n    hyp['obj'] *= (imgsz / 640) ** 2 * 3. / nl  # scale to image size and layers\n    hyp['label_smoothing'] = opt.label_smoothing\n    model.nc = nc  # attach number of classes to model\n    model.hyp = hyp  # attach hyperparameters to model\n    model.gr = 1.0  # iou loss ratio (obj_loss = 1.0 or iou)\n    model.class_weights = labels_to_class_weights(dataset.labels, nc).to(device) * nc  # attach class weights\n    model.names = names\n\n    # Start training\n    t0 = time.time()\n    nw = max(round(hyp['warmup_epochs'] * nb), 1000)  # number of warmup iterations, max(3 epochs, 1k iterations)\n    # nw = min(nw, (epochs - start_epoch) / 2 * nb)  # limit warmup to < 1/2 of training\n    maps = np.zeros(nc)  # mAP per class\n    results = (0, 0, 0, 0, 0, 0, 0)  # P, R, mAP@.5, mAP@.5-.95, val_loss(box, obj, cls)\n    scheduler.last_epoch = start_epoch - 1  # do not move\n    scaler = amp.GradScaler(enabled=cuda)\n    compute_loss = ComputeLoss(model)  # init loss class\n    logger.info(f'Image sizes {imgsz} train, {imgsz_test} test\\n'\n                f'Using {dataloader.num_workers} dataloader workers\\n'\n                f'Logging results to {save_dir}\\n'\n                f'Starting training for {epochs} epochs...')\n    for epoch in range(start_epoch, epochs):  # epoch ------------------------------------------------------------------\n        model.train()\n\n        # Update image weights (optional)\n        if opt.image_weights:\n            # Generate indices\n            if rank in [-1, 0]:\n                cw = model.class_weights.cpu().numpy() * (1 - maps) ** 2 / nc  # class weights\n                iw = labels_to_image_weights(dataset.labels, nc=nc, class_weights=cw)  # image weights\n                dataset.indices = random.choices(range(dataset.n), weights=iw, k=dataset.n)  # rand weighted idx\n            # Broadcast if DDP\n            if rank != -1:\n                indices = (torch.tensor(dataset.indices) if rank == 0 else torch.zeros(dataset.n)).int()\n                dist.broadcast(indices, 0)\n                if rank != 0:\n                    dataset.indices = indices.cpu().numpy()\n\n        # Update mosaic border\n        # b = int(random.uniform(0.25 * imgsz, 0.75 * imgsz + gs) // gs * gs)\n        # dataset.mosaic_border = [b - imgsz, -b]  # height, width borders\n\n        mloss = torch.zeros(4, device=device)  # mean losses\n        if rank != -1:\n            dataloader.sampler.set_epoch(epoch)\n        pbar = enumerate(dataloader)\n        logger.info(('\\n' + '%10s' * 8) % ('Epoch', 'gpu_mem', 'box', 'obj', 'cls', 'total', 'labels', 'img_size'))\n        if rank in [-1, 0]:\n            pbar = tqdm(pbar, total=nb)  # progress bar\n        optimizer.zero_grad()\n        for i, (imgs, targets, paths, _) in pbar:  # batch -------------------------------------------------------------\n            ni = i + nb * epoch  # number integrated batches (since train start)\n            imgs = imgs.to(device, non_blocking=True).float() / 255.0  # uint8 to float32, 0-255 to 0.0-1.0\n\n            # Warmup\n            if ni <= nw:\n                xi = [0, nw]  # x interp\n                # model.gr = np.interp(ni, xi, [0.0, 1.0])  # iou loss ratio (obj_loss = 1.0 or iou)\n                accumulate = max(1, np.interp(ni, xi, [1, nbs / total_batch_size]).round())\n                for j, x in enumerate(optimizer.param_groups):\n                    # bias lr falls from 0.1 to lr0, all other lrs rise from 0.0 to lr0\n                    x['lr'] = np.interp(ni, xi, [hyp['warmup_bias_lr'] if j == 2 else 0.0, x['initial_lr'] * lf(epoch)])\n                    if 'momentum' in x:\n                        x['momentum'] = np.interp(ni, xi, [hyp['warmup_momentum'], hyp['momentum']])\n\n            # Multi-scale\n            if opt.multi_scale:\n                sz = random.randrange(imgsz * 0.5, imgsz * 1.5 + gs) // gs * gs  # size\n                sf = sz / max(imgs.shape[2:])  # scale factor\n                if sf != 1:\n                    ns = [math.ceil(x * sf / gs) * gs for x in imgs.shape[2:]]  # new shape (stretched to gs-multiple)\n                    imgs = F.interpolate(imgs, size=ns, mode='bilinear', align_corners=False)\n\n            # Forward\n            with amp.autocast(enabled=cuda):\n                pred = model(imgs)  # forward\n                loss, loss_items = compute_loss(pred, targets.to(device))  # loss scaled by batch_size\n                if rank != -1:\n                    loss *= opt.world_size  # gradient averaged between devices in DDP mode\n                if opt.quad:\n                    loss *= 4.\n\n            # Backward\n            scaler.scale(loss).backward()\n\n            # Optimize\n            if ni % accumulate == 0:\n                scaler.step(optimizer)  # optimizer.step\n                scaler.update()\n                optimizer.zero_grad()\n                if ema:\n                    ema.update(model)\n\n            # Print\n            if rank in [-1, 0]:\n                mloss = (mloss * i + loss_items) / (i + 1)  # update mean losses\n                mem = '%.3gG' % (torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0)  # (GB)\n                s = ('%10s' * 2 + '%10.4g' * 6) % (\n                    f'{epoch}/{epochs - 1}', mem, *mloss, targets.shape[0], imgs.shape[-1])\n                pbar.set_description(s)\n\n                # Plot\n                if plots and ni < 3:\n                    f = save_dir / f'train_batch{ni}.jpg'  # filename\n                    Thread(target=plot_images, args=(imgs, targets, paths, f), daemon=True).start()\n                    if tb_writer and ni == 0:\n                        with warnings.catch_warnings():\n                            warnings.simplefilter('ignore')  # suppress jit trace warning\n                            tb_writer.add_graph(torch.jit.trace(de_parallel(model), imgs, strict=False), [])  # graph\n                elif plots and ni == 10 and wandb_logger.wandb:\n                    wandb_logger.log({'Mosaics': [wandb_logger.wandb.Image(str(x), caption=x.name) for x in\n                                                  save_dir.glob('train*.jpg') if x.exists()]})\n\n            # end batch ------------------------------------------------------------------------------------------------\n        # end epoch ----------------------------------------------------------------------------------------------------\n\n        # Scheduler\n        lr = [x['lr'] for x in optimizer.param_groups]  # for tensorboard\n        scheduler.step()\n\n        # DDP process 0 or single-GPU\n        if rank in [-1, 0]:\n            # mAP\n            ema.update_attr(model, include=['yaml', 'nc', 'hyp', 'gr', 'names', 'stride', 'class_weights'])\n            final_epoch = epoch + 1 == epochs\n            if not opt.notest or final_epoch:  # Calculate mAP\n                wandb_logger.current_epoch = epoch + 1\n                results, maps, times = test.test(data_dict,\n                                                 batch_size=batch_size * 2,\n                                                 imgsz=imgsz_test,\n                                                 model=ema.ema,\n                                                 single_cls=opt.single_cls,\n                                                 dataloader=testloader,\n                                                 save_dir=save_dir,\n                                                 save_json=is_coco and final_epoch,\n                                                 verbose=nc < 50 and final_epoch,\n                                                 plots=plots and final_epoch,\n                                                 wandb_logger=wandb_logger,\n                                                 compute_loss=compute_loss,\n                                                 is_coco=is_coco)\n\n            # Write\n            with open(results_file, 'a') as f:\n                f.write(s + '%10.4g' * 7 % results + '\\n')  # append metrics, val_loss\n\n            # Log\n            tags = ['train/box_loss', 'train/obj_loss', 'train/cls_loss',  # train loss\n                    'metrics/precision', 'metrics/recall', 'metrics/mAP_0.5', 'metrics/mAP_0.5:0.95',\n                    'val/box_loss', 'val/obj_loss', 'val/cls_loss',  # val loss\n                    'x/lr0', 'x/lr1', 'x/lr2']  # params\n            for x, tag in zip(list(mloss[:-1]) + list(results) + lr, tags):\n                if tb_writer:\n                    tb_writer.add_scalar(tag, x, epoch)  # tensorboard\n                if wandb_logger.wandb:\n                    wandb_logger.log({tag: x})  # W&B\n\n            # Update best mAP\n            fi = fitness(np.array(results).reshape(1, -1))  # weighted combination of [P, R, mAP@.5, mAP@.5-.95]\n            if fi > best_fitness:\n                best_fitness = fi\n            wandb_logger.end_epoch(best_result=best_fitness == fi)\n\n            # Save model\n            if (not opt.nosave) or (final_epoch and not opt.evolve):  # if save\n                ckpt = {'epoch': epoch,\n                        'best_fitness': best_fitness,\n                        'training_results': results_file.read_text(),\n                        'model': deepcopy(de_parallel(model)).half(),\n                        'ema': deepcopy(ema.ema).half(),\n                        'updates': ema.updates,\n                        'optimizer': optimizer.state_dict(),\n                        'wandb_id': wandb_logger.wandb_run.id if wandb_logger.wandb else None}\n\n                # Save last, best and delete\n                torch.save(ckpt, last)\n                if best_fitness == fi:\n                    torch.save(ckpt, best)\n                if wandb_logger.wandb:\n                    if ((epoch + 1) % opt.save_period == 0 and not final_epoch) and opt.save_period != -1:\n                        wandb_logger.log_model(\n                            last.parent, opt, epoch, fi, best_model=best_fitness == fi)\n                del ckpt\n\n        # end epoch ----------------------------------------------------------------------------------------------------\n    # end training\n    if rank in [-1, 0]:\n        logger.info(f'{epoch - start_epoch + 1} epochs completed in {(time.time() - t0) / 3600:.3f} hours.\\n')\n        if plots:\n            plot_results(save_dir=save_dir)  # save as results.png\n            if wandb_logger.wandb:\n                files = ['results.png', 'confusion_matrix.png', *[f'{x}_curve.png' for x in ('F1', 'PR', 'P', 'R')]]\n                wandb_logger.log({\"Results\": [wandb_logger.wandb.Image(str(save_dir / f), caption=f) for f in files\n                                              if (save_dir / f).exists()]})\n\n        if not opt.evolve:\n            if is_coco:  # COCO dataset\n                for m in [last, best] if best.exists() else [last]:  # speed, mAP tests\n                    results, _, _ = test.test(opt.data,\n                                              batch_size=batch_size * 2,\n                                              imgsz=imgsz_test,\n                                              conf_thres=0.001,\n                                              iou_thres=0.7,\n                                              model=attempt_load(m, device).half(),\n                                              single_cls=opt.single_cls,\n                                              dataloader=testloader,\n                                              save_dir=save_dir,\n                                              save_json=True,\n                                              plots=False,\n                                              is_coco=is_coco)\n\n            # Strip optimizers\n            for f in last, best:\n                if f.exists():\n                    strip_optimizer(f)  # strip optimizers\n            if wandb_logger.wandb:  # Log the stripped model\n                wandb_logger.wandb.log_artifact(str(best if best.exists() else last), type='model',\n                                                name='run_' + wandb_logger.wandb_run.id + '_model',\n                                                aliases=['latest', 'best', 'stripped'])\n        wandb_logger.finish_run()\n    else:\n        dist.destroy_process_group()\n    torch.cuda.empty_cache()\n    return results\n\n\n# CUDA_VISIBLE_DEVICES=0 python train.py --data data/cow_data.yaml --cfg models/cow_yolov5l.yaml --weights weights/yolov5l.pt --batch-size 4 --epochs 100\n# CUDA_VISIBLE_DEVICES=0 nohup python train.py --data data/cow_data.yaml --cfg models/cow_yolov5l_anchor.yaml --weights weights/yolov5l.pt --batch-size 4 --epochs 100 &\n# python train.py --data data/cow_data.yaml --cfg models/cow_yolov5l_anchor.yaml --weights weights/yolov5l.pt --batch-size 8 --epochs 200 --device 1\nif __name__ == '__main__':\n    parser = argparse.ArgumentParser()\n    parser.add_argument('--weights', type=str, default='yolov5s.pt', help='initial weights path')\n    parser.add_argument('--cfg', type=str, default='', help='model.yaml path')\n    parser.add_argument('--data', type=str, default='data/coco128.yaml', help='data.yaml path')\n    parser.add_argument('--hyp', type=str, default='data/hyp.scratch.yaml', help='hyperparameters path')\n    parser.add_argument('--epochs', type=int, default=300)\n    parser.add_argument('--batch-size', type=int, default=16, help='total batch size for all GPUs')\n    parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='[train, test] image sizes')\n    parser.add_argument('--rect', action='store_true', help='rectangular training')\n    parser.add_argument('--resume', nargs='?', const=True, default=False, help='resume most recent training')\n    parser.add_argument('--nosave', action='store_true', help='only save final checkpoint')\n    parser.add_argument('--notest', action='store_true', help='only test final epoch')\n    parser.add_argument('--noautoanchor', action='store_true', help='disable autoanchor check')\n    parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters')\n    parser.add_argument('--bucket', type=str, default='', help='gsutil bucket')\n    parser.add_argument('--cache-images', action='store_true', help='cache images for faster training')\n    parser.add_argument('--image-weights', action='store_true', help='use weighted image selection for training')\n    parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')\n    parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%')\n    parser.add_argument('--single-cls', action='store_true', help='train multi-class data as single-class')\n    parser.add_argument('--adam', action='store_true', help='use torch.optim.Adam() optimizer')\n    parser.add_argument('--sync-bn', action='store_true', help='use SyncBatchNorm, only available in DDP mode')\n    parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify')\n    parser.add_argument('--workers', type=int, default=8, help='maximum number of dataloader workers')\n    parser.add_argument('--project', default='runs/train', help='save to project/name')\n    parser.add_argument('--entity', default=None, help='W&B entity')\n    parser.add_argument('--name', default='exp', help='save to project/name')\n    parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')\n    parser.add_argument('--quad', action='store_true', help='quad dataloader')\n    parser.add_argument('--linear-lr', action='store_true', help='linear LR')\n    parser.add_argument('--label-smoothing', type=float, default=0.0, help='Label smoothing epsilon')\n    parser.add_argument('--upload_dataset', action='store_true', help='Upload dataset as W&B artifact table')\n    parser.add_argument('--bbox_interval', type=int, default=-1, help='Set bounding-box image logging interval for W&B')\n    parser.add_argument('--save_period', type=int, default=-1, help='Log model after every \"save_period\" epoch')\n    parser.add_argument('--artifact_alias', type=str, default=\"latest\", help='version of dataset artifact to be used')\n    opt = parser.parse_args()\n\n    # Set DDP variables\n    opt.world_size = int(os.environ['WORLD_SIZE']) if 'WORLD_SIZE' in os.environ else 1\n    opt.global_rank = int(os.environ['RANK']) if 'RANK' in os.environ else -1\n    set_logging(opt.global_rank)\n    if opt.global_rank in [-1, 0]:\n        check_git_status()\n        check_requirements(exclude=('pycocotools', 'thop'))\n\n    # Resume\n    wandb_run = check_wandb_resume(opt)\n    if opt.resume and not wandb_run:  # resume an interrupted run\n        ckpt = opt.resume if isinstance(opt.resume, str) else get_latest_run()  # specified or most recent path\n        assert os.path.isfile(ckpt), 'ERROR: --resume checkpoint does not exist'\n        apriori = opt.global_rank, opt.local_rank\n        with open(Path(ckpt).parent.parent / 'opt.yaml') as f:\n            opt = argparse.Namespace(**yaml.safe_load(f))  # replace\n        opt.cfg, opt.weights, opt.resume, opt.batch_size, opt.global_rank, opt.local_rank = \\\n            '', ckpt, True, opt.total_batch_size, *apriori  # reinstate\n        logger.info('Resuming training from %s' % ckpt)\n    else:\n        # opt.hyp = opt.hyp or ('hyp.finetune.yaml' if opt.weights else 'hyp.scratch.yaml')\n        opt.data, opt.cfg, opt.hyp = check_file(opt.data), check_file(opt.cfg), check_file(opt.hyp)  # check files\n        assert len(opt.cfg) or len(opt.weights), 'either --cfg or --weights must be specified'\n        opt.img_size.extend([opt.img_size[-1]] * (2 - len(opt.img_size)))  # extend to 2 sizes (train, test)\n        opt.name = 'evolve' if opt.evolve else opt.name\n        opt.save_dir = str(increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok | opt.evolve))\n\n    # DDP mode\n    opt.total_batch_size = opt.batch_size\n    device = select_device(opt.device, batch_size=opt.batch_size)\n    if opt.local_rank != -1:\n        assert torch.cuda.device_count() > opt.local_rank\n        torch.cuda.set_device(opt.local_rank)\n        device = torch.device('cuda', opt.local_rank)\n        dist.init_process_group(backend='nccl', init_method='env://')  # distributed backend\n        assert opt.batch_size % opt.world_size == 0, '--batch-size must be multiple of CUDA device count'\n        assert not opt.image_weights, '--image-weights argument is not compatible with DDP training'\n        opt.batch_size = opt.total_batch_size // opt.world_size\n\n    # Hyperparameters\n    with open(opt.hyp) as f:\n        hyp = yaml.safe_load(f)  # load hyps\n\n    # Train\n    logger.info(opt)\n    if not opt.evolve:\n        tb_writer = None  # init loggers\n        if opt.global_rank in [-1, 0]:\n            prefix = colorstr('tensorboard: ')\n            logger.info(f\"{prefix}Start with 'tensorboard --logdir {opt.project}', view at http://localhost:6006/\")\n            tb_writer = SummaryWriter(opt.save_dir)  # Tensorboard\n        train(hyp, opt, device, tb_writer)\n\n    # Evolve hyperparameters (optional)\n    else:\n        # Hyperparameter evolution metadata (mutation scale 0-1, lower_limit, upper_limit)\n        meta = {'lr0': (1, 1e-5, 1e-1),  # initial learning rate (SGD=1E-2, Adam=1E-3)\n                'lrf': (1, 0.01, 1.0),  # final OneCycleLR learning rate (lr0 * lrf)\n                'momentum': (0.3, 0.6, 0.98),  # SGD momentum/Adam beta1\n                'weight_decay': (1, 0.0, 0.001),  # optimizer weight decay\n                'warmup_epochs': (1, 0.0, 5.0),  # warmup epochs (fractions ok)\n                'warmup_momentum': (1, 0.0, 0.95),  # warmup initial momentum\n                'warmup_bias_lr': (1, 0.0, 0.2),  # warmup initial bias lr\n                'box': (1, 0.02, 0.2),  # box loss gain\n                'cls': (1, 0.2, 4.0),  # cls loss gain\n                'cls_pw': (1, 0.5, 2.0),  # cls BCELoss positive_weight\n                'obj': (1, 0.2, 4.0),  # obj loss gain (scale with pixels)\n                'obj_pw': (1, 0.5, 2.0),  # obj BCELoss positive_weight\n                'iou_t': (0, 0.1, 0.7),  # IoU training threshold\n                'anchor_t': (1, 2.0, 8.0),  # anchor-multiple threshold\n                'anchors': (2, 2.0, 10.0),  # anchors per output grid (0 to ignore)\n                'fl_gamma': (0, 0.0, 2.0),  # focal loss gamma (efficientDet default gamma=1.5)\n                'hsv_h': (1, 0.0, 0.1),  # image HSV-Hue augmentation (fraction)\n                'hsv_s': (1, 0.0, 0.9),  # image HSV-Saturation augmentation (fraction)\n                'hsv_v': (1, 0.0, 0.9),  # image HSV-Value augmentation (fraction)\n                'degrees': (1, 0.0, 45.0),  # image rotation (+/- deg)\n                'translate': (1, 0.0, 0.9),  # image translation (+/- fraction)\n                'scale': (1, 0.0, 0.9),  # image scale (+/- gain)\n                'shear': (1, 0.0, 10.0),  # image shear (+/- deg)\n                'perspective': (0, 0.0, 0.001),  # image perspective (+/- fraction), range 0-0.001\n                'flipud': (1, 0.0, 1.0),  # image flip up-down (probability)\n                'fliplr': (0, 0.0, 1.0),  # image flip left-right (probability)\n                'mosaic': (1, 0.0, 1.0),  # image mixup (probability)\n                'mixup': (1, 0.0, 1.0)}  # image mixup (probability)\n\n        assert opt.local_rank == -1, 'DDP mode not implemented for --evolve'\n        opt.notest, opt.nosave = True, True  # only test/save final epoch\n        # ei = [isinstance(x, (int, float)) for x in hyp.values()]  # evolvable indices\n        yaml_file = Path(opt.save_dir) / 'hyp_evolved.yaml'  # save best result here\n        if opt.bucket:\n            os.system('gsutil cp gs://%s/evolve.txt .' % opt.bucket)  # download evolve.txt if exists\n\n        for _ in range(300):  # generations to evolve\n            if Path('evolve.txt').exists():  # if evolve.txt exists: select best hyps and mutate\n                # Select parent(s)\n                parent = 'single'  # parent selection method: 'single' or 'weighted'\n                x = np.loadtxt('evolve.txt', ndmin=2)\n                n = min(5, len(x))  # number of previous results to consider\n                x = x[np.argsort(-fitness(x))][:n]  # top n mutations\n                w = fitness(x) - fitness(x).min()  # weights\n                if parent == 'single' or len(x) == 1:\n                    # x = x[random.randint(0, n - 1)]  # random selection\n                    x = x[random.choices(range(n), weights=w)[0]]  # weighted selection\n                elif parent == 'weighted':\n                    x = (x * w.reshape(n, 1)).sum(0) / w.sum()  # weighted combination\n\n                # Mutate\n                mp, s = 0.8, 0.2  # mutation probability, sigma\n                npr = np.random\n                npr.seed(int(time.time()))\n                g = np.array([x[0] for x in meta.values()])  # gains 0-1\n                ng = len(meta)\n                v = np.ones(ng)\n                while all(v == 1):  # mutate until a change occurs (prevent duplicates)\n                    v = (g * (npr.random(ng) < mp) * npr.randn(ng) * npr.random() * s + 1).clip(0.3, 3.0)\n                for i, k in enumerate(hyp.keys()):  # plt.hist(v.ravel(), 300)\n                    hyp[k] = float(x[i + 7] * v[i])  # mutate\n\n            # Constrain to limits\n            for k, v in meta.items():\n                hyp[k] = max(hyp[k], v[1])  # lower limit\n                hyp[k] = min(hyp[k], v[2])  # upper limit\n                hyp[k] = round(hyp[k], 5)  # significant digits\n\n            # Train mutation\n            results = train(hyp.copy(), opt, device)\n\n            # Write mutation results\n            print_mutation(hyp.copy(), results, yaml_file, opt.bucket)\n\n        # Plot results\n        plot_evolution(yaml_file)\n        print(f'Hyperparameter evolution complete. Best results saved as: {yaml_file}\\n'\n              f'Command to train a new model with these hyperparameters: $ python train.py --hyp {yaml_file}')\n\n```\n\n\n\n## 测试模型&生成提交的结果数据\n\nYolov5中自带的代码可以直接调用模型对结果进行推理，我对detect的代码进行了修改，直接运行`cow_detect_kaggle.py`就能将图片和文本文件的检测结果保存在`runs/detect`目录下，代码如下：\n\n```python\n# 用于生成txt文件\nimport argparse\nimport time\nfrom pathlib import Path\n\nimport cv2\nimport torch\nimport torch.backends.cudnn as cudnn\n\nfrom models.experimental import attempt_load\nfrom utils.datasets import LoadStreams, LoadImages\nfrom utils.general import check_img_size, check_requirements, check_imshow, non_max_suppression, apply_classifier, \\\n    scale_coords, xyxy2xywh, strip_optimizer, set_logging, increment_path, save_one_box\nfrom utils.plots import colors, plot_one_box\nfrom utils.torch_utils import select_device, load_classifier, time_synchronized\n\n\n@torch.no_grad()\ndef detect(opt):\n    source, weights, view_img, save_txt, imgsz = opt.source, opt.weights, opt.view_img, opt.save_txt, opt.img_size\n    save_img = not opt.nosave and not source.endswith('.txt')  # save inference images\n    webcam = source.isnumeric() or source.endswith('.txt') or source.lower().startswith(\n        ('rtsp://', 'rtmp://', 'http://', 'https://'))\n    save_txt = True\n    print(save_txt)\n\n    # Directories\n    save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok)  # increment run\n    (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir\n\n    # Initialize\n    set_logging()\n    device = select_device(opt.device)\n    half = opt.half and device.type != 'cpu'  # half precision only supported on CUDA\n\n    # Load model\n    model = attempt_load(weights, map_location=device)  # load FP32 model\n    stride = int(model.stride.max())  # model stride\n    imgsz = check_img_size(imgsz, s=stride)  # check img_size\n    names = model.module.names if hasattr(model, 'module') else model.names  # get class names\n    if half:\n        model.half()  # to FP16\n\n    # Second-stage classifier\n    classify = False\n    if classify:\n        modelc = load_classifier(name='resnet101', n=2)  # initialize\n        modelc.load_state_dict(torch.load('weights/resnet101.pt', map_location=device)['model']).to(device).eval()\n\n    # Set Dataloader\n    vid_path, vid_writer = None, None\n    if webcam:\n        view_img = check_imshow()\n        cudnn.benchmark = True  # set True to speed up constant image size inference\n        dataset = LoadStreams(source, img_size=imgsz, stride=stride)\n    else:\n        dataset = LoadImages(source, img_size=imgsz, stride=stride)\n\n    # Run inference\n    if device.type != 'cpu':\n        model(torch.zeros(1, 3, imgsz, imgsz).to(device).type_as(next(model.parameters())))  # run once\n    t0 = time.time()\n    for path, img, im0s, vid_cap in dataset:\n        img = torch.from_numpy(img).to(device)\n        img = img.half() if half else img.float()  # uint8 to fp16/32\n        img /= 255.0  # 0 - 255 to 0.0 - 1.0\n        if img.ndimension() == 3:\n            img = img.unsqueeze(0)\n\n        # Inference\n        t1 = time_synchronized()\n        pred = model(img, augment=opt.augment)[0]\n\n        # Apply NMS\n        pred = non_max_suppression(pred, opt.conf_thres, opt.iou_thres, opt.classes, opt.agnostic_nms,\n                                   max_det=opt.max_det)\n        t2 = time_synchronized()\n\n        # Apply Classifier\n        if classify:\n            pred = apply_classifier(pred, modelc, img, im0s)\n\n        # Process detections\n        for i, det in enumerate(pred):  # detections per image\n            if webcam:  # batch_size >= 1\n                p, s, im0, frame = path[i], f'{i}: ', im0s[i].copy(), dataset.count\n            else:\n                p, s, im0, frame = path, '', im0s.copy(), getattr(dataset, 'frame', 0)\n\n            p = Path(p)  # to Path\n            save_path = str(save_dir / p.name)  # img.jpg\n            txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}')  # img.txt\n            s += '%gx%g ' % img.shape[2:]  # print string\n            gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # normalization gain whwh\n            imc = im0.copy() if opt.save_crop else im0  # for opt.save_crop\n            if len(det):\n                # Rescale boxes from img_size to im0 size\n                det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round()\n\n                # Print results\n                for c in det[:, -1].unique():\n                    n = (det[:, -1] == c).sum()  # detections per class\n                    s += f\"{n} {names[int(c)]}{'s' * (n > 1)}, \"  # add to string\n\n                # Write results\n                for *xyxy, conf, cls in reversed(det):\n                    if save_txt:  # Write to file\n                        xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # normalized xywh\n                        line = (cls, *xywh, conf) if opt.save_conf else (cls, *xywh)  # label format\n                        with open(txt_path + '.txt', 'a') as f:\n                            f.write(('%g ' * len(line)).rstrip() % line + '\\n')\n\n                    if save_img or opt.save_crop or view_img:  # Add bbox to image\n                        c = int(cls)  # integer class\n                        label = None if opt.hide_labels else (names[c] if opt.hide_conf else f'{names[c]} {conf:.2f}')\n                        plot_one_box(xyxy, im0, label=label, color=colors(c, True), line_thickness=opt.line_thickness)\n                        if opt.save_crop:\n                            save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', BGR=True)\n\n            # Print time (inference + NMS)\n            print(f'{s}Done. ({t2 - t1:.3f}s)')\n\n            # Stream results\n            if view_img:\n                cv2.imshow(str(p), im0)\n                cv2.waitKey(1)  # 1 millisecond\n\n            # Save results (image with detections)\n            if save_img:\n                if dataset.mode == 'image':\n                    cv2.imwrite(save_path, im0)\n                else:  # 'video' or 'stream'\n                    if vid_path != save_path:  # new video\n                        vid_path = save_path\n                        if isinstance(vid_writer, cv2.VideoWriter):\n                            vid_writer.release()  # release previous video writer\n                        if vid_cap:  # video\n                            fps = vid_cap.get(cv2.CAP_PROP_FPS)\n                            w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH))\n                            h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))\n                        else:  # stream\n                            fps, w, h = 30, im0.shape[1], im0.shape[0]\n                            save_path += '.mp4'\n                        vid_writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))\n                    vid_writer.write(im0)\n\n    if save_txt or save_img:\n        s = f\"\\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}\" if save_txt else ''\n        print(f\"Results saved to {save_dir}{s}\")\n\n    print(f'Done. ({time.time() - t0:.3f}s)')\n\n\nif __name__ == '__main__':\n    parser = argparse.ArgumentParser()\n    parser.add_argument('--weights', nargs='+', type=str, default='runs/train/exp/weights/best.pt',  # 设置模型位置\n                        help='model.pt path(s)')\n    parser.add_argument('--source', type=str, default='E:/holiday/cow/cowboyoutfits/val',  # todo 设置验证集图片位置\n                        help='source')  # file/folder, 0 for webcam\n    parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')\n    parser.add_argument('--conf-thres', type=float, default=0.25, help='object confidence threshold')\n    parser.add_argument('--iou-thres', type=float, default=0.45, help='IOU threshold for NMS')\n    parser.add_argument('--max-det', type=int, default=1000, help='maximum number of detections per image')\n    parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')\n    parser.add_argument('--view-img', action='store_true', help='display results')\n    parser.add_argument('--save-txt', default=\"True\", action='store_true',\n                        help='save results to *.txt')  # todo 设置是否保存txt检测结果\n    parser.add_argument('--save-conf', default=\"True\", action='store_true',\n                        help='save confidences in --save-txt labels')  # todo 设置是否保存置信度\n    parser.add_argument('--save-crop', action='store_true', help='save cropped prediction boxes')\n    parser.add_argument('--nosave', action='store_true', help='do not save images/videos')\n    parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --class 0, or --class 0 2 3')\n    parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')\n    parser.add_argument('--augment', action='store_true', help='augmented inference')\n    parser.add_argument('--update', action='store_true', help='update all models')\n    parser.add_argument('--project', default='runs/detect', help='save results to project/name')\n    parser.add_argument('--name', default='exp', help='save results to project/name')\n    parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')\n    parser.add_argument('--line-thickness', default=3, type=int, help='bounding box thickness (pixels)')\n    parser.add_argument('--hide-labels', default=False, action='store_true', help='hide labels')\n    parser.add_argument('--hide-conf', default=False, action='store_true', help='hide confidences')\n    parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference')\n    opt = parser.parse_args()\n    print(opt)\n    check_requirements(exclude=('tensorboard', 'pycocotools', 'thop'))\n\n    if opt.update:  # update all models (to fix SourceChangeWarning)\n        for opt.weights in ['yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt']:\n            detect(opt=opt)\n            strip_optimizer(opt.weights)\n    else:\n        detect(opt=opt)\n```\n\n检测效果如下：\n\n![image-20210728160922844](https://vehicle4cm.oss-cn-beijing.aliyuncs.com/typoraimgs/image-20210728160922844.png)\n\n为了能够在竞赛的网站上进行提交，我们还需要将得到的txt格式的yolo文件转化一下，转化为竞赛网站所需的json格式，转化的代码如下：\n\n```python\nimport os\nimport pandas as pd\n# import tqdm\nfrom PIL import Image\nimport zipfile\n\nvalid_df = pd.read_csv('origin/valid.csv')\n# test_df = pd.read_csv('test.csv')\nvalid_df.head()\ncate_id_map = {87: 0, 1034: 1, 131: 2, 318: 3, 588: 4}\nPRED_PATH = \"E:/holiday/cow/yolov5_cowboy/runs/detect/exp/labels\"\nIMAGE_PATH = \"E:/holiday/cow/cowboyoutfits/val\"\n\n# list our prediction files path\nprediction_files = os.listdir(PRED_PATH)\nprint('Number of test images with detections: ', len(prediction_files))\n\n\n# convert yolo to coco annotation format\ndef yolo2cc_bbox(img_width, img_height, bbox):\n    x = (bbox[0] - bbox[2] * 0.5) * img_width\n    y = (bbox[1] - bbox[3] * 0.5) * img_height\n    w = bbox[2] * img_width\n    h = bbox[3] * img_height\n    return (x, y, w, h)\n\n\n# reverse the categories numer to the origin id\nre_cate_id_map = dict(zip(cate_id_map.values(), cate_id_map.keys()))\n\nprint(re_cate_id_map)\n\n\ndef make_submission(df, PRED_PATH, IMAGE_PATH):\n    output = []\n    # for i in tqdm(range(len(df))):\n    for i in range(len(df)):\n        print(i)\n        row = df.loc[i]\n        image_id = row['id']\n        file_name = row['file_name'].split('.')[0]\n        if f'{file_name}.txt' in prediction_files:\n            img = Image.open(f'{IMAGE_PATH}/{file_name}.jpg')\n            width, height = img.size\n            with open(f'{PRED_PATH}/{file_name}.txt', 'r') as file:\n                for line in file:\n                    preds = line.strip('\\n').split(' ')\n                    preds = list(map(float, preds))  # conver string to float\n                    print(preds)\n                    print(preds[1:-1])\n                    cc_bbox = yolo2cc_bbox(width, height, preds[1:-1])\n                    result = {\n                        'image_id': image_id,\n                        'category_id': re_cate_id_map[preds[0]],\n                        'bbox': cc_bbox,\n                        'score': preds[-1]\n                    }\n\n                    output.append(result)\n    return output\n\n\nsub_data = make_submission(valid_df, PRED_PATH, IMAGE_PATH)\n\nop_pd = pd.DataFrame(sub_data)\n\nop_pd.sample(10)\n\n\nop_pd.to_json('cow_answer/answer.json', orient='records')\nzf = zipfile.ZipFile('cow_answer/sample_answer.zip', 'w')\nzf.write('cow_answer/answer.json', 'answer.json')\nzf.close()\n```\n\n","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}}]}