Привет Хабр!
Современное нефтегазовое производство требует всё более совершенных инструментов для прогнозирования состояния оборудования и предотвращения аварийных ситуаций. Особенно это касается газопроводов-шлейфов —критически важных элементов инфраструктуры, обеспечивающих транспортировку газа от скважин до установок комплексной подготовки газа.
Проблема износа трубопроводов становится всё более актуальной. Эрозионное воздействие потока газа, содержащего воду с механическими примесями, может привести к серьёзным последствиям, вплоть до аварийных ситуаций. Традиционные методы диагностики и принятия решений уже не справляются с растущей сложностью задач.
В этой статье мы подробно разберём, как машинное обучение помогает решать проблему прогнозирования износа газопроводов. На конкретном примере покажем, как:
Создавать точные модели прогнозирования эрозионного износа
Интегрировать ML-модели в существующие системы инженерного моделирования
Автоматизировать процесс оценки прочности конструкций
Повышать эффективность эксплуатации газодобывающего оборудования
Практическая ценность материала заключается в пошаговом руководстве создания и внедрения ML-решений в реальную производственную среду. В данном руководстве представлены готовые инструменты и практические рекомендации для самостоятельной реализации проекта.
Далее мы подробно рассмотрим каждый этап разработки — от сбора и подготовки данных до финальной интеграции модели в производственную среду. Особое внимание уделим техническим аспектам реализации и возможным сложностям при внедрении.
Готовы погрузиться в мир промышленного ML? Тогда начнем.
1. Общая информация о газопроводах-шлейфах
Газопроводы-шлейфы (рисунок ниже) используются на газовых месторождениях и предназначены для транспорта газа от скважин до установки комплексной подготовки газа (УКПГ). Газ, после необходимой подготовки к транспорту на УКПГ, поступает в магистральный газопровод.

Газопровод-шлейф (№1 на рисунке выше) в процессе эксплуатации подвержен следующим воздействиям:
Внутренняя поверхность газопровода нагружена давлением и температурой газа;
Наружная поверхность газопровода нагружена переменной температурой внешней среды.
1.1. Проблема эрозионного износа газопровода-шлейфа
Процесс эксплуатации газовой скважины (№2 на рисунке выше) может сопровождаться выносом воды, содержащей механические примеси, что вызывает эрозионный износ внутренней поверхности газопровода-шлейфа.
Вынос воды, содержащей небольшое количество механических примесей, может происходить постоянно, что позволяет создавать несложные полуэмпирические аналитические методики прогнозирования эрозионного износа.
Также эрозионный износ может носить циклический характер с периодическим выносом воды и механических примесей.
Бывают и очень опасные внезапные выносы механических примесей с большим количеством воды, которые могут приводить к непредвиденному и значительному износу элементов газопровода-шлейфа.
Наиболее уязвимым элементом газопровода-шлейфа является первый отвод, если считать от фонтанной арматуры газовой скважины (№2 на рисунке выше).
1.2. Способы снижения риска
Для снижения риска инцидентов и аварий вследствие эрозионного износа газопровода-шлейфа требуется решение комплексной задачи, включающей:
Применение физико-математического моделирования процесса эксплуатации трубопроводной системы и его системы управления;
Применение автоматизированных средств диагностики технического состояния элементов газопровода;
Внедрение автоматизированных систем управления процессом эксплуатации газовых скважин, использующих результаты оценки математического моделирования актуального технического состояния газопровода.
Применение физико-математического моделирования процесса эксплуатации трубопроводной системы и его системы управления;
Применение автоматизированных средств диагностики технического состояния элементов газопровода;
Внедрение автоматизированных систем управления процессом эксплуатации газовых скважин, использующих результаты оценки математического моделирования актуального технического состояния газопровода.
2. Средства диагностики технического состояния газопроводов-шлейфов
Автоматизированные средства диагностики технического состояния элементов газопровода-шлейфа, необходимые для контроля эрозионного процесса включают следующие системы:
Системы контроля расхода;
Датчики температуры внешней среды и системы контроля давления и температуры газа;
Интрузивные датчики измерения эрозии и акустические системы контроля песка;
Системы точечной и распределённой толщинометрии.
3. Цели и задачи моделирования
Цель. Повышение производственной эффективности компании и предупреждение аварий промысловых объектов.
Задачи моделирования:
Расчёт параметров прочности на основании актуальных данных диагностики технического состояния газопроводов-шлейфов;
Расчёт параметров системы сбора на соответствие техническому проекту разработки месторождения;
Обеспечение максимальной добычи газа в пиковый сезонный период (зима, спрос) и минимальной добычи (лето);
Обеспечение оптимальных параметров работы системы сбора при незапланированном отключении скважин или при режимах работы скважин, не соответствующих проектным;
Определение параметров системы сбора при изменении входных параметров системы подготовки газа и конденсата.
Способ решения задачи №1 приводится в следующем разделе. Решение остальных задач сводится к моделированию трубопроводных систем с помощью REPEAT и решению задач глобальной и локальной оптимизации.
4. Описание задачи
Выполнить оценку напряжённо-деформированного состояния (НДС) отвода на основании исходных данных и граничных условий.
Модель машинного обучения создаётся на основе таблицы данных результатов расчётов статической прочности модели отвода. Каждая строка в таблице данных является отдельным расчётным случаем. Каждый расчётный случай включает:
-
Исходные данные и граничные условия:
Минимальная толщина стенки,
Давление газа,
Температура газа,
Температура окружающей среды,
Результат расчёта НДС (напряжённо-деформированное состояние): Максимальные напряжения по Мизесу.
Расчётные случаи основаны на технологическом регламенте конкретного месторождения:
Давление газа изменяется в диапазоне: от 8.5 до 6.0 МПа,
Температура газа изменяется в диапазоне: от 7 до 20 ,
Температура окружающей среды изменяется в диапазоне: от -55 до 40 ,
Расчётные случаи включают минимальные толщины стенки отвода от 6.45 до 4.9 мм.
Результатом расчёта статической прочности одного из расчётных случаев является распределение напряжений по Мизесу, приведённое на рисунке ниже.

Для каждого результата расчёта напряжений, действующих в отводе, приведённых на рисунке выше, выбирается максимальное значение и заносится в таблицу результатов расчётов.
5. Подготовка таблицы расчётных случаев
Расчёт статической прочности отвода выполняется в программном комплексе, реализующего метод конечных элементов (МКЭ).
5.1. Модель статической прочности
Для определения НДС отвода учитывалось влияние всего газопровода-шлейфа и связанных с ним опорных конструкций куста двух газовых скважин.
Линейные участки трубопроводов газопровода-шлейфа и опорные конструкции моделировались в балочном приближении.
Отводы и тройниковые соединения моделировались с помощью объёмных КЭ.
5.2. Основные этапы подготовки данных
Подготовка таблицы расчётных случаев сводится к автоматизированному процессу параметрических исследований статической прочности отвода, который сам по себе является отдельной инженерной задачей.
Подсказка
Если вам необходимо больше информации и навыков выполнения параметрических исследований, то ознакомьтесь с нашим практическим руководством на данную тему.
Основные этапы процесса параметрических исследований приведены на рисунке ниже.

Данный процесс повторялся в рабочем цикле параметрических исследований примерно 6000 раз для получения необходимой таблицы расчётных случаев с целью последующего создания модели машинного обучения.
6. Построение модели нейронной сети
Примечание
Вы можете скачать файл Jupyter ноутбука, если хотите быстро выполнить код данного практического руководства на вашем персональном компьютере. Дополнительно вам понадобятся данные для обучения модели и (не обязательно) данные для визуализации одного результата расчёта.
Также вы можете скачать файл модели и сразу использовать её в проекте на платформе REPEAT с помощью блока Аппроксимационная модель.
Для написания модели будет использован язык Python и фреймворк PyTorch, поэтому необходимы базовые знания этого языка и навыки работы с PyTorch.
Важно
Обратите внимание, что для установки версии PyTorch, поддерживающей работу с GPU и совместимой с Вашей версией CUDA, необходимо посетить официальный сайт.
Кроме того, для решения задачи используется множество общепринятый для соответствующих подзадач библиотек:
NumPy и Pandas — работа с массивами, линейная алгебра и обработка табличных данных соответственно.
Matplotlib, Seaborn — визуализация статических графиков для отслеживания качества работы модели.
Plotly — интерактивные графики, для наглядного представления объекта исследования.
scikit-learn — подсчет метрик и разбиение данных.
В первую очередь импортируем необходимые библиотеки для создания нейронных сетей, построения визуализаций и последующего импорта в формат ONNX.
# Импорт библиотек import numpy as np import pandas as pd import seaborn as sns import matplotlib.pyplot as plt import plotly.graph_objects as go import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import Dataset, DataLoader from sklearn.model_selection import train_test_split from sklearn.metrics import (mean_squared_error, mean_absolute_error, mean_absolute_percentage_error, r2_score) from tqdm.notebook import tqdm from typing import Tuple, List, Dict, Union # Для улучшения воспроизводимость и использования доступного устройства, необходимо зафиксировать генерацию случайных чисел и доступность графического ускорителя. # Воспроизводимость и устройство SEED = 42 torch.manual_seed(SEED) np.random.seed(SEED) device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') print(device)
6.1. Исследование данных
Для наглядного отображения распределения напряжений в отводе визуализируем трехмерное распределение точек используя интерактивную 3D-визуализация с помощью Plotly.
# Визуализация напряжений def branch_plot(df: pd.DataFrame) -> go.Figure: """ Создает 3D визуализацию распределения напряжений. Параметры: df: pd.DataFrame DataFrame с данными о координатах и напряжениях Возвращает: go.Figure: Объект графика Plotly """ marker_config = dict( size=3, color=df['Stress'], colorscale='Jet', opacity=0.8, colorbar=dict(title="Напряжение (МПа)") ) hover_template = ( '<b>Узел</b>: %{customdata}<br>' '<b>X</b>: %{x:.2f} мм<br>' '<b>Y</b>: %{y:.2f} мм<br>' '<b>Z</b>: %{z:.2f} мм<br>' '<b>Напряжение</b>: %{marker.color:.2f} МПа' ) fig = go.Figure( data=[ go.Scatter3d( x=df['X'], y=df['Y'], z=df['Z'], mode='markers', marker=marker_config, hovertemplate=hover_template, customdata=df['Node'] ) ] ) scene_config = dict( xaxis_title='X (мм)', yaxis_title='Y (мм)', zaxis_title='Z (мм)', aspectmode='data', camera=dict( eye=dict(x=0, y=0, z=2.5), center=dict(x=0, y=0, z=0), up=dict(x=0, y=1, z=0) ) ) margin_config = dict(l=0, r=0, b=0, t=30) fig.update_layout( title='Распределение напряжений', scene=scene_config, margin=margin_config ) return fig
В результате, для произвольно выбранного файла с результатами численного эксперимента, будет построен набор точек, цвет которых будет отображать величину напряжения, а в качестве осей будут использованы привычные . Проведем построение визуализации выполнением следующего кода:
# Построение визуализации напряжений df = pd.read_csv('stress_branch.csv') fig = branch_plot(df) fig.show()

В результате обработки такого рода файлов, необходимы для обучения данные были агрегированы в таблицу. Из всего множества значений напряжений по Мизесу, которые рассчитывались в отводе, в таблицу записывалось максимальное значение.
Итоговая таблица для постановки задачи регрессии имеет следующие параметры:
-
Входные параметры (признаки):
Давление (
pressure), меняется в диапазоне: 6.0 - 8.5 МПа.Температура газа (
gas_temperature), меняется в диапазоне: 7.0 - 20.0 .Температура окружающей среды (
ambient_temperature), меняется в диапазоне: -55.0 - 40.0 .Минимальная толщина стенки (
wall_thickness_min), меняется в диапазоне: 4.90 - 6.45 мм.
-
Целевая переменная:
Максимальное напряжение по Мизесу в конструкции (
stress_max), меняется в диапазоне: 45.31 - 140.40 МПа.
Распределение данных для обучения
df = pd.read_csv('branch_data.csv') df.describe()
Для отображения данных в виде непосредственно таблицы, с разделением столбцов на признаки и целевую переменную можно использовать следующий код:
Отображение данных для обучения
features = ['pressure', 'gas_temp', 'ambient_temp', 'wall_thickness'] target = 'stress_max' df[features + [target]].head()
6.2. Подготовка данных
Для эффективной работы с данными в PyTorch используется специальный класс StressDataset, который наследуется от torch.utils.data.Dataset. Родительский класс является абстрактным и требует реализации двух основных методов: len() и getitem(). В итоге, этот класс выполняет несколько важных функций:
Инкапсулирует логику загрузки и предобработки данных. В данном случае - преобразует данные в
torch.Tensorи переносит их на необходимое устройство.Обеспечивает единообразный интерфейс доступа к данным, упрощающий работу с моделью, соответствующей требованиям для интеграции в REPEAT.
Позволяет эффективно работать с большими наборами данных, загружая их порциями, что реализовано в данном случае на примере признаков.
Упрощает интеграцию с DataLoader для разбиения на батчи и перемешивания данных.
Определение класса StressDataset
class StressDataset(Dataset): """ Класс датасета для обучения модели. """ def init( self, X: np.ndarray, y: np.ndarray, device: torch.device = torch.device('cpu') ) -> None: """ Инициализация набора данных. Параметры: X: np.ndarray Массив входных признаков y: np.ndarray Массив целевых значений device: torch.device Устройство для вычислений """ super().__init__() self.device = device self.X = X self.y = torch.FloatTensor(y, device=self.device).reshape(-1, 1) def len(self) -> int: """ Методы получения количества элементов. Возвращает: int: Количество элементов """ return len(self.X) def getitem(self, idx: int) -> Tuple[Tuple[torch.Tensor, ...], torch.Tensor]: """ Возвращает пару (признаки, целевое значение) для заданного индекса. Параметры: idx: int Индекс образца Возвращает: Tuple[Tuple[torch.Tensor, ...], torch.Tensor]: Кортеж из кортежа признаков и целевого значения """ return ( tuple(torch.Tensor([feature], device=self.device) for feature in self.X[idx]), self.y[idx] )
Важно
Важно отметить, что каждый признак возвращается отдельным тензором, что важно для экспорта в ONNX и интеграции с REPEAT.
Воспользуемся созданным классом для подготовки данных для обучения.
Подготовка данных для обучения и тестирования
X = df[features].values y = df[target].values X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) train_dataset = StressDataset(X_train, y_train, device) test_dataset = StressDataset(X_test, y_test, device) batch_size = 64 train_loader = DataLoader( train_dataset, batch_size=batch_size, shuffle=True ) test_loader = DataLoader( test_dataset, batch_size=batch_size, shuffle=False )
В результате выполнения этого кода происходит:
Строки 1-2. Отделение численных значений признаков и целевой переменной.
Строки 4-8. Разбиение на обучающую и тестовую выборки.
Строка 10-11. Создание экземпляров класса
StressDataset для обучения и тестированияСтрока 13-23. Создание экземпляров класса
DataLoaderдля эффективной загрузки данных батчами.
6.3. Архитектура нейронной сети
В задаче заданы 4 входных признака: давление (pressure), температура газа (gas_temp), температура окружающей среды (ambient_temp) и толщина стенки (wall_thickness). Все они склеиваются в один вектор размера 4. Выходом же является одно численное значение (предсказанное максимальное напряжение), то есть задача является примером задачи регрессии:

Для табличных данных могут быть применены различные модели, от классического машинного обучения до глубоких нейронных сетей. В данном случае используется MLP (Multi Layer Perceptron) модель.
Архитектура модели представляет собой трехслойный MLP с фиксированной глубиной, где размер скрытых слоев настраивается при инициализации. Такая структура обеспечивает достаточную емкость для аппроксимации нелинейных зависимостей в данных.
Матричное представление трехслойной MLP модели с 4 входными каналами, 1 выходным и скрытым состоянием :

В модели применено несколько идей:
Для стабилизации обучения модель использует предварительно вычисленные статистики (
meanиstd) из тренировочного набора данных. Нормализация входных признаков в методеforwardпозволяет привести каждый пример к единому распределению, не требуя отдельного нормализатора, что осложнило бы импорт в REPEAT.В качестве регуляризации применяется комбинация
Dropout, L2-регуляризации (weight decay) и ранней остановкой (early stopping). Функция активации ReLU выбрана на основе анализа данных, хотя альтернативно можно использовать и более сглаженные варианты. Для инициализации весов используется метод Кайминга, который стабилизирует дисперсию выходов слоев и предотвращает проблемы с градиентами. Смещения инициализируются нулями, так как они быстро обучаются сами.Вместо популярного
BatchNormпредпочтение отданоLayerNorm, что обеспечивает более стабильное обучение на консистентных данных при существенных статистических различиях между батчами.
Все описанные идеи реализованы в классе StressNet. Такая архитектура обеспечивает хороший баланс между емкостью модели и регуляризацией, что особенно важно при работе с малым, как в этой задаче, набором данных.
Определение класса StressNet
class StressNet(nn.Module): """ Нейронная сеть. """ def init( self, input_size: int = 4, hidden_size: int = 32, mean: torch.Tensor = torch.zeros(4), std: torch.Tensor = torch.ones(4), dropout_rate: float = 0.2 ) -> None: """ Инициализация модели. Параметры: input_size: int Размер входного слоя hidden_size: int Размер скрытых слоев mean: torch.Tensor Средние значения для нормализации std: torch.Tensor Стандартные отклонения для нормализации dropout_rate: float Вероятность отключения нейронов """ super().__init__() self.fc1 = nn.Linear(input_size, hidden_size) self.norm1 = nn.LayerNorm(hidden_size) self.fc2 = nn.Linear(hidden_size, hidden_size) self.norm2 = nn.LayerNorm(hidden_size) self.fc3 = nn.Linear(hidden_size, hidden_size) self.norm3 = nn.LayerNorm(hidden_size) self.out = nn.Linear(hidden_size, 1) self.act = nn.ReLU() self.dropout = nn.Dropout(dropout_rate) self.mean = mean self.std = std self.apply(self._init_weights) def initweights(self, module: nn.Module) -> None: """ Инициализация весов слоев. Параметры: module: nn.Module Модуль для инициализации весов """ if isinstance(module, nn.Linear): nn.init.kaiming_uniform_( module.weight, nonlinearity='relu' ) nn.init.zeros_(module.bias) def forward( self, pressure: torch.Tensor, gas_temp: torch.Tensor, ambient_temp: torch.Tensor, wall_thickness: torch.Tensor ) -> torch.Tensor: """ Прямой проход через сеть. Параметры: pressure: torch.Tensor Давление gas_temp: torch.Tensor Температура газа ambient_temp: torch.Tensor Температура окружения wall_thickness: torch.Tensor Толщина стенки Возвращает: torch.Tensor: Предсказанное значение максимального напряжения """ batch_size = pressure.shape[0] x = torch.cat([ pressure.view(batch_size, -1), gas_temp.view(batch_size, -1), ambient_temp.view(batch_size, -1), wall_thickness.view(batch_size, -1) ], dim=1) x = (x - self.mean) / self.std x = self.fc1(x) x = self.norm1(x) x = self.act(x) x = self.dropout(x) x = self.fc2(x) x = self.norm2(x) x = self.act(x) x = self.dropout(x) x = self.fc3(x) x = self.norm3(x) x = self.act(x) x = self.dropout(x) return self.out(x)
Строки 30-39. Объявление трех слоев с LayerNorm и Dropout для регуляризации.
Строки 41-42. Поскольку входные данные нормализуются внутри модели,
meanиstdпередаются в конструктор.Строки 44. Применяется начальная инициализация весов (строки 46-59).
Строки 61-110. Основной метод,
forward, принимает необходимые входные параметры, объединяет их в один тензор и затем пропускает его через все слои в необходимом порядке.
После определения архитектуры модели, необходимо инициализировать саму модель и настроить оптимизатор и критерий (функцию потерь).
В данном случае используется следующая конфигурация:
Размер скрытого слоя (
hidden_size) установлен на 512 нейроновКоэффициент прореживания (
dropout_rate) равен 0.2Для нормализации входных данных используются статистики из обучающего набора
В качестве функции потерь выбрано среднеквадратичное отклонение (MSE)
Оптимизатор AdamW с коэффициентом обучения 5e-4 и L2-регуляризацией 1e-4
Объявление задачи и общая постановка задачи
hidden_size = 512 dropout_rate = 0.2 stats = pd.DataFrame(df[features]) mean = torch.tensor(stats.mean().values, dtype=torch.float32) std = torch.tensor(stats.std().values, dtype=torch.float32) model = StressNet( input_size=len(features), mean=mean, std=std, hidden_size=hidden_size, dropout_rate=dropout_rate ) criterion = nn.MSELoss() optimizer = optim.AdamW(model.parameters(), lr=5e-4, weight_decay=1e-4)
6.4. Обучение модели
Функция train_model предназначена для обучения нейронной сети с использованием метода градиентного спуска. Вот основные компоненты и этапы работы функции:
-
Подготовка:
Перенос модели на указанное устройство.
Инициализация списков для отслеживания потерь.
Настройка ранней остановки (early stopping).
-
Цикл обучения:
Обучение на тренировочных данных.
Валидация на проверочных данных.
Отслеживание лучшей модели.
Проверка условий ранней остановки.
-
Мониторинг:
Отслеживание значения функции потерь.
Сохранение лучшей модели.
Вывод прогресса обучения.
Данная реализация обеспечивает стабильное обучение модели с возможностью ранней остановки для предотвращения переобучения.
Определение функции train_model
def train_model( model: nn.Module, train_loader: DataLoader, val_loader: DataLoader, criterion: nn.Module, optimizer: optim.Optimizer, epochs: int, device: torch.device = torch.device('cpu'), patience: int = 20 ) -> Tuple[nn.Module, List[float], List[float]]: """ Обучает модель нейронной сети. Параметры: model: nn.Module Модель нейронной сети для обучения train_loader: DataLoader Загрузчик данных для обучающей выборки val_loader: DataLoader Загрузчик данных для валидационной выборки criterion: nn.Module Функция потерь optimizer: optim.Optimizer Оптимизатор для обновления весов epochs: int Количество эпох обучения device: torch.device Устройство для вычислений (CPU/GPU) patience: int Количество проведенных эпох без улучшения, достаточное для ранней остановки обучения Возвращает: Tuple[nn.Module, List[float], List[float]]: Обученная модель, список потерь на обучающей выборке, список потерь на валидационной выборке """ model.to(device) train_losses = [] val_losses = [] best_val_loss = float('inf') no_improvement = 0 best_model_state = None for epoch in tqdm(range(epochs), desc='Training'): model.train() train_loss = 0.0 for inputs, targets in train_loader: optimizer.zero_grad() outputs = model(*inputs) loss = criterion(outputs, targets) loss.backward() optimizer.step() train_loss += loss.item() * targets.size(0) train_loss = train_loss / len(train_loader.dataset) train_losses.append(train_loss) model.eval() val_loss = 0.0 with torch.no_grad(): for inputs, targets in val_loader: outputs = model(*inputs) loss = criterion(outputs, targets) val_loss += loss.item() * targets.size(0) val_loss = val_loss / len(val_loader.dataset) val_losses.append(val_loss) if epoch % 10 == 0: print( f'Epoch {epoch+1}/{epochs}, ' f'Train Loss: {train_loss:.6f}, ' f'Val Loss: {val_loss:.6f}' ) if val_loss < best_val_loss: best_val_loss = val_loss no_improvement = 0 best_model_state = model.state_dict().copy() else: no_improvement += 1 if no_improvement >= patience: print(f'Early stopping at epoch {epoch+1}') model.load_state_dict(best_model_state) break return model, train_losses, val_losses
Строки 49-58. Основной цикл обучения с вычислением потерь на тренировочном наборе данных.
Строки 66-71. Валидация модели и вычисление потерь на валидационном наборе.
Строки 83-92. Логика ранней остановки и сохранения лучшей модели.
Для обучения модели необходимо вызвать функцию train_model передав ей корректно созданные ранее параметры и сущности.
Вызов функции train_model
model, train_losses, val_losses = train_model( model=model, train_loader=train_loader, val_loader=test_loader, criterion=criterion, optimizer=optimizer, epochs=500, device=device, patience=10 )
6.5. Оценка модели
Функция evaluate_model предназначена для оценки производительности обученной нейронной сети на тестовом наборе данных. Она выполняет следующие действия:
-
Подготовка модели:
Переводит модель в режим оценки (
model.eval()).Отключает вычисление градиентов для оптимизации памяти.
-
Обработка данных:
Проходит по всем батчам в DataLoader.
Получает предсказания модели.
Собирает реальные значения и предсказания в списки.
-
Расчет метрик:
MSE (Mean Squared Error) - средняя квадратичная ошибка.
RMSE (Root Mean Squared Error) - корень из средней квадратичной ошибки.
MAPE (Mean Absolute Percentage Error) - средняя абсолютная процентная ошибка.
MAE (Mean Absolute Error) - средняя абсолютная ошибка.
R² (R-squared) - коэффициент детерминации.
Определение функции evaluate_model
def evaluate_model( model: nn.Module, data_loader: DataLoader, criterion: nn.Module, device: torch.device = torch.device('cpu') ) -> Dict[str, Union[float, np.ndarray]]: """ Оценивает производительность модели на заданном наборе данных. Параметры: model: nn.Module Модель PyTorch для оценки data_loader: DataLoader DataLoader с данными для оценки criterion: nn.Module Функция потерь device: torch.device Устройство для вычислений Возвращает: Dict[str, Union[float, np.ndarray]]: Словарь с метриками оценки и результатами предсказаний """ model.to(device) model.eval() all_targets = [] all_predictions = [] total_loss = 0.0 with torch.no_grad(): for inputs, targets in data_loader: outputs = model(*inputs) loss = criterion(outputs, targets) total_loss += loss.item() * targets.size(0) all_targets.extend(targets.cpu().numpy()) all_predictions.extend(outputs.cpu().numpy()) all_targets = np.array(all_targets).flatten() all_predictions = np.array(all_predictions).flatten() mse = mean_squared_error(all_targets, all_predictions) rmse = np.sqrt(mse) mape = mean_absolute_percentage_error(all_targets, all_predictions) mae = mean_absolute_error(all_targets, all_predictions) r2 = r2_score(all_targets, all_predictions) avg_loss = total_loss / len(data_loader.dataset) return { 'loss': avg_loss, 'mse': mse, 'rmse': rmse, 'mape': mape, 'mae': mae, 'r2': r2, 'targets': all_targets, 'predictions': all_predictions }
В данной реализации считается множество различных метрик задачи регрессии, для получения наиболее полного понимания о качестве и точности решения задачи.
Для получения всех метрик достаточно выполнить следующий код:
Вызов функции evaluate_model
test_results = evaluate_model(model, test_loader, criterion, device) print("Test metrics:") print(f"MSE: {test_results['loss']:.3f}") print(f"RMSE: {test_results['rmse']:.3f}") print(f"MAPE: {test_results['mape']*100:.3f}%") print(f"MAE: {test_results['mae']:.3f}") print(f"R^2: {test_results['r2']:.3f}")
Этот код выведет результаты тестирования модели.
Метрика |
Значение |
Интерпретация |
MSE |
5.281 |
Среднеквадратичная ошибка |
RMSE |
2.298 |
Корень из среднеквадратичной ошибки |
MAPE |
1.066% |
Средняя абсолютная процентная ошибка |
MAE |
0.814 |
Средняя абсолютная ошибка |
R² |
0.987 |
Коэффициент детерминации |
6.6. Визуализация результатов
Функция visualize_results предназначена для визуализации динамики изменения функции потерь на обучающей и валидационной выборках и построения гистограммы ошибок.
Определение функции visualize_results
def visualize_results( train_losses: List[float], val_losses: List[float], test_results: Dict[str, np.ndarray] ) -> None: """ Визуализирует результаты обучения модели. Параметры: train_losses: List[float] Список значений функции потерь на обучающей выборке val_losses: List[float] Список значений функции потерь на валидационной выборке test_results: Dict[str, np.ndarray] Словарь с результатами тестирования модели """ _, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4)) ax1.plot(train_losses, label='Train Loss') ax1.plot(val_losses, label='Val Loss') ax1.set_xlabel('Epoch') ax1.set_ylabel('Loss') ax1.legend() ax1.grid(True) errors: np.ndarray = test_results['predictions'] - test_results['targets'] sns.histplot(errors, bins=30) ax2.set_xlabel('Error') ax2.set_ylabel('Frequency') ax2.grid(True) plt.tight_layout() plt.show()
Вызовем эту функцию.
Вызов функции visualize_results
visualize_results(train_losses, val_losses, test_results)
В результате имеем два графика:
Первый — динамика функции потерь на обучении и валидации.
Второй — гистограмма распределения ошибок предсказания.

6.7. Экспорт модели в ONNX
Функция export_to_onnx предназначена для экспорта обученной PyTorch модели в формат ONNX. Функция учитывает необходимости перевода в режим double и учитывает имена входных и выходных параметров.
Определение функции export_to_onnx
def export_to_onnx( model: nn.Module, features: List[str], target: str, output_path: str = "stress_model.onnx" ) -> None: """ Экспортирует PyTorch модель в формат ONNX. Args: model: nn.Module PyTorch модель для экспорта features: List[str] Список имен входных признаков target: str Имя целевой переменной output_path: str Путь для сохранения ONNX модели """ model.eval() model.double() dummy_inputs: Dict[str, torch.Tensor] = { feature: torch.zeros(1, 1, dtype=torch.float64) for feature in features } torch.onnx.export( model, dummy_inputs, output_path, input_names=features, output_names=[target], opset_version=12 ) print(f"Model exported to {output_path}.")
Для конфертации обученной ранее модели необходимо передать ее в объявленную ранее модель и имена признаков и целевой переменной для корректного отображения в REPEAT.
Вызов функции export_to_onnx
export_to_onnx(model, features, target)
На следующем этапе сохранённая модель будет импортирована на платформу REPEAT в проект газопровода-шлейфа для выполнения междисциплинарного моделирования.
7. Интегрируем все в один проект
На рисунке ниже приведён проект на платформе REPEAT для моделирования газопровода-шлейфа с помощью блоков библиотек Пневматика, Автоматика и Внешние модели. Данная модель газопровода-шлейфа позволяет:
Выполнять расчёт параметров системы сбора на соответствие техническому проекту разработки месторождения;
Определять параметры системы сбора при изменении входных параметров системы подготовки газа и конденсата.

В рамках единого проекта на основании актуальных данных диагностики технического состояния газопровода-шлейфа также выполняется расчёт напряжений по Мизесу отвода с помощью блока №12 Аппроксимационная модель и импортированной в данный блок модели машинного обучения.
Проект, изображённый выше позволяет:
Выполнять расчётные эксперимены на модели машинного обучения,
В общей междисциплинарной модели выполнять разработку системы управления технологическим процессом добычи газа, учитывая оценку максимальных напряжений по Мизесу в отводе;
С помощью блоков связи с внешними устройствами, например по протоколу Modbus, позволяет создавать цифровые двойники газопроводов для этапа их эксплуатации, подключая к модели системы толщинометрии, системы измерения давлений, температур и расходов газа.
7.1. Результаты расчётных экспериментов
Динамика уменьшения толщины стенки отвода приведена на рисунке ниже.

Уменьшение толщины стенки отвода, а также изменения температуры окружающей среды при сохранении постоянного давления газа приводят к увеличению напряжений в отводе, аппроксимированные результаты которых приведены на следующем рисунке.

Заключение
Что же даёт интеграция ML‑моделей в REPEAT:
Открывает новые возможности: теперь можно закрывать больше задач в системном моделировании и создавать цифровые двойники даже для очень сложных систем без необходимости собирать всё с нуля.
Позволяет спокойно работать как с тяжёлыми МКО/МКЭ, так и моделями, опирающимися на реальные данные с объектов, например, на результаты диагностики оборудования в эксплуатации.
Если сравнивать с подключением системных моделей напрямую к стороннему инженерному софту, то в нашем случае заметно спокойнее: меньше шансов нарваться на ошибку из‑за того, что расчёт «не сошёлся» (знакомая боль для МКЭ/МКО), плюс сами расчёты идут ощутимо быстрее.
В общем, интеграция не просто добавляет функционал — она реально экономит время!