- PVSM.RU - https://www.pvsm.ru -

Julia и нейронные сети: Flux

Julia и нейронные сети: Flux - 1
Прошло чуть больше года, с тех пор как MIT объявил о релизе высокопроизводительного языка общего назначения Julia. С тех пор язык набирает популярность: он используется в более чем 1500 университетах [1] (в некоторых преподается в качестве первого ЯП), а области применения охватывают от медицинской диагностики [2] и планирования космических миссий [3] до таких насущных проблем, как оптимизация трафика школьных автобусов [4].

Одним из ключевым полей деятельности многих проектов, как не трудно догадаться, является машинное обучение, для которого на Julia есть множество мощных инструментов [5], а недавно вышел в свет довольно интересный проект — Система вероятностного программирования общего назначения "GEN" [6].

Сегодня же мы обратим внимание на, как понятно из названия, пакет Flux, предоставляющий всю мощь нейронных сетей. Постараемся пройти путь от обработки и исследования наборов изображений до обученной нейронной сети, чтобы получить полноценный классификатор!

Julia и нейронные сети: Flux - 2

Установка

С официального сайта [11] скачиваем дистрибутив и устанавливаем интерпретатор Julia (REPL) на свой компьютер.

Для корректной работы менеджера пакетов пользователи Windows 7 / Windows Server 2012 также должны установить:

Процесс работы в REPL выглядит как-то так:

Julia и нейронные сети: Flux - 3

Настоящие же датасаянтисты и машинлёнингологи предпочитают Jupyter. Здесь можно посмотреть [15] про установку, а также найти интерактивные уроки для самостоятельного изучения с заданиями на русском (ссылки на оригинальные туториалы и руководство по языку там же).

Здесь можно посмотреть [16] на то, как работать с Jupyter Notebook.

Если проблемы при установке

  • Не удается установить соединение — проверьте свои права доступа (нет ли у вас ограничений на запись в папки на C:, зайдите как админ или запустите Джулию в режиме администратора), если используете прокси, убедитесь [17], что оно настроено не только для браузера
  • Некоторые пакеты не любят кириллицу в пути файлов, так что из-за имени пользователя на русском у меня было много проблем
  • Для корректной работы некоторых пакетов на Windows нужно чтобы пути к Julia и Jupyter были занесены в переменные среды.

Julia и нейронные сети: Flux - 4

Компьютер/Свойства сиситемы/Дополнительные параметры системы/Переменные среды/Path (Создать если нет) и добавить туда путь к julia.exe
Пример C:UsersUserAppDataLocalJulia-1.2.0bin
если в Path уже есть значения, то отделяем их точкой с запятой.
Теперь если в командную консоль (cmd) вбить julia то запустится интерпретатор.

Установив все необходимое, можете переходить к загрузке пакетов необходимых на сегодня. Вводите команды в REPL или Jupyter

Код

using Pkg
pkgs = ["Plots", "TextParse", "CSV", "DataFrames", "ImageMagick", "Images", "Interact", "Flux"]

for p in pkgs
    Pkg.add(p)
end

for p in pkgs
    Pkg.build(p)
end

После изучения основ языка (работа с массивами, создание функций, загрузка пакетов, построение графиков), можно приступать к последующему материалу.

Загрузка и обработка данных

Сбор данных и их упорядочивание это отдельное искусство. Касательно Julia в сети много устаревшего материала, но для начала можно попробовать указанный выше самоучитель [18], а для более тщательного изучения ознакомьтесь с книгой Data Science with Julia [19] (в свободном доступе)

А сегодня, пожалуй, поработаем с уже подготовленными данными: датасетом [20] из огромного количества фотографий фруктов с различных ракурсов — кто хотел фруктового фреша?

Julia и нейронные сети: Flux - 5 Julia и нейронные сети: Flux - 6

Собственно это и есть задание — будем учить нейронную сеть отличать яблоки от бананов!

Перво-наперво подгрузим несколько тестовых изображений:

using Images

fnames = [
    "data/10_100.jpg",
    "data/107_100.jpg",
    "data/yellow_apple_2.jpg",
    "data/8_100.jpg",
    "data/104_100.jpg", 
    "data/3_100.jpg"
]
# массив картинок
fruits = [load(fname) for fname in fnames]

hcat(fruits...) # соединить в одно изображение

Julia и нейронные сети: Flux - 7

Чем объекты на картинках отличаются друг от друга? Во первых — формой, во вторых — цветом, ну а затем уже текстурками и прочими атрибутами. Анализ изображений [21] сама по себе интересная тема, а классификацию можно производить не только нейронками, но и, скажем, вейвлетами [22]. Мы же начнем с самого простого признака — цвета.

Как известно, изображения хранятся в памяти компьютера в виде массивов, в нашем случае — это матрицы, каждая ячейка которых содержит по три числа, обозначающих количества красного, зеленого и синего цветов в каждом пикселе изображения. Посмотрим среднее количество каждого цвета в этих изображениях:

using Statistics: mean

M1 = [ mean(float.(c.(img))) for c = [red,green,blue], img = fruits ]

3×6 Array{Float32,2}:
 0.570278  0.652852  0.977111  0.835252  0.903998  0.842564
 0.338118  0.468729  0.950773  0.806882  0.880692  0.755442
 0.322406  0.379424  0.835212  0.707626  0.799643  0.761916

Внимательно смотрим на первую строчку — ничего не смущает? Желтое яблоко и бананы краснее чем яблоки сорта "Бребурн"! Как так?! Да ладно вам корчить кислые мины, может этот туториал школьники читают, или младшекурсницы из балетно-тракторного института. Поэтому постараемся избегать недомолвок. Дело в том, что фон каждой картинки имеет белый цвет, а он в нотации RGB представлен значениями (1,1,1). А так как на изображениях 3 тире 6 фона больше, плюс раскраска бананов и желтого яблока тоже содержит красный цвет, вот и выходит, что первые две картинки проигрывают в красноте. Для наглядности, расщепим изображения по базовым цветам:

function tweaking(img)
    R = colorview( RGB, red.(img),zeroarray,zeroarray )
    G = colorview( RGB, zeroarray,green.(img),zeroarray )
    B = colorview( RGB, zeroarray,zeroarray, blue.(img) )
    [R; G; B]
end

tweaking( hcat(fruits...) )

Julia и нейронные сети: Flux - 8

Слышали когда-нибудь загадочное слово "базис?" Так вот можно сказать что эти изображения разложены в RGB-базисе. Чем чернее — тем меньше определенного цвета, и как мы и предполагали, фон своей насыщенностью зашумляет нам вычисление средних. Удалим его.

function remove_background(img)
    mtrx = copy( channelview(img) )
    for i = 1:size(mtrx, 2), j = 1:size(mtrx, 3)
        if reduce(&, mtrx[:,i,j] .> [0.8, 0.8, 0.8])
            # а точнее - усредним
            mtrx[:,i,j] .= [0.5, 0.5, 0.5]
        end
    end
    colorview(RGB, mtrx)
end

greyfruits = remove_background.(fruits)

Julia и нейронные сети: Flux - 9

M3 = [ mean(float.(c.(img))) for c = [red,green,blue], img = greyfruits ]

3×6 Array{Float32,2}:
 0.451008  0.532696  0.578967  0.527727  0.52849   0.500276
 0.218805  0.348609  0.552679  0.499192  0.505136  0.412946
 0.203528  0.260142  0.439354  0.400631  0.424784  0.419291

Все еще сказывается разница в площадях, которую занимает каждый объект, но в целом, можно сделать вывод, что бананы зеленее (и синее) яблок. Это и будет критерием оценки, то бишь — признаком. Теперь пошерудим по остальным картинкам:

pth = "C:\Users\User\Desktop\Banana" # Apple Braeburn
fnames = readdir(pth)[1:300]

300-element Array{String,1}:
 "0_100.jpg"    
 "104_100.jpg"  
 "107_100.jpg"  
 "10_100.jpg"   
 "112_100.jpg"  
 "117_100.jpg"  
 "118_100.jpg"  
 "119_100.jpg"  
...

Для каждого изображения нивелируем вклад фона, найдем среднее количество каждого цвета, попутно запоминая размеры изображений...

dataz = []
for fname in fnames
    img_i = load("$pth\$fname")
    gbimg = remove_background(img_i)
    colorz = [ mean(c.(gbimg)) for c = [red,green,blue] ]
    inform = [size(gbimg, 1) size(gbimg, 2) colorz' ]

    push!(dataz, inform)
end
dataz

… и далее можно оформить наши данные в удобные для работы структуры — датафрэймы:

using DataFrames, CSV

banans = DataFrame( vcat(dataz...), [:height, :width, :red, :green, :blue] )
CSV.write("data/bananas.csv", banans) # запись в файл

apples = CSV.read("data/Apple_Braeburn.csv") # считывание из файла
banans = CSV.read("data/bananas.csv")

Julia и нейронные сети: Flux - 10

Desc = describe(apples, :all) # боги смерти едят только яблоки

Julia и нейронные сети: Flux - 11

Постарайтесь осмыслить данные предоставленные функцией describe() и сравните с аналогичной таблицей для бананов. Ну и какой же может быть анализ данных без графиков?

function plot2features(clr)

    x_apples = apples[:, :green]
    x_banans = banans[:, :green]
    y_apples = apples[:, clr]
    y_banans = banans[:, clr]

    scatter(x_apples, y_apples, lab = "apples", colour = :red)
    scatter!(x_banans, y_banans, lab = "bananas", legend = :topleft, colour = :yellow)
    hline!([mean(y_apples), mean(y_banans) ], lab = "" )
    vline!([mean(x_apples), mean(x_banans) ], lab = "" )
    xaxis!("green")
    yaxis!("$clr")
end

plot2features(:red)

Julia и нейронные сети: Flux - 12

plot2features(:blue)

Julia и нейронные сети: Flux - 13

Средне-банановое красное очень близко по значению средне-яблочному. А вот на втором графике уже более явно прослеживается обособленность фруктов сразу по двум цветовым признакам. Обособленности можно улучшить правильной перенормировкой, например наши значения зеленого меняются от 0.2 до 0.55, а если выполнить преобразование

$ x_i'=frac{x_i - min(x)}{max(x) - min(x)} $

то мы получим данные перемасштабированные на [0,1], что увеличит зазор между этими кучками скоплениями точек.

Перцептрон

Задача классификации состоит в задании модели и подборе параметров, при которых различные данные будут однозначно получать оценку своей принадлежности тому или иному классу. Проще говоря, нам нужно ввести некую функцию и задать ее параметры так, чтобы она отделяла наши яблоки от бананов.

Julia и нейронные сети: Flux - 15

Наиболее известная и популярная для этих целей модель – это искусственный нейрон Маккалока — Питтса, разработанный в начале 1940-х годов. Впоследствии Фрэнк Розенблатт предложил обучаемую нейронную сеть — перцептрон. Про нейронные сети нетрудно найти исчерпывающие разъяснения, в том числе на данном ресурсе (Например Нейронные сети для начинающих [23], Применение нейросетей в распознавании изображений [24], Нейронные сети, фундаментальные принципы работы, многообразие и топология [25] )

Выбрав в качестве функции активации сигмоиду и задав в соответствие ее выходам обозначения классифицуруемых объектов (фруктов)

$ sigma(x; w, b) :=frac{1}{1 + exp(-wx + b)}$

$ x=mathrm{data} $

$ sigma(x;w,b) approx 0 implies mathrm{apple} $

$ sigma(x;w,b) approx 1 implies mathrm{banana}$

подберем такие параметры $W$ и $b$, чтобы выходные значения сигмоиды для получаемых данных соответствовали принятым выше обозначениям

using Interact

sigmo(x,w,b) = 1 / (1 + exp(-w*x+b))

r_apples, g_apples, b_apples = apples[:, :red], apples[:, :green], apples[:, :blue]
r_banans, g_banans, b_banans = banans[:, :red], banans[:, :green], banans[:, :blue];

@manipulate for w in 10:1:60, b in -5:1:25

    plot(x->sigmo(x,w,b), 0, 1, label="Model", legend = :topleft, lw=3)
    scatter!(g_apples[1:5], zeros(10), label="Apple", colour = :red)
    scatter!(g_banans[1:5], ones(10), label="Banana", colour = :yellow)

end

Julia и нейронные сети: Flux - 22

foon(x) = sigmo(x,60,24)
plot(foon, 0, 1, label="Model", legend = :topleft, lw=3)
scatter!(foon, g_apples, label="Apple", colour = :red)
scatter!(foon, g_banans, label="Banana", colour = :yellow)
xaxis!("green")

Julia и нейронные сети: Flux - 23

Мы вручную научили нейрончик отличать яблоки от бананов по количеству зеленого цвета!

Совершенно естественно желание автоматизировать этот процесс. Введем функцию потерь

$ L(w,b)=(0 - σ(x_1, w, b))^2 + (1 - σ(x_2, w, b))^2 $

Теперь процесс обучения будет состоять в минимизации этой функции:

Код

apples_mean_green = mean(g_apples)
banans_mean_green = mean(g_banans)

L(w, b) = (0 - sigmo(apples_mean_green,w,b))^2 + (1 - sigmo(banans_mean_green,w,b))^2

w_range = 10:0.5:30
b_range = 0:0.5:20

L_values = [L(w,b) for b in b_range, w in w_range]

@manipulate for w in w_range, b in b_range
    p1 = surface(w_range, b_range, L_values, xlabel="b", ylabel="w", cam=(80,40), cbar=false, leg=false)
    scatter!(p1, [w], [b], [L(w,b)+1e-2], markersize=5, color = :blue)
    p2 = plot(x->sigmo(x,w,b), 0, 1, label="Model", legend = :topleft, lw=3)
    scatter!(p2, [apples_mean_green],  [0.0], label="Apple", markersize=10)
    scatter!(p2, [banans_mean_green], [1.0], label="Banana", markersize=10, xlim=(0,1), ylim=(0,1))
    plot(p1, p2, layout=(2,1))
end

Julia и нейронные сети: Flux - 25

Ранее мы изучали пакеты для Julia [26] позволяющие решать задачи оптимизации различными методами. К счастью самые необходимые уже имеются в окружении Flux!

Flux

using Flux

Для начала представим данные для обучения в удобоваримом виде:

Y = [zeros(length(g_apples)); ones(length(g_banans)) ] |> permutedims
X = [g_apples; g_banans] |> permutedims;
# один признак - количество зеленого
# dataz = repeated((X, Y), 20)

Далее по порядку:

  • Создаем датасет для обучения, объединив входные данные с правильными ответами касательно классификации этих данных
  • Задаем параметры W и b матрицами случайных значений (на входе один признак и на выходе один, поэтому матрицы размера 1 x 1)
  • В качестве модели задаем плотный слой — перцептрон с сигмоидальной функцией активации
  • Задаем функцию потерь — сумма квадратов разностей (еще можно использовать более популярную Flux.crossentropy())
  • В качестве метода оптимизации выбираем градиентный спуск [27]. Она принимает параметр — скорость спуска
  • Задаем функцию оценки, которая будет округлять значения выходов модели и сравнивать их с правильными ответами.
  • И распечатаем параметры нашей необученной модели

dataz = [(X, Y)]
W = param(rand(1))
b = param(rand(1))
model = Dense(W, b, σ)
loss(x, y) = mse(model(x), y)
opt = Descent(0.1)
accuracy(x, y) = mean( round.(model(x)) .== y )
params(model)

Params([[0.3372841444115968] (tracked), [0.8430399003786011] (tracked)])

Посмотрим чему равен выход функции потерь для наших данных

loss(X, Y)
# чем меньше, тем лучше
0.310845210182773 (tracked)

И проверим результаты функции оценки

accuracy(X, Y)

0.5

Результат вполне закономерен — выходы распределены довольно однородно и правильно классифицируется половина данных:

Код

modeldataz(x) = x |> model |> data |> permutedims
# modeldataz(x) = permutedims(data(model(x)))

modelX = modeldataz(X)
modelapples = modeldataz(g_apples')
modelbanans = modeldataz(g_banans')

plot(modelX, legend = false)
hline!([0.5])
p1 = yaxis!((0,1))

curv = [-1:0.01:1;]' |> modeldataz
plot( [-1:0.01:1;], curv, label="Model", legend = :topleft, lw=3)

scatter!(g_apples, modelapples, label="Apple", colour = :red)
scatter!(g_banans, modelbanans, label="Banana",colour = :yellow)
hline!([0.5], lab = "", legend = :topleft)
p2 = xaxis!("green")

plot(p1, p2)

Julia и нейронные сети: Flux - 26

Приступим к обучению: это довольно просто. Надо только прикрикнуть на нейросеть: "Тренируйся!", указав при этом, на чем тренироваться и что минимизировать, и она выполнит один проход обучения. Посему, заставим ее отучить всё как следует, но только без фанатизма, чтоб не вышло переобучения [28]

for i in 1:7000
    train!(loss, params(model), dataz, opt)
end

model.W, model.b

([9.578663260720564] (tracked), [-3.7540362587506464] (tracked))

Потери стали гораздо меньше:

loss(X, Y)

0.09152783090457564 (tracked)

А оценка — лучше:

accuracy(X, Y)

1.0

Julia и нейронные сети: Flux - 27

Данные разделились, а дальнейшее обучение будет делать модельную функцию более вертикальной. Проверим обученную модель на самом первом наборе фруктов:

function classifier(img)
    gbimg = remove_background(img)
    greenmean = [ mean(float.(c.(gbimg))) for c = [red,green,blue] ]
    answ = data( model( [ greenmean[2] ]' ) )[1]
    fr = answ > 0.5 ? "Banana" : "Apple"

    "$fr $(round(200abs(0.5-answ)))%"
end

hcat(fruits...)

Julia и нейронные сети: Flux - 28

classifier.(fruits)

6-element Array{String,1}:
 "Apple 68.0%" 
 "Apple 20.0%" 
 "Banana 65.0%"
 "Banana 47.0%"
 "Banana 49.0%"
 "Banana 10.0%"

Специально подложенное желтое яблоко, понятное дело, распозналось некорректно, да и красный банан еле вошел в свою категорию. Но нейрон-то получает из картинки всего одно число — среднее количество зеленого. Можно добавить еще один признак, скажем, количество синего, что сделает модель немного адаптивней

Julia и нейронные сети: Flux - 29

Или можно использовать не RGB представление, а HSV [29] (hue, saturation, value), в котором канал hue будет содержать информацию о цвете изображения.

Весь смак нейронных сетей в том, что они сами могут выделить признаки, порой весьма не очевидные (соотношение цветов, их распределение, контуры и кривульки...), а помочь им можно с помощью специальных эвристик и техник, что превращает работу с нейросетями в настоящее искусство.

Julia и нейронные сети: Flux - 30

Чтобы руководство не слишком разрослось а делать цикл статей лень укажем еще в качестве примера классификацию картинок с рукописными цифрами, а заинтересованный читатель уже сам обобщит полученные знания на изображения с фруктами и создаст свою нейронную сеть, способную, скажем, размечать объекты на натюрмортах!

MNIST

using Images
using Flux, Flux.Data.MNIST, Statistics
using Flux: onehotbatch, onecold, crossentropy, throttle
using Base.Iterators: repeated
# using CuArrays

# Classify MNIST digits with a simple multi-layer-perceptron

imgs = MNIST.images()
# Stack images into one large batch
X = hcat(float.(reshape.(imgs, :))...);
hcat(imgs[1:10]...)

Julia и нейронные сети: Flux - 31

Пример интересен тем, что выходов аж десять. Здесь пригодятся так называемые One-hot [30] вектора

labels = MNIST.labels()
# One-hot-encode the labels
Y = onehotbatch(labels, 0:9) 

10×60000 Flux.OneHotMatrix{Array{Flux.OneHotVector,1}}:
 0  1  0  0  0  0  0  0  0  0  0  0  0  …  0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  1  0  0  1  0  1  0  0  0  0     0  0  0  0  0  0  1  0  0  0  0  0
 0  0  0  0  0  1  0  0  0  0  0  0  0     0  0  0  1  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  1  0  0  1  0  1     0  0  0  0  0  0  0  0  1  0  0  0
 0  0  1  0  0  0  0  0  0  1  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 1  0  0  0  0  0  0  0  0  0  0  1  0  …  0  0  0  0  0  1  0  0  0  1  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  1  0
 0  0  0  0  0  0  0  0  0  0  0  0  0     1  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0     0  1  0  0  0  0  0  1  0  0  0  1
 0  0  0  0  1  0  0  0  0  0  0  0  0     0  0  1  0  1  0  0  0  0  0  0  0

В качестве модели зададим цепочку нейронов, функцией потерь будет перекрестная энтропия [31], а в качестве метода оптимизации — Adam [32]:

m = Chain(
  Dense(28^2, 32, relu),
  Dense(32, 10),
  softmax)

loss(x, y) = crossentropy(m(x), y)

accuracy(x, y) = mean(onecold(m(x)) .== onecold(y))

dataset = repeated((X, Y), 20)
evalcb = () -> @show(loss(X, Y))
opt = ADAM()

Тренируется пускай в щадящем режиме, но распечатывая потери каждые 10 секунд:

for i = 1:10
    Flux.train!(loss, params(m), dataset, opt, cb = throttle(evalcb, 10))
end
# можно пока поесть...

accuracy(X, Y)

0.64545

И проверим на данных не использованных при обучении

# Test set accuracy
tX = hcat(float.(reshape.(MNIST.images(:test), :))...)
tY = onehotbatch(MNIST.labels(:test), 0:9)

accuracy(tX, tY)

0.6488

Нейронные сети на Julia это просто и очень увлекательно! Даже если нет необходимости искать связей своей области деятельности с машинным обучением, стоит хотя бы пощупать эту диковинку, о которой кричат со всех углов, а уж в инструментах недостатка не будет!

Всем умеренного процессорного тепла!

Автор: Yermack

Источник [35]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/programmirovanie/336232

Ссылки в тексте:

[1] 1500 университетах: https://juliacomputing.com/blog/2019/06/11/june-newsletter.html

[2] медицинской диагностики: https://juliacomputing.com/case-studies/contextflow.html

[3] планирования космических миссий: https://juliacomputing.com/case-studies/BrazilNationalinstituteforspaceResearch.html

[4] оптимизация трафика школьных автобусов: https://www.routefifty.com/tech-data/2019/08/boston-school-bus-routes/159113/

[5] множество мощных инструментов: https://juliaml.github.io

[6] Система вероятностного программирования общего назначения "GEN": http://news.mit.edu/2019/ai-programming-gen-0626

[7] Официальный сайт: https://fluxml.ai/

[8] Исходники на github: https://github.com/FluxML/Flux.jl

[9] Документация: https://fluxml.ai/Flux.jl/stable/

[10] поиграть с натренированными моделями: https://fluxml.ai/experiments/

[11] официального сайта: https://julialang.org

[12] TLS easy_fix: https://support.microsoft.com/en-us/help/3140245/update-to-enable-tls-1-1-and-tls-1-2-as-a-default-secure-protocols-in

[13] Discourse thread: https://discourse.julialang.org/t/errors-for-git-pkg/9351

[14] Windows Management Framework 3.0 or later: https://docs.microsoft.com/en-us/powershell/wmf/overview

[15] Здесь можно посмотреть: https://github.com/YermolenkoIgor/Julia_tutorial_rus

[16] Здесь можно посмотреть : https://devpractice.ru/python-lesson-6-work-in-jupyter-notebook/

[17] убедитесь: https://it-blojek.ru/nastroyka-sistemnogo-proksi-v-windows/

[18] указанный выше самоучитель: https://github.com/YermolenkoIgor/Julia_tutorial_rus/tree/master/intro_to_DS

[19] Data Science with Julia: https://www.pdfdrive.com/data-science-with-julia-e158445799.html

[20] датасетом: https://github.com/Horea94/Fruit-Images-Dataset

[21] Анализ изображений: https://habr.com/ru/company/yandex/blog/251161/

[22] вейвлетами: https://www.mathworks.com/help/wavelet/examples/digit-classification-with-wavelet-scattering.html

[23] Нейронные сети для начинающих: https://habr.com/ru/post/313216/

[24] Применение нейросетей в распознавании изображений: https://habr.com/ru/post/74326/

[25] Нейронные сети, фундаментальные принципы работы, многообразие и топология: https://habr.com/ru/post/416071/

[26] пакеты для Julia: https://habr.com/ru/post/440618/

[27] градиентный спуск: https://habr.com/ru/post/440070/

[28] переобучения: https://ru.wikipedia.org/wiki/%D0%9F%D0%B5%D1%80%D0%B5%D0%BE%D0%B1%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5

[29] HSV: https://juliaimages.org/latest/demos/rgb_hsv_thresholding/#rgb_hsv_thresholding-1

[30] One-hot: https://en.wikipedia.org/wiki/One-hot

[31] перекрестная энтропия: https://ru.wikipedia.org/wiki/%D0%9F%D0%B5%D1%80%D0%B5%D0%BA%D1%80%D1%91%D1%81%D1%82%D0%BD%D0%B0%D1%8F_%D1%8D%D0%BD%D1%82%D1%80%D0%BE%D0%BF%D0%B8%D1%8F

[32] Adam: https://habr.com/ru/post/318970/

[33] Еще примеры архитектуры сетей: https://github.com/FluxML/model-zoo/

[34] Юпитерские ноутбуки со всеми сегодняшними листингами: https://github.com/YermolenkoIgor/Julia_tutorial_rus/tree/master/intro_to_ML

[35] Источник: https://habr.com/ru/post/474084/?utm_source=habrahabr&utm_medium=rss&utm_campaign=474084