Datawhale AI 夏令营 CV方向 Task02

上篇笔记(Datawhale AI 夏令营 CV方向 Task01-CSDN博客)简单介绍了赛事规则、评分标准、数据内容和baseline代码,并简要梳理了YOLO算法的基本原理和YOLOv8的新特性。通过task01的学习,初步掌握了YOLO训练本地数据集的方法,但是由于baseline中训练集过小、yolov8n参数量少等原因,提交结果的得分不高,那么本篇笔记就着力于提分思路的研究。

Baseline进阶思路

Task02的教程Docsicon-default.png?t=N7T8https://datawhaler.feishu.cn/wiki/LiZswOp27ieilak4suRcYI9Knlf?from=from_copylink其中提供了2种思路:增加训练集和增大权重

增加训练集

在生成yolo格式的数据集时,可以通过更改列表索引设置使用的视频个数:

for anno_path, video_path in zip(train_annos[:10], train_videos[:10]):
    # 上面的列表索引规定了使用的视频个数
    # 在这个例子中,使用前10个视频生成训练集
    print(video_path)
    anno_df = pd.read_json(anno_path)
    cap = cv2.VideoCapture(video_path)
    frame_idx = 0 
    while True:
        ret, frame = cap.read()
        if not ret:
            break

        img_height, img_width = frame.shape[:2]
        
        frame_anno = anno_df[anno_df['frame_id'] == frame_idx]
        cv2.imwrite('./yolo-dataset-raw/train/' + anno_path.split('/')[-1][:-5] + '_' + str(frame_idx) + '.jpg', frame)

        if len(frame_anno) != 0:
            with open('./yolo-dataset-raw/train/' + anno_path.split('/')[-1][:-5] + '_' + str(frame_idx) + '.txt', 'w') as up:
                for category, bbox in zip(frame_anno['category'].values, frame_anno['bbox'].values):
                    category_idx = category_labels.index(category)
                    
                    x_min, y_min, x_max, y_max = bbox
                    x_center = (x_min + x_max) / 2 / img_width
                    y_center = (y_min + y_max) / 2 / img_height
                    width = (x_max - x_min) / img_width
                    height = (y_max - y_min) / img_height

                    if x_center > 1:
                        print(bbox)
                    up.write(f'{category_idx} {x_center} {y_center} {width} {height}\n')
        
        frame_idx += 1

同样,在生成验证集时也可以通过这种方法增加数据量:

for anno_path, video_path in zip(train_annos[-5:], train_videos[-5:]):
    # 使用最后5个视频生成验证集
    print(video_path)
    anno_df = pd.read_json(anno_path)
    cap = cv2.VideoCapture(video_path)
    frame_idx = 0 
    while True:
        ret, frame = cap.read()
        if not ret:
            break

        img_height, img_width = frame.shape[:2]
        
        frame_anno = anno_df[anno_df['frame_id'] == frame_idx]
        cv2.imwrite('./yolo-dataset-raw/val/' + anno_path.split('/')[-1][:-5] + '_' + str(frame_idx) + '.jpg', frame)

        if len(frame_anno) != 0:
            with open('./yolo-dataset-raw/val/' + anno_path.split('/')[-1][:-5] + '_' + str(frame_idx) + '.txt', 'w') as up:
                for category, bbox in zip(frame_anno['category'].values, frame_anno['bbox'].values):
                    category_idx = category_labels.index(category)
                    
                    x_min, y_min, x_max, y_max = bbox
                    x_center = (x_min + x_max) / 2 / img_width
                    y_center = (y_min + y_max) / 2 / img_height
                    width = (x_max - x_min) / img_width
                    height = (y_max - y_min) / img_height

                    up.write(f'{category_idx} {x_center} {y_center} {width} {height}\n')
        
        frame_idx += 1

在更大的训练集上训练可以增强模型的泛化性能,避免因训练集过小而产生过拟合;采用更大的验证集从更广泛的数据样本上验证模型的性能,综合评判模型的效果,可以筛选出性能更好的模型。

增大权重

除了训练集较小而导致效果差,模型也可能因参数量小而表征能力不足,导致无法学习复杂的数据分布,所以增大权重可以增强模型性能,但这意味着更高的显存占用和更慢的训练速度。

YOLOv8提供了以下几种权重:

可以在训练时指定使用的预训练权重:

下载权重

原始为
!wget http://mirror.coggle.club/yolo/yolov8n-v8.2.0.pt -O yolov8n.pt

改为要使用的权重
!wget http://mirror.coggle.club/yolo/yolov8s-v8.2.0.pt -O yolov8s.pt
!wget http://mirror.coggle.club/yolo/yolov8m-v8.2.0.pt -O yolov8m.pt
!wget http://mirror.coggle.club/yolo/yolov8l-v8.2.0.pt -O yolov8l.pt
!wget http://mirror.coggle.club/yolo/yolov8x-v8.2.0.pt -O yolov8x.pt

修改参数

import os
os.environ["CUDA_VISIBLE_DEVICES"] = "0"

import warnings
warnings.filterwarnings('ignore')


from ultralytics import YOLO
model = YOLO("yolov8m.pt") # 在这里修改
results = model.train(data="yolo-dataset/yolo.yaml", epochs=10, imgsz=1080, batch=16)

效果

这里我使用前10个视频作为训练集,后5个视频作为验证集,yolo权重为yolov8m.pt,实测运行时batchsize最好调为16,否则很容易爆显存(评估时会有额外的显存占用)

最终得到的分数为:0.0665543815756769

虽然相比task01中的0.004783分提升了很多,但还是不太满意,总觉得YOLOv8的性能没有完全发挥,所以进行了接下来的探索。

个人思路分享

参数调整

尝试调整模型的训练参数来提升性能,主要调整数据增强参数(训练参数不敢乱动QAQ)

在 runs/detect/train 里的 args.yaml 中可以看到所有的参数:

 Task03好像对参数的含义有明确讲解,不过我在探索改进方案时没有看到task03的教程,参考的是这篇博客:关于YOLOv8自带的数据增强方式_yolov8数据增强-CSDN博客

与数据增强的相关参数如下表所示:

参数名类型默认值取值范围描述
hsv_hfloat0.0150.0 - 1.0调整图像色调,引入颜色变异性,提高不同光照下的泛化能力。
hsv_sfloat0.70.0 - 1.0调整图像饱和度,改变颜色强度,模拟不同环境条件。
hsv_vfloat0.40.0 - 1.0调整图像亮度,帮助模型在不同光照下表现良好。
degreesfloat0-180 - +180随机旋转图像,提高识别不同方向物体的能力。
translatefloat0.10.0 - 1.0平移图像,帮助模型学习检测部分可见物体。
scalefloat0.5>=0.0缩放图像,模拟物体与相机之间的不同距离。
shearfloat0-180 - +180剪切图像,模拟从不同角度观察物体的效果。
perspectivefloat00.0 - 0.001应用随机透视变换,增强模型对3D空间物体的理解能力。
flipudfloat00.0 - 1.0上下翻转图像,增加数据变异性,不影响物体特征。
fliplrfloat0.50.0 - 1.0左右翻转图像,有助于学习对称物体和增加数据集多样性。
bgrfloat00.0 - 1.0翻转图像通道从RGB到BGR,提高对通道顺序错误的鲁棒性。
mosaicfloat10.0 - 1.0合成四张图像,模拟不同场景组合和物体交互,增强复杂场景理解。
mixupfloat00.0 - 1.0混合两张图像及标签,创建合成图像,增强泛化能力。
copy_pastefloat00.0 - 1.0复制物体并粘贴到另一图像,增加实例和学习遮挡。
auto_augmentstrrandaugmentrandaugment、autoaugment和augmix自动应用预定义增强策略,优化分类任务。
erasingfloat0.40.0 - 0.9

随机擦除图像部分,鼓励模型关注不明显特征。

crop_fractionfloat1.00.0 - 1.0

将分类图像裁剪到其大小的一小部分,以强调中心特征并适应对象比例,从而减少背景干扰。 (task03教程中没有提及)

采用默认数据增强参数时,每一批次的图片如下:

可以发现对原有数据集进行了翻转、随机拼贴、剪切等处理,

但是这些处理一定对性能提升有帮助吗?

对于一个经典的目标检测任务,待检测的目标只和自身属性有关,无论目标处于场景中的什么位置,它的 label 都不会变化,这时使用随机拼贴、剪切等方法可以增加目标所处的场景,训练模型检测不同场景中的目标,有助于提升泛化性能。

但是对于这项赛事的违规行为检测任务,除了“垃圾桶满溢”,“机动车违停”“非机动车违停”“非法经营”都与目标所处的空间环境高度相关,所以此时使用随机拼贴、剪切等方法反而会不利于模型对场景的理解。

根据上面的分析,对模型默认的数据增强参数做出如下调整:

1. 保持hsv_h,hsv_s,hsv_v。这三项只改变颜色,不改变空间关系。

2. 保持translate,scale,fliplr。同理,不改变空间关系,注意yolov8默认不使用flipud,这里不做改变。

3. mosaic设置为0.0,避免拼接打乱空间,默认不使用mixup。

4. erasing和crop_fraction设置为0.0,使模型更关注空间特征。

最终的训练代码如下:

import os
os.environ["CUDA_VISIBLE_DEVICES"] = "0"

import warnings
warnings.filterwarnings('ignore')


from ultralytics import YOLO
model = YOLO("yolov8n.pt")
results = model.train(data="yolo-dataset/yolo.yaml", epochs=10, imgsz=1080, batch=32, 
                      mosaic=0.0, 
                      erasing=0.0,
                      crop_fraction=0.0,
                     )

失败尝试

在探索的过程中还进行了一些失败尝试(QAQ),在这里分享出来引以为戒。

按照视频划分训练集和验证集时,训练集和验证集一定不能包含所有视频的视频帧,这意味着模型只能在较有限的场景下训练、验证。

然后我想先提取出所有视频的所有帧,然后随机划分为训练集和验证集,这样可以保证每个集合都包含来自所有视频的视频帧,场景数和视频数相等。

相关代码:

import cv2
import os
import sklearn
from sklearn.model_selection import train_test_split
import shutil

path = "./yolo-dataset/all/"


data = []
label = []
files = os.listdir(path)
# 统计所有帧
for file in files:
    if file.endswith('.txt'):
        # print(file)
        img_path = os.path.join(path, file.split('.')[0]+'.jpg')
        label_path = os.path.join(path, file)
        data.append(img_path)
        label.append(label_path)

# 按4:1划分训练集和验证集
xtrain, xtest, ytrain, ytest = train_test_split(data, label, test_size=0.25)
print(f'训练集大小: {len(xtrain)}')
print(f'验证集大小: {len(xtest)}')


train_dir = "./yolo-dataset/train1"
val_dir = "./yolo-dataset/val"

# 图片和label移动到相应文件夹
for img, lab in zip(xtrain, ytrain):
    shutil.move(img, train_dir)
    shutil.move(lab, train_dir)

for img, lab in zip(xtest, ytest):
    shutil.move(img, val_dir)
    shutil.move(lab, val_dir)
训练集大小: 53815
验证集大小: 17939

这种划分方式得到的效果并不好,虽然在验证时获得了0.993的mAP50得分,但这只能说明模型过拟合。视频是一组连续的图像,视频帧间的相似度非常高,按照上面提到的划分方法,训练集和验证集的相似度也很高;也就是说模型几乎直接在验证集上进行训练,这时的验证得分就无法评判泛化能力,验证得分越高说明模型过拟合越严重。

看来还是只能按视频划分,浪费了好多算力时长和提交次数(QAQ)。

不过这也引发了新的思考:如果短时间内的视频帧几乎没有区别,那么可不可以在构造训练集和验证集时进行抽帧,减少重复数据来增加训练速度?

好像这样做也没什么意义,因为模型训练时每个epoch的数据几乎也是重复的。抽帧减小数据集虽然减小了每个epoch的训练时间,但不能改变模型需要的总迭代次数,这意味着模型需要更多的epoch才能收敛,或许在epoch增多时学习率改变策略会起作用。(有些复杂,求大佬指点)

效果

将前40个视频作为训练集,其余的视频作为验证集,数据增强参数调整为上文所述,模型选择yolov8n。

最终得到的分数为:0.2031042065813974,有了巨大的进步。

  • 24
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值