1 /*  _______________________________________________________________________
2 
3     PECOS: Parallel Environment for Creation Of Stochastics
4     Copyright (c) 2011, Sandia National Laboratories.
5     This software is distributed under the GNU Lesser General Public License.
6     For more information, see the README file in the top Pecos directory.
7     _______________________________________________________________________ */
8 
9 #include <DirectANN.hpp>
10 #include <BoundedVariables.hpp>
11 #include <AffineVariableTransformation.hpp>
12 #include <RegressionBuilder.hpp>
13 #include "linear_solvers.hpp"
14 #include "OptionsList.hpp"
15 
16 namespace Pecos {
17 namespace surrogates {
18 
DirectANNBasisSet(const RealMatrix & weights_in)19 DirectANNBasisSet::DirectANNBasisSet(const RealMatrix &weights_in)
20   : weights(weights_in) {
21 }
22 
nodeSum(int index,const RealVector & x) const23 Real DirectANNBasisSet::nodeSum(int index, const RealVector &x) const
24 {
25   if (index >= weights.numRows())
26     throw(std::runtime_error("basis set index is out of bounds"));
27   if (x.length() + 1 !=  weights.numCols())
28     throw(std::runtime_error("size of input to hidden layer weights matrix is incorrect"));
29   Real sum = 0.0;
30   for (int i = 0; i < x.length(); i++) {
31     sum += weights(index,i)*x[i];
32   }
33   sum += weights(index, x.length()); // bias term
34   return sum;
35 }
36 
eval(int index,const RealVector & x) const37 Real DirectANNBasisSet::eval(int index, const RealVector &x) const
38 {
39   return tanh(nodeSum(index, x));
40 }
41 
42 /*
43  * no longer valid because output node has no tanh(.) ...
44 Real DirectANNBasisSet::deriv(int index, const RealVector &x, const IntVector &vars) const
45 {
46   if (vars.length() != 1)
47     throw(std::runtime_error("vars vector too long"));
48   if (vars[0] >= x.length())
49     throw(std::runtime_error("vars[0] entry too large"));
50   Real sum = nodeSum(index, x);
51   Real tanhsum = tanh(sum);
52   return (1.0 - tanhsum*tanhsum)*weights(index, vars[0]);
53 }
54 */
55 
56 // default constructor
DirectANN()57 DirectANN::DirectANN(){}
58 // default destructor
~DirectANN()59 DirectANN::~DirectANN(){}
60 
61 // main constructor
DirectANN(const int num_nodes,const Real range_param,const RealMatrix & samples,const RealMatrix & response,const std::string scaler_type,const int seed)62 DirectANN::DirectANN(const int num_nodes, const Real range_param,
63                      const RealMatrix &samples, const RealMatrix &response,
64                      const std::string scaler_type, const int seed) {
65 
66   const int num_vars = samples.numRows();
67   const int num_samples = samples.numCols();
68   nodes = std::min(num_nodes, maxNodes); // max of 100 allowed
69   if (num_samples < num_nodes + 1) {
70     throw(std::runtime_error("DirectANN needs at least"
71           " as many samples are the number of nodes + 1"));
72   }
73 
74   // Scale the data
75   if (scaler_type == "mean normalization")
76     ds = std::make_shared<NormalizationScaler>(samples, true);
77   else if (scaler_type == "min-max normalization")
78     ds = std::make_shared<NormalizationScaler>(samples, false);
79   else if (scaler_type == "standardization")
80     ds = std::make_shared<StandardizationScaler>(samples);
81   else
82     throw(std::string("Invalid scaler type"));
83 
84   RealMatrix scaled_samples = ds->getScaledFeatures();
85 
86   // Randomly generate weights for the first layer
87   RealVector ranges;
88   define_homogeneous_ranges(nodes, -range_param/2.0, range_param/2.0, ranges);
89   std::shared_ptr<Variables> variables(new BoundedVariables);
90   std::dynamic_pointer_cast<BoundedVariables>(variables)->set_ranges(ranges);
91   std::shared_ptr<VariableTransformation>
92     var_transform(new AffineVariableTransformation);
93   var_transform->set_variables(variables);
94 
95   RealMatrix random_weights;
96   generate_uniform_samples(nodes, num_vars + 1, seed, *var_transform,
97                              random_weights);
98   bs = std::make_shared<DirectANNBasisSet>(random_weights);
99 
100   // construct the linear system
101   RealMatrix A(num_samples, nodes + 1);
102   RealVector b(num_samples);
103 
104   for (int s = 0; s < num_samples; s++) {
105     for (int n = 0; n < nodes; n++) {
106       RealVector sample = Teuchos::getCol<int,Real> (Teuchos::View,
107                                                      scaled_samples, s);
108       A(s,n) = bs->eval(n, sample);
109     }
110     A(s, nodes) = 1.0; // bias term
111     b[s] = response(s,0);
112   }
113 
114   // set solver options and solve
115   util::OptionsList params;
116   params.set("regression_type",util::ORTHOG_MATCH_PURSUIT);
117   std::shared_ptr<util::LinearSystemSolver> solver = regression_solver_factory(params);
118 
119   solver->solve(A, b, params);
120   solver->get_final_solutions(coeffs);
121 }
122 
123 // constructor for existing (i.e. calibrated) ANN -- stills needs
124 // scaler info
125 /*
126 DirectANN::DirectANN(const RealMatrix& bs_matrix, const RealVector& coeffs_in)
127 {
128   bs = std::make_shared<DirectANNBasisSet>(bs_matrix);
129   setCoefficients(coeffs_in);
130 }
131 */
132 
133 // constructor for known 1st layer weights, unknown output layer weights
134 // initially used to compare output from pecos ANN ... no longer suitable
135 // for this in current form.
136 //
137 /*
138 DirectANN::DirectANN(int num_nodes, const RealMatrix& bs_matrix,
139                      RealMatrix &samples, RealMatrix& response)
140 {
141   bs = std::make_shared<DirectANNBasisSet>(bs_matrix);
142 
143   const int num_samples = samples.numCols();
144   nodes = std::min(num_nodes, maxNodes); // max of 100 allowed
145   if (num_samples < num_nodes + 1) {
146     throw(std::runtime_error("DirectANN needs at least"
147           " as many samples are the number of nodes + 1"));
148   }
149 
150   // Scale the data
151   const Real norm_factor = 0.8;
152   const int num_vars = samples.numRows();
153   ds = std::make_shared<NormalizationScaler>(samples, false);
154   //ds = std::make_shared<StandardizationScaler>(samples);
155   RealMatrix scaled_samples = ds->getScaledFeatures();
156 
157   // construct the linear system
158   RealMatrix A(num_samples, nodes + 1);
159   RealVector b(num_samples);
160 
161   for (int s = 0; s < num_samples; s++) {
162     for (int n = 0; n < nodes; n++) {
163       RealVector sample = Teuchos::getCol<int,Real> (Teuchos::View,
164                                                      scaled_samples, s);
165 
166       A(s,n) = bs->eval(n, sample);
167     }
168     A(s, nodes) = 1.0; // bias term
169     b(s) = response(s,0);
170   }
171 
172   // set solver options and solve
173   util::OptionsList params;
174   params.set("regression_type",util::ORTHOG_MATCH_PURSUIT);
175   //params.set("regression_type",util::QR_LEAST_SQ_REGRESSION);
176   std::shared_ptr<util::LinearSystemSolver> solver = regression_solver_factory(params);
177 
178   solver->solve(A, b, params);
179   solver->get_final_solutions(coeffs);
180 }
181 */
182 
183 
evaluate(const RealVector & x) const184 Real DirectANN::evaluate(const RealVector &x) const
185 {
186   RealVector u;
187   if (ds->has_scaling) {
188     u = ds->scaleFeatures(x);
189   }
190   else {
191     u = x;
192   }
193   const int num_features = u.length();
194   const int num_coeffs = coeffs.length();
195   if (num_features + 1 != bs->weights.numCols()) {
196     throw(std::runtime_error("DirectANN input to hidden layer weights do"
197           " not match the number of features"));
198   }
199   if (nodes != bs->weights.numRows()) {
200     throw(std::runtime_error("DirectANN input to hidden layer weights do"
201           " not match the number of nodes"));
202   }
203   Real sum = 0.0;
204   for (int i = 0; i < nodes; i++) {
205     sum += coeffs[i]*bs->eval(i, u);
206   }
207   sum += coeffs[num_coeffs - 1]; // bias term
208   return sum;
209 }
210 
value(const RealMatrix & samples,RealMatrix & approx_values)211 void DirectANN::value(const RealMatrix &samples, RealMatrix &approx_values) {
212   const int num_features = samples.numRows();
213   const int num_samples = samples.numCols();
214   if (num_samples != approx_values.numRows()) {
215     throw(std::runtime_error("Direct ANN value inputs are not consistent."
216           " Number of samples and approximation sizes do not match"));
217   }
218   RealVector x(num_features);
219   // can't use Teuchos::getCol because it doesn't compile with const samples
220   for (int i = 0; i < num_samples; i++) {
221     for (int j = 0; j < num_features; j++) {
222       x(j) = samples(j,i);
223     }
224     approx_values(i,0) = this->evaluate(x);
225   }
226 }
227 
gradient(const RealMatrix & samples,int qoi,RealMatrix & gradients)228 void DirectANN::gradient(const RealMatrix &samples, int qoi, RealMatrix &gradients) {
229   throw(std::string("This Function type does not provide gradients"));
230 }
231 
jacobian(const RealVector & sample,RealMatrix & jacobian)232 void DirectANN::jacobian(const RealVector &sample, RealMatrix &jacobian) {
233   throw(std::string("This Function type does not provide a jacobian"));
234 }
235 
hessian(const RealMatrix & samples,int qoi,RealMatrixList & hessians)236 void DirectANN::hessian(const RealMatrix &samples, int qoi, RealMatrixList &hessians) {
237   throw(std::string("This Function type does not provide hessians"));
238 }
239 
240 }  // namespace surrogates
241 }  // namespace Pecos
242