From 9da4cc8b16c2c88d5863bbc56ae29da9dd7a928a Mon Sep 17 00:00:00 2001 From: dengkaipeng Date: Mon, 30 Mar 2020 09:53:47 +0000 Subject: [PATCH 01/13] add tsm model --- tsm.py | 146 ++++++++++++++++++++++++ tsm/__init__.py | 26 +++++ tsm/kinetics_dataset.py | 142 +++++++++++++++++++++++ tsm/modeling.py | 195 +++++++++++++++++++++++++++++++ tsm/transforms.py | 247 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 756 insertions(+) create mode 100644 tsm.py create mode 100644 tsm/__init__.py create mode 100644 tsm/kinetics_dataset.py create mode 100644 tsm/modeling.py create mode 100644 tsm/transforms.py diff --git a/tsm.py b/tsm.py new file mode 100644 index 0000000000000..71b185a22f785 --- /dev/null +++ b/tsm.py @@ -0,0 +1,146 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import division +from __future__ import print_function + +import os +import argparse +import numpy as np + +from paddle import fluid +from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear + +from model import Model, CrossEntropy, Input, set_device +from metrics import Accuracy +from tsm import * + +NUM_CLASSES = 10 + + +def make_optimizer(num_samples, parameter_list=None): + step = int(num_samples / FLAGS.batch_size) + boundaries = [e * step for e in [40, 60]] + values = [FLAGS.lr * (0.1 ** i) for i in range(len(boundaries) + 1)] + + learning_rate = fluid.layers.piecewise_decay( + boundaries=boundaries, + values=values) + optimizer = fluid.optimizer.Momentum( + learning_rate=learning_rate, + regularization=fluid.regularizer.L2Decay(1e-4), + momentum=0.9, + parameter_list=parameter_list) + + return optimizer + + +def main(): + device = set_device(FLAGS.device) + fluid.enable_dygraph(device) if FLAGS.dynamic else None + + train_transform = Compose([GroupScale(), + GroupMultiScaleCrop(), + GroupRandomCrop(), + GroupRandomFlip(), + NormalizeImage()]) + train_dataset = KineticsDataset( + filelist=os.path.join(FLAGS.data, 'train_10.list'), + pickle_dir=os.path.join(FLAGS.data, 'train_10'), + transform=train_transform) + val_transform = Compose([GroupScale(), + GroupCenterCrop(), + NormalizeImage()]) + val_dataset = KineticsDataset( + filelist=os.path.join(FLAGS.data, 'val_10.list'), + pickle_dir=os.path.join(FLAGS.data, 'val_10'), + mode='val', + transform=val_transform) + + pretrained = FLAGS.eval_only and FLAGS.weights is None + model = tsm_resnet50(num_classes=NUM_CLASSES, pretrained=pretrained) + optim = make_optimizer(len(train_dataset), model.parameters()) + + inputs = [Input([None, 8, 3, 224, 224], 'float32', name='image')] + labels = [Input([None, 1], 'int64', name='label')] + + model.prepare( + optim, + CrossEntropy(), + Accuracy(topk=(1, 5)), + inputs=inputs, + labels=labels, + device=FLAGS.device) + + if FLAGS.eval_only: + if FLGAS.weights: + model.load(FLAGS.weights) + + model.evaluate( + val_dataset, + batch_size=FLAGS.batch_size, + num_workers=FLAGS.num_workers) + return + + if FLAGS.resume is not None: + model.load(FLAGS.resume) + + model.fit(train_dataset, + val_dataset, + epochs=FLAGS.epoch, + batch_size=FLAGS.batch_size, + save_dir='tsm_checkpoint', + num_workers=4, + drop_last=True, + shuffle=True) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser("CNN training on TSM") + parser.add_argument('data', metavar='DIR', help='path to kineteics dataset') + parser.add_argument( + "--device", type=str, default='gpu', help="device to use, gpu or cpu") + parser.add_argument( + "-d", "--dynamic", action='store_true', help="enable dygraph mode") + parser.add_argument( + "--eval_only", action='store_true', help="run evaluation only") + parser.add_argument( + "-e", "--epoch", default=70, type=int, help="number of epoch") + parser.add_argument( + "-j", "--num_workers", default=4, type=int, help="read worker number") + parser.add_argument( + '--lr', + '--learning-rate', + default=1e-2, + type=float, + metavar='LR', + help='initial learning rate') + parser.add_argument( + "-b", "--batch_size", default=16, type=int, help="batch size") + parser.add_argument( + "-n", "--num_devices", default=1, type=int, help="number of devices") + parser.add_argument( + "-r", + "--resume", + default=None, + type=str, + help="checkpoint path to resume") + parser.add_argument( + "-w", + "--weights", + default=None, + type=str, + help="weights path for evaluation") + FLAGS = parser.parse_args() + main() diff --git a/tsm/__init__.py b/tsm/__init__.py new file mode 100644 index 0000000000000..8adefe6b24c05 --- /dev/null +++ b/tsm/__init__.py @@ -0,0 +1,26 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . import kinetics_dataset +from .kinetics_dataset import * + +from . import modeling +from .modeling import * + +from . import transforms +from .transforms import * + +__all__ = kinetics_dataset.__all__ \ + + modeling.__all__ \ + + transforms.__all__ diff --git a/tsm/kinetics_dataset.py b/tsm/kinetics_dataset.py new file mode 100644 index 0000000000000..3662c91408fbe --- /dev/null +++ b/tsm/kinetics_dataset.py @@ -0,0 +1,142 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import six +import sys +import random +import numpy as np +from PIL import Image, ImageEnhance + +try: + import cPickle as pickle + from cStringIO import StringIO +except ImportError: + import pickle + from io import BytesIO + +from paddle.fluid.io import Dataset + +import logging +logger = logging.getLogger(__name__) + +__all__ = ['KineticsDataset'] + + +class KineticsDataset(Dataset): + """ + Kinetics dataset + + Args: + filelist (str): path to file list, default None. + num_classes (int): class number + """ + + def __init__(self, + filelist, + pickle_dir, + mode='train', + seg_num=8, + seg_len=1, + transform=None): + assert os.path.isfile(filelist), \ + "filelist {} not a file".format(filelist) + with open(filelist) as f: + self.pickle_paths = [l.strip() for l in f] + + assert os.path.isdir(pickle_dir), \ + "pickle_dir {} not a directory".format(pickle_dir) + self.pickle_dir = pickle_dir + + assert mode in ['train', 'val'], \ + "mode can only be 'train' or 'val'" + self.mode = mode + + self.seg_num = seg_num + self.seg_len = seg_len + self.transform = transform + + def __len__(self): + return len(self.pickle_paths) + + def __getitem__(self, idx): + pickle_path = os.path.join(self.pickle_dir, self.pickle_paths[idx]) + + try: + if six.PY2: + data = pickle.load(open(pickle_path, 'rb')) + else: + data = pickle.load(open(pickle_path, 'rb'), encoding='bytes') + + vid, label, frames = data + if len(frames) < 1: + logger.error("{} contains no frame".format(pickle_path)) + sys.exit(-1) + except Exception as e: + logger.error("Load {} failed: {}".format(pickle_path, e)) + sys.exit(-1) + + label_list = [0, 2, 3, 4, 6, 7, 9, 12, 14, 15] + label = label_list.index(label) + imgs = self._video_loader(frames) + + if self.transform: + imgs, label = self.transform(imgs, label) + return imgs, np.array([label]) + + def _video_loader(self, frames): + videolen = len(frames) + average_dur = int(videolen / self.seg_num) + + imgs = [] + for i in range(self.seg_num): + idx = 0 + if self.mode == 'train': + if average_dur >= self.seg_len: + idx = random.randint(0, average_dur - self.seg_len) + idx += i * average_dur + elif average_dur >= 1: + idx += i * average_dur + else: + idx = i + else: + if average_dur >= self.seg_len: + idx = (average_dur - self.seg_len) // 2 + idx += i * average_dur + elif average_dur >= 1: + idx += i * average_dur + else: + idx = i + + for jj in range(idx, idx + self.seg_len): + imgbuf = frames[int(jj % videolen)] + img = self._imageloader(imgbuf) + imgs.append(img) + + return imgs + + def _imageloader(self, buf): + if isinstance(buf, str): + img = Image.open(StringIO(buf)) + else: + img = Image.open(BytesIO(buf)) + + return img.convert('RGB') + + +if __name__ == "__main__": + kd = KineticsDataset('/paddle/ssd3/kineteics_mini/val_10.list', '/paddle/ssd3/kineteics_mini/val_10') + print("KineticsDataset length", len(kd)) + for d in kd: + print(len(d[0]), d[0][0].size, d[1]) diff --git a/tsm/modeling.py b/tsm/modeling.py new file mode 100644 index 0000000000000..81a0bf4007cac --- /dev/null +++ b/tsm/modeling.py @@ -0,0 +1,195 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +#Licensed under the Apache License, Version 2.0 (the "License"); +#you may not use this file except in compliance with the License. +#You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +#Unless required by applicable law or agreed to in writing, software +#distributed under the License is distributed on an "AS IS" BASIS, +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#See the License for the specific language governing permissions and +#limitations under the License. + +import math +import paddle.fluid as fluid +from paddle.fluid.layer_helper import LayerHelper +from paddle.fluid.dygraph.nn import Conv2D, Pool2D, BatchNorm, Linear + +from model import Model +from download import get_weights_path + +__all__ = ["TSM_ResNet", "tsm_resnet50"] + +# {num_layers: (url, md5)} +pretrain_infos = { + 50: ('https://paddlemodels.bj.bcebos.com/hapi/tsm_resnet50.pdparams', + '5755dc538e422589f417f7b38d7cc3c7') +} + + +class ConvBNLayer(fluid.dygraph.Layer): + def __init__(self, + num_channels, + num_filters, + filter_size, + stride=1, + groups=1, + act=None): + super(ConvBNLayer, self).__init__() + + self._conv = Conv2D( + num_channels=num_channels, + num_filters=num_filters, + filter_size=filter_size, + stride=stride, + padding=(filter_size - 1) // 2, + groups=None, + act=None, + param_attr=fluid.param_attr.ParamAttr(), + bias_attr=False) + + self._batch_norm = BatchNorm( + num_filters, + act=act, + param_attr=fluid.param_attr.ParamAttr(), + bias_attr=fluid.param_attr.ParamAttr()) + + def forward(self, inputs): + y = self._conv(inputs) + y = self._batch_norm(y) + + return y + + +class BottleneckBlock(fluid.dygraph.Layer): + def __init__(self, + num_channels, + num_filters, + stride, + shortcut=True, + seg_num=8): + super(BottleneckBlock, self).__init__() + + self.conv0 = ConvBNLayer( + num_channels=num_channels, + num_filters=num_filters, + filter_size=1, + act='relu') + self.conv1 = ConvBNLayer( + num_channels=num_filters, + num_filters=num_filters, + filter_size=3, + stride=stride, + act='relu') + self.conv2 = ConvBNLayer( + num_channels=num_filters, + num_filters=num_filters * 4, + filter_size=1, + act=None) + + if not shortcut: + self.short = ConvBNLayer( + num_channels=num_channels, + num_filters=num_filters * 4, + filter_size=1, + stride=stride) + self.shortcut = shortcut + self.seg_num = seg_num + self._num_channels_out = int(num_filters * 4) + + def forward(self, inputs): + shifts = fluid.layers.temporal_shift(inputs, self.seg_num, 1.0 / 8) + y = self.conv0(shifts) + conv1 = self.conv1(y) + conv2 = self.conv2(conv1) + if self.shortcut: + short = inputs + else: + short = self.short(inputs) + y = fluid.layers.elementwise_add(x=short, y=conv2, act="relu") + return y + + +class TSM_ResNet(Model): + def __init__(self, num_layers=50, seg_num=8, num_classes=400): + super(TSM_ResNet, self).__init__() + + self.layers = num_layers + self.seg_num = seg_num + self.class_dim = num_classes + + if self.layers == 50: + depth = [3, 4, 6, 3] + else: + raise NotImplementedError + num_filters = [64, 128, 256, 512] + + self.conv = ConvBNLayer( + num_channels=3, num_filters=64, filter_size=7, stride=2, act='relu') + self.pool2d_max = Pool2D( + pool_size=3, pool_stride=2, pool_padding=1, pool_type='max') + + self.bottleneck_block_list = [] + num_channels = 64 + + for block in range(len(depth)): + shortcut = False + for i in range(depth[block]): + bottleneck_block = self.add_sublayer( + 'bb_%d_%d' % (block, i), + BottleneckBlock( + num_channels=num_channels, + num_filters=num_filters[block], + stride=2 if i == 0 and block != 0 else 1, + shortcut=shortcut, + seg_num=self.seg_num)) + num_channels = int(bottleneck_block._num_channels_out) + self.bottleneck_block_list.append(bottleneck_block) + shortcut = True + self.pool2d_avg = Pool2D( + pool_size=7, pool_type='avg', global_pooling=True) + + stdv = 1.0 / math.sqrt(2048 * 1.0) + + self.out = Linear( + 2048, + self.class_dim, + act="softmax", + param_attr=fluid.param_attr.ParamAttr( + initializer=fluid.initializer.Uniform(-stdv, stdv)), + bias_attr=fluid.param_attr.ParamAttr( + learning_rate=2.0, regularizer=fluid.regularizer.L2Decay(0.))) + + def forward(self, inputs): + y = fluid.layers.reshape( + inputs, [-1, inputs.shape[2], inputs.shape[3], inputs.shape[4]]) + y = self.conv(y) + y = self.pool2d_max(y) + for bottleneck_block in self.bottleneck_block_list: + y = bottleneck_block(y) + y = self.pool2d_avg(y) + y = fluid.layers.dropout(y, dropout_prob=0.5) + y = fluid.layers.reshape(y, [-1, self.seg_num, y.shape[1]]) + y = fluid.layers.reduce_mean(y, dim=1) + y = fluid.layers.reshape(y, shape=[-1, 2048]) + y = self.out(y) + return y + + +def _tsm_resnet(num_layers, seg_num=8, num_classes=400, pretrained=True): + model = TSM_ResNet(num_layers, seg_num, num_classes) + if pretrained: + assert num_layers in pretrain_infos.keys(), \ + "TSM_ResNet{} do not have pretrained weights now, " \ + "pretrained should be set as False" + weight_path = get_weights_path(*(pretrain_infos[num_layers])) + assert weight_path.endswith('.pdparams'), \ + "suffix of weight must be .pdparams" + model.load(weight_path[:-9]) + return model + + +def tsm_resnet50(seg_num=8, num_classes=400, pretrained=True): + return _tsm_resnet(50, seg_num, num_classes, pretrained) diff --git a/tsm/transforms.py b/tsm/transforms.py new file mode 100644 index 0000000000000..49d8cf6bee4b5 --- /dev/null +++ b/tsm/transforms.py @@ -0,0 +1,247 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import random +import traceback +import numpy as np +from PIL import Image + +import logging +logger = logging.getLogger(__name__) + +__all__ = ['GroupScale', 'GroupMultiScaleCrop', 'GroupRandomCrop', + 'GroupRandomFlip', 'GroupCenterCrop', 'NormalizeImage', + 'Compose'] + + +class Compose(object): + def __init__(self, transforms=[]): + self.transforms = transforms + + def __call__(self, *data): + for f in self.transforms: + try: + data = f(*data) + except Exception as e: + stack_info = traceback.format_exc() + logger.info("fail to perform transform [{}] with error: " + "{} and stack:\n{}".format(f, e, str(stack_info))) + raise e + return data + + +class GroupScale(object): + """ + Group scale image + + Args: + target_size (int): image resize target size + """ + def __init__(self, target_size=224): + self.target_size = target_size + + def __call__(self, imgs, label): + resized_imgs = [] + for i in range(len(imgs)): + img = imgs[i] + w, h = img.size + if (w <= h and w == self.target_size) or \ + (h <= w and h == self.target_size): + resized_imgs.append(img) + continue + + if w < h: + ow = self.target_size + oh = int(self.target_size * 4.0 / 3.0) + resized_imgs.append(img.resize((ow, oh), Image.BILINEAR)) + else: + oh = self.target_size + ow = int(self.target_size * 4.0 / 3.0) + resized_imgs.append(img.resize((ow, oh), Image.BILINEAR)) + + return resized_imgs, label + + +class GroupMultiScaleCrop(object): + """ + FIXME: add comments + """ + def __init__(self, + short_size=256, + scales=None, + max_distort=1, + fix_crop=True, + more_fix_crop=True): + self.short_size = short_size + self.scales = scales if scales is not None \ + else [1, .875, .75, .66] + self.max_distort = max_distort + self.fix_crop = fix_crop + self.more_fix_crop = more_fix_crop + + def __call__(self, imgs, label): + input_size = [self.short_size, self.short_size] + + im_size = imgs[0].size + + # get random crop offset + def _sample_crop_size(im_size): + image_w, image_h = im_size[0], im_size[1] + + base_size = min(image_w, image_h) + crop_sizes = [int(base_size * x) for x in self.scales] + crop_h = [ + input_size[1] if abs(x - input_size[1]) < 3 else x + for x in crop_sizes + ] + crop_w = [ + input_size[0] if abs(x - input_size[0]) < 3 else x + for x in crop_sizes + ] + + pairs = [] + for i, h in enumerate(crop_h): + for j, w in enumerate(crop_w): + if abs(i - j) <= self.max_distort: + pairs.append((w, h)) + crop_pair = random.choice(pairs) + if not self.fix_crop: + w_offset = np.random.randint(0, image_w - crop_pair[0]) + h_offset = np.random.randint(0, image_h - crop_pair[1]) + else: + w_step = (image_w - crop_pair[0]) / 4 + h_step = (image_h - crop_pair[1]) / 4 + + ret = list() + ret.append((0, 0)) # upper left + if w_step != 0: + ret.append((4 * w_step, 0)) # upper right + if h_step != 0: + ret.append((0, 4 * h_step)) # lower left + if h_step != 0 and w_step != 0: + ret.append((4 * w_step, 4 * h_step)) # lower right + if h_step != 0 or w_step != 0: + ret.append((2 * w_step, 2 * h_step)) # center + + if self.more_fix_crop: + ret.append((0, 2 * h_step)) # center left + ret.append((4 * w_step, 2 * h_step)) # center right + ret.append((2 * w_step, 4 * h_step)) # lower center + ret.append((2 * w_step, 0 * h_step)) # upper center + + ret.append((1 * w_step, 1 * h_step)) # upper left quarter + ret.append((3 * w_step, 1 * h_step)) # upper right quarter + ret.append((1 * w_step, 3 * h_step)) # lower left quarter + ret.append((3 * w_step, 3 * h_step)) # lower righ quarter + + w_offset, h_offset = random.choice(ret) + + return crop_pair[0], crop_pair[1], w_offset, h_offset + + crop_w, crop_h, offset_w, offset_h = _sample_crop_size(im_size) + crop_imgs = [ + img.crop((offset_w, offset_h, offset_w + crop_w, offset_h + crop_h)) + for img in imgs + ] + ret_imgs = [ + img.resize((input_size[0], input_size[1]), Image.BILINEAR) + for img in crop_imgs + ] + + return ret_imgs, label + + +class GroupRandomCrop(object): + def __init__(self, target_size=224): + self.target_size = target_size + + def __call__(self, imgs, label): + w, h = imgs[0].size + th, tw = self.target_size, self.target_size + + assert (w >= self.target_size) and (h >= self.target_size), \ + "image width({}) and height({}) should be larger than " \ + "crop size".format(w, h, self.target_size) + + out_images = [] + x1 = np.random.randint(0, w - tw) + y1 = np.random.randint(0, h - th) + + for img in imgs: + if w == tw and h == th: + out_images.append(img) + else: + out_images.append(img.crop((x1, y1, x1 + tw, y1 + th))) + + return out_images, label + + +class GroupRandomFlip(object): + def __call__(self, imgs, label): + v = np.random.random() + if v < 0.5: + ret = [img.transpose(Image.FLIP_LEFT_RIGHT) for img in imgs] + return ret, label + else: + return imgs, label + + +class GroupCenterCrop(object): + def __init__(self, target_size=224): + self.target_size = target_size + + def __call__(self, imgs, label): + crop_imgs = [] + for img in imgs: + w, h = img.size + th, tw = self.target_size, self.target_size + assert (w >= self.target_size) and (h >= self.target_size), \ + "image width({}) and height({}) should be larger " \ + "than crop size".format(w, h, self.target_size) + x1 = int(round((w - tw) / 2.)) + y1 = int(round((h - th) / 2.)) + crop_imgs.append(img.crop((x1, y1, x1 + tw, y1 + th))) + + return crop_imgs, label + + +class NormalizeImage(object): + def __init__(self, + target_size=224, + img_mean=[0.485, 0.456, 0.406], + img_std=[0.229, 0.224, 0.225], + seg_num=8, + seg_len=1): + self.target_size = target_size + self.img_mean = np.array(img_mean).reshape((3, 1, 1)).astype('float32') + self.img_std = np.array(img_std).reshape((3, 1, 1)).astype('float32') + self.seg_num = seg_num + self.seg_len = seg_len + + def __call__(self, imgs, label): + np_imgs = (np.array(imgs[0]).astype('float32').transpose( + (2, 0, 1))).reshape(1, 3, self.target_size, + self.target_size) / 255 + for i in range(len(imgs) - 1): + img = (np.array(imgs[i + 1]).astype('float32').transpose( + (2, 0, 1))).reshape(1, 3, self.target_size, + self.target_size) / 255 + np_imgs = np.concatenate((np_imgs, img)) + + np_imgs -= self.img_mean + np_imgs /= self.img_std + np_imgs = np.reshape(np_imgs, (self.seg_num, self.seg_len * 3, + self.target_size, self.target_size)) + + return np_imgs, label From 946a54e531f2c40b5c8d2eab217dcf95d1ef73d4 Mon Sep 17 00:00:00 2001 From: dengkaipeng Date: Mon, 30 Mar 2020 10:35:07 +0000 Subject: [PATCH 02/13] refine format --- tsm.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tsm.py b/tsm.py index 71b185a22f785..7f0932fa46600 100644 --- a/tsm.py +++ b/tsm.py @@ -30,7 +30,7 @@ def make_optimizer(num_samples, parameter_list=None): - step = int(num_samples / FLAGS.batch_size) + step = int(num_samples / FLAGS.batch_size / FLAGS.num_devices) boundaries = [e * step for e in [40, 60]] values = [FLAGS.lr * (0.1 ** i) for i in range(len(boundaries) + 1)] @@ -78,13 +78,13 @@ def main(): model.prepare( optim, CrossEntropy(), - Accuracy(topk=(1, 5)), + metrics=Accuracy(topk=(1, 5)), inputs=inputs, labels=labels, device=FLAGS.device) if FLAGS.eval_only: - if FLGAS.weights: + if FLGAS.weights is not None: model.load(FLAGS.weights) model.evaluate( @@ -96,8 +96,8 @@ def main(): if FLAGS.resume is not None: model.load(FLAGS.resume) - model.fit(train_dataset, - val_dataset, + model.fit(train_data=train_dataset, + eval_data=val_dataset, epochs=FLAGS.epoch, batch_size=FLAGS.batch_size, save_dir='tsm_checkpoint', From 3de4bd2616d85611e782f09f436e9fa1d11a94b6 Mon Sep 17 00:00:00 2001 From: dengkaipeng Date: Tue, 31 Mar 2020 09:16:48 +0000 Subject: [PATCH 03/13] fix indent --- tsm/kinetics_dataset.py | 70 +++++------ tsm/transforms.py | 267 ++++++++++++++++++++-------------------- 2 files changed, 168 insertions(+), 169 deletions(-) diff --git a/tsm/kinetics_dataset.py b/tsm/kinetics_dataset.py index 3662c91408fbe..7c665bc3f8cca 100644 --- a/tsm/kinetics_dataset.py +++ b/tsm/kinetics_dataset.py @@ -96,43 +96,43 @@ def __getitem__(self, idx): return imgs, np.array([label]) def _video_loader(self, frames): - videolen = len(frames) - average_dur = int(videolen / self.seg_num) - - imgs = [] - for i in range(self.seg_num): - idx = 0 - if self.mode == 'train': - if average_dur >= self.seg_len: - idx = random.randint(0, average_dur - self.seg_len) - idx += i * average_dur - elif average_dur >= 1: - idx += i * average_dur - else: - idx = i - else: - if average_dur >= self.seg_len: - idx = (average_dur - self.seg_len) // 2 - idx += i * average_dur - elif average_dur >= 1: - idx += i * average_dur - else: - idx = i - - for jj in range(idx, idx + self.seg_len): - imgbuf = frames[int(jj % videolen)] - img = self._imageloader(imgbuf) - imgs.append(img) - - return imgs + videolen = len(frames) + average_dur = int(videolen / self.seg_num) + + imgs = [] + for i in range(self.seg_num): + idx = 0 + if self.mode == 'train': + if average_dur >= self.seg_len: + idx = random.randint(0, average_dur - self.seg_len) + idx += i * average_dur + elif average_dur >= 1: + idx += i * average_dur + else: + idx = i + else: + if average_dur >= self.seg_len: + idx = (average_dur - self.seg_len) // 2 + idx += i * average_dur + elif average_dur >= 1: + idx += i * average_dur + else: + idx = i + + for jj in range(idx, idx + self.seg_len): + imgbuf = frames[int(jj % videolen)] + img = self._imageloader(imgbuf) + imgs.append(img) + + return imgs def _imageloader(self, buf): - if isinstance(buf, str): - img = Image.open(StringIO(buf)) - else: - img = Image.open(BytesIO(buf)) - - return img.convert('RGB') + if isinstance(buf, str): + img = Image.open(StringIO(buf)) + else: + img = Image.open(BytesIO(buf)) + + return img.convert('RGB') if __name__ == "__main__": diff --git a/tsm/transforms.py b/tsm/transforms.py index 49d8cf6bee4b5..230e2f8a33279 100644 --- a/tsm/transforms.py +++ b/tsm/transforms.py @@ -52,25 +52,25 @@ def __init__(self, target_size=224): self.target_size = target_size def __call__(self, imgs, label): - resized_imgs = [] - for i in range(len(imgs)): - img = imgs[i] - w, h = img.size - if (w <= h and w == self.target_size) or \ + resized_imgs = [] + for i in range(len(imgs)): + img = imgs[i] + w, h = img.size + if (w <= h and w == self.target_size) or \ (h <= w and h == self.target_size): - resized_imgs.append(img) - continue + resized_imgs.append(img) + continue - if w < h: - ow = self.target_size - oh = int(self.target_size * 4.0 / 3.0) - resized_imgs.append(img.resize((ow, oh), Image.BILINEAR)) - else: - oh = self.target_size - ow = int(self.target_size * 4.0 / 3.0) - resized_imgs.append(img.resize((ow, oh), Image.BILINEAR)) - - return resized_imgs, label + if w < h: + ow = self.target_size + oh = int(self.target_size * 4.0 / 3.0) + resized_imgs.append(img.resize((ow, oh), Image.BILINEAR)) + else: + oh = self.target_size + ow = int(self.target_size * 4.0 / 3.0) + resized_imgs.append(img.resize((ow, oh), Image.BILINEAR)) + + return resized_imgs, label class GroupMultiScaleCrop(object): @@ -91,75 +91,74 @@ def __init__(self, self.more_fix_crop = more_fix_crop def __call__(self, imgs, label): - input_size = [self.short_size, self.short_size] - - im_size = imgs[0].size - - # get random crop offset - def _sample_crop_size(im_size): - image_w, image_h = im_size[0], im_size[1] - - base_size = min(image_w, image_h) - crop_sizes = [int(base_size * x) for x in self.scales] - crop_h = [ - input_size[1] if abs(x - input_size[1]) < 3 else x - for x in crop_sizes - ] - crop_w = [ - input_size[0] if abs(x - input_size[0]) < 3 else x - for x in crop_sizes - ] - - pairs = [] - for i, h in enumerate(crop_h): - for j, w in enumerate(crop_w): - if abs(i - j) <= self.max_distort: - pairs.append((w, h)) - crop_pair = random.choice(pairs) - if not self.fix_crop: - w_offset = np.random.randint(0, image_w - crop_pair[0]) - h_offset = np.random.randint(0, image_h - crop_pair[1]) - else: - w_step = (image_w - crop_pair[0]) / 4 - h_step = (image_h - crop_pair[1]) / 4 - - ret = list() - ret.append((0, 0)) # upper left - if w_step != 0: - ret.append((4 * w_step, 0)) # upper right - if h_step != 0: - ret.append((0, 4 * h_step)) # lower left - if h_step != 0 and w_step != 0: - ret.append((4 * w_step, 4 * h_step)) # lower right - if h_step != 0 or w_step != 0: - ret.append((2 * w_step, 2 * h_step)) # center - - if self.more_fix_crop: - ret.append((0, 2 * h_step)) # center left - ret.append((4 * w_step, 2 * h_step)) # center right - ret.append((2 * w_step, 4 * h_step)) # lower center - ret.append((2 * w_step, 0 * h_step)) # upper center - - ret.append((1 * w_step, 1 * h_step)) # upper left quarter - ret.append((3 * w_step, 1 * h_step)) # upper right quarter - ret.append((1 * w_step, 3 * h_step)) # lower left quarter - ret.append((3 * w_step, 3 * h_step)) # lower righ quarter - - w_offset, h_offset = random.choice(ret) - - return crop_pair[0], crop_pair[1], w_offset, h_offset - - crop_w, crop_h, offset_w, offset_h = _sample_crop_size(im_size) - crop_imgs = [ - img.crop((offset_w, offset_h, offset_w + crop_w, offset_h + crop_h)) - for img in imgs - ] - ret_imgs = [ - img.resize((input_size[0], input_size[1]), Image.BILINEAR) - for img in crop_imgs - ] - - return ret_imgs, label + input_size = [self.short_size, self.short_size] + im_size = imgs[0].size + + # get random crop offset + def _sample_crop_size(im_size): + image_w, image_h = im_size[0], im_size[1] + + base_size = min(image_w, image_h) + crop_sizes = [int(base_size * x) for x in self.scales] + crop_h = [ + input_size[1] if abs(x - input_size[1]) < 3 else x + for x in crop_sizes + ] + crop_w = [ + input_size[0] if abs(x - input_size[0]) < 3 else x + for x in crop_sizes + ] + + pairs = [] + for i, h in enumerate(crop_h): + for j, w in enumerate(crop_w): + if abs(i - j) <= self.max_distort: + pairs.append((w, h)) + crop_pair = random.choice(pairs) + if not self.fix_crop: + w_offset = np.random.randint(0, image_w - crop_pair[0]) + h_offset = np.random.randint(0, image_h - crop_pair[1]) + else: + w_step = (image_w - crop_pair[0]) / 4 + h_step = (image_h - crop_pair[1]) / 4 + + ret = list() + ret.append((0, 0)) # upper left + if w_step != 0: + ret.append((4 * w_step, 0)) # upper right + if h_step != 0: + ret.append((0, 4 * h_step)) # lower left + if h_step != 0 and w_step != 0: + ret.append((4 * w_step, 4 * h_step)) # lower right + if h_step != 0 or w_step != 0: + ret.append((2 * w_step, 2 * h_step)) # center + + if self.more_fix_crop: + ret.append((0, 2 * h_step)) # center left + ret.append((4 * w_step, 2 * h_step)) # center right + ret.append((2 * w_step, 4 * h_step)) # lower center + ret.append((2 * w_step, 0 * h_step)) # upper center + + ret.append((1 * w_step, 1 * h_step)) # upper left quarter + ret.append((3 * w_step, 1 * h_step)) # upper right quarter + ret.append((1 * w_step, 3 * h_step)) # lower left quarter + ret.append((3 * w_step, 3 * h_step)) # lower righ quarter + + w_offset, h_offset = random.choice(ret) + + return crop_pair[0], crop_pair[1], w_offset, h_offset + + crop_w, crop_h, offset_w, offset_h = _sample_crop_size(im_size) + crop_imgs = [ + img.crop((offset_w, offset_h, offset_w + crop_w, offset_h + crop_h)) + for img in imgs + ] + ret_imgs = [ + img.resize((input_size[0], input_size[1]), Image.BILINEAR) + for img in crop_imgs + ] + + return ret_imgs, label class GroupRandomCrop(object): @@ -167,34 +166,34 @@ def __init__(self, target_size=224): self.target_size = target_size def __call__(self, imgs, label): - w, h = imgs[0].size - th, tw = self.target_size, self.target_size - - assert (w >= self.target_size) and (h >= self.target_size), \ - "image width({}) and height({}) should be larger than " \ - "crop size".format(w, h, self.target_size) - - out_images = [] - x1 = np.random.randint(0, w - tw) - y1 = np.random.randint(0, h - th) - - for img in imgs: - if w == tw and h == th: - out_images.append(img) - else: - out_images.append(img.crop((x1, y1, x1 + tw, y1 + th))) - - return out_images, label + w, h = imgs[0].size + th, tw = self.target_size, self.target_size + + assert (w >= self.target_size) and (h >= self.target_size), \ + "image width({}) and height({}) should be larger than " \ + "crop size".format(w, h, self.target_size) + + out_images = [] + x1 = np.random.randint(0, w - tw) + y1 = np.random.randint(0, h - th) + + for img in imgs: + if w == tw and h == th: + out_images.append(img) + else: + out_images.append(img.crop((x1, y1, x1 + tw, y1 + th))) + + return out_images, label class GroupRandomFlip(object): def __call__(self, imgs, label): - v = np.random.random() - if v < 0.5: - ret = [img.transpose(Image.FLIP_LEFT_RIGHT) for img in imgs] - return ret, label - else: - return imgs, label + v = np.random.random() + if v < 0.5: + ret = [img.transpose(Image.FLIP_LEFT_RIGHT) for img in imgs] + return ret, label + else: + return imgs, label class GroupCenterCrop(object): @@ -202,18 +201,18 @@ def __init__(self, target_size=224): self.target_size = target_size def __call__(self, imgs, label): - crop_imgs = [] - for img in imgs: - w, h = img.size - th, tw = self.target_size, self.target_size - assert (w >= self.target_size) and (h >= self.target_size), \ - "image width({}) and height({}) should be larger " \ - "than crop size".format(w, h, self.target_size) - x1 = int(round((w - tw) / 2.)) - y1 = int(round((h - th) / 2.)) - crop_imgs.append(img.crop((x1, y1, x1 + tw, y1 + th))) - - return crop_imgs, label + crop_imgs = [] + for img in imgs: + w, h = img.size + th, tw = self.target_size, self.target_size + assert (w >= self.target_size) and (h >= self.target_size), \ + "image width({}) and height({}) should be larger " \ + "than crop size".format(w, h, self.target_size) + x1 = int(round((w - tw) / 2.)) + y1 = int(round((h - th) / 2.)) + crop_imgs.append(img.crop((x1, y1, x1 + tw, y1 + th))) + + return crop_imgs, label class NormalizeImage(object): @@ -230,18 +229,18 @@ def __init__(self, self.seg_len = seg_len def __call__(self, imgs, label): - np_imgs = (np.array(imgs[0]).astype('float32').transpose( - (2, 0, 1))).reshape(1, 3, self.target_size, + np_imgs = (np.array(imgs[0]).astype('float32').transpose( + (2, 0, 1))).reshape(1, 3, self.target_size, self.target_size) / 255 - for i in range(len(imgs) - 1): - img = (np.array(imgs[i + 1]).astype('float32').transpose( - (2, 0, 1))).reshape(1, 3, self.target_size, + for i in range(len(imgs) - 1): + img = (np.array(imgs[i + 1]).astype('float32').transpose( + (2, 0, 1))).reshape(1, 3, self.target_size, self.target_size) / 255 - np_imgs = np.concatenate((np_imgs, img)) - - np_imgs -= self.img_mean - np_imgs /= self.img_std - np_imgs = np.reshape(np_imgs, (self.seg_num, self.seg_len * 3, - self.target_size, self.target_size)) - + np_imgs = np.concatenate((np_imgs, img)) + + np_imgs -= self.img_mean + np_imgs /= self.img_std + np_imgs = np.reshape(np_imgs, (self.seg_num, self.seg_len * 3, + self.target_size, self.target_size)) + return np_imgs, label From 527c90baf7fb3f06e6878091fafec6854603a3fd Mon Sep 17 00:00:00 2001 From: dengkaipeng Date: Wed, 1 Apr 2020 03:20:58 +0000 Subject: [PATCH 04/13] refine comment --- tsm.py | 25 +++++++++++----------- tsm/kinetics_dataset.py | 47 +++++++++++++++++++++++++++++------------ tsm/modeling.py | 9 ++++++++ 3 files changed, 55 insertions(+), 26 deletions(-) diff --git a/tsm.py b/tsm.py index c3d0a26f17bba..aa59aee59b978 100644 --- a/tsm.py +++ b/tsm.py @@ -20,18 +20,15 @@ import numpy as np from paddle import fluid -from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear +from paddle.fluid.dygraph.parallel import ParallelEnv from model import Model, CrossEntropy, Input, set_device from metrics import Accuracy from tsm import * -NUM_CLASSES = 10 - -def make_optimizer(num_samples, parameter_list=None): - step = int(num_samples / FLAGS.batch_size / FLAGS.num_devices) - boundaries = [e * step for e in [40, 60]] +def make_optimizer(step_per_epoch, parameter_list=None): + boundaries = [e * step_per_epoch for e in [40, 60]] values = [FLAGS.lr * (0.1 ** i) for i in range(len(boundaries) + 1)] learning_rate = fluid.layers.piecewise_decay( @@ -56,20 +53,26 @@ def main(): GroupRandomFlip(), NormalizeImage()]) train_dataset = KineticsDataset( - filelist=os.path.join(FLAGS.data, 'train_10.list'), + file_list=os.path.join(FLAGS.data, 'train_10.list'), pickle_dir=os.path.join(FLAGS.data, 'train_10'), + label_list=os.path.join(FLAGS.data, 'label_list'), transform=train_transform) val_transform = Compose([GroupScale(), GroupCenterCrop(), NormalizeImage()]) val_dataset = KineticsDataset( - filelist=os.path.join(FLAGS.data, 'val_10.list'), + file_list=os.path.join(FLAGS.data, 'val_10.list'), pickle_dir=os.path.join(FLAGS.data, 'val_10'), + label_list=os.path.join(FLAGS.data, 'label_list'), mode='val', transform=val_transform) pretrained = FLAGS.eval_only and FLAGS.weights is None - model = tsm_resnet50(num_classes=NUM_CLASSES, pretrained=pretrained) + model = tsm_resnet50(num_classes=train_dataset.num_classes, + pretrained=pretrained) + + step_per_epoch = int(len(train_dataset) / FLAGS.batch_size \ + / ParallelEnv().nranks) optim = make_optimizer(len(train_dataset), model.parameters()) inputs = [Input([None, 8, 3, 224, 224], 'float32', name='image')] @@ -101,7 +104,7 @@ def main(): epochs=FLAGS.epoch, batch_size=FLAGS.batch_size, save_dir='tsm_checkpoint', - num_workers=4, + num_workers=FLAGS.num_workers, drop_last=True, shuffle=True) @@ -128,8 +131,6 @@ def main(): help='initial learning rate') parser.add_argument( "-b", "--batch_size", default=16, type=int, help="batch size") - parser.add_argument( - "-n", "--num_devices", default=1, type=int, help="number of devices") parser.add_argument( "-r", "--resume", diff --git a/tsm/kinetics_dataset.py b/tsm/kinetics_dataset.py index 7c665bc3f8cca..7d0e8fe11db6b 100644 --- a/tsm/kinetics_dataset.py +++ b/tsm/kinetics_dataset.py @@ -33,32 +33,52 @@ __all__ = ['KineticsDataset'] +KINETICS_CLASS_NUM = 400 + class KineticsDataset(Dataset): """ Kinetics dataset Args: - filelist (str): path to file list, default None. - num_classes (int): class number + file_list (str): path to file list + pickle_dir (str): path to pickle file directory + label_list (str): path to label_list file, if set None, the + default class number 400 of kinetics dataset will be + used. Default None + mode (str): 'train' or 'val' mode, segmentation methods will + be different in these 2 modes. Default 'train' + seg_num (int): segment number to sample from each video. + Default 8 + seg_len (int): frame number of each segment. Default 1 + transform (callable): transforms to perform on video samples, + None for no transforms. Default None. """ def __init__(self, - filelist, + file_list, pickle_dir, + label_list=None, mode='train', seg_num=8, seg_len=1, transform=None): - assert os.path.isfile(filelist), \ - "filelist {} not a file".format(filelist) - with open(filelist) as f: + assert os.path.isfile(file_list), \ + "file_list {} not a file".format(file_list) + with open(file_list) as f: self.pickle_paths = [l.strip() for l in f] assert os.path.isdir(pickle_dir), \ "pickle_dir {} not a directory".format(pickle_dir) self.pickle_dir = pickle_dir + self.label_list = label_list + if self.label_list is not None: + assert os.path.isfile(self.label_list), \ + "label_list {} not a file".format(self.label_list) + with open(self.label_list) as f: + self.label_list = [int(l.strip()) for l in f] + assert mode in ['train', 'val'], \ "mode can only be 'train' or 'val'" self.mode = mode @@ -87,14 +107,19 @@ def __getitem__(self, idx): logger.error("Load {} failed: {}".format(pickle_path, e)) sys.exit(-1) - label_list = [0, 2, 3, 4, 6, 7, 9, 12, 14, 15] - label = label_list.index(label) + if self.label_list is not None: + label = self.label_list.index(label) imgs = self._video_loader(frames) if self.transform: imgs, label = self.transform(imgs, label) return imgs, np.array([label]) + @property + def num_classes(self): + return KINETICS_CLASS_NUM if self.label_list is None \ + else len(self.label_list) + def _video_loader(self, frames): videolen = len(frames) average_dur = int(videolen / self.seg_num) @@ -134,9 +159,3 @@ def _imageloader(self, buf): return img.convert('RGB') - -if __name__ == "__main__": - kd = KineticsDataset('/paddle/ssd3/kineteics_mini/val_10.list', '/paddle/ssd3/kineteics_mini/val_10') - print("KineticsDataset length", len(kd)) - for d in kd: - print(len(d[0]), d[0][0].size, d[1]) diff --git a/tsm/modeling.py b/tsm/modeling.py index 81a0bf4007cac..973d310e16029 100644 --- a/tsm/modeling.py +++ b/tsm/modeling.py @@ -113,6 +113,15 @@ def forward(self, inputs): class TSM_ResNet(Model): + """ + TSM network with ResNet as backbone + + Args: + num_layers (int): ResNet layer number, only support 50 currently. + Default 50. + seg_num (int): segment number of each video sample. Default 8. + num_classes (int): video class number. Default 400. + """ def __init__(self, num_layers=50, seg_num=8, num_classes=400): super(TSM_ResNet, self).__init__() From 9ecec85402444cefc39ce2144f20918eee2f4110 Mon Sep 17 00:00:00 2001 From: dengkaipeng Date: Wed, 1 Apr 2020 07:25:19 +0000 Subject: [PATCH 05/13] refine code and add README --- tsm/README.md | 102 ++++++++++++++++++++++++++++++++++++++++++ tsm/__init__.py | 26 ----------- tsm.py => tsm/main.py | 13 +++++- tsm/modeling.py | 2 +- 4 files changed, 114 insertions(+), 29 deletions(-) create mode 100644 tsm/README.md delete mode 100644 tsm/__init__.py rename tsm.py => tsm/main.py (93%) diff --git a/tsm/README.md b/tsm/README.md new file mode 100644 index 0000000000000..019e1543e51b4 --- /dev/null +++ b/tsm/README.md @@ -0,0 +1,102 @@ +# TSM 视频分类模型 + +--- + +## 内容 + +- [模型简介](#模型简介) +- [快速开始](#快速开始) +- [参考论文](#参考论文) + + +## 模型简介 + +Temporal Shift Module是由MIT和IBM Watson AI Lab的Ji Lin,Chuang Gan和Song Han等人提出的通过时间位移来提高网络视频理解能力的模块,其位移操作原理如下图所示。 + +

+
+Temporal shift module +

+ +上图中矩阵表示特征图中的temporal和channel维度,通过将一部分的channel在temporal维度上向前位移一步,一部分的channel在temporal维度上向后位移一步,位移后的空缺补零。通过这种方式在特征图中引入temporal维度上的上下文交互,提高在时间维度上的视频理解能力。 + +TSM模型是将Temporal Shift Module插入到ResNet网络中构建的视频分类模型,本模型库实现版本为以ResNet-50作为主干网络的TSM模型。 + +详细内容请参考论文[Temporal Shift Module for Efficient Video Understanding](https://arxiv.org/abs/1811.08383v1) + +## 快速开始 + +### 安装说明 + +#### paddle安装 + + 本项目依赖于 PaddlePaddle 1.7及以上版本或适当的develop版本,请参考 [安装指南](http://www.paddlepaddle.org/#quick-start) 进行安装 + +#### 代码下载及环境变量设置 + + 克隆代码库到本地,并设置`PYTHONPATH`环境变量 + ```shell + git clone https://github.com/PaddlePaddle/hapi + cd hapi + export PYTHONPATH=$PYTHONPATH:`pwd` + cd tsm + ``` + +### 数据准备 + +TSM的训练数据采用由DeepMind公布的Kinetics-400动作识别数据集。数据下载及准备请参考[数据说明](https://github.com/PaddlePaddle/models/blob/release/1.7/PaddleCV/video/data/dataset/README.md) + +#### 小数据集验证 + +为了便于快速迭代,我们采用了较小的数据集进行动态图训练验证,从Kinetics-400数据集中选取分类标签(label)分别为 0, 2, 3, 4, 6, 7, 9, 12, 14, 15的即前10类数据验证模型精度。 + +### 模型训练及评估 + +数据准备完毕后,可以通过如下方式启动训练和评估,如下脚本会自动每epoch交替进行训练和模型评估,并将checkpoint默认保存在`tsm_checkpoint`目录下。 + +#### 静态图训练 + +使用如下方式进行单卡训练: + +```shell +export CUDA_VISIBLE_DEVICES=0 +python main.py --data= --batch_size=16 +``` + +使用如下方式进行多卡训练: + +```shell +CUDA_VISIBLE_DEVICES=0,1 python main.py --data= --batch_size=8 +``` + +#### 动态图训练 + +动态图训练只需要在运行脚本时添加`-d`参数即可。 + +使用如下方式进行单卡训练: + +```shell +export CUDA_VISIBLE_DEVICES=0 +python main.py --data= --batch_size=16 -d +``` + +使用如下方式进行多卡训练: + +```shell +CUDA_VISIBLE_DEVICES=0,1 python main.py --data= --batch_size=8 -d +``` + +**注意:** 对于静态图和动态图,多卡训练中`--batch_size`为每卡上的batch_size,即总batch_size为`--batch_size`乘以卡数 + +### 评估精度 + +在10类小数据集下训练模型权重见[model](https://paddlemodels.bj.bcebos.com/hapi/tsm_resnet50.pdparams),评估精度如下: + +|Top-1|Top-5| +|:-:|:-:| +|76.5%|98.0%| + +## 参考论文 + +- [Temporal Shift Module for Efficient Video Understanding](https://arxiv.org/abs/1811.08383v1), Ji Lin, Chuang Gan, Song Han + diff --git a/tsm/__init__.py b/tsm/__init__.py deleted file mode 100644 index 8adefe6b24c05..0000000000000 --- a/tsm/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from . import kinetics_dataset -from .kinetics_dataset import * - -from . import modeling -from .modeling import * - -from . import transforms -from .transforms import * - -__all__ = kinetics_dataset.__all__ \ - + modeling.__all__ \ - + transforms.__all__ diff --git a/tsm.py b/tsm/main.py similarity index 93% rename from tsm.py rename to tsm/main.py index aa59aee59b978..2266bb46e83d1 100644 --- a/tsm.py +++ b/tsm/main.py @@ -24,7 +24,11 @@ from model import Model, CrossEntropy, Input, set_device from metrics import Accuracy -from tsm import * + +from check import check_gpu, check_version +from modeling import tsm_resnet50 +from kinetics_dataset import KineticsDataset +from transforms import * def make_optimizer(step_per_epoch, parameter_list=None): @@ -111,7 +115,9 @@ def main(): if __name__ == '__main__': parser = argparse.ArgumentParser("CNN training on TSM") - parser.add_argument('data', metavar='DIR', help='path to kineteics dataset') + parser.add_argument( + "--data", type=str, default='dataset/kinetics', + help="path to dataset root directory") parser.add_argument( "--device", type=str, default='gpu', help="device to use, gpu or cpu") parser.add_argument( @@ -144,4 +150,7 @@ def main(): type=str, help="weights path for evaluation") FLAGS = parser.parse_args() + + check_gpu(str.lower(FLAGS.device) == 'gpu') + check_version() main() diff --git a/tsm/modeling.py b/tsm/modeling.py index 973d310e16029..bac51016851ed 100644 --- a/tsm/modeling.py +++ b/tsm/modeling.py @@ -169,7 +169,7 @@ def __init__(self, num_layers=50, seg_num=8, num_classes=400): param_attr=fluid.param_attr.ParamAttr( initializer=fluid.initializer.Uniform(-stdv, stdv)), bias_attr=fluid.param_attr.ParamAttr( - learning_rate=2.0, regularizer=fluid.regularizer.L2Decay(0.))) + learning_rate=1.0, regularizer=fluid.regularizer.L2Decay(0.))) def forward(self, inputs): y = fluid.layers.reshape( From 3b089ed523e1ab7d5c5208390b380d91403f7532 Mon Sep 17 00:00:00 2001 From: dengkaipeng Date: Wed, 1 Apr 2020 07:30:20 +0000 Subject: [PATCH 06/13] refine README --- tsm/README.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/tsm/README.md b/tsm/README.md index 019e1543e51b4..918a7e01cd5f0 100644 --- a/tsm/README.md +++ b/tsm/README.md @@ -50,7 +50,7 @@ TSM的训练数据采用由DeepMind公布的Kinetics-400动作识别数据集。 为了便于快速迭代,我们采用了较小的数据集进行动态图训练验证,从Kinetics-400数据集中选取分类标签(label)分别为 0, 2, 3, 4, 6, 7, 9, 12, 14, 15的即前10类数据验证模型精度。 -### 模型训练及评估 +### 模型训练 数据准备完毕后,可以通过如下方式启动训练和评估,如下脚本会自动每epoch交替进行训练和模型评估,并将checkpoint默认保存在`tsm_checkpoint`目录下。 @@ -88,9 +88,25 @@ CUDA_VISIBLE_DEVICES=0,1 python main.py --data= --batch_size=8 **注意:** 对于静态图和动态图,多卡训练中`--batch_size`为每卡上的batch_size,即总batch_size为`--batch_size`乘以卡数 -### 评估精度 +### 模型评估 -在10类小数据集下训练模型权重见[model](https://paddlemodels.bj.bcebos.com/hapi/tsm_resnet50.pdparams),评估精度如下: +可通过如下两种方式进行模型评估。 + +1. 自动下载Paddle发布的[TSM-ResNet50](https://paddlemodels.bj.bcebos.com/hapi/tsm_resnet50.pdparams)权重评估 + +``` +python main.py --data --eval_only +``` + +2. 加载checkpoint进行精度评估 + +``` +python main.py --data --eval_only --weights=tsm_checkpoint/final +``` + +#### 评估精度 + +在10类小数据集下训练模型权重见[TSM-ResNet50](https://paddlemodels.bj.bcebos.com/hapi/tsm_resnet50.pdparams),评估精度如下: |Top-1|Top-5| |:-:|:-:| From 285753a934dc3d36de0a427c073acc5d9d856eff Mon Sep 17 00:00:00 2001 From: dengkaipeng Date: Wed, 1 Apr 2020 07:37:57 +0000 Subject: [PATCH 07/13] refine README --- tsm/README.md | 8 ++++++- tsm/check.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 tsm/check.py diff --git a/tsm/README.md b/tsm/README.md index 918a7e01cd5f0..68e7c37b012dd 100644 --- a/tsm/README.md +++ b/tsm/README.md @@ -52,7 +52,13 @@ TSM的训练数据采用由DeepMind公布的Kinetics-400动作识别数据集。 ### 模型训练 -数据准备完毕后,可以通过如下方式启动训练和评估,如下脚本会自动每epoch交替进行训练和模型评估,并将checkpoint默认保存在`tsm_checkpoint`目录下。 +数据准备完毕后,可使用`main.py`脚本启动训练和评估,如下脚本会自动每epoch交替进行训练和模型评估,并将checkpoint默认保存在`tsm_checkpoint`目录下。 + +`main.py`脚本参数可通过如下命令查询 + +```shell +python main.py --help +``` #### 静态图训练 diff --git a/tsm/check.py b/tsm/check.py new file mode 100644 index 0000000000000..16c07568c7f1a --- /dev/null +++ b/tsm/check.py @@ -0,0 +1,62 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import sys + +import paddle.fluid as fluid + +import logging +logger = logging.getLogger(__name__) + +__all__ = ['check_gpu', 'check_version'] + + +def check_gpu(use_gpu): + """ + Log error and exit when set use_gpu=true in paddlepaddle + cpu version. + """ + err = "Config use_gpu cannot be set as true while you are " \ + "using paddlepaddle cpu version ! \nPlease try: \n" \ + "\t1. Install paddlepaddle-gpu to run model on GPU \n" \ + "\t2. Set use_gpu as false in config file to run " \ + "model on CPU" + + try: + if use_gpu and not fluid.is_compiled_with_cuda(): + logger.error(err) + sys.exit(1) + except Exception as e: + pass + + +def check_version(version='1.7.0'): + """ + Log error and exit when the installed version of paddlepaddle is + not satisfied. + """ + err = "PaddlePaddle version {} or higher is required, " \ + "or a suitable develop version is satisfied as well. \n" \ + "Please make sure the version is good with your code." \ + .format(version) + + try: + fluid.require_version(version) + except Exception as e: + logger.error(err) + sys.exit(1) From ed0360eac2059c490408ad578051a325c508408c Mon Sep 17 00:00:00 2001 From: dengkaipeng Date: Wed, 1 Apr 2020 07:57:44 +0000 Subject: [PATCH 08/13] add image and dataset --- tsm/README.md | 4 +- tsm/dataset/README.md | 78 ++++++++++++++++++++++ tsm/dataset/kinetics/generate_label.py | 44 +++++++++++++ tsm/dataset/kinetics/video2pkl.py | 87 +++++++++++++++++++++++++ tsm/images/temporal_shift.png | Bin 0 -> 170430 bytes 5 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 tsm/dataset/README.md create mode 100644 tsm/dataset/kinetics/generate_label.py create mode 100644 tsm/dataset/kinetics/video2pkl.py create mode 100644 tsm/images/temporal_shift.png diff --git a/tsm/README.md b/tsm/README.md index 68e7c37b012dd..bb5d148376bc0 100644 --- a/tsm/README.md +++ b/tsm/README.md @@ -14,7 +14,7 @@ Temporal Shift Module是由MIT和IBM Watson AI Lab的Ji Lin,Chuang Gan和Song Han等人提出的通过时间位移来提高网络视频理解能力的模块,其位移操作原理如下图所示。

-
+
Temporal shift module

@@ -44,7 +44,7 @@ TSM模型是将Temporal Shift Module插入到ResNet网络中构建的视频分 ### 数据准备 -TSM的训练数据采用由DeepMind公布的Kinetics-400动作识别数据集。数据下载及准备请参考[数据说明](https://github.com/PaddlePaddle/models/blob/release/1.7/PaddleCV/video/data/dataset/README.md) +TSM的训练数据采用由DeepMind公布的Kinetics-400动作识别数据集。数据下载及准备请参考[数据说明](./dataset/README.md) #### 小数据集验证 diff --git a/tsm/dataset/README.md b/tsm/dataset/README.md new file mode 100644 index 0000000000000..55613ef3cbaf9 --- /dev/null +++ b/tsm/dataset/README.md @@ -0,0 +1,78 @@ +# 数据使用说明 + +## Kinetics数据集 + +Kinetics数据集是DeepMind公开的大规模视频动作识别数据集,有Kinetics400与Kinetics600两个版本。这里使用Kinetics400数据集,具体的数据预处理过程如下。 + +### mp4视频下载 +在Code\_Root目录下创建文件夹 + + cd $Code_Root/data/dataset && mkdir kinetics + + cd kinetics && mkdir data_k400 && cd data_k400 + + mkdir train_mp4 && mkdir val_mp4 + +ActivityNet官方提供了Kinetics的下载工具,具体参考其[官方repo ](https://github.com/activitynet/ActivityNet/tree/master/Crawler/Kinetics)即可下载Kinetics400的mp4视频集合。将kinetics400的训练与验证集合分别下载到data/dataset/kinetics/data\_k400/train\_mp4与data/dataset/kinetics/data\_k400/val\_mp4。 + +### mp4文件预处理 + +为提高数据读取速度,提前将mp4文件解帧并打pickle包,dataloader从视频的pkl文件中读取数据(该方法耗费更多存储空间)。pkl文件里打包的内容为(video-id, label, [frame1, frame2,...,frameN])。 + +在 data/dataset/kinetics/data\_k400目录下创建目录train\_pkl和val\_pkl + + cd $Code_Root/data/dataset/kinetics/data_k400 + + mkdir train_pkl && mkdir val_pkl + +进入$Code\_Root/data/dataset/kinetics目录,使用video2pkl.py脚本进行数据转化。首先需要下载[train](https://github.com/activitynet/ActivityNet/tree/master/Crawler/Kinetics/data/kinetics-400_train.csv)和[validation](https://github.com/activitynet/ActivityNet/tree/master/Crawler/Kinetics/data/kinetics-400_val.csv)数据集的文件列表。 + +首先生成预处理需要的数据集标签文件 + + python generate_label.py kinetics-400_train.csv kinetics400_label.txt + +然后执行如下程序: + + python video2pkl.py kinetics-400_train.csv $Source_dir $Target_dir 8 #以8个进程为例 + +- 该脚本依赖`ffmpeg`库,请预先安装`ffmpeg` + +对于train数据, + + Source_dir = $Code_Root/data/dataset/kinetics/data_k400/train_mp4 + + Target_dir = $Code_Root/data/dataset/kinetics/data_k400/train_pkl + +对于val数据, + + Source_dir = $Code_Root/data/dataset/kinetics/data_k400/val_mp4 + + Target_dir = $Code_Root/data/dataset/kinetics/data_k400/val_pkl + +这样即可将mp4文件解码并保存为pkl文件。 + +### 生成训练和验证集list +·· + cd $Code_Root/data/dataset/kinetics + + ls $Code_Root/data/dataset/kinetics/data_k400/train_pkl/* > train.list + + ls $Code_Root/data/dataset/kinetics/data_k400/val_pkl/* > val.list + + ls $Code_Root/data/dataset/kinetics/data_k400/val_pkl/* > test.list + + ls $Code_Root/data/dataset/kinetics/data_k400/val_pkl/* > infer.list + +即可生成相应的文件列表,train.list和val.list的每一行表示一个pkl文件的绝对路径,示例如下: + + /ssd1/user/models/PaddleCV/PaddleVideo/data/dataset/kinetics/data_k400/train_pkl/data_batch_100-097 + /ssd1/user/models/PaddleCV/PaddleVideo/data/dataset/kinetics/data_k400/train_pkl/data_batch_100-114 + /ssd1/user/models/PaddleCV/PaddleVideo/data/dataset/kinetics/data_k400/train_pkl/data_batch_100-118 + ... + +或者 + + /ssd1/user/models/PaddleCV/PaddleVideo/data/dataset/kinetics/data_k400/val_pkl/data_batch_102-085 + /ssd1/user/models/PaddleCV/PaddleVideo/data/dataset/kinetics/data_k400/val_pkl/data_batch_102-086 + /ssd1/user/models/PaddleCV/PaddleVideo/data/dataset/kinetics/data_k400/val_pkl/data_batch_102-090 + ... diff --git a/tsm/dataset/kinetics/generate_label.py b/tsm/dataset/kinetics/generate_label.py new file mode 100644 index 0000000000000..d7608e86244c3 --- /dev/null +++ b/tsm/dataset/kinetics/generate_label.py @@ -0,0 +1,44 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import sys + +# kinetics-400_train.csv should be down loaded first and set as sys.argv[1] +# sys.argv[2] can be set as kinetics400_label.txt +# python generate_label.py kinetics-400_train.csv kinetics400_label.txt + +num_classes = 400 + +fname = sys.argv[1] +outname = sys.argv[2] +fl = open(fname).readlines() +fl = fl[1:] +outf = open(outname, 'w') + +label_list = [] +for line in fl: + label = line.strip().split(',')[0].strip('"') + if label in label_list: + continue + else: + label_list.append(label) + +assert len(label_list + ) == num_classes, "there should be {} labels in list, but ".format( + num_classes, len(label_list)) + +label_list.sort() +for i in range(num_classes): + outf.write('{} {}'.format(label_list[i], i) + '\n') + +outf.close() diff --git a/tsm/dataset/kinetics/video2pkl.py b/tsm/dataset/kinetics/video2pkl.py new file mode 100644 index 0000000000000..78d1b09b7bf6e --- /dev/null +++ b/tsm/dataset/kinetics/video2pkl.py @@ -0,0 +1,87 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +#Licensed under the Apache License, Version 2.0 (the "License"); +#you may not use this file except in compliance with the License. +#You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +#Unless required by applicable law or agreed to in writing, software +#distributed under the License is distributed on an "AS IS" BASIS, +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#See the License for the specific language governing permissions and +#limitations under the License. + +import os +import sys +import glob +try: + import cPickle as pickle +except: + import pickle +from multiprocessing import Pool + +# example command line: python generate_k400_pkl.py kinetics-400_train.csv 8 +# +# kinetics-400_train.csv is the training set file of K400 official release +# each line contains laebl,youtube_id,time_start,time_end,split,is_cc + +assert (len(sys.argv) == 5) + +f = open(sys.argv[1]) +source_dir = sys.argv[2] +target_dir = sys.argv[3] +num_threads = sys.argv[4] +all_video_entries = [x.strip().split(',') for x in f.readlines()] +all_video_entries = all_video_entries[1:] +f.close() + +category_label_map = {} +f = open('kinetics400_label.txt') +for line in f: + ens = line.strip().split(' ') + category = " ".join(ens[0:-1]) + label = int(ens[-1]) + category_label_map[category] = label +f.close() + + +def generate_pkl(entry): + mode = entry[4] + category = entry[0].strip('"') + category_dir = category + video_path = os.path.join( + './', + entry[1] + "_%06d" % int(entry[2]) + "_%06d" % int(entry[3]) + ".mp4") + video_path = os.path.join(source_dir, category_dir, video_path) + label = category_label_map[category] + + vid = './' + video_path.split('/')[-1].split('.')[0] + if os.path.exists(video_path): + if not os.path.exists(vid): + os.makedirs(vid) + os.system('ffmpeg -i ' + video_path + ' -q 0 ' + vid + '/%06d.jpg') + else: + print("File not exists {}".format(video_path)) + return + + images = sorted(glob.glob(vid + '/*.jpg')) + ims = [] + for img in images: + f = open(img, 'rb') + ims.append(f.read()) + f.close() + + output_pkl = vid + ".pkl" + output_pkl = os.path.join(target_dir, output_pkl) + f = open(output_pkl, 'wb') + pickle.dump((vid, label, ims), f, protocol=2) + f.close() + + os.system('rm -rf %s' % vid) + + +pool = Pool(processes=int(sys.argv[4])) +pool.map(generate_pkl, all_video_entries) +pool.close() +pool.join() diff --git a/tsm/images/temporal_shift.png b/tsm/images/temporal_shift.png new file mode 100644 index 0000000000000000000000000000000000000000..7679c4459d2b0ee37134b99fe1e8177b1a69f8b0 GIT binary patch literal 170430 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 z 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

DYrlftZ;k+}s@N(N#I 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 Date: Wed, 1 Apr 2020 15:19:16 +0000 Subject: [PATCH 09/13] fix type --- tsm/README.md | 23 ++++++++++++----------- tsm/main.py | 2 +- tsm/modeling.py | 4 ++-- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/tsm/README.md b/tsm/README.md index bb5d148376bc0..5d4e2b708743f 100644 --- a/tsm/README.md +++ b/tsm/README.md @@ -35,7 +35,8 @@ TSM模型是将Temporal Shift Module插入到ResNet网络中构建的视频分 #### 代码下载及环境变量设置 克隆代码库到本地,并设置`PYTHONPATH`环境变量 - ```shell + + ```bash git clone https://github.com/PaddlePaddle/hapi cd hapi export PYTHONPATH=$PYTHONPATH:`pwd` @@ -56,7 +57,7 @@ TSM的训练数据采用由DeepMind公布的Kinetics-400动作识别数据集。 `main.py`脚本参数可通过如下命令查询 -```shell +```bash python main.py --help ``` @@ -64,14 +65,14 @@ python main.py --help 使用如下方式进行单卡训练: -```shell +```bash export CUDA_VISIBLE_DEVICES=0 python main.py --data= --batch_size=16 ``` 使用如下方式进行多卡训练: -```shell +```bash CUDA_VISIBLE_DEVICES=0,1 python main.py --data= --batch_size=8 ``` @@ -81,14 +82,14 @@ CUDA_VISIBLE_DEVICES=0,1 python main.py --data= --batch_size=8 使用如下方式进行单卡训练: -```shell +```bash export CUDA_VISIBLE_DEVICES=0 python main.py --data= --batch_size=16 -d ``` 使用如下方式进行多卡训练: -```shell +```bash CUDA_VISIBLE_DEVICES=0,1 python main.py --data= --batch_size=8 -d ``` @@ -100,14 +101,14 @@ CUDA_VISIBLE_DEVICES=0,1 python main.py --data= --batch_size=8 1. 自动下载Paddle发布的[TSM-ResNet50](https://paddlemodels.bj.bcebos.com/hapi/tsm_resnet50.pdparams)权重评估 -``` -python main.py --data --eval_only +```bash +python main.py --data= --eval_only ``` 2. 加载checkpoint进行精度评估 -``` -python main.py --data --eval_only --weights=tsm_checkpoint/final +```bash +python main.py --data= --eval_only --weights=tsm_checkpoint/final ``` #### 评估精度 @@ -116,7 +117,7 @@ python main.py --data --eval_only --weights=tsm_checkpoint/fina |Top-1|Top-5| |:-:|:-:| -|76.5%|98.0%| +|76%|98%| ## 参考论文 diff --git a/tsm/main.py b/tsm/main.py index 2266bb46e83d1..b1d023f326a2e 100644 --- a/tsm/main.py +++ b/tsm/main.py @@ -92,7 +92,7 @@ def main(): if FLAGS.eval_only: if FLAGS.weights is not None: - model.load(FLAGS.weights) + model.load(FLAGS.weights, reset_optimizer=True) model.evaluate( val_dataset, diff --git a/tsm/modeling.py b/tsm/modeling.py index bac51016851ed..b2002f2ccc258 100644 --- a/tsm/modeling.py +++ b/tsm/modeling.py @@ -191,8 +191,8 @@ def _tsm_resnet(num_layers, seg_num=8, num_classes=400, pretrained=True): model = TSM_ResNet(num_layers, seg_num, num_classes) if pretrained: assert num_layers in pretrain_infos.keys(), \ - "TSM_ResNet{} do not have pretrained weights now, " \ - "pretrained should be set as False" + "TSM-ResNet{} do not have pretrained weights now, " \ + "pretrained should be set as False".format(num_layers) weight_path = get_weights_path(*(pretrain_infos[num_layers])) assert weight_path.endswith('.pdparams'), \ "suffix of weight must be .pdparams" From d9f6c64f428029de5380143182f25a183045ee38 Mon Sep 17 00:00:00 2001 From: dengkaipeng Date: Thu, 2 Apr 2020 15:35:19 +0000 Subject: [PATCH 10/13] fix step_per_epoch --- tsm/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsm/main.py b/tsm/main.py index b1d023f326a2e..cdec30b593694 100644 --- a/tsm/main.py +++ b/tsm/main.py @@ -77,7 +77,7 @@ def main(): step_per_epoch = int(len(train_dataset) / FLAGS.batch_size \ / ParallelEnv().nranks) - optim = make_optimizer(len(train_dataset), model.parameters()) + optim = make_optimizer(step_per_epoch, model.parameters()) inputs = [Input([None, 8, 3, 224, 224], 'float32', name='image')] labels = [Input([None, 1], 'int64', name='label')] From 41adc109fe05e8458d835e8f768edac589fcbf2c Mon Sep 17 00:00:00 2001 From: dengkaipeng Date: Fri, 3 Apr 2020 07:41:18 +0000 Subject: [PATCH 11/13] add infer.py --- tsm/README.md | 22 ++++++++++ tsm/infer.py | 91 +++++++++++++++++++++++++++++++++++++++++ tsm/kinetics_dataset.py | 35 +++++++++------- 3 files changed, 134 insertions(+), 14 deletions(-) create mode 100644 tsm/infer.py diff --git a/tsm/README.md b/tsm/README.md index 5d4e2b708743f..8e6138942c76e 100644 --- a/tsm/README.md +++ b/tsm/README.md @@ -119,6 +119,28 @@ python main.py --data= --eval_only --weights=tsm_checkpoint/fin |:-:|:-:| |76%|98%| +### 模型推断 + +可通过如下两种方式进行模型推断。 + +1. 自动下载Paddle发布的[TSM-ResNet50](https://paddlemodels.bj.bcebos.com/hapi/tsm_resnet50.pdparams)权重推断 + +```bash +python infer.py --data= --label_list= --infer_file= +``` + +2. 加载checkpoint进行精度推断 + +```bash +python infer.py --data= --label_list= --infer_file= --weights=tsm_checkpoint/final +``` + +模型推断结果会以如下日志形式输出 + +```text +2020-04-03 07:37:16,321-INFO: Sample ./kineteics/val_10/data_batch_10-042_6 predict label: 6, ground truth label: 6 +``` + ## 参考论文 - [Temporal Shift Module for Efficient Video Understanding](https://arxiv.org/abs/1811.08383v1), Ji Lin, Chuang Gan, Song Han diff --git a/tsm/infer.py b/tsm/infer.py new file mode 100644 index 0000000000000..c7995dfa62b2e --- /dev/null +++ b/tsm/infer.py @@ -0,0 +1,91 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import division +from __future__ import print_function + +import os +import argparse +import numpy as np + +from model import Input, set_device + +from check import check_gpu, check_version +from modeling import tsm_resnet50 +from kinetics_dataset import KineticsDataset +from transforms import * + +import logging +logger = logging.getLogger(__name__) + + +def main(): + device = set_device(FLAGS.device) + fluid.enable_dygraph(device) if FLAGS.dynamic else None + + transform = Compose([GroupScale(), + GroupCenterCrop(), + NormalizeImage()]) + dataset = KineticsDataset( + pickle_file=FLAGS.infer_file, + label_list=FLAGS.label_list, + mode='test', + transform=transform) + labels = dataset.label_list + + model = tsm_resnet50(num_classes=len(labels), + pretrained=FLAGS.weights is None) + + inputs = [Input([None, 8, 3, 224, 224], 'float32', name='image')] + + model.prepare(inputs=inputs, device=FLAGS.device) + + if FLAGS.weights is not None: + model.load(FLAGS.weights, reset_optimizer=True) + + imgs, label = dataset[0] + pred = model.test([imgs[np.newaxis, :]]) + pred = labels[np.argmax(pred)] + logger.info("Sample {} predict label: {}, ground truth label: {}" \ + .format(FLAGS.infer_file, pred, labels[int(label)])) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser("CNN training on TSM") + parser.add_argument( + "--data", type=str, default='dataset/kinetics', + help="path to dataset root directory") + parser.add_argument( + "--device", type=str, default='gpu', + help="device to use, gpu or cpu") + parser.add_argument( + "-d", "--dynamic", action='store_true', + help="enable dygraph mode") + parser.add_argument( + "--label_list", type=str, default=None, + help="path to category index label list file") + parser.add_argument( + "--infer_file", type=str, default=None, + help="path to pickle file for inference") + parser.add_argument( + "-w", + "--weights", + default=None, + type=str, + help="weights path for evaluation") + FLAGS = parser.parse_args() + + check_gpu(str.lower(FLAGS.device) == 'gpu') + check_version() + main() diff --git a/tsm/kinetics_dataset.py b/tsm/kinetics_dataset.py index 7d0e8fe11db6b..7e07543f37392 100644 --- a/tsm/kinetics_dataset.py +++ b/tsm/kinetics_dataset.py @@ -56,21 +56,32 @@ class KineticsDataset(Dataset): """ def __init__(self, - file_list, - pickle_dir, + file_list=None, + pickle_dir=None, + pickle_file=None, label_list=None, mode='train', seg_num=8, seg_len=1, transform=None): - assert os.path.isfile(file_list), \ - "file_list {} not a file".format(file_list) - with open(file_list) as f: - self.pickle_paths = [l.strip() for l in f] - - assert os.path.isdir(pickle_dir), \ - "pickle_dir {} not a directory".format(pickle_dir) - self.pickle_dir = pickle_dir + assert str.lower(mode) in ['train', 'val', 'test'], \ + "mode can only be 'train' 'val' or 'test'" + self.mode = str.lower(mode) + + if self.mode in ['train', 'val']: + assert os.path.isfile(file_list), \ + "file_list {} not a file".format(file_list) + with open(file_list) as f: + self.pickle_paths = [l.strip() for l in f] + + assert os.path.isdir(pickle_dir), \ + "pickle_dir {} not a directory".format(pickle_dir) + self.pickle_dir = pickle_dir + else: + assert os.path.isfile(pickle_file), \ + "pickle_file {} not a file".format(pickle_file) + self.pickle_dir = '' + self.pickle_paths = [pickle_file] self.label_list = label_list if self.label_list is not None: @@ -79,10 +90,6 @@ def __init__(self, with open(self.label_list) as f: self.label_list = [int(l.strip()) for l in f] - assert mode in ['train', 'val'], \ - "mode can only be 'train' or 'val'" - self.mode = mode - self.seg_num = seg_num self.seg_len = seg_len self.transform = transform From 2065e6c5ca62eb85aa52073aa9ee0693bac83800 Mon Sep 17 00:00:00 2001 From: dengkaipeng Date: Fri, 3 Apr 2020 15:12:38 +0000 Subject: [PATCH 12/13] mv modeling.py to models/tsm.py --- models/__init__.py | 21 +++++ tsm/infer.py | 2 +- tsm/main.py | 2 +- tsm/modeling.py | 204 --------------------------------------------- 4 files changed, 23 insertions(+), 206 deletions(-) delete mode 100644 tsm/modeling.py diff --git a/models/__init__.py b/models/__init__.py index 7928da7df031c..b169c8c931b58 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1 +1,22 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +#Licensed under the Apache License, Version 2.0 (the "License"); +#you may not use this file except in compliance with the License. +#You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +#Unless required by applicable law or agreed to in writing, software +#distributed under the License is distributed on an "AS IS" BASIS, +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#See the License for the specific language governing permissions and +#limitations under the License. + +from . import resnet from .resnet import * + +from . import tsm +from .tsm import * + +__all__ = resnet.__all__ \ + + tsm.__all__ diff --git a/tsm/infer.py b/tsm/infer.py index c7995dfa62b2e..78dbe2cc6ab92 100644 --- a/tsm/infer.py +++ b/tsm/infer.py @@ -20,9 +20,9 @@ import numpy as np from model import Input, set_device +from models import tsm_resnet50 from check import check_gpu, check_version -from modeling import tsm_resnet50 from kinetics_dataset import KineticsDataset from transforms import * diff --git a/tsm/main.py b/tsm/main.py index cdec30b593694..07868dbdc4356 100644 --- a/tsm/main.py +++ b/tsm/main.py @@ -24,9 +24,9 @@ from model import Model, CrossEntropy, Input, set_device from metrics import Accuracy +from models import tsm_resnet50 from check import check_gpu, check_version -from modeling import tsm_resnet50 from kinetics_dataset import KineticsDataset from transforms import * diff --git a/tsm/modeling.py b/tsm/modeling.py deleted file mode 100644 index b2002f2ccc258..0000000000000 --- a/tsm/modeling.py +++ /dev/null @@ -1,204 +0,0 @@ -# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -import math -import paddle.fluid as fluid -from paddle.fluid.layer_helper import LayerHelper -from paddle.fluid.dygraph.nn import Conv2D, Pool2D, BatchNorm, Linear - -from model import Model -from download import get_weights_path - -__all__ = ["TSM_ResNet", "tsm_resnet50"] - -# {num_layers: (url, md5)} -pretrain_infos = { - 50: ('https://paddlemodels.bj.bcebos.com/hapi/tsm_resnet50.pdparams', - '5755dc538e422589f417f7b38d7cc3c7') -} - - -class ConvBNLayer(fluid.dygraph.Layer): - def __init__(self, - num_channels, - num_filters, - filter_size, - stride=1, - groups=1, - act=None): - super(ConvBNLayer, self).__init__() - - self._conv = Conv2D( - num_channels=num_channels, - num_filters=num_filters, - filter_size=filter_size, - stride=stride, - padding=(filter_size - 1) // 2, - groups=None, - act=None, - param_attr=fluid.param_attr.ParamAttr(), - bias_attr=False) - - self._batch_norm = BatchNorm( - num_filters, - act=act, - param_attr=fluid.param_attr.ParamAttr(), - bias_attr=fluid.param_attr.ParamAttr()) - - def forward(self, inputs): - y = self._conv(inputs) - y = self._batch_norm(y) - - return y - - -class BottleneckBlock(fluid.dygraph.Layer): - def __init__(self, - num_channels, - num_filters, - stride, - shortcut=True, - seg_num=8): - super(BottleneckBlock, self).__init__() - - self.conv0 = ConvBNLayer( - num_channels=num_channels, - num_filters=num_filters, - filter_size=1, - act='relu') - self.conv1 = ConvBNLayer( - num_channels=num_filters, - num_filters=num_filters, - filter_size=3, - stride=stride, - act='relu') - self.conv2 = ConvBNLayer( - num_channels=num_filters, - num_filters=num_filters * 4, - filter_size=1, - act=None) - - if not shortcut: - self.short = ConvBNLayer( - num_channels=num_channels, - num_filters=num_filters * 4, - filter_size=1, - stride=stride) - self.shortcut = shortcut - self.seg_num = seg_num - self._num_channels_out = int(num_filters * 4) - - def forward(self, inputs): - shifts = fluid.layers.temporal_shift(inputs, self.seg_num, 1.0 / 8) - y = self.conv0(shifts) - conv1 = self.conv1(y) - conv2 = self.conv2(conv1) - if self.shortcut: - short = inputs - else: - short = self.short(inputs) - y = fluid.layers.elementwise_add(x=short, y=conv2, act="relu") - return y - - -class TSM_ResNet(Model): - """ - TSM network with ResNet as backbone - - Args: - num_layers (int): ResNet layer number, only support 50 currently. - Default 50. - seg_num (int): segment number of each video sample. Default 8. - num_classes (int): video class number. Default 400. - """ - def __init__(self, num_layers=50, seg_num=8, num_classes=400): - super(TSM_ResNet, self).__init__() - - self.layers = num_layers - self.seg_num = seg_num - self.class_dim = num_classes - - if self.layers == 50: - depth = [3, 4, 6, 3] - else: - raise NotImplementedError - num_filters = [64, 128, 256, 512] - - self.conv = ConvBNLayer( - num_channels=3, num_filters=64, filter_size=7, stride=2, act='relu') - self.pool2d_max = Pool2D( - pool_size=3, pool_stride=2, pool_padding=1, pool_type='max') - - self.bottleneck_block_list = [] - num_channels = 64 - - for block in range(len(depth)): - shortcut = False - for i in range(depth[block]): - bottleneck_block = self.add_sublayer( - 'bb_%d_%d' % (block, i), - BottleneckBlock( - num_channels=num_channels, - num_filters=num_filters[block], - stride=2 if i == 0 and block != 0 else 1, - shortcut=shortcut, - seg_num=self.seg_num)) - num_channels = int(bottleneck_block._num_channels_out) - self.bottleneck_block_list.append(bottleneck_block) - shortcut = True - self.pool2d_avg = Pool2D( - pool_size=7, pool_type='avg', global_pooling=True) - - stdv = 1.0 / math.sqrt(2048 * 1.0) - - self.out = Linear( - 2048, - self.class_dim, - act="softmax", - param_attr=fluid.param_attr.ParamAttr( - initializer=fluid.initializer.Uniform(-stdv, stdv)), - bias_attr=fluid.param_attr.ParamAttr( - learning_rate=1.0, regularizer=fluid.regularizer.L2Decay(0.))) - - def forward(self, inputs): - y = fluid.layers.reshape( - inputs, [-1, inputs.shape[2], inputs.shape[3], inputs.shape[4]]) - y = self.conv(y) - y = self.pool2d_max(y) - for bottleneck_block in self.bottleneck_block_list: - y = bottleneck_block(y) - y = self.pool2d_avg(y) - y = fluid.layers.dropout(y, dropout_prob=0.5) - y = fluid.layers.reshape(y, [-1, self.seg_num, y.shape[1]]) - y = fluid.layers.reduce_mean(y, dim=1) - y = fluid.layers.reshape(y, shape=[-1, 2048]) - y = self.out(y) - return y - - -def _tsm_resnet(num_layers, seg_num=8, num_classes=400, pretrained=True): - model = TSM_ResNet(num_layers, seg_num, num_classes) - if pretrained: - assert num_layers in pretrain_infos.keys(), \ - "TSM-ResNet{} do not have pretrained weights now, " \ - "pretrained should be set as False".format(num_layers) - weight_path = get_weights_path(*(pretrain_infos[num_layers])) - assert weight_path.endswith('.pdparams'), \ - "suffix of weight must be .pdparams" - model.load(weight_path[:-9]) - return model - - -def tsm_resnet50(seg_num=8, num_classes=400, pretrained=True): - return _tsm_resnet(50, seg_num, num_classes, pretrained) From 3080634e50d2bf697fff15771df2397a2477dba0 Mon Sep 17 00:00:00 2001 From: dengkaipeng Date: Wed, 8 Apr 2020 07:10:47 +0000 Subject: [PATCH 13/13] fix README --- tsm/README.md | 4 ++-- yolov3/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tsm/README.md b/tsm/README.md index 8e6138942c76e..6bc5c794432cb 100644 --- a/tsm/README.md +++ b/tsm/README.md @@ -73,7 +73,7 @@ python main.py --data= --batch_size=16 使用如下方式进行多卡训练: ```bash -CUDA_VISIBLE_DEVICES=0,1 python main.py --data= --batch_size=8 +CUDA_VISIBLE_DEVICES=0,1 python -m paddle.distributed.launch main.py --data= --batch_size=8 ``` #### 动态图训练 @@ -90,7 +90,7 @@ python main.py --data= --batch_size=16 -d 使用如下方式进行多卡训练: ```bash -CUDA_VISIBLE_DEVICES=0,1 python main.py --data= --batch_size=8 -d +CUDA_VISIBLE_DEVICES=0,1 python -m paddle.distributed.launch main.py --data= --batch_size=8 -d ``` **注意:** 对于静态图和动态图,多卡训练中`--batch_size`为每卡上的batch_size,即总batch_size为`--batch_size`乘以卡数 diff --git a/yolov3/README.md b/yolov3/README.md index 88e153509c4e1..cc6d302a544f9 100644 --- a/yolov3/README.md +++ b/yolov3/README.md @@ -117,7 +117,7 @@ python main.py --help 使用如下方式进行多卡训练: ```bash -CUDA_VISIBLE_DEVICES=0,1,2,3 python main.py --data= --batch_size=16 +CUDA_VISIBLE_DEVICES=0,1,2,3 python -m paddle.distributed.launch main.py --data= --batch_size=16 ``` #### 动态图训练 @@ -127,7 +127,7 @@ CUDA_VISIBLE_DEVICES=0,1,2,3 python main.py --data= --batch_siz 使用如下方式进行多卡训练: ```bash -CUDA_VISIBLE_DEVICES=0,1,2,3 python main.py --data= --batch_size=16 -d +CUDA_VISIBLE_DEVICES=0,1,2,3 python main.py -m paddle.distributed.launch --data= --batch_size=16 -d ```