Merge branch 'master' into advanced_logging
This commit is contained in:
17 files changed
+541
-169
No files matched your search
+36
-13
@@ -62,7 +62,7 @@ def create_dataloader(path, imgsz, batch_size, stride, opt, hyp=None, augment=Fa
|
||||
|
||||
|
||||
class LoadImages: # for inference
|
||||
def __init__(self, path, img_size=416):
|
||||
def __init__(self, path, img_size=640):
|
||||
path = str(Path(path)) # os-agnostic
|
||||
files = []
|
||||
if os.path.isdir(path):
|
||||
@@ -139,7 +139,7 @@ class LoadImages: # for inference
|
||||
|
||||
|
||||
class LoadWebcam: # for inference
|
||||
def __init__(self, pipe=0, img_size=416):
|
||||
def __init__(self, pipe=0, img_size=640):
|
||||
self.img_size = img_size
|
||||
|
||||
if pipe == '0':
|
||||
@@ -204,7 +204,7 @@ class LoadWebcam: # for inference
|
||||
|
||||
|
||||
class LoadStreams: # multiple IP or RTSP cameras
|
||||
def __init__(self, sources='streams.txt', img_size=416):
|
||||
def __init__(self, sources='streams.txt', img_size=640):
|
||||
self.mode = 'images'
|
||||
self.img_size = img_size
|
||||
|
||||
@@ -277,7 +277,7 @@ class LoadStreams: # multiple IP or RTSP cameras
|
||||
|
||||
|
||||
class LoadImagesAndLabels(Dataset): # for training/testing
|
||||
def __init__(self, path, img_size=416, batch_size=16, augment=False, hyp=None, rect=False, image_weights=False,
|
||||
def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, rect=False, image_weights=False,
|
||||
cache_images=False, single_cls=False, stride=32, pad=0.0):
|
||||
try:
|
||||
path = str(Path(path)) # os-agnostic
|
||||
@@ -307,6 +307,8 @@ class LoadImagesAndLabels(Dataset): # for training/testing
|
||||
self.image_weights = image_weights
|
||||
self.rect = False if image_weights else rect
|
||||
self.mosaic = self.augment and not self.rect # load 4 images at a time into a mosaic (only during training)
|
||||
self.mosaic_border = [-img_size // 2, -img_size // 2]
|
||||
self.stride = stride
|
||||
|
||||
# Define labels
|
||||
self.label_files = [x.replace('images', 'labels').replace(os.path.splitext(x)[-1], '.txt')
|
||||
@@ -585,7 +587,7 @@ def load_mosaic(self, index):
|
||||
|
||||
labels4 = []
|
||||
s = self.img_size
|
||||
xc, yc = [int(random.uniform(s * 0.5, s * 1.5)) for _ in range(2)] # mosaic center x, y
|
||||
yc, xc = [int(random.uniform(-x, 2 * s + x)) for x in self.mosaic_border] # mosaic center x, y
|
||||
indices = [index] + [random.randint(0, len(self.labels) - 1) for _ in range(3)] # 3 additional image indices
|
||||
for i, index in enumerate(indices):
|
||||
# Load image
|
||||
@@ -626,6 +628,9 @@ def load_mosaic(self, index):
|
||||
# np.clip(labels4[:, 1:] - s / 2, 0, s, out=labels4[:, 1:]) # use with center crop
|
||||
np.clip(labels4[:, 1:], 0, 2 * s, out=labels4[:, 1:]) # use with random_affine
|
||||
|
||||
# Replicate
|
||||
# img4, labels4 = replicate(img4, labels4)
|
||||
|
||||
# Augment
|
||||
# img4 = img4[s // 2: int(s * 1.5), s // 2:int(s * 1.5)] # center crop (WARNING, requires box pruning)
|
||||
img4, labels4 = random_affine(img4, labels4,
|
||||
@@ -633,12 +638,29 @@ def load_mosaic(self, index):
|
||||
translate=self.hyp['translate'],
|
||||
scale=self.hyp['scale'],
|
||||
shear=self.hyp['shear'],
|
||||
border=-s // 2) # border to remove
|
||||
border=self.mosaic_border) # border to remove
|
||||
|
||||
return img4, labels4
|
||||
|
||||
|
||||
def letterbox(img, new_shape=(416, 416), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True):
|
||||
def replicate(img, labels):
|
||||
# Replicate labels
|
||||
h, w = img.shape[:2]
|
||||
boxes = labels[:, 1:].astype(int)
|
||||
x1, y1, x2, y2 = boxes.T
|
||||
s = ((x2 - x1) + (y2 - y1)) / 2 # side length (pixels)
|
||||
for i in s.argsort()[:round(s.size * 0.5)]: # smallest indices
|
||||
x1b, y1b, x2b, y2b = boxes[i]
|
||||
bh, bw = y2b - y1b, x2b - x1b
|
||||
yc, xc = int(random.uniform(0, h - bh)), int(random.uniform(0, w - bw)) # offset x, y
|
||||
x1a, y1a, x2a, y2a = [xc, yc, xc + bw, yc + bh]
|
||||
img[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b] # img4[ymin:ymax, xmin:xmax]
|
||||
labels = np.append(labels, [[labels[i, 0], x1a, y1a, x2a, y2a]], axis=0)
|
||||
|
||||
return img, labels
|
||||
|
||||
|
||||
def letterbox(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True):
|
||||
# Resize image to a 32-pixel-multiple rectangle https://github.com/ultralytics/yolov3/issues/232
|
||||
shape = img.shape[:2] # current shape [height, width]
|
||||
if isinstance(new_shape, int):
|
||||
@@ -671,13 +693,13 @@ def letterbox(img, new_shape=(416, 416), color=(114, 114, 114), auto=True, scale
|
||||
return img, ratio, (dw, dh)
|
||||
|
||||
|
||||
def random_affine(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10, border=0):
|
||||
def random_affine(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10, border=(0, 0)):
|
||||
# torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(.1, .1), scale=(.9, 1.1), shear=(-10, 10))
|
||||
# https://medium.com/uruvideo/dataset-augmentation-with-random-homographies-a8f4b44830d4
|
||||
# targets = [cls, xyxy]
|
||||
|
||||
height = img.shape[0] + border * 2
|
||||
width = img.shape[1] + border * 2
|
||||
height = img.shape[0] + border[0] * 2 # shape(h,w,c)
|
||||
width = img.shape[1] + border[1] * 2
|
||||
|
||||
# Rotation and Scale
|
||||
R = np.eye(3)
|
||||
@@ -689,8 +711,8 @@ def random_affine(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10,
|
||||
|
||||
# Translation
|
||||
T = np.eye(3)
|
||||
T[0, 2] = random.uniform(-translate, translate) * img.shape[0] + border # x translation (pixels)
|
||||
T[1, 2] = random.uniform(-translate, translate) * img.shape[1] + border # y translation (pixels)
|
||||
T[0, 2] = random.uniform(-translate, translate) * img.shape[1] + border[1] # x translation (pixels)
|
||||
T[1, 2] = random.uniform(-translate, translate) * img.shape[0] + border[0] # y translation (pixels)
|
||||
|
||||
# Shear
|
||||
S = np.eye(3)
|
||||
@@ -699,7 +721,7 @@ def random_affine(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10,
|
||||
|
||||
# Combined rotation matrix
|
||||
M = S @ T @ R # ORDER IS IMPORTANT HERE!!
|
||||
if (border != 0) or (M != np.eye(3)).any(): # image changed
|
||||
if (border[0] != 0) or (border[1] != 0) or (M != np.eye(3)).any(): # image changed
|
||||
img = cv2.warpAffine(img, M[:2], dsize=(width, height), flags=cv2.INTER_LINEAR, borderValue=(114, 114, 114))
|
||||
|
||||
# Transform label coordinates
|
||||
@@ -762,6 +784,7 @@ def cutout(image, labels):
|
||||
box2_area = (b2_x2 - b2_x1) * (b2_y2 - b2_y1) + 1e-16
|
||||
|
||||
# Intersection over box2 area
|
||||
|
||||
return inter_area / box2_area
|
||||
|
||||
# create random masks
|
||||
|
||||
+48
-28
@@ -54,6 +54,11 @@ def time_synchronized():
|
||||
return time.time()
|
||||
|
||||
|
||||
def is_parallel(model):
|
||||
# is model is parallel with DP or DDP
|
||||
return type(model) in (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel)
|
||||
|
||||
|
||||
def initialize_weights(model):
|
||||
for m in model.modules():
|
||||
t = type(m)
|
||||
@@ -71,16 +76,36 @@ def find_modules(model, mclass=nn.Conv2d):
|
||||
return [i for i, m in enumerate(model.module_list) if isinstance(m, mclass)]
|
||||
|
||||
|
||||
def sparsity(model):
|
||||
# Return global model sparsity
|
||||
a, b = 0., 0.
|
||||
for p in model.parameters():
|
||||
a += p.numel()
|
||||
b += (p == 0).sum()
|
||||
return b / a
|
||||
|
||||
|
||||
def prune(model, amount=0.3):
|
||||
# Prune model to requested global sparsity
|
||||
import torch.nn.utils.prune as prune
|
||||
print('Pruning model... ', end='')
|
||||
for name, m in model.named_modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
prune.l1_unstructured(m, name='weight', amount=amount) # prune
|
||||
prune.remove(m, 'weight') # make permanent
|
||||
print(' %.3g global sparsity' % sparsity(model))
|
||||
|
||||
|
||||
def fuse_conv_and_bn(conv, bn):
|
||||
# https://tehnokv.com/posts/fusing-batchnorm-and-conv/
|
||||
with torch.no_grad():
|
||||
# init
|
||||
fusedconv = torch.nn.Conv2d(conv.in_channels,
|
||||
conv.out_channels,
|
||||
kernel_size=conv.kernel_size,
|
||||
stride=conv.stride,
|
||||
padding=conv.padding,
|
||||
bias=True)
|
||||
fusedconv = nn.Conv2d(conv.in_channels,
|
||||
conv.out_channels,
|
||||
kernel_size=conv.kernel_size,
|
||||
stride=conv.stride,
|
||||
padding=conv.padding,
|
||||
bias=True).to(conv.weight.device)
|
||||
|
||||
# prepare filters
|
||||
w_conv = conv.weight.clone().view(conv.out_channels, -1)
|
||||
@@ -88,10 +113,7 @@ def fuse_conv_and_bn(conv, bn):
|
||||
fusedconv.weight.copy_(torch.mm(w_bn, w_conv).view(fusedconv.weight.size()))
|
||||
|
||||
# prepare spatial bias
|
||||
if conv.bias is not None:
|
||||
b_conv = conv.bias
|
||||
else:
|
||||
b_conv = torch.zeros(conv.weight.size(0), device=conv.weight.device)
|
||||
b_conv = torch.zeros(conv.weight.size(0), device=conv.weight.device) if conv.bias is None else conv.bias
|
||||
b_bn = bn.bias - bn.weight.mul(bn.running_mean).div(torch.sqrt(bn.running_var + bn.eps))
|
||||
fusedconv.bias.copy_(torch.mm(w_bn, b_conv.reshape(-1, 1)).reshape(-1) + b_bn)
|
||||
|
||||
@@ -111,8 +133,8 @@ def model_info(model, verbose=False):
|
||||
|
||||
try: # FLOPS
|
||||
from thop import profile
|
||||
macs, _ = profile(model, inputs=(torch.zeros(1, 3, 480, 640),), verbose=False)
|
||||
fs = ', %.1f GFLOPS' % (macs / 1E9 * 2)
|
||||
flops = profile(deepcopy(model), inputs=(torch.zeros(1, 3, 64, 64),), verbose=False)[0] / 1E9 * 2
|
||||
fs = ', %.1f GFLOPS' % (flops * 100) # 640x640 FLOPS
|
||||
except:
|
||||
fs = ''
|
||||
|
||||
@@ -134,8 +156,8 @@ def load_classifier(name='resnet101', n=2):
|
||||
|
||||
# Reshape output to n classes
|
||||
filters = model.fc.weight.shape[1]
|
||||
model.fc.bias = torch.nn.Parameter(torch.zeros(n), requires_grad=True)
|
||||
model.fc.weight = torch.nn.Parameter(torch.zeros(n, filters), requires_grad=True)
|
||||
model.fc.bias = nn.Parameter(torch.zeros(n), requires_grad=True)
|
||||
model.fc.weight = nn.Parameter(torch.zeros(n, filters), requires_grad=True)
|
||||
model.fc.out_features = n
|
||||
return model
|
||||
|
||||
@@ -170,33 +192,31 @@ class ModelEMA:
|
||||
"""
|
||||
|
||||
def __init__(self, model, decay=0.9999, device=''):
|
||||
# make a copy of the model for accumulating moving average of weights
|
||||
self.ema = deepcopy(model)
|
||||
# Create EMA
|
||||
self.ema = deepcopy(model.module if is_parallel(model) else model) # FP32 EMA
|
||||
self.ema.eval()
|
||||
self.updates = 0 # number of EMA updates
|
||||
self.decay = lambda x: decay * (1 - math.exp(-x / 2000)) # decay exponential ramp (to help early epochs)
|
||||
self.device = device # perform ema on different device from model if set
|
||||
if device:
|
||||
self.ema.to(device=device)
|
||||
self.ema.to(device)
|
||||
for p in self.ema.parameters():
|
||||
p.requires_grad_(False)
|
||||
|
||||
def update(self, model):
|
||||
self.updates += 1
|
||||
d = self.decay(self.updates)
|
||||
# Update EMA parameters
|
||||
with torch.no_grad():
|
||||
if type(model) in (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel):
|
||||
msd, esd = model.module.state_dict(), self.ema.module.state_dict()
|
||||
else:
|
||||
msd, esd = model.state_dict(), self.ema.state_dict()
|
||||
self.updates += 1
|
||||
d = self.decay(self.updates)
|
||||
|
||||
for k, v in esd.items():
|
||||
msd = model.module.state_dict() if is_parallel(model) else model.state_dict() # model state_dict
|
||||
for k, v in self.ema.state_dict().items():
|
||||
if v.dtype.is_floating_point:
|
||||
v *= d
|
||||
v += (1. - d) * msd[k].detach()
|
||||
|
||||
def update_attr(self, model):
|
||||
# Assign attributes (which may change during training)
|
||||
for k in model.__dict__.keys():
|
||||
if not k.startswith('_'):
|
||||
setattr(self.ema, k, getattr(model, k))
|
||||
# Update EMA attributes
|
||||
for k, v in model.__dict__.items():
|
||||
if not k.startswith('_') and k not in ["process_group", "reducer"]:
|
||||
setattr(self.ema, k, v)
|
||||
+13
-12
@@ -53,7 +53,7 @@ def check_git_status():
|
||||
|
||||
def check_img_size(img_size, s=32):
|
||||
# Verify img_size is a multiple of stride s
|
||||
new_size = make_divisible(img_size, s) # ceil gs-multiple
|
||||
new_size = make_divisible(img_size, int(s)) # ceil gs-multiple
|
||||
if new_size != img_size:
|
||||
print('WARNING: --img-size %g must be multiple of max stride %g, updating to %g' % (img_size, s, new_size))
|
||||
return new_size
|
||||
@@ -443,7 +443,9 @@ def compute_loss(p, targets, model): # predictions, targets, model
|
||||
BCEcls, BCEobj = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g)
|
||||
|
||||
# per output
|
||||
nt = 0 # targets
|
||||
nt = 0 # number of targets
|
||||
np = len(p) # number of outputs
|
||||
balance = [1.0, 1.0, 1.0]
|
||||
for i, pi in enumerate(p): # layer index, layer predictions
|
||||
b, a, gj, gi = indices[i] # image, anchor, gridy, gridx
|
||||
tobj = torch.zeros_like(pi[..., 0]) # target obj
|
||||
@@ -473,11 +475,12 @@ def compute_loss(p, targets, model): # predictions, targets, model
|
||||
# with open('targets.txt', 'a') as file:
|
||||
# [file.write('%11.5g ' * 4 % tuple(x) + '\n') for x in torch.cat((txy[i], twh[i]), 1)]
|
||||
|
||||
lobj += BCEobj(pi[..., 4], tobj) # obj loss
|
||||
lobj += BCEobj(pi[..., 4], tobj) * balance[i] # obj loss
|
||||
|
||||
lbox *= h['giou']
|
||||
lobj *= h['obj']
|
||||
lcls *= h['cls']
|
||||
s = 3 / np # output count scaling
|
||||
lbox *= h['giou'] * s
|
||||
lobj *= h['obj'] * s
|
||||
lcls *= h['cls'] * s
|
||||
bs = tobj.shape[0] # batch size
|
||||
if red == 'sum':
|
||||
g = 3.0 # loss gain
|
||||
@@ -514,16 +517,14 @@ def build_targets(p, targets, model):
|
||||
a, t = at[j], t.repeat(na, 1, 1)[j] # filter
|
||||
|
||||
# overlaps
|
||||
g = 0.5 # offset
|
||||
gxy = t[:, 2:4] # grid xy
|
||||
z = torch.zeros_like(gxy)
|
||||
if style == 'rect2':
|
||||
g = 0.2 # offset
|
||||
j, k = ((gxy % 1. < g) & (gxy > 1.)).T
|
||||
a, t = torch.cat((a, a[j], a[k]), 0), torch.cat((t, t[j], t[k]), 0)
|
||||
offsets = torch.cat((z, z[j] + off[0], z[k] + off[1]), 0) * g
|
||||
|
||||
elif style == 'rect4':
|
||||
g = 0.5 # offset
|
||||
j, k = ((gxy % 1. < g) & (gxy > 1.)).T
|
||||
l, m = ((gxy % 1. > (1 - g)) & (gxy < (gain[[2, 3]] - 1.))).T
|
||||
a, t = torch.cat((a, a[j], a[k], a[l], a[m]), 0), torch.cat((t, t[j], t[k], t[l], t[m]), 0)
|
||||
@@ -770,11 +771,11 @@ def kmean_anchors(path='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen=10
|
||||
wh0 = np.concatenate([l[:, 3:5] * s for s, l in zip(shapes, dataset.labels)]) # wh
|
||||
|
||||
# Filter
|
||||
i = (wh0 < 4.0).any(1).sum()
|
||||
i = (wh0 < 3.0).any(1).sum()
|
||||
if i:
|
||||
print('WARNING: Extremely small objects found. '
|
||||
'%g of %g labels are < 4 pixels in width or height.' % (i, len(wh0)))
|
||||
wh = wh0[(wh0 >= 4.0).any(1)] # filter > 2 pixels
|
||||
'%g of %g labels are < 3 pixels in width or height.' % (i, len(wh0)))
|
||||
wh = wh0[(wh0 >= 2.0).any(1)] # filter > 2 pixels
|
||||
|
||||
# Kmeans calculation
|
||||
from scipy.cluster.vq import kmeans
|
||||
|
||||
Reference in new issue
Block a user