Engee 文档
Notebook

使用多层神经网络进行回归

在这项工作中,我们需要训练一个神经网络,通过两个参数来预测一个连续函数的输出,并将其作为另一个块放置在Engee画布上。例如,作为替代模型来取代某些复杂的子系统。

数据描述

我们的数据由$y = f(x_1, x_2)$ 形式的流程生成:

In [ ]:
Pkg.add(["JLD2", "Flux"])
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 个示例,每个示例有两个特征:x1x2)

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( , y ) = Flux.mse( , y )             # и функцию потерь, в которую они будут передаваться процедурой обучения
loss_history = []                           # Будем сохранять историю обучения
opt_state = Flux.setup( Adam( learning_rate ), model ); # Алгоритм оптимизации
for i in 1:epochs
    Flux.train!( model, data, opt_state) do m, x, y
        loss( m(x), y ) # Функция потерь - ошибка на каждом элементе датасета
    end
    push!( loss_history, loss( model(Xs'), Ys' ) ) # Запомним значение функции потерь
end
plot( loss_history, size=(300,200), label="loss" ) # Выведем хронику обучения
Out[0]:

将神经网络转入 Engee 画布

model 变量存储了新训练的神经网络。

我们使用画布上的元素重新创建了它的结构:

image.png

☝️ 我们的模型需要变量model ,从这里获取参数。如果该变量不存在,模型将尝试在主目录中查找文件model.jld2 。如果找不到该文件,模型将创建一个空的神经网络,并随机设置参数

全连接层由最常见的块ProductAdd 构建,输入乘以矩阵并添加偏移向量。

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

结论

我们训练了一个由三个全连接层组成的多层神经网络。然后,我们将其所有系数转移到画布上,以便在方便的面向模型的环境中使用(比通过代码处理神经网络更方便)

我们很容易用任何表格数据重复这项工作,并根据项目需要调整神经网络的规模。将训练好的网络保存到文件中,就可以与恩吉图的其他元素一起运行模型,而无需重新训练神经网络。

如果需要,可以将权重和偏移写入画布上的Constant 块,以生成独立于model.jld2 文件的神经网络块。

示例中使用的块