Документация Engee
Notebook

Регрессия при помощи многослойной нейросети

В этой работе нам нужно будет обучить нейросеть прогнозировать выход непрерывной функции от двух параметров и поместить ее на холст Engee в качестве еще одного блока. Например, в качестве суррогатной модели на замену некоторой сложной подсистемы.

Описание данных

Наши данные порождаются процессом вида $y = f(x_1, x_2)$:

In [ ]:
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 (столбец прогнозов, если будете считывать

Процесс обучения

Зададимся параметрами для процедуры обучения и запустим достаточно простой вариант цикла.

In [ ]:
# @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) и с одним выходом. Важно, чтобы выход нейросети имел линейную функцию активации.

In [ ]:
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" ) # Выведем хронику обучения
Out[0]:

Переносим нейросеть на холст Engee

В переменной model хранится только что обученная нейросеть.

Мы воссоздали ее структуру при помощи элементов на холсте:

image.png

☝️ Нашей модели требуется переменная model, откуда она получает параметры. Если такой переменной нет, модель попробует найти файл model.jld2 в домашнем каталоге. Если же этого файла не обнаружится, модель создаст пустую нейросеть со случайными параметрами

Полносвязанные слои собраны из самых обычных блоков Product и Add, осуществляющих перемножение входов на матрицу и добавление вектора смещений.

image.png

Функции активации, для большей ясности, не спрятаны внутрь слоев, а вынесены наружу.

image.png

Запустим эту модель на наборе из нескольких сотен случайных точек.

In [ ]:
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:

In [ ]:
model_x1 = model_data["X1"].value;
model_x2 = model_data["X2"].value;
model_y = vec( hcat( model_data["Y"].value... ));

Выводим результаты

In [ ]:
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)
)
Out[0]:

Сохраняем модель

Наконец, сохраним обученную нейросеть в файл, если этот файл отсутствует.

In [ ]:
if !isfile( "model.jld2" )
    using JLD2
    jldsave("model.jld2"; model)
end

Заключение

Мы обучили многослойную нейросеть, состоящую из трех полносвязанных слоев. Затем перенесли все ее коэффициенты на холст для использования удобном модельно-ориентированном контексте (более удобном, чем работа с нейросетями через код).

Эту работу легко повторить с любыми табличными данными и масштабировать нейросеть под нужды проекта. Сохранение обученной сети в файл позволяет выполнять модель вместе с другими элементами диаграммы Engee, не обучая нейросеть заново.

При желании, веса и смещения можно записать в блоки Constant на холсте и получить блок с нейросетью, независимый от наличия файла model.jld2.

Блоки, использованные в примере