‹ Back to Tutorials

Approximation: Airfoil self-noise prediction

This example builds an approximation model that estimates the scaled sound pressure level generated by an airfoil from five aerodynamic and geometric variables.

It follows the current OpenNN airfoil self-noise example.

Contents:

  1. Identify the application type
  2. Load and split the data
  3. Build the neural network
  4. Configure and train the model
  5. Evaluate the results
  6. Generate predictions and export the model
  7. Complete example

1. Identify the application type

The target, scaled_sound_pressure_level, is a continuous value measured in decibels. This makes the problem an approximation, or regression, task.

The model learns sound pressure level as a function of frequency, angle of attack, chord length, free-stream velocity and suction-side displacement thickness.

2. Load and split the data

The airfoil_self_noise.csv file contains 1,503 samples, five input variables and one target. Its fields are separated by semicolons and the first row contains the variable names.

set_seed(42);

TabularDataset dataset(
    "../data/airfoil_self_noise/airfoil_self_noise.csv",
    ";",
    true,
    false);

dataset.split_samples_random(0.8f, 0.0f, 0.2f);

The explicit split assigns 80% of the samples to training and 20% to testing. No validation subset is used in the maintained example.

  • frequency — hertz, input.
  • angle_of_attack — degrees, input.
  • chord_lenght — metres, input. The source CSV retains this historical spelling.
  • free-stream_velocity — metres per second, input.
  • suction_side_displacement_thickness — metres, input.
  • scaled_sound_pressure_level — decibels, target.

dataset.get_input_shape() and dataset.get_target_shape() provide the dimensions required by the network constructor.

3. Build the neural network

ApproximationNetwork creates the standard regression architecture: scaling, a hidden dense layer, a dense identity output, unscaling and clamping.

const Index neurons_number = 12;

ApproximationNetwork approximation_network(
    dataset.get_input_shape(),
    {neurons_number},
    dataset.get_target_shape());

auto* clamping = dynamic_cast<Clamping*>(
    approximation_network.get_first("Clamping"));

if(clamping)
    clamping->set_clamping_method("NoClamping");

The hidden dense layer uses the default hyperbolic tangent activation. The output remains unclamped because this example estimates an unrestricted continuous response.

4. Configure and train the model

The maintained example uses mean squared error with L2 regularization and stochastic gradient descent:

TrainingStrategy training_strategy(
    &approximation_network,
    &dataset);

training_strategy.set_loss("MeanSquaredError");
training_strategy.get_loss()->set_regularization("L2");
training_strategy.get_loss()->set_regularization_weight(0.001f);

training_strategy.set_optimization_algorithm(
    "StochasticGradientDescent");

auto* sgd = dynamic_cast<StochasticGradientDescent*>(
    training_strategy.get_optimization_algorithm());

if(!sgd)
    throw runtime_error("SGD configuration failed.");

sgd->set_initial_learning_rate(0.3f);
sgd->set_display_period(50);

TrainingResult result = training_strategy.train();

TrainingResult contains the final errors, histories, elapsed time and stopping condition.

5. Evaluate the results

Goodness-of-fit analysis compares predicted and observed sound levels on the testing subset:

TestingAnalysis testing_analysis(
    &approximation_network,
    &dataset);

testing_analysis.print_goodness_of_fit_analysis();

Review the determination coefficient and the predicted-versus-observed plot. Performance should be assessed on held-out testing samples rather than the training error alone.

6. Generate predictions and export the model

New samples must provide the five inputs in the same order used by the data set:

MatrixR inputs(1, 5);
inputs << 800.0f, 6.782f, 0.136f, 50.860f, 0.011f;

const MatrixR outputs =
    approximation_network.calculate_outputs(inputs);

cout << "Estimated sound pressure level: "
     << outputs(0, 0) << " dB\n";

Save the complete OpenNN network as JSON or export a standalone expression:

approximation_network.save("airfoil_model.json");

ModelExpression expression(&approximation_network);
expression.save(
    "airfoil_model.py",
    ModelExpression::ProgrammingLanguage::Python);

7. Complete example

#include <iostream>
#include <stdexcept>

#include "opennn/core/configuration.h"
#include "opennn/core/random_utilities.h"
#include "opennn/dataset/tabular_dataset.h"
#include "opennn/models/models.h"
#include "opennn/neural_network/layers/clamping_layer.h"
#include "opennn/neural_network/model_expression.h"
#include "opennn/testing_analysis/testing_analysis.h"
#include "opennn/training_strategy/stochastic_gradient_descent.h"
#include "opennn/training_strategy/training_strategy.h"

using namespace opennn;

int main()
{
    try
    {
        set_seed(42);
        Configuration::instance().set(Device::Auto, Type::FP32);

        TabularDataset dataset(
            "../data/airfoil_self_noise/airfoil_self_noise.csv",
            ";", true, false);

        dataset.split_samples_random(0.8f, 0.0f, 0.2f);

        ApproximationNetwork approximation_network(
            dataset.get_input_shape(),
            {12},
            dataset.get_target_shape());

        auto* clamping = dynamic_cast<Clamping*>(
            approximation_network.get_first("Clamping"));

        if(clamping)
            clamping->set_clamping_method("NoClamping");

        TrainingStrategy training_strategy(
            &approximation_network,
            &dataset);

        training_strategy.set_loss("MeanSquaredError");
        training_strategy.get_loss()->set_regularization("L2");
        training_strategy.get_loss()->set_regularization_weight(0.001f);
        training_strategy.set_optimization_algorithm(
            "StochasticGradientDescent");

        auto* sgd = dynamic_cast<StochasticGradientDescent*>(
            training_strategy.get_optimization_algorithm());

        if(!sgd)
            throw runtime_error("SGD configuration failed.");

        sgd->set_initial_learning_rate(0.3f);
        sgd->set_display_period(50);

        TrainingResult result = training_strategy.train();
        result.print();

        TestingAnalysis testing_analysis(
            &approximation_network,
            &dataset);
        testing_analysis.print_goodness_of_fit_analysis();

        MatrixR inputs(1, 5);
        inputs << 800.0f, 6.782f, 0.136f, 50.860f, 0.011f;

        const MatrixR outputs =
            approximation_network.calculate_outputs(inputs);
        cout << "Estimated sound pressure level: "
             << outputs(0, 0) << " dB\n";

        approximation_network.save("airfoil_model.json");
        ModelExpression expression(&approximation_network);
        expression.save(
            "airfoil_model.py",
            ModelExpression::ProgrammingLanguage::Python);

        return 0;
    }
    catch(const exception& error)
    {
        cerr << error.what() << '\n';
        return 1;
    }
}

References