Configuring the neural network learning process
In this example, we will create an environment for training a neural network, controlled using a convenient graphical form.
Task description
The development of neural networks has long ceased to be the domain of researchers with many years of experience in writing code. Today it is a tool for an engineer, analyst, and product scientist. However, the entry threshold is often still high: even for a simple fully connected network with three layers, you need to remember the syntax of libraries like Flux.jl and correctly load the dataset from CSV and output the correct graphs... This is where graphical interfaces (GUIs) come to the rescue. They allow you to focus on the essence of the task — working on data quality and interpretation of the result, rather than on trivial mistakes that everyone makes at the first stage.
Let's create an interface for training a neural network, which will still hide the same mathematics and data operations that load, distribute, train, and check the quality of the neural network.
Preparatory operations include the installation of a library for training neural networks:
# It may take a minute to compile the Flux library.
Pkg.add(["Flux", "Interpolations"])
using Flux, CSV, DataFrames, Random
using Flux: mse
using Statistics, Interpolations
gr()
Now we will develop a GUI that allows you to move the slider and train a neural network with the required number of neurons, which will load data and train the neural network.
> Assumptions: the script folder contains the train.csv and test.csv files. The first column of each file contains the predicted variable, the remaining columns contain the attributes. We are solving a regression problem.
# Let's move to the directory with this script.
cd(@__DIR__)
train_filename = "train.csv" # @param {type:"string"}
test_filename = "test.csv" # @param {type:"string"}
train_df = CSV.read(train_filename, DataFrame, types=Float32)
test_df = CSV.read(test_filename, DataFrame, types=Float32)
# We assume that the first column contains the predicted values, and the rest contain the signs
X_train = Matrix(train_df[:, 2:end])' # (n_features, n_samples)
X_test = Matrix(test_df[:, 2:end])' # (n_features, n_samples)
y_train_raw = reshape(train_df[:, 1], 1, :) # (1, n_samples)
y_test_raw = reshape(test_df[:, 1], 1, :) # (1, n_samples)
# Normalization of signs
X_train_mean = mean(X_train, dims=2)
X_train_std = std(X_train, dims=2)
# Normalize train and test with train selection parameters
X_train_norm = (X_train .- X_train_mean) ./ X_train_std
X_test_norm = (X_test .- X_train_mean) ./ X_train_std
# Normalize the target values
y_train_mean = mean(y_train_raw)
y_train_std = std(y_train_raw)
y_train_norm = (y_train_raw .- y_train_mean) ./ y_train_std
y_test_norm = (y_test_raw .- y_train_mean) ./ y_train_std
features_count = size(X_train_norm, 1)
l1_neurons = 30 # @param {type:"slider",min:1,max:30,step:1}
l2_neurons = 28 # @param {type:"slider",min:1,max:30,step:1}
l3_neurons = 25 # @param {type:"slider",min:1,max:30,step:1}
l4_neurons = 13 # @param {type:"slider",min:1,max:30,step:1}
l5_neurons = 1 # @param {type:"slider",min:1,max:30,step:1}
n_epochs = 499 # @param {type:"slider",min:1,max:500,step:1}
# Architecture for regression
model = Chain(
Dense(features_count => l1_neurons, relu),
Dense(l1_neurons => l2_neurons, relu),
Dense(l2_neurons => l3_neurons, relu),
Dense(l3_neurons => l4_neurons, relu),
Dense(l4_neurons => l5_neurons, relu),
Dense(l5_neurons => 1)
)
# Training
loss(m, x, y) = mse(m(x), y)
opt_state = Flux.setup(Adam(0.001), model)
data = [(X_train_norm, y_train_norm)]
train_losses = []
test_losses = []
# Starting the training
for epoch in 1:n_epochs
Flux.train!(loss, model, data, opt_state)
push!(train_losses, loss(model, X_train_norm, y_train_norm))
push!(test_losses, loss(model, X_test_norm, y_test_norm))
end
# Functions for converting between scales
denormalize_y(y_norm) = y_norm .* y_train_std .+ y_train_mean
denormalize_X(X_norm, dim) = X_norm .* X_train_std[dim] .+ X_train_mean[dim]
test_loss = loss(model, X_test_norm, y_test_norm)
println("\nMSE на тесте (в нормализованном масштабе): $(round(test_loss, digits=6))")
println("RMSE на тесте (в исходных единицах): $(round(sqrt(test_loss) * y_train_std, digits=2))")
# An example of a prediction for the first object from the test
prediction_norm = model(X_test_norm[:, 1:1])
actual_norm = y_test_norm[:, 1:1]
prediction = denormalize_y(prediction_norm)
actual = denormalize_y(actual_norm)
println("\nПример (нормализованный): предсказано $(round.(prediction_norm, digits=3)), реально $(round.(actual_norm, digits=3))")
println("Пример (исходный масштаб): предсказано $(round.(prediction, digits=2)), реально $(round.(actual, digits=2))")
# Graph 1: Learning Curves
p1 = plot(1:n_epochs, train_losses, label="Train Loss", lw=2, marker=:circle, markersize=2)
plot!(p1, 1:n_epochs, test_losses, label="Test Loss", lw=2, marker=:square, markersize=2)
title!(p1, "Learning Dynamics (normalized MSE)")
xlabel!(p1, "Era")
ylabel!(p1, "MSE")
# Graph 2: 3D surface
if features_count >= 2
n_points = 30
# Ranges in a normalized space
x1_range_norm = range(extrema(X_train_norm[1, :])..., length=n_points)
x2_range_norm = range(extrema(X_train_norm[2, :])..., length=n_points)
# Creating a grid in a normalized space
grid_x1_norm = repeat(x1_range_norm', n_points, 1)
grid_x2_norm = repeat(x2_range_norm, 1, n_points)
# For the remaining signs, we take the average values (in a normalized space)
grid_other_norm = zeros(features_count-2, n_points, n_points)
for i in 3:features_count
grid_other_norm[i-2, :, :] .= mean(X_train_norm[i, :])
end
# Creating a complete set of features for the grid (in a normalized space)
grid_points_norm = vcat(
reshape(grid_x1_norm, 1, n_points, n_points),
reshape(grid_x2_norm, 1, n_points, n_points),
grid_other_norm
)
grid_points_flat_norm = reshape(grid_points_norm, features_count, n_points * n_points)
# Predictions of the model on a grid (on a normalized scale)
predictions_norm = model(grid_points_flat_norm)
predictions_2d_norm = reshape(predictions_norm, n_points, n_points)
# Converting the predictions to the original scale for visualization
predictions_2d_original = denormalize_y(predictions_2d_norm)
# Converting the grid coordinates to the original scale for visualization
x1_range_original = denormalize_X(x1_range_norm, 1)
x2_range_original = denormalize_X(x2_range_norm, 2)
# Initial data points (train) in the original scale
X1_train_original = denormalize_X(X_train_norm[1, :], 1)
X2_train_original = denormalize_X(X_train_norm[2, :], 2)
y_train_original = vec(denormalize_y(y_train_norm))
# Test data points at the original scale
X1_test_original = denormalize_X(X_test_norm[1, :], 1)
X2_test_original = denormalize_X(X_test_norm[2, :], 2)
y_test_original = vec(denormalize_y(y_test_norm))
# Prediction surface at the initial scale
p2 = surface(x1_range_original, x2_range_original, predictions_2d_original,
alpha=0.6, label="Neural network forecast",
camera=(30, 30), color=:reds)
# Adding training points (blue)
scatter!(p2, X1_train_original, X2_train_original, y_train_original,
label="Train data",
color=:blue,
markersize=4,
alpha=0.7,
markeralpha=0.6)
# Adding test points (green)
scatter!(p2, X1_test_original, X2_test_original, y_test_original,
label="Test data",
color=:green,
markersize=4,
alpha=0.7,
markeralpha=0.6,
marker=:square)
title!(p2, "Forecast (Red) vs Train (Blue) vs Test (Green)")
xlabel!(p2, "Sign 1")
ylabel!(p2, "Sign 2")
zlabel!(p2, "Target variable")
# # We output statistics for verification
# println("\n Ranges for visualization:")
# println("x1: [$(round(minimum(x1_range_original), digits=2)), $(round(maximum(x1_range_original), digits=2))]")
# println("x2: [$(round(minimum(x2_range_original), digits=2)), $(round(maximum(x2_range_original), digits=2))]")
# println("y(predictions): [$(round(minimum(predictions_2d_original), digits=2)), $(round(maximum(predictions_2d_original), digits=2))]")
# println("y (train): [$(round(minimum(y_train_original), digits=2)), $(round(maximum(y_train_original), digits=2))]")
# println("y (test): [$(round(minimum(y_test_original), digits=2)), $(round(maximum(y_test_original), digits=2))]")
plot(p1, p2, layout=(1, 2), titlefont=font(9), guidesfont=font(7), size=(1100,500))
else
plot(p1, titlefont=font(9), guidesfont=font(7), size=(800,300))
end
> Hide the code by double-clicking on the form to enter the parameters.
>
> Check that the cell auto execution enabled (button <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" class="autoplay-icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M14.7874 6.88404C14.7826 6.89463 14.7777 6.90522 14.7732 6.916C14.7686 6.92717 14.765 6.93882 14.7614 6.95044C14.7556 6.96934 14.7498 6.98815 14.7399 7.00467C14.7202 7.03735 14.6944 7.06478 14.6685 7.09231C14.6612 7.10016 14.6538 7.10801 14.6465 7.116C14.6385 7.12435 14.6313 7.1335 14.6242 7.14265C14.6142 7.15542 14.6043 7.16818 14.5919 7.17867C14.5545 7.20916 14.5114 7.23036 14.4684 7.25148C14.4653 7.25299 14.4623 7.25449 14.4592 7.256C14.4501 7.26041 14.4415 7.26595 14.4328 7.27151C14.4206 7.2794 14.4082 7.28731 14.3945 7.292C14.3225 7.31867 14.2459 7.33333 14.1665 7.33333C14.1305 7.33333 14.0932 7.33067 14.0565 7.324L11.2232 6.854C10.8599 6.794 10.6145 6.45067 10.6745 6.08733C10.7345 5.72467 11.0772 5.476 11.4412 5.53933L12.5372 5.72067C11.7072 4.26333 10.1225 3.33333 8.37854 3.33333C6.40587 3.33333 4.66787 4.47867 3.94987 6.25C3.84521 6.51 3.59521 6.66667 3.33187 6.66667C3.24854 6.66667 3.16387 6.65133 3.08187 6.618C2.74054 6.48 2.57587 6.09133 2.71454 5.75C3.63721 3.472 5.86054 2 8.37854 2C10.6532 2 12.7205 3.24667 13.7639 5.192L14.0099 3.87667C14.0779 3.51533 14.4272 3.276 14.7879 3.34533C15.1499 3.41267 15.3885 3.76133 15.3205 4.12333L14.8212 6.79C14.8148 6.82401 14.8011 6.854 14.7874 6.88404ZM12.7142 9.74877C12.8522 9.40667 13.2415 9.24263 13.5822 9.38066C13.9235 9.5187 14.0882 9.90748 13.9495 10.2489C13.0269 12.5276 10.8035 14 8.28557 14C6.01092 14 3.94361 12.753 2.90028 10.8071L2.65362 12.1221C2.59428 12.4429 2.31429 12.6663 1.99896 12.6663C1.95829 12.6663 1.91696 12.6623 1.87562 12.6543C1.51363 12.5869 1.27563 12.2382 1.34363 11.8761L1.84296 9.20862C1.84802 9.18242 1.85925 9.15918 1.87052 9.13582C1.87704 9.12231 1.88358 9.10876 1.88896 9.09458C1.89423 9.0813 1.89883 9.06754 1.90344 9.05377C1.91341 9.02398 1.92342 8.99408 1.94029 8.96855C1.95013 8.95337 1.96299 8.94123 1.97594 8.929C1.98404 8.92135 1.99218 8.91367 1.99962 8.90519C2.00549 8.89839 2.0113 8.89155 2.0171 8.88471C2.04883 8.8473 2.08027 8.81022 2.12029 8.78316C2.12422 8.7803 2.12891 8.77859 2.13356 8.7769C2.13757 8.77544 2.14155 8.77399 2.14495 8.77182C2.20962 8.72981 2.27895 8.69713 2.35562 8.68046C2.36029 8.67938 2.36515 8.67966 2.37 8.67994C2.37415 8.68017 2.37829 8.68041 2.38229 8.6798C2.45562 8.66779 2.52962 8.66046 2.60762 8.67379L5.44093 9.1446C5.80426 9.20395 6.04959 9.54805 5.98959 9.91149C5.93559 10.2376 5.65293 10.469 5.33226 10.469C5.29626 10.469 5.25959 10.4663 5.22293 10.4596L4.12694 10.2783C4.95693 11.736 6.54158 12.6663 8.28557 12.6663C10.2582 12.6663 11.9962 11.5206 12.7142 9.74877Z" fill="#989DA0"></path></svg> should be green).
>
> And post the output to the right of the cell, if it is on the bottom. Your toolkit is ready!
Conclusion
The code for this task is concise and solves a specific problem. But since we packed this functionality into a graphical interface, you just had to select the number of neurons in each layer and see how the quality of the forecast changes.
This is the fastest way to train the first 5-10 neural networks for unknown data and see if this type of model is suitable for your tasks.
