Compare commits

...

7 Commits

Author SHA1 Message Date
  KubeEdge Bot e12ccac853
Merge pull request #361 from JimmyYang20/lifelong-ci 3 years ago
  JimmyYang20 572f4e302b Add pylint in ci 3 years ago
  KubeEdge Bot 3ed85a0c4c
Merge pull request #350 from luosiqi/dev-lifelong-n 3 years ago
  luosiqi 88827ef951 Code check and base model improvement 3 years ago
  KubeEdge Bot 53e5ae4436
Merge pull request #349 from luosiqi/dev-lifelong-n 3 years ago
  luosiqi e5d2291ad5 Sedna integrates unstructured data in lib 3 years ago
  luosiqi 98a4047cf9 Add lifelong learning example: semantic segmentation 3 years ago
78 changed files with 7945 additions and 198 deletions
Split View
  1. +14
    -0
      .github/workflows/main.yaml
  2. +38
    -0
      examples/lifelong_learning/RFNet/accuracy.py
  3. +390
    -0
      examples/lifelong_learning/RFNet/basemodel.py
  4. +119
    -0
      examples/lifelong_learning/RFNet/dataloaders/__init__.py
  5. +240
    -0
      examples/lifelong_learning/RFNet/dataloaders/custom_transforms.py
  6. +230
    -0
      examples/lifelong_learning/RFNet/dataloaders/custom_transforms_rgb.py
  7. +0
    -0
      examples/lifelong_learning/RFNet/dataloaders/datasets/__init__.py
  8. +276
    -0
      examples/lifelong_learning/RFNet/dataloaders/datasets/citylostfound.py
  9. +151
    -0
      examples/lifelong_learning/RFNet/dataloaders/datasets/cityrand.py
  10. +151
    -0
      examples/lifelong_learning/RFNet/dataloaders/datasets/cityscapes.py
  11. +151
    -0
      examples/lifelong_learning/RFNet/dataloaders/datasets/e1.py
  12. +152
    -0
      examples/lifelong_learning/RFNet/dataloaders/datasets/mapillary.py
  13. +152
    -0
      examples/lifelong_learning/RFNet/dataloaders/datasets/target.py
  14. +38
    -0
      examples/lifelong_learning/RFNet/dataloaders/datasets/temp.txt
  15. +152
    -0
      examples/lifelong_learning/RFNet/dataloaders/datasets/xrlab.py
  16. +244
    -0
      examples/lifelong_learning/RFNet/dataloaders/utils.py
  17. +246
    -0
      examples/lifelong_learning/RFNet/eval.py
  18. +88
    -0
      examples/lifelong_learning/RFNet/models/replicate.py
  19. +0
    -0
      examples/lifelong_learning/RFNet/models/resnet/__init__.py
  20. +391
    -0
      examples/lifelong_learning/RFNet/models/resnet/resnet_single_scale_single_attention.py
  21. +396
    -0
      examples/lifelong_learning/RFNet/models/resnet/resnet_single_scale_single_attention_unseen.py
  22. +27
    -0
      examples/lifelong_learning/RFNet/models/rfnet.py
  23. +33
    -0
      examples/lifelong_learning/RFNet/models/rfnet_for_unseen.py
  24. +99
    -0
      examples/lifelong_learning/RFNet/models/util.py
  25. +20
    -0
      examples/lifelong_learning/RFNet/mypath.py
  26. +82
    -0
      examples/lifelong_learning/RFNet/predict.py
  27. +252
    -0
      examples/lifelong_learning/RFNet/run_server.py
  28. +41
    -0
      examples/lifelong_learning/RFNet/sedna_evaluate.py
  29. +123
    -0
      examples/lifelong_learning/RFNet/sedna_predict.py
  30. +65
    -0
      examples/lifelong_learning/RFNet/sedna_train.py
  31. +52
    -0
      examples/lifelong_learning/RFNet/test.py
  32. +342
    -0
      examples/lifelong_learning/RFNet/train.py
  33. +0
    -0
      examples/lifelong_learning/RFNet/utils/__init__.py
  34. +29
    -0
      examples/lifelong_learning/RFNet/utils/calculate_weights.py
  35. +142
    -0
      examples/lifelong_learning/RFNet/utils/iouEval.py
  36. +64
    -0
      examples/lifelong_learning/RFNet/utils/loss.py
  37. +70
    -0
      examples/lifelong_learning/RFNet/utils/lr_scheduler.py
  38. +145
    -0
      examples/lifelong_learning/RFNet/utils/metrics.py
  39. +68
    -0
      examples/lifelong_learning/RFNet/utils/saver.py
  40. +39
    -0
      examples/lifelong_learning/RFNet/utils/summaries.py
  41. +4
    -0
      lib/sedna/algorithms/__init__.py
  42. +18
    -0
      lib/sedna/algorithms/knowledge_management/__init__.py
  43. +141
    -0
      lib/sedna/algorithms/knowledge_management/cloud_knowledge_management.py
  44. +193
    -0
      lib/sedna/algorithms/knowledge_management/edge_knowledge_management.py
  45. +15
    -0
      lib/sedna/algorithms/knowledge_management/task_evaluation/__init__.py
  46. +80
    -0
      lib/sedna/algorithms/knowledge_management/task_evaluation/task_evaluation.py
  47. +2
    -0
      lib/sedna/algorithms/knowledge_management/task_update_decision/__init__.py
  48. +110
    -0
      lib/sedna/algorithms/knowledge_management/task_update_decision/task_update_decision.py
  49. +26
    -0
      lib/sedna/algorithms/seen_task_learning/__init__.py
  50. +45
    -0
      lib/sedna/algorithms/seen_task_learning/artifact.py
  51. +3
    -0
      lib/sedna/algorithms/seen_task_learning/inference_integrate/__init__.py
  52. +57
    -0
      lib/sedna/algorithms/seen_task_learning/inference_integrate/inference_integrate.py
  53. +580
    -0
      lib/sedna/algorithms/seen_task_learning/seen_task_learning.py
  54. +4
    -0
      lib/sedna/algorithms/seen_task_learning/task_allocation/__init__.py
  55. +116
    -0
      lib/sedna/algorithms/seen_task_learning/task_allocation/task_allocation.py
  56. +63
    -0
      lib/sedna/algorithms/seen_task_learning/task_allocation/task_allocation_by_origin.py
  57. +3
    -0
      lib/sedna/algorithms/seen_task_learning/task_definition/__init__.py
  58. +213
    -0
      lib/sedna/algorithms/seen_task_learning/task_definition/task_definition.py
  59. +80
    -0
      lib/sedna/algorithms/seen_task_learning/task_definition/task_definition_by_origin.py
  60. +3
    -0
      lib/sedna/algorithms/seen_task_learning/task_relation_discover/__init__.py
  61. +52
    -0
      lib/sedna/algorithms/seen_task_learning/task_relation_discover/task_relation_discover.py
  62. +2
    -0
      lib/sedna/algorithms/seen_task_learning/task_remodeling/__init__.py
  63. +78
    -0
      lib/sedna/algorithms/seen_task_learning/task_remodeling/task_remodeling.py
  64. +16
    -0
      lib/sedna/algorithms/unseen_task_detection/__init__.py
  65. +15
    -0
      lib/sedna/algorithms/unseen_task_detection/unseen_sample_re_recognition/__init__.py
  66. +43
    -0
      lib/sedna/algorithms/unseen_task_detection/unseen_sample_re_recognition/unseen_sample_re_recognition.py
  67. +15
    -0
      lib/sedna/algorithms/unseen_task_detection/unseen_sample_recognition/__init__.py
  68. +95
    -0
      lib/sedna/algorithms/unseen_task_detection/unseen_sample_recognition/unseen_sample_recognition.py
  69. +2
    -0
      lib/sedna/algorithms/unseen_task_processing/__init__.py
  70. +1
    -0
      lib/sedna/algorithms/unseen_task_processing/unseen_task_allocation/__init__.py
  71. +46
    -0
      lib/sedna/algorithms/unseen_task_processing/unseen_task_allocation/unseen_task_allocation.py
  72. +157
    -0
      lib/sedna/algorithms/unseen_task_processing/unseen_task_processing.py
  73. +9
    -0
      lib/sedna/backend/base.py
  74. +5
    -0
      lib/sedna/common/class_factory.py
  75. +5
    -0
      lib/sedna/common/constant.py
  76. +280
    -191
      lib/sedna/core/lifelong_learning/lifelong_learning.py
  77. +38
    -1
      lib/sedna/datasources/__init__.py
  78. +18
    -6
      lib/sedna/service/server/knowledgeBase/server.py

+ 14
- 0
.github/workflows/main.yaml View File

@@ -11,6 +11,9 @@ jobs:
verify-and-lint:
runs-on: ubuntu-latest
name: Verify codegen/vendor/licenses, do lint
strategy:
matrix:
python-version: [ "3.6", "3.7", "3.8", "3.9" ]
env:
GOPATH: ${{ github.workspace }}

@@ -50,6 +53,17 @@ jobs:
run: pycodestyle lib
working-directory: ${{ env.CODE_DIR }}

- name: Set python version
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}

- name: Run python lint test
run: |
python -m pip install pylint
pylint lib
working-directory: ${{ env.CODE_DIR }}

build:
runs-on: ubuntu-latest
name: build gm and lc


+ 38
- 0
examples/lifelong_learning/RFNet/accuracy.py View File

@@ -0,0 +1,38 @@
from basemodel import val_args
from utils.metrics import Evaluator
from tqdm import tqdm
from dataloaders import make_data_loader
from sedna.common.class_factory import ClassType, ClassFactory

__all__ = ('accuracy')

@ClassFactory.register(ClassType.GENERAL)
def accuracy(y_true, y_pred, **kwargs):
args = val_args()
_, _, test_loader, num_class = make_data_loader(args, test_data=y_true)
evaluator = Evaluator(num_class)

tbar = tqdm(test_loader, desc='\r')
for i, (sample, img_path) in enumerate(tbar):
if args.depth:
image, depth, target = sample['image'], sample['depth'], sample['label']
else:
image, target = sample['image'], sample['label']
if args.cuda:
image, target = image.cuda(), target.cuda()
if args.depth:
depth = depth.cuda()

target[target > evaluator.num_class-1] = 255
target = target.cpu().numpy()
# Add batch sample into evaluator
evaluator.add_batch(target, y_pred[i])

# Test during the training
# Acc = evaluator.Pixel_Accuracy()
CPA = evaluator.Pixel_Accuracy_Class()
mIoU = evaluator.Mean_Intersection_over_Union()
FWIoU = evaluator.Frequency_Weighted_Intersection_over_Union()

print("CPA:{}, mIoU:{}, fwIoU: {}".format(CPA, mIoU, FWIoU))
return CPA

+ 390
- 0
examples/lifelong_learning/RFNet/basemodel.py View File

@@ -0,0 +1,390 @@
import os
import numpy as np
import torch
from PIL import Image
import argparse
from tqdm import tqdm

from torchvision import transforms
from torch.utils.data import DataLoader
from sedna.common.config import Context
from sedna.common.file_ops import FileOps
from sedna.common.log import LOGGER

from utils.metrics import Evaluator
from train import Trainer
from eval import Validator
from eval import load_my_state_dict
from dataloaders import make_data_loader
from dataloaders import custom_transforms as tr

def preprocess(image_urls):
transformed_images = []
for paths in image_urls:
if len(paths) == 2:
img_path, depth_path = paths
_img = Image.open(img_path).convert('RGB')
_depth = Image.open(depth_path)
else:
img_path = paths[0]
_img = Image.open(img_path).convert('RGB')
_depth = _img

sample = {'image': _img, 'depth': _depth, 'label': _img}
composed_transforms = transforms.Compose([
# tr.CropBlackArea(),
# tr.FixedResize(size=self.args.crop_size),
tr.Normalize(
mean=(
0.485, 0.456, 0.406), std=(
0.229, 0.224, 0.225)),
tr.ToTensor()])

transformed_images.append((composed_transforms(sample), img_path))

return transformed_images

class Model:
def __init__(self, **kwargs):
self.val_args = val_args()
self.train_args = train_args()

self.train_args.lr = kwargs.get("learning_rate", 1e-4)
self.train_args.epochs = kwargs.get("epochs", 2)
self.train_args.eval_interval = kwargs.get("eval_interval", 2)
self.train_args.no_val = kwargs.get("no_val", True)
# self.train_args.resume = Context.get_parameters("PRETRAINED_MODEL_URL", None)
self.trainer = None
self.train_model_url = None

label_save_dir = Context.get_parameters(
"INFERENCE_RESULT_DIR", "./inference_results")
self.val_args.color_label_save_path = os.path.join(
label_save_dir, "color")
self.val_args.merge_label_save_path = os.path.join(
label_save_dir, "merge")
self.val_args.label_save_path = os.path.join(label_save_dir, "label")
self.val_args.save_predicted_image = kwargs.get(
"save_predicted_image", "true").lower()
self.validator = Validator(self.val_args)

def train(self, train_data, valid_data=None, **kwargs):
self.trainer = Trainer(self.train_args, train_data=train_data)
print("Total epoches:", self.trainer.args.epochs)
for epoch in range(
self.trainer.args.start_epoch,
self.trainer.args.epochs):
if epoch == 0 and self.trainer.val_loader:
self.trainer.validation(epoch)
self.trainer.training(epoch)

if self.trainer.args.no_val and (
epoch %
self.trainer.args.eval_interval == (
self.trainer.args.eval_interval -
1) or epoch == self.trainer.args.epochs -
1):
# save checkpoint when it meets eval_interval or the training
# finished
is_best = False
self.train_model_url = self.trainer.saver.save_checkpoint({
'epoch': epoch + 1,
'state_dict': self.trainer.model.state_dict(),
'optimizer': self.trainer.optimizer.state_dict(),
'best_pred': self.trainer.best_pred,
}, is_best)

# if not self.trainer.args.no_val and \
# epoch % self.train_args.eval_interval == (self.train_args.eval_interval - 1) \
# and self.trainer.val_loader:
# self.trainer.validation(epoch)

self.trainer.writer.close()

return self.train_model_url

def predict(self, data, **kwargs):
if not isinstance(data[0][0], dict):
data = preprocess(data)

if isinstance(data, np.ndarray):
data = data.tolist()

self.validator.test_loader = DataLoader(
data,
batch_size=self.val_args.test_batch_size,
shuffle=False,
pin_memory=True)
return self.validator.validate()

def evaluate(self, data, **kwargs):
samples = preprocess(data.x)
predictions = self.predict(samples)
return accuracy(data.y, predictions)

def load(self, model_url, **kwargs):
if model_url:
self.validator.new_state_dict = torch.load(
model_url, map_location=torch.device("cpu"))
self.validator.model = load_my_state_dict(
self.validator.model, self.validator.new_state_dict['state_dict'])

self.train_args.resume = model_url
else:
raise Exception("model url does not exist.")

def save(self, model_path=None):
# TODO: save unstructured data model
if not model_path:
LOGGER.warning(f"Not specify model path.")
return self.train_model_url

return FileOps.upload(self.train_model_url, model_path)


def train_args():
parser = argparse.ArgumentParser(description="PyTorch RFNet Training")
parser.add_argument(
'--depth',
action="store_true",
default=False,
help='training with depth image or not (default: False)')
parser.add_argument(
'--dataset',
type=str,
default='cityscapes',
choices=[
'citylostfound',
'cityscapes',
'cityrand',
'target',
'xrlab',
'e1',
'mapillary'],
help='dataset name (default: cityscapes)')
parser.add_argument('--workers', type=int, default=4,
metavar='N', help='dataloader threads')
parser.add_argument('--base-size', type=int, default=1024,
help='base image size')
parser.add_argument('--crop-size', type=int, default=768,
help='crop image size')
parser.add_argument('--loss-type', type=str, default='ce',
choices=['ce', 'focal'],
help='loss func type (default: ce)')
# training hyper params
# parser.add_argument('--epochs', type=int, default=None, metavar='N',
# help='number of epochs to train (default: auto)')
parser.add_argument('--epochs', type=int, default=None, metavar='N',
help='number of epochs to train (default: auto)')
parser.add_argument('--start_epoch', type=int, default=0,
metavar='N', help='start epochs (default:0)')
parser.add_argument('--batch-size', type=int, default=None,
metavar='N', help='input batch size for \
training (default: auto)')
parser.add_argument('--val-batch-size', type=int, default=1,
metavar='N', help='input batch size for \
testing (default: auto)')
parser.add_argument('--test-batch-size', type=int, default=1,
metavar='N', help='input batch size for \
testing (default: auto)')
parser.add_argument(
'--use-balanced-weights',
action='store_true',
default=False,
help='whether to use balanced weights (default: True)')
parser.add_argument('--num-class', type=int, default=24,
help='number of training classes (default: 24')
# optimizer params
parser.add_argument('--lr', type=float, default=1e-4, metavar='LR',
help='learning rate (default: auto)')
parser.add_argument('--lr-scheduler', type=str, default='cos',
choices=['poly', 'step', 'cos', 'inv'],
help='lr scheduler mode: (default: cos)')
parser.add_argument('--momentum', type=float, default=0.9,
metavar='M', help='momentum (default: 0.9)')
parser.add_argument('--weight-decay', type=float, default=2.5e-5,
metavar='M', help='w-decay (default: 5e-4)')
# cuda, seed and logging
parser.add_argument(
'--no-cuda',
action='store_true',
default=False,
help='disables CUDA training')
parser.add_argument('--gpu-ids', type=str, default='0',
help='use which gpu to train, must be a \
comma-separated list of integers only (default=0)')
parser.add_argument('--seed', type=int, default=1, metavar='S',
help='random seed (default: 1)')
# checking point
parser.add_argument('--resume', type=str,
default=None,
help='put the path to resuming file if needed')
parser.add_argument('--checkname', type=str, default=None,
help='set the checkpoint name')
# finetuning pre-trained models
parser.add_argument('--ft', action='store_true', default=True,
help='finetuning on a different dataset')
# evaluation option
parser.add_argument('--eval-interval', type=int, default=1,
help='evaluation interval (default: 1)')
parser.add_argument('--no-val', action='store_true', default=False,
help='skip validation during training')

args = parser.parse_args()
args.cuda = not args.no_cuda and torch.cuda.is_available()
print(torch.cuda.is_available())
if args.cuda:
try:
args.gpu_ids = [int(s) for s in args.gpu_ids.split(',')]
except ValueError:
raise ValueError(
'Argument --gpu_ids must be a comma-separated list of integers only')

if args.epochs is None:
epoches = {
'cityscapes': 200,
'citylostfound': 200,
}
args.epochs = epoches[args.dataset.lower()]

if args.batch_size is None:
args.batch_size = 4 * len(args.gpu_ids)

if args.test_batch_size is None:
args.test_batch_size = args.batch_size

if args.lr is None:
lrs = {
'cityscapes': 0.0001,
'citylostfound': 0.0001,
'cityrand': 0.0001
}
args.lr = lrs[args.dataset.lower()] / \
(4 * len(args.gpu_ids)) * args.batch_size

if args.checkname is None:
args.checkname = 'RFNet'
print(args)
torch.manual_seed(args.seed)

return args


def val_args():
parser = argparse.ArgumentParser(description="PyTorch RFNet validation")
parser.add_argument(
'--dataset',
type=str,
default='cityscapes',
choices=[
'citylostfound',
'cityscapes',
'xrlab',
'mapillary'],
help='dataset name (default: cityscapes)')
parser.add_argument('--workers', type=int, default=4,
metavar='N', help='dataloader threads')
parser.add_argument('--base-size', type=int, default=1024,
help='base image size')
parser.add_argument('--crop-size', type=int, default=768,
help='crop image size')
parser.add_argument('--batch-size', type=int, default=6,
help='batch size for training')
parser.add_argument('--val-batch-size', type=int, default=1,
metavar='N', help='input batch size for \
validating (default: auto)')
parser.add_argument('--test-batch-size', type=int, default=1,
metavar='N', help='input batch size for \
testing (default: auto)')
parser.add_argument('--num-class', type=int, default=24,
help='number of training classes (default: 24)')
parser.add_argument(
'--no-cuda',
action='store_true',
default=False,
help='disables CUDA training')
parser.add_argument('--gpu-ids', type=str, default='0',
help='use which gpu to train, must be a \
comma-separated list of integers only (default=0)')
parser.add_argument('--checkname', type=str, default=None,
help='set the checkpoint name')
parser.add_argument(
'--weight-path',
type=str,
default="./models/530_exp3_2.pth",
help='enter your path of the weight')
parser.add_argument(
'--save-predicted-image',
action='store_true',
default=False,
help='save predicted images')
parser.add_argument('--color-label-save-path', type=str,
default='./test/color/',
help='path to save label')
parser.add_argument('--merge-label-save-path', type=str,
default='./test/merge/',
help='path to save merged label')
parser.add_argument('--label-save-path', type=str, default='./test/label/',
help='path to save merged label')
parser.add_argument(
'--merge',
action='store_true',
default=False,
help='merge image and label')
parser.add_argument(
'--depth',
action='store_true',
default=False,
help='add depth image or not')

args = parser.parse_args()
args.cuda = not args.no_cuda and torch.cuda.is_available()
if args.cuda:
try:
args.gpu_ids = [int(s) for s in args.gpu_ids.split(',')]
except ValueError:
raise ValueError(
'Argument --gpu_ids must be a comma-separated list of integers only')

return args


def accuracy(y_true, y_pred, **kwargs):
args = val_args()
_, _, test_loader, num_class = make_data_loader(args, test_data=y_true)
evaluator = Evaluator(num_class)

tbar = tqdm(test_loader, desc='\r')
for i, (sample, img_path) in enumerate(tbar):
if args.depth:
image, depth, target = sample['image'], sample['depth'], sample['label']
else:
image, target = sample['image'], sample['label']
if args.cuda:
image, target = image.cuda(), target.cuda()
if args.depth:
depth = depth.cuda()

target[target > evaluator.num_class - 1] = 255
target = target.cpu().numpy()
# Add batch sample into evaluator
evaluator.add_batch(target, y_pred[i])

# Test during the training
# Acc = evaluator.Pixel_Accuracy()
CPA = evaluator.Pixel_Accuracy_Class()
mIoU = evaluator.Mean_Intersection_over_Union()
FWIoU = evaluator.Frequency_Weighted_Intersection_over_Union()

print("CPA:{}, mIoU:{}, fwIoU: {}".format(CPA, mIoU, FWIoU))
return CPA


if __name__ == '__main__':
model_path = "/tmp/RFNet/"
if not os.path.exists(model_path):
os.makedirs(model_path)

p1 = Process(target=exp_train, args=(10,))
p1.start()
p1.join()

+ 119
- 0
examples/lifelong_learning/RFNet/dataloaders/__init__.py View File

@@ -0,0 +1,119 @@
from dataloaders.datasets import cityscapes, citylostfound, cityrand, target, xrlab, e1, mapillary
from torch.utils.data import DataLoader

def make_data_loader(args, train_data=None, valid_data=None, test_data=None, **kwargs):

if args.dataset == 'cityscapes':
if train_data is not None:
train_set = cityscapes.CityscapesSegmentation(args, data=train_data, split='train')
num_class = train_set.NUM_CLASSES
train_loader = DataLoader(train_set, batch_size=args.batch_size, shuffle=True, **kwargs)
else:
train_loader, num_class = None, cityscapes.CityscapesSegmentation.NUM_CLASSES

if valid_data is not None:
val_set = cityscapes.CityscapesSegmentation(args, data=valid_data, split='val')
num_class = val_set.NUM_CLASSES
val_loader = DataLoader(val_set, batch_size=args.val_batch_size, shuffle=False, **kwargs)
else:
val_loader, num_class = None, cityscapes.CityscapesSegmentation.NUM_CLASSES

if test_data is not None:
test_set = cityscapes.CityscapesSegmentation(args, data=test_data, split='test')
num_class = test_set.NUM_CLASSES
test_loader = DataLoader(test_set, batch_size=args.test_batch_size, shuffle=False, **kwargs)
else:
test_loader, num_class = None, cityscapes.CityscapesSegmentation.NUM_CLASSES

# custom_set = cityscapes.CityscapesSegmentation(args, split='custom_resize')
# custom_loader = DataLoader(custom_set, batch_size=args.test_batch_size, shuffle=False, **kwargs)
# return train_loader, val_loader, test_loader, custom_loader, num_class
return train_loader, val_loader, test_loader, num_class

if args.dataset == 'citylostfound':
if args.depth:
train_set = citylostfound.CitylostfoundSegmentation(args, split='train')
val_set = citylostfound.CitylostfoundSegmentation(args, split='val')
test_set = citylostfound.CitylostfoundSegmentation(args, split='test')
num_class = train_set.NUM_CLASSES
train_loader = DataLoader(train_set, batch_size=args.batch_size, shuffle=True, **kwargs)
val_loader = DataLoader(val_set, batch_size=args.val_batch_size, shuffle=False, **kwargs)
test_loader = DataLoader(test_set, batch_size=args.test_batch_size, shuffle=False, **kwargs)
else:
train_set = citylostfound.CitylostfoundSegmentation_rgb(args, split='train')
val_set = citylostfound.CitylostfoundSegmentation_rgb(args, split='val')
test_set = citylostfound.CitylostfoundSegmentation_rgb(args, split='test')
num_class = train_set.NUM_CLASSES
train_loader = DataLoader(train_set, batch_size=args.batch_size, shuffle=True, **kwargs)
val_loader = DataLoader(val_set, batch_size=args.val_batch_size, shuffle=False, **kwargs)
test_loader = DataLoader(test_set, batch_size=args.test_batch_size, shuffle=False, **kwargs)

return train_loader, val_loader, test_loader, num_class
if args.dataset == 'cityrand':
train_set = cityrand.CityscapesSegmentation(args, split='train')
val_set = cityrand.CityscapesSegmentation(args, split='val')
test_set = cityrand.CityscapesSegmentation(args, split='test')
custom_set = cityrand.CityscapesSegmentation(args, split='custom_resize')
num_class = train_set.NUM_CLASSES
train_loader = DataLoader(train_set, batch_size=args.batch_size, shuffle=True, **kwargs)
val_loader = DataLoader(val_set, batch_size=args.val_batch_size, shuffle=False, **kwargs)
test_loader = DataLoader(test_set, batch_size=args.test_batch_size, shuffle=False, **kwargs)
custom_loader = DataLoader(custom_set, batch_size=args.test_batch_size, shuffle=False, **kwargs)

return train_loader, val_loader, test_loader, custom_loader, num_class
if args.dataset == 'target':
train_set = target.CityscapesSegmentation(args, split='train')
val_set = target.CityscapesSegmentation(args, split='val')
test_set = target.CityscapesSegmentation(args, split='test')
custom_set = target.CityscapesSegmentation(args, split='test')
num_class = train_set.NUM_CLASSES
train_loader = DataLoader(train_set, batch_size=args.batch_size, shuffle=True, **kwargs)
val_loader = DataLoader(val_set, batch_size=args.val_batch_size, shuffle=False, **kwargs)
test_loader = DataLoader(test_set, batch_size=args.test_batch_size, shuffle=False, **kwargs)
custom_loader = DataLoader(custom_set, batch_size=args.test_batch_size, shuffle=False, **kwargs)

return train_loader, val_loader, test_loader, custom_loader, num_class
if args.dataset == 'xrlab':
train_set = xrlab.CityscapesSegmentation(args, split='train')
val_set = xrlab.CityscapesSegmentation(args, split='val')
test_set = xrlab.CityscapesSegmentation(args, split='test')
custom_set = xrlab.CityscapesSegmentation(args, split='test')
num_class = train_set.NUM_CLASSES
train_loader = DataLoader(train_set, batch_size=args.batch_size, shuffle=True, **kwargs)
val_loader = DataLoader(val_set, batch_size=args.val_batch_size, shuffle=False, **kwargs)
test_loader = DataLoader(test_set, batch_size=args.test_batch_size, shuffle=False, **kwargs)
custom_loader = DataLoader(custom_set, batch_size=args.test_batch_size, shuffle=False, **kwargs)

return train_loader, val_loader, test_loader, custom_loader, num_class
if args.dataset == 'e1':
train_set = e1.CityscapesSegmentation(args, split='train')
val_set = e1.CityscapesSegmentation(args, split='val')
test_set = e1.CityscapesSegmentation(args, split='test')
custom_set = e1.CityscapesSegmentation(args, split='test')
num_class = train_set.NUM_CLASSES
train_loader = DataLoader(train_set, batch_size=args.batch_size, shuffle=True, **kwargs)
val_loader = DataLoader(val_set, batch_size=args.val_batch_size, shuffle=False, **kwargs)
test_loader = DataLoader(test_set, batch_size=args.test_batch_size, shuffle=False, **kwargs)
custom_loader = DataLoader(custom_set, batch_size=args.test_batch_size, shuffle=False, **kwargs)

return train_loader, val_loader, test_loader, custom_loader, num_class
if args.dataset == 'mapillary':
train_set = mapillary.CityscapesSegmentation(args, split='train')
val_set = mapillary.CityscapesSegmentation(args, split='val')
test_set = mapillary.CityscapesSegmentation(args, split='test')
custom_set = mapillary.CityscapesSegmentation(args, split='test')
num_class = train_set.NUM_CLASSES
train_loader = DataLoader(train_set, batch_size=args.batch_size, shuffle=True, **kwargs)
val_loader = DataLoader(val_set, batch_size=args.val_batch_size, shuffle=False, **kwargs)
test_loader = DataLoader(test_set, batch_size=args.test_batch_size, shuffle=False, **kwargs)
custom_loader = DataLoader(custom_set, batch_size=args.test_batch_size, shuffle=False, **kwargs)

return train_loader, val_loader, test_loader, custom_loader, num_class

else:
raise NotImplementedError


+ 240
- 0
examples/lifelong_learning/RFNet/dataloaders/custom_transforms.py View File

@@ -0,0 +1,240 @@
import torch
import random
import numpy as np

from PIL import Image, ImageOps, ImageFilter

class Normalize(object):
"""Normalize a tensor image with mean and standard deviation.
Args:
mean (tuple): means for each channel.
std (tuple): standard deviations for each channel.
"""
def __init__(self, mean=(0., 0., 0.), std=(1., 1., 1.)):
self.mean = mean
self.std = std

def __call__(self, sample):
img = sample['image']
depth = sample['depth']
mask = sample['label']
img = np.array(img).astype(np.float32)
depth = np.array(depth).astype(np.float32)
mask = np.array(mask).astype(np.float32)
img /= 255.0
img -= self.mean
img /= self.std

# mean and std for original depth images
mean_depth = 0.12176
std_depth = 0.09752

depth /= 255.0
depth -= mean_depth
depth /= std_depth

return {'image': img,
'depth': depth,
'label': mask}


class ToTensor(object):
"""Convert Image object in sample to Tensors."""

def __call__(self, sample):
# swap color axis because
# numpy image: H x W x C
# torch image: C X H X W
img = sample['image']
depth = sample['depth']
mask = sample['label']
img = np.array(img).astype(np.float32).transpose((2, 0, 1))
depth = np.array(depth).astype(np.float32)
mask = np.array(mask).astype(np.float32)

img = torch.from_numpy(img).float()
depth = torch.from_numpy(depth).float()
mask = torch.from_numpy(mask).float()

return {'image': img,
'depth': depth,
'label': mask}

class CropBlackArea(object):
"""
crop black area for depth image
"""
def __call__(self, sample):
img = sample['image']
depth = sample['depth']
mask = sample['label']
width, height = img.size
left = 140
top = 30
right = 2030
bottom = 900
# crop
img = img.crop((left, top, right, bottom))
depth = depth.crop((left, top, right, bottom))
mask = mask.crop((left, top, right, bottom))
# resize
img = img.resize((width,height), Image.BILINEAR)
depth = depth.resize((width,height), Image.BILINEAR)
mask = mask.resize((width,height), Image.NEAREST)
# img = img.resize((512,1024), Image.BILINEAR)
# depth = depth.resize((512,1024), Image.BILINEAR)
# mask = mask.resize((512,1024), Image.NEAREST)

return {'image': img,
'depth': depth,
'label': mask}


class RandomHorizontalFlip(object):
def __call__(self, sample):
img = sample['image']
depth = sample['depth']
mask = sample['label']
if random.random() < 0.5:
img = img.transpose(Image.FLIP_LEFT_RIGHT)
depth = depth.transpose(Image.FLIP_LEFT_RIGHT)
mask = mask.transpose(Image.FLIP_LEFT_RIGHT)

return {'image': img,
'depth': depth,
'label': mask}


class RandomRotate(object):
def __init__(self, degree):
self.degree = degree

def __call__(self, sample):
img = sample['image']
depth = sample['depth']
mask = sample['label']
rotate_degree = random.uniform(-1*self.degree, self.degree)
img = img.rotate(rotate_degree, Image.BILINEAR)
depth = depth.rotate(rotate_degree, Image.BILINEAR)
mask = mask.rotate(rotate_degree, Image.NEAREST)

return {'image': img,
'depth': depth,
'label': mask}


class RandomGaussianBlur(object):
def __call__(self, sample):
img = sample['image']
depth = sample['depth']
mask = sample['label']
if random.random() < 0.5:
img = img.filter(ImageFilter.GaussianBlur(
radius=random.random()))

return {'image': img,
'depth': depth,
'label': mask}


class RandomScaleCrop(object):
def __init__(self, base_size, crop_size, fill=0):
self.base_size = base_size
self.crop_size = crop_size
self.fill = fill

def __call__(self, sample):
img = sample['image']
depth = sample['depth']
mask = sample['label']
# random scale (short edge)
short_size = random.randint(int(self.base_size * 0.5), int(self.base_size * 2.0))
w, h = img.size
if h > w:
ow = short_size
oh = int(1.0 * h * ow / w)
else:
oh = short_size
ow = int(1.0 * w * oh / h)
img = img.resize((ow, oh), Image.BILINEAR)
depth = depth.resize((ow, oh), Image.BILINEAR)
mask = mask.resize((ow, oh), Image.NEAREST)
# pad crop
if short_size < self.crop_size:
padh = self.crop_size - oh if oh < self.crop_size else 0
padw = self.crop_size - ow if ow < self.crop_size else 0
img = ImageOps.expand(img, border=(0, 0, padw, padh), fill=0)
depth = ImageOps.expand(depth, border=(0, 0, padw, padh), fill=0) # depth多余的部分填0
mask = ImageOps.expand(mask, border=(0, 0, padw, padh), fill=self.fill)
# random crop crop_size
w, h = img.size
x1 = random.randint(0, w - self.crop_size)
y1 = random.randint(0, h - self.crop_size)
img = img.crop((x1, y1, x1 + self.crop_size, y1 + self.crop_size))
depth = depth.crop((x1, y1, x1 + self.crop_size, y1 + self.crop_size))
mask = mask.crop((x1, y1, x1 + self.crop_size, y1 + self.crop_size))

return {'image': img,
'depth': depth,
'label': mask}


class FixScaleCrop(object):
def __init__(self, crop_size):
self.crop_size = crop_size

def __call__(self, sample):
img = sample['image']
depth = sample['depth']
mask = sample['label']
w, h = img.size
if w > h:
oh = self.crop_size
ow = int(1.0 * w * oh / h)
else:
ow = self.crop_size
oh = int(1.0 * h * ow / w)
img = img.resize((ow, oh), Image.BILINEAR)
depth = depth.resize((ow, oh), Image.BILINEAR)
mask = mask.resize((ow, oh), Image.NEAREST)
# center crop
w, h = img.size
x1 = int(round((w - self.crop_size) / 2.))
y1 = int(round((h - self.crop_size) / 2.))
img = img.crop((x1, y1, x1 + self.crop_size, y1 + self.crop_size))
depth = depth.crop((x1, y1, x1 + self.crop_size, y1 + self.crop_size))
mask = mask.crop((x1, y1, x1 + self.crop_size, y1 + self.crop_size))

return {'image': img,
'depth': depth,
'label': mask}

class FixedResize(object):
def __init__(self, size):
self.size = (size, size) # size: (h, w)

def __call__(self, sample):
img = sample['image']
depth = sample['depth']
mask = sample['label']

assert img.size == depth.size == mask.size

img = img.resize(self.size, Image.BILINEAR)
depth = depth.resize(self.size, Image.BILINEAR)
mask = mask.resize(self.size, Image.NEAREST)

return {'image': img,
'depth': depth,
'label': mask}

class Relabel(object):
def __init__(self, olabel, nlabel): # change trainid label from olabel to nlabel
self.olabel = olabel
self.nlabel = nlabel

def __call__(self, tensor):
# assert (isinstance(tensor, torch.LongTensor) or isinstance(tensor,
# torch.ByteTensor)), 'tensor needs to be LongTensor'
tensor[tensor == self.olabel] = self.nlabel
return tensor

+ 230
- 0
examples/lifelong_learning/RFNet/dataloaders/custom_transforms_rgb.py View File

@@ -0,0 +1,230 @@
import torch
import random
import numpy as np

from PIL import Image, ImageOps, ImageFilter

class Normalize(object):
"""Normalize a tensor image with mean and standard deviation.
Args:
mean (tuple): means for each channel.
std (tuple): standard deviations for each channel.
"""
def __init__(self, mean=(0., 0., 0.), std=(1., 1., 1.)):
self.mean = mean
self.std = std

def __call__(self, sample):
img = sample['image']
mask = sample['label']
img = np.array(img).astype(np.float32)
mask = np.array(mask).astype(np.float32)
img /= 255.0
img -= self.mean
img /= self.std

return {'image': img,
'label': mask}


class Normalize_test(object):
def __init__(self, mean=(0., 0., 0.), std=(1., 1., 1.)):
self.mean = mean
self.std = std

def __call__(self, sample):
img = sample
img = np.array(img).astype(np.float32)
img /= 255.0
img -= self.mean
img /= self.std

return img


class ToTensor(object):
"""Convert Image object in sample to Tensors."""

def __call__(self, sample):
# swap color axis because
# numpy image: H x W x C
# torch image: C X H X W
img = sample['image']
mask = sample['label']
img = np.array(img).astype(np.float32).transpose((2, 0, 1))
mask = np.array(mask).astype(np.float32)

img = torch.from_numpy(img).float()
mask = torch.from_numpy(mask).float()

return {'image': img,
'label': mask}

class CropBlackArea(object):
"""
crop black area for depth image
"""
def __call__(self, sample):
img = sample['image']
mask = sample['label']
width, height = img.size
left = 140
top = 30
right = 2030
bottom = 900
# crop
img = img.crop((left, top, right, bottom))
mask = mask.crop((left, top, right, bottom))
# resize
img = img.resize((width,height), Image.BILINEAR)
mask = mask.resize((width,height), Image.NEAREST)
# img = img.resize((512,1024), Image.BILINEAR)
# mask = mask.resize((512,1024), Image.NEAREST)
print(img.size)

return {'image': img,
'label': mask}

class ToTensor_test(object):
"""Convert Image object in sample to Tensors."""

def __call__(self, sample):
# swap color axis because
# numpy image: H x W x C
# torch image: C X H X W
img = sample
img = np.array(img).astype(np.float32).transpose((2, 0, 1))

img = torch.from_numpy(img).float()

return img


class RandomHorizontalFlip(object):
def __call__(self, sample):
img = sample['image']
mask = sample['label']
if random.random() < 0.5:
img = img.transpose(Image.FLIP_LEFT_RIGHT)
mask = mask.transpose(Image.FLIP_LEFT_RIGHT)

return {'image': img,
'label': mask}


class RandomRotate(object):
def __init__(self, degree):
self.degree = degree

def __call__(self, sample):
img = sample['image']
mask = sample['label']
rotate_degree = random.uniform(-1*self.degree, self.degree)
img = img.rotate(rotate_degree, Image.BILINEAR)
mask = mask.rotate(rotate_degree, Image.NEAREST)

return {'image': img,
'label': mask}


class RandomGaussianBlur(object):
def __call__(self, sample):
img = sample['image']
mask = sample['label']
if random.random() < 0.5:
img = img.filter(ImageFilter.GaussianBlur(
radius=random.random()))

return {'image': img,
'label': mask}


class RandomScaleCrop(object):
def __init__(self, base_size, crop_size, fill=0):
self.base_size = base_size
self.crop_size = crop_size
self.fill = fill

def __call__(self, sample):
img = sample['image']
mask = sample['label']
# random scale (short edge)
short_size = random.randint(int(self.base_size * 0.5), int(self.base_size * 2.0))
w, h = img.size
if h > w:
ow = short_size
oh = int(1.0 * h * ow / w)
else:
oh = short_size
ow = int(1.0 * w * oh / h)
img = img.resize((ow, oh), Image.BILINEAR)
mask = mask.resize((ow, oh), Image.NEAREST)
# pad crop
if short_size < self.crop_size:
padh = self.crop_size - oh if oh < self.crop_size else 0
padw = self.crop_size - ow if ow < self.crop_size else 0
img = ImageOps.expand(img, border=(0, 0, padw, padh), fill=0)
mask = ImageOps.expand(mask, border=(0, 0, padw, padh), fill=self.fill)
# random crop crop_size
w, h = img.size
x1 = random.randint(0, w - self.crop_size)
y1 = random.randint(0, h - self.crop_size)
img = img.crop((x1, y1, x1 + self.crop_size, y1 + self.crop_size))
mask = mask.crop((x1, y1, x1 + self.crop_size, y1 + self.crop_size))

return {'image': img,
'label': mask}


class FixScaleCrop(object):
def __init__(self, crop_size):
self.crop_size = crop_size

def __call__(self, sample):
img = sample['image']
mask = sample['label']
w, h = img.size
if w > h:
oh = self.crop_size
ow = int(1.0 * w * oh / h)
else:
ow = self.crop_size
oh = int(1.0 * h * ow / w)
img = img.resize((ow, oh), Image.BILINEAR)
mask = mask.resize((ow, oh), Image.NEAREST)
# center crop
w, h = img.size
x1 = int(round((w - self.crop_size) / 2.))
y1 = int(round((h - self.crop_size) / 2.))
img = img.crop((x1, y1, x1 + self.crop_size, y1 + self.crop_size))
mask = mask.crop((x1, y1, x1 + self.crop_size, y1 + self.crop_size))

return {'image': img,
'label': mask}

class FixedResize(object):
def __init__(self, size):
self.size = (size, size) # size: (h, w)

def __call__(self, sample):
img = sample['image']
mask = sample['label']

assert img.size == mask.size

img = img.resize(self.size, Image.BILINEAR)
mask = mask.resize(self.size, Image.NEAREST)

return {'image': img,
'label': mask}

class Relabel(object):
def __init__(self, olabel, nlabel): # change trainid label from olabel to nlabel
self.olabel = olabel
self.nlabel = nlabel

def __call__(self, tensor):
# assert (isinstance(tensor, torch.LongTensor) or isinstance(tensor,
# torch.ByteTensor)), 'tensor needs to be LongTensor'
tensor[tensor == self.olabel] = self.nlabel
return tensor

+ 0
- 0
examples/lifelong_learning/RFNet/dataloaders/datasets/__init__.py View File


+ 276
- 0
examples/lifelong_learning/RFNet/dataloaders/datasets/citylostfound.py View File

@@ -0,0 +1,276 @@
import os
import numpy as np
from PIL import Image
from torch.utils import data
from mypath import Path
from torchvision import transforms
from dataloaders import custom_transforms as tr
from dataloaders import custom_transforms_rgb as tr_rgb

class CitylostfoundSegmentation(data.Dataset):
NUM_CLASSES = 20

def __init__(self, args, root=Path.db_root_dir('citylostfound'), split="train"):

self.root = root
self.split = split
self.args = args
self.images = {}
self.disparities = {}
self.labels = {}

self.images_base = os.path.join(self.root, 'leftImg8bit', self.split)
self.disparities_base = os.path.join(self.root,'disparity',self.split)
self.annotations_base = os.path.join(self.root, 'gtFine', self.split)

self.images[split] = self.recursive_glob(rootdir=self.images_base, suffix= '.png')
self.images[split].sort()

self.disparities[split] = self.recursive_glob(rootdir=self.disparities_base, suffix= '.png')
self.disparities[split].sort()

self.labels[split] = self.recursive_glob(rootdir=self.annotations_base,
suffix='labelTrainIds.png')
self.labels[split].sort()

self.ignore_index = 255

if not self.images[split]:
raise Exception("No RGB images for split=[%s] found in %s" % (split, self.images_base))
if not self.disparities[split]:
raise Exception("No depth images for split=[%s] found in %s" % (split, self.disparities_base))


print("Found %d %s RGB images" % (len(self.images[split]), split))
print("Found %d %s disparity images" % (len(self.disparities[split]), split))


def __len__(self):
return len(self.images[self.split])

def __getitem__(self, index):

img_path = self.images[self.split][index].rstrip()
disp_path = self.disparities[self.split][index].rstrip()
lbl_path = self.labels[self.split][index].rstrip()

_img = Image.open(img_path).convert('RGB')
_depth = Image.open(disp_path)
_tmp = np.array(Image.open(lbl_path), dtype=np.uint8)
if self.split == 'train':
if index < 1036: # lostandfound
_tmp = self.relabel_lostandfound(_tmp)
else: # cityscapes
pass
elif self.split == 'val':
if index < 1203: # lostandfound
_tmp = self.relabel_lostandfound(_tmp)
else: # cityscapes
pass
_target = Image.fromarray(_tmp)

sample = {'image': _img, 'depth': _depth, 'label': _target}

# data augment
if self.split == 'train':
return self.transform_tr(sample)
elif self.split == 'val':
return self.transform_val(sample), img_path
elif self.split == 'test':
return self.transform_ts(sample)


def relabel_lostandfound(self, input):
input = tr.Relabel(0, self.ignore_index)(input) # background->255 ignore
input = tr.Relabel(1, 0)(input) # road 1->0
# input = Relabel(255, 20)(input) # unlabel 20
input = tr.Relabel(2, 19)(input) # obstacle 19
return input

def recursive_glob(self, rootdir='.', suffix=None):
"""Performs recursive glob with given suffix and rootdir
:param rootdir is the root directory
:param suffix is the suffix to be searched
"""
if isinstance(suffix, str):
return [os.path.join(looproot, filename)
for looproot, _, filenames in os.walk(rootdir)
for filename in filenames if filename.endswith(suffix)]
elif isinstance(suffix, list):
return [os.path.join(looproot, filename)
for looproot, _, filenames in os.walk(rootdir)
for x in suffix for filename in filenames if filename.startswith(x)]


def transform_tr(self, sample):

composed_transforms = transforms.Compose([
tr.CropBlackArea(),
tr.RandomHorizontalFlip(),
tr.RandomScaleCrop(base_size=self.args.base_size, crop_size=self.args.crop_size, fill=255),
# tr.RandomGaussianBlur(),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

def transform_val(self, sample):

composed_transforms = transforms.Compose([
tr.CropBlackArea(),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

def transform_ts(self, sample):

composed_transforms = transforms.Compose([
# tr.CropBlackArea(),
tr.FixedResize(size=self.args.crop_size),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)


class CitylostfoundSegmentation_rgb(data.Dataset):
NUM_CLASSES = 19

def __init__(self, args, root=Path.db_root_dir('citylostfound'), split="train"):

self.root = root
self.split = split
self.args = args
self.files = {}
self.labels = {}

self.images_base = os.path.join(self.root, 'leftImg8bit', self.split)
self.annotations_base = os.path.join(self.root, 'gtFine', self.split)

self.files[split] = self.recursive_glob(rootdir=self.images_base, suffix='.png')
self.files[split].sort()

self.labels[split] = self.recursive_glob(rootdir=self.annotations_base, suffix='labelTrainIds.png')
self.labels[split].sort()

self.ignore_index = 255

if not self.files[split]:
raise Exception("No files for split=[%s] found in %s" % (split, self.images_base))

print("Found %d %s images" % (len(self.files[split]), split))

def __len__(self):
return len(self.files[self.split])

def __getitem__(self, index):

img_path = self.files[self.split][index].rstrip()
lbl_path = self.labels[self.split][index].rstrip()
_img = Image.open(img_path).convert('RGB')
_tmp = np.array(Image.open(lbl_path), dtype=np.uint8)
if self.split == 'train':
if index < 1036: # lostandfound
_tmp = self.relabel_lostandfound(_tmp)
else: # cityscapes
pass
elif self.split == 'val':
if index < 1203: # lostandfound
_tmp = self.relabel_lostandfound(_tmp)
else: # cityscapes
pass
_target = Image.fromarray(_tmp)

sample = {'image': _img, 'label': _target}

if self.split == 'train':
return self.transform_tr(sample)
elif self.split == 'val':
return self.transform_val(sample), img_path
elif self.split == 'test':
return self.transform_ts(sample)


def relabel_lostandfound(self, input):
input = tr.Relabel(0, self.ignore_index)(input)
input = tr.Relabel(1, 0)(input) # road 1->0
input = tr.Relabel(2, 19)(input) # obstacle 19
return input

def recursive_glob(self, rootdir='.', suffix=''):
"""Performs recursive glob with given suffix and rootdir
:param rootdir is the root directory
:param suffix is the suffix to be searched
"""
return [os.path.join(looproot, filename)
for looproot, _, filenames in os.walk(rootdir)
for filename in filenames if filename.endswith(suffix)]

def transform_tr(self, sample):
composed_transforms = transforms.Compose([
tr_rgb.CropBlackArea(),
tr_rgb.RandomHorizontalFlip(),
tr_rgb.RandomScaleCrop(base_size=self.args.base_size, crop_size=self.args.crop_size, fill=255),
# tr.RandomGaussianBlur(),
tr_rgb.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr_rgb.ToTensor()])

return composed_transforms(sample)

def transform_val(self, sample):

composed_transforms = transforms.Compose([
tr_rgb.CropBlackArea(),
tr_rgb.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr_rgb.ToTensor()])

return composed_transforms(sample)

def transform_ts(self, sample):

composed_transforms = transforms.Compose([
tr_rgb.FixedResize(size=self.args.crop_size),
tr_rgb.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr_rgb.ToTensor()])

return composed_transforms(sample)

if __name__ == '__main__':
from dataloaders.utils import decode_segmap
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import argparse

parser = argparse.ArgumentParser()
args = parser.parse_args()
args.base_size = 513
args.crop_size = 513

cityscapes_train = CitylostfoundSegmentation(args, split='train')

dataloader = DataLoader(cityscapes_train, batch_size=2, shuffle=True, num_workers=2)

for ii, sample in enumerate(dataloader):
for jj in range(sample["image"].size()[0]):
img = sample['image'].numpy()
gt = sample['label'].numpy()
tmp = np.array(gt[jj]).astype(np.uint8)
segmap = decode_segmap(tmp, dataset='cityscapes')
img_tmp = np.transpose(img[jj], axes=[1, 2, 0])
img_tmp *= (0.229, 0.224, 0.225)
img_tmp += (0.485, 0.456, 0.406)
img_tmp *= 255.0
img_tmp = img_tmp.astype(np.uint8)
plt.figure()
plt.title('display')
plt.subplot(211)
plt.imshow(img_tmp)
plt.subplot(212)
plt.imshow(segmap)

if ii == 1:
break

plt.show(block=True)


+ 151
- 0
examples/lifelong_learning/RFNet/dataloaders/datasets/cityrand.py View File

@@ -0,0 +1,151 @@
import os
import numpy as np
import scipy.misc as m
from PIL import Image
from torch.utils import data
from mypath import Path
from torchvision import transforms
from dataloaders import custom_transforms as tr

class CityscapesSegmentation(data.Dataset):
NUM_CLASSES = 19

def __init__(self, args, root=Path.db_root_dir('cityrand'), split="train"):

self.root = root
self.split = split
self.args = args
self.images = {}
self.disparities = {}
self.labels = {}

self.images_base = os.path.join(self.root, 'leftImg8bit', self.split)
self.disparities_base = os.path.join(self.root, 'disparity', self.split)
self.annotations_base = os.path.join(self.root, 'gtFine', self.split)

self.images[split] = self.recursive_glob(rootdir=self.images_base, suffix='.png')
self.images[split].sort()

self.disparities[split] = self.recursive_glob(rootdir=self.disparities_base, suffix='.png')
self.disparities[split].sort()

self.labels[split] = self.recursive_glob(rootdir=self.annotations_base, suffix='TrainIds.png')
self.labels[split].sort()

self.ignore_index = 255

if not self.images[split]:
raise Exception("No RGB images for split=[%s] found in %s" % (split, self.images_base))
if not self.disparities[split]:
raise Exception("No depth images for split=[%s] found in %s" % (split, self.disparities_base))

print("Found %d %s RGB images" % (len(self.images[split]), split))
print("Found %d %s disparity images" % (len(self.disparities[split]), split))


def __len__(self):
return len(self.images[self.split])

def __getitem__(self, index):

img_path = self.images[self.split][index].rstrip()
disp_path = self.disparities[self.split][index].rstrip()
#print(index)
try:
lbl_path = self.labels[self.split][index].rstrip()
_img = Image.open(img_path).convert('RGB')
_depth = Image.open(disp_path)
_target = Image.open(lbl_path)
sample = {'image': _img,'depth':_depth, 'label': _target}
except:
_img = Image.open(img_path).convert('RGB')
_depth = Image.open(disp_path)
sample = {'image': _img,'depth':_depth, 'label': _img}

if self.split == 'train':
return self.transform_tr(sample)
elif self.split == 'val':
return self.transform_val(sample), img_path
elif self.split == 'test':
return self.transform_ts(sample), img_path
elif self.split == 'custom_resize':
return self.transform_ts(sample), img_path


def recursive_glob(self, rootdir='.', suffix=''):
"""Performs recursive glob with given suffix and rootdir
:param rootdir is the root directory
:param suffix is the suffix to be searched
"""
return [os.path.join(looproot, filename)
for looproot, _, filenames in os.walk(rootdir)
for filename in filenames if filename.endswith(suffix)]

def transform_tr(self, sample):
composed_transforms = transforms.Compose([
tr.CropBlackArea(),
tr.RandomHorizontalFlip(),
tr.RandomScaleCrop(base_size=self.args.base_size, crop_size=self.args.crop_size, fill=255),
# tr.RandomGaussianBlur(),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

def transform_val(self, sample):

composed_transforms = transforms.Compose([
tr.CropBlackArea(),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

def transform_ts(self, sample):

composed_transforms = transforms.Compose([
#tr.CropBlackArea(),
#tr.FixedResize(size=self.args.crop_size),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

if __name__ == '__main__':
from dataloaders.utils import decode_segmap
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import argparse

parser = argparse.ArgumentParser()
args = parser.parse_args()
args.base_size = 513
args.crop_size = 513

cityscapes_train = CityscapesSegmentation(args, split='train')

dataloader = DataLoader(cityscapes_train, batch_size=2, shuffle=True, num_workers=2)

for ii, sample in enumerate(dataloader):
for jj in range(sample["image"].size()[0]):
img = sample['image'].numpy()
gt = sample['label'].numpy()
tmp = np.array(gt[jj]).astype(np.uint8)
segmap = decode_segmap(tmp, dataset='cityscapes')
img_tmp = np.transpose(img[jj], axes=[1, 2, 0])
img_tmp *= (0.229, 0.224, 0.225)
img_tmp += (0.485, 0.456, 0.406)
img_tmp *= 255.0
img_tmp = img_tmp.astype(np.uint8)
plt.figure()
plt.title('display')
plt.subplot(211)
plt.imshow(img_tmp)
plt.subplot(212)
plt.imshow(segmap)

if ii == 1:
break

plt.show(block=True)

+ 151
- 0
examples/lifelong_learning/RFNet/dataloaders/datasets/cityscapes.py View File

@@ -0,0 +1,151 @@
import os
import numpy as np
from PIL import Image
from torch.utils import data
from mypath import Path
from torchvision import transforms
from dataloaders import custom_transforms as tr

class CityscapesSegmentation(data.Dataset):
NUM_CLASSES = 24 # 25

def __init__(self, args, root=Path.db_root_dir('cityscapes'), data=None, split="train"):

# self.root = root
self.root = "/home/lsq/Dataset/"
self.split = split
self.args = args
self.images = {}
self.disparities = {}
self.labels = {}

self.disparities_base = os.path.join(self.root, self.split, "depth", "cityscapes_real")
self.images[split] = [img[0] for img in data.x] if hasattr(data, "x") else data

if hasattr(data, "x") and len(data.x[0]) == 1:
# TODO: fit the case that depth images don't exist.
self.disparities[split] = self.images[split]
elif hasattr(data, "x") and len(data.x[0]) == 2:
self.disparities[split] = [img[1] for img in data.x]
else:
self.disparities[split] = data

self.labels[split] = data.y if hasattr(data, "y") else data

self.ignore_index = 255

if len(self.images[split]) == 0:
raise Exception("No RGB images for split=[%s] found in %s" % (split, self.images_base))
if len(self.disparities[split]) == 0:
raise Exception("No depth images for split=[%s] found in %s" % (split, self.disparities_base))

print("Found %d %s RGB images" % (len(self.images[split]), split))
print("Found %d %s disparity images" % (len(self.disparities[split]), split))


def __len__(self):
return len(self.images[self.split])

def __getitem__(self, index):

img_path = self.images[self.split][index].rstrip()
disp_path = self.disparities[self.split][index].rstrip()
#print(index)
try:
lbl_path = self.labels[self.split][index].rstrip()
_img = Image.open(img_path).convert('RGB')
_depth = Image.open(disp_path)
_target = Image.open(lbl_path)
sample = {'image': _img,'depth':_depth, 'label': _target}
except:
_img = Image.open(img_path).convert('RGB')
_depth = Image.open(disp_path)
sample = {'image': _img,'depth':_depth, 'label': _img}

if self.split == 'train':
return self.transform_tr(sample)
elif self.split == 'val':
return self.transform_val(sample), img_path
elif self.split == 'test':
return self.transform_ts(sample), img_path
elif self.split == 'custom_resize':
return self.transform_ts(sample), img_path


def recursive_glob(self, rootdir='.', suffix=''):
"""Performs recursive glob with given suffix and rootdir
:param rootdir is the root directory
:param suffix is the suffix to be searched
"""
return [os.path.join(looproot, filename)
for looproot, _, filenames in os.walk(rootdir)
for filename in filenames if filename.endswith(suffix)]

def transform_tr(self, sample):
composed_transforms = transforms.Compose([
tr.CropBlackArea(),
tr.RandomHorizontalFlip(),
tr.RandomScaleCrop(base_size=self.args.base_size, crop_size=self.args.crop_size, fill=255),
# tr.RandomGaussianBlur(),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

def transform_val(self, sample):

composed_transforms = transforms.Compose([
tr.CropBlackArea(),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

def transform_ts(self, sample):

composed_transforms = transforms.Compose([
#tr.CropBlackArea(),
#tr.FixedResize(size=self.args.crop_size),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

if __name__ == '__main__':
from dataloaders.utils import decode_segmap
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import argparse

parser = argparse.ArgumentParser()
args = parser.parse_args()
args.base_size = 513
args.crop_size = 513

cityscapes_train = CityscapesSegmentation(args, split='train')

dataloader = DataLoader(cityscapes_train, batch_size=2, shuffle=True, num_workers=2)

for ii, sample in enumerate(dataloader):
for jj in range(sample["image"].size()[0]):
img = sample['image'].numpy()
gt = sample['label'].numpy()
tmp = np.array(gt[jj]).astype(np.uint8)
segmap = decode_segmap(tmp, dataset='cityscapes')
img_tmp = np.transpose(img[jj], axes=[1, 2, 0])
img_tmp *= (0.229, 0.224, 0.225)
img_tmp += (0.485, 0.456, 0.406)
img_tmp *= 255.0
img_tmp = img_tmp.astype(np.uint8)
plt.figure()
plt.title('display')
plt.subplot(211)
plt.imshow(img_tmp)
plt.subplot(212)
plt.imshow(segmap)

if ii == 1:
break

plt.show(block=True)


+ 151
- 0
examples/lifelong_learning/RFNet/dataloaders/datasets/e1.py View File

@@ -0,0 +1,151 @@
import os
import numpy as np
import scipy.misc as m
from PIL import Image
from torch.utils import data
from mypath import Path
from torchvision import transforms
from dataloaders import custom_transforms as tr

class CityscapesSegmentation(data.Dataset):
NUM_CLASSES = 24

def __init__(self, args, root=Path.db_root_dir('e1'), split="train"):

self.root = root
self.split = split
self.args = args
self.images = {}
self.disparities = {}
self.labels = {}

self.images_base = os.path.join(self.root, 'leftImg8bit', self.split)
self.disparities_base = os.path.join(self.root, 'disparity', self.split)
self.annotations_base = os.path.join(self.root, 'gtFine', self.split)

self.images[split] = self.recursive_glob(rootdir=self.images_base, suffix='.png')
self.images[split].sort()

self.disparities[split] = self.recursive_glob(rootdir=self.disparities_base, suffix='.png')
self.disparities[split].sort()

self.labels[split] = self.recursive_glob(rootdir=self.annotations_base, suffix='.png')
self.labels[split].sort()

self.ignore_index = 255

if not self.images[split]:
raise Exception("No RGB images for split=[%s] found in %s" % (split, self.images_base))
if not self.disparities[split]:
raise Exception("No depth images for split=[%s] found in %s" % (split, self.disparities_base))

print("Found %d %s RGB images" % (len(self.images[split]), split))
print("Found %d %s disparity images" % (len(self.disparities[split]), split))


def __len__(self):
return len(self.images[self.split])

def __getitem__(self, index):

img_path = self.images[self.split][index].rstrip()
disp_path = self.disparities[self.split][index].rstrip()
#print(index)
try:
lbl_path = self.labels[self.split][index].rstrip()
_img = Image.open(img_path).convert('RGB')
_depth = Image.open(disp_path)
_target = Image.open(lbl_path)
sample = {'image': _img,'depth':_depth, 'label': _target}
except:
_img = Image.open(img_path).convert('RGB')
_depth = Image.open(disp_path)
sample = {'image': _img,'depth':_depth, 'label': _img}

if self.split == 'train':
return self.transform_tr(sample)
elif self.split == 'val':
return self.transform_val(sample), img_path
elif self.split == 'test':
return self.transform_ts(sample), img_path
elif self.split == 'custom_resize':
return self.transform_ts(sample), img_path


def recursive_glob(self, rootdir='.', suffix=''):
"""Performs recursive glob with given suffix and rootdir
:param rootdir is the root directory
:param suffix is the suffix to be searched
"""
return [os.path.join(looproot, filename)
for looproot, _, filenames in os.walk(rootdir)
for filename in filenames if filename.endswith(suffix)]

def transform_tr(self, sample):
composed_transforms = transforms.Compose([
#tr.CropBlackArea(),
tr.RandomHorizontalFlip(),
tr.RandomScaleCrop(base_size=self.args.base_size, crop_size=self.args.crop_size, fill=255),
# tr.RandomGaussianBlur(),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

def transform_val(self, sample):

composed_transforms = transforms.Compose([
tr.CropBlackArea(),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

def transform_ts(self, sample):

composed_transforms = transforms.Compose([
#tr.CropBlackArea(),
#tr.FixedResize(size=self.args.crop_size),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

if __name__ == '__main__':
from dataloaders.utils import decode_segmap
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import argparse

parser = argparse.ArgumentParser()
args = parser.parse_args()
args.base_size = 513
args.crop_size = 513

cityscapes_train = CityscapesSegmentation(args, split='train')

dataloader = DataLoader(cityscapes_train, batch_size=2, shuffle=True, num_workers=2)

for ii, sample in enumerate(dataloader):
for jj in range(sample["image"].size()[0]):
img = sample['image'].numpy()
gt = sample['label'].numpy()
tmp = np.array(gt[jj]).astype(np.uint8)
segmap = decode_segmap(tmp, dataset='cityscapes')
img_tmp = np.transpose(img[jj], axes=[1, 2, 0])
img_tmp *= (0.229, 0.224, 0.225)
img_tmp += (0.485, 0.456, 0.406)
img_tmp *= 255.0
img_tmp = img_tmp.astype(np.uint8)
plt.figure()
plt.title('display')
plt.subplot(211)
plt.imshow(img_tmp)
plt.subplot(212)
plt.imshow(segmap)

if ii == 1:
break

plt.show(block=True)


+ 152
- 0
examples/lifelong_learning/RFNet/dataloaders/datasets/mapillary.py View File

@@ -0,0 +1,152 @@
import os
import numpy as np
import scipy.misc as m
from PIL import Image
from torch.utils import data
from mypath import Path
from torchvision import transforms
from dataloaders import custom_transforms as tr

class CityscapesSegmentation(data.Dataset):
NUM_CLASSES = 24

def __init__(self, args, root=Path.db_root_dir('mapillary'), split="train"):

self.root = root
self.split = split
self.args = args
self.images = {}
self.disparities = {}
self.labels = {}

self.images_base = os.path.join(self.root, 'leftImg8bit', self.split)
self.disparities_base = os.path.join(self.root, 'disparity', self.split)
self.annotations_base = os.path.join(self.root, 'gtFine', self.split)

self.images[split] = self.recursive_glob(rootdir=self.images_base, suffix='.png')
self.images[split].sort()

self.disparities[split] = self.recursive_glob(rootdir=self.disparities_base, suffix='.png')
self.disparities[split].sort()

self.labels[split] = self.recursive_glob(rootdir=self.annotations_base, suffix='.png')
self.labels[split].sort()

self.ignore_index = 255

if not self.images[split]:
raise Exception("No RGB images for split=[%s] found in %s" % (split, self.images_base))
if not self.disparities[split]:
raise Exception("No depth images for split=[%s] found in %s" % (split, self.disparities_base))

print("Found %d %s RGB images" % (len(self.images[split]), split))
print("Found %d %s disparity images" % (len(self.disparities[split]), split))


def __len__(self):
return len(self.images[self.split])

def __getitem__(self, index):

img_path = self.images[self.split][index].rstrip()
disp_path = self.disparities[self.split][index].rstrip()
#print(index)
try:
lbl_path = self.labels[self.split][index].rstrip()
_img = Image.open(img_path).convert('RGB')
_depth = Image.open(disp_path)
_target = Image.open(lbl_path)
sample = {'image': _img,'depth':_depth, 'label': _target}
except:
_img = Image.open(img_path).convert('RGB')
_depth = Image.open(disp_path)
sample = {'image': _img,'depth':_depth, 'label': _img}

if self.split == 'train':
return self.transform_tr(sample)
elif self.split == 'val':
return self.transform_val(sample), img_path
elif self.split == 'test':
return self.transform_ts(sample), img_path
elif self.split == 'custom_resize':
return self.transform_ts(sample), img_path


def recursive_glob(self, rootdir='.', suffix=''):
"""Performs recursive glob with given suffix and rootdir
:param rootdir is the root directory
:param suffix is the suffix to be searched
"""
return [os.path.join(looproot, filename)
for looproot, _, filenames in os.walk(rootdir)
for filename in filenames if filename.endswith(suffix)]

def transform_tr(self, sample):
composed_transforms = transforms.Compose([
tr.CropBlackArea(),
tr.RandomHorizontalFlip(),
tr.RandomScaleCrop(base_size=self.args.base_size, crop_size=self.args.crop_size, fill=255),
# tr.RandomGaussianBlur(),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

def transform_val(self, sample):

composed_transforms = transforms.Compose([
tr.CropBlackArea(),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

def transform_ts(self, sample):

composed_transforms = transforms.Compose([
tr.CropBlackArea(),
#tr.FixedResize(size=self.args.crop_size),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

if __name__ == '__main__':
from dataloaders.utils import decode_segmap
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import argparse

parser = argparse.ArgumentParser()
args = parser.parse_args()
args.base_size = 768
args.crop_size = 768

cityscapes_train = CityscapesSegmentation(args, split='train')

dataloader = DataLoader(cityscapes_train, batch_size=2, shuffle=True, num_workers=2)

for ii, sample in enumerate(dataloader):
for jj in range(sample["image"].size()[0]):
img = sample['image'].numpy()
gt = sample['label'].numpy()
tmp = np.array(gt[jj]).astype(np.uint8)
segmap = decode_segmap(tmp, dataset='cityscapes')
img_tmp = np.transpose(img[jj], axes=[1, 2, 0])
img_tmp *= (0.229, 0.224, 0.225)
img_tmp += (0.485, 0.456, 0.406)
img_tmp *= 255.0
img_tmp = img_tmp.astype(np.uint8)
plt.figure()
plt.title('display')
plt.subplot(211)
plt.imshow(img_tmp)
plt.subplot(212)
plt.imshow(segmap)

if ii == 1:
break

plt.show(block=True)


+ 152
- 0
examples/lifelong_learning/RFNet/dataloaders/datasets/target.py View File

@@ -0,0 +1,152 @@
import os
import numpy as np
import scipy.misc as m
from PIL import Image
from torch.utils import data
from mypath import Path
from torchvision import transforms
from dataloaders import custom_transforms as tr

class CityscapesSegmentation(data.Dataset):
NUM_CLASSES = 24

def __init__(self, args, root=Path.db_root_dir('target'), split="train"):

self.root = root
self.split = split
self.args = args
self.images = {}
self.disparities = {}
self.labels = {}

self.images_base = os.path.join(self.root, 'leftImg8bit', self.split)
self.disparities_base = os.path.join(self.root, 'disparity', self.split)
self.annotations_base = os.path.join(self.root, 'gtFine', self.split)

self.images[split] = self.recursive_glob(rootdir=self.images_base, suffix='.png')
self.images[split].sort()

self.disparities[split] = self.recursive_glob(rootdir=self.disparities_base, suffix='.png')
self.disparities[split].sort()

self.labels[split] = self.recursive_glob(rootdir=self.annotations_base, suffix='TrainIds.png')
self.labels[split].sort()

self.ignore_index = 255

if not self.images[split]:
raise Exception("No RGB images for split=[%s] found in %s" % (split, self.images_base))
if not self.disparities[split]:
raise Exception("No depth images for split=[%s] found in %s" % (split, self.disparities_base))

print("Found %d %s RGB images" % (len(self.images[split]), split))
print("Found %d %s disparity images" % (len(self.disparities[split]), split))


def __len__(self):
return len(self.images[self.split])

def __getitem__(self, index):

img_path = self.images[self.split][index].rstrip()
disp_path = self.disparities[self.split][index].rstrip()
#print(index)
try:
lbl_path = self.labels[self.split][index].rstrip()
_img = Image.open(img_path).convert('RGB')
_depth = Image.open(disp_path)
_target = Image.open(lbl_path)
sample = {'image': _img,'depth':_depth, 'label': _target}
except:
_img = Image.open(img_path).convert('RGB')
_depth = Image.open(disp_path)
sample = {'image': _img,'depth':_depth, 'label': _img}

if self.split == 'train':
return self.transform_tr(sample)
elif self.split == 'val':
return self.transform_val(sample), img_path
elif self.split == 'test':
return self.transform_ts(sample), img_path
elif self.split == 'custom_resize':
return self.transform_ts(sample), img_path


def recursive_glob(self, rootdir='.', suffix=''):
"""Performs recursive glob with given suffix and rootdir
:param rootdir is the root directory
:param suffix is the suffix to be searched
"""
return [os.path.join(looproot, filename)
for looproot, _, filenames in os.walk(rootdir)
for filename in filenames if filename.endswith(suffix)]

def transform_tr(self, sample):
composed_transforms = transforms.Compose([
tr.CropBlackArea(),
tr.RandomHorizontalFlip(),
tr.RandomScaleCrop(base_size=self.args.base_size, crop_size=self.args.crop_size, fill=255),
# tr.RandomGaussianBlur(),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

def transform_val(self, sample):

composed_transforms = transforms.Compose([
tr.CropBlackArea(),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

def transform_ts(self, sample):

composed_transforms = transforms.Compose([
tr.CropBlackArea(),
#tr.FixedResize(size=self.args.crop_size),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

if __name__ == '__main__':
from dataloaders.utils import decode_segmap
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import argparse

parser = argparse.ArgumentParser()
args = parser.parse_args()
args.base_size = 513
args.crop_size = 513

cityscapes_train = CityscapesSegmentation(args, split='train')

dataloader = DataLoader(cityscapes_train, batch_size=2, shuffle=True, num_workers=2)

for ii, sample in enumerate(dataloader):
for jj in range(sample["image"].size()[0]):
img = sample['image'].numpy()
gt = sample['label'].numpy()
tmp = np.array(gt[jj]).astype(np.uint8)
segmap = decode_segmap(tmp, dataset='cityscapes')
img_tmp = np.transpose(img[jj], axes=[1, 2, 0])
img_tmp *= (0.229, 0.224, 0.225)
img_tmp += (0.485, 0.456, 0.406)
img_tmp *= 255.0
img_tmp = img_tmp.astype(np.uint8)
plt.figure()
plt.title('display')
plt.subplot(211)
plt.imshow(img_tmp)
plt.subplot(212)
plt.imshow(segmap)

if ii == 1:
break

plt.show(block=True)


+ 38
- 0
examples/lifelong_learning/RFNet/dataloaders/datasets/temp.txt View File

@@ -0,0 +1,38 @@
for i in range(len(nam_label)):
img_label = cv2.imread(label_ori_path+nam_label[i], -1)[:,:,2]
img_label_temp = img_label.copy()
img_label_temp[img_label == 0] = 22
img_label_temp[img_label == 1] = 10
img_label_temp[img_label == 2] = 2
img_label_temp[img_label == 3] = 0
img_label_temp[img_label == 4] = 1
img_label_temp[img_label == 5] = 4
img_label_temp[img_label == 6] = 8
img_label_temp[img_label == 7] = 5
img_label_temp[img_label == 8] = 13
img_label_temp[img_label == 9] = 7
img_label_temp[img_label == 10] = 11
img_label_temp[img_label == 11] = 18
img_label_temp[img_label == 12] = 17
img_label_temp[img_label == 13] = 21
img_label_temp[img_label == 14] = 20
img_label_temp[img_label == 15] = 6
img_label_temp[img_label == 16] = 9
img_label_temp[img_label == 17] = 12
img_label_temp[img_label == 18] = 14
img_label_temp[img_label == 19] = 15
img_label_temp[img_label == 20] = 16
img_label_temp[img_label == 21] = 3
img_label_temp[img_label == 22] = 19
#print(img_label)
#img_label[img_label == 0] = 10
#img_label[img_label == 6] = 0
#img_label[img_label == 5] = 11
#img_label[img_label == 1] = 5
#img_label[img_label == 2] = 1
#img_label[img_label == 4] = 9
#img_label[img_label == 3] = 4
#img_label[img_label == 7] = 8
#img_label[img_label == 11] = 2
img_resize_lab = cv2.resize(img_label_temp, (2048,1024), interpolation=cv2.INTER_NEAREST)
cv2.imwrite(label_save_path+str(i)+'TrainIds.png', img_resize_lab.astype(np.uint16))

+ 152
- 0
examples/lifelong_learning/RFNet/dataloaders/datasets/xrlab.py View File

@@ -0,0 +1,152 @@
import os
import numpy as np
import scipy.misc as m
from PIL import Image
from torch.utils import data
from mypath import Path
from torchvision import transforms
from dataloaders import custom_transforms as tr

class CityscapesSegmentation(data.Dataset):
NUM_CLASSES = 25

def __init__(self, args, root=Path.db_root_dir('xrlab'), split="train"):

self.root = root
self.split = split
self.args = args
self.images = {}
self.disparities = {}
self.labels = {}

self.images_base = os.path.join(self.root, 'leftImg8bit', self.split)
self.disparities_base = os.path.join(self.root, 'disparity', self.split)
self.annotations_base = os.path.join(self.root, 'gtFine', self.split)

self.images[split] = self.recursive_glob(rootdir=self.images_base, suffix='.png')
self.images[split].sort()

self.disparities[split] = self.recursive_glob(rootdir=self.disparities_base, suffix='.png')
self.disparities[split].sort()

self.labels[split] = self.recursive_glob(rootdir=self.annotations_base, suffix='.png')
self.labels[split].sort()

self.ignore_index = 255

if not self.images[split]:
raise Exception("No RGB images for split=[%s] found in %s" % (split, self.images_base))
if not self.disparities[split]:
raise Exception("No depth images for split=[%s] found in %s" % (split, self.disparities_base))

print("Found %d %s RGB images" % (len(self.images[split]), split))
print("Found %d %s disparity images" % (len(self.disparities[split]), split))


def __len__(self):
return len(self.images[self.split])

def __getitem__(self, index):

img_path = self.images[self.split][index].rstrip()
disp_path = self.disparities[self.split][index].rstrip()
#print(index)
try:
lbl_path = self.labels[self.split][index].rstrip()
_img = Image.open(img_path).convert('RGB')
_depth = Image.open(disp_path)
_target = Image.open(lbl_path)
sample = {'image': _img,'depth':_depth, 'label': _target}
except:
_img = Image.open(img_path).convert('RGB')
_depth = Image.open(disp_path)
sample = {'image': _img,'depth':_depth, 'label': _img}

if self.split == 'train':
return self.transform_tr(sample)
elif self.split == 'val':
return self.transform_val(sample), img_path
elif self.split == 'test':
return self.transform_ts(sample), img_path
elif self.split == 'custom_resize':
return self.transform_ts(sample), img_path


def recursive_glob(self, rootdir='.', suffix=''):
"""Performs recursive glob with given suffix and rootdir
:param rootdir is the root directory
:param suffix is the suffix to be searched
"""
return [os.path.join(looproot, filename)
for looproot, _, filenames in os.walk(rootdir)
for filename in filenames if filename.endswith(suffix)]

def transform_tr(self, sample):
composed_transforms = transforms.Compose([
tr.CropBlackArea(),
tr.RandomHorizontalFlip(),
tr.RandomScaleCrop(base_size=self.args.base_size, crop_size=self.args.crop_size, fill=255),
# tr.RandomGaussianBlur(),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

def transform_val(self, sample):

composed_transforms = transforms.Compose([
tr.CropBlackArea(),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

def transform_ts(self, sample):

composed_transforms = transforms.Compose([
tr.CropBlackArea(),
#tr.FixedResize(size=self.args.crop_size),
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

return composed_transforms(sample)

if __name__ == '__main__':
from dataloaders.utils import decode_segmap
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import argparse

parser = argparse.ArgumentParser()
args = parser.parse_args()
args.base_size = 513
args.crop_size = 513

cityscapes_train = CityscapesSegmentation(args, split='train')

dataloader = DataLoader(cityscapes_train, batch_size=2, shuffle=True, num_workers=2)

for ii, sample in enumerate(dataloader):
for jj in range(sample["image"].size()[0]):
img = sample['image'].numpy()
gt = sample['label'].numpy()
tmp = np.array(gt[jj]).astype(np.uint8)
segmap = decode_segmap(tmp, dataset='cityscapes')
img_tmp = np.transpose(img[jj], axes=[1, 2, 0])
img_tmp *= (0.229, 0.224, 0.225)
img_tmp += (0.485, 0.456, 0.406)
img_tmp *= 255.0
img_tmp = img_tmp.astype(np.uint8)
plt.figure()
plt.title('display')
plt.subplot(211)
plt.imshow(img_tmp)
plt.subplot(212)
plt.imshow(segmap)

if ii == 1:
break

plt.show(block=True)


+ 244
- 0
examples/lifelong_learning/RFNet/dataloaders/utils.py View File

@@ -0,0 +1,244 @@
import matplotlib.pyplot as plt
import numpy as np
import torch

def decode_seg_map_sequence(label_masks, dataset='pascal'):
rgb_masks = []
for label_mask in label_masks:
rgb_mask = decode_segmap(label_mask, dataset)
rgb_masks.append(rgb_mask)
rgb_masks = torch.from_numpy(np.array(rgb_masks).transpose([0, 3, 1, 2])) # change for val
return rgb_masks


def decode_segmap(label_mask, dataset, plot=False):
"""Decode segmentation class labels into a color image
Args:
label_mask (np.ndarray): an (M,N) array of integer values denoting
the class label at each spatial location.
plot (bool, optional): whether to show the resulting color image
in a figure.
Returns:
(np.ndarray, optional): the resulting decoded color image.
"""
if dataset == 'pascal' or dataset == 'coco':
n_classes = 21
label_colours = get_pascal_labels()
elif dataset == 'cityscapes':
n_classes = 24
label_colours = get_cityscapes_labels()
elif dataset == 'target':
n_classes = 24
label_colours = get_cityscapes_labels()
elif dataset == 'cityrand':
n_classes = 19
label_colours = get_cityscapes_labels()
elif dataset == 'citylostfound':
n_classes = 20
label_colours = get_citylostfound_labels()
elif dataset == 'xrlab':
n_classes = 25
label_colours = get_cityscapes_labels()
elif dataset == 'e1':
n_classes = 24
label_colours = get_cityscapes_labels()
elif dataset == 'mapillary':
n_classes = 24
label_colours = get_cityscapes_labels()
else:
raise NotImplementedError

r = label_mask.copy()
g = label_mask.copy()
b = label_mask.copy()
for ll in range(0, n_classes):
r[label_mask == ll] = label_colours[ll, 0]
g[label_mask == ll] = label_colours[ll, 1]
b[label_mask == ll] = label_colours[ll, 2]
rgb = np.zeros((label_mask.shape[0], label_mask.shape[1], 3)) # change for val
# rgb = torch.ByteTensor(3, label_mask.shape[0], label_mask.shape[1]).fill_(0)
rgb[:, :, 0] = r / 255.0
rgb[:, :, 1] = g / 255.0
rgb[:, :, 2] = b / 255.0
# r = torch.from_numpy(r)
# g = torch.from_numpy(g)
# b = torch.from_numpy(b)

rgb[:, :, 0] = r / 255.0
rgb[:, :, 1] = g / 255.0
rgb[:, :, 2] = b / 255.0
if plot:
plt.imshow(rgb)
plt.show()
else:
return rgb


def encode_segmap(mask):
"""Encode segmentation label images as pascal classes
Args:
mask (np.ndarray): raw segmentation label image of dimension
(M, N, 3), in which the Pascal classes are encoded as colours.
Returns:
(np.ndarray): class map with dimensions (M,N), where the value at
a given location is the integer denoting the class index.
"""
mask = mask.astype(int)
label_mask = np.zeros((mask.shape[0], mask.shape[1]), dtype=np.int16)
for ii, label in enumerate(get_pascal_labels()):
label_mask[np.where(np.all(mask == label, axis=-1))[:2]] = ii
label_mask = label_mask.astype(int)
return label_mask


def get_cityscapes_labels():
return np.array([
[128, 64, 128],
[244, 35, 232],
[70, 70, 70],
[102, 102, 156],
[190, 153, 153],
[153, 153, 153],
[250, 170, 30],
[220, 220, 0],
[107, 142, 35],
[152, 251, 152],
[0, 130, 180],
[220, 20, 60],
[255, 0, 0],
[0, 0, 142],
[0, 0, 70],
[0, 60, 100],
[0, 80, 100],
[0, 0, 230],
[119, 11, 32],
[119, 11, 119],
[128, 64, 64],
[102, 10, 156],
[102, 102, 15],
[10, 102, 156],
[10, 102, 156],
[10, 102, 156],
[10, 102, 156]])

def get_citylostfound_labels():
return np.array([
[128, 64, 128],
[244, 35, 232],
[70, 70, 70],
[102, 102, 156],
[190, 153, 153],
[153, 153, 153],
[250, 170, 30],
[220, 220, 0],
[107, 142, 35],
[152, 251, 152],
[0, 130, 180],
[220, 20, 60],
[255, 0, 0],
[0, 0, 142],
[0, 0, 70],
[0, 60, 100],
[0, 80, 100],
[0, 0, 230],
[119, 11, 32],
[111, 74, 0]])


def get_pascal_labels():
"""Load the mapping that associates pascal classes with label colors
Returns:
np.ndarray with dimensions (21, 3)
"""
return np.asarray([[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],
[0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],
[64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],
[64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],
[0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],
[0, 64, 128]])


def colormap_bdd(n):
cmap=np.zeros([n, 3]).astype(np.uint8)
cmap[0,:] = np.array([128, 64, 128])
cmap[1,:] = np.array([244, 35, 232])
cmap[2,:] = np.array([ 70, 70, 70])
cmap[3,:] = np.array([102, 102, 156])
cmap[4,:] = np.array([190, 153, 153])
cmap[5,:] = np.array([153, 153, 153])

cmap[6,:] = np.array([250, 170, 30])
cmap[7,:] = np.array([220, 220, 0])
cmap[8,:] = np.array([107, 142, 35])
cmap[9,:] = np.array([152, 251, 152])
cmap[10,:]= np.array([70, 130, 180])

cmap[11,:]= np.array([220, 20, 60])
cmap[12,:]= np.array([255, 0, 0])
cmap[13,:]= np.array([0, 0, 142])
cmap[14,:]= np.array([0, 0, 70])
cmap[15,:]= np.array([0, 60, 100])

cmap[16,:]= np.array([0, 80, 100])
cmap[17,:]= np.array([0, 0, 230])
cmap[18,:]= np.array([119, 11, 32])
cmap[19,:]= np.array([111, 74, 0]) #多加了一类small obstacle

return cmap

def colormap_bdd0(n):
cmap=np.zeros([n, 3]).astype(np.uint8)
cmap[0,:] = np.array([0, 0, 0])
cmap[1,:] = np.array([70, 130, 180])
cmap[2,:] = np.array([70, 70, 70])
cmap[3,:] = np.array([128, 64, 128])
cmap[4,:] = np.array([244, 35, 232])
cmap[5,:] = np.array([64, 64, 128])

cmap[6,:] = np.array([107, 142, 35])
cmap[7,:] = np.array([153, 153, 153])
cmap[8,:] = np.array([0, 0, 142])
cmap[9,:] = np.array([220, 220, 0])
cmap[10,:]= np.array([220, 20, 60])

cmap[11,:]= np.array([119, 11, 32])
cmap[12,:]= np.array([0, 0, 230])
cmap[13,:]= np.array([250, 170, 160])
cmap[14,:]= np.array([128, 64, 64])
cmap[15,:]= np.array([250, 170, 30])

cmap[16,:]= np.array([152, 251, 152])
cmap[17,:]= np.array([255, 0, 0])
cmap[18,:]= np.array([0, 0, 70])
cmap[19,:]= np.array([0, 60, 100]) #small obstacle
cmap[20,:]= np.array([0, 80, 100])
cmap[21,:]= np.array([102, 102, 156])
cmap[22,:]= np.array([102, 102, 156])

return cmap

class Colorize:

def __init__(self, n=24): # n = nClasses
# self.cmap = colormap(256)
self.cmap = colormap_bdd(256)
self.cmap[n] = self.cmap[-1]
self.cmap = torch.from_numpy(self.cmap[:n])

def __call__(self, gray_image):
size = gray_image.size()
# print(size)
color_images = torch.ByteTensor(size[0], 3, size[1], size[2]).fill_(0)
# color_image = torch.ByteTensor(3, size[0], size[1]).fill_(0)

# for label in range(1, len(self.cmap)):
for i in range(color_images.shape[0]):
for label in range(0, len(self.cmap)):
mask = gray_image[0] == label
# mask = gray_image == label

color_images[i][0][mask] = self.cmap[label][0]
color_images[i][1][mask] = self.cmap[label][1]
color_images[i][2][mask] = self.cmap[label][2]

return color_images

+ 246
- 0
examples/lifelong_learning/RFNet/eval.py View File

@@ -0,0 +1,246 @@
import argparse
import os
import numpy as np
from tqdm import tqdm
import time
import torch
from torchvision.transforms import ToPILImage
from PIL import Image

from dataloaders import make_data_loader
from dataloaders.utils import decode_seg_map_sequence, Colorize
from utils.metrics import Evaluator
from models.rfnet import RFNet
from models import rfnet_for_unseen
from models.resnet.resnet_single_scale_single_attention import *
from models.resnet import resnet_single_scale_single_attention_unseen
import torch.backends.cudnn as cudnn

class Validator(object):
def __init__(self, args, data=None, unseen_detection=False):
self.args = args
self.time_train = []
self.num_class = args.num_class

# Define Dataloader
kwargs = {'num_workers': args.workers, 'pin_memory': False}
# _, self.val_loader, _, self.custom_loader, self.num_class = make_data_loader(args, **kwargs)
_, _, self.test_loader, _ = make_data_loader(args, test_data=data, **kwargs)
print('un_classes:'+str(self.num_class))

# Define evaluator
self.evaluator = Evaluator(self.num_class)

# Define network
if unseen_detection:
self.resnet = resnet_single_scale_single_attention_unseen.\
resnet18(pretrained=False, efficient=False, use_bn=True)
self.model = rfnet_for_unseen.RFNet(self.resnet, num_classes=self.num_class, use_bn=True)
else:
self.resnet = resnet18(pretrained=False, efficient=False, use_bn=True)
self.model = RFNet(self.resnet, num_classes=self.num_class, use_bn=True)

if args.cuda:
self.model = torch.nn.DataParallel(self.model, device_ids=self.args.gpu_ids)
self.model = self.model.cuda()
cudnn.benchmark = True # accelarate speed
print('Model loaded successfully!')

# Load weights
assert os.path.exists(args.weight_path), 'weight-path:{} doesn\'t exit!'.format(args.weight_path)
self.new_state_dict = torch.load(args.weight_path, map_location=torch.device("cpu"))
self.model = load_my_state_dict(self.model, self.new_state_dict['state_dict'])

def validate(self):
self.model.eval()
self.evaluator.reset()
# tbar = tqdm(self.test_loader, desc='\r')
predictions = []
for sample, image_name in self.test_loader:
if self.args.depth:
image, depth, target = sample['image'], sample['depth'], sample['label']
else:
image, target = sample['image'], sample['label']
if self.args.cuda:
image = image.cuda()
if self.args.depth:
depth = depth.cuda()
with torch.no_grad():
if self.args.depth:
output = self.model(image, depth)
else:
output = self.model(image)
if self.args.cuda:
torch.cuda.synchronize()

pred = output.data.cpu().numpy()
# todo
pred = np.argmax(pred, axis=1)
predictions.append(pred)

if self.args.save_predicted_image != "true":
continue
pre_colors = Colorize()(torch.max(output, 1)[1].detach().cpu().byte())
pre_labels = torch.max(output, 1)[1].detach().cpu().byte()
print(pre_labels.shape)
# save
for i in range(pre_colors.shape[0]):
print(image_name[0])

if not image_name[0]:
img_name = "test.png"
else:
img_name = os.path.basename(image_name[0])

color_label_name = os.path.join(self.args.color_label_save_path, img_name)
label_name = os.path.join(self.args.label_save_path, img_name)
merge_label_name = os.path.join(self.args.merge_label_save_path, img_name)

os.makedirs(os.path.dirname(color_label_name), exist_ok=True)
os.makedirs(os.path.dirname(merge_label_name), exist_ok=True)
os.makedirs(os.path.dirname(label_name), exist_ok=True)

pre_color_image = ToPILImage()(pre_colors[i]) # pre_colors.dtype = float64
pre_color_image.save(color_label_name)

pre_label_image = ToPILImage()(pre_labels[i])
pre_label_image.save(label_name)

if (self.args.merge):
image_merge(image[i], pre_color_image, merge_label_name)
print('save image: {}'.format(merge_label_name))
return predictions
def task_divide(self):
seen_task_samples, unseen_task_samples = [], []
self.model.eval()
self.evaluator.reset()
tbar = tqdm(self.test_loader, desc='\r')
for i, (sample, image_name) in enumerate(tbar):

if self.args.depth:
image, depth, target = sample['image'], sample['depth'], sample['label']
else:
image, target = sample['image'], sample['label']
if self.args.cuda:
image = image.cuda()
if self.args.depth:
depth = depth.cuda()
start_time = time.time()
with torch.no_grad():
if self.args.depth:
output_, output, _ = self.model(image, depth)
else:
output_, output, _ = self.model(image)
if self.args.cuda:
torch.cuda.synchronize()
if i != 0:
fwt = time.time() - start_time
self.time_train.append(fwt)
print("Forward time per img (bath size=%d): %.3f (Mean: %.3f)" % (
self.args.val_batch_size, fwt / self.args.val_batch_size,
sum(self.time_train) / len(self.time_train) / self.args.val_batch_size))
time.sleep(0.1) # to avoid overheating the GPU too much

# pred colorize
pre_colors = Colorize()(torch.max(output, 1)[1].detach().cpu().byte())
pre_labels = torch.max(output, 1)[1].detach().cpu().byte()
for i in range(pre_colors.shape[0]):
task_sample = dict()
task_sample.update(image=sample["image"][i])
task_sample.update(label=sample["label"][i])
if self.args.depth:
task_sample.update(depth=sample["depth"][i])

if torch.max(pre_labels) == output.shape[1] - 1:
unseen_task_samples.append((task_sample, image_name[i]))
else:
seen_task_samples.append((task_sample, image_name[i]))

return seen_task_samples, unseen_task_samples
def image_merge(image, label, save_name):
image = ToPILImage()(image.detach().cpu().byte())
# width, height = image.size
left = 140
top = 30
right = 2030
bottom = 900
# crop
image = image.crop((left, top, right, bottom))
# resize
image = image.resize(label.size, Image.BILINEAR)

image = image.convert('RGBA')
label = label.convert('RGBA')
image = Image.blend(image, label, 0.6)
image.save(save_name)

def load_my_state_dict(model, state_dict): # custom function to load model when not all dict elements
own_state = model.state_dict()
for name, param in state_dict.items():
if name not in own_state:
print('{} not in model_state'.format(name))
continue
else:
own_state[name].copy_(param)

return model

def main():
parser = argparse.ArgumentParser(description="PyTorch RFNet validation")
parser.add_argument('--dataset', type=str, default='cityscapes',
choices=['citylostfound', 'cityscapes', 'xrlab', 'mapillary'],
help='dataset name (default: cityscapes)')
parser.add_argument('--workers', type=int, default=4,
metavar='N', help='dataloader threads')
parser.add_argument('--base-size', type=int, default=1024,
help='base image size')
parser.add_argument('--crop-size', type=int, default=768,
help='crop image size')
parser.add_argument('--batch-size', type=int, default=6,
help='batch size for training')
parser.add_argument('--val-batch-size', type=int, default=1,
metavar='N', help='input batch size for \
validating (default: auto)')
parser.add_argument('--test-batch-size', type=int, default=1,
metavar='N', help='input batch size for \
testing (default: auto)')
parser.add_argument('--no-cuda', action='store_true', default=
False, help='disables CUDA training')
parser.add_argument('--gpu-ids', type=str, default='0',
help='use which gpu to train, must be a \
comma-separated list of integers only (default=0)')
parser.add_argument('--checkname', type=str, default=None,
help='set the checkpoint name')
parser.add_argument('--weight-path', type=str, default=None,
help='enter your path of the weight')
parser.add_argument('--color-label-save-path', type=str, default='D:/m0063/project/RFNet-master/test/color/',
help='path to save label')
parser.add_argument('--merge-label-save-path', type=str, default='D:/m0063/project/RFNet-master/test/merge/',
help='path to save merged label')
parser.add_argument('--label-save-path', type=str, default='D:/m0063/project/RFNet-master/test/label/',
help='path to save merged label')
parser.add_argument('--merge', action='store_true', default=False, help='merge image and label')
parser.add_argument('--depth', action='store_true', default=False, help='add depth image or not')


args = parser.parse_args()
args.cuda = not args.no_cuda and torch.cuda.is_available()
if args.cuda:
try:
args.gpu_ids = [int(s) for s in args.gpu_ids.split(',')]
except ValueError:
raise ValueError('Argument --gpu_ids must be a comma-separated list of integers only')

validator = Validator(args)
validator.validate()


if __name__ == "__main__":
main()

+ 88
- 0
examples/lifelong_learning/RFNet/models/replicate.py View File

@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
# File : replicate.py
# Author : Jiayuan Mao
# Email : maojiayuan@gmail.com
# Date : 27/01/2018
#
# This file is part of Synchronized-BatchNorm-PyTorch.
# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch
# Distributed under MIT License.

import functools

from torch.nn.parallel.data_parallel import DataParallel

__all__ = [
'CallbackContext',
'execute_replication_callbacks',
'DataParallelWithCallback',
'patch_replication_callback'
]


class CallbackContext(object):
pass


def execute_replication_callbacks(modules):
"""
Execute an replication callback `__data_parallel_replicate__` on each module created by original replication.
The callback will be invoked with arguments `__data_parallel_replicate__(ctx, copy_id)`
Note that, as all modules are isomorphism, we assign each sub-module with a context
(shared among multiple copies of this module on different devices).
Through this context, different copies can share some information.
We guarantee that the callback on the master copy (the first copy) will be called ahead of calling the callback
of any slave copies.
"""
master_copy = modules[0]
nr_modules = len(list(master_copy.modules()))
ctxs = [CallbackContext() for _ in range(nr_modules)]

for i, module in enumerate(modules):
for j, m in enumerate(module.modules()):
if hasattr(m, '__data_parallel_replicate__'):
m.__data_parallel_replicate__(ctxs[j], i)


class DataParallelWithCallback(DataParallel):
"""
Data Parallel with a replication callback.
An replication callback `__data_parallel_replicate__` of each module will be invoked after being created by
original `replicate` function.
The callback will be invoked with arguments `__data_parallel_replicate__(ctx, copy_id)`
Examples:
> sync_bn = SynchronizedBatchNorm1d(10, eps=1e-5, affine=False)
> sync_bn = DataParallelWithCallback(sync_bn, device_ids=[0, 1])
# sync_bn.__data_parallel_replicate__ will be invoked.
"""

def replicate(self, module, device_ids):
modules = super(DataParallelWithCallback, self).replicate(module, device_ids)
execute_replication_callbacks(modules)
return modules


def patch_replication_callback(data_parallel):
"""
Monkey-patch an existing `DataParallel` object. Add the replication callback.
Useful when you have customized `DataParallel` implementation.
Examples:
> sync_bn = SynchronizedBatchNorm1d(10, eps=1e-5, affine=False)
> sync_bn = DataParallel(sync_bn, device_ids=[0, 1])
> patch_replication_callback(sync_bn)
# this is equivalent to
> sync_bn = SynchronizedBatchNorm1d(10, eps=1e-5, affine=False)
> sync_bn = DataParallelWithCallback(sync_bn, device_ids=[0, 1])
"""

assert isinstance(data_parallel, DataParallel)

old_replicate = data_parallel.replicate

@functools.wraps(old_replicate)
def new_replicate(module, device_ids):
modules = old_replicate(module, device_ids)
execute_replication_callbacks(modules)
return modules

data_parallel.replicate = new_replicate

+ 0
- 0
examples/lifelong_learning/RFNet/models/resnet/__init__.py View File


+ 391
- 0
examples/lifelong_learning/RFNet/models/resnet/resnet_single_scale_single_attention.py View File

@@ -0,0 +1,391 @@
import torch
import torch.nn as nn
import torch.utils.model_zoo as model_zoo
from itertools import chain
import torch.utils.checkpoint as cp

from ..util import _Upsample, SpatialPyramidPooling

__all__ = ['ResNet', 'resnet18']

model_urls = {
'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
}


def conv3x3(in_planes, out_planes, stride=1):
"""3x3 convolution with padding"""
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=1, bias=False)

def conv1x1(in_planes, out_planes, stride=1):
"""3x3 convolution with padding"""
return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride,
padding=0, bias=False)

def _bn_function_factory(conv, norm, relu=None):
"""return a conv-bn-relu function"""
def bn_function(x):
x = conv(x)
if norm is not None:
x = norm(x)
if relu is not None:
x = relu(x)
return x

return bn_function


def do_efficient_fwd(block, x, efficient):
if efficient and x.requires_grad:
return cp.checkpoint(block, x)
else:
return block(x)


def channel_shuffle(x, groups):
batchsize, num_channels, height, width = x.data.size()
channels_per_group = num_channels // groups

# reshape
x = x.view(batchsize, groups,
channels_per_group, height, width)

x = torch.transpose(x, 1, 2).contiguous()

# flatten
x = x.view(batchsize, -1, height, width)

return x

class BasicBlock(nn.Module):
expansion = 1

def __init__(self, inplanes, planes, stride=1, downsample=None, efficient=False, use_bn=True):
super(BasicBlock, self).__init__()
self.use_bn = use_bn
self.conv1 = conv3x3(inplanes, planes, stride)
self.bn1 = nn.BatchNorm2d(planes) if self.use_bn else None
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(planes, planes)
self.bn2 = nn.BatchNorm2d(planes) if self.use_bn else None
self.downsample = downsample
self.stride = stride
self.efficient = efficient

def forward(self, x):
residual = x

bn_1 = _bn_function_factory(self.conv1, self.bn1, self.relu)
bn_2 = _bn_function_factory(self.conv2, self.bn2)

out = do_efficient_fwd(bn_1, x, self.efficient)
out = do_efficient_fwd(bn_2, out, self.efficient)

if self.downsample is not None:
residual = self.downsample(x)

out = out + residual
relu = self.relu(out)

return relu, out


class Bottleneck(nn.Module):
expansion = 4

def __init__(self, inplanes, planes, stride=1, downsample=None, efficient=True, use_bn=True):
super(Bottleneck, self).__init__()
self.use_bn = use_bn
self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
self.bn1 = nn.BatchNorm2d(planes) if self.use_bn else None
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(planes) if self.use_bn else None
self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(planes * self.expansion) if self.use_bn else None
self.relu = nn.ReLU(inplace=True)
self.downsample = downsample
self.stride = stride
self.efficient = efficient

def forward(self, x):
residual = x

bn_1 = _bn_function_factory(self.conv1, self.bn1, self.relu)
bn_2 = _bn_function_factory(self.conv2, self.bn2, self.relu)
bn_3 = _bn_function_factory(self.conv3, self.bn3, self.relu)

out = do_efficient_fwd(bn_1, x, self.efficient)
out = do_efficient_fwd(bn_2, out, self.efficient)
out = do_efficient_fwd(bn_3, out, self.efficient)

if self.downsample is not None:
residual = self.downsample(x)

out = out + residual
relu = self.relu(out)

return relu, out


class ResNet(nn.Module):
def __init__(self, block, layers, *, num_features=128, k_up=3, efficient=True, use_bn=True,
spp_grids=(8, 4, 2, 1), spp_square_grid=False, **kwargs):
super(ResNet, self).__init__()
self.inplanes = 64
self.efficient = efficient
self.use_bn = use_bn

# rgb branch
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
self.bn1 = nn.BatchNorm2d(64) if self.use_bn else lambda x: x
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)

# depth branch
self.conv1_d = nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3,bias=False)
self.bn1_d = nn.BatchNorm2d(64) if self.use_bn else lambda x: x
self.relu_d = nn.ReLU(inplace=True)
self.maxpool_d = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)

upsamples = []
# 修改 _make_layer_rgb _make_layer
self.layer1 = self._make_layer_rgb(block, 64, 64, layers[0])
self.layer1_d = self._make_layer_d(block, 64, 64, layers[0])
self.attention_1 = self.attention(64)
self.attention_1_d = self.attention(64)
upsamples += [_Upsample(num_features, self.inplanes, num_features, use_bn=self.use_bn, k=k_up)] # num_maps_in, skip_maps_in, num_maps_out, k: kernel size of blend conv

self.layer2 = self._make_layer_rgb(block, 64, 128, layers[1], stride=2)
self.layer2_d = self._make_layer_d(block, 64, 128, layers[1], stride=2)
self.attention_2 = self.attention(128)
self.attention_2_d = self.attention(128)
upsamples += [_Upsample(num_features, self.inplanes, num_features, use_bn=self.use_bn, k=k_up)]

self.layer3 = self._make_layer_rgb(block, 128, 256, layers[2], stride=2)
self.layer3_d = self._make_layer_d(block, 128, 256, layers[2], stride=2)
self.attention_3 = self.attention(256)
self.attention_3_d = self.attention(256)
upsamples += [_Upsample(num_features, self.inplanes, num_features, use_bn=self.use_bn, k=k_up)]

self.layer4 = self._make_layer_rgb(block, 256, 512, layers[3], stride=2)
self.layer4_d = self._make_layer_d(block, 256, 512, layers[3], stride=2)
self.attention_4 = self.attention(512)
self.attention_4_d = self.attention(512)

self.fine_tune = [self.conv1, self.maxpool, self.layer1, self.layer2, self.layer3, self.layer4,
self.conv1_d, self.maxpool_d, self.layer1_d, self.layer2_d, self.layer3_d, self.layer4_d]
if self.use_bn:
self.fine_tune += [self.bn1, self.bn1_d, self.attention_1, self.attention_1_d, self.attention_2, self.attention_2_d,
self.attention_3, self.attention_3_d, self.attention_4, self.attention_4_d]

num_levels = 3
self.spp_size = num_features
bt_size = self.spp_size

level_size = self.spp_size // num_levels

self.spp = SpatialPyramidPooling(self.inplanes, num_levels, bt_size=bt_size, level_size=level_size,
out_size=self.spp_size, grids=spp_grids, square_grid=spp_square_grid,
bn_momentum=0.01 / 2, use_bn=self.use_bn)
self.upsample = nn.ModuleList(list(reversed(upsamples)))

self.random_init = []#[ self.spp, self.upsample]
self.fine_tune += [self.spp, self.upsample]

self.num_features = num_features

for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
elif isinstance(m, nn.BatchNorm2d):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)

def _make_layer_rgb(self, block, inplanes, planes, blocks, stride=1):
downsample = None
if stride != 1 or inplanes != planes * block.expansion:
layers = [nn.Conv2d(inplanes, planes * block.expansion, kernel_size=1, stride=stride, bias=False)]
if self.use_bn:
layers += [nn.BatchNorm2d(planes * block.expansion)]
downsample = nn.Sequential(*layers)
layers = [block(inplanes, planes, stride, downsample, efficient=self.efficient, use_bn=self.use_bn)]
inplanes = planes * block.expansion
for i in range(1, blocks):
layers += [block(inplanes, planes, efficient=self.efficient, use_bn=self.use_bn)]

return nn.Sequential(*layers)

def _make_layer_d(self, block, inplanes, planes, blocks, stride=1):
downsample = None
if stride != 1 or inplanes != planes * block.expansion:
layers = [nn.Conv2d(inplanes, planes * block.expansion, kernel_size=1, stride=stride, bias=False)]
if self.use_bn:
layers += [nn.BatchNorm2d(planes * block.expansion)]
downsample = nn.Sequential(*layers)
layers = [block(inplanes, planes, stride, downsample, efficient=self.efficient, use_bn=self.use_bn)]
inplanes = planes * block.expansion
self.inplanes = inplanes
for i in range(1, blocks):
layers += [block(inplanes, planes, efficient=self.efficient, use_bn=self.use_bn)]

return nn.Sequential(*layers)

def channel_attention(self, rgb_skip, depth_skip, attention):
assert rgb_skip.shape == depth_skip.shape, 'rgb skip shape:{} != depth skip shape:{}'.format(rgb_skip.shape, depth_skip.shape)
# single_attenton
rgb_attention = attention(rgb_skip)
depth_attention = attention(depth_skip)
rgb_after_attention = torch.mul(rgb_skip, rgb_attention)
depth_after_attention = torch.mul(depth_skip, depth_attention)
skip_after_attention = rgb_after_attention + depth_after_attention
return skip_after_attention

def attention(self, num_channels):
pool_attention = nn.AdaptiveAvgPool2d(1)
conv_attention = nn.Conv2d(num_channels, num_channels, kernel_size=1)
activate = nn.Sigmoid()

return nn.Sequential(pool_attention, conv_attention, activate)


def random_init_params(self):
return chain(*[f.parameters() for f in self.random_init])

def fine_tune_params(self):
return chain(*[f.parameters() for f in self.fine_tune])

def forward_resblock(self, x, layers):
skip = None
for l in layers:
x = l(x)
if isinstance(x, tuple):
x, skip = x
return x, skip

def forward_down(self, rgb):
x = self.conv1(rgb)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)

features = []
x, skip = self.forward_resblock(x, self.layer1)
features += [skip]
x, skip = self.forward_resblock(x, self.layer2)
features += [skip]
x, skip = self.forward_resblock(x, self.layer3)
features += [skip]
x, skip = self.forward_resblock(x.detach(), self.layer4)
features += [self.spp.forward(skip)]
return features

def forward_down_fusion(self, rgb, depth):
x = self.conv1(rgb)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)

depth = depth.unsqueeze(1)
y = self.conv1_d(depth)
y = self.bn1_d(y)
y = self.relu_d(y)
y = self.maxpool_d(y)

features = []
# block 1
x, skip_rgb = self.forward_resblock(x.detach(), self.layer1)
y, skip_depth = self.forward_resblock(y.detach(), self.layer1_d)
x_attention = self.attention_1(x)
y_attention = self.attention_1_d(y)
x = torch.mul(x, x_attention)
y = torch.mul(y, y_attention)
x = x + y
features += [skip_rgb.detach()]
# block 2
x, skip_rgb = self.forward_resblock(x.detach(), self.layer2)
y, skip_depth = self.forward_resblock(y.detach(), self.layer2_d)
x_attention = self.attention_2(x)
y_attention = self.attention_2_d(y)
x = torch.mul(x, x_attention)
y = torch.mul(y, y_attention)
x = x + y
features += [skip_rgb.detach()]
# block 3
x, skip_rgb = self.forward_resblock(x.detach(), self.layer3)
y, skip_depth = self.forward_resblock(y.detach(), self.layer3_d)
x_attention = self.attention_3(x)
y_attention = self.attention_3_d(y)
x = torch.mul(x, x_attention)
y = torch.mul(y, y_attention)
x = x + y
features += [skip_rgb.detach()]
# block 4
x, skip_rgb = self.forward_resblock(x.detach(), self.layer4)
y, skip_depth = self.forward_resblock(y.detach(), self.layer4_d)
x_attention = self.attention_4(x)
y_attention = self.attention_4_d(y)
x = torch.mul(x, x_attention)
y = torch.mul(y, y_attention)
x = x + y
features += [self.spp.forward(x)]
return features


def forward_up(self, features):
features = features[::-1]

x = features[0]

upsamples = []
i = 0
for skip, up in zip(features[1:], self.upsample):
i += 1
#print(len(self.upsample))
if i < len(self.upsample):
x = up(x, skip)
else:
x = up(x, skip)
upsamples += [x]
return x, {'features': features, 'upsamples': upsamples}

def forward(self, rgb, depth = None):
if depth is None:
return self.forward_up(self.forward_down(rgb))
else:
return self.forward_up(self.forward_down_fusion(rgb, depth))

def _load_resnet_pretrained(self, url):
pretrain_dict = model_zoo.load_url(model_urls[url])
model_dict = {}
state_dict = self.state_dict()
for k, v in pretrain_dict.items():
# print('%%%%% ', k)
if k in state_dict:
if k.startswith('conv1'):
model_dict[k] = v
# print('##### ', k)
model_dict[k.replace('conv1', 'conv1_d')] = torch.mean(v, 1).data. \
view_as(state_dict[k.replace('conv1', 'conv1_d')])

elif k.startswith('bn1'):
model_dict[k] = v
model_dict[k.replace('bn1', 'bn1_d')] = v
elif k.startswith('layer'):
model_dict[k] = v
model_dict[k[:6]+'_d'+k[6:]] = v
state_dict.update(model_dict)
self.load_state_dict(state_dict)


def resnet18(pretrained=True, **kwargs):
"""Constructs a ResNet-18 model.
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
"""
model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
if pretrained:
model.load_state_dict(model_zoo.load_url(model_urls['resnet18']), strict=False)
print('pretrained dict loaded sucessfully')
return model

+ 396
- 0
examples/lifelong_learning/RFNet/models/resnet/resnet_single_scale_single_attention_unseen.py View File

@@ -0,0 +1,396 @@
import torch
import torch.nn as nn
import torch.utils.model_zoo as model_zoo
from itertools import chain
import torch.utils.checkpoint as cp
import cv2
import numpy as np

from ..util import _Upsample, SpatialPyramidPooling

__all__ = ['ResNet', 'resnet18']

model_urls = {
'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
}


def conv3x3(in_planes, out_planes, stride=1):
"""3x3 convolution with padding"""
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=1, bias=False)

def conv1x1(in_planes, out_planes, stride=1):
"""3x3 convolution with padding"""
return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride,
padding=0, bias=False)

def _bn_function_factory(conv, norm, relu=None):
"""return a conv-bn-relu function"""
def bn_function(x):
x = conv(x)
if norm is not None:
x = norm(x)
if relu is not None:
x = relu(x)
return x

return bn_function


def do_efficient_fwd(block, x, efficient):
if efficient and x.requires_grad:
return cp.checkpoint(block, x)
else:
return block(x)


def channel_shuffle(x, groups):
batchsize, num_channels, height, width = x.data.size()
channels_per_group = num_channels // groups

# reshape
x = x.view(batchsize, groups,
channels_per_group, height, width)

x = torch.transpose(x, 1, 2).contiguous()

# flatten
x = x.view(batchsize, -1, height, width)

return x

class BasicBlock(nn.Module):
expansion = 1

def __init__(self, inplanes, planes, stride=1, downsample=None, efficient=False, use_bn=True):
super(BasicBlock, self).__init__()
self.use_bn = use_bn
self.conv1 = conv3x3(inplanes, planes, stride)
self.bn1 = nn.BatchNorm2d(planes) if self.use_bn else None
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(planes, planes)
self.bn2 = nn.BatchNorm2d(planes) if self.use_bn else None
self.downsample = downsample
self.stride = stride
self.efficient = efficient

def forward(self, x):
residual = x

bn_1 = _bn_function_factory(self.conv1, self.bn1, self.relu)
bn_2 = _bn_function_factory(self.conv2, self.bn2)

out = do_efficient_fwd(bn_1, x, self.efficient)
out = do_efficient_fwd(bn_2, out, self.efficient)

if self.downsample is not None:
residual = self.downsample(x)

out = out + residual
relu = self.relu(out)

return relu, out


class Bottleneck(nn.Module):
expansion = 4

def __init__(self, inplanes, planes, stride=1, downsample=None, efficient=True, use_bn=True):
super(Bottleneck, self).__init__()
self.use_bn = use_bn
self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
self.bn1 = nn.BatchNorm2d(planes) if self.use_bn else None
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(planes) if self.use_bn else None
self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(planes * self.expansion) if self.use_bn else None
self.relu = nn.ReLU(inplace=True)
self.downsample = downsample
self.stride = stride
self.efficient = efficient

def forward(self, x):
residual = x

bn_1 = _bn_function_factory(self.conv1, self.bn1, self.relu)
bn_2 = _bn_function_factory(self.conv2, self.bn2, self.relu)
bn_3 = _bn_function_factory(self.conv3, self.bn3, self.relu)

out = do_efficient_fwd(bn_1, x, self.efficient)
out = do_efficient_fwd(bn_2, out, self.efficient)
out = do_efficient_fwd(bn_3, out, self.efficient)

if self.downsample is not None:
residual = self.downsample(x)

out = out + residual
relu = self.relu(out)

return relu, out


class ResNet(nn.Module):
def __init__(self, block, layers, *, num_features=128, k_up=3, efficient=True, use_bn=True,
spp_grids=(8, 4, 2, 1), spp_square_grid=False, **kwargs):
super(ResNet, self).__init__()
self.inplanes = 64
self.efficient = efficient
self.use_bn = use_bn

# rgb branch
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
self.bn1 = nn.BatchNorm2d(64) if self.use_bn else lambda x: x
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)

# depth branch
self.conv1_d = nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3,bias=False)
self.bn1_d = nn.BatchNorm2d(64) if self.use_bn else lambda x: x
self.relu_d = nn.ReLU(inplace=True)
self.maxpool_d = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)

upsamples = []
# 修改 _make_layer_rgb _make_layer
self.layer1 = self._make_layer_rgb(block, 64, 64, layers[0])
self.layer1_d = self._make_layer_d(block, 64, 64, layers[0])
self.attention_1 = self.attention(64)
self.attention_1_d = self.attention(64)
upsamples += [_Upsample(num_features, self.inplanes, num_features, use_bn=self.use_bn, k=k_up)] # num_maps_in, skip_maps_in, num_maps_out, k: kernel size of blend conv

self.layer2 = self._make_layer_rgb(block, 64, 128, layers[1], stride=2)
self.layer2_d = self._make_layer_d(block, 64, 128, layers[1], stride=2)
self.attention_2 = self.attention(128)
self.attention_2_d = self.attention(128)
upsamples += [_Upsample(num_features, self.inplanes, num_features, use_bn=self.use_bn, k=k_up)]

self.layer3 = self._make_layer_rgb(block, 128, 256, layers[2], stride=2)
self.layer3_d = self._make_layer_d(block, 128, 256, layers[2], stride=2)
self.attention_3 = self.attention(256)
self.attention_3_d = self.attention(256)
upsamples += [_Upsample(num_features, self.inplanes, num_features, use_bn=self.use_bn, k=k_up)]

self.layer4 = self._make_layer_rgb(block, 256, 512, layers[3], stride=2)
self.layer4_d = self._make_layer_d(block, 256, 512, layers[3], stride=2)
self.attention_4 = self.attention(512)
self.attention_4_d = self.attention(512)

self.fine_tune = [self.conv1, self.maxpool, self.layer1, self.layer2, self.layer3, self.layer4,
self.conv1_d, self.maxpool_d, self.layer1_d, self.layer2_d, self.layer3_d, self.layer4_d]
if self.use_bn:
self.fine_tune += [self.bn1, self.bn1_d, self.attention_1, self.attention_1_d, self.attention_2, self.attention_2_d,
self.attention_3, self.attention_3_d, self.attention_4, self.attention_4_d]

num_levels = 3
self.spp_size = num_features
bt_size = self.spp_size

level_size = self.spp_size // num_levels

self.spp = SpatialPyramidPooling(self.inplanes, num_levels, bt_size=bt_size, level_size=level_size,
out_size=self.spp_size, grids=spp_grids, square_grid=spp_square_grid,
bn_momentum=0.01 / 2, use_bn=self.use_bn)
self.upsample = nn.ModuleList(list(reversed(upsamples)))

self.random_init = [ self.spp, self.upsample]

self.num_features = num_features

for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
elif isinstance(m, nn.BatchNorm2d):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)

def output_num(self):
return self.__in_features

def _make_layer_rgb(self, block, inplanes, planes, blocks, stride=1):
downsample = None
if stride != 1 or inplanes != planes * block.expansion:
layers = [nn.Conv2d(inplanes, planes * block.expansion, kernel_size=1, stride=stride, bias=False)]
if self.use_bn:
layers += [nn.BatchNorm2d(planes * block.expansion)]
downsample = nn.Sequential(*layers)
layers = [block(inplanes, planes, stride, downsample, efficient=self.efficient, use_bn=self.use_bn)]
inplanes = planes * block.expansion
for i in range(1, blocks):
layers += [block(inplanes, planes, efficient=self.efficient, use_bn=self.use_bn)]

return nn.Sequential(*layers)

def _make_layer_d(self, block, inplanes, planes, blocks, stride=1):
downsample = None
if stride != 1 or inplanes != planes * block.expansion:
layers = [nn.Conv2d(inplanes, planes * block.expansion, kernel_size=1, stride=stride, bias=False)]
if self.use_bn:
layers += [nn.BatchNorm2d(planes * block.expansion)]
downsample = nn.Sequential(*layers)
layers = [block(inplanes, planes, stride, downsample, efficient=self.efficient, use_bn=self.use_bn)]
inplanes = planes * block.expansion
self.inplanes = inplanes
for i in range(1, blocks):
layers += [block(inplanes, planes, efficient=self.efficient, use_bn=self.use_bn)]

return nn.Sequential(*layers)

def channel_attention(self, rgb_skip, depth_skip, attention):
assert rgb_skip.shape == depth_skip.shape, 'rgb skip shape:{} != depth skip shape:{}'.format(rgb_skip.shape, depth_skip.shape)
# single_attenton
rgb_attention = attention(rgb_skip)
depth_attention = attention(depth_skip)
rgb_after_attention = torch.mul(rgb_skip, rgb_attention)
depth_after_attention = torch.mul(depth_skip, depth_attention)
skip_after_attention = rgb_after_attention + depth_after_attention
return skip_after_attention

def attention(self, num_channels):
pool_attention = nn.AdaptiveAvgPool2d(1)
conv_attention = nn.Conv2d(num_channels, num_channels, kernel_size=1)
activate = nn.Sigmoid()

return nn.Sequential(pool_attention, conv_attention, activate)


def random_init_params(self):
return chain(*[f.parameters() for f in self.random_init])

def fine_tune_params(self):
return chain(*[f.parameters() for f in self.fine_tune])

def forward_resblock(self, x, layers):
skip = None
for l in layers:
x = l(x)
if isinstance(x, tuple):
x, skip = x
return x, skip

def forward_down(self, rgb):
x = self.conv1(rgb)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)

features = []
x, skip = self.forward_resblock(x, self.layer1)
features += [skip]
x, skip = self.forward_resblock(x, self.layer2)
features += [skip]
x, skip = self.forward_resblock(x, self.layer3)
features += [skip]
x, skip = self.forward_resblock(x, self.layer4)
features += [self.spp.forward(skip)]
features_da = self.spp.forward(skip)
return features, features_da

def forward_down_fusion(self, rgb, depth):
x = self.conv1(rgb)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)

depth = depth.unsqueeze(1)
y = self.conv1_d(depth)
y = self.bn1_d(y)
y = self.relu_d(y)
y = self.maxpool_d(y)

features = []
# block 1
x, skip_rgb = self.forward_resblock(x, self.layer1)
y, skip_depth = self.forward_resblock(y, self.layer1_d)
x_attention = self.attention_1(x)
y_attention = self.attention_1_d(y)
x = torch.mul(x, x_attention)
y = torch.mul(y, y_attention)
x = x + y
features += [skip_rgb]
# block 2
x, skip_rgb = self.forward_resblock(x, self.layer2)
y, skip_depth = self.forward_resblock(y, self.layer2_d)
x_attention = self.attention_2(x)
y_attention = self.attention_2_d(y)
x = torch.mul(x, x_attention)
y = torch.mul(y, y_attention)
x = x + y
features += [skip_rgb]
# block 3
x, skip_rgb = self.forward_resblock(x, self.layer3)
y, skip_depth = self.forward_resblock(y, self.layer3_d)
x_attention = self.attention_3(x)
y_attention = self.attention_3_d(y)
x = torch.mul(x, x_attention)
y = torch.mul(y, y_attention)
x = x + y
features += [skip_rgb]
# block 4
x, skip_rgb = self.forward_resblock(x, self.layer4)
y, skip_depth = self.forward_resblock(y, self.layer4_d)
x_attention = self.attention_4(x)
y_attention = self.attention_4_d(y)
x = torch.mul(x, x_attention)
y = torch.mul(y, y_attention)
x = x + y
features += [self.spp.forward(x)]
features_da = self.spp.forward(x)
return features, features_da


def forward_up(self, features):
features = features[::-1]

x = features[0]

upsamples = []
for skip, up in zip(features[1:], self.upsample):
x = up(x, skip)
upsamples += [x]
return x, {'features': features, 'upsamples': upsamples}

def forward(self, rgb, depth = None):
if depth is None:
down_features, da_features = self.forward_down(rgb)
x, additional = self.forward_up(down_features)
return x, additional, da_features#self.forward_up(self.forward_down(rgb)), self.forward_down(rgb)
else:
down_features, da_features = self.forward_down_fusion(rgb, depth)
x, additional = self.forward_up(down_features)
#print(down_features.shape)
return x, additional, da_features#self.forward_up(self.forward_down_fusion(rgb, depth)), self.forward_down_fusion(rgb, depth)

def _load_resnet_pretrained(self, url):
pretrain_dict = model_zoo.load_url(model_urls[url])
model_dict = {}
state_dict = self.state_dict()
for k, v in pretrain_dict.items():
# print('%%%%% ', k)
if k in state_dict:
if k.startswith('conv1'):
model_dict[k] = v
# print('##### ', k)
model_dict[k.replace('conv1', 'conv1_d')] = torch.mean(v, 1).data. \
view_as(state_dict[k.replace('conv1', 'conv1_d')])

elif k.startswith('bn1'):
model_dict[k] = v
model_dict[k.replace('bn1', 'bn1_d')] = v
elif k.startswith('layer'):
model_dict[k] = v
model_dict[k[:6]+'_d'+k[6:]] = v
state_dict.update(model_dict)
self.load_state_dict(state_dict)


def resnet18(pretrained=True, **kwargs):
"""Constructs a ResNet-18 model.
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
"""
model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
if pretrained:
model.load_state_dict(model_zoo.load_url(model_urls['resnet18']), strict=False)
print('pretrained dict loaded sucessfully')
return model

+ 27
- 0
examples/lifelong_learning/RFNet/models/rfnet.py View File

@@ -0,0 +1,27 @@
import torch.nn as nn
from itertools import chain # 串联多个迭代对象

from .util import _BNReluConv, upsample


class RFNet(nn.Module):
def __init__(self, backbone, num_classes, use_bn=True):
super(RFNet, self).__init__()
self.backbone = backbone
self.num_classes = num_classes
print(self.backbone.num_features)
self.logits = _BNReluConv(self.backbone.num_features, self.num_classes, batch_norm=use_bn)

def forward(self, rgb_inputs, depth_inputs = None):
x, additional = self.backbone(rgb_inputs, depth_inputs)
logits = self.logits.forward(x)
logits_upsample = upsample(logits, rgb_inputs.shape[2:])
#print(logits_upsample.size)
return logits_upsample


def random_init_params(self):
return chain(*([self.logits.parameters(), self.backbone.random_init_params()]))

def fine_tune_params(self):
return self.backbone.fine_tune_params()

+ 33
- 0
examples/lifelong_learning/RFNet/models/rfnet_for_unseen.py View File

@@ -0,0 +1,33 @@
import torch.nn as nn
from itertools import chain # 串联多个迭代对象

from .util import _BNReluConv, upsample


class RFNet(nn.Module):
def __init__(self, backbone, num_classes, use_bn=True):
super(RFNet, self).__init__()
self.backbone = backbone
self.num_classes = num_classes
#self.bottleneck = _BNReluConv(self.backbone.num_features, 128, k = 3, batch_norm=use_bn)
#self.logits = _BNReluConv(128, self.num_classes+1, k = 1, batch_norm=use_bn)
self.logits = _BNReluConv(self.backbone.num_features, self.num_classes, batch_norm=use_bn)
#self.logits_target = _BNReluConv(self.backbone.num_features, self.num_classes, batch_norm=use_bn)
self.logits_aux = _BNReluConv(self.backbone.num_features, self.num_classes, batch_norm=use_bn)

def forward(self, rgb_inputs, depth_inputs = None):
x, additional, da_features = self.backbone(rgb_inputs, depth_inputs)
#print(additional['features'][0].shape)
#bottleneck = self.bottleneck(x)
logits = self.logits.forward(x)
logits_aux = self.logits_aux.forward(x)
#print(logits_aux.shape)
logits_upsample = upsample(logits, rgb_inputs.shape[2:])
logits_aux_upsample = upsample(logits_aux, rgb_inputs.shape[2:])
return logits_upsample, logits_aux_upsample, da_features

def random_init_params(self):
return chain(*([self.logits.parameters(), self.logits_aux.parameters(), self.backbone.random_init_params()]))

def fine_tune_params(self):
return self.backbone.fine_tune_params()

+ 99
- 0
examples/lifelong_learning/RFNet/models/util.py View File

@@ -0,0 +1,99 @@
import torch
import torch.nn as nn
import torch.nn.functional as F

upsample = lambda x, size: F.interpolate(x, size, mode='bilinear', align_corners=False)
batchnorm_momentum = 0.01 / 2


def get_n_params(parameters):
pp = 0
for p in parameters:
nn = 1
for s in list(p.size()):
nn = nn * s
pp += nn
return pp


class _BNReluConv(nn.Sequential):
def __init__(self, num_maps_in, num_maps_out, k=3, batch_norm=True, bn_momentum=0.1, bias=False, dilation=1):
super(_BNReluConv, self).__init__()
if batch_norm:
self.add_module('norm', nn.BatchNorm2d(num_maps_in, momentum=bn_momentum))
self.add_module('relu', nn.ReLU(inplace=batch_norm is True))
padding = k // 2 # same conv
self.add_module('conv', nn.Conv2d(num_maps_in, num_maps_out,
kernel_size=k, padding=padding, bias=bias, dilation=dilation))


class _Upsample(nn.Module):
def __init__(self, num_maps_in, skip_maps_in, num_maps_out, use_bn=True, k=3):
super(_Upsample, self).__init__()
print(f'Upsample layer: in = {num_maps_in}, skip = {skip_maps_in}, out = {num_maps_out}')
self.bottleneck = _BNReluConv(skip_maps_in, num_maps_in, k=1, batch_norm=use_bn)
self.blend_conv = _BNReluConv(num_maps_in, num_maps_out, k=k, batch_norm=use_bn)

def forward(self, x, skip):
skip = self.bottleneck.forward(skip)
skip_size = skip.size()[2:4]
x = upsample(x, skip_size)
x = x + skip
x = self.blend_conv.forward(x)
return x


class SpatialPyramidPooling(nn.Module):
def __init__(self, num_maps_in, num_levels, bt_size=512, level_size=128, out_size=128,
grids=(6, 3, 2, 1), square_grid=False, bn_momentum=0.1, use_bn=True):
super(SpatialPyramidPooling, self).__init__()
self.grids = grids
self.square_grid = square_grid
self.spp = nn.Sequential()
self.spp.add_module('spp_bn',
_BNReluConv(num_maps_in, bt_size, k=1, bn_momentum=bn_momentum, batch_norm=use_bn))
num_features = bt_size
final_size = num_features
for i in range(num_levels):
final_size += level_size
self.spp.add_module('spp' + str(i),
_BNReluConv(num_features, level_size, k=1, bn_momentum=bn_momentum, batch_norm=use_bn))
self.spp.add_module('spp_fuse',
_BNReluConv(final_size, out_size, k=1, bn_momentum=bn_momentum, batch_norm=use_bn))

def forward(self, x):
levels = []
target_size = x.size()[2:4]

ar = target_size[1] / target_size[0]

x = self.spp[0].forward(x)
levels.append(x)
num = len(self.spp) - 1

for i in range(1, num):
if not self.square_grid:
grid_size = (self.grids[i - 1], max(1, round(ar * self.grids[i - 1])))
x_pooled = F.adaptive_avg_pool2d(x, grid_size)
else:
x_pooled = F.adaptive_avg_pool2d(x, self.grids[i - 1])
level = self.spp[i].forward(x_pooled)

level = upsample(level, target_size)
levels.append(level)
x = torch.cat(levels, 1)
x = self.spp[-1].forward(x)
return x


class _UpsampleBlend(nn.Module):
def __init__(self, num_features, use_bn=True):
super(_UpsampleBlend, self).__init__()
self.blend_conv = _BNReluConv(num_features, num_features, k=3, batch_norm=use_bn)

def forward(self, x, skip):
skip_size = skip.size()[2:4]
x = upsample(x, skip_size)
x = x + skip
x = self.blend_conv.forward(x)
return x

+ 20
- 0
examples/lifelong_learning/RFNet/mypath.py View File

@@ -0,0 +1,20 @@
class Path(object):
@staticmethod
def db_root_dir(dataset):
if dataset == 'cityscapes':
return '/home/robo/m0063/project/RFNet-master/Data/cityscapes/' # folder that contains leftImg8bit/
elif dataset == 'citylostfound':
return '/home/robo/m0063/project/RFNet-master/Data/cityscapesandlostandfound/' # folder that mixes Cityscapes and Lost and Found
elif dataset == 'cityrand':
return '/home/robo/m0063/project/RFNet-master/Data/cityrand/'
elif dataset == 'target':
return '/home/robo/m0063/project/RFNet-master/Data/target/'
elif dataset == 'xrlab':
return '/home/robo/m0063/project/RFNet-master/Data/xrlab/'
elif dataset == 'e1':
return '/home/robo/m0063/project/RFNet-master/Data/e1/'
elif dataset == 'mapillary':
return '/home/robo/m0063/project/RFNet-master/Data/mapillary/'
else:
print('Dataset {} not available.'.format(dataset))
raise NotImplementedError

+ 82
- 0
examples/lifelong_learning/RFNet/predict.py View File

@@ -0,0 +1,82 @@
import cv2
import time
import numpy as np
from PIL import Image
import warnings

from sedna.datasources import BaseDataSource
from sedna.core.lifelong_learning import LifelongLearning
from sedna.common.config import Context
from torchvision import transforms

from dataloaders import custom_transforms as tr
from basemodel import preprocess, val_args, Model

def preprocess(samples):
composed_transforms = transforms.Compose([
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

data = BaseDataSource(data_type="test")
data.x = [(composed_transforms(samples), "")]
return data

def init_ll_job():
estimator = Model()

task_allocation = {
"method": "TaskAllocationByOrigin",
"param": {
"default": "real"
}
}
unseen_task_allocation = {
"method": "UnseenTaskAllocationDefault"
}

ll_job = LifelongLearning(
estimator,
task_definition=None,
task_relationship_discovery=None,
task_allocation=task_allocation,
task_remodeling=None,
inference_integrate=None,
task_update_decision=None,
unseen_task_allocation=unseen_task_allocation,
unseen_sample_recognition=None,
unseen_sample_re_recognition=None)

return ll_job

def predict():
ll_job = init_ll_job()

camera_address = Context.get_parameters('video_url')
# use video streams for testing
camera = cv2.VideoCapture(camera_address)
fps = 10
nframe = 0
while 1:
ret, input_yuv = camera.read()
if not ret:
time.sleep(5)
camera = cv2.VideoCapture(camera_address)
continue

if nframe % fps:
nframe += 1
continue

img_rgb = cv2.cvtColor(input_yuv, cv2.COLOR_BGR2RGB)
nframe += 1
if nframe % 1000 == 1: # logs every 1000 frames
warnings.warn(f"camera is open, current frame index is {nframe}")

img_rgb = cv2.resize(np.array(img_rgb), (2048, 1024), interpolation=cv2.INTER_CUBIC)
img_rgb = Image.fromarray(img_rgb)
sample = {'image': img_rgb, "depth": img_rgb, "label": img_rgb}
data = preprocess(sample)
print("Inference results:", ll_job.inference(data=data))

if __name__ == '__main__':
predict()

+ 252
- 0
examples/lifelong_learning/RFNet/run_server.py View File

@@ -0,0 +1,252 @@
# Copyright 2021 The KubeEdge Authors.
#
# 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
from io import BytesIO
from typing import Optional, Any

import cv2
import numpy as np
from PIL import Image
import uvicorn
import time
from pydantic import BaseModel
from fastapi import FastAPI, UploadFile, File
from fastapi.routing import APIRoute
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse
import sedna_predict
from sedna.common.utils import get_host_ip
from dataloaders.datasets.cityscapes import CityscapesSegmentation


class ImagePayload(BaseModel):
image: UploadFile = File(...)
depth: Optional[UploadFile] = None


class ResultModel(BaseModel):
type: int = 0
box: Any = None
curr: str = None
future: str = None
img: str = None


class ResultResponse(BaseModel):
msg: str = ""
result: Optional[ResultModel] = None
code: int


class BaseServer:
# pylint: disable=too-many-instance-attributes,too-many-arguments
DEBUG = True
WAIT_TIME = 15

def __init__(
self,
servername: str,
host: str,
http_port: int = 8080,
grpc_port: int = 8081,
workers: int = 1,
ws_size: int = 16 * 1024 * 1024,
ssl_key=None,
ssl_cert=None,
timeout=300):
self.server_name = servername
self.app = None
self.host = host or '0.0.0.0'
self.http_port = http_port or 80
self.grpc_port = grpc_port
self.workers = workers
self.keyfile = ssl_key
self.certfile = ssl_cert
self.ws_size = int(ws_size)
self.timeout = int(timeout)
protocal = "https" if self.certfile else "http"
self.url = f"{protocal}://{self.host}:{self.http_port}"

def run(self, app, **kwargs):
if hasattr(app, "add_middleware"):
app.add_middleware(
CORSMiddleware, allow_origins=["*"], allow_credentials=True,
allow_methods=["*"], allow_headers=["*"],
)

uvicorn.run(
app,
host=self.host,
port=self.http_port,
ssl_keyfile=self.keyfile,
ssl_certfile=self.certfile,
workers=self.workers,
timeout_keep_alive=self.timeout,
**kwargs)

def get_all_urls(self):
url_list = [{"path": route.path, "name": route.name}
for route in getattr(self.app, 'routes', [])]
return url_list


class InferenceServer(BaseServer): # pylint: disable=too-many-arguments
"""
rest api server for inference
"""

def __init__(
self,
servername,
host: str,
http_port: int = 5000,
max_buffer_size: int = 104857600,
workers: int = 1):
super(
InferenceServer,
self).__init__(
servername=servername,
host=host,
http_port=http_port,
workers=workers)

self.job, self.detection_validator = sedna_predict.init_ll_job()

self.max_buffer_size = max_buffer_size
self.app = FastAPI(
routes=[
APIRoute(
f"/{servername}",
self.model_info,
methods=["GET"],
),
APIRoute(
f"/{servername}/predict",
self.predict,
methods=["POST"],
response_model=ResultResponse
),
],
log_level="trace",
timeout=600,
)
self.index_frame = 0

def start(self):
return self.run(self.app)

@staticmethod
def model_info():
return HTMLResponse(
"""<h1>Welcome to the RestNet API!</h1>
<p>To use this service, send a POST HTTP request to {this-url}/predict</p>
<p>The JSON payload has the following format: {"image": "BASE64_STRING_OF_IMAGE",
"depth": "BASE64_STRING_OF_DEPTH"}</p>
""")

async def predict(self, image: UploadFile = File(...), depth: Optional[UploadFile] = None) -> ResultResponse:
contents = await image.read()
recieve_img_time = time.time()
print("Recieve image from the robo:", recieve_img_time)

image = Image.open(BytesIO(contents)).convert('RGB')

img_dep = None
self.index_frame = self.index_frame + 1

if depth:
depth_contents = await depth.read()
depth = Image.open(BytesIO(depth_contents)).convert('RGB')
img_dep = cv2.resize(np.array(depth), (2048, 1024), interpolation=cv2.INTER_CUBIC)
img_dep = Image.fromarray(img_dep)

img_rgb = cv2.resize(np.array(image), (2048, 1024), interpolation=cv2.INTER_CUBIC)
img_rgb = Image.fromarray(img_rgb)

sample = {'image': img_rgb, "depth": img_dep, "label": img_rgb}
results = sedna_predict.predict(self.job, data=sample, validator=self.detection_validator)

predict_finish_time = time.time()
print(f"Prediction costs {predict_finish_time - recieve_img_time} seconds")

post_process = True
if results["result"]["box"] is None:
results["result"]["curr"] = None
results["result"]["future"] = None
elif post_process:
curr, future = get_curb(results["result"]["box"])
results["result"]["curr"] = curr
results["result"]["future"] = future
results["result"]["box"] = None
print("Post process cost at worker:", (time.time()-predict_finish_time))
else:
results["result"]["curr"] = None
results["result"]["future"] = None

print("Result transmit to robo time:", time.time())
return results

def parse_result(label, count):
label_map = ['road', 'sidewalk', ]
count_d = dict(zip(label, count))
curb_count = count_d.get(19, 0)
if curb_count / np.sum(count) > 0.3:
return "curb"
r = sorted(label, key=count_d.get, reverse=True)[0]
try:
c = label_map[r]
except:
c = "other"

return c

def get_curb(results):
results = np.array(results[0])
input_height, input_width = results.shape
closest = np.array([
[0, int(input_height)],
[int(input_width),
int(input_height)],
[int(0.118 * input_width + .5),
int(.8 * input_height + .5)],
[int(0.882 * input_width + .5),
int(.8 * input_height + .5)],
])
future = np.array([
[int(0.118 * input_width + .5),
int(.8 * input_height + .5)],
[int(0.882 * input_width + .5),
int(.8 * input_height + .5)],
[int(.765 * input_width + .5),
int(.66 * input_height + .5)],
[int(.235 * input_width + .5),
int(.66 * input_height + .5)]
])
mask = np.zeros((input_height, input_width), dtype=np.uint8)
mask = cv2.fillPoly(mask, [closest], 1)
mask = cv2.fillPoly(mask, [future], 2)
d1, c1 = np.unique(results[mask == 1], return_counts=True)
d2, c2 = np.unique(results[mask == 2], return_counts=True)
c = parse_result(d1, c1)
f = parse_result(d2, c2)
return c, f

if __name__ == '__main__':
web_app = InferenceServer("lifelong-learning-robo", host=get_host_ip())
web_app.start()

+ 41
- 0
examples/lifelong_learning/RFNet/sedna_evaluate.py View File

@@ -0,0 +1,41 @@
import os

from sedna.core.lifelong_learning import LifelongLearning
from sedna.datasources import IndexDataParse
from sedna.common.config import Context

from accuracy import accuracy
from basemodel import Model

def _load_txt_dataset(dataset_url):
# use original dataset url
original_dataset_url = Context.get_parameters('original_dataset_url')
return os.path.join(os.path.dirname(original_dataset_url), dataset_url)

def eval():
estimator = Model()
eval_dataset_url = Context.get_parameters("test_dataset_url")
eval_data = IndexDataParse(data_type="eval", func=_load_txt_dataset)
eval_data.parse(eval_dataset_url, use_raw=False)

task_allocation = {
"method": "TaskAllocationByOrigin"
}

ll_job = LifelongLearning(estimator,
task_definition=None,
task_relationship_discovery=None,
task_allocation=task_allocation,
task_remodeling=None,
inference_integrate=None,
task_update_decision=None,
unseen_task_allocation=None,
unseen_sample_recognition=None,
unseen_sample_re_recognition=None
)

ll_job.evaluate(eval_data, metrics=accuracy)


if __name__ == '__main__':
print(eval())

+ 123
- 0
examples/lifelong_learning/RFNet/sedna_predict.py View File

@@ -0,0 +1,123 @@
import os

import torch
import numpy as np
from PIL import Image
import base64
import tempfile
from io import BytesIO
from torchvision.transforms import ToPILImage
from torchvision import transforms
from torch.utils.data import DataLoader

from sedna.datasources import IndexDataParse
from sedna.core.lifelong_learning import LifelongLearning
from sedna.common.config import Context

from eval import Validator
from accuracy import accuracy
from basemodel import preprocess, val_args, Model
from dataloaders.utils import Colorize
from dataloaders import custom_transforms as tr
from dataloaders.datasets.cityscapes import CityscapesSegmentation

def _load_txt_dataset(dataset_url):
# use original dataset url
original_dataset_url = Context.get_parameters('original_dataset_url')
return os.path.join(os.path.dirname(original_dataset_url), dataset_url)

def fetch_data():
test_dataset_url = Context.get_parameters("test_dataset_url")
test_data = IndexDataParse(data_type="test", func=_load_txt_dataset)
test_data.parse(test_dataset_url, use_raw=False)
return test_data

def pre_data_process(samples):
composed_transforms = transforms.Compose([
tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
tr.ToTensor()])

data = BaseDataSource(data_type="test")
data.x = [(composed_transforms(samples), "")]
return data

def post_process(res, is_unseen_task):
if is_unseen_task:
res, base64_string = None, None
else:
res = res[0].tolist()

type = 0 if not is_unseen_task else 1
mesg = {
"msg": "",
"result": {
"type": type,
"box": res
},
"code": 0
}
return mesg

def image_merge(raw_img, result):
raw_img = ToPILImage()(raw_img)

pre_colors = Colorize()(torch.from_numpy(result))
pre_color_image = ToPILImage()(pre_colors[0]) # pre_colors.dtype = float64

image = raw_img.resize(pre_color_image.size, Image.BILINEAR)
image = image.convert('RGBA')
label = pre_color_image.convert('RGBA')
image = Image.blend(image, label, 0.6)
with tempfile.NamedTemporaryFile(suffix='.png') as f:
image.save(f.name)

with open(f.name, 'rb') as open_file:
byte_content = open_file.read()
base64_bytes = base64.b64encode(byte_content)
base64_string = base64_bytes.decode('utf-8')
return base64_string

def init_ll_job():
estimator = Model()
inference_integrate = {
"method": "BBoxInferenceIntegrate"
}
unseen_task_allocation = {
"method": "UnseenTaskAllocationDefault"
}
unseen_sample_recognition = {
"method": "SampleRegonitionByRFNet"
}

ll_job = LifelongLearning(
estimator,
task_definition=None,
task_relationship_discovery=None,
task_allocation=None,
task_remodeling=None,
inference_integrate=inference_integrate,
task_update_decision=None,
unseen_task_allocation=unseen_task_allocation,
unseen_sample_recognition=unseen_sample_recognition,
unseen_sample_re_recognition=None)

args = val_args()
args.weight_path = "./models/detection_model.pth"
args.num_class = 31

return ll_job, Validator(args, unseen_detection=True)

def predict(ll_job, data=None, validator=None):
if data:
data = pre_data_process(data)
else:
data = fetch_data()
data.x = preprocess(data.x)

res, is_unseen_task, _ = ll_job.inference(
data, validator=validator, initial=False)
return post_process(res, is_unseen_task)

if __name__ == '__main__':
ll_job, validator = init_ll_job()
print("Inference result:", predict(ll_job, validator=validator))

+ 65
- 0
examples/lifelong_learning/RFNet/sedna_train.py View File

@@ -0,0 +1,65 @@
import os

from sedna.datasources import IndexDataParse
from sedna.common.config import Context, BaseConfig
from sedna.core.lifelong_learning import LifelongLearning

from basemodel import Model

def _load_txt_dataset(dataset_url):
# use original dataset url
original_dataset_url = Context.get_parameters('original_dataset_url')
return os.path.join(os.path.dirname(original_dataset_url), dataset_url)

def train(estimator, train_data):
task_definition = {
"method": "TaskDefinitionByOrigin"
}

task_allocation = {
"method": "TaskAllocationByOrigin"
}

ll_job = LifelongLearning(estimator,
task_definition=task_definition,
task_relationship_discovery=None,
task_allocation=task_allocation,
task_remodeling=None,
inference_integrate=None,
task_update_decision=None,
unseen_task_allocation=None,
unseen_sample_recognition=None,
unseen_sample_re_recognition=None
)

ll_job.train(train_data)

def update(estimator, train_data):
ll_job = LifelongLearning(estimator,
task_definition=None,
task_relationship_discovery=None,
task_allocation=None,
task_remodeling=None,
inference_integrate=None,
task_update_decision=None,
unseen_task_allocation=None,
unseen_sample_recognition=None,
unseen_sample_re_recognition=None
)

ll_job.update(train_data)

def run():
estimator = Model()
train_dataset_url = BaseConfig.train_dataset_url
train_data = IndexDataParse(data_type="train", func=_load_txt_dataset)
train_data.parse(train_dataset_url, use_raw=False)

is_completed_initilization = str(Context.get_parameters("HAS_COMPLETED_INITIAL_TRAINING", "false")).lower()
if is_completed_initilization == "false":
train(estimator, train_data)
else:
update(estimator, train_data)

if __name__ == '__main__':
run()

+ 52
- 0
examples/lifelong_learning/RFNet/test.py View File

@@ -0,0 +1,52 @@
import numpy as np
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt

CPA_results = np.load("./cpa_results.npy").T
ratios = [0.3, 0.5, 0.6, 0.7, 0.8, 0.9]
ratio_counts = np.zeros((len(CPA_results), len(ratios)), dtype=float)

for i in range(len(CPA_results)):
for j in range(len(ratios)):
result = CPA_results[i]
result = result[result <= ratios[j]]

ratio_counts[i][j] = len(result) / 275

plt.figure(figsize=(45, 10))
ratio_counts = pd.DataFrame(data=ratio_counts.T, index=ratios)
sns.heatmap(data=ratio_counts, annot=True, cmap="YlGnBu", annot_kws={'fontsize': 15})
plt.xticks(fontsize=20)
plt.yticks(fontsize=25)
plt.xlabel("Test images", fontsize=25)
plt.ylabel("Ratio of PA ranges", fontsize=25)
plt.savefig("./figs/ratio_count.png")
plt.show()


# data = pd.DataFrame(CPA_results.T)
#
# plt.figure(figsize=(30, 15))
# cpa_result = pd.DataFrame(data=data)
# sns.heatmap(data=cpa_result)
# plt.savefig("./figs/heatmap_pa.png")
# plt.show()
#
# plt.figure(figsize=(30, 15))
# cpa_result = pd.DataFrame(data=data[:15])
# sns.heatmap(data=cpa_result)
# plt.savefig("./figs/door_heatmap_pa.png")
# plt.show()
#
# plt.figure(figsize=(30, 15))
# cpa_result = pd.DataFrame(data=data[15:31])
# sns.heatmap(data=cpa_result)
# plt.savefig("./figs/garden1_heatmap_pa.png")
# plt.show()
#
# plt.figure(figsize=(30, 15))
# cpa_result = pd.DataFrame(data=data[31:])
# sns.heatmap(data=cpa_result)
# plt.savefig("./figs/garden2_heatmap_pa.png")
# plt.show()

+ 342
- 0
examples/lifelong_learning/RFNet/train.py View File

@@ -0,0 +1,342 @@
import argparse
import os
import numpy as np
from tqdm import tqdm

from sedna.datasources import BaseDataSource

import torch
from mypath import Path
from models.rfnet import RFNet
from models.resnet.resnet_single_scale_single_attention import *
from models.replicate import patch_replication_callback
from dataloaders import make_data_loader
from utils.loss import SegmentationLosses
from utils.calculate_weights import calculate_weigths_labels
from utils.lr_scheduler import LR_Scheduler
from utils.saver import Saver
from utils.summaries import TensorboardSummary
from utils.metrics import Evaluator

class Trainer(object):
def __init__(self, args, train_data=None, valid_data=None):
self.args = args
# Define Saver
self.saver = Saver(args)
self.saver.save_experiment_config()
# Define Tensorboard Summary
self.summary = TensorboardSummary(self.saver.experiment_dir)
self.writer = self.summary.create_summary()
# denormalize for detph image
self.mean_depth = torch.as_tensor(0.12176, dtype=torch.float32, device='cpu')
self.std_depth = torch.as_tensor(0.09752, dtype=torch.float32, device='cpu')
self.nclass = args.num_class
# Define Dataloader
kwargs = {'num_workers': args.workers, 'pin_memory': False}
self.train_loader, self.val_loader, self.test_loader, _ = make_data_loader(args, train_data=train_data,
valid_data=valid_data, **kwargs)
# Define network
resnet = resnet18(pretrained=True, efficient=False, use_bn=True)
model = RFNet(resnet, num_classes=self.nclass, use_bn=True)
train_params = [{'params': model.random_init_params(), 'lr': args.lr},
{'params': model.fine_tune_params(), 'lr': 0.1*args.lr, 'weight_decay':args.weight_decay}]
# Define Optimizer
optimizer = torch.optim.Adam(train_params, lr=args.lr,
weight_decay=args.weight_decay)
# Define Criterion
# whether to use class balanced weights
if args.use_balanced_weights:
classes_weights_path = os.path.join(Path.db_root_dir(args.dataset), args.dataset+'_classes_weights.npy')
if os.path.isfile(classes_weights_path):
weight = np.load(classes_weights_path)
else:
weight = calculate_weigths_labels(args.dataset, self.train_loader, self.nclass)
weight = torch.from_numpy(weight.astype(np.float32))
else:
weight = None
# Define loss function
self.criterion = SegmentationLosses(weight=weight, cuda=args.cuda).build_loss(mode=args.loss_type)
self.model, self.optimizer = model, optimizer
# Define Evaluator
self.evaluator = Evaluator(self.nclass)
# # Define lr scheduler
self.scheduler = LR_Scheduler(args.lr_scheduler, args.lr, args.epochs, len(self.train_loader))
# Using cuda
if args.cuda:
self.model = torch.nn.DataParallel(self.model, device_ids=self.args.gpu_ids)
patch_replication_callback(self.model)
self.model = self.model.cuda()

# Resuming checkpoint
self.best_pred = 0.0
if args.resume is not None:
if not os.path.isfile(args.resume):
raise RuntimeError("=> no checkpoint found at '{}'" .format(args.resume))
print(f"Training: load model from {args.resume}")
checkpoint = torch.load(args.resume, map_location=torch.device("cpu"))
args.start_epoch = checkpoint['epoch']
# if args.cuda:
# self.model.load_state_dict(checkpoint['state_dict'])
# else:
# self.model.load_state_dict(checkpoint['state_dict'])
self.model.load_state_dict(checkpoint['state_dict'])
if not args.ft:
self.optimizer.load_state_dict(checkpoint['optimizer'])
self.best_pred = checkpoint['best_pred']
print("=> loaded checkpoint '{}' (epoch {})".format(args.resume, checkpoint['epoch']))

# Clear start epoch if fine-tuning
if args.ft:
args.start_epoch = 0

def training(self, epoch):
train_loss = 0.0
print(self.optimizer.state_dict()['param_groups'][0]['lr'])
self.model.train()
tbar = tqdm(self.train_loader)
num_img_tr = len(self.train_loader)
for i, sample in enumerate(tbar):
if self.args.depth:
image, depth, target = sample['image'], sample['depth'], sample['label']
#print(target.shape)
else:
image, target = sample['image'], sample['label']
print(image.shape)
if self.args.cuda:
image, target = image.cuda(), target.cuda()
if self.args.depth:
depth = depth.cuda()
self.scheduler(self.optimizer, i, epoch, self.best_pred)
self.optimizer.zero_grad()
if self.args.depth:
output = self.model(image, depth)
else:
output = self.model(image)
#print(target.max())
#print(output.shape)
target[target > self.nclass-1] = 255
loss = self.criterion(output, target)
loss.backward()
self.optimizer.step()
#print(self.optimizer.state_dict()['param_groups'][0]['lr'])
train_loss += loss.item()
tbar.set_description('Train loss: %.3f' % (train_loss / (i + 1)))
self.writer.add_scalar('train/total_loss_iter', loss.item(), i + num_img_tr * epoch)
# Show 10 * 3 inference results each epoch
if i % (num_img_tr // 10) == 0:
global_step = i + num_img_tr * epoch
if self.args.depth:
self.summary.visualize_image(self.writer, self.args.dataset, image, target, output, global_step)

depth_display = depth[0].cpu().unsqueeze(0)
depth_display = depth_display.mul_(self.std_depth).add_(self.mean_depth)
depth_display = depth_display.numpy()
depth_display = depth_display*255
depth_display = depth_display.astype(np.uint8)
self.writer.add_image('Depth', depth_display, global_step)

else:
self.summary.visualize_image(self.writer, self.args.dataset, image, target, output, global_step)

self.writer.add_scalar('train/total_loss_epoch', train_loss, epoch)
print('[Epoch: %d, numImages: %5d]' % (epoch, i * self.args.batch_size + image.data.shape[0]))
print('Loss: %.3f' % train_loss)

# if self.args.no_val:
# # save checkpoint every epoch
# is_best = False
# checkpoint_path = self.saver.save_checkpoint({
# 'epoch': epoch + 1,
# 'state_dict': self.model.state_dict(),
# 'optimizer': self.optimizer.state_dict(),
# 'best_pred': self.best_pred,
# }, is_best)

def validation(self, epoch):
self.model.eval()
self.evaluator.reset()
tbar = tqdm(self.val_loader, desc='\r')
test_loss = 0.0
for i, (sample, img_path) in enumerate(tbar):
if self.args.depth:
image, depth, target = sample['image'], sample['depth'], sample['label']
else:
image, target = sample['image'], sample['label']
# print(f"val image is {image}")
if self.args.cuda:
image, target = image.cuda(), target.cuda()
if self.args.depth:
depth = depth.cuda()
with torch.no_grad():
if self.args.depth:
output = self.model(image, depth)
else:
output = self.model(image)
target[target > self.nclass-1] = 255
loss = self.criterion(output, target)
test_loss += loss.item()
tbar.set_description('Test loss: %.3f' % (test_loss / (i + 1)))
pred = output.data.cpu().numpy()
target = target.cpu().numpy()
pred = np.argmax(pred, axis=1)
# Add batch sample into evaluator
self.evaluator.add_batch(target, pred)

# Fast test during the training
Acc = self.evaluator.Pixel_Accuracy()
Acc_class = self.evaluator.Pixel_Accuracy_Class()
mIoU = self.evaluator.Mean_Intersection_over_Union()
FWIoU = self.evaluator.Frequency_Weighted_Intersection_over_Union()
self.writer.add_scalar('val/total_loss_epoch', test_loss, epoch)
self.writer.add_scalar('val/mIoU', mIoU, epoch)
self.writer.add_scalar('val/Acc', Acc, epoch)
self.writer.add_scalar('val/Acc_class', Acc_class, epoch)
self.writer.add_scalar('val/fwIoU', FWIoU, epoch)
print('Validation:')
print('[Epoch: %d, numImages: %5d]' % (epoch, i * self.args.batch_size + image.data.shape[0]))
print("Acc:{}, Acc_class:{}, mIoU:{}, fwIoU: {}".format(Acc, Acc_class, mIoU, FWIoU))
print('Loss: %.3f' % test_loss)

new_pred = mIoU
if new_pred > self.best_pred:
is_best = True
self.best_pred = new_pred
self.saver.save_checkpoint({
'epoch': epoch + 1,
'state_dict': self.model.state_dict(),
'optimizer': self.optimizer.state_dict(),
'best_pred': self.best_pred,
}, is_best)

def train():
parser = argparse.ArgumentParser(description="PyTorch RFNet Training")
parser.add_argument('--depth', action="store_true", default=False,
help='training with depth image or not (default: False)')
parser.add_argument('--dataset', type=str, default='cityscapes',
choices=['citylostfound', 'cityscapes', 'cityrand', 'target', 'xrlab', 'e1', 'mapillary'],
help='dataset name (default: cityscapes)')
parser.add_argument('--workers', type=int, default=4,
metavar='N', help='dataloader threads')
parser.add_argument('--base-size', type=int, default=1024,
help='base image size')
parser.add_argument('--crop-size', type=int, default=768,
help='crop image size')
parser.add_argument('--loss-type', type=str, default='ce',
choices=['ce', 'focal'],
help='loss func type (default: ce)')
# training hyper params
# parser.add_argument('--epochs', type=int, default=None, metavar='N',
# help='number of epochs to train (default: auto)')
parser.add_argument('--epochs', type=int, default=1, metavar='N',
help='number of epochs to train (default: auto)')
parser.add_argument('--start_epoch', type=int, default=0,
metavar='N', help='start epochs (default:0)')
parser.add_argument('--batch-size', type=int, default=None,
metavar='N', help='input batch size for \
training (default: auto)')
parser.add_argument('--val-batch-size', type=int, default=1,
metavar='N', help='input batch size for \
testing (default: auto)')
parser.add_argument('--test-batch-size', type=int, default=1,
metavar='N', help='input batch size for \
testing (default: auto)')
parser.add_argument('--use-balanced-weights', action='store_true', default=False,
help='whether to use balanced weights (default: True)')

parser.add_argument('--num-class', type=int, default=24,
help='number of training classes (default: 24')
# optimizer params
parser.add_argument('--lr', type=float, default=1e-4, metavar='LR',
help='learning rate (default: auto)')
parser.add_argument('--lr-scheduler', type=str, default='cos',
choices=['poly', 'step', 'cos', 'inv'],
help='lr scheduler mode: (default: cos)')
parser.add_argument('--momentum', type=float, default=0.9,
metavar='M', help='momentum (default: 0.9)')
parser.add_argument('--weight-decay', type=float, default=2.5e-5,
metavar='M', help='w-decay (default: 5e-4)')
# cuda, seed and logging
parser.add_argument('--no-cuda', action='store_true', default=
False, help='disables CUDA training')
parser.add_argument('--gpu-ids', type=str, default='0',
help='use which gpu to train, must be a \
comma-separated list of integers only (default=0)')
parser.add_argument('--seed', type=int, default=1, metavar='S',
help='random seed (default: 1)')
# checking point
parser.add_argument('--resume', type=str, default=None,
help='put the path to resuming file if needed')
parser.add_argument('--checkname', type=str, default=None,
help='set the checkpoint name')
# finetuning pre-trained models
parser.add_argument('--ft', action='store_true', default=True,
help='finetuning on a different dataset')
# evaluation option
parser.add_argument('--eval-interval', type=int, default=1,
help='evaluation interval (default: 1)')
parser.add_argument('--no-val', action='store_true', default=False,
help='skip validation during training')

args = parser.parse_args()
args.cuda = not args.no_cuda and torch.cuda.is_available()
print(torch.cuda.is_available())
if args.cuda:
try:
args.gpu_ids = [int(s) for s in args.gpu_ids.split(',')]
except ValueError:
raise ValueError('Argument --gpu_ids must be a comma-separated list of integers only')

if args.epochs is None:
epoches = {
'cityscapes': 200,
'citylostfound': 200,
}
args.epochs = epoches[args.dataset.lower()]

if args.batch_size is None:
args.batch_size = 4 * len(args.gpu_ids)

if args.test_batch_size is None:
args.test_batch_size = args.batch_size

if args.lr is None:
lrs = {
'cityscapes': 0.0001,
'citylostfound': 0.0001,
'cityrand': 0.0001
}
args.lr = lrs[args.dataset.lower()] / (4 * len(args.gpu_ids)) * args.batch_size


if args.checkname is None:
args.checkname = 'RFNet'
print(args)
torch.manual_seed(args.seed)
args.resume = "/home/lsq/ianvs_new/examples/robo/workspace/pcb-algorithm-test/test-algorithm/6441f8be-d809-11ec-ac65-3b30682caaa6/knowledgeable/1/seen_task/checkpoint_1653029539.6730478.pth"
trainer = Trainer(args, train_data=val_data, valid_data=val_data)
trainer.validation(0)
# print('Starting Epoch:', trainer.args.start_epoch)
# print('Total Epoches:', trainer.args.epochs)
# for epoch in range(trainer.args.start_epoch, trainer.args.epochs):
# if epoch == 0:
# trainer.validation(epoch)
# trainer.training(epoch)
# if not trainer.args.no_val and epoch % args.eval_interval == (args.eval_interval - 1):
# trainer.validation(epoch)
#
# trainer.writer.close()

if __name__ == "__main__":
val_data = BaseDataSource(data_type="eval")
x, y = [], []
with open("/home/lsq/ianvs_new/examples/robo/trainData_depth.txt", "r") as f:
for line in f.readlines()[-120:]:
lines = line.strip().split()
x.append(lines[:2])
y.append(lines[-1])

val_data.x = x
val_data.y = y

train()

+ 0
- 0
examples/lifelong_learning/RFNet/utils/__init__.py View File


+ 29
- 0
examples/lifelong_learning/RFNet/utils/calculate_weights.py View File

@@ -0,0 +1,29 @@
import os
from tqdm import tqdm
import numpy as np
from mypath import Path

def calculate_weigths_labels(dataset, dataloader, num_classes):
# Create an instance from the data loader
z = np.zeros((num_classes,))
# Initialize tqdm
tqdm_batch = tqdm(dataloader)
print('Calculating classes weights')
for sample in tqdm_batch:
y = sample['label']
y = y.detach().cpu().numpy()
mask = (y >= 0) & (y < num_classes)
labels = y[mask].astype(np.uint8)
count_l = np.bincount(labels, minlength=num_classes)
z += count_l
tqdm_batch.close()
total_frequency = np.sum(z)
class_weights = []
for frequency in z:
class_weight = 1 / (np.log(1.02 + (frequency / total_frequency)))
class_weights.append(class_weight)
ret = np.array(class_weights)
classes_weights_path = os.path.join(Path.db_root_dir(dataset), dataset+'_classes_weights.npy')
np.save(classes_weights_path, ret)

return ret

+ 142
- 0
examples/lifelong_learning/RFNet/utils/iouEval.py View File

@@ -0,0 +1,142 @@
import torch


class iouEval:

def __init__(self, nClasses, ignoreIndex=20):

self.nClasses = nClasses
self.ignoreIndex = ignoreIndex if nClasses > ignoreIndex else -1 # if ignoreIndex is larger than nClasses, consider no ignoreIndex
self.reset()

def reset(self):
classes = self.nClasses if self.ignoreIndex == -1 else self.nClasses - 1
self.tp = torch.zeros(classes).double()
self.fp = torch.zeros(classes).double()
self.fn = torch.zeros(classes).double()
self.cdp_obstacle = torch.zeros(1).double()
self.tp_obstacle = torch.zeros(1).double()
self.idp_obstacle = torch.zeros(1).double()
self.tp_nonobstacle = torch.zeros(1).double()
# self.cdi = torch.zeros(1).double()

def addBatch(self, x, y): # x=preds, y=targets
# sizes should be "batch_size x nClasses x H x W"
# cdi = 0

# print ("X is cuda: ", x.is_cuda)
# print ("Y is cuda: ", y.is_cuda)

if (x.is_cuda or y.is_cuda):
x = x.cuda()
y = y.cuda()

# if size is "batch_size x 1 x H x W" scatter to onehot
if (x.size(1) == 1):
x_onehot = torch.zeros(x.size(0), self.nClasses, x.size(2), x.size(3))
if x.is_cuda:
x_onehot = x_onehot.cuda()
x_onehot.scatter_(1, x, 1).float() # dim index src 按照列用1替换0,索引为x
else:
x_onehot = x.float()

if (y.size(1) == 1):
y_onehot = torch.zeros(y.size(0), self.nClasses, y.size(2), y.size(3))
if y.is_cuda:
y_onehot = y_onehot.cuda()
y_onehot.scatter_(1, y, 1).float()
else:
y_onehot = y.float()

if (self.ignoreIndex != -1):
ignores = y_onehot[:, self.ignoreIndex].unsqueeze(1) # 加一维
x_onehot = x_onehot[:, :self.ignoreIndex] # ignoreIndex后的都不要
y_onehot = y_onehot[:, :self.ignoreIndex]
else:
ignores = 0


tpmult = x_onehot * y_onehot # times prediction and gt coincide is 1
tp = torch.sum(torch.sum(torch.sum(tpmult, dim=0, keepdim=True), dim=2, keepdim=True), dim=3,
keepdim=True).squeeze()
fpmult = x_onehot * (
1 - y_onehot - ignores) # times prediction says its that class and gt says its not (subtracting cases when its ignore label!)
fp = torch.sum(torch.sum(torch.sum(fpmult, dim=0, keepdim=True), dim=2, keepdim=True), dim=3,
keepdim=True).squeeze()
fnmult = (1 - x_onehot) * (y_onehot) # times prediction says its not that class and gt says it is
fn = torch.sum(torch.sum(torch.sum(fnmult, dim=0, keepdim=True), dim=2, keepdim=True), dim=3,
keepdim=True).squeeze()

self.tp += tp.double().cpu()
self.fp += fp.double().cpu()
self.fn += fn.double().cpu()

cdp_obstacle = tpmult[:, 19].sum() # obstacle index 19
tp_obstacle = y_onehot[:, 19].sum()

idp_obstacle = (x_onehot[:, 19] - tpmult[:, 19]).sum()
tp_nonobstacle = (-1*y_onehot+1).sum()

# for i in range(0, x.size(0)):
# if tpmult[i].sum()/(y_onehot[i].sum() + 1e-15) >= 0.5:
# cdi += 1


self.cdp_obstacle += cdp_obstacle.double().cpu()
self.tp_obstacle += tp_obstacle.double().cpu()
self.idp_obstacle += idp_obstacle.double().cpu()
self.tp_nonobstacle += tp_nonobstacle.double().cpu()
# self.cdi += cdi.double().cpu()



def getIoU(self):
num = self.tp
den = self.tp + self.fp + self.fn + 1e-15
iou = num / den
iou_not_zero = list(filter(lambda x: x != 0, iou))
# print(len(iou_not_zero))
iou_mean = sum(iou_not_zero) / len(iou_not_zero)
tfp = self.tp + self.fp + 1e-15
acc = num / tfp
acc_not_zero = list(filter(lambda x: x != 0, acc))
acc_mean = sum(acc_not_zero) / len(acc_not_zero)

return iou_mean, iou, acc_mean, acc # returns "iou mean", "iou per class"

def getObstacleEval(self):

pdr_obstacle = self.cdp_obstacle / (self.tp_obstacle+1e-15)
pfp_obstacle = self.idp_obstacle / (self.tp_nonobstacle+1e-15)

return pdr_obstacle, pfp_obstacle


# Class for colors
class colors:
RED = '\033[31;1m'
GREEN = '\033[32;1m'
YELLOW = '\033[33;1m'
BLUE = '\033[34;1m'
MAGENTA = '\033[35;1m'
CYAN = '\033[36;1m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
ENDC = '\033[0m'


# Colored value output if colorized flag is activated.
def getColorEntry(val):
if not isinstance(val, float):
return colors.ENDC
if (val < .20):
return colors.RED
elif (val < .40):
return colors.YELLOW
elif (val < .60):
return colors.BLUE
elif (val < .80):
return colors.CYAN
else:
return colors.GREEN


+ 64
- 0
examples/lifelong_learning/RFNet/utils/loss.py View File

@@ -0,0 +1,64 @@
import torch
import torch.nn as nn

class SegmentationLosses(object):
def __init__(self, weight=None, size_average=True, batch_average=True, ignore_index=255, cuda=False): # ignore_index=255
self.ignore_index = ignore_index
self.weight = weight
self.size_average = size_average
self.batch_average = batch_average
self.cuda = cuda

def build_loss(self, mode='ce'):
"""Choices: ['ce' or 'focal']"""
if mode == 'ce':
return self.CrossEntropyLoss
elif mode == 'focal':
return self.FocalLoss
else:
raise NotImplementedError

def CrossEntropyLoss(self, logit, target):
n, c, h, w = logit.size()
#criterion = nn.CrossEntropyLoss(weight=self.weight, ignore_index=self.ignore_index,
#size_average=self.size_average)
criterion = nn.CrossEntropyLoss(reduction='mean', ignore_index=self.ignore_index)
if self.cuda:
criterion = criterion.cuda()

loss = criterion(logit, target.long())

if self.batch_average:
loss /= n

return loss

def FocalLoss(self, logit, target, gamma=2, alpha=0.5):
n, c, h, w = logit.size()
criterion = nn.CrossEntropyLoss(weight=self.weight, ignore_index=self.ignore_index,
size_average=self.size_average)
if self.cuda:
criterion = criterion.cuda()

logpt = -criterion(logit, target.long())
pt = torch.exp(logpt)
if alpha is not None:
logpt *= alpha
loss = -((1 - pt) ** gamma) * logpt

if self.batch_average:
loss /= n

return loss

if __name__ == "__main__":
loss = SegmentationLosses(cuda=True)
a = torch.rand(1, 3, 7, 7).cuda()
b = torch.rand(1, 7, 7).cuda()
print(loss.CrossEntropyLoss(a, b).item())
print(loss.FocalLoss(a, b, gamma=0, alpha=None).item())
print(loss.FocalLoss(a, b, gamma=2, alpha=0.5).item())





+ 70
- 0
examples/lifelong_learning/RFNet/utils/lr_scheduler.py View File

@@ -0,0 +1,70 @@
##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
## Created by: Hang Zhang
## ECE Department, Rutgers University
## Email: zhang.hang@rutgers.edu
## Copyright (c) 2017
##
## This source code is licensed under the MIT-style license found in the
## LICENSE file in the root directory of this source tree
##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

import math

class LR_Scheduler(object):
"""Learning Rate Scheduler

Step mode: ``lr = baselr * 0.1 ^ {floor(epoch-1 / lr_step)}``

Cosine mode: ``lr = baselr * 0.5 * (1 + cos(iter/maxiter))``

Poly mode: ``lr = baselr * (1 - iter/maxiter) ^ 0.9``

Args:
args:
:attr:`args.lr_scheduler` lr scheduler mode (`cos`, `poly`),
:attr:`args.lr` base learning rate, :attr:`args.epochs` number of epochs,
:attr:`args.lr_step`

iters_per_epoch: number of iterations per epoch
"""
def __init__(self, mode, base_lr, num_epochs, iters_per_epoch=0,
lr_step=0, warmup_epochs=0):
self.mode = mode
print('Using {} LR Scheduler!'.format(self.mode))
self.lr = base_lr
if mode == 'step':
assert lr_step
self.lr_step = lr_step
self.iters_per_epoch = iters_per_epoch
self.N = num_epochs * iters_per_epoch
self.epoch = -1
self.warmup_iters = warmup_epochs * iters_per_epoch

def __call__(self, optimizer, i, epoch, best_pred):
T = epoch * self.iters_per_epoch + i
if self.mode == 'cos':
lr = 0.5 * self.lr * (1 + math.cos(1.0 * T / self.N * math.pi))
elif self.mode == 'poly':
lr = self.lr * pow((1 - 1.0 * T / self.N), 2)
elif self.mode == 'step':
lr = self.lr * (0.1 ** (epoch // self.lr_step))
else:
raise NotImplemented
# warm up lr schedule
if self.warmup_iters > 0 and T < self.warmup_iters:
lr = lr * 1.0 * T / self.warmup_iters
if epoch > self.epoch:
print('\n=>Epoches %i, learning rate = %.4f, \
previous best = %.4f' % (epoch, lr, best_pred))
self.epoch = epoch
assert lr >= 0
self._adjust_learning_rate(optimizer, lr)

def _adjust_learning_rate(self, optimizer, lr):
if len(optimizer.param_groups) == 1:
optimizer.param_groups[0]['lr'] = lr * 4
else:
# enlarge the lr at the head
optimizer.param_groups[0]['lr'] = lr * 4
for i in range(1, len(optimizer.param_groups)):
optimizer.param_groups[i]['lr'] = lr

+ 145
- 0
examples/lifelong_learning/RFNet/utils/metrics.py View File

@@ -0,0 +1,145 @@
import numpy as np


class Evaluator(object):
def __init__(self, num_class):
self.num_class = num_class
self.confusion_matrix = np.zeros((self.num_class,)*2) # shape:(num_class, num_class)

def Pixel_Accuracy(self):
Acc = np.diag(self.confusion_matrix).sum() / self.confusion_matrix.sum()
return Acc
def Pixel_Accuracy_Class_Curb(self):
Acc = np.diag(self.confusion_matrix) / self.confusion_matrix.sum(axis=1)
print('-----------Acc of each classes-----------')
print("road : %.6f" % (Acc[0] * 100.0), "%\t")
print("sidewalk : %.6f" % (Acc[1] * 100.0), "%\t")
Acc = np.nanmean(Acc[:2])
return Acc

def Pixel_Accuracy_Class(self):
Acc = np.diag(self.confusion_matrix) / self.confusion_matrix.sum(axis=1)
print('-----------Acc of each classes-----------')
print("road : %.6f" % (Acc[0] * 100.0), "%\t")
print("sidewalk : %.6f" % (Acc[1] * 100.0), "%\t")
print("building : %.6f" % (Acc[2] * 100.0), "%\t")
print("wall : %.6f" % (Acc[3] * 100.0), "%\t")
print("fence : %.6f" % (Acc[4] * 100.0), "%\t")
print("pole : %.6f" % (Acc[5] * 100.0), "%\t")
print("traffic light: %.6f" % (Acc[6] * 100.0), "%\t")
print("traffic sign : %.6f" % (Acc[7] * 100.0), "%\t")
print("vegetation : %.6f" % (Acc[8] * 100.0), "%\t")
print("terrain : %.6f" % (Acc[9] * 100.0), "%\t")
print("sky : %.6f" % (Acc[10] * 100.0), "%\t")
print("person : %.6f" % (Acc[11] * 100.0), "%\t")
print("rider : %.6f" % (Acc[12] * 100.0), "%\t")
print("car : %.6f" % (Acc[13] * 100.0), "%\t")
print("truck : %.6f" % (Acc[14] * 100.0), "%\t")
print("bus : %.6f" % (Acc[15] * 100.0), "%\t")
print("train : %.6f" % (Acc[16] * 100.0), "%\t")
print("motorcycle : %.6f" % (Acc[17] * 100.0), "%\t")
print("bicycle : %.6f" % (Acc[18] * 100.0), "%\t")
print("dynamic : %.6f" % (Acc[19] * 100.0), "%\t")
print("stair : %.6f" % (Acc[20] * 100.0), "%\t")
if self.num_class == 20:
print("small obstacles: %.6f" % (Acc[19] * 100.0), "%\t")
Acc = np.nanmean(Acc)
return Acc

def Mean_Intersection_over_Union(self):
MIoU = np.diag(self.confusion_matrix) / (
np.sum(self.confusion_matrix, axis=1) + np.sum(self.confusion_matrix, axis=0) -
np.diag(self.confusion_matrix))

# print MIoU of each class
print('-----------IoU of each classes-----------')
print("road : %.6f" % (MIoU[0] * 100.0), "%\t")
print("sidewalk : %.6f" % (MIoU[1] * 100.0), "%\t")
print("building : %.6f" % (MIoU[2] * 100.0), "%\t")
print("wall : %.6f" % (MIoU[3] * 100.0), "%\t")
print("fence : %.6f" % (MIoU[4] * 100.0), "%\t")
print("pole : %.6f" % (MIoU[5] * 100.0), "%\t")
print("traffic light: %.6f" % (MIoU[6] * 100.0), "%\t")
print("traffic sign : %.6f" % (MIoU[7] * 100.0), "%\t")
print("vegetation : %.6f" % (MIoU[8] * 100.0), "%\t")
print("terrain : %.6f" % (MIoU[9] * 100.0), "%\t")
print("sky : %.6f" % (MIoU[10] * 100.0), "%\t")
print("person : %.6f" % (MIoU[11] * 100.0), "%\t")
print("rider : %.6f" % (MIoU[12] * 100.0), "%\t")
print("car : %.6f" % (MIoU[13] * 100.0), "%\t")
print("truck : %.6f" % (MIoU[14] * 100.0), "%\t")
print("bus : %.6f" % (MIoU[15] * 100.0), "%\t")
print("train : %.6f" % (MIoU[16] * 100.0), "%\t")
print("motorcycle : %.6f" % (MIoU[17] * 100.0), "%\t")
print("bicycle : %.6f" % (MIoU[18] * 100.0), "%\t")
print("dynamic : %.6f" % (MIoU[19] * 100.0), "%\t")
print("stair : %.6f" % (MIoU[20] * 100.0), "%\t")
if self.num_class == 20:
print("small obstacles: %.6f" % (MIoU[19] * 100.0), "%\t")

MIoU = np.nanmean(MIoU)
return MIoU
def Mean_Intersection_over_Union_Curb(self):
MIoU = np.diag(self.confusion_matrix) / (
np.sum(self.confusion_matrix, axis=1) + np.sum(self.confusion_matrix, axis=0) -
np.diag(self.confusion_matrix))

# print MIoU of each class
print('-----------IoU of each classes-----------')
print("road : %.6f" % (MIoU[0] * 100.0), "%\t")
print("sidewalk : %.6f" % (MIoU[1] * 100.0), "%\t")
if self.num_class == 20:
print("small obstacles: %.6f" % (MIoU[19] * 100.0), "%\t")

MIoU = np.nanmean(MIoU[:2])
return MIoU

def Frequency_Weighted_Intersection_over_Union(self):
freq = np.sum(self.confusion_matrix, axis=1) / np.sum(self.confusion_matrix)
iu = np.diag(self.confusion_matrix) / (
np.sum(self.confusion_matrix, axis=1) + np.sum(self.confusion_matrix, axis=0) -
np.diag(self.confusion_matrix))

FWIoU = (freq[freq > 0] * iu[freq > 0]).sum()
CFWIoU = freq[freq > 0] * iu[freq > 0]
print('-----------FWIoU of each classes-----------')
print("road : %.6f" % (CFWIoU[0] * 100.0), "%\t")
print("sidewalk : %.6f" % (CFWIoU[1] * 100.0), "%\t")
return FWIoU

def Frequency_Weighted_Intersection_over_Union_Curb(self):
freq = np.sum(self.confusion_matrix, axis=1) / np.sum(self.confusion_matrix)
iu = np.diag(self.confusion_matrix) / (
np.sum(self.confusion_matrix, axis=1) + np.sum(self.confusion_matrix, axis=0) -
np.diag(self.confusion_matrix))

# FWIoU = (freq[freq > 0] * iu[freq > 0]).sum()
CFWIoU = freq[freq > 0] * iu[freq > 0]
print('-----------FWIoU of each classes-----------')
print("road : %.6f" % (CFWIoU[0] * 100.0), "%\t")
print("sidewalk : %.6f" % (CFWIoU[1] * 100.0), "%\t")

return np.nanmean(CFWIoU[:2])

def _generate_matrix(self, gt_image, pre_image):
mask = (gt_image >= 0) & (gt_image < self.num_class)
label = self.num_class * gt_image[mask].astype('int') + pre_image[mask]
count = np.bincount(label, minlength=self.num_class**2)
confusion_matrix = count.reshape(self.num_class, self.num_class)
return confusion_matrix

def add_batch(self, gt_image, pre_image):
assert gt_image.shape == pre_image.shape
self.confusion_matrix += self._generate_matrix(gt_image, pre_image)

def reset(self):
self.confusion_matrix = np.zeros((self.num_class,) * 2)





+ 68
- 0
examples/lifelong_learning/RFNet/utils/saver.py View File

@@ -0,0 +1,68 @@
import os
import time
import shutil
import tempfile
import torch
from collections import OrderedDict
import glob

class Saver(object):

def __init__(self, args):
self.args = args
self.directory = os.path.join('/tmp', args.dataset, args.checkname)
self.runs = sorted(glob.glob(os.path.join(self.directory, 'experiment_*')))
run_id = int(self.runs[-1].split('_')[-1]) + 1 if self.runs else 0

self.experiment_dir = os.path.join(self.directory, 'experiment_{}'.format(str(run_id)))
if not os.path.exists(self.experiment_dir):
os.makedirs(self.experiment_dir)

def save_checkpoint(self, state, is_best): # filename from .pth.tar change to .pth?
"""Saves checkpoint to disk"""
filename = f'checkpoint_{time.time()}.pth'
checkpoint_path = os.path.join(self.experiment_dir, filename)
torch.save(state, checkpoint_path)
if is_best:
best_pred = state['best_pred']
with open(os.path.join(self.experiment_dir, 'best_pred.txt'), 'w') as f:
f.write(str(best_pred))
if self.runs:
previous_miou = [0.0]
for run in self.runs:
run_id = run.split('_')[-1]
path = os.path.join(self.directory, 'experiment_{}'.format(str(run_id)), 'best_pred.txt')
if os.path.exists(path):
with open(path, 'r') as f:
miou = float(f.readline())
previous_miou.append(miou)
else:
continue
max_miou = max(previous_miou)
if best_pred > max_miou:
checkpoint_path_best = os.path.join(self.directory, 'model_best.pth')
shutil.copyfile(checkpoint_path, checkpoint_path_best)
checkpoint_path = checkpoint_path_best
else:
checkpoint_path_best = os.path.join(self.directory, 'model_best.pth')
shutil.copyfile(checkpoint_path, checkpoint_path_best)
checkpoint_path = checkpoint_path_best

return checkpoint_path

def save_experiment_config(self):
logfile = os.path.join(self.experiment_dir, 'parameters.txt')
log_file = open(logfile, 'w')
p = OrderedDict()
p['datset'] = self.args.dataset
# p['out_stride'] = self.args.out_stride
p['lr'] = self.args.lr
p['lr_scheduler'] = self.args.lr_scheduler
p['loss_type'] = self.args.loss_type
p['epoch'] = self.args.epochs
p['base_size'] = self.args.base_size
p['crop_size'] = self.args.crop_size

for key, val in p.items():
log_file.write(key + ':' + str(val) + '\n')
log_file.close()

+ 39
- 0
examples/lifelong_learning/RFNet/utils/summaries.py View File

@@ -0,0 +1,39 @@
import os
import torch
from torchvision.utils import make_grid
# from tensorboardX import SummaryWriter
from torch.utils.tensorboard import SummaryWriter
from dataloaders.utils import decode_seg_map_sequence

class TensorboardSummary(object):
def __init__(self, directory):
self.directory = directory

def create_summary(self):
writer = SummaryWriter(log_dir=os.path.join(self.directory))
return writer

def visualize_image(self, writer, dataset, image, target, output, global_step, depth=None):
if depth is None:
grid_image = make_grid(image[:3].clone().cpu().data, 3, normalize=True)
writer.add_image('Image', grid_image, global_step)

grid_image = make_grid(decode_seg_map_sequence(torch.max(output[:3], 1)[1].detach().cpu().numpy(),
dataset=dataset), 3, normalize=False, range=(0, 255))
writer.add_image('Predicted label', grid_image, global_step)
grid_image = make_grid(decode_seg_map_sequence(torch.squeeze(target[:3], 1).detach().cpu().numpy(),
dataset=dataset), 3, normalize=False, range=(0, 255))
writer.add_image('Groundtruth label', grid_image, global_step)
else:
grid_image = make_grid(image[:3].clone().cpu().data, 4, normalize=True)
writer.add_image('Image', grid_image, global_step)

grid_image = make_grid(depth[:3].clone().cpu().data, 4, normalize=True) # normalize=False?
writer.add_image('Depth', grid_image, global_step)

grid_image = make_grid(decode_seg_map_sequence(torch.max(output[:3], 1)[1].detach().cpu().numpy(),
dataset=dataset), 4, normalize=False, range=(0, 255))
writer.add_image('Predicted label', grid_image, global_step)
grid_image = make_grid(decode_seg_map_sequence(torch.squeeze(target[:3], 1).detach().cpu().numpy(),
dataset=dataset), 4, normalize=False, range=(0, 255))
writer.add_image('Groundtruth label', grid_image, global_step)

+ 4
- 0
lib/sedna/algorithms/__init__.py View File

@@ -17,3 +17,7 @@ from . import hard_example_mining
from . import multi_task_learning
from . import unseen_task_detect
from . import reid
from . import knowledge_management
from . import seen_task_learning
from . import unseen_task_detection
from . import unseen_task_processing

+ 18
- 0
lib/sedna/algorithms/knowledge_management/__init__.py View File

@@ -0,0 +1,18 @@
# Copyright 2021 The KubeEdge Authors.
#
# 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 .cloud_knowledge_management import CloudKnowledgeManagement
from .edge_knowledge_management import EdgeKnowledgeManagement
from . import task_update_decision
from . import task_evaluation

+ 141
- 0
lib/sedna/algorithms/knowledge_management/cloud_knowledge_management.py View File

@@ -0,0 +1,141 @@
import os
import time
import tempfile

from sedna.common.log import LOGGER
from sedna.common.class_factory import ClassType, ClassFactory
from sedna.common.file_ops import FileOps
from sedna.common.constant import KBResourceConstant

__all__ = ('CloudKnowledgeManagement', )


@ClassFactory.register(ClassType.KM)
class CloudKnowledgeManagement:
"""
Manage task processing, kb update and task deployment, etc., at cloud.

Parameters:
----------
config: Dict
parameters to initialize an object
"""

def __init__(self, config, **kwargs):
self.last_task_index = kwargs.get("last_task_index", None)
self.cloud_output_url = config.get(
"cloud_output_url", "/tmp")
self.task_index = FileOps.join_path(
self.cloud_output_url, config["task_index"])
self.local_task_index_url = KBResourceConstant.KB_INDEX_NAME.value

task_evaluation = kwargs.get("task_evaluation") or {}
self.task_evaluation = task_evaluation.get(
"method", "TaskEvaluationDefault")
self.task_evaluation_param = task_evaluation.get("param", {})

self.estimator = kwargs.get("estimator") or None
self.log = LOGGER

self.seen_task_key = KBResourceConstant.SEEN_TASK.value
self.unseen_task_key = KBResourceConstant.UNSEEN_TASK.value
self.task_group_key = KBResourceConstant.TASK_GROUPS.value
self.extractor_key = KBResourceConstant.EXTRACTOR.value

def update_kb(self, task_index, kb_server):
if isinstance(task_index, str):
task_index = FileOps.load(task_index)

seen_task_index = task_index.get(self.seen_task_key)
unseen_task_index = task_index.get(self.unseen_task_key)

seen_extractor, seen_task_groups = self._save_task_index(
seen_task_index, kb_server, task_type=self.seen_task_key)
unseen_extractor, unseen_task_groups = self._save_task_index(
unseen_task_index, kb_server, task_type=self.unseen_task_key)

task_info = {
self.seen_task_key: {
self.task_group_key: seen_task_groups,
self.extractor_key: seen_extractor
},
self.unseen_task_key: {
self.task_group_key: unseen_task_groups,
self.extractor_key: unseen_extractor
},
"create_time": str(time.time())
}

fd, name = tempfile.mkstemp()
FileOps.dump(task_info, name)

index_file = kb_server.update_db(name)
if not index_file:
self.log.error("KB update Fail !")
index_file = name

return FileOps.upload(index_file, self.task_index)

def _save_task_index(self, task_index, kb_server, task_type="seen_task"):
extractor = task_index[self.extractor_key]
if isinstance(extractor, str):
extractor = FileOps.load(extractor)
task_groups = task_index[self.task_group_key]

model_upload_key = {}
for task_group in task_groups:
model_file = task_group.model.model
save_model = FileOps.join_path(
self.cloud_output_url, task_type,
os.path.basename(model_file)
)
if model_file not in model_upload_key:
model_upload_key[model_file] = FileOps.upload(
model_file, save_model)

model_file = model_upload_key[model_file]

try:
model = kb_server.upload_file(save_model)
except Exception as err:
self.log.error(
f"Upload task model of {model_file} fail: {err}"
)
model = FileOps.join_path(
self.cloud_output_url,
task_type,
os.path.basename(model_file))

task_group.model.model = model

for _task in task_group.tasks:
_task.model = FileOps.join_path(
self.cloud_output_url, task_type, os.path.basename(model_file))
sample_dir = FileOps.join_path(
self.cloud_output_url, task_type,
f"{_task.samples.data_type}_{_task.entry}.sample")
task_group.samples.save(sample_dir)

try:
sample_dir = kb_server.upload_file(sample_dir)
except Exception as err:
self.log.error(
f"Upload task samples of {_task.entry} fail: {err}")
_task.samples.data_url = sample_dir

save_extractor = FileOps.join_path(
self.cloud_output_url, task_type,
f"{task_type}_{KBResourceConstant.TASK_EXTRACTOR_NAME.value}"
)
extractor = FileOps.dump(extractor, save_extractor)
try:
extractor = kb_server.upload_file(extractor)
except Exception as err:
self.log.error(f"Upload task extractor fail: {err}")

return extractor, task_groups

def evaluate_tasks(self, tasks_detail, **kwargs):
method_cls = ClassFactory.get_cls(
ClassType.KM, self.task_evaluation)(**self.task_evaluation_param)
return method_cls(tasks_detail, **kwargs)

+ 193
- 0
lib/sedna/algorithms/knowledge_management/edge_knowledge_management.py View File

@@ -0,0 +1,193 @@
import os
import time
import tempfile
import threading

from sedna.common.log import LOGGER
from sedna.common.config import Context
from sedna.common.class_factory import ClassType, ClassFactory
from sedna.common.file_ops import FileOps
from sedna.common.constant import KBResourceConstant, K8sResourceKindStatus

__all__ = ('EdgeKnowledgeManagement', )


@ClassFactory.register(ClassType.KM)
class EdgeKnowledgeManagement:
"""
Manage task processing at the edge.

Parameters:
----------
config: Dict
parameters to initialize an object
estimator: Instance
An instance with the high-level API that greatly simplifies
machine learning programming. Estimators encapsulate training,
evaluation, prediction, and exporting for your model.
"""

def __init__(self, config, estimator, **kwargs):
self.edge_output_url = config.get(
"edge_output_url") or "/tmp/edge_output_url"
self.task_index = FileOps.join_path(
self.edge_output_url, config["task_index"])
self.estimator = estimator
self.log = LOGGER

self.seen_task_key = KBResourceConstant.SEEN_TASK.value
self.unseen_task_key = KBResourceConstant.UNSEEN_TASK.value
self.task_group_key = KBResourceConstant.TASK_GROUPS.value
self.extractor_key = KBResourceConstant.EXTRACTOR.value

ModelLoadingThread(self, self.task_index).start()

def update_kb(self, task_index_url):
if isinstance(task_index_url, str):
try:
task_index = FileOps.load(task_index_url)
except Exception as err:
self.log.error(f"{err}")
self.log.error(
"Load task index failed. KB deployment to the edge failed.")
return None
else:
task_index = task_index_url

FileOps.clean_folder(self.edge_output_url)
seen_task_index = task_index.get(self.seen_task_key)
unseen_task_index = task_index.get(self.unseen_task_key)

seen_extractor, seen_task_groups = self._save_task_index(
seen_task_index, task_type=self.seen_task_key)
unseen_extractor, unseen_task_groups = self._save_task_index(
unseen_task_index, task_type=self.unseen_task_key)

task_info = {
self.seen_task_key: {
self.task_group_key: seen_task_groups,
self.extractor_key: seen_extractor
},
self.unseen_task_key: {
self.task_group_key: unseen_task_groups,
self.extractor_key: unseen_extractor
},
"created_time": task_index.get("created_time", str(time.time()))
}

fd, name = tempfile.mkstemp()
FileOps.dump(task_info, name)
return FileOps.upload(name, self.task_index)

def _save_task_index(self, task_index, task_type="seen_task"):
extractor = task_index[self.extractor_key]
if isinstance(extractor, str):
extractor = FileOps.load(extractor)
task_groups = task_index[self.task_group_key]

model_upload_key = {}
for task in task_groups:
model_file = task.model.model
save_model = FileOps.join_path(
self.edge_output_url, task_type,
os.path.basename(model_file)
)
if model_file not in model_upload_key:
model_upload_key[model_file] = FileOps.download(
model_file, save_model)
model_file = model_upload_key[model_file]

task.model.model = save_model

for _task in task.tasks:
_task.model = FileOps.join_path(
self.edge_output_url, task_type, os.path.basename(model_file))
sample_dir = FileOps.join_path(
self.edge_output_url, task_type,
f"{_task.samples.data_type}_{_task.entry}.sample")
_task.samples.data_url = FileOps.download(
_task.samples.data_url, sample_dir)

save_extractor = FileOps.join_path(
self.edge_output_url, task_type,
KBResourceConstant.TASK_EXTRACTOR_NAME.value
)
extractor = FileOps.dump(extractor, save_extractor)

return extractor, task_groups

def save_unseen_samples(self, samples, post_process):
# TODO: save unseen samples to specified directory.
if callable(post_process):
samples = post_process(samples)

fd, name = tempfile.mkstemp()

FileOps.dump(samples, name)
unseen_save_url = FileOps.join_path(
Context.get_parameters(
"unseen_save_url",
self.edge_output_url),
f"unseen_samples_{time.time()}.pkl")
return FileOps.upload(name, unseen_save_url)


class ModelLoadingThread(threading.Thread):
"""Hot task index loading with multithread support"""
MODEL_MANIPULATION_SEM = threading.Semaphore(1)

def __init__(self,
edge_knowledge_management,
task_index,
callback=None
):
self.run_flag = True
hot_update_task_index = Context.get_parameters("MODEL_URLS")
if not hot_update_task_index:
LOGGER.error("As `MODEL_URLS` unset a value, skipped")
self.run_flag = False
if not FileOps.exists(task_index):
LOGGER.error("As local task index has not been loaded, skipped")
self.run_flag = False
model_check_time = int(Context.get_parameters(
"MODEL_POLL_PERIOD_SECONDS", "30")
)
if model_check_time < 1:
LOGGER.warning("Catch an abnormal value in "
"`MODEL_POLL_PERIOD_SECONDS`, fallback with 60")
model_check_time = 30

self.edge_knowledge_management = edge_knowledge_management
self.hot_update_task_index = hot_update_task_index
self.check_time = model_check_time
self.task_index = task_index
self.callback = callback
super(ModelLoadingThread, self).__init__()

def run(self):
while self.run_flag:
time.sleep(self.check_time)
tmp_task_index = FileOps.load(self.hot_update_task_index)
latest_version = tmp_task_index.get("create_time")
current_version = FileOps.load(self.task_index).get("create_time")
if latest_version == current_version:
continue
current_version = latest_version
with self.MODEL_MANIPULATION_SEM:
LOGGER.info(
f"Update model start with version {current_version}")
try:
FileOps.dump(tmp_task_index, self.task_index)
# TODO: update local kb with the latest index.pkl
self.edge_knowledge_management.update_kb(self.task_index)

status = K8sResourceKindStatus.COMPLETED.value
LOGGER.info(f"Update task index complete "
f"with version {self.version}")
except Exception as e:
LOGGER.error(f"fail to update task index: {e}")
status = K8sResourceKindStatus.FAILED.value
if self.callback:
self.callback(
task_info=None, status=status, kind="deploy"
)

+ 15
- 0
lib/sedna/algorithms/knowledge_management/task_evaluation/__init__.py View File

@@ -0,0 +1,15 @@
# Copyright 2021 The KubeEdge Authors.
#
# 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 task_evaluation

+ 80
- 0
lib/sedna/algorithms/knowledge_management/task_evaluation/task_evaluation.py View File

@@ -0,0 +1,80 @@
from sedna.common.config import Context
from sedna.common.log import LOGGER
from sedna.common.class_factory import ClassType, ClassFactory

__all__ = ('TaskEvaluationDefault', )


@ClassFactory.register(ClassType.KM)
class TaskEvaluationDefault:
"""
Evaluated the performance of each seen task and filter seen tasks
based on defined rules.

Parameters
----------
estimator: Instance
An instance with the high-level API that greatly simplifies
machine learning programming. Estimators encapsulate training,
evaluation, prediction, and exporting for your model.
"""

def __init__(self, **kwargs):
self.log = LOGGER

def __call__(self, tasks_detail, **kwargs):
"""
Parameters
----------
tasks_detail: List[Task]
output of module task_update_decision, consisting of results of evaluation.
metrics : function / str
Metrics to assess performance on the task by given prediction.
metrics_param : Dict
parameter for metrics function.
kwargs: Dict
parameters for `estimator` evaluate.

Returns
-------
drop_task: List[str]
names of the tasks which will not to be deployed to the edge.
"""

self.model_filter_operator = Context.get_parameters("operator", ">")
self.model_threshold = float(
Context.get_parameters(
"model_threshold", 0.1))

drop_tasks = []

operator_map = {
">": lambda x, y: x > y,
"<": lambda x, y: x < y,
"=": lambda x, y: x == y,
">=": lambda x, y: x >= y,
"<=": lambda x, y: x <= y,
}
if self.model_filter_operator not in operator_map:
self.log.warn(
f"operator {self.model_filter_operator} use to "
f"compare is not allow, set to <"
)
self.model_filter_operator = "<"
operator_func = operator_map[self.model_filter_operator]

for detail in tasks_detail:
scores = detail.scores
entry = detail.entry
self.log.info(f"{entry} scores: {scores}")
if any(map(lambda x: operator_func(float(x),
self.model_threshold),
scores.values())):
self.log.warn(
f"{entry} will not be deploy because all "
f"scores {self.model_filter_operator} {self.model_threshold}")
drop_tasks.append(entry)
continue
drop_task = ",".join(drop_tasks)

return drop_task

+ 2
- 0
lib/sedna/algorithms/knowledge_management/task_update_decision/__init__.py View File

@@ -0,0 +1,2 @@

from . import task_update_decision

+ 110
- 0
lib/sedna/algorithms/knowledge_management/task_update_decision/task_update_decision.py View File

@@ -0,0 +1,110 @@
# Copyright 2021 The KubeEdge Authors.
#
# 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.

"""
Divide multiple tasks based on data

Parameters
----------
samples: Train data, see `sedna.datasources.BaseDataSource` for more detail.

Returns
-------
tasks: All tasks based on training data.
task_extractor: Model with a method to predicting target tasks
"""

import time

from sedna.common.file_ops import FileOps
from sedna.datasources import BaseDataSource
from sedna.common.class_factory import ClassType, ClassFactory
from sedna.algorithms.seen_task_learning.artifact import Task

__all__ = ('UpdateStrategyDefault', )


@ClassFactory.register(ClassType.KM)
class UpdateStrategyDefault:
"""
Decide processing strategies for different tasks

Parameters
----------
task_index: str or Dict
"""

def __init__(self, task_index, **kwargs):
if isinstance(task_index, str):
task_index = FileOps.load(task_index)
self.task_index = task_index

def __call__(self, samples, task_type):
"""
Parameters
----------
samples: BaseDataSource
seen task samples or unseen task samples to be processed.
task_type: str
"seen_task" or "unseen_task".
See sedna.common.constant.KBResourceConstant for more details.

Returns
-------
self.tasks: List[Task]
tasks to be processed.
task_update_strategies: Dict
strategies to process each task.
"""

if task_type == "seen_task":
task_index = self.task_index["seen_task"]
else:
task_index = self.task_index["unseen_task"]

self.extractor = task_index["extractor"]
task_groups = task_index["task_groups"]

tasks = [task_group.tasks[0] for task_group in task_groups]

task_update_strategies = {}
for task in tasks:
task_update_strategies[task.entry] = {
"raw_data_update": None,
"target_model_update": None,
"task_attr_update": None,
}

x_data = samples.x
y_data = samples.y
d_type = samples.data_type

for task in tasks:
origin = task.meta_attr
_x = [x for x in x_data if origin in x[0]]
_y = [y for y in y_data if origin in y]

task_df = BaseDataSource(data_type=d_type)
task_df.x = _x
task_df.y = _y

task.samples = task_df

task_update_strategies[task.entry] = {
"raw_data_update": samples,
"target_model_update": samples,
"task_attr_update": samples
}

return tasks, task_update_strategies

+ 26
- 0
lib/sedna/algorithms/seen_task_learning/__init__.py View File

@@ -0,0 +1,26 @@
# Copyright 2021 The KubeEdge Authors.
#
# 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.

# train
from . import task_definition
from . import task_relation_discover

# inference
from . import task_remodeling
from . import task_allocation

# result integrate
from . import inference_integrate

from .seen_task_learning import SeenTaskLearning

+ 45
- 0
lib/sedna/algorithms/seen_task_learning/artifact.py View File

@@ -0,0 +1,45 @@
# Copyright 2021 The KubeEdge Authors.
#
# 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 typing import List

__all__ = ('Task', 'TaskGroup', 'Model')


class Task:
def __init__(self, entry, samples, meta_attr=None):
self.entry = entry
self.samples = samples
self.meta_attr = meta_attr
self.test_samples = None # assign on task definition and use in TRD
self.model = None # assign on running
self.result = None # assign on running


class TaskGroup:

def __init__(self, entry, tasks: List[Task]):
self.entry = entry
self.tasks = tasks
self.samples = None # assign with task_relation_discover algorithms
self.model = None # assign on train


class Model:
def __init__(self, index: int, entry, model, result):
self.index = index # integer
self.entry = entry
self.model = model
self.result = result
self.meta_attr = None # assign on running

+ 3
- 0
lib/sedna/algorithms/seen_task_learning/inference_integrate/__init__.py View File

@@ -0,0 +1,3 @@

from . import inference_integrate


+ 57
- 0
lib/sedna/algorithms/seen_task_learning/inference_integrate/inference_integrate.py View File

@@ -0,0 +1,57 @@
# Copyright 2021 The KubeEdge Authors.
#
# 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.

"""
Integrate the inference results of all related tasks
"""

from typing import List

import numpy as np

from sedna.common.class_factory import ClassFactory, ClassType

from ..artifact import Task

__all__ = ('DefaultInferenceIntegrate', )


@ClassFactory.register(ClassType.STP)
class DefaultInferenceIntegrate:
"""
Default calculation algorithm for inference integration

Parameters
----------
models: All models used for sample inference
"""

def __init__(self, models: list, **kwargs):
self.models = models

def __call__(self, tasks: List[Task]):
"""
Parameters
----------
tasks: All tasks with sample result

Returns
-------
result: minimum result
"""
res = {}
for task in tasks:
res.update(dict(zip(task.samples.inx, task.result)))
return np.array([z[1]
for z in sorted(res.items(), key=lambda x: x[0])])

+ 580
- 0
lib/sedna/algorithms/seen_task_learning/seen_task_learning.py View File

@@ -0,0 +1,580 @@
# Copyright 2021 The KubeEdge Authors.
#
# 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.

"""Multiple task transfer learning algorithms"""

import json
import time

import pandas as pd
from sklearn import metrics as sk_metrics

from sedna.datasources import BaseDataSource
from sedna.backend import set_backend
from sedna.common.log import LOGGER
from sedna.common.file_ops import FileOps
from sedna.common.config import Context
from sedna.common.constant import KBResourceConstant
from sedna.common.class_factory import ClassFactory, ClassType

from .artifact import Model, Task, TaskGroup

__all__ = ('SeenTaskLearning',)


class SeenTaskLearning:
"""
An auto machine learning framework for edge-cloud multitask learning

See Also
--------
Train: Data + Estimator -> Task Definition -> Task Relationship Discovery
-> Feature Engineering -> Training
Inference: Data -> Task Allocation -> Feature Engineering
-> Task Remodeling -> Inference

Parameters
----------
estimator : Instance
An instance with the high-level API that greatly simplifies
machine learning programming. Estimators encapsulate training,
evaluation, prediction, and exporting for your model.
task_definition : Dict
Divide multiple tasks based on data,
see `task_jobs.task_definition` for more detail.
task_relationship_discovery : Dict
Discover relationships between all tasks, see
`task_jobs.task_relationship_discovery` for more detail.
seen_task_allocation : Dict
Mining tasks of inference sample,
see `task_jobs.task_mining` for more detail.
task_remodeling : Dict
Remodeling tasks based on their relationships,
see `task_jobs.task_remodeling` for more detail.
inference_integrate : Dict
Integrate the inference results of all related
tasks, see `task_jobs.inference_integrate` for more detail.

Examples
--------
>>> from xgboost import XGBClassifier
>>> from sedna.algorithms.multi_task_learning import MulTaskLearning
>>> estimator = XGBClassifier(objective="binary:logistic")
>>> task_definition = {
"method": "TaskDefinitionByDataAttr",
"param": {"attribute": ["season", "city"]}
}
>>> task_relationship_discovery = {
"method": "DefaultTaskRelationDiscover", "param": {}
}
>>> seen_task_allocation = {
"method": "TaskAllocationByDataAttr",
"param": {"attribute": ["season", "city"]}
}
>>> task_remodeling = None
>>> inference_integrate = {
"method": "DefaultInferenceIntegrate", "param": {}
}
>>> mul_task_instance = MulTaskLearning(
estimator=estimator,
task_definition=task_definition,
task_relationship_discovery=task_relationship_discovery,
seen_task_allocation=seen_task_allocation,
task_remodeling=task_remodeling,
inference_integrate=inference_integrate
)

Notes
-----
All method defined under `task_jobs` and registered in `ClassFactory`.
"""

_method_pair = {
'TaskDefinitionBySVC': 'TaskMiningBySVC',
'TaskDefinitionByDataAttr': 'TaskMiningByDataAttr',
}

def __init__(self,
estimator=None,
task_definition=None,
task_relationship_discovery=None,
seen_task_allocation=None,
task_remodeling=None,
inference_integrate=None
):

self.task_definition = task_definition or {
"method": "TaskDefinitionByDataAttr"
}
self.task_relationship_discovery = task_relationship_discovery or {
"method": "DefaultTaskRelationDiscover"
}
self.seen_task_allocation = seen_task_allocation or {
"method": "TaskAllocationDefault"
}
self.task_remodeling = task_remodeling or {
"method": "DefaultTaskRemodeling"
}
self.inference_integrate = inference_integrate or {
"method": "DefaultInferenceIntegrate"
}

self.seen_models = None
self.seen_extractor = None
self.base_model = estimator
self.seen_task_groups = None

self.min_train_sample = int(Context.get_parameters(
"MIN_TRAIN_SAMPLE", KBResourceConstant.MIN_TRAIN_SAMPLE.value
))

self.seen_task_key = KBResourceConstant.SEEN_TASK.value
self.task_group_key = KBResourceConstant.TASK_GROUPS.value
self.extractor_key = KBResourceConstant.EXTRACTOR.value

self.log = LOGGER

@staticmethod
def _parse_param(param_str):
if not param_str:
return {}
if isinstance(param_str, dict):
return param_str
try:
raw_dict = json.loads(param_str, encoding="utf-8")
except json.JSONDecodeError:
raw_dict = {}
return raw_dict

def _task_definition(self, samples, **kwargs):
"""
Task attribute extractor and multi-task definition
"""
method_name = self.task_definition.get(
"method", "TaskDefinitionByDataAttr"
)
extend_param = self._parse_param(
self.task_definition.get("param")
)
method_cls = ClassFactory.get_cls(
ClassType.STP, method_name)(**extend_param)
return method_cls(samples, **kwargs)

def _task_relationship_discovery(self, tasks):
"""
Merge tasks from task_definition
"""
method_name = self.task_relationship_discovery.get("method")
extend_param = self._parse_param(
self.task_relationship_discovery.get("param")
)
method_cls = ClassFactory.get_cls(
ClassType.STP, method_name)(**extend_param)
return method_cls(tasks)

def _task_allocation(self, samples):
"""
Mining tasks of inference sample base on task attribute extractor
"""
method_name = self.seen_task_allocation.get("method")
extend_param = self._parse_param(
self.seen_task_allocation.get("param")
)

if not method_name:
task_definition = self.task_definition.get(
"method", "TaskDefinitionByDataAttr"
)
method_name = self._method_pair.get(task_definition,
'TaskAllocationByDataAttr')
extend_param = self._parse_param(
self.task_definition.get("param"))

method_cls = ClassFactory.get_cls(ClassType.STP, method_name)(
task_extractor=self.seen_extractor, **extend_param
)
return method_cls(samples=samples)

def _task_remodeling(self, samples, mappings):
"""
Remodeling tasks from task mining
"""
method_name = self.task_remodeling.get("method")
extend_param = self._parse_param(
self.task_remodeling.get("param"))
method_cls = ClassFactory.get_cls(ClassType.STP, method_name)(
models=self.seen_models, **extend_param)
return method_cls(samples=samples, mappings=mappings)

def _inference_integrate(self, tasks):
"""
Aggregate inference results from target models
"""
method_name = self.inference_integrate.get("method")
extend_param = self._parse_param(
self.inference_integrate.get("param"))
method_cls = ClassFactory.get_cls(ClassType.STP, method_name)(
models=self.seen_models, **extend_param)
return method_cls(tasks=tasks) if method_cls else tasks

def _task_process(
self,
task_groups,
train_data=None,
valid_data=None,
callback=None,
**kwargs):
"""
Train seen task samples based on grouped tasks.
"""
feedback = {}
rare_task = []
for i, task in enumerate(task_groups):
if not isinstance(task, TaskGroup):
rare_task.append(i)
self.seen_models.append(None)
self.seen_task_groups.append(None)
continue
if not (task.samples and len(task.samples)
>= self.min_train_sample):
self.seen_models.append(None)
self.seen_task_groups.append(None)
rare_task.append(i)
n = len(task.samples)
LOGGER.info(f"Sample {n} of {task.entry} will be merge")
continue
LOGGER.info(f"MTL Train start {i} : {task.entry}")

model = None
for t in task.tasks: # if model has train in tasks
if not (t.model and t.result):
continue
if isinstance(t.model, str):
model_path = t.model
else:
model_path = t.model.save(model_name=f"{task.entry}.model")

t.model = model_path
model = Model(index=i, entry=t.entry,
model=model_path, result=t.result)
model.meta_attr = t.meta_attr
break
if not model:
model_obj = set_backend(estimator=self.base_model)
res = model_obj.train(train_data=task.samples, **kwargs)
if callback:
res = callback(model_obj, res)
if isinstance(res, str):
model_path = model_obj.save(model_name=f"{task.entry}.pth")
model = Model(index=i, entry=task.entry,
model=model_path, result={})
else:
model_path = model_obj.save(
model_name=f"{task.entry}.model")
model = Model(index=i, entry=task.entry,
model=model_path, result=res)

model.meta_attr = [t.meta_attr for t in task.tasks]
task.model = model
self.seen_models.append(model)
feedback[task.entry] = model.result
self.seen_task_groups.append(task)

if len(rare_task):
model_obj = set_backend(estimator=self.base_model)
res = model_obj.train(train_data=train_data, **kwargs)
model_path = model_obj.save(model_name="global.model")
for i in rare_task:
task = task_groups[i]
entry = getattr(task, 'entry', "global")
if not isinstance(task, TaskGroup):
task = TaskGroup(
entry=entry, tasks=[]
)
model = Model(index=i, entry=entry,
model=model_path, result=res)
model.meta_attr = [t.meta_attr for t in task.tasks]
task.model = model
task.samples = train_data
self.seen_models[i] = model
feedback[entry] = res
self.seen_task_groups[i] = task

task_index = {
self.extractor_key: self.seen_extractor,
self.task_group_key: self.seen_task_groups
}

if valid_data:
feedback, _ = self.evaluate(valid_data, **kwargs)
return feedback, task_index

def train(self, train_data: BaseDataSource,
valid_data: BaseDataSource = None,
post_process=None, **kwargs):
"""
fit for update the knowledge based on training data.

Parameters
----------
train_data : BaseDataSource
Train data, see `sedna.datasources.BaseDataSource` for more detail.
valid_data : BaseDataSource
Valid data, BaseDataSource or None.
post_process : function
function or a registered method, callback after `estimator` train.
kwargs : Dict
parameters for `estimator` training, Like:
`early_stopping_rounds` in Xgboost.XGBClassifier

Returns
-------
feedback : Dict
contain all training result in each tasks.
task_index_url : str
task extractor model path, used for task allocation.
"""

tasks, task_extractor, train_data = self._task_definition(
train_data, model=self.base_model, **kwargs)
self.seen_extractor = task_extractor
task_groups = self._task_relationship_discovery(tasks)
self.seen_models = []
callback = None
if isinstance(post_process, str):
callback = ClassFactory.get_cls(ClassType.CALLBACK, post_process)()
self.seen_task_groups = []

return self._task_process(
task_groups,
train_data=train_data,
valid_data=valid_data,
callback=callback)

def update(self, tasks, task_update_strategies, **kwargs):
"""
Parameters:
----------
tasks: List[Task]
from the output of module task_update_decision
task_update_strategies: object
from the output of module task_update_decision

Returns
-------
task_index : Dict
updated seen task index of knowledge base
"""
if not (self.seen_models and self.seen_extractor):
self.load(kwargs.get("task_index", None))

task_groups = self._task_relationship_discovery(tasks)

# TODO: to fit retraining
self.seen_task_groups = []
self.seen_models = []

feedback = {}
for i, task in enumerate(task_groups):
LOGGER.info(f"MTL Train start {i} : {task.entry}")
for _task in task.tasks:
model_obj = set_backend(estimator=self.base_model)
model_obj.load(_task.model)
res = model_obj.train(train_data=task.samples)
if isinstance(res, str):
model_path = model_obj.save(
model_name=f"{task.entry}.pth")
model = Model(index=i, entry=task.entry,
model=model_path, result={})
else:
model_path = model_obj.save(
model_name=f"{task.entry}.model")
model = Model(index=i, entry=task.entry,
model=model_path, result=res)
break

model.meta_attr = [t.meta_attr for t in task.tasks]
task.model = model
self.seen_models.append(model)
feedback[task.entry] = model.result
self.seen_task_groups.append(task)

task_index = {
self.extractor_key: {"real": 0, "sim": 1},
self.task_group_key: self.seen_task_groups
}

return task_index

def load(self, task_index):
"""
load task_detail (tasks/models etc ...) from task index file.
It'll automatically loaded during `inference` and `evaluation` phases.

Parameters
----------
task_index : str or Dict
task index file path, default self.task_index_url.
"""
assert task_index, "Task index can't be None."

if isinstance(task_index, str):
task_index = FileOps.load(task_index)

self.seen_extractor = task_index[self.seen_task_key][self.extractor_key]
if isinstance(self.seen_extractor, str):
self.seen_extractor = FileOps.load(self.seen_extractor)
self.seen_task_groups = task_index[self.seen_task_key][self.task_group_key]
self.seen_models = [task.model for task in self.seen_task_groups]

def predict(self, data: BaseDataSource,
post_process=None, **kwargs):
"""
predict the result for input data based on training knowledge.

Parameters
----------
data : BaseDataSource
inference sample, see `sedna.datasources.BaseDataSource` for
more detail.
post_process: function
function or a registered method, effected after `estimator`
prediction, like: label transform.
kwargs: Dict
parameters for `estimator` predict, Like:
`ntree_limit` in Xgboost.XGBClassifier

Returns
-------
result : array_like
results array, contain all inference results in each sample.
tasks : List
tasks assigned to each sample.
"""
if not (self.seen_models and self.seen_extractor):
self.load(kwargs.get("task_index", None))

data, mappings = self._task_allocation(samples=data)
samples, models = self._task_remodeling(samples=data,
mappings=mappings
)

callback = None
if post_process:
callback = ClassFactory.get_cls(ClassType.CALLBACK, post_process)()

tasks = []
for inx, df in enumerate(samples):
m = models[inx]
if not isinstance(m, Model):
continue
if isinstance(m.model, str):
evaluator = set_backend(estimator=self.base_model)
evaluator.load(m.model)
else:
evaluator = m.model
pred = evaluator.predict(df.x, **kwargs)
if callable(callback):
pred = callback(pred, df)
task = Task(entry=m.entry, samples=df)
task.result = pred
task.model = m
tasks.append(task)
res = self._inference_integrate(tasks)
return res, tasks

def evaluate(self, data: BaseDataSource,
metrics=None,
metrics_param=None,
**kwargs):
"""
evaluated the performance of each task from training, filter tasks
based on the defined rules.

Parameters
----------
data : BaseDataSource
valid data, see `sedna.datasources.BaseDataSource` for more detail.
metrics : function / str
Metrics to assess performance on the task by given prediction.
metrics_param : Dict
parameter for metrics function.
kwargs: Dict
parameters for `estimator` evaluate, Like:
`ntree_limit` in Xgboost.XGBClassifier

Returns
-------
task_eval_res : Dict
all metric results.
tasks_detail : List[Object]
all metric results in each task.
"""
result, tasks = self.predict(data, **kwargs)
m_dict = {}

if metrics:
if callable(metrics): # if metrics is a function
m_name = getattr(metrics, '__name__', "mtl_eval")
m_dict = {
m_name: metrics
}
elif isinstance(metrics, (set, list)): # if metrics is multiple
for inx, m in enumerate(metrics):
m_name = getattr(m, '__name__', f"mtl_eval_{inx}")
if isinstance(m, str):
m = getattr(sk_metrics, m)
if not callable(m):
continue
m_dict[m_name] = m
elif isinstance(metrics, str): # if metrics is single
m_dict = {
metrics: getattr(sk_metrics, metrics, sk_metrics.log_loss)
}
elif isinstance(metrics, dict): # if metrics with name
for k, v in metrics.items():
if isinstance(v, str):
v = getattr(sk_metrics, v)
if not callable(v):
continue
m_dict[k] = v

if not len(m_dict):
m_dict = {
'precision_score': sk_metrics.precision_score
}
metrics_param = {"average": "micro"}

if isinstance(data.x, pd.DataFrame):
data.x['pred_y'] = result
data.x['real_y'] = data.y
if not metrics_param:
metrics_param = {}
elif isinstance(metrics_param, str):
metrics_param = self._parse_param(metrics_param)
tasks_detail = []
for task in tasks:
sample = task.samples
pred = task.result
scores = {
name: metric(sample.y, pred, **metrics_param)
for name, metric in m_dict.items()
}
task.scores = scores
tasks_detail.append(task)
task_eval_res = {
name: metric(data.y, result, **metrics_param)
for name, metric in m_dict.items()
}
return task_eval_res, tasks_detail

+ 4
- 0
lib/sedna/algorithms/seen_task_learning/task_allocation/__init__.py View File

@@ -0,0 +1,4 @@

from . import task_allocation
from . import task_allocation_by_origin


+ 116
- 0
lib/sedna/algorithms/seen_task_learning/task_allocation/task_allocation.py View File

@@ -0,0 +1,116 @@
# Copyright 2021 The KubeEdge Authors.
#
# 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.

"""
Mining tasks of inference sample base on task attribute extractor

Parameters
----------
samples : infer sample, see `sedna.datasources.BaseDataSource` for more detail.

Returns
-------
allocations : tasks that assigned to each sample
"""

from sedna.datasources import BaseDataSource
from sedna.common.class_factory import ClassFactory, ClassType


__all__ = (
'TaskAllocationBySVC',
'TaskAllocationByDataAttr',
'TaskAllocationDefault',
)


@ClassFactory.register(ClassType.STP)
class TaskAllocationBySVC:
"""
Corresponding to `TaskDefinitionBySVC`

Parameters
----------
task_extractor : Model
SVC Model used to predicting target tasks
"""

def __init__(self, task_extractor, **kwargs):
self.task_extractor = task_extractor

def __call__(self, samples: BaseDataSource):
df = samples.x
allocations = [0, ] * len(df)
legal = list(
filter(lambda col: df[col].dtype == 'float64', df.columns))
if not len(legal):
return allocations

allocations = list(self.task_extractor.predict(df[legal]))
return samples, allocations


@ClassFactory.register(ClassType.STP)
class TaskAllocationByDataAttr:
"""
Corresponding to `TaskDefinitionByDataAttr`

Parameters
----------
task_extractor : Dict
used to match target tasks
attr_filed: List[Metadata]
metadata is usually a class feature
label with a finite values.
"""

def __init__(self, task_extractor, **kwargs):
self.task_extractor = task_extractor
self.attr_filed = kwargs.get("attribute", [])

def __call__(self, samples: BaseDataSource):
df = samples.x
meta_attr = df[self.attr_filed]

allocations = meta_attr.apply(
lambda x: self.task_extractor.get(
"_".join(
map(lambda y: str(x[y]).replace("_", "-").replace(" ", ""),
self.attr_filed)
),
0),
axis=1).values.tolist()
samples.x = df.drop(self.attr_filed, axis=1)
samples.meta_attr = meta_attr
return samples, allocations


@ClassFactory.register(ClassType.STP)
class TaskAllocationDefault:
"""
Task allocation specifically for unstructured data

Parameters
----------
task_extractor : Dict
used to match target tasks
"""

def __init__(self, task_extractor, **kwargs):
self.task_extractor = task_extractor

def __call__(self, samples: BaseDataSource):
allocations = [0] * len(samples)

return samples, allocations

+ 63
- 0
lib/sedna/algorithms/seen_task_learning/task_allocation/task_allocation_by_origin.py View File

@@ -0,0 +1,63 @@
from sedna.datasources import BaseDataSource
from sedna.common.class_factory import ClassFactory, ClassType


@ClassFactory.register(ClassType.STP)
class TaskAllocationByOrigin:
"""
Corresponding to `TaskDefinitionByOrigin`

Parameters
----------
task_extractor : Dict
used to match target tasks
origins: List[Metadata]
metadata is usually a class feature
label with a finite values.
"""

def __init__(self, task_extractor, **kwargs):
self.task_extractor = task_extractor
self.default_origin = kwargs.get("default", None)

def __call__(self, samples: BaseDataSource):
if self.default_origin:
return samples, [int(self.task_extractor.get(
self.default_origin))] * len(samples.x)

cities = [
"aachen",
"berlin",
"bochum",
"bremen",
"cologne",
"darmstadt",
"dusseldorf",
"erfurt",
"hamburg",
"hanover",
"jena",
"krefeld",
"monchengladbach",
"strasbourg",
"stuttgart",
"tubingen",
"ulm",
"weimar",
"zurich"]

sample_origins = []
for _x in samples.x:
is_real = False
for city in cities:
if city in _x[0]:
is_real = True
sample_origins.append("real")
break
if not is_real:
sample_origins.append("sim")

allocations = [int(self.task_extractor.get(sample_origin))
for sample_origin in sample_origins]

return samples, allocations

+ 3
- 0
lib/sedna/algorithms/seen_task_learning/task_definition/__init__.py View File

@@ -0,0 +1,3 @@

from . import task_definition
from . import task_definition_by_origin

+ 213
- 0
lib/sedna/algorithms/seen_task_learning/task_definition/task_definition.py View File

@@ -0,0 +1,213 @@
# Copyright 2021 The KubeEdge Authors.
#
# 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.

"""
Divide multiple tasks based on data

Parameters
----------
samples: Train data, see `sedna.datasources.BaseDataSource` for more detail.

Returns
-------
tasks: All tasks based on training data.
task_extractor: Model with a method to predicting target tasks
"""

from typing import List, Any, Tuple
import time
import numpy as np
import pandas as pd

from sedna.datasources import BaseDataSource
from sedna.common.class_factory import ClassType, ClassFactory

from ..artifact import Task


__all__ = (
'TaskDefinitionBySVC',
'TaskDefinitionByDataAttr',
'TaskDefinitionByCluster')


@ClassFactory.register(ClassType.STP)
class TaskDefinitionBySVC:
"""
Dividing datasets with `AgglomerativeClustering` based on kernel distance,
Using SVC to fit the clustering result.

Parameters
----------
n_class: int or None
The number of clusters to find, default=2.
"""

def __init__(self, **kwargs):
n_class = kwargs.get("n_class", "")
self.n_class = max(2, int(n_class)) if str(n_class).isdigit() else 2

def __call__(self,
samples: BaseDataSource) -> Tuple[List[Task],
Any,
BaseDataSource]:
from sklearn.svm import SVC
from sklearn.cluster import AgglomerativeClustering

d_type = samples.data_type
x_data = samples.x
y_data = samples.y
if not isinstance(x_data, pd.DataFrame):
raise TypeError(f"{d_type} data should only be pd.DataFrame")
tasks = []
legal = list(
filter(lambda col: x_data[col].dtype == 'float64', x_data.columns))

df = x_data[legal]
c1 = AgglomerativeClustering(n_clusters=self.n_class).fit_predict(df)
c2 = SVC(gamma=0.01)
c2.fit(df, c1)

for task in range(self.n_class):
g_attr = f"svc_{task}"
task_df = BaseDataSource(data_type=d_type)
task_df.x = x_data.iloc[np.where(c1 == task)]
task_df.y = y_data.iloc[np.where(c1 == task)]

task_obj = Task(entry=g_attr, samples=task_df)
tasks.append(task_obj)
samples.x = df
return tasks, c2, samples


@ClassFactory.register(ClassType.STP)
class TaskDefinitionByDataAttr:
"""
Dividing datasets based on the common attributes,
generally used for structured data.

Parameters
----------
attribute: List[Metadata]
metadata is usually a class feature label with a finite values.
"""

def __init__(self, **kwargs):
self.attr_filed = kwargs.get("attribute", [])

def __call__(self,
samples: BaseDataSource, **kwargs) -> Tuple[List[Task],
Any,
BaseDataSource]:
tasks = []
d_type = samples.data_type
x_data = samples.x
y_data = samples.y
if not isinstance(x_data, pd.DataFrame):
raise TypeError(f"{d_type} data should only be pd.DataFrame")

_inx = 0
task_index = {}
for meta_attr, df in x_data.groupby(self.attr_filed):
if isinstance(meta_attr, (list, tuple, set)):
g_attr = "_".join(
map(lambda x: str(x).replace("_", "-"), meta_attr))
meta_attr = list(meta_attr)
else:
g_attr = str(meta_attr).replace("_", "-")
meta_attr = [meta_attr]
g_attr = g_attr.replace(" ", "")
if g_attr in task_index:
old_task = tasks[task_index[g_attr]]
old_task.x = pd.concat([old_task.x, df])
old_task.y = pd.concat([old_task.y, y_data.iloc[df.index]])
continue
task_index[g_attr] = _inx

task_df = BaseDataSource(data_type=d_type)
task_df.x = df.drop(self.attr_filed, axis=1)
task_df.y = y_data.iloc[df.index]

task_obj = Task(entry=g_attr, samples=task_df, meta_attr=meta_attr)
tasks.append(task_obj)
_inx += 1
x_data.drop(self.attr_filed, axis=1, inplace=True)
samples = BaseDataSource(data_type=d_type)
samples.x = x_data
samples.y = y_data
return tasks, task_index, samples


@ClassFactory.register(ClassType.STP)
class TaskDefinitionByCluster:
"""
Dividing datasets with all sorts of clustering methods.

Parameters
----------
n_class: int or None
The number of clusters to find, default=1.
"""

def __init__(self, **kwargs):
self.n_class = int(kwargs.get("n_class", 1))
self.train_ratio = float(kwargs.get("train_ratio", 0.8))

def __call__(self,
samples: BaseDataSource, **kwargs) -> Tuple[List[Task],
Any,
BaseDataSource]:
from sklearn.svm import SVC
model = kwargs.get("model")

tasks = []
c2 = SVC(gamma=0.01)
partition_length = int(len(samples.x) / self.n_class)

for i in range(self.n_class):
# sample = BaseDataSource()
# sample.x = samples.x[i * partition_length: (i + 1) * partition_length]
# sample.y = samples.y[i * partition_length: (i + 1) * partition_length]
#
# train_num = int(len(sample.x) * train_ratio)
#
# train_samples = BaseDataSource(data_type="train")
# train_samples.x = sample.x[:train_num]
# train_samples.y = sample.y[:train_num]
#
# test_samples = BaseDataSource(data_type="eval")
# test_samples.x = sample.x[train_num:]
# test_samples.y = sample.y[train_num:]

train_num = int(len(samples.x) * self.train_ratio)
train_samples = BaseDataSource(data_type="train")
train_samples.x = samples.x[:train_num]
train_samples.y = samples.y[:train_num]

test_samples = BaseDataSource(data_type="eval")
test_samples.x = samples.x[train_num:]
test_samples.y = samples.y[train_num:]

model_url = model.train(train_samples, test_samples)
model.load(model_url)
result = model.evaluate(test_samples, checkpoint_path=model_url)

g_attr = f"svc_{i}_{time.time()}"
task_obj = Task(entry=g_attr, samples=train_samples)
task_obj.test_samples = test_samples
task_obj.model = model_url
task_obj.result = result
tasks.append(task_obj)

return tasks, c2, samples

+ 80
- 0
lib/sedna/algorithms/seen_task_learning/task_definition/task_definition_by_origin.py View File

@@ -0,0 +1,80 @@
from typing import List, Any, Tuple

from sedna.datasources import BaseDataSource
from sedna.common.class_factory import ClassType, ClassFactory

from ..artifact import Task


@ClassFactory.register(ClassType.STP)
class TaskDefinitionByOrigin:
"""
Dividing datasets based on the their origins.

Parameters
----------
origins: List[Metadata]
metadata is usually a class feature label with a finite values.
"""

def __init__(self, **kwargs):
self.origins = kwargs.get("origins", ["real", "sim"])

def __call__(self,
samples: BaseDataSource, **kwargs) -> Tuple[List[Task],
Any,
BaseDataSource]:
cities = [
"aachen",
"berlin",
"bochum",
"bremen",
"cologne",
"darmstadt",
"dusseldorf",
"erfurt",
"hamburg",
"hanover",
"jena",
"krefeld",
"monchengladbach",
"strasbourg",
"stuttgart",
"tubingen",
"ulm",
"weimar",
"zurich"]

tasks = []
d_type = samples.data_type
x_data = samples.x
y_data = samples.y

task_index = dict(zip(self.origins, range(len(self.origins))))

real_df = BaseDataSource(data_type=d_type)
real_df.x, real_df.y = [], []
sim_df = BaseDataSource(data_type=d_type)
sim_df.x, sim_df.y = [], []

for i in range(samples.num_examples()):
is_real = False
for city in cities:
if city in x_data[i][0]:
is_real = True
real_df.x.append(x_data[i])
real_df.y.append(y_data[i])
break
if not is_real:
sim_df.x.append(x_data[i])
sim_df.y.append(y_data[i])

g_attr = "real_semantic_segamentation_model"
task_obj = Task(entry=g_attr, samples=real_df, meta_attr="real")
tasks.append(task_obj)

g_attr = "sim_semantic_segamentation_model"
task_obj = Task(entry=g_attr, samples=sim_df, meta_attr="sim")
tasks.append(task_obj)

return tasks, task_index, samples

+ 3
- 0
lib/sedna/algorithms/seen_task_learning/task_relation_discover/__init__.py View File

@@ -0,0 +1,3 @@

from . import task_relation_discover


+ 52
- 0
lib/sedna/algorithms/seen_task_learning/task_relation_discover/task_relation_discover.py View File

@@ -0,0 +1,52 @@
# Copyright 2021 The KubeEdge Authors.
#
# 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.

"""
Discover relationships between all tasks

Parameters
----------
tasks :all tasks form `task_definition`

Returns
-------
task_groups : List of groups which including at least 1 task.
"""

from typing import List

from sedna.common.class_factory import ClassType, ClassFactory

from ..artifact import Task, TaskGroup


__all__ = ('DefaultTaskRelationDiscover',)


@ClassFactory.register(ClassType.STP)
class DefaultTaskRelationDiscover:
"""
Assume that each task is independent of each other
"""

def __init__(self, **kwargs):
pass

def __call__(self, tasks: List[Task]) -> List[TaskGroup]:
tgs = []
for task in tasks:
tg_obj = TaskGroup(entry=task.entry, tasks=[task])
tg_obj.samples = task.samples
tgs.append(tg_obj)
return tgs

+ 2
- 0
lib/sedna/algorithms/seen_task_learning/task_remodeling/__init__.py View File

@@ -0,0 +1,2 @@

from . import task_remodeling

+ 78
- 0
lib/sedna/algorithms/seen_task_learning/task_remodeling/task_remodeling.py View File

@@ -0,0 +1,78 @@
# Copyright 2021 The KubeEdge Authors.
#
# 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.

"""
Remodeling tasks based on their relationships

Parameters
----------
mappings :all assigned tasks get from the `task_mining`
samples : input samples

Returns
-------
models : List of groups which including at least 1 task.
"""

from typing import List

import numpy as np
import pandas as pd

from sedna.datasources import BaseDataSource
from sedna.common.class_factory import ClassFactory, ClassType

__all__ = ('DefaultTaskRemodeling',)


@ClassFactory.register(ClassType.STP)
class DefaultTaskRemodeling:
"""
Assume that each task is independent of each other
"""

def __init__(self, models: list, **kwargs):
self.models = models

def __call__(self, samples: BaseDataSource, mappings: List):
"""
Grouping based on assigned tasks
"""
mappings = np.array(mappings)
data, models = [], []
d_type = samples.data_type
for m in np.unique(mappings):
task_df = BaseDataSource(data_type=d_type)
_inx = np.where(mappings == m)
if isinstance(samples.x, pd.DataFrame):
task_df.x = samples.x.iloc[_inx]
else:
task_df.x = np.array(samples.x)[_inx]
if d_type != "test":
if isinstance(samples.x, pd.DataFrame):
task_df.y = samples.y.iloc[_inx]
else:
task_df.y = np.array(samples.y)[_inx]
task_df.inx = _inx[0].tolist()
if samples.meta_attr is not None:
task_df.meta_attr = np.array(samples.meta_attr)[_inx]
data.append(task_df)
# TODO: if m is out of index
try:
model = self.models[m]
except Exception as err:
print(f"self.models[{m}] not exists. {err}")
model = self.models[0]
models.append(model)
return data, models

+ 16
- 0
lib/sedna/algorithms/unseen_task_detection/__init__.py View File

@@ -0,0 +1,16 @@
# Copyright 2021 The KubeEdge Authors.
#
# 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 unseen_sample_recognition
from . import unseen_sample_re_recognition

+ 15
- 0
lib/sedna/algorithms/unseen_task_detection/unseen_sample_re_recognition/__init__.py View File

@@ -0,0 +1,15 @@
# Copyright 2021 The KubeEdge Authors.
#
# 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 unseen_sample_re_recognition

+ 43
- 0
lib/sedna/algorithms/unseen_task_detection/unseen_sample_re_recognition/unseen_sample_re_recognition.py View File

@@ -0,0 +1,43 @@
from sedna.datasources import BaseDataSource
from sedna.common.class_factory import ClassFactory, ClassType

__all__ = ('SampleReRegonitionDefault', )


@ClassFactory.register(ClassType.UTD)
class SampleReRegonitionDefault:
# TODO: to be completed
'''
Divide inference samples into seen tasks and unseen tasks.

Parameters
----------
task_index: str or Dict
'''

def __init__(self, task_index, **kwargs):
pass

def __call__(self, samples: BaseDataSource):
'''
Parameters
----------
samples: training samples

Returns
-------
seen_task_samples: BaseDataSource
unseen_task_samples: BaseDataSource
'''

sample_num = int(len(samples.x) / 2)

seen_task_samples = BaseDataSource(data_type=samples.data_type)
seen_task_samples.x = samples.x[:sample_num]
seen_task_samples.y = samples.y[:sample_num]

unseen_task_samples = BaseDataSource(data_type=samples.data_type)
unseen_task_samples.x = samples.x[sample_num:]
unseen_task_samples.y = samples.y[sample_num:]

return seen_task_samples, unseen_task_samples

+ 15
- 0
lib/sedna/algorithms/unseen_task_detection/unseen_sample_recognition/__init__.py View File

@@ -0,0 +1,15 @@
# Copyright 2021 The KubeEdge Authors.
#
# 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 unseen_sample_recognition

+ 95
- 0
lib/sedna/algorithms/unseen_task_detection/unseen_sample_recognition/unseen_sample_recognition.py View File

@@ -0,0 +1,95 @@
from typing import Tuple

from sedna.common.file_ops import FileOps
from sedna.datasources import BaseDataSource
from sedna.common.class_factory import ClassFactory, ClassType

__all__ = ('SampleRegonitionDefault', 'SampleRegonitionByRFNet')


@ClassFactory.register(ClassType.UTD)
class SampleRegonitionDefault:
'''
Divide inference samples into seen samples and unseen samples

Parameters
----------
task_index: str or dict
knowledge base index which includes indexes of tasks, samples and etc.
'''

def __init__(self, task_index, **kwargs):
if isinstance(task_index, str) and FileOps.exists(task_index):
self.task_index = FileOps.load(task_index)
else:
self.task_index = task_index

def __call__(self,
samples: BaseDataSource) -> Tuple[BaseDataSource,
BaseDataSource]:
'''
Parameters
----------
samples : BaseDataSource
inference samples

Returns
-------
seen_task_samples: BaseDataSource
unseen_task_samples: BaseDataSource
'''
sample_num = int(len(samples.x) / 2)

seen_task_samples = BaseDataSource(data_type=samples.data_type)
seen_task_samples.x = samples.x[sample_num:]

unseen_task_samples = BaseDataSource(data_type=samples.data_type)
unseen_task_samples.x = samples.x[:sample_num]

return seen_task_samples, unseen_task_samples


@ClassFactory.register(ClassType.UTD)
class SampleRegonitionByRFNet:
'''
Divide inference samples into seen samples and unseen samples by confidence.

Parameters
----------
task_index: str or dict
knowledge base index which includes indexes of tasks, samples and etc.
'''

def __init__(self, task_index, **kwargs):
if isinstance(task_index, str) and FileOps.exists(task_index):
self.task_index = FileOps.load(task_index)
else:
self.task_index = task_index

self.validator = kwargs.get("validator")

def __call__(self, samples: BaseDataSource, **
kwargs) -> Tuple[BaseDataSource, BaseDataSource]:
'''
Parameters
----------
samples : BaseDataSource
inference samples

Returns
-------
seen_task_samples: BaseDataSource
unseen_task_samples: BaseDataSource
'''
from torch.utils.data import DataLoader

self.validator.test_loader = DataLoader(
samples.x, batch_size=1, shuffle=False)

seen_task_samples = BaseDataSource(data_type=samples.data_type)
unseen_task_samples = BaseDataSource(data_type=samples.data_type)

seen_task_samples.x, unseen_task_samples.x = self.validator.task_divide()

return seen_task_samples, unseen_task_samples


+ 2
- 0
lib/sedna/algorithms/unseen_task_processing/__init__.py View File

@@ -0,0 +1,2 @@
from . import unseen_task_allocation
from .unseen_task_processing import UnseenTaskProcessing

+ 1
- 0
lib/sedna/algorithms/unseen_task_processing/unseen_task_allocation/__init__.py View File

@@ -0,0 +1 @@
from . import unseen_task_allocation

+ 46
- 0
lib/sedna/algorithms/unseen_task_processing/unseen_task_allocation/unseen_task_allocation.py View File

@@ -0,0 +1,46 @@
from sedna.common.log import LOGGER
from sedna.datasources import BaseDataSource
from sedna.common.class_factory import ClassFactory, ClassType

__all__ = ('UnseenTaskAllocationDefault', )


@ClassFactory.register(ClassType.UTP)
class UnseenTaskAllocationDefault:
# TODO: to be completed
"""
Task allocation for unseen data

Parameters
----------
task_extractor : Dict
used to match target tasks
"""

def __init__(self, task_extractor, **kwargs):
self.task_extractor = task_extractor
self.log = LOGGER

def __call__(self, samples: BaseDataSource):
'''
Parameters
----------
samples: samples to be allocated

Returns
-------
samples: BaseDataSource
allocations: List
allocation decision for actual inference
'''

try:
allocations = [self.task_extractor.fit(
sample) for sample in samples.x]
except Exception as err:
self.log.exception(err)

allocations = [0] * len(samples)
self.log.info("Use the first task to inference all the samples.")

return samples, allocations

+ 157
- 0
lib/sedna/algorithms/unseen_task_processing/unseen_task_processing.py View File

@@ -0,0 +1,157 @@
import json

from sedna.backend import set_backend
from sedna.common.file_ops import FileOps
from sedna.common.constant import KBResourceConstant
from sedna.common.class_factory import ClassFactory, ClassType

__all__ = ('UnseenTaskProcessing', )


class UnseenTaskProcessing:
'''
Process unseen tasks given task update strategies

Parameters:
----------
estimator: Instance
An instance with the high-level API that greatly simplifies
machine learning programming. Estimators encapsulate training,
evaluation, prediction, and exporting for your model.
cloud_knowledge_management: Instance of class CloudKnowledgeManagement
unseen_task_allocation: Dict
Mining tasks of unseen inference sample.
'''

def __init__(self, estimator, config,
cloud_knowledge_management,
edge_knowledge_management,
unseen_task_allocation,
**kwargs):
self.estimator = set_backend(estimator=estimator, config=config)
self.cloud_knowledge_management = cloud_knowledge_management
self.edge_knowledge_management = edge_knowledge_management

self.unseen_task_allocation = unseen_task_allocation or {
"method": "UnseenTaskAllocationDefault"
}
self.unseen_models = None
self.unseen_extractor = None
self.unseen_task_groups = None
self.unseen_task_key = KBResourceConstant.UNSEEN_TASK.value
self.task_group_key = KBResourceConstant.TASK_GROUPS.value
self.extractor_key = KBResourceConstant.EXTRACTOR.value

@staticmethod
def _parse_param(param_str):
if not param_str:
return {}
if isinstance(param_str, dict):
return param_str
try:
raw_dict = json.loads(param_str, encoding="utf-8")
except json.JSONDecodeError:
raw_dict = {}
return raw_dict

def _unseen_task_allocation(self, samples):
"""
Mining unseen tasks of inference sample base on task attribute extractor
"""
method_name = self.unseen_task_allocation.get("method")
extend_param = self._parse_param(
self.unseen_task_allocation.get("param")
)

method_cls = ClassFactory.get_cls(ClassType.UTP, method_name)(
task_extractor=self.unseen_extractor, **extend_param
)
return method_cls(samples=samples)

def initialize(self):
"""
Intialize unseen task groups

Returns:
res: Dict
evaluation result.
task_index: Dict or str
unseen task index which includes models, samples, extractor and etc.
"""
task_index = {
self.extractor_key: None,
self.task_group_key: []
}

res = {}
return res, task_index

def update(self, tasks, task_update_strategies, **kwargs):
"""
Parameters:
----------
tasks: List[Task]
from the output of module task_update_decision
task_update_strategies: Dict
from the output of module task_update_decision

Returns
-------
task_index : Dict
updated unseen task index of knowledge base
"""
task_index = {
self.extractor_key: None,
self.task_group_key: []
}

return task_index

def predict(self, data, post_process=None, **kwargs):
"""
Predict the result for unseen data.

Parameters
----------
data : BaseDataSource
inference sample, see `sedna.datasources.BaseDataSource` for
more detail.
post_process: function
function or a registered method, effected after `estimator`
prediction, like: label transform.

Returns
-------
result : array_like
results array, contain all inference results in each sample.
tasks : List
tasks assigned to each sample.
"""
if not self.unseen_task_groups and not self.unseen_models:
self.load(self.edge_knowledge_management.task_index)

samples, mappings = self._unseen_task_allocation(data)
result = {}
tasks = []

return result, tasks

def load(self, task_index):
"""
load task_detail (tasks/models etc ...) from task index file.
It'll automatically loaded during `inference` phases.

Parameters
----------
task_index_url : str
task index file path.
"""
assert task_index is not None, "task index url is None!!!"
if isinstance(task_index, str):
task_index = FileOps.load(task_index)

self.unseen_extractor = task_index[self.unseen_task_key][self.extractor_key]
if isinstance(self.unseen_extractor, str):
self.unseen_extractor = FileOps.load(self.unseen_extractor)
self.unseen_task_groups = task_index[self.unseen_task_key][self.task_group_key]
self.unseen_models = [task.model for task in self.unseen_task_groups]

+ 9
- 0
lib/sedna/backend/base.py View File

@@ -58,6 +58,15 @@ class BackendBase:
varkw = self.parse_kwargs(fit_method, **kwargs)
return fit_method(*args, **varkw)

def update(self, *args, **kwargs):
"""update model by training."""
if callable(self.estimator):
varkw = self.parse_kwargs(self.estimator, **kwargs)
self.estimator = self.estimator(**varkw)
fit_method = getattr(self.estimator, "fit", self.estimator.update)
varkw = self.parse_kwargs(fit_method, **kwargs)
return fit_method(*args, **varkw)

def predict(self, *args, **kwargs):
"""Inference model."""
varkw = self.parse_kwargs(self.estimator.predict, **kwargs)


+ 5
- 0
lib/sedna/common/class_factory.py View File

@@ -37,6 +37,11 @@ class ClassType:
DATASET = 'data_process'
CALLBACK = 'post_process_callback'

# TODO
UTP = 'unseen_task_processing'
KM = 'knowledge_management'
STP = 'seen_task_processing'


class ClassFactory(object):
"""


+ 5
- 0
lib/sedna/common/constant.py View File

@@ -48,3 +48,8 @@ class KBResourceConstant(Enum):
MIN_TRAIN_SAMPLE = 10
KB_INDEX_NAME = "index.pkl"
TASK_EXTRACTOR_NAME = "task_attr_extractor.pkl"
SEEN_TASK = "seen_task"
UNSEEN_TASK = "unseen_task"
TASK_GROUPS = "task_groups"
EXTRACTOR = "extractor"
EDGE_KB_DIR = "/var/lib/sedna/kb"

+ 280
- 191
lib/sedna/core/lifelong_learning/lifelong_learning.py View File

@@ -12,18 +12,22 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import tempfile
import numpy as np

from sedna.backend import set_backend
from sedna.core.base import JobBase
from sedna.common.file_ops import FileOps
from sedna.common.constant import K8sResourceKind, K8sResourceKindStatus
from sedna.common.constant import KBResourceConstant
from sedna.common.config import Context
from sedna.datasources import BaseDataSource
from sedna.common.class_factory import ClassType, ClassFactory
from sedna.algorithms.multi_task_learning import MulTaskLearning
from sedna.algorithms.seen_task_learning.seen_task_learning import SeenTaskLearning
from sedna.algorithms.unseen_task_processing import UnseenTaskProcessing
from sedna.service.client import KBClient
from sedna.algorithms.knowledge_management.cloud_knowledge_management \
import CloudKnowledgeManagement
from sedna.algorithms.knowledge_management.edge_knowledge_management \
import EdgeKnowledgeManagement


class LifelongLearning(JobBase):
@@ -42,23 +46,31 @@ class LifelongLearning(JobBase):
evaluation, prediction, and exporting for your model.
task_definition : Dict
Divide multiple tasks based on data,
see `task_jobs.task_definition` for more detail.
see `task_definition.task_definition` for more detail.
task_relationship_discovery : Dict
Discover relationships between all tasks, see
`task_jobs.task_relationship_discovery` for more detail.
task_mining : Dict
Mining tasks of inference sample,
see `task_jobs.task_mining` for more detail.
`task_relationship_discovery.task_relationship_discovery` for more detail.
task_allocation : Dict
Mining seen tasks of inference sample,
see `task_allocation.task_allocation` for more detail.
task_remodeling : Dict
Remodeling tasks based on their relationships,
see `task_jobs.task_remodeling` for more detail.
see `task_remodeling.task_remodeling` for more detail.
inference_integrate : Dict
Integrate the inference results of all related
tasks, see `task_jobs.inference_integrate` for more detail.
unseen_task_detect: Dict
unseen task detect algorithms with parameters which has registered to
ClassFactory, see `sedna.algorithms.unseen_task_detect` for more detail

tasks, see `inference_integrate.inference_integrate` for more detail.
task_update_decision: Dict
Task update strategy making algorithms,
see 'knowledge_management.task_update_decision.task_update_decision' for more detail.
unseen_task_allocation: Dict
Mining unseen tasks of inference sample,
see `unseen_task_processing.unseen_task_allocation.unseen_task_allocation` for more detail.
unseen_sample_recognition: Dict
Dividing inference samples into seen tasks and unseen tasks,
see 'unseen_task_processing.unseen_sample_recognition.unseen_sample_recognition' for more detail.
unseen_sample_re_recognition: Dict
Dividing unseen training samples into seen tasks and unseen tasks,
see 'unseen_task_processing.unseen_sample_re_recognition.unseen_sample_re_recognition' for more detail.

Examples
--------
@@ -78,17 +90,29 @@ class LifelongLearning(JobBase):
>>> inference_integrate = {
"method": "DefaultInferenceIntegrate", "param": {}
}
>>> unseen_task_detect = {
"method": "TaskAttrFilter", "param": {}
>>> task_update_decision = {
"method": "UpdateStrategyDefault", "param": {}
}
>>> unseen_task_allocation = {
"method": "UnseenTaskAllocationDefault", "param": {}
}
>>> unseen_sample_recognition = {
"method": "SampleRegonitionDefault", "param": {}
}
>>> unseen_sample_re_recognition = {
"method": "SampleReRegonitionDefault", "param": {}
}
>>> ll_jobs = LifelongLearning(
estimator=estimator,
task_definition=task_definition,
task_relationship_discovery=task_relationship_discovery,
task_mining=task_mining,
task_remodeling=task_remodeling,
inference_integrate=inference_integrate,
unseen_task_detect=unseen_task_detect
estimator,
task_definition=None,
task_relationship_discovery=None,
task_allocation=None,
task_remodeling=None,
inference_integrate=None,
task_update_decision=None,
unseen_task_allocation=None,
unseen_sample_recognition=None,
unseen_sample_re_recognition=None,
)
"""

@@ -96,37 +120,72 @@ class LifelongLearning(JobBase):
estimator,
task_definition=None,
task_relationship_discovery=None,
task_mining=None,
task_allocation=None,
task_remodeling=None,
inference_integrate=None,
unseen_task_detect=None):

if not task_definition:
task_definition = {
"method": "TaskDefinitionByDataAttr"
}
if not unseen_task_detect:
unseen_task_detect = {
"method": "TaskAttrFilter"
}
e = MulTaskLearning(
task_update_decision=None,
unseen_task_allocation=None,
unseen_sample_recognition=None,
unseen_sample_re_recognition=None
):

e = SeenTaskLearning(
estimator=estimator,
task_definition=task_definition,
task_relationship_discovery=task_relationship_discovery,
task_mining=task_mining,
seen_task_allocation=task_allocation,
task_remodeling=task_remodeling,
inference_integrate=inference_integrate)
self.unseen_task_detect = unseen_task_detect.get("method",
"TaskAttrFilter")
self.unseen_task_detect_param = e._parse_param(
unseen_task_detect.get("param", {})
inference_integrate=inference_integrate
)

self.unseen_sample_recognition = unseen_sample_recognition or {
"method": "SampleRegonitionDefault"
}
self.unseen_sample_recognition_param = e._parse_param(
self.unseen_sample_recognition.get("param", {}))

self.unseen_sample_re_recognition = unseen_sample_re_recognition or {
"method": "SampleReRegonitionDefault"
}
self.unseen_sample_re_recognition_param = e._parse_param(
self.unseen_sample_re_recognition.get("param", {}))

self.task_update_decision = task_update_decision or {
"method": "UpdateStrategyDefault"
}
self.task_update_decision_param = e._parse_param(
self.task_update_decision.get("param", {})
)

config = dict(
ll_kb_server=Context.get_parameters("KB_SERVER"),
output_url=Context.get_parameters("OUTPUT_URL", "/tmp")
)
output_url=Context.get_parameters(
"OUTPUT_URL",
"/tmp"),
cloud_output_url=Context.get_parameters(
"OUTPUT_URL",
"/tmp"),
edge_output_url=Context.get_parameters(
"EDGE_OUTPUT_URL",
KBResourceConstant.EDGE_KB_DIR.value),
task_index=KBResourceConstant.KB_INDEX_NAME.value)

self.cloud_knowledge_management = CloudKnowledgeManagement(
config, estimator=e)

self.edge_knowledge_management = EdgeKnowledgeManagement(
config, estimator=e)

self.unseen_task_processing = UnseenTaskProcessing(
estimator,
config,
self.cloud_knowledge_management,
self.edge_knowledge_management,
unseen_task_allocation)

task_index = FileOps.join_path(config['output_url'],
KBResourceConstant.KB_INDEX_NAME.value)

config['task_index'] = task_index
super(LifelongLearning, self).__init__(
estimator=e, config=config
@@ -165,95 +224,113 @@ class LifelongLearning(JobBase):
if post_process is not None:
callback_func = ClassFactory.get_cls(
ClassType.CALLBACK, post_process)
res, task_index_url = self.estimator.train(
res, seen_task_index = self.estimator.train(
train_data=train_data,
valid_data=valid_data,
**kwargs
) # todo: Distinguishing incremental update and fully overwrite

if isinstance(task_index_url, str) and FileOps.exists(task_index_url):
task_index = FileOps.load(task_index_url)
else:
task_index = task_index_url
unseen_res, unseen_task_index = self.unseen_task_processing.initialize()

extractor = task_index['extractor']
task_groups = task_index['task_groups']

model_upload_key = {}
for task in task_groups:
model_file = task.model.model
save_model = FileOps.join_path(
self.config.output_url,
os.path.basename(model_file)
)
if model_file not in model_upload_key:
model_upload_key[model_file] = FileOps.upload(model_file,
save_model)
model_file = model_upload_key[model_file]

try:
model = self.kb_server.upload_file(save_model)
except Exception as err:
self.log.error(
f"Upload task model of {model_file} fail: {err}"
)
model = set_backend(
estimator=self.estimator.estimator.base_model
)
model.load(model_file)
task.model.model = model

for _task in task.tasks:
sample_dir = FileOps.join_path(
self.config.output_url,
f"{_task.samples.data_type}_{_task.entry}.sample")
task.samples.save(sample_dir)
try:
sample_dir = self.kb_server.upload_file(sample_dir)
except Exception as err:
self.log.error(
f"Upload task samples of {_task.entry} fail: {err}")
_task.samples.data_url = sample_dir

save_extractor = FileOps.join_path(
self.config.output_url,
KBResourceConstant.TASK_EXTRACTOR_NAME.value
)
extractor = FileOps.dump(extractor, save_extractor)
try:
extractor = self.kb_server.upload_file(extractor)
except Exception as err:
self.log.error(f"Upload task extractor fail: {err}")
task_info = {
"task_groups": task_groups,
"extractor": extractor
}
fd, name = tempfile.mkstemp()
FileOps.dump(task_info, name)
task_index = dict(
seen_task=seen_task_index,
unseen_task=unseen_task_index)
task_index_url = FileOps.dump(
task_index, self.cloud_knowledge_management.local_task_index_url)

index_file = self.kb_server.update_db(name)
if not index_file:
self.log.error(f"KB update Fail !")
index_file = name
FileOps.upload(index_file, self.config.task_index)
task_index = self.cloud_knowledge_management.update_kb(
task_index_url, self.kb_server)
res.update(unseen_res)

task_info_res = self.estimator.model_info(
self.config.task_index,
task_index,
relpath=self.config.data_path_prefix)
self.report_task_info(
None, K8sResourceKindStatus.COMPLETED.value, task_info_res)
self.log.info(f"Lifelong learning Train task Finished, "
f"KB idnex save in {self.config.task_index}")
f"KB index save in {task_index}")
return callback_func(self.estimator, res) if callback_func else res

def update(self, train_data, valid_data=None, post_process=None, **kwargs):
return self.train(
train_data=train_data,
valid_data=valid_data,
post_process=post_process,
action="update",
**kwargs
)
"""
fit for update the knowledge based on incremental data.

Parameters
----------
train_data : BaseDataSource
Train data, see `sedna.datasources.BaseDataSource` for more detail.
valid_data : BaseDataSource
Valid data, BaseDataSource or None.
post_process : function
function or a registered method, callback after `estimator` train.
kwargs : Dict
parameters for `estimator` training, Like:
`early_stopping_rounds` in Xgboost.XGBClassifier

Returns
-------
train_history : object
"""
callback_func = None
if post_process is not None:
callback_func = ClassFactory.get_cls(
ClassType.CALLBACK, post_process)

task_index_url = self.get_parameters(
"CLOUD_KB_INDEX", self.cloud_knowledge_management.task_index)
index_url = self.cloud_knowledge_management.local_task_index_url
FileOps.download(task_index_url, index_url)

unseen_sample_re_recognition = ClassFactory.get_cls(
ClassType.UTD, self.unseen_sample_re_recognition["method"])(
index_url, **self.unseen_sample_re_recognition_param)

seen_samples, unseen_samples = unseen_sample_re_recognition(train_data)

# TODO: retrain temporarily
# historical_data = self._fetch_historical_data(index_url)
# seen_samples.x = np.concatenate(
# (historical_data.x, seen_samples.x, unseen_samples.x), axis=0)
# seen_samples.y = np.concatenate(
# (historical_data.y, seen_samples.y, unseen_samples.y), axis=0)

seen_samples.x = np.concatenate(
(seen_samples.x, unseen_samples.x), axis=0)
seen_samples.y = np.concatenate(
(seen_samples.y, unseen_samples.y), axis=0)

task_update_decision = ClassFactory.get_cls(
ClassType.KM, self.task_update_decision["method"])(
index_url, **self.task_update_decision_param)

tasks, task_update_strategies = task_update_decision(
seen_samples, task_type="seen_task")
seen_task_index = self.cloud_knowledge_management.estimator.update(
tasks, task_update_strategies, task_index=index_url)

tasks, task_update_strategies = task_update_decision(
unseen_samples, task_type="unseen_task")
unseen_task_index = self.unseen_task_processing.update(
tasks, task_update_strategies, task_index=index_url)

task_index = {
"seen_task": seen_task_index,
"unseen_task": unseen_task_index,
}

task_index = self.cloud_knowledge_management.update_kb(
task_index, self.kb_server)

task_info_res = self.estimator.model_info(
task_index,
relpath=self.config.data_path_prefix)

self.report_task_info(
None, K8sResourceKindStatus.COMPLETED.value, task_info_res)
self.log.info(f"Lifelong learning Update task Finished, "
f"KB index save in {task_index}")
return callback_func(self.estimator,
task_index) if callback_func else task_index

def evaluate(self, data, post_process=None, **kwargs):
"""
@@ -275,55 +352,23 @@ class LifelongLearning(JobBase):
elif post_process is not None:
callback_func = ClassFactory.get_cls(
ClassType.CALLBACK, post_process)
task_index_url = self.get_parameters(
"MODEL_URLS", self.config.task_index)
index_url = self.estimator.estimator.task_index_url

task_index_url = Context.get_parameters(
"MODEL_URLS", self.cloud_knowledge_management.task_index)
index_url = self.cloud_knowledge_management.local_task_index_url
self.log.info(
f"Download kb index from {task_index_url} to {index_url}")
FileOps.download(task_index_url, index_url)
res, tasks_detail = self.estimator.evaluate(data=data, **kwargs)
drop_tasks = []

model_filter_operator = self.get_parameters("operator", ">")
model_threshold = float(self.get_parameters('model_threshold', 0.1))

operator_map = {
">": lambda x, y: x > y,
"<": lambda x, y: x < y,
"=": lambda x, y: x == y,
">=": lambda x, y: x >= y,
"<=": lambda x, y: x <= y,
}
if model_filter_operator not in operator_map:
self.log.warn(
f"operator {model_filter_operator} use to "
f"compare is not allow, set to <"
)
model_filter_operator = "<"
operator_func = operator_map[model_filter_operator]

for detail in tasks_detail:
scores = detail.scores
entry = detail.entry
self.log.info(f"{entry} scores: {scores}")
if any(map(lambda x: operator_func(float(x),
model_threshold),
scores.values())):
self.log.warn(
f"{entry} will not be deploy because all "
f"scores {model_filter_operator} {model_threshold}")
drop_tasks.append(entry)
continue
drop_task = ",".join(drop_tasks)
index_file = self.kb_server.update_task_status(drop_task, new_status=0)
if not index_file:
self.log.error(f"KB update Fail !")
index_file = str(index_url)

res, index_file = self._task_evaluation(
data, task_index=index_url, **kwargs)
self.log.info("Task evaluation finishes.")

FileOps.upload(index_file, self.cloud_knowledge_management.task_index)
self.log.info(
f"upload kb index from {index_file} to {self.config.task_index}")
FileOps.upload(index_file, self.config.task_index)
f"upload kb index from {index_file} to {self.cloud_knowledge_management.task_index}")
task_info_res = self.estimator.model_info(
self.config.task_index, result=res,
self.cloud_knowledge_management.task_index, result=res,
relpath=self.config.data_path_prefix)
self.report_task_info(
None,
@@ -350,36 +395,80 @@ class LifelongLearning(JobBase):

Returns
-------
result : array_like
results array, contain all inference results in each sample.
is_unseen_task : bool
`true` means detect an unseen task, `false` means not
tasks : List
tasks assigned to each sample.
"""
task_index_url = self.get_parameters(
"MODEL_URLS", self.config.task_index)
index_url = self.estimator.estimator.task_index_url
FileOps.download(task_index_url, index_url)
res, tasks = self.estimator.predict(
data=data, post_process=post_process, **kwargs
)
seen_res, unseen_res = None, None
task_index_url = Context.get_parameters(
"MODEL_URLS", self.cloud_knowledge_management.task_index)
index_url = self.edge_knowledge_management.task_index
if not FileOps.exists(index_url):
FileOps.download(task_index_url, index_url)
self.log.info(
f"Download kb index from {task_index_url} to {index_url}")

self.edge_knowledge_management.update_kb(index_url)
self.log.info(f"Tasks are deployed at the edge.")

unseen_sample_recognition = ClassFactory.get_cls(
ClassType.UTD,
self.unseen_sample_recognition["method"])(
self.edge_knowledge_management.task_index,
**self.unseen_sample_recognition_param)

seen_samples, unseen_samples = unseen_sample_recognition(
data, **kwargs)
if unseen_samples.x is not None and len(unseen_samples.x) > 0:
self.edge_knowledge_management.log.info(
f"Unseen task is detected.")
unseen_res, unseen_tasks = self.unseen_task_processing.predict(
unseen_samples)

unseen_save_url = self.edge_knowledge_management.save_unseen_samples(
unseen_samples, post_process=post_process)
self.log.info(
f"Unseen samples are being uploaded to {unseen_save_url}.")

if seen_samples.x is not None and len(seen_samples.x) > 0:
seen_res, seen_tasks = self.edge_knowledge_management.estimator.predict(
data=seen_samples, post_process=post_process,
task_index=index_url,
task_type="seen_task",
**kwargs
)

return seen_res, unseen_res

def _task_evaluation(self, data, **kwargs):
res, tasks_detail = self.cloud_knowledge_management.estimator.evaluate(
data=data, **kwargs)
drop_task = self.cloud_knowledge_management.evaluate_tasks(
tasks_detail, **kwargs)

is_unseen_task = False
if self.unseen_task_detect:

try:
if callable(self.unseen_task_detect):
unseen_task_detect_algorithm = self.unseen_task_detect()
else:
unseen_task_detect_algorithm = ClassFactory.get_cls(
ClassType.UTD, self.unseen_task_detect
)()
except ValueError as err:
self.log.error("Lifelong learning "
"Inference [UTD] : {}".format(err))
index_file = self.kb_server.update_task_status(drop_task, new_status=0)

if not index_file:
self.log.error(f"KB update Fail !")
index_file = str(
self.cloud_knowledge_management.local_task_index_url)
else:
self.log.info(f"Deploy {index_file} to the edge.")

return res, index_file

def _fetch_historical_data(self, task_index):
if isinstance(task_index, str):
task_index = FileOps.load(task_index)

samples = BaseDataSource(data_type="train")

for task_group in task_index["seen_task"]["task_groups"]:
if isinstance(task_group.samples, BaseDataSource):
_samples = task_group.samples
else:
is_unseen_task = unseen_task_detect_algorithm(
tasks=tasks, result=res, **self.unseen_task_detect_param
)
return res, is_unseen_task, tasks
_samples = FileOps.load(task_group.samples.data_url)

samples.x = _samples.x if samples.x is None else np.concatenate(
(samples.x, _samples.x), axis=0)
samples.y = _samples.y if samples.y is None else np.concatenate(
(samples.y, _samples.y), axis=0)

return samples

+ 38
- 1
lib/sedna/datasources/__init__.py View File

@@ -106,7 +106,6 @@ class TxtDataParse(BaseDataSource, ABC):
self.x = np.array(x_data)
self.y = np.array(y_data)


class CSVDataParse(BaseDataSource, ABC):
"""
csv file which contain Structured Data parser
@@ -150,3 +149,41 @@ class CSVDataParse(BaseDataSource, ABC):
return
self.x = pd.concat(x_data)
self.y = pd.concat(y_data)

class IndexDataParse(BaseDataSource, ABC):
"""
txt file which contain image list parser
"""

def __init__(self, data_type, func=None):
super(IndexDataParse, self).__init__(data_type=data_type, func=func)

def parse(self, *args, **kwargs):
x_data = []
y_data = []
use_raw = kwargs.get("use_raw")
for f in args:
if not (f and FileOps.exists(f)):
continue
with open(f) as fin:
if self.process_func:
res = []
for line in fin.readlines():
lines = list(map(self.process_func, line.strip().split()))
res.append(lines)
else:
res = [line.strip().split() for line in fin.readlines()]
for tup in res:
if not len(tup):
continue
if use_raw:
x_data.append(tup)
else:
x_data.append(tup[:-1])
if not self.is_test_data:
if len(tup) > 1:
y_data.append(tup[1])
else:
y_data.append(0)
self.x = np.array(x_data)
self.y = np.array(y_data)

+ 18
- 6
lib/sedna/service/server/knowledgeBase/server.py View File

@@ -28,6 +28,7 @@ from starlette.responses import JSONResponse
from sedna.service.server.base import BaseServer
from sedna.common.file_ops import FileOps
from sedna.common.constant import KBResourceConstant
from sedna.common.config import Context

from .model import *

@@ -54,6 +55,10 @@ class KBServer(BaseServer):
self.save_dir = FileOps.clean_folder([save_dir], clean=False)[0]
self.url = f"{self.url}/{servername}"
self.kb_index = KBResourceConstant.KB_INDEX_NAME.value
self.seen_task_key = KBResourceConstant.SEEN_TASK.value
self.unseen_task_key = KBResourceConstant.UNSEEN_TASK.value
self.task_group_key = KBResourceConstant.TASK_GROUPS.value
self.extractor_key = KBResourceConstant.EXTRACTOR.value
self.app = FastAPI(
routes=[
APIRoute(
@@ -120,7 +125,7 @@ class KBServer(BaseServer):
return f"/file/download?files={filename}&name={filename}"

def update_status(self, data: KBUpdateResult = Body(...)):
deploy = True if data.status else False
deploy = bool(data.status)
tasks = data.tasks.split(",") if data.tasks else []
with Session(bind=engine) as session:
session.query(TaskGrp).filter(
@@ -131,17 +136,19 @@ class KBServer(BaseServer):

# todo: get from kb
_index_path = FileOps.join_path(self.save_dir, self.kb_index)
task_info = joblib.load(_index_path)
task_info = FileOps.load(_index_path)
new_task_group = []

default_task = task_info["task_groups"][0]
# TODO: to fit seen tasks and unseen tasks
default_task = task_info[self.seen_task_key][self.task_group_key][0]
# todo: get from transfer learning
for task_group in task_info["task_groups"]:
for task_group in task_info[self.seen_task_key][self.task_group_key]:
if not ((task_group.entry in tasks) == deploy):
new_task_group.append(default_task)
continue
new_task_group.append(task_group)
task_info["task_groups"] = new_task_group
task_info[self.seen_task_key][self.task_group_key] = new_task_group

_index_path = FileOps.join_path(self.save_dir, self.kb_index)
FileOps.dump(task_info, _index_path)
return f"/file/download?files={self.kb_index}&name={self.kb_index}"
@@ -153,9 +160,14 @@ class KBServer(BaseServer):
fout.write(tasks)
os.close(fd)
upload_info = joblib.load(name)
# TODO: to adapt unseen tasks
task_groups = upload_info[self.seen_task_key][self.task_group_key]
task_groups.extend(upload_info[self.unseen_task_key][self.task_group_key])

with Session(bind=engine) as session:
for task_group in upload_info["task_groups"]:
# TODO: to adapt unseen tasks
# for task_group in upload_info["task_groups"]:
for task_group in task_groups:
grp, g_create = get_or_create(
session=session, model=TaskGrp, name=task_group.entry)
if g_create:


Loading…
Cancel
Save