Machine learning model of maximum stresses in a gas pipeline branch arising from erosive wear#

The how-to guide provides a detailed description of the creation of a machine learning model for approximating the maximum stresses acting in a bend and arising from erosive wear of the inner surface of the bend during the operation of a gas pipeline.

As you read this guide, you will learn:

  1. Where and why is a gas pipeline loop needed?

  2. The purpose and objectives of modeling the process of operating gas pipelines;

  3. Basic steps to create a machine learning model (ML model) using Python to evaluate the erosion process;

  4. How to use the ML model with a gas-dynamic model of a gas pipeline on the REPEAT VISION platform.

1.  General information about gas pipelines#

Gas pipelines (see figure below) are used at gas fields and are designed to transport gas from wells to an integrated gas treatment unit (IGTU). After undergoing the necessary preparation for transport to the IGTU, the gas enters the main gas pipeline.

../../../_images/gas_pipeline_with_annotations.png

A gas pipeline loop on a cluster of two gas wells.#

The gas pipeline (№1 in the figure above) is subject to the following influences during operation:

  1. The inner surface of the gas pipeline is loaded with gas pressure and temperature;

  2. The outer surface of the gas pipeline is subject to variable temperatures of the external environment.

1.1.  The problem of erosive wear of the gas pipeline#

The process of operating a gas well (№2 in the figure above) may be accompanied by the removal of water containing mechanical impurities, which causes erosive wear of the inner surface of the gas pipeline.

The removal of water containing a small amount of mechanical impurities can occur continuously, which makes it possible to create simple semi-empirical analytical methods for predicting erosive wear.

Erosive wear can also be cyclical in nature with periodic removal of water and mechanical impurities.

There are also very dangerous sudden releases of mechanical impurities with large quantities of water, which can lead to unexpected and significant wear of gas pipeline components.

The most vulnerable element of a gas pipeline is the first branch, if you count from the wellhead of the gas well (№2 in the figure above).

1.2.  Ways to reduce risk#

To reduce the risk of incidents and accidents due to erosive wear of the gas pipeline, a comprehensive solution is required, including:

  1. Application of physical and mathematical modeling of the process of operation of a pipeline system and its control system;

  2. Application of automated diagnostic tools for the technical condition of gas pipeline elements;

  3. Implementation of automated control systems for gas well operation processes using the results of mathematical modeling of the current technical condition of the gas pipeline.

2.  Diagnostic tools for the technical condition of gas pipelines#

Automated diagnostic tools for the technical condition of gas pipeline components required to monitor the erosion process include the following systems:

  1. Flow control systems;

  2. External temperature sensors and gas pressure and temperature control systems;

  3. Intrusive erosion measurement sensors and acoustic sand monitoring systems;

  4. Point and distributed thickness measurement systems.

3.  The purpose and objectives of modeling the operation process of gas pipelines#

Objective: To improve the company’s production efficiency and prevent accidents at industrial facilities.

Modeling tasks:

  1. Calculation of strength parameters based on current data on the technical condition of gas pipelines;

  2. Calculation of the parameters of the collection system for compliance with the technical project for field development;

  3. Ensuring maximum gas production during peak seasonal periods (winter, demand) and minimum production (summer);

  4. Ensuring optimal operating parameters of the collection system during unplanned shutdown of wells or when well operating modes do not correspond to the design ones;

  5. Determination of parameters of the collection system when changing the input parameters of the gas and condensate treatment system.

The solution to problem #1 is presented in the next section. The remaining problems are solved by modeling pipeline systems using REPEAT VISION and solving global and local optimization problems.

4.  Description of the task#

Perform an assessment of the stress-strain state (SSS) of the branch based on the initial data and boundary conditions.

The machine learning model is created based on a data table of the static strength calculation results for the branch model. Each row in the data table represents a separate design case. Each design case includes:

  1. Initial data and boundary conditions:

    1. Minimum wall thickness,

    2. Gas pressure

    3. Gas temperature,

    4. Ambient temperature,

  2. Calculation result SSS: Maximum stresses according to von Mises.

The calculation cases are based on the technological regulations of a specific field:

  1. Gas pressure varies in the range: from 8.5 to 6.0 \([MPa]\),

  2. The gas temperature varies in the range: from 7 to 20 \([^{\circ}C]\),

  3. The ambient temperature varies in the range: from -55 to 40 \([^{\circ}C]\),

  4. The design cases include minimum branch wall thicknesses from 6.45 to 4.9 \([mm]\).

The result of the calculation of the static strength of one of the design cases is the von Mises stress distribution shown in the figure below.

../../../_images/stress_results.en.png

An example of von Mises stress distribution in a bend.#

For each result of the calculation of stresses acting in the branch, shown in the figure above, the maximum value is selected and entered into the table of calculation results.

5.  Preparing a table of calculation cases#

The calculation of the static strength of the bend is performed in a software package that implements the finite element method (FEM).

5.1.  Static strength model#

To determine the SSS of the branch, the influence of the entire gas pipeline-loop and the associated support structures a cluster of two gas wells was taken into account.

Linear sections of the gas pipeline loop and supporting structures were modeled in the beam approximation.

The bends and tees were modeled using volumetric FE.

5.2.  The main stages of data preparation#

The preparation of the table of design cases is reduced to an automated process of parametric studies of the static strength of the bend, which in itself is a separate engineering task.

Hint

If you need more information and skills in performing parametric studies, please check out our how-to guide on this topic.

The main stages of the parametric study process are shown in the figure below.

../../../_images/param_study_proc.en.png

The process of data preparation based on the modeling of the static strength of the bend.#

This process was repeated approximately 6000 times in the parametric study workflow to obtain the required table of design cases for subsequent creation of the machine learning model.

6.  Building a neural network model#

Note

You can download the Jupyter notebook file and the requirements.txt file to create the necessary virtual environment if you want to quickly run the code in this tutorial on your personal computer.

Additionally, you will need data for training the model and (optionally) data for visualization of one calculation result.

You can also download the model file and immediately use it in a project on the REPEAT VISION platform using the Approximation model block.

The model will be written using the Python language and the PyTorch framework, so basic knowledge of this language and skills in working with PyTorch are required.

Important

Please note that to install a GPU-enabled version of PyTorch that is compatible with your CUDA version, you need to visit the official website.

In addition, to solve the problem, many libraries that are generally accepted for the corresponding subtasks are used:

  • NumPy and Pandas — working with arrays, linear algebra, and processing tabular data, respectively.

  • Matplotlib, Seaborn — visualization of static graphs to monitor the quality of model performance.

  • Plotly - interactive graphs for visual representation of the object of study.

  • scikit-learn — metrics calculation and data partitioning.

Перед началом подготовки ML модели необходимо установить Python, а также требуется установить пакеты для работы с данными и создания ML модели. Для этого необходимо выполнить в командной строке вашего компьютера следующие команды и продолжить работу в Jupyter ноутбуке.

mkdir ml_branch_stress
cd ml_branch_stress
python -m venv repeat_ml.
source repeat_ml/bin/activate
python -m pip install numpy pandas scikit-learn onnx onnxscript 
python -m pip install seaborn matplotlib plotly 
python -m pip install jupyterlab tqdm ipywidgets netron
python -m pip install torch --index-url https://download.pytorch.org/whl/cpu

jupyter lab
mkdir ml_branch_stress
cd ml_branch_stress
python -m venv repeat_ml.
repeat_ml\Scripts\Activate.ps1
python -m pip install numpy pandas scikit-learn onnx onnxscript 
python -m pip install seaborn matplotlib plotly 
python -m pip install jupyterlab tqdm ipywidgets netron
python -m pip install torch --index-url https://download.pytorch.org/whl/cpu

jupyter lab

First of all, we import the necessary libraries for creating neural networks, building visualizations, and subsequent import into the ONNX format.

Importing libraries#
 1import numpy as np
 2import pandas as pd
 3
 4import seaborn as sns
 5import matplotlib.pyplot as plt
 6import plotly.graph_objects as go
 7
 8import torch
 9import torch.nn as nn
10import torch.optim as optim
11from torch.utils.data import Dataset, DataLoader
12
13from sklearn.model_selection import train_test_split
14from sklearn.metrics import (mean_squared_error, 
15                             mean_absolute_error,
16                             mean_absolute_percentage_error,
17                             r2_score)
18
19from tqdm.notebook import tqdm
20from typing import Tuple, List, Dict, Union

To improve reproducibility and utilization of the available device, it is necessary to fix random number generation and graphics accelerator availability.

Reproducibility and design#
1SEED = 42
2torch.manual_seed(SEED)
3np.random.seed(SEED)
4
5device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
6print(device)

6.1.  Data exploration#

To visualize the stress distribution in the branch, we visualize the three-dimensional distribution of points using interactive 3D visualization with Plotly.

Stress visualization#
 1def branch_plot(df: pd.DataFrame) -> go.Figure:
 2    """
 3    Создает 3D визуализацию распределения напряжений.
 4    
 5    Параметры:
 6        df: pd.DataFrame
 7            DataFrame с данными о координатах и напряжениях
 8    Возвращает:
 9        go.Figure: Объект графика Plotly
10    """
11    marker_config = dict(
12        size=3,
13        color=df['Stress'],
14        colorscale='Jet',
15        opacity=0.8,
16        colorbar=dict(title="Напряжение (МПа)")
17    )
18    hover_template = (
19        '<b>Узел</b>: %{customdata}<br>'
20        '<b>X</b>: %{x:.2f} мм<br>'
21        '<b>Y</b>: %{y:.2f} мм<br>'
22        '<b>Z</b>: %{z:.2f} мм<br>'
23        '<b>Напряжение</b>: %{marker.color:.2f} МПа'
24    )
25    fig = go.Figure(
26        data=[
27            go.Scatter3d(
28                x=df['X'],
29                y=df['Y'],
30                z=df['Z'],
31                mode='markers',
32                marker=marker_config,
33                hovertemplate=hover_template,
34                customdata=df['Node']
35            )
36        ]
37    )
38    scene_config = dict(
39        xaxis_title='X (мм)',
40        yaxis_title='Y (мм)',
41        zaxis_title='Z (мм)',
42        aspectmode='data',
43        camera=dict(
44            eye=dict(x=0, y=0, z=2.5),
45            center=dict(x=0, y=0, z=0),
46            up=dict(x=0, y=1, z=0)
47        )
48    )
49    margin_config = dict(l=0, r=0, b=0, t=30)
50    fig.update_layout(
51        title='Распределение напряжений',
52        scene=scene_config,
53        margin=margin_config
54    )
55
56    return fig

As a result, for a randomly selected file containing the results of a numerical experiment, a set of points will be plotted. The color of the points will represent the voltage magnitude, and the familiar \(X, Y, Z\) axes will be used. We’ll create the visualization by executing the following code:

Building stress visualization#
1df = pd.read_csv('stress_branch.csv')
2fig = branch_plot(df)
3fig.show()
Stress_distribution

Stress distribution in the branch.#

After processing these types of files, the data needed for training was aggregated into a table. Of all the von Mises stress values ​​calculated in the bend, the maximum value was recorded in the table.

The final table for setting the regression problem has the following parameters:

  • Input parameters (features):

    • Pressure varies in the range: 6.0 - 8.5 \([MPa]\).

    • Gas temperature (gas_temperature) varies in the range: 7.0 - 20.0 \([^{\circ}C]\).

    • Ambient temperature (ambient_temperature) varies in the range: -55.0 - 40.0 \([^{\circ}C]\).

    • Minimum wall thickness (wall_thickness_min), varies in the range: 4.90 - 6.45 \([mm]\).

  • Target variable:

    • The maximum von Mises stress in the structure (stress_max) varies in the range: 45.31 - 140.40 \([MPa]\).

Distribution of training data#
1df = pd.read_csv('branch_data.csv')
2df.describe()

To display data directly in the form of a table, with columns divided into features and a target variable, you can use the following code:

Displaying training data#
1features = ['pressure', 'gas_temp', 'ambient_temp', 'wall_thickness']
2target = 'stress_max'
3
4df[features + [target]].head()

6.2.  Data preparation#

To efficiently work with data, PyTorch uses a special class, StressDataset, which inherits from torch.utils.data.Dataset. The parent class is abstract and requires the implementation of two main methods: __len__() and __getitem__(). Ultimately, this class performs several important functions:

  1. Encapsulates data loading and preprocessing logic. In this case, it converts data to a torch.Tensor and transfers it to the required device.

  2. Provides a consistent data access interface that simplifies working with a model that meets the requirements for integration into REPEAT VISION.

  3. Allows you to work efficiently with large data sets by loading them in portions, which is implemented in this case using features as an example.

  4. Simplifies integration with DataLoader for batching and shuffling data.

Definition of the StressDataset class#
 1class StressDataset(Dataset):
 2    """
 3    Класс датасета для обучения модели.
 4    """
 5    def __init__(
 6        self,
 7        X: np.ndarray,
 8        y: np.ndarray,
 9        device: torch.device = torch.device('cpu')
10    ) -> None:
11        """
12        Инициализация набора данных.
13
14        Параметры:
15            X: np.ndarray
16                Массив входных признаков
17            y: np.ndarray
18                Массив целевых значений
19            device: torch.device
20                Устройство для вычислений
21        """
22        super().__init__()
23        self.device = device
24        self.X = X
25        self.y = torch.FloatTensor(y, device=self.device).reshape(-1, 1)
26    
27    def __len__(self) -> int:
28        """
29        Методы получения количества элементов.
30        
31        Возвращает:
32            int: Количество элементов
33        """
34        return len(self.X)
35    
36    def __getitem__(self, idx: int) -> Tuple[Tuple[torch.Tensor, ...], torch.Tensor]:
37        """
38        Возвращает пару (признаки, целевое значение) для заданного индекса.
39        
40        Параметры:
41            idx: int
42                Индекс образца
43            
44        Возвращает:
45            Tuple[Tuple[torch.Tensor, ...], torch.Tensor]: 
46                Кортеж из кортежа признаков и целевого значения
47        """
48        return (
49            tuple(torch.Tensor([feature], device=self.device) 
50                  for feature in self.X[idx]),
51            self.y[idx]
52        )

Important

It is important to note that each feature is returned as a separate tensor, which is important for export to ONNX and integration with REPEAT VISION.

Let’s use the created class to prepare data for training.

Preparing data for training and testing#
 1X = df[features].values
 2y = df[target].values
 3
 4X_train, X_test, y_train, y_test = train_test_split(
 5    X, y,
 6    test_size=0.2,
 7    random_state=42
 8)
 9
10train_dataset = StressDataset(X_train, y_train, device)
11test_dataset = StressDataset(X_test, y_test, device)
12
13batch_size = 64
14train_loader = DataLoader(
15    train_dataset, 
16    batch_size=batch_size, 
17    shuffle=True
18)
19test_loader = DataLoader(
20    test_dataset, 
21    batch_size=batch_size, 
22    shuffle=False
23)

The result of executing this code is:

  • Lines 1-2. Separation of numerical values ​​of features and the target variable.

  • Lines 4-8. Splitting into training and test samples.

  • Line 10-11. Creating instances of the StressDataset class for training and testing

  • Line 13-23. Creating instances of the DataLoader class to efficiently load data in batches.

6.3.  Neural network architecture#

The problem contains four input features: pressure (pressure), gas temperature (gas_temp), ambient temperature (ambient_temp), and wall thickness (wall_thickness). These are all combined into a single vector of size 4. The output is a single numerical value (the predicted maximum stress), making the problem an example of a regression problem:

\[ f: \mathbb{R}^4 \rightarrow \mathbb{R} \]

where:

\[ f(p, t_g, t_a, w) = \sigma_{max} \]

where:

\(p\) - давление;

\(t_g\) - gas temperature;

\(t_a\) - ambient temperature;

\(w\) - wall thickness;

\(\sigma_{max}\) - maximum voltage.

Various models can be applied to tabular data, from classical machine learning to deep neural networks. In this case, the MLP (Multi Layer Perceptron) model is used.

  • The model’s architecture is a three-layer fixed-depth MLP, where the size of the hidden layers is configured during initialization. This structure provides sufficient capacity to approximate nonlinear relationships in the data.

Matrix representation of a three-layer MLP model with 4 input channels, 1 output channel and a hidden state \( h \):

\[\begin{split} \begin{align*} h_1 &= \text{ReLU}(\text{LayerNorm}(W_1 x + b_1)) \\ h_2 &= \text{ReLU}(\text{LayerNorm}(W_2 h_1 + b_2)) \\ h_3 &= \text{ReLU}(\text{LayerNorm}(W_3 h_2 + b_3)) \\ y &= W_4 h_3 + b_4 \end{align*} \end{split}\]

where:

  • \(x \in \mathbb{R}^4\) - input feature vector

  • \(W_1 \in \mathbb{R}^{h \times 4}, b_1 \in \mathbb{R}^{h}\) - weights and biases of the first layer

  • \(W_2 \in \mathbb{R}^{h \times h}, b_2 \in \mathbb{R}^{h}\) - weights and biases of the second layer

  • \(W_3 \in \mathbb{R}^{h \times h}, b_3 \in \mathbb{R}^{h}\) - weights and biases of the third layer

  • \(W_4 \in \mathbb{R}^{1 \times h}, b_4 \in \mathbb{R}\) - weights and biases of the output layer

  • \(y \in \mathbb{R}\) is the output value (predicted voltage)

The model uses several ideas:

  • To stabilize training, the model uses pre-computed statistics (mean and std) from the training dataset. Normalizing the input features in the forward method allows each example to be brought to a single distribution without requiring a separate normalizer, which would complicate import into REPEAT VISION.

  • A combination of Dropout, L2 regularization (weight decay) and early stopping (early stopping) is used as regularization. The ReLU activation function was chosen based on data analysis, although smoother variants could be used alternatively. The Kaiming method is used to initialize the weights, which stabilizes the variance of the layer outputs and prevents gradient problems. The biases are initialized to zeros since they learn themselves quickly.

  • Instead of the popular BatchNorm, preference is given to LayerNorm, which ensures more stable training on consistent data with significant statistical differences between batches.

All the described ideas are implemented in the StressNet class. This architecture provides a good balance between model capacity and regularization, which is especially important when working with small datasets, as in this problem.

Definition of the StressNet class#
  1class StressNet(nn.Module):
  2    """
  3    Нейронная сеть.
  4    """
  5    def __init__(
  6        self,
  7        input_size: int = 4,
  8        hidden_size: int = 32,
  9        mean: torch.Tensor = torch.zeros(4),
 10        std: torch.Tensor = torch.ones(4),
 11        dropout_rate: float = 0.2
 12    ) -> None:
 13        """
 14        Инициализация модели.
 15        
 16        Параметры:
 17            input_size: int
 18                Размер входного слоя
 19            hidden_size: int
 20                Размер скрытых слоев
 21            mean: torch.Tensor
 22                Средние значения для нормализации
 23            std: torch.Tensor
 24                Стандартные отклонения для нормализации
 25            dropout_rate: float
 26                Вероятность отключения нейронов
 27        """
 28        super().__init__()
 29        
 30        self.fc1 = nn.Linear(input_size, hidden_size)
 31        self.norm1 = nn.LayerNorm(hidden_size)
 32        self.fc2 = nn.Linear(hidden_size, hidden_size)
 33        self.norm2 = nn.LayerNorm(hidden_size)
 34        self.fc3 = nn.Linear(hidden_size, hidden_size)
 35        self.norm3 = nn.LayerNorm(hidden_size)
 36        self.out = nn.Linear(hidden_size, 1)
 37
 38        self.act = nn.ReLU()
 39        self.dropout = nn.Dropout(dropout_rate)
 40
 41        self.mean = mean
 42        self.std = std
 43
 44        self.apply(self._init_weights)
 45
 46    def _init_weights(self, module: nn.Module) -> None:
 47        """
 48        Инициализация весов слоев.
 49        
 50        Параметры:
 51            module: nn.Module
 52                Модуль для инициализации весов
 53        """
 54        if isinstance(module, nn.Linear):
 55            nn.init.kaiming_uniform_(
 56                module.weight,
 57                nonlinearity='relu'
 58            )
 59            nn.init.zeros_(module.bias)
 60
 61    def forward(
 62        self,
 63        pressure: torch.Tensor,
 64        gas_temp: torch.Tensor,
 65        ambient_temp: torch.Tensor,
 66        wall_thickness: torch.Tensor
 67    ) -> torch.Tensor:
 68        """
 69        Прямой проход через сеть.
 70        
 71        Параметры:
 72            pressure: torch.Tensor
 73                Давление
 74            gas_temp: torch.Tensor
 75                Температура газа
 76            ambient_temp: torch.Tensor
 77                Температура окружения
 78            wall_thickness: torch.Tensor
 79                Толщина стенки
 80            
 81        Возвращает:
 82            torch.Tensor: Предсказанное значение максимального напряжения
 83        """
 84        batch_size = pressure.shape[0]
 85        
 86        x = torch.cat([
 87            pressure.view(batch_size, -1),
 88            gas_temp.view(batch_size, -1),
 89            ambient_temp.view(batch_size, -1),
 90            wall_thickness.view(batch_size, -1)
 91        ], dim=1)
 92
 93        x = (x - self.mean) / self.std
 94
 95        x = self.fc1(x)
 96        x = self.norm1(x)
 97        x = self.act(x)
 98        x = self.dropout(x)
 99
100        x = self.fc2(x)
101        x = self.norm2(x)
102        x = self.act(x)
103        x = self.dropout(x)
104
105        x = self.fc3(x)
106        x = self.norm3(x)
107        x = self.act(x)
108        x = self.dropout(x)
109
110        return self.out(x)
  • Lines 30-39. Declaring three layers with LayerNorm and Dropout for regularization.

  • Lines 41-42 Since the input data is normalized within the model, mean and std are passed to the constructor.

  • Lines 44. The initialization of the weights is applied (lines 46-59).

  • Lines 61-110 The main method, forward, takes the required input parameters, combines them into a single tensor, and then passes it through all the layers in the required order.

After defining the model architecture, it is necessary to initialize the model itself and configure the optimizer and criterion (loss function).

In this case, the following configuration is used:

  • The hidden layer size (hidden_size) is set to 512 neurons.

  • The dropout rate is 0.2

  • Statistics from the training set are used to normalize the input data

  • The mean square error (MSE) is chosen as the loss function.

  • AdamW optimizer with learning rate 5e-4 and L2 regularization 1e-4

Announcement of the task and general statement of the task#
 1hidden_size = 512
 2dropout_rate = 0.2
 3
 4stats = pd.DataFrame(df[features])
 5mean = torch.tensor(stats.mean().values, dtype=torch.float32)
 6std = torch.tensor(stats.std().values,  dtype=torch.float32)
 7
 8model = StressNet(
 9    input_size=len(features),
10    mean=mean,
11    std=std,
12    hidden_size=hidden_size,
13    dropout_rate=dropout_rate 
14)
15
16criterion = nn.MSELoss()
17optimizer = optim.AdamW(model.parameters(), lr=5e-4, weight_decay=1e-4)

6.4.  Model training#

The train_model function is designed to train a neural network using the gradient descent method. Here are the main components and steps of the function:

  • Preparation:

    • Transfer the model to the specified device.

    • Initializing lists for tracking losses.

    • Setting up early stopping.

  • Learning cycle:

    • Learning from training data.

    • Validation on test data.

    • Tracking the best model.

    • Checking early stopping conditions.

  • Monitoring:

    • Tracking the value of the loss function.

    • Saving the best model.

    • Conclusion of learning progress.

This implementation provides stable model training with early stopping capability to prevent overfitting.

Definition of the train_model function#
 1def train_model(
 2    model: nn.Module,
 3    train_loader: DataLoader,
 4    val_loader: DataLoader,
 5    criterion: nn.Module,
 6    optimizer: optim.Optimizer,
 7    epochs: int,
 8    device: torch.device = torch.device('cpu'),
 9    patience: int = 20
10) -> Tuple[nn.Module, List[float], List[float]]:
11    """
12    Обучает модель нейронной сети.
13    
14    Параметры:
15        model: nn.Module
16            Модель нейронной сети для обучения
17        train_loader: DataLoader
18            Загрузчик данных для обучающей выборки
19        val_loader: DataLoader
20            Загрузчик данных для валидационной выборки
21        criterion: nn.Module
22            Функция потерь
23        optimizer: optim.Optimizer
24            Оптимизатор для обновления весов
25        epochs: int
26            Количество эпох обучения
27        device: torch.device
28            Устройство для вычислений (CPU/GPU)
29        patience: int
30            Количество проведенных эпох без улучшения, достаточное для ранней остановки обучения
31        
32    Возвращает:
33        Tuple[nn.Module, List[float], List[float]]: Обученная модель, 
34            список потерь на обучающей выборке, список потерь на валидационной выборке
35    """
36    model.to(device)
37    
38    train_losses = []
39    val_losses = []
40    
41    best_val_loss = float('inf')
42    no_improvement = 0
43    best_model_state = None
44    
45    for epoch in tqdm(range(epochs), desc='Training'):
46        model.train()
47        train_loss = 0.0
48        
49        for inputs, targets in train_loader:
50            optimizer.zero_grad()
51            
52            outputs = model(*inputs)
53            loss = criterion(outputs, targets)
54            
55            loss.backward()
56            optimizer.step()
57            
58            train_loss += loss.item() * targets.size(0)
59            
60        train_loss = train_loss / len(train_loader.dataset)
61        train_losses.append(train_loss)
62        
63        model.eval()
64        val_loss = 0.0
65        
66        with torch.no_grad():
67            for inputs, targets in val_loader:
68                outputs = model(*inputs)
69                loss = criterion(outputs, targets)
70                
71                val_loss += loss.item() * targets.size(0)
72                
73        val_loss = val_loss / len(val_loader.dataset)
74        val_losses.append(val_loss)
75        
76        if epoch % 10 == 0:
77            print(
78                f'Epoch {epoch+1}/{epochs}, '
79                f'Train Loss: {train_loss:.6f}, '
80                f'Val Loss: {val_loss:.6f}'
81            )
82        
83        if val_loss < best_val_loss:
84            best_val_loss = val_loss
85            no_improvement = 0
86            best_model_state = model.state_dict().copy()
87        else:
88            no_improvement += 1
89            if no_improvement >= patience:
90                print(f'Early stopping at epoch {epoch+1}')
91                model.load_state_dict(best_model_state)
92                break
93    
94    return model, train_losses, val_losses
  • Lines 49-58. The main training loop with loss calculation on the training dataset.

  • Lines 66-71. Model validation and loss calculation on the validation set.

  • Lines 83-92. Early stopping and best model retention logic.

To train the model, you need to call the train_model function, passing it the previously created parameters and entities correctly.

Calling the train_model function#
 1model, train_losses, val_losses = train_model(
 2    model=model,
 3    train_loader=train_loader,
 4    val_loader=test_loader,
 5    criterion=criterion,
 6    optimizer=optimizer,
 7    epochs=500,
 8    device=device,
 9    patience=10
10)

6.5.  Model evaluation#

The ‘evaluate_model’ function is used to evaluate the performance of a trained neural network on a test dataset. It performs the following steps:

  1. Preparing the model:

    • Puts the model into evaluation mode (model.eval()).

    • Disables gradient calculation to optimize memory.

  2. Data processing:

    • Iterates through all batches in DataLoader.

    • Gets the model’s predictions.

    • Collects real values ​​and predictions into lists.

  3. Metrics calculation:

    • MSE (Mean Squared Error) - root mean square error.

    • RMSE (Root Mean Squared Error) is the root of the root mean square error.

    • MAPE (Mean Absolute Percentage Error) - mean absolute percentage error.

    • MAE (Mean Absolute Error) - mean absolute error.

    • R² (R-squared) - coefficient of determination.

Defining the evaluate_model function#
 1def evaluate_model(
 2    model: nn.Module,
 3    data_loader: DataLoader,
 4    criterion: nn.Module,
 5    device: torch.device = torch.device('cpu')
 6) -> Dict[str, Union[float, np.ndarray]]:
 7    """
 8    Оценивает производительность модели на заданном наборе данных.
 9    
10    Параметры:
11        model: nn.Module
12            Модель PyTorch для оценки
13        data_loader: DataLoader
14            DataLoader с данными для оценки
15        criterion: nn.Module
16            Функция потерь
17        device: torch.device
18            Устройство для вычислений
19        
20    Возвращает:
21        Dict[str, Union[float, np.ndarray]]: Словарь с метриками оценки
22        и результатами предсказаний
23    """
24    model.to(device)
25    model.eval()
26    
27    all_targets = []
28    all_predictions = []
29    total_loss = 0.0
30    
31    with torch.no_grad():
32        for inputs, targets in data_loader:
33            outputs = model(*inputs)
34            loss = criterion(outputs, targets)
35            total_loss += loss.item() * targets.size(0)
36            
37            all_targets.extend(targets.cpu().numpy())
38            all_predictions.extend(outputs.cpu().numpy())
39    
40    all_targets = np.array(all_targets).flatten()
41    all_predictions = np.array(all_predictions).flatten()
42    
43    mse = mean_squared_error(all_targets, all_predictions)
44    rmse = np.sqrt(mse)
45    mape = mean_absolute_percentage_error(all_targets, all_predictions)
46    mae = mean_absolute_error(all_targets, all_predictions)
47    r2 = r2_score(all_targets, all_predictions)
48    
49    avg_loss = total_loss / len(data_loader.dataset)
50    
51    return {
52        'loss': avg_loss,
53        'mse': mse,
54        'rmse': rmse,
55        'mape': mape,
56        'mae': mae,
57        'r2': r2,
58        'targets': all_targets,
59        'predictions': all_predictions
60    }

In this implementation, many different metrics of the regression problem are considered in order to obtain the most complete understanding of the quality and accuracy of the problem solution.

To get all the metrics, simply run the following code:

Calling the evaluate_model function#
1test_results = evaluate_model(model, test_loader, criterion, device)
2
3print("Test metrics:")
4print(f"MSE: {test_results['loss']:.3f}")
5print(f"RMSE: {test_results['rmse']:.3f}")
6print(f"MAPE: {test_results['mape']*100:.3f}%")
7print(f"MAE: {test_results['mae']:.3f}")
8print(f"R^2: {test_results['r2']:.3f}")

This code will output the results of the model testing.

Metrics

Meaning

Interpretation

MSE

5.281

Root mean square error

RMSE

2.298

Root of the mean square error

MAPE

1.066%

Mean absolute percentage error

MAE

0.814

Mean absolute error

R²

0.987

Coefficient of determination

6.6.  Visualization of results#

The visualize_results function is designed to visualize the dynamics of the change in the loss function on the training and validation samples and to construct an error histogram.

Defining the visualize_results function#
 1def visualize_results(
 2    train_losses: List[float],
 3    val_losses: List[float],
 4    test_results: Dict[str, np.ndarray]
 5) -> None:
 6    """
 7    Визуализирует результаты обучения модели.
 8    
 9    Параметры:
10        train_losses: List[float]
11            Список значений функции потерь на обучающей выборке
12        val_losses: List[float]
13            Список значений функции потерь на валидационной выборке
14        test_results: Dict[str, np.ndarray]
15            Словарь с результатами тестирования модели
16    """
17    _, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
18    
19    ax1.plot(train_losses, label='Train Loss')
20    ax1.plot(val_losses, label='Val Loss')
21    ax1.set_xlabel('Epoch')
22    ax1.set_ylabel('Loss')
23    ax1.legend()
24    ax1.grid(True)
25    
26    errors: np.ndarray = test_results['predictions'] - test_results['targets']
27    sns.histplot(errors, bins=30)
28    ax2.set_xlabel('Error')
29    ax2.set_ylabel('Frequency')
30    ax2.grid(True)
31    
32    plt.tight_layout()
33    plt.show()

Let’s call this function.

Calling the visualize_results function#
1visualize_results(train_losses, val_losses, test_results)

As a result, we have two graphs:

  • The first is the dynamics of the loss function during training and validation.

  • The second is a histogram of the distribution of prediction errors.

LossAndErrors

Graphs of changes in the value of the loss function and the distribution of errors.#

6.7.  Exporting a model to ONNX#

The export_to_onnx function is designed to export a trained PyTorch model to the ONNX format. The function takes into account the need to convert to double mode and takes into account the names of the input and output parameters.

Definition of the export_to_onnx function#
 1def export_to_onnx(
 2    model: nn.Module,
 3    features: List[str],
 4    target: str,
 5    output_path: str = "stress_model.onnx"
 6) -> None:
 7    """
 8    Экспортирует PyTorch модель в формат ONNX.
 9    
10    Args:
11        model: nn.Module
12            PyTorch модель для экспорта
13        features: List[str]
14            Список имен входных признаков
15        target: str
16            Имя целевой переменной
17        output_path: str
18            Путь для сохранения ONNX модели
19    """
20    model.eval()
21    model.double()
22    
23    dummy_inputs: Dict[str, torch.Tensor] = {
24        feature: torch.zeros(1, 1, dtype=torch.float64)
25        for feature in features
26    }
27    
28    torch.onnx.export(
29        model,
30        dummy_inputs,
31        output_path,
32        input_names=features,
33        output_names=[target],
34        opset_version=12,
35        dynamo = False
36        )
37    
38    print(f"Model exported to {output_path}.")

To configure a previously trained model, it is necessary to pass it to the previously declared model and the names of the features and target variable for correct display in REPEAT VISION.

Calling the export_to_onnx function#
1export_to_onnx(model, features, target)

In the next step, the saved model will be imported into the REPEAT VISION platform in the gas pipeline project to perform interdisciplinary modeling.

7.  The REPEAT VISION platform’s gas pipeline project#

The figure below shows a project on the REPEAT VISION platform for modeling a gas pipeline using blocks from the Pneumatics, Automation, and External models libraries. This model of gas pipeline-loop allows:

  1. Calculate the parameters of the collection system to ensure compliance with the technical design for the field development;

  2. Determine the parameters of the collection system when changing the input parameters of the gas and condensate treatment system.

../../../_images/repeat_project.en.png

Gas pipeline project on the REPEAT VISION platform.#

Within the framework of a single project, based on current data from diagnostics of the technical condition of the gas pipeline-loop, the calculation of von Mises stresses of the branch is also performed using the block №12 Approximation model and the machine learning model imported into this block.

The project shown above allows:

  1. Perform computational experiments on machine learning models,

  2. In a general interdisciplinary model develop a control system for the technological process of gas production, taking into account the assessment of the maximum von Mises stresses in the branch;

  3. Using Modbus blocks, it is possible to create digital twins of gas pipelines for the stage of their operation, connecting thickness measurement systems, pressure, temperature and gas flow measurement systems to the model.

7.1.  Results of computational experiments#

The dynamics of the decrease in the wall thickness of the bend is shown in the figure below.

../../../_images/branch_wall_thicks.en.png

Dynamics of reduction of minimum wall thickness of the bend.#

A decrease in the thickness of the branch wall, as well as changes in the ambient temperature while maintaining a constant gas pressure, lead to an increase in stress in the branch, the approximate results of which are shown in the next figure.

../../../_images/approx_stress.en.png

Dynamics of increase of maximum von Mises stresses in a bend.#

8.  Conclusions#

Integration of machine learning models into the REPEAT VISION platform:

  1. Expands the range of tasks to be solved in system modeling and in the creation of digital twins of complex systems;

  2. Provides the use of FVM and FEM models of large dimensions, as well as models based on the results of diagnostics of operating systems;

  3. Compared to direct integration with external engineering software: eliminates the risk of errors due to problems with the convergence of the FEM (FVM) model calculation, significantly increases the speed of structural design calculation.