From 12012305111b2ac6dfcccf5538e0a8b0e6986a9d Mon Sep 17 00:00:00 2001 From: Tim Esler Date: Mon, 9 Sep 2019 19:34:39 -0700 Subject: [PATCH] Add training utilities --- __init__.py | 1 + ....tfevents.1568080115.tim-xps-ubuntu.7662.6 | Bin 0 -> 949 bytes ....tfevents.1568080114.tim-xps-ubuntu.7662.3 | Bin 0 -> 79 bytes ....tfevents.1568080004.tim-xps-ubuntu.7662.0 | Bin 0 -> 40 bytes ....tfevents.1568080115.tim-xps-ubuntu.7662.5 | Bin 0 -> 949 bytes ....tfevents.1568080114.tim-xps-ubuntu.7662.2 | Bin 0 -> 79 bytes ....tfevents.1568080115.tim-xps-ubuntu.7662.4 | Bin 0 -> 971 bytes ....tfevents.1568080114.tim-xps-ubuntu.7662.1 | Bin 0 -> 80 bytes ...tfevents.1568080768.tim-xps-ubuntu.31566.0 | Bin 0 -> 40 bytes models/inception_resnet_v1.py | 23 +-- models/utils/training.py | 136 ++++++++++++++++++ setup.py | 29 ++-- tests/travis_test.py | 2 - 13 files changed, 171 insertions(+), 20 deletions(-) create mode 100644 examples/runs/Sep09_18-46-44_tim-xps-ubuntu/acc/Train/events.out.tfevents.1568080115.tim-xps-ubuntu.7662.6 create mode 100644 examples/runs/Sep09_18-46-44_tim-xps-ubuntu/acc/Valid/events.out.tfevents.1568080114.tim-xps-ubuntu.7662.3 create mode 100644 examples/runs/Sep09_18-46-44_tim-xps-ubuntu/events.out.tfevents.1568080004.tim-xps-ubuntu.7662.0 create mode 100644 examples/runs/Sep09_18-46-44_tim-xps-ubuntu/fps/Train/events.out.tfevents.1568080115.tim-xps-ubuntu.7662.5 create mode 100644 examples/runs/Sep09_18-46-44_tim-xps-ubuntu/fps/Valid/events.out.tfevents.1568080114.tim-xps-ubuntu.7662.2 create mode 100644 examples/runs/Sep09_18-46-44_tim-xps-ubuntu/loss/Train/events.out.tfevents.1568080115.tim-xps-ubuntu.7662.4 create mode 100644 examples/runs/Sep09_18-46-44_tim-xps-ubuntu/loss/Valid/events.out.tfevents.1568080114.tim-xps-ubuntu.7662.1 create mode 100644 examples/runs/Sep09_18-59-28_tim-xps-ubuntu/events.out.tfevents.1568080768.tim-xps-ubuntu.31566.0 create mode 100644 models/utils/training.py diff --git a/__init__.py b/__init__.py index 3e7452a2..201b5e56 100644 --- a/__init__.py +++ b/__init__.py @@ -1,3 +1,4 @@ from .models.inception_resnet_v1 import InceptionResnetV1 from .models.mtcnn import MTCNN, PNet, RNet, ONet, prewhiten from .models.utils.detect_face import extract_face +from .models.utils import training diff --git a/examples/runs/Sep09_18-46-44_tim-xps-ubuntu/acc/Train/events.out.tfevents.1568080115.tim-xps-ubuntu.7662.6 b/examples/runs/Sep09_18-46-44_tim-xps-ubuntu/acc/Train/events.out.tfevents.1568080115.tim-xps-ubuntu.7662.6 new file mode 100644 index 0000000000000000000000000000000000000000..9b647a845f830f8adb7714e980918bee3eada470 GIT binary patch literal 949 zcmZ9}TPQ;T9LMn^igQrN!V>bpC{rGB+&A0YJqVf1HOgtUx#U49UXXd=VW}0xFr^g9 zWhv!Blq18NTco5>E=lCSJ@}pf>;KcYfBhpF&&&=M#j|dcnCaBJa^=B|-G+8Ssp@GF zOzCO27aQgXue(3`h+%Jx9+w=%ah$KAsY&*??Ky3&jPkNkcYuPO((dzrxeyMaFDFw0 ztW&RN5e!FYb}Bys8k6kV1Y;1s7RQYMw5(Zk2r3XhRx@&dr;hPlf@uf?C&hGtmD7WH z1TzupUZTeUy2P%0f@*|c15bK@q1DX=1k3+JCx7K%HxDpXU!@{g`yVpQgz-2I;M}uD zO|ThZf#n8o{Ly9<5)=UPqHCodilUIBA=n3y=|8h=%Dul1v!180--cK%!66@%=JyI8 zkkRj!FyMixPRApzj2E zYr8BVxh9bcyQ2+c*ZyHC$sLJ&?p+=RxuDoBBY7Z^iQ6&_NR_Z!PVxjLZ*o}dzn2W` AZvX%Q literal 0 HcmV?d00001 diff --git a/examples/runs/Sep09_18-46-44_tim-xps-ubuntu/acc/Valid/events.out.tfevents.1568080114.tim-xps-ubuntu.7662.3 b/examples/runs/Sep09_18-46-44_tim-xps-ubuntu/acc/Valid/events.out.tfevents.1568080114.tim-xps-ubuntu.7662.3 new file mode 100644 index 0000000000000000000000000000000000000000..ecb9b881b0c60bc373a93f7f7b041284d0f0fc09 GIT binary patch literal 79 zcmb1OfPlsI-b$SJGM3pKh`sJ8#hX-=n3<>NT9%quVr5jc;*^{?RNeD)Kjb)_lOXD} Wc(}N@m=lweMeP#GtZqa~O9B9!qZiBo literal 0 HcmV?d00001 diff --git a/examples/runs/Sep09_18-46-44_tim-xps-ubuntu/events.out.tfevents.1568080004.tim-xps-ubuntu.7662.0 b/examples/runs/Sep09_18-46-44_tim-xps-ubuntu/events.out.tfevents.1568080004.tim-xps-ubuntu.7662.0 new file mode 100644 index 0000000000000000000000000000000000000000..65192e1acdf2fc61a25dfb6b13da309aff857450 GIT binary patch literal 40 rcmb1OfPlsI-b$PfY*vZ~Vy`<&@g@}|X6EU+mZj#ESQ&NnWN-oiv9Jo; literal 0 HcmV?d00001 diff --git a/examples/runs/Sep09_18-46-44_tim-xps-ubuntu/fps/Train/events.out.tfevents.1568080115.tim-xps-ubuntu.7662.5 b/examples/runs/Sep09_18-46-44_tim-xps-ubuntu/fps/Train/events.out.tfevents.1568080115.tim-xps-ubuntu.7662.5 new file mode 100644 index 0000000000000000000000000000000000000000..912eeffb6adfbfce182bb4d07649285375dd5a71 GIT binary patch literal 949 zcmb1OfPlsI-b$Q$d6#Vt#9nul;!P?_%*@ksElbTSu`>FxE$Ny#RNeD)Kjb(wvLNcT zc(}N@n9~Z1Ma!PbIteZ^oGS@c*8Pz~gLA*fQCpC40$d1%C4W~qY04JdL{TW+h=B0 zC<+@*80ajRnQ25+^rPD9n zV`5U!xE7jyLzh!6QQi}*u!#|&QtR(ZrB zNphJ}QB;b|zvT;7xdln3k?u;T9IK52C@R++IpYUbxerNY%Z`;!9MLx)p{Vp$-|r7r kc>+l#C+|w9D~*n7C@QOSHwJ)JUP4l7d~&6eu#dYn0DYSiVgLXD literal 0 HcmV?d00001 diff --git a/examples/runs/Sep09_18-46-44_tim-xps-ubuntu/fps/Valid/events.out.tfevents.1568080114.tim-xps-ubuntu.7662.2 b/examples/runs/Sep09_18-46-44_tim-xps-ubuntu/fps/Valid/events.out.tfevents.1568080114.tim-xps-ubuntu.7662.2 new file mode 100644 index 0000000000000000000000000000000000000000..614dac6c48f8b8123826e6505209485bacf2d8fc GIT binary patch literal 79 zcmb1OfPlsI-b$QzVwTw)h`sJ8#hX-=n3<>NT9%quVrAs%z5AayRNeD)Kjb*?hC$S6 W@o;f*F{c$2i+b#sO-R!L7{~F7rENKwI+@LwkOxIkV$(8b`S`NsTFZr4V5n$IkmX;@9A09+5I8Wq zq)ZK4*u*S{iC`x9-%DE}N@;|lp6U`&bkR^c6g~6sJiqgY@8y?#D7rDH`IL0`=Ep*R z%|@|W-f*YBNysrcZV7h_w3;_AA(^@_H=-_6E;}xh+-_-WOX~R=fr=9%%jEExxeF+jM5+z%;B;L38k4@X=S3(rM1GJ&KArr=fU=rMzdi&Vt>%3u8-J7=QQC-H9t?u6 z&fCo7jzNDBr9kB9Q~+kQFBMES*|W=0b`WWEiEu3MZ)eKE?`rvIqV#B8stINHVM6V? zAe?N>uVvJ<6MqS5AEoB30GL*%{fv5IBxa<8l&1I=!LgOAV|07yca(Z4eWnRP!^HMF zqg8X?E@O3)()6bRn1AwJ#puuBO$*XlN=pkwaQ8ht$EYW`as}xkrOHqMGCQZ*8BI?U gE08Wx8eb`b@Zy)A(H8qFE7EmJyHz4=ImQh`sJ8#hX-=n3<>NT9%quVr5kSV0*g+vbta!h&nA^E^aQC Soc!Wq(f1FJIb3XC_zVE4bsB{L literal 0 HcmV?d00001 diff --git a/examples/runs/Sep09_18-59-28_tim-xps-ubuntu/events.out.tfevents.1568080768.tim-xps-ubuntu.31566.0 b/examples/runs/Sep09_18-59-28_tim-xps-ubuntu/events.out.tfevents.1568080768.tim-xps-ubuntu.31566.0 new file mode 100644 index 0000000000000000000000000000000000000000..f064e06b7ee2fe65289a0510d4802f014cb9bc04 GIT binary patch literal 40 rcmb1OfPlsI-b$QYo5da+h`sJ8#hX-=n3<>NT9%quVrA6krj!Q&%JU1v literal 0 HcmV?d00001 diff --git a/models/inception_resnet_v1.py b/models/inception_resnet_v1.py index 2294cd79..d4225ecb 100644 --- a/models/inception_resnet_v1.py +++ b/models/inception_resnet_v1.py @@ -191,10 +191,12 @@ class InceptionResnetV1(nn.Module): (default: {None}) classify {bool} -- Whether the model should output classification probabilities or feature embeddings. (default: {False}) - num_classes {int} -- Number of output classes. Ignored if 'pretrained' is set, in which - case the number of classes is set to that used for training. (default: {1001}) + num_classes {int} -- Number of output classes. If 'pretrained' is set and num_classes not + equal to that used for the pretrained model, the final linear layer will be randomly + initialized. (default: {1001}) + dropout_prob {float} -- Dropout probability. (default: {0.6}) """ - def __init__(self, pretrained=None, classify=False, num_classes=1001): + def __init__(self, pretrained=None, classify=False, num_classes=1001, dropout_prob=0.6): super().__init__() # Set simple attributes @@ -202,10 +204,11 @@ def __init__(self, pretrained=None, classify=False, num_classes=1001): self.classify = classify self.num_classes = num_classes + tmp_classes = self.num_classes if pretrained == 'vggface2': - self.num_classes = 8631 + tmp_classes = 8631 elif pretrained == 'casia-webface': - self.num_classes = 10575 + tmp_classes = 10575 # Define layers self.conv2d_1a = BasicConv2d(3, 32, kernel_size=3, stride=2) @@ -245,14 +248,16 @@ def __init__(self, pretrained=None, classify=False, num_classes=1001): ) self.block8 = Block8(noReLU=True) self.avgpool_1a = nn.AdaptiveAvgPool2d(1) + self.dropout = nn.Dropout(dropout_prob) self.last_linear = nn.Linear(1792, 512, bias=False) self.last_bn = nn.BatchNorm1d(512, eps=0.001, momentum=0.1, affine=True) - - self.logits = nn.Linear(512, self.num_classes) - self.softmax = nn.Softmax(dim=1) + self.logits = nn.Linear(512, tmp_classes) if pretrained is not None: load_weights(self, pretrained) + + if self.num_classes != tmp_classes: + self.logits = nn.Linear(512, self.num_classes) def forward(self, x): """Calculate embeddings or probabilities given a batch of input image tensors. @@ -277,12 +282,12 @@ def forward(self, x): x = self.repeat_3(x) x = self.block8(x) x = self.avgpool_1a(x) + x = self.dropout(x) x = self.last_linear(x.view(x.shape[0], -1)) x = self.last_bn(x) x = F.normalize(x, p=2, dim=1) if self.classify: x = self.logits(x) - x = self.softmax(x) return x diff --git a/models/utils/training.py b/models/utils/training.py new file mode 100644 index 00000000..8356f08b --- /dev/null +++ b/models/utils/training.py @@ -0,0 +1,136 @@ +import torch +import numpy as np +import time + + +class Logger(object): + + def __init__(self, mode, length, calculate_mean=False): + self.mode = mode + self.length = length + self.calculate_mean = calculate_mean + if self.calculate_mean: + self.fn = lambda x, i: x / (i + 1) + else: + self.fn = lambda x, i: x + + def __call__(self, loss, metrics, i): + track_str = f'\r{self.mode} | {i + 1:5d}/{self.length:<5d}| ' + loss_str = f'loss: {self.fn(loss, i):9.4f} | ' + metric_str = ' | '.join(f'{k}: {self.fn(v, i):9.4f}' for k, v in metrics.items()) + print(track_str + loss_str + metric_str + ' ', end='') + if i + 1 == self.length: + print('') + + +class BatchTimer(object): + """Batch timing class. + Use this class for tracking training and testing time/rate per batch or per sample. + + Keyword Arguments: + rate {bool} -- Whether to report a rate (batches or samples per second) or a time (seconds + per batch or sample). (default: {True}) + per_sample {bool} -- Whether to report times or rates per sample or per batch. + (default: {True}) + """ + + def __init__(self, rate=True, per_sample=True): + self.start = time.time() + self.end = None + self.rate = rate + self.per_sample = per_sample + + def __call__(self, y_pred, y): + self.end = time.time() + elapsed = self.end - self.start + self.start = self.end + self.end = None + + if self.per_sample: + elapsed /= len(y_pred) + if self.rate: + elapsed = 1 / elapsed + + return torch.tensor(elapsed) + + +def accuracy(logits, y): + _, preds = torch.max(logits, 1) + return (preds == y).float().mean() + + +def pass_epoch( + model, loss_fn, loader, optimizer=None, scheduler=None, + batch_metrics={'time': BatchTimer()}, show_running=True, + device='cpu', writer=None +): + """Train or evaluate over a data epoch. + + Arguments: + model {torch.nn.Module} -- Pytorch model. + loss_fn {callable} -- A function to compute (scalar) loss. + loader {torch.utils.data.DataLoader} -- A pytorch data loader. + + Keyword Arguments: + optimizer {torch.optim.Optimizer} -- A pytorch optimizer. + scheduler {torch.optim.lr_scheduler._LRScheduler} -- LR scheduler (default: {None}) + batch_metrics {dict} -- Dictionary of metric functions to call on each batch. The default + is a simple timer. A progressive average of these metrics, along with the average + loss, is printed every batch. (default: {{'time': iter_timer()}}) + show_running {bool} -- Whether or not to print losses and metrics for the current batch + or rolling averages. (default: {False}) + device {str or torch.device} -- Device for pytorch to use. (default: {'cpu'}) + writer {torch.utils.tensorboard.SummaryWriter} -- Tensorboard SummaryWriter. (default: {None}) + + Returns: + tuple(torch.Tensor, dict) -- A tuple of the average loss and a dictionary of average + metric values across the epoch. + """ + + mode = 'Train' if model.training else 'Valid' + logger = Logger(mode, length=len(loader), calculate_mean=show_running) + loss = 0 + metrics = {} + + for i_batch, (x, y) in enumerate(loader): + x = x.to(device) + y = y.to(device) + y_pred = model(x) + loss_batch = loss_fn(y_pred, y) + + if model.training: + loss_batch.backward() + optimizer.step() + optimizer.zero_grad() + + metrics_batch = {} + for metric_name, metric_fn in batch_metrics.items(): + metrics_batch[metric_name] = metric_fn(y_pred, y).detach().cpu() + metrics[metric_name] = metrics.get(metric_name, 0) + metrics_batch[metric_name] + + if writer is not None and model.training: + if writer.iteration % writer.interval == 0: + writer.add_scalars('loss', {mode: loss_batch.detach().cpu()}, writer.iteration) + for metric_name, metric_batch in metrics_batch.items(): + writer.add_scalars(metric_name, {mode: metric_batch}, writer.iteration) + writer.iteration += 1 + + loss_batch = loss_batch.detach().cpu() + loss += loss_batch + if show_running: + logger(loss, metrics, i_batch) + else: + logger(loss_batch, metrics_batch, i_batch) + + if model.training and scheduler is not None: + scheduler.step() + + loss = loss / (i_batch + 1) + metrics = {k: v / (i_batch + 1) for k, v in metrics.items()} + + if writer is not None and not model.training: + writer.add_scalars('loss', {mode: loss.detach()}, writer.iteration) + for metric_name, metric in metrics.items(): + writer.add_scalars(metric_name, {mode: metric}) + + return loss, metrics diff --git a/setup.py b/setup.py index cd1af6ae..6d3f7f1a 100644 --- a/setup.py +++ b/setup.py @@ -1,23 +1,34 @@ -import setuptools +import setuptools, os -with open('facenet_pytorch/README.md', 'r') as f: +PACKAGE_NAME = 'facenet-pytorch' +VERSION = '0.2.2' +AUTHOR = 'Tim Esler' +EMAIL = 'tim.esler@gmail.com' +DESCRIPTION = 'Pretrained Pytorch face detection and recognition models' +GITHUB_URL = 'https://github.com/timesler/facenet-pytorch' + +parent_dir = os.path.dirname(os.path.realpath(__file__)) +import_name = os.path.basename(parent_dir) + +with open(f'{parent_dir}/README.md', 'r') as f: long_description = f.read() setuptools.setup( - name='facenet-pytorch', - version='0.1.0', - author='Tim Esler', - author_email='tim.esler@gmail.com', - description='Pretrained Pytorch face detection and recognition models', + name=PACKAGE_NAME, + version=VERSION, + author=AUTHOR, + author_email=EMAIL, + description=DESCRIPTION, long_description=long_description, long_description_content_type='text/markdown', - url='https://github.com/timesler/facenet-pytorch', + url=GITHUB_URL, packages=[ 'facenet_pytorch', 'facenet_pytorch.models', 'facenet_pytorch.models.utils', 'facenet_pytorch.data', - ], + ], + package_dir={'facenet_pytorch':'.'}, package_data={'': ['*net.pt']}, classifiers=[ "Programming Language :: Python :: 3", diff --git a/tests/travis_test.py b/tests/travis_test.py index 1822bf94..f0e2f432 100644 --- a/tests/travis_test.py +++ b/tests/travis_test.py @@ -118,8 +118,6 @@ def get_image(path, trans): resnet_pt = InceptionResnetV1(pretrained=ds, classify=True).eval() prob = resnet_pt(aligned) -if sys.platform != 'win32': - assert prob.mean().detach().item() - 9.4563e-05 < 1e-5 # MULTI-FACE TEST