Обзор Flux: Основные действия
Flux представляет собой чистый стек машинного обучения Julia, позволяющий строить модели прогнозирования. Ниже приведены шаги типичной программы Flux.
-
Предоставление обучающих и тестовых данных
-
Построение модели с настраиваемыми параметрами для составления прогнозов
-
Итеративное обучение модели с изменением параметров для улучшения прогнозов
-
Проверка модели
На внутреннем уровне Flux использует технику автоматического дифференцирования для получения градиентов, которые помогают улучшить прогнозы. Flux также полностью написан на Julia, поэтому вы можете легко заменить любой слой Flux своим собственным кодом, чтобы улучшить понимание или выполнить особые требования.
Далее описывается пошаговое использование Flux для создания и обучения самых простых моделей.
Простейшее прогнозирование
В этом примере будет спрогнозирован вывод функции 4x + 2
. Создание таких прогнозов называется «линейной регрессией», и этот процесс действительно слишком прост, чтобы требовалось использовать нейронную сеть. Но это хороший пример для развлечения.
Сначала импортируйте Flux
и определите функцию, которую нужно смоделировать:
julia> using Flux
julia> actual(x) = 4x + 2
actual (generic function with 1 method)
В этом примере будет построена модель для аппроксимации функции actual
.
1. Предоставление обучающих и тестовых данных
Используйте функцию actual
для создания наборов данных для обучения и проверки:
julia> x_train, x_test = hcat(0:5...), hcat(6:10...)
([0 1 … 4 5], [6 7 … 9 10])
julia> y_train, y_test = actual.(x_train), actual.(x_test)
([2 6 … 18 22], [26 30 … 38 42])
Обычно обучающие и тестовые данные берутся из реальных наблюдений, но здесь мы их смоделируем.
2. Построение модели для составления прогнозов
Теперь постройте модель, которая будет делать прогнозы, с 1
входом и 1
выводом:
julia> model = Dense(1 => 1)
Dense(1 => 1) # 2 параметра
julia> model.weight
1×1 Matrix{Float32}:
0.95041317
julia> model.bias
1-element Vector{Float32}:
0.0
На внутреннем уровне плотный слой представляет собой структуру с полями weight
и bias
. weight
представляет собой матрицу весов, а bias
— вектор отклонения. Есть и другой способ представления модели. Во Flux модели являются концептуально прогнозирующими функциями:
julia> predict = Dense(1 => 1)
Dense(1 => 1) # 2 параметра
Dense(1 => 1)
также реализует функцию σ(Wx+b)
, где W
и b
— это веса и отклонения. σ
является функцией активации (подробнее об активации мы поговорим позже). У нашей модели один вес и одно отклонение, но у типичных моделей их гораздо больше. Представим веса и отклонения как ручки и рычаги, которые Flux может использовать для настройки прогнозов. Функции активации — это преобразования, которые настраивают модели в соответствии с вашими потребностями.
Эта модель уже будет делать прогнозы, хотя пока и не очень точные:
julia> predict(x_train)
1×6 Matrix{Float32}:
0.0 0.906654 1.81331 2.71996 3.62662 4.53327
Чтобы составлять более точные предсказания, нужно предоставить функцию потерь, которая подскажет Flux, как объективно оценивать качество предсказания. Функции потерь вычисляют суммарное расхождение между фактическими значениями и прогнозами.
julia> using Statistics
julia> loss(model, x, y) = mean(abs2.(model(x) .- y));
julia> loss(predict, x_train, y_train)
122.64734f0
Более точные прогнозы приведут к меньшим потерям. Можно написать собственные функции потерь или использовать те, которые уже предоставлены Flux. Эта функция потерь называется среднеквадратичной погрешностью (и встраивается как mse
). Flux работает, итеративно уменьшая потери через обучение.
3. Повышение точности прогнозирования
На внутреннем уровне функция Flux Flux.train!
использует функцию потерь и обучающие данные для улучшения параметров модели на основе подключаемого оптимизатора (optimiser
):
julia> using Flux: train!
julia> opt = Descent()
Descent(0.1)
julia> data = [(x_train, y_train)]
1-element Vector{Tuple{Matrix{Int64}, Matrix{Int64}}}:
([0 1 … 4 5], [2 6 … 18 22])
Теперь у нас есть оптимизатор и данные, которые мы передадим функции train!
. Осталось только определить параметры модели. Помните, что каждая модель представляет собой структуру Julia с функцией и настраиваемыми параметрами. Помните, что плотный слой имеет веса и отклонения, которые зависят от измерений входных и выходных данных:
julia> predict.weight
1×1 Matrix{Float32}:
0.9066542
julia> predict.bias
1-element Vector{Float32}:
0.0
Измерения этих параметров модели зависят от количества входных и выходных данных.
Flux будет корректировать прогнозы, итеративно изменяя эти параметры в соответствии с оптимизатором.
Этот оптимизатор реализует классическую стратегию градиентного спуска. Улучшите параметры модели с помощью вызова функции Flux.train!
, как показано далее:
julia> train!(loss, predict, data, opt)
И проверьте потери:
julia> loss(predict, x_train, y_train)
116.38745f0
Они уменьшились. Почему?
julia> predict.weight, predict.bias
(Float32[7.246838;;], Float32[1.748103])
Параметры изменились. В этом единственном шаге и заключается суть машинного обучения.
3+. Итеративное обучение модели
В предыдущем разделе мы сделали один вызов функции train!
, которая итерирует переданные данные всего один раз. Эпоха — это один проход по набору данных. Как правило, мы проводим обучение в течение нескольких эпох, чтобы еще больше снизить потери. Давайте запустим его еще несколько раз:
julia> for epoch in 1:200
train!(loss, predict, data, opt)
end
julia> loss(predict, x_train, y_train)
0.00339581f0
julia> predict.weight, predict.bias
(Float32[4.0159144;;], Float32[2.004479])
После 200 шагов обучения потери уменьшились, а параметры приблизились к параметрам функции, для предсказания которых построена модель.
4. Проверка результатов
Теперь проверим прогнозы:
julia> predict(x_test)
1×5 Matrix{Float32}:
26.1121 30.13 34.1479 38.1657 42.1836
julia> y_test
1×5 Matrix{Int64}:
26 30 34 38 42
Они хорошие. Вот как мы этого добились.
Сначала мы собрали реальные данные в переменные x_train
, y_train
, x_test
и y_test
. Данные x_*
определяют входные данные, а данные y_*
— выходные. Данные *_train
предназначены для обучения модели, а данные *_test
— для ее проверки. Данные были основаны на функции 4x + 2
.
Затем мы построили модель прогнозирования predict = Dense(1 => 1)
с одними входными и одними выходными данными. Первые прогнозы не были точными, потому что мы еще не обучили модель.
Построив модель, мы обучили ее с помощью функции train!(loss, predict, data, opt)
. Сначала задается функция потерь, затем сама модель, обучающие данные и оптимизатор Descent
, предоставляемый Flux. Мы выполнили шаг обучения один раз и заметили, что параметры изменились, а потери уменьшились. Затем мы выполнили функцию train!
много раз, чтобы завершить процесс обучения.
После обучения модели мы проверили ее на основе тестовых данных, чтобы убедиться в правильности результатов.
Эта общая схема отражает работу Flux. Давайте немного углубимся в детали, чтобы понять, что происходит внутри отдельных слоев Flux.