2025MathorCup大数据竞赛A题思路分析模型代码,完整模型论文持续更新见文末名片

咱们今天就盯着赛道A(集装箱破损检测)往细了说,从数据怎么收拾、模型怎么选、代码怎么写,到每个环节要踩的坑,全给你捋明白——全程不沾赛道B的边,就扣着A题的需求来。

首先得明确,A题的核心是“跟图片打交道”,但图片里全是坑:港口背景乱(机器、天空、地面混在一起)、光照一会儿亮一会儿暗、还有反光阴影雨水渍,更头疼的是数据不均衡——没破损的图可能占一大半,有破损的里还分“大锈蚀”“小裂纹”,小裂纹的样本可能少得可怜。所以第一步必须先把数据“捋顺”,不然模型学半天全学歪了,要么把背景当破损,要么漏了小裂纹。

一、先搞定“数据预处理”——这是所有模型的基础,比选模型还重要

咱们先解决A题里提到的“背景复杂、干扰多、数据不平衡”这三个破事,代码用Python+OpenCV+Albumentations(处理图像超好用的库)来写,咱们一步步来:

1. 先给图片“减减肥”——去掉没用的背景

港口的图里,集装箱可能只占中间一部分,周围的机器、天空都是干扰。咱们可以先做“粗定位”:要么用简单的边缘检测(Canny算法)把集装箱的矩形轮廓抠出来,要么如果数据集里有标注(比如标了集装箱的位置),直接按标注裁图。
举个代码例子,用边缘检测抠集装箱:

import cv2
import numpy as np

def crop_container(image):
    # 转灰度图,方便边缘检测
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # 去噪(高斯模糊),避免把污渍当边缘
    blur = cv2.GaussianBlur(gray, (5,5), 0)
    # 边缘检测(Canny),调阈值找集装箱的硬边缘
    edges = cv2.Canny(blur, 50, 150)
    # 找轮廓,只留面积最大的(大概率是集装箱)
    contours, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if not contours:
        return image  # 没找到轮廓就返回原图
    max_contour = max(contours, key=cv2.contourArea)
    # 给轮廓画个 bounding box
    x, y, w, h = cv2.boundingRect(max_contour)
    # 裁剪集装箱区域(留10个像素边距,避免裁掉边缘破损)
    cropped = image[y-10:y+h+10, x-10:x+w+10]
    # 防止裁剪出负坐标(比如集装箱靠 edge)
    cropped = np.clip(cropped, 0, 255)
    return cropped

这么一弄,图片里就只剩集装箱了,背景干扰少了一大半。

2. 给图片“做美容”——解决光照、反光、阴影

A题里提到的光照变化、反光、阴影,会让同一个破损在不同图里看起来不一样,模型认不出来。咱们用“图像增强”来解决:
光照/反光:调亮度、对比度,用“直方图均衡化”(CLAHE,比普通均衡化效果好);
阴影:用“灰度世界法”或者“Retinex算法”去阴影,这里给个简单的去阴影代码;
污渍:加轻微的高斯模糊或者椒盐噪声(反过来想,训练时让模型见过“脏图”,测试时就不怕污渍了)。

import albumentations as A

# 定义增强 pipeline(训练用,测试不用随机增强)
train_transform = A.Compose([
    # 先裁剪集装箱(刚才写的函数)
    A.Lambda(image=crop_container),
    # 调整大小(统一输入尺寸,比如640x640,方便模型处理)
    A.Resize(height=640, width=640),
    # 解决光照:随机调亮度、对比度、饱和度
    A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.5),
    # 解决反光:随机调HUE(色调),模拟不同光线颜色
    A.HueSaturationValue(hue_shift_limit=10, sat_shift_limit=15, val_shift_limit=10, p=0.5),
    # 去阴影(简单版:用图像均值减阴影区域)
    A.Lambda(image=lambda x: remove_shadow(x)),
    # 数据扩充(解决数据不平衡):随机翻转、旋转
    A.HorizontalFlip(p=0.5),
    A.RandomRotate90(p=0.5),
    # 加轻微模糊,模拟污渍
    A.GaussianBlur(blur_limit=(3, 5), p=0.3),
    # 转Tensor(给PyTorch用)
    A.ToFloat(max_value=255.0),
])

# 去阴影函数(简单有效版)
def remove_shadow(image):
    if len(image.shape) == 3:
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    else:
        gray = image
    # 用闭运算(先膨胀再腐蚀)找出阴影区域(暗的大块区域)
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (20,20))
    closed = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, kernel)
    # 阴影区域 = 原图灰度 闭运算结果(暗的地方差值大)
    shadow_mask = cv2.absdiff(gray, closed)
    # 阈值化,把阴影区域标出来
    _, shadow_mask = cv2.threshold(shadow_mask, 10, 255, cv2.THRESH_BINARY_INV)
    # 用周围非阴影区域的均值填充阴影
    mean_val = cv2.mean(gray, mask=cv2.bitwise_not(shadow_mask))[0]
    gray[shadow_mask == 255] = mean_val
    # 转回去彩色图
    if len(image.shape) == 3:
        return cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)
    return gray
3. 解决“数据不平衡”——别让模型只认“没破损”

A题里说“无残损图片远多于有残损”,还有“裂缝样本少”。咱们用两种办法:
过采样:对“有残损”的图多做几种增强(比如旋转、翻转、调亮度),相当于造更多有破损的样本;
加权损失:训练时给“少样本类别”(比如裂缝)更高的权重,比如没破损的样本权重设1,裂缝设5,让模型更重视少样本。

二、问题1:二分类(有没有残损)——简单但要“稳”

这个任务不用找位置,就判断“有/无”,核心是“别漏检”(漏了破损会出安全事故,比误检严重)。所以模型不用太复杂,用“迁移学习”(站在巨人肩膀上)最划算——拿预训练好的图像分类模型改改就行。

1. 选模型:优先ResNet50/EfficientNetB0

这俩模型成熟、效果好,还不占太多显存。ResNet50抗过拟合能力强,EfficientNetB0更轻量(速度快),选一个就行。咱们用PyTorch来写,因为灵活。

2. 代码核心步骤
(1)定义数据集类(加载图片+标签)
dataset/
├─ train/
│  ├─ normal/  # 无残损(标签0)
│  │  ├─ img1.jpg
│  │  └─ ...
│  └─ damaged/  # 有残损(标签1)
│     ├─ img2.jpg
│     └─ ...
└─ val/  # 验证集,结构同上

代码:

from torch.utils.data import Dataset, DataLoader
import os

class ContainerBinaryDataset(Dataset):
    def __init__(self, root_dir, transform=None):
        self.root_dir = root_dir
        self.transform = transform
        # 加载图片路径和标签
        self.image_paths = []
        self.labels = []
        # 无残损(标签0)
        normal_dir = os.path.join(root_dir, "normal")
        for img_name in os.listdir(normal_dir):
            self.image_paths.append(os.path.join(normal_dir, img_name))
            self.labels.append(0)
        # 有残损(标签1)
        damaged_dir = os.path.join(root_dir, "damaged")
        for img_name in os.listdir(damaged_dir):
            self.image_paths.append(os.path.join(damaged_dir, img_name))
            self.labels.append(1)
    
    def __len__(self):
        return len(self.image_paths)
    
    def __getitem__(self, idx):
        # 读图片(OpenCV读的是BGR,转RGB)
        img_path = self.image_paths[idx]
        image = cv2.imread(img_path)
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        label = self.labels[idx]
        # 应用增强
        if self.transform:
            augmented = self.transform(image=image)
            image = augmented["image"]
        # 转成PyTorch的Tensor格式(通道在前:HWC→CHW)
        image = image.transpose(2, 0, 1)  # 原来的HWC(640,640,3)→ CHW(3,640,640)
        return image, label

# 加载数据
train_dataset = ContainerBinaryDataset(
    root_dir="dataset/train",
    transform=train_transform  # 刚才定义的训练增强
)
val_dataset = ContainerBinaryDataset(
    root_dir="dataset/val",
    transform=val_transform  # 验证集只裁剪+resize+转Tensor,不随机增强
)
# 数据加载器(batch_size根据显卡调,比如8/16)
train_loader = DataLoader(train_dataset, batch_size=8, shuffle=True, num_workers=4)
val_loader = DataLoader(val_dataset, batch_size=8, shuffle=False, num_workers=4)
(2)定义模型(迁移学习)
import torch
import torch.nn as nn
from torchvision import models

class ContainerBinaryModel(nn.Module):
    def __init__(self, pretrained=True):
        super().__init__()
        # 加载预训练的ResNet50
        self.backbone = models.resnet50(pretrained=pretrained)
        # 把最后一层全连接层改掉:原来ResNet50输出1000类,咱们输出2类(0/1)
        in_features = self.backbone.fc.in_features
        self.backbone.fc = nn.Linear(in_features, 2)  # 2类:无残损/有残损
    
    def forward(self, x):
        return self.backbone(x)

# 初始化模型(用GPU训练,快很多)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = ContainerBinaryModel(pretrained=True).to(device)
(3)训练:重点解决“数据不平衡”

这里用“交叉熵损失+类别权重”,比如无残损样本有1000张,有残损有200张,权重就是1:5。代码里自动算权重:

from torch.optim import AdamW
from torch.optim.lr_scheduler import StepLR
import torch.nn.functional as F

# 1. 算类别权重(解决不平衡)
class_counts = [len(os.listdir("dataset/train/normal")), len(os.listdir("dataset/train/damaged"))]
class_weights = torch.FloatTensor([1/count for count in class_counts]).to(device)
class_weights = class_weights / class_weights.sum() * 2  # 归一化,让权重和为2
criterion = nn.CrossEntropyLoss(weight=class_weights)

# 2. 优化器和学习率调度(AdamW比Adam好,防过拟合)
optimizer = AdamW(model.parameters(), lr=1e-4, weight_decay=1e-5)
scheduler = StepLR(optimizer, step_size=5, gamma=0.5)  # 每5轮学习率减半

# 3. 训练循环(简单版,实际要加早停防止过拟合)
num_epochs = 20
best_val_recall = 0.0  # 重点看召回率,别漏检

for epoch in range(num_epochs):
    model.train()
    train_loss = 0.0
    for images, labels in train_loader:
        images, labels = images.to(device), labels.to(device)
        # 前向传播
        outputs = model(images)
        loss = criterion(outputs, labels)
        # 反向传播+优化
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        train_loss += loss.item() * images.size(0)
    train_loss /= len(train_loader.dataset)
    
    # 验证(算召回率、精确率、F1)
    model.eval()
    val_loss = 0.0
    true_positive = 0  # 真阳性(有破损判对)
    false_negative = 0  # 假阴性(有破损判错→漏检)
    false_positive = 0  # 假阳性(无破损判错→误检)
    with torch.no_grad():
        for images, labels in val_loader:
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            loss = criterion(outputs, labels)
            val_loss += loss.item() * images.size(0)
            # 算预测结果(取概率最大的类)
            preds = torch.argmax(outputs, dim=1)
            # 统计指标
            true_positive += ((preds == 1) & (labels == 1)).sum().item()
            false_negative += ((preds == 0) & (labels == 1)).sum().item()
            false_positive += ((preds == 1) & (labels == 0)).sum().item()
    val_loss /= len(val_loader.dataset)
    # 算关键指标(召回率优先)
    recall = true_positive / (true_positive + false_negative + 1e-6)  # 加1e-6防除0
    precision = true_positive / (true_positive + false_positive + 1e-6)
    f1 = 2 * recall * precision / (recall + precision + 1e-6)
    
    # 保存最好的模型(召回率最高的)
    if recall > best_val_recall:
        best_val_recall = recall
        torch.save(model.state_dict(), "best_binary_model.pth")
        print(f"Epoch {epoch}: 召回率提升到 {recall:.4f},保存模型!")
    
    print(f"Epoch {epoch} | 训练损失: {train_loss:.4f} | 验证损失: {val_loss:.4f}")
    print(f"召回率: {recall:.4f} | 精确率: {precision:.4f} | F1: {f1:.4f}")
    scheduler.step()  # 调整学习率
(4)评估:别只看准确率!

A题里无残损样本多,准确率会很高,但没意义。比如1000张无残损、200张有残损,模型全判“无残损”,准确率83%,但召回率0%(全漏了),这绝对不行。所以必须看召回率(越高越好,漏检少)、F1分数(平衡召回率和精确率)。

三、问题2:检测+分割(定位+类别)——A题最难,但有现成框架能用

这个任务要“画框标破损位置”+“说类别”+“像素级分割破损区域”,自己搭模型太费劲,直接用成熟的检测分割框架——Detectron2(Facebook出的,好用)或者MMDetection(国内团队出的,文档全)。这里用Detectron2,因为Mask R-CNN在上面实现得很成熟,能同时做检测和分割。

1. 核心思路:用Mask R-CNN解决“多任务”

Mask R-CNN本质是“Faster R-CNN+分割头”:先找破损的框(检测),再在框里做像素级分割(判断每个像素是不是破损)。针对A题的难点:
多尺度破损(大锈蚀、小裂纹):用FPN(特征金字塔网络),把不同尺度的特征融合,小目标用高层特征(看得远)+低层特征(细节多),大目标用高层特征。
类别不平衡(比如裂缝样本少):在训练时给少类别样本更高的“采样权重”,或者在损失函数里加权重。
背景干扰:之前预处理已经裁了集装箱,这里再在模型里加“上下文注意力”(Detectron2里有现成的配置),让模型更关注集装箱区域。

2. 数据准备:按Detectron2格式来

Detectron2需要数据集是COCO格式,或者自定义格式。咱们简单点,用“JSON标注文件”,每个样本标注:
图片路径;
破损的 bounding box(x1,y1,x2,y2,左上角和右下角坐标);
破损类别(比如0=裂纹,1=凹陷,2=穿孔,3=锈蚀);
分割掩码(用RLE格式,或者多边形坐标,标注工具推荐LabelMe,能自动生成)。

假设标注文件是train_annotations.json,格式大概这样:

{
  "images": [
    {"id": 1, "file_name": "train/img1.jpg", "height": 640, "width": 640},
    ...
  ],
  "annotations": [
    {
      "id": 1,
      "image_id": 1,
      "category_id": 1,  # 1=凹陷
      "bbox": [100, 200, 50, 40],  # x1,y1,w,h(注意Detectron2默认是x1,y1,x2,y2,要转格式)
      "segmentation": [[100,200, 150,200, 150,240, 100,240]],  # 多边形坐标(矩形为例)
      "area": 2000  # bbox面积
    },
    ...
  ],
  "categories": [
    {"id": 0, "name": "crack"},  # 裂纹
    {"id": 1, "name": "dent"},   # 凹陷
    {"id": 2, "name": "hole"},   # 穿孔
    {"id": 3, "name": "rust"}    # 锈蚀
  ]
}
3. 代码核心步骤(Detectron2)
(1)安装Detectron2
pip install detectron2 -f https://dl.fbaipublicfiles.com/detectron2/wheels/cu118/torch2.0/index.html

(根据自己的CUDA和PyTorch版本换链接,官网有教程)

(2)定义数据集和配置
from detectron2.config import get_cfg
from detectron2.data import DatasetCatalog, MetadataCatalog
from detectron2.data.datasets import register_coco_instances
from detectron2.engine import DefaultTrainer, DefaultPredictor
from detectron2.model_zoo import model_zoo
from detectron2.evaluation import COCOEvaluator, inference_on_dataset
from detectron2.data import build_detection_test_loader

# 1. 注册数据集(告诉Detectron2数据集在哪)
register_coco_instances(
    "container_train",  # 数据集名
    {},
    "train_annotations.json",  # 标注文件路径
    "dataset/train"  # 图片文件夹路径
)
register_coco_instances(
    "container_val",
    {},
    "val_annotations.json",
    "dataset/val"
)

# 2. 获取元数据(类别信息)
container_metadata = MetadataCatalog.get("container_train")
num_classes = len(container_metadata.thing_classes)  # 4类(裂纹、凹陷、穿孔、锈蚀)

# 3. 配置模型(用预训练的Mask R-CNN)
cfg = get_cfg()
# 加载预训练配置(选COCO数据集上预训练的Mask R-CNN,ResNet50-FPN backbone)
cfg.merge_from_file(model_zoo.get_config_file("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml"))
# 改数据集配置
cfg.DATASETS.TRAIN = ("container_train",)
cfg.DATASETS.TEST = ("container_val",)
cfg.DATALOADER.NUM_WORKERS = 4  # 线程数,根据CPU调

# 4. 改模型配置(重点)
cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml")  # 预训练权重
cfg.MODEL.ROI_HEADS.NUM_CLASSES = num_classes  # 4类,改这里!
cfg.MODEL.ROI_HEADS.BATCH_SIZE_PER_IMAGE = 128  # 每个图片的ROI数量,调大训练更准(别超显存)
cfg.MODEL.ROI_HEADS.POSITIVE_FRACTION = 0.3  # 正样本比例,不平衡时可以调高点

# 5. 训练配置(防过拟合+解决不平衡)
cfg.SOLVER.IMS_PER_BATCH = 4  # 每个batch的图片数,根据GPU显存调(比如12G显存设4)
cfg.SOLVER.BASE_LR = 1e-4  # 学习率,预训练模型微调用小学习率
cfg.SOLVER.MAX_ITER = 5000  # 训练迭代次数,根据数据量调(比如5000次够了)
cfg.SOLVER.STEPS = (3000, 4000)  # 学习率衰减步骤
cfg.SOLVER.GAMMA = 0.5  # 学习率衰减系数
cfg.SOLVER.CLIP_GRADIENTS.ENABLED = True  # 梯度裁剪,防梯度爆炸

# 6. 数据增强(Detectron2里的配置,和之前的预处理互补)
cfg.INPUT.RANDOM_FLIP = "horizontal"  # 随机水平翻转
cfg.INPUT.MAX_SIZE_TRAIN = 640  # 训练图片最大尺寸
cfg.INPUT.MIN_SIZE_TRAIN = (512, 640, 768)  # 多尺度训练,解决多尺度破损!
cfg.INPUT.MIN_SIZE_TRAIN_SAMPLING = "choice"  # 随机选训练尺寸

# 7. 损失权重(解决类别不平衡,比如裂纹样本少,权重设高)
cfg.MODEL.ROI_HEADS.LOSS_WEIGHTS = {
    "loss_cls": 1.0,  # 分类损失权重
    "loss_box_reg": 1.0,  # 框回归损失权重
    "loss_mask": 1.0  # 分割损失权重
}
# 如果裂纹样本少,比如裂纹占比10%,可以把分类损失里的裂纹权重设5:
# 需要自定义损失函数,这里简单点,先按默认来,后续再调

# 8. 输出路径(模型和日志保存在哪)
cfg.OUTPUT_DIR = "mask_rcnn_output"
os.makedirs(cfg.OUTPUT_DIR, exist_ok=True)
(3)训练模型
# 自定义Trainer(加评估)
class MyTrainer(DefaultTrainer):
    @classmethod
    def build_evaluator(cls, cfg, dataset_name, output_folder=None):
        if output_folder is None:
            output_folder = os.path.join(cfg.OUTPUT_DIR, "inference")
        return COCOEvaluator(dataset_name, output_dir=output_folder)

# 开始训练
trainer = MyTrainer(cfg)
trainer.resume_or_load(resume=False)  # 第一次训练设False
trainer.train()
(4)预测(测试集用)

训练完后,用最好的模型做预测,输出到test_result.csv

# 加载最好的模型(训练时会自动保存best model)
cfg.MODEL.WEIGHTS = os.path.join(cfg.OUTPUT_DIR, "model_final.pth")
cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.5  # 置信度阈值,低于0.5的预测忽略(减少误检)
predictor = DefaultPredictor(cfg)

# 加载测试集图片(假设测试集在test_folder里)
test_folder = "test_images"
test_result = []

for img_name in os.listdir(test_folder):
    img_path = os.path.join(test_folder, img_name)
    image = cv2.imread(img_path)
    # 预处理(和训练时一样:裁剪集装箱+resize)
    image = crop_container(image)
    image = cv2.resize(image, (640, 640))
    
    # 预测
    outputs = predictor(image)
    # outputs里有:
    # pred_boxes:预测的框(x1,y1,x2,y2)
    # pred_classes:预测的类别(0=裂纹,1=凹陷...)
    # pred_masks:预测的掩码(像素级)
    # scores:置信度
    
    # 整理结果
    has_damage = len(outputs["instances"]) > 0  # 有没有残损
    if has_damage:
        # 取置信度最高的前4个(A题提示:验证集最多取4个严重破损)
        instances = outputs["instances"].to("cpu")
        sorted_indices = torch.argsort(instances.scores, descending=True)[:4]
        for idx in sorted_indices:
            box = instances.pred_boxes.tensor[idx].numpy()  # x1,y1,x2,y2
            cls = instances.pred_classes[idx].item()
            cls_name = container_metadata.thing_classes[cls]
            score = instances.scores[idx].item()
            # 掩码转成字符串(方便存CSV,比如用RLE格式)
            mask = instances.pred_masks[idx].numpy()
            rle = mask_to_rle(mask)  # 自己写个RLE编码函数
            
            test_result.append({
                "image_name": img_name,
                "has_damage": 1,
                "damage_class": cls_name,
                "bbox_x1": box[0],
                "bbox_y1": box[1],
                "bbox_x2": box[2],
                "bbox_y2": box[3],
                "mask_rle": rle,
                "confidence": score
            })
    else:
        test_result.append({
            "image_name": img_name,
            "has_damage": 0,
            "damage_class": "",
            "bbox_x1": "",
            "bbox_y1": "",
            "bbox_x2": "",
            "bbox_y2": "",
            "mask_rle": "",
            "confidence": ""
        })

# 保存到CSV
import pandas as pd
df = pd.DataFrame(test_result)
df.to_csv("test_result.csv", index=False)
4. 关键优化点(解决A题难点)

小裂纹检测:调小cfg.MODEL.ANCHOR_GENERATOR.SIZES(锚点大小),比如默认是(32,64,128,256,512),加个16,让模型能捕捉更小的目标;
相似类别区分(比如深凹痕和破损):在训练集里给这些类别加更多标注,或者用“细分类损失”(比如Triplet Loss),让模型学出更细的特征;
分割精度:调大cfg.MODEL.ROI_MASK_HEAD.CONV_DIM(分割头的通道数),从256改成512,让分割更精细。

四、问题3:模型评估——对着A题需求“打分”

别瞎用指标,要紧扣A题的“业务场景”(港口安全、减少事故),每个任务的指标要对应需求:

1. 问题1(二分类)评估

核心指标:召回率(Recall) → 漏检率低,安全第一;
辅助指标:F1分数 → 平衡漏检和误检(误检多了会增加人工复核成本);
其他:ROC-AUC → 看模型整体区分能力。
计算方式就是训练时写的:召回率=真阳性/(真阳性+假阴性),F1=2*(召回率*精确率)/(召回率+精确率)。

2. 问题2(检测+分割)评估

检测部分:mAP(平均精度均值) → 框得准不准、类别判得对不对。用COCO标准的mAP@0.5(IoU≥0.5就算对)和mAP@0.5:0.95(不同IoU阈值下的平均,更严格);
分割部分:mIoU(平均交并比) → 分割得精不精确。IoU=(预测掩码∩真实掩码)/(预测掩码∪真实掩码),mIoU是所有类别的平均;
业务指标:小目标mAP → 专门看小裂纹的检测效果,避免模型只认大锈蚀。

用Detectron2的评估工具自动算:

# 评估检测和分割指标
evaluator = COCOEvaluator("container_val", cfg, False, output_dir=os.path.join(cfg.OUTPUT_DIR, "inference"))
val_loader = build_detection_test_loader(cfg, "container_val")
results = inference_on_dataset(trainer.model, val_loader, evaluator)
# 打印结果(会输出mAP、mIoU等)
print(results)
3. 错误分析(必做!)

A题要求“从不同维度评估”,光甩指标没用,得分析模型哪里不行:
漏检原因:是不是小裂纹没看见?是不是阴影里的破损没检测到?
误检原因:是不是把背景里的螺栓当成裂纹了?是不是污渍当成锈蚀了?
类别混淆:是不是把深凹痕当成破损了?拿出几个错误案例,截图分析,这样评估才完整。

五、最后给学生团队的建议

  1. 别从零造轮子:问题1用ResNet/EfficientNet,问题2用Mask R-CNN,用现成框架(PyTorch、Detectron2),省时间;
  2. 数据预处理别偷懒:背景不裁、干扰不除,模型再牛也白搭,这步至少占40%的工作量;
  3. 优先保证召回率:A题是安全相关,漏检比误检严重,训练时多关注召回率;
  4. 测试集预留接口:最后24小时给测试集,提前写好测试代码(加载图片→预处理→预测→存CSV),别临时慌。

这么一套下来,A题的三个任务就都覆盖了,思路、模型、代码都能落地,而且紧扣题目里的所有难点,不会跑偏。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐