看不懂的代码可以复制进讯飞星火问问AI。以下是逐语句调试得出的执行顺序。
首先在根目录新建一个py文件,能够训练数据。
python">from ultralytics import YOLO
from ultralytics.utils import DEFAULT_CFG
from datetime import datetimecontroller=1def traindata():current_time = datetime.now()time_str = current_time.strftime("%Y-%m-%d_%H-%M-%S") # 个人习惯, 用训练时间命名保存路径, 或者你自己自定义info=r'_only_yolov8'DEFAULT_CFG.save_dir = f"./Vincent/{time_str+info}"# 加载模型model = YOLO(r"E:\DeepLearning\ultralytics\ultralytics\cfg\models\v8\yolov8.yaml") # 从头开始构建新模型# model = YOLO("yolov8n.pt") # 加载预训练模型(推荐用于训练)# Use the modelresults = model.train(data=r"E:\DeepLearning\ultralytics\ultralytics\data\DVOR2024830_yolov8\data.yaml",epochs=200,batch=30,lr0=0.1) # 训练模型metrics = model.val() # 在验证集上评估模型性能# results = model("https://ultralytics.com/images/bus.jpg") # 预测图像# success = model.export(format="onnx") # 将模型导出为 ONNX 格式1def predict():model = YOLO(r"E:\DeepLearning\ultralytics\runs\detect\train86\weights\best.pt") # pretrained YOLOv8n modelmetrics = model.val(data=r"E:\DeepLearning\ultralytics\ultralytics\data\DVOR2024830_yolov8\data.yaml")vpath=r"E:\DeepLearning\ultralytics\ultralytics\data\ceshi/"# results = model([vpath+"vor (1).jpg",vpath+"vor (2).jpg",vpath+"vor (3).jpg",vpath+"VORBlack (1).jpg",vpath+"VORBlack (2).jpg",vpath+"VORBlack (3).jpg"], save=True,conf=0.1) # return a list of Results objects# 视频路径file_path = r"E:\DeepLearning\ultralytics\ultralytics\data\video\vor3.mp4"file_path2 = r"E:\DeepLearning\ultralytics\ultralytics\data\video\vor.mp4"file_path3 = r"E:\DeepLearning\ultralytics\ultralytics\data\video\vor2.mp4"# 检测视频# results = model.predict(source=file_path3, device=0, show=False, save=True, conf=0.1)# # Process results list# for result in results:# boxes = result.boxes # Boxes object for bounding box outputs# masks = result.masks # Masks object for segmentation masks outputs# keypoints = result.keypoints # Keypoints object for pose outputs# probs = result.probs # Probs object for classification outputs# obb = result.obb # Oriented boxes object for OBB outputs# result.show() # display to screen# result.save(filename="result.jpg") # save to diskdef exportVincent():# Load the YOLOv8 modelmodel = YOLO(r"E:\DeepLearning\ultralytics\runs\detect\train21\weights\best.pt")# Export the model to ONNX formatmodel.export(format="onnx",dynamic=True)if __name__ == '__main__':if(controller==1):traindata()elif controller==2:predict()else:print("null")## exportVincent()
创建Yolov8模型结构
-
根据vincentTrain.py中。代码
model = YOLO("yolov8n.yaml")
初始化时,调用ultralytics/models/yolo/model.py
中的YOLO类初始化函数。 -
ultralytics/models/yolo/model.py 中
def __init__(self, model="yolov8n.pt", task=None, verbose=False):
由于传入参数为"yolov8n.yaml",执行super().__init__(model=model, task=task, verbose=verbose)
super().init()函数调用基类ultralytics/engine/model.py中Model类的构造函数,这里的self参数传入的是子类也就是YOLO类。 -
ultralytics/engine/model.py中。在基类Model的构造函数中,识别到yaml文件,需要新创建模型,则执行代码self._new(model, task=task, verbose=verbose)。该代码会调用Model类中的_new()函数。
-
ultralytics/engine/model.py中。在_new函数中通过cfg_dict = yaml_model_load(cfg)读取模型结构yaml文件。
-
ultralytics/engine/model.py。读取结构配置后,通过self.task = task or guess_model_task(cfg_dict),确定任务类型。guess_model_task()函数用于猜测任务类型检测,分割,分类等。
-
ultralytics/engine/model.py。
self.model = (model or self._smart_load("model"))(cfg_dict, verbose=verbose and RANK == -1)
self._smart_load()只读属性表示调用YOLO类中的_smart_load创建只读属性,该属性变量在基类中并未实现。self._smart_load(“model”)获取到需要实例化的哪个类,然后传入参数(cfg_dict, verbose=verbose and RANK == -1)。 -
ultralytics/models/yolo/model.py。
"detect": { "model": DetectionModel, "trainer": yolo.detect.DetectionTrainer, "validator": yolo.detect.DetectionValidator, "predictor": yolo.detect.DetectionPredictor, },
根据代码self.model返回DetectionModel类,该类定义在ultralytics/nn/tasks.py中。 -
ultralytics/nn/tasks.py。调用DetectionModel的构造函数def init(self, cfg=“yolov8n.yaml”, ch=3, nc=None, verbose=True)
-
ultralytics/nn/tasks.py。
self.model, self.save = parse_model(deepcopy(self.yaml), ch=ch, verbose=verbose)
parse_model()函数对模型进行生成。deepcopy函数用于深度拷贝yaml模型配置信息。ch表示通道数。是后来加上去的参数。
开始遍历yaml中的backbone+head内容
- ultralytics/nn/tasks.py。在parse_model(d, ch, verbose=True)函数中,for i, (f, n, m, args) in enumerate(d[“backbone”] + d[“head”])对yaml内容解析。enumerate函数使得d[“backbone”] + d[“head”]将backbone与head拼接到一个list中。i表示list的索引位置,(f, n, m, args)表示yaml中的参数。
- ultralytics/cfg/models/v8/yolov8.yaml中。在yaml文件中标注如下
python">backbone:# [from, repeats, module, args]- [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
f表示from -1,n表示repeats 1,m表示模块module Conv,args代表参数[64, 3, 2]
- ultralytics/nn/tasks.py的parse_model()。
m = getattr(torch.nn, m[3:]) if "nn." in m else globals()[m]
。getattr函数用于获取它某个属性的值。该语句判断m中是否包含nn.字段,1)如果m为nn.Upsample,则m=torch.nn中Upsample对应的值。2)如果m为Conv,则globals()找到当前位置的全局变量(返回的可以是类型,也可以是具体的变量,或者函数,此处返回类型),返回名为Conv对应的值。class Conv(nn.Module)定义在ultralytics/nn/modules/conv.py
。
python"># ultralytics/nn/modules/conv.py
class Conv(nn.Module):"""Standard convolution with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation)."""default_act = nn.SiLU() # default activationdef __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):"""Initialize Conv layer with given arguments including activation."""super().__init__()self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False)self.bn = nn.BatchNorm2d(c2)self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()def forward(self, x):"""Apply convolution, batch normalization and activation to input tensor."""return self.act(self.bn(self.conv(x)))
- ultralytics/nn/tasks.py的parse_model()。
c2 = make_divisible(min(c2, max_channels) * width, 8)
。make_divisible函数将yaml模型结构中arg参数中第一个表示通道数的数据处理,确保是8的倍数。若不是,则c2设置为比参数1大的8倍数。 - ultralytics/nn/tasks.py的parse_model()。
m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args)
m存放了当前需要创建的模块类型,当前是Conv,m(*args)相当于Conv(*args),创建Conv类实例,调用ultralytics/nn/modules/conv.py中的构造函数,配置该类。并放入nn.Sequential类型的m_中。 - ultralytics/nn/tasks.py的parse_model()。
m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args)
。m存放了当前需要创建的模块类型,当前是Conv,m(*args)相当于Conv(*args),创建Conv类实例,调用ultralytics/nn/modules/conv.py中的构造函数,配置该类。并放入nn.Sequential类型的m_中。 - ultralytics/nn/tasks.py的parse_model()。
m.np = sum(x.numel() for x in m_.parameters())
。m_表示整个神经网络模型,x作为模型中所有参数的迭代项,使用numel()函数计算每个参数的元素数量,最后求和得总数。
第三次循环出现m出现C2f模块
- ultralytics/nn/tasks.py的parse_model()。
python">if m in {BottleneckCSP, C1, C2, C2f, C2fAttn, C3, C3TR, C3Ghost, C3x, RepC3}:args.insert(2, n) # number of repeatsn = 1
m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args)
识别到模型为C2f后,在arg中插入参数n。代用m(*args)创建C2f模块。
- ultralytics/nn/modules/block.py。
class C2f(nn.Module),创建C2f模块,使用构造函数def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5)
。
python"># ultralytics/nn/modules/block.py
class C2f(nn.Module):"""Faster Implementation of CSP Bottleneck with 2 convolutions."""def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):"""Initialize CSP bottleneck layer with two convolutions with arguments ch_in, ch_out, number, shortcut, groups,expansion."""super().__init__()self.c = int(c2 * e) # hidden channelsself.cv1 = Conv(c1, 2 * self.c, 1, 1)self.cv2 = Conv((2 + n) * self.c, c2, 1) # optional act=FReLU(c2)self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))def forward(self, x):"""Forward pass through C2f layer."""y = list(self.cv1(x).chunk(2, 1))y.extend(m(y[-1]) for m in self.m)return self.cv2(torch.cat(y, 1))
- ultralytics/nn/modules/block.py。
self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))
。构造函数中实例化Bottleneck类,该类同样定义在ultralytics/nn/modules/block.py中。
python"># ultralytics/nn/modules/block.py
class Bottleneck(nn.Module):"""Standard bottleneck."""def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5):"""Initializes a bottleneck module with given input/output channels, shortcut option, group, kernels, andexpansion."""super().__init__()c_ = int(c2 * e) # hidden channelsself.cv1 = Conv(c1, c_, k[0], 1)self.cv2 = Conv(c_, c2, k[1], 1, g=g)self.add = shortcut and c1 == c2def forward(self, x):"""'forward()' applies the YOLO FPN to input data."""return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
-
ultralytics/nn/modules/block.py。在Bottleneck中关注构造函数__init__(),以及forward()函数。
-
模型创建迭代到SPPF模块时,ultralytics/nn/tasks.py的parse_model()。ultralytics/nn/modules/block.py中的class SPPF(nn.Module)。定义了该类的结构。关注构造函数__init__(),以及forward()函数即可。
-
接下来创建模型中head内容。nn.Upsample创建方式与前面相似。接下来详细说Concat。
-
ultralytics/cfg/models/v8/yolov8.yaml。在模型结构配置文件head中 - [[-1, 6], 1, Concat, [1]] 。其中[-1, 6]的-1表示前面一层,6表示第六层,在ultralytics/nn/tasks.py的parse_model()中layers变量按顺序存放了之前创建的每个层级模块。在yolov8的模型结构图中已经标识了层级的编号。这里的concat使用了6层的C2f输出,也使用了-1层也就是第10层的Upsample输出。
-
创建concat时,concat类定义在ultralytics/nn/modules/conv.py中。在concat类中,无论是构造函数,还是前向传播函数,均没有涉及到使用参数[-1,6],只是返回了一个torch.cat(x, self.d)的拼接操作。
detect检测层
- ultralytics/nn/tasks.py的parse_model()。最后,创建detect检测层时候。
args[j] = locals()[a] if a in locals() else ast.literal_eval(a)
。a表示-[[15,18,21],1,Detect,[nc]]中的nc,为了映射为前面的nc值,使用了locals()查询当前局部变量有哪些,若存在有名字为nc的变量,则将变量的值赋值给args[j]。所谓检测的种类数nc被放入了args中。
python">#ultralytics/nn/tasks.py的parse_model()elif m in {Detect, WorldDetect, Segment, Pose, OBB, ImagePoolingAttn}:args.append([ch[x] for x in f])if m is Segment:args[2] = make_divisible(min(args[2], max_channels) * width, 8)
-
f表示yaml中的from参数,这里是[15,18,21],分别指第15层,18层,21层的输出。将这些输出的通道数量存放入args中。
-
ultralytics/nn/tasks.py的parse_model()。使用m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args)。其中m(*args)语句创建detect模块。调用Detect类。
-
Detect类构造函数如下
python">#ultralytics/nn/modules/head.py的class Detect(nn.Module)
#class Detect(nn.Module):def __init__(self, nc=80, ch=()):"""Initializes the YOLOv8 detection layer with specified number of classes and channels."""super().__init__()self.nc = nc # number of classesself.nl = len(ch) # number of detection layersself.reg_max = 16 # DFL channels (ch[0] // 16 to scale 4/8/12/16/20 for n/s/m/l/x)self.no = nc + self.reg_max * 4 # number of outputs per anchorself.stride = torch.zeros(self.nl) # strides computed during buildc2, c3 = max((16, ch[0] // 4, self.reg_max * 4)), max(ch[0], min(self.nc, 100)) # channelsself.cv2 = nn.ModuleList(nn.Sequential(Conv(x, c2, 3), Conv(c2, c2, 3), nn.Conv2d(c2, 4 * self.reg_max, 1)) for x in ch)self.cv3 = nn.ModuleList(nn.Sequential(Conv(x, c3, 3), Conv(c3, c3, 3), nn.Conv2d(c3, self.nc, 1)) for x in ch)self.dfl = DFL(self.reg_max) if self.reg_max > 1 else nn.Identity()
DFL,全称Distribution Focal Loss
未完待续。。。。。