Введение
В данной статье будет рассматриваться задача многомерной интерполяции с помощью нейронной сети. Нейронная сеть построена с помощью среды математического моделирования Engee. Данная задача имеет серьезное практическое применение так как нарушение целостности трубопровода связано с большими экономическими последствиями или даже может привести к техногенной катастрофе (в случае если трубопровод промышленный). Основная проблема обнаружения места утечки что нельзя напрямую зафиксировать факт утечки и определить её можно только алгоритмически измеряя косвенные параметры (давление, скорость потока жидкости или газа в трубопроводе, температура). Для повышения точности измерений данные параметры измеряются на всём протяжении трубопровода. Наиболее простой и распространенный способ. Но у данного способа есть существенный недостаток, он позволяет определить только участок трубопровода и для более точного определения места утечки нужен другой (более точный) алгоритм поиска.
В качестве наиболее важных параметров, описывающих процесс транспортировки нефтепродукта в трубопроводе, были выбраны следующие: избыточное давление в трубопроводе и расход нефтепродукта. Во-первых, эти параметры
наиболее существенно изменяются при возникновении утечки. Во-вторых, они
легко поддаются измерению датчиками и не требуется больших затрат на установку и эксплуатацию датчиков давления и расхода.
Используемые библиотеки
using Flux using Statistics using Random using Plots
Исходные данные для обучения нейронной сети (расстояние, избыточное давление, расход)
data = [ 160 79 0.0355; 160 60 0.045; 160 53 0.05; 160 44 0.057; 160 40 0.0625; 120 79 0.035; 120 60 0.0385; 120 53 0.043; 120 44 0.0495; 120 40 0.0535; 100 79 0.029; 100 60 0.036; 100 53 0.04; 100 44 0.0465; 100 40 0.05; 80 79 0.0265; 80 60 0.0278; 80 53 0.0284; 80 44 0.03; 80 40 0.0315; 60 44 0.024; 60 40 0.0248; 40 40 0.0195; ]
Подготовка данных
Перед обучением нейронной сети необходимо провести подготовку исходных данных (разделение на вектор-столбцы, нормализация). Для более наглядного представления выходы и входы сети представлены как отдельные переменные. Нормализация требуется для повышения качества работы нейронной сети и приведения входных данных разного типа к одному масштабу а также для предотвращения насыщения для функций активации.
# Разделяем таблицу на входы (X) и выходы (Y) X_raw = Float32.(data[:, 1:2]') # Транспонируем до размера 2 x N Y_raw = Float32.(reshape(data[:, 3], 1, :)) # Размер 1 x N # 2. Нормализация данных (критически важно!) # Нейросети плохо работают с данными разного масштаба (сотни у X и сотые доли у Y). stats_x = (mean(X_raw, dims=2), std(X_raw, dims=2) .+ eps(Float32)) stats_y = (mean(Y_raw), std(Y_raw) .+ eps(Float32)) function normalize(arr, stats) return @. (arr - stats[1]) / stats[2] end X_norm = normalize(X_raw, stats_x) Y_norm = normalize(Y_raw, stats_y)
Сравнение различных моделей
Для поиска наилучшего варианта интерполяции будут рассмотрены разные комбинации числа слоёв сети и количества узлов в них, функция активации во всех случаях выбрана гиперболический тангенс (tanh) так как другие функции активации для данных входных параметров дают результат интерполяции на порядок хуже.
Модель 1 (4 слоя, максимальное число узлов 32)
model = Chain( Dense(2 => 32, tanh), Dense(32 => 32, tanh), Dense(32 => 32, tanh), Dense(32 => 1) )
Модель 2 (8 слоёв, максимальное число узлов 128)
model2 = Chain( Dense(2 => 128, tanh), Dense(128 => 128, tanh), Dense(128 => 128, tanh), Dense(128 => 128, tanh), Dense(128 => 64, tanh), Dense(64 => 32, tanh), Dense(32 => 16, tanh), Dense(16 => 1) )
Модель 3 (12 слоёв, максимальное число узлов 128)
model3 = Chain( Dense(2 => 32, tanh), Dense(32 => 32, tanh), Dense(32 => 64, tanh), Dense(64 => 128, tanh), Dense(128 => 64, tanh), Dense(64 => 32, tanh), Dense(32 => 32, tanh), Dense(32 => 32, tanh), Dense(32 => 32, tanh), Dense(32 => 32, tanh), Dense(32 => 16, tanh), Dense(16 => 1) )
Для каждой из моделей создана функция для расчета погрешностей при работе нейронной сети.
loss(x, y) = Flux.Losses.mse(model(x), y) opt = Flux.setup(Flux.Adam(0.00005), model) loss2(x, y) = Flux.Losses.mse(model2(x), y) opt2 = Flux.setup(Flux.Adam(0.00005), model2) loss3(x, y) = Flux.Losses.mse(model3(x), y) opt3 = Flux.setup(Flux.Adam(0.00005), model3)
Функция для обучения моделей
function train!(epochs,model) losses = Float32[] best_model_state = nothing best_loss = Inf println("Начало обучения...") for epoch in 1:epochs if model == model2 grads2 = Flux.gradient(m -> loss2(X_norm, Y_norm), model2)[1] Flux.Optimise.update!(opt2, model2, grads2) current_loss = loss2(X_norm, Y_norm) elseif model == model3 grads3 = Flux.gradient(m -> loss3(X_norm, Y_norm), model3)[1] Flux.Optimise.update!(opt3, model3, grads3) current_loss = loss3(X_norm, Y_norm) else grads = Flux.gradient(m -> loss(X_norm, Y_norm), model)[1] Flux.Optimise.update!(opt, model, grads) current_loss = loss(X_norm, Y_norm) end if current_loss < best_loss best_loss = current_loss best_model_state = deepcopy(Flux.state(model)) end push!(losses, current_loss) if epoch % 1000 == 0 || epoch == 1 println("Эпоха $epoch | Потери: $(round(current_loss, sigdigits=10))") end end return best_loss end
Обучение моделей
Вызов функции для обучения моделей (вызывается для каждой модели)
train!(10000,model) # обучение модели 1 train!(10000,model2) # обучение модели 2 train!(10000,model3) # обучение модели 3
Отображение результата обработки
Для отображения результата работы обученной нейронной сети необходимо провести денормализацию данных и восстановление исходного масштаба.
# 6. Оценка качества на исходных данных (денормализация) Y_pred_norm = model(X_norm) Y_pred = Y_pred_norm .* stats_y[2] .+ stats_y[1] mae = mean(abs.(Y_pred' .- data[:, 3])) # Средняя абсолютная ошибка в исходных единицах println("\nОбучение завершено.") println("Оценка точности на узлах таблицы (MAE): $(round(mae, digits=6))") xg = range(minimum(data[:, 1]), maximum(data[:, 1]), length=50) yg = range(minimum(data[:, 2]), maximum(data[:, 2]), length=50) z_grid = [model(normalize([x, y], stats_x))[1] for y in yg, x in xg] z_grid_denorm = z_grid .* stats_y[2] .+ stats_y[1] #plotlyjs() surface(xg, yg, z_grid_denorm; title="Определение места утечки", xlabel="Давление", zlabel="Расход", ylabel="Расстояние до места утечки", legend=false, alpha=0.8) scatter3d!(data[:, 1], data[:, 2], data[:, 3]; color=:red, markersize=4, label="Исходные узлы")

Денормализация результата и отображение на графике (для второй модели)
# 6. Оценка качества на исходных данных (денормализация) Y_pred_norm = model2(X_norm) Y_pred = Y_pred_norm .* stats_y[2] .+ stats_y[1] mae = mean(abs.(Y_pred' .- data[:, 3])) # Средняя абсолютная ошибка в исходных единицах println("\nОбучение завершено.") println("Оценка точности на узлах таблицы (MAE): $(round(mae, digits=6))") xg = range(minimum(data[:, 1]), maximum(data[:, 1]), length=50) yg = range(minimum(data[:, 2]), maximum(data[:, 2]), length=50) z_grid = [model2(normalize([x, y], stats_x))[1] for y in yg, x in xg] z_grid_denorm = z_grid .* stats_y[2] .+ stats_y[1] #plotlyjs() surface(xg, yg, z_grid_denorm; title="Определение места утечки", xlabel="Давление", ylabel="Расход", zlabel="Расстояние до места утечки", legend=false, alpha=0.8) scatter3d!(data[:, 1], data[:, 2], data[:, 3]; color=:red, markersize=4, label="Исходные узлы")

Денормализация результата и отображение на графике (для третьей модели)
# 6. Оценка качества на исходных данных (денормализация) Y_pred_norm = model3(X_norm) Y_pred = Y_pred_norm .* stats_y[2] .+ stats_y[1] mae = mean(abs.(Y_pred' .- data[:, 3])) # Средняя абсолютная ошибка в исходных единицах println("\nОбучение завершено.") println("Оценка точности на узлах таблицы (MAE): $(round(mae, digits=6))") xg = range(minimum(data[:, 1]), maximum(data[:, 1]), length=50) yg = range(minimum(data[:, 2]), maximum(data[:, 2]), length=50) z_grid = [model3(normalize([x, y], stats_x))[1] for y in yg, x in xg] z_grid_denorm = z_grid .* stats_y[2] .+ stats_y[1] #plotlyjs() surface(xg, yg, z_grid_denorm; title="Определение места утечки", xlabel="Давление", zlabel="Расход", ylabel="Расстояние до места утечки", legend=false, alpha=0.8) scatter3d!(data[:, 1], data[:, 2], data[:, 3]; color=:red, markersize=4, label="Исходные узлы")

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