利用神经网络进行回归(最小示例)¶
在本例中,我们将讨论在回归任务中训练全连接(full-connected,FC)神经网络所需的最少操作数。
任务描述¶
我们将训练最经典的神经网络来 "预测 "某个一维函数的值。我们的目标是建立一个最简单的算法,随后再将其复杂化(而不是相反)。
Pkg.add(["Flux"])
# @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 = Коэффициент_скорость_обучения;
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 )
使用 以评估更改设置对预报质量的影响。
在画布上创建神经网络块¶
我们的神经网络结构非常简单,因此很容易将其 "画布化",并用于自己的图块库中。
该模型的 "回调 "包含训练神经网络的所有代码,因此当首次打开文件
neural_regression_simple.engee
时,如果变量model
不存在,神经网络将重新训练。
模型可以很容易地从工作区中的块中组装起来。它可以从变量工作区获取参数,也可以将参数作为固定矩阵和向量输入这些块的属性中。
让我们运行这个模型,比较一下结果:
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 )
如果图表的结构与神经网络的结构相同,那么运行 "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 代码。通过这种方式,我们可以提供端到端的系统更新流程--从数据采样到控制器。