Engee 文档
Notebook

利用神经网络进行回归(最小示例)

在本例中,我们将讨论在回归任务中训练全连接(full-connected,FC)神经网络所需的最少操作数。

任务描述

我们将训练最经典的神经网络来 "预测 "某个一维函数的值。我们的目标是建立一个最简单的算法,随后再将其复杂化(而不是相反)

In [ ]:
Pkg.add(["Flux"])
In [ ]:
# @markdown ## Настройка параметров нейросети
# @markdown *(двойной клик позволяет скрыть код)*
# @markdown
Параметры_по_умолчанию = false #@param {type: "boolean"}
if Параметры_по_умолчанию
    Коэффициент_скорость_обучения = 0.01
    Количество_циклов_обучения = 100
else
    Количество_циклов_обучения = 80 # @param {type:"slider", min:1, max:150, step:1}
    Коэффициент_скорость_обучения = 0.1 # @param {type:"slider", min:0.001, max:0.5, step:0.001}
end

epochs = Количество_циклов_обучения;
learning_rate = Коэффициент_скорость_обучения;
In [ ]:
using Flux

Xs = Float32.( 0:0.1:10 );                    # Генерация данных для обучения
Ys = Float32.( Xs .+ 2 .* rand(length(Xs)) ); # <- ожидаемые от нейросети выходные данные
data = [(Xs', Ys')];                          # В таком формате данные передаются в функцию loss
model = Dense( 1 => 1 )                       # Архитектура нейросети: один FC-слой
opt_state = Flux.setup( Adam( learning_rate ), model ); # Алгоритм оптимизации
for i in 1:epochs
    Flux.train!( model, data, opt_state) do m, x, y
        Flux.mse( m(x), y ) # Функция потерь - ошибка на каждом элементе датасета
    end
end
X_прогноз = [ [x] for x in Xs ]               # Нейросеть принимает векторы, даже если у нас функция от одного аргумента
Y_прогноз = model.( X_прогноз )               # Для каждого [x] нейросеть вычисляет нам [y]

gr()                                          # Мы получили "вектор из векторов", который преобразуем для вывода на график
plot( Xs, Ys, label="Исходная выборка", legend=:topleft, lw=2 )
plot!( Xs, vec(hcat(Y_прогноз...)), label="Прогноз", lw=2 )
Out[0]:

使用 <path d="M8 11.5V5L17 11.5L8 18V11.5Z" fill="url(#paint0_linear_44954_74187)" 更改学习过程的参数并重新启动单元格。stroke="url(#paint1_linear_44954_74187)"stroke-width="4.16987" stroke-linejoin="round"><stop/linearGradient> 以评估更改设置对预报质量的影响。

在画布上创建神经网络块

我们的神经网络结构非常简单,因此很容易将其 "画布化",并用于自己的图块库中。

该模型的 "回调 "包含训练神经网络的所有代码,因此当首次打开文件neural_regression_simple.engee 时,如果变量model 不存在,神经网络将重新训练。

模型可以很容易地从工作区中的块中组装起来。它可以从变量工作区获取参数,也可以将参数作为固定矩阵和向量输入这些块的属性中。

image.png

让我们运行这个模型,比较一下结果:

In [ ]:
if "neural_regression_simple"  getfield.(engee.get_all_models(), :name)
  engee.load( "$(@__DIR__)/neural_regression_simple.engee");
end

data = engee.run( "neural_regression_simple" );

# Поскольку в модели все операции у нас матричные, нам снова приходится "разглаживать" переменную Y
plot!( data["Y"].time, vec(hcat(data["Y"].value...)), label="Блок regression_net", lw=2 )
Out[0]:

如果图表的结构与神经网络的结构相同,那么运行 "from code "和 "from canvas "的结果也将相同。

通常,神经网络结构的变化频率低于数据集和问题表述的变化频率。因此,可以对结构进行两次建模:先用代码建模,然后在画布上用图形块建模。

代码说明

让我们回顾一下我们的简短代码,并就有趣的地方发表评论。

我们使用了Float32 而不是Float64 ,后者是 Julia 的默认设置(没有它一切都能正常工作,但Flux 库会发出一次性警告)。

Xs = Float32.( 0:0.0.1:10 );
Ys = Float32.(Xs .+ 2 .* rand(length(Xs)));

Float32 的精度对于神经网络来说绰绰有余,由于比特网格较粗,它们的预测误差通常会超过舍入误差。此外,在 GPU 上执行这类数据会更快。

数据将通过迭代器输入损失函数。在数据集中,应该有一列数据的元组(Tuple )--一列输入,一列输出。还有其他几种输入数据的方法,现在我们主要讨论下面的方法。

data = [(Xs', Ys')];

神经网络由单个元素组成,是输入和权重的线性组合,并添加了偏置(无激活函数,或同样有线性激活函数)。我们甚至没有在对象Dense 的周围使用Chain() 这一结构,它通常用于创建多层神经网络(尽管这两种方式的网络工作原理相同)

model = Dense( 1 => 1 )

让我们设置 Adam(自适应矩估计)优化算法,它是神经网络训练中最有效的优化算法之一。我们传递给它的唯一参数就是学习率系数。

opt_state = Flux.setup( Adam( learning_rate ), model_cpu )

** 现在是训练模型的时候了。** 我们对样本进行一定次数的重复遍历,计算损失函数,并调整所有神经网络变量,以减少误差梯度。

损失函数(loss function)是模型在训练过程中唯一**明确执行的地方。它通常通过每个数据项的误差之和(cost 函数之和)来表示。在这里,我们只需使用 Flux 库中的标准均方误差(MSE)函数。

for i in 1:epochs
    Flux.train!(model, data, opt_state) do m, x, y
        Flux.mse( m(x), y )
    结束
结束

剩下的就是使用训练好的模型了。我们将 * 输入数据* 作为函数传递给它,然后得到 * 输出预测结果*。

Y_forecast = model.( X_forecast )
X_forecast = [ [x] for x in Xs ] ` Y_forecast = model.

结论

我们需要 10 行代码来生成数据和训练神经网络,还需要 5 行代码在图表上显示预测结果。只需稍作改动,你就可以让神经网络多层化,或根据 XLSX 表格中的数据对其进行训练。

我们发现,一旦训练完成,就可以很容易地将神经网络转移到画布上,并将其用作系统图中的另一个块,如果系统图足够简化,甚至可以从中生成 C 代码。通过这种方式,我们可以提供端到端的系统更新流程--从数据采样到控制器。

示例中使用的块