yolo11-seg在ISIC2016医疗数据集训练预测流程(含AOP调loss函数方法)
1.数据集介绍
ISIC 2016 是国际皮肤影像协作组织(ISIC)举办的皮肤病变分析挑战赛,旨在推动黑色素瘤自动检测技术发展。

ISIC 2016 包含了5个数据集(Task):
| Task | 内容 | 训练数据 | 测试数据 |
|---|---|---|---|
| Task 1 | 病变分割 | 900张皮肤镜图像 + 二值掩膜 | 379张图像 |
| Task 2 | 皮肤镜特征提取 | 807张图像 + 超像素掩膜 + JSON特征文件 | 335张图像 |
| Task 2B | 病变分割 | 807张图像 + 1614张二值掩膜 | 335张图像 |
| Task 3 | 恶性分类 | 900张图像 + 恶性状态标注 | 379张图像 |
| Task 3B | 恶性分类(含分割) | 900张图像 + 分割掩膜 + 恶性状态标注 | 379张图像 + 掩膜 |
核心特点:
-
三大任务方向:分割(Task 1/2B)、特征提取(Task 2)、分类(Task 3/3B)
-
CC-0许可证:可自由商用
-
数据格式:JPEG图像 + PNG掩膜 + JSON/CSV标注适用场景:医学图像分割、皮肤癌分类、计算机辅助诊断算法研究。
数据集构成:
-
训练图像 + 测试图像,均为皮肤镜拍摄的病变图像
-
包含三个任务:病变分割(Task 1)、皮肤镜特征提取(Task 2)、恶性分类(Task 3)
数据特点:
-
图像格式:JPEG(已去除EXIF信息)
-
标注格式:PNG掩膜/JSON特征文件
-
许可证:CC-0(可自由使用)
适用场景: 医学图像分割、分类算法研究,特别是皮肤癌辅助诊断模型开发。
2.前期准备
可到iscic官网的https://challenge.isic-archive.com/data/#2016下载数据集
也可以使用下面脚本download-isic2016.py下载
√import requests
import os
from tqdm import tqdm
# ISIC 2016 Task 1 直接链接(从页面解析)
urls = {
'train_images': 'https://isic-challenge-data.s3.amazonaws.com/2016/ISBI2016_ISIC_Part1_Training_Data.zip',
'train_masks': 'https://isic-challenge-data.s3.amazonaws.com/2016/ISBI2016_ISIC_Part1_Training_GroundTruth.zip',
'test_images': 'https://isic-challenge-data.s3.amazonaws.com/2016/ISBI2016_ISIC_Part1_Test_Data.zip',
'test_masks': 'https://isic-challenge-data.s3.amazonaws.com/2016/ISBI2016_ISIC_Part1_Test_GroundTruth.zip'
}
def download(url, dest):
r = requests.get(url, stream=True)
total = int(r.headers.get('content-length', 0))
with open(dest, 'wb') as f, tqdm(total=total, unit='B', unit_scale=True) as pbar:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
pbar.update(len(chunk))
os.makedirs('isic2016', exist_ok=True)
for name, url in urls.items():
download(url, f'isic2016/{name}.zip')
print(f"Downloaded {name}")
运行脚本下载

到ultralytics官网https://platform.ultralytics.com/ultralytics/yolo11选择任意一个yolo11-seg模型下载,放到自己的模型目录比如model(没有则创建)
下载后可以把isic2016移动到datasets目录(没有则创建)
解压完整理文件夹重写命名,mask也是类似的
因为没有符合yolo的标签文件,需要编写脚本mask2label.py进行转化
执行uv run mask2label.py分别对训练集和验证集的掩码图像转化为标签
编写show-isic2016.py脚本查看标签在原图的位置是否正确
看起来还行,如果觉得轮廓不够平滑,可以调整mask2label.py的参数、拟合轮廓的算法或加插值算法等
如果不方便下载或处理数据,可以通过网盘分享的文件获取:ISIC2016Data.zip
链接: https://pan.baidu.com/s/1bDTJDUFmwMdUXMWbX2qtvQ?pwd=9ckp 提取码: 9ckp
若感兴趣转化和可视化的代码,可以通过网盘分享的文件获取:ISIC2016Code.zip
链接: https://pan.baidu.com/s/1A0KKjdhmPPE8Acij90_Ibg?pwd=kcn1 提取码: kcn1
在项目的cfg(没有则创建)目录放入isic2016.yaml
path: datasets/isic2016 # 数据集根目录
train: images/train
val: images/test #这里测试集当验证集用
test: images/test
names:
0: lesion # ISIC 只有病灶一类(背景自动处理)
# 可选:图像大小(ISIC 原图 ~600-1000 分辨率)
imgsz: 640
3.基准模型训练评估
编写训练评估的代码train_isic2016.py
import os
os.environ['PYTORCH_MPS_DISABLE_INFERENCE_TENSOR'] = '1'
"""
train_isic2016.py
目标:快速完成YOLO11s-seg训练+评估
标准指标:mAP, Dice, IoU, Sensitivity, Specificity
"""
import torch
import json
import time
import numpy as np
import cv2
from pathlib import Path
from datetime import datetime
from tqdm import tqdm
from ultralytics import YOLO
from ultralytics.utils.loss import v8SegmentationLoss
from ultralytics.utils import LOGGER, colorstr
from ultralytics.data.dataset import YOLODataset
# ========== 设备 ==========
DEVICE = 'mps' if torch.backends.mps.is_available() else 'cuda' if torch.cuda.is_available() else 'cpu'
LOGGER.info(f"Device: {DEVICE}")
BATCH_SIZE = 8#8 根据内存情况调整16
# ========== 配置(医疗保守增强)=========
CONFIG = {
'model': 'model/yolo11s-seg.pt',#模型路径
'data': 'cfg/isic2016.yaml',
'epochs': 100, # 轮数
'imgsz': 640,
'batch': BATCH_SIZE,
'patience': 20, # 早停
'device': DEVICE,
'optimizer': 'AdamW',
'lr0': 0.001,
'lrf': 0.01,
'amp': DEVICE != 'cpu',
# 几何增强开,颜色关(避免病变特征改变)
'degrees': 15,
'translate': 0.1,
'scale': 0.15,
'shear': 5,
'flipud': 0.0,
'fliplr': 0.5,
'hsv_h': 0.0, # 关色调
'hsv_s': 0.0, # 关饱和
'hsv_v': 0.0, # 关明度
'mosaic': 0.0,
'mixup': 0.0,
}
# ========== 论文标准指标(仅最终评估用)=========
class MedicalMetrics:
"""
公式来源:
- Dice: Dice 1945, MICCAI标准
- IoU: Jaccard 1912, 等价于mAP的segmentation版本
- Sensitivity/Specificity: 经典混淆矩阵指标
"""
@staticmethod
def compute_all(pred_masks, gt_masks, threshold=0.5):
"""
Args:
pred_masks: [N, H, W] 概率图
gt_masks: [N, H, W] 0/1
Returns:
dict of metrics
"""
pred_binary = (pred_masks > threshold).astype(np.float32)
gt_binary = gt_masks.astype(np.float32)
# flatten
p = pred_binary.reshape(-1)
g = gt_binary.reshape(-1)
tp = (p * g).sum()
fp = (p * (1-g)).sum()
fn = ((1-p) * g).sum()
tn = ((1-p) * (1-g)).sum()
eps = 1e-7
metrics = {
'Dice': float((2*tp + eps) / (2*tp + fp + fn + eps)),
'IoU': float((tp + eps) / (tp + fp + fn + eps)),
'Sensitivity': float((tp + eps) / (tp + fn + eps)), # Recall
'Specificity': float((tn + eps) / (tn + fp + eps)),
'Precision': float((tp + eps) / (tp + fp + eps)),
'Accuracy': float((tp + tn + eps) / (p.shape[0] + eps)),
}
return metrics
# ========== 快速训练指标(每epoch计算)=========
def fast_metrics(pred_masks, gt_masks):
"""训练时快速计算Dice+IoU,不计算Sensitivity/Specificity(省时间)"""
p = (pred_masks > 0.5).astype(np.float32).flatten()
g = gt_masks.astype(np.float32).flatten()
intersection = (p * g).sum()
union = p.sum() + g.sum() - intersection
eps = 1e-7
return {
'Dice': float((2*intersection + eps) / (p.sum() + g.sum() + eps)),
'IoU': float((intersection + eps) / (union + eps)),
}
# ========== Tversky Loss 注入 =========
def apply_tversky_loss(alpha=0.3, beta=0.7):
"""
alpha: 假阴性权重 (漏检) - 提高以提升Sensitivity
beta: 假阳性权重 (误检) - 降低以提升Specificity
"""
from ultralytics.utils.loss import v8SegmentationLoss
import torch
orig_single_mask_loss = v8SegmentationLoss.single_mask_loss
def tversky_single_mask_loss(gt_mask, pred, proto, xyxy, area):
"""替换单张图像的mask损失计算"""
# 原始预测
pred_mask = torch.einsum("in,nhw->ihw", pred, proto)
# Sigmoid获取概率
pred_prob = pred_mask.sigmoid()
# 计算TP, FP, FN
tp = (pred_prob * gt_mask).sum(dim=(1, 2))
fp = (pred_prob * (1 - gt_mask)).sum(dim=(1, 2))
fn = ((1 - pred_prob) * gt_mask).sum(dim=(1, 2))
# Tversky损失
tversky = (tp + 1e-7) / (tp + alpha * fn + beta * fp + 1e-7)
loss = (1 - tversky) / area # 归一化
return loss.sum()
v8SegmentationLoss.single_mask_loss = staticmethod(tversky_single_mask_loss)
return orig_single_mask_loss
def restore_tversky_loss(orig):
v8SegmentationLoss.single_mask_loss = orig
# ========== 训练流程 ==========
def train(exp_name='baseline',tversky_cfg=None):
LOGGER.info(colorstr('yellow', f"
{'='*50}"))
LOGGER.info(colorstr('yellow', f"Experiment: {exp_name}"))
LOGGER.info(colorstr('yellow', f"Tversky: {tversky_cfg if tversky_cfg else 'No'}"))
LOGGER.info(colorstr('yellow', f"{'='*50}"))
start = time.time()
# 注入严格损失
orig_loss = None
if tversky_cfg:
orig_loss = apply_tversky_loss(**tversky_cfg)
try:
model = YOLO(CONFIG['model'])
# 训练(自动每epoch验证)
results = model.train(
**CONFIG,
name=exp_name,
)
elapsed = time.time() - start
LOGGER.info(colorstr('green', f"
训练完成: {elapsed/3600:.1f}小时"))
# 返回最佳模型路径
best_pt = Path(results.best) if hasattr(results, 'best') else Path(f'runs/segment/{exp_name}/weights/best.pt')
return str(best_pt), results
finally:
if orig_loss:
restore_tversky_loss(orig_loss)#restore_loss(orig_loss)
# ========== 最终评估(标准所有指标)=========
def final_evaluate(weights_path, exp_name):
"""
在验证集上计算所有标准指标
使用原始图像分辨率,不做增强
"""
LOGGER.info(colorstr('cyan', f"
{'='*50}"))
LOGGER.info(colorstr('cyan', f"Final Evaluation: {exp_name}"))
LOGGER.info(colorstr('cyan', f"{'='*50}"))
model = YOLO(weights_path)
# 1. 标准YOLO验证(快速得到mAP)
yolo_metrics = model.val(data=CONFIG['data'], split='val', verbose=False)
# 2. 自定义计算Dice/IoU/Sensitivity/Specificity(逐像素精确计算)
# 加载验证集
dataset = YOLODataset(
img_path='datasets/isic2016/images/test',
imgsz=640,
data={'names': {0: 'lesion'}, 'nc': 1, 'path': 'datasets/isic2016'},
task='segment',
augment=False, # 评估时无增强
)
all_pred = []
all_gt = []
skipped = 0 # 记录跳过的图像数
LOGGER.info("Computing pixel-wise metrics...")
for i, batch in enumerate(tqdm(dataset, desc="Eval")):
img_path = dataset.im_files[i]
with torch.no_grad():
results = model.predict(img_path, verbose=False, device='cpu')# DEVICE
# 获取原始尺寸的 GT mask
gt_mask = batch['masks'].numpy()[0] # [H, W]
# 获取预测 mask(需要 resize 到原图尺寸)
pred_mask = None
# 关键修复:检查是否有检测结果
if len(results) > 0 and results[0].masks is not None and len(results[0].masks.data) > 0:
pred_mask = results[0].masks.data[0].cpu().numpy()
# resize 到 GT 尺寸
if pred_mask.shape != gt_mask.shape:
pred_mask = cv2.resize(pred_mask, (gt_mask.shape[1], gt_mask.shape[0]),
interpolation=cv2.INTER_LINEAR)
else:
# 没有检测到目标,使用全零掩码
pred_mask = np.zeros_like(gt_mask)
skipped += 1
all_pred.append(pred_mask)
all_gt.append(gt_mask)
if skipped > 0:
LOGGER.info(colorstr('yellow', f"Warning: {skipped}/{len(dataset)} images had no detections (using zero masks)"))
# 堆叠计算
all_pred = np.stack(all_pred) # [N, H, W]
all_gt = np.stack(all_gt)
# 计算论文标准指标
medical = MedicalMetrics.compute_all(all_pred, all_gt)
# 汇总结果
final_results = {
'experiment': exp_name,
'weights': weights_path,
'timestamp': datetime.now().isoformat(),
'YOLO_mAP50': float(yolo_metrics.seg.map50),
'YOLO_mAP75': float(yolo_metrics.seg.map75),
'images_evaluated': len(dataset),
'images_no_detection': skipped,
**medical,
}
# 保存
out_file = f'results_{exp_name}.json'
with open(out_file, 'w') as f:
json.dump(final_results, f, indent=2)
# 打印
LOGGER.info(colorstr('green', "
最终结果:"))
print(f" mAP50: {final_results['YOLO_mAP50']:.4f}")
print(f" mAP75: {final_results['YOLO_mAP75']:.4f}")
print(f" Dice: {final_results['Dice']:.4f}")
print(f" IoU: {final_results['IoU']:.4f}")
print(f" Sensitivity: {final_results['Sensitivity']:.4f}")
print(f" Specificity: {final_results['Specificity']:.4f}")
print(f" Evaluated: {final_results['images_evaluated']} images")
if skipped > 0:
print(f" No detection:{final_results['images_no_detection']} images")
print(f"
结果保存: {out_file}")
return final_results
# ========== 快速对比(3组实验)=========
def quick_compare():
"""短期内完成:基准 + 2组"""
# 医疗优化配置(解决低Sensitivity问题)
experiments = [
# 基线:BCE损失
(False, None, 'baseline'),
# Tversky v1:提高漏检权重(解决Sensitivity=0.866问题)
({'alpha': 0.7, 'beta': 0.3}, 'tversky_high_recall'),
# Tversky v2:平衡配置
({'alpha': 0.5, 'beta': 0.5}, 'tversky_balanced'),
]
all_results = []
for cfg, name in experiments:
# 训练
best_pt, _ = train(name, cfg)
# MPS清理
if DEVICE == 'mps':
torch.mps.empty_cache()
# 最终评估
results = final_evaluate(best_pt, name)
all_results.append((name, results))
# 对比
LOGGER.info(colorstr('red', f"
{'='*50}"))
LOGGER.info(colorstr('red', "对比结果"))
LOGGER.info(colorstr('red', f"{'='*50}"))
print(f"{'Exp':<15} {'mAP50':<8} {'Dice':<8} {'IoU':<8} {'Sens':<8} {'Spec':<8}")
print("-" * 60)
for name, r in all_results:
print(f"{name:<15} {r['YOLO_mAP50']:<8.3f} {r['Dice']:<8.3f} "
f"{r['IoU']:<8.3f} {r['Sensitivity']:<8.3f} {r['Specificity']:<8.3f}")
# ========== 主入口 ==========
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--mode', choices=['train', 'eval', 'auto'], default='auto')
parser.add_argument('--exp', default='baseline')
parser.add_argument('--weights', help='eval mode only')
args = parser.parse_args()
if args.mode == 'train':
cfg=None#基准训练 {'alpha': 0.7, 'beta': 0.3}
best_pt, _ = train(args.exp, cfg)
#final_evaluate(best_pt, args.exp)
elif args.mode == 'eval':
final_evaluate(args.weights, args.exp)
elif args.mode == 'auto':
quick_compare()
3.1训练
执行uv run train_isic2016.py --mode auto,自动训练和评估
也可以执行uv run train_isic2016.py --mode train进行基准训练


基准训练结束

训练概况
| 指标 | 数值 |
|---|---|
| 总训练时长 | 8.23小时(100 epochs) |
| 硬件平台 | Apple M2 (MPS加速) |
| 模型 | YOLO11s-seg (实例分割) |
| 模型大小 | 10.07M 参数 / 20.5MB 权重文件 |
性能指标
最终验证结果(最佳模型,Epoch 80)
| 任务 | Precision | Recall | mAP@50 | mAP@50-95 |
|---|---|---|---|---|
| 边界框 (Box) | 98.6% | 95.4% | 97.5% | 81.1% |
| 分割掩码 (Mask) | 98.6% | 95.4% | 97.5% | 76.7% |
关键观察
-
早停触发:第80轮达到最佳性能,后续20轮无提升,训练自动停止(
patience=20) -
显存占用:稳定在 ~9.5GB(接近M2芯片的内存压力极限)
-
推理速度:单图处理 77.7ms(预处理0.3ms + 推理65.3ms + 后处理12.1ms)
-
性能饱和:
-
Box mAP@50-95: 81.1%(较高水平)
-
Mask mAP@50-95: 76.7%(比Box低约4.4%,符合分割任务难度预期)
-
结论
✅ 训练成功完成,模型收敛良好,无过充迹象(Precision/Recall均衡)
结合图像进一步分析

损失曲线分析
1)训练损失(Train Loss)
| 损失类型 | 趋势 | 终值 | 状态 |
|---|---|---|---|
box_loss | 平滑下降 | ~0.35 | ✅ 正常收敛 |
seg_loss | 平滑下降 | ~0.72 | ✅ 正常收敛 |
cls_loss | 平滑下降 | ~0.18 | ✅ 正常收敛 |
dfl_loss | 平滑下降 | ~0.90 | ✅ 正常收敛 |
sem_loss | 恒为0 | 0 | ⚠️ 未启用/无语义分割任务 |
关键观察:所有训练损失在前20个epoch快速下降,50epoch后趋于平缓,无震荡,表明学习率设置合理。
2)验证损失(Val Loss)
| 损失类型 | 趋势 | 关键特征 | 状态 |
|---|---|---|---|
val/box_loss | 快速下降→平稳 | 初始高值(~1.8),20epoch后稳定(~0.78) | ✅ 无过拟合 |
val/seg_loss | 快速下降→微升 | 50epoch后轻微上扬(~1.8→~2.0) | ⚠️ 轻微过拟合迹象 |
val/cls_loss | 快速下降→平稳 | 与训练损失差距小 | ✅ 良好 |
val/dfl_loss | 快速下降→平稳 | 与训练同步 | ✅ 良好 |
关键发现:val/seg_loss在50epoch后出现轻微反弹,这是早停触发的主要原因。
性能指标曲线分析
边界框检测(Box)vs 分割掩码(Mask)
| 指标 | Box | Mask | 差距 | 分析 |
|---|---|---|---|---|
| Precision | ~0.99 | ~0.99 | 持平 | 两者均快速收敛至高位 |
| Recall | ~0.96 | ~0.96 | 持平 | 召回率稳定,无漏检问题 |
| mAP@50 | ~0.975 | ~0.975 | 持平 | 极高水平,接近饱和 |
| mAP@50-95 | ~0.81 | ~0.77 | -0.04 | 分割任务难度更高,符合预期 |
收敛速度分析
-
快速收敛期:前20个epoch,所有指标从0.6-0.7跃升至0.9+
-
精细调整期:20-80epoch,缓慢爬升并稳定
-
瓶颈期:80epoch后,指标波动<0.01,逐渐接近早停
结论
基准训练成功
-
无严重过拟合:训练/验证损失差距可控(除seg_loss轻微分离)
-
早停机制有效:第80epoch为最佳点,避免无效训练
-
性能优异:mAP@50达97.5%,属于顶尖水平
-
稳定性高:曲线平滑无剧烈震荡,超参数设置合理
综合指标评估
执行uv run train_isic2016.py --mode eval --weight runs/segment/baseline/weights/best.pt

这说明在使用 model.predict() 时,Ultralytics 内部使用了 torch.inference_mode() 或 torch.no_grad() 上下文,与MPS 后端的某些操作不兼容 ,改results = model.predict(img_path, verbose=False, device= DEVICE)#为results = model.predict(img_path, verbose=False, device='cpu')# DEVICE

3.2综合评估
当前基线结果分析
| 指标 | 数值 | 分析 |
|---|---|---|
| mAP50 | 97.52% | 极高,检测性能优秀 |
| mAP75 | 86.16% | 高,但比mAP50低11%,说明高IoU阈值下性能下降 |
| Dice | 78.94% | 良好,但比mAP50低约18% |
| IoU | 65.20% | 中等,分割边界精度有提升空间 |
| Sensitivity | 86.64% | 召回率不错,漏检较少 |
| Specificity | 91.05% | 特异性好,误检控制得当 |
| 无检测图像 | 8/379 (2.1%) | 少量失败案例 |
关键发现:YOLO的mAP指标(97.5%)与像素级Dice(78.9%)存在显著差距,这说明虽然检测框很准,但掩码边界的精细度不足。
核心发现:YOLO mAP vs 像素级指标严重不一致
| 指标类型 | 数值 | 含义 |
|---|---|---|
| YOLO mAP50 | 0.975 | 实例级检测完美 |
| YOLO mAP75 | 0.862 | 严格IoU下下降 |
| 像素级 Dice | 0.789 | ⚠️ 临床关键指标偏低 |
| 像素级 IoU | 0.652 | ⚠️ 分割重叠度不足 |
| Sensitivity | 0.866 | 漏诊率13.4% |
| Specificity | 0.911 | 误诊率8.9% |
医疗AI视角的关键问题
1)实例级 vs 像素级鸿沟
-
YOLO的mAP基于边界框和实例mask计算
-
Dice=0.789是逐像素计算,直接反映临床可用性
-
差距原因:YOLO的mask分辨率低(通常160×160),上采样后边界模糊
2.)临床可接受性评估
| 指标 | 当前值 | 临床经验目标值 | 状态 |
|---|---|---|---|
| Dice | 0.789 | >0.85 | ❌ 未达标 |
| IoU | 0.652 | >0.70 | ❌ 未达标 |
| Sensitivity | 0.866 | >0.90(筛查) | ❌ 偏低 |
| Specificity | 0.911 | >0.90 | ✅ 达标 |
核心问题:对筛查取向任务而言,敏感度 0.866(漏诊率 13.4%)提示仍存在显著漏诊风险;许多筛查系统会将工作点设定在 ≥0.90 甚至 ≥0.95 的敏感度水平,以降低漏诊
3)8张无检测图像(2.1%)
这是严重医疗事故风险——模型完全漏掉病变,临床表现为"假阴性"。
4.通过AOP注入修改loss的模型训练
4.1采用 AOP + Tversky Loss的合理性
为什么要用 AOP?
当前模型表现可以接受,但有优化空间
✅ 通过 AOP(面向切面编程)注入方式,修改训练时的损失函数
✅ 不改 YOLO 主体结构,不 fork 框架
✅ 只改变:模型“被优化的目标”
通俗解释 AOP 在这里的作用:不改 YOLO 源码,通过“钩子”方式把原来的损失函数,换成更适合医学分割的版本。
为什么要用Tversky Loss
Tversky Loss 是在“教模型更怕漏诊”
原始 BCE / Dice 类损失:
-
对 假阳性(FP) 和 假阴性(FN) 权重差不多
-
但在医疗里:
-
❗ 漏诊(FN) = 严重事故
-
❗ 误诊(FP) = 可以复查 / 次要问题
-
Tversky Loss:

目前考虑用的是:alpha = 0.7 提高对 FN(漏检)的惩罚 和 beta = 0.3 降低对 FP(误检)的惩罚
这相当于在训练时明确告诉模型:“宁可多画一点,也不要漏掉病灶。”
为什么这一步在基准结果的背景下合理
因为基线已经说明:
-
检测能力很强(mAP 很高)
-
但:
-
像素边界不精细(Dice / IoU 偏低)
-
漏诊率偏高(Sensitivity 0.866)
-
还有 完全没检测到的病例
-
这正是 Tversky Loss 最擅长解决的问题组合:
-
提升对 FN(漏检) 的惩罚 → 提高 Sensitivity
-
推动模型 覆盖更完整的病灶区域 → 提升 Dice / IoU
-
牺牲一点“保守性”,换取更安全的召回
4.2训练
将cfg = None改为cfg = cfg={'alpha': 0.7, 'beta': 0.3}
执行uv run train_isic2016.py --mode train --exp medical_v1
出现报错

报错根因:Ultralytics 版本“内部接口不一致”
报错位置在 ultralytics/utils/tal.py:
topk_metrics, topk_idxs = torch.topk(metrics, self.topk, dim=-1, largest=True) TypeError: topk(): argument 'k' (position 2) must be int, not bool
这说明:self.topk 变成了 bool。
而在 Ultralytics 里,self.topk 来自 TaskAlignedAssigner(topk=tal_topk, ...),tal_topk 本应是 int(比如 10)。
为什么会变成 bool?典型原因是:
-
旧版代码里
v8SegmentationLoss.__init__的第二个参数是overlap=True/False -
新版代码里第二个参数变成了
tal_topk: int = 10
如果安装包里 tasks.py 仍按旧方式传 overlap(bool)作为第二个位置参数,但 loss.py 已是新签名(第二参数当 tal_topk),就会出现:
tal_topk = overlap_mask(bool) → assigner.topk = bool → torch.topk(k=bool) 直接炸。
日志里也提示有更新版本:
New ultralytics 8.4.12 available …
而现在是 8.4.7。说明很可能正处在一个“接口切换期”的版本组合上。
解决方法:升级 ultralytics 到最新小版本(同一个包内部就一致了)。
执行uv pip install ultralytics==8.4.12

执行uv run python -c "from ultralytics import YOLO; m=YOLO('model/yolo11s-seg.pt'); m.train(data='cfg/isic2016.yaml', epochs=1, imgsz=640, batch=16, device='mps', workers=0, patience=1, verbose=False)"最小化验证

没有报错

再次执行uv run train_isic2016.py --mode train --exp medical_v1开始训练


创作不易,禁止抄袭,转载请附上原文链接及标题
本文地址:https://www.yitenyun.com/6905.html











