PyTorch实现yolov3

PyTorch实现yolov3

yolo系列是目标识别的重头戏为了更好的理解掌握它,我们必须从源码出发深刻理解代码。下面我们来讲解pytorch实现的yolov3源码。

创建YOLO网络
首先我们知道yolov3将resnet改造变成了具有更好性能的Darknet作为它的backbone,称为darknet。
配置文件
官方代码(authored in C)使用一个配置文件来构建网络,即 cfg 文件一块块地描述了网络架构。我们开始要做的就是用pytorch来读取网络结构形成自己的module进行前向与反向传播。

[convolutional]
batch_normalize=1
filters=1024
size=3
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear

我们看到图中有两个卷积层与一个用于残差相加的跳转连接,下面我们逐一讲解darknet中所有的层级。

1.卷积层

[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=leaky

普通的卷积操作,filters其实就是输出的通道数,激活函数用的是leaky,这里batch_normalize=1 相当于一个flag并不是bn的参数。
详细解释一下pytorch中的bn,BN这个类(BatchNorm2d)需要一些参数,num_features是feature map的通道数,因为bn最终优化的参数是γ和β,这两个参数是相对于每个维度来讲的,即一个维度对应一组参数。其他的参数不是很重要,想了解的话可以去官网查看。
2.跳转连接

[shortcut]
from=-3
activation=linear

它于残差网络中使用的结构类似,参数 from 为-3 表明该层的输出为前一层的输出加上前三层的输出(要求维度相同,两组输出直接相加)。
3.上采样

[upsamle]
stride=2

通过参数 stride 在前面层级中双线性上采样特征图。
4.路由层(Route)

[route]
layers=-4

[route]
layers-161

它的参数 layers 有一个或两个值。当只有一个值时,它输出这一层通过该值索引的特征图。在我们的实验中设置为了-4,所以层级将输出路由层之前第四个层的特征图。

当层级有两个值时,它将返回由这两个值索引的拼接特征图。在我们的实验中为-1 和61,因此该层级将输出从前一层级(-1)到第 61 层的特征图,并将它们按深度拼接。
5.YOLO

[yolo]
mask=0,1,2
anchors=10,13,16,30,33,23,30,61,62,45,59,119,116,90,156,198,373,326
classes=80
num=9
jitter=.3
ignore_thresh=.5
truth_thresh=1
random=1

这里的9个anchor是论文中提出用k-means求出来的,yolov3有三条预测之路(多尺度由此而来)mask=0,1,2代表第一条支路,分别对应其anchors。

还有一个net,其描述的不是层的信息,而是训练相关的参数。

[net]
# Testing
batch=1
subdivisions=1
# Training
# batch=64
# subdivisions=16
width= 320
height = 320
channels=3
momentum=0.9
decay=0.0005
angle=0
saturation = 1.5
exposure = 1.5
hue=.1

解析配置文件
在开始之前,我们先在 darknet.py 文件顶部添加必要的导入项。

from __future__ import division

import torch 
import torch.nn as nn
import torch.nn.functional as F 
from torch.autograd import Variable
import numpy as np

我们定义一个函数 parse_cfg,该函数使用配置文件的路径作为输入。

def parse_cfg(cfgfile):
"""
 Takes a configuration file

 Returns a list of blocks. Each blocks describes a block in the neural
 network to be built. Block is represented as a dictionary in the list

 """

这里的思路是解析 cfg,将每个块存储为词典。这些块的属性和值都以键值对的形式存储在词典中。解析过程中,我们将这些词典(由代码中的变量 block 表示)添加到列表 blocks 中。我们的函数将返回该 block。
我们首先将配置文件内容保存在字符串列表中。下面的代码对该列表执行预处理:

file = open(cfgfile, 'r')
lines = file.read().split('\n') # store the lines in a list
lines = [x for x in lines if len(x) > 0] # get read of the empty lines 
lines = [x for x in lines if x[0] != '#'] # get rid of comments
lines = [x.rstrip().lstrip() for x in lines] # get rid of fringe whitespaces


block = {}   #遍历预处理的列表,得到块
blocks = []

for line in lines:
if line[0] == "[": # This marks the start of a new block
if len(block) != 0: # If block is not empty, implies it is storing values of previous block.
 blocks.append(block) # add it the blocks list
 block = {} # re-init the block
 block["type"] = line[1:-1].rstrip() 
else:
 key,value = line.split("=") 
 block[key.rstrip()] = value.lstrip()
blocks.append(block)

return blocks
#定义创建构建块
def create_modules(blocks):
 net_info = blocks[0] #Captures the information about the input and pre-processing 
 module_list = nn.ModuleList()
 prev_filters = 3
 output_filters = []

在迭代该列表之前,我们先定义变量 net_info,来存储该网络的信息。

nn.ModuleList

我们的函数将会返回一个 nn.ModuleList。这个类几乎等同于一个包含 nn.Module 对象的普通列表。然而,当添加 nn.ModuleList 作为 nn.Module 对象的一个成员时(即当我们添加模块到我们的网络时),所有 nn.ModuleList 内部的 nn.Module 对象(模块)的 parameter 也被添加作为 nn.Module 对象(即我们的网络,添加 nn.ModuleList 作为其成员)的 parameter。

当我们定义一个新的卷积层时,我们必须定义它的卷积核维度。虽然卷积核的高度和宽度由 cfg 文件提供,但卷积核的深度是由上一层的卷积核数量(或特征图深度)决定的。这意味着我们需要持续追踪被应用卷积层的卷积核数量。我们使用变量 prev_filter 来做这件事。我们将其初始化为 3,因为图像有对应 RGB 通道的 3 个通道。

路由层(route layer)从前面层得到特征图(可能是拼接的)。如果在路由层之后有一个卷积层,那么卷积核将被应用到前面层的特征图上,精确来说是路由层得到的特征图。因此,我们不仅需要追踪前一层的卷积核数量,还需要追踪之前每个层。随着不断地迭代,我们将每个模块的输出卷积核数量添加到 output_filters 列表上。

现在,我们的思路是迭代模块的列表,并为每个模块创建一个 PyTorch 模块。

 for index, x in enumerate(blocks[1:]):
 module = nn.Sequential()

#check the type of block
#create a new module for the block
#append to module_list

nn.Sequential 类被用于按顺序地执行 nn.Module 对象的一个数字。如果你查看 cfg 文件,你会发现,一个模块可能包含多于一个层。例如,一个 convolutional 类型的模块有一个批量归一化层、一个 leaky ReLU 激活层以及一个卷积层。我们使用 nn.Sequential 将这些层串联起来,得到 add_module 函数。例如,以下展示了我们如何创建卷积层和上采样层的例子:

 if (x["type"] == "convolutional"):
#Get the info about the layer
 activation = x["activation"]
try:
 batch_normalize = int(x["batch_normalize"])
 bias = False
except:
 batch_normalize = 0
 bias = True

 filters= int(x["filters"])
 padding = int(x["pad"])
 kernel_size = int(x["size"])
 stride = int(x["stride"])

if padding:
 pad = (kernel_size - 1) // 2
else:
 pad = 0

#Add the convolutional layer
 conv = nn.Conv2d(prev_filters, filters, kernel_size, stride, pad, bias = bias)
 module.add_module("conv_{0}".format(index), conv)

#Add the Batch Norm Layer
if batch_normalize:
 bn = nn.BatchNorm2d(filters)
 module.add_module("batch_norm_{0}".format(index), bn)

#Check the activation. 
#It is either Linear or a Leaky ReLU for YOLO
if activation == "leaky":
 activn = nn.LeakyReLU(0.1, inplace = True)
 module.add_module("leaky_{0}".format(index), activn)

#If it's an upsampling layer
#We use Bilinear2dUpsampling
elif (x["type"] == "upsample"):
 stride = int(x["stride"])
 upsample = nn.Upsample(scale_factor = 2, mode = "bilinear")
 module.add_module("upsample_{}".format(index), upsample)

路由层/捷径层

接下来,我们来写创建路由层(Route Layer)和捷径层(Shortcut Layer)的代码:

 #If it is a route layer
elif (x["type"] == "route"):
 x["layers"] = x["layers"].split(',')
#Start of a route
 start = int(x["layers"][0])
#end, if there exists one.
try:
 end = int(x["layers"][1])
except:
 end = 0
#Positive anotation
if start > 0: 
 start = start - index
if end > 0:
 end = end - index
 route = EmptyLayer()
 module.add_module("route_{0}".format(index), route)
if end < 0:
 filters = output_filters[index + start] + output_filters[index + end]
else:
 filters= output_filters[index + start]

#shortcut corresponds to skip connection
elif x["type"] == "shortcut":
 shortcut = EmptyLayer()
 module.add_module("shortcut_{}".format(index), shortcut)

创建路由层的代码需要做一些解释。首先,我们提取关于层属性的值,将其表示为一个整数,并保存在一个列表中。

然后我们得到一个新的称为 EmptyLayer 的层,顾名思义,就是空的层。

route = EmptyLayer()

#定义如下
class EmptyLayer(nn.Module):
def __init__(self):
 super(EmptyLayer, self).__init__()

一个空的层可能会令人困惑,因为它没有做任何事情。而 Route Layer 正如其它层将执行某种操作(获取之前层的拼接)。在 PyTorch 中,当我们定义了一个新的层,我们在子类 nn.Module 中写入层在 nn.Module 对象的 forward 函数的运算。

对于在 Route 模块中设计一个层,我们必须建立一个 nn.Module 对象,其作为 layers 的成员被初始化。然后,我们可以写下代码,将 forward 函数中的特征图拼接起来并向前馈送。最后,我们执行网络的某个 forward 函数的这个层。

但拼接操作的代码相当地短和简单(在特征图上调用 torch.cat),像上述过程那样设计一个层将导致不必要的抽象,增加样板代码。取而代之,我们可以将一个假的层置于之前提出的路由层的位置上,然后直接在代表 darknet 的 nn.Module 对象的 forward 函数中执行拼接运算。(如果感到困惑,我建议你读一下 nn.Module 类在 PyTorch 中的使用)。

在路由层之后的卷积层会把它的卷积核应用到之前层的特征图(可能是拼接的)上。以下的代码更新了 filters 变量以保存路由层输出的卷积核数量。

if end < 0:
#If we are concatenating maps
 filters = output_filters[index + start] + output_filters[index + end]
else:
 filters= output_filters[index + start]

捷径层也使用空的层,因为它还要执行一个非常简单的操作(加)。没必要更新 filters 变量,因为它只是将前一层的特征图添加到后面的层上而已。
YOLO 层

 #Yolo is the detection layer
elif x["type"] == "yolo":
 mask = x["mask"].split(",")
 mask = [int(x) for x in mask]

 anchors = x["anchors"].split(",")
 anchors = [int(a) for a in anchors]
 anchors = [(anchors[i], anchors[i+1]) for i in range(0, len(anchors),2)]
 anchors = [anchors[i] for i in mask]

 detection = DetectionLayer(anchors)
 module.add_module("Detection_{}".format(index), detection)

检测层的定义如下:

class DetectionLayer(nn.Module):
def __init__(self, anchors):
 super(DetectionLayer, self).__init__()
 self.anchors = anchors

 module_list.append(module)
 prev_filters = filters
 output_filters.append(filters)

测试代码

你可以在 darknet.py 后通过输入以下命令行测试代码,运行文件。

blocks = parse_cfg("cfg/yolov3.cfg")
print(create_modules(blocks))

实现网络的前向传播
决条件

阅读本教程前两部分;

PyTorch 基础知识,包括如何使用 nn.Module、nn.Sequential 和 torch.nn.parameter 创建自定义架构;

在 PyTorch 中处理图像。

定义网络

如前所述,我们使用 nn.Module 在 PyTorch 中构建自定义架构。这里,我们可以为检测器定义一个网络。在 darknet.py 文件中,我们添加了以下类别:

class Darknet(nn.Module):
def __init__(self, cfgfile):
 super(Darknet, self).__init__()
 self.blocks = parse_cfg(cfgfile)
 self.net_info, self.module_list = create_modules(self.blocks)

这里,我们对 nn.Module 类别进行子分类,并将我们的类别命名为 Darknet。我们用 members、blocks、net_info 和 module_list 对网络进行初始化。

实现该网络的前向传播

该网络的前向传播通过覆写 nn.Module 类别的 forward 方法而实现。

forward 主要有两个目的。一,计算输出;二,尽早处理的方式转换输出检测特征图(例如转换之后,这些不同尺度的检测图就能够串联,不然会因为不同维度不可能实现串联)。

def forward(self, x, CUDA):
 modules = self.blocks[1:]
 outputs = {} #We cache the outputs for the route layer

forward 函数有三个参数:self、输入 x 和 CUDA(如果是 true,则使用 GPU 来加速前向传播)。

这里,我们迭代 self.block[1:] 而不是 self.blocks,因为 self.blocks 的第一个元素是一个 net 块,它不属于前向传播。

由于路由层和捷径层需要之前层的输出特征图,我们在字典 outputs 中缓存每个层的输出特征图。关键在于层的索引,且值对应特征图。

正如 create_module 函数中的案例,我们现在迭代 module_list,它包含了网络的模块。需要注意的是这些模块是以在配置文件中相同的顺序添加的。这意味着,我们可以简单地让输入通过每个模块来得到输出。

 if module_type == "convolutional" or module_type == "upsample":
 x = self.module_list[i](x)

路由层/捷径层

如果你查看路由层的代码,我们必须说明两个案例(正如第二部分中所描述的)。对于第一个案例,我们必须使用 torch.cat 函数将两个特征图级联起来,第二个参数设为 1。这是因为我们希望将特征图沿深度级联起来。(在 PyTorch 中,卷积层的输入和输出的格式为`B X C X H X W。深度对应通道维度)。

 elif module_type == "route":
 layers = module["layers"]
 layers = [int(a) for a in layers]

if (layers[0]) > 0:
 layers[0] = layers[0] - i

if len(layers) == 1:
 x = outputs[i + (layers[0])]

else:
if (layers[1]) > 0:
 layers[1] = layers[1] - i

 map1 = outputs[i + layers[0]]
 map2 = outputs[i + layers[1]]

 x = torch.cat((map1, map2), 1)

elif module_type == "shortcut":
 from_ = int(module["from"])
 x = outputs[i-1] + outputs[i+from_]

YOLO(检测层)

YOLO 的输出是一个卷积特征图,包含沿特征图深度的边界框属性。边界框属性由彼此堆叠的单元格预测得出。因此,如果你需要在 (5,6) 处访问单元格的第二个边框,那么你需要通过 map[5,6, (5+C): 2*(5+C)] 将其编入索引。这种格式对于输出处理过程(例如通过目标置信度进行阈值处理、添加对中心的网格偏移、应用锚点等)很不方便。

另一个问题是由于检测是在三个尺度上进行的,预测图的维度将是不同的。虽然三个特征图的维度不同,但对它们执行的输出处理过程是相似的。如果能在单个张量而不是三个单独张量上执行这些运算,就太好了。
为了解决这些问题,我们引入了函数 predict_transform。

变换输出

函数 predict_transform 在文件 util.py 中,我们在 Darknet 类别的 forward 中使用该函数时,将导入该函数。

在 util.py 顶部添加导入项:

from __future__ import division

import torch 
import torch.nn as nn
import torch.nn.functional as F 
from torch.autograd import Variable
import numpy as np
import cv2 

predict_transform 使用 5 个参数:prediction(我们的输出)、inp_dim(输入图像的维度)、anchors、num_classes、CUDA flag(可选)。

def predict_transform(prediction, inp_dim, anchors, num_classes, CUDA = True):

predict_transform 函数把检测特征图转换成二维张量,张量的每一行对应边界框的属性

 batch_size = prediction.size(0)
 stride = inp_dim // prediction.size(2)
 grid_size = inp_dim // stride
 bbox_attrs = 5 + num_classes
 num_anchors = len(anchors)

 prediction = prediction.view(batch_size, bbox_attrs*num_anchors, grid_size*grid_size)
 prediction = prediction.transpose(1,2).contiguous()
 prediction = prediction.view(batch_size, grid_size*grid_size*num_anchors, bbox_attrs)

对 (x,y) 坐标和 objectness 分数执行 Sigmoid 函数操作。

 #Sigmoid the centre_X, centre_Y. and object confidencce
 prediction[:,:,0] = torch.sigmoid(prediction[:,:,0])
 prediction[:,:,1] = torch.sigmoid(prediction[:,:,1])
 prediction[:,:,4] = torch.sigmoid(prediction[:,:,4])

将网格偏移添加到中心坐标预测中:

 #Add the center offsets
 grid = np.arange(grid_size)
 a,b = np.meshgrid(grid, grid)

 x_offset = torch.FloatTensor(a).view(-1,1)
 y_offset = torch.FloatTensor(b).view(-1,1)

if CUDA:
 x_offset = x_offset.cuda()
 y_offset = y_offset.cuda()

 x_y_offset = torch.cat((x_offset, y_offset), 1).repeat(1,num_anchors).view(-1,2).unsqueeze(0)

 prediction[:,:,:2] += x_y_offset

将锚点应用到边界框维度中:

 #log space transform height and the width
 anchors = torch.FloatTensor(anchors)

if CUDA:
 anchors = anchors.cuda()

 anchors = anchors.repeat(grid_size*grid_size, 1).unsqueeze(0)
 prediction[:,:,2:4] = torch.exp(prediction[:,:,2:4])*anchors

将 sigmoid 激活函数应用到类别分数中:

 prediction[:,:,5: 5 + num_classes] = torch.sigmoid((prediction[:,:, 5 : 5 + num_classes]))
 #将检测图的大小调整到与输入图像大小一致。边界框属性根据特征图的大小而定(如 13 x 13)。如果输入图像大小是 416 x 416,那么我们将属性乘 32,或乘 stride 变量
 prediction[:,:,:4] *= stride

重新访问的检测层

我们已经变换了输出张量,现在可以将三个不同尺度的检测图级联成一个大的张量。注意这必须在变换之后进行,因为你无法级联不同空间维度的特征图。变换之后,我们的输出张量把边界框表格呈现为行,级联就比较可行了。

一个阻碍是我们无法初始化空的张量,再向其级联一个(不同形态的)非空张量。因此,我们推迟收集器(容纳检测的张量)的初始化,直到获得第一个检测图,再把这些检测图级联起来。

注意 write = 0 在函数 forward 的 loop 之前。write flag 表示我们是否遇到第一个检测。如果 write 是 0,则收集器尚未初始化。如果 write 是 1,则收集器已经初始化,我们只需要将检测图与收集器级联起来即可。

现在,我们具备了 predict_transform 函数,我们可以写代码,处理 forward 函数中的检测特征图。

在 darknet.py 文件的顶部,添加以下导入项:

from util import * 

然后在 forward 函数中定义:

 elif module_type == 'yolo': 

 anchors = self.module_list[i][0].anchors
#Get the input dimensions
 inp_dim = int (self.net_info["height"])

#Get the number of classes
 num_classes = int (module["classes"])

#Transform 
 x = x.data
 x = predict_transform(x, inp_dim, anchors, num_classes, CUDA)
if not write: #if no collector has been intialised. 
 detections = x
 write = 1

else: 
 detections = torch.cat((detections, x), 1)

 outputs[i] = x

现在,只需返回检测结果。

 return detections

测试前向传播

下面的函数将创建一个伪造的输入,我们可以将该输入传入我们的网络。在写该函数之前,我们可以使用以下命令行将这张图像保存到工作目录:

wget https://github.com/ayooshkathuria/pytorch-yolo-v3/raw/master/dog-cycle-car.png

也可以直接下载图像:https://github.com/ayooshkathuria/pytorch-yolo-v3/raw/master/dog-cycle-car.png

现在,在 darknet.py 文件的顶部定义以下函数:

def get_test_input():
 img = cv2.imread("dog-cycle-car.png")
 img = cv2.resize(img, (416,416)) #Resize to the input dimension
 img_ = img[:,:,::-1].transpose((2,0,1)) # BGR -> RGB | H X W C -> C X H X W 
 img_ = img_[np.newaxis,:,:,:]/255.0 #Add a channel at 0 (for batch) | Normalise
 img_ = torch.from_numpy(img_).float() #Convert to float
 img_ = Variable(img_) # Convert to Variable
return img_

我们需要键入以下代码:

model = Darknet("cfg/yolov3.cfg")
inp = get_test_input()
pred = model(inp)
print (pred)

你将看到如下输出:

( 0 ,.,.) = 
16.0962 17.0541 91.5104 ... 0.4336 0.4692 0.5279
15.1363 15.2568 166.0840 ... 0.5561 0.5414 0.5318
14.4763 18.5405 409.4371 ... 0.5908 0.5353 0.4979... 
411.2625 412.0660 9.0127 ... 0.5054 0.4662 0.5043
412.1762 412.4936 16.0449 ... 0.4815 0.4979 0.4582
412.1629 411.4338 34.9027 ... 0.4306 0.5462 0.4138
[torch.FloatTensor of size 1x10647x85]

张量的形状为 1×10647×85,第一个维度为批量大小,这里我们只使用了单张图像。对于批量中的图像,我们会有一个 100647×85 的表,它的每一行表示一个边界框(4 个边界框属性、1 个 objectness 分数和 80 个类别分数)。

现在,我们的网络有随机权重,并且不会输出正确的类别。我们需要为网络加载权重文件,因此可以利用官方权重文件。

下载预训练权重

下载权重文件并放入检测器目录下,我们可以直接使用命令行下载:

wget https://pjreddie.com/media/files/yolov3.weights

也可以通过该地址下载:https://pjreddie.com/media/files/yolov3.weights

官方的权重文件是一个二进制文件,它以序列方式储存神经网络权重。

我们必须小心地读取权重,因为权重只是以浮点形式储存,没有其它信息能告诉我们到底它们属于哪一层。所以如果读取错误,那么很可能权重加载就全错了,模型也完全不能用。因此,只阅读浮点数,无法区别权重属于哪一层。因此,我们必须了解权重是如何存储的。

首先,权重只属于两种类型的层,即批归一化层(batch norm layer)和卷积层。这些层的权重储存顺序和配置文件中定义层级的顺序完全相同。所以,如果一个 convolutional 后面跟随着 shortcut 块,而 shortcut 连接了另一个 convolutional 块,则你会期望文件包含了先前 convolutional 块的权重,其后则是后者的权重。

当批归一化层出现在卷积模块中时,它是不带有偏置项的。然而,当卷积模块不存在批归一化,则偏置项的「权重」就会从文件中读取。。

加载权重

我们写一个函数来加载权重,它是 Darknet 类的成员函数。它使用 self 以外的一个参数作为权重文件的路径。

def load_weights(self, weightfile):

第一个 160 比特的权重文件保存了 5 个 int32 值,它们构成了文件的标头。

 #Open the weights file
 fp = open(weightfile, "rb")

#The first 5 values are header information 
# 1. Major version number
# 2. Minor Version Number
# 3. Subversion number 
# 4,5. Images seen by the network (during training)
 header = np.fromfile(fp, dtype = np.int32, count = 5)
 self.header = torch.from_numpy(header)
 self.seen = self.header[3]

之后的比特代表权重,按上述顺序排列。权重被保存为 float32 或 32 位浮点数。我们来加载 np.ndarray 中的剩余权重。

weights = np.fromfile(fp, dtype = np.float32)

现在,我们迭代地加载权重文件到网络的模块上。

 ptr = 0
for i in range(len(self.module_list)):
 module_type = self.blocks[i + 1]["type"]

#If module_type is convolutional load weights
#Otherwise ignore.

在循环过程中,我们首先检查 convolutional 模块是否有 batch_normalize(True)。基于此,我们加载权重。

 if module_type == "convolutional":
 model = self.module_list[i]
try:
 batch_normalize = int(self.blocks[i+1]["batch_normalize"])
except:
 batch_normalize = 0

 conv = model[0]

我们保持一个称为 ptr 的变量来追踪我们在权重数组中的位置。现在,如果 batch_normalize 检查结果是 True,则我们按以下方式加载权重:

 if (batch_normalize):
 bn = model[1]

#Get the number of weights of Batch Norm Layer
 num_bn_biases = bn.bias.numel()

#Load the weights
 bn_biases = torch.from_numpy(weights[ptr:ptr + num_bn_biases])
 ptr += num_bn_biases

 bn_weights = torch.from_numpy(weights[ptr: ptr + num_bn_biases])
 ptr += num_bn_biases

 bn_running_mean = torch.from_numpy(weights[ptr: ptr + num_bn_biases])
 ptr += num_bn_biases

 bn_running_var = torch.from_numpy(weights[ptr: ptr + num_bn_biases])
 ptr += num_bn_biases

#Cast the loaded weights into dims of model weights. 
 bn_biases = bn_biases.view_as(bn.bias.data)
 bn_weights = bn_weights.view_as(bn.weight.data)
 bn_running_mean = bn_running_mean.view_as(bn.running_mean)
 bn_running_var = bn_running_var.view_as(bn.running_var)

#Copy the data to model
 bn.bias.data.copy_(bn_biases)
 bn.weight.data.copy_(bn_weights)
 bn.running_mean.copy_(bn_running_mean)
 bn.running_var.copy_(bn_running_var)

如果 batch_normalize 的检查结果不是 True,只需要加载卷积层的偏置项。

 else:
#Number of biases
 num_biases = conv.bias.numel()

#Load the weights
 conv_biases = torch.from_numpy(weights[ptr: ptr + num_biases])
 ptr = ptr + num_biases

#reshape the loaded weights according to the dims of the model weights
 conv_biases = conv_biases.view_as(conv.bias.data)

#Finally copy the data
 conv.bias.data.copy_(conv_biases)

最后,我们加载卷积层的权重。

#Let us load the weights for the Convolutional layers
num_weights = conv.weight.numel()

#Do the same as above for weights
conv_weights = torch.from_numpy(weights[ptr:ptr+num_weights])
ptr = ptr + num_weights

conv_weights = conv_weights.view_as(conv.weight.data)
conv.weight.data.copy_(conv_weights)

该函数的介绍到此为止,你现在可以通过调用 darknet 对象上的 load_weights 函数来加载 Darknet 对象中的权重。

model = Darknet("cfg/yolov3.cfg")
model.load_weights("yolov3.weights")

通过模型构建和权重加载,我们终于可以开始进行目标检测了。未来,我们还将介绍如何利用 objectness 置信度阈值和非极大值抑制生成最终的检测结果。

你可能感兴趣的:(python,人工智能,pytorch)