Pythonic way to pass a method to another function - python

I'm unsure the best way to do the following. That is, I'm not sure if I should have a parent class UniSamplingStrategy and child classes UniformSampling, and RandomSampling. Or should I just have UniSamplingStrategy and have the types of samplings as methods? For example, this is what I did:
import numpy as np
## make a base class w/ child classes instead?
class UniSamplingStrategy():
def __init__(self,
left=0,
right=0,
num_samples=0,
cluster_center=None,
defined_array=[0]
):
self._left = left
self._right = right
self._num_samples = num_samples
self._cluster_center = cluster_center
self._defined_array = defined_array
# uniform sampling
def uniform_sampling(self):
return np.linspace(start=self._left,
stop=self._right,
num=self._num_samples,
endpoint=True,
dtype=np.float32)
# random spacing
def clustered_sampling(self):
return np.random.normal(loc=self._clust_center,
scale=(self._right - self._left)/4,
size=self._num_samples)
What I want to do with this class (or perhaps classes, if I need to rewrite for good python) is pass a sampling strategy to my data_generation method.
def data_generation(noise_scale,
sampling_strategy,
test_func,
noise_type
):
x_samples = sampling_strategy
y_samples = test_func(x=x_samples)
if noise_type is not None:
_, y_samples_noise = noise_type(x=x_samples, scale=noise_scale)
y_samples = y_samples + y_samples_noise
return x_samples, y_samples
def test_func(x):
return (np.cos(x))**2/((x/6)**2+1)
def hmskd_noise(x, scale):
scales = scale
return scales, np.random.normal(scale=scale, size=x.shape[0])
So that ideally, I could try different test functions, noise, and sampling schemes. Where I could write function calls like:
x_true, y_true = data_generation(sampling_strategy=uniform_sampling(left=0, right=10, num_samples=1000)
test_func = test_func,
noise_type=None,
noise_scale = 0)
x_obs, y_obs = data_generation(sampling_strategy=clustered_sampling(clustered_center=5, left=0, right=10, num_samples = 20),
test_func = test_func,
noise_type=hmskd_noise,
noise_scale=0.2)
Essentially, I'm interested in the best way to pass a sampling strategy to data_generation when each method can have different parameters to pass (e.g., see uniform_sampling and clustered_sampling parameters).
Thanks for your time :)

For example, you can have a set of classes with __call__ method. Like
class UniformSampling:
def __init__(self,
left=0,
right=0,
num_samples=0,
cluster_center=None,
defined_array=[0]
):
self._left = left
self._right = right
self._num_samples = num_samples
self._cluster_center = cluster_center
self._defined_array = defined_array
def __call__(self, arg1, arg2):
return np.linspace(start=self._left,
stop=self._right,
num=self._num_samples,
endpoint=True,
dtype=np.float32)
Then you can pass instantiated object to data_generation as
x_true, y_true = data_generation(sampling_strategy=UniformSampling(left=0, right=10, num_samples=1000),
test_func = test_func,
noise_type=None,
noise_scale = 0)

Related

Getting a type error while using fori_loop with JAX

I'm developing a code using JAX, and I wanted to JIT some parts of that had big loops. I didn't want the code to be unrolled so I used fori_loop, but I'm getting an error and can't figure out what I am doing wrong.
The error is:
self.arr = self.arr.reshape(new_shape+new_shape)
TypeError: 'aval_method' object is not callable
I was able to reduce the code to the following:
import jax.numpy as jnp
import jax
class UB():
def __init__(self, arr, new_shape):
self.arr = arr
self.shape = new_shape
if type(arr) is not object:
self.arr = self.arr.reshape(new_shape+new_shape)
def _tree_flatten(self):
children = (self.arr,) # arrays / dynamic values
aux_data = {
'new_shape': self.shape
} # static values
return (children, aux_data)
#classmethod
def _tree_unflatten(cls, aux_data, children):
return cls(*children, **aux_data)
class UM():
def __init__(self, arr, r=None):
self.arr = arr
self.r = tuple(r)
def _tree_flatten(self):
children = (self.arr,) # arrays / dynamic values
aux_data = {
'r': self.r
} # static values
return (children, aux_data)
#classmethod
def _tree_unflatten(cls, aux_data, children):
return cls(*children, **aux_data)
for C in [UB, UM]:
jax.tree_util.register_pytree_node(
C,
C._tree_flatten,
C._tree_unflatten,
)
def s_w(ub, ums):
e = jnp.identity(2)
u = UM(e, [2])
ums[0] = u
return ub, ums
def s_c(t, uns):
n = 20
ums = []
for un in uns:
ums.append(UM(un, [2]))
tub = UB(t.arr, t.r)
s_loop_body = lambda i,x: s_w( ub=x[0], ums=x[1])
tub, ums = jax.lax.fori_loop(0, n, s_loop_body, (tub, ums))
# for i in range(n):
# tub, ums = s_loop_body(i, (tub, ums))
return jnp.array([u.arr.flatten() for u in ums])
uns = jnp.array([jnp.array([1, 2, 3, 4]) for _ in range(6)])
t = UM(jnp.array([1, 0, 0, 1]), r=[2])
uns = s_c(t, uns)
Has anyone encountered this issue or can explain how to fix it?
The issue is discussed here: https://jax.readthedocs.io/en/latest/pytrees.html#custom-pytrees-and-initialization
Namely, in JAX pytrees are used as general containers, and are sometimes initialized with abstract values or other place-holders, and so you cannot assume that arguments to a custom PyTree will be of array type. You might account for this by doing something like the following:
class UB():
def __init__(self, arr, new_shape):
self.arr = arr
self.shape = new_shape
if isinstance(arr, jnp.ndarray):
self.arr = self.arr.reshape(new_shape+new_shape)
When I run your code with this modification, it gets past the error you asked about, but unfortunately does trigger another error due to the body function of the fori_loop not having a valid signature (namely, the arr attributes of the ums have different shapes on input and output, which is not supported by fori_loop).
Hopefully this gets you on the path toward working code!

set variable network layers based on parameters in pytorch

I want to make the following network definition to a parametric one. The number of continuous and discrete columns varies for different data. I first pass the whole input data, which in this case is 110 dimensional , from a linear with a relu activation. The output of each categorical field of my data varies based on a previous one-hot encoding data transformation. I need to define a nn.Linear(110, number of encodings) for each of them.
class Generator(nn.Module):
def __init__(self):
super(Generator, self).__init__(110)
self.lin1 = nn.Linear(110,110)
self.lin_numerical = nn.Linear(110, 6)
self.lin_cat_job = nn.Linear(110, 9)
self.lin_cat_sex = nn.Linear(110, 2)
self.lin_cat_incomeclass = nn.Linear(110, 7)
def forward(self, x):
x = torch.relu(self.lin1(x))
x_numerical = f.leaky_relu(self.lin_numerical(x))
x_cat1 = f.gumbel_softmax(self.lin_cat_job(x), tau=0.2)
x_cat2 = f.gumbel_softmax(self.lin_cat_sex(x), tau=0.2)
x_cat3 = f.gumbel_softmax(self.lin_cat_incomeclass(x), tau=0.2)
x_final = torch.cat((x_numerical, x_cat1, x_cat2, x_cat3),1)
return x_final
I have managed to change the init part, using discrete_columns input which is an ordereddict that has the name and number of one-hot-encoding of each categorical field of my data as key and values, and continuous_columns which is only a list with the names of the continuous columns. But I have no idea how to edit the forward part:
class Generator(nn.Module):
def __init__(self, input_dim, continuous_columns, discrete_columns):
super(Generator, self).__init__()
self._input_dim = input_dim
self._discrete_columns = discrete_columns
self._num_continuous_columns = len(continuous_columns)
self.lin1 = nn.Linear(self._input_dim, self._input_dim)
self.lin_numerical = nn.Linear(self._input_dim, self._num_continuous_columns)
for key, value in self._discrete_columns.items():
setattr(self, "lin_cat_{}".format(key), nn.Linear(self._input_dim, value))
def forward(self, x):
x = torch.relu(self.lin1(x))
x_numerical = f.leaky_relu(self.lin_numerical(x))
####
This is the problematic part
#####
return x
You don't need to use setattr and honestly should not since you'd need getattr, it brings more trouble than it solves if there's any other ways to do the job.
Now this is what I'd do for this task
self.lin_cat = nn.ModuleDict()
for key, value in self._discrete_columns.items():
self.lin_cat[key] = nn.Linear(self._input_dim, value)
# setattr(self, "lin_cat_{}".format(key), nn.Linear(self._input_dim, value))
def forward(self, x):
x = torch.relu(self.lin1(x))
x_numerical = f.leaky_relu(self.lin_numerical(x))
x_cat = []
for key in self.lin_cat:
x_cat.append(f.gumbel_softmax(self.lin_cat[key](x), tau=0.2))
x_final = torch.cat((x_numerical, *x_cat), 1)
return x

Python Object initialization,_init_ method

How Can I create class if i have to create object for my class like below.
Obj1 = Class()
Obj2 = Class(para1,para2,para3)
This is related to a task that i need to complete just started learning Python.
I tried construction overloading but it seems to not work in Python .Can anyone tell me how can i achieve this or it is technically wrong to have both line in one code.
You can use *args or **kwargs
class Class1:
def __init__(self, *args):
pass
obj1 = Class1()
obj2 = Class1(para1,para2,para3)
or
class Class1:
def __init__(self, **kwargs):
pass
obj1 = Class1()
obj2 = Class1(para1=para1,para2=para2,para3=para3)
Refer this to learn more about *args and **kwargs
If you set default values like length = 80, you don't have to set them. But if you set the values, it ll be set as you wish. The following code demonstrates almost what you want.
class Rectangle:
def __init__(self, length = 80, breadth = 60, unit_cost=100):
self.length = length
self.breadth = breadth
self.unit_cost = unit_cost
def get_perimeter(self):
return 2 * (self.length + self.breadth)
def get_area(self):
return self.length * self.breadth
def calculate_cost(self):
area = self.get_area()
return area * self.unit_cost
# r = Rectangle() <- this returns with default values
# breadth = 120 cm, length = 160 cm, 1 cm^2 = Rs 2000
r = Rectangle(160, 120, 2000)
print("Area of Rectangle: %s cm^2" % (r.get_area()))
print("Cost of rectangular field: Rs. %s " %(r.calculate_cost()))

Class Method Not Returning Value When Accessed Via Inheritance

I have a class method that stops returning a value when I try accessing through an inherited subclass.
Have no idea what's causing it to not return the appropriate value.
Here's what I have:
class KNN():
def __init__(self, neighbors=5, centered=True):
self.neighbors = neighbors
self.centered = centered
def _get_distance(self, xi):
return np.sqrt(((xi - self.X_fit)**2).sum(1))
def fit(self, X, y):
if self.centered:
self.X_fit = standardize(X)
else:
self.X_fit = X
self.y_fit = y
def predict(self, X, centered=False):
m, n = X.shape[0], self.X_fit.shape[0]
self.dist_matrix = np.zeros((m, n))
X_pred = np.zeros(X.shape)
if standardize:
X_pred = standardize(X)
else:
X_pred = X
for row in range(m):
self.dist_matrix[row] = self._get_distance(X_pred[row])
self.idx_vals = np.argsort(self.dist_matrix)[:, :self.neighbors]
self.y_idx = self.y_fit[self.idx_vals]
self.preds = [self.neighbor_calculation(self.y_idx[i]) for i in range(len(self.y_idx))]
return self.preds
If I access the KNN class directly the predict method works as intended, and it returns an array of the predicted values.
However, it stops when I try and create a subclass that inherits from KNN:
class KNNClassifier(KNN):
def predict(self, X, centered=False):
self.neighbor_calculation = majority_vote
super().predict(X, standardize)
When I access the predict method through the KNNClassifier class it doesn't return a value.
However, self.preds contains the actual predictions.
But trying something like KNNClassifier.predict(X)[:10] gives the error message:
'NoneType' object is not subscriptable'
I don't know why the returned value is suddenly being interpreted as None.
You are using super to call the parent method from the child, which is right, but you need to use return to return the value:
return super().predict(X, standardize)

Would I be able to separate variables into another class but keep the usage the same?

I'm trying to rewrite a script and I'm stuck on making it easy to use. Basically it's an assembly script (like the reverse of destruction), where you input a load of variables such as location, whether the location is absolute or relative, scale, rotation, visibility, random offset, etc, to create an animation. The first version was very non user friendly, so I'm trying to get it working nicely from the start this time.
I've thought of how I'd like it to work, and I've managed to keep it clean, but there is a flaw. As you can see below, it'd be possible to use anything like SetGroup.frame[i].save(), which I don't want (and I don't want to put checks on if name is None throughout the class).
Here is the code I have:
class SetGroup(object):
def __init__(self, name=None, _frame_only=False):
if name is None and not _frame_only:
raise TypeError('name of group must be provided')
self.selection = None
self.origin = None
self.start = None
self.end = None
self.offset = 0
self.distance = None
self.random = 0
self.location = None
self.rotation = None
self.scale = None
self.visibility = None
if not _frame_only:
self.frame = defaultdict(lambda: SetGroup(_frame_only=True))
def save(self):
self.load()
#do a bit of error checking here
self.data[self.name] = {'ObjectSelection': self.selection,
'ObjectOrigin': self.origin,
'FrameStart': self.start,
'FrameEnd': self.end,
'FrameOffset': self.offset,
'FrameDistance': self.distance,
'FrameRandom': self.random,
'StartLocation': self.location,
'StartRotation': self.rotation,
'StartScale': self.scale,
'StartVisibility': self.visibility,
'ExtraFrames': self.frame}
pm.fileInfo['AssemblyScript'] = StoreData().save(self.data)
def load(self):
try:
self.data = StoreData().load(pm.fileInfo['AssemblyScript'])
except KeyError:
pm.fileInfo['AssemblyScript'] = StoreData().save({})
The way I'd like it to work is like this:
a = SetGroup('test')
a.location = ((0, 0, 0), True)
a.start = 0
a.end = 10
a.frame[5].location = ((10, 10, 10), False)
a.frame[5].scale = ((2, 1, 1), True)
a.save()
Unless anyone can think of a way which would make it more friendly to use, how would I separate location, rotation, scale, and visibility into another class and link them up again, so that they still work at the core level of the class, but also work for the frame dictionary too?
Edit - Got it working to a basic level:
class _MovementInfo(object):
def __init__(self, location=None, rotation=None, scale=None, visibility=None):
self.location = location
self.rotation = rotation
self.scale = scale
self.visibility = visibility
def __repr__(self):
return '_MovementInfo(location={x.location}, rotation={x.rotation}, scale={x.scale}, visibility={x.visibility}'.format(x=self)
Then I used this in the main class to merge the dictionaries:
self.__dict__.update({k: v for k, v in _MovementInfo().__dict__.iteritems() if '__' not in k})
self.frame = defaultdict(_MovementInfo)
I would change the code like this:
class SetGroup(_Movement):
def __init__(self, name=None):
if name is None:
# ...
super().__init__()
# ...
self.random = 0 # __init__ should end here
# ...
But you should check that all _MovementInfo's in all frames are _MovementInfo's or have inherited from them (to check this: isinstance(x, _MovementInfo)), but are not SetGroup's (to check this: not isinstance(x, SetGroup)).
super() is short for super(SetGroup, self) (you have to use the last option for python2), and is basicly an object that holds all things that the base class has, and allows you to call methods that modify the class calling it.
Or in code:
class A(object):
def __init__(self, y):
self.x = 2
self.y = y
class B(A):
def __init__(self, y, z):
super().__init__(y) # equivalent to: A.__init__(self, y)
self.z = z
b = B(3, 4)
# b's x is 2, b's y is 3 (both set by A.__init__, the last one was passed by B), and b's z is 4 (set by B.__init__)
I hope this helped,
CodenameLambda

Categories