@@ -32,16 +32,17 @@ def __init__(self, fit_intercept=True):
32
32
33
33
self ._is_fit = False
34
34
35
- def update (self , x , y ):
35
+ def update (self , X , y ):
36
36
r"""
37
- Incrementally update the least-squares coefficients on a new example
38
- via recursive least-squares (RLS) [1]_ .
37
+ Incrementally update the least-squares coefficients for a set of new
38
+ examples .
39
39
40
40
Notes
41
41
-----
42
- The RLS algorithm [2]_ is used to efficiently update the regression
43
- parameters as new examples become available. For a new example
44
- :math:`(\mathbf{x}_{t+1}, \mathbf{y}_{t+1})`, the parameter updates are
42
+ The recursive least-squares algorithm [1]_ [2]_ is used to efficiently
43
+ update the regression parameters as new examples become available. For
44
+ a single new example :math:`(\mathbf{x}_{t+1}, \mathbf{y}_{t+1})`, the
45
+ parameter updates are
45
46
46
47
.. math::
47
48
@@ -55,33 +56,41 @@ def update(self, x, y):
55
56
:math:`\mathbf{X}_{1:t}` and :math:`\mathbf{Y}_{1:t}` are the set of
56
57
examples observed from timestep 1 to *t*.
57
58
58
- To perform the above update efficiently, the RLS algorithm makes use of
59
- the Sherman-Morrison formula [3]_ to avoid re-inverting the covariance
60
- matrix on each new update.
59
+ In the single-example case, the RLS algorithm uses the Sherman-Morrison
60
+ formula [3]_ to avoid re-inverting the covariance matrix on each new
61
+ update. In the multi-example case (i.e., where :math:`\mathbf{X}_{t+1}`
62
+ and :math:`\mathbf{y}_{t+1}` are matrices of `N` examples each), we use
63
+ the generalized Woodbury matrix identity [4]_ to update the inverse
64
+ covariance. This comes at a performance cost, but is still more
65
+ performant than doing multiple single-example updates if *N* is large.
61
66
62
67
References
63
68
----------
64
69
.. [1] Gauss, C. F. (1821) _Theoria combinationis observationum
65
70
erroribus minimis obnoxiae_, Werke, 4. Gottinge
66
71
.. [2] https://en.wikipedia.org/wiki/Recursive_least_squares_filter
67
72
.. [3] https://en.wikipedia.org/wiki/Sherman%E2%80%93Morrison_formula
73
+ .. [4] https://en.wikipedia.org/wiki/Woodbury_matrix_identity
68
74
69
75
Parameters
70
76
----------
71
- x : :py:class:`ndarray <numpy.ndarray>` of shape `(1, M)`
72
- A single example of rank `M`
73
- y : :py:class:`ndarray <numpy.ndarray>` of shape `(1, K)`
74
- A `K`-dimensional target vector for the current example
77
+ X : :py:class:`ndarray <numpy.ndarray>` of shape `(N, M)`
78
+ A dataset consisting of `N` examples, each of dimension `M`
79
+ y : :py:class:`ndarray <numpy.ndarray>` of shape `(N, K)`
80
+ The targets for each of the `N` examples in `X`, where each target
81
+ has dimension `K`
75
82
"""
76
83
if not self ._is_fit :
77
84
raise RuntimeError ("You must call the `fit` method before calling `update`" )
78
85
79
- x , y = np .atleast_2d (x ), np .atleast_2d (y )
80
- beta , S_inv = self .beta , self .sigma_inv
86
+ X , y = np .atleast_2d (X ), np .atleast_2d (y )
81
87
82
- X1 , Y1 = x .shape [0 ], y .shape [0 ]
83
- err_str = f"First dimension of x and y must be 1, but got { X1 } and { Y1 } "
84
- assert X1 == Y1 == 1 , err_str
88
+ X1 , Y1 = X .shape [0 ], y .shape [0 ]
89
+ self ._update1D (X , y ) if X1 == Y1 == 1 else self ._update2D (X , y )
90
+
91
+ def _update1D (self , x , y ):
92
+ """Sherman-Morrison update for a single example"""
93
+ beta , S_inv = self .beta , self .sigma_inv
85
94
86
95
# convert x to a design vector if we're fitting an intercept
87
96
if self .fit_intercept :
@@ -93,6 +102,22 @@ def update(self, x, y):
93
102
# update the model coefficients
94
103
beta += S_inv @ x .T @ (y - x @ beta )
95
104
105
+ def _update2D (self , X , y ):
106
+ """Woodbury update for multiple examples"""
107
+ beta , S_inv = self .beta , self .sigma_inv
108
+
109
+ # convert X to a design matrix if we're fitting an intercept
110
+ if self .fit_intercept :
111
+ X = np .c_ [np .ones (X .shape [0 ]), X ]
112
+
113
+ I = np .eye (X .shape [0 ])
114
+
115
+ # update the inverse of the covariance matrix via Woodbury identity
116
+ S_inv -= S_inv @ X .T @ np .linalg .pinv (I + X @ S_inv @ X .T ) @ X @ S_inv
117
+
118
+ # update the model coefficients
119
+ beta += S_inv @ X .T @ (y - X @ beta )
120
+
96
121
def fit (self , X , y ):
97
122
"""
98
123
Fit the regression coefficients via maximum likelihood.
0 commit comments