This notebook is intended to introduce and demonstrate some of the features of pyMAISE and examine the performance of machine learning models on a common nuclear engineering application. For further information on the capabilities of the classes and functions shown in this notebook, please refer to the pyMAISE API reference documentation.
MIT Reactor
Inputs: Control rod heights (\(cm\))
Outputs: Pin power (\(W\))
The MIT reactor data set represents the institution’s light-water-cooled 6 MW thermal power reactor. The figure below shows the core contains 22 fuel elements, 5 locations for in-core experiments, and six control blades surrounding them. The data set is used to find a relationship between the six control blade heights and the power produced by the 22 fuel elements in the core. Therefore, the data set is constructed by perturbing the depths of the control blades in the reactor—the corresponding output results in the power levels for each of the 22 fuel elements. The data was generated using MCNP, and the data set size includes 1000 simulations/samples [RPS+23]. The goal is to use pyMAISE to build, tune, and compare the performance of various ML models in predicting the core power distribution based on the control blade insertion depth.
The following are a few standard packages and functions that will prove helpful while using pyMAISE.
[1]:
%load_ext autoreload
%autoreload 2
#import functions as F
import time
import cv2
import numpy as np
import pandas as pd
# Set display option to show all rows and columns
pd.set_option('display.max_rows', None)
pd.set_option('display.max_columns', None)
# Set the width of the columns
pd.set_option('display.width', None)
# See the full content of each column
pd.set_option('display.max_colwidth', None)
import matplotlib.pyplot as plt
from scipy.stats import uniform, randint
from sklearn.preprocessing import MinMaxScaler
# Plot settings
matplotlib_settings = {
"font.size": 12,
"legend.fontsize": 11,
"figure.figsize": (8, 8)
}
plt.rcParams.update(**matplotlib_settings)
We need to import several functions and classes for machine learning tuning and analysis with pyMAISE. Firstly, we need the MITR data set, which we can get using pyMAISE.datasets.load_MITR. We must split the data into training/testing data and scale it. For this, we use the pyMAISE.preprocessing Python module. The remaining classes we can get from pyMAISE directly which we import as mai for convenience.
[2]:
from pyMAISE.datasets import load_MITR
from pyMAISE.preprocessing import correlation_matrix, train_test_split, scale_data
import pyMAISE as mai
pyMAISE Initialization
Starting any pyMAISE job requires initialization. This includes the definition of global settings used throughout pyMAISE. These settings and their defaults include:
problem_type: the problem type, either regression or classification, defined bypyMAISE.ProblemType,verbosity=0: the level of output from pyMAISE,random_state=None: the seed for the random number generator, which can be used to get reproducible results from pyMAISE,num_configs_saved=5: the number of top hyperparameter configurations for each model evaluated during tuning,new_nn_architecture=True: a boolean that dictates whether to use the old deprecated pyMAISE neural network tuning architecture,cuda_visible_devices=None: sets theCUDA_VISIBLE_DEVICESenvironment variable,run_parallel=False: whether to run the NN tuner in parallel (only supported for GPUs),max_models_per_device=numpy.inf: the maximum number of NNs training on a single GPU; if it’snumpy.inf, then pyMAISE will try to submit as many jobs as are available in memory on a given GPU.
The only argument that needs to be specified is problem_type. We also pass "-1" to cuda_visible_devices to ensure we only use tensorflow on the CPU. This is useful for this problem since we will build relatively simple, dense feedforward neural networks with a reasonably small data set. Therefore, running tensorflow on a GPU may hurt our performance. We leave the others default, giving us five hyperparameter configurations for each model, keeping the stochastic nature of some of the
algorithms, and using the current neural network hyperparameter tuning architecture.
[3]:
global_settings = mai.init(
problem_type=mai.ProblemType.REGRESSION, # Define a regression problem
cuda_visible_devices="-1" # Use CPU only
)
Data Loading and Preprocessing
pyMAISE has several benchmarked data sets, such as this MIT reactor data set. Each data set has an accompanying load function. Most load functions return three xarray.DataArrays: the raw, input, and output data. You can load personal data using the pyMAISE.preprocessing.read_csv function. Refer to the pyMAISE API reference documentation for specifics on the use of this function. Loading the MIT reactor data gives the
following data:
[4]:
data, inputs, outputs = load_MITR()
The MIT reactor data set has six inputs; one for each control rod position:
[5]:
inputs
[5]:
<xarray.DataArray (index: 1000, variable: 6)>
array([[25.95991738, 22.94937226, 20.85317495, 24.66916763, 20.48104739,
25.35726597],
[21.75386762, 25.3606259 , 20.58853009, 20.11087249, 27.46711019,
25.81658492],
[27.42919944, 23.5701803 , 27.59630663, 26.39044508, 23.99603712,
24.61182164],
...,
[24.47247392, 23.21393049, 21.36224456, 25.72137934, 21.72606189,
20.486756 ],
[25.14502476, 25.88719097, 22.63841745, 23.11818585, 27.13820033,
20.12257523],
[20.80736327, 20.47244047, 26.3283514 , 24.93870275, 22.13608868,
20.23749172]])
Coordinates:
* index (index) int64 0 1 2 3 4 5 6 7 ... 992 993 994 995 996 997 998 999
* variable (variable) object 'CR1' 'CR2' 'CR3' 'CR4' 'CR5' 'CR6'and 22 fuel element power outputs:
[6]:
outputs
[6]:
<xarray.DataArray (index: 1000, variable: 22)>
array([[25930.9161377 , 22958.31494141, 21725.51635742, ...,
17912.01452637, 18207.04260254, 19089.96942139],
[25883.078125 , 22856.06195068, 21602.10876465, ...,
18699.55557251, 18381.29052734, 19052.58782959],
[25672.20825195, 22584.91094971, 21419.95025635, ...,
17878.91418457, 17831.21044922, 18702.88995361],
...,
[26011.28759766, 22868.43511963, 21718.16387939, ...,
17537.16012573, 17521.70654297, 18469.28430176],
[25867.42346191, 22746.65338135, 21650.78973389, ...,
17898.8989563 , 17467.56048584, 18369.0256958 ],
[26047.4777832 , 22729.4630127 , 21553.59606934, ...,
17622.36529541, 17503.63659668, 18324.41955566]])
Coordinates:
* index (index) int64 0 1 2 3 4 5 6 7 ... 992 993 994 995 996 997 998 999
* variable (variable) object 'A-2' 'B-1' 'B-2' 'B-4' ... 'C-13' 'C-14' 'C-15'To get a better understanding of this data set lets plot a correlation matrix of the data using pyMAISE.preprocessing.correlation_matrix.
[7]:
correlation_matrix(data)
plt.show()
A negative correlation exists between the control rod positions and the inner fuel elements (A and B elements).
With the data loaded, we can now preprocess it. This includes splitting into training and testing data sets and scaling. For splitting, we use the pyMAISE.preprocessing.train_test_split function and define the fraction of data for testing using test_size. On MITR, we take 30% of the data for testing. The result is a tuple of data: (xtrain, xtest, ytrain, ytest). We can then scale this data using pyMAISE.preprocessing.scale_data with any object that supports fit_transform and
transform functions. Common scalers include sklearn.preprocessing.MinMaxScaler and sklearn.preprocessing.StandardScaler. Many machine learning models learn best with scaled data. We use min-max scaling on both the input and output data for this data set.
pyMAISE.preprocessing.scale_data returns the scaled data, and the scaler fits the data. These scalers are used in postprocessing to evaluate the performance of the models.
[8]:
xtrain, xtest, ytrain, ytest = train_test_split(data=[inputs, outputs], test_size=0.3)
xtrain, xtest, xscaler = scale_data(xtrain, xtest, scaler=MinMaxScaler())
ytrain, ytest, yscaler = scale_data(ytrain, ytest, scaler=MinMaxScaler())
Model Initialization
For model initialization and tuning, we use the pyMAISE.Tuner. pyMAISE supports both classical ML methods with scikit-learn and sequential neural networks with Keras. We strongly recommend you refer to pyMAISE.Tuner in the pyMAISE API reference documentation for a list of supported models, neural network layers, and other information crucial for
optimal tuning. We can define all the models we wish to hyperparameter tune using a dictionary. For the MITR data set, we define the following regression models with their dictionary keys:
linear:
"Linear",ridge:
"RD",elastic net:
"EN",lasso:
"Lasso",decision tree:
"DT",random forest:
"RF",extra trees:
"ET",k-nearest neighbors:
"KN",gradient boosting:
GB,stacking:
Stacking,multi-output:
MultiOutput,feedforward neural network:
"FNN".
pyMAISE uses the dictionary keys for classical models to determine which scikit-learn model you request. If the keys do not match any supported keys, then it is assumed to be a neural network. In the "models" key, we define these models in a list. We can then define the hyperparameters for classical models, which will remain constant throughout tuning. These parameters must only be defined if you want something different from the default.
Refer to the model documentation in scikit-learn for hyperparameters and defaults.
Note that this is a multi-output problem. Thus, we need to define how we are going to extend. In pyMAISE, we do so using a dictionary key "multi_output to support models that don’t natively have multi-output capabilities (eg. GB and AB below). We also provided a specific multi-output model, which is demonstrated below for SVM. For Stacking, the user must pass through their estimators using the same dictionary keys as with the other model in the benchmark.
For the neural network model, we need to define the architecture, optimizer, compiling, and fitting parameters under the "structural_params", "optimizer", "compile_params", and "fitting_params" keys. These parameters include those that remain constant and change during tuning. To define hyperparameters for tuning, we use the pyMAISE.Int, pyMAISE.Float, pyMAISE.Choice, pyMAISE.Boolean, and pyMAISE.Fixed classes. These classes are wrappers for
KerasTuner.
To define the model architecture, we use the supported Keras layers defined in the pyMAISE API reference documentation under the pyMAISE.Tuner. These layers must differ in name, but use the base layer name. Within the layers, pyMAISE supports additional hyperparameters, which include "sublayer", "wrapper", and "num_layers" to define sublayers, wrappers (such as
keras.layers.TimeDistributed), and the number of layers.
We can then pass all this information to pyMAISE.Tuner.
[9]:
model_settings = {
"models": ["Linear", "Lasso", "Stacking", "MultiOutput", "GB", "DT", "FNN", "RD", "ET", "EN", "AB", "RF", "KN"],
"FNN": {
"structural_params": {
"Dense_hidden": {
"num_layers": mai.Int(min_value=0, max_value=3),
"units": mai.Int(min_value=25, max_value=400),
"activation": "relu",
"kernel_initializer": "normal",
"sublayer": mai.Choice(["Dropout_hidden", "None"]),
"Dropout_hidden": {
"rate": mai.Float(min_value=0.4, max_value=0.6),
},
},
"Dense_output": {
"units": ytrain.shape[-1],
"activation": "linear",
"kernel_initializer": "normal",
},
},
"optimizer": "Adam",
"Adam": {
"learning_rate": mai.Float(min_value=1e-5, max_value=0.001),
},
"compile_params": {
"loss": "mean_absolute_error",
"metrics": ["mean_absolute_error"],
},
"fitting_params": {
"batch_size": mai.Choice([8, 16, 32]),
"epochs": 50,
"validation_split": 0.15,
},
},
"GB":{
"multi_output": True
},
"AB":{
"multi_output": True
},
"MultiOutput":{
"estimator": "SVM"
},
"Stacking":{
"estimators": ["EN", "DT"],
"_multi_output": True,
},
}
tuner = mai.Tuner(xtrain, ytrain, model_settings=model_settings)
Hyperparameter Tuning
Hyperparameter tuning is split into two options: classical model and neural network tuning. For the classical models, pyMAISE supports grid, random, and Bayesian search. For the neural network models, pyMAISE supports grid, random, Bayesian, and hyperband search. All search methods use cross-validation.
We define the hyperparameter search space for the classical models by defining the arrays, distributions, or skopt.space.space for each hyperparameter we plan to tune. We describe these within subdictionaries within the model keys. For MITR, we use pyMAISE.Tuner.random_search with 200 iterations as the data set is relatively small and the classical models are computationally cheap. For these models,
we define the search space using scipy.stats.
We do not hyperparameter tune linear regression because the hyperparameter options are limited, and the default scikit-learn model performs well on linear problems.
Note that for specific models below, we use estimator__ to get access to model parameters. This is because when we use any ensemble methods (eg. multi-output/stacking), you need to enter into that estimator to acess the parameters. For stacking, you must also specific which model you are trying to access with {model key}. Note that here we have estimator__{model key} since we have multi-output ontop of stacking.
Since we have already defined the feedforward neural network search space in the previous section, all we need to do is call the search method function. For these NNs, we can call pyMAISE.Tuner.nn_bayesian_search with 50 iterations as NNs tend to be more computationally expensive to train over the classical models, and the Bayesian optimization may converge on the optimal model for fewer iterations.
We set cv for both methods to 5, which gives us five cross-validation splits for each model. This will avoid overfitting. Therefore, for each classical model, we run 1000 hyperparameter configurations, and for the neural networks, we run 250 hyperparameter configurations.
[10]:
random_search_spaces = {
"Lasso": {
"alpha": uniform(loc=0.0001, scale=0.0099), # 0.0001 - 0.01
},
"DT": {
"max_depth": randint(low=5, high=50), # 5 - 50
"max_features": [None, "sqrt", "log2", 2, 4, 6],
"min_samples_split": randint(low=2, high=20), # 2 - 20
"min_samples_leaf": randint(low=1, high=20), # 1 - 20
},
"RF": {
"n_estimators": randint(low=50, high=200), # 50 - 200
"criterion": ["squared_error", "absolute_error", "poisson"],
"min_samples_split": randint(low=2, high=20), # 2 - 20
"min_samples_leaf": randint(low=1, high=20), # 1 - 20
"max_features": [None, "sqrt", "log2", 2, 4, 6],
},
"KN": {
"n_neighbors": randint(low=1, high=20), # 1 - 20
"weights": ["uniform", "distance"],
"leaf_size": randint(low=1, high=30), # 1 - 30
"p": randint(low=1, high=10), # 1 - 10
},
"RD":{
"alpha": uniform(loc=1e-11, scale=1e-9)
},
"EN": {
"alpha": uniform(loc=1e-11, scale=1e-9)
},
"ET":{
"max_depth": randint(low=5, high=50), # 5 - 50
"min_samples_split": randint(low=2, high=20), # 2 - 20
"min_samples_leaf": randint(low=1, high=20), # 1 - 20
},
"GB": {
"estimator__learning_rate": uniform(loc=0.1, scale=1e-2)
},
"AB":{
"estimator__learning_rate": uniform(loc=1, scale=0.5)
},
"MultiOutput":{
"estimator__C": uniform(loc=0.01, scale=99.99), # 0.01 - 100
},
"Stacking":{
"estimator__EN__alpha": uniform(loc=1e-11, scale=1e-9),
"estimator__DT__max_depth": randint(low=5, high=50), # 5 - 50
"estimator__DT__max_features": [None, "sqrt", "log2", 2, 4, 6],
"estimator__DT__min_samples_split": randint(low=2, high=20), # 2 - 20
"estimator__DT__min_samples_leaf": randint(low=1, high=20), # 1 - 20
},
}
start = time.time()
random_search_configs = tuner.random_search(
param_spaces=random_search_spaces,
n_iter=200,
n_jobs=6,
cv=5,
)
bayesian_search_configs = tuner.nn_bayesian_search(
objective="r2_score",
max_trials=50,
cv=5,
)
print("Hyperparameter tuning took " + str((time.time() - start) / 60) + " minutes to process.")
Hyperparameter tuning took 23.69085413614909 minutes to process.
After the training, we can see training results for each iteration using the pyMAISE.Tuner.convergence_plot function. For the neural networks, we have the following:
[20]:
ax = tuner.convergence_plot(model_types="FNN")
ax.set_ylim([0, 1])
plt.show()
The Bayesian search explores the search space but finds several suitable hyperparameter configurations.
Model Postprocessing
With the models tuned and the top pyMAISE.Settings.num_configs_saved saved, we can now pass these models to the pyMAISE.PostProcessor for model comparison and analysis. Additionally, we can update some hyperparameters after tuning. For MITR, we changed the number of FNN epochs to 200 since we are only running five models. We give the pyMAISE.PostProcessor all the scaled data, the model configurations, the new model settings, and the output scaler.
[21]:
postprocessor = mai.PostProcessor(
data=(xtrain, xtest, ytrain, ytest),
model_configs=[random_search_configs, bayesian_search_configs],
new_model_settings={
"FNN": {"fitting_params": {"epochs": 200}},
},
yscaler=yscaler,
)
We can now evaluate the performance of the models using the pyMAISE.PostProcessor.metrics function. This returns an ordered table with training and testing performance metrics. By default, pyMAISE.PostProcessor.metrics evaluates the training and testing \(R^2\), mean absolute error (MAE), mean absolute percentage error (MAPE), root mean squared error (RMSE), and root mean squared percentage error (RMSPE) for regression problems. The table is sorted by descending testing \(R^2\).
We can also add additional metrics, sort by a different metric, or choose the output pyMAISE computes the metrics with. Since all outputs are in watts, we do not need to evaluate each output individually. For further functionality on pyMAISE.PostProcessor.metrics refer to the pyMAISE API reference documentation.
[22]:
postprocessor.metrics().drop("Parameter Configurations", axis=1)
[22]:
| Model Types | Train R2 | Train MAE | Train MAPE | Train RMSE | Train RMSPE | Test R2 | Test MAE | Test MAPE | Test RMSE | Test RMSPE | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 57 | FNN | 0.998522 | 7.651470 | 0.040188 | 10.990297 | 0.052270 | 0.998321 | 8.178382 | 0.042891 | 11.580021 | 0.055818 |
| 60 | FNN | 0.998508 | 7.014305 | 0.036585 | 10.486746 | 0.051757 | 0.998316 | 7.574305 | 0.039428 | 11.020411 | 0.054630 |
| 56 | FNN | 0.998393 | 7.634138 | 0.039536 | 10.204548 | 0.049374 | 0.998201 | 8.314544 | 0.043119 | 11.126426 | 0.054253 |
| 59 | FNN | 0.998328 | 7.755986 | 0.040237 | 10.704594 | 0.051773 | 0.998101 | 8.419915 | 0.043704 | 11.552209 | 0.056252 |
| 58 | FNN | 0.998166 | 8.086968 | 0.041680 | 10.607666 | 0.052269 | 0.997801 | 9.016867 | 0.046574 | 11.829436 | 0.058491 |
| 0 | Linear | 0.994589 | 12.895922 | 0.067056 | 18.689971 | 0.093390 | 0.995613 | 12.226755 | 0.063559 | 17.099414 | 0.085183 |
| 30 | RD | 0.994589 | 12.895922 | 0.067056 | 18.689971 | 0.093390 | 0.995613 | 12.226755 | 0.063559 | 17.099414 | 0.085183 |
| 29 | RD | 0.994589 | 12.895922 | 0.067056 | 18.689971 | 0.093390 | 0.995613 | 12.226755 | 0.063559 | 17.099414 | 0.085183 |
| 27 | RD | 0.994589 | 12.895922 | 0.067056 | 18.689971 | 0.093390 | 0.995613 | 12.226755 | 0.063559 | 17.099414 | 0.085183 |
| 26 | RD | 0.994589 | 12.895922 | 0.067056 | 18.689971 | 0.093390 | 0.995613 | 12.226755 | 0.063559 | 17.099414 | 0.085183 |
| 28 | RD | 0.994589 | 12.895922 | 0.067056 | 18.689971 | 0.093390 | 0.995613 | 12.226755 | 0.063559 | 17.099414 | 0.085183 |
| 39 | EN | 0.994589 | 12.895922 | 0.067056 | 18.689971 | 0.093390 | 0.995613 | 12.226755 | 0.063559 | 17.099414 | 0.085183 |
| 40 | EN | 0.994589 | 12.895922 | 0.067056 | 18.689971 | 0.093390 | 0.995613 | 12.226755 | 0.063559 | 17.099414 | 0.085183 |
| 38 | EN | 0.994589 | 12.895922 | 0.067056 | 18.689971 | 0.093390 | 0.995613 | 12.226755 | 0.063559 | 17.099414 | 0.085183 |
| 37 | EN | 0.994589 | 12.895922 | 0.067056 | 18.689971 | 0.093390 | 0.995613 | 12.226755 | 0.063559 | 17.099414 | 0.085183 |
| 36 | EN | 0.994589 | 12.895922 | 0.067056 | 18.689971 | 0.093390 | 0.995613 | 12.226755 | 0.063559 | 17.099414 | 0.085183 |
| 1 | Lasso | 0.994566 | 13.064965 | 0.067923 | 18.734206 | 0.093580 | 0.995543 | 12.419661 | 0.064541 | 17.230611 | 0.085796 |
| 2 | Lasso | 0.994524 | 13.215825 | 0.068701 | 18.814318 | 0.093962 | 0.995469 | 12.593155 | 0.065434 | 17.374703 | 0.086490 |
| 3 | Lasso | 0.994367 | 13.629701 | 0.070847 | 19.112596 | 0.095419 | 0.995243 | 13.044244 | 0.067763 | 17.817935 | 0.088656 |
| 4 | Lasso | 0.994208 | 13.972201 | 0.072627 | 19.408889 | 0.096881 | 0.995038 | 13.417249 | 0.069694 | 18.217272 | 0.090624 |
| 5 | Lasso | 0.994113 | 14.162937 | 0.073619 | 19.585710 | 0.097756 | 0.994918 | 13.624632 | 0.070770 | 18.446925 | 0.091759 |
| 9 | Stacking | 0.993333 | 15.427020 | 0.080379 | 21.221407 | 0.105473 | 0.993089 | 16.241389 | 0.084547 | 21.709552 | 0.107421 |
| 7 | Stacking | 0.993185 | 15.580625 | 0.080994 | 21.191958 | 0.105642 | 0.993064 | 16.311708 | 0.084765 | 21.520878 | 0.107160 |
| 10 | Stacking | 0.993168 | 15.612716 | 0.081260 | 21.368638 | 0.106510 | 0.992609 | 16.860849 | 0.087705 | 22.254331 | 0.110713 |
| 8 | Stacking | 0.993084 | 15.895874 | 0.082805 | 21.712463 | 0.107736 | 0.992456 | 17.094955 | 0.088988 | 22.847127 | 0.112987 |
| 6 | Stacking | 0.992452 | 16.769559 | 0.087348 | 22.670303 | 0.112780 | 0.991845 | 17.900181 | 0.093148 | 23.654289 | 0.117097 |
| 18 | GB | 0.996414 | 11.652970 | 0.060569 | 15.410644 | 0.076915 | 0.979806 | 28.037931 | 0.145550 | 36.429789 | 0.181778 |
| 17 | GB | 0.996419 | 11.641918 | 0.060503 | 15.389189 | 0.076796 | 0.979779 | 28.053830 | 0.145631 | 36.425126 | 0.181777 |
| 19 | GB | 0.996428 | 11.633960 | 0.060466 | 15.364124 | 0.076670 | 0.979773 | 28.055968 | 0.145653 | 36.466874 | 0.181903 |
| 16 | GB | 0.996420 | 11.639010 | 0.060487 | 15.382631 | 0.076799 | 0.979767 | 28.056177 | 0.145653 | 36.473334 | 0.181956 |
| 20 | GB | 0.996433 | 11.662542 | 0.060624 | 15.426311 | 0.076829 | 0.979521 | 28.268099 | 0.146710 | 36.747966 | 0.182866 |
| 53 | KN | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.941764 | 50.280813 | 0.263252 | 66.749644 | 0.330709 |
| 51 | KN | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.941513 | 50.674117 | 0.265386 | 67.026931 | 0.331908 |
| 52 | KN | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.941218 | 50.897468 | 0.266673 | 67.375957 | 0.333413 |
| 55 | KN | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.941099 | 50.342355 | 0.263431 | 66.963304 | 0.331805 |
| 13 | MultiOutput | 0.943199 | 49.330086 | 0.257756 | 64.754166 | 0.321625 | 0.938272 | 49.116152 | 0.256356 | 66.778377 | 0.332670 |
| 12 | MultiOutput | 0.943199 | 49.330086 | 0.257756 | 64.754166 | 0.321625 | 0.938272 | 49.116152 | 0.256356 | 66.778377 | 0.332670 |
| 15 | MultiOutput | 0.943199 | 49.330086 | 0.257756 | 64.754166 | 0.321625 | 0.938272 | 49.116152 | 0.256356 | 66.778377 | 0.332670 |
| 14 | MultiOutput | 0.943199 | 49.330086 | 0.257756 | 64.754166 | 0.321625 | 0.938272 | 49.116152 | 0.256356 | 66.778377 | 0.332670 |
| 11 | MultiOutput | 0.943199 | 49.330086 | 0.257756 | 64.754166 | 0.321625 | 0.938272 | 49.116152 | 0.256356 | 66.778377 | 0.332670 |
| 54 | KN | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.937977 | 52.551795 | 0.275380 | 69.226016 | 0.342367 |
| 31 | ET | 0.995062 | 14.486439 | 0.075838 | 19.467468 | 0.096187 | 0.934138 | 51.232586 | 0.267875 | 70.578198 | 0.349732 |
| 32 | ET | 0.995230 | 14.237860 | 0.074581 | 19.132383 | 0.094537 | 0.933101 | 51.899850 | 0.271556 | 71.262238 | 0.353028 |
| 34 | ET | 0.984064 | 25.068874 | 0.131177 | 34.774141 | 0.172325 | 0.925610 | 54.291129 | 0.284014 | 75.000859 | 0.372073 |
| 33 | ET | 0.983971 | 25.175986 | 0.131659 | 34.884440 | 0.172725 | 0.925002 | 54.750377 | 0.286241 | 75.314955 | 0.372986 |
| 35 | ET | 0.980793 | 28.152816 | 0.147287 | 38.419301 | 0.189901 | 0.923701 | 55.579070 | 0.290643 | 76.039966 | 0.376509 |
| 43 | AB | 0.930691 | 53.138599 | 0.275749 | 66.690694 | 0.333620 | 0.907799 | 60.139175 | 0.312135 | 77.689743 | 0.387559 |
| 44 | AB | 0.929857 | 53.569285 | 0.278074 | 67.381991 | 0.336612 | 0.906902 | 60.698030 | 0.315230 | 78.364714 | 0.390649 |
| 45 | AB | 0.930191 | 53.577110 | 0.278190 | 67.384974 | 0.336485 | 0.906518 | 60.794817 | 0.315599 | 78.486415 | 0.391348 |
| 41 | AB | 0.930494 | 53.165917 | 0.275953 | 66.736017 | 0.333969 | 0.906339 | 60.375576 | 0.313350 | 77.993015 | 0.389640 |
| 42 | AB | 0.929634 | 53.591065 | 0.278197 | 67.321103 | 0.336413 | 0.905960 | 60.561209 | 0.314278 | 78.411401 | 0.391092 |
| 47 | RF | 0.962164 | 40.484389 | 0.211932 | 53.899722 | 0.266339 | 0.884928 | 71.136447 | 0.372161 | 93.766269 | 0.463894 |
| 46 | RF | 0.981332 | 27.860081 | 0.145888 | 37.839969 | 0.186820 | 0.884815 | 70.002958 | 0.365806 | 93.259719 | 0.460897 |
| 48 | RF | 0.932460 | 54.016528 | 0.282515 | 71.968080 | 0.355049 | 0.868691 | 75.083263 | 0.392446 | 99.518885 | 0.492844 |
| 49 | RF | 0.930966 | 54.842553 | 0.287011 | 72.954149 | 0.360228 | 0.865970 | 76.409399 | 0.399578 | 101.213943 | 0.501018 |
| 50 | RF | 0.931283 | 54.857252 | 0.287130 | 72.814569 | 0.359563 | 0.864165 | 77.082358 | 0.403241 | 101.910463 | 0.504550 |
| 22 | DT | 0.903526 | 64.734217 | 0.339298 | 86.170114 | 0.426064 | 0.720725 | 110.630982 | 0.579576 | 146.601384 | 0.724877 |
| 21 | DT | 0.879990 | 72.471361 | 0.379819 | 96.091326 | 0.475046 | 0.715738 | 112.062329 | 0.587186 | 147.475184 | 0.730209 |
| 25 | DT | 0.875137 | 74.045449 | 0.387996 | 97.909219 | 0.483931 | 0.714681 | 112.972956 | 0.592017 | 148.488063 | 0.734188 |
| 24 | DT | 0.875137 | 74.045449 | 0.387996 | 97.909219 | 0.483931 | 0.713980 | 113.097348 | 0.592596 | 148.724465 | 0.735041 |
| 23 | DT | 0.850537 | 81.763843 | 0.428436 | 107.331722 | 0.530457 | 0.712929 | 114.007295 | 0.597593 | 148.918379 | 0.736472 |
This data set is very linear, with fantastic performance from linear, elastic-net, ridge, and lasso regression. The FNNs proved to be marginally better-performing models over the linear classical models; however, this is at the cost of complexity. K-nearest neighbor, extra-trees, adaboost, random forest, and decision tree struggled on this data set with all three overfittings.
Using the pyMAISE.PostProcessor.print_model function, we can see all the top performing (based on test \(R^2\)) models’ hyperparameters.
[14]:
for model in ["Lasso", "DT", "FNN", "RD", "ET", "EN", "GB", "AB", "MultiOutput", "Stacking", "RF", "KN"]:
postprocessor.print_model(model_type=model)
print()
Model Type: Lasso
alpha: 0.0001133351011862626
Model Type: DT
max_depth: 43
max_features: 6
min_samples_leaf: 3
min_samples_split: 6
Model Type: FNN
Structural Hyperparameters
Layer: Dense_hidden_0
units: 309
sublayer: None
Layer: Dense_output_0
Compile/Fitting Hyperparameters
Adam_learning_rate: 0.0008321972582830564
batch_size: 8
Model: "FNN"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
Dense_hidden_0 (Dense) (None, 309) 2163
Dense_output_0 (Dense) (None, 22) 6820
=================================================================
Total params: 8983 (35.09 KB)
Trainable params: 8983 (35.09 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________
Model Type: RD
alpha: 9.643960473209805e-10
Model Type: ET
max_depth: 33
min_samples_leaf: 1
min_samples_split: 3
Model Type: EN
alpha: 3.724600700139202e-11
Model Type: GB
estimator__learning_rate: 0.10929699916511056
Model Type: AB
estimator__learning_rate: 1.47118944569141
Model Type: MultiOutput
estimator__C: 46.16762541225824
Model Type: Stacking
estimator__DT__max_depth: 9
estimator__DT__max_features: 2
estimator__DT__min_samples_leaf: 16
estimator__DT__min_samples_split: 7
estimator__EN__alpha: 4.850855576743397e-10
Model Type: RF
criterion: absolute_error
max_features: None
min_samples_leaf: 1
min_samples_split: 4
n_estimators: 186
Model Type: KN
leaf_size: 4
n_neighbors: 8
p: 2
weights: distance
Additionally, we can visualize the structure of the FNN using pyMAISE.PostProcessor.nn_network_plot.
[15]:
postprocessor.nn_network_plot(
to_file="./supporting/mit_reactor_network.png",
show_shapes=True,
show_layer_names=True,
expand_nested=True,
show_layer_activations=True,
)
[15]:
We can use the pyMAISE.PostProcessor.diagonal_validation_plot and pyMAISE.PostProcessor.validation_plot functions to visualize the performance of these models. Diagonal validation plots show the actual versus predicted outcome. A well-performing model follows \(y=x\) on this plot. You can choose what outputs to display on the diagonal validation plot; however, since all outputs have the same units, we can display all of them. Both functions use the top-performing model on test
\(R^2\) by default.
[16]:
models = np.array([["Linear", "Lasso"], ["RD", "FNN"], ["EN", "ET"], ["AB", "MultiOutput"], ["Stacking", "GB"], ["RF", "KN"], ["RD", "RD"]] )
fig, axarr = plt.subplots(models.shape[0], models.shape[1], sharex=True, sharey=True, figsize=(15,40))
for i in range(models.shape[0]):
for j in range(models.shape[1]):
plt.sca(axarr[i, j])
axarr[i, j] = postprocessor.diagonal_validation_plot(model_type=models[i, j])
axarr[i, j].set_title(models[i, j])
plt.show()
All models display a close spread to \(y=x\); however, the lack of performance of the decision tree, extra-trees, random forest, and k-nearest neighbor is apparent, given the more extensive spread.
Validation plots show the absolute error of the predicted output relative to the actual outputs of the testing data set. The function can evaluate all output or show just what is given in a list. This list can include column positions in the data set or the output names. Only the A-2, B-8, and C-8 fuel elements are shown for the validation plot below.
[17]:
fig, axarr = plt.subplots(models.shape[0], models.shape[1], figsize=(17,22))
y = ["A-2", "B-8", "C-8"]
for i in range(models.shape[0]):
for j in range(models.shape[1]):
plt.sca(axarr[i, j])
axarr[i, j] = postprocessor.validation_plot(model_type=models[i, j], y=y)
axarr[i, j].set_title(models[i, j])
axarr[i, j].get_legend().remove()
plt.subplots_adjust(hspace=0.5) # Adjust hspace to increase spacing between rows
fig.legend(y, loc="upper center", ncol=4)
plt.show()
The diagonal validation and validation plots agree with the performance metrics. Interestingly, all models increase prediction error as we move radially from the middle of the core.
To further understand the behavior of the top neural network configurations, we can plot the learning curve. Here, the top neural network learning curve is shown but, similar to the diagonal and validation plot functions, pyMAISE.PostProcessor.nn_learning_plot shows the neural network based on the index in pyMAISE.PostProcessor.metrics or, if no index is provided, the one with the best test \(R^2\).
[18]:
postprocessor.nn_learning_plot()
plt.show()
The FNN is not overfitting, as the validation curve closely follows the training curve.
Finally, using the best FNN model, we can generate a predicted power for each fuel element given the control blade heights. This code block retrieves the top-performing model using the pyMAISE.PostProcessor.get_model function.
[19]:
def plot_mitr(model, x, yscaler=None):
pos=[(400,330), (465,230), (500,300), (395,400), (320,400), (285,255), (318,193),
(500,165), (535,230), (575,295), (535,360), (500,430), (430,465), (355,465),
(280,465), (240,400), (205,335), (210,255), (243,193), (280,130), (355,130),
(430,130), (393,193), (460,360), (280,335), (400,265), (340,295)]
Ynn=model.predict(np.array([x,]))
if yscaler:
Ynn=yscaler.inverse_transform(Ynn)
Ynn=Ynn.flatten().tolist()
image = cv2.imread("./supporting/mitr.png")
for i in range(len(pos)):
if i==0:
image=cv2.putText(
img=np.copy(image),
text=str(int(Ynn[i])),
org=pos[i],
fontFace=1,
fontScale=1.1,
color=(0,0,0)
)
if i in [1,2,3,4,5,6]:
image=cv2.putText(
img=np.copy(image),
text=str(int(Ynn[i])),
org=pos[i],
fontFace=1,
fontScale=1.1,
color=(0,0,255)
)
if i in list(range(7,22)):
image=cv2.putText(
img=np.copy(image),
text=str(int(Ynn[i])),
org=pos[i],
fontFace=1,
fontScale=1.1,
color=(0, 100, 0)
)
if i in list(range(22,28)):
image=cv2.putText(
img=np.copy(image),
text=str('Empty'),
org=pos[i],
fontFace=1,
fontScale=1.1,
color=(233, 150, 122)
)
plt.figure(figsize=(12, 12))
plt.imshow(image)
plt.axis('off')
x=[0.75266553, 0.90280633, 0.00539489, 0.25308624, 0.57678792, 0.77792903]
plot_mitr(postprocessor.get_model(), x, yscaler)
1/1 [==============================] - 0s 20ms/step