Skip to content

Commit 62cc15a

Browse files
committed
Merge branch 'dev' of github.com:theislab/batchglm into dev
2 parents 7761bca + 3432635 commit 62cc15a

File tree

7 files changed

+126
-144
lines changed

7 files changed

+126
-144
lines changed

batchglm/train/tf/nb_glm/base.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,15 @@ def np_clip_param(param, name):
7777
)
7878

7979

80-
def apply_constraints(constraints: np.ndarray, var: tf.Variable, dtype: str):
80+
def apply_constraints(
81+
constraints: np.ndarray,
82+
dtype: str,
83+
var_all: tf.Variable = None,
84+
var_indep: tf.Tensor = None
85+
):
8186
""" Iteratively build depend variables from other variables via constraints
8287
88+
:type var_all: object
8389
:param constraints: Array with constraints in rows and model parameters in columns.
8490
Each constraint contains non-zero entries for the a of parameters that
8591
has to sum to zero. This constraint is enforced by binding one parameter
@@ -88,16 +94,21 @@ def apply_constraints(constraints: np.ndarray, var: tf.Variable, dtype: str):
8894
parameter is indicated by a -1 in this array, the independent parameters
8995
of that constraint (which may be dependent at an earlier constraint)
9096
are indicated by a 1.
91-
:param var: Variable tensor features x independent parameters.
97+
:param var_all: Variable tensor features x independent parameters.
98+
All model parameters.
99+
:param var_all: Variable tensor features x independent parameters.
100+
Only independent model parameters, ie. not parameters defined by constraints.
92101
:param dtype: Precision used in tensorflow.
93102
94103
:return: Full model parameter matrix with dependent parameters.
95104
"""
96105

97106
# Find all independent variables:
98-
idx_indep = np.where(np.all(constraints == -1, axis=0))[0]
107+
idx_indep = np.where(np.all(constraints != -1, axis=0))[0]
108+
idx_indep.astype(dtype=np.int64)
99109
# Relate constraints to dependent variables:
100110
idx_dep = np.array([np.where(constr == -1)[0] for constr in constraints])
111+
idx_dep.astype(dtype=np.int64)
101112
# Only choose dependent variable which was not already defined above:
102113
idx_dep = np.concatenate([
103114
x[[xx not in np.concatenate(idx_dep[:i]) for xx in x]] if i > 0 else x
@@ -109,7 +120,13 @@ def apply_constraints(constraints: np.ndarray, var: tf.Variable, dtype: str):
109120
# tensor is initialised with the independent variables var
110121
# and is grown by one varibale in each iteration until
111122
# all variables are there.
112-
x = var
123+
if var_all is None:
124+
x = var_indep
125+
elif var_indep is None:
126+
x = tf.gather(params=var_all, indices=idx_indep, axis=0)
127+
else:
128+
raise ValueError("only give var_all or var_indep to apply_constraints.")
129+
113130
for i in range(constraints.shape[0]):
114131
idx_var_i = np.concatenate([idx_indep, idx_dep[:i]])
115132
constraint_model = constraints[[i], :][:, idx_var_i]
@@ -150,12 +167,11 @@ def __init__(
150167
# Define first layer of computation graph on identifiable variables
151168
# to yield dependent set of parameters of model for each location
152169
# and scale model.
153-
154170
if constraints_loc is not None:
155-
a = apply_constraints(constraints_loc, a, dtype=dtype)
171+
a = apply_constraints(constraints=constraints_loc, var_all=a, dtype=dtype)
156172

157173
if constraints_scale is not None:
158-
b = apply_constraints(constraints_scale, b, dtype=dtype)
174+
b = apply_constraints(constraints=constraints_scale, var_all=b, dtype=dtype)
159175

160176
with tf.name_scope("mu"):
161177
log_mu = tf.matmul(design_loc, a, name="log_mu_obs")

batchglm/train/tf/nb_glm/estimator.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,9 @@ def map_model(idx, data) -> BasicModelGraph:
7373
with tf.name_scope("loss"):
7474
loss = tf.reduce_sum(norm_neg_log_likelihood)
7575

76-
# TODO: remove this and decide for one implementation
77-
if pkg_constants.HESSIAN_MODE == "obs":
78-
# Only need iterator that yields single observations for hessian mode obs:
79-
singleobs_data = dataset.map(fetch_fn, num_parallel_calls=pkg_constants.TF_NUM_THREADS)
80-
singleobs_data = singleobs_data.prefetch(1)
81-
else:
82-
singleobs_data = None
83-
8476
with tf.name_scope("hessians"):
8577
hessians = Hessians(
8678
batched_data=batched_data,
87-
singleobs_data=singleobs_data,
8879
sample_indices=sample_indices,
8980
constraints_loc=constraints_loc,
9081
constraints_scale=constraints_scale,
@@ -258,20 +249,19 @@ def __init__(
258249
constraints_loc=constraints_loc,
259250
constraints_scale=constraints_scale,
260251
model_vars=model_vars,
261-
mode="analytic",
252+
mode=pkg_constants.JACOBIAN_MODE, #"analytic",
262253
iterator=False,
263254
dtype=dtype
264255
)
265256

266257
# Define the hessian on the batched model for newton-rhapson:
267258
batch_hessians = Hessians(
268259
batched_data=batch_data,
269-
singleobs_data=None,
270260
sample_indices=batch_sample_index,
271261
constraints_loc=constraints_loc,
272262
constraints_scale=constraints_scale,
273263
model_vars=model_vars,
274-
mode="obs_batched",
264+
mode=pkg_constants.HESSIAN_MODE, #"obs_batched",
275265
iterator=False,
276266
dtype=dtype
277267
)
@@ -637,6 +627,16 @@ class TrainingStrategy(Enum):
637627
"optim_algo": "newton",
638628
},
639629
]
630+
CONSTRAINED = [ # Should not contain newton-rhapson right now.
631+
{
632+
"learning_rate": 0.5,
633+
"convergence_criteria": "scaled_moving_average",
634+
"stopping_criteria": 1e-10,
635+
"loss_window_size": 10,
636+
"use_batching": False,
637+
"optim_algo": "ADAM",
638+
},
639+
]
640640
CONTINUOUS = [
641641
{
642642
"learning_rate": 0.5,
@@ -810,7 +810,7 @@ def __init__(
810810
design_scale=input_data.design_scale,
811811
constraints=input_data.constraints_scale,
812812
size_factors=size_factors_init,
813-
groupwise_means=groupwise_means,
813+
groupwise_means=None, # Could only use groupwise_means from a init if design_loc and design_scale were the same.
814814
link_fn=lambda r: np.log(np_clip_param(r, "r"))
815815
)
816816

batchglm/train/tf/nb_glm/hessians.py

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,6 @@ class Hessians:
175175
def __init__(
176176
self,
177177
batched_data: tf.data.Dataset,
178-
singleobs_data: tf.data.Dataset,
179178
sample_indices: tf.Tensor,
180179
constraints_loc,
181180
constraints_scale,
@@ -188,7 +187,6 @@ def __init__(
188187
189188
:param batched_data:
190189
Dataset iterator over mini-batches of data (used for training) or tf.Tensors of mini-batch.
191-
:param singleobs_data: Dataset iterator over single observation batches of data.
192190
:param sample_indices: Indices of samples to be used.
193191
:param constraints_loc: Constraints for location model.
194192
Array with constraints in rows and model parameters in columns.
@@ -218,29 +216,14 @@ def __init__(
218216
evaluation of the hessian via the tf.hessian function,
219217
which is done by feature for implementation reasons.
220218
:param iterator: bool
221-
Whether an iterator or a tensor (single yield of an iterator) is given
222-
in
219+
Whether batched_data is an iterator or a tensor (such as single yield of an iterator).
223220
"""
224-
if constraints_loc != None and mode != "tf":
221+
if constraints_loc is not None and mode != "tf":
225222
raise ValueError("closed form hessian does not work if constraints_loc is not None")
226-
if constraints_scale != None and mode != "tf":
223+
if constraints_scale is not None and mode != "tf":
227224
raise ValueError("closed form hessian does not work if constraints_scale is not None")
228225

229-
if mode == "obs":
230-
logger.info("Performance warning for hessian mode: " +
231-
"obs_batched is strongly recommended as an alternative to obs.")
232-
self.hessian = self.byobs(
233-
batched_data=singleobs_data,
234-
sample_indices=sample_indices,
235-
constraints_loc=constraints_loc,
236-
constraints_scale=constraints_scale,
237-
model_vars=model_vars,
238-
batched=False,
239-
iterator=iterator,
240-
dtype=dtype
241-
)
242-
self.neg_hessian = tf.negative(self.hessian)
243-
elif mode == "obs_batched":
226+
if mode == "obs_batched":
244227
self.hessian = self.byobs(
245228
batched_data=batched_data,
246229
sample_indices=sample_indices,
@@ -259,6 +242,7 @@ def __init__(
259242
constraints_loc=constraints_loc,
260243
constraints_scale=constraints_scale,
261244
model_vars=model_vars,
245+
iterator=iterator,
262246
dtype=dtype
263247
)
264248
self.neg_hessian = tf.negative(self.hessian)
@@ -272,6 +256,7 @@ def __init__(
272256
constraints_loc=constraints_loc,
273257
constraints_scale=constraints_scale,
274258
model_vars=model_vars,
259+
iterator=iterator,
275260
dtype=dtype
276261
)
277262
self.hessian = tf.negative(self.neg_hessian)
@@ -517,8 +502,8 @@ def _red(prev, cur):
517502
return tf.add(prev, cur)
518503

519504
params = model_vars.params
520-
p_shape_a = model_vars.a.shape[0]
521-
p_shape_b = model_vars.b.shape[0]
505+
p_shape_a = model_vars.a_var.shape[0] # This has to be _var to work with constraints.
506+
p_shape_b = model_vars.b_var.shape[0] # This has to be _var to work with constraints.
522507

523508
if iterator:
524509
H = op_utils.map_reduce(
@@ -542,6 +527,7 @@ def byfeature(
542527
constraints_loc,
543528
constraints_scale,
544529
model_vars: ModelVars,
530+
iterator,
545531
dtype
546532
):
547533
"""
@@ -685,18 +671,24 @@ def _red(prev, cur):
685671
return [tf.add(p, c) for p, c in zip(prev, cur)]
686672

687673
params = model_vars.params
688-
p_shape_a = model_vars.a.shape[0]
689-
p_shape_b = model_vars.b.shape[0]
690-
691-
H = op_utils.map_reduce(
692-
last_elem=tf.gather(sample_indices, tf.size(sample_indices) - 1),
693-
data=batched_data,
694-
map_fn=_map,
695-
reduce_fn=_red,
696-
parallel_iterations=1,
697-
)
698-
H = H[0]
699-
return H
674+
p_shape_a = model_vars.a_var.shape[0] # This has to be _var to work with constraints.
675+
p_shape_b = model_vars.b_var.shape[0] # This has to be _var to work with constraints.
676+
677+
if iterator:
678+
H = op_utils.map_reduce(
679+
last_elem=tf.gather(sample_indices, tf.size(sample_indices) - 1),
680+
data=batched_data,
681+
map_fn=_map,
682+
reduce_fn=_red,
683+
parallel_iterations=1
684+
)
685+
else:
686+
H = _map(
687+
idx=sample_indices,
688+
data=batched_data
689+
)
690+
691+
return H[0]
700692

701693
def tf_byfeature(
702694
self,
@@ -705,6 +697,7 @@ def tf_byfeature(
705697
constraints_loc,
706698
constraints_scale,
707699
model_vars: ModelVars,
700+
iterator,
708701
dtype
709702
) -> List[tf.Tensor]:
710703
"""
@@ -804,20 +797,27 @@ def _map(idx, data):
804797
constraints_loc=constraints_loc,
805798
constraints_scale=constraints_scale,
806799
params=model_vars.params,
807-
p_shape_a=model_vars.a.shape[0],
808-
p_shape_b=model_vars.b.shape[0],
800+
p_shape_a=model_vars.a_var.shape[0], # This has to be _var to work with constraints.
801+
p_shape_b=model_vars.b_var.shape[0], # This has to be _var to work with constraints.
809802
dtype=dtype,
810803
size_factors=size_factors
811804
)
812805

813806
def _red(prev, cur):
814807
return [tf.add(p, c) for p, c in zip(prev, cur)]
815808

816-
H = op_utils.map_reduce(
817-
last_elem=tf.gather(sample_indices, tf.size(sample_indices) - 1),
818-
data=batched_data,
819-
map_fn=_map,
820-
reduce_fn=_red,
821-
parallel_iterations=1,
822-
)
809+
if iterator:
810+
H = op_utils.map_reduce(
811+
last_elem=tf.gather(sample_indices, tf.size(sample_indices) - 1),
812+
data=batched_data,
813+
map_fn=_map,
814+
reduce_fn=_red,
815+
parallel_iterations=1
816+
)
817+
else:
818+
H = _map(
819+
idx=sample_indices,
820+
data=batched_data
821+
)
822+
823823
return H[0]

batchglm/train/tf/nb_glm/jacobians.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,9 @@ def __init__(
159159
in
160160
"""
161161
if constraints_loc is not None and mode != "tf":
162-
raise ValueError("closed form hessian does not work if constraints_loc is not None")
162+
raise ValueError("closed form jacobians do not work if constraints_loc is not None")
163163
if constraints_scale is not None and mode != "tf":
164-
raise ValueError("closed form hessian does not work if constraints_scale is not None")
164+
raise ValueError("closed form jacobians do not work if constraints_scale is not None")
165165

166166
if mode == "analytic":
167167
self.jac = self.analytic(
@@ -286,8 +286,8 @@ def _red(prev, cur):
286286
return tf.add(prev, cur)
287287

288288
params = model_vars.params
289-
p_shape_a = model_vars.a.shape[0]
290-
p_shape_b = model_vars.b.shape[0]
289+
p_shape_a = model_vars.a_var.shape[0] # This has to be _var to work with constraints.
290+
p_shape_b = model_vars.b_var.shape[0] # This has to be _var to work with constraints.
291291

292292
if iterator:
293293
J = op_utils.map_reduce(
@@ -374,8 +374,8 @@ def _red(prev, cur):
374374
return tf.add(prev, cur)
375375

376376
params = model_vars.params
377-
p_shape_a = model_vars.a.shape[0]
378-
p_shape_b = model_vars.b.shape[0]
377+
p_shape_a = model_vars.a_var.shape[0] # This has to be _var to work with constraints.
378+
p_shape_b = model_vars.b_var.shape[0] # This has to be _var to work with constraints.
379379

380380
if iterator == True and batch_model is None:
381381
J = op_utils.map_reduce(

0 commit comments

Comments
 (0)