Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

""" 

BaseOptimizer serves as base for all optimizers. 

""" 

 

import numpy as np 

from abc import ABC, abstractmethod 

from typing import Any, Dict, Tuple, Union 

from .fit_methods import available_fit_methods 

from .io import _write_pickle 

 

 

class BaseOptimizer(ABC): 

"""BaseOptimizer class. 

 

Serves as base class for all Optimizers solving the linear 

:math:`\\boldsymbol{X}\\boldsymbol{a} = \\boldsymbol{y}` problem. 

 

Parameters 

---------- 

fit_data : tuple(numpy.ndarray, numpy.ndarray) 

the first element of the tuple represents the `NxM`-dimensional 

fit matrix `A` whereas the second element represents the 

vector of `N`-dimensional target values `y`; here `N` (=rows of 

`A`, elements of `y`) equals the number of target values and 

`M` (=columns of `A`) equals the number of parameters 

fit_method : str 

method to be used for training; possible choice are 

"least-squares", "lasso", "elasticnet", "bayesian-ridge", "ardr", 

"rfe", "split-bregman" 

standardize : bool 

if True the fit matrix and target values are standardized before fitting, 

meaning columns in the fit matrix and th target values are rescaled to 

have a standard deviation of 1.0. 

check_condition : bool 

if True the condition number will be checked 

(this can be sligthly more time consuming for larger 

matrices) 

seed : int 

seed for pseudo random number generator 

""" 

 

def __init__(self, 

fit_data: Tuple[np.ndarray, np.ndarray], 

fit_method: str, 

standardize: bool = True, 

check_condition: bool = True, 

seed: int = 42): 

""" 

Attributes 

---------- 

_A : numpy.ndarray 

fit matrix (N, M) 

_y : numpy.ndarray 

target values (N) 

""" 

 

if fit_method not in available_fit_methods: 

raise ValueError('Unknown fit_method: {}'.format(fit_method)) 

 

60 ↛ 61line 60 didn't jump to line 61, because the condition on line 60 was never true if fit_data is None: 

raise TypeError('Invalid fit data; Fit data can not be None') 

if fit_data[0].shape[0] != fit_data[1].shape[0]: 

raise ValueError('Invalid fit data; shapes of fit matrix' 

' and target vector do not match') 

65 ↛ 66line 65 didn't jump to line 66, because the condition on line 65 was never true if len(fit_data[0].shape) != 2: 

raise ValueError('Invalid fit matrix; must have two dimensions') 

 

self._A, self._y = fit_data 

self._n_rows = self._A.shape[0] 

self._n_cols = self._A.shape[1] 

self._fit_method = fit_method 

self._standarize = standardize 

self._check_condition = check_condition 

self._seed = seed 

self._fit_results = {'parameters': None} 

 

def compute_rmse(self, A: np.ndarray, y: np.ndarray) -> float: 

""" 

Returns the root mean squared error (RMSE) using 

:math:`\\boldsymbol{A}`, :math:`\\boldsymbol{y}`, and the vector of 

fitted parameters :math:`\\boldsymbol{x}`, corresponding to 

:math:`\\|\\boldsymbol{A}\\boldsymbol{x}-\\boldsymbol{y}\\|_2`. 

 

Parameters 

---------- 

A 

fit matrix (`N,M` array) where `N` (=rows of `A`, elements 

of `y`) equals the number of target values and `M` 

(=columns of `A`) equals the number of parameters 

(=elements of `x`) 

y 

vector of target values 

""" 

y_predicted = self.predict(A) 

delta_y = y_predicted - y 

rmse = np.sqrt(np.mean(delta_y**2)) 

return rmse 

 

def predict(self, A: np.ndarray) -> Union[np.ndarray, float]: 

""" 

Predicts data given an input matrix :math:`\\boldsymbol{A}`, 

i.e., :math:`\\boldsymbol{A}\\boldsymbol{x}`, where 

:math:`\\boldsymbol{x}` is the vector of the fitted parameters. 

The method returns the vector of predicted values or a float 

if a single row provided as input. 

 

Parameters 

---------- 

A 

fit matrix where `N` (=rows of `A`, elements of `y`) equals the 

number of target values and `M` (=columns of `A`) equals the number 

of parameters 

""" 

return np.dot(A, self.parameters) 

 

def get_contributions(self, A: np.ndarray) -> np.ndarray: 

""" 

Returns the average contribution for each row of `A` 

to the predicted values from each element of the parameter vector. 

 

Parameters 

---------- 

A 

fit matrix where `N` (=rows of `A`, elements of `y`) equals the 

number of target values and `M` (=columns of `A`) equals the number 

of parameters 

""" 

return np.mean(np.abs(np.multiply(A, self.parameters)), axis=0) 

 

@abstractmethod 

def train(self) -> None: 

pass 

 

@property 

def summary(self) -> Dict[str, Any]: 

""" comprehensive information about the optimizer """ 

target_values_std = np.std(self._y) 

 

info = dict() 

info['seed'] = self.seed 

info['fit_method'] = self.fit_method 

info['standardize'] = self.standardize 

info['n_target_values'] = self.n_target_values 

info['n_parameters'] = self.n_parameters 

info['n_nonzero_parameters'] = self.n_nonzero_parameters 

info['parameters_norm'] = self.parameters_norm 

info['target_values_std'] = target_values_std 

return {**info, **self._fit_results} 

 

def write_summary(self, fname: str): 

""" Writes summary dict to file """ 

_write_pickle(fname, self.summary) 

 

def __str__(self) -> str: 

width = 54 

s = [] 

s.append(' {} '.format(self.__class__.__name__).center(width, '=')) 

for key in sorted(self.summary.keys()): 

value = self.summary[key] 

if isinstance(value, (str, int, np.integer)): 

s.append('{:30} : {}'.format(key, value)) 

elif isinstance(value, (float)): 

s.append('{:30} : {:.7g}'.format(key, value)) 

s.append(''.center(width, '=')) 

return '\n'.join(s) 

 

def __repr__(self) -> str: 

return 'BaseOptimizer((A, y), {}, {}'.format( 

self.fit_method, self.seed) 

 

@property 

def fit_method(self) -> str: 

""" fit method """ 

return self._fit_method 

 

@property 

def parameters(self) -> np.ndarray: 

""" copy of parameter vector """ 

if self._fit_results['parameters'] is None: 

return None 

else: 

return self._fit_results['parameters'].copy() 

 

@property 

def parameters_norm(self) -> float: 

""" the norm of the parameters """ 

if self.parameters is None: 

return None 

else: 

return np.linalg.norm(self.parameters) 

 

@property 

def n_nonzero_parameters(self) -> int: 

""" number of non-zero parameters """ 

if self.parameters is None: 

return None 

else: 

return np.count_nonzero(self.parameters) 

 

@property 

def n_target_values(self) -> int: 

""" number of target values (=rows in `A` matrix) """ 

return self._n_rows 

 

@property 

def n_parameters(self) -> int: 

""" number of parameters (=columns in `A` matrix) """ 

return self._n_cols 

 

@property 

def standardize(self) -> bool: 

""" if True standardize the fit matrix before fitting """ 

return self._standarize 

 

@property 

def seed(self) -> int: 

""" seed used to initialize pseudo random number generator """ 

return self._seed