Solar power is a free and clean alternative to traditional fossil fuels.

However, nowadays the efficiency of solar cells is not as high as we would like, so selecting the ideal conditions for its installation is key in obtaining the maximum amount of energy out of it.

We want to predict the power output for a particular array of solar power generators knowing some environmental conditions.

This is an approximation project, since the variable to be predicted is continuous (energy production).

The basic goal here is to model the energy production, as a function of the environmental variables.

The first step is to prepare the data set , which is the source of information for the approximation problem. It is composed of:

- Data source.
- Variables.
- Instances.

The file solarpowergeneration.csv contains the data for this example. Here the number of variables (columns) is 21 and the number of instances (rows) is 4213.

We have the following variables for this analysis:

**temperature_2_m_above_gnd****relative_humidity_2_m_above_gnd****mean_sea_level_pressure_MSL****total_precipitation_sfc****snowfall_amount_sfc****total_cloud_cover_sfc****high_cloud_cover_high_cld_lay****medium_cloud_cover_mid_cld_lay****low_cloud_cover_low_cld_lay****shortwave_radiation_backwards_sfc****wind_speed_10_m_above_gnd****wind_direction_10_m_above_gnd****wind_speed_80_m_above_gnd****wind_direction_80_m_above_gnd****wind_speed_900_mb****wind_direction_900_mb****wind_gust_10_m_above_gnd****angle_of_incidence****zenith****azimuth****generated_power_kw**

Our final, working dataset is now composed of 1600 instances out of 2920 observations. Of those, 952 (32.6%) are for training, 327 (11.2%) are for selection and 321 (11%) are for testing.

The standard procedure to prepare a model for training is to perform some analytic tasks in order to check de quality of the data. For this kind of projects with so many variables, knowing the correlations of the inputs with the target is a good starting point.

In the chart above we can see that zenith and angle_of_incidence are the most correlated variables, while azimuth or snowfall_amount_sfc are the least.

Sometimes, a low correlation is not enough to safely discard a variable for the model. Displaying scatter charts is a good way of knowing the exact influence of the inputs on the target. The next chart shows the scatter plot for the azimuth and the target variable.

As we can see, the azimuth has a normal distribution, so for this case, we are going to keep all the variables in the dataset for the model.

The neural network will output the scaled power generated as a function of all the input variables

For this approximation example, the neural network is composed by:

- Scaling layer.
- Perceptron layers.
- Unscaling layer.

The scaling layer transforms the original inputs to normalized values. Here the mean and standard deviation scaling method is set so that the input values have mean 0 and standard deviation 1.

Here two perceptron layers are added to the neural network. This number of layers is enough for the vast majority of applications. The first layer has 20 inputs and 3 neurons with hyperbolic tangent activation function. The second layer has 3 inputs and 1 neuron with linear activation function.

The unscaling layer transforms the normalized values from the neural network into original outputs. Here the minimum and maximum unscaling method will be used.

The next figure shows the resulting network architecture.

This neural network represents a function containing 67 adjustable parameters.

The fourth step is to configure the training strategy, which is composed of two concepts:

- A loss index.
- An optimization algorithm.

The loss index is the normalized squared error. If the normalized squared error squared error has a value of unity then the neural network is predicting the data 'in the mean', while a value of zero means perfect prediction of the data. We will also apply a strong regularization term.

The optimization algorithm is applied to the neural network to get the best performance. The chosen algorithm here is the quasi-Newton method which is the default choice for approximation applications.

Once the strategy has been set, we can train the neural network. The following chart shows how the training (blue) and selection (orange) errors decrease with the training epoch during the training process.

The most important training result is the final selection error.
Indeed, this a measure of the generalization capabilities of the neural network.
Here the final selection error is **selection error = 0.234 NSE**.

The objective of model selection is to find the network architecture with the best generalization properties. That is, we want to improve the final selection error obtained before (0.234 NSE).

The best selection error is achieved by using a model whose complexity is the most appropriate to produce and adequate fit of the data. Order selection algorithms are responsible to find the optimal number of perceptrons in the neural network.

The following chart shows the results of the incremental order algorithm. The blue line plots the final training error as a function of the number of neurons. The orange line plots the final selection error as a function of the number of neurons.

As we can see, both the training error and selection error barely change as the complexity goes up.
However, the final selection error takes a minimum value at some point, which is the value we are interested in.
Here, the optimal number of neurons is 8, which corresponds to a selection error of **0.207 NSE**.

The following figure shows the optimal network architecture for this application.

The objective of testing analysis is to validate the generalization performance of the trained neural network. Testing compares the values provided by this technique to the actually observed values.

The most common testing technique in approximation problems is to perform a linear regression analysis between the predicted and the real values, using an independent testing set. The next figure illustrates a graphical output provided by this testing analysis.

From the above chart, we see that the neural network is predicting well the entire range of generated power data.
The correlation value is **R2 = 0.868**, which is close to 1.

The model is now ready to estimate the power generated by a solar plant with satisfactory quality over the same range of data.

We can plot a directional output of the neural network to see hoy the generated power varies with a given input, for all other inputs fixed. The next plot shows the generated power as a function of the azimuth.

The mathematical expression represented by the neural network is written below. This mathematical expression can be used in any software a solar plant could use to predict the power it is going to generate given the values for the model input variables.

scaled_temperature_2_m_above_gnd = (temperature_2_m_above_gnd-15.0681)/8.85368; scaled_relative_humidity_2_m_above_gnd = (relative_humidity_2_m_above_gnd-51.361)/23.5259; scaled_mean_sea_level_pressure_MSL = (mean_sea_level_pressure_MSL-1019.34)/7.02287; scaled_total_precipitation_sfc = (total_precipitation_sfc-0.0317588)/0.170212; scaled_snowfall_amount_sfc = (snowfall_amount_sfc-0.00280798)/0.0380148; scaled_total_cloud_cover_sfc = (total_cloud_cover_sfc-34.057)/42.8436; scaled_high_cloud_cover_high_cld_lay = (high_cloud_cover_high_cld_lay-14.4588)/30.7117; scaled_medium_cloud_cover_mid_cld_lay = (medium_cloud_cover_mid_cld_lay-20.0235)/36.3879; scaled_low_cloud_cover_low_cld_lay = (low_cloud_cover_low_cld_lay-21.3734)/38.0139; scaled_shortwave_radiation_backwards_sfc = (shortwave_radiation_backwards_sfc-387.759)/278.459; scaled_wind_speed_10_m_above_gnd = (wind_speed_10_m_above_gnd-16.2288)/9.87695; scaled_wind_direction_10_m_above_gnd = (wind_direction_10_m_above_gnd-195.078)/106.627; scaled_wind_speed_80_m_above_gnd = (wind_speed_80_m_above_gnd-18.9785)/12; scaled_wind_direction_80_m_above_gnd = (wind_direction_80_m_above_gnd-191.167)/108.76; scaled_wind_speed_900_mb = (wind_speed_900_mb-16.3632)/9.88533; scaled_wind_direction_900_mb = (wind_direction_900_mb-192.448)/106.516; scaled_wind_gust_10_m_above_gnd = (wind_gust_10_m_above_gnd-20.5835)/12.6489; scaled_angle_of_incidence = (angle_of_incidence-50.8375)/26.639; scaled_zenith = (zenith-59.9809)/19.8577; scaled_azimuth = (azimuth-169.168)/64.5684; y_1_1 = tanh (-0.906195+ (scaled_temperature_2_m_above_gnd*-0.153461)+ (scaled_relative_humidity_2_m_above_gnd*-0.0938515)+ (scaled_mean_sea_level_pressure_MSL*0.290687)+ (scaled_total_precipitation_sfc*-0.0218839)+ (scaled_snowfall_amount_sfc*0.240507)+ (scaled_total_cloud_cover_sfc*-0.111046)+ (scaled_high_cloud_cover_high_cld_lay*-0.120054)+ (scaled_medium_cloud_cover_mid_cld_lay*-0.128212)+ (scaled_low_cloud_cover_low_cld_lay*0.0228723)+ (scaled_shortwave_radiation_backwards_sfc*0.0171274)+ (scaled_wind_speed_10_m_above_gnd*-0.0175202)+ (scaled_wind_direction_10_m_above_gnd*0.098862)+ (scaled_wind_speed_80_m_above_gnd*0.0176796)+ (scaled_wind_direction_80_m_above_gnd*-0.00168937)+ (scaled_wind_speed_900_mb*-0.10895)+ (scaled_wind_direction_900_mb*-0.0367349)+ (scaled_wind_gust_10_m_above_gnd*-0.0712569)+ (scaled_angle_of_incidence*0.561521)+ (scaled_zenith*-0.372087)+ (scaled_azimuth*-0.2144)); y_1_2 = tanh (0.835016+ (scaled_temperature_2_m_above_gnd*0.216201)+ (scaled_relative_humidity_2_m_above_gnd*0.189559)+ (scaled_mean_sea_level_pressure_MSL*-0.23903)+ (scaled_total_precipitation_sfc*0.310726)+ (scaled_snowfall_amount_sfc*0.0842171)+ (scaled_total_cloud_cover_sfc*0.137356)+ (scaled_high_cloud_cover_high_cld_lay*0.121997)+ (scaled_medium_cloud_cover_mid_cld_lay*0.042435)+ (scaled_low_cloud_cover_low_cld_lay*0.184413)+ (scaled_shortwave_radiation_backwards_sfc*-0.456094)+ (scaled_wind_speed_10_m_above_gnd*-0.0442705)+ (scaled_wind_direction_10_m_above_gnd*-0.0631511)+ (scaled_wind_speed_80_m_above_gnd*-0.0564552)+ (scaled_wind_direction_80_m_above_gnd*0.00571266)+ (scaled_wind_speed_900_mb*0.0958698)+ (scaled_wind_direction_900_mb*0.0677284)+ (scaled_wind_gust_10_m_above_gnd*0.130828)+ (scaled_angle_of_incidence*0.272595)+ (scaled_zenith*0.0179898)+ (scaled_azimuth*0.542183)); y_1_3 = tanh (0.490393+ (scaled_temperature_2_m_above_gnd*0.025496)+ (scaled_relative_humidity_2_m_above_gnd*0.0770068)+ (scaled_mean_sea_level_pressure_MSL*0.129209)+ (scaled_total_precipitation_sfc*-0.0845903)+ (scaled_snowfall_amount_sfc*0.193702)+ (scaled_total_cloud_cover_sfc*0.116878)+ (scaled_high_cloud_cover_high_cld_lay*-0.0527215)+ (scaled_medium_cloud_cover_mid_cld_lay*-0.17534)+ (scaled_low_cloud_cover_low_cld_lay*-0.0611996)+ (scaled_shortwave_radiation_backwards_sfc*0.334216)+ (scaled_wind_speed_10_m_above_gnd*0.00549648)+ (scaled_wind_direction_10_m_above_gnd*0.0530615)+ (scaled_wind_speed_80_m_above_gnd*0.0916396)+ (scaled_wind_direction_80_m_above_gnd*0.0364087)+ (scaled_wind_speed_900_mb*-0.188803)+ (scaled_wind_direction_900_mb*0.0478927)+ (scaled_wind_gust_10_m_above_gnd*-0.000496165)+ (scaled_angle_of_incidence*-0.179766)+ (scaled_zenith*-0.756985)+ (scaled_azimuth*-0.633151)); scaled_generated_power_kw = (-0.286912+ (y_1_1*-0.376337)+ (y_1_2*-0.673666)+ (y_1_3*0.405254)); generated_power_kw = (0.5*(scaled_generated_power_kw+1.0)*(3056.79-0.000595238)+0.000595238);