Browse Source

Merge pull request #149 from Learnware-LAMDA/fix_image_rkme

[FIX] Fix image rkme
tags/v0.3.2
bxdd GitHub 2 years ago
parent
commit
b61378d4aa
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 96 additions and 25 deletions
  1. +3
    -1
      learnware/specification/module.py
  2. +93
    -24
      learnware/specification/regular/image/rkme.py

+ 3
- 1
learnware/specification/module.py View File

@@ -73,10 +73,12 @@ def generate_rkme_image_spec(
step_size: float = 0.01, step_size: float = 0.01,
steps: int = 100, steps: int = 100,
resize: bool = True, resize: bool = True,
sample_size: int = 5000,
nonnegative_beta: bool = True, nonnegative_beta: bool = True,
reduce: bool = True, reduce: bool = True,
verbose: bool = True, verbose: bool = True,
cuda_idx: int = None, cuda_idx: int = None,
**kwargs
) -> RKMEImageSpecification: ) -> RKMEImageSpecification:
""" """
Interface for users to generate Reduced Kernel Mean Embedding (RKME) specification for Image. Interface for users to generate Reduced Kernel Mean Embedding (RKME) specification for Image.
@@ -119,7 +121,7 @@ def generate_rkme_image_spec(
# Generate rkme spec # Generate rkme spec
rkme_image_spec = RKMEImageSpecification(cuda_idx=cuda_idx) rkme_image_spec = RKMEImageSpecification(cuda_idx=cuda_idx)
rkme_image_spec.generate_stat_spec_from_data( rkme_image_spec.generate_stat_spec_from_data(
X, reduced_set_size, step_size, steps, resize, nonnegative_beta, reduce, verbose
X, reduced_set_size, step_size, steps, resize, sample_size, nonnegative_beta, reduce, verbose, **kwargs
) )
return rkme_image_spec return rkme_image_spec




+ 93
- 24
learnware/specification/regular/image/rkme.py View File

@@ -6,16 +6,19 @@ import json
import os import os


from typing import Any from typing import Any
from contextlib import contextmanager


import numpy as np import numpy as np
import torch import torch
from torch import nn from torch import nn
from torch.utils.data import TensorDataset, DataLoader from torch.utils.data import TensorDataset, DataLoader
from tqdm import tqdm from tqdm import tqdm
from numpy.random import RandomState


from . import cnn_gp from . import cnn_gp
from ..base import RegularStatSpecification from ..base import RegularStatSpecification
from ..table.rkme import rkme_solve_qp from ..table.rkme import rkme_solve_qp
from .... import setup_seed
from ....logger import get_module_logger from ....logger import get_module_logger
from ....utils import choose_device, allocate_cuda_idx from ....utils import choose_device, allocate_cuda_idx


@@ -36,6 +39,8 @@ class RKMEImageSpecification(RegularStatSpecification):
""" """
self.RKME_IMAGE_VERSION = 1 # Please maintain backward compatibility. self.RKME_IMAGE_VERSION = 1 # Please maintain backward compatibility.


self.msg=None

self.z = None self.z = None
self.beta = None self.beta = None
self._cuda_idx = allocate_cuda_idx() if cuda_idx is None else cuda_idx self._cuda_idx = allocate_cuda_idx() if cuda_idx is None else cuda_idx
@@ -47,6 +52,7 @@ class RKMEImageSpecification(RegularStatSpecification):
if "model_config" not in kwargs if "model_config" not in kwargs
else kwargs["model_config"] else kwargs["model_config"]
) )
self._random_generator = None


super(RKMEImageSpecification, self).__init__(type=self.__class__.__name__) super(RKMEImageSpecification, self).__init__(type=self.__class__.__name__)


@@ -54,13 +60,11 @@ class RKMEImageSpecification(RegularStatSpecification):
def device(self): def device(self):
return self._device return self._device


def _generate_models(self, n_models: int, channel: int = 3, fixed_seed=None):
def _generate_models(self, n_models: int, channel: int = 3):
model_class = functools.partial(_ConvNet_wide, channel=channel, **self.model_config) model_class = functools.partial(_ConvNet_wide, channel=channel, **self.model_config)


def __builder(i): def __builder(i):
if fixed_seed is not None:
torch.manual_seed(fixed_seed[i])
return model_class().to(self._device)
return model_class(random_generator=self._random_generator).to(self._device)


return (__builder(m) for m in range(n_models)) return (__builder(m) for m in range(n_models))


@@ -71,6 +75,7 @@ class RKMEImageSpecification(RegularStatSpecification):
step_size: float = 0.01, step_size: float = 0.01,
steps: int = 100, steps: int = 100,
resize: bool = True, resize: bool = True,
sample_size: int = 5000,
nonnegative_beta: bool = True, nonnegative_beta: bool = True,
reduce: bool = True, reduce: bool = True,
verbose: bool = True, verbose: bool = True,
@@ -90,6 +95,8 @@ class RKMEImageSpecification(RegularStatSpecification):
Total rounds in the iterative optimization. Total rounds in the iterative optimization.
resize : bool resize : bool
Whether to scale the image to the requested size, by default True. Whether to scale the image to the requested size, by default True.
sample_size: int
Size of sampled set used to generate specification
nonnegative_beta : bool, optional nonnegative_beta : bool, optional
True if weights for the reduced set are intended to be kept non-negative, by default False. True if weights for the reduced set are intended to be kept non-negative, by default False.
reduce : bool, optional reduce : bool, optional
@@ -153,30 +160,42 @@ class RKMEImageSpecification(RegularStatSpecification):
self.beta = torch.from_numpy(self.beta).to(self._device) self.beta = torch.from_numpy(self.beta).to(self._device)
return return


random_models = list(self._generate_models(n_models=self.n_models, channel=X.shape[1]))
self.z = torch.zeros(Z_shape).to(self._device).float().normal_(0, 1)
with torch.no_grad():
x_features = self._generate_random_feature(X_train, random_models=random_models)
self._update_beta(x_features, nonnegative_beta, random_models=random_models)
# auto sample
if len(X_train) > sample_size:
indices = np.random.choice(len(X_train), size=sample_size, replace=False)
X_train = X_train[indices]


try: try:
import torch_optimizer import torch_optimizer
except ModuleNotFoundError: except ModuleNotFoundError:
raise ModuleNotFoundError( raise ModuleNotFoundError(
f"RKMEImageSpecification is not available because 'torch-optimizer' is not installed! Please install it manually."
)
f"RKMEImageSpecification is not available because 'torch-optimizer' is not installed! Please install it manually.")


optimizer = torch_optimizer.AdaBelief([{"params": [self.z]}], lr=step_size, eps=1e-16)
# Cross-platform by default, unless the spec is specified to be generated specifically for local experiments.
cross_platform = "experimental" not in kwargs or not kwargs["experimental"]
# crucial
with deterministic(cross_platform, self._device) as random_generator:
self._random_generator = random_generator


for _ in tqdm(range(steps)) if verbose else range(steps):
# Regenerate Random Models
random_models = list(self._generate_models(n_models=self.n_models, channel=X.shape[1])) random_models = list(self._generate_models(n_models=self.n_models, channel=X.shape[1]))
self.z = torch.zeros(Z_shape).to(self._device).float()
self._random_generator.normal_(self.z, 0, 1)


with torch.no_grad(): with torch.no_grad():
x_features = self._generate_random_feature(X_train, random_models=random_models) x_features = self._generate_random_feature(X_train, random_models=random_models)
self._update_z(x_features, optimizer, random_models=random_models)
self._update_beta(x_features, nonnegative_beta, random_models=random_models) self._update_beta(x_features, nonnegative_beta, random_models=random_models)


optimizer = torch_optimizer.AdaBelief([{"params": [self.z]}], lr=step_size, eps=1e-16)

for _ in tqdm(range(steps)) if verbose else range(steps):
# Regenerate Random Models
random_models = list(self._generate_models(n_models=self.n_models, channel=X.shape[1]))

with torch.no_grad():
x_features = self._generate_random_feature(X_train, random_models=random_models)
self._update_z(x_features, optimizer, random_models=random_models)
self._update_beta(x_features, nonnegative_beta, random_models=random_models)

@torch.no_grad() @torch.no_grad()
def _update_beta(self, x_features: Any, nonnegative_beta: bool = True, random_models=None): def _update_beta(self, x_features: Any, nonnegative_beta: bool = True, random_models=None):
Z = self.z Z = self.z
@@ -331,7 +350,22 @@ class RKMEImageSpecification(RegularStatSpecification):
return K_12 return K_12


def herding(self, T: int) -> np.ndarray: def herding(self, T: int) -> np.ndarray:
raise NotImplementedError("The function herding hasn't been supported in Image RKME Specification.")
"""Iteratively sample examples from an unknown distribution with the help of its RKME specification

Parameters
----------
T : int
Total iteration number for sampling.

Returns
-------
np.ndarray
A collection of examples which approximate the unknown distribution.
"""
indices = torch.multinomial(self.beta, T, replacement=True)
mock = self.z[indices] + torch.randn_like(self.z[indices]) * 0.01

return mock.numpy()


def _sampling_candidates(self, N: int) -> np.ndarray: def _sampling_candidates(self, N: int) -> np.ndarray:
raise NotImplementedError() raise NotImplementedError()
@@ -383,7 +417,7 @@ class RKMEImageSpecification(RegularStatSpecification):
rkme_load = json.loads(obj_text) rkme_load = json.loads(obj_text)
rkme_load["z"] = torch.from_numpy(np.array(rkme_load["z"], dtype="float32")) rkme_load["z"] = torch.from_numpy(np.array(rkme_load["z"], dtype="float32"))
rkme_load["beta"] = torch.from_numpy(np.array(rkme_load["beta"], dtype="float64")) rkme_load["beta"] = torch.from_numpy(np.array(rkme_load["beta"], dtype="float64"))
for d in self.get_states(): for d in self.get_states():
if d in rkme_load.keys(): if d in rkme_load.keys():
if d == "type" and rkme_load[d] != self.type: if d == "type" and rkme_load[d] != self.type:
@@ -405,11 +439,46 @@ def _get_zca_matrix(X, reg_coef=0.1):
return whitening_transform return whitening_transform




class RandomGenerator:

def __init__(self, seed=0, cross_platform=True):
self.cross_platform=cross_platform
self.state = RandomState(seed)

def normal_(self, tensor: torch.Tensor, mean=0.0, std=1.0):
if self.cross_platform:
data = self.state.normal(mean, std, size=tensor.shape)
with torch.no_grad():
tensor.copy_(torch.asarray(data, dtype=tensor.dtype))
else:
torch.nn.init.normal_(tensor, mean, std)


@contextmanager
def deterministic(cross_platform, device):
torch.manual_seed(0)
torch.cuda.manual_seed_all(0)
deterministic_state = torch.backends.cudnn.deterministic
torch.backends.cudnn.deterministic = True
if cross_platform and torch.cuda.is_available():
torch.cuda.set_rng_state(
new_state=torch.cuda.get_rng_state(device.index),
device="cpu")

yield RandomGenerator(seed=0, cross_platform=cross_platform)

torch.backends.cudnn.deterministic = deterministic_state
if cross_platform and torch.cuda.is_available():
torch.cuda.set_rng_state(
new_state=torch.cuda.get_rng_state(device.index),
device="cuda")


class _ConvNet_wide(nn.Module): class _ConvNet_wide(nn.Module):
def __init__(self, channel, mu=None, sigma=None, k=2, net_width=128, net_depth=3, im_size=(32, 32)):
def __init__(self, channel, random_generator, mu=None, sigma=None, k=2, net_width=128, net_depth=3, im_size=(32, 32)):
self.k = k self.k = k
super().__init__() super().__init__()
self.features, shape_feat = self._make_layers(channel, net_width, net_depth, im_size, mu, sigma)
self.features, shape_feat = self._make_layers(channel, net_width, net_depth, im_size, mu, sigma, random_generator)
# self.aggregation = nn.AvgPool2d(kernel_size=shape_feat[1]) # self.aggregation = nn.AvgPool2d(kernel_size=shape_feat[1])


def forward(self, x): def forward(self, x):
@@ -418,14 +487,14 @@ class _ConvNet_wide(nn.Module):
# out = self.aggregation(out).reshape(out.size(0), -1) # out = self.aggregation(out).reshape(out.size(0), -1)
return out return out


def _make_layers(self, channel, net_width, net_depth, im_size, mu, sigma):
def _make_layers(self, channel, net_width, net_depth, im_size, mu, sigma, random_generator):
k = self.k k = self.k


layers = [] layers = []
in_channels = channel in_channels = channel
shape_feat = [in_channels, im_size[0], im_size[1]] shape_feat = [in_channels, im_size[0], im_size[1]]
for d in range(net_depth): for d in range(net_depth):
layers += [_build_conv2d_gaussian(in_channels, int(k * net_width), 3, 1, mean=mu, std=sigma)]
layers += [_build_conv2d_gaussian(in_channels, int(k * net_width), random_generator, 3, 1, mean=mu, std=sigma)]
shape_feat[0] = int(k * net_width) shape_feat[0] = int(k * net_width)


layers += [nn.ReLU(inplace=True)] layers += [nn.ReLU(inplace=True)]
@@ -438,15 +507,15 @@ class _ConvNet_wide(nn.Module):
return nn.Sequential(*layers), shape_feat return nn.Sequential(*layers), shape_feat




def _build_conv2d_gaussian(in_channels, out_channels, kernel=3, padding=1, mean=None, std=None):
def _build_conv2d_gaussian(in_channels, out_channels, random_generator: RandomGenerator, kernel=3, padding=1, mean=None, std=None):
layer = nn.Conv2d(in_channels, out_channels, kernel, padding=padding) layer = nn.Conv2d(in_channels, out_channels, kernel, padding=padding)
if mean is None: if mean is None:
mean = 0 mean = 0
if std is None: if std is None:
std = np.sqrt(2) / np.sqrt(layer.weight.shape[1] * layer.weight.shape[2] * layer.weight.shape[3]) std = np.sqrt(2) / np.sqrt(layer.weight.shape[1] * layer.weight.shape[2] * layer.weight.shape[3])
# print('Initializing Conv. Mean=%.2f, std=%.2f'%(mean, std)) # print('Initializing Conv. Mean=%.2f, std=%.2f'%(mean, std))
torch.nn.init.normal_(layer.weight, mean, std)
torch.nn.init.normal_(layer.bias, 0, 0.1)
random_generator.normal_(layer.weight, mean, std)
random_generator.normal_(layer.bias, 0, 0.1)
return layer return layer






Loading…
Cancel
Save