Регрессия при помощи многослойной нейросети¶
В этой работе нам нужно будет обучить нейросеть прогнозировать выход непрерывной функции от двух параметров и поместить ее на холст Engee в качестве еще одного блока. Например, в качестве суррогатной модели на замену некоторой сложной подсистемы.
Описание данных¶
Наши данные порождаются процессом вида $y = f(x_1, x_2)$:
Nx1, Nx2 = 30, 40
x1 = Float32.( range( -3, 3, length=Nx1 ) )
x2 = Float32.( range( -3, 3, length=Nx2 ) )
Xs = [ repeat( x1, outer=Nx2) repeat( x2, inner=Nx1) ];
Ys = @. 3*(1-Xs[:,1])^2*exp(-(Xs[:,1]^2) - (Xs[:,2]+1)^2) - 10*(Xs[:,1]/5 - Xs[:,1]^3 - Xs[:,2]^5)*exp(-Xs[:,1]^2-Xs[:,2]^2) - 1/3*exp(-(Xs[:,1]+1) ^ 2 - Xs[:,2]^2);
#Ys = @. 1*(1-Xs[:,1])^2*exp(-(Xs[:,1]^2) - (Xs[:,2]+1)^2) - 15*(Xs[:,1]/5 - Xs[:,1]^3 - Xs[:,2]^6)*exp(-Xs[:,1]^2-Xs[:,2]^2) - 1/3*exp(-(Xs[:,1]+1) ^ 2 - Xs[:,2]^2);
Xs
– матрица размером 1200x2
(в выборке 1200 примеров, каждый характеризуется двумя признаками: x1
и x2
.
Ys
– матрица размером 1200х1
(столбец прогнозов, если будете считывать
Процесс обучения¶
Зададимся параметрами для процедуры обучения и запустим достаточно простой вариант цикла.
# @markdown ## Настройка параметров нейросети
# @markdown *(двойной клик позволяет скрыть код)*
# @markdown
Параметры_по_умолчанию = false #@param {type: "boolean"}
if Параметры_по_умолчанию
Коэффициент_скорости_обучения = 0.01
Количество_циклов_обучения = 2000
else
Количество_циклов_обучения = 2001 # @param {type:"slider", min:1, max:2001, step:10}
Коэффициент_скорости_обучения = 0.08 # @param {type:"slider", min:0.001, max:0.5, step:0.001}
end
epochs = Количество_циклов_обучения;
learning_rate = Коэффициент_скорости_обучения;
Создадим нейросеть с двумя входами, двумя промежуточными состояниями (размером 20 и 5) и с одним выходом. Важно, чтобы выход нейросети имел линейную функцию активации.
using Flux
model = Chain( Dense( 2 => 20, relu ), # Структура модели, которую мы будем обучать
Dense( 20 => 5, relu ),
Dense( 5 => 1 ) )
data = [ (Xs', Ys') ] # Зададим структуру данных
loss( x, y ) = Flux.mse( model( x ), y ) # и функцию потерь, в которую они будут передаваться процедурой обучения
loss_history = [] # Будем сохранять историю обучения
opt = ADAM( learning_rate ); # Подготовим оптимизатор и запустим обучение
for i in 1:epochs
Flux.train!( loss, Flux.params(model), data, opt )
push!( loss_history, loss( Xs', Ys' ) ) # Запомним значение функции потерь
end
plot( loss_history, size=(300,200), label="loss" ) # Выведем хронику обучения
Переносим нейросеть на холст Engee¶
В переменной model
хранится только что обученная нейросеть.
Мы воссоздали ее структуру при помощи элементов на холсте:
☝️ Нашей модели требуется переменная
model
, откуда она получает параметры. Если такой переменной нет, модель попробует найти файлmodel.jld2
в домашнем каталоге. Если же этого файла не обнаружится, модель создаст пустую нейросеть со случайными параметрами
Полносвязанные слои собраны из самых обычных блоков Product
и Add
, осуществляющих перемножение входов на матрицу и добавление вектора смещений.
Функции активации, для большей ясности, не спрятаны внутрь слоев, а вынесены наружу.
Запустим эту модель на наборе из нескольких сотен случайных точек.
if "neural_regression_multilayer" ∉ getfield.(engee.get_all_models(), :name)
engee.load( "$(@__DIR__)/neural_regression_multilayer.engee");
end
model_data = engee.run( "neural_regression_multilayer" );
Модель возвращает нам матрицу из матриц. Можно исправить это при помощи блоков холсте, а можно реализовать свой вариант функции flatten
:
model_x1 = model_data["X1"].value;
model_x2 = model_data["X2"].value;
model_y = vec( hcat( model_data["Y"].value... ));
Выводим результаты¶
gr()
plot(
surface( Xs[:,1], Xs[:,2], vec(Ys), c=:viridis, cbar=:false, title="Обучающая выборка", titlefont=font(10)),
wireframe( x1, x2, vec(model( Xs' )), title="Прогноз от нейросети (через скрипт)", titlefont=font(10) ),
scatter( model_x1, model_x2, model_y, ms=2.5, msw=.5, leg=false, zcolor=model_y,
xlimits=(-3,3), ylimits=(-3,3), title="Прогноз от модели на холсте", titlefont=font(10) ),
layout=(1,3), size=(1000,400)
)
Сохраняем модель¶
Наконец, сохраним обученную нейросеть в файл, если этот файл отсутствует.
if !isfile( "model.jld2" )
using JLD2
jldsave("model.jld2"; model)
end
Заключение¶
Мы обучили многослойную нейросеть, состоящую из трех полносвязанных слоев. Затем перенесли все ее коэффициенты на холст для использования удобном модельно-ориентированном контексте (более удобном, чем работа с нейросетями через код).
Эту работу легко повторить с любыми табличными данными и масштабировать нейросеть под нужды проекта. Сохранение обученной сети в файл позволяет выполнять модель вместе с другими элементами диаграммы Engee, не обучая нейросеть заново.
При желании, веса и смещения можно записать в блоки Constant
на холсте и получить блок с нейросетью, независимый от наличия файла model.jld2
.