Skip to content

Commit 3a70089

Browse files
batchglm numpy backend support functional from diffxpy
1 parent a450383 commit 3a70089

3 files changed

Lines changed: 129 additions & 9 deletions

File tree

batchglm/train/numpy/base_glm/estimator.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import abc
2+
import logging
23
import numpy as np
4+
import pprint
35
import scipy
46
import scipy.optimize
57

68
from .external import _EstimatorGLM, pkg_constants
79
from .training_strategies import TrainingStrategies
810

11+
logger = logging.getLogger("batchglm")
12+
913

1014
class EstimatorGlm(_EstimatorGLM, metaclass=abc.ABCMeta):
1115
"""
@@ -18,6 +22,8 @@ def __init__(
1822
input_data,
1923
dtype,
2024
):
25+
if input_data.design_scale.shape[1] != 1:
26+
raise ValueError("cannot model more than one scale parameter with numpy backend right now.")
2127
_EstimatorGLM.__init__(
2228
self=self,
2329
model=model,
@@ -32,9 +38,17 @@ def __init__(
3238
def initialize(self):
3339
pass
3440

35-
def train_sequence(self, training_strategy: str):
41+
def train_sequence(
42+
self,
43+
training_strategy: str = "DEFAULT"
44+
):
3645
if isinstance(training_strategy, str):
3746
training_strategy = self.TrainingStrategies[training_strategy].value[0]
47+
48+
if training_strategy is None:
49+
training_strategy = self.TrainingStrategies.DEFAULT.value
50+
51+
logging.getLogger("batchglm").info("training strategy:\n%s", pprint.pformat(training_strategy))
3852
self.train(**training_strategy)
3953

4054
def train(
@@ -44,17 +58,17 @@ def train(
4458
):
4559
# Iterate until conditions are fulfilled.
4660
train_step = 0
47-
delayed_b_converged = np.tile(False, self.model.model_vars.n_features)
61+
delayed_converged = np.tile(False, self.model.model_vars.n_features)
4862

4963
ll_current = - self.model.ll_byfeature
50-
print("iter %i: ll=%f" % (0, np.sum(ll_current)))
51-
while (np.any(np.logical_not(self.model.converged)) or np.any(np.logical_not(delayed_b_converged))) and \
64+
logging.getLogger("batchglm").debug("iter %i: ll=%f" % (0, np.sum(ll_current)))
65+
while np.any(np.logical_not(delayed_converged)) and \
5266
train_step < max_steps:
5367
# Update parameters:
5468
# Line search step for scale model:
5569
if train_step % update_b_freq == 0 and train_step > 0:
5670
b_var_cache = self.model.b_var.copy()
57-
self.model.b_var = self.b_step(idx=np.where(np.logical_not(delayed_b_converged))[0])
71+
self.model.b_var = self.b_step(idx=np.where(np.logical_not(delayed_converged))[0])
5872
# Reverse update by feature if update leads to worse loss:
5973
ll_proposal = - self.model.ll_byfeature
6074
b_var_new = self.model.b_var.copy()
@@ -71,10 +85,14 @@ def train(
7185
# Location model convergence status has to be updated if b model was updated
7286
if train_step % update_b_freq == 0 and train_step > 0:
7387
self.model.converged = converged_f
88+
delayed_converged = converged_f
7489
else:
7590
self.model.converged = np.logical_or(self.model.converged, converged_f)
7691
train_step += 1
77-
print("iter %i: ll=%f, converged: %i" % (train_step, np.sum(ll_current), np.sum(self.model.converged)))
92+
logging.getLogger("batchglm").debug(
93+
"iter %i: ll=%f, converged: %i" %
94+
(train_step, np.sum(ll_current), np.sum(self.model.converged))
95+
)
7896
self.lls.append(ll_current)
7997

8098
def iwls_step(self) -> np.ndarray:
@@ -167,10 +185,11 @@ def finalize(self):
167185
transfers relevant attributes.
168186
"""
169187
# Read from numpy-IRLS estimator specific model:
170-
self._hessian = - self.model.fim
188+
189+
self._hessian = self.model.hessian
171190
self._fisher_inv = np.linalg.inv(- self._hessian)
172-
self._jacobian = self.model.jac
173-
self._log_likelihood = self.model.ll
191+
self._jacobian = np.sum(np.abs(self.model.jac / self.model.x.shape[0]), axis=1)
192+
self._log_likelihood = self.model.ll_byfeature
174193
self._loss = np.sum(self._log_likelihood)
175194

176195
@abc.abstractmethod

batchglm/train/numpy/base_glm/model.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,74 @@ def fim(self) -> np.ndarray:
109109
xh
110110
)
111111

112+
@abc.abstractmethod
113+
def hessian_weight_aa(self) -> np.ndarray:
114+
pass
115+
116+
@property
117+
def hessian_aa(self) -> np.ndarray:
118+
"""
119+
120+
:return: (features x inferred param x inferred param)
121+
"""
122+
w = self.hessian_weight_aa
123+
xh = np.matmul(self.design_loc, self.constraints_loc)
124+
return np.einsum(
125+
'fob,oc->fbc',
126+
np.einsum('ob,of->fob', xh, w),
127+
xh
128+
)
129+
130+
@abc.abstractmethod
131+
def hessian_weight_ab(self) -> np.ndarray:
132+
pass
133+
134+
@property
135+
def hessian_ab(self) -> np.ndarray:
136+
"""
137+
138+
:return: (features x inferred param x inferred param)
139+
"""
140+
w = self.hessian_weight_ab
141+
return np.einsum(
142+
'fob,oc->fbc',
143+
np.einsum('ob,of->fob', np.matmul(self.design_loc, self.constraints_loc), w),
144+
np.matmul(self.design_scale, self.constraints_scale)
145+
)
146+
147+
@abc.abstractmethod
148+
def hessian_weight_bb(self) -> np.ndarray:
149+
pass
150+
151+
@property
152+
def hessian_bb(self) -> np.ndarray:
153+
"""
154+
155+
:return: (features x inferred param x inferred param)
156+
"""
157+
w = self.hessian_weight_bb
158+
xh = np.matmul(self.design_scale, self.constraints_scale)
159+
return np.einsum(
160+
'fob,oc->fbc',
161+
np.einsum('ob,of->fob', xh, w),
162+
xh
163+
)
164+
165+
@property
166+
def hessian(self) -> np.ndarray:
167+
"""
168+
169+
:return: (features x inferred param x inferred param)
170+
"""
171+
h_aa = self.hessian_aa
172+
h_bb = self.hessian_bb
173+
h_ab = self.hessian_ab
174+
h_ba = np.transpose(h_ab, axes=[0, 2, 1])
175+
return np.concatenate([
176+
np.concatenate([h_aa, h_ab], axis=2),
177+
np.concatenate([h_ba, h_bb], axis=2)
178+
], axis=1)
179+
112180
@property
113181
def jac(self) -> np.ndarray:
114182
return np.concatenate([self.jac_a, self.jac_b], axis=-1)

batchglm/train/numpy/glm_nb/model.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,39 @@ def jac_weight_b_j(self, j):
101101
const3 = np.log(scale) + np.ones_like(scale) - np.log(r_plus_mu)
102102
return scale * (const1 + const2 + const3)
103103

104+
@property
105+
def hessian_weight_ab(self):
106+
scale = self.scale
107+
loc = self.location
108+
return np.multiply(
109+
loc * scale,
110+
np.asarray(self.x - loc) / np.square(loc + scale)
111+
)
112+
113+
@property
114+
def hessian_weight_aa(self):
115+
scale = self.scale
116+
loc = self.location
117+
if isinstance(self.x, np.ndarray):
118+
x_by_scale_plus_one = self.x / scale + np.ones_like(scale)
119+
else:
120+
x_by_scale_plus_one = np.asarray(self.x.divide(scale) + np.ones_like(scale))
121+
122+
return - loc * x_by_scale_plus_one / np.square((loc / scale) + np.ones_like(loc))
123+
124+
@property
125+
def hessian_weight_bb(self):
126+
scale = self.scale
127+
loc = self.location
128+
scale_plus_x = np.asarray(self.x + scale)
129+
scale_plus_loc = scale + loc
130+
# Define graphs for individual terms of constant term of hessian:
131+
const1 = scipy.special.digamma(scale_plus_x) + scale * scipy.special.polygamma(n=1, x=scale_plus_x)
132+
const2 = - scipy.special.digamma(scale) + scale * scipy.special.polygamma(n=1, x=scale)
133+
const3 = - loc * scale_plus_x + np.ones_like(scale) * 2. * scale * scale_plus_loc / np.square(scale_plus_loc)
134+
const4 = np.log(scale) + np.ones_like(scale) * 2. - np.log(scale_plus_loc)
135+
return scale * (const1 + const2 + const3 + const4)
136+
104137
@property
105138
def ll(self):
106139
scale = self.scale

0 commit comments

Comments
 (0)